feat : app/src/module_game

This commit is contained in:
eggmanQQQ2
2025-07-07 10:36:04 +08:00
parent 7821fe7c9a
commit 490cce1256
75 changed files with 3693 additions and 0 deletions

View File

@@ -0,0 +1,301 @@
package com.chwl.app.game.core
import android.app.Activity
import android.view.View
import android.widget.FrameLayout
import androidx.activity.viewModels
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.chwl.app.R
import com.chwl.app.base.BaseViewBindingActivity
import com.chwl.app.common.widget.dialog.DialogManager
import com.chwl.app.game.core.engine.GameEngineAbility
import com.chwl.app.game.data.bean.GameInfoUiState
import com.chwl.app.game.ui.game.GameViewModel
import com.chwl.app.game.ui.result.GameResultDialog
import com.chwl.app.ui.pay.ChargeActivity
import com.chwl.core.bean.game.GameResultBean
import com.chwl.core.support.room.RoomContext
import com.chwl.core.support.room.RoomView
import com.chwl.core.support.room.RoomWidget
import com.chwl.core.utils.net.BalanceNotEnoughExeption
import com.chwl.core.utils.net.ServerException
import com.chwl.library.utils.ResUtil
import com.netease.nim.uikit.StatusBarUtil
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
abstract class BaseGameActivity<T : ViewBinding> : BaseViewBindingActivity<T>(), RoomView {
protected val viewModel: GameViewModel by viewModels()
protected var widgets: HashMap<String, RoomWidget> = HashMap()
protected val gameContext get() = viewModel.gameContextLiveData.value
protected val stateAbility get() = gameContext?.findAbility<GameStateAbility>(GameStateAbility::class.java.simpleName)
protected val gameEngineAbility
get() = gameContext?.findAbility<GameEngineAbility>(
GameEngineAbility::class.java.simpleName
)
@Deprecated("Dialog管理后续需要优化")
private var gameInfoDialogManager: DialogManager? = null
@Deprecated("Dialog管理后续需要优化")
protected var loadingDialogManager: DialogManager? = null
@Deprecated("Dialog管理后续需要优化")
protected var matchFailedDialogManager: DialogManager? = null
@Deprecated("Dialog管理后续需要优化")
protected var balanceNotEnoughDialogManager: DialogManager? = null
private var resultDialog: GameResultDialog? = null
override fun init() {
initView()
initEvent()
initObserver()
initWidgets()
}
protected open fun initView() {
loadingDialogManager = DialogManager(this)
}
protected open fun initEvent() {
}
protected open fun initObserver() {
lifecycleScope.launch {
viewModel.closeRoomFlow.collectLatest {
loadingDialogManager?.dismissDialog()
finish()
}
}
viewModel.gameContextLiveData.observe(this) {
onGameContext(it)
}
}
protected open fun initWidgets() {
}
protected open fun onGameContext(gameContext: GameContext) {
gameContext.onViewAttach(this)
initGameEngine(gameContext)
stateAbility?.let {
observeGameInfoUiState(it)
lifecycleScope.launch {
it.matchFailedFlow.collectLatest {
onMatchFailed()
}
}
lifecycleScope.launch {
it.gameResultFlow.collectLatest { list ->
onGameResult(list)
}
}
}
}
private fun observeGameInfoUiState(stateAbility: GameStateAbility) {
lifecycleScope.launch {
stateAbility.gameInfoUiStateFlow.collectLatest {
when (it) {
is GameInfoUiState.Loading -> {
if (gameInfoDialogManager == null) {
gameInfoDialogManager = DialogManager(context)
}
gameInfoDialogManager?.showProgressDialog(context)
}
is GameInfoUiState.Success -> {
matchFailedDialogManager?.dismissDialog()
balanceNotEnoughDialogManager?.dismissDialog()
dismissResultDialog()
gameInfoDialogManager?.dismissDialog()
}
is GameInfoUiState.Failed -> {
gameInfoDialogManager?.dismissDialog()
if (it.throwable is ServerException && it.throwable.code == BalanceNotEnoughExeption.code) {
onBalanceNotEnough()
} else {
it.throwable.message?.let { msg ->
toast(msg)
}
}
}
is GameInfoUiState.Empty -> {
gameInfoDialogManager?.dismissDialog()
toast(R.string.game_end_tips)
finish()
}
}
}
}
}
protected open fun initGameEngine(gameContext: GameContext) {
val gameEngineAbility = gameContext.findAbility<GameEngineAbility>(GameEngineAbility::class.java.simpleName)
lifecycleScope.launch {
gameEngineAbility?.gameViewFlow?.collectLatest {
onGameViewChanged(it)
}
}
}
protected open fun onGameResult(list: List<GameResultBean>?) {
if (list == null) {
return
}
dismissResultDialog()
resultDialog = GameResultDialog(list, onClose = {
closeRoom()
}, onRestart = {
restart()
})
resultDialog?.show(supportFragmentManager, "GAME_RESULT")
}
private fun onMatchFailed() {
if (matchFailedDialogManager == null) {
matchFailedDialogManager = DialogManager(context)
}
matchFailedDialogManager?.showOkCancelDialog(null,
getString(R.string.game_match_failed),
getString(R.string.game_rematch),
getString(R.string.exit_text), false, false, object :
DialogManager.OkCancelDialogListener {
override fun onOk() {
restart()
}
override fun onCancel() {
super.onCancel()
closeRoom()
}
})
}
protected open fun onBalanceNotEnough() {
if (balanceNotEnoughDialogManager == null) {
balanceNotEnoughDialogManager = DialogManager(this)
}
balanceNotEnoughDialogManager?.showOkCancelDialog(
ResUtil.getString(R.string.widget_dialog_dialoguihelper_04),
ResUtil.getString(R.string.treasure_to_charge)
) {
ChargeActivity.start(context)
}
}
private fun closeRoom() {
loadingDialogManager?.showProgressDialog(this@BaseGameActivity)
viewModel.closeRoom()
}
private fun restart() {
val intent = viewModel.getGameIntent()
if (intent == null) {
toast(R.string.data_error)
return
}
stateAbility?.restart(intent)
}
protected open fun onGameViewChanged(view: View?) {
if (view == null) {
getGameViewGroup().removeAllViews()
} else {
getGameViewGroup().addView(
view,
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
)
}
}
abstract fun getGameViewGroup(): FrameLayout
/**
* 注册组件
*/
protected open fun registerWidget(name: String, widget: RoomWidget) {
widgets[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]
}
override fun onDestroy() {
super.onDestroy()
balanceNotEnoughDialogManager?.dismissDialog()
balanceNotEnoughDialogManager = null
gameInfoDialogManager?.dismissDialog()
gameInfoDialogManager = null
loadingDialogManager?.dismissDialog()
loadingDialogManager = null
dismissResultDialog()
resultDialog = null
matchFailedDialogManager?.dismissDialog()
matchFailedDialogManager = null
}
private fun dismissResultDialog() {
try {
resultDialog?.dismissAllowingStateLoss()
} catch (e: Exception) {
}
}
}

View File

@@ -0,0 +1,26 @@
package com.chwl.app.game.core
import com.chwl.app.game.core.engine.GameEngineAbility
import com.chwl.core.bean.game.GameRoomInfo
import com.chwl.core.support.room.RoomAbility
import com.chwl.core.support.room.RoomContext
class GameContext(roomId: Long) : RoomContext(roomId) {
override fun loadAbility(list: MutableMap<String, RoomAbility>) {
super.loadAbility(list)
list[GameStateAbility::class.java.simpleName] = GameStateAbility()
list[GameEngineAbility::class.java.simpleName] = GameEngineAbility()
list[GameIMEngineAbility::class.java.simpleName] = GameIMEngineAbility()
list[GameQueueAbility::class.java.simpleName] = GameQueueAbility()
list[GameMessageAbility::class.java.simpleName] = GameMessageAbility()
}
override fun performStart() {
super.performStart()
}
fun restart(roomInfo: GameRoomInfo) {
val stateAbility = findAbility<GameStateAbility>(GameStateAbility::class.java.simpleName)
}
}

View File

@@ -0,0 +1,137 @@
package com.chwl.app.game.core
import com.chwl.app.public_chat.core.ChatRoomClient
import com.chwl.app.public_chat.core.ChatRoomClientManager
import com.chwl.core.manager.IMNetEaseManager
import com.chwl.core.support.listener.ListenerOwner
import com.chwl.core.support.listener.ListenerStore
import com.chwl.core.support.listener.SafeListenerOwner
import com.chwl.core.support.room.RoomAbility
import com.chwl.core.support.room.RoomContext
import com.netease.nimlib.sdk.StatusCode
import com.netease.nimlib.sdk.chatroom.ChatRoomMessageBuilder
import com.netease.nimlib.sdk.chatroom.model.ChatRoomMessage
import io.reactivex.Single
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
class GameIMEngineAbility(private val listenerOwner: ListenerOwner<GameIMEngineAbility.Listener> = SafeListenerOwner()) :
RoomAbility(), ListenerStore<GameIMEngineAbility.Listener> by listenerOwner {
val stateFlow = MutableStateFlow<StatusCode>(StatusCode.INVALID)
// 恢复连接(中断后恢复)
val reconnectedFlow = MutableSharedFlow<Boolean>()
private var chatRoomClient: ChatRoomClient? = null
private var netBrokenTag = false
private val stateAbility: GameStateAbility?
get() = roomContext?.findAbility<GameStateAbility>(
GameStateAbility::class.java.simpleName
)
override fun onStart(context: RoomContext) {
super.onStart(context)
safeLaunch {
stateAbility?.let {
combine(
*arrayOf(
it.imIdFlow,
it.gameStateFlow
), transform = {
true
})
.collectLatest {
checkRunning()
}
}
}
}
private fun checkRunning() {
val stateAbility = this.stateAbility
val gameState = stateAbility?.gameStateFlow?.value
val sessionId = stateAbility?.imIdFlow?.value?.toString()
logD("checkRunning() gameState:$gameState sessionId:$sessionId chatRoomClient?.sessionId = ${chatRoomClient?.sessionId} GameIMEngineAbility ")
if (sessionId != null && (gameState == GameStateAbility.STATE_MATCHING || gameState == GameStateAbility.STATE_MATCH_SUCCESS)) {
if (chatRoomClient?.sessionId != sessionId) {
logD("checkRunning() 1 GameIMEngineAbility ")
exitRoom()
initClient(sessionId)
}
} else {
logD("checkRunning() 2 GameIMEngineAbility ")
exitRoom()
}
}
private fun exitRoom() {
logD("exitRoom() id:${chatRoomClient?.sessionId}")
IMNetEaseManager.gameChatRoomId = null
chatRoomClient?.onCleared()
chatRoomClient = null
}
private fun initClient(sessionId: String) {
logD("GameIMEngineAbility initClient() sessionId$sessionId")
netBrokenTag = false
chatRoomClient = ChatRoomClientManager.getClient(sessionId)
chatRoomClient?.let {
safeLaunch {
it.stateFlow.collect {
if (it == StatusCode.NET_BROKEN) {
netBrokenTag = true
}
if (it == StatusCode.LOGINED) {
if (netBrokenTag) {
reconnectedFlow.emit(true)
}
netBrokenTag = false
}
this@GameIMEngineAbility.stateFlow.value = it
}
}
addDisposable(it.messageObservable.subscribe { list ->
listenerOwner.postEvent { it.onReceiveMessage(list) }
})
enterChatRoom(it)
}
}
private fun enterChatRoom(client: ChatRoomClient) {
IMNetEaseManager.gameChatRoomId = client.sessionId
addDisposable(client.enterChatRoom().subscribe({
}, {
}))
}
fun sendMessage(message: ChatRoomMessage) {
chatRoomClient?.let {
addDisposable(it.sendMessage(message).subscribe({}, {}))
}
}
fun buildTextMessage(text: String): ChatRoomMessage? {
val client = chatRoomClient ?: return null
return ChatRoomMessageBuilder.createChatRoomTextMessage(client.sessionId, text)
}
fun sendMessageRx(message: ChatRoomMessage): Single<Any> {
val client = chatRoomClient ?: return Single.error(IllegalStateException("client is NULL"))
return client.sendMessage(message)
}
override fun onStop(context: RoomContext) {
super.onStop(context)
listenerOwner.removeAllListener()
exitRoom()
}
interface Listener {
fun onReceiveMessage(messages: List<ChatRoomMessage>)
}
}

View File

@@ -0,0 +1,121 @@
package com.chwl.app.game.core
import com.chwl.app.R
import com.chwl.core.support.listener.ListenerOwner
import com.chwl.core.support.listener.ListenerStore
import com.chwl.core.support.listener.SafeListenerOwner
import com.chwl.core.support.room.RoomAbility
import com.chwl.core.support.room.RoomContext
import com.chwl.library.utils.ResUtil
import com.netease.nimlib.sdk.chatroom.ChatRoomMessageBuilder
import com.netease.nimlib.sdk.chatroom.model.ChatRoomMessage
import com.netease.nimlib.sdk.msg.constant.MsgTypeEnum
import io.reactivex.Single
import kotlinx.coroutines.flow.collectLatest
import java.lang.IllegalStateException
class GameMessageAbility(private val listenerOwner: ListenerOwner<GameMessageAbility.Listener> = SafeListenerOwner()) :
RoomAbility(), ListenerStore<GameMessageAbility.Listener> by listenerOwner,
GameIMEngineAbility.Listener {
private val dataList = ArrayList<Any>()
private val imEngineAbility
get() =
roomContext?.findAbility<GameIMEngineAbility>(GameIMEngineAbility::class.java.simpleName)
override fun onStart(context: RoomContext) {
super.onStart(context)
val imEngineAbility =
context.findAbility<GameIMEngineAbility>(GameIMEngineAbility::class.java.simpleName)
imEngineAbility?.addListener(this)
val stateAbility =
context.findAbility<GameStateAbility>(GameStateAbility::class.java.simpleName)
stateAbility?.let {
safeLaunch {
it.imIdFlow.collectLatest {
resetMessageList()
}
}
safeLaunch {
it.gameStateFlow.collectLatest {
if (it == GameStateAbility.STATE_GAME_END) {
resetMessageList()
}
}
}
}
}
private fun resetMessageList() {
val stateAbility =
roomContext?.findAbility<GameStateAbility>(GameStateAbility::class.java.simpleName)
dataList.clear()
val sessionId = stateAbility?.imIdFlow?.value
if (sessionId != null) {
dataList.add(getSystemNotifyMessage(sessionId.toString()))
}
listenerOwner.postEvent {
it.onReplaceMessageList(dataList)
}
}
fun getMessageList(): List<Any> {
return dataList
}
fun addMessage(message: Any) {
dataList.add(message)
listenerOwner.postEvent {
it.onAddMessage(message)
}
}
fun sendTextMessage(text: String): Single<Any> {
imEngineAbility?.let {
val textMessage = it.buildTextMessage(text)
if (textMessage == null) {
return Single.error(IllegalStateException("message is NULL"))
} else {
return it.sendMessageRx(textMessage).map {
addMessage(textMessage)
}
}
}
return Single.error(IllegalStateException("engine is NULL"))
}
private fun getSystemNotifyMessage(sessionId: String): ChatRoomMessage {
val message = ChatRoomMessageBuilder.createTipMessage(
sessionId
)
message.content = ResUtil.getString(
R.string.yizhuan_xchat_android_constants_xchatconstants_01
)
return message
}
override fun onStop(context: RoomContext) {
super.onStop(context)
listenerOwner.removeAllListener()
}
override fun onReceiveMessage(messages: List<ChatRoomMessage>) {
messages.forEach {
onReceiveMessage(it)
}
}
private fun onReceiveMessage(message: ChatRoomMessage) {
if (message.msgType == MsgTypeEnum.text) {
addMessage(message)
}
}
interface Listener {
fun onAddMessage(message: Any)
fun onReplaceMessageList(list: List<Any>)
}
}

View File

@@ -0,0 +1,25 @@
package com.chwl.app.game.core
import com.chwl.core.bean.room.RoomMicBean
import com.chwl.core.support.room.RoomAbility
import com.chwl.core.support.room.RoomContext
import kotlinx.coroutines.flow.MutableStateFlow
class GameQueueAbility : RoomAbility() {
val queueFlow = MutableStateFlow<List<RoomMicBean>?>(null)
override fun onStart(context: RoomContext) {
super.onStart(context)
}
suspend fun updateQueue(data: List<RoomMicBean>?) {
queueFlow.value = data
}
fun findQueueItem(uid: Long): RoomMicBean? {
return queueFlow.value?.firstOrNull {
it.micUser?.uid == uid
}
}
}

View File

@@ -0,0 +1,237 @@
package com.chwl.app.game.core
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import com.alibaba.fastjson2.JSON
import com.chwl.app.game.data.GameModel2
import com.chwl.app.game.data.bean.GameInfoUiState
import com.chwl.app.game.ui.game.GameIntent
import com.chwl.core.bean.game.GameResultBean
import com.chwl.core.bean.game.GameRoomInfo
import com.chwl.core.im.custom.bean.CustomAttachment
import com.chwl.core.im.custom.bean.GameForcedEndAttachment
import com.chwl.core.im.custom.bean.GameQueueChangedAttachment
import com.chwl.core.support.room.RoomAbility
import com.chwl.core.support.room.RoomContext
import com.chwl.library.common.util.doLog
import com.netease.nimlib.sdk.chatroom.model.ChatRoomMessage
import com.netease.nimlib.sdk.msg.constant.MsgTypeEnum
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
class GameStateAbility : RoomAbility(), GameIMEngineAbility.Listener {
companion object {
// 匹配中
const val STATE_MATCHING = 0
// 匹配成功
const val STATE_MATCH_SUCCESS = 1
// 游戏结束
const val STATE_GAME_END = 2
// 匹配失败
const val STATE_MATCH_FAILED = 3
// 错误状态
const val STATE_ERROR = -1
}
/**
* 游戏状态:
* 匹配状态(服务端定义)0:匹配中、1:匹配成功、2:游戏结束、3:匹配失败)
*/
val gameStateFlow: MutableStateFlow<Int?> = MutableStateFlow(null)
val gameIconFlow = MutableStateFlow<String?>(null)
val gameIdFlow = MutableStateFlow<Long?>(null)
val roomIdFlow = MutableStateFlow<Long?>(null)
val imIdFlow = MutableStateFlow<Long?>(null)
val matchRoundIdFlow = MutableStateFlow<Long?>(null)
val scoresFlow = MutableStateFlow<List<Double>?>(null)
val gameConfigFlow = MutableStateFlow<String?>(null)
@Deprecated("里面的属性有可能不是最新的建议通过具体属性flow获取数据")
val gameInfoFlow = MutableStateFlow<GameRoomInfo?>(null)
val gameInfoUiStateFlow = MutableStateFlow<GameInfoUiState>(GameInfoUiState.Loading)
val matchFailedFlow = MutableSharedFlow<Boolean>()
val gameResultFlow = MutableSharedFlow<List<GameResultBean>?>()
private var viewBackground = false
private val queueAbility get() = roomContext?.findAbility<GameQueueAbility>(GameQueueAbility::class.java.simpleName)
override fun onStart(context: RoomContext) {
super.onStart(context)
requestRoomInfo(true)
val imEngineAbility =
context.findAbility<GameIMEngineAbility>(GameIMEngineAbility::class.java.simpleName)
imEngineAbility?.addListener(this)
safeLaunch {
imEngineAbility?.reconnectedFlow?.collectLatest {
requestSyncState(true)
}
}
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
super.onStateChanged(source, event)
when (event) {
Lifecycle.Event.ON_RESUME -> {
if (viewBackground) {
if (gameStateFlow.value == STATE_MATCHING || gameStateFlow.value == STATE_MATCH_SUCCESS) {
requestSyncState(false)
}
}
viewBackground = false
}
Lifecycle.Event.ON_STOP -> {
//todo do 暂时注释这个 , 游戏目前没有最小化
// viewBackground = true
}
else -> {
}
}
}
// 请求同步服务端的最新状态
private fun requestSyncState(needLoading: Boolean) {
logD("requestSyncState")
requestRoomInfo(needLoading)
}
private fun requestRoomInfo(needLoading: Boolean) {
logD("requestRoomInfo needLoading:$needLoading")
if (needLoading) {
gameInfoUiStateFlow.value = GameInfoUiState.Loading
}
safeLaunch(onError = {
logD("requestRoomInfo Failed:$it")
gameInfoUiStateFlow.value = GameInfoUiState.Failed(it, gameInfoFlow.value)
}) {
val info = GameModel2.getGameRoomInfo() //chatRoom/getByType
if (info != null) {
gameInfoUiStateFlow.value = GameInfoUiState.Success
info.data?.matchStatus = 0
logD("飞行棋 请求 游戏信息成功 requestRoomInfo Success =${JSON.toJSONString(info)}")
syncRoomInfo(info)
} else {
logD("requestRoomInfo Empty")
gameInfoUiStateFlow.value = GameInfoUiState.Empty(gameInfoFlow.value)
syncGameState(STATE_ERROR)
}
}
}
fun restart(intent: GameIntent) {
gameInfoUiStateFlow.value = GameInfoUiState.Loading
safeLaunch(onError = {
gameInfoUiStateFlow.value = GameInfoUiState.Failed(it, gameInfoFlow.value)
}) {
GameModel2.startGame(intent.gameId.toString(), intent.gameMode)
requestRoomInfo(true)
}
}
private suspend fun syncRoomInfo(info: GameRoomInfo) {
"飞行棋 同步房间信息 syncRoomInfo ${info}".doLog()
roomContext?.roomId = info.roomId ?: 0
gameInfoFlow.value = info
roomIdFlow.value = info.roomId
imIdFlow.value = info.roomId
gameIdFlow.value = info.data?.mgId?.toLongOrNull()
gameIconFlow.value = info.data?.gameRoomIcon
info.data?.scores?.let {
scoresFlow.value = it
}
gameConfigFlow.value = info.data?.configJson
matchRoundIdFlow.value = info.data?.matchRoundId
syncQueue(info)
// 最后变更状态
syncGameState(info.data?.matchStatus)
}
fun syncGameState(matchStatus: Int?) {
gameStateFlow.value = matchStatus
}
private suspend fun syncQueue(info: GameRoomInfo) {
"飞行棋 同步麦位信息 syncQueue ${info}".doLog()
gameIdFlow?.value = info.data?.mgId?.toLongOrNull()
info.data?.scores?.let {
scoresFlow.value = it
}
queueAbility?.updateQueue(info.roomMics)
matchRoundIdFlow.value = info.data?.matchRoundId
syncGameState(info.data?.matchStatus)
}
override fun onReceiveMessage(messages: List<ChatRoomMessage>) {
messages.forEach {
if (it.msgType == MsgTypeEnum.custom) {
val attachment: CustomAttachment = (it.attachment as? CustomAttachment) ?: return
onReceiveCustomMessage(it, attachment)
}
}
}
private fun onReceiveCustomMessage(message: ChatRoomMessage, attachment: CustomAttachment) {
when (attachment.first) {
CustomAttachment.CUSTOM_MSG_MINI_GAME -> {
when (attachment.second) {
// 麦位变更
CustomAttachment.CUSTOM_MSG_MINI_GAME_QUEUE_CHANGED -> {
logD("飞行棋 onReceiveMessage 麦位变更")
val gameInfo = (attachment as? GameQueueChangedAttachment)?.gameInfo ?: return
if (gameInfo.roomId != roomContext?.roomId) {
logD("onReceiveMessage 房间变更")
safeLaunch {
syncRoomInfo(gameInfo)
}
} else {
safeLaunch {
syncQueue(gameInfo)
}
}
}
// 匹配失败
CustomAttachment.CUSTOM_MSG_MINI_GAME_MATCH_FAILED -> {
logD("onReceiveMessage 匹配失败")
safeLaunch {
syncGameState(STATE_MATCH_FAILED)
matchFailedFlow.emit(true)
}
}
// 提前结束
CustomAttachment.CUSTOM_MSG_MINI_GAME_FORCED_END -> {
logD("onReceiveMessage 提前结束")
val data =
(attachment as? GameForcedEndAttachment)?.msgData ?: return
val results = data.results
safeLaunch {
syncGameState(data.matchStatus)
gameResultFlow.emit(results)
}
}
}
}
}
}
}

View File

@@ -0,0 +1,226 @@
package com.chwl.app.game.core.engine
import com.chwl.app.game.core.GameQueueAbility
import com.chwl.app.game.core.GameStateAbility
import com.chwl.core.auth.AuthModel
import com.chwl.core.bean.game.GameResultBean
import com.chwl.core.bean.game.SudGameConfigBean
import com.chwl.core.sud.model.GameViewInfoModel
import com.chwl.core.sud.state.SudMGPAPPState
import com.chwl.core.sud.state.SudMGPMGState
import com.chwl.core.support.room.RoomContext
import com.chwl.library.utils.json.JsonUtils
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import org.json.JSONObject
import tech.sud.mgp.core.ISudFSMStateHandle
class GameEngineAbility : SudEngineAbility() {
private val stateAbility get() = roomContext?.findAbility<GameStateAbility>(GameStateAbility::class.java.simpleName)
private var gameConfig: SudGameConfigBean? = null
val gameViewRectFlow = MutableStateFlow<GameViewInfoModel.GameViewRectModel?>(null)
override fun onStart(context: RoomContext) {
super.onStart(context)
safeLaunch {
stateAbility?.gameConfigFlow?.collectLatest {
parseGameConfig(it)
}
}
safeLaunch {
stateAbility?.let {
combine(
*arrayOf(
gameViewRectFlow,
it.gameIdFlow,
it.roomIdFlow,
it.gameStateFlow
), transform = {
true
})
.collectLatest {
checkRunning()
}
}
}
}
private fun checkRunning() {
logD("checkRunning() roomIdFlow = ${stateAbility?.roomIdFlow?.value} GameEngineAbility ")
if (gameViewRectFlow.value == null) {
logD("checkRunning() gameViewRect:null GameEngineAbility ")
return
}
val stateAbility = this.stateAbility
val gameState = stateAbility?.gameStateFlow?.value
logD("checkRunning() gameState:$gameState GameEngineAbility ")
if (gameState == GameStateAbility.STATE_MATCH_SUCCESS) {
logD("checkRunning() gameState:$gameState roomView =${roomView?.getActivity()} roomIdFlow=${stateAbility.roomIdFlow.value} gameIdFlow=${stateAbility.gameIdFlow.value} GameEngineAbility -> loadGame()")
val activity = roomView?.getActivity() ?: return
val roomId = stateAbility.roomIdFlow.value ?: return
val gameId = stateAbility.gameIdFlow.value ?: return
loadGame(activity, roomId.toString(), gameId)
} else {
logD("checkRunning() destroyGame GameEngineAbility ")
exitGame()
destroyGame()
}
}
private fun parseGameConfig(json: String?) {
if (json != null) {
gameConfig = JsonUtils.fromJson(json, SudGameConfigBean::class.java)
} else {
gameConfig = null
}
}
private fun autoPlayGame() {
if (!isRunning) {
return
}
val stateAbility = stateAbility ?: return
logD("autoPlayGame state:${stateAbility.gameStateFlow.value}")
if (stateAbility.gameStateFlow.value != GameStateAbility.STATE_MATCH_SUCCESS) {
return
}
val queueAbility =
roomContext?.findAbility<GameQueueAbility>(GameQueueAbility::class.java.simpleName)
?: return
val queueSize = queueAbility.queueFlow.value?.size ?: 0
if (queueSize == 0) {
return
}
logD("autoPlayGame queueSize:$queueSize size:${sudFSMMG.sudFSMMGCache.playerReadySet.size}")
if (sudFSMMG.sudFSMMGCache.playerReadySet.size >= queueSize) {
val roundId = stateAbility.matchRoundIdFlow.value
val jsonObject = JSONObject()
jsonObject.put("value0", roundId)
val extras = jsonObject.toString()
logD("autoPlayGame roundId:${roundId}")
sudFSTAPP.notifyAPPCommonSelfPlaying(true, extras, "value0")
}
}
private fun autoJoinGame() {
if (!isRunning) {
return
}
logD("autoJoinGame")
sudFSTAPP.notifyAPPCommonSelfIn(true, -1, true, 1)
sudFSTAPP.notifyAPPCommonSelfReady(true)
val queueAbility = roomContext?.findAbility<GameQueueAbility>(GameQueueAbility::class.java.simpleName)
val firstOrNull = queueAbility?.queueFlow?.value?.firstOrNull { it?.micUser?.uid != AuthModel.get().currentUid }
firstOrNull?.micUser?.let { user->
if (user.aiLevel != null) {
sudFSTAPP.notifyAPPCommonGameAddAIPlayers(listOf(SudMGPAPPState.AIPlayers().apply {
userId = user.uid?.toString()
name = user.nick
avatar = user.avatar
level = user.aiLevel?:SudMGPAPPState.AIPlayers.LEVEL_MODERATE
// gender
}),1);
}
}
}
fun setGameViewRect(rect: GameViewInfoModel.GameViewRectModel) {
this.gameViewRectFlow.value = rect
}
override fun getGameViewRect(): GameViewInfoModel.GameViewRectModel {
return this.gameViewRectFlow.value ?: super.getGameViewRect()
}
override fun onGameStarted() {
super.onGameStarted()
logD("onGameStarted")
autoJoinGame()
autoPlayGame()
updateGameSettingSelectConfig()
}
private fun updateGameSettingSelectConfig() {
val configStr = gameConfig?.getGameSettingSelectConfigStr()
if (!configStr.isNullOrEmpty()) {
logD("updateGameSettingSelectConfig() configStr:$configStr")
sudFSTAPP.notifyStateChange(SudMGPAPPState.APP_COMMON_GAME_SETTING_SELECT_INFO, configStr)
}
}
override fun onPlayerMGCommonPlayerReady(
handle: ISudFSMStateHandle?,
userId: String?,
model: SudMGPMGState.MGCommonPlayerReady?
) {
super.onPlayerMGCommonPlayerReady(handle, userId, model)
logD("onPlayerMGCommonPlayerReady userId:$userId model:${model?.isReady}")
if (model?.isReady != false) {
autoPlayGame()
}
}
override fun onGameMGCommonGameSettle(
handle: ISudFSMStateHandle,
model: SudMGPMGState.MGCommonGameSettle?
) {
super.onGameMGCommonGameSettle(handle, model)
val list = ArrayList<GameResultBean>()
val queueAbility = roomContext?.findAbility<GameQueueAbility>(GameQueueAbility::class.java.simpleName)
val stateAbility = roomContext?.findAbility<GameStateAbility>(GameStateAbility::class.java.simpleName)
val scoresList = stateAbility?.scoresFlow?.value
model?.results?.sortedBy { it.rank }?.forEachIndexed { index, it ->
val uid = it.uid?.toLong() ?: -1
val queueItem = queueAbility?.findQueueItem(uid)
val scores = scoresList?.getOrNull(index)
val item = GameResultBean().apply {
this.uid = it.uid.toLongOrNull()
this.rank = it.rank
this.nick = queueItem?.micUser?.nick
this.avatar = queueItem?.micUser?.avatar
this.winNum = scores
// 目前1v1第一名成功后面需要调整这个逻辑
this.isWin = index == 0
this.isEscaped = it.isEscaped == 1
}
list.add(item)
}
safeLaunch {
stateAbility?.gameResultFlow?.emit(list)
stateAbility?.syncGameState(GameStateAbility.STATE_GAME_END)
}
}
override fun onGetGameCfg(handle: ISudFSMStateHandle, p1: String?) {
val configStr = gameConfig?.getGameConfigStr()
logD("onGetGameCfg() configStr:$configStr")
if (configStr.isNullOrEmpty()) {
super.onGetGameCfg(handle, p1)
} else {
handle.success(configStr)
}
}
override fun onPlayerStateChange(
handle: ISudFSMStateHandle?,
userId: String?,
state: String?,
dataJson: String?
): Boolean {
logD("onPlayerStateChange userId:$userId state:$state dataJson:$dataJson")
return super.onPlayerStateChange(handle, userId, state, dataJson)
}
override fun onGameStateChange(
handle: ISudFSMStateHandle?,
state: String?,
dataJson: String?
): Boolean {
logD("onGameStateChange state:$state dataJson:${dataJson}")
return super.onGameStateChange(handle, state, dataJson)
}
}

View File

@@ -0,0 +1,298 @@
package com.chwl.app.game.core.engine
import android.annotation.SuppressLint
import android.app.Activity
import android.view.View
import android.view.ViewTreeObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import com.chwl.app.R
import com.chwl.app.avroom.game.AppConfig
import com.chwl.core.auth.AuthModel
import com.chwl.core.room.game.GameModel
import com.chwl.core.sud.decorator.SudFSMMGDecorator
import com.chwl.core.sud.decorator.SudFSMMGListener
import com.chwl.core.sud.decorator.SudFSTAPPDecorator
import com.chwl.core.sud.model.GameConfigModel
import com.chwl.core.sud.model.GameViewInfoModel
import com.chwl.core.sud.state.MGStateResponse
import com.chwl.core.sud.state.SudMGPMGState
import com.chwl.core.support.room.RoomAbility
import com.chwl.core.support.room.RoomContext
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 kotlinx.coroutines.flow.MutableStateFlow
import tech.sud.mgp.core.ISudFSMStateHandle
import tech.sud.mgp.core.ISudListenerInitSDK
import tech.sud.mgp.core.SudMGP
@SuppressLint("StaticFieldLeak")
open class SudEngineAbility : RoomAbility(), SudFSMMGListener, ILog {
val gameViewFlow = MutableStateFlow<View?>(null)
protected var isRunning = false
protected val sudFSMMG = SudFSMMGDecorator().apply {
setSudFSMMGListener(this@SudEngineAbility)
}
protected val sudFSTAPP = SudFSTAPPDecorator()
private var roomId: String = ""
private var gameId: Long = 0
fun loadGame(activity: Activity, roomId: String, gameId: Long) {
logD("loadGame() roomId:$roomId gameId:$gameId")
if (isRunning && roomId == getRoomId() && gameId == getGameId()) {
logD("loadGame() 重复")
return
}
if (activity.isFinishing || activity.isDestroyed) {
logD("loadGame() activity 状态异常")
return
}
exitGame()
destroyGame()
this.isRunning = true
this.roomId = roomId
this.gameId = gameId
getGameCode({
initSDK(activity, gameId, it)
}, {
toast(it.message)
})
}
// 该用户是否在游戏中
fun playerIsPlaying(userId: Long): Boolean {
return sudFSMMG.playerIsPlaying(userId.toString())
}
// 返回该玩家是否已加入了游戏
fun playerIsIn(userId: Long): Boolean {
return sudFSMMG.playerIsIn(userId.toString())
}
// 自己是否已加入了游戏
fun isSelfInGame(): Boolean {
return playerIsIn(getUserId())
}
fun exitGame() {
if (!isRunning) {
return
}
val userId = getUserId()
if (playerIsPlaying(userId)) {
logD("exitGame() 1")
// 用户正在游戏中,先退出本局游戏,再退出游戏
sudFSTAPP.notifyAPPCommonSelfPlaying(false, null, null)
sudFSTAPP.notifyAPPCommonSelfReady(false)
sudFSTAPP.notifyAPPCommonSelfIn(false, -1, true, 1)
} else if (sudFSMMG.playerIsReady(userId.toString())) {
logD("exitGame() 2")
// 用户已加入并且已经准备,先取消准备,再退出游戏
sudFSTAPP.notifyAPPCommonSelfReady(false)
sudFSTAPP.notifyAPPCommonSelfIn(false, -1, true, 1)
} else if (isSelfInGame()) {
logD("exitGame() 3")
// 用户已加入游戏 退出游戏
sudFSTAPP.notifyAPPCommonSelfIn(false, -1, true, 1)
}
}
fun destroyGame() {
if (!isRunning) {
return
}
logD("destroyGame()")
isRunning = false
sudFSTAPP.destroyMG()
sudFSMMG.destroyMG()
gameViewFlow.value = null
roomId = ""
gameId = 0
}
private fun initSDK(activity: Activity, gameId: Long, code: String) {
logD("initSDK isTestEnv() = ${isTestEnv()}")
SudMGP.initSDK(
activity,
getAppId(),
getAppKey(),
isTestEnv(),
object : ISudListenerInitSDK {
override fun onSuccess() {
loadGameSDK(activity, gameId, code)
}
override fun onFailure(code: Int, errInfo: String) {
logD("initSDK onFailure code:$code errInfo:$errInfo")
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
}
logD("loadGameSDK")
sudFSTAPP.destroyMG()
sudFSMMG.destroyMG()
sudFSMMG.setSudFSMMGListener(this)
val iSudFSTAPP =
SudMGP.loadMG(activity, getUserId().toString(), getRoomId(), code, gameId, getGameLanguage(), sudFSMMG)
sudFSTAPP.setISudFSTAPP(iSudFSTAPP)
gameViewFlow.value = iSudFSTAPP.gameView
}
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)
}
}
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 = getGameViewRect()
handle.success(JsonUtils.toJson(response))
}
override fun onStop(context: RoomContext) {
super.onStop(context)
logD("onStop")
destroyGame()
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
super.onStateChanged(source, event)
logD("onStateChanged event:$event")
when (event) {
Lifecycle.Event.ON_START -> {
sudFSTAPP.startMG()
}
Lifecycle.Event.ON_STOP -> {
sudFSTAPP.stopMG()
}
Lifecycle.Event.ON_RESUME -> {
sudFSTAPP.playMG()
}
Lifecycle.Event.ON_PAUSE -> {
sudFSTAPP.pauseMG()
}
Lifecycle.Event.ON_DESTROY -> {
destroyGame()
}
else -> {
}
}
}
override fun onGameStarted() {
logD("onGameStarted")
}
override fun onGameDestroyed() {
logD("onGameDestroyed")
}
override fun onExpireCode(handle: ISudFSMStateHandle, p1: String?) {
logD("onExpireCode")
getGameCode({
handle.success(MGStateResponse.success().toJson())
sudFSTAPP.updateCode(it, null)
}, {
logE(it)
})
}
override fun onGetGameViewInfo(handle: ISudFSMStateHandle, p1: String?) {
logD("onGetGameViewInfo")
val gameLayout = gameViewFlow.value
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)
}
})
}
override fun onGetGameCfg(handle: ISudFSMStateHandle, p1: String?) {
logD("onGetGameCfg")
handle.success(JsonUtils.toJson(GameConfigModel()))
}
override fun onGameMGCommonSelfClickJoinBtn(
handle: ISudFSMStateHandle?,
model: SudMGPMGState.MGCommonSelfClickJoinBtn?
) {
logD("onGameMGCommonSelfClickJoinBtn")
sudFSTAPP.notifyAPPCommonSelfIn(true, -1, true, 1)
}
private fun getRoomId() = roomId
private fun getGameId() = gameId
protected open fun getUserId(): Long = AuthModel.get().currentUid
protected open fun getAppId() = AppConfig.APP_ID
protected open fun getAppKey() = AppConfig.APP_KEY
protected open fun isTestEnv() = AppConfig.isTestEnv
protected open fun getGameViewRect(): GameViewInfoModel.GameViewRectModel {
return GameViewInfoModel.GameViewRectModel()
}
protected open fun getGameLanguage(): String {
return when (LanguageHelper.getCurrentLanguageType()) {
LanguageHelper.ZH -> {
"zh-TW"
}
LanguageHelper.AR -> {
"ar-SA"
}
else -> {
"en-US"
}
}
}
}

View File

@@ -0,0 +1,99 @@
package com.chwl.app.game.data
import com.chwl.core.base.BaseModel
import com.chwl.core.bean.game.GameConfigBean
import com.chwl.core.bean.game.GameRoomInfo
import com.chwl.core.bean.response.ServiceResult
import com.chwl.core.bean.room.BaseRoomInfo
import com.chwl.core.pay.PayModel
import com.chwl.core.utils.net.RxHelper
import com.chwl.core.utils.net.launchRequest
import com.chwl.library.net.rxnet.RxNet
import io.reactivex.Single
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
object GameModel2 : BaseModel() {
private val api = RxNet.create(Api::class.java)
suspend fun getHomeGameConfig(): GameConfigBean? =
launchRequest {
api.getHomeGameConfig()
}
fun getResumeGameRoomInfo(): Single<GameRoomInfo?> {
return api.getResumeGameRoom()
.compose(RxHelper.handleCommon(RxHelper.NullHandle { return@NullHandle GameRoomInfo() }))
.compose(RxHelper.handleSchAndExce())
}
suspend fun getGameRoomInfo(): GameRoomInfo? =
launchRequest {
api.getChatRoomInfo(BaseRoomInfo.ROOM_TYPE_GAME)
}
suspend fun startGame(gameId: String, gameMode: Int): String? =
launchRequest {
val result = api.startGame(gameId, gameMode)
PayModel.get().refreshWalletInfo(true)
result
}
suspend fun closeGame(roomId: Long): String? =
launchRequest {
api.closeGame(roomId)
}
fun closeGameRx(roomId: Long): Single<String?> {
return api.closeGameRx(roomId)
.compose(RxHelper.handleBeanData())
.compose(RxHelper.handleSchAndExce())
}
private interface Api {
/**
* 恢复游戏房间信息
*/
@GET("miniGame/nav/resumeRoom")
fun getResumeGameRoom(): Single<ServiceResult<GameRoomInfo>>
/**
* 房间信息
*/
@GET("chatRoom/getByType")
suspend fun getChatRoomInfo(@Query("roomType") roomType: Int): ServiceResult<GameRoomInfo>
/**
* 首页游戏配置
*/
@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>
/**
* 关闭游戏
*/
@GET("miniGame/nav/close")
suspend fun closeGame(@Query("roomId") roomId: Long): ServiceResult<String>
/**
* 关闭游戏
*/
@GET("miniGame/nav/close")
fun closeGameRx(@Query("roomId") roomId: Long):Single<ServiceResult<String>>
}
}

View File

@@ -0,0 +1,15 @@
package com.chwl.app.game.data.bean
import com.chwl.core.bean.game.GameRoomInfo
sealed class GameInfoUiState {
object Loading : GameInfoUiState()
object Success : GameInfoUiState()
data class Failed(val throwable: Throwable, val gameInfo: GameRoomInfo?) : GameInfoUiState()
data class Empty(val gameInfo: GameRoomInfo?) : GameInfoUiState()
}

View File

@@ -0,0 +1,114 @@
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.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.bean.game.GameConfigBean
import com.chwl.core.bean.game.GameModeBean
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(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(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.widget_dialog_dialoguihelper_04),
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

@@ -0,0 +1,153 @@
package com.chwl.app.game.ui.game
import android.content.Context
import android.content.Intent
import android.widget.FrameLayout
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.chwl.app.R
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.GameContext
import com.chwl.app.game.core.GameStateAbility
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.app.ui.utils.loadImage
import com.chwl.core.sud.model.GameViewInfoModel
import com.chwl.core.support.room.RoomView
import com.chwl.library.common.util.doLog
import com.example.lib_utils.ktx.getColorById
import com.example.lib_utils.ktx.singleClick
import com.example.lib_utils.log.ILog
import com.example.lib_utils.spannable.SpannableTextBuilder
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
class GameActivity : BaseGameActivity<GameActivityBinding>(), RoomView, ILog {
companion object {
fun start(context: Context, intent: GameIntent) {
context.startActivity(Intent(context, GameActivity::class.java).apply {
putExtra("intent", intent)
})
}
}
override fun init() {
val intentData = intent.getSerializableExtra("intent") as? GameIntent
if (intentData == null) {
toast(R.string.utils_net_beanobserver_05)
finish()
return
}
super.init()
viewModel.init(intentData)
}
override fun initView() {
super.initView()
}
override fun initEvent() {
super.initEvent()
binding.ivClose.singleClick {
onBackPressed()
}
}
override fun onGameContext(gameContext: GameContext) {
super.onGameContext(gameContext)
val stateAbility =
gameContext.findAbility<GameStateAbility>(GameStateAbility::class.java.simpleName)
?: return
lifecycleScope.launch {
stateAbility.gameIconFlow.collectLatest {
binding.ivLogo.loadImage(it)
}
}
lifecycleScope.launch {
stateAbility.scoresFlow.collectLatest {
"飞行棋 游戏奖励钱币的变化 ${it}".doLog()
updateAward(it?.first())
}
}
}
override fun initGameEngine(gameContext: GameContext) {
binding.spaceGameRect.post {
val rect = GameViewInfoModel.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}")
gameEngineAbility?.setGameViewRect(rect)
}
super.initGameEngine(gameContext)
}
private fun updateAward(number: Double?) {
val numberStr = number?.toInt()?.toString() ?: ""
if (numberStr.isEmpty()) {
binding.layoutAward.isInvisible = true
binding.tvAwardTips.isInvisible = true
} else {
binding.layoutAward.isVisible = true
binding.tvAwardTips.isVisible = true
binding.tvAwardValue.text = numberStr
val awardTips = getString(R.string.game_award_tips_format, numberStr)
SpannableTextBuilder(binding.tvAwardTips).appendText(awardTips)
.setTextStyle(numberStr, textColor = getColorById(R.color.color_FF6629))
.setTextStyle(getString(R.string.game_award_tips_coins_text), textColor = getColorById(R.color.color_FF6629))
.apply()
}
}
override fun initObserver() {
super.initObserver()
}
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 onBackPressed() {
// super.onBackPressed()
showExitTips()
}
override fun getGameViewGroup(): FrameLayout {
return binding.layoutGame
}
private fun showExitTips() {
val stateAbility =
getRoomContext()?.findAbility<GameStateAbility>(GameStateAbility::class.java.simpleName)
val message = if (stateAbility?.gameStateFlow?.value == GameStateAbility.STATE_MATCHING) {
getString(R.string.game_exit_tips_matching)
} else {
getString(R.string.game_exit_tips)
}
dialogManager.showOkCancelDialog(null,
message,
getString(R.string.continue_game),
getString(R.string.exit_text), false, false, object : OkCancelDialogListener {
override fun onOk() {
dialogManager.dismissDialog()
}
override fun onCancel() {
super.onCancel()
loadingDialogManager?.showProgressDialog(this@GameActivity)
viewModel.closeRoom()
}
})
}
}

View File

@@ -0,0 +1,8 @@
package com.chwl.app.game.ui.game
import androidx.annotation.Keep
import java.io.Serializable
@Keep
data class GameIntent(val gameId: Long, val gameMode: Int) : Serializable {
}

View File

@@ -0,0 +1,49 @@
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.app.game.data.GameModel2
import com.chwl.core.bean.response.BeanResult
import kotlinx.coroutines.flow.MutableSharedFlow
class GameViewModel : BaseViewModel() {
val gameContextLiveData = MutableLiveData<GameContext>()
val closeRoomFlow = MutableSharedFlow<BeanResult<String?>>()
private var gameIntent: GameIntent? = null
fun init(intent: GameIntent) {
this.gameIntent = intent
val gameContext = GameContext(0)
gameContext.performStart()
gameContextLiveData.value = gameContext
}
fun getGameIntent(): GameIntent? {
return gameIntent
}
override fun onCleared() {
super.onCleared()
gameContextLiveData.value?.performStop()
}
fun closeRoom() {
val roomId = gameContextLiveData.value?.roomId
if (roomId != null) {
safeLaunch(onError = {
closeRoomFlow.emit(BeanResult.failed(it))
}) {
GameModel2.closeGame(roomId)
closeRoomFlow.emit(BeanResult.success(null))
}
} else {
safeLaunch {
closeRoomFlow.emit(BeanResult.success(null))
}
}
}
}

View File

@@ -0,0 +1,95 @@
package com.chwl.app.game.ui.game.input
import android.app.Dialog
import android.os.Bundle
import android.view.KeyEvent
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.app.game.core.GameContext
import com.chwl.app.game.core.GameMessageAbility
import com.chwl.core.support.room.RoomContext
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(private var onSend: ((GameMessageInputDialog, String) -> Unit)?) :
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?.inputEdit?.setOnKeyListener { _, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_UP) {
send()
return@setOnKeyListener true
}
false
}
binding?.inputSend?.setOnClickListener {
send()
}
}
private fun send() {
val text = binding?.inputEdit?.text?.toString()?.trim()
onSend?.invoke(this, text ?: "")
}
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)
}
}
override fun onDestroy() {
super.onDestroy()
onSend = null
}
}

View File

@@ -0,0 +1,74 @@
package com.chwl.app.game.ui.game.widgets.bottom
import android.content.Context
import android.text.TextUtils
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.core.GameMessageAbility
import com.chwl.app.game.ui.game.input.GameMessageInputDialog
import com.chwl.core.support.room.FrameLayoutRoomWidget
import com.chwl.library.utils.ResUtil
import com.chwl.library.utils.SingleToastUtil
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 {
roomContext?.let { context ->
GameMessageInputDialog { dialog, text ->
sendMessage(dialog, text)
}.show(
it.getViewFragmentManager(),
"MESSAGE_INPUT"
)
}
}
}
}
private fun sendMessage(dialog: GameMessageInputDialog, message: String) {
if (TextUtils.isEmpty(message)) {
SingleToastUtil.showToast(ResUtil.getString(R.string.avroom_fragment_baseroomfragment_08))
return
}
val messageAbility =
roomContext?.findAbility<GameMessageAbility>(GameMessageAbility::class.java.simpleName)
if (messageAbility == null) {
dialog.dismissAllowingStateLoss()
SingleToastUtil.showToast(ResUtil.getString(R.string.retryTips))
return
}
addDisposable(messageAbility.sendTextMessage(message).subscribe({
dialog.dismissAllowingStateLoss()
}, {
dialog.dismissAllowingStateLoss()
SingleToastUtil.showToast(it.message)
}))
}
}

View File

@@ -0,0 +1,224 @@
package com.chwl.app.game.ui.game.widgets.message
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.text.style.ForegroundColorSpan
import android.text.style.ImageSpan
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.RecyclerView
import com.chwl.app.R
import com.chwl.app.common.widget.CustomImageSpan
import com.chwl.app.ui.utils.ImageLoadUtils
import com.chwl.app.ui.widget.TextSpannableBuilder
import com.chwl.core.auth.AuthModel
import com.chwl.core.level.UserLevelResourceType
import com.chwl.core.manager.AvRoomDataManager
import com.chwl.core.noble.NobleUtil
import com.chwl.core.user.bean.UserInfo
import com.chwl.library.common.util.Utils
import com.chwl.library.utils.ResUtil
import com.chwl.library.utils.SizeUtils
import com.example.lib_utils.AppUtils
import com.example.lib_utils.UiUtils.isRtl
import com.example.lib_utils.ktx.getColorById
import com.netease.nim.uikit.business.session.emoji.MoonUtil
import com.netease.nim.uikit.common.util.sys.ScreenUtil
import com.netease.nimlib.sdk.chatroom.model.ChatRoomMessage
import com.netease.nimlib.sdk.msg.constant.MsgTypeEnum
class GameMessageAdapter(val dataList: MutableList<Any>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>(), View.OnClickListener {
private val itemPaddingHorizontal = Utils.dip2px(AppUtils.getApp(), 11f)
private val itemPaddingVertical = Utils.dip2px(AppUtils.getApp(), 6f)
private val expLevelHeight = Utils.dip2px(AppUtils.getApp(), 18f)
private val badgeWidth = Utils.dip2px(AppUtils.getApp(), 15f)
private val badgeHeight = Utils.dip2px(AppUtils.getApp(), 15f)
private val greyColor = ContextCompat.getColor(AppUtils.getApp(), R.color.white_transparent_50)
private val imageSpanFactory = object : Function1<Drawable, ImageSpan> {
override fun invoke(p1: Drawable): ImageSpan {
return CustomImageSpan(p1)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameMessageNormalViewHolder {
return GameMessageNormalViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.game_message_widget_item_text, parent, false)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
val viewHolder = holder as GameMessageNormalViewHolder
viewHolder.textView.let {
it.setLineSpacing(0f, 1f)
it.setTextColor(Color.WHITE)
it.textSize = 12f
it.setOnClickListener(this)
it.setOnLongClickListener(null)
it.tag = item
it.gravity = Gravity.START or Gravity.CENTER_VERTICAL
if (isRtl(it.context)) {
it.textDirection = View.TEXT_DIRECTION_RTL
}
resetItemBackground(it)
}
if (item is ChatRoomMessage) {
if (item.msgType == MsgTypeEnum.text) {
convertText(viewHolder, item)
} else if (item.msgType == MsgTypeEnum.tip) {
convertTips(viewHolder, item)
}
}
}
private fun convertTips(holder: GameMessageNormalViewHolder, message: ChatRoomMessage) {
val textView = holder.textView
textView.setTextColor(ContextCompat.getColor(textView.context, R.color.color_white))
textView.text = message.content
textView.setBackgroundResource(R.drawable.shape_room_message_tip_bg)
}
private fun convertText(holder: GameMessageNormalViewHolder, message: ChatRoomMessage) {
val textView = holder.textView
val text = TextSpannableBuilder(textView)
addUserTag(message, text, textView)
val nickName =
if (message.fromAccount != null && message.fromAccount == AuthModel.get().currentUid.toString() + "") {
ResUtil.getString(R.string.avroom_widget_messageview_0116)
} else {
NobleUtil.getNamePlate(UserInfo.NICK, message)
}
text.append(nickName, ForegroundColorSpan(greyColor))
text.append(
": " + message.content,
ForegroundColorSpan(textView.context.getColorById(R.color.white))
)
MoonUtil.replaceEmoticons(
textView.context,
text.builder.toString(),
text.builder,
imageSpanFactory
)
textView.text = text.build()
loadItemBackgroundVip(message, textView)
}
private fun addUserTag(
chatRoomMessage: ChatRoomMessage,
builder: TextSpannableBuilder,
textView: TextView
) {
val extension = chatRoomMessage.chatRoomMessageExtension
val userLevel = NobleUtil.getLevel(UserLevelResourceType.EXPER_URL, chatRoomMessage)
val isOfficial = NobleUtil.getIsOfficial(UserInfo.IS_OFFICIAL, chatRoomMessage)
val vipIcon = NobleUtil.getResource(UserInfo.VIP_ICON, chatRoomMessage)
builder.append(vipIcon, expLevelHeight)
.append(
if (isOfficial) ResourcesCompat.getDrawable(
textView.resources,
R.mipmap.ic_user_official_13dp, null
) else null,
badgeWidth, badgeHeight
)
.append(getNewUserDrawable(textView.context, chatRoomMessage), badgeWidth, badgeHeight)
.append(
if (AvRoomDataManager.get()
.isSuperAdmin(chatRoomMessage.fromAccount)
) ResourcesCompat.getDrawable(
textView.resources,
R.drawable.ic_room_super_admin, null
) else null,
SizeUtils.dp2px(textView.context, 23f), expLevelHeight
)
// 官方主播認證
val tvOfficialMask =
NobleUtil.getLevel(UserInfo.OAC_NAME, chatRoomMessage).trim { it <= ' ' }
val ivOfficialMask = NobleUtil.getLevel(UserInfo.OAC_ICON, chatRoomMessage)
if (!TextUtils.isEmpty(tvOfficialMask) && !TextUtils.isEmpty(ivOfficialMask) && extension != null) { // extension != null 表示自己
builder.appendBgAndContent(ivOfficialMask, tvOfficialMask)
} else if (!TextUtils.isEmpty(ivOfficialMask)) {
builder.append(ivOfficialMask, SizeUtils.dp2px(textView.context, 62f), expLevelHeight)
}
//等級
builder.append(userLevel, expLevelHeight)
//銘牌
val tvNamePlate =
NobleUtil.getNamePlate(UserInfo.NAMEPLATE_WORD, chatRoomMessage).trim { it <= ' ' }
val ivNamePlate = NobleUtil.getNamePlate(UserInfo.NAMEPLATE_PIC, chatRoomMessage)
if (!TextUtils.isEmpty(tvNamePlate) && !TextUtils.isEmpty(ivNamePlate)) { // extension != null 表示自己
builder.appendBgAndContent(ivNamePlate, tvNamePlate)
} else if (!TextUtils.isEmpty(ivNamePlate)) {
builder.append(ivNamePlate, expLevelHeight)
}
}
private fun getNewUserDrawable(context: Context, chatRoomMessage: ChatRoomMessage): Drawable? {
val newUser = NobleUtil.getIsNewUser(UserInfo.IS_NEW_USER, chatRoomMessage)
val isHelloUser =
NobleUtil.getIsNewUser(UserInfo.IS_FROM_SAY_HELLO_CHANNEL, chatRoomMessage)
return if (newUser) {
ResourcesCompat.getDrawable(
context.resources,
if (isHelloUser) R.drawable.ic_new_user_hello else R.drawable.ic_new_user,
null
)
} else null
}
private fun resetItemBackground(textView: TextView) {
textView.text = ""
textView.setBackgroundResource(R.drawable.shape_room_message_bg)
textView.setPadding(
itemPaddingHorizontal,
itemPaddingVertical,
itemPaddingHorizontal,
itemPaddingVertical
)
}
private fun loadItemBackgroundVip(chatRoomMessage: ChatRoomMessage?, view: View) {
val androidBubbleUrl = NobleUtil.getResource(UserInfo.BUBBLE_URL_ANDROID, chatRoomMessage)
if (TextUtils.isEmpty(androidBubbleUrl)) return
view.setPadding(
itemPaddingHorizontal,
ScreenUtil.dip2px(10f),
itemPaddingHorizontal,
ScreenUtil.dip2px(10f)
)
ImageLoadUtils.loadNinePatchBg(view, androidBubbleUrl)
}
fun setDataList(list: List<Any>) {
dataList.clear()
dataList.addAll(list)
notifyDataSetChanged()
}
fun addData(item: Any) {
dataList.add(item)
notifyItemInserted(dataList.size)
}
fun getItem(position: Int): Any? {
return dataList.get(position)
}
override fun getItemCount(): Int {
return dataList.size
}
override fun onClick(v: View) {
}
}

View File

@@ -0,0 +1,12 @@
package com.chwl.app.game.ui.game.widgets.message
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.chwl.app.R
class GameMessageNormalViewHolder(val itemView: View) : RecyclerView.ViewHolder(itemView) {
var textView: TextView = itemView.findViewById(R.id.tv_content)
}

View File

@@ -0,0 +1,121 @@
package com.chwl.app.game.ui.game.widgets.message
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.chwl.app.R
import com.chwl.app.game.core.GameMessageAbility
import com.chwl.app.ui.widget.DividerItemDecoration
import com.chwl.app.ui.widget.MyItemAnimator
import com.chwl.app.ui.widget.RecyclerViewNoViewpagerScroll
import com.chwl.core.support.room.FrameLayoutRoomWidget
import com.chwl.core.support.room.RoomContext
import com.chwl.core.support.room.RoomView
class GameMessageWidget : FrameLayoutRoomWidget, GameMessageAbility.Listener {
private val recyclerView = RecyclerViewNoViewpagerScroll(context)
private val adapter = GameMessageAdapter(ArrayList())
private var isAtBottom = 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 {
initListView()
initEvent()
}
private fun initListView() {
recyclerView.layoutParams =
ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
recyclerView.setFadingEdgeLength(60)
recyclerView.isVerticalFadingEdgeEnabled = true
recyclerView.overScrollMode = OVER_SCROLL_NEVER
recyclerView.isHorizontalScrollBarEnabled = false
addView(recyclerView)
val layoutManger = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
recyclerView.layoutManager = layoutManger
recyclerView.addItemDecoration(
DividerItemDecoration(
context,
layoutManger.orientation,
16,
R.color.transparent
)
)
recyclerView.itemAnimator = MyItemAnimator().apply {
this.supportsChangeAnimations = false
this.addDuration = 0
this.changeDuration = 0
this.moveDuration = 0
this.removeDuration = 0
}
recyclerView.adapter = adapter
}
private fun initEvent() {
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
val lastPosition = layoutManager.findLastVisibleItemPosition()
if (lastPosition == RecyclerView.NO_POSITION) {
isAtBottom = true
} else isAtBottom = lastPosition == adapter.itemCount - 1
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy < 0) {
isAtBottom = false
}
}
})
}
override fun onInitialize(roomView: RoomView, roomContext: RoomContext) {
super.onInitialize(roomView, roomContext)
val messageAbility =
roomContext.findAbility<GameMessageAbility>(GameMessageAbility::class.java.simpleName)
messageAbility?.getMessageList()?.let {
adapter.setDataList(it)
}
messageAbility?.addListener(this)
}
override fun onAddMessage(message: Any) {
adapter.addData(message)
tryScrollToBottom()
}
override fun onReplaceMessageList(list: List<Any>) {
adapter.setDataList(list)
tryScrollToBottom()
}
private fun tryScrollToBottom() {
logD("tryScrollToBottom isAtBottom:$isAtBottom")
if (isAtBottom) {
if (adapter.itemCount > 0) {
recyclerView.smoothScrollToPosition(adapter.itemCount - 1)
}
}
}
}

View File

@@ -0,0 +1,96 @@
package com.chwl.app.game.ui.game.widgets.queue
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.GameQueueWidgetBinding
import com.chwl.app.game.core.GameQueueAbility
import com.chwl.app.game.core.GameStateAbility
import com.chwl.app.ui.utils.loadAvatar
import com.chwl.core.auth.AuthModel
import com.chwl.core.bean.room.RoomMicBean
import com.chwl.core.support.room.FrameLayoutRoomWidget
import com.chwl.core.support.room.RoomContext
import com.chwl.core.support.room.RoomView
import kotlinx.coroutines.flow.collectLatest
class GameQueueWidget : FrameLayoutRoomWidget {
private val binding: GameQueueWidgetBinding =
DataBindingUtil.inflate(
LayoutInflater.from(
context
), R.layout.game_queue_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)
override fun onInitialize(roomView: RoomView, roomContext: RoomContext) {
super.onInitialize(roomView, roomContext)
val stateAbility =
roomContext.findAbility<GameStateAbility>(GameStateAbility::class.java.simpleName)
val queueAbility =
roomContext.findAbility<GameQueueAbility>(GameQueueAbility::class.java.simpleName)
queueAbility?.let {
safeLaunch {
it.queueFlow.collectLatest {
updateQueue(it)
}
}
}
stateAbility?.let {
safeLaunch {
it.gameStateFlow.collectLatest {
updateState(it)
}
}
}
}
private fun updateQueue(list: List<RoomMicBean>?) {
val selfInfo = list?.firstOrNull {
it.micUser?.uid == AuthModel.get().currentUid
}
val targetInfo = list?.firstOrNull {
it.micUser?.uid != AuthModel.get().currentUid
}
binding.ivQueue1.loadAvatar(selfInfo?.micUser?.avatar)
binding.ivQueue2.loadAvatar(targetInfo?.micUser?.avatar)
}
private fun updateState(gameState: Int?) {
when (gameState) {
GameStateAbility.STATE_MATCH_SUCCESS, GameStateAbility.STATE_GAME_END -> {
binding.tvState.setText(R.string.match_successfully)
binding.ivState.setImageResource(R.drawable.game_ic_vs)
}
GameStateAbility.STATE_MATCH_FAILED -> {
binding.tvState.setText(R.string.match_failed)
binding.ivState.setImageResource(R.drawable.game_ic_link)
}
else -> {
binding.tvState.setText(R.string.matchmaking)
binding.ivState.setImageResource(R.drawable.game_ic_link)
}
}
}
}

View File

@@ -0,0 +1,14 @@
package com.chwl.app.game.ui.home
import android.widget.ImageView
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseViewHolder
import com.chwl.app.R
import com.chwl.core.bean.game.GameModeBean
import com.chwl.app.ui.utils.load
class GameHomeAdapter : BaseQuickAdapter<GameModeBean, BaseViewHolder>(R.layout.game_home_item) {
override fun convert(helper: BaseViewHolder, item: GameModeBean?) {
helper.getView<ImageView>(R.id.iv_game).load(item?.modeIcon)
}
}

View File

@@ -0,0 +1,139 @@
package com.chwl.app.game.ui.home
import androidx.fragment.app.activityViewModels
import com.chwl.app.MainTabContentView
import com.chwl.app.base.BaseViewBindingFragment
import com.chwl.app.databinding.GameHomeFragmentBinding
import com.chwl.app.game.ui.buy.GameBuyDialog
import com.chwl.app.support.FragmentVisibleStateHelper
import com.chwl.app.ui.pay.ChargeActivity
import com.chwl.app.ui.utils.loadAvatar
import com.chwl.app.ui.webview.CommonWebViewActivity
import com.chwl.app.ui.widget.recyclerview.decoration.GridSpacingItemNewDecoration
import com.chwl.app.utils.HomeUIManager
import com.chwl.core.UriProvider
import com.chwl.core.bean.game.GameConfigBean
import com.chwl.core.pay.PayModel
import com.chwl.core.pay.event.GetWalletInfoEvent
import com.chwl.core.pay.event.UpdateWalletInfoEvent
import com.chwl.core.user.UserModel
import com.chwl.core.user.event.LoginUserInfoUpdateEvent
import com.chwl.core.utils.CoreTextUtils
import com.chwl.library.utils.FormatUtils
import com.example.lib_utils.UiUtils
import com.example.lib_utils.ktx.singleClick
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class GameHomeFragment : BaseViewBindingFragment<GameHomeFragmentBinding>(), MainTabContentView {
private val adapter = GameHomeAdapter()
private val viewModel: GameHomeViewModel by activityViewModels()
override fun init() {
EventBus.getDefault().register(this)
initView()
initEvent()
initObserver()
FragmentVisibleStateHelper(this).start {
onVisibleChanged(it)
}
PayModel.get().refreshWalletInfo(true)
refreshWalletInfo()
refreshUserInfo()
}
private fun initView() {
HomeUIManager.setHomeUI(binding.root)
binding.recyclerView.addItemDecoration(
GridSpacingItemNewDecoration(
UiUtils.dip2px(10f),
UiUtils.dip2px(18.5f),
false
)
)
binding.recyclerView.adapter = adapter
}
private fun initEvent() {
binding.layoutWallet.singleClick {
ChargeActivity.start(context)
}
binding.ivRank.singleClick {
CommonWebViewActivity.start(requireContext(), UriProvider.getGameRank())
}
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)
} else {
toast(it.message)
}
}
}
private fun onVisibleChanged(isVisible: Boolean) {
if (isVisible) {
if (viewModel.gameConfigLiveData.value?.isSuccess != true) {
viewModel.getGameList()
}
}
}
private fun loadGameInfo(config: GameConfigBean) {
// binding.ivGameLogo.load(config.pic)
adapter.setNewData(config.gameModes)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onWalletInfoUpdate(event: UpdateWalletInfoEvent?) {
if (_binding == null) {
return
}
refreshWalletInfo()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onGetWalletInfoEvent(event: GetWalletInfoEvent?) {
if (_binding == null) {
return
}
refreshWalletInfo()
}
private fun refreshWalletInfo() {
binding.tvWalletValue.text =
FormatUtils.formatBigInteger(PayModel.get().currentWalletInfo?.diamondNum ?: 0.0)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onLoginUserInfoUpdateEvent(event: LoginUserInfoUpdateEvent?) {
if (_binding == null) {
return
}
refreshUserInfo()
}
private fun refreshUserInfo() {
val userInfo = UserModel.get().cacheLoginUserInfo
binding.ivAvatar.loadAvatar(if (CoreTextUtils.isEmptyText(userInfo?.newAvatar)) userInfo?.avatar else userInfo?.newAvatar)
}
override fun onDestroy() {
super.onDestroy()
EventBus.getDefault().unregister(this)
}
}

View File

@@ -0,0 +1,33 @@
package com.chwl.app.game.ui.home
import androidx.lifecycle.MutableLiveData
import com.chwl.app.base.BaseViewModel
import com.chwl.app.game.data.GameModel2
import com.chwl.core.bean.game.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 startGameFlow = MutableSharedFlow<BeanResult<String?>>()
fun getGameList() {
safeLaunch(onError = {
gameConfigLiveData.postValue(BeanResult.Companion.failed(it))
}) {
val configBean = GameModel2.getHomeGameConfig()
gameConfigLiveData.postValue(BeanResult.success(configBean))
}
}
fun startGame(gameId: String, gameMode: Int) {
safeLaunch(onError = {
startGameFlow.emit(BeanResult.Companion.failed(it))
}) {
GameModel2.startGame(gameId, gameMode)
startGameFlow.emit(BeanResult.success(null))
}
}
}

View File

@@ -0,0 +1,44 @@
package com.chwl.app.game.ui.result
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseViewHolder
import com.chwl.app.R
import com.chwl.core.bean.game.GameResultBean
import com.chwl.app.ui.utils.loadAvatar
class GameResultAdapter :
BaseQuickAdapter<GameResultBean, BaseViewHolder>(R.layout.game_result_item) {
override fun convert(helper: BaseViewHolder, item: GameResultBean?) {
helper.setText(R.id.tv_name, item?.nick ?: "")
helper.setText(R.id.tv_coins, item?.getScoreStr() ?: "0")
helper.getView<ImageView>(R.id.iv_user_avatar).loadAvatar(item?.avatar)
val rank = item?.rank ?: (helper.bindingAdapterPosition + 1)
val rankView = helper.getView<TextView>(R.id.tv_rank)
val rootView = helper.getView<View>(R.id.layout_root)
val coinsBgView = helper.getView<View>(R.id.iv_coins_bg)
val avatarBorderView = helper.getView<View>(R.id.iv_user_avatar_border)
if (rank == 1) {
avatarBorderView.setBackgroundResource(R.drawable.game_result_avatar_border_top_1)
rankView.setBackgroundResource(R.drawable.game_result_ic_top1)
rankView.text = ""
} else if (rank == 2) {
avatarBorderView.setBackgroundResource(R.drawable.game_result_avatar_border_top_2)
rankView.setBackgroundResource(R.drawable.game_result_ic_top2)
rankView.text = ""
} else {
avatarBorderView.background = null
rankView.background = null
rankView.text = "$rank"
}
if (rank % 2 == 1) {
rootView.setBackgroundResource(R.drawable.game_result_item_bg_top1)
coinsBgView.setBackgroundResource(R.drawable.game_result_coins_top1)
} else {
rootView.setBackgroundResource(R.drawable.game_result_item_bg_top2)
coinsBgView.setBackgroundResource(R.drawable.game_result_coins_top2)
}
}
}

View File

@@ -0,0 +1,93 @@
package com.chwl.app.game.ui.result
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.fragment.app.DialogFragment
import com.alibaba.fastjson2.JSON
import com.chwl.app.R
import com.chwl.app.databinding.GameResultDialogBinding
import com.chwl.app.ui.widget.recyclerview.decoration.SpacingDecoration
import com.chwl.core.auth.AuthModel
import com.chwl.core.bean.game.GameResultBean
import com.chwl.library.common.util.Utils
import com.example.lib_utils.ktx.singleClick
import com.example.lib_utils.log.ILog
import com.example.lib_utils.log.LogUtil
class GameResultDialog(
private val list: List<GameResultBean>,
private var onClose: (() -> Unit)?,
private var onRestart: (() -> Unit)?
) : DialogFragment(), ILog {
private var binding: GameResultDialogBinding? = null
private val adapter = GameResultAdapter()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
adapter.setNewData(list.sortedBy { it.rank })
val isWin = list.firstOrNull {
it.uid == AuthModel.get().currentUid
}?.isWin
if (isWin == true) {
binding?.ivState?.setImageResource(R.drawable.game_result_bg_win)
} else {
binding?.ivState?.setImageResource(R.drawable.game_result_bg_fail)
}
}
private fun initView() {
LogUtil.d(" 游戏结束 弹窗 ${JSON.toJSONString(list)}")
binding?.tvClose?.singleClick {
onClose?.invoke()
}
binding?.tvRestart?.singleClick {
onRestart?.invoke()
}
binding?.recyclerView?.addItemDecoration(
SpacingDecoration(
0,
Utils.dip2px(context, 10f),
false
)
)
binding?.recyclerView?.adapter = adapter
}
override fun onDestroyView() {
super.onDestroyView()
onClose = null
onRestart = null
}
override fun getTheme(): Int {
return R.style.full_screen_dialog
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = GameResultDialogBinding.inflate(LayoutInflater.from(context))
return binding?.root
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
this.setCancelable(false)
this.setCanceledOnTouchOutside(false)
window?.setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT
)
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="@dimen/dp_25" />
<solid android:color="#66006263" />
<stroke
android:width="@dimen/dp_0_5"
android:color="#36C0C8" />
</shape>

View File

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

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="270"
android:centerColor="#BEFFEB"
android:endColor="#F9FFFD"
android:startColor="#FAFEFD" />
</shape>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="@dimen/dp_18" />
<solid android:color="#FAF6D3" />
<stroke
android:width="@dimen/dp_0_5"
android:color="#FFE358" />
</shape>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#1ADCE5" />
<corners android:radius="@dimen/dp_16" />
</shape>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#E2F6FF" />
<corners android:radius="@dimen/dp_12" />
</shape>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners
android:radius="@dimen/dp_5"/>
<solid android:color="#9B4300" />
<stroke
android:width="@dimen/dp_1"
android:color="#FFBC1E" />
</shape>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners
android:radius="@dimen/dp_5"/>
<solid android:color="#00666B" />
<stroke
android:width="@dimen/dp_1"
android:color="#FCFF1E" />
</shape>

View File

@@ -0,0 +1,145 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/game_bg" />
<FrameLayout
android:id="@+id/layout_game"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Space
android:id="@+id/space_title_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_44"
android:layout_marginTop="@dimen/dp_44"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_logo"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="@dimen/dp_11"
app:layout_constraintBottom_toBottomOf="@id/space_title_bar"
app:layout_constraintDimensionRatio="90:80"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/space_title_bar"
app:layout_constraintWidth_percent="0.24" />
<ImageView
android:id="@+id/iv_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/dp_21"
android:scaleType="fitCenter"
android:src="@drawable/game_ic_close"
app:layout_constraintBottom_toBottomOf="@id/space_title_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/space_title_bar" />
<com.chwl.app.game.ui.game.widgets.queue.GameQueueWidget
tools:ignore="Instantiatable"
android:id="@+id/queue_widget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_13"
app:layout_constraintTop_toTopOf="@id/space_title_bar" />
<Space
android:id="@+id/space_award_tips_top"
android:layout_width="wrap_content"
android:layout_height="@dimen/dp_3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/queue_widget" />
<TextView
android:id="@+id/tv_award_tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="@dimen/dp_14"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/space_award_tips_top"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="@string/game_award_tips_format"
tools:visibility="visible" />
<FrameLayout
android:id="@+id/layout_award"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_21"
android:layout_marginTop="@dimen/dp_5"
android:visibility="invisible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_award_tips"
tools:visibility="visible">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="@dimen/dp_25"
android:layout_gravity="start|center_vertical"
android:background="@drawable/game_bg_award">
<TextView
android:id="@+id/tv_award_value"
android:layout_width="wrap_content"
android:layout_height="@dimen/dp_25"
android:layout_gravity="start"
android:layout_marginStart="@dimen/dp_21"
android:gravity="center|center_vertical"
android:includeFontPadding="false"
android:minWidth="@dimen/dp_56"
android:paddingStart="@dimen/dp_10"
android:paddingEnd="@dimen/dp_10"
android:textColor="@color/white"
android:textSize="@dimen/dp_17"
tools:text="0" />
</FrameLayout>
<ImageView
android:id="@+id/iv_award"
android:layout_width="@dimen/dp_26"
android:layout_height="@dimen/dp_26"
android:layout_gravity="start|center_vertical"
android:src="@drawable/ic_coin_84" />
</FrameLayout>
<com.chwl.app.game.ui.game.widgets.message.GameMessageWidget
tools:ignore="Instantiatable"
android:id="@+id/message_widget"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/bottom_widget"
app:layout_constraintDimensionRatio="283:129"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.277"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_percent="0.75" />
<com.chwl.app.game.ui.game.widgets.bottom.GameBottomWidget
tools:ignore="Instantiatable"
android:id="@+id/bottom_widget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" />
<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/layout_award" />
</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

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/game_buy_bg"
app:layout_constraintDimensionRatio="375:252"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:textColor="@color/white"
android:textSize="@dimen/dp_18"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.13"
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_21"
android:src="@drawable/game_buy_ic_help"
app:layout_constraintBottom_toBottomOf="@id/tv_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_title" />
<ImageView
android:id="@+id/iv_body"
android:layout_width="0dp"
android:layout_height="0dp"
android:src="@drawable/game_buy_bg_body"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="348.5:162.5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.77"
app:layout_constraintWidth_percent="0.929" />
<ImageView
android:id="@+id/iv_coins_bg"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/base_shape_00757b_8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="297:44"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.792" />
<ImageView
android:id="@+id/iv_coins"
android:layout_width="@dimen/dp_22"
android:layout_height="@dimen/dp_22"
android:layout_marginEnd="@dimen/dp_6"
android:src="@drawable/ic_coin_84"
app:layout_constraintBottom_toBottomOf="@id/iv_coins_bg"
app:layout_constraintEnd_toStartOf="@id/tv_coins"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/iv_coins_bg" />
<TextView
android:id="@+id/tv_coins"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#F2EF25"
android:textSize="@dimen/dp_18"
app:layout_constraintBottom_toBottomOf="@id/iv_coins_bg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/iv_coins"
app:layout_constraintTop_toTopOf="@id/iv_coins_bg"
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"
android:gravity="center"
android:maxLines="1"
android:paddingHorizontal="@dimen/dp_5"
android:text="@string/start"
android:textColor="#A75906"
android:textSize="@dimen/dp_20"
app:autoSizeMaxTextSize="@dimen/dp_20"
app:autoSizeMinTextSize="@dimen/dp_12"
app:autoSizeStepGranularity="1px"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.809" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,150 @@
<?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:id="@+id/homeRootView"
android:layout_height="match_parent"
android:background="@color/color_FCF4DF">
<ImageView
android:id="@+id/homeTopImg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="@drawable/bg_theme_top_home"
app:layout_constraintTop_toTopOf="parent" />
<Space
android:id="@+id/space_title_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_44"
android:layout_marginTop="@dimen/dp_44"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_avatar"
android:layout_width="@dimen/dp_34"
android:layout_height="@dimen/dp_34"
android:layout_marginStart="@dimen/dp_20"
android:src="@drawable/default_avatar"
app:layout_constraintBottom_toBottomOf="@id/space_title_bar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/space_title_bar"
app:shapeAppearance="@style/shape_circle" />
<!-- <ImageView-->
<!-- android:id="@+id/iv_avatar_border"-->
<!-- android:layout_width="@dimen/dp_37"-->
<!-- android:layout_height="@dimen/dp_37"-->
<!-- android:src="@drawable/game_home_ic_avatar_border"-->
<!-- app:layout_constraintBottom_toBottomOf="@id/iv_avatar"-->
<!-- app:layout_constraintEnd_toEndOf="@id/iv_avatar"-->
<!-- app:layout_constraintStart_toStartOf="@id/iv_avatar"-->
<!-- app:layout_constraintTop_toTopOf="@id/iv_avatar" />-->
<LinearLayout
android:id="@+id/layout_wallet"
android:layout_width="wrap_content"
android:layout_height="@dimen/dp_36"
android:layout_marginStart="@dimen/dp_13"
android:background="@drawable/game_home_bg_wallet"
android:gravity="center_vertical"
android:paddingEnd="@dimen/dp_41"
app:layout_constraintBottom_toBottomOf="@id/space_title_bar"
app:layout_constraintStart_toEndOf="@id/iv_avatar"
app:layout_constraintTop_toTopOf="@id/space_title_bar">
<ImageView
android:id="@+id/iv_wallet_icon"
android:layout_width="@dimen/dp_22"
android:layout_height="@dimen/dp_22"
android:layout_marginStart="@dimen/dp_6"
android:src="@drawable/ic_coin_84" />
<TextView
android:id="@+id/tv_wallet_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_2"
android:ellipsize="end"
android:maxLines="1"
android:text=" "
android:textColor="#273132"
android:textSize="@dimen/dp_17"
tools:text="123" />
</LinearLayout>
<ImageView
android:layout_width="@dimen/dp_37"
android:layout_height="@dimen/dp_37"
android:src="@drawable/game_home_ic_add"
app:layout_constraintBottom_toBottomOf="@id/layout_wallet"
app:layout_constraintEnd_toEndOf="@id/layout_wallet"
app:layout_constraintTop_toTopOf="@id/layout_wallet" />
<ImageView
android:id="@+id/iv_rank"
android:layout_width="wrap_content"
android:layout_height="@dimen/dp_44"
android:layout_marginEnd="@dimen/dp_21"
android:src="@drawable/game_home_ic_rank"
app:layout_constraintBottom_toBottomOf="@id/space_title_bar"
app:layout_constraintEnd_toEndOf="@id/space_title_bar"
app:layout_constraintTop_toTopOf="@id/space_title_bar" />
<!-- <TextView-->
<!-- android:id="@+id/tv_rank"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginHorizontal="@dimen/dp_3"-->
<!-- android:gravity="center"-->
<!-- android:includeFontPadding="false"-->
<!-- android:maxLines="1"-->
<!-- android:text="Rank"-->
<!-- android:textColor="#FFEB6F"-->
<!-- android:textSize="@dimen/dp_9"-->
<!-- app:autoSizeMaxTextSize="@dimen/dp_9"-->
<!-- app:autoSizeMinTextSize="@dimen/dp_6"-->
<!-- app:autoSizeStepGranularity="1px"-->
<!-- app:autoSizeTextType="uniform"-->
<!-- app:layout_constraintBottom_toBottomOf="@id/iv_rank"-->
<!-- app:layout_constraintEnd_toEndOf="@id/iv_rank"-->
<!-- app:layout_constraintStart_toStartOf="@id/iv_rank" />-->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/line_logo_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.184" />
<ImageView
android:id="@+id/iv_game_logo"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
android:src="@drawable/ludo_game_logo"
app:layout_constraintDimensionRatio="187:139"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/line_logo_top"
app:layout_constraintWidth_percent="0.377" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="@dimen/dp_10"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_game_logo"
app:layout_constraintVertical_bias="1.0"
app:spanCount="2"
tools:layout_editor_absoluteX="-23dp"
tools:listitem="@layout/game_home_item" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,17 @@
<?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">
<ImageView
android:id="@+id/iv_game"
android:layout_width="match_parent"
android:layout_height="0dp"
android:scaleType="centerCrop"
android:src="@drawable/default_cover"
app:layout_constraintDimensionRatio="177.5:247"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,39 @@
<?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:imeOptions="actionSend"
android:inputType="text"
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

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:background="#ff0">
<com.chwl.app.avroom.widget.FixRoomTitleTextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:background="@drawable/shape_room_message_bg"
android:gravity="start|center_vertical"
android:textColor="@android:color/white"
android:textSize="12dp"
tools:text="@string/layout_list_item_chatrrom_msg_01"
tools:textColor="#000" />
</FrameLayout>

View File

@@ -0,0 +1,82 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="@dimen/dp_18"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
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: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"
app:layout_constraintWidth_percent="0.242"
tools:src="@drawable/game_ic_vs" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_queue1"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="@dimen/dp_2"
android:src="@drawable/default_avatar"
app:layout_constraintBottom_toBottomOf="@id/iv_queue1_border"
app:layout_constraintEnd_toEndOf="@id/iv_queue1_border"
app:layout_constraintStart_toStartOf="@id/iv_queue1_border"
app:layout_constraintTop_toTopOf="@id/iv_queue1_border" />
<ImageView
android:id="@+id/iv_queue1_border"
android:layout_width="0dp"
android:layout_height="0dp"
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.311"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.152" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_queue2"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="@dimen/dp_2"
android:src="@drawable/default_avatar"
app:layout_constraintBottom_toBottomOf="@id/iv_queue2_border"
app:layout_constraintEnd_toEndOf="@id/iv_queue2_border"
app:layout_constraintStart_toStartOf="@id/iv_queue2_border"
app:layout_constraintTop_toTopOf="@id/iv_queue2_border" />
<ImageView
android:id="@+id/iv_queue2_border"
android:layout_width="0dp"
android:layout_height="0dp"
android:src="@drawable/game_ic_queue_avatar_border"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.688"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/iv_queue1_border"
app:layout_constraintWidth_percent="0.152" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/layout_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@drawable/game_result_bg_1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.47"
app:layout_constraintWidth_percent="0.832">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dp_7"
android:layout_marginTop="@dimen/dp_41"
android:layout_marginBottom="@dimen/dp_8"
android:background="@drawable/game_result_bg_2">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dp_13"
android:layout_marginTop="@dimen/dp_15"
android:maxHeight="@dimen/dp_150"
android:minHeight="@dimen/dp_120"
android:orientation="vertical"
android:scrollbars="none"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:layout_height="@dimen/dp_120" />
<TextView
android:id="@+id/tv_close"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="@dimen/dp_14"
android:layout_marginTop="@dimen/dp_30"
android:layout_marginBottom="@dimen/dp_20"
android:background="@drawable/game_result_bg_close"
android:gravity="center"
android:text="@string/close"
android:textColor="@color/white"
android:textSize="@dimen/dp_18"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="132:40"
app:layout_constraintEnd_toStartOf="@id/tv_restart"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/recycler_view" />
<TextView
android:id="@+id/tv_restart"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="@dimen/dp_6"
android:layout_marginEnd="@dimen/dp_14"
android:background="@drawable/game_result_bg_restart"
android:gravity="center"
android:text="@string/game_rematch"
android:textColor="@color/white"
android:textSize="@dimen/dp_18"
app:layout_constraintDimensionRatio="132:40"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_close"
app:layout_constraintTop_toTopOf="@id/tv_close" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
<androidx.legacy.widget.Space
android:id="@+id/space_state_bottom"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="375:85"
app:layout_constraintTop_toTopOf="@id/layout_content" />
<ImageView
android:id="@+id/iv_state"
android:layout_width="match_parent"
android:layout_height="0dp"
android:src="@drawable/game_result_bg_win"
app:layout_constraintBottom_toBottomOf="@id/space_state_bottom"
app:layout_constraintDimensionRatio="375:317" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,107 @@
<?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:id="@+id/layout_root"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_60"
android:background="@drawable/game_result_item_bg_top1">
<TextView
android:id="@+id/tv_rank"
android:layout_width="@dimen/dp_42"
android:layout_height="@dimen/dp_42"
android:layout_marginStart="@dimen/dp_7"
android:gravity="center"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="@dimen/dp_20"
android:textStyle="bold"
app:autoSizeMaxTextSize="@dimen/dp_20"
app:autoSizeMinTextSize="@dimen/dp_12"
app:autoSizeStepGranularity="1px"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="1" />
<ImageView
android:id="@+id/iv_user_avatar"
android:layout_width="@dimen/dp_46"
android:layout_height="@dimen/dp_46"
android:layout_marginStart="@dimen/dp_5"
android:src="@drawable/default_avatar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_rank"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_user_avatar_border"
android:layout_width="@dimen/dp_49"
android:layout_height="@dimen/dp_49"
android:layout_marginStart="@dimen/dp_2"
app:layout_constraintBottom_toBottomOf="@id/iv_user_avatar"
app:layout_constraintEnd_toEndOf="@id/iv_user_avatar"
app:layout_constraintStart_toStartOf="@id/iv_user_avatar"
app:layout_constraintTop_toTopOf="@id/iv_user_avatar"
tools:src="@drawable/game_result_avatar_border_top_1" />
<ImageView
android:id="@+id/iv_coins_bg"
android:layout_width="0dp"
android:layout_height="@dimen/dp_22"
android:layout_marginStart="@dimen/dp_9"
android:background="@drawable/game_result_coins_top1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/tv_coins"
app:layout_constraintStart_toStartOf="@id/iv_coins"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_coins"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/dp_10"
android:gravity="center"
android:includeFontPadding="false"
android:maxLines="1"
android:minWidth="@dimen/dp_48"
android:paddingStart="@dimen/dp_5"
android:paddingEnd="@dimen/dp_6"
android:text="0"
android:textColor="#FFE829"
android:textSize="@dimen/dp_15"
app:autoSizeMaxTextSize="@dimen/dp_15"
app:autoSizeMinTextSize="@dimen/dp_10"
app:autoSizeStepGranularity="1px"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_coins"
android:layout_width="@dimen/dp_24"
android:layout_height="@dimen/dp_24"
android:src="@drawable/ic_coin_84"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/tv_coins"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_8"
android:layout_marginEnd="@dimen/dp_3"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="@dimen/dp_14"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/iv_coins"
app:layout_constraintStart_toEndOf="@id/iv_user_avatar"
app:layout_constraintTop_toTopOf="parent"
tools:text="Name" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="matchmaking">جاري المطابقة</string>
<string name="match_successfully">تمت المطابقة</string>
<string name="match_failed">فشلت المطابقة</string>
<string name="game_award_tips_coins_text">عملة ذهبية</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>
<string name="game_exit_tips_matching">جاري المطابقة، الخروج من الغرفة ،نهاية المطابقة. هل تريد الخروج؟</string>
<string name="game_match_failed">فشلت المطابقة، هل تريد المحاولة مرة أخرى؟</string>
<string name="game_rematch">إعادة المطابقة</string>
<string name="resume_game_tips">جاري التحميل، هل تريد العودة إلى الغرفة؟</string>
<string name="game_end_tips">انتهت اللعبة~</string>
<string name="continue_game">متابعة اللعبة</string>
</resources>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="matchmaking">Eşleştirme</string>
<string name="match_successfully">Eşleştirme başarılı</string>
<string name="match_failed">Eşleştirme başarısız</string>
<string name="game_award_tips_coins_text">Altın</string>
<string name="game_award_tips_format">%s altın kazandınız</string>
<string name="start">Başlat</string>
<string name="game_ticket_format">Giriş ücreti(%s)</string>
<string name="game_exit_tips">Oyun başladı, odadan çıkarsanız oyunu kaybedersiniz, çıkmak istediğinize emin misiniz?</string>
<string name="game_exit_tips_matching">Oyun eşleştirme aşamasında, odadan çıkarsanız eşleştirme sona erecek, çıkmak istiyor musunuz?</string>
<string name="game_match_failed">Eşleştirme başarısız, tekrar denemek ister misiniz?</string>
<string name="game_rematch">Rövanş</string>
<string name="resume_game_tips">Oyun devam ediyor, odasına geri dönmek istiyor musunuz?</string>
<string name="game_end_tips">Oyun sona erdi~</string>
<string name="continue_game">Oyuna devam et</string>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="matchmaking">匹配中</string>
<string name="match_successfully">匹配成功</string>
<string name="match_failed">匹配失敗</string>
<string name="game_award_tips_coins_text">金幣</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>
<string name="game_exit_tips_matching">遊戲匹配中,退出房間則結束匹配,是否退出?</string>
<string name="game_match_failed">匹配失敗,是否重新開始?</string>
<string name="game_rematch">重新匹配</string>
<string name="resume_game_tips">遊戲進行中,是否返回房間?</string>
<string name="game_end_tips">遊戲已結束~</string>
<string name="continue_game">繼續遊戲</string>
</resources>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="matchmaking">Matching</string>
<string name="match_successfully">Match successful</string>
<string name="match_failed">Match failed</string>
<string name="game_award_tips_coins_text">gold coins</string>
<string name="game_award_tips_format">Winning reward %s gold coins</string>
<string name="start">Start</string>
<string name="game_ticket_format">Admission Fee (%s)</string>
<string name="game_exit_tips">The game has started. Exiting the room will be considered a game failure. Are you sure you want to exit the room?</string>
<string name="game_exit_tips_matching">During a match, exiting a room will end the match. Do you want to exit?</string>
<string name="game_match_failed">Match failed, do you want to rematch?</string>
<string name="game_rematch">Rematch</string>
<string name="resume_game_tips">Game in progress, do you want to return to the room?</string>
<string name="game_end_tips">The game is over ~</string>
<string name="continue_game">Continue</string>
</resources>