Compare commits

..

16 Commits

Author SHA1 Message Date
8b077e4962 fix: 修复刷新后滚动图集数据可能出现重复的问题 2025-04-11 11:48:52 +08:00
ab795ab538 Merge branch 'feat/more-xapk-error-log' into 'release'
feat: xapk 解压失败增加异常摘要字段

See merge request halo/android/assistant-android!2156
2025-04-10 10:08:07 +08:00
ba0854ad9a feat: xapk 解压失败增加异常摘要字段 2025-04-10 10:00:57 +08:00
fc2051387a Merge branch 'cherry-pick' into 'release'
合并部分需求到 release

See merge request halo/android/assistant-android!2155
2025-04-10 09:43:58 +08:00
ab2973c7be feat: 游戏预约相关优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7828 2025-04-10 09:35:01 +08:00
2a08ea681d fix: 修复游戏详情视频/图集Tab闪烁的问题 2025-04-10 09:35:00 +08:00
e23d510fb0 fix: 游戏详情-视频/图集tab 神策埋点补充—客户端 https://jira.shanqu.cc/browse/GHZSCY-7781 2025-04-10 09:35:00 +08:00
3c6ee0c782 chore: 版本更新至 5.40.2 2025-04-09 15:43:49 +08:00
07840822a8 Merge branch 'fix/GHZSCY-7806-pick' into 'release'
fix:【光环助手】通知栏目 滚动问题 https://jira.shanqu.cc/browse/GHZSCY-7806

See merge request halo/android/assistant-android!2154
2025-04-09 13:45:37 +08:00
12e547d333 fix:【光环助手】通知栏目 滚动问题 https://jira.shanqu.cc/browse/GHZSCY-7806 2025-04-09 13:42:14 +08:00
8bb3736ad1 Merge branch 'fix/redirect-crash' into 'release'
fix: 捕抓重定向后的下载地址 url 异常造成的闪退

See merge request halo/android/assistant-android!2153
2025-04-09 11:28:39 +08:00
aa9ba5163f fix: 捕抓重定向后的下载地址 url 异常造成的闪退 2025-04-09 11:27:54 +08:00
23033edc42 Merge branch 'fix/sentry-user-id' into 'release'
feat: sentry 日志使用 base64 转码后的 androidId 替换原始随机 id

See merge request halo/android/assistant-android!2152
2025-04-09 11:18:05 +08:00
308e134aff feat: sentry 日志使用 base64 转码后的 androidId 替换原始随机 id 2025-04-09 11:17:30 +08:00
ac78ea0498 Merge branch 'feat/upload-accelerator-set-token-log' into 'release'
feat:将奇游加速器 setToken 错误日志从sentry转移到火山云

See merge request halo/android/assistant-android!2151
2025-04-09 11:13:31 +08:00
38bab9cf4f feat:将奇游加速器 setToken 错误日志从sentry转移到火山云 2025-04-09 11:13:30 +08:00
26 changed files with 189 additions and 490 deletions

View File

@ -386,15 +386,6 @@ dependencies {
implementation "com.j256.ormlite:ormlite-android:${ormlite}"
implementation "com.j256.ormlite:ormlite-core:${ormlite}"
implementation("io.coil-kt:coil:2.6.0") {
exclude group: "androidx.core"
exclude group: "androidx.lifecycle"
}
implementation("io.coil-kt:coil-svg:2.6.0") {
exclude group: "androidx.core"
exclude group: "androidx.lifecycle"
}
implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

View File

@ -13,14 +13,6 @@ class IAcceleratorDataHolderProviderImpl : IAcceleratorDataHolderProvider {
}
}
override fun addInitFunResult(result: String) {
AcceleratorDataHolder.instance.addInitFunResult(result)
}
override fun getInitFunResults(): List<String> {
return AcceleratorDataHolder.instance.initResults
}
override fun getGhVersionName(): String {
return PackageUtils.getGhVersionName()
}

View File

@ -208,7 +208,20 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, df.uri)
}
tempLauncher = null
tempLauncher =
activity.registerActivityResultLauncher(ActivityResultContracts.OpenDocumentTree()) { uri ->
if (uri != null) {
activity.contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
useDocStyleToUnzip = true
unzipAction.invoke()
}
tempLauncher?.unregister()
}
tempLauncher?.launch(obbUri)
},
@ -293,21 +306,14 @@ 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
downloadEntity.categoryChinese,
exception.localizedMessage ?: "unknown error"
)
}

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

@ -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

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

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

@ -1,8 +1,5 @@
package com.gh.gamecenter.wrapper
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PorterDuff
import android.net.Uri
import android.os.Bundle
@ -17,11 +14,6 @@ import androidx.core.text.color
import androidx.core.view.doOnNextLayout
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import coil.Coil
import coil.decode.SvgDecoder
import coil.load
import coil.request.ImageRequest
import coil.transform.Transformation
import com.ethanhua.skeleton.Skeleton
import com.ethanhua.skeleton.SkeletonScreen
import com.gh.common.iinterface.ISuperiorChain
@ -47,7 +39,6 @@ import com.gh.gamecenter.entity.BottomTab
import com.gh.gamecenter.login.entity.UserInfoEntity
import com.halo.assistant.HaloApp
import com.lightgame.listeners.OnBackPressedListener
import com.lightgame.utils.Utils
import org.greenrobot.eventbus.EventBus
class MainWrapperFragment : BaseBottomTabFragment<PieceBottomTabBinding>(), OnBackPressedListener, ISuperiorChain, IBusiness {
@ -167,15 +158,6 @@ class MainWrapperFragment : BaseBottomTabFragment<PieceBottomTabBinding>(), OnBa
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val request = ImageRequest.Builder(requireContext())
.data("https://www.svgrepo.com/show/43967/colours-samples.svg") // Provide the SVG resource ID
.decoderFactory(SvgDecoder.Factory())
.transformations(SvgColorTintTransformation(requireContext().getColor(com.gh.gamecenter.common.R.color.primary_theme)))
.target(mBinding.svgIv)
.build()
Coil.imageLoader(requireContext()).enqueue(request)
mViewModel?.tabSelectedLiveData?.observe(viewLifecycleOwner) {
val selectedTab = it.getContentWithHandled()
if (selectedTab is MainSelectedEvent.SelectedTab && selectedTab.bottomTabIndex != -1) {
@ -461,19 +443,3 @@ class MainWrapperFragment : BaseBottomTabFragment<PieceBottomTabBinding>(), OnBa
override fun getBusinessId(): Pair<String, String> = (mCurrentFragment as? IBusiness)?.getBusinessId() ?: Pair("", "")
}
class SvgColorTintTransformation(
private val tintColor: Int
) : Transformation {
override val cacheKey: String = "${javaClass.name}-$tintColor"
override suspend fun transform(input: Bitmap, size: coil.size.Size): Bitmap {
val bitmap = input.copy(Bitmap.Config.ARGB_8888, true) // Create a mutable copy
val canvas = Canvas(bitmap)
val paint = Paint()
paint.colorFilter = android.graphics.PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN)
canvas.drawBitmap(input, 0f, 0f, paint)
return bitmap
}
}

View File

@ -26,6 +26,7 @@ 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
@ -341,6 +342,9 @@ 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

@ -20,14 +20,6 @@ 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

@ -1,5 +1,4 @@
<com.gh.gamecenter.common.view.MaterializedConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<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"
@ -110,8 +109,8 @@
android:id="@+id/installApiContentTv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:text="您未授予已安装列表权限,可能导致无法安装及更新等异常情况,建议开启权限!"
android:textSize="@dimen/secondary_size"
app:layout_constraintBottom_toBottomOf="parent"
@ -153,9 +152,4 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/svgIv"
android:layout_width="100dp"
android:layout_height="100dp" />
</com.gh.gamecenter.common.view.MaterializedConstraintLayout>

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

@ -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

@ -683,9 +683,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

@ -683,8 +683,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>

View File

@ -7,8 +7,8 @@ ext {
targetSdkVersion = 30 // 升级targetSdkVersion到 34 时需要根据官方文档补全前台服务的权限类型。比如 NDownloadServiceKeepAliveService
// application info (每个大版本之间的 versionCode 增加 20)
versionCode = 1151
versionName = "5.40.1"
versionCode = 1152
versionName = "5.40.2"
applicationId = "com.gh.gamecenter"
applicationIdGat = "com.gh.gamecenter.intl"

View File

@ -8,6 +8,7 @@ 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
@ -25,6 +26,8 @@ import java.io.File
class AcceleratorProviderImpl : IAcceleratorProvider {
private var _token = ""
private val initResults = arrayListOf<String>()
// 根据包名回调
private val listeners = hashMapOf<String, OnAccelerateListener>()
@ -93,8 +96,7 @@ class AcceleratorProviderImpl : IAcceleratorProvider {
override fun onExecLifecycleInitFun(isEnter: Boolean) {
super.onExecLifecycleInitFun(isEnter)
Utils.log(LOG_TAG, "onExecLifecycleInitFun:$isEnter")
TheRouter.get(IAcceleratorDataHolderProvider::class.java)
?.addInitFunResult("onExecLifecycleInitFun:$isEnter")
initResults.add("onExecLifecycleInitFun:$isEnter")
}
})
QyAccelerator.getInstance().bindQyAccRelatedListener(qyListener)
@ -113,8 +115,8 @@ class AcceleratorProviderImpl : IAcceleratorProvider {
if (isSuccess) {
_token = token
} else {
// 将setToken错误事件上报的sentry,便于后期分析原因
SentryHelper.onEventInAllChannel(SENTRY_EVENT_ID, KEY_SET_TOKEN_ERROR_MESSAGE, errMsg)
// 将setToken错误事件上报的火山云,便于后期分析原因
NewFlatLogUtils.logAcceleratorSetTokenError(initResults, errMsg ?: "")
}
callback?.invoke(isSuccess)
})
@ -142,6 +144,7 @@ class AcceleratorProviderImpl : IAcceleratorProvider {
_token = ""
listeners.clear()
allListener.clear()
initResults.clear()
return isDeleted
}
@ -186,8 +189,6 @@ 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"
}
}

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.utils.toJson
import com.gh.gamecenter.common.exposure.meta.MetaUtil
import com.gh.gamecenter.core.HaloApp
import com.gh.gamecenter.core.provider.IAcceleratorDataHolderProvider
import com.gh.gamecenter.core.provider.IAppProvider
@ -16,6 +16,7 @@ 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 {
@ -31,6 +32,14 @@ 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)
@ -131,23 +140,7 @@ class SentryProviderImpl : ISentryProvider {
}
Utils.log("Sentry", "$eventId + [${kv.joinToString(" , ")}]")
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)
}
}
})
}
Sentry.captureEvent(sentryEvent)
}
override fun captureException(e: Throwable) {

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
@ -1291,18 +1292,21 @@ 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
gameType: String,
exceptionDigest: 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)
@ -5340,4 +5344,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

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

View File

@ -13,6 +13,8 @@ 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))
@ -52,4 +54,14 @@ 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)
}
}