Compare commits

...

21 Commits

Author SHA1 Message Date
718ed2d209 feat: 简单对接云游戏 SDK 2025-04-22 15:21:47 +08:00
e3f883b784 Merge branch 'feat/GHZSCY-7828' into 'dev'
feat: 游戏预约相关优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7828

See merge request halo/android/assistant-android!2150
2025-04-08 15:31:12 +08:00
fbb81d7e45 feat: 游戏预约相关优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7828 2025-04-08 15:28:21 +08:00
750ec04c9d Merge branch 'fix/game_detail_cover_tab' into 'dev'
fix: 修复游戏详情视频/图集Tab闪烁的问题

See merge request halo/android/assistant-android!2149
2025-04-07 18:00:37 +08:00
e23a3d3938 fix: 修复游戏详情视频/图集Tab闪烁的问题 2025-04-07 17:57:14 +08:00
d75020cdb5 Merge branch 'feat/GHZSCY-7781' into 'dev'
fix: 游戏详情-视频/图集tab 神策埋点补充—客户端 https://jira.shanqu.cc/browse/GHZSCY-7781

See merge request halo/android/assistant-android!2148
2025-04-07 09:54:40 +08:00
c56c71d1f1 fix: 游戏详情-视频/图集tab 神策埋点补充—客户端 https://jira.shanqu.cc/browse/GHZSCY-7781 2025-04-07 09:54:40 +08:00
ccfa50d748 Merge branch 'feat/GHZSCY-7515' into 'dev'
feat:开服订阅通知频率优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7515

See merge request halo/android/assistant-android!2145
2025-04-03 11:33:41 +08:00
7176a5a4c4 feat:开服订阅通知频率优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7515 2025-04-03 11:33:41 +08:00
d007092193 Merge branch 'feat/GHZSCY-7702' into 'dev'
feat: 实名认证字符优化 (光环助手和畅玩)

See merge request halo/android/assistant-android!2147
2025-04-03 10:45:54 +08:00
2552743ff2 Merge branch 'feat/GHZSCY-7702' into 'dev'
feat: 实名认证字符优化 (光环助手和畅玩)

See merge request halo/android/assistant-android!2146
2025-04-03 10:42:01 +08:00
4faf15ffe7 feat: 实名认证字符优化 (光环助手和畅玩) 2025-04-03 10:42:01 +08:00
33d6ed75ad ci: revert 2025-04-03 10:39:55 +08:00
2eb4878b12 Merge branch 'fix/GHZSCY-7806' into 'dev'
fix:【光环助手】通知栏目 滚动问题 https://jira.shanqu.cc/browse/GHZSCY-7806

See merge request halo/android/assistant-android!2144
2025-04-03 10:07:41 +08:00
2bea3c72d7 fix:【光环助手】通知栏目 滚动问题 https://jira.shanqu.cc/browse/GHZSCY-7806 2025-04-03 09:59:57 +08:00
fa3db9d521 fix: 实名认证页面首页,右上角多显示了“·”的按钮 https://jira.shanqu.cc/browse/GHZSCY-7702,https://jira.shanqu.cc/browse/GHZSCY-7791 2025-04-02 16:36:30 +08:00
efee9409bf Merge branch 'feat/add-room-ktx' into 'dev'
为了Room能和Flow一起使用,需要引入room-ktx

See merge request halo/android/assistant-android!2141
2025-04-01 09:31:26 +08:00
38a7eaf780 为了Room能和Flow一起使用,需要引入room-ktx 2025-04-01 09:28:05 +08:00
a1b4233fdb Merge branch 'feat/GHZSCY-7784' into 'dev'
feat:处理以 LiveData 形式直接监听数据库变更的代码 https://jira.shanqu.cc/browse/GHZSCY-7784

See merge request halo/android/assistant-android!2140
2025-03-31 17:20:33 +08:00
60c24e7457 feat:处理以 LiveData 形式直接监听数据库变更的代码 https://jira.shanqu.cc/browse/GHZSCY-7784 2025-03-31 17:20:33 +08:00
729685e764 feat: 实名认证字符优化 (光环助手和畅玩)
https://jira.shanqu.cc/browse/GHZSCY-7702https://jira.shanqu.cc/browse/GHZSCY-7704
2025-03-25 17:54:35 +08:00
50 changed files with 1297 additions and 414 deletions

View File

@ -538,6 +538,10 @@ dependencies {
if(!gradle.ext.excludeOptionalModules || gradle.ext.enableWechatPay){
implementation(project(":feature:wechat_pay"))
}
if(!gradle.ext.excludeOptionalModules || gradle.ext.enableCloudGame){
implementation(project(":feature:cloud_game"))
}
}
File propFile = file('sign.properties')

View File

@ -18,6 +18,7 @@ import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.provider.IHelpAndFeedbackProvider
import com.gh.gamecenter.common.utils.PackageFlavorHelper
import com.gh.gamecenter.core.provider.IPushProvider
import com.gh.gamecenter.feature.cloudgame.CloudGameHelper
import com.gh.gamecenter.login.utils.QuickLoginHelper
import com.gh.gamecenter.login.view.LoginActivity
import com.gh.gamecenter.va.VCore
@ -106,6 +107,8 @@ class GlobalActivityLifecycleObserver : Application.ActivityLifecycleCallbacks {
if (PackageFlavorHelper.IS_TEST_FLAVOR && activity is AppCompatActivity) {
DarkModeSwitchHelper.showDarkModeSwitchFloatingView(activity)
CloudGameHelper.showCloudGameFloat(activity)
}
if (activity is AppCompatActivity

View File

@ -114,7 +114,6 @@ class GameDetailActivity : DownloadToolbarActivity() {
view,
listOf(
R.id.menu_download_iv,
R.id.gameBigEvent,
R.id.cardContainer,
R.id.iv_reserve,
R.id.iv_concern,

View File

@ -127,6 +127,8 @@ class GameDetailViewModel(
var isSkipOnPageSelected = false // 是否触发论坛/专区Tab跳转
var defaultCoverEntity: CoverEntity? = null
var coverTabSequence = 1 // 用于埋点,详情视频/图集tab 当前选中tab的序号从1开始
var coverTabName = "" // 用于埋点,详情视频/图集tab 详情视频/图集tab 当前选中tab的tab名称
var isGameInstalled = false
private var isGameUpdatable = false

View File

@ -187,15 +187,19 @@ class GameDetailFragment : LazyFragment(), IScrollable {
coverSfv.goneIf(!shouldShowCoverFilter) {
val defaultTabPosition =
tabNameList.indexOfFirst { tabName -> viewModel.defaultCoverEntity?.tabName == tabName }
viewModel.coverTabSequence = defaultTabPosition + 1
viewModel.coverTabName = viewModel.defaultCoverEntity?.tabName ?: ""
coverSfv.setItemList(tabNameList, if (defaultTabPosition != -1) defaultTabPosition else 0)
coverSfv.setOnCheckedCallback { position ->
val checkedText = tabNameList.getOrNull(position)
val currentCoverEntity = it.getOrNull(coverPosition)
viewModel.coverTabSequence = position + 1
viewModel.coverTabName = checkedText ?: ""
SensorsBridge.trackEvent("GameDetailMediaTabClick", json {
"game_id" to gameEntity?.id
"game_name" to gameEntity?.name
"sequence" to position + 1
"tab_name" to checkedText
"sequence" to viewModel.coverTabSequence
"tab_name" to viewModel.coverTabName
})
if (currentCoverEntity?.tabName == checkedText) return@setOnCheckedCallback

View File

@ -115,7 +115,6 @@ class GameDetailCoverAdapter(
})
.build(holder.binding.player)
holder.binding.player.gameName = viewModel.game?.name ?: ""
holder.binding.player.viewModel = viewModel
holder.binding.player.showOrHideCoverFilter = showOrHideCoverFilter
holder.binding.player.video = topVideo

View File

@ -143,13 +143,11 @@ class ServersCalendarActivity : ToolBarActivity() {
setToolbarMenu(R.menu.menu_server_calendar_more)
mBinding.subscribe.visibility = View.GONE
mBinding.subscribeHint1.visibility = View.GONE
mBinding.subscribeHint2.visibility = View.GONE
mBinding.subscribe.setOnClickListener(null)
} else {// 用户已登录并处于未订阅状态
clearMenu()
mBinding.subscribe.visibility = View.VISIBLE
mBinding.subscribeHint1.visibility = View.VISIBLE
mBinding.subscribeHint2.visibility = View.VISIBLE
mBinding.subscribe.setOnClickListener {
CheckLoginUtils.checkLogin(this, "游戏详情-开服日历表-开启订阅") {
mViewModel.subscribeServer()

View File

@ -238,7 +238,7 @@ class ServersCalendarDetailNoDataDialog : BottomSheetDialogFragment() {
wechatRemindCheckIv.alpha = 1.0F
wechatRemind.setOnClickListener {
wechatRemindCheckIv.isChecked = !wechatRemindCheckIv.isChecked
viewModel.wechatRemind = wechatRemindCheckIv.isChecked
// viewModel.wechatRemind = wechatRemindCheckIv.isChecked
SPUtils.setBoolean(Constants.SP_SERVERS_CALENDAR_BY_WECHAT, wechatRemindCheckIv.isChecked)
}

View File

@ -30,14 +30,14 @@ class ServersCalendarDetailNoDataViewModel(
var appRemind: Boolean = false
var wechatRemind: Boolean = false
val wechatRemind: Boolean = false
val timeInSeconds: Int = (serverTimeInMills / 1000).toInt()
fun initData(notifySetting: ServerCalendarNotifySetting?) {
if (isDataInit) return
appRemind = notifySetting?.byApp ?: SPUtils.getBoolean(Constants.SP_SERVERS_CALENDAR_BY_APP, true)
wechatRemind = notifySetting?.byWechat ?: SPUtils.getBoolean(Constants.SP_SERVERS_CALENDAR_BY_WECHAT, true)
// wechatRemind = notifySetting?.byWechat ?: SPUtils.getBoolean(Constants.SP_SERVERS_CALENDAR_BY_WECHAT, true)
isDataInit = true
}

View File

@ -308,7 +308,7 @@ class ServersCalendarDetailsRemindDialog : BottomSheetDialogFragment() {
viewBinding.wechatRemind.setOnClickListener {
viewBinding.wechatRemindCheckIv.isChecked = !viewBinding.wechatRemindCheckIv.isChecked
viewModel.wechatRemind = viewBinding.wechatRemindCheckIv.isChecked
// viewModel.wechatRemind = viewBinding.wechatRemindCheckIv.isChecked
SPUtils.setBoolean(Constants.SP_SERVERS_CALENDAR_BY_WECHAT, viewBinding.wechatRemindCheckIv.isChecked)
}

View File

@ -36,7 +36,7 @@ class ServersCalendarDetailsRemindViewModel(
var appRemind: Boolean = false
var wechatRemind: Boolean = false
val wechatRemind: Boolean = false
val selectedNotifySeconds: Int get() = (selectedNotifyTime / 1000).toInt()
@ -58,7 +58,7 @@ class ServersCalendarDetailsRemindViewModel(
selectedAdvancedTime = ServersCalendarAdvancedTime.valueOf(notifySetting?.secondsAdvance)
selectedNotifyTime = (notifySetting?.notifyTime ?: calendarEntity.getTime()) * 1000
appRemind = notifySetting?.byApp ?: SPUtils.getBoolean(Constants.SP_SERVERS_CALENDAR_BY_APP, true)
wechatRemind = notifySetting?.byWechat ?: SPUtils.getBoolean(Constants.SP_SERVERS_CALENDAR_BY_WECHAT, true)
// wechatRemind = notifySetting?.byWechat ?: SPUtils.getBoolean(Constants.SP_SERVERS_CALENDAR_BY_WECHAT, true)
isDataInit = true
}

View File

@ -14,6 +14,7 @@ import androidx.fragment.app.Fragment
import com.gh.common.util.LogUtils
import com.gh.download.cache.ExoCacheManager
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.observer.MuteCallback
import com.gh.gamecenter.common.observer.VolumeObserver
import com.gh.gamecenter.common.utils.*
@ -40,7 +41,6 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
private var mMuteCallback: MuteCallback
private var mVolumeObserver: VolumeObserver? = null
var gameName = ""
var video: CoverTabEntity.Video? = null
var viewModel: GameDetailViewModel? = null
var uuid = UUID.randomUUID().toString()
@ -87,11 +87,21 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
}
setBackFromFullScreenListener {
// if (it.id == R.id.fullscreen) {
// MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-退出全屏", combinedTitleAndId)
// } else if (it.id == R.id.back) {
// MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-点击返回", combinedTitleAndId)
// }
if (it.id == R.id.fullscreen || it.id == R.id.back) {
SensorsBridge.trackGameDetailVideoClick(
gameName = viewModel?.game?.name ?: "",
gameId = viewModel?.game?.id ?: "",
gameType = viewModel?.game?.categoryChinese ?: "",
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
action = if (it.id == R.id.fullscreen) "退出全屏" else "点击返回",
playType = if (mIsAutoPlay) "自动播放" else "主动播放",
isFullScreen = mIfCurrentIsFullscreen,
sequence = viewModel?.coverTabSequence ?: 1,
tabName = viewModel?.coverTabName ?: "",
playLength = (currentPositionWhenPlaying / 1000).toString()
)
}
clearFullscreenLayout()
}
@ -126,13 +136,22 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
fun startPlayLogic(isAutoPlay: Boolean) {
mIsAutoPlay = isAutoPlay
violenceUpdateMuteStatus()
// if (isAutoPlay) {
// MtaHelper.onEvent("游戏详情_顶部视频", "视频播放方式", "自动播放")
// MtaHelper.onEvent("游戏详情_顶部视频", "顶部视频-自动播放", combinedTitleAndId)
// } else {
// MtaHelper.onEvent("游戏详情_顶部视频", "视频播放方式", "手动播放")
// }
if (isAutoPlay) {
SensorsBridge.trackGameDetailVideoClick(
gameName = viewModel?.game?.name ?: "",
gameId = viewModel?.game?.id ?: "",
gameType = viewModel?.game?.categoryChinese ?: "",
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
action = "自动播放",
playType = if (mIsAutoPlay) "自动播放" else "主动播放",
isFullScreen = mIfCurrentIsFullscreen,
sequence = viewModel?.coverTabSequence ?: 1,
tabName = viewModel?.coverTabName ?: "",
playLength = (currentPositionWhenPlaying / 1000).toString()
)
val seekTime = ScrollCalculatorHelper.getPlaySchedule(MD5Utils.getContentMD5(video?.url))
seekOnStart = seekTime
mTouchingProgressBar = false
@ -289,7 +308,7 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
override fun onStopTrackingTouch(seekBar: SeekBar?) {
super.onStopTrackingTouch(seekBar)
uploadVideoStreamingPlaying("拖动")
uploadVideoStreamingPlaying("拖动进度条", seekBar)
}
override fun isShowNetConfirm(): Boolean {
@ -533,7 +552,7 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
}
override fun releaseVideos() {
uploadVideoStreamingPlaying("结束播放")
uploadVideoStreamingPlaying("播放完毕")
CustomManager.releaseAllVideos(getKey())
}
@ -556,11 +575,21 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
override fun onClick(v: View) {
when (v.id) {
R.id.start -> {
// if (currentState == GSYVideoView.CURRENT_STATE_PLAYING) {
// MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-点击暂停", combinedTitleAndId)
// } else {
// MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-点击播放", combinedTitleAndId)
// }
SensorsBridge.trackGameDetailVideoClick(
gameName = viewModel?.game?.name ?: "",
gameId = viewModel?.game?.id ?: "",
gameType = viewModel?.game?.categoryChinese ?: "",
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
action = if (currentState == GSYVideoView.CURRENT_STATE_PLAYING) "点击暂停" else "点击播放",
playType = if (mIsAutoPlay) "自动播放" else "主动播放",
isFullScreen = mIfCurrentIsFullscreen,
sequence = viewModel?.coverTabSequence ?: 1,
tabName = viewModel?.coverTabName ?: "",
playLength = (currentPositionWhenPlaying / 1000).toString()
)
// 手动触发暂停/播放,后续行为都算主动播放
mIsAutoPlay = false
super.onClick(v)
}
@ -568,13 +597,11 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
}
}
private fun getMtaKeyPrefix() = if (mIfCurrentIsFullscreen) "顶部视频(全屏)" else "顶部视频"
fun getCurrentPosition(): Long {
return mCurrentPosition
}
fun uploadVideoStreamingPlaying(action: String) {
fun uploadVideoStreamingPlaying(action: String, seekBar: SeekBar? = null) {
if (video == null || video?.url.isNullOrEmpty()) return
runOnIoThread(isHeavyWightTask = true) {
val isLandscape = mOrientationUtils != null
@ -598,6 +625,26 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
action, video?.videoId, video?.title, viewModel?.game?.id, viewModel?.game?.name,
videoPlayModel, videoPlayStatus(), mContentLength, videoTotalTime, videoPlayTs, progress.toInt()
)
if (action != "开始播放" && action != "暂停播放" && action != "退出全屏") {
val playLength = when (action) {
"播放完毕" -> (duration / 1000).toString()
"拖动进度条" -> ((seekBar?.progress ?: 0) * duration / 100000).toString()
else -> (currentPositionWhenPlaying / 1000).toString()
}
SensorsBridge.trackGameDetailVideoClick(
gameName = viewModel?.game?.name ?: "",
gameId = viewModel?.game?.id ?: "",
gameType = viewModel?.game?.categoryChinese ?: "",
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
action = action,
playType = if (mIsAutoPlay) "自动播放" else "主动播放",
isFullScreen = mIfCurrentIsFullscreen,
sequence = viewModel?.coverTabSequence ?: 1,
tabName = viewModel?.coverTabName ?: "",
playLength = playLength
)
}
}
}
}

View File

@ -72,7 +72,7 @@ object CustomViewExt {
private fun getTestDescription(game: GameEntity): String {
val timeText = TimeUtils.formatTestTime(game.test?.start ?: 0L)
val eventName = if (game.test?.type == "删档内测") {
R.string.first_release.toResString()
R.string.delete_test.toResString()
} else {
R.string.go_live.toResString()
}

View File

@ -37,6 +37,8 @@ class NotificationColumnViewHolder(
private var isScrolling = false
private var targetPosition = -1
private val bannerController = BannerInRecyclerController {
nextToPage()
}
@ -190,6 +192,10 @@ class NotificationColumnViewHolder(
override fun onViewAttach(parent: RecyclerView?) {
viewModel.shareHiddenNotifications.observe(lifecycleOwner, hiddenNotifiesObserver)
bannerController.onViewAttachedToWindow(parent)
val selectedPosition = (_item as? CustomCommonContentCollectionItem)?.selectedPosition ?: 0
if (targetPosition != -1 && targetPosition != selectedPosition) {
binding.rvNotification.scrollToPosition(targetPosition)
}
}
override fun onViewDetach(parent: RecyclerView?) {
@ -203,7 +209,10 @@ class NotificationColumnViewHolder(
if (layoutManager is LinearLayoutManager) {
val firstPosition = layoutManager.findFirstCompletelyVisibleItemPosition()
if (firstPosition != -1) {
binding.rvNotification.smoothScrollToPosition(firstPosition + 1)
// 请注意有可能smoothScrollToPosition正在执行滚动动画时当前ViewHolder会调用onViewDetach导致RecyclerView无法滚动到目标位置所以这里需要先记录目标位置
// 当ViewHolder Detach之后再次Attach时检查targetPosition是否等于selectedPosition如果不相等说明发生了以上情况需要再次调用scrollToPosition将RecyclerView滚动到指定位置
targetPosition = firstPosition + 1
binding.rvNotification.smoothScrollToPosition(targetPosition)
}
}
}

View File

@ -2,23 +2,32 @@ package com.gh.vspace
import android.os.Bundle
import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.exposure.ExposureListener
import com.gh.common.util.NewFlatLogUtils
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.baselist.ListFragment
import com.gh.gamecenter.common.baselist.LoadType
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.setSwitchAnimation
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.FragmentVdownloadManagerBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.history.IBatchDelete
import com.gh.gamecenter.history.ManageOption
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
class VDownloadManagerFragment :
ListFragment<GameEntity, VDownloadManagerViewModel>(), IBatchDelete {
@ -93,8 +102,16 @@ class VDownloadManagerFragment :
mListRv.addOnScrollListener(mExposureListener)
}
VHelper.vGameDao.getAllLiveData().observe(viewLifecycleOwner) {
onLoadRefresh()
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
mViewModel.vGames
.catch {
ToastUtils.showToast(it.message ?: "")
}
.collectLatest {
onLoadRefresh()
}
}
}
}

View File

@ -38,6 +38,8 @@ class VDownloadManagerViewModel(application: Application) :
load(it)
}
val vGames = VHelper.vGameDao.getAllGames()
fun refresh() {
loadPublishSubject.onNext(LoadType.REFRESH)
}

View File

@ -1,7 +1,10 @@
package com.gh.vspace.db
import androidx.lifecycle.LiveData
import androidx.room.*
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Dao
interface VGameDao {
@ -13,7 +16,7 @@ interface VGameDao {
fun getAll(): List<VGameEntity>
@Query("SELECT * FROM v_game")
fun getAllLiveData(): LiveData<List<VGameEntity>>
fun getAllGames(): Flow<List<VGameEntity>>
@Query("DELETE FROM v_game WHERE packageName = :packageName")
fun delete(packageName: String)

View File

@ -187,6 +187,7 @@ class RealNameInfoFragment : ToolbarFragment() {
mBinding.nameEt.inputType = InputType.TYPE_NULL
mBinding.idCardEt.inputType = InputType.TYPE_NULL
mBinding.nameEt.visibility = View.GONE
mBinding.textDotIndicator.visibility = View.GONE
mBinding.idCardEt.visibility = View.GONE
mBinding.nameTv.visibility = View.GONE
mBinding.idCardTv.visibility = View.GONE

View File

@ -153,21 +153,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:layout_marginBottom="7dp"
android:text="@string/servers_calendar_subscribe_hint_1"
android:textColor="@color/text_tertiary"
android:textSize="11sp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/subscribe"
app:layout_constraintBottom_toTopOf="@id/subscribe_hint_2" />
<TextView
android:id="@+id/subscribe_hint_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:layout_marginBottom="15dp"
android:text="@string/servers_calendar_subscribe_hint_2"
android:text="@string/servers_calendar_subscribe_hint_1"
android:textColor="@color/text_tertiary"
android:textSize="11sp"
android:visibility="gone"

View File

@ -128,6 +128,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:gravity="center"
android:visibility="invisible"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="@id/app_remind"
app:layout_constraintStart_toEndOf="@id/app_remind">

View File

@ -1,310 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
android:fitsSystemWindows="true"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/gamedetail_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/ui_surface"
android:fitsSystemWindows="true"
android:gravity="center"
app:layout_behavior="com.gh.gamecenter.common.view.FixAppBarLayoutBehavior"
tools:visibility="visible">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsingToolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="@color/ui_surface"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:scrimAnimationDuration="0"
app:scrimVisibleHeightTrigger="105dp"
app:titleEnabled="false">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:gravity="center_horizontal"
android:orientation="vertical">
<include
android:id="@+id/game_detail_video"
layout="@layout/piece_game_detail_video" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="12dp"
android:paddingTop="16dp"
android:paddingRight="16dp"
android:paddingBottom="12dp">
<RelativeLayout
android:id="@+id/gameIconContainer"
android:layout_width="96dp"
android:layout_height="88dp"
android:layout_marginRight="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.gh.gamecenter.feature.view.GameIconView
android:id="@+id/gamedetail_iv_thumb"
android:layout_width="88dp"
android:layout_height="88dp"
android:layout_centerHorizontal="true"
app:gameIconFadeDuration="0" />
<LinearLayout
android:id="@+id/gameDetailRankLl"
android:layout_width="match_parent"
android:layout_height="25dp"
android:layout_alignParentBottom="true"
android:background="@drawable/bg_game_detail_rank"
android:orientation="horizontal"
android:visibility="gone"
tools:visibility="visible">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="6dp"
android:layout_marginTop="7dp"
android:src="@drawable/ic_game_detail_rank_trophy" />
<TextView
android:id="@+id/gameDetailRankTv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginLeft="2dp"
android:layout_marginTop="8dp"
android:layout_marginRight="2dp"
android:layout_weight="1"
android:ellipsize="marquee"
android:gravity="center"
android:includeFontPadding="false"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
android:textColor="@color/white"
android:textSize="9sp"
android:textStyle="bold"
tools:text="预约榜第1名" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginRight="6dp"
android:src="@drawable/ic_game_detail_rank_arrow" />
</LinearLayout>
</RelativeLayout>
<LinearLayout
android:id="@+id/gameTitleContainer"
android:layout_width="0dp"
android:layout_height="70dp"
android:layout_marginLeft="8dp"
android:gravity="center_vertical"
android:orientation="vertical"
app:layout_constraintEnd_toStartOf="@+id/rating_score_container"
app:layout_constraintStart_toEndOf="@+id/gameIconContainer"
app:layout_constraintTop_toTopOf="@+id/gameIconContainer">
<TextView
android:id="@+id/gamedetail_tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:includeFontPadding="false"
android:lineSpacingExtra="2dp"
android:maxLines="2"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold"
tools:text="地海争霸2" />
<com.gh.common.view.FlexLinearLayout
android:id="@+id/gamedetail_gametag"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_marginTop="4dp"
android:clipToPadding="false"
android:orientation="horizontal" />
</LinearLayout>
<ImageView
android:id="@+id/recommendAgeIv"
android:layout_width="58dp"
android:layout_height="14dp"
android:layout_marginLeft="8dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/gameIconContainer"
app:layout_constraintStart_toEndOf="@+id/gameIconContainer"
tools:src="@drawable/ic_recommend_age8"
tools:visibility="visible" />
<TextView
android:id="@+id/realnameHintTv"
android:layout_width="wrap_content"
android:layout_height="14dp"
android:layout_marginStart="8dp"
android:background="@drawable/bg_ebf8ff_radius_2"
android:gravity="center"
android:paddingEnd="6dp"
android:paddingStart="6dp"
android:paddingTop="0.5dp"
android:text="登陆游戏后需进行实名认证"
android:textColor="@color/text_theme"
android:textSize="@dimen/tag_text_size"
android:includeFontPadding="false"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/gameIconContainer"
app:layout_constraintStart_toEndOf="@id/recommendAgeIv"
tools:visibility="visible" />
<RelativeLayout
android:id="@+id/rating_score_container"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginLeft="6dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/gameTitleContainer"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/bg_game_detail_rating_score" />
<TextView
android:id="@+id/rating_score_average"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold"
tools:text="8.5" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/gameBigEvent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/bg_game_big_event"
android:drawableLeft="@drawable/ic_game_detail_big_event_gray"
android:drawableRight="@drawable/ic_game_detail_big_event_arrow_gray"
android:drawablePadding="3dp"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
android:padding="4dp"
android:textColor="@color/text_tertiary"
android:textSize="10sp"
android:visibility="gone"
tools:text="游戏大事件游戏大事件"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/contentCardContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:visibility="gone" />
<View
android:id="@+id/toolbarGapView"
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="@color/ui_background" />
</LinearLayout>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
style="@style/Base_ToolbarStyle"
android:layout_width="match_parent"
android:layout_height="44dp"
android:background="@android:color/transparent"
app:contentInsetStartWithNavigation="0dp"
app:layout_collapseMode="pin">
<LinearLayout
android:id="@+id/toolbarContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/transparent"
android:gravity="center_vertical"
android:orientation="horizontal"
android:translationX="-10dp">
<com.gh.gamecenter.feature.view.GameIconView
android:id="@+id/gamedetail_thumb_small"
android:layout_width="28dp"
android:layout_height="28dp"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:id="@+id/titleTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
<RelativeLayout
android:id="@+id/gamedetail_tabbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.gh.gamecenter.common.view.TabIndicatorView
android:id="@+id/tab_indicator"
android:layout_width="match_parent"
android:layout_height="@dimen/default_tab_indicator_height"
android:layout_alignParentBottom="true" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="@dimen/tab_layout_height"
app:tabIndicator="@null"
app:tabTextAppearance="@style/TabLayoutTextAppearance" />
</RelativeLayout>
</com.google.android.material.appbar.AppBarLayout>
<com.lightgame.view.NoScrollableViewPager
android:id="@+id/gamedetail_vp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -69,6 +69,7 @@
android:layout_marginTop="12dp"
android:layout_marginStart="20dp"
android:gravity="center"
android:visibility="gone"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/remind_title"
app:layout_constraintStart_toEndOf="@id/app_remind"

View File

@ -6,7 +6,7 @@
android:layout_height="112dp"
android:background="@color/ui_surface"
app:layout_constraintBottom_toBottomOf="parent"
tools:showIn="@layout/activity_main">
tools:showIn="@layout/activity_cloudgame">
<ImageView
android:layout_width="wrap_content"

View File

@ -1,39 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/video_placeholder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<com.gh.gamecenter.common.view.StatusBarView
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/appbar_height" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.gh.gamecenter.gamedetail.video.TopVideoView
android:id="@+id/player"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="h,180:101"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -480,7 +480,6 @@
<string name="archive_limit_description">請選擇替換覆蓋的存檔</string>
<string name="servers_calendar_subscribe">加入訂閱</string>
<string name="servers_calendar_subscribe_hint_1">訂閱開服表,訂閱後助你獲得一手新服信息</string>
<string name="servers_calendar_subscribe_hint_2">通知時間:8:00、12:00、18:00</string>
<string name="servers_calendar_more_dialog_title">操作</string>
<string name="servers_calendar_dialog_unsubscribe_title">取消訂閱</string>
<string name="servers_calendar_dialog_unsubscribe_hint">不再接收開服消息</string>
@ -686,9 +685,9 @@
<string name="select_the_region">選擇加速區服</string>
<string name="tips_for_new_users">新用戶免費3小時</string>
<string name="recent_played">最近在玩</string>
<string name="first_release">先發</string>
<string name="go_live">上線</string>
<string name="number_of_reservations">%1$s人預約</string>
<string name="wechat_app_not_install_tips">請檢查是否安裝微信客戶端</string>
<string name="delete_test">刪測</string>
</resources>

View File

@ -493,13 +493,12 @@
<string name="archive_limit_description">请选择替换覆盖的存档</string>
<string name="servers_calendar_subscribe">加入订阅</string>
<string name="servers_calendar_subscribe_hint_1">订阅开服表,订阅后助你获得一手新服信息</string>
<string name="servers_calendar_subscribe_hint_2">通知时间:8:00、12:00、18:00</string>
<string name="servers_calendar_more_dialog_title">操作</string>
<string name="servers_calendar_dialog_unsubscribe_title">取消订阅</string>
<string name="servers_calendar_dialog_unsubscribe_hint">不再接收开服消息</string>
<string name="servers_calendar_more_dialog_cancel_title">取消</string>
<string name="servers_calendar_subscription_dialog_wechat_content">游戏发布新服信息时,您将在消息中心收到通知。为了避免错过通知,建议您开启微信公众号提醒</string>
<string name="servers_calendar_subscription_dialog_content">游戏发布新服信息时,您将在消息中心和微信公众号收到通知,不会错过任何开服的消</string>
<string name="servers_calendar_subscription_dialog_content">游戏发布新服信息时,您将在消息中心收到通知,不会错过任何开服</string>
<string name="servers_calendar_subscription_dialog_title">游戏订阅成功</string>
<string name="servers_calendar_subscription_dialog_confirm">我知道了</string>
<string name="servers_calendar_remind_time_setting_dialog_title">设置提醒时间</string>
@ -686,8 +685,8 @@
<string name="select_the_region">选择加速区服</string>
<string name="tips_for_new_users">新用户免费3小时</string>
<string name="recent_played">最近在玩</string>
<string name="first_release">首发</string>
<string name="go_live">上线</string>
<string name="number_of_reservations">%1$s人预约</string>
<string name="wechat_app_not_install_tips">请检查是否安装微信客户端</string>
<string name="delete_test">删测</string>
</resources>

1
feature/cloud_game/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,40 @@
apply plugin: "com.android.library"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "kotlin-kapt"
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
minSdk rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
buildFeatures {
viewBinding true
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
implementation(project(path: ":module_common")) {
exclude group: 'androidx.swiperefreshlayout'
}
implementation(project(':module_core_feature')) {
exclude group: 'androidx.swiperefreshlayout'
}
implementation "com.tencent.tcr:tcrsdk-full:3.23.0"
implementation "com.tencent.tcr:tcr-gamepad:2.2.4"
implementation "com.lg:easyfloat:2.0.4-fix_proguard"
}

21
feature/cloud_game/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.gh.gamecenter.cloudgame">
<application>
<activity
android:name="com.gh.gamecenter.feature.cloudgame.CloudGameActivity"
android:screenOrientation="landscape" />
</application>
</manifest>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,500 @@
package com.gh.gamecenter.feature.cloudgame
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.view.Window
import android.view.WindowManager
import android.widget.FrameLayout
import android.widget.Toast
import androidx.core.view.isVisible
import com.gh.gamecenter.cloudgame.R
import com.gh.gamecenter.cloudgame.databinding.ActivityCloudgameBinding
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.AppExecutor.ioExecutor
import com.gh.gamecenter.feature.cloudgame.OkHelper.obtainServerSession
import com.tencent.tcr.sdk.api.AsyncCallback
import com.tencent.tcr.sdk.api.CustomDataChannel
import com.tencent.tcr.sdk.api.TcrSdk
import com.tencent.tcr.sdk.api.TcrSession
import com.tencent.tcr.sdk.api.TcrSessionConfig
import com.tencent.tcr.sdk.api.data.CursorState
import com.tencent.tcr.sdk.api.data.ScreenConfig
import com.tencent.tcr.sdk.api.data.StatsInfo
import com.tencent.tcr.sdk.api.data.VideoStreamConfig
import com.tencent.tcr.sdk.api.view.MobileTouchListener
import com.tencent.tcr.sdk.api.view.PcClickListener
import com.tencent.tcr.sdk.api.view.PcTouchListener
import com.tencent.tcr.sdk.api.view.TcrRenderView
import com.tencent.tcr.sdk.api.view.TcrRenderView.TcrRenderViewType
import com.tencent.tcr.sdk.api.view.TcrRenderView.VideoRotation
import com.tencent.tcrgamepad.GamepadManager
import com.tencent.tcrgamepad.GamepadManager.OnEditListener
import com.tencent.tcrgui.keyboard.KeyboardView
import java.io.InputStreamReader
import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets
import java.text.DecimalFormat
/**
* 该类演示了如何初始化TcrSdk创建会话、请求远端会话、启动云应用并将云应用画面展示到界面上的基础流程。<br></br>
*
* 要使用TcrSdk你需要先调用[TcrSdk.init]接口初始化TcrSdk,<br></br>
* 在[AsyncCallback.onSuccess] 回调以后才能做进一步操作,例如创建[TcrSession]以及[TcrRenderView]。
*
* 在启动会话[TcrSession.start]后才可以与云端实例进行交互。<br></br>
* 具体的流程如下:
*
* <pre>
* `┌──────────────┐ ┌────────────┐ ┌────────────┐ ┌───────────┐
* │ MainActivity │ │ TcrSession │ │ App Server │ │ Cloud Api │
* └──────┬───────┘ └──────┬─────┘ └──────┬─────┘ └─────┬─────┘
* │ setObserver() │ │ │
* ├────────────────►│ │ │
* │ ├─────┐ │ │
* │ │ │ │ │
* │ Event.INITED │◄────┘ │ │
* │◄────────────────┤ │ │
* │ │ │ │
* │ startGame(clientSession) │ │
* ├─────────────────┬─────────────►│ │
* │ │ │ tryLock │
* │ │ ├─────────────►│
* │ │ │ │
* │ │ │createSession │
* │ │ ├─────────────►│
* │ │ │ │
* │ onSuccess(serverSession) │ │
* │◄────────────────┬──────────────┤ │
* │ │ │ │
* │ start() │ │ │
* ├────────────────►│ │ │
* │ │ │ │
* SDK调用流程 后台交互流程
` *
</pre> *
*
* 1.调用[TcrSdk.init]初始化SDK初始化成功后创建TcrSession,
* 并通过[TcrSessionConfig.Builder.observer]将Observer设置好通过Observer的回调拿到TcrSession对外的通知<br></br>
* 2.通过Observer的回调得到TcrSession初始化成功[TcrSession.Event.STATE_INITED]的事件后将事件传递的数据解析为clientSession<br></br>
* 3.调用业务后台接口, 将`clientSession`传递给云端实例,并获取`serverSession`。<br></br>
* 4.拿到`serverSession`之后,调用[TcrSession.start]启动会话。<br></br>
* 5.当会话启动成功之后用户便可以和云端实例进行交互。<br></br>
*
* 详细的TcrSdk接口如何使用请参考文档<br></br>
* 业务后台的搭建,请参考链接<br></br>
* @see [TcrSdK API](https://tencentyun.github.io/cloudgame-android-sdk/tcrsdk/index.html)
*
* @see [搭建业务后台](https://github.com/tencentyun/gs-server-demo)
*/
class CloudGameActivity : Activity() {
private val mDf = DecimalFormat("#.##")
// 渲染视图
private var mRenderView: TcrRenderView? = null
// 云渲染会话
private var mTcrSession: TcrSession? = null
// 创建的数据通道
private var mCustomDataChannel: CustomDataChannel? = null
// 云端横竖屏信息
private var mScreenConfig: ScreenConfig? = null
// 视频流分辨率信息
private var mVideoStreamConfig: VideoStreamConfig? = null
// 记录云端屏幕配置是否发生变化
private var mScreenConfigChanged = false
// 记录视频分辨率是否发生变化
private var mVideoStreamConfigChanged = false
// 游戏手柄管理器
private var gamepadManager: GamepadManager? = null
// 键盘管理器
private var keyboardView: KeyboardView? = null
private var isCursorEnabled = true
private val binding by lazy { ActivityCloudgameBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initWindow()
setContentView(binding.root)
initView()
}
private fun initWindow() {
// 不显示标题栏
requestWindowFeature(Window.FEATURE_NO_TITLE)
// 全屏展示
window.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
// 屏幕常亮
window.setFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
)
}
private fun initView() {
binding.run {
joystickBtn.setOnClickListener {
if (joystickContainer.isVisible) {
joystickContainer.visibility = View.GONE
} else {
if (mTcrSession == null) return@setOnClickListener
if (gamepadManager == null) {
gamepadManager = GamepadManager(this@CloudGameActivity, mTcrSession)
joystickContainer.addView(gamepadManager)
val customGamePadCfg =
readConfigFile(this@CloudGameActivity, "lol_5v5.cfg")
gamepadManager?.showGamepad(customGamePadCfg)
gamepadManager?.setEditListener(OnEditListener { isChanged: Boolean, newCfg: String ->
if (isChanged) {
gamepadManager?.showGamepad(newCfg)
}
})
}
joystickContainer.visibility = View.VISIBLE
}
}
joystickEditBtn.setOnClickListener {
if (mTcrSession == null || gamepadManager == null) return@setOnClickListener
val customGamePadCfg =
readConfigFile(this@CloudGameActivity, "lol_5v5.cfg")
gamepadManager?.editGamepad(customGamePadCfg)
}
keyboardBtn.setOnClickListener {
if (keyboardContainer.isVisible) {
keyboardContainer.visibility = View.GONE
} else {
if (mTcrSession == null) return@setOnClickListener
if (keyboardView == null) {
keyboardView = KeyboardView(this@CloudGameActivity, mTcrSession)
binding.keyboardContainer.addView(keyboardView)
}
keyboardContainer.visibility = View.VISIBLE
}
}
cursorBtn.setOnClickListener {
if (mTcrSession == null) return@setOnClickListener
if (isCursorEnabled) {
mRenderView?.setOnTouchListener(null)
} else {
setTouchHandler(
mTcrSession!!,
mRenderView!!,
PC_GAME
)
}
isCursorEnabled = !isCursorEnabled
}
connectBtn.setOnClickListener {
// 初始化TcrSdk初始化成功后创建TcrSession
TcrSdk.getInstance().init(this@CloudGameActivity, null, object : AsyncCallback<Void?> {
override fun onSuccess(result: Void?) {
Log.i(TAG, "init SDK success")
showToast("sdk 初始化成功", Toast.LENGTH_SHORT)
// 为TcrSession创建配置参数对象。参考https://tencentyun.github.io/cloudgame-android-sdk/tcrsdk/com/tencent/tcr/sdk/api/config/TcrSessionConfig.Builder.html
val tcrSessionConfig = TcrSessionConfig.builder()
.observer(mSessionEventObserver)
.idleThreshold(30000)
.build()
// 创建会话对象
mTcrSession = TcrSdk.getInstance().createTcrSession(tcrSessionConfig)
if (mTcrSession == null) {
Log.e(TAG, "mTcrSession = null")
showToast("创建TcrSession失败请查看日志", Toast.LENGTH_SHORT)
return
}
// 创建和初始化渲染视图
runOnUiThread { initTcrRenderView() }
}
override fun onFailure(code: Int, msg: String) {
val errorMsg = "init SDK failed:$code msg:$msg"
Log.e(TAG, errorMsg)
showToast(errorMsg, Toast.LENGTH_LONG)
}
})
}
disconnectBtn.setOnClickListener {
AppExecutor.ioExecutor.execute {
OkHelper.stopServerSession("rbmk")
}
}
}
}
/**
* 初始化渲染视图
*/
private fun initTcrRenderView() {
// 创建渲染视图
mRenderView = TcrSdk.getInstance()
.createTcrRenderView(this@CloudGameActivity, mTcrSession!!, TcrRenderViewType.SURFACE)
if (mRenderView == null) {
Log.e(TAG, "mRenderView = null")
showToast("创建TcrRenderView失败,请查看日志", Toast.LENGTH_SHORT)
return
}
// 将渲染视图添加到界面上
(findViewById<View>(R.id.render_view_parent) as FrameLayout).addView(mRenderView)
}
/**
* 通过http请求业务后台并获取ServerSession拿到ServerSession后启动会话<br></br>
* 如果您需要启动云应用请调用CloudRenderBiz.getInstance().startProject()<br></br>
* 如果您需要启动云游戏请调用CloudRenderBiz.getInstance().startGame()<br></br>
* <br></br>
* 无论启动的是云应用还是云游戏,都需要接入方准备相应的业务后台环境。<br></br>
* 云渲染团队提供了一个测试环境,可通过[TcrTestEnv]工具进行调用方便您做客户端SDK接入测试。<br></br>
*/
private fun requestServerSession(clientSession: String) {
Log.i(TAG, "init session success:$clientSession")
ioExecutor.execute {
val session = obtainServerSession(clientSession, "rbmk")
if (!TextUtils.isEmpty(session)) {
mTcrSession!!.start(session)
}
}
}
/**
* 旋转屏幕方向, 以便本地的屏幕方向和云端保持一致<br></br>
* 注意: 请确保Manifest中的Activity有android:configChanges="orientation|screenSize"配置, 避免Activity因旋转而被销毁.<br></br>
*/
@SuppressLint("SourceLockedOrientationActivity")
private fun updateOrientation() {
Log.i(TAG, "updateOrientation:" + mScreenConfig!!.orientation)
if (mScreenConfig!!.orientation == "portrait") {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
} else if (mScreenConfig!!.orientation == "landscape") {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
}
/**
* 根据云端屏幕配置旋转视频画面
*/
private fun updateRotation() {
if (!mScreenConfigChanged || !mVideoStreamConfigChanged) {
Log.w(
TAG, ("updateRotation failed,mScreenConfigChanged=" + mScreenConfigChanged
+ " mVideoStreamConfigChanged=" + mScreenConfigChanged)
)
return
}
if (mVideoStreamConfig!!.width > mVideoStreamConfig!!.height) {
if (mScreenConfig!!.orientation == "portrait") {
mRenderView!!.setVideoRotation(VideoRotation.ROTATION_90)
} else {
mRenderView!!.setVideoRotation(VideoRotation.ROTATION_0)
}
} else {
if (mScreenConfig!!.orientation == "landscape") {
mRenderView!!.setVideoRotation(VideoRotation.ROTATION_270)
} else {
mRenderView!!.setVideoRotation(VideoRotation.ROTATION_0)
}
}
}
/**
* 为不同的云端实例设置处理器
*
*
* 对于云端PC应用可以使用[PcTouchListener]
* (云端PC应用在支持windows多点触摸时可使用[MobileTouchListener])
*
* 对于手游要使用[MobileTouchListener]
*/
private fun setTouchHandler(session: TcrSession, renderView: TcrRenderView, gameType: Int) {
when (gameType) {
MOBILE_GAME -> renderView.setOnTouchListener(MobileTouchListener(session))
PC_GAME -> {
val pcTouchListener = PcTouchListener(session)
pcTouchListener.zoomHandler.setZoomRatio(1f, 5f)
renderView.setOnTouchListener(pcTouchListener)
pcTouchListener.setShortClickListener(PcClickListener(session))
}
else -> Log.e(TAG, "UNKNOWN DeviceMode!!")
}
}
private fun showToast(msg: String, duration: Int) {
runOnUiThread {
Toast.makeText(this@CloudGameActivity, msg, duration)
.show()
}
}
/**
* 观察TcrSession通知出的各类事件处理各类事件通知的消息和数据
*
* @see TcrSession.Event
*/
private val mSessionEventObserver = TcrSession.Observer { event, eventData ->
when (event) {
TcrSession.Event.STATE_INITED -> {
// 回调数据中拿到client session并请求ServerSession
val clientSession = eventData as String
requestServerSession(clientSession)
}
TcrSession.Event.STATE_CONNECTED -> {
// 连接成功后设置操作模式
// 与云端的交互需在此事件回调后开始调用接口
runOnUiThread {
setTouchHandler(
mTcrSession!!,
mRenderView!!, PC_GAME
)
}
createCustomDataChannel()
}
TcrSession.Event.STATE_RECONNECTING -> showToast("重连中...", Toast.LENGTH_LONG)
TcrSession.Event.STATE_CLOSED -> {
showToast("会话关闭", Toast.LENGTH_SHORT)
finish()
}
TcrSession.Event.SCREEN_CONFIG_CHANGE -> {
mScreenConfig = eventData as ScreenConfig
updateOrientation()
mScreenConfigChanged = true
updateRotation()
}
TcrSession.Event.VIDEO_STREAM_CONFIG_CHANGED -> {
mVideoStreamConfig = eventData as VideoStreamConfig
mVideoStreamConfigChanged = true
updateRotation()
}
TcrSession.Event.CLIENT_STATS -> {
val statsInfo = eventData as StatsInfo
runOnUiThread {
binding.statsValue.text =
(" fps: " + statsInfo.fps + " bitrate: " + mDf.format(
statsInfo.bitrate / 1024.0 / 1024.0
)
+ "Mb/s rtt: " + statsInfo.rtt + "ms")
}
}
TcrSession.Event.CURSOR_STATE_CHANGE -> {
val cursorState = eventData as CursorState
Log.i(
TAG,
"cursor showing state changed, $cursorState"
)
}
else -> {}
}
}
/**
* 创建自定义数据通道
*/
private fun createCustomDataChannel() {
if (mTcrSession == null) {
return
}
// 10000为数据通道端口请替换为你们自己业务的端口
mCustomDataChannel =
mTcrSession!!.createCustomDataChannel(10000, object : CustomDataChannel.Observer {
override fun onConnected(port: Int) {
val msg = "Your message"
mCustomDataChannel!!.send(ByteBuffer.wrap(msg.toByteArray(StandardCharsets.UTF_8)))
Log.i(
TAG,
"onConnected() send data to port $port: $msg"
)
}
override fun onError(port: Int, code: Int, msg: String) {
Log.e(TAG, "onError() $port msg:$msg")
}
override fun onMessage(port: Int, data: ByteBuffer) {
Log.i(
TAG,
"onMessage() port=" + port + " data=" + StandardCharsets.UTF_8.decode(data)
)
}
})
}
override fun onBackPressed() {
if (mTcrSession != null) return
super.onBackPressed()
}
override fun onDestroy() {
if (mTcrSession != null) {
mTcrSession!!.release()
}
if (mRenderView != null) {
mRenderView!!.release()
}
super.onDestroy()
}
private fun readConfigFile(context: Context, fileName: String): String? {
try {
val am = context.assets
val `is` = am.open(fileName)
val isr = InputStreamReader(`is`, StandardCharsets.UTF_8)
val input = CharArray(`is`.available())
isr.read(input)
isr.close()
`is`.close()
return String(input)
} catch (e: Exception) {
Log.e(
TAG,
"readConfigFile failed:$e"
)
}
return null
}
companion object {
private const val TAG = "CloudGameActivity"
private const val MOBILE_GAME = 1
private const val PC_GAME = 2
}
}

View File

@ -0,0 +1,41 @@
package com.gh.gamecenter.feature.cloudgame
import android.content.Intent
import android.view.Gravity
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.gh.gamecenter.cloudgame.R
import com.gh.gamecenter.common.utils.PackageFlavorHelper
import com.gh.gamecenter.common.utils.dip2px
import com.lzf.easyfloat.EasyFloat
import com.lzf.easyfloat.enums.ShowPattern
import com.lzf.easyfloat.enums.SidePattern
object CloudGameHelper {
fun showCloudGameFloat(activity: AppCompatActivity) {
if (PackageFlavorHelper.IS_TEST_FLAVOR) {
EasyFloat.with(activity)
.setLayout(R.layout.layout_cloud_window)
.setTag("cloud_game_float")
.setAnimator(null)
.setGravity(Gravity.TOP.xor(Gravity.END), 0, 250F.dip2px())
.setSidePattern(SidePattern.RESULT_SIDE)
.setDragEnable(true)
.setShowPattern(ShowPattern.CURRENT_ACTIVITY)
.registerCallback {
createResult { _, _, view ->
val tv = view?.findViewById<TextView>(R.id.iconTv)
tv?.text = "\uFE0F\uD83C\uDFAE"
view?.setOnClickListener {
// Handle click event
activity.startActivity(Intent(activity, CloudGameActivity::class.java))
}
}
}
.show()
}
}
}

View File

@ -0,0 +1,44 @@
package com.gh.gamecenter.feature.cloudgame
import android.app.Application
import android.content.Context
import android.content.res.Configuration
import com.gh.gamecenter.core.iinterface.IApplication
import com.google.auto.service.AutoService
@AutoService(IApplication::class)
class HaloApp : IApplication {
override fun attachBaseContext(base: Context) {
// Do nothing
}
override fun onCreate(application: Application) {
mApp = application
}
override fun onLowMemory() {
// Do nothing
}
override fun onTerminate() {
// Do nothing
}
override fun onTrimMemory(level: Int) {
// Do nothing
}
override fun onConfigurationChanged(newConfig: Configuration) {
// Do nothing
}
companion object {
private lateinit var mApp: Application
@JvmStatic
fun getInstance(): Application {
return mApp
}
}
}

View File

@ -0,0 +1,97 @@
package com.gh.gamecenter.feature.cloudgame
import androidx.annotation.WorkerThread
import com.gh.gamecenter.common.utils.EnvHelper
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
object OkHelper {
@WorkerThread
fun obtainServerSession(clientSession: String, userId: String): String {
val client = OkHttpClient()
// Define the JSON body as a string
val json = """
{
"user_id": "${userId}",
"client_session": "${clientSession}",
"application_id": "app-attadogx"
}
"""
// Create a RequestBody with the JSON and the appropriate content type
val mediaType = "application/json; charset=utf-8".toMediaType()
val requestBody = json.toRequestBody(mediaType)
// Create the Request object
val request = Request.Builder()
.url(EnvHelper.getNewHost() + "tencent/car/start_app")
.post(requestBody)
.build()
try {
val response = client.newCall(request).execute()
if (response.isSuccessful) {
val resultString = response.body?.string() ?: ""
if (resultString.isNotEmpty()) {
return JSONObject(resultString).optString("server_session", "")
} else {
return ""
}
} else {
"Request failed with code: ${response.code}" // Handle error case
}
} catch (e: Exception) {
"Error: ${e.message}" // Handle exceptions
}
return ""
}
@WorkerThread
fun stopServerSession(userId: String): String {
val client = OkHttpClient()
// Define the JSON body as a string
val json = """
{
"user_id": "${userId}"
}
"""
// Create a RequestBody with the JSON and the appropriate content type
val mediaType = "application/json; charset=utf-8".toMediaType()
val requestBody = json.toRequestBody(mediaType)
// Create the Request object
val request = Request.Builder()
.url(EnvHelper.getNewHost() + "tencent/car/stop_app")
.post(requestBody)
.build()
try {
val response = client.newCall(request).execute()
if (response.isSuccessful) {
val resultString = response.body?.string() ?: ""
if (resultString.isNotEmpty()) {
return JSONObject(resultString).optString("server_session", "")
} else {
return ""
}
} else {
"Request failed with code: ${response.code}" // Handle error case
}
} catch (e: Exception) {
"Error: ${e.message}" // Handle exceptions
}
return ""
}
}

View File

@ -0,0 +1,35 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- First Stroke Layer -->
<item>
<shape android:shape="rectangle">
<corners android:radius="4dp" />
<stroke
android:width="3dp"
android:color="@color/black" /> <!-- Second stroke color -->
</shape>
</item>
<!-- Second Stroke Layer -->
<item>
<shape android:shape="rectangle">
<corners android:radius="4dp" />
<stroke
android:width="2dp"
android:color="@color/primary_theme" /> <!-- First stroke color -->
</shape>
</item>
<!-- Transparent Content Layer -->
<item
android:bottom="4dp"
android:left="4dp"
android:right="4dp"
android:top="4dp">
<shape android:shape="rectangle">
<corners android:radius="2dp" />
<solid android:color="@android:color/transparent" />
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<group>
<clip-path
android:pathData="M0,0h48v48h-48z"/>
<path
android:fillAlpha="0.6"
android:fillColor="#000000"
android:pathData="M24,24m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"/>
<path
android:fillColor="#ffffff"
android:pathData="M18,17.608C18,16.038 19.728,15.08 21.06,15.913L31.286,22.304C32.54,23.087 32.54,24.913 31.286,25.696L21.06,32.088C19.728,32.92 18,31.962 18,30.392V17.608Z"/>
</group>
</vector>

View File

@ -0,0 +1,112 @@
<?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"
android:background="@color/black">
<FrameLayout
android:id="@+id/render_view_parent"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintEnd_toStartOf="@id/rightController"
app:layout_constraintStart_toEndOf="@id/leftController" />
<RelativeLayout
android:id="@+id/keyboardContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="bottom"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/rightController"
app:layout_constraintStart_toEndOf="@id/leftController" />
<RelativeLayout
android:id="@+id/joystickContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="bottom"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/rightController"
app:layout_constraintStart_toEndOf="@id/leftController" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/stats_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="10dp"
android:textColor="@color/white"
android:textSize="11sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/leftController"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<Button
android:id="@+id/connectBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="启动" />
<Button
android:id="@+id/disconnectBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="关闭" />
</LinearLayout>
<LinearLayout
android:id="@+id/rightController"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<Button
android:id="@+id/keyboardBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="键盘" />
<Button
android:id="@+id/joystickBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="手柄" />
<Button
android:id="@+id/joystickEditBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="编辑手柄" />
<Button
android:id="@+id/cursorBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="鼠标" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<com.gh.gamecenter.common.view.MaterializedConstraintLayout 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"
android:background="@color/ui_background_fixed_dark"
android:orientation="vertical">
<com.gh.gamecenter.common.view.StatusBarView
android:id="@+id/statusBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/ui_surface_fixed_dark" />
<RelativeLayout
android:id="@+id/normal_toolbar_container"
android:layout_width="match_parent"
android:layout_height="@dimen/appbar_height"
app:layout_constraintTop_toBottomOf="@id/statusBar">
<androidx.appcompat.widget.Toolbar
android:id="@+id/normal_toolbar"
style="@style/Base_ToolbarStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/ui_surface_fixed_dark"
app:contentInsetEnd="0dp"
app:contentInsetStartWithNavigation="0dp"
app:navigationIcon="@null">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/backContainer"
android:layout_width="48dp"
android:layout_height="48dp">
<ImageView
android:id="@+id/backBtn"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
app:srcCompat="@drawable/ic_function_close" />
</FrameLayout>
<TextView
android:id="@+id/normal_title"
style="@style/toolbar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="@color/text_aw_primary"
android:textSize="16sp"
tools:text="12345" />
<ImageView
android:id="@+id/arrowIv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="-12dp"
android:layout_toRightOf="@+id/normal_title"
android:padding="16dp"
app:srcCompat="@drawable/ic_pin_down" />
</RelativeLayout>
</androidx.appcompat.widget.Toolbar>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@color/ui_background_fixed_dark"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/normal_toolbar_container">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/list_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_rv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<include
android:id="@+id/pieceMediaControl"
layout="@layout/piece_media_control" />
<com.gh.gamecenter.common.view.NavigationBarView
android:id="@+id/navigationBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/ui_surface_fixed_dark" />
</LinearLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<include
android:id="@+id/reuse_ll_loading"
layout="@layout/reuse_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
<include
android:id="@+id/reuse_no_connection"
layout="@layout/reuse_no_connection" />
<include
android:id="@+id/reuse_none_data"
layout="@layout/reuse_none_data" />
<include
android:id="@+id/reuse_data_exception"
layout="@layout/reuse_data_exception" />
</RelativeLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/layout_activity_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.gh.gamecenter.common.view.MaterializedConstraintLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="100dp"
app:cardBackgroundColor="@color/secondary_yellow">
<TextView
android:id="@+id/iconTv"
android:layout_width="40dp"
android:layout_height="40dp" />
</androidx.cardview.widget.CardView>

View File

@ -0,0 +1,3 @@
<resources xmlns:tools="http://schemas.android.com/tools">
</resources>

View File

@ -0,0 +1,3 @@
<resources>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">SimpleDemo</string>
</resources>

View File

@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.SimpleDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -73,6 +73,7 @@ dependencies {
api "com.tencent.mm.opensdk:wechat-sdk-android-without-mta:${mta}"
api "androidx.room:room-runtime:${room}"
api "androidx.room:room-rxjava2:${room}"
api "androidx.room:room-ktx:${room}"
ksp("androidx.room:room-compiler:${room}")
api "androidx.collection:collection-ktx:${collection}"
api "androidx.activity:activity:${activity}"

View File

@ -337,6 +337,7 @@ object SensorsBridge {
private const val EVENT_CALENDAR_PERMISSIONS_DIALOG_SHOW = "CalendarPermissionsDialogShow"
private const val EVENT_CALENDAR_PERMISSIONS_DIALOG_CLICK = "CalendarPermissionsDialogClick"
private const val CALENDAR_PERMISSIONS_DIALOG_RESULT = "CalendarPermissionsDialogResult"
private const val EVENT_GAME_DETAIL_VIDEO_CLICK= "GameDetailVideoClick"
private var mIsSensorsEnabled = false
@ -5340,4 +5341,37 @@ object SensorsBridge {
}
trackEvent(CALENDAR_PERMISSIONS_DIALOG_RESULT, json)
}
/**
* 事件ID:GameDetailVideoClick
* 事件名称:游戏详情视频点击事件
*/
fun trackGameDetailVideoClick(
gameName: String,
gameId: String,
gameType: String,
lastPageName: String,
lastPageId: String,
action: String,
playType: String,
isFullScreen: Boolean,
sequence: Int,
tabName: String,
playLength: String
) {
val json = json {
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_GAME_TYPE to gameType
KEY_LAST_PAGE_ID to lastPageId
KEY_LAST_PAGE_NAME to lastPageName
KEY_ACTION to action
KEY_PLAY_TYPE to playType
"is_full_screen" to isFullScreen
KEY_SEQUENCE to sequence
"tab_name" to tabName
"play_length" to playLength
}
trackEvent(EVENT_GAME_DETAIL_VIDEO_CLICK, json)
}
}

View File

@ -105,6 +105,7 @@ class SegmentedFilterView @JvmOverloads constructor(context: Context, attrs: Att
if (mContainerBackground != null) background = mContainerBackground
mContainer = FrameLayout(context)
mIndicator = View(context).apply {
visibility = View.GONE
background = mIndicatorBackground
}
mRadioGroup = RadioGroup(context).apply {
@ -127,6 +128,7 @@ class SegmentedFilterView @JvmOverloads constructor(context: Context, attrs: Att
mIndicator.updateLayoutParams<ViewGroup.LayoutParams> {
width = mRadioGroup.getChildAt(defaultCheckPosition)?.width ?: mItemWidth
}
mIndicator.visibility = View.VISIBLE
setChecked(defaultCheckPosition)
}
}

View File

@ -18,6 +18,7 @@ gradle.ext.enableQuickLogin = gradle.startParameter.projectProperties.get('ENABL
gradle.ext.enableAccelerator = gradle.startParameter.projectProperties.get('ENABLE_ACCELERATOR')?.toBoolean() ?: false
gradle.ext.enableWechatPay = gradle.startParameter.projectProperties.get('ENABLE_WECHAT_PAY')?.toBoolean() ?: false
gradle.ext.enableAliPay = gradle.startParameter.projectProperties.get('ENABLE_ALI_PAY')?.toBoolean() ?: false
gradle.ext.enableCloudGame = gradle.startParameter.projectProperties.get('ENABLE_CLOUD_GAME')?.toBoolean() ?: false
// 是否启用路由文档输出
gradle.ext.enableRouteDoc = gradle.startParameter.projectProperties.get('ENABLE_ROUTE_DOC')?.toBoolean() ?: false
@ -68,7 +69,8 @@ def optionalModules = [
':feature:ali_pay',
':module_message',
':module_sensors_data',
':module_va_impl'
':module_va_impl',
':feature:cloud_game',
]
// 定义 va 可选模块
@ -164,4 +166,7 @@ if (gradle.ext.excludeOptionalModules == false) { // 默认全部模块都包含
if (gradle.ext.enableAliPay) {
include ':feature:ali_pay'
}
if (gradle.ext.enableCloudGame) {
include ':feature:cloud_game'
}
}

2
vasdk

Submodule vasdk updated: 932474708c...0da6ac4194