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
63 changed files with 1295 additions and 154 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

@ -1,6 +1,5 @@
package com.gh.common.provider
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.core.provider.IAcceleratorDataHolderProvider
import com.gh.gamecenter.feature.entity.VipEntity
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
@ -13,8 +12,12 @@ class IAcceleratorDataHolderProviderImpl : IAcceleratorDataHolderProvider {
}
}
override fun getGhVersionName(): String {
return PackageUtils.getGhVersionName()
override fun addInitFunResult(result: String) {
AcceleratorDataHolder.instance.addInitFunResult(result)
}
override fun getInitFunResults(): List<String> {
return AcceleratorDataHolder.instance.initResults
}
override fun clear() {

View File

@ -306,14 +306,21 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
DownloadNotificationHelper.addOrUpdateDownloadNotification(downloadEntity)
NDataChanger.notifyDataChanged(downloadEntity)
DownloadManager.getInstance().updateDownloadEntity(downloadEntity)
// 仅官网渠道上报 XAPK 异常信息
if (HaloApp.getInstance().channel == "GH_206") {
SentryHelper.onEvent(
"XAPK_UNZIP_ERROR",
"gameName", downloadEntity.name,
"errorDigest", exception.localizedMessage
)
}
DownloadDataHelper.uploadDownloadStatusEvent(downloadEntity, "xapk解压失败")
SensorsBridge.trackGameDecompressionFailed(
downloadEntity.gameId,
downloadEntity.name,
downloadEntity.categoryChinese,
exception.localizedMessage ?: "unknown error"
downloadEntity.categoryChinese
)
}

View File

@ -45,7 +45,6 @@ import com.gh.gamecenter.common.base.fragment.BaseLazyFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.mvvm.Status
@ -1112,13 +1111,6 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
return true
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(reuse: EBReuse) {
if ("download" == reuse.type) {
downloadBinding.detailProgressbar.performClick()
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(status: EBDownloadStatus) {
updateDownloadCountHint(packageViewModel.filterSameUpdateLiveData.value)

View File

@ -21,14 +21,13 @@ class StartingAcceleratorViewModel : ViewModel() {
val useCase = AccelerationUseCase()
private val _restartingAcceleratorAction = MutableLiveData<Event<Unit>>()
val restartingAcceleratorAction: LiveData<Event<Unit>> = _restartingAcceleratorAction
private val _restartingAcceleratorAction = MutableLiveData<Event<Boolean>>()
val restartingAcceleratorAction: LiveData<Event<Boolean>> = _restartingAcceleratorAction
fun loadAcceleratorToken() {
val userId = UserManager.getInstance().userId
if (userId.isNotBlank()) {
UserRepository.getInstance().setAcceleratorToken(userId) {
// 这里就算setToken失败也要调用启动加速失败会走正常的日志上报
_restartingAcceleratorAction.value = Event(Unit)
_restartingAcceleratorAction.value = Event(it)
}
}
}

View File

@ -311,7 +311,7 @@ class GameDetailFragment : LazyFragment(), IScrollable {
private fun initDetailRv() {
binding.detailRv.run {
itemAnimator = null
(itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
layoutManager = detailLayoutManager
adapter = detailListAdapter
addOnScrollListener(object : OnScrollListener() {

View File

@ -46,7 +46,7 @@ class GameDetailBriefItemViewHolder(
highlightedTextClickListener = TextHelper.CopyToClipboardHighlightedTextClick()
)
briefTv.post {
expandTv.isVisible = briefTv.lineCount == 3 && (briefTv.layout?.getEllipsisCount(2) ?: 0) > 0
expandTv.isVisible = briefTv.lineCount == 3 && briefTv.layout.getEllipsisCount(2) > 0
}
expandTv.setOnClickListener {
SensorsBridge.trackGameDetailModuleClick(

View File

@ -194,8 +194,6 @@ class GameDetailComprehensivePanelItemViewHolder(
maxLines = if (parentViewHolder.showPart && !parentViewHolder.isExpand) 1 else Int.MAX_VALUE
text = data.text
post {
if (layout == null) return@post
val hasEllipsize = layout.getEllipsisCount(0) > 0
if (parentViewHolder.showPart && hasEllipsize && !parentViewHolder.binding.expandTv.isVisible) {
parentViewHolder.binding.expandTv.isVisible = true

View File

@ -27,7 +27,7 @@ class GameDetailDeveloperWordItemViewHolder(
.fromHtmlCompat(PicassoImageGetter(contentTv), ExtraTagHandler())
)
contentTv.post {
expandTv.isVisible = (contentTv.lineCount == 3 && (contentTv.layout?.getEllipsisCount(2) ?: 0) > 0) || contentTv.lineCount > 3
expandTv.isVisible = (contentTv.lineCount == 3 && contentTv.layout.getEllipsisCount(2) > 0) || contentTv.lineCount > 3
}
expandTv.background = R.drawable.bg_ui_surface_expand_gradient.toDrawable(context)
expandTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))

View File

@ -36,7 +36,7 @@ class GameDetailUpdateItemViewHolder(
}
contentTv.text = entity.updateDes
contentTv.post {
expandTv.isVisible = contentTv.lineCount == 3 && (contentTv.layout?.getEllipsisCount(2) ?: 0) > 0
expandTv.isVisible = contentTv.lineCount == 3 && contentTv.layout.getEllipsisCount(2) > 0
}
expandTv.background = R.drawable.bg_ui_surface_expand_gradient.toDrawable(context)
expandTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))

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

@ -63,7 +63,7 @@ class HistoryApkListAdapter(
holder.binding.expandTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(mContext))
holder.binding.updateDescTv.text = apkEntity.updateDesc
holder.binding.updateDescTv.post {
holder.binding.expandTv.isVisible = holder.binding.updateDescTv.lineCount == 3 && (holder.binding.updateDescTv.layout?.getEllipsisCount(2) ?: 0) > 0
holder.binding.expandTv.isVisible = holder.binding.updateDescTv.lineCount == 3 && holder.binding.updateDescTv.layout.getEllipsisCount(2) > 0
}
holder.binding.versionTv.text = "版本${apkEntity.version}"
holder.binding.releaseDateTv.text = TimeUtils.getFormatTime(apkEntity.updateTime)

View File

@ -110,11 +110,8 @@ class CustomGameGallerySlideViewHolder(
private val dataList = arrayListOf<GameEntity>()
fun submitList(data: List<GameEntity>) {
val newSubData = data.filterIndexed { i, _ -> i % 3 == index }
dataList.clear()
dataList.addAll(newSubData)
dataList.addAll(data)
notifyDataSetChanged()
}
@ -127,7 +124,8 @@ class CustomGameGallerySlideViewHolder(
override fun onBindViewHolder(holder: GameGallerySlideItemViewHolder, position: Int) {
if (dataList.isEmpty()) return
val realPosition = position % dataList.size
val dataPosition = position * 3 + index
val realPosition = dataPosition % dataList.size
val gameEntity = dataList[realPosition]
exposureInvoke(realPosition, gameEntity)

View File

@ -52,7 +52,7 @@ class UploadThread(
.setCancelHook(true)
.setCheckpointFile(recordDirectory)
.setPartSize(10 * 1024 * 1024)
.setTaskNum(1)
.setTaskNum(2)
.setDataTransferListener {
Utils.log(
"OssUpload",
@ -74,9 +74,9 @@ class UploadThread(
mUploadInput = this
}
val config = TransportConfig.builder()
.readTimeoutMills(5 * 1000)
.writeTimeoutMills(5 * 1000)
.connectTimeoutMills(5 * 1000)
.readTimeoutMills(15 * 1000)
.writeTimeoutMills(15 * 1000)
.connectTimeoutMills(15 * 1000)
.maxRetryCount(2)
.build()

View File

@ -26,7 +26,6 @@ import org.json.JSONException
import org.json.JSONObject
import java.io.File
import java.io.IOException
import java.net.MalformedURLException
import java.net.URL
import java.net.URLConnection
import java.text.DecimalFormat
@ -342,9 +341,6 @@ object NDownloadBridge : InnerDownloadListener, IErrorRetryHandler {
// 由于这里的异常不会影响正常下载,所以直接打印异常,不做处理
// 具体可见 https://sentry.shanqu.cc/organizations/lightgame/issues/371082/
e.printStackTrace()
} catch (e: MalformedURLException) {
// 由于重定向的 url 可能是一个不合法的 url这里捕获异常
e.printStackTrace()
}
NDataChanger.notifyDataChanged(downloadEntity)

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

@ -20,6 +20,14 @@ class AcceleratorDataHolder {
private val listeners = mutableSetOf<OnDataHolderListener>()
private val _initResults = arrayListOf<String>()
val initResults: List<String>
get() = _initResults
fun addInitFunResult(result: String) {
_initResults.add(result)
}
private var _hasAcctGameInfoInLocal = false
val hasAcctGameInfoInLocal: Boolean
get() = _hasAcctGameInfoInLocal

View File

@ -106,8 +106,7 @@ class RealNameInfoFragment : ToolbarFragment() {
}
private fun initEditingView() {
val bodyString =
"为响应《国家新闻出版署关于防止未成年沉迷网络游戏的通知》,请认真填写您的身份信息。您提供的证件信息将受到严格保护,仅用于用户实名制认证,不会用作其他用途,请放心填写。前往了解更多信息>>"
val bodyString = getText(R.string.realname_body_tv)
mBinding.bodyTv.text =
SpanBuilder(bodyString)
.click(
@ -125,13 +124,13 @@ class RealNameInfoFragment : ToolbarFragment() {
.build()
mBinding.bodyTv.movementMethod = CustomLinkMovementMethod.getInstance()
val hintString = "特别说明:由于部分用户之前的实名信息不正确或认证失败,可能需要重新认证,请提交真实的信息进行认证即可。部分游戏仅对成年用户进行开放"
val hintString = getText(R.string.realname_hint_tv)
mBinding.hintTv.text =
SpanBuilder(hintString)
.bold(0, 5)
.build()
val manualHintString = "若您提交的真实身份信息未通过认证或者您持有的为港澳台\\国外身份证件,可转交人工审核"
val manualHintString = getText(R.string.realname_manual_hint_tv)
mBinding.manualHintTv.text =
SpanBuilder(manualHintString)
.click(
@ -161,6 +160,9 @@ class RealNameInfoFragment : ToolbarFragment() {
mBinding.nameEt.doOnTextChanged { _, _, _, _ ->
updateSubmitBtn()
}
mBinding.textDotIndicator.setOnClickListener {
mBinding.nameEt.text.insert(mBinding.nameEt.selectionStart, "\u00B7")
}
mBinding.idCardEt.doOnTextChanged { _, _, _, _ ->
updateSubmitBtn()
}
@ -185,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

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

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

@ -34,7 +34,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="为响应《国家新闻出版署关于防止未成年沉迷网络游戏的通知》,请认真填写您的身份信息。您提供的证件信息将受到严格保护,仅用于用户实名制认证,不会用作其他用途,请放心填写。前往了解更多信息>>" />
tools:text="@string/realname_body_tv" />
<TextView
android:id="@+id/hintTv"
@ -48,7 +48,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/bodyTv"
tools:text="特别说明:由于部分用户之前的实名信息不正确或认证失败,可能需要重新认证,请提交真实的信息进行认证即可。部分游戏仅对成年用户进行开放" />
tools:text="@string/realname_hint_tv" />
<LinearLayout
android:id="@+id/badgeContainer"
@ -109,9 +109,10 @@
<EditText
android:id="@+id/nameEt"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:background="@drawable/bg_shape_fa_radius_2"
android:hint="请输入真实姓名"
android:padding="8dp"
@ -120,8 +121,22 @@
android:textColorHint="@color/text_instance"
android:textCursorDrawable="@drawable/cursor_color"
android:textSize="14sp"
app:layout_constraintEnd_toStartOf="@+id/textDotIndicator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/nameTv" />
<TextView
android:id="@+id/textDotIndicator"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/bg_stroke_fa_radius_2"
android:gravity="center"
android:text="@string/realname_dot_indicator"
android:textSize="14sp"
android:textColor="@color/text_secondary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/nameEt" />
<TextView
android:id="@+id/idCardTv"
android:layout_width="wrap_content"
@ -177,7 +192,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/submitBtn"
tools:text="若您提交的真实身份信息未通过认证或者您持有的为港澳台\国外身份证件,可转交人工审核" />
tools:text="@string/realname_manual_hint_tv" />
<TextView
android:id="@+id/errorHintTv"

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

@ -412,6 +412,9 @@
<string name="realname_success_badge_hint_extra_underage">當前認証狀態:未成年</string>
<string name="realname_pending_badge_hint">實名信息認証中...</string>
<string name="realname_pending_badge_hint_extra">認証期間不影響您正常的遊戲體驗</string>
<string name="realname_body_tv">爲響應《國家新聞出版署關於防止未成年沉迷網絡遊戲的通知》,請認真填冩您的身份信息。您提供的証件信息將受到嚴格保護,僅用於用戶實名製認証,不會用作其他用途,請放心填冩。前往了解更多信息>></string>
<string name="realname_hint_tv">特別説明:由於部分用戶之前的實名信息不正確或認証失敗,可能需要重新認証,請提交真實的信息進行認証即可。部分遊戲僅對成年用戶進行開放。(特殊字符“·”可通過快捷鍵進行添加)</string>
<string name="realname_manual_hint_tv">若您提交的真實身份信息未通過認証或者您持有的爲港澳颱\國外身份証件,可轉交人工審核</string>
<string name="interested_game_footer_hint"><Data><![CDATA[<font color="#1383EB">設置偏好</font>]]></Data> ,讓推薦更懂你的心~</string>
@ -477,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>

View File

@ -412,7 +412,10 @@
<string name="realname_success_badge_hint_extra_underage">当前认证状态:未成年</string>
<string name="realname_pending_badge_hint">实名信息认证中...</string>
<string name="realname_pending_badge_hint_extra">部分游戏在认证期间无法进行下载</string>
<string name="realname_body_tv">为响应《国家新闻出版署关于防止未成年沉迷网络游戏的通知》,请认真填写您的身份信息。您提供的证件信息将受到严格保护,仅用于用户实名制认证,不会用作其他用途,请放心填写。前往了解更多信息>></string>
<string name="realname_hint_tv">特别说明:由于部分用户之前的实名信息不正确或认证失败,可能需要重新认证,请提交真实的信息进行认证即可。部分游戏仅对成年用户进行开放。(特殊字符“·”可通过快捷键进行添加)</string>
<string name="realname_manual_hint_tv">若您提交的真实身份信息未通过认证或者您持有的为港澳台\国外身份证件,可转交人工审核</string>
<string name="realname_dot_indicator" translatable="false"></string>
<string name="interested_game_footer_hint"><Data><![CDATA[<font color="#1383EB">设置偏好</font>]]></Data> ,让推荐更懂你的心~</string>
<item name="download_item_type" type="id" />
@ -490,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>

View File

@ -7,8 +7,8 @@ ext {
targetSdkVersion = 30 // 升级targetSdkVersion到 34 时需要根据官方文档补全前台服务的权限类型。比如 NDownloadServiceKeepAliveService
// application info (每个大版本之间的 versionCode 增加 20)
versionCode = 1152
versionName = "5.40.2"
versionCode = 1150
versionName = "5.40.0"
applicationId = "com.gh.gamecenter"
applicationIdGat = "com.gh.gamecenter.intl"
@ -100,7 +100,7 @@ ext {
skeleton = "1.1.5"
mta = "6.8.0"
romChecker = "1.0.3"
oss = "2.8.8"
oss = "2.6.0"
desugarJdkLibs = "1.1.5"
toolargetool = "0.3.0"
chart = "3.1.0"
@ -150,7 +150,7 @@ ext {
xcrashVersion = "3.1.0"
aliPayVersion = "15.8.17"
acceleratorVersion = "2.0.2"
acceleratorVersion = "1.0.1"
}
apply from: 'dependencies_vasdk.gradle'

View File

@ -1,14 +1,11 @@
package com.gh.gamecenter.accelerator.provider
import android.app.Application
import com.gh.gamecenter.common.utils.EnvHelper
import com.gh.gamecenter.core.HaloApp
import com.gh.gamecenter.core.callback.AccelerateState
import com.gh.gamecenter.core.callback.OnAccelerateListener
import com.gh.gamecenter.core.provider.IAcceleratorDataHolderProvider
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.utils.NewFlatLogUtils
import com.gh.gamecenter.feature.utils.SentryHelper
import com.lightgame.utils.Utils
import com.qeeyou.qyvpn.QyAccelerator
@ -26,8 +23,6 @@ import java.io.File
class AcceleratorProviderImpl : IAcceleratorProvider {
private var _token = ""
private val initResults = arrayListOf<String>()
// 根据包名回调
private val listeners = hashMapOf<String, OnAccelerateListener>()
@ -96,7 +91,8 @@ class AcceleratorProviderImpl : IAcceleratorProvider {
override fun onExecLifecycleInitFun(isEnter: Boolean) {
super.onExecLifecycleInitFun(isEnter)
Utils.log(LOG_TAG, "onExecLifecycleInitFun:$isEnter")
initResults.add("onExecLifecycleInitFun:$isEnter")
TheRouter.get(IAcceleratorDataHolderProvider::class.java)
?.addInitFunResult("onExecLifecycleInitFun:$isEnter")
}
})
QyAccelerator.getInstance().bindQyAccRelatedListener(qyListener)
@ -109,29 +105,16 @@ class AcceleratorProviderImpl : IAcceleratorProvider {
// 避免外部多次设置相同的token
return
}
fun setToken() {
QyAccelerator.getInstance().setQyUserToken(token, setResultCallback = { isSuccess, errMsg ->
Utils.log(LOG_TAG, "setQyUserToken:$token --isSuccess:$isSuccess --errMsg:$errMsg")
if (isSuccess) {
_token = token
} else {
// 将setToken错误事件上报的火山云便于后期分析原因
NewFlatLogUtils.logAcceleratorSetTokenError(initResults, errMsg ?: "")
}
callback?.invoke(isSuccess)
})
}
if (QyAccelerator.getInstance().checkApplicationContextIsInit()) {
setToken()
} else {
// 初始化失败再次初始化之后在setToken
val ghVersionName = TheRouter.get(IAcceleratorDataHolderProvider::class.java)?.getGhVersionName() ?: ""
init(EnvHelper.isDevEnv, HaloApp.getInstance(), ghVersionName)
// 不管初始化成功还是失败都需要再次setToken,如果失败,正常上报失败日志
setToken()
}
QyAccelerator.getInstance().setQyUserToken(token, setResultCallback = { isSuccess, errMsg ->
Utils.log(LOG_TAG, "setQyUserToken:$token --isSuccess:$isSuccess --errMsg:$errMsg")
if (isSuccess) {
_token = token
} else {
// 将setToken错误事件上报的sentry便于后期分析原因
SentryHelper.onEventInAllChannel(SENTRY_EVENT_ID, KEY_SET_TOKEN_ERROR_MESSAGE, errMsg)
}
callback?.invoke(isSuccess)
})
}
override fun deleteQyUserToken(): Boolean {
@ -144,7 +127,6 @@ class AcceleratorProviderImpl : IAcceleratorProvider {
_token = ""
listeners.clear()
allListener.clear()
initResults.clear()
return isDeleted
}
@ -189,6 +171,10 @@ class AcceleratorProviderImpl : IAcceleratorProvider {
companion object {
private const val LOG_TAG = "AcceleratorProviderImpl"
private const val SENTRY_EVENT_ID = "ACCELERATOR_SET_TOKEN_ERROR"
private const val KEY_SET_TOKEN_ERROR_MESSAGE = "set_token_error"
private const val KEY_ACC_FAILURE_ERROR = "key_acc_failure_error"
}
}

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

@ -3,7 +3,7 @@ package com.lg
import android.content.Context
import android.text.TextUtils
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.exposure.meta.MetaUtil
import com.gh.gamecenter.common.utils.toJson
import com.gh.gamecenter.core.HaloApp
import com.gh.gamecenter.core.provider.IAcceleratorDataHolderProvider
import com.gh.gamecenter.core.provider.IAppProvider
@ -16,7 +16,6 @@ import io.sentry.*
import io.sentry.android.core.SentryAndroid
import io.sentry.android.fragment.FragmentLifecycleIntegration
import io.sentry.protocol.Message
import io.sentry.protocol.User
@com.therouter.inject.ServiceProvider
class SentryProviderImpl : ISentryProvider {
@ -32,14 +31,6 @@ class SentryProviderImpl : ISentryProvider {
options.setAnrEnabled(false)
}
val androidId = MetaUtil.getBase64EncodedAndroidId()
if (androidId.isNotEmpty()) {
val user = User()
user.id = androidId
Sentry.setUser(user)
}
options.setDebug(BuildConfig.DEBUG)
options.setEnableAutoSessionTracking(true)
options.setEnvironment(flavor)
@ -140,7 +131,23 @@ class SentryProviderImpl : ISentryProvider {
}
Utils.log("Sentry", "$eventId + [${kv.joinToString(" , ")}]")
Sentry.captureEvent(sentryEvent)
Sentry.captureEvent(sentryEvent) {
// 添加Breadcrumb
it.addBreadcrumb(Breadcrumb().apply {
type = "initSdk"
category = "Init"
this.message = "init qiyou sdk"
level = SentryLevel.INFO
val iAcceleratorDataHolderProvider = TheRouter.get(IAcceleratorDataHolderProvider::class.java)
if (iAcceleratorDataHolderProvider != null) {
val initFunResults = iAcceleratorDataHolderProvider.getInitFunResults()
initFunResults.forEachIndexed { index, result ->
setData("init_sdk_fun_$index", result)
}
}
})
}
}
override fun captureException(e: Throwable) {

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

@ -44,7 +44,7 @@ object EnvHelper {
fun getHost(): String {
val buildConfig = TheRouter.get(IBuildConfigProvider::class.java)
return if (!PackageFlavorHelper.IS_TEST_FLAVOR) {
buildConfig?.getApiHost() ?: "https://and-api.ghzs6.com/v5d5d0/"
buildConfig?.getApiHost() ?: ""
} else {
if (isDevEnv) {
buildConfig?.getDevApiHost() ?: ""
@ -58,7 +58,7 @@ object EnvHelper {
fun getVHost(): String {
val buildConfig = TheRouter.get(IBuildConfigProvider::class.java)
return if (!PackageFlavorHelper.IS_TEST_FLAVOR) {
buildConfig?.getVApiHost() ?: "https://app-api.796697.com"
buildConfig?.getVDevApiHost() ?: ""
} else {
if (isDevEnv) {
buildConfig?.getVDevApiHost() ?: ""
@ -72,7 +72,7 @@ object EnvHelper {
fun getNewHost(): String {
val buildConfig = TheRouter.get(IBuildConfigProvider::class.java)
return if (!PackageFlavorHelper.IS_TEST_FLAVOR) {
buildConfig?.getNewApiHost() ?: "https://app-api.ghzs6.com/"
buildConfig?.getNewApiHost() ?: ""
} else {
if (isDevEnv) {
buildConfig?.getNewDevApiHost() ?: ""

View File

@ -1179,15 +1179,10 @@ fun DownloadEntity.putGameCategory(gameCategory: String) {
inline fun doOnMainProcessOnly(f: () -> Unit) {
val buildConfig = TheRouter.get(IBuildConfigProvider::class.java)
val processName = ProcessUtil.getCurrentProcessName()
var applicationId = buildConfig?.getApplicationId()
val packageUtilsConfig = TheRouter.get(IPackageUtilsProvider::class.java)
val processName = packageUtilsConfig?.obtainProcessName()
// buildConfig 为空的兜底方案,至于你说为什么会为空,我也不知道
if (applicationId.isNullOrEmpty()) {
applicationId = HaloApp.getInstance().packageName ?: "com.gh.gamecenter"
}
if (processName == null || applicationId == processName) {
if (processName == null || buildConfig?.getApplicationId() == processName) {
f.invoke()
} else {
tryWithDefaultCatch {

View File

@ -1292,21 +1292,18 @@ object SensorsBridge {
* @param gameId 游戏ID
* @param gameName 游戏名称
* @param gameType 游戏类型
* @param exceptionDigest 异常摘要
* @see EVENT_GAME_DEPRESSION_FAILED
*/
@JvmStatic
fun trackGameDecompressionFailed(
gameId: String,
gameName: String,
gameType: String,
exceptionDigest: String,
gameType: String
) {
val json = json {
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_GAME_TYPE to gameType
"exception_digest" to exceptionDigest
}
trackEvent(EVENT_GAME_DEPRESSION_FAILED, json)

View File

@ -4,7 +4,9 @@ interface IAcceleratorDataHolderProvider {
fun setVipEntity(vip: Any)
fun getGhVersionName(): String
fun addInitFunResult(result: String)
fun getInitFunResults(): List<String>
fun clear()
}

View File

@ -13,8 +13,6 @@ object NewFlatLogUtils {
private const val KEY_GAME_ID = "game_id"
private const val KEY_GAME_NAME = "game_name"
private const val KEY_TEXT = "text"
private const val KEY_ERROR = "error"
private const val KEY_INIT_SDK_FUN = "init_sdk_fun"
private fun log(jsonObject: JSONObject, logStore: String = "event", uploadImmediately: Boolean = false) {
Utils.log("NewFlatLogUtils", jsonObject.toString(4))
@ -54,14 +52,4 @@ object NewFlatLogUtils {
}
log(json)
}
@JvmStatic
fun logAcceleratorSetTokenError(initResults: List<String>, error: String) {
json {
KEY_EVENT to "accelerator_set_token_error"
KEY_ERROR to error
KEY_INIT_SDK_FUN to initResults.toString()
parseAndPutMeta().invoke(this)
}.let(::log)
}
}

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: 2ee1c3d532...0da6ac4194