feat:初步实现购买游戏接口(待完整联调)

feat:完善游戏房UI
feat:初步实现游戏引擎功能
This commit is contained in:
max
2024-05-28 19:34:03 +08:00
parent c8f83b4ad8
commit c72af689e5
33 changed files with 977 additions and 108 deletions

View File

@@ -11,6 +11,7 @@ import static com.chwl.core.im.custom.bean.CustomAttachment.CUSTOM_MSG_SUB_ROOM_
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.app.FragmentManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -33,6 +34,7 @@ import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.viewpager2.widget.ViewPager2;
import com.alibaba.fastjson.JSON;
@@ -1357,18 +1359,35 @@ public class AVRoomActivity extends BaseMvpActivity<IAvRoomView, AvRoomPresenter
return this;
}
@Override
public FragmentManager getFragmentManager() {
return super.getFragmentManager();
}
@Nullable
@Override
public RoomContext getRoomContext() {
return AudioRoomContext.Companion.get();
}
@NonNull
@Override
public LiveData<? extends RoomContext> getRoomContextLiveData() {
return AudioRoomContext.Companion.getContextLiveData();
}
@NonNull
@Override
public LifecycleOwner getLifecycleOwner() {
return this;
}
@NonNull
@Override
public androidx.fragment.app.FragmentManager getViewFragmentManager() {
return getSupportFragmentManager();
}
@Nullable
@Override
public RoomWidget findWidget(@NonNull String name) {

View File

@@ -23,8 +23,10 @@ import android.widget.TextView
import androidx.annotation.CallSuper
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.withResumed
import androidx.recyclerview.widget.RecyclerView
@@ -1460,6 +1462,10 @@ open class BaseRoomFragment<V : IBaseRoomView?, P1 : BaseRoomPresenter<V>?> :
return this
}
override fun getViewFragmentManager(): FragmentManager {
return activity?.supportFragmentManager ?: childFragmentManager
}
override fun findWidget(name: String): RoomWidget? {
return widgets[name]
}
@@ -1468,6 +1474,10 @@ open class BaseRoomFragment<V : IBaseRoomView?, P1 : BaseRoomPresenter<V>?> :
return AudioRoomContext.get()
}
override fun getRoomContextLiveData(): LiveData<out RoomContext?> {
return AudioRoomContext.contextLiveData
}
open fun initWidget() {
publicChatMessageWidget?.let {
registerWidget(PublicChatRoomMessageWidget::class.java.simpleName, it)

View File

@@ -9,8 +9,8 @@ import com.chwl.app.BuildConfig;
public class AppConfig {
protected static final String APP_ID = "1578948593831571457";
protected static final String APP_KEY = "J9lHOXvFWkAZiTfl4SK7IGt0wDnW3fWd";
protected static boolean isTestEnv = BuildConfig.DEBUG;
public static final String APP_ID = "1578948593831571457";
public static final String APP_KEY = "J9lHOXvFWkAZiTfl4SK7IGt0wDnW3fWd";
public static boolean isTestEnv = BuildConfig.DEBUG;
}

View File

@@ -0,0 +1,93 @@
package com.chwl.app.game.core
import android.app.Activity
import androidx.activity.viewModels
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.viewbinding.ViewBinding
import com.chwl.app.base.BaseViewBindingActivity
import com.chwl.app.game.core.engine.GameEngineViewModel
import com.chwl.app.game.ui.game.GameViewModel
import com.chwl.core.support.room.RoomContext
import com.chwl.core.support.room.RoomView
import com.chwl.core.support.room.RoomWidget
import com.netease.nim.uikit.StatusBarUtil
abstract class BaseGameActivity<T : ViewBinding> : BaseViewBindingActivity<T>(), RoomView {
protected val viewModel: GameViewModel by viewModels()
protected val gameViewModel: GameEngineViewModel by viewModels()
protected var widgets: HashMap<String, RoomWidget> = HashMap()
override fun init() {
initView()
initEvent()
initObserver()
initWidgets()
}
protected open fun initView() {
}
protected open fun initEvent() {
}
protected open fun initObserver() {
}
protected open fun initWidgets() {
}
/**
* 注册组件
*/
protected open fun registerWidget(name: String, widget: RoomWidget) {
widgets.put(name, widget)
widget.onStart(this)
}
/**
* 取消注册组件
*/
protected open fun unregisterWidgets() {
widgets.values.forEach {
it.onStop()
}
widgets.clear()
}
override fun needSteepStateBar(): Boolean {
return true
}
override fun setStatusBar() {
super.setStatusBar()
StatusBarUtil.transparencyBar(this)
}
override fun getLifecycleOwner(): LifecycleOwner {
return this
}
override fun getActivity(): Activity? {
return this
}
override fun getRoomContext(): RoomContext? {
return viewModel.gameContextLiveData.value
}
override fun getViewFragmentManager(): FragmentManager {
return supportFragmentManager
}
override fun getRoomContextLiveData(): LiveData<out RoomContext?> {
return viewModel.gameContextLiveData
}
override fun findWidget(name: String): RoomWidget? {
return widgets[name]
}
}

View File

@@ -1,6 +1,6 @@
package com.chwl.app.game.core
import com.chwl.app.game.data.GameModel
import com.chwl.app.game.data.GameModel2
import com.chwl.core.support.room.RoomAbility
import kotlinx.coroutines.flow.MutableStateFlow
@@ -10,7 +10,7 @@ class GameStateAbility : RoomAbility() {
fun requestRoomInfo() {
safeLaunch {
val info = GameModel.getGameRoomInfo()
val info = GameModel2.getGameRoomInfo()
}
}
}

View File

@@ -0,0 +1,206 @@
package com.chwl.app.game.core.engine
import android.annotation.SuppressLint
import android.app.Activity
import android.view.View
import android.view.ViewTreeObserver
import android.widget.FrameLayout
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import com.chwl.app.R
import com.chwl.app.avroom.game.AppConfig
import com.chwl.app.base.BaseViewModel
import com.chwl.app.game.core.engine.model.GameStateResponse
import com.chwl.app.game.core.engine.model.GameViewInfoModel
import com.chwl.app.game.core.engine.model.GameViewInfoModel.GameViewRectModel
import com.chwl.core.auth.AuthModel
import com.chwl.core.room.game.GameModel
import com.chwl.core.room.game.bean.GameCfg
import com.chwl.library.language.LanguageHelper
import com.chwl.library.utils.SingleToastUtil
import com.chwl.library.utils.json.JsonUtils
import com.example.lib_utils.log.ILog
import tech.sud.mgp.core.ISudFSMStateHandle
import tech.sud.mgp.core.ISudListenerInitSDK
import tech.sud.mgp.core.SudMGP
import java.lang.IllegalStateException
@SuppressLint("StaticFieldLeak")
open class GameEngineViewModel : BaseViewModel(), ILog, LifecycleEventObserver {
private var isRunning = true
private val gameFSMMG = GameSudFSMMG(this)
private val gameFSTAPP = GameSudFSTAPP(this)
private var lifecycle: Lifecycle? = null
private var gameLayout: FrameLayout? = null
private var gameViewRect: GameViewRectModel = GameViewRectModel()
private var roomId: String = ""
fun init(lifecycle: Lifecycle, gameLayout: FrameLayout) {
this.lifecycle = lifecycle
this.gameLayout = gameLayout
lifecycle.addObserver(this)
}
fun loadGame(activity: Activity, roomId: String, gameId: Long) {
if (!this.isRunning) {
return
}
if (lifecycle == null) {
throw IllegalStateException("未初始化")
}
this.roomId = roomId
getGameCode({
initSDK(activity, gameId, it)
}, {
toast(it.message)
})
}
private fun initSDK(activity: Activity, gameId: Long, code: String) {
SudMGP.initSDK(
activity,
getAppId(),
getAppKey(),
isTestEnv(),
object : ISudListenerInitSDK {
override fun onSuccess() {
loadGameSDK(activity, gameId, code)
}
override fun onFailure(code: Int, errInfo: String) {
val msg =
activity.getString(R.string.game_failed_tips).format("$code-${errInfo}")
toast(msg)
}
})
}
private fun loadGameSDK(activity: Activity, gameId: Long, code: String) {
if (!isRunning) {
return
}
val userId = AuthModel.get().currentUid.toString()
gameFSTAPP.destroyMG()
val iSudFSTAPP =
SudMGP.loadMG(activity, userId, getRoomId(), code, gameId, getGameLanguage(), gameFSMMG)
gameFSTAPP.updateSudFSTAPP(iSudFSTAPP)
updateGameView(iSudFSTAPP.gameView)
}
private fun updateGameView(view: View?) {
gameLayout?.removeAllViews()
if (view != null) {
gameLayout?.addView(view)
}
}
fun setGameViewRect(rect: GameViewRectModel) {
this.gameViewRect = rect
}
private fun getGameCode(onSuccess: (String) -> Unit, onFailed: (Throwable) -> Unit) {
addDisposable(
GameModel.getGameCode()
.subscribe({
onSuccess.invoke(it.code)
}, {
onFailed.invoke(it)
})
)
}
private fun toast(message: String?) {
if (!message.isNullOrEmpty()) {
SingleToastUtil.showToast(message)
}
}
fun onExpireCode(handle: ISudFSMStateHandle) {
getGameCode({
handle.success(GameStateResponse.success().toJson())
gameFSTAPP.updateCode(it, null)
}, {
logE(it)
})
}
fun onGetGameCfg(handle: ISudFSMStateHandle) {
handle.success(JsonUtils.toJson(GameCfg()))
}
fun onGetGameViewInfo(handle: ISudFSMStateHandle) {
val gameLayout = gameLayout
if (gameLayout == null) {
handle.failure("gameLayout is NULL")
return
}
val gameViewWidth = gameLayout.measuredWidth
val gameViewHeight = gameLayout.measuredHeight
if (gameViewWidth > 0 && gameViewHeight > 0) {
notifyGameViewInfo(handle, gameViewWidth, gameViewHeight)
return
}
gameLayout.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
gameLayout.viewTreeObserver.removeOnGlobalLayoutListener(this)
notifyGameViewInfo(handle, gameLayout.measuredWidth, gameLayout.measuredHeight)
}
})
}
private fun notifyGameViewInfo(handle: ISudFSMStateHandle, width: Int, height: Int) {
val response = GameViewInfoModel()
response.ret_code = 0
response.view_size.width = width
response.view_size.height = height
response.view_game_rect = gameViewRect
handle.success(JsonUtils.toJson(response))
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
onViewDestroy()
}
}
private fun getRoomId() = roomId
private fun getAppId() = AppConfig.APP_ID
private fun getAppKey() = AppConfig.APP_KEY
private fun isTestEnv() = AppConfig.isTestEnv
private fun getGameLanguage(): String {
return when (LanguageHelper.getCurrentLanguageType()) {
LanguageHelper.ZH -> {
"zh-TW"
}
LanguageHelper.AR -> {
"ar-SA"
}
else -> {
"en-US"
}
}
}
override fun onCleared() {
super.onCleared()
onViewDestroy()
}
private fun onViewDestroy() {
isRunning = false
lifecycle?.removeObserver(this)
lifecycle = null
}
}

View File

@@ -0,0 +1,48 @@
package com.chwl.app.game.core.engine
import com.chwl.app.game.core.engine.model.GameStateResponse
import com.example.lib_utils.log.ILog
import tech.sud.mgp.core.ISudFSMMG
import tech.sud.mgp.core.ISudFSMStateHandle
class GameSudFSMMG(private val engineViewModel: GameEngineViewModel) : ISudFSMMG, ILog {
override fun onGameLog(p0: String?) {
if (p0 != null) {
logD(p0)
}
}
override fun onGameLoadingProgress(p0: Int, p1: Int, p2: Int) {
}
override fun onGameStarted() {
}
override fun onGameDestroyed() {
}
override fun onExpireCode(p0: ISudFSMStateHandle, p1: String?) {
engineViewModel.onExpireCode(p0)
}
override fun onGetGameViewInfo(p0: ISudFSMStateHandle, p1: String?) {
engineViewModel.onGetGameViewInfo(p0)
}
override fun onGetGameCfg(p0: ISudFSMStateHandle, p1: String?) {
engineViewModel.onGetGameCfg(p0)
}
override fun onGameStateChange(p0: ISudFSMStateHandle, p1: String?, p2: String?) {
p0.success(GameStateResponse.success().toJson())
}
override fun onPlayerStateChange(
p0: ISudFSMStateHandle,
p1: String?,
p2: String?,
p3: String?
) {
p0.success(GameStateResponse.success().toJson())
}
}

View File

@@ -0,0 +1,37 @@
package com.chwl.app.game.core.engine
import tech.sud.mgp.core.ISudFSTAPP
import tech.sud.mgp.core.ISudListenerNotifyStateChange
class GameSudFSTAPP(private val engineViewModel: GameEngineViewModel) {
private var sudFSTAPP: ISudFSTAPP? = null
fun updateSudFSTAPP(iSudFSTAPP: ISudFSTAPP) {
this.sudFSTAPP = iSudFSTAPP
}
fun updateCode(code: String, listener: ISudListenerNotifyStateChange?) {
this.sudFSTAPP?.updateCode(code, listener)
}
fun startMG() {
this.sudFSTAPP?.startMG()
}
fun playMG() {
this.sudFSTAPP?.playMG()
}
fun pauseMG() {
this.sudFSTAPP?.pauseMG()
}
fun stopMG() {
this.sudFSTAPP?.stopMG()
}
fun destroyMG() {
sudFSTAPP?.destroyMG()
sudFSTAPP = null
}
}

View File

@@ -0,0 +1,25 @@
package com.chwl.app.game.core.engine.model
import androidx.annotation.Keep
import com.chwl.library.utils.json.JsonUtils
import java.io.Serializable
@Keep
data class GameStateResponse(
var ret_code: Int = CODE_SUCCESS, // 返回码
var ret_msg: String? = null // 返回消息
) : Serializable {
fun toJson(): String {
return JsonUtils.toJson(this)
}
// 返回码,成功
companion object {
val CODE_SUCCESS = 0
fun success(): GameStateResponse {
return GameStateResponse(CODE_SUCCESS, "success")
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright © Sud.Tech
* https://sud.tech
*/
package com.chwl.app.game.core.engine.model;
import androidx.annotation.Keep;
/**
* 游戏视图
* 参考文档https://docs.sud.tech/zh-CN/app/Client/API/ISudFSMMG/onGetGameViewInfo.html
*/
@Keep
public class GameViewInfoModel {
// 返回码
public int ret_code;
// 返回消息
public String ret_msg;
// 游戏View的大小
public GameViewSizeModel view_size = new GameViewSizeModel();
// 游戏安全操作区域
public GameViewRectModel view_game_rect = new GameViewRectModel();
@Keep
public static class GameViewSizeModel {
// 游戏View的宽 (单位像素)
public int width;
// 游戏View的高 (单位像素)
public int height;
}
@Keep
public static class GameViewRectModel {
// 相对于view_size左边框偏移单位像素
public int left;
// 相对于view_size上边框偏移单位像素
public int top;
// 相对于view_size右边框偏移单位像素
public int right;
// 相对于view_size下边框偏移单位像素
public int bottom;
}
}

View File

@@ -7,9 +7,12 @@ import com.chwl.core.bean.response.ServiceResult
import com.chwl.core.home.bean.*
import com.chwl.core.utils.net.launchRequest
import com.chwl.library.net.rxnet.RxNet
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.POST
object GameModel : BaseModel() {
object GameModel2 : BaseModel() {
private val api = RxNet.create(Api::class.java)
@@ -23,6 +26,11 @@ object GameModel : BaseModel() {
api.getGameRoomInfo()
}
suspend fun startGame(gameId: String, gameMode: Int): String? =
launchRequest {
api.startGame(gameId, gameMode)
}
private interface Api {
/**
* 游戏房信息
@@ -37,6 +45,16 @@ object GameModel : BaseModel() {
*/
@GET("miniGame/nav/config")
suspend fun getHomeGameConfig(): ServiceResult<GameConfigBean>
/**
* 开始游戏
*/
@POST("/miniGame/nav/start")
@FormUrlEncoded
suspend fun startGame(
@Field("mgId") mgId: String,
@Field("gameMode") gameMode: Int
): ServiceResult<String>
}
}

View File

@@ -9,4 +9,6 @@ class GameModeBean : Serializable {
val modeIcon: String? = null
val gameMode: Int? = null
val scores: List<Int>? = null
val ruleUrl: String? = null
val modeName: String? = null
}

View File

@@ -1,9 +1,115 @@
package com.chwl.app.game.ui.buy
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.chwl.app.R
import com.chwl.app.common.widget.dialog.DialogManager
import com.chwl.app.databinding.GameBuyDialogBinding
import com.chwl.library.common.base.BaseDialogFragment
import com.chwl.app.game.data.bean.GameConfigBean
import com.chwl.app.game.data.bean.GameModeBean
import com.chwl.app.game.ui.game.GameActivity
import com.chwl.app.game.ui.game.GameIntent
import com.chwl.app.game.ui.home.GameHomeViewModel
import com.chwl.app.ui.pay.ChargeActivity
import com.chwl.app.ui.webview.DialogWebViewActivity
import com.chwl.core.utils.net.BalanceNotEnoughExeption
import com.chwl.library.utils.ResUtil
import com.chwl.library.utils.SingleToastUtil
import com.example.lib_utils.ktx.singleClick
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
class GameBuyDialog : BaseDialogFragment<GameBuyDialogBinding>() {
class GameBuyDialog(private val gameConfig: GameConfigBean, private val gameMode: GameModeBean) :
BottomSheetDialogFragment() {
private var binding: GameBuyDialogBinding? = null
private val viewModel: GameHomeViewModel by activityViewModels()
private var dialogManager: DialogManager? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
dialogManager = DialogManager(requireContext())
initView()
initObserver()
}
private fun initView() {
binding?.tvTitle?.text = context?.getString(R.string.game_ticket_format, gameMode.modeName)
binding?.tvCoins?.text = gameMode.ticket?.toString()
binding?.ivHelp?.singleClick {
DialogWebViewActivity.start(context, gameMode.ruleUrl ?: "", true)
}
binding?.tvStart?.singleClick {
dialogManager?.showProgressDialog(requireContext())
viewModel.startGame(gameConfig.mgIdStr ?: "", gameMode.gameMode ?: -1)
}
}
private fun initObserver() {
viewLifecycleOwner.lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.startGameFlow.collectLatest {
dialogManager?.dismissDialog()
if (it.isSuccess) {
dismissAllowingStateLoss()
val intent =
GameIntent(1, gameConfig.mgId ?: 0L, gameMode.gameMode ?: -1)
GameActivity.start(requireContext(), intent)
} else {
if (it.code == BalanceNotEnoughExeption.code) {
showBalanceNotEnoughDialog()
} else {
it.message?.let { msg ->
SingleToastUtil.showToast(msg)
}
}
}
}
}
}
}
private fun showBalanceNotEnoughDialog() {
dialogManager?.showOkCancelDialog(
ResUtil.getString(R.string.star_send_gift_balance),
ResUtil.getString(R.string.treasure_to_charge)
) {
ChargeActivity.start(context)
}
}
override fun onDestroyView() {
super.onDestroyView()
dialogManager?.dismissDialog()
dialogManager = null
}
override fun getTheme(): Int {
return R.style.ErbanBottomSheetDialog
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = GameBuyDialogBinding.inflate(LayoutInflater.from(context))
return binding?.root
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
this.setCanceledOnTouchOutside(true)
}
}
}

View File

@@ -2,17 +2,19 @@ package com.chwl.app.game.ui.game
import android.content.Context
import android.content.Intent
import androidx.activity.viewModels
import com.chwl.app.R
import com.chwl.app.base.BaseViewBindingActivity
import com.chwl.app.common.widget.dialog.DialogManager.OkCancelDialogListener
import com.chwl.app.databinding.GameActivityBinding
import com.chwl.app.game.core.BaseGameActivity
import com.chwl.app.game.core.engine.model.GameViewInfoModel.GameViewRectModel
import com.chwl.app.game.ui.game.widgets.bottom.GameBottomWidget
import com.chwl.app.game.ui.game.widgets.message.GameMessageWidget
import com.chwl.app.game.ui.game.widgets.queue.GameQueueWidget
import com.chwl.core.support.room.RoomView
import com.example.lib_utils.ktx.singleClick
import com.netease.nim.uikit.StatusBarUtil
import com.example.lib_utils.log.ILog
class GameActivity : BaseViewBindingActivity<GameActivityBinding>() {
private val viewModel: GameViewModel by viewModels()
private val gameViewModel: GameEngineViewModel by viewModels()
class GameActivity : BaseGameActivity<GameActivityBinding>(), RoomView, ILog {
companion object {
fun start(context: Context, intent: GameIntent) {
@@ -29,36 +31,60 @@ class GameActivity : BaseViewBindingActivity<GameActivityBinding>() {
finish()
return
}
initView()
initEvent()
initObserver()
super.init()
viewModel.init(intentData)
initGameEngine(intentData)
}
private fun initView() {
private fun initGameEngine(intentData: GameIntent) {
gameViewModel.init(lifecycle, binding.layoutGame)
binding.spaceGameRect.post {
val rect = GameViewRectModel().apply {
top = binding.spaceGameRect.top
bottom = binding.root.height - binding.spaceGameRect.bottom
}
logD("initGameEngine() height:${binding.root.height}")
logD("initGameEngine() top:${rect.top} bottom:${rect.bottom}")
gameViewModel.setGameViewRect(rect)
gameViewModel.loadGame(this, intentData.roomId.toString(), intentData.gameId)
}
}
private fun initEvent() {
override fun initEvent() {
super.initEvent()
binding.ivClose.singleClick {
toast("close")
finish()
onBackPressed()
}
}
private fun initObserver() {
gameViewModel.gameViewLiveData.observe(this) {
}
override fun initObserver() {
super.initObserver()
}
override fun needSteepStateBar(): Boolean {
return true
override fun initWidgets() {
super.initWidgets()
registerWidget(GameMessageWidget::class.java.simpleName, binding.messageWidget)
registerWidget(GameQueueWidget::class.java.simpleName, binding.queueWidget)
registerWidget(GameBottomWidget::class.java.simpleName, binding.bottomWidget)
}
override fun setStatusBar() {
super.setStatusBar()
StatusBarUtil.transparencyBar(this)
override fun onBackPressed() {
// super.onBackPressed()
showExitTips()
}
private fun showExitTips() {
dialogManager.showOkCancelDialog(
getString(R.string.game_exit_tips),
getString(R.string.layout_dialog_game_exit_04),
getString(R.string.exit_text), object : OkCancelDialogListener {
override fun onOk() {
}
override fun onCancel() {
super.onCancel()
this@GameActivity.finish()
}
})
}
}

View File

@@ -1,10 +0,0 @@
package com.chwl.app.game.ui.game
import android.view.View
import androidx.lifecycle.MutableLiveData
import com.chwl.app.base.BaseViewModel
class GameEngineViewModel : BaseViewModel() {
val gameViewLiveData = MutableLiveData<View?>()
}

View File

@@ -4,5 +4,5 @@ import androidx.annotation.Keep
import java.io.Serializable
@Keep
data class GameIntent(val roomId: Long, val gameId: String, val gameMode: String) : Serializable {
data class GameIntent(val roomId: Long, val gameId: Long, val gameMode: Int) : Serializable {
}

View File

@@ -1,13 +1,16 @@
package com.chwl.app.game.ui.game
import androidx.lifecycle.MutableLiveData
import com.chwl.app.base.BaseViewModel
import com.chwl.app.game.core.GameContext
import com.chwl.core.support.room.RoomContext
class GameViewModel : BaseViewModel() {
private var roomContext: GameContext? = null
val gameContextLiveData = MutableLiveData<GameContext>()
fun init(intent: GameIntent) {
roomContext = GameContext(intent.roomId)
val gameContext = GameContext(intent.roomId)
gameContextLiveData.value = gameContext
}
}

View File

@@ -0,0 +1,74 @@
package com.chwl.app.game.ui.game.input
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.chwl.app.R
import com.chwl.app.common.widget.dialog.DialogManager
import com.chwl.app.databinding.GameMessageInputDialogBinding
import com.chwl.library.utils.SingleToastUtil
import com.chwl.library.utils.keyboard.KeyboardUtil
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.coroutines.launch
class GameMessageInputDialog :
BottomSheetDialogFragment() {
private var binding: GameMessageInputDialogBinding? = null
private var dialogManager: DialogManager? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
dialogManager = DialogManager(requireContext())
initView()
initObserver()
}
private fun initView() {
binding?.inputEdit?.let {
KeyboardUtil.showKeyboardInDialog(this.dialog, it)
}
binding?.inputSend?.setOnClickListener {
SingleToastUtil.showToast("SEND")
}
}
private fun initObserver() {
viewLifecycleOwner.lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
}
}
}
override fun onDestroyView() {
super.onDestroyView()
dialogManager?.dismissDialog()
dialogManager = null
}
override fun getTheme(): Int {
return R.style.ErbanBottomSheetDialogDimFalse
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = GameMessageInputDialogBinding.inflate(LayoutInflater.from(context))
return binding?.root
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
this.setCanceledOnTouchOutside(true)
}
}
}

View File

@@ -0,0 +1,43 @@
package com.chwl.app.game.ui.game.widgets.bottom
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.databinding.DataBindingUtil
import com.chwl.app.R
import com.chwl.app.databinding.GameBottomWidgetBinding
import com.chwl.app.game.ui.game.input.GameMessageInputDialog
import com.chwl.core.support.room.FrameLayoutRoomWidget
class GameBottomWidget : FrameLayoutRoomWidget {
private val binding: GameBottomWidgetBinding =
DataBindingUtil.inflate(
LayoutInflater.from(
context
), R.layout.game_bottom_widget, this, true
)
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes)
init {
binding.tvInput.setOnClickListener {
roomView?.let {
GameMessageInputDialog().show(it.getViewFragmentManager(), "MESSAGE_INPUT")
}
}
}
}

View File

@@ -6,8 +6,7 @@ import com.chwl.app.base.BaseViewBindingFragment
import com.chwl.app.databinding.GameHomeFragmentBinding
import com.chwl.app.game.data.bean.GameConfigBean
import com.chwl.app.game.ui.buy.GameBuyDialog
import com.chwl.app.game.ui.game.GameActivity
import com.chwl.app.game.ui.game.GameIntent
import com.chwl.app.support.FragmentVisibleStateHelper
import com.chwl.app.ui.pay.ChargeActivity
import com.chwl.app.ui.utils.load
import com.chwl.app.ui.utils.loadAvatar
@@ -32,6 +31,9 @@ class GameHomeFragment : BaseViewBindingFragment<GameHomeFragmentBinding>(), Mai
initView()
initEvent()
initObserver()
FragmentVisibleStateHelper(this) {
onVisibleChanged(it)
}
PayModel.get().refreshWalletInfo(true)
refreshWalletInfo()
refreshUserInfo()
@@ -54,16 +56,22 @@ class GameHomeFragment : BaseViewBindingFragment<GameHomeFragmentBinding>(), Mai
}
binding.ivRank.singleClick {
toast("RANK")
GameActivity.start(requireContext(), GameIntent(111,"2","2"))
}
adapter.setOnItemClickListener { adapter, view, position ->
toast("$position")
GameBuyDialog().show(childFragmentManager,"GAME_BUY")
adapter.setOnItemClickListener { _, view, position ->
val config = viewModel.gameConfigLiveData.value?.data
val gameMode = adapter.getItem(position)
if (config == null || gameMode == null) {
return@setOnItemClickListener
}
GameBuyDialog(config, gameMode).show(childFragmentManager, "GAME_BUY")
}
}
private fun initObserver() {
viewModel.gameConfigLiveData.observe(this) {
if (it == null) {
return@observe
}
val configData = it.data
if (it.isSuccess && configData != null) {
loadGameInfo(configData)
@@ -71,7 +79,14 @@ class GameHomeFragment : BaseViewBindingFragment<GameHomeFragmentBinding>(), Mai
toast(it.message)
}
}
viewModel.getGameList()
}
private fun onVisibleChanged(isVisible: Boolean) {
if (isVisible) {
if (viewModel.gameConfigLiveData.value?.isSuccess != true) {
viewModel.getGameList()
}
}
}
private fun loadGameInfo(config: GameConfigBean) {

View File

@@ -2,19 +2,33 @@ package com.chwl.app.game.ui.home
import androidx.lifecycle.MutableLiveData
import com.chwl.app.base.BaseViewModel
import com.chwl.app.game.data.GameModel
import com.chwl.app.game.data.GameModel2
import com.chwl.app.game.data.bean.GameConfigBean
import com.chwl.core.bean.response.BeanResult
import com.chwl.core.pay.PayModel
import kotlinx.coroutines.flow.MutableSharedFlow
class GameHomeViewModel : BaseViewModel() {
val gameConfigLiveData = MutableLiveData<BeanResult<GameConfigBean>>()
val gameConfigLiveData = MutableLiveData<BeanResult<GameConfigBean>?>()
val startGameFlow = MutableSharedFlow<BeanResult<String>>()
fun getGameList() {
safeLaunch(onError = {
gameConfigLiveData.postValue(BeanResult.Companion.failed(it))
}) {
val configBean = GameModel.getHomeGameConfig()
val configBean = GameModel2.getHomeGameConfig()
gameConfigLiveData.postValue(BeanResult.success(configBean))
}
}
fun startGame(gameId: String, gameMode: Int) {
safeLaunch(onError = {
startGameFlow.emit(BeanResult.Companion.failed(it))
}) {
PayModel.get().refreshWalletInfo(true)
val value = GameModel2.startGame(gameId, gameMode)
startGameFlow.emit(BeanResult.success(value))
}
}
}

View File

@@ -49,18 +49,19 @@
android:id="@+id/queue_widget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/space_title_bar" />
android:layout_marginTop="@dimen/dp_13"
app:layout_constraintTop_toTopOf="@id/space_title_bar" />
<TextView
android:id="@+id/tv_award_tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="-5dp"
android:layout_marginTop="@dimen/dp_9"
android:textColor="@color/white"
android:textSize="@dimen/dp_14"
app:layout_constraintBottom_toBottomOf="@id/queue_widget"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/queue_widget"
tools:text="@string/game_award_tips_format" />
<FrameLayout
@@ -100,34 +101,25 @@
android:src="@drawable/game_ic_coins" />
</FrameLayout>
<com.chwl.app.game.ui.game.widgets.message.GameMessageWidget
android:id="@+id/message_widget"
app:layout_constraintBottom_toTopOf="@id/layout_bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="@dimen/dp_100"
app:layout_constraintBottom_toTopOf="@id/bottom_widget"
app:layout_constraintStart_toStartOf="parent" />
<FrameLayout
android:id="@+id/layout_bottom"
<com.chwl.app.game.ui.game.widgets.bottom.GameBottomWidget
android:id="@+id/bottom_widget"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_61"
android:background="@drawable/game_bg_bottom"
app:layout_constraintBottom_toBottomOf="parent">
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/tv_input"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_28"
android:layout_gravity="center"
android:layout_marginHorizontal="@dimen/dp_39"
android:background="@drawable/base_shape_190b032d_14dp"
android:gravity="center_vertical|start"
android:paddingHorizontal="@dimen/dp_21"
android:text="@string/room_say_something"
android:textColor="@color/white"
android:textSize="@dimen/dp_10" />
</FrameLayout>
<Space
android:id="@+id/space_game_rect"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/message_widget"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/queue_widget" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<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/layout_root"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_61"
android:background="@drawable/game_bg_bottom">
<TextView
android:id="@+id/tv_input"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_28"
android:layout_gravity="center"
android:layout_marginHorizontal="@dimen/dp_39"
android:background="@drawable/base_shape_190b032d_14dp"
android:gravity="center_vertical|start"
android:paddingHorizontal="@dimen/dp_21"
android:text="@string/room_say_something"
android:textColor="@color/white"
android:textSize="@dimen/dp_10"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -25,12 +25,13 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.13"
tools:text="Name" />
tools:text="@string/game_ticket_format" />
<ImageView
android:id="@+id/iv_help"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/dp_32"
android:layout_marginEnd="@dimen/dp_21"
android:src="@drawable/game_buy_ic_help"
app:layout_constraintBottom_toBottomOf="@id/tv_title"
app:layout_constraintEnd_toEndOf="parent"
@@ -86,6 +87,7 @@
tools:text="0" />
<TextView
android:id="@+id/tv_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/game_buy_btn"

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<layout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@android:color/white"
android:gravity="center_vertical"
android:orientation="horizontal">
<EditText
android:id="@+id/input_edit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@android:color/white"
android:hint="@string/room_say_here"
android:paddingStart="10dp"
android:paddingTop="10dp"
android:paddingEnd="0dp"
android:paddingBottom="10dp"
android:textColor="#888889"
android:textColorHint="#c8c8c8"
android:textSize="12sp" />
<ImageView
android:id="@+id/input_send"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_marginStart="10dp"
android:background="@drawable/click_white_gray_selector"
android:scaleType="center"
android:src="@android:drawable/ic_menu_send" />
</LinearLayout>
</layout>

View File

@@ -18,6 +18,20 @@
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/matchmaking" />
<ImageView
android:id="@+id/iv_state"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="-4.5dp"
android:layout_marginEnd="-4.5dp"
android:scaleType="fitCenter"
android:src="@drawable/game_ic_link"
app:layout_constraintBottom_toBottomOf="@id/iv_queue1_border"
app:layout_constraintEnd_toStartOf="@id/iv_queue2_border"
app:layout_constraintStart_toEndOf="@id/iv_queue1_border"
app:layout_constraintTop_toTopOf="@id/iv_queue1_border"
tools:src="@drawable/game_ic_vs" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_queue1"
android:layout_width="0dp"
@@ -33,14 +47,14 @@
android:id="@+id/iv_queue1_border"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="@dimen/dp_30"
android:layout_marginTop="@dimen/dp_26"
android:src="@drawable/game_ic_queue_avatar_border"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.194"
app:layout_constraintHorizontal_bias="0.311"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.213" />
app:layout_constraintWidth_percent="0.152" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_queue2"
@@ -60,23 +74,10 @@
android:src="@drawable/game_ic_queue_avatar_border"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.805"
app:layout_constraintHorizontal_bias="0.688"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/iv_queue1_border"
app:layout_constraintWidth_percent="0.213" />
<ImageView
android:id="@+id/iv_state_link"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:scaleType="fitCenter"
android:src="@drawable/game_ic_link"
app:layout_constraintBottom_toBottomOf="@id/iv_queue1_border"
app:layout_constraintEnd_toStartOf="@id/iv_queue2_border"
app:layout_constraintStart_toEndOf="@id/iv_queue1_border"
app:layout_constraintTop_toTopOf="@id/iv_queue1_border"
tools:src="@drawable/game_ic_vs" />
app:layout_constraintWidth_percent="0.152" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -3,4 +3,6 @@
<string name="matchmaking">匹配中</string>
<string name="game_award_tips_format">獲勝獎勵%s金幣</string>
<string name="start">开始</string>
<string name="game_ticket_format">入场费(%s)</string>
<string name="game_exit_tips">遊戲已經開始,退出房間將默認遊戲失敗,確認退出房間?</string>
</resources>

View File

@@ -3,5 +3,7 @@
<string name="matchmaking">匹配中</string>
<string name="game_award_tips_format">獲勝獎勵%s金幣</string>
<string name="start"></string>
<string name="start"></string>
<string name="game_ticket_format">入场费(%s)</string>
<string name="game_exit_tips">遊戲已經開始,退出房間將默認遊戲失敗,確認退出房間?</string>
</resources>

View File

@@ -3,4 +3,6 @@
<string name="matchmaking">匹配中</string>
<string name="game_award_tips_format">獲勝獎勵%s金幣</string>
<string name="start">开始</string>
<string name="game_ticket_format">入场费(%s)</string>
<string name="game_exit_tips">遊戲已經開始,退出房間將默認遊戲失敗,確認退出房間?</string>
</resources>

View File

@@ -27,9 +27,15 @@ import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import java.lang.Exception
open class ChatRoomClient(val sessionId: String) : ICleared, ILog {
private val clientScope = MainScope()
private var isLogin = false
private var loginData: EnterChatRoomResultData? = null
private val messagePublishSubject: PublishSubject<List<ChatRoomMessage>> =
@@ -39,6 +45,7 @@ open class ChatRoomClient(val sessionId: String) : ICleared, ILog {
.toObservable()
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
val stateFlow = MutableStateFlow<StatusCode>(StatusCode.INVALID)
private val receiveMessageObserver = Observer<List<ChatRoomMessage>> {
val list = it.filter { item ->
@@ -55,6 +62,9 @@ open class ChatRoomClient(val sessionId: String) : ICleared, ILog {
} else {
isLogin = false
}
clientScope.launch {
stateFlow.emit(it.status)
}
}
init {
@@ -126,8 +136,8 @@ open class ChatRoomClient(val sessionId: String) : ICleared, ILog {
}.compose(RxHelper.handleSchedulers())
}
fun exitChatRoom(sessionId: String) {
logD("exitChatRoom() sessionId:${sessionId}")
fun exitChatRoom() {
logD("exitChatRoom()")
NIMChatRoomSDK.getChatRoomService().exitChatRoom(sessionId)
}
@@ -208,6 +218,11 @@ open class ChatRoomClient(val sessionId: String) : ICleared, ILog {
ChatRoomClientManager.onClientCleared(this)
registerReceiveMessage(false)
registerOnlineStatus(false)
exitChatRoom(sessionId)
exitChatRoom()
try {
clientScope.cancel()
} catch (e: Exception) {
e.printStackTrace()
}
}
}

View File

@@ -5,7 +5,6 @@ import android.util.AttributeSet
import android.widget.FrameLayout
import androidx.annotation.CallSuper
import androidx.lifecycle.Observer
import androidx.lifecycle.viewModelScope
import com.chwl.core.utils.extension.toast
import com.chwl.library.net.rxnet.exception.ExceptionHandle
import io.reactivex.disposables.CompositeDisposable
@@ -64,13 +63,13 @@ abstract class FrameLayoutRoomWidget : FrameLayout, RoomWidget {
@CallSuper
override fun onStart(roomView: RoomView) {
this.roomView = roomView
AudioRoomContext.contextLiveData.observeForever(contextObserver)
roomView.getRoomContextLiveData().observeForever(contextObserver)
}
@CallSuper
override fun onStop() {
// 注销监听
AudioRoomContext.contextLiveData.removeObserver(contextObserver)
roomView?.getRoomContextLiveData()?.removeObserver(contextObserver)
// 解绑
onUnbindContext()
this.roomView = null

View File

@@ -1,7 +1,10 @@
package com.chwl.core.support.room
import android.app.Activity
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.room.Room
/**
* Created by Max on 2023/10/26 15:05
@@ -13,6 +16,10 @@ interface RoomView {
fun getActivity(): Activity?
fun getViewFragmentManager(): FragmentManager
fun getRoomContextLiveData(): LiveData<out RoomContext?>
/**
* 获取房间上下文
*/

View File

@@ -7,11 +7,16 @@ import com.google.gson.Gson;
*/
public class JsonUtils {
public static Gson gson;
public static String toJson(Object object) {
if (gson == null) {
gson = new Gson();
}
String result = null;
try {
if (object != null) {
result = new Gson().toJson(object);
result = gson.toJson(object);
}
} catch (Exception ex) {
ex.printStackTrace();