Преглед изворни кода

feat: 房间外游戏入口 (#17)

* feat: 房间底部,增加游戏入口RoomBottomGameView

* feat: 新增HeadLineFloatView

* feat: 房间外横幅场景

* feat: 房间外横幅场景

* feat: 房间右下角,增加More game入口

* feat: 房间底部,游戏入口轮播简单实现

* feat: 游戏增加LuckyGameSource来源

* feat: 游戏增加CocosWindowViewData埋点上报

* feat: 游戏埋点上报

* feat: 游戏埋点上报

* feat: 游戏入口修过

* feat: 游戏入口修过

* feat: 游戏挂件入口修改,支持svga

* feat: WebViewDialogFragment,支持设置背景

* feat: GameViewModel#getGamePlayedRecords,查询游戏记录

* feat: GameEntrance定义,区分房间内外的icon

* feat: 房间底部轮播入口RoomBottomGameView

* feat: HeadlineFloatView高度自适应,避免拦截事件

* feat: 代码优化

* feat(out-room-game): 接入房间外游戏入口

* feat(out-room-game): 接入房间外游戏指引提示

* feat: 联调修改

* feat: LuckyGameSource修改

* feat: 房间底部按钮

* feat(out-room-game): 接入红点逻辑

* feat: 代码冲突问题

* feat: 游戏面板,游戏顺序调整

* feat: 统一游戏红点,GameRedPointManager

* feat: 新增GameEntranceDot

* feat: 点击事件处理

* fix: 特效设置面板加个人水果机入口

* fix: 麦下用户的房间底部操作,say hi自适应

* fix: greedy-pro链接错误

* fix: 用户在第9号麦位,送礼面板上显示第10号

* fix: 关闭后没生效,仍可收到横幅

* fix: 游戏蒙层问题

* fix: 删除无用代码

* fix: 当前处于RoomActivity,不处理OUTER_ROOM_STREAMER_NOTIFY

* fix: 删除测试代码

* fix: HeadlineManager房间外补充日志

* fix: 文案翻译问题

* feat(out-room-game): If gameList is empty,on gone the view.

* feat(out-room-game): 回调时检测引导组件

* feat: 模块New逻辑+动画

* fix: FloatView移除

* feat: New逻辑

* feat: 回调时刷新

* feat: 横幅悬浮控件

* feat: 横幅悬浮控件

* feat: LayoutViewContainer.removeView不清除自己

* fix: FloatView,支持最多2个常规展示,income可以插队

* fix: FloatView,支持slideDirection定制

* fix: 删掉SwipeToTopConstraintLayout

* fix: clearWhenExitRoom

* fix: WM缺失,导致removeSelf不生效

* 调整顶部悬浮窗

* fix: 房间内游戏点击空白处不会关闭游戏页面

* 调整顶部悬浮窗

* fix: 红点只加个人水果机

* 修复onResume.removeSelf ConcurrentModificationException异常

* fix: 房间右下角功能组件游戏需调整排序

* fix: 长按拖拽悬浮控件问题

* fix: showGameEntranceIcon优化

* fix: SlideDirection修改

* fix: SlideDirection修改

* feat: 合并冲突

* feat: OutRoomGameViewComp代码优化

* feat: 部分代码review

* review: 调整review部份

* feat: 定制化实现RoomGameCompRectangleIndicator

* feat: 定制化实现RoomGameCompRectangleIndicator

* feat: me红点问题,点击防抖

* feat: me红点问题,点击防抖

* review: 重新设置图标,避免过大的图片(差不多对应px/2即可)

* feat: 横幅点击事件

* feat: me挂件点击事件

* fix: 相关bug

* fix: HeadlineFloatView context问题

---------

Co-authored-by: reyimu <evanbiz08@163.com>
Co-authored-by: linjiajia <319408893@qq.com>
Co-authored-by: DoggyZhang <doggyzhang1219@gmail.com>
LXD312569496 пре 8 месеци
родитељ
комит
23cdb822e3
100 измењених фајлова са 2122 додато и 796 уклоњено
  1. 123 0
      app/src/main/java/com/adealink/weparty/commonui/widget/banner/indicator/RoomGameCompRectangleIndicator.java
  2. 2 1
      app/src/main/java/com/adealink/weparty/commonui/widget/floatview/FloatViewFactory.kt
  3. 3 0
      app/src/main/java/com/adealink/weparty/commonui/widget/floatview/IWindowManager.kt
  4. 7 0
      app/src/main/java/com/adealink/weparty/commonui/widget/floatview/ModeWindowManagerProxy.kt
  5. 1 1
      app/src/main/java/com/adealink/weparty/commonui/widget/floatview/data/IFloatData.kt
  6. 44 8
      app/src/main/java/com/adealink/weparty/commonui/widget/floatview/mode/ApplicationModeWindowManager.kt
  7. 4 0
      app/src/main/java/com/adealink/weparty/commonui/widget/floatview/mode/fix/Android10FixApplicationModeWindowManager.kt
  8. 5 21
      app/src/main/java/com/adealink/weparty/commonui/widget/floatview/view/BaseLongPressDragFloatView.kt
  9. 3 1
      app/src/main/java/com/adealink/weparty/commonui/widget/floatview/view/BaseSlideFloatView.kt
  10. 12 4
      app/src/main/java/com/adealink/weparty/commonui/widget/floatview/view/FloatLinearLayout.kt
  11. 30 3
      app/src/main/java/com/adealink/weparty/module/game/GameModule.kt
  12. 19 5
      app/src/main/java/com/adealink/weparty/module/game/IGameService.kt
  13. 1 1
      app/src/main/java/com/adealink/weparty/module/game/data/GameData.kt
  14. 46 64
      app/src/main/java/com/adealink/weparty/module/game/data/GameEntranceData.kt
  15. 89 0
      app/src/main/java/com/adealink/weparty/module/game/data/OutRoomGameData.kt
  16. 12 21
      app/src/main/java/com/adealink/weparty/module/game/floatview/GameEntranceFloatView.kt
  17. 3 2
      app/src/main/java/com/adealink/weparty/module/game/floatview/GameEntranceFloatViewComp.kt
  18. 76 0
      app/src/main/java/com/adealink/weparty/module/game/util/GameRedPointManager.kt
  19. 3 0
      app/src/main/java/com/adealink/weparty/module/game/viewmodel/IGameViewModel.kt
  20. 3 3
      app/src/main/java/com/adealink/weparty/module/headline/HeadlineModule.kt
  21. 1 1
      app/src/main/java/com/adealink/weparty/module/headline/IHeadlineService.kt
  22. 1 0
      app/src/main/java/com/adealink/weparty/module/room/IRoomService.kt
  23. 8 0
      app/src/main/java/com/adealink/weparty/module/room/RoomModule.kt
  24. 7 0
      app/src/main/java/com/adealink/weparty/module/room/Router.kt
  25. 13 2
      app/src/main/java/com/adealink/weparty/module/room/data/RoomNotifyData.kt
  26. 14 11
      app/src/main/java/com/adealink/weparty/module/room/datasource/local/RoomLocalService.kt
  27. 1 0
      app/src/main/java/com/adealink/weparty/module/webview/Router.kt
  28. 4 4
      app/src/main/java/com/adealink/weparty/module/webview/WebViewDialogFragmentBuilder.kt
  29. 3 1
      app/src/main/java/com/adealink/weparty/module/webview/data/WebData.kt
  30. 21 0
      app/src/main/java/com/adealink/weparty/url/LuckyGameSource.kt
  31. 25 0
      app/src/main/java/com/adealink/weparty/url/UrlConfig.kt
  32. 5 0
      app/src/main/java/com/adealink/weparty/util/IntentUtil.kt
  33. BIN
      app/src/main/res/drawable-xhdpi/common_more_game_ic.png
  34. BIN
      app/src/main/res/drawable-xhdpi/game_icon_dragon_out_room.png
  35. BIN
      app/src/main/res/drawable-xhdpi/game_icon_greedy_box_out_room.png
  36. BIN
      app/src/main/res/drawable-xhdpi/game_icon_greedy_pro_out_room.png
  37. BIN
      app/src/main/res/drawable-xhdpi/game_icon_jackpot_out_room.png
  38. BIN
      app/src/main/res/drawable-xhdpi/game_icon_jackpot_slot_out_room.png
  39. BIN
      app/src/main/res/drawable-xhdpi/game_icon_lucky_77_out_room.png
  40. BIN
      app/src/main/res/drawable-xhdpi/game_icon_lucky_fruit_out_room.png
  41. BIN
      app/src/main/res/drawable-xhdpi/game_icon_lucky_pro_out_room.png
  42. BIN
      app/src/main/res/drawable-xhdpi/game_icon_roulette_out_room.png
  43. BIN
      app/src/main/res/drawable-xhdpi/game_icon_teenpatti_out_room.png
  44. BIN
      app/src/main/res/drawable-xhdpi/game_icon_texas_cowboy_out_room.png
  45. BIN
      app/src/main/res/drawable-xhdpi/game_tag_dragon_tiger_slot_out_room.webp
  46. BIN
      app/src/main/res/drawable-xhdpi/game_tag_greedy_box_out_room.webp
  47. BIN
      app/src/main/res/drawable-xhdpi/game_tag_greedy_pro_out_room.webp
  48. BIN
      app/src/main/res/drawable-xhdpi/game_tag_jackpot_out_room.webp
  49. BIN
      app/src/main/res/drawable-xhdpi/game_tag_jackpot_slot_out_room.webp
  50. BIN
      app/src/main/res/drawable-xhdpi/game_tag_lucky_77_out_room.webp
  51. BIN
      app/src/main/res/drawable-xhdpi/game_tag_lucky_fruit_out_room.webp
  52. BIN
      app/src/main/res/drawable-xhdpi/game_tag_lucky_pro_out_room.webp
  53. BIN
      app/src/main/res/drawable-xhdpi/game_tag_roulette_out_room.webp
  54. BIN
      app/src/main/res/drawable-xhdpi/game_tag_teenpatti_out_room.webp
  55. BIN
      app/src/main/res/drawable-xhdpi/game_tag_texas_cowboy_out_room.webp
  56. 3 3
      app/src/main/res/layout/item_game_entrance_float_view.xml
  57. 2 2
      app/src/main/res/layout/layout_lucky_gift_big_reward.xml
  58. 2 2
      app/src/main/res/layout/layout_lucky_gift_reward.xml
  59. 2 0
      app/src/main/res/values-ar/strings.xml
  60. 1 0
      app/src/main/res/values-zh/strings.xml
  61. 5 2
      app/src/main/res/values/ids.xml
  62. 1 0
      app/src/main/res/values/strings.xml
  63. 1 0
      module/call/src/main/java/com/adealink/weparty/call/view/floatview/incoming/InComingFloatData.kt
  64. 9 0
      module/call/src/main/java/com/adealink/weparty/call/view/floatview/incoming/InComingFloatView.kt
  65. 16 2
      module/game/src/main/java/com/adealink/weparty/game/GameServiceImpl.kt
  66. 0 5
      module/game/src/main/java/com/adealink/weparty/game/data/GameData.kt
  67. 12 0
      module/game/src/main/java/com/adealink/weparty/game/data/GamePlayRecord.kt
  68. 3 0
      module/game/src/main/java/com/adealink/weparty/game/datasource/local/GameLocalService.kt
  69. 162 31
      module/game/src/main/java/com/adealink/weparty/game/manager/GameEntranceManager.kt
  70. 28 4
      module/game/src/main/java/com/adealink/weparty/game/manager/GameManager.kt
  71. 25 2
      module/game/src/main/java/com/adealink/weparty/game/manager/IGameEntranceManager.kt
  72. 6 0
      module/game/src/main/java/com/adealink/weparty/game/manager/IGameManager.kt
  73. 10 0
      module/game/src/main/java/com/adealink/weparty/game/viewmodel/GameViewModel.kt
  74. 8 8
      module/headline/src/main/java/com/adealink/weparty/headline/HeadlineServiceImpl.kt
  75. 0 13
      module/headline/src/main/java/com/adealink/weparty/headline/floatview/GreedyPersonalGlobalHeadlineFloatData.kt
  76. 0 84
      module/headline/src/main/java/com/adealink/weparty/headline/floatview/GreedyPersonalGlobalHeadlineFloatView.kt
  77. 34 0
      module/headline/src/main/java/com/adealink/weparty/headline/floatview/HeadlineFloatData.kt
  78. 288 0
      module/headline/src/main/java/com/adealink/weparty/headline/floatview/HeadlineFloatView.kt
  79. 10 393
      module/headline/src/main/java/com/adealink/weparty/headline/fragment/HeadlineFragment.kt
  80. 72 76
      module/headline/src/main/java/com/adealink/weparty/headline/manager/HeadlineManager.kt
  81. 12 0
      module/headline/src/main/java/com/adealink/weparty/headline/manager/IHeadlineManager.kt
  82. 3 13
      module/headline/src/main/res/layout/fragment_healine.xml
  83. 1 0
      module/message/src/main/java/com/adealink/weparty/message/floatview/NotificationMessageFloatData.kt
  84. 9 0
      module/message/src/main/java/com/adealink/weparty/message/floatview/NotificationMessageFloatView.kt
  85. 3 0
      module/profile/src/main/java/com/adealink/weparty/profile/data/Constants.kt
  86. 3 0
      module/profile/src/main/java/com/adealink/weparty/profile/datasource/local/ProfileLocalService.kt
  87. 73 0
      module/profile/src/main/java/com/adealink/weparty/profile/game/adapter/OutRoomGameItemViewBinder.kt
  88. 123 0
      module/profile/src/main/java/com/adealink/weparty/profile/game/component/OutRoomGameViewComp.kt
  89. 104 0
      module/profile/src/main/java/com/adealink/weparty/profile/game/dialog/OutRoomGameDialogFragment.kt
  90. 27 0
      module/profile/src/main/java/com/adealink/weparty/profile/game/util/OutRoomGameUtil.kt
  91. 214 0
      module/profile/src/main/java/com/adealink/weparty/profile/game/view/OutRoomGameView.kt
  92. 49 0
      module/profile/src/main/java/com/adealink/weparty/profile/me/MeFragment.kt
  93. BIN
      module/profile/src/main/res/drawable-xhdpi/profile_game_room_out_ic.png
  94. 31 0
      module/profile/src/main/res/drawable/profile_me_out_room_game_bg.xml
  95. 6 0
      module/profile/src/main/res/drawable/profile_out_game_new_tag_bg.xml
  96. 65 0
      module/profile/src/main/res/layout/dialog_out_room_game.xml
  97. 13 2
      module/profile/src/main/res/layout/fragment_me.xml
  98. 73 0
      module/profile/src/main/res/layout/item_out_room_game.xml
  99. 18 0
      module/profile/src/main/res/layout/layout_me_out_room_game_guide.xml
  100. 6 0
      module/profile/src/main/res/layout/layout_out_room_game.xml

+ 123 - 0
app/src/main/java/com/adealink/weparty/commonui/widget/banner/indicator/RoomGameCompRectangleIndicator.java

@@ -0,0 +1,123 @@
+package com.adealink.weparty.commonui.widget.banner.indicator;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+
+import com.adealink.frame.util.DisplayUtil;
+
+/**
+ * 限制指示器数量的矩形(条形)指示器
+ * 矩形(条形)指示器
+ * 1、可以设置选中和默认的宽度、指示器的圆角
+ * 2、如果需要正方形将圆角设置为0,可将宽度和高度设置为一样
+ * 3、如果不想选中时变长,可将选中的宽度和默认宽度设置为一样
+ */
+public class RoomGameCompRectangleIndicator extends BaseIndicator {
+    RectF rectF;
+
+    public RoomGameCompRectangleIndicator(Context context) {
+        this(context, null);
+    }
+
+    public RoomGameCompRectangleIndicator(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public RoomGameCompRectangleIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        rectF = new RectF();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        int count = config.getIndicatorSize();
+        if (count <= 1) {
+            return;
+        }
+        //间距*(总数-1)+默认宽度*(总数-1)+选中宽度
+        int space = config.getIndicatorSpace() * (count - 1);
+        int normal = config.getNormalWidth() * (count - 1);
+        setMeasuredDimension(space + normal + config.getSelectedWidth(), config.getHeight());
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        // 限制最大显示数量
+        int maxCount = 7;
+        int totalIndicators = config.getIndicatorSize();
+        if (totalIndicators <= 1) {
+            return;
+        }
+
+        int selectedIndex = config.getCurrentPosition();
+
+// 计算绘制起始下标
+        int half = maxCount / 2;
+        int startIndex = Math.max(0, selectedIndex - half);
+        int endIndex = Math.min(totalIndicators, startIndex + maxCount);
+
+// 如果到了尾部,重新调整起始位置
+        if (endIndex - startIndex < maxCount && startIndex > 0) {
+            startIndex = Math.max(0, endIndex - maxCount);
+        }
+
+        boolean isRTL = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+
+        int selectedWidth = DisplayUtil.dp2px(6f); // 选中项宽度
+        int circleDiameter = DisplayUtil.dp2px(3f); // 小圆直径
+        int circleRadius = circleDiameter / 2;
+        int spacing = DisplayUtil.dp2px(2f); // 间距
+
+        int count = endIndex - startIndex;
+        int totalWidth = (count - 1) * (circleDiameter + spacing) + selectedWidth;
+        float startX = (getWidth() - totalWidth) / 2f; // 居中起点
+        float centerY = getHeight() / 2f;
+
+        float x = startX;
+
+        if (!isRTL) {
+            // LTR 正常绘制,从左到右
+            for (int i = startIndex; i < endIndex; i++) {
+                boolean isSelected = (i == selectedIndex);
+                mPaint.setColor(isSelected ? config.getSelectedColor() : config.getNormalColor());
+
+                if (isSelected) {
+                    float top = centerY - circleRadius;
+                    float bottom = centerY + circleRadius;
+                    rectF.set(x, top, x + selectedWidth, bottom);
+                    canvas.drawRoundRect(rectF, circleRadius, circleRadius, mPaint);
+                    x += selectedWidth + spacing;
+                } else {
+                    canvas.drawCircle(x + circleRadius, centerY, circleRadius, mPaint);
+                    x += circleDiameter + spacing;
+                }
+            }
+        } else {
+            // RTL 方向,需要从右向左绘制
+            // 这里从右侧起点开始绘制,x 向左递减
+            x = startX + totalWidth;
+
+            for (int i = startIndex; i < endIndex; i++) {
+                boolean isSelected = (i == selectedIndex);
+                mPaint.setColor(isSelected ? config.getSelectedColor() : config.getNormalColor());
+
+                if (isSelected) {
+                    float top = centerY - circleRadius;
+                    float bottom = centerY + circleRadius;
+                    rectF.set(x - selectedWidth, top, x, bottom);
+                    canvas.drawRoundRect(rectF, circleRadius, circleRadius, mPaint);
+                    x -= selectedWidth + spacing;
+                } else {
+                    canvas.drawCircle(x - circleRadius, centerY, circleRadius, mPaint);
+                    x -= circleDiameter + spacing;
+                }
+            }
+        }
+
+
+    }
+}

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

@@ -45,7 +45,8 @@ class FloatViewFactory : IFloatViewFactory {
             FloatWindowType.MEMORY_USAGE -> SysMemoryUsageFloatView(data as SysMemoryUsageFloatData)
             FloatWindowType.ROCKET_HEADLINE -> GameModule.getRocketHeadlineFloatView(data)
             FloatWindowType.GAME_ENTRANCE -> GameEntranceFloatView(data as GameEntranceFloatData)
-            FloatWindowType.GREEDY_PERSONAL_GLOBAL_HEADLINE -> HeadlineModule.getGreedyPersonalGlobalHeadlineFloatView(data)
+            FloatWindowType.GLOBAL_HEADLINE -> HeadlineModule.getGlobalHeadlineFloatView(data)
+            else -> null
         }
         return floatView as? V
     }

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

@@ -11,4 +11,7 @@ interface IWindowManager<V : BaseFloatView<out IFloatData>> {
     fun isFloatViewAdded(type: FloatWindowType): Boolean
     fun removeFloatViewByType(type: FloatWindowType, reason: String)
     fun setFloatViewVisibility(type: FloatWindowType, visibility: Int)
+
+    //移除所有正在显示或者未显示的浮窗
+    fun removeAllFloatViews(type: FloatWindowType, reason: String)
 }

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

@@ -174,4 +174,11 @@ class ModeWindowManagerProxy : BaseWindowManager() {
             it.notifyWhenActivityChanged(activity)
         }
     }
+
+    override fun removeAllFloatViews(type: FloatWindowType, reason: String) {
+        Log.i(TAG_FLOAT_VIEW, "removeAllFloatViews, type: $type, reason: $reason")
+        windowManagerMap.values.forEach {
+            it.removeAllFloatViews(type, reason)
+        }
+    }
 }

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

@@ -17,7 +17,7 @@ enum class FloatWindowType(val type: String) {
     MEMORY_USAGE("memory_usage"),
     ROCKET_HEADLINE("rocket_headline"),
     GAME_ENTRANCE("game_entrance"),
-    GREEDY_PERSONAL_GLOBAL_HEADLINE("greedy_personal_global_headline")
+    GLOBAL_HEADLINE("global_headline") //全服横幅,房间内房间外
 }
 
 

+ 44 - 8
app/src/main/java/com/adealink/weparty/commonui/widget/floatview/mode/ApplicationModeWindowManager.kt

@@ -20,6 +20,8 @@ import com.adealink.weparty.commonui.widget.floatview.view.BaseLayoutFloatView
 import com.adealink.weparty.commonui.widget.floatview.view.BaseWindowFloatView
 import com.adealink.weparty.commonui.widget.floatview.view.FloatLinearLayout
 import java.util.LinkedList
+import java.util.concurrent.CopyOnWriteArrayList
+import java.util.concurrent.CopyOnWriteArraySet
 
 open class ApplicationModeWindowManager : BaseWindowManager() {
 
@@ -47,12 +49,14 @@ open class ApplicationModeWindowManager : BaseWindowManager() {
         }
     )
 
-    private val viewAddMap = mutableListOf<BaseFloatView<out IFloatData>>()
-    private val viewTypes = mutableSetOf<FloatWindowType>()
-    private val viewTags = mutableSetOf<String>()
+    private val viewAddMap = CopyOnWriteArrayList<BaseFloatView<out IFloatData>>()
+    private val viewTypes = CopyOnWriteArraySet<FloatWindowType>()
+    private val viewTags = CopyOnWriteArraySet<String>()
     private var isCanAddToView = false
     private val toAddViewQueue = LinkedList<BaseFloatView<out IFloatData>>()
 
+    private val MAX_SHOW_VIEW_COUNT = 2 //默认同时最多展示2条
+
     override fun addView(view: BaseFloatView<out IFloatData>) {
         Log.i(TAG_FLOAT_VIEW, "${SUB_TAG}, addView: $view")
         runOnUiThread {
@@ -78,6 +82,21 @@ open class ApplicationModeWindowManager : BaseWindowManager() {
                 return false
             }
 
+//            //同一个位置,限制2个,但是放income过去
+            val topViewCount = viewAddMap.count {
+                it is BaseLayoutFloatView && it.baseFloatData.gravity() == GRAVITY_TOP
+            }
+            if (topViewCount >= MAX_SHOW_VIEW_COUNT
+                && (view.baseFloatData.windowType() != FloatWindowType.CALL_1V1_INCOMING
+                        && view.baseFloatData.windowType() != FloatWindowType.NOTIFICATION_MESSAGE)
+            ) {
+                Log.w(
+                    TAG_FLOAT_VIEW,
+                    "${SUB_TAG}, addViewImmediate, viewAddMap size(${viewTags.size}) >= MAX_SHOW_VIEW_COUNT($MAX_SHOW_VIEW_COUNT), not add $view"
+                )
+                return false
+            }
+
             Log.i(TAG_FLOAT_VIEW, "${SUB_TAG}, addViewImmediate, viewAddMap.add($view)")
             return addViewInner(wm, view).also { addView ->
                 if (addView) {
@@ -126,6 +145,7 @@ open class ApplicationModeWindowManager : BaseWindowManager() {
                         )
                     }
                 }
+
             } else if (view is BaseWindowFloatView) {
                 if (view.windowParams.token == null) {
                     Log.i(
@@ -229,7 +249,9 @@ open class ApplicationModeWindowManager : BaseWindowManager() {
     }
 
     internal fun doOnResume(activity: Activity) {
-        viewAddMap.forEach { view ->
+        val iterator = viewAddMap.iterator()
+        while (iterator.hasNext()) {
+            val view = iterator.next()
             addViewInner(activity.windowManager, view).also {
                 if (it) {
                     view.onResume()
@@ -256,7 +278,9 @@ open class ApplicationModeWindowManager : BaseWindowManager() {
         if (topViewContainer.windowLayoutParams.token != null) {
             topViewContainer.onPause()
         }
-        viewAddMap.forEach { view ->
+        val iterator = viewAddMap.iterator()
+        while (iterator.hasNext()) {
+            val view = iterator.next()
             removeViewInner(activity.windowManager, view, "onPause").also {
                 view.onPause()
             }
@@ -277,9 +301,11 @@ open class ApplicationModeWindowManager : BaseWindowManager() {
     }
 
     override fun findFloatViewByType(type: FloatWindowType): BaseFloatView<out IFloatData>? {
-        viewAddMap.forEach {
-            if (it.baseFloatData.windowType() == type) {
-                return it
+        val iterator = viewAddMap.iterator()
+        while (iterator.hasNext()) {
+            val view = iterator.next()
+            if (view.baseFloatData.windowType() == type) {
+                return view
             }
         }
         return null
@@ -343,4 +369,14 @@ open class ApplicationModeWindowManager : BaseWindowManager() {
             }
         }
     }
+
+    override fun removeAllFloatViews(type: FloatWindowType, reason: String) {
+        Log.i(TAG_FLOAT_VIEW, "${SUB_TAG}, removeAllFloatViews, type: $type, reason: $reason")
+        runOnUiThread {
+            toAddViewQueue.removeAll { it.baseFloatData.windowType() == type }
+            viewAddMap.filter { it.baseFloatData.windowType() == type }.forEach { view ->
+                removeView(view, reason)
+            }
+        }
+    }
 }

+ 4 - 0
app/src/main/java/com/adealink/weparty/commonui/widget/floatview/mode/fix/Android10FixApplicationModeWindowManager.kt

@@ -124,4 +124,8 @@ class Android10FixApplicationModeWindowManager(
         base.notifyWhenActivityChanged(activity)
     }
 
+    override fun removeAllFloatViews(type: FloatWindowType, reason: String) {
+        base.removeFloatViewByType(type, reason)
+    }
+
 }

+ 5 - 21
app/src/main/java/com/adealink/weparty/commonui/widget/floatview/view/BaseLongPressDragFloatView.kt

@@ -52,8 +52,6 @@ abstract class BaseLongPressDragFloatView(baseFloatData: IWindowFloatData) : Bas
             override fun onLongPress(e: MotionEvent) {
                 Log.d(TAG, "GestureDetector: onLongPress")
                 isDragging = true
-                this@BaseLongPressDragFloatView.onLongPress()
-                requestDisallowInterceptTouchEvent(true)
                 performHapticFeedback()
             }
         })
@@ -61,35 +59,30 @@ abstract class BaseLongPressDragFloatView(baseFloatData: IWindowFloatData) : Bas
 
     override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
         gestureDetector.onTouchEvent(ev)
-        if (ev.action == MotionEvent.ACTION_DOWN || ev.action == MotionEvent.ACTION_UP) {
+        if (ev.action == MotionEvent.ACTION_DOWN) {
             isDragging = false
-            this@BaseLongPressDragFloatView.onTouchDown()
-            requestDisallowInterceptTouchEvent(true)
+        }
+        if (isDragging) {
+            return onTouchEvent(ev)
         }
         return super.dispatchTouchEvent(ev)
     }
 
     override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
-        if (isDragging) {
-            return true
-        }
-        return super.onInterceptTouchEvent(ev)
+        return isDragging
     }
 
     override fun onTouchEvent(event: MotionEvent): Boolean {
         when (event.action) {
             MotionEvent.ACTION_DOWN -> {
-                downPoint.x = event.rawX.toInt()
                 downPoint.y = event.rawY.toInt()
                 lastPoint.x = downPoint.x
                 lastPoint.y = downPoint.y
-                Log.d(TAG, "onTouchEvent(ACTION_MOVE), $downPoint")
             }
 
             MotionEvent.ACTION_MOVE -> {
                 movePoint.x = event.rawX.toInt()
                 movePoint.y = event.rawY.toInt()
-                Log.d(TAG, "onTouchEvent(ACTION_MOVE), $movePoint")
                 updateViewLayout(movePoint.x - lastPoint.x, movePoint.y - lastPoint.y)
                 maxMovePoint.x = Math.max(lastPoint.x, movePoint.x)
                 maxMovePoint.y = Math.max(lastPoint.y, movePoint.y)
@@ -150,7 +143,6 @@ abstract class BaseLongPressDragFloatView(baseFloatData: IWindowFloatData) : Bas
     private fun updateViewLayout(changeX: Int, changeY: Int) {
         val newX = changeX + windowParams.x
         val newY = changeY + windowParams.y
-        Log.d(TAG, "updateViewLayout, newX:$newX, newY:$newY")
         if (newX in 0 until maxX) {
             windowParams.x = newX
         }
@@ -263,14 +255,6 @@ abstract class BaseLongPressDragFloatView(baseFloatData: IWindowFloatData) : Bas
      */
     abstract fun applySnapToEdge(): Boolean
 
-    open fun onTouchDown() {
-
-    }
-
-    open fun onLongPress() {
-
-    }
-
     protected open fun getFixedLocation(): Int {
         val location = IntArray(2)
         getLocationOnScreen(location)

+ 3 - 1
app/src/main/java/com/adealink/weparty/commonui/widget/floatview/view/BaseSlideFloatView.kt

@@ -27,9 +27,11 @@ abstract class BaseSlideFloatView(baseFloatData: ILayoutFloatData) : BaseLayoutF
         }
     }
 
+    abstract fun slideDirection(): Slide.SlideDirection
+
     private val slide: Slide = SlideBuilder(this@BaseSlideFloatView)
         .slideToParent()
-        .slideDirection(Slide.SlideDirection.HORIZONTAL) //支持左右滑动
+        .slideDirection(slideDirection()) //支持左右滑动
         .autoSlideToEndPercent(DISMISS_PERCENT)
         .listeners(object : Slide.Listener {
             override fun onSlide(percent: Float) {

+ 12 - 4
app/src/main/java/com/adealink/weparty/commonui/widget/floatview/view/FloatLinearLayout.kt

@@ -25,7 +25,11 @@ class FloatLinearLayout @JvmOverloads constructor(
 ) : LinearLayoutCompat(context, attrs, defStyleAttr),
     IFloatViewGroup<BaseLayoutFloatView> {
 
-    private val layersDefine: IntArray = intArrayOf()
+    private val layersDefine: IntArray = intArrayOf(
+        APP_R.id.id_float_incoming_view, //1v1来电呼叫置顶
+        APP_R.id.id_float_message_view
+    )
+
     private val layerViewIndexSA = SparseIntArray()
 
     /**
@@ -70,6 +74,9 @@ class FloatLinearLayout @JvmOverloads constructor(
     }
 
     private fun getViewIndex(@IdRes id: Int): Int {
+        if (id == NO_ID) {
+            return 0
+        }
         val defineIndex = getLayerViewIndex(id)
         if (defineIndex == -1) {
             if (BuildConfig.DEBUG) {
@@ -115,7 +122,7 @@ class FloatLinearLayout @JvmOverloads constructor(
             }
 
             else -> {
-                -1
+                0
             }
         }
     }
@@ -148,8 +155,9 @@ class FloatLinearLayout @JvmOverloads constructor(
     }
 
     override fun addFloatView(view: BaseLayoutFloatView) {
-        Log.i(TAG_FLOAT_LAYOUT_VIEW, "addFloatView, $view")
-        addView(view, 0, view.layoutParams)
+        val index = getViewIndex(view.id)
+        Log.i(TAG_FLOAT_LAYOUT_VIEW, "addFloatView(index:$index) $view")
+        addView(view, index, view.layoutParams)
     }
 
     override fun findFloatViewByType(type: FloatWindowType): BaseLayoutFloatView? {

+ 30 - 3
app/src/main/java/com/adealink/weparty/module/game/GameModule.kt

@@ -15,6 +15,7 @@ import com.adealink.weparty.module.game.data.GameShowConfig
 import com.adealink.weparty.module.game.data.GameType
 import com.adealink.weparty.module.game.data.MiniSlotConfigInfoResponse
 import com.adealink.weparty.module.game.data.MiniSlotNeedData
+import com.adealink.weparty.module.game.data.OutRoomGameItem
 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
@@ -24,6 +25,7 @@ import com.adealink.weparty.module.game.viewmodel.IRedPacketViewModel
 import com.adealink.weparty.module.game.viewmodel.IRouletteViewModel
 import com.adealink.weparty.module.profile.decorate.data.DecorType
 import com.adealink.weparty.module.room.data.RedPacketInfo
+import com.adealink.weparty.url.LuckyGameSource
 
 /**
  * Created by sunxiaodong on 2021/7/1.
@@ -72,8 +74,12 @@ object GameModule : BaseDynamicModule<IGameService>(IGameService::class), IGameS
         return getService().getAllGameShowConfigs()
     }
 
-    override fun navigateToGame(gameType: GameEntranceType, canceledOnTouchOutside: Boolean) {
-        return getService().navigateToGame(gameType, canceledOnTouchOutside)
+    override fun navigateToGame(
+        gameType: GameEntranceType,
+        canceledOnTouchOutside: Boolean,
+        source: LuckyGameSource
+    ) {
+        return getService().navigateToGame(gameType, canceledOnTouchOutside, source)
     }
 
     override suspend fun getActivityGameRewardInfo(req: CommonActivityRewardInfoReq): com.adealink.weparty.module.game.data.GameActivityRewardInfo? {
@@ -119,10 +125,18 @@ object GameModule : BaseDynamicModule<IGameService>(IGameService::class), IGameS
         return getService().getRocketHeadlineFloatView(data)
     }
 
+    override suspend fun getOutRoomGames(): List<OutRoomGameItem> {
+        return getService().getOutRoomGames()
+    }
+
     override suspend fun getMiniSlotConfigInfo(needType: MiniSlotNeedData): Rlt<Res<MiniSlotConfigInfoResponse>>  {
         return getService().getMiniSlotConfigInfo(needType)
     }
 
+    override fun getGamePlayedRecords(): List<GameEntranceType> {
+        return getService().getGamePlayedRecords()
+    }
+
     override fun emptyService(): IGameService {
         return object : IGameService {
 
@@ -161,7 +175,12 @@ object GameModule : BaseDynamicModule<IGameService>(IGameService::class), IGameS
                 return emptyMap()
             }
 
-            override fun navigateToGame(gameType: GameEntranceType, canceledOnTouchOutside: Boolean) {
+            override fun navigateToGame(
+                gameType: GameEntranceType,
+                canceledOnTouchOutside: Boolean,
+                source: LuckyGameSource
+            ) {
+
             }
 
             override suspend fun getActivityGameRewardInfo(req: CommonActivityRewardInfoReq): com.adealink.weparty.module.game.data.GameActivityRewardInfo? {
@@ -207,6 +226,10 @@ object GameModule : BaseDynamicModule<IGameService>(IGameService::class), IGameS
                 return null
             }
 
+            override suspend fun getOutRoomGames(): List<OutRoomGameItem> {
+                return emptyList()
+            }
+
             override fun getService(): IGameService? {
                 return null
             }
@@ -214,6 +237,10 @@ object GameModule : BaseDynamicModule<IGameService>(IGameService::class), IGameS
             override suspend fun getMiniSlotConfigInfo(needType: MiniSlotNeedData): Rlt<Res<MiniSlotConfigInfoResponse>>  {
                 return Rlt.Failed(IError())
             }
+
+            override fun getGamePlayedRecords(): List<GameEntranceType> {
+                return emptyList()
+            }
         }
 
     }

+ 19 - 5
app/src/main/java/com/adealink/weparty/module/game/IGameService.kt

@@ -14,6 +14,7 @@ import com.adealink.weparty.module.game.data.GameShowConfig
 import com.adealink.weparty.module.game.data.GameType
 import com.adealink.weparty.module.game.data.MiniSlotConfigInfoResponse
 import com.adealink.weparty.module.game.data.MiniSlotNeedData
+import com.adealink.weparty.module.game.data.OutRoomGameItem
 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
@@ -23,6 +24,7 @@ import com.adealink.weparty.module.game.viewmodel.IRedPacketViewModel
 import com.adealink.weparty.module.game.viewmodel.IRouletteViewModel
 import com.adealink.weparty.module.profile.decorate.data.DecorType
 import com.adealink.weparty.module.room.data.RedPacketInfo
+import com.adealink.weparty.url.LuckyGameSource
 
 /**
  * Created by sunxiaodong on 2021/7/1.
@@ -36,9 +38,9 @@ interface IGameService : IService<IGameService> {
     fun getRocketViewModel(owner: ViewModelStoreOwner): IRocketViewModel?
     fun updateCurRedPacketInfo(redPacketInfo: RedPacketInfo?)
     suspend fun isShowRoulette(roomId: Long): Boolean
-    suspend fun getActivityGameRewardInfo(req: CommonActivityRewardInfoReq) : GameActivityRewardInfo?
-    suspend fun getUserGameLevelInfo(uids: List<Long>) : UserGameLevelInfoResult?
-    suspend fun toReceiveGameReward(req: ReceiveRewardReq) : Rlt<Boolean>?
+    suspend fun getActivityGameRewardInfo(req: CommonActivityRewardInfoReq): GameActivityRewardInfo?
+    suspend fun getUserGameLevelInfo(uids: List<Long>): UserGameLevelInfoResult?
+    suspend fun toReceiveGameReward(req: ReceiveRewardReq): Rlt<Boolean>?
     fun isShowSuperGift(): Boolean
     fun getPlayingGame(): GameType?
     fun logout()
@@ -47,14 +49,26 @@ interface IGameService : IService<IGameService> {
      * @param gameType 游戏类型, 为null则查询所有游戏类型
      * @return <gameType, <gameMode, 下注配置表>>
      */
-    suspend fun getGameBetCoinsConfig(game: Game, gameType: Int? = null): Map<Int, Map<Int, List<Int>>>
+    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>?
 
+    suspend fun getOutRoomGames(): List<OutRoomGameItem>
+
     suspend fun isGameShow(configTypes: List<DecorType>): Map<DecorType, GameShowConfig>
     suspend fun getAllGameShowConfigs(): Map<DecorType, GameShowConfig>
-    fun navigateToGame(gameType: GameEntranceType, canceledOnTouchOutside: Boolean = true)
+    fun navigateToGame(
+        gameType: GameEntranceType,
+        canceledOnTouchOutside: Boolean = true,
+        source: LuckyGameSource = LuckyGameSource.Room
+    )
     suspend fun getMiniSlotConfigInfo(needType: MiniSlotNeedData): Rlt<Res<MiniSlotConfigInfoResponse>>
+
+    fun getGamePlayedRecords(): List<GameEntranceType>
 }

+ 1 - 1
app/src/main/java/com/adealink/weparty/module/game/data/GameData.kt

@@ -43,7 +43,7 @@ data class GameShowConfig(
     @SerializedName("showInRoomActPps") val showInRoomActPps: Boolean, //房间右下角游戏组件开关
     @GsonNullable
     @SerializedName("url") val url: String? = null, //Slot游戏
-    @SerializedName("showOutRoomGame") val showOutRoomGame: Boolean = false,
+    @SerializedName("showOutRoomGame") val showOutRoomGame: Boolean = false, //游戏挂件展示控制
 )
 
 data class CommonActivityRewardInfoReq(

+ 46 - 64
app/src/main/java/com/adealink/weparty/module/game/data/GameEntranceData.kt

@@ -1,16 +1,7 @@
 package com.adealink.weparty.module.game.data
 
-import android.os.Bundle
-import androidx.fragment.app.FragmentActivity
 import com.adealink.frame.locale.language.languageManager
-import com.adealink.frame.router.Router
-import com.adealink.frame.util.AppUtil
-import com.adealink.weparty.commonui.dialogfragment.BaseDialogFragment
-import com.adealink.weparty.module.game.Game
-import com.adealink.weparty.module.game.GameModule
-import com.adealink.weparty.module.operation.Operation
-import com.adealink.weparty.module.operation.Operation.RechargePackage.Companion.EXTRA_SOURCE
-import com.adealink.weparty.module.operation.Operation.RechargePackage.Companion.SOURCE_ROOM_OPERATION
+import com.adealink.weparty.R
 import com.adealink.weparty.module.operation.rechargepackage.data.RechargePackageConfig
 import com.opensource.svgaplayer.utils.UriUtil
 
@@ -33,8 +24,8 @@ enum class GameEntranceType(val type: Int) {
     DRAGON_TIGER_FIGHT(13),
     ROCKET(14),
     GREEDY_PERSONAL(15),
-    MINI_SLOT(16) // 小老虎机
-    ;
+    MINI_SLOT(16), // 小老虎机
+    MORE_GAME(17);
 
     companion object{
         /**
@@ -61,117 +52,108 @@ enum class GameEntranceType(val type: Int) {
                 else -> null
             }
         }
+
+        fun map(type: Int): GameEntranceType? {
+            return values().find { it.type == type }
+        }
     }
 }
 
-sealed class GameEntrance(val icon: String, val type: GameEntranceType, val onClick: (() -> Unit) = { })
+sealed class GameEntrance(
+    val inRoomIcon: String, //房间内使用的icon
+    val outRoomIcon: String = inRoomIcon, //房间外使用的icon
+    val type: GameEntranceType,
+)
 
+//更多游戏入口
+class MoreGanemEntrance : GameEntrance(
+    inRoomIcon = UriUtil.getUriForResourceId(R.drawable.common_more_game_ic).toString(),
+    outRoomIcon = UriUtil.getUriForResourceId(R.drawable.common_more_game_ic).toString(),
+    type = GameEntranceType.LUCKY_FRUIT
+)
 
 class RechargePackageEntrance(val config: RechargePackageConfig) : GameEntrance(
     config.getIcon(languageManager?.getLanguageCode()),
-    GameEntranceType.RECHARGE_PACKAGE,
-    {
-        val topActivity = AppUtil.currentActivity
-        if (topActivity is FragmentActivity) {
-            Router.getRouterInstance<BaseDialogFragment>(Operation.RechargePackage.PATH)
-                ?.apply {
-                    arguments = Bundle().apply {
-                        putInt(EXTRA_SOURCE, SOURCE_ROOM_OPERATION)
-                    }
-                }
-                ?.show(topActivity.supportFragmentManager)
-        }
-    }
+    config.getIcon(languageManager?.getLanguageCode()),
+    GameEntranceType.RECHARGE_PACKAGE
 )
 
 class DailyRechargeEntrance : GameEntrance(
+    "",
     "",
     GameEntranceType.DAILY_RECHARGE,
-    {
-        val fragment =
-            Router.getRouterInstance<BaseDialogFragment>(Operation.RechargeDaily.PATH)
-        val topActivity = AppUtil.currentActivity
-        if (topActivity is FragmentActivity) {
-            fragment?.show(topActivity.supportFragmentManager)
-        }
-    }
 )
 
 data class RocketEntrance(var rocketLevel: Int, var progress: Int) : GameEntrance(
+    "",
     "",
     GameEntranceType.ROCKET,
-    {
-        val topActivity = AppUtil.currentActivity
-        if (topActivity is FragmentActivity) {
-            Router.getRouterInstance<BaseDialogFragment>(Game.Rocket.RocketPanel.PATH)
-                ?.show(topActivity.supportFragmentManager)
-        }
-
-    }
 )
 
 class LuckyFruitEntrance : GameEntrance(
-    UriUtil.getUriForResourceId(com.adealink.weparty.R.drawable.common_lucky_fruit_ic).toString(),
+    inRoomIcon = UriUtil.getUriForResourceId(R.drawable.common_lucky_fruit_ic).toString(),
+    outRoomIcon = UriUtil.getUriForResourceId(R.drawable.game_icon_lucky_fruit_out_room).toString(),
     GameEntranceType.LUCKY_FRUIT,
-    { GameModule.navigateToGame(GameEntranceType.LUCKY_FRUIT) }
 )
 
 class SlotEntrance : GameEntrance(
-    UriUtil.getUriForResourceId(com.adealink.weparty.R.drawable.common_jackpot_ic).toString(),
+    inRoomIcon = UriUtil.getUriForResourceId(R.drawable.common_jackpot_ic).toString(),
+    outRoomIcon = UriUtil.getUriForResourceId(R.drawable.game_icon_jackpot_out_room).toString(),
     GameEntranceType.JACKPOT,
-    { GameModule.navigateToGame(GameEntranceType.JACKPOT) }
 )
 
 class GreedyProEntrance : GameEntrance(
-    UriUtil.getUriForResourceId(com.adealink.weparty.R.drawable.common_greedy_pro_ic).toString(),
+    inRoomIcon = UriUtil.getUriForResourceId(R.drawable.common_greedy_pro_ic).toString(),
+    outRoomIcon = UriUtil.getUriForResourceId(R.drawable.game_icon_greedy_pro_out_room).toString(),
     GameEntranceType.GREEDY_PRO,
-    { GameModule.navigateToGame(GameEntranceType.GREEDY_PRO) }
 )
 
 class GreedyBoxEntrance : GameEntrance(
-    UriUtil.getUriForResourceId(com.adealink.weparty.R.drawable.common_greedy_box_ic).toString(),
+    inRoomIcon = UriUtil.getUriForResourceId(R.drawable.common_greedy_box_ic).toString(),
+    outRoomIcon = UriUtil.getUriForResourceId(R.drawable.game_icon_greedy_box_out_room).toString(),
     GameEntranceType.GREEDY_BOX,
-    { GameModule.navigateToGame(GameEntranceType.GREEDY_BOX) }
 )
 
 class SlotProEntrance : GameEntrance(
-    UriUtil.getUriForResourceId(com.adealink.weparty.R.drawable.common_jackpot_slot_ic).toString(),
+    inRoomIcon = UriUtil.getUriForResourceId(R.drawable.common_jackpot_slot_ic).toString(),
+    outRoomIcon = UriUtil.getUriForResourceId(R.drawable.game_icon_jackpot_slot_out_room)
+        .toString(),
     GameEntranceType.JACKPOT_SLOT,
-    { GameModule.navigateToGame(GameEntranceType.JACKPOT_SLOT) }
 )
 
 class LuckyProEntrance : GameEntrance(
-    UriUtil.getUriForResourceId(com.adealink.weparty.R.drawable.common_lucky_pro_ic).toString(),
+    inRoomIcon = UriUtil.getUriForResourceId(R.drawable.common_lucky_pro_ic).toString(),
+    outRoomIcon = UriUtil.getUriForResourceId(R.drawable.game_icon_lucky_pro_out_room).toString(),
     GameEntranceType.LUCKY_PRO,
-    { GameModule.navigateToGame(GameEntranceType.LUCKY_PRO) }
 )
 
 class TeenPattiEntrance : GameEntrance(
-    UriUtil.getUriForResourceId(com.adealink.weparty.R.drawable.common_teen_patti_ic).toString(),
+    inRoomIcon = UriUtil.getUriForResourceId(R.drawable.common_teen_patti_ic).toString(),
+    outRoomIcon = UriUtil.getUriForResourceId(R.drawable.game_icon_teenpatti_out_room).toString(),
     GameEntranceType.TEEN_PATTI,
-    { GameModule.navigateToGame(GameEntranceType.TEEN_PATTI) }
 )
 
 class RussianRouletteEntrance : GameEntrance(
-    UriUtil.getUriForResourceId(com.adealink.weparty.R.drawable.common_russian_roulette_ic).toString(),
+    inRoomIcon = UriUtil.getUriForResourceId(R.drawable.common_russian_roulette_ic).toString(),
+    outRoomIcon = UriUtil.getUriForResourceId(R.drawable.game_icon_roulette_out_room).toString(),
     GameEntranceType.RUSSIAN_ROULETTE,
-    { GameModule.navigateToGame(GameEntranceType.RUSSIAN_ROULETTE) }
 )
 
 class TexasCowboyEntrance : GameEntrance(
-    UriUtil.getUriForResourceId(com.adealink.weparty.R.drawable.common_texas_cowboy_ic).toString(),
+    inRoomIcon = UriUtil.getUriForResourceId(R.drawable.common_texas_cowboy_ic).toString(),
+    outRoomIcon = UriUtil.getUriForResourceId(R.drawable.game_icon_texas_cowboy_out_room)
+        .toString(),
     GameEntranceType.TEXAS_COWBOY,
-    { GameModule.navigateToGame(GameEntranceType.TEXAS_COWBOY) }
 )
 
 class DragonTigerEntrance : GameEntrance(
-    UriUtil.getUriForResourceId(com.adealink.weparty.R.drawable.common_dragon_tiger_ic).toString(),
+    inRoomIcon = UriUtil.getUriForResourceId(R.drawable.common_dragon_tiger_ic).toString(),
+    outRoomIcon = UriUtil.getUriForResourceId(R.drawable.game_icon_dragon_out_room).toString(),
     GameEntranceType.DRAGON_TIGER_FIGHT,
-    { GameModule.navigateToGame(GameEntranceType.DRAGON_TIGER_FIGHT) }
 )
 
 class GreedyPersonalEntrance(canceledOnTouchOutside: Boolean = true) : GameEntrance(
-    UriUtil.getUriForResourceId(com.adealink.weparty.R.drawable.common_greedy_personal_ic).toString(),
+    inRoomIcon = UriUtil.getUriForResourceId(R.drawable.common_greedy_personal_ic).toString(),
+    outRoomIcon = UriUtil.getUriForResourceId(R.drawable.common_greedy_personal_ic).toString(),
     GameEntranceType.GREEDY_PERSONAL,
-    { GameModule.navigateToGame(GameEntranceType.GREEDY_PERSONAL, canceledOnTouchOutside) }
 )

+ 89 - 0
app/src/main/java/com/adealink/weparty/module/game/data/OutRoomGameData.kt

@@ -0,0 +1,89 @@
+package com.adealink.weparty.module.game.data
+
+import android.os.Parcelable
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import com.adealink.weparty.R
+import com.adealink.weparty.commonui.recycleview.diffutil.BaseListItemData
+import kotlinx.parcelize.Parcelize
+
+/**
+ * 房间外游戏项
+ */
+@Parcelize
+data class OutRoomGameItem(
+    val gameType: GameEntranceType
+) : BaseListItemData, Parcelable {
+    override fun areContentsTheSame(newItem: Any): Boolean {
+        if (newItem !is OutRoomGameItem) {
+            return false
+        }
+        return gameType == newItem.gameType
+    }
+
+    override fun areItemsTheSame(newItem: Any): Boolean {
+        if (newItem !is OutRoomGameItem) {
+            return false
+        }
+        return gameType == newItem.gameType
+    }
+}
+
+/**
+ * 房间外游戏
+ */
+object OutRoomGameMapping {
+    @DrawableRes
+    fun getGameIcon(gameType: GameEntranceType): Int {
+        return when (gameType) {
+            GameEntranceType.LUCKY_FRUIT -> R.drawable.game_icon_lucky_fruit_out_room
+            GameEntranceType.DRAGON_TIGER_FIGHT -> R.drawable.game_icon_dragon_out_room
+            GameEntranceType.LUCKY_PRO -> R.drawable.game_icon_lucky_pro_out_room
+            GameEntranceType.JACKPOT -> R.drawable.game_icon_jackpot_out_room
+            GameEntranceType.JACKPOT_SLOT -> R.drawable.game_icon_jackpot_slot_out_room
+            GameEntranceType.GREEDY_BOX -> R.drawable.game_icon_greedy_box_out_room
+            GameEntranceType.GREEDY_PRO -> R.drawable.game_icon_greedy_pro_out_room
+            GameEntranceType.RUSSIAN_ROULETTE -> R.drawable.game_icon_roulette_out_room
+            GameEntranceType.TEEN_PATTI -> R.drawable.game_icon_teenpatti_out_room
+            GameEntranceType.TEXAS_COWBOY -> R.drawable.game_icon_texas_cowboy_out_room
+            GameEntranceType.GREEDY_PERSONAL -> R.drawable.game_icon_lucky_77_out_room
+            else -> 0
+        }
+    }
+
+    @StringRes
+    fun getGameTitle(gameType: GameEntranceType): Int {
+        return when (gameType) {
+            GameEntranceType.LUCKY_FRUIT -> R.string.common_lucky_fruit
+            GameEntranceType.DRAGON_TIGER_FIGHT -> R.string.common_dragon_tiger_fight
+            GameEntranceType.LUCKY_PRO -> R.string.common_lucky_pro
+            GameEntranceType.JACKPOT -> R.string.common_jackpot
+            GameEntranceType.JACKPOT_SLOT -> R.string.common_jackpot_slot
+            GameEntranceType.GREEDY_BOX -> R.string.common_greedy_box
+            GameEntranceType.GREEDY_PRO -> R.string.common_greedy_pro
+            GameEntranceType.RUSSIAN_ROULETTE -> R.string.common_russian_turntable
+            GameEntranceType.TEEN_PATTI -> R.string.common_teen_patti
+            GameEntranceType.TEXAS_COWBOY -> R.string.common_texas_cowboy
+            GameEntranceType.GREEDY_PERSONAL -> R.string.common_greedy_personal
+            else -> 0
+        }
+    }
+
+    @DrawableRes
+    fun getGameTag(gameType: GameEntranceType): Int {
+        return when (gameType) {
+            GameEntranceType.LUCKY_FRUIT -> R.drawable.game_tag_lucky_fruit_out_room
+            GameEntranceType.DRAGON_TIGER_FIGHT -> R.drawable.game_tag_dragon_tiger_slot_out_room
+            GameEntranceType.LUCKY_PRO -> R.drawable.game_tag_lucky_pro_out_room
+            GameEntranceType.JACKPOT -> R.drawable.game_tag_jackpot_out_room
+            GameEntranceType.JACKPOT_SLOT -> R.drawable.game_tag_jackpot_slot_out_room
+            GameEntranceType.GREEDY_BOX -> R.drawable.game_tag_greedy_box_out_room
+            GameEntranceType.GREEDY_PRO -> R.drawable.game_tag_greedy_pro_out_room
+            GameEntranceType.RUSSIAN_ROULETTE -> R.drawable.game_tag_roulette_out_room
+            GameEntranceType.TEEN_PATTI -> R.drawable.game_tag_teenpatti_out_room
+            GameEntranceType.TEXAS_COWBOY -> R.drawable.game_tag_texas_cowboy_out_room
+            GameEntranceType.GREEDY_PERSONAL -> R.drawable.game_tag_lucky_77_out_room
+            else -> 0
+        }
+    }
+}

+ 12 - 21
app/src/main/java/com/adealink/weparty/module/game/floatview/GameEntranceFloatView.kt

@@ -10,7 +10,7 @@ import com.adealink.frame.util.onClick
 import com.adealink.weparty.AppModule
 import com.adealink.weparty.commonui.ext.dp
 import com.adealink.weparty.commonui.ext.gone
-import com.adealink.weparty.commonui.ext.isValid
+import com.adealink.weparty.commonui.ext.isSVGAImage
 import com.adealink.weparty.commonui.ext.show
 import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
 import com.adealink.weparty.commonui.widget.banner.adapter.BannerAdapter
@@ -20,10 +20,12 @@ import com.adealink.weparty.commonui.widget.floatview.data.MODE_APPLICATION
 import com.adealink.weparty.commonui.widget.floatview.view.BaseLongPressDragFloatView
 import com.adealink.weparty.databinding.ItemGameEntranceFloatViewBinding
 import com.adealink.weparty.databinding.LayoutGameEntranceFloatViewBinding
+import com.adealink.weparty.module.game.GameModule
 import com.adealink.weparty.module.game.data.GameEntrance
 import com.adealink.weparty.module.game.data.GreedyPersonalEntrance
 import com.adealink.weparty.module.message.Message
 import com.adealink.weparty.ui.home.util.HomeUIUtil
+import com.adealink.weparty.url.LuckyGameSource
 
 class GameEntranceFloatData : IWindowFloatData {
     override fun windowType(): FloatWindowType = FloatWindowType.GAME_ENTRANCE
@@ -56,24 +58,6 @@ class GameEntranceFloatView(floatData: GameEntranceFloatData) : BaseLongPressDra
         show(isShow)
     }
 
-    override fun onTouchDown() {
-        super.onTouchDown()
-        if (isValid()) {
-            binding?.let {
-                it.bannerView.viewPager2?.isUserInputEnabled = true
-            }
-        }
-    }
-
-    override fun onLongPress() {
-        super.onLongPress()
-        if (isValid()) {
-            binding?.let {
-                it.bannerView.viewPager2?.isUserInputEnabled = false
-            }
-        }
-    }
-
     fun setEntranceList(
         list: List<GameEntrance>,
         handleBannerClick: (clickEntrance: GameEntrance) -> Unit,
@@ -87,7 +71,10 @@ class GameEntranceFloatView(floatData: GameEntranceFloatData) : BaseLongPressDra
             setAdapter(bannerAdapter)
             setOnBannerListener { data, _ ->
                 val gameEntrance = data as? GameEntrance ?: return@setOnBannerListener
-                handleBannerClick(gameEntrance)
+                GameModule.navigateToGame(
+                    gameType = gameEntrance.type,
+                    source = LuckyGameSource.OUT_ROOM_IM
+                )
             }
         }
 
@@ -157,9 +144,13 @@ class GameEntranceFloatView(floatData: GameEntranceFloatData) : BaseLongPressDra
                 binding.svgaView.show()
                 binding.svgaView.setAsset("ic_greedy_personal_out_room.svga")
                 binding.icon.gone()
+            } else if (isSVGAImage(data.outRoomIcon)) {
+                binding.svgaView.show()
+                binding.svgaView.setUrl(url = data.outRoomIcon)
+                binding.icon.gone()
             } else {
                 binding.icon.show()
-                binding.icon.setImageUrl(data.icon)
+                binding.icon.setImageUrl(data.outRoomIcon)
                 binding.svgaView.gone()
             }
         }

+ 3 - 2
app/src/main/java/com/adealink/weparty/module/game/floatview/GameEntranceFloatViewComp.kt

@@ -73,8 +73,9 @@ class GameEntranceFloatViewComp(lifecycleOwner: LifecycleOwner) : ViewComponent(
         if (gameEntrances.isNotEmpty()) {
             addFloatView()
             floatView?.setEntranceList(
-                gameEntrances,
-                { it.onClick() },
+                gameEntrances,{
+
+                },
                 {
                     removeFloatView("User close")
                     userClose = true

+ 76 - 0
app/src/main/java/com/adealink/weparty/module/game/util/GameRedPointManager.kt

@@ -0,0 +1,76 @@
+package com.adealink.weparty.module.game.util
+
+import com.adealink.frame.dot.Dot
+import com.adealink.frame.dot.NormalDot
+import com.adealink.weparty.module.game.data.GameEntranceType
+import com.adealink.weparty.module.room.datasource.local.RoomLocalService
+
+
+/**
+ * 房间外游戏 标签管理
+ */
+object GameRedPointManager {
+
+    val roomGameEntranceRedDot by lazy {
+        GameEntranceDot()
+    }
+
+    class GameEntranceDot() : Dot(arrayListOf(), "GameEntranceDot") {
+
+        init {
+            updateDot()
+        }
+
+        fun updateDot() {
+            //只要有一个为true,就显示红点
+            val needShow = RoomLocalService.showLuckyFruitNew
+                    || RoomLocalService.showLuckyProNew
+                    || RoomLocalService.showGreedyProNew
+                    || RoomLocalService.showJackpotNew
+                    || RoomLocalService.showJackpotProNew
+                    || RoomLocalService.showTeenPattiNew
+                    || RoomLocalService.showRussianRouletteNew
+                    || RoomLocalService.showTexasCowboyNew
+                    || RoomLocalService.showDragonTigerNew
+                    || RoomLocalService.showGreedyBoxNew
+                    || RoomLocalService.showGreedyPersonalNew
+            show(NormalDot(show = needShow))
+        }
+
+    }
+
+    fun isGameNew(gameType: GameEntranceType): Boolean {
+        return when (gameType) {
+            GameEntranceType.LUCKY_FRUIT -> RoomLocalService.showLuckyFruitNew
+            GameEntranceType.LUCKY_PRO -> RoomLocalService.showLuckyProNew
+            GameEntranceType.GREEDY_PRO -> RoomLocalService.showGreedyProNew
+            GameEntranceType.JACKPOT -> RoomLocalService.showJackpotNew
+            GameEntranceType.JACKPOT_SLOT -> RoomLocalService.showJackpotProNew
+            GameEntranceType.TEEN_PATTI -> RoomLocalService.showTeenPattiNew
+            GameEntranceType.RUSSIAN_ROULETTE -> RoomLocalService.showRussianRouletteNew
+            GameEntranceType.TEXAS_COWBOY -> RoomLocalService.showTexasCowboyNew
+            GameEntranceType.DRAGON_TIGER_FIGHT -> RoomLocalService.showDragonTigerNew
+            GameEntranceType.GREEDY_BOX -> RoomLocalService.showGreedyBoxNew
+            GameEntranceType.GREEDY_PERSONAL -> RoomLocalService.showGreedyPersonalNew
+            else -> false
+        }
+    }
+
+    fun setGameNew(gameType: GameEntranceType, isNew: Boolean) {
+        when (gameType) {
+            GameEntranceType.LUCKY_FRUIT -> RoomLocalService.showLuckyFruitNew = isNew
+            GameEntranceType.LUCKY_PRO -> RoomLocalService.showLuckyProNew = isNew
+            GameEntranceType.GREEDY_PRO -> RoomLocalService.showGreedyProNew = isNew
+            GameEntranceType.JACKPOT -> RoomLocalService.showJackpotNew = isNew
+            GameEntranceType.JACKPOT_SLOT -> RoomLocalService.showJackpotProNew = isNew
+            GameEntranceType.TEEN_PATTI -> RoomLocalService.showTeenPattiNew = isNew
+            GameEntranceType.RUSSIAN_ROULETTE -> RoomLocalService.showRussianRouletteNew = isNew
+            GameEntranceType.TEXAS_COWBOY -> RoomLocalService.showTexasCowboyNew = isNew
+            GameEntranceType.DRAGON_TIGER_FIGHT -> RoomLocalService.showDragonTigerNew = isNew
+            GameEntranceType.GREEDY_BOX -> RoomLocalService.showGreedyBoxNew = isNew
+            GameEntranceType.GREEDY_PERSONAL -> RoomLocalService.showGreedyPersonalNew = isNew
+            else -> {}
+        }
+        roomGameEntranceRedDot.updateDot()
+    }
+}

+ 3 - 0
app/src/main/java/com/adealink/weparty/module/game/viewmodel/IGameViewModel.kt

@@ -3,6 +3,7 @@ package com.adealink.weparty.module.game.viewmodel
 import androidx.lifecycle.LiveData
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.network.data.Res
+import com.adealink.weparty.module.game.data.GameEntranceType
 import com.adealink.weparty.module.game.data.GameShowConfig
 import com.adealink.weparty.module.game.data.GameType
 import com.adealink.weparty.module.game.data.LuckyFruitShowRes
@@ -25,4 +26,6 @@ interface IGameViewModel {
 
     fun isGameShow(configTypes: List<DecorType>): LiveData<Map<DecorType, GameShowConfig>>
     fun getAllGameShowConfigs(): LiveData<Map<DecorType, GameShowConfig>>
+
+    suspend fun getGamePlayedRecords(): List<GameEntranceType>
 }

+ 3 - 3
app/src/main/java/com/adealink/weparty/module/headline/HeadlineModule.kt

@@ -21,7 +21,7 @@ object HeadlineModule: BaseDynamicModule<IHeadlineService>(IHeadlineService::cla
                 return null
             }
 
-            override fun getGreedyPersonalGlobalHeadlineFloatView(data: IFloatData): BaseFloatView<out IFloatData>? {
+            override fun getGlobalHeadlineFloatView(data: IFloatData): BaseFloatView<out IFloatData>? {
                 return null
             }
 
@@ -49,8 +49,8 @@ object HeadlineModule: BaseDynamicModule<IHeadlineService>(IHeadlineService::cla
         return getService().getHeadLineViewModel(owner)
     }
 
-    override fun getGreedyPersonalGlobalHeadlineFloatView(data: IFloatData): BaseFloatView<out IFloatData>? {
-        return getService().getGreedyPersonalGlobalHeadlineFloatView(data)
+    override fun getGlobalHeadlineFloatView(data: IFloatData): BaseFloatView<out IFloatData>? {
+        return getService().getGlobalHeadlineFloatView(data)
     }
 
     override fun addListener(l: IHeadlineListener) {

+ 1 - 1
app/src/main/java/com/adealink/weparty/module/headline/IHeadlineService.kt

@@ -13,7 +13,7 @@ interface IHeadlineService: IService<IHeadlineService> {
 
     fun getHeadLineViewModel(owner: ViewModelStoreOwner): IHeadlineViewModel?
 
-    fun getGreedyPersonalGlobalHeadlineFloatView(data: IFloatData): BaseFloatView<out IFloatData>?
+    fun getGlobalHeadlineFloatView(data: IFloatData): BaseFloatView<out IFloatData>?
 
     fun addListener(l: IHeadlineListener)
 

+ 1 - 0
app/src/main/java/com/adealink/weparty/module/room/IRoomService.kt

@@ -156,6 +156,7 @@ interface IRoomService : IService<IRoomService>, IMediaOperatorGet {
 
     suspend fun exitRoomGame(game: Game): Rlt<Any>
     fun isInRoomGamePage(): Boolean
+    fun isTopActivityIsRoomActivity(): Boolean
 
     fun getRoomGamePlayerMicStatus(): List<UserMicStatus>
 

+ 8 - 0
app/src/main/java/com/adealink/weparty/module/room/RoomModule.kt

@@ -577,7 +577,15 @@ object RoomModule : BaseDynamicModule<IRoomService>(IRoomService::class), IRoomS
                 return emptyList()
             }
 
+            override fun isTopActivityIsRoomActivity(): Boolean {
+                return false
+            }
+
         }
     }
 
+    override fun isTopActivityIsRoomActivity(): Boolean {
+        return getService().isTopActivityIsRoomActivity()
+    }
+
 }

+ 7 - 0
app/src/main/java/com/adealink/weparty/module/room/Router.kt

@@ -290,4 +290,11 @@ interface Room {
         }
     }
 
+    interface PlayCenterPanel{
+        companion object {
+            const val PATH = "/room/play_center_panel"
+            const val TAG = "PlayCenterPanel"
+            const val EXTRA_ONLY_LUCKY_GAME_LIST = "extra_onlyLuckGameList"
+        }
+    }
 }

+ 13 - 2
app/src/main/java/com/adealink/weparty/module/room/data/RoomNotifyData.kt

@@ -42,15 +42,18 @@ enum class RoomNotifyType(val uri: String) {
     SendGift("GIFT_SEND_NOTIFY"),
     SendBackPack("GOODS_SEND_NOTIFY"),
     SendRedPacket("NEW_REDPACKET"),
+
     //    InviteReward("INVITE_REWARD_NOTIFY"),
     GiftWish("RAMADAN_GIFT_SEND_NOTIFY"),
     SuperGift("SUPER_GIFT_REWARD_NOTIFY"),
     RTC_CHANGED_NOTIFY("RTC_CHANGED_NOTIFY"),
+
     //    FAMILY_TOP_ROOM("FAMILY_TOP_ROOM"),
     //    FLAG_RAISING("FLAG_RAISING"),
     //    WORLD_CUP_POINTS_REACH_THRESHOLD("WORLDCUP_POINTS_REACH_THRESHOLD"),
     COMMON_GLOBAL_ROOM_BROADCAST_NOTIFY("COMMON_GLOBAL_NOTIFY"),
     DAILY_RECHARGE_REWARD_NOTIFY("URI_USER_DAILY_CHARGE_REWARD_NOTIFY"),
+
     //    LEVEL_UP_NOTIFY("URI_LEVEL_UP_OTHER_NOTIFY"),
     //    BETTING_PK_NOTIFY("SUPER_PK_GLOBAL_NOTIFY"),
     ADMIN_LOTTERY_ACTIVITY_REWARD_NOTIFY("URI_USER_ADMIN_LOTTERY_ACTIVITY_REWARD_NOTIFY"),
@@ -72,12 +75,16 @@ enum class RoomNotifyType(val uri: String) {
     SVIP_GLOBAL_NOTIFY("URI_SVIP_GLOBAL_NOTIFY"),   // SVIP 升级全服通知
     COMMON_LEVEL_CHANGE_NOTIFY("COMMON_LEVEL_CHANGE_NOTIFY"),
     URI_CONGRATULATE_LEVEL_UP_NOTIFY("URI_CONGRATULATE_LEVEL_UP_NOTIFY"),  //财富等级升级通知
+
     // Q:拉新活动相关奖励的Notify应不仅仅隶属Room,目前只有房间可见通知横幅,故沿用写法;
-    URI_INVITE_TASK_REWARD_NOTIFY("URI_INVITE_TASK_REWARD_NOTIFY"); // 拉新活动相关奖励
+    URI_INVITE_TASK_REWARD_NOTIFY("URI_INVITE_TASK_REWARD_NOTIFY"), // 拉新活动相关奖励
+
+    LUCKY_GIFT("uri_lucky_gift"),
+    ROCKET("uri_rocket");
 
     companion object {
         fun getGameDecorType(uri: String): DecorType {
-            return when(uri) {
+            return when (uri) {
                 LuckyFruit.uri -> DecorType.LUCKY_FRUIT_GAME
                 Slot.uri -> DecorType.JACKPOT_GAME
                 SlotPro.uri -> DecorType.JACKPOT_SLOT_GAME
@@ -92,6 +99,10 @@ enum class RoomNotifyType(val uri: String) {
                 else -> DecorType.EMPTY
             }
         }
+
+        fun map(uri: String): RoomNotifyType? {
+            return RoomNotifyType.entries.find { it.uri == uri }
+        }
     }
 }
 

+ 14 - 11
app/src/main/java/com/adealink/weparty/module/room/datasource/local/RoomLocalService.kt

@@ -64,27 +64,27 @@ object RoomLocalService : TypeDelegationPrefs(
 
     var shouldShowInvitedBellGuideDialog: Boolean by PrefKey("shouldShowInvitedBellGuideDialog", true)
 
-    var showFishingNew: Boolean by PrefUserKey("key_show_fishing_new", true)
+    var showFishingNew: Boolean by PrefUserKey("key_show_fishing_new", false)
 
-    var showLuckyFruitNew: Boolean by PrefUserKey("key_show_lucky_fruit_new", true)
+    var showLuckyFruitNew: Boolean by PrefUserKey("key_show_lucky_fruit_new", false)
 
-    var showLuckyProNew: Boolean by PrefUserKey("key_show_lucky_pro_new", true)
+    var showLuckyProNew: Boolean by PrefUserKey("key_show_lucky_pro_new", false)
 
-    var showGreedyProNew: Boolean by PrefUserKey("key_show_greedy_pro_new", true)
+    var showGreedyProNew: Boolean by PrefUserKey("key_show_greedy_pro_new", false)
 
-    var showJackpotNew: Boolean by PrefUserKey("key_show_jackpot_new", true)
+    var showJackpotNew: Boolean by PrefUserKey("key_show_jackpot_new", false)
 
-    var showJackpotProNew: Boolean by PrefUserKey("key_show_jackpot_pro_new", true)
+    var showJackpotProNew: Boolean by PrefUserKey("key_show_jackpot_pro_new", false)
 
-    var showTeenPattiNew: Boolean by PrefUserKey("key_show_teen_patti_new", true)
+    var showTeenPattiNew: Boolean by PrefUserKey("key_show_teen_patti_new", false)
 
-    var showRussianRouletteNew: Boolean by PrefUserKey("key_show_russian_roulette_new", true)
+    var showRussianRouletteNew: Boolean by PrefUserKey("key_show_russian_roulette_new", false)
 
-    var showTexasCowboyNew: Boolean by PrefUserKey("key_show_texas_cowboy_new", true)
+    var showTexasCowboyNew: Boolean by PrefUserKey("key_show_texas_cowboy_new", false)
 
-    var showDragonTigerNew: Boolean by PrefUserKey("key_show_dragon_tiger_new", true)
+    var showDragonTigerNew: Boolean by PrefUserKey("key_show_dragon_tiger_new", false)
 
-    var showGreedyBoxNew: Boolean by PrefUserKey("key_show_greedy_box_new", true)
+    var showGreedyBoxNew: Boolean by PrefUserKey("key_show_greedy_box_new", false)
 
     var showGreedyPersonalNew: Boolean by PrefUserKey("key_show_greedy_personal_new", true)
 
@@ -96,4 +96,7 @@ object RoomLocalService : TypeDelegationPrefs(
 
     // 是否开启隐身进房
     var isOpenInvisible: Boolean by PrefUserKey("key_user_is_open_invisible", false)
+
+    // 最近玩游戏记录,格式 "gameType:timestamp"
+    var lastPlayedGameRecord: String by PrefUserKey("key_last_played_game_record", "")
 }

+ 1 - 0
app/src/main/java/com/adealink/weparty/module/webview/Router.kt

@@ -37,6 +37,7 @@ interface Web {
             const val PATH = "/web/half_screen"
             const val EXTRA_HEIGHT = "height"
             const val EXTRA_TRANSPARENT = "extra_transparent"
+            const val EXTRA_WINDOW_BACKGROUND_COLOR = "window_background_color"
         }
 
     }

+ 4 - 4
app/src/main/java/com/adealink/weparty/module/webview/WebViewDialogFragmentBuilder.kt

@@ -1,7 +1,6 @@
 package com.adealink.weparty.module.webview
 
 import android.os.Bundle
-import androidx.fragment.app.Fragment
 import com.adealink.frame.router.Router
 import com.adealink.weparty.commonui.dialogfragment.BaseDialogFragment
 import com.adealink.weparty.module.webview.Web.HalfScreen.Companion.EXTRA_HEIGHT
@@ -14,7 +13,7 @@ import com.adealink.weparty.module.webview.data.OfflineH5GameInfo
 class WebViewDialogFragmentBuilder {
 
     private var heightPx = 0
-    private var trans: Boolean = false
+    private var transparent: Boolean = false
     private var loadingUrl: String? = null
     private var offlineH5GameInfo: OfflineH5GameInfo? = null
     private var canceledOnTouchOutside: Boolean = true
@@ -23,8 +22,9 @@ class WebViewDialogFragmentBuilder {
         this.heightPx = heightPx
         return this
     }
+
     fun transparent(trans: Boolean): WebViewDialogFragmentBuilder {
-        this.trans = trans
+        this.transparent = trans
         return this
     }
 
@@ -47,7 +47,7 @@ class WebViewDialogFragmentBuilder {
         val fragment = Router.getRouterInstance<BaseDialogFragment>(Web.HalfScreen.PATH)
         fragment?.arguments = Bundle().apply {
             putInt(EXTRA_HEIGHT, heightPx)
-            putBoolean(EXTRA_TRANSPARENT, trans)
+            putBoolean(EXTRA_TRANSPARENT, transparent)
             putString(Web.Common.EXTRA_LOADING_URL, loadingUrl)
             putParcelable(Web.Common.EXTRA_OFFLINE_H5_GAME_INFO, offlineH5GameInfo)
         }

+ 3 - 1
app/src/main/java/com/adealink/weparty/module/webview/data/WebData.kt

@@ -1,6 +1,7 @@
 package com.adealink.weparty.module.webview.data
 
 import android.os.Parcelable
+import com.adealink.weparty.url.LuckyGameSource
 import com.google.gson.annotations.JsonAdapter
 import com.google.gson.annotations.Must
 import com.google.gson.annotations.SerializedName
@@ -19,5 +20,6 @@ data class WebConfig(
 data class OfflineH5GameInfo(
     val downloadUrl: String?,
     val gameType: Int,
-    val needDownload: Boolean = true
+    val needDownload: Boolean = true,
+    val source: String? = null,        // 打开游戏的入口来源, 参考LuckyGameSource
 ): Parcelable

+ 21 - 0
app/src/main/java/com/adealink/weparty/url/LuckyGameSource.kt

@@ -0,0 +1,21 @@
+package com.adealink.weparty.url
+
+/**
+ * Created by XiaoDongLin.
+ * Date: 2025/8/1
+ *
+ * 房间内banner入口、房间内菜单面板入口、房间外-我的页面、房间外私聊挂件
+ */
+
+enum class LuckyGameSource(val source: String) {
+    Room("in_room"),// 房间内
+    Room_Banner("in_room_banner"),// 房间内banner入口
+    OUT_ROOM_IM("out_room_im"), // 房间外私聊挂件
+    OUT_ROOM_ME("out_room_me"); //房间外-我的页面
+
+    companion object {
+        fun map(source: String?): LuckyGameSource? {
+            return LuckyGameSource.values().firstOrNull { it.source == source }
+        }
+    }
+}

+ 25 - 0
app/src/main/java/com/adealink/weparty/url/UrlConfig.kt

@@ -258,4 +258,29 @@ object UrlConfig {
         isProdEnv -> "https://web.yoki.chat/index?projectname=yoki-greedy-personal&aspect_ratio=1.607"
         else -> "http://web-test.yoki.chat/index?projectname=yoki-greedy-personal&aspect_ratio=1.607"
     }
+
+
+    /**
+     * H5游戏拼接来源字段,用于埋点。不传默认room
+     */
+    fun String.appendSourceToUrl(source: LuckyGameSource): String {
+        val stringBuilder = StringBuilder()
+        stringBuilder.append(this)
+            .append(if (this.contains("?")) "&" else "?")
+            .append("source=${source.source}")
+        //非房间来源,传transparent=false
+        if (source == LuckyGameSource.OUT_ROOM_IM ||source == LuckyGameSource.OUT_ROOM_ME) {
+            stringBuilder.append(if (this.contains("?")) "&" else "?")
+                .append("transparent=false")
+            stringBuilder.append(if (this.contains("?")) "&" else "?")
+                .append("canceled_on_touch_outside=false")
+        }else{
+            stringBuilder.append(if (this.contains("?")) "&" else "?")
+                .append("transparent=true")
+                .append("canceled_on_touch_outside=true")
+        }
+
+        return stringBuilder.toString()
+    }
+
 }

+ 5 - 0
app/src/main/java/com/adealink/weparty/util/IntentUtil.kt

@@ -42,11 +42,16 @@ fun goLocalLinkPage(context: Context?, link: String?) {
                 val params = getParamsFromUri(link.toUri())
                 val aspectRatio = params["aspect_ratio"]?.toFloatOrNull() //高度/宽度
                 val activity = context as? FragmentActivity
+                val transparent = params["transparent"]?.toBoolean() ?: true //是否透明,默认为true
+                val canceledOnTouchOutside =
+                    params["canceled_on_touch_outside"]?.toBoolean() ?: true //是否点击外部取消,默认为true
                 if (activity != null && aspectRatio != null && aspectRatio > 0) {
                     //有比例半屏加载
                     val height = (aspectRatio * DisplayUtil.getScreenWidth()).toInt()
                     WebViewDialogFragmentBuilder()
                         .height(height)
+                        .transparent(transparent)
+                        .canceledOnTouchOutside(canceledOnTouchOutside)
                         .build()
                         ?.showUrl(activity.supportFragmentManager, link)
                 } else {

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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


+ 3 - 3
app/src/main/res/layout/item_game_entrance_float_view.xml

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools">
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
 
     <com.adealink.frame.image.view.NetworkImageView
         android:id="@+id/icon"

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

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<com.adealink.weparty.commonui.widget.floatview.view.SwipeToTopConstraintLayout
+<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"
@@ -64,4 +64,4 @@
         app:layout_constraintStart_toStartOf="@id/tv_name"
         app:layout_constraintTop_toBottomOf="@id/tv_name" />
 
-</com.adealink.weparty.commonui.widget.floatview.view.SwipeToTopConstraintLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>

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

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<com.adealink.weparty.commonui.widget.floatview.view.SwipeToTopConstraintLayout
+<androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
@@ -57,4 +57,4 @@
 
     </androidx.constraintlayout.widget.ConstraintLayout>
 
-</com.adealink.weparty.commonui.widget.floatview.view.SwipeToTopConstraintLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>

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

@@ -692,4 +692,6 @@
    <string name="rank_room">غرفة</string>
    <string name="commonui_mic_anim">رسوم متحركة للميكروفون</string>
     <string name="set_gif_avatar_tips">قم بتعيين صورة GIF كصورة رمزية لك.</string>
+    <string name="common_game">لعبة</string>
+
 </resources>

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

@@ -691,4 +691,5 @@
    <string name="rank_room">房间</string>
    <string name="commonui_mic_anim">麦克风动画</string>
    <string name="set_gif_avatar_tips">支持设置gif头像</string>
+    <string name="common_game">游戏</string>
 </resources>

+ 5 - 2
app/src/main/res/values/ids.xml

@@ -10,8 +10,8 @@
     <item name="effect_view_vap" type="id" />
 
     <item name="tagViewHolder" type="id" />
-    <item name="banner_data_key" type="id"/>
-    <item name="banner_pos_key" type="id"/>
+    <item name="banner_data_key" type="id" />
+    <item name="banner_pos_key" type="id" />
 
     <item name="edge_flash_effect_view_image" type="id" />
     <item name="edge_flash_effect_view_svga" type="id" />
@@ -21,4 +21,7 @@
     <item name="id_spread_linear_layout_group_2" type="id" />
 
     <item name="tag_view_need_remove" type="id" />
+
+    <item name="id_float_incoming_view" type="id" />
+    <item name="id_float_message_view" type="id" />
 </resources>

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

@@ -808,4 +808,5 @@
    <string name="commonui_mic_anim">Mic Animation</string>
    <string name="set_gif_avatar_tips">Set a GIF as your avatar.</string>
    <string name="common_mini_slot">Mini Slot</string>
+   <string name="common_game">Game</string>
 </resources>

+ 1 - 0
module/call/src/main/java/com/adealink/weparty/call/view/floatview/incoming/InComingFloatData.kt

@@ -3,6 +3,7 @@ package com.adealink.weparty.call.view.floatview.incoming
 import com.adealink.weparty.commonui.widget.floatview.data.FloatWindowType
 import com.adealink.weparty.commonui.widget.floatview.data.GRAVITY_TOP
 import com.adealink.weparty.commonui.widget.floatview.data.ILayoutFloatData
+import com.adealink.weparty.commonui.widget.slide.Slide
 
 class InComingFloatData(private val windowMode: Int) : ILayoutFloatData {
     override fun windowType(): FloatWindowType = FloatWindowType.CALL_1V1_INCOMING

+ 9 - 0
module/call/src/main/java/com/adealink/weparty/call/view/floatview/incoming/InComingFloatView.kt

@@ -11,6 +11,7 @@ import com.adealink.weparty.call.view.floatview.incoming.IncomingView.ICancelVie
 import com.adealink.weparty.commonui.widget.floatview.WindowManagerProxy
 import com.adealink.weparty.commonui.widget.floatview.data.FloatWindowType
 import com.adealink.weparty.commonui.widget.floatview.view.BaseSlideFloatView
+import com.adealink.weparty.commonui.widget.slide.Slide
 import com.tencent.cloud.tuikit.engine.call.TUICallDefine
 import com.tencent.qcloud.tuicore.TUICore
 import com.tencent.qcloud.tuicore.interfaces.ITUINotification
@@ -50,6 +51,10 @@ class InComingFloatView(floatData: InComingFloatData) :
             marginEnd = getCompatDimensionPixelSize(R.dimen.top_float_view_margin_horizontal)
         }
 
+    init {
+        id = R.id.id_float_incoming_view
+    }
+
     override fun onCreate() {
         super.onCreate()
         setContentView(incomingView, layoutParams)
@@ -91,6 +96,10 @@ class InComingFloatView(floatData: InComingFloatData) :
         removeObserver()
     }
 
+    override fun slideDirection(): Slide.SlideDirection {
+        return Slide.SlideDirection.HORIZONTAL
+    }
+
     override fun onSlideFinish(slideToEnd: Boolean) {
         if (slideToEnd) {
             callManager.ignore(null)

+ 16 - 2
module/game/src/main/java/com/adealink/weparty/game/GameServiceImpl.kt

@@ -33,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.MiniSlotConfigInfoResponse
 import com.adealink.weparty.module.game.data.MiniSlotNeedData
+import com.adealink.weparty.module.game.data.OutRoomGameItem
 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
@@ -44,6 +45,7 @@ import com.adealink.weparty.module.pk.PKModule
 import com.adealink.weparty.module.profile.ProfileModule
 import com.adealink.weparty.module.profile.decorate.data.DecorType
 import com.adealink.weparty.module.room.data.RedPacketInfo
+import com.adealink.weparty.url.LuckyGameSource
 
 /**
  * Created by sunxiaodong on 2021/7/1.
@@ -89,8 +91,12 @@ class GameServiceImpl : IGameService {
         return gameEntranceManager.getGameShowConfigs(configTypes)
     }
 
-    override fun navigateToGame(gameType: GameEntranceType, canceledOnTouchOutside: Boolean) {
-        return gameEntranceManager.navigateToGame(gameType, canceledOnTouchOutside)
+    override fun navigateToGame(
+        gameType: GameEntranceType,
+        canceledOnTouchOutside: Boolean,
+        source: LuckyGameSource
+    ) {
+        gameEntranceManager.navigateToGame(gameType, canceledOnTouchOutside, source)
     }
 
     override suspend fun getActivityGameRewardInfo(req: CommonActivityRewardInfoReq): GameActivityRewardInfo? {
@@ -180,7 +186,15 @@ class GameServiceImpl : IGameService {
         return RocketHeadlineFloatView(data as RocketHeadlineFloatData)
     }
 
+    override suspend fun getOutRoomGames(): List<OutRoomGameItem> {
+        return gameManager.getOutRoomGames()
+    }
+
     override fun getService(): IGameService {
         return this
     }
+
+    override fun getGamePlayedRecords(): List<GameEntranceType> {
+        return gameEntranceManager.getGamePlayedRecords()
+    }
 }

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

@@ -1,5 +0,0 @@
-package com.adealink.weparty.game.data
-
-/**
- * Created by sunxiaodong on 2021/7/1.
- */

+ 12 - 0
module/game/src/main/java/com/adealink/weparty/game/data/GamePlayRecord.kt

@@ -0,0 +1,12 @@
+package com.adealink.weparty.game.data
+
+import com.google.gson.annotations.SerializedName
+
+/**
+ * Created by XiaoDongLin.
+ * Date: 2025/8/1
+ */
+data class GamePlayRecord(
+    @SerializedName("gameType") val gameType: Int, //GameEntranceType,
+    @SerializedName("lastPlayedTime") val lastPlayedTime: Long // 时间戳(毫秒)
+)

+ 3 - 0
module/game/src/main/java/com/adealink/weparty/game/datasource/local/GameLocalService.kt

@@ -14,4 +14,7 @@ object GameLocalService : TypeDelegationPrefs(
     }
 ) {
     var gameShowConfigMap: String by PrefUserKey("key_game_show_config_map", "")
+
+    // 游戏记录,默认是一个空数组
+    var playGameRecords: String by PrefUserKey("key_play_game_records", "[]")
 }

+ 162 - 31
module/game/src/main/java/com/adealink/weparty/game/manager/GameEntranceManager.kt

@@ -5,6 +5,7 @@ import androidx.fragment.app.FragmentActivity
 import com.adealink.frame.base.CommonDataNullError
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.data.json.froJsonErrorNull
+import com.adealink.frame.data.json.gson
 import com.adealink.frame.data.json.toJsonErrorNull
 import com.adealink.frame.frame.BaseFrame
 import com.adealink.frame.frame.IListener
@@ -12,6 +13,7 @@ import com.adealink.frame.game.TAG_GAME
 import com.adealink.frame.network.data.Res
 import com.adealink.frame.util.AppUtil
 import com.adealink.frame.util.DisplayUtil
+import com.adealink.frame.util.ONE_DAY
 import com.adealink.frame.util.PackageUtil
 import com.adealink.weparty.App
 import com.adealink.weparty.channel.getChannel
@@ -19,6 +21,7 @@ import com.adealink.weparty.commonui.toast.util.showToast
 import com.adealink.weparty.config.GameVersionConfig
 import com.adealink.weparty.config.GlobalConfigType
 import com.adealink.weparty.config.globalConfigManager
+import com.adealink.weparty.game.data.GamePlayRecord
 import com.adealink.weparty.game.datasource.local.GameLocalService
 import com.adealink.weparty.game.datasource.remote.GameHttpService
 import com.adealink.weparty.module.game.data.GameAppVersionOldCanPlayGameError
@@ -29,8 +32,11 @@ import com.adealink.weparty.module.profile.decorate.data.DecorType
 import com.adealink.weparty.module.webview.WebViewDialogFragmentBuilder
 import com.adealink.weparty.module.webview.data.OfflineH5GameInfo
 import com.adealink.weparty.url.H5Page
+import com.adealink.weparty.url.LuckyGameSource
+import com.adealink.weparty.url.UrlConfig.appendSourceToUrl
 import com.adealink.weparty.url.urlConfigService
 import com.adealink.weparty.util.goLocalLinkPage
+import com.google.gson.reflect.TypeToken
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import java.util.concurrent.ConcurrentHashMap
@@ -69,8 +75,13 @@ class GameEntranceManager : BaseFrame<IListener>(), IGameEntranceManager {
         return getGameShowConfigs(allGameDecorTypeList, forceNet)
     }
 
-    override suspend fun getGameShowConfig(configType: DecorType, forceNet: Boolean): GameShowConfig? {
-        if (forceNet || System.currentTimeMillis() - (fetchTimeMap[configType] ?: 0) > GET_GAME_SHOW_CONFIG_TIME_INTERVAL) {
+    override suspend fun getGameShowConfig(
+        configType: DecorType,
+        forceNet: Boolean
+    ): GameShowConfig? {
+        if (forceNet || System.currentTimeMillis() - (fetchTimeMap[configType]
+                ?: 0) > GET_GAME_SHOW_CONFIG_TIME_INTERVAL
+        ) {
             val oneConfigList = listOf(configType)
             val rlt = getGameShowConfigsRemote(oneConfigList)
             if (rlt is Rlt.Success) {
@@ -80,12 +91,16 @@ class GameEntranceManager : BaseFrame<IListener>(), IGameEntranceManager {
         return gameShowConfigMap[configType]
     }
 
-    override suspend fun getGameShowConfigs(configTypes: List<DecorType>, forceNet: Boolean): Map<DecorType, GameShowConfig> {
+    override suspend fun getGameShowConfigs(
+        configTypes: List<DecorType>,
+        forceNet: Boolean
+    ): Map<DecorType, GameShowConfig> {
         val configsToFetch = if (forceNet) {
             configTypes
         } else {
             configTypes.filter {
-                System.currentTimeMillis() - (fetchTimeMap[it] ?: 0) > GET_GAME_SHOW_CONFIG_TIME_INTERVAL
+                System.currentTimeMillis() - (fetchTimeMap[it]
+                    ?: 0) > GET_GAME_SHOW_CONFIG_TIME_INTERVAL
             }
         }
 
@@ -125,23 +140,70 @@ class GameEntranceManager : BaseFrame<IListener>(), IGameEntranceManager {
         return rlt
     }
 
-    override fun navigateToGame(gameType: GameEntranceType, canceledOnTouchOutside: Boolean) {
+    override fun navigateToGame(
+        gameType: GameEntranceType,
+        canceledOnTouchOutside: Boolean,
+        source: LuckyGameSource
+    ) {
+        markGamePlayed(gameType, source) // 记录游戏记录
+
         when (gameType) {
             // h5游戏
-            GameEntranceType.LUCKY_FRUIT -> openLuckyFruit()
-            GameEntranceType.LUCKY_PRO -> openLuckyPro()
-            GameEntranceType.GREEDY_PRO -> openGreedyPro()
-            GameEntranceType.GREEDY_PERSONAL -> openGreedyPersonal(canceledOnTouchOutside)
-            GameEntranceType.JACKPOT -> openJackpot()
+            GameEntranceType.LUCKY_FRUIT -> openLuckyFruit(source)
+            GameEntranceType.LUCKY_PRO -> openLuckyPro(source)
+            GameEntranceType.GREEDY_PRO -> openGreedyPro(source)
+            GameEntranceType.GREEDY_PERSONAL -> openGreedyPersonal(canceledOnTouchOutside, source)
+            GameEntranceType.JACKPOT -> openJackpot(source)
 
             // cocos游戏
-            GameEntranceType.JACKPOT_SLOT -> openOfflineGame(GlobalConfigType.GLOBAL_JACKPOT_SLOT_VERSION_INFO, DecorType.JACKPOT_SLOT_GAME, 1340)
-            GameEntranceType.GREEDY_BOX -> openOfflineGame(GlobalConfigType.GLOBAL_GREEDYBOX_VERSION_INFO, DecorType.GREEDY_BOX_GAME, 761)
-            GameEntranceType.TEEN_PATTI -> openOfflineGame(GlobalConfigType.GLOBAL_TEENPATTI_VERSION_INFO, DecorType.TEEN_PATTI_GAME, 1170)
-            GameEntranceType.RUSSIAN_ROULETTE -> openOfflineGame(GlobalConfigType.GLOBAL_RUSSIAN_TURNTABLE_VERSION_INFO, DecorType.RUSSIAN_TURNTABLE_GAME, 1123)
-            GameEntranceType.TEXAS_COWBOY -> openOfflineGame(GlobalConfigType.GLOBAL_TEXAS_COWBOY_VERSION_INFO, DecorType.TEXAS_GAME, 1230)
-            GameEntranceType.DRAGON_TIGER_FIGHT -> openOfflineGame(GlobalConfigType.GLOBAL_DRAGON_TIGER_FIGHT_VERSION_INFO, DecorType.DRAGON_TIGER_FIGHT_GAME, 1070)
-            GameEntranceType.MINI_SLOT -> openOfflineGame(GlobalConfigType.GLOBAL_MINI_SLOT, DecorType.SHOW_MINI_SLOT, 1286)
+            GameEntranceType.JACKPOT_SLOT -> openOfflineGame(
+                GlobalConfigType.GLOBAL_JACKPOT_SLOT_VERSION_INFO,
+                DecorType.JACKPOT_SLOT_GAME,
+                1340,
+                source
+            )
+
+            GameEntranceType.GREEDY_BOX -> openOfflineGame(
+                GlobalConfigType.GLOBAL_GREEDYBOX_VERSION_INFO,
+                DecorType.GREEDY_BOX_GAME,
+                761,
+                source
+            )
+
+            GameEntranceType.TEEN_PATTI -> openOfflineGame(
+                GlobalConfigType.GLOBAL_TEENPATTI_VERSION_INFO,
+                DecorType.TEEN_PATTI_GAME,
+                1170,
+                source
+            )
+
+            GameEntranceType.RUSSIAN_ROULETTE -> openOfflineGame(
+                GlobalConfigType.GLOBAL_RUSSIAN_TURNTABLE_VERSION_INFO,
+                DecorType.RUSSIAN_TURNTABLE_GAME,
+                1123,
+                source
+            )
+
+            GameEntranceType.TEXAS_COWBOY -> openOfflineGame(
+                GlobalConfigType.GLOBAL_TEXAS_COWBOY_VERSION_INFO,
+                DecorType.TEXAS_GAME,
+                1230,
+                source
+            )
+
+            GameEntranceType.DRAGON_TIGER_FIGHT -> openOfflineGame(
+                GlobalConfigType.GLOBAL_DRAGON_TIGER_FIGHT_VERSION_INFO,
+                DecorType.DRAGON_TIGER_FIGHT_GAME,
+                1070,
+                source
+            )
+            GameEntranceType.MINI_SLOT -> openOfflineGame(
+                GlobalConfigType.GLOBAL_MINI_SLOT,
+                DecorType.SHOW_MINI_SLOT,
+                1286,
+                source
+            )
+
 
             else -> {
 
@@ -154,42 +216,64 @@ class GameEntranceManager : BaseFrame<IListener>(), IGameEntranceManager {
         gameShowConfigMap.clear()
     }
 
-    private fun openLuckyFruit() {
+    private fun openLuckyFruit(source: LuckyGameSource) {
         val activity = AppUtil.currentActivity ?: return
-        goLocalLinkPage(activity, urlConfigService.getH5Url(H5Page.LUCKY_FRUIT))
+        goLocalLinkPage(
+            activity,
+            urlConfigService.getH5Url(H5Page.LUCKY_FRUIT).appendSourceToUrl(source)
+        )
     }
 
-    private fun openLuckyPro() {
+    private fun openLuckyPro(source: LuckyGameSource) {
         val activity = AppUtil.currentActivity ?: return
-        goLocalLinkPage(activity, urlConfigService.getH5Url(H5Page.LUCKY_PRO))
+        goLocalLinkPage(
+            activity,
+            urlConfigService.getH5Url(H5Page.LUCKY_PRO).appendSourceToUrl(source)
+        )
     }
 
-    private fun openGreedyPro() {
+    private fun openGreedyPro(source: LuckyGameSource) {
         val activity = AppUtil.currentActivity ?: return
-        goLocalLinkPage(activity, urlConfigService.getH5Url(H5Page.GREEDY_PRO))
+        goLocalLinkPage(
+            activity,
+            urlConfigService.getH5Url(H5Page.GREEDY_PRO).appendSourceToUrl(source)
+        )
     }
 
-    private fun openGreedyPersonal(canceledOnTouchOutside: Boolean = true) {
+    private fun openGreedyPersonal(
+        canceledOnTouchOutside: Boolean = true,
+        source: LuckyGameSource
+    ) {
         val activity = AppUtil.currentActivity as? FragmentActivity ?: return
         WebViewDialogFragmentBuilder()
             .height(1205 * DisplayUtil.getScreenWidth() / 750)
-            .canceledOnTouchOutside(canceledOnTouchOutside)
+            .canceledOnTouchOutside(source== LuckyGameSource.Room || source == LuckyGameSource.Room_Banner)
+            .transparent(trans = source== LuckyGameSource.Room || source == LuckyGameSource.Room_Banner)
             .build()
-            ?.showUrl(activity.supportFragmentManager, urlConfigService.getH5Url(H5Page.GREEDY_PERSONAL))
+            ?.showUrl(
+                activity.supportFragmentManager,
+                urlConfigService.getH5Url(H5Page.GREEDY_PERSONAL).appendSourceToUrl(source)
+            )
     }
 
-    private fun openJackpot() {
+    private fun openJackpot(source: LuckyGameSource) {
         val activity = AppUtil.currentActivity as? FragmentActivity ?: return
         WebViewDialogFragmentBuilder()
             .height(1300 * DisplayUtil.getScreenWidth() / 750)
+            .canceledOnTouchOutside(source== LuckyGameSource.Room || source == LuckyGameSource.Room_Banner)
+            .transparent(trans = source== LuckyGameSource.Room || source == LuckyGameSource.Room_Banner)
             .build()
-            ?.showUrl(activity.supportFragmentManager, urlConfigService.getH5Url(H5Page.SLOT))
+            ?.showUrl(
+                activity.supportFragmentManager,
+                urlConfigService.getH5Url(H5Page.SLOT).appendSourceToUrl(source)
+            )
     }
 
     private fun openOfflineGame(
         configType: GlobalConfigType,
         decorType: DecorType,
         heightInDesign: Int,
+        source: LuckyGameSource
     ) {
         launch {
             val (isValid, newVersion) = checkGameValid(configType)
@@ -198,7 +282,7 @@ class GameEntranceManager : BaseFrame<IListener>(), IGameEntranceManager {
             }
             val resourceUrl = newVersion?.resourceUrl
             val needDownload = newVersion?.needDownload ?: true
-            showGameDialog(resourceUrl, needDownload, decorType, heightInDesign)
+            showGameDialog(resourceUrl, needDownload, decorType, heightInDesign, source = source)
         }
     }
 
@@ -208,6 +292,7 @@ class GameEntranceManager : BaseFrame<IListener>(), IGameEntranceManager {
         decorType: DecorType,
         heightInDesign: Int,
         loadingUrl: String? = "",
+        source: LuckyGameSource = LuckyGameSource.Room
     ) {
         val activity = AppUtil.currentActivity as? FragmentActivity ?: return
         val heightInPixel = heightInDesign * DisplayUtil.getScreenWidth() / 750
@@ -215,13 +300,25 @@ class GameEntranceManager : BaseFrame<IListener>(), IGameEntranceManager {
         WebViewDialogFragmentBuilder()
             .height(heightInPixel)
             .loadingUrl(loadingUrl)
-            .offlineGameInfo(OfflineH5GameInfo(offlineUrl, decorType.value, needDownload))
+            .offlineGameInfo(
+                OfflineH5GameInfo(
+                    offlineUrl,
+                    decorType.value,
+                    needDownload,
+                    source = source.source
+                )
+            )
+            .transparent(trans = source== LuckyGameSource.Room || source == LuckyGameSource.Room_Banner)
+            .canceledOnTouchOutside(source== LuckyGameSource.Room || source == LuckyGameSource.Room_Banner)
             .build()
             ?.showUrl(activity.supportFragmentManager, "")
     }
 
     private suspend fun checkGameValid(type: GlobalConfigType): Pair<Boolean, GameVersionConfig?> {
-        val newVersion = froJsonErrorNull(globalConfigManager.suspendGetConfig(type)?.firstOrNull(), GameVersionConfig::class.java)
+        val newVersion = froJsonErrorNull(
+            globalConfigManager.suspendGetConfig(type)?.firstOrNull(),
+            GameVersionConfig::class.java
+        )
         return when (val rlt = globalConfigManager.checkGameValid(type)) {
             is Rlt.Success -> {
                 Pair(true, newVersion)
@@ -247,4 +344,38 @@ class GameEntranceManager : BaseFrame<IListener>(), IGameEntranceManager {
             }
         }
     }
+
+    private val MAX_GAME_COUNT = 20 //最多只保留的游戏记录数量,避免记录过多
+    override fun markGamePlayed(gameType: GameEntranceType, source: LuckyGameSource) {
+        val json = GameLocalService.playGameRecords
+        val type = object : TypeToken<MutableList<GamePlayRecord>>() {}.type
+        val list = gson.fromJson<MutableList<GamePlayRecord>>(json, type).toMutableList()
+
+        // 移除同一个游戏
+        list.removeAll { it.gameType == gameType.type }
+
+        // 添加新记录
+        list.add(GamePlayRecord(gameType.type, System.currentTimeMillis()))
+
+        // 过滤出最近7天
+        val threshold = System.currentTimeMillis() - ONE_DAY * 7
+        val filtered = list.filter { it.lastPlayedTime >= threshold }
+            .sortedByDescending { it.lastPlayedTime }
+            .take(MAX_GAME_COUNT)
+
+        //保存
+        GameLocalService.playGameRecords = gson.toJson(filtered)
+    }
+
+    override fun getGamePlayedRecords(): List<GameEntranceType> {
+        val json = GameLocalService.playGameRecords
+        val type = object : TypeToken<MutableList<GamePlayRecord>>() {}.type
+        val list = gson.fromJson<MutableList<GamePlayRecord>>(json, type).toMutableList()
+
+        // 过滤出最近7天
+        val threshold = System.currentTimeMillis() - ONE_DAY * 7
+        return list.filter { it.lastPlayedTime >= threshold }
+            .sortedByDescending { it.lastPlayedTime }
+            .mapNotNull { GameEntranceType.map(it.gameType) }
+    }
 }

+ 28 - 4
module/game/src/main/java/com/adealink/weparty/game/manager/GameManager.kt

@@ -30,6 +30,10 @@ import com.adealink.weparty.module.gamehub.uno.data.UnoGameFrom
 import com.adealink.weparty.module.room.Room
 import com.adealink.weparty.room.data.EnterRoomInfo
 import com.adealink.weparty.room.data.JoinRoomFrom
+import com.adealink.weparty.module.game.data.GameEntranceType
+import com.adealink.weparty.module.game.data.OutRoomGameItem
+import com.adealink.weparty.module.profile.decorate.data.DecorType
+import com.adealink.weparty.module.game.GameModule
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.launch
@@ -39,7 +43,6 @@ val gameManager: IGameManager by lazy { GameManager() }
 
 class GameManager : IGameManager {
 
-
     private val gameHttpService by fastLazy {
         App.instance.networkService.getHttpService(CommonGameHttpService::class.java)
     }
@@ -49,7 +52,6 @@ class GameManager : IGameManager {
      */
     private val gameBetCoinsConfig = mutableMapOf<Game, Map<Int, Map<Int, List<Int>>>>()
 
-
     override suspend fun getBetCoinConfig(game: Game, gameType: Int?): Map<Int, Map<Int, List<Int>>> {
         return if (gameType != null) {
             getBetCoinsConfigBy(game, gameType)
@@ -109,7 +111,6 @@ class GameManager : IGameManager {
         }
     }
 
-
     private var playingGameTipShowing = false
 
     override fun checkPlayingGame() {
@@ -172,7 +173,6 @@ class GameManager : IGameManager {
         }
     }
 
-
     private fun doReturnGame() {
         CoroutineScope(Dispatcher.UI).launch {
             //再获取一次
@@ -223,4 +223,28 @@ class GameManager : IGameManager {
         }
     }
 
+    override suspend fun getOutRoomGames(): List<OutRoomGameItem> {
+        val decorToEntranceMap = mapOf(
+            DecorType.GREEDY_PERSONAL_GAME to GameEntranceType.GREEDY_PERSONAL,
+            DecorType.DRAGON_TIGER_FIGHT_GAME to GameEntranceType.DRAGON_TIGER_FIGHT,
+            DecorType.LUCKY_PRO_GAME to GameEntranceType.LUCKY_PRO,
+            DecorType.JACKPOT_GAME to GameEntranceType.JACKPOT,
+            DecorType.JACKPOT_SLOT_GAME to GameEntranceType.JACKPOT_SLOT,
+            DecorType.LUCKY_FRUIT_GAME to GameEntranceType.LUCKY_FRUIT,
+            DecorType.GREEDY_BOX_GAME to GameEntranceType.GREEDY_BOX,
+            DecorType.GREEDY_PRO_GAME to GameEntranceType.GREEDY_PRO,
+            DecorType.RUSSIAN_TURNTABLE_GAME to GameEntranceType.RUSSIAN_ROULETTE,
+            DecorType.TEEN_PATTI_GAME to GameEntranceType.TEEN_PATTI,
+            DecorType.TEXAS_GAME to GameEntranceType.TEXAS_COWBOY
+        )
+        val gameConfigMap = gameEntranceManager.getGameShowConfigs(decorToEntranceMap.keys.toList(), true)
+        return decorToEntranceMap.mapNotNull { (decorType, entranceType) ->
+            if (gameConfigMap[decorType]?.show == true) {
+                OutRoomGameItem(entranceType)
+            } else {
+                null
+            }
+        }
+    }
+
 }

+ 25 - 2
module/game/src/main/java/com/adealink/weparty/game/manager/IGameEntranceManager.kt

@@ -3,11 +3,34 @@ package com.adealink.weparty.game.manager
 import com.adealink.weparty.module.game.data.GameEntranceType
 import com.adealink.weparty.module.game.data.GameShowConfig
 import com.adealink.weparty.module.profile.decorate.data.DecorType
+import com.adealink.weparty.url.LuckyGameSource
 
 interface IGameEntranceManager {
     suspend fun getGameShowConfig(configType: DecorType, forceNet: Boolean = false): GameShowConfig?
-    suspend fun getGameShowConfigs(configTypes: List<DecorType>, forceNet: Boolean = false): Map<DecorType, GameShowConfig>
+    suspend fun getGameShowConfigs(
+        configTypes: List<DecorType>,
+        forceNet: Boolean = false
+    ): Map<DecorType, GameShowConfig>
+
     suspend fun getAllGameShowConfigs(forceNet: Boolean = false): Map<DecorType, GameShowConfig>
-    fun navigateToGame(gameType: GameEntranceType, canceledOnTouchOutside: Boolean = true)
+    fun navigateToGame(
+        gameType: GameEntranceType,
+        canceledOnTouchOutside: Boolean = true,
+        source: LuckyGameSource = LuckyGameSource.Room
+    )
+
+    /**
+     * 标记游戏记录
+     */
+    fun markGamePlayed(
+        gameType: GameEntranceType,
+        source: LuckyGameSource = LuckyGameSource.Room
+    )
+
+    /**
+     * 获取游戏记录
+     */
+    fun getGamePlayedRecords(): List<GameEntranceType>
+
     fun onLogout()
 }

+ 6 - 0
module/game/src/main/java/com/adealink/weparty/game/manager/IGameManager.kt

@@ -1,6 +1,7 @@
 package com.adealink.weparty.game.manager
 
 import com.adealink.weparty.cocosgame.data.Game
+import com.adealink.weparty.module.game.data.OutRoomGameItem
 
 
 interface IGameManager {
@@ -16,4 +17,9 @@ interface IGameManager {
      */
     suspend fun getBetCoinConfig(game: Game, gameType: Int? = null): Map<Int, Map<Int, List<Int>>>
 
+
+    /**
+     * 获取房间外游戏列表
+     */
+    suspend fun getOutRoomGames(): List<OutRoomGameItem>
 }

+ 10 - 0
module/game/src/main/java/com/adealink/weparty/game/viewmodel/GameViewModel.kt

@@ -13,6 +13,7 @@ import com.adealink.weparty.App
 import com.adealink.weparty.game.datasource.remote.GameHttpService
 import com.adealink.weparty.game.manager.gameEntranceManager
 import com.adealink.weparty.game.miccharmpk.manager.micCharmPKManager
+import com.adealink.weparty.module.game.data.GameEntranceType
 import com.adealink.weparty.module.game.data.GamePlayConflictError
 import com.adealink.weparty.module.game.data.GameShowConfig
 import com.adealink.weparty.module.game.data.GameType
@@ -100,4 +101,13 @@ class GameViewModel : BaseViewModel(), IGameViewModel {
         }
     }
 
+    override suspend fun getGamePlayedRecords(): List<GameEntranceType> {
+        return try {
+            gameEntranceManager.getGamePlayedRecords()
+        } catch (e: Exception) {
+            Log.e("GameViewModel", "Error fetching game played records", e)
+            emptyList()
+        }
+    }
+
 }

+ 8 - 8
module/headline/src/main/java/com/adealink/weparty/headline/HeadlineServiceImpl.kt

@@ -5,18 +5,17 @@ import androidx.lifecycle.ViewModelStoreOwner
 import com.adealink.frame.spi.RegisterService
 import com.adealink.weparty.commonui.widget.floatview.data.IFloatData
 import com.adealink.weparty.commonui.widget.floatview.view.BaseFloatView
-import com.adealink.weparty.headline.floatview.GreedyPersonalGlobalHeadlineFloatData
-import com.adealink.weparty.headline.floatview.GreedyPersonalGlobalHeadlineFloatView
-import com.adealink.weparty.module.headline.listener.IHeadlineListener
+import com.adealink.weparty.headline.floatview.HeadlineFloatData
+import com.adealink.weparty.headline.floatview.HeadlineFloatView
 import com.adealink.weparty.headline.manager.headlineManager
 import com.adealink.weparty.headline.viewmodel.HeadlineViewModel
 import com.adealink.weparty.headline.viewmodel.HeadlineViewModelFactory
-import com.adealink.weparty.module.game.GameModule
-import com.adealink.weparty.module.headline.viewmodel.IHeadlineViewModel
 import com.adealink.weparty.module.headline.IHeadlineService
+import com.adealink.weparty.module.headline.listener.IHeadlineListener
+import com.adealink.weparty.module.headline.viewmodel.IHeadlineViewModel
 
 @RegisterService(IHeadlineService::class)
-class HeadlineServiceImpl: IHeadlineService {
+class HeadlineServiceImpl : IHeadlineService {
     override fun init() {
         headlineManager.init()
     }
@@ -26,8 +25,9 @@ class HeadlineServiceImpl: IHeadlineService {
     }
 
 
-    override fun getGreedyPersonalGlobalHeadlineFloatView(data: IFloatData): BaseFloatView<out IFloatData>? {
-        return GreedyPersonalGlobalHeadlineFloatView(data as GreedyPersonalGlobalHeadlineFloatData)
+    override fun getGlobalHeadlineFloatView(data: IFloatData): BaseFloatView<out IFloatData>? {
+        val floatData = (data as? HeadlineFloatData) ?: return null
+        return HeadlineFloatView(floatData)
     }
 
     override fun addListener(l: IHeadlineListener) {

+ 0 - 13
module/headline/src/main/java/com/adealink/weparty/headline/floatview/GreedyPersonalGlobalHeadlineFloatData.kt

@@ -1,13 +0,0 @@
-package com.adealink.weparty.headline.floatview
-
-import com.adealink.weparty.commonui.widget.floatview.data.FloatWindowType
-import com.adealink.weparty.commonui.widget.floatview.data.IWindowFloatData
-import com.adealink.weparty.commonui.widget.floatview.data.MODE_APPLICATION
-import com.adealink.weparty.module.room.data.GlobalRoomBroadcastNotify
-import com.adealink.weparty.module.room.data.LotteryRewardNotify
-
-class GreedyPersonalGlobalHeadlineFloatData(val notify: LotteryRewardNotify) : IWindowFloatData {
-    override fun windowType(): FloatWindowType = FloatWindowType.GREEDY_PERSONAL_GLOBAL_HEADLINE
-
-    override fun windowMode() = MODE_APPLICATION
-}

+ 0 - 84
module/headline/src/main/java/com/adealink/weparty/headline/floatview/GreedyPersonalGlobalHeadlineFloatView.kt

@@ -1,84 +0,0 @@
-package com.adealink.weparty.headline.floatview
-
-import android.annotation.SuppressLint
-import android.graphics.PixelFormat
-import android.os.Bundle
-import android.view.Gravity
-import android.view.View
-import android.view.WindowManager
-import com.adealink.frame.base.fastLazy
-import com.adealink.frame.router.Router
-import com.adealink.frame.util.DisplayUtil
-import com.adealink.weparty.commonui.ext.dp
-import com.adealink.weparty.commonui.widget.floatview.view.BaseWindowFloatView
-import com.adealink.weparty.headline.view.GreedyPersonalGlobalHeadline
-import com.adealink.weparty.module.account.Account
-import com.adealink.weparty.module.headline.listener.IHeadlineOpListener
-import com.adealink.weparty.module.room.Room
-import com.adealink.weparty.module.room.data.RoomNotifyType
-import com.adealink.weparty.module.webview.Web
-
-@SuppressLint("ViewConstructor")
-class GreedyPersonalGlobalHeadlineFloatView(private val data: GreedyPersonalGlobalHeadlineFloatData) : BaseWindowFloatView(data) {
-
-    private val show: Boolean
-        get() {
-            return windowManager?.currentActivity?.get()
-                ?.let { !DISMISS_ACTIVITIES.contains(it.javaClass.name) }
-                ?: false
-        }
-
-    private val greedyPersonalGlobalHeadline by fastLazy {
-        GreedyPersonalGlobalHeadline(context).apply {
-            headlineOpListener = object : IHeadlineOpListener {
-                override fun onHeadlineRightBtnClick(type: RoomNotifyType?, params: Bundle?) {
-                    removeSelf("")
-                }
-            }
-        }
-    }
-
-    private var screenWidth = DisplayUtil.getScreenWidth()
-
-    override fun onCreate() {
-        super.onCreate()
-        val transX = when (DisplayUtil.isRtlLayout()) {
-            true -> -screenWidth.toFloat()
-            else -> screenWidth.toFloat()
-        }
-        setContentView(greedyPersonalGlobalHeadline)
-        visibility = if (show) View.VISIBLE else View.GONE
-        translationX = transX
-        greedyPersonalGlobalHeadline.updateUI(data.notify)
-        animate().translationX(0f).setDuration(300).withEndAction {
-            animate().setDuration(5000).withEndAction {
-                animate().translationX(-transX).setDuration(300).withEndAction {
-                    removeSelf("")
-                }.start()
-            }.start()
-        }.start()
-    }
-
-    override val windowParams = WindowManager.LayoutParams().apply {
-        width = getLayoutParamWidth()
-        height = getLayoutParamHeight()
-        format = PixelFormat.TRANSLUCENT
-        flags =
-            WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-        gravity = Gravity.START or Gravity.TOP
-        x = 0
-        y = 32.dp()
-    }
-
-    private fun getLayoutParamWidth(): Int = screenWidth
-
-    private fun getLayoutParamHeight(): Int = 98.dp()
-
-    companion object {
-        private val DISMISS_ACTIVITIES = hashSetOf<String>().apply {
-            Router.getClazz(Room.Room.PATH)?.let { this.add(it.name) }
-            Router.getClazz(Account.Login.PATH)?.let { this.add(it.name) }
-            Router.getClazz(Web.BSFullScreen.PATH)?.let { this.add(it.name) }
-        }
-    }
-}

+ 34 - 0
module/headline/src/main/java/com/adealink/weparty/headline/floatview/HeadlineFloatData.kt

@@ -0,0 +1,34 @@
+package com.adealink.weparty.headline.floatview
+
+import com.adealink.frame.effect.data.IEffectEntity
+import com.adealink.weparty.commonui.widget.floatview.data.FloatWindowType
+import com.adealink.weparty.commonui.widget.floatview.data.GRAVITY_TOP
+import com.adealink.weparty.commonui.widget.floatview.data.ILayoutFloatData
+import com.adealink.weparty.commonui.widget.floatview.data.MODE_APPLICATION
+import com.adealink.weparty.commonui.widget.slide.Slide
+import com.adealink.weparty.module.room.data.RoomNotifyType
+
+/**
+ * Created by XiaoDongLin.
+ * Date: 2025/7/31
+ */
+class HeadlineFloatData(
+    val type: RoomNotifyType,
+) : ILayoutFloatData {
+
+    var entity: IEffectEntity<*>? = null
+
+    override fun windowType(): FloatWindowType = FloatWindowType.GLOBAL_HEADLINE
+
+    override fun windowMode() = MODE_APPLICATION
+
+    override fun gravity(): Int = GRAVITY_TOP
+
+    override fun showOnlyOne(): Boolean {
+        return false
+    }
+
+    override fun windowTag(): String {
+        return type.name + entity?.hashCode()
+    }
+}

+ 288 - 0
module/headline/src/main/java/com/adealink/weparty/headline/floatview/HeadlineFloatView.kt

@@ -0,0 +1,288 @@
+package com.adealink.weparty.headline.floatview
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import com.adealink.frame.aab.util.getCompatDimensionPixelSize
+import com.adealink.frame.base.fastLazy
+import com.adealink.frame.effect.listener.IPlayListener
+import com.adealink.frame.locale.language.languageManager
+import com.adealink.frame.router.Router
+import com.adealink.frame.util.AppUtil
+import com.adealink.weparty.commonui.ext.dp
+import com.adealink.weparty.commonui.toast.util.showToast
+import com.adealink.weparty.commonui.widget.floatview.WindowManagerProxy
+import com.adealink.weparty.commonui.widget.floatview.data.FloatWindowType
+import com.adealink.weparty.commonui.widget.floatview.view.BaseSlideFloatView
+import com.adealink.weparty.commonui.widget.slide.Slide
+import com.adealink.weparty.headline.R
+import com.adealink.weparty.headline.databinding.FragmentHealineBinding
+import com.adealink.weparty.headline.effect.data.GlobalHeadlineEntity
+import com.adealink.weparty.module.game.GameModule
+import com.adealink.weparty.module.game.data.GameEntranceType
+import com.adealink.weparty.module.game.rocket.RocketUpgradeNotify
+import com.adealink.weparty.module.game.rocket.effect.IHeadlineBtnClickListener
+import com.adealink.weparty.module.game.rocket.effect.RocketHeadlineEffectEntity
+import com.adealink.weparty.module.gift.data.LuckyGiftLotteryNotify
+import com.adealink.weparty.module.gift.effect.luckygift.LuckyGiftRewardEffectEntity
+import com.adealink.weparty.module.headline.Headline
+import com.adealink.weparty.module.headline.listener.IHeadlineOpListener
+import com.adealink.weparty.module.room.Room
+import com.adealink.weparty.module.room.data.GlobalRoomBroadcastNotify
+import com.adealink.weparty.module.room.data.RoomNotifyType
+import com.adealink.weparty.module.room.wedding.IHeadlineClickListener
+import com.adealink.weparty.module.webview.Web
+import com.adealink.weparty.room.data.EnterRoomInfo
+import com.adealink.weparty.url.H5Page
+import com.adealink.weparty.url.urlConfigService
+import com.adealink.weparty.util.goLocalLinkPage
+import com.adealink.weparty.R as APP_R
+
+/**
+ * 全局单例,用于横幅展示
+ * Created by XiaoDongLin.
+ * Date: 2025/7/31
+ */
+
+class HeadlineFloatView(floatData: HeadlineFloatData) : BaseSlideFloatView(floatData),
+    IHeadlineOpListener,
+    IPlayListener,
+    IHeadlineBtnClickListener,
+    IHeadlineClickListener {
+
+    companion object {
+
+        @JvmOverloads
+        fun addInRoomHeadline(notify: GlobalRoomBroadcastNotify) {
+            val notifyType = RoomNotifyType.map(notify.broadcastUri) ?: return
+            val floatData = HeadlineFloatData(notifyType)
+            val floatView = HeadlineFloatView(floatData)
+            val entity = GlobalHeadlineEntity(
+                headlineNotify = notify,
+                path = notify.broadcastUri,
+                playListener = floatView,
+                headlineOpListener = floatView
+            )
+            floatData.entity = entity
+            WindowManagerProxy.getWindowManager().addView(floatView)
+        }
+
+        @JvmOverloads
+        fun addLuckyGiftLotteryNotify(notify: LuckyGiftLotteryNotify) {
+            val floatData = HeadlineFloatData(RoomNotifyType.LUCKY_GIFT)
+            val floatView = HeadlineFloatView(floatData)
+            val entity = LuckyGiftRewardEffectEntity("", notify)
+            floatData.entity = entity
+            WindowManagerProxy.getWindowManager().addView(floatView)
+        }
+
+        @JvmOverloads
+        fun addRocketHeadlineEffectEntity(notify: RocketUpgradeNotify) {
+            val floatData = HeadlineFloatData(RoomNotifyType.ROCKET)
+            val floatView = HeadlineFloatView(floatData)
+            val entity = RocketHeadlineEffectEntity(
+                path = "",
+                levelUpgradeNotify = notify,
+                playListener = floatView,
+                headlineOpListener = floatView
+            )
+            floatData.entity = entity
+            WindowManagerProxy.getWindowManager().addView(floatView)
+        }
+
+        @JvmOverloads
+        fun addOutRoomHeadline(notify: GlobalRoomBroadcastNotify) {
+            val notifyType = RoomNotifyType.map(notify.broadcastUri) ?: return
+            val floatData = HeadlineFloatData(notifyType)
+            val floatView = HeadlineFloatView(floatData)
+            val entity = GlobalHeadlineEntity(
+                headlineNotify = notify,
+                path = notify.broadcastUri,
+                headlineOpListener = floatView,
+                playListener = floatView
+            )
+            floatData.entity = entity
+            WindowManagerProxy.getWindowManager().addView(floatView)
+        }
+
+        @JvmOverloads
+        fun clearWhenExitRoom(roomId: Long) {
+            WindowManagerProxy.getWindowManager().removeAllFloatViews(
+                type = FloatWindowType.GLOBAL_HEADLINE,
+                reason = "clearWhenExitRoom"
+            )
+        }
+    }
+
+    override fun slideDirection(): Slide.SlideDirection {
+        return Slide.SlideDirection.UP
+    }
+
+    override var autoDismiss: Boolean = false
+
+    override val layoutParams: LayoutParams
+        get() = LayoutParams(LayoutParams.MATCH_PARENT, 98.dp()).apply {
+            topToTop = LayoutParams.PARENT_ID
+            startToStart = LayoutParams.PARENT_ID
+            endToEnd = LayoutParams.PARENT_ID
+
+            marginStart = getCompatDimensionPixelSize(APP_R.dimen.top_float_view_margin_horizontal)
+            marginEnd = getCompatDimensionPixelSize(APP_R.dimen.top_float_view_margin_horizontal)
+        }
+
+    private val localeContext: Context
+
+    init {
+        val locale = languageManager?.getLanguage()?.locale
+        val config = context.resources.configuration
+        config.setLocale(locale)
+        localeContext = context.createConfigurationContext(config)
+    }
+
+    private val binding by fastLazy {
+        FragmentHealineBinding.inflate(LayoutInflater.from(localeContext))
+    }
+
+    override fun onCreate() {
+        super.onCreate()
+        setContentView(binding.root)
+        (baseFloatData as? HeadlineFloatData)?.entity?.let {
+            binding.headlineEffectView.play()
+            binding.headlineEffectView.add(it)
+        } ?: removeSelf("")
+    }
+
+    override fun onResume() {
+        super.onResume()
+        if (!binding.headlineEffectView.isEffectPlaying()) {
+            removeSelf("headlineEffectView is stop")
+        }
+    }
+
+    override fun onPause() {
+        super.onPause()
+        binding.headlineEffectView.stop()
+    }
+
+    override fun onHeadlineRightBtnClick(type: RoomNotifyType?, params: Bundle?) {
+        when (type) {
+            RoomNotifyType.SendRedPacket, RoomNotifyType.SendGift -> {
+                params?.getLong(Headline.Common.EXTRA_ROOM_ID)?.let {
+                    enterRoomClick(it, params.getString(Headline.Common.EXTRA_ENTER_ROOM_FROM, ""))
+                }
+            }
+
+            RoomNotifyType.SuperGift -> {
+                goSuperGiftWeb()
+            }
+
+            RoomNotifyType.COMMON_GLOBAL_ROOM_BROADCAST_NOTIFY -> {
+                params?.getString(Headline.Common.EXTRA_DEEPLINK)?.let {
+                    val activity = AppUtil.currentActivity ?: return
+                    goLocalLinkPage(activity, it)
+                }
+            }
+
+            RoomNotifyType.LuckyFruit -> {
+                GameModule.navigateToGame(GameEntranceType.LUCKY_FRUIT)
+            }
+
+            RoomNotifyType.Slot -> {
+                GameModule.navigateToGame(GameEntranceType.JACKPOT)
+            }
+
+            RoomNotifyType.SlotPro -> {
+                GameModule.navigateToGame(GameEntranceType.JACKPOT_SLOT)
+            }
+
+            RoomNotifyType.GREEDY_BOX_NOTIFY -> {
+                GameModule.navigateToGame(GameEntranceType.GREEDY_BOX)
+            }
+
+            RoomNotifyType.TEEN_PATTI_REWARD_NOTIFY -> {
+                GameModule.navigateToGame(GameEntranceType.TEEN_PATTI)
+            }
+
+            RoomNotifyType.GREEDY_PRO_NOTIFY -> {
+                GameModule.navigateToGame(GameEntranceType.GREEDY_PRO)
+            }
+
+            RoomNotifyType.NEW_GREEDY_PRO_REWARD_NOTIFY -> {
+                GameModule.navigateToGame(GameEntranceType.LUCKY_PRO)
+            }
+
+            RoomNotifyType.DRAGON_TIGER_FIGHT_REWARD_NOTIFY -> {
+                GameModule.navigateToGame(GameEntranceType.DRAGON_TIGER_FIGHT)
+            }
+
+            RoomNotifyType.RUSSIA_ROULETTE_REWARD_NOTIFY -> {
+                GameModule.navigateToGame(GameEntranceType.RUSSIAN_ROULETTE)
+            }
+
+            RoomNotifyType.TEXAS_COWBOY_REWARD_NOTIFY -> {
+                GameModule.navigateToGame(GameEntranceType.TEXAS_COWBOY)
+            }
+
+            RoomNotifyType.ADMIN_LOTTERY_ACTIVITY_REWARD_NOTIFY -> {
+                val activity = AppUtil.currentActivity ?: return
+                val webUrl = params?.getString(Headline.Common.EXTRA_WEB_URL) ?: return
+                Router.build(activity, Web.FullScreen.PATH)
+                    .putExtra(Web.Common.EXTRA_URL, webUrl)
+                    .start()
+            }
+
+            RoomNotifyType.GREEDY_PERSONAL_REWARD_NOTIFY -> {
+                GameModule.navigateToGame(GameEntranceType.GREEDY_PERSONAL)
+            }
+
+            else -> {
+                //ntd.
+            }
+        }
+        removeSelf("onHeadlineRightBtnClick")
+    }
+
+    override fun onComplete() {
+        removeSelf("Headline Effect complete")
+    }
+
+    override fun onError(errCode: Int) {
+        removeSelf("Headline Effect play Error")
+    }
+
+
+    /**
+     * 进房间
+     */
+    private fun enterRoomClick(roomId: Long, from: String) {
+        val activity = AppUtil.currentActivity ?: return
+        Router.build(activity, Room.Room.PATH)
+            .putExtra(
+                Room.Room.EXTRA_ENTER_ROOM_INFO,
+                EnterRoomInfo(roomId, from)
+            )
+            .start()
+    }
+
+    private fun goSuperGiftWeb() {
+        if (!GameModule.isShowSuperGift()) {
+            showToast(R.string.headline_super_gift_level_limit)
+            return
+        }
+
+        val activity = AppUtil.currentActivity ?: return
+        Router.build(activity, Web.FullScreen.PATH)
+            .putExtra(Web.Common.EXTRA_URL, urlConfigService.getH5Url(H5Page.SUPER_GIFT))
+            .start()
+    }
+
+    override fun onHeadlineBtnClick(type: RoomNotifyType?, params: Bundle?) {
+        removeSelf("onHeadlineBtnClick $type")
+    }
+
+    override fun onHeadlineClick(type: RoomNotifyType?, params: Bundle?) {
+        removeSelf("onHeadlineClick $type")
+    }
+
+
+}

+ 10 - 393
module/headline/src/main/java/com/adealink/weparty/headline/fragment/HeadlineFragment.kt

@@ -2,130 +2,50 @@ package com.adealink.weparty.headline.fragment
 
 import android.os.Bundle
 import androidx.fragment.app.viewModels
-import androidx.lifecycle.lifecycleScope
 import com.adealink.frame.aab.util.getCompatString
-import com.adealink.frame.base.Rlt
 import com.adealink.frame.base.fastLazy
-import com.adealink.frame.coroutine.dispatcher.Dispatcher
-import com.adealink.frame.data.json.froJsonErrorNull
-import com.adealink.frame.effect.data.IEffectEntity
-import com.adealink.frame.effect.listener.IPlayListener
-import com.adealink.frame.effect.view.EffectView
-import com.adealink.frame.effect.view.IEffectView
-import com.adealink.frame.ext.isViewBindingValid
-import com.adealink.frame.log.Log
 import com.adealink.frame.mvvm.view.viewBinding
 import com.adealink.frame.room.data.FlowStateInfo
 import com.adealink.frame.router.Router
 import com.adealink.frame.router.annotation.RouterUri
-import com.adealink.frame.util.runOnUiThread
 import com.adealink.weparty.commonui.BaseFragment
 import com.adealink.weparty.commonui.dialogfragment.BaseDialogFragment
-import com.adealink.weparty.commonui.ext.show
 import com.adealink.weparty.commonui.toast.util.showToast
-import com.adealink.weparty.commonui.widget.floatview.view.HideMode
-import com.adealink.weparty.commonui.widget.floatview.view.ITouchDispatchEventListener
-import com.adealink.weparty.commonui.widget.floatview.view.SwipeToTopConstraintLayout
 import com.adealink.weparty.headline.R
-import com.adealink.weparty.headline.data.TAG_HEADLINE
 import com.adealink.weparty.headline.databinding.FragmentHealineBinding
-import com.adealink.weparty.headline.effect.data.GiftTreasureEffectEntity
-import com.adealink.weparty.headline.effect.data.GlobalHeadlineEntity
+import com.adealink.weparty.headline.manager.headlineManager
 import com.adealink.weparty.headline.viewmodel.HeadlineViewModel
 import com.adealink.weparty.headline.viewmodel.HeadlineViewModelFactory
 import com.adealink.weparty.module.game.GameModule
-import com.adealink.weparty.module.game.data.GameEntranceType
-import com.adealink.weparty.module.game.rocket.effect.IHeadlineBtnClickListener
-import com.adealink.weparty.module.game.rocket.effect.RocketHeadlineEffectEntity
 import com.adealink.weparty.module.gift.Gift
 import com.adealink.weparty.module.gift.GiftModule
 import com.adealink.weparty.module.gift.data.LuckyGiftLotteryNotify
-import com.adealink.weparty.module.gift.effect.luckygift.LuckyGiftRewardEffectEntity
 import com.adealink.weparty.module.headline.Headline
-import com.adealink.weparty.module.headline.listener.IHeadlineOpListener
-import com.adealink.weparty.module.profile.Profile
 import com.adealink.weparty.module.profile.ProfileModule
-import com.adealink.weparty.module.room.Room
 import com.adealink.weparty.module.room.RoomModule
-import com.adealink.weparty.module.room.data.RoomMicMode
-import com.adealink.weparty.module.room.data.RoomNotifyType
 import com.adealink.weparty.module.room.listener.IRoomListener
-import com.adealink.weparty.module.room.wedding.IHeadlineClickListener
-import com.adealink.weparty.module.share.ShareModule
-import com.adealink.weparty.module.webview.Web
-import com.adealink.weparty.room.data.EnterRoomInfo
-import com.adealink.weparty.url.H5Page
-import com.adealink.weparty.url.urlConfigService
-import com.adealink.weparty.util.goLocalLinkPage
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import java.util.TreeMap
 
 @RouterUri(path = [Headline.Fragment.PATH], desc = "头条fragment")
-class HeadlineFragment : BaseFragment(R.layout.fragment_healine), IHeadlineOpListener,
-    IPlayListener, IRoomListener, IHeadlineBtnClickListener, IHeadlineClickListener {
+class HeadlineFragment : BaseFragment(R.layout.fragment_healine), IRoomListener {
     private val binding by viewBinding(FragmentHealineBinding::bind)
     private val headlineViewModel by viewModels<HeadlineViewModel> { HeadlineViewModelFactory() }
-    private var isShowInviteRebate: Boolean? = null
 
-    private val gameViewModel by fastLazy { GameModule.getGameViewModel(this) }
     private val giftViewModel by fastLazy { GiftModule.getGiftViewModel(this) }
-    private val shareViewModel by fastLazy { ShareModule.getShareViewModel(this) }
     private val rocketViewModel by fastLazy { GameModule.getRocketViewModel(this) }
-    private val giftTreasureViewMode by fastLazy { GiftModule.getTreasureGiftViewModel(requireActivity()) }
-    private var regionRandomQueueEnable = false
-    private var isDelayed = false
 
-    //头条队列
-    private val entityPriorityQueue = TreeMap<String, IEffectEntity<out IEffectView>>()
 
     override fun initViews() {
         super.initViews()
         RoomModule.registerListener(this)
-        binding.swipeLayout.hideView()
-        binding.swipeLayout.setOnHideListener {
-            if (it == HideMode.MODE_MANUAL) {
-                stopHeadlineEffect()
-                showContainerDelayed()
-            }
-        }
-        if (parentFragment is ITouchDispatchEventListener) {
-            binding.swipeLayout.setTouchDispatchEventListener(parentFragment as ITouchDispatchEventListener)
-        }
     }
 
     override fun observeViewModel() {
         super.observeViewModel()
         headlineViewModel.globalHeadlineNotifyLD.observe(viewLifecycleOwner) {
-            addHeadline(
-                GlobalHeadlineEntity(
-                    headlineNotify = it,
-                    path = it.broadcastUri,
-                    headlineOpListener = this,
-                    playListener = this
-                )
-            )
+            headlineManager.addHeadLine(it)
         }
         rocketViewModel?.levelUpgradedLD?.observeWithoutCache(viewLifecycleOwner) {
-            addHeadline(
-                RocketHeadlineEffectEntity(
-                    path = "",
-                    levelUpgradeNotify = it,
-                    playListener = this,
-                    headlineOpListener = this
-                )
-            )
-        }
-        giftTreasureViewMode?.treasureGiftGlobalNotifyLd?.observeWithoutCache(viewLifecycleOwner) {
-            addHeadline(
-                GiftTreasureEffectEntity(
-                    path = "",
-                    notify = it,
-                    playListener = this,
-                    headlineOpListener = this
-                )
-            )
+            headlineManager.addRocketHeadlineEffectEntity(it)
         }
         giftViewModel?.luckyGiftRewardNotifyLD?.observeWithoutCache(viewLifecycleOwner) {
             handleLuckGiftRewardNotify(it)
@@ -138,7 +58,8 @@ class HeadlineFragment : BaseFragment(R.layout.fragment_healine), IHeadlineOpLis
                 //幸运礼物combo弹toast提醒
                 showToast(
                     getCompatString(
-                        com.adealink.weparty.R.string.common_lucky_gift_reward, notify.giftInfo.name,
+                        com.adealink.weparty.R.string.common_lucky_gift_reward,
+                        notify.giftInfo.name,
                         notify.lotteryRatio
                     )
                 )
@@ -151,327 +72,23 @@ class HeadlineFragment : BaseFragment(R.layout.fragment_healine), IHeadlineOpLis
             }
         }
         if (notify.showPlace == 1 && notify.lotteryRatio in 100..299) {
-            addHeadline(LuckyGiftRewardEffectEntity("", notify))
-        }
-    }
-
-    private fun showContainerImmediately() {
-        if (!binding.swipeLayout.isViewHidden()) {
-            return
-        }
-        binding.swipeLayout.resetToInitialPositionImmediately()
-    }
-
-    private fun showContainerDelayed() {
-        if (isDelayed) {
-            return
-        }
-        isDelayed = true
-        //如果隐藏了,5s后重新展示
-        lifecycleScope.launch(Dispatcher.WENEXT_THREAD_POOL) {
-            delay(SwipeToTopConstraintLayout.DEFAULT_DELAY_TIME)
-            if (entityPriorityQueue.isEmpty()) {
-                isDelayed = false
-                return@launch
-            }
-            withContext(Dispatcher.UI) {
-                showContainerImmediately()
-                showNextHeadLine()
-                isDelayed = false
-            }
-        }
-    }
-
-    private fun hideContainerImmediately() {
-        if (binding.swipeLayout.isViewHidden()) {
-            return
-        }
-        binding.swipeLayout.hideView()
-    }
-
-    private fun addHeadline(entity: IEffectEntity<out IEffectView>) {
-        entityPriorityQueue[entity.priority] = entity
-        if (!isDelayed) {
-            showNextHeadLine()
-        }
-    }
-
-    override fun onComplete() {
-        Log.d(TAG_HEADLINE, "onComplete, showNextHeadLine")
-        if (!isViewBindingValid()) {
-            return
-        }
-        if (animatorPlayEnd()) {
-            hideContainerImmediately()
-            return
-        }
-        showNextHeadLine()
-    }
-
-    override fun onError(errCode: Int) {
-        Log.d(TAG_HEADLINE, "onError($errCode), showNextHeadLine")
-        if (!isViewBindingValid()) {
-            return
-        }
-        if (animatorPlayEnd()) {
-            hideContainerImmediately()
-            return
-        }
-        showNextHeadLine()
-    }
-
-    private fun findIdleEffectView(): EffectView? {
-        if (!isViewBindingValid()) {
-            return null
-        }
-        if (!binding.headlineEffectView1.isEffectPlaying()
-            && binding.headlineEffectView1.isQueueEmpty()
-        ) {
-            Log.d(TAG_HEADLINE, "findIdleEffectView, headlineEffectView1")
-            return binding.headlineEffectView1
-        }
-        val micMode = RoomModule.getJoinedRoomMicMode()
-        when (micMode?.first) {
-            RoomMicMode.ROOM_MIC_VIDEO_ROOM -> {
-                return null
-            }
-
-            else -> {
-                if (!binding.headlineEffectView2.isEffectPlaying()
-                    && binding.headlineEffectView2.isQueueEmpty()
-                ) {
-                    Log.d(TAG_HEADLINE, "findIdleEffectView, headlineEffectView2")
-                    return binding.headlineEffectView2
-                }
-            }
-        }
-        return null
-    }
-
-    private fun showNextHeadLine() {
-        Log.d(TAG_HEADLINE, "showNextHeadLine")
-        playHeadlineEffect()
-        val effectView = findIdleEffectView()
-        if (effectView == null) {
-            Log.d(TAG_HEADLINE, "showNextHeadLine return, for no idle effectView")
-            return
-        }
-        val firstEffectEntry = pollNext()
-        if (firstEffectEntry == null) {
-            Log.d(TAG_HEADLINE, "showNextHeadLine return, effect entity is empty")
-            return
-        }
-        effectView.show()
-        showContainerImmediately()
-        effectView.add(firstEffectEntry)
-    }
-
-    private fun pollNext(): IEffectEntity<out IEffectView>? {
-        if (entityPriorityQueue.isEmpty()) {
-            return null
-        }
-
-        if (!regionRandomQueueEnable || entityPriorityQueue.size < 2) {
-            val pollFirstEntry = entityPriorityQueue.pollFirstEntry()
-            return pollFirstEntry?.value
-        }
-
-        val keys = mutableListOf<String>()
-        for (item in entityPriorityQueue) {
-            keys += item.key
-        }
-
-        val random = (0 until keys.size).random()
-        val key = keys[random]
-        return entityPriorityQueue.remove(key)
-    }
-
-    override fun loadData() {
-        super.loadData()
-        shareViewModel?.isShowInviteRebate()?.observe(viewLifecycleOwner) {
-            val show = (it as? Rlt.Success)?.data?.data?.show
-            isShowInviteRebate = (show == true)
-        }
-
-        headlineViewModel.getHeadlineQueueRandomConfig().observe(viewLifecycleOwner) {
-            if (it == null) {
-                regionRandomQueueEnable = false
-                return@observe
-            }
-
-            val regionMap = froJsonErrorNull<Map<String, Boolean>>(it.firstOrNull())
-            regionRandomQueueEnable = regionMap?.containsKey(ProfileModule.getMyUserInfo()?.region) == true
-        }
-    }
-
-    /**
-     * 进房间
-     */
-    private fun enterRoomClick(roomId: Long, from: String) {
-        val activity = activity ?: return
-        hideContainerImmediately()
-        Router.build(activity, Room.Room.PATH)
-            .putExtra(
-                Room.Room.EXTRA_ENTER_ROOM_INFO,
-                EnterRoomInfo(roomId, from)
-            )
-            .start()
-    }
-
-    /**
-     * 去个人主页
-     */
-    private fun goUserProfile(uid: Long) {
-        val activity = activity ?: return
-        Router.build(activity, Profile.UserProfile.PATH)
-            .putExtra(Profile.Common.EXTRA_UID, uid).start()
-    }
-
-    private fun goSuperGiftWeb() {
-        if (!GameModule.isShowSuperGift()) {
-            showToast(R.string.headline_super_gift_level_limit)
-            return
-        }
-
-        val activity = activity ?: return
-        Router.build(activity, Web.FullScreen.PATH)
-            .putExtra(Web.Common.EXTRA_URL, urlConfigService.getH5Url(H5Page.SUPER_GIFT))
-            .start()
-    }
-
-    override fun onHeadlineRightBtnClick(type: RoomNotifyType?, params: Bundle?) {
-        when (type) {
-            RoomNotifyType.SendRedPacket, RoomNotifyType.SendGift -> {
-                params?.getLong(Headline.Common.EXTRA_ROOM_ID)?.let {
-                    enterRoomClick(it, params.getString(Headline.Common.EXTRA_ENTER_ROOM_FROM, ""))
-                }
-            }
-
-            RoomNotifyType.SVIP_GLOBAL_NOTIFY -> {
-                val svipParams = params ?: return
-                val roomId = svipParams.getLong(Headline.Common.EXTRA_ROOM_ID)
-                if(roomId > 0L) {
-                    enterRoomClick(roomId, svipParams.getString(Headline.Common.EXTRA_ENTER_ROOM_FROM, ""))
-                } else {
-                    goUserProfile(svipParams.getLong(Headline.Common.EXTRA_UID))
-                }
-            }
-            RoomNotifyType.SuperGift -> {
-                goSuperGiftWeb()
-            }
-
-            RoomNotifyType.COMMON_GLOBAL_ROOM_BROADCAST_NOTIFY -> {
-                params?.getString(Headline.Common.EXTRA_DEEPLINK)?.let {
-                    val activity = activity ?: return
-                    goLocalLinkPage(activity, it)
-                }
-            }
-
-            RoomNotifyType.LuckyFruit -> {
-                GameModule.navigateToGame(GameEntranceType.LUCKY_FRUIT)
-            }
-
-            RoomNotifyType.Slot -> {
-                GameModule.navigateToGame(GameEntranceType.JACKPOT)
-            }
-
-            RoomNotifyType.SlotPro -> {
-                GameModule.navigateToGame(GameEntranceType.JACKPOT_SLOT)
-            }
-
-            RoomNotifyType.GREEDY_BOX_NOTIFY -> {
-                GameModule.navigateToGame(GameEntranceType.GREEDY_BOX)
-            }
-
-            RoomNotifyType.TEEN_PATTI_REWARD_NOTIFY -> {
-                GameModule.navigateToGame(GameEntranceType.TEEN_PATTI)
-            }
-
-            RoomNotifyType.GREEDY_PRO_NOTIFY -> {
-                GameModule.navigateToGame(GameEntranceType.GREEDY_PRO)
-            }
-
-            RoomNotifyType.NEW_GREEDY_PRO_REWARD_NOTIFY -> {
-                GameModule.navigateToGame(GameEntranceType.LUCKY_PRO)
-            }
-
-            RoomNotifyType.DRAGON_TIGER_FIGHT_REWARD_NOTIFY -> {
-                GameModule.navigateToGame(GameEntranceType.DRAGON_TIGER_FIGHT)
-            }
-
-            RoomNotifyType.RUSSIA_ROULETTE_REWARD_NOTIFY -> {
-                GameModule.navigateToGame(GameEntranceType.RUSSIAN_ROULETTE)
-            }
-
-            RoomNotifyType.TEXAS_COWBOY_REWARD_NOTIFY -> {
-                GameModule.navigateToGame(GameEntranceType.TEXAS_COWBOY)
-            }
-
-            RoomNotifyType.ADMIN_LOTTERY_ACTIVITY_REWARD_NOTIFY -> {
-                val act = activity ?: return
-                val webUrl = params?.getString(Headline.Common.EXTRA_WEB_URL) ?: return
-                Router.build(act, Web.FullScreen.PATH)
-                    .putExtra(Web.Common.EXTRA_URL, webUrl)
-                    .start()
-            }
-
-            RoomNotifyType.GREEDY_PERSONAL_REWARD_NOTIFY -> {
-                GameModule.navigateToGame(GameEntranceType.GREEDY_PERSONAL)
-            }
-
-            else -> {
-                //ntd.
-            }
+            headlineManager.addLuckyGiftLotteryNotify(notify)
         }
-        hideContainerImmediately()
     }
 
     override fun onDestroyView() {
         super.onDestroyView()
         RoomModule.unregisterListener(this)
-        entityPriorityQueue.clear()
     }
 
     override fun onRoomIn(roomId: Long, flowStateInfo: FlowStateInfo) {
         super.onRoomIn(roomId, flowStateInfo)
-        entityPriorityQueue.clear()
-        stopHeadlineEffect()
-        playHeadlineEffect()
-    }
-
-    private fun stopHeadlineEffect() {
-        runOnUiThread {
-            if (!isViewBindingValid()) {
-                return@runOnUiThread
-            }
-            binding.headlineEffectView1.stop()
-            binding.headlineEffectView2.stop()
-        }
-    }
-
-    private fun playHeadlineEffect() {
-        runOnUiThread {
-            if (!isViewBindingValid()) {
-                return@runOnUiThread
-            }
-            binding.headlineEffectView1.play()
-            binding.headlineEffectView2.play()
-        }
     }
 
-    private fun animatorPlayEnd(): Boolean {
-        if (!isViewBindingValid()) {
-            return false
-        }
-        return !binding.headlineEffectView1.isEffectPlaying() && !binding.headlineEffectView2.isEffectPlaying() && entityPriorityQueue.isEmpty()
+    override fun onRoomLeaved(roomId: Long, flowStateInfo: FlowStateInfo) {
+        super.onRoomLeaved(roomId, flowStateInfo)
+        headlineManager.clearWhenExitRoom(roomId)
     }
 
-    override fun onHeadlineBtnClick(type: RoomNotifyType?, params: Bundle?) {
-        hideContainerImmediately()
-    }
-
-    override fun onHeadlineClick(type: RoomNotifyType?, params: Bundle?) {
-        hideContainerImmediately()
-    }
 
 }

+ 72 - 76
module/headline/src/main/java/com/adealink/weparty/headline/manager/HeadlineManager.kt

@@ -2,25 +2,24 @@ package com.adealink.weparty.headline.manager
 
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.base.fastLazy
-import com.adealink.frame.coroutine.dispatcher.Dispatcher
 import com.adealink.frame.frame.BaseFrame
 import com.adealink.frame.log.Log
 import com.adealink.frame.network.ISocketNotify
+import com.adealink.frame.util.runOnUiThread
 import com.adealink.weparty.App
 import com.adealink.weparty.commonui.ext.onSuccess
 import com.adealink.weparty.commonui.widget.floatview.FloatViewFactory
-import com.adealink.weparty.commonui.widget.floatview.WindowManagerProxy
 import com.adealink.weparty.headline.data.TAG_HEADLINE
-import com.adealink.weparty.headline.floatview.GreedyPersonalGlobalHeadlineFloatData
-import com.adealink.weparty.headline.floatview.GreedyPersonalGlobalHeadlineFloatView
+import com.adealink.weparty.headline.floatview.HeadlineFloatView
+import com.adealink.weparty.module.game.rocket.RocketUpgradeNotify
 import com.adealink.weparty.module.gift.GiftModule
+import com.adealink.weparty.module.gift.data.LuckyGiftLotteryNotify
 import com.adealink.weparty.module.headline.listener.IHeadlineListener
 import com.adealink.weparty.module.profile.ProfileModule
-import com.adealink.weparty.module.room.data.DailyRechargeRewardBroadcastNotify
+import com.adealink.weparty.module.room.RoomModule
 import com.adealink.weparty.module.room.data.GlobalRoomBroadcastNotify
 import com.adealink.weparty.module.room.data.InviteTaskRewardBroadcastNotify
 import com.adealink.weparty.module.room.data.LotteryActivityBroadcastNotify
-import com.adealink.weparty.module.room.data.LotteryRewardNotify
 import com.adealink.weparty.module.room.data.RoomNotifyType
 import kotlinx.coroutines.launch
 
@@ -43,59 +42,61 @@ class HeadlineManager : BaseFrame<IHeadlineListener>(), IHeadlineManager {
         }
     }
 
-    // 个人水果机横幅通知(只通知给自己,房间外显示
-    private val greedyPersonalNotify = object : ISocketNotify<LotteryRewardNotify> {
+    //房间外横幅通知(给自己看
+    private val outRoomBroadcastNotify = object : ISocketNotify<GlobalRoomBroadcastNotify> {
 
-        override val uri: String = "URI_PERSONAL_LUCKY_FRUIT_PERSONAL_NOTIFY"
+        override val uri: String = "OUTER_ROOM_STREAMER_NOTIFY"
 
-        override fun needHandle(data: LotteryRewardNotify?): Boolean {
-            return data != null
-        }
-
-        override fun onNotify(data: LotteryRewardNotify) {
-            handleGreedyPersonalNotify(data)
-        }
-    }
-
-    private val dailyRechargeRewardGlobalHeadlineNotify = object : ISocketNotify<DailyRechargeRewardBroadcastNotify> {
-
-        override val uri: String = RoomNotifyType.DAILY_RECHARGE_REWARD_NOTIFY.uri
-
-        override fun needHandle(data: DailyRechargeRewardBroadcastNotify?): Boolean {
+        override fun needHandle(data: GlobalRoomBroadcastNotify?): Boolean {
             return data != null
         }
 
-        override fun onNotify(data: DailyRechargeRewardBroadcastNotify) {
-            handleGlobalRoomBroadcastNotify(
-                GlobalRoomBroadcastNotify(
-                    seqId = System.currentTimeMillis(),
-                    broadcastUri = RoomNotifyType.DAILY_RECHARGE_REWARD_NOTIFY.uri
-                ).also {
-                    it.dailyRechargeRewardNotify = data
+        override fun onNotify(notify: GlobalRoomBroadcastNotify) {
+            Log.d(TAG_HEADLINE, "OUTER_ROOM_STREAMER_NOTIFY onNotify: $notify")
+            if (notify.broadcastUri == RoomNotifyType.LuckyFruit.uri ||
+                notify.broadcastUri == RoomNotifyType.Slot.uri ||
+                notify.broadcastUri == RoomNotifyType.SlotPro.uri ||
+                notify.broadcastUri == RoomNotifyType.GREEDY_BOX_NOTIFY.uri ||
+                notify.broadcastUri == RoomNotifyType.TEEN_PATTI_REWARD_NOTIFY.uri ||
+                notify.broadcastUri == RoomNotifyType.GREEDY_PRO_NOTIFY.uri ||
+                notify.broadcastUri == RoomNotifyType.NEW_SLOT_REWARD_NOTIFY.uri ||
+                notify.broadcastUri == RoomNotifyType.TEXAS_COWBOY_REWARD_NOTIFY.uri ||
+                notify.broadcastUri == RoomNotifyType.DRAGON_TIGER_FIGHT_REWARD_NOTIFY.uri ||
+                notify.broadcastUri == RoomNotifyType.NEW_GREEDY_PRO_REWARD_NOTIFY.uri ||
+                notify.broadcastUri == RoomNotifyType.RUSSIA_ROULETTE_REWARD_NOTIFY.uri ||
+                notify.broadcastUri == RoomNotifyType.GREEDY_PERSONAL_REWARD_NOTIFY.uri
+            ) {
+                val isTopActivityIsRoomActivity = RoomModule.isTopActivityIsRoomActivity()
+                if (isTopActivityIsRoomActivity) {
+                    return
                 }
-            )
+                runOnUiThread {
+                    HeadlineFloatView.addOutRoomHeadline(notify)
+                }
+            }
         }
     }
 
-    private val lotteryActivityRewardGlobalHeadlineNotify = object : ISocketNotify<LotteryActivityBroadcastNotify> {
+    private val lotteryActivityRewardGlobalHeadlineNotify =
+        object : ISocketNotify<LotteryActivityBroadcastNotify> {
 
-        override val uri: String = RoomNotifyType.ADMIN_LOTTERY_ACTIVITY_REWARD_NOTIFY.uri
+            override val uri: String = RoomNotifyType.ADMIN_LOTTERY_ACTIVITY_REWARD_NOTIFY.uri
 
-        override fun needHandle(data: LotteryActivityBroadcastNotify?): Boolean {
-            return data != null
-        }
+            override fun needHandle(data: LotteryActivityBroadcastNotify?): Boolean {
+                return data != null
+            }
 
-        override fun onNotify(data: LotteryActivityBroadcastNotify) {
-            handleGlobalRoomBroadcastNotify(
-                GlobalRoomBroadcastNotify(
-                    seqId = System.currentTimeMillis(),
-                    broadcastUri = RoomNotifyType.ADMIN_LOTTERY_ACTIVITY_REWARD_NOTIFY.uri
-                ).also {
-                    it.lotteryActivityRewardNotify = data
-                }
-            )
+            override fun onNotify(data: LotteryActivityBroadcastNotify) {
+                handleGlobalRoomBroadcastNotify(
+                    GlobalRoomBroadcastNotify(
+                        seqId = System.currentTimeMillis(),
+                        broadcastUri = RoomNotifyType.ADMIN_LOTTERY_ACTIVITY_REWARD_NOTIFY.uri
+                    ).also {
+                        it.lotteryActivityRewardNotify = data
+                    }
+                )
+            }
         }
-    }
 
     // 拉新活动相关奖励
     private val inviteTaskRewardNotify = object : ISocketNotify<InviteTaskRewardBroadcastNotify> {
@@ -120,10 +121,9 @@ class HeadlineManager : BaseFrame<IHeadlineListener>(), IHeadlineManager {
 
     init {
         App.instance.networkService.subscribeNotify(roomGlobalHeadlineNotify)
-        App.instance.networkService.subscribeNotify(greedyPersonalNotify)
-        App.instance.networkService.subscribeNotify(dailyRechargeRewardGlobalHeadlineNotify)
         App.instance.networkService.subscribeNotify(lotteryActivityRewardGlobalHeadlineNotify)
         App.instance.networkService.subscribeNotify(inviteTaskRewardNotify)
+        App.instance.networkService.subscribeNotify(outRoomBroadcastNotify)
     }
 
     private fun handleGlobalRoomBroadcastNotify(notify: GlobalRoomBroadcastNotify) {
@@ -147,21 +147,7 @@ class HeadlineManager : BaseFrame<IHeadlineListener>(), IHeadlineManager {
                     }
                 }
 
-//                RoomNotifyType.InviteReward.uri -> {
-//                    InviteNotifyType.map(notify.inviteRewardNotify?.type) ?: return@launch
-//                    dispatch {
-//                        it.onHeadlineNotify(notify)
-//                    }
-//                }
-
-                RoomNotifyType.DAILY_RECHARGE_REWARD_NOTIFY.uri -> {
-                    notify.dailyRechargeRewardNotify ?: return@launch
-                    dispatch {
-                        it.onHeadlineNotify(notify)
-                    }
-                }
-
-                RoomNotifyType.COMMON_LEVEL_CHANGE_NOTIFY.uri->{
+                RoomNotifyType.COMMON_LEVEL_CHANGE_NOTIFY.uri -> {
                     notifyCommonLevelChange(notify)
                 }
 
@@ -175,19 +161,6 @@ class HeadlineManager : BaseFrame<IHeadlineListener>(), IHeadlineManager {
         }
     }
 
-    private fun handleGreedyPersonalNotify(notify: LotteryRewardNotify) {
-        //在房间外玩个人水果机需要在房间外也展示横幅给自己看
-        if (notify.rewardUid == ProfileModule.getMyUid()) {
-            launch(Dispatcher.UI) {
-                floatViewFactory.createFloatView<GreedyPersonalGlobalHeadlineFloatView>(
-                    GreedyPersonalGlobalHeadlineFloatData(notify)
-                )?.let {
-                    WindowManagerProxy.getWindowManager().addView(it)
-                }
-            }
-        }
-    }
-
     private suspend fun notifyCommonLevelChange(notify: GlobalRoomBroadcastNotify) {
         val commonLevelChangeNotify = notify.commonLevelChangeNotify ?: return
         val userInfo = commonLevelChangeNotify.userInfo ?: return
@@ -208,4 +181,27 @@ class HeadlineManager : BaseFrame<IHeadlineListener>(), IHeadlineManager {
         //Ntd.
     }
 
+    override fun addHeadLine(notify: GlobalRoomBroadcastNotify) {
+        HeadlineFloatView.addInRoomHeadline(notify)
+    }
+
+    override fun addLuckyGiftLotteryNotify(notify: LuckyGiftLotteryNotify) {
+        runOnUiThread {
+            HeadlineFloatView.addLuckyGiftLotteryNotify(notify)
+        }
+    }
+
+    override fun addRocketHeadlineEffectEntity(notify: RocketUpgradeNotify) {
+        runOnUiThread {
+            HeadlineFloatView.addRocketHeadlineEffectEntity(notify)
+        }
+    }
+
+    override fun clearWhenExitRoom(roomId: Long) {
+        Log.d(TAG_HEADLINE, "clearWhenExitRoom: $roomId")
+        runOnUiThread {
+            HeadlineFloatView.clearWhenExitRoom(roomId)
+        }
+    }
+
 }

+ 12 - 0
module/headline/src/main/java/com/adealink/weparty/headline/manager/IHeadlineManager.kt

@@ -1,8 +1,20 @@
 package com.adealink.weparty.headline.manager
 
 import com.adealink.frame.frame.IBaseFrame
+import com.adealink.weparty.module.game.rocket.RocketUpgradeNotify
+import com.adealink.weparty.module.gift.data.LuckyGiftLotteryNotify
 import com.adealink.weparty.module.headline.listener.IHeadlineListener
+import com.adealink.weparty.module.room.data.GlobalRoomBroadcastNotify
 
 interface IHeadlineManager : IBaseFrame<IHeadlineListener> {
     fun init()
+
+    //添加横幅信息展示
+    fun addHeadLine(notify: GlobalRoomBroadcastNotify)
+
+    fun addLuckyGiftLotteryNotify(notify: LuckyGiftLotteryNotify)
+    fun addRocketHeadlineEffectEntity(notify: RocketUpgradeNotify)
+
+    //退出房间时清除对应的横幅信息
+    fun clearWhenExitRoom(roomId: Long)
 }

+ 3 - 13
module/headline/src/main/res/layout/fragment_healine.xml

@@ -1,26 +1,16 @@
 <?xml version="1.0" encoding="utf-8"?>
-<com.adealink.weparty.commonui.widget.floatview.view.SwipeToTopConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/swipe_layout"
     android:layout_width="match_parent"
     android:layout_height="wrap_content">
 
     <com.adealink.frame.effect.view.EffectView
-        android:id="@+id/headline_effect_view_1"
+        android:id="@+id/headline_effect_view"
         android:layout_width="match_parent"
         android:layout_height="98dp"
-        android:visibility="gone"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
 
-    <com.adealink.frame.effect.view.EffectView
-        android:id="@+id/headline_effect_view_2"
-        android:layout_width="match_parent"
-        android:layout_height="98dp"
-        android:visibility="gone"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/headline_effect_view_1" />
-
-</com.adealink.weparty.commonui.widget.floatview.view.SwipeToTopConstraintLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 1 - 0
module/message/src/main/java/com/adealink/weparty/message/floatview/NotificationMessageFloatData.kt

@@ -3,6 +3,7 @@ package com.adealink.weparty.message.floatview
 import com.adealink.weparty.commonui.widget.floatview.data.FloatWindowType
 import com.adealink.weparty.commonui.widget.floatview.data.GRAVITY_TOP
 import com.adealink.weparty.commonui.widget.floatview.data.ILayoutFloatData
+import com.adealink.weparty.commonui.widget.slide.Slide
 import com.adealink.weparty.push.data.NotificationMessage
 
 class NotificationMessageFloatData(val windowMode: Int, val data: NotificationMessage) :

+ 9 - 0
module/message/src/main/java/com/adealink/weparty/message/floatview/NotificationMessageFloatView.kt

@@ -11,6 +11,7 @@ import com.adealink.weparty.commonui.ext.gone
 import com.adealink.weparty.commonui.ext.setVisible
 import com.adealink.weparty.commonui.ext.show
 import com.adealink.weparty.commonui.widget.floatview.view.BaseSlideFloatView
+import com.adealink.weparty.commonui.widget.slide.Slide
 import com.adealink.weparty.message.databinding.LayoutNotificationMessageViewBinding
 import com.adealink.weparty.message.manager.IMessageManager
 import com.adealink.weparty.module.message.data.isOfficialTarget
@@ -39,8 +40,16 @@ class NotificationMessageFloatView(private val notificationMessageFloatData: Not
             marginEnd = getCompatDimensionPixelSize(R.dimen.top_float_view_margin_horizontal)
         }
 
+    override fun slideDirection(): Slide.SlideDirection {
+        return Slide.SlideDirection.HORIZONTAL
+    }
+
     override var autoDismiss: Boolean = true
 
+    init {
+        id = R.id.id_float_message_view
+    }
+
     override fun onCreate() {
         super.onCreate()
         binding = LayoutNotificationMessageViewBinding.inflate(LayoutInflater.from(context))

+ 3 - 0
module/profile/src/main/java/com/adealink/weparty/profile/data/Constants.kt

@@ -23,6 +23,9 @@ const val PHOTO_WALL_IMAGE_MIN_QUALITY = 50
 
 // 聊天成就引导 label
 const val LABEL_GUIDE_CHAT_ACHIEVEMENT = "label_guide_chat_achievement"
+
+// 房间外游戏引导 label (仅男号)
+const val LABEL_GUIDE_OUT_ROOM_GAME = "label_guide_out_room_game"
 const val CHAT_ACHIEVEMENT_ACTIVIST_DEFAULT_VALUE = 80
 const val CHAT_ACHIEVEMENT_CHARMER_DEFAULT_VALUE = 30
 

+ 3 - 0
module/profile/src/main/java/com/adealink/weparty/profile/datasource/local/ProfileLocalService.kt

@@ -70,4 +70,7 @@ object ProfileLocalService : TypeDelegationPrefs(
     var showChatSettingVideoNew: Boolean by PrefKey("key_show_chat_setting_video_new", true)
 
     var showSVIPNew: Boolean by PrefKey("key_show_svip_new", true)
+    
+    // 游戏模块New标签
+    var showGameModuleNew: Boolean by PrefKey("key_show_game_module_new", true)
 }

+ 73 - 0
module/profile/src/main/java/com/adealink/weparty/profile/game/adapter/OutRoomGameItemViewBinder.kt

@@ -0,0 +1,73 @@
+package com.adealink.weparty.profile.game.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import com.adealink.frame.aab.util.getCompatString
+import com.adealink.frame.util.onClick
+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.module.game.data.GameEntranceType
+import com.adealink.weparty.module.game.data.OutRoomGameItem
+import com.adealink.weparty.module.game.data.OutRoomGameMapping
+import com.adealink.weparty.module.game.util.GameRedPointManager
+import com.adealink.weparty.profile.databinding.ItemOutRoomGameBinding
+import com.adealink.weparty.profile.datasource.local.ProfileLocalService
+import com.adealink.weparty.profile.game.util.RecentGameUtil
+
+/**
+ * 房间外游戏项ViewBinder
+ * by RYM
+ */
+class OutRoomGameItemViewBinder(
+    private val onItemClick: (OutRoomGameItem) -> Unit,
+    private val onAnyGamePlayed: (() -> Unit)? = null
+) : ItemViewBinder<OutRoomGameItem, OutRoomGameItemViewBinder.ViewHolder>() {
+
+    override fun onBindViewHolder(holder: ViewHolder, item: OutRoomGameItem) {
+        holder.update(item)
+        holder.binding.root.onClick(wait = 500) {
+            // 更新最近游戏记录(用于女号显示)
+            RecentGameUtil.updateLastPlayedGame(item.gameType)
+            // 标记本地New状态
+            GameRedPointManager.setGameNew(item.gameType, false)
+            // 更新模块New为已使用
+            ProfileLocalService.showGameModuleNew = false
+            holder.binding.tvNewTagOnly.gone()
+            onAnyGamePlayed?.invoke()
+            onItemClick.invoke(item)
+        }
+    }
+
+    override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
+        return ViewHolder(ItemOutRoomGameBinding.inflate(inflater, parent, false))
+    }
+
+    inner class ViewHolder(
+        binding: ItemOutRoomGameBinding
+    ) : BindingViewHolder<ItemOutRoomGameBinding>(binding) {
+        
+        fun update(gameItem: OutRoomGameItem) {
+            val iconRes = OutRoomGameMapping.getGameIcon(gameItem.gameType)
+            if (iconRes != 0) {
+                binding.ivEntrance.setImageResource(iconRes)
+            }
+            val titleRes = OutRoomGameMapping.getGameTitle(gameItem.gameType)
+            if (titleRes != 0) {
+                binding.tvEntrance.text = getCompatString(titleRes)
+            } else {
+                binding.tvEntrance.text = gameItem.gameType.name
+            }
+            /*
+             * 目前的游戏仅做红点,后面上线新游戏做NEW标签逻辑(UI已做)
+             * 仅对个人水果机
+             */
+            if (gameItem.gameType == GameEntranceType.GREEDY_PERSONAL && GameRedPointManager.isGameNew(GameEntranceType.GREEDY_PERSONAL)) {
+                binding.tvNewTagOnly.show()
+            } else {
+                binding.tvNewTagOnly.gone()
+            }
+        }
+    }
+}

+ 123 - 0
module/profile/src/main/java/com/adealink/weparty/profile/game/component/OutRoomGameViewComp.kt

@@ -0,0 +1,123 @@
+package com.adealink.weparty.profile.game.component
+
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.adealink.frame.mvvm.viewmodel.viewModels
+import com.adealink.weparty.commonui.BaseFragment
+import com.adealink.weparty.commonui.BaseViewComponent
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.onSuccess
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.module.game.GameModule
+import com.adealink.weparty.module.game.data.OutRoomGameItem
+import com.adealink.weparty.module.profile.ProfileModule
+import com.adealink.weparty.module.profile.data.Gender
+import com.adealink.weparty.module.profile.data.UserInfo
+import com.adealink.weparty.profile.databinding.FragmentMeBinding
+import com.adealink.weparty.profile.game.dialog.OutRoomGameDialogFragment
+import com.adealink.weparty.profile.viewmodel.ProfileViewModel
+import com.adealink.weparty.profile.viewmodel.ProfileViewModelFactory
+import com.adealink.weparty.url.LuckyGameSource
+import kotlinx.coroutines.launch
+
+/**
+ * 房间外游戏ViewComp
+ * 处理游戏列表显示和性别差异化UI
+ * by RYM
+ */
+class OutRoomGameViewComp(
+    lifecycleOwner: LifecycleOwner,
+    private val binding: FragmentMeBinding
+) : BaseViewComponent(lifecycleOwner) {
+
+    private var currentUserInfo: UserInfo? = null
+    private var gameList: List<OutRoomGameItem> = emptyList()
+
+    private val profileViewModel by viewModels<ProfileViewModel> { ProfileViewModelFactory() }
+
+    override fun initViews() {
+        super.initViews()
+        binding.itemGameMale.outRoomGameView.apply {
+            setOnHeaderClickListener {
+                onHeaderClick()
+            }
+            setOnGameClickListener { gameItem ->
+                onGameItemClick(gameItem)
+            }
+        }
+    }
+
+    override fun onCreate() {
+        super.onCreate()
+
+        ProfileModule.getMyUserInfo()?.let {
+            updateWithUserInfo(it)
+        }
+
+        profileViewModel.uidUserInfoRltLD.observe(viewLifecycleOwner) {
+            it.onSuccess { userInfo ->
+                updateWithUserInfo(userInfo = userInfo)
+            }
+        }
+    }
+
+    private fun updateWithUserInfo(userInfo: UserInfo?) {
+        currentUserInfo = userInfo
+        // 每次onResume时刷新房间外游戏数据
+        refreshGameData()
+    }
+
+    private fun refreshGameData() {
+        lifecycleScope.launch {
+            try {
+                gameList = GameModule.getOutRoomGames()
+                updateUI()
+            } catch (e: Exception) {
+                // nth.
+            }
+        }
+    }
+
+    private fun updateUI() {
+        if (currentUserInfo == null) {
+            return
+        }
+        // 如果gameList为空,则不显示
+        if (gameList.isEmpty()) {
+            binding.itemGameMale.outRoomGameView.gone()
+            return
+        }
+        binding.itemGameMale.outRoomGameView.show()
+        binding.itemGameMale.outRoomGameView.setupWithUserInfo(currentUserInfo, gameList)
+    }
+
+    private fun onHeaderClick() {
+        val userInfo = currentUserInfo ?: return
+        when (Gender.getGender(userInfo.gender)) {
+            Gender.FEMALE -> {
+                showGameDialog()
+            }
+
+            Gender.MALE -> {
+                binding.itemGameMale.outRoomGameView.toggleExpanded()
+            }
+
+            else -> {
+                // nth.
+            }
+        }
+    }
+
+    private fun showGameDialog() {
+        val fragmentManager = (lifecycleOwner as? BaseFragment)?.parentFragmentManager ?: return
+        OutRoomGameDialogFragment.newInstance(ArrayList(gameList))
+            .show(fragmentManager, "OutRoomGameDialog")
+    }
+
+    private fun onGameItemClick(gameItem: OutRoomGameItem) {
+        GameModule.navigateToGame(
+            gameItem.gameType,
+            source = LuckyGameSource.OUT_ROOM_ME
+        )
+    }
+}

+ 104 - 0
module/profile/src/main/java/com/adealink/weparty/profile/game/dialog/OutRoomGameDialogFragment.kt

@@ -0,0 +1,104 @@
+package com.adealink.weparty.profile.game.dialog
+
+import android.graphics.Color
+import android.os.Bundle
+import android.view.Gravity
+import android.view.Window
+import android.view.WindowManager
+import androidx.core.graphics.drawable.toDrawable
+import androidx.recyclerview.widget.GridLayoutManager
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.util.onClick
+import com.adealink.weparty.commonui.dialogfragment.BaseDialogFragment
+import com.adealink.weparty.commonui.recycleview.adapter.MultiTypeListAdapter
+import com.adealink.weparty.commonui.recycleview.diffutil.BaseListDiffUtil
+import com.adealink.weparty.module.game.GameModule
+import com.adealink.weparty.module.game.data.OutRoomGameItem
+import com.adealink.weparty.profile.R
+import com.adealink.weparty.profile.databinding.DialogOutRoomGameBinding
+import com.adealink.weparty.profile.game.adapter.OutRoomGameItemViewBinder
+import com.adealink.weparty.profile.game.util.RecentGameUtil
+import com.adealink.weparty.url.LuckyGameSource
+
+/**
+ * 房间外游戏弹窗 (女号专用)
+ * by RYM
+ */
+class OutRoomGameDialogFragment : BaseDialogFragment(R.layout.dialog_out_room_game) {
+
+    companion object {
+        private const val KEY_GAME_LIST = "key_game_list"
+
+        fun newInstance(gameList: ArrayList<OutRoomGameItem>): OutRoomGameDialogFragment {
+            return OutRoomGameDialogFragment().apply {
+                arguments = Bundle().apply {
+                    putParcelableArrayList(KEY_GAME_LIST, gameList)
+                }
+            }
+        }
+    }
+
+    private val binding by viewBinding(DialogOutRoomGameBinding::bind)
+
+    private val gameAdapter = MultiTypeListAdapter(BaseListDiffUtil()).apply {
+        register(
+            OutRoomGameItemViewBinder(
+                onItemClick = { gameItem ->
+                    onGameItemClick(gameItem)
+                },
+                onAnyGamePlayed = {
+                    // 不需要 nth.
+                }
+            ))
+    }
+
+    override fun initViews() {
+        super.initViews()
+        binding.ivClose.onClick {
+            dismiss()
+        }
+        binding.rvGameList.apply {
+            layoutManager = GridLayoutManager(requireContext(), 4) // 4列网格
+            adapter = gameAdapter
+        }
+    }
+
+    override fun loadData() {
+        super.loadData()
+        loadGameData()
+    }
+
+    private fun loadGameData() {
+        val gameList = arguments?.getParcelableArrayList<OutRoomGameItem>(KEY_GAME_LIST)
+        if (gameList.isNullOrEmpty()) {
+            dismiss()
+            return
+        }
+        gameAdapter.submitList(gameList)
+    }
+
+    private fun onGameItemClick(gameItem: OutRoomGameItem) {
+        // 更新最近游戏记录
+        RecentGameUtil.updateLastPlayedGame(gameItem.gameType)
+        GameModule.navigateToGame(
+            gameItem.gameType,
+            source = LuckyGameSource.OUT_ROOM_ME
+        )
+        dismiss()
+    }
+
+    override fun resetWindowAttributes(window: Window) {
+        super.resetWindowAttributes(window)
+        // 使用底部滑入动画
+        window.setWindowAnimations(com.adealink.weparty.R.style.BottomDialog_Window)
+        window.setLayout(
+            WindowManager.LayoutParams.MATCH_PARENT,
+            WindowManager.LayoutParams.WRAP_CONTENT
+        )
+        window.setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
+        val attr = window.attributes
+        attr.gravity = Gravity.BOTTOM
+        attr.dimAmount = 0.6f
+        window.attributes = attr
+    }
+}

+ 27 - 0
module/profile/src/main/java/com/adealink/weparty/profile/game/util/OutRoomGameUtil.kt

@@ -0,0 +1,27 @@
+package com.adealink.weparty.profile.game.util
+
+import com.adealink.weparty.module.game.GameModule
+import com.adealink.weparty.module.game.data.GameEntranceType
+import com.adealink.weparty.module.room.datasource.local.RoomLocalService
+
+object RecentGameUtil {
+    private val defaultGameType = GameEntranceType.GREEDY_PERSONAL
+
+    fun updateLastPlayedGame(gameType: GameEntranceType) {
+        RoomLocalService.lastPlayedGameRecord = "${gameType.name}:${System.currentTimeMillis()}"
+    }
+
+    /**
+     * 获取女号显示的游戏类型
+     * 优先返回近7天最后打开的游戏,否则返回默认的个人版水果机
+     */
+    fun getFemaleDisplayGameType(): GameEntranceType {
+        val records = GameModule.getGamePlayedRecords()
+        if (records.isEmpty()) {
+            return defaultGameType
+        }
+
+        val first = records.first()
+        return first
+    }
+}

+ 214 - 0
module/profile/src/main/java/com/adealink/weparty/profile/game/view/OutRoomGameView.kt

@@ -0,0 +1,214 @@
+package com.adealink.weparty.profile.game.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.FrameLayout
+import androidx.recyclerview.widget.GridLayoutManager
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.commonui.recycleview.adapter.MultiTypeListAdapter
+import com.adealink.weparty.commonui.recycleview.diffutil.BaseListDiffUtil
+import com.adealink.weparty.module.game.data.OutRoomGameItem
+import com.adealink.weparty.module.game.data.OutRoomGameMapping
+import com.adealink.weparty.module.game.util.GameRedPointManager
+import com.adealink.weparty.module.profile.data.Gender
+import com.adealink.weparty.module.profile.data.UserInfo
+import com.adealink.weparty.profile.databinding.LayoutOutRoomGameFemaleBinding
+import com.adealink.weparty.profile.databinding.LayoutOutRoomGameMaleBinding
+import com.adealink.weparty.profile.datasource.local.ProfileLocalService
+import com.adealink.weparty.profile.game.adapter.OutRoomGameItemViewBinder
+import com.adealink.weparty.profile.game.util.RecentGameUtil
+
+/**
+ * 房间外游戏自定义View
+ * by RYM
+ */
+class OutRoomGameView @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0
+) : FrameLayout(context, attrs, defStyleAttr) {
+
+    private var femaleBinding: LayoutOutRoomGameFemaleBinding? = null
+    private var maleBinding: LayoutOutRoomGameMaleBinding? = null
+    private var currentGender: Gender? = null
+    private var gameList: List<OutRoomGameItem> = emptyList()
+    private var onHeaderClickListener: (() -> Unit)? = null
+    private var onGameClickListener: ((OutRoomGameItem) -> Unit)? = null
+
+    // 折叠展开状态
+    private var isExpanded: Boolean = false
+    private val defaultDisplayCount = 4
+
+    private val gameAdapter = MultiTypeListAdapter(BaseListDiffUtil()).apply {
+        register(
+            OutRoomGameItemViewBinder(
+            onItemClick = { gameItem ->
+                onGameClickListener?.invoke(gameItem)
+            },
+            onAnyGamePlayed = {
+                updateNewTag()
+            }
+        ))
+    }
+
+    /**
+     * 设置游戏数据
+     * @param userInfo 用户信息(便于后续查看别人的可能性,所以改成参数传递)
+     * @param gameList 游戏数据
+     */
+    fun setupWithUserInfo(userInfo: UserInfo?, gameList: List<OutRoomGameItem>) {
+        this.gameList = gameList
+        val gender = userInfo?.let { Gender.Companion.getGender(it.gender) }
+
+        // 如果性别没变且已经有布局,只更新数据
+        if (currentGender == gender && (femaleBinding != null || maleBinding != null)) {
+            updateGameData()
+            return
+        }
+
+        removeAllViews()
+        femaleBinding = null
+        maleBinding = null
+        currentGender = gender
+        when (gender) {
+            Gender.FEMALE -> {
+                setupFemaleLayout()
+            }
+            Gender.MALE -> {
+                setupMaleLayout()
+            }
+            else -> {
+                gone()
+                return
+            }
+        }
+        updateGameData()
+        show()
+    }
+
+    /**
+     * 女性布局
+     */
+    private fun setupFemaleLayout() {
+        femaleBinding = LayoutOutRoomGameFemaleBinding.inflate(
+            LayoutInflater.from(context),
+            this,
+            true
+        )
+        femaleBinding?.clRoot?.setOnClickListener {
+            onHeaderClickListener?.invoke()
+        }
+        //标签图标(产品:取该用户近7天最近打开过(最后打开)的1款)
+        setupFemaleGameTag()
+    }
+
+    /**
+     * 设置女性布局的游戏标签
+     */
+    private fun setupFemaleGameTag() {
+        femaleBinding?.let { binding ->
+            //(产品要求:近7天最后打开的 或 默认lucky77)
+            val displayGameType = RecentGameUtil.getFemaleDisplayGameType()
+            val gameTagRes = OutRoomGameMapping.getGameTag(displayGameType)
+            if (gameTagRes != 0) {
+                binding.ivGameTag.setImageResource(gameTagRes)
+                binding.ivGameTag.show()
+            } else {
+                binding.ivGameTag.gone()
+            }
+        }
+    }
+
+    /**
+     * 男性布局
+     */
+    private fun setupMaleLayout() {
+        maleBinding = LayoutOutRoomGameMaleBinding.inflate(
+            LayoutInflater.from(context),
+            this,
+            true
+        )
+
+        // 设置header点击事件
+        maleBinding?.clHeaderLayout?.setOnClickListener {
+            onHeaderClickListener?.invoke()
+        }
+
+        // 设置RecyclerView
+        maleBinding?.rvGameList?.apply {
+            layoutManager = GridLayoutManager(context, 4)
+            adapter = gameAdapter
+        }
+    }
+
+    /**
+     * 更新数据
+     */
+    private fun updateGameData() {
+        when (currentGender) {
+            Gender.FEMALE -> {
+                setupFemaleGameTag()
+            }
+            Gender.MALE -> {
+                val displayList = if (isExpanded) {
+                    gameList
+                } else {
+                    gameList.take(defaultDisplayCount)
+                }
+                gameAdapter.submitList(displayList)
+                updateExpandIcon()
+                updateNewTag()
+            }
+            else -> {
+                // nth.
+            }
+        }
+    }
+
+    fun toggleExpanded() {
+        if (currentGender == Gender.MALE && gameList.size > defaultDisplayCount) {
+            isExpanded = !isExpanded
+            updateGameData()
+        }
+    }
+
+    private fun updateExpandIcon() {
+        maleBinding?.let { binding ->
+            if (gameList.size <= defaultDisplayCount) {
+                binding.ivOpen.gone()
+            } else {
+                binding.ivOpen.show()
+                val rotation = if (isExpanded) -90f else 0f
+                binding.ivOpen.animate()
+                    .rotation(rotation)
+                    .setDuration(300)
+                    .start()
+            }
+        }
+    }
+
+    /**
+     * 男号Header布局上的New逻辑(产品:默认展示一行游戏,点击展开所有游戏)
+     * 上新游戏模块展示,玩任何一款游戏后,new标签消失
+     */
+    private fun updateNewTag() {
+        maleBinding?.let { binding ->
+            // 模块New标记
+            if (ProfileLocalService.showGameModuleNew) {
+                binding.tvNewTag.show()
+            } else {
+                binding.tvNewTag.gone()
+            }
+        }
+    }
+
+    fun setOnHeaderClickListener(listener: () -> Unit) {
+        onHeaderClickListener = listener
+    }
+
+    fun setOnGameClickListener(listener: (OutRoomGameItem) -> Unit) {
+        onGameClickListener = listener
+    }
+}

+ 49 - 0
module/profile/src/main/java/com/adealink/weparty/profile/me/MeFragment.kt

@@ -7,6 +7,7 @@ import androidx.annotation.StringRes
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.view.updateLayoutParams
 import androidx.fragment.app.viewModels
+import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.lifecycleScope
 import com.adealink.frame.aab.util.getCompatColor
 import com.adealink.frame.aab.util.getCompatDrawable
@@ -61,6 +62,7 @@ import com.adealink.weparty.profile.R
 import com.adealink.weparty.profile.data.CHAT_ACHIEVEMENT_ACTIVIST_DEFAULT_VALUE
 import com.adealink.weparty.profile.data.CHAT_ACHIEVEMENT_CHARMER_DEFAULT_VALUE
 import com.adealink.weparty.profile.data.LABEL_GUIDE_CHAT_ACHIEVEMENT
+import com.adealink.weparty.profile.data.LABEL_GUIDE_OUT_ROOM_GAME
 import com.adealink.weparty.profile.databinding.FragmentMeBinding
 import com.adealink.weparty.profile.databinding.LayoutProfileMeListItemBinding
 import com.adealink.weparty.profile.databinding.LayoutProfileMeListItemChatAchievementBinding
@@ -69,6 +71,7 @@ import com.adealink.weparty.profile.me.data.MeInfoViewComp
 import com.adealink.weparty.profile.me.data.RechargeViewComp
 import com.adealink.weparty.profile.me.data.TopEntranceViewComp
 import com.adealink.weparty.profile.me.data.TopLayoutViewComp
+import com.adealink.weparty.profile.game.component.OutRoomGameViewComp
 import com.adealink.weparty.profile.me.dialog.UploadAvtarGuideDialogTask
 import com.adealink.weparty.profile.stat.MeTabStatEvent
 import com.adealink.weparty.profile.viewmodel.ProfileViewModel
@@ -95,6 +98,7 @@ class MeFragment : BaseFragment(R.layout.fragment_me), IScrollManager {
     private val walletViewModel by fastLazy { WalletModule.getWalletViewModel(this) }
     private val messageViewModel by fastLazy { MessageModule.getMessageViewModel(this.requireActivity()) }
     private var customerInfo: CustomerInfo? = null
+    private var outRoomGameViewComp: OutRoomGameViewComp? = null
 
     override fun initViews() {
         val activity = activity ?: return
@@ -296,6 +300,9 @@ class MeFragment : BaseFragment(R.layout.fragment_me), IScrollManager {
         RechargeViewComp(this, binding).attach()
         //顶部各种入口
         TopEntranceViewComp(this, binding).attach()
+        //房间外游戏
+        outRoomGameViewComp = OutRoomGameViewComp(this, binding)
+        outRoomGameViewComp?.attach()
         //完善资料引导
         CompleteProfileViewComp(this).attach()
     }
@@ -466,6 +473,7 @@ class MeFragment : BaseFragment(R.layout.fragment_me), IScrollManager {
         getAnchorCenterConfig()
 
         reportEnterPage(Page.ME_TAB)
+        checkOutRoomGameGuide()
     }
 
     private fun checkChatAchievementGuide() {
@@ -514,6 +522,47 @@ class MeFragment : BaseFragment(R.layout.fragment_me), IScrollManager {
             .show()
     }
 
+    private fun checkOutRoomGameGuide() {
+        // 检查是否有游戏数据(即游戏模块已解锁)
+        if (outRoomGameViewComp == null) return
+
+        val userInfo = ProfileModule.getMyUserInfo()
+        if (userInfo?.isFemale()?.not() == false) return
+        if (GuideLabel.isGuideFinished("${LABEL_GUIDE_OUT_ROOM_GAME}_${ProfileModule.getMyUid()}")) return
+
+        lifecycleScope.launchWhenResumed {
+            val gameList = GameModule.getOutRoomGames()
+            if (gameList.isEmpty()) return@launchWhenResumed
+            // 游戏视图已显示???
+            if (binding.itemGameMale.outRoomGameView.visibility != View.VISIBLE) return@launchWhenResumed
+
+            NewbieGuide.with(this@MeFragment)
+                .addGuidePage(GuidePage().apply {
+                    setLabel("${LABEL_GUIDE_OUT_ROOM_GAME}_${ProfileModule.getMyUid()}")
+                    addHighLight(
+                        R.string.profile_me_tag_out_room_game,
+                        object : RelativeGuide(
+                            R.layout.layout_me_out_room_game_guide,
+                            Gravity.TOP or Gravity.CENTER_HORIZONTAL,
+                            false
+                        ) {
+                            override fun onMarginInfo(
+                                relativeView: View,
+                                marginInfo: MarginInfo,
+                                outOffset: Offset,
+                            ) {
+                                super.onMarginInfo(relativeView, marginInfo, outOffset)
+                                outOffset.top -= 12.dp()
+                            }
+                        },
+                        enableHighLightDecorPath = false
+                    )
+                })
+                .build()
+                .show()
+        }
+    }
+
     private fun updateWithUserInfo(userInfo: UserInfo?) {
         userInfo?.let {
             binding.itemSellcoin.root.visibility =

BIN
module/profile/src/main/res/drawable-xhdpi/profile_game_room_out_ic.png


+ 31 - 0
module/profile/src/main/res/drawable/profile_me_out_room_game_bg.xml

@@ -0,0 +1,31 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="200dp"
+    android:height="67dp"
+    android:viewportWidth="612"
+    android:viewportHeight="206">
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M24.48,61.63
+        C11.22,61.63 0.48,72.37 0.48,85.63
+        V163.63C0.48,176.88 11.22,187.63 24.48,187.63
+        H291.19L305.59,202.65C307.17,204.3 309.79,204.3 311.37,202.65
+        L325.77,187.63H587.48C600.73,187.63 611.48,176.88 611.48,163.63
+        V85.63C611.48,72.37 600.73,61.63 587.48,61.63H24.48Z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="0.67"
+                android:endY="183.12"
+                android:startX="626.98"
+                android:startY="36.7"
+                android:type="linear">
+                <item
+                    android:color="#FFFF76FA"
+                    android:offset="0" />
+                <item
+                    android:color="#FF9845FF"
+                    android:offset="1" />
+            </gradient>
+        </aapt:attr>
+    </path>
+</vector>

+ 6 - 0
module/profile/src/main/res/drawable/profile_out_game_new_tag_bg.xml

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

+ 65 - 0
module/profile/src/main/res/layout/dialog_out_room_game.xml

@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/room_common_bottom_panel_bg">
+
+    <!-- 标题 -->
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tvTitle"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
+        android:gravity="center"
+        android:text="@string/common_game"
+        android:textColor="@color/color_FF333333"
+        android:textSize="18sp"
+        android:textStyle="bold"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <!-- 关闭按钮 -->
+    <androidx.appcompat.widget.AppCompatImageView
+        android:id="@+id/ivClose"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginEnd="16dp"
+        android:background="?attr/selectableItemBackgroundBorderless"
+        android:src="@drawable/common_close_black_20_ic"
+        app:layout_constraintBottom_toBottomOf="@id/tvTitle"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/tvTitle" />
+
+    <!-- 分隔线 -->
+    <View
+        android:id="@+id/divider"
+        android:layout_width="0dp"
+        android:layout_height="1dp"
+        android:layout_marginTop="10dp"
+        android:background="@color/color_FFF0F0F0"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/tvTitle" />
+
+    <!-- 游戏列表 -->
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/rvGameList"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:layout_marginBottom="10dp"
+        android:nestedScrollingEnabled="false"
+        android:overScrollMode="never"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/divider"
+        tools:itemCount="8"
+        tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
+        tools:listitem="@layout/item_out_room_game"
+        tools:spanCount="4" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

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

@@ -505,11 +505,22 @@
                 android:layout_width="match_parent"
                 android:layout_height="6dp"
                 android:background="@color/color_F5F7FA"
-                app:layout_constraintBottom_toTopOf="@id/item_store"
+                app:layout_constraintBottom_toTopOf="@id/item_game_male"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toBottomOf="@+id/item_anchor_center" />
 
+            <!-- 房间外游戏布局 -->
+            <include
+                android:id="@+id/item_game_male"
+                layout="@layout/layout_out_room_game"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                app:layout_constraintTop_toBottomOf="@id/divider_2"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintBottom_toTopOf="@+id/item_store" />
+
             <include
                 android:id="@+id/item_store"
                 layout="@layout/layout_profile_me_list_item"
@@ -518,7 +529,7 @@
                 app:layout_constraintBottom_toTopOf="@id/item_backpack"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@+id/divider_2" />
+                app:layout_constraintTop_toBottomOf="@+id/item_game_male" />
 
             <include
                 android:id="@+id/item_backpack"

+ 73 - 0
module/profile/src/main/res/layout/item_out_room_game.xml

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <com.adealink.frame.image.view.NetworkImageView
+        android:id="@+id/iv_entrance"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_marginTop="6dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:roundedCornerRadius="16dp"
+        tools:actualImageResource="@drawable/game_icon_roulette_out_room" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_entrance"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="8dp"
+        android:layout_marginTop="4dp"
+        android:layout_marginBottom="4dp"
+        android:ellipsize="end"
+        android:gravity="center"
+        android:includeFontPadding="false"
+        android:textColor="@color/black"
+        android:textSize="10sp"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/iv_entrance"
+        tools:ignore="SmallSp"
+        tools:text="PK" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_new_tag"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="-6dp"
+        android:layout_marginEnd="-16dp"
+        android:background="@drawable/profile_out_game_new_tag_bg"
+        android:includeFontPadding="false"
+        android:paddingHorizontal="5dp"
+        android:paddingVertical="1dp"
+        android:text="@string/common_new"
+        android:textColor="@color/white"
+        android:textSize="10sp"
+        android:visibility="gone"
+        app:layout_constraintEnd_toEndOf="@+id/iv_entrance"
+        app:layout_constraintTop_toTopOf="@+id/iv_entrance"
+        tools:ignore="SmallSp" />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_new_tag_only"
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:background="@drawable/profile_out_game_new_tag_bg"
+        android:includeFontPadding="false"
+        android:paddingHorizontal="5dp"
+        android:paddingVertical="1dp"
+        android:textColor="@color/white"
+        android:textSize="10sp"
+        android:visibility="gone"
+        tools:visibility="visible"
+        app:layout_constraintEnd_toEndOf="@+id/iv_entrance"
+        app:layout_constraintTop_toTopOf="@+id/iv_entrance"
+        tools:ignore="SmallSp" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 18 - 0
module/profile/src/main/res/layout/layout_me_out_room_game_guide.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.appcompat.widget.AppCompatTextView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="256dp"
+    android:layout_height="wrap_content"
+    android:layout_marginBottom="12dp"
+    android:background="@drawable/profile_me_out_room_game_bg"
+    android:drawablePadding="8dp"
+    android:paddingStart="16dp"
+    android:paddingTop="34dp"
+    android:paddingEnd="16dp"
+    android:paddingBottom="17dp"
+    android:text="@string/profile_me_out_room_game_guide"
+    android:textColor="@color/white"
+    android:textSize="13sp"
+    app:layout_constraintEnd_toEndOf="parent"
+    app:layout_constraintStart_toStartOf="parent"
+    app:layout_constraintTop_toTopOf="parent" />

+ 6 - 0
module/profile/src/main/res/layout/layout_out_room_game.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.adealink.weparty.profile.game.view.OutRoomGameView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/out_room_game_view"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:tag="@string/profile_me_tag_out_room_game" />

Неке датотеке нису приказане због велике количине промена