Compare commits

...

34 Commits

Author SHA1 Message Date
d98b00f2dc 重置 2025-01-22 17:55:46 +08:00
8127ba7bd0 批量注册用户 2025-01-21 17:44:54 +08:00
158c36a6b1 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-20 17:03:20 +08:00
c4184b9cbb sdk只支持正式环境 2025-01-20 16:57:06 +08:00
585153cc0e 新增获取区服失败提示处理 2025-01-20 16:50:04 +08:00
112ce8da4c fix: 修复 build_with_simple_backup.sh 不能在 mac Bash 上编译的问题 2025-01-20 14:49:28 +08:00
88cfb99a12 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-17 18:24:45 +08:00
b9fbad85b2 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-17 18:22:36 +08:00
86f8aaf1e1 sdk改为只支持正式环境 2025-01-16 17:27:58 +08:00
59da6eb171 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-16 10:04:05 +08:00
4d5af5044f feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 17:58:10 +08:00
30105b3cc2 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 17:23:56 +08:00
f0b4085455 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 17:17:25 +08:00
4e896ceccf feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
52a3d4a9a7 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
8df60fcc1d feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
9946dd0df3 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
e3e218dd2b feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
e089347f8c feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
174ca03ce4 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
6adae085b1 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
6a3b164177 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
d08d1bf8e9 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
7ece9b0d4e feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
c45a2c34eb feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
06b4c3f8ab feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
7f71c1d740 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
c1c2078294 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
34d31647c3 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
e6badcb7c3 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:59:00 +08:00
8e1973bad1 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:58:59 +08:00
d9a08037b5 feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:58:59 +08:00
a65fb3535d feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-01-15 16:58:56 +08:00
f6d5d6f719 ci 2025-01-15 16:56:09 +08:00
122 changed files with 5818 additions and 116 deletions

View File

@ -72,6 +72,8 @@ android_build:
only:
- dev
- release
- feat/GHZSCY-6976
- feat/GHZSCY-6976-log
# 代码检查
sonarqube_analysis:
@ -103,6 +105,8 @@ sonarqube_analysis:
only:
- dev
- release
- feat/GHZSCY-6976
- feat/GHZSCY-6976-log
## 发送简易检测结果报告
send_sonar_report:
@ -121,6 +125,8 @@ send_sonar_report:
only:
- dev
- release
- feat/GHZSCY-6976
- feat/GHZSCY-6976-log
oss-upload&send-email:
tags:
@ -157,3 +163,5 @@ oss-upload&send-email:
only:
- dev
- release
- feat/GHZSCY-6976
- feat/GHZSCY-6976-log

View File

@ -402,7 +402,7 @@ dependencies {
exclude module: "gsyvideoplayer-androidvideocache"
exclude group: "tv.danmaku.ijk.media"
})
implementation ("com.github.CarGuo.GSYVideoPlayer:gsyVideoPlayer-exo_player2:$gsyVideo") {
implementation("com.github.CarGuo.GSYVideoPlayer:gsyVideoPlayer-exo_player2:$gsyVideo") {
exclude group: 'com.google.android.exoplayer', module: 'extension-rtmp'
}
@ -526,6 +526,18 @@ dependencies {
debugImplementation 'com.bytedance.android:shadowhook:1.0.9'
debugImplementation 'io.github.shiqos:wytrace:1.0.1'
if (!gradle.ext.excludeOptionalModules || gradle.ext.enableAccelerator) {
implementation(project(":feature:accelerator"))
}
if (!gradle.ext.excludeOptionalModules || gradle.ext.enableAliPay) {
implementation(project(":feature:ali_pay"))
}
if(!gradle.ext.excludeOptionalModules || gradle.ext.enableWechatPay){
implementation(project(":feature:wechat_pay"))
}
}
File propFile = file('sign.properties')

View File

@ -10,9 +10,9 @@
<queries>
<package android:name="com.gh.toolmap" />
<intent>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="ghtoolmap"/>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="ghtoolmap" />
</intent>
</queries>
@ -197,7 +197,9 @@
android:name="io.sentry.breadcrumbs.system-events"
android:value="false" />
<meta-data android:name="module_version" android:value="${VA_VERSION_NAME}" />
<meta-data
android:name="module_version"
android:value="${VA_VERSION_NAME}" />
<service android:name="com.gh.ndownload.NDownloadService" />
@ -814,6 +816,14 @@
android:name=".video.poster.PosterEditActivity"
android:screenOrientation="portrait" />
<activity
android:name="com.halo.assistant.member.MemberActivity"
android:screenOrientation="portrait" />
<activity
android:name=".BatchRegisterActivity"
android:screenOrientation="portrait" />
<!-- <activity-->
<!-- android:name="${applicationId}.douyinapi.DouYinEntryActivity"-->
<!-- android:launchMode="singleTask"-->

View File

@ -22,7 +22,8 @@ import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.WebActivity
import com.gh.gamecenter.common.callback.BiCallback
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.constant.Constants.SP_MEMBER_PAYMENT_BUTTON_CLICK
import com.gh.gamecenter.common.constant.Constants.SP_MEMBER_RECHARGE_BUTTON_CLICK
import com.gh.gamecenter.common.entity.NotificationUgc
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.loghub.LoghubUtils
@ -30,20 +31,23 @@ import com.gh.gamecenter.common.provider.IHelpAndFeedbackProvider
import com.gh.gamecenter.common.tracker.Tracker
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.NewFlatLogUtils
import com.gh.gamecenter.common.utils.SensorsBridge.EVENT_MEMBER_RECHARGE_BUTTON_CLICK
import com.gh.gamecenter.common.utils.SensorsBridge.EVENT_NAME
import com.gh.gamecenter.common.utils.SensorsBridge.KEY_IS_FIRST_TIME
import com.gh.gamecenter.common.view.dsbridge.CompletionHandler
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.provider.IPushProvider
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.runOnUiThread
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.entity.SensorsEvent
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.AcctRecordEntity
import com.gh.gamecenter.feature.entity.Badge
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.OrderEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.login.user.LoginTag
import com.gh.gamecenter.login.user.UserManager
@ -55,6 +59,9 @@ import com.gh.gamecenter.personalhome.border.AvatarBorderActivity
import com.gh.gamecenter.setting.SettingBridge
import com.gh.vspace.VHelper
import com.halo.assistant.HaloApp
import com.halo.assistant.member.MemberRepository
import com.halo.assistant.member.MemberRepository.Companion.PAYMENT_TYPE_ALIPAY
import com.halo.assistant.member.MemberRepository.Companion.PAYMENT_TYPE_WECHAT
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import com.lightgame.download.DownloadStatus.*
@ -75,6 +82,7 @@ class DefaultJsApi(
private var mBbsId: String? = "",
private var mOriginUrl: String? = "",
private val mForumName: String? = "",
private val listener: OnWebClickListener? = null
) {
companion object {
@ -699,6 +707,84 @@ class DefaultJsApi(
}
}
@JavascriptInterface
fun preOrderWithAli(json: Any) {
val order = json.toString().toObject<OrderEntity>() ?: return
trackMemberPaymentButtonClick(order, PAYMENT_TYPE_ALIPAY)
listener?.onPreOrderWithAli(order)
}
@JavascriptInterface
fun preOrderWithWechat(json: Any) {
val order = json.toString().toObject<OrderEntity>() ?: return
trackMemberPaymentButtonClick(order, PAYMENT_TYPE_WECHAT)
listener?.onPreOrderWithWechat(order)
}
private fun trackMemberPaymentButtonClick(order: OrderEntity, paymentType: String) {
val isFirstTime = SPUtils.getBoolean(SP_MEMBER_PAYMENT_BUTTON_CLICK, true)
SPUtils.setBoolean(SP_MEMBER_PAYMENT_BUTTON_CLICK, false)
SensorsBridge.trackMemberPaymentButtonClick(
isFirstTime,
paymentType,
order.setMenuName,
order.paymentAmount
)
}
@JavascriptInterface
fun startGameAccelerate(acctJson: Any) {
if (acctJson is String) {
val acctRecord = GsonUtils.fromJson(acctJson, AcctRecordEntity::class.java)
val accInfo = acctRecord.accInfo
listener?.onStartGameAccelerate(accInfo)
}
}
@JavascriptInterface
fun getCurAcctGameId(): String {
val isSpeeding = TheRouter.get(IAcceleratorProvider::class.java)?.isCurAccSuccess() ?: false
val gameId = if (isSpeeding) {
MemberRepository.instance.acctGameRecord.gameId
} else {
""
}
return gameId
}
@JavascriptInterface
fun stopGameAccelerate() {
listener?.onStopGameAccelerate()
}
@JavascriptInterface
fun refreshToken(token: Any, handler: CompletionHandler<Any>) {
val accessToken = token.toString()
UserManager.getInstance().refreshToken(accessToken, object : UserManager.refreshCallBack {
override fun onLogin() {
handler.complete(true)
}
override fun onLoginFailure(errorMessage: String?) {
handler.complete(false)
}
})
}
@JavascriptInterface
fun trackSensorsAnalytics(json: Any) {
val hashMap = json.toString().toObject<HashMap<String, Any>>() ?: return
val eventName = hashMap.remove(EVENT_NAME) ?: return
when (eventName) {
EVENT_MEMBER_RECHARGE_BUTTON_CLICK -> {
hashMap[KEY_IS_FIRST_TIME] = SPUtils.getBoolean(SP_MEMBER_RECHARGE_BUTTON_CLICK, true)
SPUtils.setBoolean(SP_MEMBER_RECHARGE_BUTTON_CLICK, false)
}
}
SensorsBridge.trackSensorsAnalyticsFromWeb(eventName.toString(), hashMap)
}
/**
* 获取 ExposureEvent可能为空
*/
@ -815,4 +901,15 @@ class DefaultJsApi(
}
}
}
interface OnWebClickListener {
fun onPreOrderWithAli(order: OrderEntity)
fun onPreOrderWithWechat(order: OrderEntity)
fun onStartGameAccelerate(accInfo: AcctRecordEntity.AccInfo)
fun onStopGameAccelerate()
}
}

View File

@ -8,12 +8,21 @@ import com.gh.gamecenter.feature.entity.GameEntity
class PackageCheckHandler : DownloadChainHandler() {
override fun handleRequest(context: Context, gameEntity: GameEntity, asVGame: Boolean) {
PackageCheckDialogFragment.show((context as AppCompatActivity), gameEntity) {
fun nextOrProcessEnd() {
if (hasNext()) {
getNext()?.handleRequest(context, gameEntity, asVGame)
} else {
processEndCallback?.invoke(asVGame, null)
}
}
if (gameEntity.canSpeed) {
nextOrProcessEnd()
} else {
PackageCheckDialogFragment.show((context as AppCompatActivity), gameEntity) {
nextOrProcessEnd()
}
}
}
}

View File

@ -0,0 +1,61 @@
package com.gh.common.provider
import android.annotation.SuppressLint
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.provider.IWechatPayResultProvider
import com.gh.gamecenter.feature.entity.OrderEntity
import com.gh.gamecenter.feature.entity.VipEntity
import com.gh.gamecenter.feature.eventbus.EBPayState
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserRepository
import com.halo.assistant.member.MemberRepository
import com.therouter.TheRouter
import org.greenrobot.eventbus.EventBus
@com.therouter.inject.ServiceProvider
class WechatPayResultProviderImpl : IWechatPayResultProvider {
private val repository = MemberRepository.instance
@SuppressLint("CheckResult")
override fun onPayComplete(nonceStr: String, order: Any?) {
val orderEntity = order as? OrderEntity
repository.getWechatPayResult(nonceStr)
.compose(singleToMain())
.subscribe({
// 支付成功
EventBus.getDefault().post(EBPayState.PaySuccess)
// 先刷新本地状态,支付成功,肯定是付费会员
TheRouter.get(IAcceleratorProvider::class.java)?.setVipEntity(
VipEntity(
_vipStatus = true,
_isNewUser = false,
_isTryVip = false
)
)
val userId = UserManager.getInstance().userId
if (userId.isNotBlank()) {
UserRepository.getInstance().refreshVipStatus(userId, true)
}
SensorsBridge.trackMemberRechargeResult(
MemberRepository.PAYMENT_TYPE_WECHAT,
orderEntity?.setMenuName ?: "",
orderEntity?.paymentAmount ?: "",
MemberRepository.RECHARGE_RESULT_SUCCESS
)
}, {
// 支付失败
EventBus.getDefault().post(EBPayState.PayFail)
SensorsBridge.trackMemberRechargeResult(
MemberRepository.PAYMENT_TYPE_WECHAT,
orderEntity?.setMenuName ?: "",
orderEntity?.paymentAmount ?: "",
MemberRepository.RECHARGE_RESULT_FAILURE
)
})
}
}

View File

@ -5,6 +5,8 @@ import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.gh.common.filter.RegionSetting;
import com.gh.common.filter.RegionSettingHelper;
import com.gh.common.repository.ReservationRepository;
@ -41,7 +43,8 @@ public class DetailDownloadUtils {
/**
* 更新底部下载区域
* @param viewHolder 下载区域的包裹
*
* @param viewHolder 下载区域的包裹
* @param ignoreDownloadEntity 忽略下载实体(往往用于下载异常时)
*/
public static void updateViewHolder(DetailViewHolder viewHolder, boolean ignoreDownloadEntity) {
@ -52,6 +55,11 @@ public class DetailDownloadUtils {
if (viewHolder.getMultiVersionDownloadTv() != null) {
viewHolder.getMultiVersionDownloadTv().setVisibility(View.GONE);
}
if (viewHolder.getSpeedContainer() != null) {
viewHolder.getSpeedContainer().setVisibility(View.GONE);
}
viewHolder.setSpeedViewsVisible(false);
// 根据预置的配置更新 ViewHolder 的状态 (譬如青少年模式、下载内容为空等)
if (updateViewHolderWithPredefinedConfig(viewHolder, gameEntity)) {
@ -219,7 +227,7 @@ public class DetailDownloadUtils {
downloadButton.setText("");
}
} else {
decoratedBtnText = rawBtnText + (containsAddWord? "" : downloadAddWord) + getWrappedDownloadSizeText(viewHolder);
decoratedBtnText = rawBtnText + (containsAddWord ? "" : downloadAddWord) + getWrappedDownloadSizeText(viewHolder);
if (overlayTv != null && downloadButton.getVisibility() != View.GONE) {
if (context.getString(com.gh.gamecenter.feature.R.string.launch).equals(rawBtnText)
@ -246,16 +254,24 @@ public class DetailDownloadUtils {
}
}
} else {
boolean isLaunchState = false;
// 非畅玩,显示为普通游戏
if (context.getString(com.gh.gamecenter.feature.R.string.pluggable).equals(rawBtnText)) {
downloadButton.setButtonStyle(DownloadButton.ButtonStyle.PLUGIN);
} else if (context.getString(com.gh.gamecenter.feature.R.string.launch).equals(rawBtnText)) {
downloadButton.setButtonStyle(DownloadButton.ButtonStyle.LAUNCH_OR_OPEN);
isLaunchState = true;
} else if (context.getString(com.gh.gamecenter.feature.R.string.install).equals(rawBtnText)) {
downloadButton.setButtonStyle(DownloadButton.ButtonStyle.INSTALL_NORMAL);
} else {
downloadButton.setButtonStyle(DownloadButton.ButtonStyle.NORMAL);
}
// 只有下载按钮状态为 “启动” 时才需要展示加速ui
if (isLaunchState) {
viewHolder.showAcceleratorGuideLayer();
} else {
viewHolder.hideSpeedUi();
}
if (showDualDownloadButton && viewHolder.getLocalDownloadSizeTv() != null) {
viewHolder.getLocalDownloadSizeTv().setVisibility(View.GONE);

View File

@ -6,6 +6,7 @@ import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.runOnUiThread
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.login.user.UserManager
import com.gh.ndownload.NHttpClient
import com.lightgame.utils.AppManager
import org.json.JSONObject
@ -89,4 +90,12 @@ object TempCertificationUtils {
return stringBuffer.toString()
}
/**
* 检测光环是否实名
*/
fun isUserVerified(): Boolean {
val idCard = UserManager.getInstance().userInfoEntity?.idCard
// 账号已实名
return idCard != null && idCard.status == 0
}
}

View File

@ -0,0 +1,81 @@
package com.gh.gamecenter
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.feature.entity.BaseEntity
import com.gh.gamecenter.login.retrofit.RetrofitManager
import com.gh.gamecenter.login.user.UserManager
import com.therouter.TheRouter
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import retrofit2.HttpException
/**
* 奇游用户批量注册
*/
class BatchRegisterActivity : AppCompatActivity() {
private val userIds = listOf(
""
)
private var iAcceleratorProvider: IAcceleratorProvider? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_batch_register)
iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java)
val btnRegister = findViewById<Button>(R.id.btn_register)
btnRegister.setOnClickListener {
doRegister()
}
}
private fun doRegister() {
println("kayn -->doRegister:${userIds.size}")
Observable.fromIterable(userIds)
.flatMap({ userId ->
RetrofitManager.getInstance().newApi
.getQyToken(userId, "gjonline_vip")
.onErrorReturnItem(BaseEntity())
.flatMap { entity ->
val token = entity.data?.token ?: ""
Single.create<Pair<Boolean, String>> {
if (token.isBlank()) {
it.onSuccess(false to userId)
} else {
iAcceleratorProvider?.setQyUserToken(token) { isSuccess ->
it.onSuccess(isSuccess to userId)
}
}
}
}.toObservable()
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<Pair<Boolean, String>>() {
override fun onNext(response: Pair<Boolean, String>) {
super.onNext(response)
val (isSuccess, userId) = response
println("kayn -->isSuccess:$isSuccess --userId:$userId")
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
}
override fun onComplete() {
super.onComplete()
}
})
}
}

View File

@ -14,8 +14,6 @@ import androidx.core.app.NotificationCompat
import androidx.core.text.bold
import androidx.core.text.buildSpannedString
import androidx.core.text.color
import com.therouter.router.Route
import com.therouter.TheRouter
import com.gh.common.dialog.NewPrivacyPolicyDialogFragment
import com.gh.common.util.DeviceTokenUtils
import com.gh.common.util.DialogUtils
@ -32,13 +30,14 @@ import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.iinterface.ISplashScreen
import com.gh.gamecenter.core.provider.IAppProvider
import com.gh.gamecenter.core.provider.IPackageUtilsProvider
import com.gh.gamecenter.core.provider.IPushProvider
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.feature.utils.PlatformUtils
import com.gh.gamecenter.pkg.PkgHelper
import com.halo.assistant.HaloApp
import com.therouter.TheRouter
import com.therouter.router.Route
import org.json.JSONObject
import splitties.systemservices.notificationManager
import java.text.SimpleDateFormat
@ -90,8 +89,10 @@ class SplashScreenActivity : BaseActivity(), ISplashScreen {
showPrivacyDialog()
} else {
val spanBuilder = buildSpannedString {
append("这个弹窗只会在右上角有环境标签的测试包出现" +
"\n进入应用以后还可以到关于我们页面长按应用图标重新选择")
append(
"这个弹窗只会在右上角有环境标签的测试包出现" +
"\n进入应用以后还可以到关于我们页面长按应用图标重新选择"
)
bold {
color(com.gh.gamecenter.common.R.color.text_theme.toColor(this@SplashScreenActivity)) {
append("\n点击这里进行预设置渠道")
@ -103,7 +104,7 @@ class SplashScreenActivity : BaseActivity(), ISplashScreen {
executeDex2OatInAdvance()
DialogHelper.showDialog(
context = this,
title ="选择环境",
title = "选择环境",
content = spanBuilder,
confirmText = "正式环境",
cancelText = "测试环境",
@ -130,6 +131,7 @@ class SplashScreenActivity : BaseActivity(), ISplashScreen {
} else {
launchMainActivity()
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
SPUtils.setString(Constants.SP_XAPK_UNZIP_ACTIVITY, "")
SPUtils.setString(Constants.SP_XAPK_URL, "")

View File

@ -6,17 +6,19 @@ import android.os.Bundle
import android.text.TextUtils
import android.view.KeyEvent
import android.view.View
import com.therouter.router.Route
import com.gh.common.constant.Config
import com.gh.gamecenter.common.base.activity.ToolBarActivity
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.ToolBoxEntity
import com.gh.gamecenter.common.utils.EnvHelper
import com.gh.gamecenter.common.utils.updateStatusBarColor
import com.gh.gamecenter.feature.entity.ConcernEntity
import com.gh.gamecenter.feature.entity.NewsEntity
import com.gh.gamecenter.common.entity.ToolBoxEntity
import com.halo.assistant.fragment.WebFragment
import com.halo.assistant.member.MemberActivity
import com.therouter.router.Route
@Route(path = RouteConsts.activity.webActivity)
open class WebActivity : ToolBarActivity() {
@ -29,6 +31,8 @@ open class WebActivity : ToolBarActivity() {
val mIsBackpressRequireConfirmation =
bundle.getBoolean(WebFragment.KEY_REQUIRE_BACK_CONFIRMATION, false)
mIsFullScreen = !TextUtils.isEmpty(mGameName) && mIsBackpressRequireConfirmation
mIsFullScreen = true
if (mIsFullScreen) {
setTheme(R.style.AppFullScreenTheme)
}
@ -305,5 +309,17 @@ open class WebActivity : ToolBarActivity() {
intent.putExtra(NORMAL_FRAGMENT_BUNDLE, bundle)
return intent
}
@JvmStatic
fun getMyAssetsIntent(context: Context): Intent {
val intent = Intent(context, MemberActivity::class.java)
val url = if (EnvHelper.isDevEnv) {
Constants.MY_ASSETS_DEV
} else {
Constants.MY_ASSETS
}
intent.putExtra(EntranceConsts.KEY_URL, url)
return intent
}
}
}

View File

@ -4,8 +4,11 @@ import android.content.Context
import android.content.Intent
import android.text.TextUtils
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.Group
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import com.airbnb.lottie.LottieAnimationView
@ -13,6 +16,7 @@ import com.gh.common.chain.*
import com.gh.common.constant.Config
import com.gh.common.dialog.DeviceRemindDialog
import com.gh.common.dialog.GameOffServiceDialogFragment
import com.gh.common.dialog.PackageCheckDialogFragment
import com.gh.common.filter.RegionSettingHelper
import com.gh.common.history.HistoryHelper
import com.gh.common.simulator.NewSimulatorGameManager
@ -45,6 +49,7 @@ import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.feature.view.DownloadButton.ButtonStyle
import com.gh.gamecenter.gamedetail.GameDetailFragment
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper
import com.gh.gamecenter.gamedetail.dialog.GamePermissionDialogFragment
import com.gh.gamecenter.teenagermode.TeenagerModeActivity.Companion.getIntent
import com.gh.vspace.VHelper
@ -62,7 +67,8 @@ class DetailViewHolder(
name: String?,
title: String?,
val traceEvent: ExposureEvent?,
val isSupportDualButton: Boolean = false // 是否支持双下载按钮,不支持的时候跟普通列表意义选用优先级高的那个来显示
val isSupportDualButton: Boolean = false, // 是否支持双下载按钮,不支持的时候跟普通列表意义选用优先级高的那个来显示,
val acceleratorUiHelper: GameDetailAcceleratorUiHelper? = null // 网速加速,只有游戏详情才有
) {
var context: Context
var downloadBottom: View
@ -90,6 +96,12 @@ class DetailViewHolder(
// 多版本下载文字
var multiVersionDownloadTv: TextView?
// 加速按钮
val speedContainer: ConstraintLayout?
private val ivFreeVipTag: ImageView?
private val gMoreZone: Group?
// 注意 View 的命名
init {
downloadBottom = view.findViewById(R.id.detail_ll_bottom)
@ -104,6 +116,10 @@ class DetailViewHolder(
localDownloadTitleTv = view.findViewById(R.id.localDownloadTitleTv)
localDownloadButton = view.findViewById(R.id.localDownloadButton)
speedContainer = view.findViewById(R.id.cl_speed_container)
ivFreeVipTag = view.findViewById(R.id.iv_free_vip_tag)
gMoreZone = view.findViewById(R.id.g_more_zone)
context = view.context
var gameDownloadMode = gameEntity.getGameDownloadButtonMode()
@ -158,7 +174,9 @@ class DetailViewHolder(
localDownloadButton?.putObject(gameEntity)
localDownloadButton?.setTag(
com.gh.gamecenter.feature.R.string.download, context.getString(
com.gh.gamecenter.feature.R.string.download_local))
com.gh.gamecenter.feature.R.string.download_local
)
)
}
}
downloadPb.putWidgetBusinessName("游戏详情页")
@ -176,6 +194,19 @@ class DetailViewHolder(
gamePermissionDialogFragment?.dismissAllowingStateLoss()
}
fun hideSpeedUi() {
acceleratorUiHelper?.showSpeedUi = false
}
fun showAcceleratorGuideLayer() {
acceleratorUiHelper?.checkIfShowGuideLayer(context)
}
fun setSpeedViewsVisible(isVisible: Boolean) {
ivFreeVipTag?.goneIf(!isVisible)
speedContainer?.goneIf(!isVisible)
}
internal class OnDetailDownloadClickListener(
private val mViewHolder: DetailViewHolder,
private val mEntrance: String?,
@ -334,7 +365,19 @@ class DetailViewHolder(
if (mAsVGame) {
VHelper.installOrLaunch(mViewHolder.context, mGameEntity, null)
} else {
PackageLauncher.launchApp(mViewHolder.context, mGameEntity, mGameEntity.getUniquePackageName())
// 如果游戏配置了加速,则启动时需要进行包名检测
if (mGameEntity.canSpeed) {
PackageCheckDialogFragment.show(mViewHolder.context as AppCompatActivity, mGameEntity) {
PackageLauncher.launchApp(
mViewHolder.context, mGameEntity, mGameEntity.getUniquePackageName()
)
}
} else {
PackageLauncher.launchApp(
mViewHolder.context, mGameEntity, mGameEntity.getUniquePackageName()
)
}
}
} else {
GamePermissionDialogFragment.show(
@ -594,8 +637,9 @@ class DetailViewHolder(
val apkEntity = mGameEntity.getApk().firstOrNull()
val msg = FileUtils.isCanDownload(mViewHolder.context, apkEntity?.size ?: "")
if (TextUtils.isEmpty(msg)) {
val btnContainsUpdateText = mViewHolder.context.getString(com.gh.gamecenter.feature.R.string.update_v) == buttonText
|| buttonText.contains(mViewHolder.context.getString(com.gh.gamecenter.feature.R.string.update))
val btnContainsUpdateText =
mViewHolder.context.getString(com.gh.gamecenter.feature.R.string.update_v) == buttonText
|| buttonText.contains(mViewHolder.context.getString(com.gh.gamecenter.feature.R.string.update))
if (asVGame && btnContainsUpdateText) {
VHelper.updateOrReDownload(mGameEntity)

View File

@ -0,0 +1,13 @@
package com.gh.gamecenter.gamedetail
import androidx.lifecycle.ViewModel
import com.halo.assistant.member.MemberUseCase
class AcceleratorZoneViewModel : ViewModel() {
val useCase = MemberUseCase()
override fun onCleared() {
useCase.onClear()
}
}

View File

@ -58,20 +58,20 @@ import com.gh.gamecenter.common.mvvm.Status
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.WrapContentDraweeView
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.databinding.*
import com.gh.gamecenter.entity.GameUpdateEntity
import com.gh.gamecenter.entity.RecommendPopupEntity
import com.gh.gamecenter.eventbus.*
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.PluginLocation
import com.gh.gamecenter.feature.entity.SimpleGame
import com.gh.gamecenter.feature.entity.TagStyleEntity
import com.gh.gamecenter.feature.entity.*
import com.gh.gamecenter.feature.eventbus.EBConcernChanged
import com.gh.gamecenter.feature.eventbus.EBPayState
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.utils.ApkActiveUtils
import com.gh.gamecenter.feature.utils.SentryHelper
import com.gh.gamecenter.forum.detail.ForumDetailActivity
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper
import com.gh.gamecenter.gamedetail.cloudarchive.CloudArchiveFragment
import com.gh.gamecenter.gamedetail.desc.DescFragment
import com.gh.gamecenter.gamedetail.dialog.*
@ -86,7 +86,6 @@ import com.gh.gamecenter.home.video.ScrollCalculatorHelper
import com.gh.gamecenter.login.user.UserViewModel
import com.gh.gamecenter.newsdetail.NewsDetailActivity
import com.gh.gamecenter.packagehelper.PackageViewModel
import com.gh.gamecenter.SearchActivity
import com.gh.gamecenter.simulatorgame.SimulatorGameActivity
import com.gh.gamecenter.tag.TagsActivity
import com.gh.gamecenter.video.detail.CustomManager
@ -101,6 +100,7 @@ import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
import com.shuyu.gsyvideoplayer.utils.OrientationUtils
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
import com.therouter.TheRouter
import io.reactivex.disposables.Disposable
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@ -155,6 +155,8 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
private val mLooperHandle = LooperHandle(this)
private val mServerLooperKey = 123
private lateinit var acceleratorUiHelper: GameDetailAcceleratorUiHelper
private val dataWatcher = object : DataWatcher() {
override fun onDataChanged(downloadEntity: DownloadEntity) {
if (downloadEntity.gameId == mViewModel.gameId) {
@ -265,7 +267,8 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
name = "游戏详情",
title = mGameEntity!!.name ?: "",
traceEvent = mTraceEvent,
isSupportDualButton = true
isSupportDualButton = true,
acceleratorUiHelper
)
private val contentCardClick: (contentCard: ContentCardEntity, position: Int) -> Unit =
@ -412,6 +415,7 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
mVideoBinding = mBodyBinding.gameDetailVideo
mDownloadBinding = mBinding.detailLlBottom
mRecommendBinding = mBinding.gameDetailRecommendView
acceleratorUiHelper = GameDetailAcceleratorUiHelper(mDownloadBinding)
}.root
}
@ -548,7 +552,12 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
pauseVideo()
mIsPauseTopVideo = true
}
mBodyBinding.toolbar.setTitleTextColor(ContextCompat.getColor(requireContext(), com.gh.gamecenter.common.R.color.black))
mBodyBinding.toolbar.setTitleTextColor(
ContextCompat.getColor(
requireContext(),
com.gh.gamecenter.common.R.color.black
)
)
} else if (mIsPauseTopVideo && absVerticalOffset == 0 && mVideoBinding.player.currentState == GSYVideoView.CURRENT_STATE_PAUSE) {
resumeVideo()
mIsPauseTopVideo = false
@ -669,7 +678,9 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
}
} else {
setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(requireContext()))
background = com.gh.gamecenter.feature.R.drawable.bg_advance_download_game_subtitle.toDrawable(requireContext())
background = com.gh.gamecenter.feature.R.drawable.bg_advance_download_game_subtitle.toDrawable(
requireContext()
)
}
}
tagView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
@ -768,6 +779,8 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
if (gameResource.status == Status.SUCCESS) {
mViewModel.logHistory(gameResource.data!!)
mGameEntity = gameResource.data
acceleratorUiHelper.setGame(gameResource.data)
loadAccelerationData(mGameEntity!!)
showBrowserInstallHintIfNeeded()
controlInstallHint()
// 添加启动弹窗的相关信息
@ -787,7 +800,9 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
mViewModel.gameDetailLiveData.observeNonNull(this) { detailResource ->
if (detailResource.status == Status.SUCCESS) {
val data = detailResource.data ?: return@observeNonNull
if (detailResource.data?.acceleratorStatus == true) {
acceleratorUiHelper.initSpeedUi(requireActivity())
}
DataLogUtils.uploadGameLog(context, mGameEntity!!.id, mGameEntity!!.name, mEntrance)
postDelayedRunnable({
@ -839,7 +854,12 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
DetailDownloadUtils.updateViewHolder(viewHolder)
mDestinationTab =
getTabPositionFromTabType(arguments?.getString(EntranceConsts.KEY_TARGET, EntranceConsts.TAB_TYPE_DESC) ?: EntranceConsts.TAB_TYPE_DESC)
getTabPositionFromTabType(
arguments?.getString(
EntranceConsts.KEY_TARGET,
EntranceConsts.TAB_TYPE_DESC
) ?: EntranceConsts.TAB_TYPE_DESC
)
// destinationTab 的优先级最高,关注和关联关注在它的后面
if (mDestinationTab != -1) {
@ -994,6 +1014,32 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
}
}
/**
* 调用此方法时只是能获取game的包名还无法确认该游戏是否支持加速
* 所以这里只是提前加载加速所需的数据并不需要初始化加速相关的UI
*/
private fun loadAccelerationData(game: GameEntity) {
game.getApk().size
if (game.getApk().size == 1) {
// 单版本游戏才能支持加速(后台未对此作限制,所以需要在客户端判断)
val pkgName = game.getUniquePackageName() ?: ""
mViewModel.getLastSpeedLiveData(pkgName).observe(viewLifecycleOwner) {
acceleratorUiHelper.setCurrentAcctGameInfo(it)
}
mViewModel.getZoneListLiveData(pkgName).observe(viewLifecycleOwner) {
acceleratorUiHelper.setZoneList(it)
}
mViewModel.upsertAcctZoneListBeanAction.observe(viewLifecycleOwner) { (pkgName, zoneList) ->
mViewModel.upsertAcctZoneListBean(pkgName, zoneList)
}
mViewModel.loadAccelerationData(pkgName)
}
}
@SuppressLint("ClickableViewAccessibility")
private fun initViewPage(data: NewGameDetailEntity) {
// 各个 tab 显示的顺序为:详情>云存档>评价>专区>论坛
@ -1032,7 +1078,8 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
mTabTitleList.clear()
val tag = "android:switcher:${mBodyBinding.gamedetailVp.id}:"
val descFragment = childFragmentManager.findFragmentByTag("${tag}${EntranceConsts.TAB_TYPE_DESC}") ?: DescFragment()
val descFragment =
childFragmentManager.findFragmentByTag("${tag}${EntranceConsts.TAB_TYPE_DESC}") ?: DescFragment()
descFragment.arguments = bundle
mFragmentsList.add(descFragment)
mTabTitleList.add(getString(R.string.game_detail_desc))
@ -1040,7 +1087,8 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
if (data.showArchive) {
val cloudArchiveFragment =
childFragmentManager.findFragmentByTag("${tag}${EntranceConsts.TAB_TYPE_ARCHIVE}") ?: CloudArchiveFragment()
childFragmentManager.findFragmentByTag("${tag}${EntranceConsts.TAB_TYPE_ARCHIVE}")
?: CloudArchiveFragment()
bundle.putParcelable(EntranceConsts.KEY_GAME, mGameEntity ?: GameEntity())
bundle.putString(EntranceConsts.KEY_ARCHIVE_CONFIG_URL, data.archiveTab.configUrl)
cloudArchiveFragment.arguments = bundle
@ -1056,7 +1104,8 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
}
if (data.showComment) {
val ratingFragment = childFragmentManager.findFragmentByTag("${tag}${EntranceConsts.TAB_TYPE_RATING}") ?: RatingFragment()
val ratingFragment =
childFragmentManager.findFragmentByTag("${tag}${EntranceConsts.TAB_TYPE_RATING}") ?: RatingFragment()
bundle.putBoolean(EntranceConsts.KEY_COMMENT_AS_DEFAULT_TAB, mSkipGameComment)
bundle.putBoolean(EntranceConsts.KEY_DIRECT_COMMENT, data.directComment)
ratingFragment.arguments = bundle
@ -1073,7 +1122,8 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
mFragmentsList.add(Fragment())
} else if (it.style == "link") {
//显示web页面
val webFragment = childFragmentManager.findFragmentByTag("${tag}${EntranceConsts.TAB_TYPE_TRENDS}") ?: WebFragment()
val webFragment =
childFragmentManager.findFragmentByTag("${tag}${EntranceConsts.TAB_TYPE_TRENDS}") ?: WebFragment()
val webBundle = Bundle()
webBundle.putString(EntranceConsts.KEY_ENTRANCE, "游戏专区")
webBundle.putString(EntranceConsts.KEY_URL, it.link)
@ -1081,7 +1131,8 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
webFragment.arguments = webBundle
mFragmentsList.add(webFragment)
} else {
val fuliFragment = childFragmentManager.findFragmentByTag("${tag}${EntranceConsts.TAB_TYPE_TRENDS}") ?: FuLiFragment()
val fuliFragment =
childFragmentManager.findFragmentByTag("${tag}${EntranceConsts.TAB_TYPE_TRENDS}") ?: FuLiFragment()
fuliFragment.arguments = bundle
mFragmentsList.add(fuliFragment)
}
@ -1181,7 +1232,8 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
}
private fun doShowAlertDialog(dialog: GameEntity.Dialog) {
SensorsBridge.trackEvent("GameDetailDialogShow",
SensorsBridge.trackEvent(
"GameDetailDialogShow",
"game_id", mGameEntity?.id ?: "",
"game_name", mGameEntity?.name ?: "",
"game_type", mGameEntity?.categoryChinese ?: ""
@ -1193,7 +1245,8 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
dialog.confirmButton.text.toString(),
dialog.closeButtonText,
{
SensorsBridge.trackEvent("GameDetailDialogClick",
SensorsBridge.trackEvent(
"GameDetailDialogClick",
"game_id", mGameEntity?.id ?: "",
"game_name", mGameEntity?.name ?: "",
"game_type", mGameEntity?.categoryChinese ?: "",
@ -1206,7 +1259,8 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
DirectUtils.directToLinkPage(requireContext(), dialog.confirmButton, mEntrance, "")
},
{
SensorsBridge.trackEvent("GameDetailDialogClick",
SensorsBridge.trackEvent(
"GameDetailDialogClick",
"game_id", mGameEntity?.id ?: "",
"game_name", mGameEntity?.name ?: "",
"game_type", mGameEntity?.categoryChinese ?: "",
@ -1214,7 +1268,8 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
)
},
{
SensorsBridge.trackEvent("GameDetailDialogClick",
SensorsBridge.trackEvent(
"GameDetailDialogClick",
"game_id", mGameEntity?.id ?: "",
"game_name", mGameEntity?.name ?: "",
"game_type", mGameEntity?.categoryChinese ?: "",
@ -1386,7 +1441,12 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
mNewGameDetailEntity?.event?.let {
mBodyBinding.gameBigEvent.visibility = View.VISIBLE
if (it.highLight) {
mBodyBinding.gameBigEvent.setTextColor(ContextCompat.getColor(requireContext(), com.gh.gamecenter.common.R.color.text_2A85FB))
mBodyBinding.gameBigEvent.setTextColor(
ContextCompat.getColor(
requireContext(),
com.gh.gamecenter.common.R.color.text_2A85FB
)
)
mBodyBinding.gameBigEvent.background =
ContextCompat.getDrawable(requireContext(), R.drawable.bg_game_big_event_light)
mBodyBinding.gameBigEvent.setCompoundDrawablesWithIntrinsicBounds(
@ -1853,8 +1913,7 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
requireContext(),
mGameEntity,
callback = getCallback(),
)
)
} else {
ReservationHelper.showCancelReservationDialog(requireContext(), mGameEntity) {
ReservationHelper.cancelReservation(mGameEntity!!) {
@ -2214,7 +2273,8 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
pluginLocation = PluginLocation.only_game
)
val isLaunch = (status == getString(com.gh.gamecenter.feature.R.string.launch) || status == getString(
com.gh.gamecenter.feature.R.string.open))
com.gh.gamecenter.feature.R.string.open
))
if (SPUtils.getBoolean(
Constants.SP_SHOULD_SHOW_GAME_DETAIL_INSTALL_GUIDE,
false
@ -2374,6 +2434,9 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
}
override fun onBackPressed(): Boolean {
if (acceleratorUiHelper.onBack()) {
return true
}
mOrientationUtils?.backToProtVideo()
val trendsTabPosition = getTabPositionFromTabType(EntranceConsts.TAB_TYPE_TRENDS)
@ -2434,6 +2497,11 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
NewFlatLogUtils.logGameDetailExit(mGameEntity?.name ?: "", mGameEntity?.id ?: "")
}
override fun onDestroyView() {
acceleratorUiHelper.clear()
super.onDestroyView()
}
override fun onDestroy() {
super.onDestroy()
releaseVideo()
@ -2527,9 +2595,21 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
// 更新分割线样式
updateDivider()
mBodyBinding.collapsingToolbar.setContentScrimColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(requireContext()))
mBodyBinding.gamedetailAppbar.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(requireContext()))
mBodyBinding.toolbarGapView.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(requireContext()))
mBodyBinding.collapsingToolbar.setContentScrimColor(
com.gh.gamecenter.common.R.color.ui_surface.toColor(
requireContext()
)
)
mBodyBinding.gamedetailAppbar.setBackgroundColor(
com.gh.gamecenter.common.R.color.ui_surface.toColor(
requireContext()
)
)
mBodyBinding.toolbarGapView.setBackgroundColor(
com.gh.gamecenter.common.R.color.ui_background.toColor(
requireContext()
)
)
mBodyBinding.gamedetailTvName.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
mBodyBinding.gameBigEvent.background = R.drawable.bg_game_big_event.toDrawable(requireContext())
mBodyBinding.gameBigEvent.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(requireContext()))
@ -2541,7 +2621,11 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
}
}
if (it is ImageView) {
if (mIsDarkModeOn) it.setColorFilter(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext())) else it.colorFilter =
if (mIsDarkModeOn) it.setColorFilter(
com.gh.gamecenter.common.R.color.text_primary.toColor(
requireContext()
)
) else it.colorFilter =
null
it.background = (it.background as GradientDrawable).apply {
setStroke(0.5F.dip2px(), Color.parseColor(if (mIsDarkModeOn) "#33FFFFFF" else "#CCCCCC"))
@ -2553,7 +2637,11 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
val textView = tab.customView?.findViewById(R.id.tab_title) as? TextView ?: break
TextViewCompat.setTextAppearance(textView, com.gh.gamecenter.common.R.style.TabLayoutTextAppearance)
}
mBinding.detailLlBottom.detailLlBottom.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(requireContext()))
mBinding.detailLlBottom.detailLlBottom.setBackgroundColor(
com.gh.gamecenter.common.R.color.ui_background.toColor(
requireContext()
)
)
updateToolbarStyle(!mViewModel.displayTopVideo || mBodyBinding.gamedetailThumbSmall.visibility == View.VISIBLE)
mViewModel.gameDetailLiveData.value?.data?.let {
if (it.isShowContentCard(mGameEntity)) {
@ -2586,6 +2674,7 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
* 3. 当前游戏 APK 不为 1 个
* 4. 当前游戏类型不为畅玩
* 5. 当前游戏不是双下载时使用本地下载进行下载
* 6. 当前游戏配置了网络加速
*/
private fun isSpecialDownloadDialogAvailable(downloadEntity: DownloadEntity? = null): Boolean {
if (Config.getNewApiSettingsEntity()?.install?.questionTip?.linkEntity == null) return false
@ -2594,6 +2683,7 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
if (downloadEntity?.asVGame() == true) return false
if (downloadEntity?.isSimulatorGame() == true) return false
if (downloadEntity?.isLocalDownloadInDualDownloadMode() == true) return false
if (mGameEntity?.canSpeed == true) return false
return true
}

View File

@ -5,14 +5,10 @@ import android.app.Application
import android.net.Uri
import android.os.Build
import android.text.TextUtils
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.*
import com.gh.common.filter.RegionSettingHelper
import com.gh.common.history.HistoryHelper
import com.gh.common.util.CheckLoginUtils
import com.gh.gamecenter.feature.utils.ConcernUtils
import com.gh.common.util.LibaoUtils
import com.gh.common.util.PackageHelper
import com.gh.gamecenter.common.constant.Constants
@ -20,28 +16,53 @@ import com.gh.gamecenter.common.mvvm.Resource
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.toArrayList
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.utils.GsonUtils
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.entity.*
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.feature.entity.LibaoStatusEntity
import com.gh.gamecenter.feature.entity.SimpleGame
import com.gh.gamecenter.entity.RecommendPopupEntity
import com.gh.gamecenter.entity.UnifiedUserTrendEntity
import com.gh.gamecenter.feature.entity.*
import com.gh.gamecenter.feature.utils.ApkActiveUtils
import com.gh.gamecenter.feature.utils.ConcernUtils
import com.gh.gamecenter.feature.utils.ContentBlockedHelper
import com.gh.gamecenter.gamedetail.entity.*
import com.gh.gamecenter.gamedetail.accelerator.AccelerationDataBase
import com.gh.gamecenter.gamedetail.entity.BigEvent
import com.gh.gamecenter.gamedetail.entity.CustomColumn
import com.gh.gamecenter.gamedetail.entity.DetailEntity
import com.gh.gamecenter.gamedetail.entity.NewGameDetailEntity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserRepository
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import com.halo.assistant.member.MemberUseCase
import com.therouter.TheRouter
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
import org.json.JSONObject
import retrofit2.HttpException
import tv.danmaku.ijk.media.exo2.ExoSourceManager
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.collections.ArrayList
import kotlin.collections.List
import kotlin.collections.arrayListOf
import kotlin.collections.find
import kotlin.collections.firstOrNull
import kotlin.collections.forEach
import kotlin.collections.forEachIndexed
import kotlin.collections.hashMapOf
import kotlin.collections.isNotEmpty
import kotlin.collections.isNullOrEmpty
import kotlin.collections.removeAll
import kotlin.collections.set
import kotlin.collections.sortByDescending
import kotlin.collections.withIndex
class GameDetailViewModel(
application: Application,
@ -69,6 +90,10 @@ class GameDetailViewModel(
var videoIsMuted = SPUtils.getBoolean(Constants.SP_VIDEO_PLAY_MUTE, true)
var displayTopVideo: Boolean = false
private val compositeDisposable = CompositeDisposable()
val memberUseCase = MemberUseCase()
init {
loadData()
}
@ -85,9 +110,17 @@ class GameDetailViewModel(
getGameDetailNew()
getRecommendPopup(game?.id ?: "")
}
gameId != null -> getGameDigest()
else -> gameLiveData.postValue(null)
}
val userId = UserManager.getInstance().userId
if (userId.isNotBlank()) {
// 如果是登录状态获取最新的vip状态
UserRepository.getInstance().refreshVipStatus(userId, false)
}
}
// 获取游戏摘要
@ -162,6 +195,11 @@ class GameDetailViewModel(
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<NewGameDetailEntity>() {
override fun onSuccess(data: NewGameDetailEntity) {
game?.acceleratorStatus = data.acceleratorStatus
if (game?.canSpeed == true) {
// 此游戏支持加速
pkgName.value = game?.getUniquePackageName() ?: ""
}
if (data.newNotice != null) {
// 存在new_notice字段时移除detail_tab里面type=notice的项并添加new_notice到detail_tab
var noticePosition = -1
@ -659,6 +697,66 @@ class GameDetailViewModel(
})
}
fun getLastSpeedLiveData(pkgName: String) =
AccelerationDataBase.instance.accelerationDao().findByPackageName(pkgName)
fun getZoneListLiveData(pkgName: String) =
AccelerationDataBase.instance.accelerationDao().findZoneListBeanByPkgName(pkgName)
.map {
it?.acctGameList?.toArrayList()
}
private val zoneList = MutableLiveData<List<AcctGameInfo>>()
private val pkgName = MutableLiveData<String>()
val upsertAcctZoneListBeanAction = MediatorLiveData<Pair<String, List<AcctGameInfo>>>().apply {
addSource(zoneList) {
val pkgNameValue = pkgName.value
if (!pkgNameValue.isNullOrBlank()) {
value = pkgNameValue to it
}
}
addSource(pkgName) {
val zoneListValue = zoneList.value
if (zoneListValue != null) {
value = it to zoneListValue
}
}
}
fun loadAccelerationData(pkgName: String) {
Single.create<ArrayList<AcctGameInfo>> { emitter ->
val iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java)
iAcceleratorProvider?.loadQyGameZoneData(pkgName) {
val zoneData = (it as List<AcctGameInfo>).toArrayList()
emitter.onSuccess(zoneData)
} ?: emitter.onSuccess(arrayListOf())
}.timeout(5, TimeUnit.SECONDS)
.doOnSuccess {
zoneList.value = it
}
.subscribe({}, {}).let(compositeDisposable::add)
}
/**
* 只有能够加速的游戏需要保存区服信息
*/
@SuppressLint("CheckResult")
fun upsertAcctZoneListBean(pkgName: String, zoneList: List<AcctGameInfo>) {
AccelerationDataBase.instance.accelerationDao()
.upsertAcctZoneListBean(AcctZoneListBean(pkgName, zoneList))
.compose(singleToMain())
.subscribe({}, {})
}
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
memberUseCase.onClear()
}
class Factory(
private val mApplication: Application,
private val gameId: String?,

View File

@ -0,0 +1,76 @@
package com.gh.gamecenter.gamedetail
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.feature.entity.BaseEntity
import com.gh.gamecenter.feature.entity.TrialEntity
import com.gh.gamecenter.feature.entity.VipEntity
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserRepository
import com.halo.assistant.member.MemberUseCase
import com.therouter.TheRouter
import io.reactivex.disposables.CompositeDisposable
class StartingAcceleratorViewModel : ViewModel() {
private val compositeDisposable = CompositeDisposable()
val useCase = MemberUseCase()
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) {
_restartingAcceleratorAction.value = Event(it)
}
}
}
private val _rechargeTrailResult = MutableLiveData<Event<Boolean>>()
val rechargeTrailResult: LiveData<Event<Boolean>> = _rechargeTrailResult
fun rechargeTrial() {
val userId = UserManager.getInstance().userId
useCase.rechargeTrial(userId)
.compose(singleToMain())
.subscribe(object : BiResponse<BaseEntity<TrialEntity>>() {
override fun onSuccess(data: BaseEntity<TrialEntity>) {
if (data.data?.result == true) {
// 刷新vip状态
// 这里先刷新内存数据再去刷新api数据
TheRouter.get(IAcceleratorProvider::class.java)?.setVipEntity(
VipEntity(
_vipStatus = true,
_isNewUser = false,
_isTryVip = true
)
)
refreshVipStatus(userId)
_rechargeTrailResult.value = Event(true)
} else {
_rechargeTrailResult.value = Event(false)
}
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
_rechargeTrailResult.value = Event(false)
}
}).let(compositeDisposable::add)
}
private fun refreshVipStatus(userId: String) {
UserRepository.getInstance().refreshVipStatus(userId, true)
}
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
}
}

View File

@ -0,0 +1,35 @@
package com.gh.gamecenter.gamedetail.accelerator
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.AcctZoneListBean
import com.gh.gamecenter.room.converter.AcctGameInfoConverter
import com.halo.assistant.HaloApp
@Database(
entities = [AcctGameInfo::class, AcctZoneListBean::class],
version = 1,
exportSchema = false
)
@TypeConverters(
AcctGameInfoConverter::class
)
abstract class AccelerationDataBase : RoomDatabase() {
abstract fun accelerationDao(): AcceleratorDao
companion object {
private const val DATABASE_NAME: String = "acceleration_db"
val instance: AccelerationDataBase by lazy { buildDatabase(HaloApp.getInstance().application) }
private fun buildDatabase(context: Context): AccelerationDataBase {
return Room.databaseBuilder(context, AccelerationDataBase::class.java, DATABASE_NAME)
.build()
}
}
}

View File

@ -0,0 +1,25 @@
package com.gh.gamecenter.gamedetail.accelerator
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Upsert
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.AcctZoneListBean
import io.reactivex.Single
@Dao
interface AcceleratorDao {
@Upsert
fun upsertAcctGameInfo(gameInfo: AcctGameInfo): Single<Long>
@Query("SELECT * FROM AcctGameInfo WHERE accGamePkgName = :pkgName")
fun findByPackageName(pkgName: String): LiveData<AcctGameInfo?>
@Upsert
fun upsertAcctZoneListBean(acctZoneListBean: AcctZoneListBean): Single<Long>
@Query("SELECT * FROM AcctZoneListBean WHERE pkgName = :pkgName")
fun findZoneListBeanByPkgName(pkgName: String): LiveData<AcctZoneListBean?>
}

View File

@ -0,0 +1,182 @@
package com.gh.gamecenter.gamedetail.accelerator
import android.app.Activity
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.updateLayoutParams
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.databinding.LayoutAcceleratorGuidePageBinding
class AcceleratorGuideView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
def: Int = 0
) : FrameLayout(context, attrs, def) {
private val binding: LayoutAcceleratorGuidePageBinding
private val rect = Rect()
private var _isShowing = false
val isShowing: Boolean
get() = _isShowing
private var isCanDismiss = false
private val guideBackgroundColor =
com.gh.gamecenter.common.R.color.black_alpha_60.toColor(context)
private val xfermode by lazy {
val mode = PorterDuff.Mode.CLEAR
PorterDuffXfermode(mode)
}
private val paint by lazy {
Paint().apply {
color = Color.YELLOW
isAntiAlias = true
isDither = true
}
}
private var onStartSpeed: (() -> Unit)? = null
init {
val inflater = LayoutInflater.from(context)
binding = LayoutAcceleratorGuidePageBinding.inflate(inflater, this, true)
binding.root.setOnClickListener {
// 点击透明区域消失
dismiss()
}
binding.clContainer.setOnClickListener {
//覆盖外部点击事件,点击此区域 guideView 不消失
}
binding.vIKnow.setOnClickListener {
dismiss()
}
binding.vSpeed.setOnClickListener {
dismiss()
onStartSpeed?.invoke()
}
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, left, top, right, bottom)
val bottom = rect.bottom
val top = bottom - binding.clContainer.height
val right = rect.right + BORDER_WIDTH.dip2px()
val left = right - binding.clContainer.width
binding.clContainer.layout(left, top, right, bottom)
}
override fun dispatchDraw(canvas: Canvas) {
val saveCount = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), paint)
paint.color = guideBackgroundColor
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
onDrawDecoration(canvas)
paint.xfermode = xfermode
canvas.drawRoundRect(
RectF(rect),
HIGH_LIGHT_RADIUS.dip2px().toFloat(),
HIGH_LIGHT_RADIUS.dip2px().toFloat(),
paint
)
paint.xfermode = null
canvas.restoreToCount(saveCount)
super.dispatchDraw(canvas)
}
private fun onDrawDecoration(canvas: Canvas) {
paint.color = com.gh.gamecenter.common.R.color.ui_surface.toColor(context)
val border = BORDER_WIDTH.dip2px()
val radius = HIGH_LIGHT_RADIUS.dip2px().toFloat()
val dRectF = RectF(
rect.left.toFloat() - border,
rect.top.toFloat() - border,
rect.right.toFloat() + border,
rect.bottom.toFloat() + border
)
canvas.drawRoundRect(dRectF, radius, radius, paint)
}
private fun setHighLightRect(newRect: Rect) {
rect.set(newRect)
binding.vSpeed.updateLayoutParams {
width = rect.width()
}
}
fun show(anchor: View, activity: Activity, block: () -> Unit) {
onStartSpeed = block
val newRect = getLocationInWindow(anchor)
val content = activity.window.decorView as ViewGroup
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
setHighLightRect(newRect)
content.addView(this)
setOnClickListener {
if (isCanDismiss) {
dismiss()
}
}
setOnClickListener {
}
_isShowing = true
isCanDismiss = false
AppExecutor.uiExecutor.executeWithDelay({
isCanDismiss = true
}, SHOW_MIN_DURATION)
}
fun dismiss() {
(parent as? ViewGroup)?.removeView(this)
_isShowing = false
onDismissListener?.invoke()
}
private var onDismissListener: (() -> Unit)? = null
fun setDismissListener(listener: () -> Unit) {
onDismissListener = listener
}
private fun getLocationInWindow(anchor: View): Rect {
val result = Rect()
val pos = IntArray(2)
anchor.getLocationInWindow(pos)
result.left = pos[0]
result.top = pos[1]
result.right = result.left + anchor.width
result.bottom = result.top + anchor.height
return result
}
companion object {
private const val BORDER_WIDTH = 4F
private const val HIGH_LIGHT_RADIUS = 100F
private const val SHOW_MIN_DURATION = 500L
}
}

View File

@ -0,0 +1,401 @@
package com.gh.gamecenter.gamedetail.accelerator
import android.content.Context
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import com.gh.common.util.CheckLoginUtils
import com.gh.common.util.PackageLauncher
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.callback.AccelerateState
import com.gh.gamecenter.core.callback.OnAccelerateListener
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.DetailDownloadItemBinding
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.gamedetail.accelerator.chain.*
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment.Companion.SPEED_STOP
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorZoneDialogFragment
import com.gh.gamecenter.gamedetail.accelerator.dialog.StartingAcceleratorDialogFragment
import com.halo.assistant.member.MemberRepository
import com.therouter.TheRouter
class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBinding) {
private var iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java)
private var guideView: AcceleratorGuideView? = null
val isGuideLayerShowing: Boolean
get() = guideView?.isShowing ?: false
private val zoneList = arrayListOf<AcctGameInfo>()
private var hasZoneListLoaded = false
private var _last: AcctGameInfo? = null
val last: AcctGameInfo?
get() = _last
private val hasMultiZone: Boolean
get() = zoneList.size > 1
private val hasZone: Boolean
get() = zoneList.isNotEmpty()
private val isVip: Boolean
get() = iAcceleratorProvider?.isVip() ?: false
val isNewUser: Boolean
get() = iAcceleratorProvider?.isNewUser() ?: false
private var canSpeed = false
var showSpeedUi = false
private var _game: GameEntity? = null
fun setGame(game: GameEntity?) {
_game = game
}
private val accelerationListener = object : OnAccelerateListener {
override fun onStateChanged(state: AccelerateState) {
when (state) {
is AccelerateState.Success -> {
_game?.let {
val acctGameInfo = state.acctGameInfo
if (acctGameInfo is AcctGameInfo) {
setCurrentAcctGameInfo(acctGameInfo)
}
updateSpeedUi()
}
}
is AccelerateState.Normal -> {
if (!state.isTokenExpired) {
updateSpeedUi()
}
}
else -> Unit
}
}
override fun onProgress(progress: Int, curGamePkgName: String?, curGameZoneFlag: String?) = Unit
override fun onVipStatusChanged(isNewUser: Boolean, isVip: Boolean) {
binding.ivFreeVipTag.visibleIf(
showSpeedUi &&
CheckLoginUtils.isLogin() &&
isNewUser &&
!isGuideLayerShowing
)
}
}
fun initSpeedUi(context: Context) {
canSpeed = true
_game?.let {
iAcceleratorProvider?.bindAccRelatedListener(it.getUniquePackageName() ?: "", accelerationListener)
}
binding.vSpeedContent.setOnClickListener {
_game?.let { game ->
checkDataReady {
startAccelerating(context, game, false)
}
}
}
binding.vMoreZone.setOnClickListener {
_game?.let { game ->
checkDataReady {
showZoneList(context, game)
}
}
}
binding.tvStopSpeed.setOnClickListener {
_game?.let {
SensorsBridge.trackNetworkAccelerationOtherButtonClick(
it.getUniquePackageName() ?: "",
it.id,
it.name ?: "",
iAcceleratorProvider?.getMemberType() ?: "",
BUTTON_NAME_STOP_ACCELERATOR,
SOURCE_ENTRANCE_GAME_DETAIL
)
AcceleratorDialogFragment.show(
SPEED_STOP, it.getUniquePackageName() ?: "", it.id, it.name ?: "",
SOURCE_ENTRANCE_GAME_DETAIL, context
)
}
}
binding.tvEnterGame.setOnClickListener {
_game?.let { game ->
SensorsBridge.trackNetworkAccelerationOtherButtonClick(
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
iAcceleratorProvider?.getMemberType() ?: "",
BUTTON_NAME_ENTER_GAME,
SOURCE_ENTRANCE_GAME_DETAIL
)
PackageLauncher.launchApp(context, game, game.getUniquePackageName())
}
}
updateSpeedUi()
}
fun updateSpeedUi() {
if (!showSpeedUi) {
return
}
val hasGameBeingAccelerated = iAcceleratorProvider?.isCurAccSuccess() ?: false
val isCurAccSuccess = hasGameBeingAccelerated &&
MemberRepository.instance.acctGameRecord.gameId == (_game?.id ?: "")
binding.detailProgressbar.goneIf(isCurAccSuccess)
binding.clSpeed.goneIf(isCurAccSuccess)
binding.gAccelerating.goneIf(!isCurAccSuccess)
binding.gMoreZone.goneIf(!hasMultiZone)
binding.ivFreeVipTag.visibleIf(
showSpeedUi &&
CheckLoginUtils.isLogin() &&
isNewUser &&
!isGuideLayerShowing
)
binding.tvSpeed.text = if (hasMultiZone) {
last?.zoneName
} else {
null
} ?: R.string.network_acceleration.toResString()
}
/**
* 在这个方法里确定是否需要展示 加速ui
* 当 clSpeedContainer没有显示时调用updateSpeedUi()无效,可以调用initSpeedUi()
* 请注意,只要是 gameDetail 发生变化,这里就会回调,所以当 canSpeed 确定之后,这里最少还会回调一次
*/
fun checkIfShowGuideLayer(context: Context) {
showSpeedUi = canSpeed
binding.clSpeedContainer.goneIf(!showSpeedUi) {
// canSpeed =true 说明detail接口已完成
binding.detailProgressbar.setBackgroundResource(com.gh.gamecenter.common.R.drawable.bg_common_button_light_fill_blue)
binding.detailProgressbar.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
updateSpeedUi()
if (CheckLoginUtils.isLogin()) {
if (!hasShow()) {
// 优先显示弹窗
binding.ivFreeVipTag.visibleIf(false)
showGuideLayer(context)
}
} else {
// 未登录
binding.ivFreeVipTag.visibleIf(false)
showGuideLayer(context)
}
}
}
private fun showGuideLayer(context: Context) {
if (!hasShow()) {
binding.root.post {
val uiListener = object : OnAcceleratorListener {
override fun onStartAccelerator() {
_game?.let {
startAccelerating(context, it, true)
}
}
override fun onGuideLayerDismiss() {
binding.ivFreeVipTag.visibleIf(CheckLoginUtils.isLogin() && hasZone && isNewUser)
}
}
SPUtils.setBoolean(Constants.SP_HAS_SHOW_ACCELERATION_GUIDE_LAYER, true)
if (guideView == null) {
guideView = AcceleratorGuideView(context).apply {
setDismissListener(uiListener::onGuideLayerDismiss)
}
}
(guideView?.parent as? ViewGroup)?.removeView(guideView)
if (context is AppCompatActivity) {
_game?.let {
SensorsBridge.trackNetworkAccelerationGuidanceDiagramShow(
it.getUniquePackageName() ?: "",
it.id,
it.name ?: ""
)
}
guideView?.show(binding.clSpeedContainer, context, uiListener::onStartAccelerator)
}
}
}
}
fun onBack(): Boolean {
val isShowing = guideView?.isShowing ?: false
if (isShowing) {
guideView?.dismiss()
return true
} else {
return false
}
}
private fun hasShow(): Boolean {
return SPUtils.getBoolean(Constants.SP_HAS_SHOW_ACCELERATION_GUIDE_LAYER)
}
private fun startAccelerating(context: Context, game: GameEntity, isShowing: Boolean) {
if (isInvalidClick() || !hasZone) {
return
}
val lastAcctGameInfo = last
val memberType = if (CheckLoginUtils.isLogin()) {
iAcceleratorProvider?.getMemberType() ?: ""
} else {
MEMBER_TYPE_NOT_LOGIN
}
val districtServer = when {
hasMultiZone && lastAcctGameInfo != null -> lastAcctGameInfo.zoneName
hasMultiZone -> DISTRICT_SERVER_EMPTY
else -> DISTRICT_SERVER_HAVA
}
SensorsBridge.trackNetworkAccelerationButtonClick(
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
memberType,
districtServer,
if (isShowing) SCENE_TYPE_HAVE_GUIDE_LAYER else SCENE_TYPE_NO_GUIDE_LAYER,
SOURCE_ENTRANCE_GAME_DETAIL
)
when {
lastAcctGameInfo != null ->
doStartAccelerating(context, game, lastAcctGameInfo)
hasMultiZone -> AcceleratorZoneDialogFragment.show(context, zoneList, game)
else -> {
val gameInfo = zoneList.firstOrNull() ?: AcctGameInfo("", AcctGameInfo.ZoneInfo(0))
doStartAccelerating(context, game, gameInfo)
}
}
}
private fun showZoneList(context: Context, game: GameEntity) {
if (isInvalidClick() || !hasZone) {
return
}
AcceleratorZoneDialogFragment.show(context, zoneList, game)
}
private var clickTime = 0L
private fun doStartAccelerating(context: Context, game: GameEntity, acctGameInfo: AcctGameInfo) {
val request = AcceleratorValidator.Request(isVip, isNewUser, game, SOURCE_ENTRANCE_GAME_DETAIL)
AcceleratorClient.newInstance()
.execute(context, request, object : AcceleratorValidator.ValidateListener {
override fun finished(context: Context) {
StartingAcceleratorDialogFragment.show(
context, acctGameInfo, game, true, hasMultiZone,
SOURCE_ENTRANCE_GAME_DETAIL,
)
}
})
}
fun setCurrentAcctGameInfo(acctGameInfo: AcctGameInfo?) {
_last = acctGameInfo
if (hasZoneListLoaded) {
updateSpeedUi()
}
}
/**
* 第一次调用 setZoneList 是使用缓存中的数据
* 第二次调用是使用sdk中的最新数据
* 这里优先使用缓存中的sdk中的数据更新到数据库后待下次进入详情页在显示
* 当zoneList为null时说明磁盘上没有数据这时候使用sdk中的
*
*/
fun setZoneList(newZoneList: ArrayList<AcctGameInfo>?) {
if (newZoneList != null && zoneList.isEmpty()) {
hasZoneListLoaded = true
zoneList.clear()
zoneList.addAll(newZoneList)
updateSpeedUi()
}
}
private fun isInvalidClick(): Boolean {
// 300ms内只能调用一次
val currentTime = System.currentTimeMillis()
val difTime = currentTime - clickTime
if (difTime <= CLICK_DURATION) {
return true
}
clickTime = currentTime
return false
}
private fun checkDataReady(block: () -> Unit) {
when {
!hasZoneListLoaded -> {
ToastUtils.showToast("区服信息正在加载中,请稍后再试!")
}
zoneList.isEmpty() -> {
ToastUtils.showToast("区服信息加载失败,请稍后再试!")
}
else -> {
block()
}
}
}
fun clear() {
iAcceleratorProvider?.unBindAccRelatedListener(accelerationListener)
guideView?.dismiss()
}
companion object {
private const val CLICK_DURATION = 300
const val MEMBER_TYPE_NOT_LOGIN = "未登录"
private const val DISTRICT_SERVER_EMPTY = ""
const val DISTRICT_SERVER_HAVA = ""
private const val SCENE_TYPE_HAVE_GUIDE_LAYER = "有引导图"
const val SCENE_TYPE_NO_GUIDE_LAYER = "无引导图"
const val SOURCE_ENTRANCE_GAME_DETAIL = "游戏详情页"
const val SOURCE_ENTRANCE_MY_ASSETS = "我的资产"
const val BUTTON_NAME_ENTER_GAME = "进入游戏"
const val BUTTON_NAME_STOP_ACCELERATOR = "停止加速"
}
interface OnAcceleratorListener {
fun onStartAccelerator()
fun onGuideLayerDismiss()
}
}

View File

@ -0,0 +1,54 @@
package com.gh.gamecenter.gamedetail.accelerator
import android.content.Context
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.ShellActivity
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.DialogHelper
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.toResString
/**
* 会员功能 实名认证相关弹窗
*/
object UserVerifyDialogUtils {
fun showUnVerifiedDialog(context: Context, title: String, content: String) {
DialogHelper.showDialog(
context,
title = title,
content = content,
confirmText = R.string.go_to_real_name_authentication.toResString(),
cancelText = "取消",
confirmClickCallback = {
context.startActivity(
ShellActivity.getIntent(
context,
ShellActivity.Type.REAL_NAME_INFO
).apply {
putExtra(EntranceConsts.KEY_SOURCE_ENTRANCE, "游戏实名")
putExtra(EntranceConsts.KEY_IS_FORCED_TO_CERTIFICATE, true)
}
)
NewLogUtils.logCertificationHintDialogOptionsClicked("前往实名认证")
SensorsBridge.trackVerificationPopupClick("前往实名认证")
},
cancelClickCallback = {
NewLogUtils.logCertificationHintDialogOptionsClicked("取消")
SensorsBridge.trackVerificationPopupClick("取消")
}
)
}
fun showMinorsOrVerifyingDialog(context: Context, title: String, content: String) {
DialogHelper.showDialog(
context,
title = title,
content = content,
confirmText = R.string.dialog_hint_confirm.toResString(),
cancelText = "",
)
}
}

View File

@ -0,0 +1,28 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment.Companion.SPEED_NOT_INSTALLED
class AcceleratorCheckInstallInterceptor : AcceleratorValidator.Interceptor {
override fun intercept(
context: Context,
chain: AcceleratorValidator.Chain,
listener: AcceleratorValidator.ValidateListener?
) {
val request = chain.request
if (PackageUtils.isInstalled(context, request.game.getUniquePackageName() ?: "")) {
chain.proceed(context, request, listener)
} else {
val game = request.game
AcceleratorDialogFragment.show(
SPEED_NOT_INSTALLED,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
request.sourceEntrance, context
)
}
}
}

View File

@ -0,0 +1,29 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
class AcceleratorClient(
private vararg val interceptors: AcceleratorValidator.Interceptor,
) {
fun execute(
context: Context,
request: AcceleratorValidator.Request,
listener: AcceleratorValidator.ValidateListener
) {
val chain = RealAcceleratorInterceptorChain(interceptors.toList(), 0, request)
chain.proceed(context, request, listener)
}
companion object {
fun newInstance() = AcceleratorClient(
AcceleratorCheckInstallInterceptor(),
AcceleratorLoginInterceptor(),
AcceleratorPackageCheckInterceptor(),
AcceleratorRealNameInterceptor(),
AcceleratorVipInterceptor(),
AcceleratorStateInterceptor()
)
}
}

View File

@ -0,0 +1,18 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
import com.gh.gamecenter.common.utils.ifLogin
class AcceleratorLoginInterceptor : AcceleratorValidator.Interceptor {
override fun intercept(
context: Context,
chain: AcceleratorValidator.Chain,
listener: AcceleratorValidator.ValidateListener?
) {
context.ifLogin("[网络加速]") {
val request = chain.request
chain.proceed(context, request, listener)
}
}
}

View File

@ -0,0 +1,20 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import com.gh.common.dialog.PackageCheckDialogFragment
class AcceleratorPackageCheckInterceptor : AcceleratorValidator.Interceptor {
override fun intercept(
context: Context,
chain: AcceleratorValidator.Chain,
listener: AcceleratorValidator.ValidateListener?
) {
val request = chain.request
if (context is AppCompatActivity) {
PackageCheckDialogFragment.show(context, request.game) {
chain.proceed(context, request, listener)
}
}
}
}

View File

@ -0,0 +1,26 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
import com.gh.common.util.TempCertificationUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.gamedetail.accelerator.UserVerifyDialogUtils
class AcceleratorRealNameInterceptor : AcceleratorValidator.Interceptor {
override fun intercept(
context: Context,
chain: AcceleratorValidator.Chain,
listener: AcceleratorValidator.ValidateListener?
) {
val request = chain.request
if (TempCertificationUtils.isUserVerified()) {
chain.proceed(context, request, listener)
} else {
UserVerifyDialogUtils.showUnVerifiedDialog(
context,
R.string.real_name_tips.toResString(),
R.string.acceleration_service_without_real_name_description.toResString()
)
}
}
}

View File

@ -0,0 +1,37 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorReplaceDialogFragment
import com.therouter.TheRouter
class AcceleratorStateInterceptor : AcceleratorValidator.Interceptor {
override fun intercept(
context: Context,
chain: AcceleratorValidator.Chain,
listener: AcceleratorValidator.ValidateListener?
) {
val request = chain.request
val iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java)
val isAccelerating = iAcceleratorProvider?.isCurAccSuccess() ?: false
if (isAccelerating) {
val game = request.game
AcceleratorReplaceDialogFragment.show(
context,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
request.sourceEntrance
) {
if (chain.isValidContext(context)) {
listener?.finished(context)
}
}
} else {
chain.proceed(context, request, listener)
}
}
}

View File

@ -0,0 +1,71 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
import android.os.Parcelable
import androidx.appcompat.app.AppCompatActivity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorValidator.Request
import kotlinx.parcelize.Parcelize
class AcceleratorValidator {
interface ValidateListener {
fun finished(context: Context)
}
interface Interceptor {
fun intercept(context: Context, chain: Chain, listener: ValidateListener?)
}
interface Chain {
val request: Request
fun proceed(context: Context, request: Request, listener: ValidateListener?)
fun isValidContext(context: Context) =
context is AppCompatActivity && !context.isFinishing && !context.isDestroyed
}
@Parcelize
data class Request(
val isVip: Boolean,
val isNewUser: Boolean,
val game: GameEntity,
val sourceEntrance: String
):Parcelable
}
class RealAcceleratorInterceptorChain(
private val interceptors: List<AcceleratorValidator.Interceptor>,
private val index: Int,
private val _request: Request
) : AcceleratorValidator.Chain {
override val request: Request
get() = _request
override
fun proceed(context: Context, request: Request, listener: AcceleratorValidator.ValidateListener?) {
if (!isValidContext(context)) {
// 如果 context失效说明当前页面已销毁直接中断流程
return
}
if (index >= interceptors.size) {
// 验证完成
listener?.finished(context)
return
}
val next = RealAcceleratorInterceptorChain(
interceptors,
index + 1,
_request
)
interceptors[index].intercept(context, next, listener)
}
}

View File

@ -0,0 +1,31 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment.Companion.SPEED_ENABLE_VIP
class AcceleratorVipInterceptor : AcceleratorValidator.Interceptor {
override fun intercept(
context: Context,
chain: AcceleratorValidator.Chain,
listener: AcceleratorValidator.ValidateListener?
) {
val request = chain.request
val isVip = request.isVip
val isNewUser = request.isNewUser
if (isVip || isNewUser) {
chain.proceed(context, request, listener)
} else {
val game = request.game
AcceleratorDialogFragment.show(
SPEED_ENABLE_VIP,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
request.sourceEntrance,
context
)
}
}
}

View File

@ -0,0 +1,241 @@
package com.gh.gamecenter.gamedetail.accelerator.dialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IntDef
import androidx.appcompat.app.AppCompatActivity
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.WebActivity
import com.gh.gamecenter.common.HaloApp
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.databinding.DialogFragmentSpeedReplaceGameBinding
import com.therouter.TheRouter
/**
* 加速器相关的dialog
*/
class AcceleratorDialogFragment : BaseDialogFragment() {
private lateinit var binding: DialogFragmentSpeedReplaceGameBinding
private lateinit var uiHelper: SpeedDialogUiHelper
private var pkgName = ""
private var gameId = ""
private var gameName = ""
private var sourceEntrance = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val type = arguments?.getInt(EntranceConsts.KEY_SPEED_TYPE) ?: 0
pkgName = arguments?.getString(EntranceConsts.KEY_PACKAGENAME) ?: ""
gameId = arguments?.getString(EntranceConsts.KEY_GAMEID) ?: ""
gameName = arguments?.getString(EntranceConsts.KEY_GAMENAME) ?: ""
sourceEntrance = arguments?.getString(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: ""
uiHelper = when (type) {
SPEED_ENABLE_VIP -> {
SensorsBridge.trackMembershipActivationDialogShow(pkgName, gameId, gameName, sourceEntrance)
EnableVipUi()
}
SPEED_START_FAILURE -> {
SensorsBridge.trackNetworkAccelerationFailureDialogShow(pkgName, gameId, gameName, sourceEntrance)
SpeedFailureUi()
}
SPEED_STOP -> StopSpeedUi()
SPEED_NOT_INSTALLED -> NotInstalledUi()
else -> throw IllegalArgumentException("请传递正确的参数 SpeedType !")
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val createDialog = super.onCreateDialog(savedInstanceState)
createDialog.setCanceledOnTouchOutside(true)
createDialog.setCancelable(true)
return createDialog
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return DialogFragmentSpeedReplaceGameBinding.inflate(inflater, container, false)
.also {
binding = it
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.tvContent.setText(uiHelper.contentResId)
binding.tvCancel.goneIf(!uiHelper.isShowCancelButton) {
if (uiHelper.cancelResId != -1) {
binding.tvCancel.setText(uiHelper.cancelResId)
}
binding.tvCancel.setOnClickListener {
dismiss()
}
}
binding.tvSubmit.setText(uiHelper.submitResId)
binding.tvSubmit.setTextColor(uiHelper.submitTextColorResId.toColor(view.context))
binding.tvSubmit.setBackgroundResource(uiHelper.submitBackgroundResId)
binding.tvSubmit.setOnClickListener {
dismiss()
when (uiHelper) {
is EnableVipUi -> {
SensorsBridge.trackMembershipActivationDialogClick(
pkgName,
gameId,
gameName,
sourceEntrance
)
SensorsBridge.trackMyAssetsPageShow(pkgName, gameId, gameName, sourceEntrance)
val intent = WebActivity.getMyAssetsIntent(requireContext())
startActivity(intent)
}
is SpeedFailureUi -> {
SensorsBridge.trackNetworkAccelerationFailureDialogClick(pkgName, gameId, gameName, sourceEntrance)
context?.let {
DirectUtils.directToWebView(it, Constants.QQ_QIDIAN_ADDRESS, "")
}
}
is StopSpeedUi -> {
TheRouter.get(IAcceleratorProvider::class.java)?.stopQyGameAccelerate()
}
}
}
}
override fun onStart() {
super.onStart()
val width = HaloApp.getInstance().resources.displayMetrics.widthPixels - 60F.dip2px()
val height = dialog?.window?.attributes?.height ?: ViewGroup.LayoutParams.WRAP_CONTENT
dialog?.window?.setLayout(width, height)
}
companion object {
const val SPEED_ENABLE_VIP = 1
const val SPEED_START_FAILURE = 2
const val SPEED_STOP = 3
const val SPEED_NOT_INSTALLED = 4
fun show(
@SpeedType type: Int,
pkgName: String,
gameId: String,
gameName: String,
sourceEntrance: String,
context: Context
) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = AcceleratorDialogFragment().apply {
arguments = Bundle().apply {
putInt(EntranceConsts.KEY_SPEED_TYPE, type)
putString(EntranceConsts.KEY_PACKAGENAME, pkgName)
putString(EntranceConsts.KEY_GAMEID, gameId)
putString(EntranceConsts.KEY_GAMENAME, gameName)
putString(EntranceConsts.KEY_SOURCE_ENTRANCE, sourceEntrance)
}
}
fragment.show(it, AcceleratorDialogFragment::class.java.simpleName)
}
}
}
@IntDef(SPEED_ENABLE_VIP, SPEED_START_FAILURE, SPEED_STOP, SPEED_NOT_INSTALLED)
@Retention(AnnotationRetention.SOURCE)
annotation class SpeedType
abstract class SpeedDialogUiHelper {
abstract val contentResId: Int
abstract val isShowCancelButton: Boolean
open val cancelResId: Int = -1
abstract val submitResId: Int
open val submitTextColorResId = com.gh.gamecenter.common.R.color.text_aw_primary
open val submitBackgroundResId = com.gh.gamecenter.common.R.drawable.bg_common_button_fill_blue
}
class EnableVipUi : SpeedDialogUiHelper() {
override val contentResId: Int
get() = R.string.enable_vip_tips
override val isShowCancelButton: Boolean
get() = false
override val submitResId: Int
get() = R.string.go_to_activate
}
class SpeedFailureUi : SpeedDialogUiHelper() {
override val contentResId: Int
get() = R.string.speed_failure_tips
override val isShowCancelButton: Boolean
get() = false
override val submitResId: Int
get() = R.string.contact_service
override val submitTextColorResId: Int
get() = com.gh.gamecenter.common.R.color.primary_theme
override val submitBackgroundResId: Int
get() = com.gh.gamecenter.common.R.drawable.bg_common_button_light_fill_blue
}
class StopSpeedUi : SpeedDialogUiHelper() {
override val contentResId: Int
get() = R.string.stop_speed_tips
override val isShowCancelButton: Boolean
get() = true
override val submitResId: Int
get() = R.string.stop_speed
override val cancelResId: Int
get() = R.string.cancel
}
class NotInstalledUi : SpeedDialogUiHelper() {
override val contentResId: Int
get() = R.string.speed_not_installed_tips
override val isShowCancelButton: Boolean
get() = false
override val submitResId: Int
get() = R.string.dialog_hint_confirm
}
}

View File

@ -0,0 +1,116 @@
package com.gh.gamecenter.gamedetail.accelerator.dialog
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.HaloApp
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.databinding.DialogFragmentSpeedReplaceGameBinding
import com.lightgame.dialog.BaseDialogFragment
class AcceleratorReplaceDialogFragment : BaseDialogFragment() {
private lateinit var binding: DialogFragmentSpeedReplaceGameBinding
private var pkgName = ""
private var gameId = ""
private var gameName = ""
private var sourceEntrance = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pkgName = arguments?.getString(EntranceConsts.KEY_PACKAGENAME) ?: ""
gameId = arguments?.getString(EntranceConsts.KEY_GAMEID) ?: ""
gameName = arguments?.getString(EntranceConsts.KEY_GAMENAME) ?: ""
sourceEntrance = arguments?.getString(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: ""
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return DialogFragmentSpeedReplaceGameBinding.inflate(inflater, container, false)
.also {
binding = it
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
SensorsBridge.trackNetworkAccelerationConflictDialogShow(pkgName, gameId, gameName, sourceEntrance)
binding.tvContent.text = getString(R.string.speed_replace_game_tips, gameName)
binding.tvCancel.setOnClickListener {
SensorsBridge.trackNetworkAccelerationConflictDialogClick(
pkgName,
gameId,
gameName,
BUTTON_NAME_CANCEL,
sourceEntrance
)
dismiss()
}
binding.tvSubmit.setOnClickListener {
SensorsBridge.trackNetworkAccelerationConflictDialogClick(
pkgName,
gameId,
gameName,
BUTTON_NAME_CONTINUE,
sourceEntrance
)
// 加速
dismiss()
callback?.invoke()
}
}
override fun onStart() {
super.onStart()
val width = HaloApp.getInstance().resources.displayMetrics.widthPixels - 60F.dip2px()
val height = dialog?.window?.attributes?.height ?: ViewGroup.LayoutParams.WRAP_CONTENT
dialog?.window?.setLayout(width, height)
}
private var callback: (() -> Unit)? = null
fun setOnSubmitListener(callback: () -> Unit) {
this.callback = callback
}
companion object {
private const val BUTTON_NAME_CANCEL = "暂不启动"
private const val BUTTON_NAME_CONTINUE = "继续启动"
fun show(
context: Context,
pkgName: String,
gameId: String,
gameName: String,
sourceEntrance: String,
callback: () -> Unit
) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = AcceleratorReplaceDialogFragment().apply {
setOnSubmitListener(callback)
arguments = Bundle().apply {
putString(EntranceConsts.KEY_PACKAGENAME, pkgName)
putString(EntranceConsts.KEY_GAMEID, gameId)
putString(EntranceConsts.KEY_GAMENAME, gameName)
putString(EntranceConsts.KEY_SOURCE_ENTRANCE, sourceEntrance)
}
}
fragment.show(it, AcceleratorDialogFragment::class.java.simpleName)
}
}
}
}

View File

@ -0,0 +1,197 @@
package com.gh.gamecenter.gamedetail.accelerator.dialog
import android.content.Context
import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.common.util.CheckLoginUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseBottomDialogFragment
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.databinding.DialogFragmentAcceleratorZoneBinding
import com.gh.gamecenter.databinding.RecyclerAcceleratorZoneBinding
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.MEMBER_TYPE_NOT_LOGIN
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SCENE_TYPE_NO_GUIDE_LAYER
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SOURCE_ENTRANCE_GAME_DETAIL
import com.gh.gamecenter.gamedetail.AcceleratorZoneViewModel
import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorClient
import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorValidator
import com.therouter.TheRouter
import kotlin.math.roundToInt
class AcceleratorZoneDialogFragment : BaseBottomDialogFragment<DialogFragmentAcceleratorZoneBinding>() {
private val viewModel by viewModels<AcceleratorZoneViewModel>()
private lateinit var adapter: ZoneAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val data =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
arguments?.getParcelableArrayList(KEY_DATA, AcctGameInfo::class.java)
} else {
arguments?.getParcelableArrayList(KEY_DATA)
} ?: listOf<AcctGameInfo>()
val game = arguments?.getParcelable<GameEntity>(KEY_GAME)!!
mBinding.titleView.setOnRightClickListener {
dismiss()
}
adapter = ZoneAdapter {
startAccelerating(game, it)
dismiss()
}
mBinding.recyclerZone.layoutManager = GridLayoutManager(requireContext(), 3)
mBinding.recyclerZone.addItemDecoration(MyDecorationItem())
mBinding.recyclerZone.adapter = adapter
adapter.submitList(data)
}
private fun startAccelerating(game: GameEntity, acctGameInfo: AcctGameInfo) {
context?.let {
viewModel.useCase.insertAcctGameInfo(acctGameInfo)
val iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java)
val memberType = if (CheckLoginUtils.isLogin()) {
iAcceleratorProvider?.getMemberType() ?: ""
} else {
MEMBER_TYPE_NOT_LOGIN
}
SensorsBridge.trackNetworkAccelerationButtonClick(
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
memberType,
acctGameInfo.zoneInfo.cnName ?: "",
SCENE_TYPE_NO_GUIDE_LAYER,
SOURCE_ENTRANCE_GAME_DETAIL
)
val isVip = iAcceleratorProvider?.isVip() ?: false
val isNewUser = iAcceleratorProvider?.isNewUser() ?: false
val request = AcceleratorValidator.Request(isVip, isNewUser, game, SOURCE_ENTRANCE_GAME_DETAIL)
AcceleratorClient.newInstance()
.execute(it, request, object : AcceleratorValidator.ValidateListener {
override fun finished(context: Context) {
StartingAcceleratorDialogFragment.show(
context, acctGameInfo, game, isNeedRecord = true, hasMultiZone = true,
sourceEntrance = SOURCE_ENTRANCE_GAME_DETAIL
)
}
})
}
}
companion object {
private const val KEY_DATA = "key_data"
private const val KEY_GAME = "key_game"
fun show(context: Context, data: ArrayList<AcctGameInfo>, game: GameEntity) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = AcceleratorZoneDialogFragment().apply {
arguments = Bundle().apply {
putParcelableArrayList(KEY_DATA, data)
putParcelable(KEY_GAME, game)
}
}
fragment.show(it, fragment::class.java.simpleName)
}
}
}
private class MyDecorationItem : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val position = parent.getChildAdapterPosition(view)
val space = (16F.dip2px() / 3F).roundToInt()
val (left, right) = when (position % 3) {
0 -> 0 to space
1 -> space to space
else -> space to 0
}
outRect.top = 8F.dip2px()
outRect.left = left
outRect.right = right
}
}
class ZoneAdapter(private val click: (AcctGameInfo) -> Unit) :
ListAdapter<AcctGameInfo, ZoneAdapter.ZoneViewHolder>(diffCallback) {
private var selectedPosition = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ZoneViewHolder {
return ZoneViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: ZoneViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isNotEmpty()) {
updateSelectedState(holder, position)
} else {
super.onBindViewHolder(holder, position, payloads)
}
}
override fun onBindViewHolder(holder: ZoneViewHolder, position: Int) {
val item = getItem(position) ?: return
updateSelectedState(holder, position)
holder.binding.tvName.text = item.zoneName
holder.itemView.setOnClickListener {
click(item)
}
}
private fun updateSelectedState(holder: ZoneViewHolder, position: Int) {
val (textColorResId, backgroundResId) = if (position == selectedPosition) {
com.gh.gamecenter.common.R.color.text_theme to R.drawable.bg_shape_2496ff_alpha_10_radius_8
} else {
com.gh.gamecenter.common.R.color.text_secondary to R.drawable.bg_shape_f8_radius_8
}
holder.binding.tvName.setTextColor(textColorResId.toColor(holder.itemView.context))
holder.binding.tvName.setBackgroundResource(backgroundResId)
}
companion object {
private const val SELECTED_CHANGED_PAYLOAD = "selected_changed_payload"
private val diffCallback = object : DiffUtil.ItemCallback<AcctGameInfo>() {
override fun areItemsTheSame(oldItem: AcctGameInfo, newItem: AcctGameInfo): Boolean {
return oldItem.zoneInfo.id == newItem.zoneInfo.id
}
override fun areContentsTheSame(oldItem: AcctGameInfo, newItem: AcctGameInfo): Boolean {
return oldItem == newItem
}
}
}
class ZoneViewHolder(val binding: RecyclerAcceleratorZoneBinding) : ViewHolder(binding.root)
}
}

View File

@ -0,0 +1,258 @@
package com.gh.gamecenter.gamedetail.accelerator.dialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.viewModels
import com.gh.common.util.PackageLauncher
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.core.callback.AccelerateState
import com.gh.gamecenter.core.callback.OnAccelerateListener
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.DialogFragmentStartingAcceleratorBinding
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.gamedetail.StartingAcceleratorViewModel
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.DISTRICT_SERVER_HAVA
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SOURCE_ENTRANCE_GAME_DETAIL
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment.Companion.SPEED_START_FAILURE
import com.gh.gamecenter.livedata.EventObserver
import com.therouter.TheRouter
import io.reactivex.disposables.CompositeDisposable
import kotlin.math.max
class StartingAcceleratorDialogFragment : BaseDialogFragment() {
private val viewModel by viewModels<StartingAcceleratorViewModel>()
private lateinit var binding: DialogFragmentStartingAcceleratorBinding
private lateinit var acctGameInfo: AcctGameInfo
private lateinit var game: GameEntity
private var isNeedRecord: Boolean = false
private var iAcceleratorProvider: IAcceleratorProvider? = null
private var hasMultiZone = false
private var sourceEntrance = SOURCE_ENTRANCE_GAME_DETAIL
private val compositeDisposable = CompositeDisposable()
private var _progress = 0
private val handler = Handler(Looper.getMainLooper())
private var refreshTokenCount = 0
private val accelerationListener = object : OnAccelerateListener {
override fun onStateChanged(state: AccelerateState) {
// 如果状态能成功回,则移除计时
handler.removeCallbacksAndMessages(null)
when (state) {
is AccelerateState.Success -> {
ToastUtils.showToast("加速成功")
// 加速成功,启动游戏
PackageLauncher.launchApp(requireContext(), game, game.getUniquePackageName())
// 记录加速记录
if (isNeedRecord) {
viewModel.useCase.recordAcctGameInfo(game.id, acctGameInfo, hasMultiZone)
}
trackNetworkAccelerationStartupResult("成功")
dismissAllowingStateLoss()
}
is AccelerateState.Failure -> {
// 有些错误需要额外处理,这里直接跳过错误提示(如token失效登录时token设置失败)
if ((state.isTokenExpired || state.isTokenEmpty) && refreshTokenCount < REFRESH_TOKEN_MAX_COUNT) {
// do nothing
} else {
if (!state.isSkipError) {
context?.let {
AcceleratorDialogFragment.show(
SPEED_START_FAILURE,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
sourceEntrance,
it
)
}
}
dismissAllowingStateLoss()
trackNetworkAccelerationStartupResult("失败(${state.code})")
}
}
is AccelerateState.Normal -> {
if (state.isTokenExpired || state.isTokenEmpty) {
// token过期/登录时token设置失败,在此获取token并重新启动加速(最多重试三次)
if (refreshTokenCount < REFRESH_TOKEN_MAX_COUNT) {
viewModel.loadAcceleratorToken()
refreshTokenCount++
}
}
}
else -> Unit
}
}
override fun onProgress(progress: Int, curGamePkgName: String?, curGameZoneFlag: String?) {
_progress = max(_progress, progress)
binding.tvProgress.text = getString(R.string.accelerating_with_progress, "$progress")
}
override fun onVipStatusChanged(isNewUser: Boolean, isVip: Boolean) = Unit
}
private fun trackNetworkAccelerationStartupResult(result: String) {
SensorsBridge.trackNetworkAccelerationStartupResult(
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
iAcceleratorProvider?.getMemberType() ?: "",
if (hasMultiZone) acctGameInfo.zoneInfo.cnName ?: "" else DISTRICT_SERVER_HAVA,
result,
sourceEntrance
)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
setCancelable(false)
setCanceledOnTouchOutside(false)
this@StartingAcceleratorDialogFragment.isCancelable = false
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java)
acctGameInfo = arguments?.getParcelable(EntranceConsts.KEY_ACCT_GAME_INFO)!!
game = arguments?.getParcelable(EntranceConsts.KEY_GAME)!!
isNeedRecord = arguments?.getBoolean(EntranceConsts.KEY_IS_NEED_RECORD) ?: false
hasMultiZone = arguments?.getBoolean(EntranceConsts.KEY_HAS_MULTI_ZONE) ?: false
sourceEntrance = arguments?.getString(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: SOURCE_ENTRANCE_GAME_DETAIL
}
override fun onBack(): Boolean {
return true
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return DialogFragmentStartingAcceleratorBinding.inflate(inflater, container, false)
.also {
binding = it
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.tvProgress.text = getString(R.string.accelerating_with_progress, "0")
iAcceleratorProvider?.bindAccRelatedListener("", accelerationListener)
val isVip = iAcceleratorProvider?.isVip() ?: false
val isNewUser = iAcceleratorProvider?.isNewUser() ?: false
if (isNewUser && !isVip) {
// 新用户并且还不是vip
viewModel.rechargeTrial()
} else {
startGameAccelerate()
}
viewModel.restartingAcceleratorAction.observe(viewLifecycleOwner, EventObserver {
startGameAccelerate()
})
viewModel.rechargeTrailResult.observe(viewLifecycleOwner, EventObserver {
if (it) {
startGameAccelerate()
} else {
// 充值失败
AcceleratorDialogFragment.show(
SPEED_START_FAILURE,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
sourceEntrance,
requireContext()
)
dismissAllowingStateLoss()
}
})
}
private fun startGameAccelerate() {
handler.removeCallbacksAndMessages(null)
handler.postDelayed({
// 15s以后不管成功还是失败都要关闭当前页面
dismissAllowingStateLoss()
}, TIME_OUT)
viewModel.useCase.setLastAcctGameRecord(game)
iAcceleratorProvider?.startQyGameAccelerate(acctGameInfo)
if (isNeedRecord) {
viewModel.useCase.insertAcctGameInfo(acctGameInfo)
}
if (refreshTokenCount == 0) {
SensorsBridge.trackNetworkAccelerationStartup(
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
iAcceleratorProvider?.getMemberType() ?: "",
if (hasMultiZone) acctGameInfo.zoneInfo.cnName ?: "" else DISTRICT_SERVER_HAVA,
sourceEntrance
)
}
}
override fun onDestroyView() {
handler.removeCallbacksAndMessages(null)
iAcceleratorProvider?.unBindAccRelatedListener(accelerationListener)
super.onDestroyView()
compositeDisposable.clear()
}
companion object {
private const val TIME_OUT = 1000 * 15L
private const val REFRESH_TOKEN_MAX_COUNT = 3
fun show(
context: Context,
acctGameInfo: AcctGameInfo,
game: GameEntity,
isNeedRecord: Boolean,
hasMultiZone: Boolean,
sourceEntrance: String
) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = StartingAcceleratorDialogFragment().apply {
arguments = Bundle().apply {
putParcelable(EntranceConsts.KEY_ACCT_GAME_INFO, acctGameInfo)
putParcelable(EntranceConsts.KEY_GAME, game)
putBoolean(EntranceConsts.KEY_IS_NEED_RECORD, isNeedRecord)
putBoolean(EntranceConsts.KEY_HAS_MULTI_ZONE, hasMultiZone)
putString(EntranceConsts.KEY_SOURCE_ENTRANCE, sourceEntrance)
}
}
fragment.show(it, fragment::class.java.simpleName)
}
}
}
}

View File

@ -61,7 +61,14 @@ class NewGameDetailEntity(
@SerializedName("new_notice")
var newNotice: ArrayList<LinkEntity>? = null,
@SerializedName("accelerator_status")
private var _acceleratorStatus: Boolean? = null
) {
val acceleratorStatus: Boolean
get() = _acceleratorStatus ?: false
fun isShowContentCard(gameEntity: GameEntity?): Boolean {
return contentCard.size > 1 && (gameEntity?.shouldUseMirrorInfo() == false || (gameEntity?.shouldUseMirrorInfo() == true && mirrorData?.contentCardStatus == "on"))
}

View File

@ -144,7 +144,7 @@ public class LibaoDetailFragment extends ToolbarFragment implements LibaoDetailA
private DetailViewHolder getDetailViewHolder() {
// 每次获取需要重新创建, 防止数据刷新
return new DetailViewHolder(mCachedView, mGameEntity, false, mEntrance, mName, mTitle, null, false); // 下载按钮ViewHolder
return new DetailViewHolder(mCachedView, mGameEntity, false, mEntrance, mName, mTitle, null, false, null); // 下载按钮ViewHolder
}
public LibaoEntity getLibaoEntity() {

View File

@ -192,7 +192,8 @@ public class NewsDetailFragment extends ToolbarFragment {
"新闻详情",
adapter.getTitle(),
mExposureEvent,
false); // 下载按钮ViewHolder
false,
null); // 下载按钮ViewHolder
}
@Override

View File

@ -28,10 +28,7 @@ import com.gh.common.util.*
import com.gh.common.util.LogUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.CollectionActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.ShareGhActivity
import com.gh.gamecenter.WebActivity
import com.gh.gamecenter.*
import com.gh.gamecenter.common.base.fragment.BaseLazyFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
@ -118,9 +115,11 @@ class HaloPersonalFragment : BaseLazyFragment() {
com.tencent.connect.common.Constants.REQUEST_LOGIN -> {
LoginHelper.onQQLoginCallback(requestCode, resultCode, data)
}
32973 -> {
LoginHelper.onWeiboLoginCallback(requireActivity(), requestCode, resultCode, data)
}
REQUEST_MESSAGE -> {
mUnreadViewModel.retry()
}
@ -137,15 +136,24 @@ class HaloPersonalFragment : BaseLazyFragment() {
SensorsBridge.trackHaloSelfClick("用户信息", "立即登录", "", "", "")
CheckLoginUtils.checkLogin(context, "我的光环-立即登录", null)
} else {
mStubBinding.ivArrow.performClick()
NewFlatLogUtils.logHaloSelfClick("用户信息", "个人主页")
SensorsBridge.trackHaloSelfClick("用户信息", "个人主页", "", "", "")
DirectUtils.directToHomeActivity(
requireContext(),
UserManager.getInstance().userId,
"",
"我的光环"
)
}
}
mStubBinding.toolbarContainer -> {
if (mUserInfoEntity == null) {
NewFlatLogUtils.logHaloSelfLogin()
CheckLoginUtils.checkLogin(context, "我的光环-立即登录", null)
}
}
mStubBinding.darkModeIv -> {
if (!ClickUtils.isFastDoubleClick(v.id, 1000)) {
NewFlatLogUtils.logHaloSelfClick("右上角", "切换模式")
@ -182,6 +190,7 @@ class HaloPersonalFragment : BaseLazyFragment() {
}
}
}
mStubBinding.personalMsg -> {
// 优先进入有数字提醒的消息tab其次是有红点提醒的游戏动态最后是没有提醒的消息tab
val defaultTabIndex = if ((mUnreadViewModel.messageUnreadCountLiveData.value?.message
@ -208,6 +217,7 @@ class HaloPersonalFragment : BaseLazyFragment() {
DirectUtils.directToMessageCenter(defaultTabIndex, "我的光环-消息")
}
mStubBinding.personalUserIcon,
mStubBinding.personalUserName -> {
if (mUserInfoEntity != null) {
@ -226,22 +236,21 @@ class HaloPersonalFragment : BaseLazyFragment() {
CheckLoginUtils.checkLogin(context, "我的光环-立即登录", null)
}
}
mStubBinding.ivArrow -> {
mStubBinding.tvMyAssets -> {
if (mUserInfoEntity != null) {
NewFlatLogUtils.logHaloSelfClick("用户信息", "个人主页")
SensorsBridge.trackHaloSelfClick("用户信息", "个人主页", "", "", "")
DirectUtils.directToHomeActivity(
requireContext(),
UserManager.getInstance().userId,
"",
"我的光环"
)
SensorsBridge.trackMyAssetsPageShow("", "", "", "我的光环")
val intent = WebActivity.getMyAssetsIntent(requireContext())
startActivity(intent)
} else {
NewFlatLogUtils.logHaloSelfClick("用户信息", "立即登录")
SensorsBridge.trackHaloSelfClick("用户信息", "立即登录", "", "", "")
CheckLoginUtils.checkLogin(context, "我的光环-个人主页", null)
}
}
mStubBinding.personalBadge -> {
NewFlatLogUtils.logHaloSelfClick("用户信息", "我的徽章")
SensorsBridge.trackHaloSelfClick("用户信息", "我的徽章", "", "", "")
@ -252,6 +261,7 @@ class HaloPersonalFragment : BaseLazyFragment() {
mUserInfoEntity?.icon
)
}
mStubBinding.myGameTv -> {
if (UserManager.getInstance().isLoggedIn) {
NewFlatLogUtils.logHaloSelfClick("常用功能", "我的游戏")
@ -268,6 +278,7 @@ class HaloPersonalFragment : BaseLazyFragment() {
CheckLoginUtils.checkLogin(requireContext(), "我的光环-我的游戏") {}
}
}
mStubBinding.myPostTv -> {
if (UserManager.getInstance().isLoggedIn) {
NewFlatLogUtils.logHaloSelfClick("常用功能", "我的发布")
@ -284,6 +295,7 @@ class HaloPersonalFragment : BaseLazyFragment() {
CheckLoginUtils.checkLogin(requireContext(), "我的光环-我的发布") { }
}
}
mStubBinding.myGameCollectionTv -> {
if (UserManager.getInstance().isLoggedIn) {
NewFlatLogUtils.logHaloSelfClick("常用功能", "我的游戏单")
@ -300,6 +312,7 @@ class HaloPersonalFragment : BaseLazyFragment() {
CheckLoginUtils.checkLogin(requireContext(), "我的光环-我的游戏单") { }
}
}
mStubBinding.historyTv -> {
NewFlatLogUtils.logHaloSelfClick("常用功能", "浏览记录")
SensorsBridge.trackHaloSelfClick(
@ -311,6 +324,7 @@ class HaloPersonalFragment : BaseLazyFragment() {
)
startActivity(HistoryActivity.getHistoryIntent(requireContext(), "我的光环-浏览记录"))
}
mStubBinding.myCollectionTv -> {
if (UserManager.getInstance().isLoggedIn) {
NewFlatLogUtils.logHaloSelfClick("常用功能", "我的收藏")
@ -606,7 +620,7 @@ class HaloPersonalFragment : BaseLazyFragment() {
null
)
accelerateSet.add(it[0].messageId)
SPUtils.setStringSet(Constants.SP_ACCELERATE_NOTIFICATION_POP_UP_SET, accelerateSet)
SPUtils.setStringSet(Constants.SP_ACCELERATE_NOTIFICATION_POP_UP_SET, accelerateSet)
}
} else {
accelerateNotificationHandler.doPreProcess(
@ -637,16 +651,30 @@ class HaloPersonalFragment : BaseLazyFragment() {
getConstraintSet(R.id.start).run {
setVisibility(R.id.personal_badge, View.VISIBLE)
clear(R.id.personal_user_name, ConstraintSet.BOTTOM)
clear(R.id.tv_my_assets, ConstraintSet.TOP)
clear(R.id.tv_my_assets, ConstraintSet.BOTTOM)
if (!mUserInfoEntity?.getShortUserId().isNullOrEmpty()) {
setMargin(R.id.personal_user_name, ConstraintSet.TOP, 14F.dip2px())
setMargin(R.id.personal_badge, ConstraintSet.BOTTOM, 14F.dip2px())
setMargin(R.id.userIdTv, ConstraintSet.TOP, 0)
connect(R.id.userIdTv, ConstraintSet.TOP, R.id.personal_user_name, ConstraintSet.BOTTOM)
connect(R.id.userIdTv, ConstraintSet.BOTTOM, R.id.personal_badge, ConstraintSet.TOP)
connect(R.id.tv_my_assets, ConstraintSet.BOTTOM, R.id.personal_badge, ConstraintSet.BOTTOM)
setVisibility(R.id.userIdTv, View.VISIBLE)
} else {
connect(R.id.personal_user_name, ConstraintSet.BOTTOM, R.id.personal_badge, ConstraintSet.TOP)
connect(R.id.personal_badge, ConstraintSet.TOP, R.id.personal_user_name, ConstraintSet.BOTTOM)
connect(
R.id.personal_user_name,
ConstraintSet.BOTTOM,
R.id.personal_badge,
ConstraintSet.TOP
)
connect(
R.id.personal_badge,
ConstraintSet.TOP,
R.id.personal_user_name,
ConstraintSet.BOTTOM
)
connect(R.id.tv_my_assets, ConstraintSet.BOTTOM, R.id.personal_badge, ConstraintSet.BOTTOM)
setVerticalChainStyle(R.id.personal_user_name, ConstraintSet.CHAIN_PACKED)
setVerticalChainStyle(R.id.personal_badge, ConstraintSet.CHAIN_PACKED)
setMargin(R.id.personal_badge, ConstraintSet.TOP, 6F.dip2px())
@ -681,6 +709,8 @@ class HaloPersonalFragment : BaseLazyFragment() {
connect(R.id.personal_user_name, ConstraintSet.BOTTOM, R.id.userIdTv, ConstraintSet.TOP)
connect(R.id.userIdTv, ConstraintSet.TOP, R.id.personal_user_name, ConstraintSet.BOTTOM)
connect(R.id.userIdTv, ConstraintSet.BOTTOM, R.id.personal_user_icon, ConstraintSet.BOTTOM)
connect(R.id.tv_my_assets, ConstraintSet.BOTTOM, R.id.personal_user_icon, ConstraintSet.BOTTOM)
connect(R.id.tv_my_assets, ConstraintSet.TOP, R.id.personal_user_icon, ConstraintSet.TOP)
setMargin(R.id.personal_user_name, ConstraintSet.TOP, 0)
setMargin(R.id.userIdTv, ConstraintSet.TOP, 3F.dip2px())
}
@ -706,7 +736,6 @@ class HaloPersonalFragment : BaseLazyFragment() {
mStubBinding.statusBar.goneIf(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
mStubBinding.darkModeIv.goneIf(!(Config.getNightModeSetting()?.icon ?: false))
mStubBinding.darkModeIv.setImageResource(if (mIsDarkModeOn) R.drawable.ic_personal_light_mode else R.drawable.ic_personal_dark_mode)
mStubBinding.ivArrow.enlargeTouchArea()
mStubBinding.loginMessageHint.typeface =
Typeface.createFromAsset(requireContext().assets, Constants.DIN_FONT_PATH)
@ -714,7 +743,7 @@ class HaloPersonalFragment : BaseLazyFragment() {
mStubBinding.toolbarContainer.setOnClickListener(this)
mStubBinding.darkModeIv.setOnClickListener(this)
mStubBinding.personalMsg.setOnClickListener(this)
mStubBinding.ivArrow.setOnClickListener(this)
mStubBinding.tvMyAssets.setOnClickListener(this)
mStubBinding.personalUserName.setOnClickListener(this)
mStubBinding.personalUserIcon.setOnClickListener(this)
mStubBinding.personalBadge.setOnClickListener(this)
@ -724,6 +753,12 @@ class HaloPersonalFragment : BaseLazyFragment() {
mStubBinding.historyTv.setOnClickListener(this)
mStubBinding.myCollectionTv.setOnClickListener(this)
mStubBinding.tvMyAssets.setOnLongClickListener {
val intent = Intent(requireContext(), BatchRegisterActivity::class.java)
startActivity(intent)
return@setOnLongClickListener true
}
val statusBarHeight =
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) 0 else DisplayUtils.getStatusBarHeight(resources)
mStubBinding.motionLayout.minimumHeight = statusBarHeight + 48F.dip2px()
@ -799,7 +834,10 @@ class HaloPersonalFragment : BaseLazyFragment() {
linkId = "",
linkText = ""
)
DirectUtils.directToHelpAndFeedback(requireContext(), bundleOf(EntranceConsts.KEY_ENTRANCE to "我的光环"))
DirectUtils.directToHelpAndFeedback(
requireContext(),
bundleOf(EntranceConsts.KEY_ENTRANCE to "我的光环")
)
}
// 港澳台APP不显示帮助与反馈
root.goneIf(EnvHelper.isGATApp)
@ -977,6 +1015,7 @@ class HaloPersonalFragment : BaseLazyFragment() {
MotionEvent.ACTION_DOWN -> {
mStubBinding.listRefresh.isEnabled = false
}
MotionEvent.ACTION_UP -> {
mStubBinding.listRefresh.isEnabled = true
}
@ -1084,7 +1123,7 @@ class HaloPersonalFragment : BaseLazyFragment() {
fun onEventMainThread(busNetworkState: EBNetworkState) {
if (busNetworkState.isNetworkConnected
&& UserManager.getInstance().isLoggedIn
&& (mUserInfoEntity == null || TextUtils.isEmpty(UserManager.getInstance().token))
&& (mUserViewModel.loginObsUserinfo.value == null)
) {
mUserViewModel.retryCheckLogin()
}
@ -1164,6 +1203,9 @@ class HaloPersonalFragment : BaseLazyFragment() {
R.drawable.ic_personal_my_post
)
)
tvMyAssets.background =
com.gh.gamecenter.common.R.drawable.bg_common_button_stroke_gray.toDrawable(requireContext())
tvMyAssets.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(requireContext()))
}
}
}

View File

@ -101,12 +101,14 @@ import com.gh.gamecenter.entity.VideoEntity;
import com.gh.gamecenter.entity.VideoTagEntity;
import com.gh.gamecenter.entity.VoteEntity;
import com.gh.gamecenter.entity.WhitePackageListEntity;
import com.gh.gamecenter.feature.entity.AliPayEntity;
import com.gh.gamecenter.feature.entity.AnswerEntity;
import com.gh.gamecenter.feature.entity.ApkEntity;
import com.gh.gamecenter.feature.entity.ArticleDraftEntity;
import com.gh.gamecenter.feature.entity.ArticleEntity;
import com.gh.gamecenter.feature.entity.AvatarBorderEntity;
import com.gh.gamecenter.feature.entity.BackgroundImageEntity;
import com.gh.gamecenter.feature.entity.BaseEntity;
import com.gh.gamecenter.feature.entity.CommentEntity;
import com.gh.gamecenter.feature.entity.CommentnumEntity;
import com.gh.gamecenter.feature.entity.ConcernEntity;
@ -128,9 +130,12 @@ import com.gh.gamecenter.feature.entity.ServerCalendarGame;
import com.gh.gamecenter.feature.entity.ServerCalendarNotifySetting;
import com.gh.gamecenter.feature.entity.SettingsEntity;
import com.gh.gamecenter.feature.entity.SimulatorEntity;
import com.gh.gamecenter.feature.entity.TrialEntity;
import com.gh.gamecenter.feature.entity.UserEntity;
import com.gh.gamecenter.feature.entity.ViewsEntity;
import com.gh.gamecenter.feature.entity.VipEntity;
import com.gh.gamecenter.feature.entity.WXSubscribeMsgConfig;
import com.gh.gamecenter.feature.entity.WechatPayEntity;
import com.gh.gamecenter.gamedetail.entity.BigEvent;
import com.gh.gamecenter.gamedetail.entity.NewGameDetailEntity;
import com.gh.gamecenter.home.custom.model.CustomPageData;
@ -146,6 +151,7 @@ import com.gh.gamecenter.qa.entity.QuestionsIndexEntity;
import com.gh.gamecenter.qa.entity.TopCommunityCategory;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.protobuf.Any;
import java.util.ArrayList;
import java.util.HashMap;
@ -3424,6 +3430,7 @@ public interface ApiService {
*/
@PATCH("app/{module}/diverter_visit_time")
Single<ResponseBody> patchDiverterVisitTime(@Path("module") String module, @Body RequestBody body);
/**
* 游戏单搜索(复用 游戏单合集-内容列表 (适用于刷新轮换) 接口实体)
*/
@ -3435,4 +3442,22 @@ public interface ApiService {
*/
@GET("app/column_test_v2/{link_id}/top_games")
Observable<List<GameServerTestTopGame>> getServerTestV2TopGames(@Path("link_id") String linkId);
@POST("pay/wechat/goods/{goodId}/user/{userId}")
Single<BaseEntity<WechatPayEntity>> preOrderWithWechat(@Path("goodId") String goodId, @Path("userId") String userId, @Query("type") String type);
@POST("pay/alipay/goods/{goodId}/user/{userId}")
Single<BaseEntity<AliPayEntity>> preOrderWithAli(@Path("goodId") String goodId, @Path("userId") String userId, @Query("type") String type);
@GET("pay/alipay/order/{orderNo}/user/{userId}")
Single<BaseEntity<Any>> getAliPayResult(@Path("orderNo") String orderNo, @Path("userId") String userId, @Query("type") String type);
@GET("pay/wechat/order/{orderNo}/user/{userId}")
Single<BaseEntity<Any>> getWechatPayResult(@Path("orderNo") String orderNo, @Path("userId") String userId, @Query("type") String type);
@POST("vip/user/{userId}/game")
Single<BaseEntity<Any>> recordAcctGameInfo(@Path("userId") String userId, @Query("type") String type, @Body RequestBody body);
@POST("vip/user/{userId}/recharge_trial")
Single<BaseEntity<TrialEntity>> rechargeTrial(@Path("userId") String userId, @Query("type") String type);
}

View File

@ -0,0 +1,33 @@
package com.gh.gamecenter.room.converter
import androidx.room.TypeConverter
import com.gh.gamecenter.common.utils.toJson
import com.gh.gamecenter.common.utils.toObject
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.AcctZoneListBean
import com.google.gson.Gson
class AcctGameInfoConverter {
@TypeConverter
fun fromZoneInfo(zoneInfo: AcctGameInfo.ZoneInfo): String {
// 使用Gson将ZoneInfo转换为JSON字符串
return zoneInfo.toJson()
}
@TypeConverter
fun toZoneInfo(value: String): AcctGameInfo.ZoneInfo {
// 将JSON字符串转回ZoneInfo对象
return value.toObject<AcctGameInfo.ZoneInfo>() ?: AcctGameInfo.ZoneInfo(0)
}
@TypeConverter
fun fromZoneList(zoneList: List<AcctGameInfo>): String {
return zoneList.toJson()
}
@TypeConverter
fun toZoneList(value: String): List<AcctGameInfo> {
return value.toObject<List<AcctGameInfo>>() ?: listOf()
}
}

View File

@ -57,6 +57,7 @@ import com.gh.gamecenter.common.utils.ImageUtils;
import com.gh.gamecenter.common.utils.SensorsBridge;
import com.gh.gamecenter.core.AppExecutor;
import com.gh.gamecenter.core.iinterface.IApplication;
import com.gh.gamecenter.core.provider.IAcceleratorProvider;
import com.gh.gamecenter.core.provider.IFlavorProvider;
import com.gh.gamecenter.core.provider.IPushProvider;
import com.gh.gamecenter.core.provider.IQGameProvider;
@ -420,6 +421,11 @@ public class HaloApp extends MultiDexApplication {
});
}
}, delay);
IAcceleratorProvider acceleratorProvider = TheRouter.get(IAcceleratorProvider.class);
if (acceleratorProvider != null) {
acceleratorProvider.init(this, PackageUtils.getGhVersionName());
}
}
public void getWebviewAbiList() {

View File

@ -15,6 +15,7 @@ import android.view.MenuItem
import android.view.View
import android.webkit.*
import androidx.annotation.RequiresApi
import androidx.fragment.app.viewModels
import com.gh.common.DefaultJsApi
import com.gh.common.DefaultUrlHandler
import com.gh.common.util.*
@ -35,7 +36,10 @@ import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.PermissionHelper.checkStoragePermissionBeforeAction
import com.gh.gamecenter.common.view.dsbridge.DWebView
import com.gh.gamecenter.core.AppExecutor.uiExecutor
import com.gh.gamecenter.core.callback.AccelerateState
import com.gh.gamecenter.core.callback.OnAccelerateListener
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.utils.ElapsedTimeCallback
import com.gh.gamecenter.core.utils.GsonUtils.fromJson
import com.gh.gamecenter.core.utils.MtaHelper.onEventWithTime
@ -44,11 +48,29 @@ import com.gh.gamecenter.databinding.FragmentWebBinding
import com.gh.gamecenter.databinding.FragmentWebWarningBinding
import com.gh.gamecenter.entity.WebShareEntity
import com.gh.gamecenter.eventbus.EBTypeChange
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.AcctRecordEntity
import com.gh.gamecenter.feature.entity.CommentnumEntity
import com.gh.gamecenter.feature.entity.OrderEntity
import com.gh.gamecenter.feature.eventbus.EBPayState
import com.gh.gamecenter.gamedetail.GameDetailFragment
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.BUTTON_NAME_STOP_ACCELERATOR
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.DISTRICT_SERVER_HAVA
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.MEMBER_TYPE_NOT_LOGIN
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SCENE_TYPE_NO_GUIDE_LAYER
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SOURCE_ENTRANCE_MY_ASSETS
import com.gh.gamecenter.gamedetail.accelerator.UserVerifyDialogUtils
import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorClient
import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorValidator
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment.Companion.SPEED_STOP
import com.gh.gamecenter.gamedetail.accelerator.dialog.StartingAcceleratorDialogFragment
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.message.MessageDetailFragment
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.member.MemberUseCase
import com.lightgame.utils.Utils
import com.therouter.TheRouter
import com.zhihu.matisse.Matisse
import com.zhihu.matisse.MimeType
import com.zhihu.matisse.engine.impl.PicassoEngine
@ -61,6 +83,8 @@ import kotlin.math.abs
class WebFragment : LazyFragment(), IScrollable {
private val viewModel by viewModels<WebViewModel>()
private var mBinding: FragmentWebBinding? = null
var mMenuShare: MenuItem? = null
var mMenuCollect: MenuItem? = null
@ -114,6 +138,19 @@ class WebFragment : LazyFragment(), IScrollable {
}
}
private val acctListener = object : OnAccelerateListener {
override fun onStateChanged(state: AccelerateState) {
if (state is AccelerateState.Success || state is AccelerateState.Normal) {
onAccelerateCallback()
}
}
override fun onProgress(progress: Int, curGamePkgName: String?, curGameZoneFlag: String?) = Unit
override fun onVipStatusChanged(isNewUser: Boolean, isVip: Boolean) = Unit
}
override fun onCreate(savedInstanceState: Bundle?) {
updateIsWebViewInstalled()
super.onCreate(savedInstanceState)
@ -311,7 +348,132 @@ class WebFragment : LazyFragment(), IScrollable {
mLeaveWebpageToHandleTitle = args.getBoolean(KEY_LEAVE_WEB_PAGE_TO_HANDLE_TITLE, false)
mWebUrl = dealWithUrl(args.getString(EntranceConsts.KEY_URL, ""))
}
mJsApi = DefaultJsApi(requireContext(), mEntrance, this, mBbsId, mWebUrl, mForumName)
mJsApi = DefaultJsApi(
requireContext(),
mEntrance,
this,
mBbsId,
mWebUrl,
mForumName,
object : DefaultJsApi.OnWebClickListener {
override fun onPreOrderWithAli(order: OrderEntity) {
viewModel.memberUseCase.preOrderWithAlipay(order)
}
override fun onPreOrderWithWechat(order: OrderEntity) {
viewModel.memberUseCase.preOrderWithWechat(order)
}
override fun onStartGameAccelerate(accInfo: AcctRecordEntity.AccInfo) {
context?.let {
val iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java)
val game = accInfo.game
val pkg = game.getUniquePackageName() ?: ""
val hasMultiZone = accInfo.isMoreServiceArea
val acctGameInfo =
AcctGameInfo(pkg, AcctGameInfo.ZoneInfo(accInfo.serviceAreaId, accInfo.serviceArea))
val memberType = if (CheckLoginUtils.isLogin()) {
iAcceleratorProvider?.getMemberType() ?: ""
} else {
MEMBER_TYPE_NOT_LOGIN
}
SensorsBridge.trackNetworkAccelerationButtonClick(
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
memberType,
if (hasMultiZone) accInfo.serviceArea else DISTRICT_SERVER_HAVA,
SCENE_TYPE_NO_GUIDE_LAYER,
SOURCE_ENTRANCE_MY_ASSETS
)
// h5 页面点击加速
val isVip = iAcceleratorProvider?.isVip() ?: false
val request = AcceleratorValidator.Request(isVip, false, game, SOURCE_ENTRANCE_MY_ASSETS)
AcceleratorClient.newInstance()
.execute(it, request, object : AcceleratorValidator.ValidateListener {
override fun finished(context: Context) {
StartingAcceleratorDialogFragment.show(
context,
acctGameInfo,
game,
false,
hasMultiZone,
SOURCE_ENTRANCE_MY_ASSETS
)
}
})
}
}
override fun onStopGameAccelerate() {
context?.let {
val acctGameRecord = viewModel.memberUseCase.lastAcctRecord
SensorsBridge.trackNetworkAccelerationOtherButtonClick(
acctGameRecord.pkgName,
acctGameRecord.gameId,
acctGameRecord.gameName,
TheRouter.get(IAcceleratorProvider::class.java)?.getMemberType() ?: "",
BUTTON_NAME_STOP_ACCELERATOR,
SOURCE_ENTRANCE_MY_ASSETS
)
AcceleratorDialogFragment.show(
SPEED_STOP,
acctGameRecord.pkgName,
acctGameRecord.gameId,
acctGameRecord.gameName,
SOURCE_ENTRANCE_MY_ASSETS,
it
)
}
}
})
with(viewModel.memberUseCase) {
requestAlipayAction.observe(viewLifecycleOwner, EventObserver { (order, payEntity) ->
viewModel.memberUseCase.sendAlipayRequest(requireActivity(), order, payEntity)
})
requestWechatPayAction.observe(viewLifecycleOwner, EventObserver { (order, payEntity) ->
viewModel.memberUseCase.sendWechatPayRequest(order, payEntity)
})
showUserVerifyDialog.observe(viewLifecycleOwner, EventObserver {
when (it) {
is MemberUseCase.UserVerifyState.UnVerified -> { // 未实名
UserVerifyDialogUtils.showUnVerifiedDialog(
requireContext(),
R.string.archive_dialog_title.toResString(),
it.toast ?: R.string.recharge_without_real_name_description.toResString()
)
}
is MemberUseCase.UserVerifyState.Minors -> { // 已实名:未成年
UserVerifyDialogUtils.showMinorsOrVerifyingDialog(
requireContext(),
R.string.archive_dialog_title.toResString(),
it.toast ?: R.string.recharge_with_minors_description.toResString()
)
}
is MemberUseCase.UserVerifyState.Verifying -> { // 实名审核中
UserVerifyDialogUtils.showMinorsOrVerifyingDialog(
requireContext(),
R.string.archive_dialog_title.toResString(),
it.toast ?: R.string.recharge_with_verifying_description.toResString()
)
}
}
})
}
TheRouter.get(IAcceleratorProvider::class.java)?.bindAccRelatedListener("", acctListener)
}
@SuppressLint("SetJavaScriptEnabled")
@ -362,6 +524,20 @@ class WebFragment : LazyFragment(), IScrollable {
// 用webview打开url
webview.webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView?,
request: WebResourceRequest
): WebResourceResponse? {
val url = request.url.toString()
// 检查是否是字体请求
if (isFontRequest(url)) {
// 获取字体文件名
return loadFontFromAssets()
}
return super.shouldInterceptRequest(view, request)
}
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
return if (isAdded) {
val originalUrl = requireArguments().getString(EntranceConsts.KEY_URL, "")
@ -599,6 +775,42 @@ class WebFragment : LazyFragment(), IScrollable {
}
}
private fun onAccelerateCallback() {
val gameId = viewModel.memberUseCase.lastAcctGameId
mBinding?.webview?.callHandler("onAccelerateCallback", arrayOf(gameId)) { _: Any -> }
}
private fun isFontRequest(url: String): Boolean {
return url.lowercase().endsWith(Constants.DIN_FONT_PATH)
}
/**
* 从assets加载字体文件
*/
private fun loadFontFromAssets(): WebResourceResponse? {
return try {
val inputStream = requireContext().assets.open(Constants.DIN_FONT_PATH)
val mineType = "font/ttf"
WebResourceResponse(
mineType,
"UTF-8",
inputStream
).apply {
// 设置响应头
setStatusCodeAndReasonPhrase(200, "OK")
setResponseHeaders(
mapOf(
"Access-Control-Allow-Origin" to "*",
"Content-Type" to mineType
)
)
}
} catch (e: Exception) {
null
}
}
override fun onDestroy() {
super.onDestroy()
if (!TextUtils.isEmpty(mGameName)) {
@ -609,6 +821,7 @@ class WebFragment : LazyFragment(), IScrollable {
removeJavascriptObject("share")
removeJavascriptObject("internal")
}
TheRouter.get(IAcceleratorProvider::class.java)?.unBindAccRelatedListener(acctListener)
}
private fun getNewsCommentNum() {
@ -681,6 +894,16 @@ class WebFragment : LazyFragment(), IScrollable {
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(data: EBPayState) {
val payStatus = when (data) {
EBPayState.PaySuccess -> "success"
EBPayState.PayFail -> "fail"
EBPayState.PayCancel -> "cancel"
}
mBinding?.webview?.callHandler("onPayCallback", arrayOf(payStatus)) { _: Any -> }
}
override fun onFragmentResume() {
super.onFragmentResume()
mBinding?.webview?.callHandler("videoPlay") { _: Any? -> }

View File

@ -0,0 +1,14 @@
package com.halo.assistant.fragment
import androidx.lifecycle.ViewModel
import com.halo.assistant.member.MemberUseCase
class WebViewModel:ViewModel() {
val memberUseCase = MemberUseCase()
override fun onCleared() {
super.onCleared()
memberUseCase.onClear()
}
}

View File

@ -0,0 +1,30 @@
package com.halo.assistant.member
import android.graphics.Color
import android.os.Bundle
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.core.utils.DisplayUtils
import com.halo.assistant.fragment.WebFragment
class MemberActivity : BaseActivity() {
override fun getLayoutId(): Int {
return com.gh.gamecenter.common.R.layout.activity_member
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DisplayUtils.setLightStatusBar(this, true)
setStatusBarColor(Color.TRANSPARENT)
val containerFragment = supportFragmentManager.findFragmentByTag(WebFragment::class.java.name)
?: WebFragment().with(intent.extras)
// 若 placeholder 外层为 RelativeLayout 的话,会出现莫名的偏移
supportFragmentManager.beginTransaction()
.replace(
com.gh.gamecenter.selector.R.id.layout_activity_content,
containerFragment,
WebFragment::class.java.name
)
.commitAllowingStateLoss()
}
}

View File

@ -0,0 +1,181 @@
package com.halo.assistant.member
import android.annotation.SuppressLint
import android.app.Activity
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.provider.IAliPayProvider
import com.gh.gamecenter.core.provider.IWechatPayProvider
import com.gh.gamecenter.feature.entity.*
import com.gh.gamecenter.feature.eventbus.EBPayState
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserRepository
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.therouter.TheRouter
import io.reactivex.Flowable
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.EventBus
import java.util.concurrent.TimeUnit
/**
* 会员相关单利类
* 会保存一些加速器状态相关数据
* 加速器相关的操作,统一放在这里
*/
class MemberRepository private constructor(private val api: ApiService) {
// 最后一次加速的游戏id失败或者成功都会记录
private var _acctGameRecord: AcctGameRecord? = null
val acctGameRecord: AcctGameRecord
get() = _acctGameRecord ?: AcctGameRecord()
fun setAcctGameRecord(record: AcctGameRecord) {
_acctGameRecord = record
}
fun preOrderWithAli(goodId: String): Single<AliPayEntity> {
val userId = UserManager.getInstance().userId
return api.preOrderWithAli(goodId, userId, VIP_TYPE)
.map {
it.data
}
}
@SuppressLint("CheckResult")
fun payWithAli(order: OrderEntity, payEntity: AliPayEntity, activity: Activity) {
TheRouter.get(IAliPayProvider::class.java)
?.sendRequest(activity, payEntity.orderStr)
?.flatMap {
if (it == 0) {
// 需要后端确认支付结果
getAlipayResult(payEntity.orderNo)
} else {
Single.just(it)
}
}
?.compose(singleToMain())
?.subscribe({
val (payState, result) = when (it) {
0 -> EBPayState.PaySuccess to RECHARGE_RESULT_SUCCESS
1 -> EBPayState.PayCancel to RECHARGE_RESULT_CANCEL
else -> EBPayState.PayFail to RECHARGE_RESULT_FAILURE
}
if (payState is EBPayState.PaySuccess) {
refreshVipStatus()
}
SensorsBridge.trackMemberRechargeResult(
PAYMENT_TYPE_ALIPAY,
order.setMenuName,
order.paymentAmount,
result
)
EventBus.getDefault().post(payState)
}, {
// 支付失败
EventBus.getDefault().post(EBPayState.PayFail)
})
}
private fun getAlipayResult(orderNo: String): Single<Int> {
val userId = UserManager.getInstance().userId
return RetrofitManager.getInstance().newApi
.getAliPayResult(orderNo, userId, VIP_TYPE)
.retryWhen { errors ->
errors.zipWith(Flowable.range(1, 3)) { error, attempt ->
if (attempt <= 3) {
attempt
} else {
throw error
}
}.flatMap {
Flowable.timer(1, TimeUnit.SECONDS)
}
}.map {
if (it.data != null) 0 else 2
}
}
fun preOrderWithWechat(goodId: String): Single<BaseEntity<WechatPayEntity>> {
val userId = UserManager.getInstance().userId
return api.preOrderWithWechat(goodId, userId, VIP_TYPE)
}
fun sendWechatPayRequest(order: OrderEntity, payEntity: WechatPayEntity) {
TheRouter.get(IWechatPayProvider::class.java)?.payRequest(order, payEntity)
}
fun getWechatPayResult(orderNo: String): Single<Boolean> {
val userId = UserManager.getInstance().userId
return RetrofitManager.getInstance().newApi
.getWechatPayResult(orderNo, userId, VIP_TYPE)
.retryWhen { errors ->
errors.zipWith(Flowable.range(1, 3)) { error, attempt ->
if (attempt <= 3) {
attempt
} else {
throw error
}
}.flatMap {
Flowable.timer(1, TimeUnit.SECONDS)
}
}.map {
it.data != null
}
}
private fun refreshVipStatus() {
// 先刷新本地状态,支付成功,肯定是付费会员
TheRouter.get(IAcceleratorProvider::class.java)?.setVipEntity(
VipEntity(
_vipStatus = true,
_isNewUser = false,
_isTryVip = false
)
)
val userId = UserManager.getInstance().userId
if (userId.isNotBlank()) {
UserRepository.getInstance().refreshVipStatus(userId, true)
}
}
@SuppressLint("CheckResult")
fun recordAcctGameInfo(gameId: String, zoneId: Int, zoneName: String, hasMultiZone: Boolean) {
val userId = UserManager.getInstance().userId
val body =
hashMapOf(
"game_id" to gameId,
"service_area_id" to zoneId,
"service_area" to zoneName,
"is_more_service_area" to hasMultiZone
).toRequestBody()
RetrofitManager.getInstance().newApi
.recordAcctGameInfo(userId, VIP_TYPE, body)
.subscribeOn(Schedulers.io())
.subscribe({}, {})
}
fun rechargeTrial(userId: String) =
api.rechargeTrial(userId, VIP_TYPE)
companion object {
private const val VIP_TYPE = "gjonline_vip"
const val RECHARGE_RESULT_SUCCESS = "充值成功"
private const val RECHARGE_RESULT_CANCEL = "充值取消"
const val RECHARGE_RESULT_FAILURE = "充值失败"
const val PAYMENT_TYPE_ALIPAY = "支付宝"
const val PAYMENT_TYPE_WECHAT = "微信"
val instance by lazy {
MemberRepository(RetrofitManager.getInstance().newApi)
}
}
data class AcctGameRecord(
val gameId: String = "",
val pkgName: String = "",
val gameName: String = ""
)
}

View File

@ -0,0 +1,147 @@
package com.halo.assistant.member
import android.annotation.SuppressLint
import android.app.Activity
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.gh.gamecenter.common.entity.ErrorEntity
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.toObject
import com.gh.gamecenter.feature.entity.*
import com.gh.gamecenter.gamedetail.accelerator.AccelerationDataBase
import com.gh.gamecenter.livedata.Event
import com.halo.assistant.member.MemberUseCase.UserVerifyState.Companion.ERROR_CODE_MINORS
import com.halo.assistant.member.MemberUseCase.UserVerifyState.Companion.ERROR_CODE_UNVERIFIED
import com.halo.assistant.member.MemberUseCase.UserVerifyState.Companion.ERROR_CODE_VERIFYING
import io.reactivex.Single
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import retrofit2.HttpException
class MemberUseCase {
private val compositeDisposable = CompositeDisposable()
private val repository = MemberRepository.instance
val lastAcctGameId: String
get() = repository.acctGameRecord.gameId
val lastAcctRecord: MemberRepository.AcctGameRecord
get() = repository.acctGameRecord
fun setLastAcctGameRecord(game: GameEntity) {
repository.setAcctGameRecord(
MemberRepository.AcctGameRecord(game.id, game.getUniquePackageName() ?: "", game.name ?: "")
)
}
private val _showUserVerifyDialog = MutableLiveData<Event<UserVerifyState>>()
val showUserVerifyDialog: LiveData<Event<UserVerifyState>> = _showUserVerifyDialog
private val _requestAlipayAction = MutableLiveData<Event<Pair<OrderEntity, AliPayEntity>>>()
val requestAlipayAction: LiveData<Event<Pair<OrderEntity, AliPayEntity>>> = _requestAlipayAction
fun preOrderWithAlipay(order: OrderEntity) {
repository.preOrderWithAli(order.goodId)
.compose(singleToMain())
.subscribe(object : BiResponse<AliPayEntity>() {
override fun onSuccess(data: AliPayEntity) {
_requestAlipayAction.value = Event(order to data)
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
handlePreOrderError(exception)
}
}).let(compositeDisposable::add)
}
fun sendAlipayRequest(activity: Activity, order: OrderEntity, payEntity: AliPayEntity) {
repository.payWithAli(order, payEntity, activity)
}
private val _requestWechatPayAction = MutableLiveData<Event<Pair<OrderEntity, WechatPayEntity>>>()
val requestWechatPayAction: LiveData<Event<Pair<OrderEntity, WechatPayEntity>>> = _requestWechatPayAction
fun preOrderWithWechat(order: OrderEntity) {
repository.preOrderWithWechat(order.goodId)
.compose(singleToMain())
.subscribe(object : BiResponse<BaseEntity<WechatPayEntity>>() {
override fun onSuccess(data: BaseEntity<WechatPayEntity>) {
val payEntity = data.data
if (payEntity != null) {
_requestWechatPayAction.value = Event(order to payEntity)
}
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
handlePreOrderError(exception)
}
}).let(compositeDisposable::add)
}
fun sendWechatPayRequest(order: OrderEntity, payEntity: WechatPayEntity) {
repository.sendWechatPayRequest(order, payEntity)
}
private fun handlePreOrderError(exception: Exception) {
if (exception is HttpException) {
val errorEntity = exception.response().errorBody()?.string()?.toObject<ErrorEntity>()
val code = errorEntity?.code ?: 0
val toast = errorEntity?.toast
when (code) {
ERROR_CODE_UNVERIFIED -> UserVerifyState.UnVerified(toast)
ERROR_CODE_MINORS -> UserVerifyState.Minors(toast)
ERROR_CODE_VERIFYING -> UserVerifyState.Verifying(toast)
else -> null
}?.let {
_showUserVerifyDialog.value = Event(it)
}
}
}
@SuppressLint("CheckResult")
fun insertAcctGameInfo(acctGameInfo: AcctGameInfo) {
AccelerationDataBase.instance.accelerationDao()
.upsertAcctGameInfo(acctGameInfo)
.subscribeOn(Schedulers.io())
.subscribe({
}, {})
}
fun recordAcctGameInfo(gameId: String, acctGameInfo: AcctGameInfo, hasMultiZone: Boolean) {
// 如果是 web 端 启动的加速,则不需要记录
repository.recordAcctGameInfo(
gameId,
acctGameInfo.zoneInfo.id,
acctGameInfo.zoneInfo.cnName ?: "",
hasMultiZone
)
}
fun rechargeTrial(userId: String): Single<BaseEntity<TrialEntity>> = repository.rechargeTrial(userId)
fun onClear() {
compositeDisposable.clear()
}
sealed class UserVerifyState(val toast: String?) {
class UnVerified(toast: String?) : UserVerifyState(toast)
class Minors(toast: String?) : UserVerifyState(toast)
class Verifying(toast: String?) : UserVerifyState(toast)
companion object {
const val ERROR_CODE_UNVERIFIED = 403129
const val ERROR_CODE_MINORS = 403128
const val ERROR_CODE_VERIFYING = 403130
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M18.705,4.64C17.097,4.269 13.525,3.991 9.853,7.563C9.646,7.765 9.361,7.867 9.073,7.843L6.446,7.624C5.806,7.571 5.179,7.828 4.761,8.315L3.593,9.678L5.447,10.606C5.786,10.775 6,11.121 6,11.5V12.586L11.414,18H12.5C12.879,18 13.225,18.214 13.394,18.553L14.322,20.407L15.685,19.239C16.172,18.821 16.429,18.194 16.376,17.554L16.157,14.927C16.133,14.639 16.235,14.354 16.437,14.147C20.009,10.475 19.731,6.903 19.36,5.295C19.283,4.96 19.039,4.717 18.705,4.64ZM8.793,5.813C12.972,1.971 17.136,2.226 19.155,2.691C20.234,2.94 21.06,3.766 21.309,4.845C21.774,6.864 22.029,11.028 18.187,15.207L18.369,17.388C18.476,18.668 17.961,19.922 16.986,20.757L15.623,21.926C14.635,22.772 13.115,22.465 12.533,21.302L11.882,20H11C10.735,20 10.48,19.895 10.293,19.707L4.293,13.707C4.105,13.52 4,13.265 4,13V12.118L2.698,11.467C1.535,10.885 1.227,9.365 2.074,8.377L3.242,7.014C4.078,6.039 5.332,5.524 6.612,5.631L8.793,5.813ZM13,10C12.448,10 12,10.448 12,11C12,11.552 12.448,12 13,12C13.552,12 14,11.552 14,11C14,10.448 13.552,10 13,10ZM10,11C10,9.343 11.343,8 13,8C14.657,8 16,9.343 16,11C16,12.657 14.657,14 13,14C11.343,14 10,12.657 10,11ZM4.949,17.316C5.123,16.792 4.84,16.226 4.316,16.051C3.792,15.877 3.226,16.16 3.051,16.684C2.765,17.542 2.632,18.506 2.567,19.222C2.533,19.587 2.517,19.902 2.508,20.127C2.504,20.24 2.502,20.33 2.501,20.394C2.501,20.426 2.5,20.451 2.5,20.469L2.5,20.49L2.5,20.497L2.5,20.499L2.5,20.499L2.5,20.5C2.5,20.5 2.5,20.5 3.5,20.5H2.5C2.5,21.052 2.948,21.5 3.5,21.5V20.5C3.5,21.5 3.5,21.5 3.5,21.5H3.501H3.501L3.503,21.5L3.51,21.5L3.531,21.5C3.549,21.5 3.574,21.499 3.606,21.499C3.67,21.498 3.76,21.496 3.873,21.492C4.098,21.483 4.413,21.467 4.778,21.433C5.494,21.368 6.458,21.235 7.316,20.949C7.84,20.774 8.123,20.208 7.949,19.684C7.774,19.16 7.208,18.877 6.684,19.051C6.042,19.265 5.256,19.382 4.597,19.442L4.555,19.445L4.558,19.403C4.618,18.744 4.735,17.958 4.949,17.316Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<clip-path
android:pathData="M0,0h24v24h-24z"/>
<path
android:pathData="M4.793,4.793C5.183,4.402 5.817,4.402 6.207,4.793L12,10.586L17.793,4.793C18.183,4.402 18.817,4.402 19.207,4.793C19.598,5.183 19.598,5.817 19.207,6.207L13.414,12L19.207,17.793C19.598,18.183 19.598,18.817 19.207,19.207C18.817,19.598 18.183,19.598 17.793,19.207L12,13.414L6.207,19.207C5.817,19.598 5.183,19.598 4.793,19.207C4.402,18.817 4.402,18.183 4.793,17.793L10.586,12L4.793,6.207C4.402,5.817 4.402,5.183 4.793,4.793Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</group>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group>
<clip-path
android:pathData="M0,0h24v24h-24z"/>
<path
android:pathData="M4.793,4.793C5.183,4.402 5.817,4.402 6.207,4.793L12,10.586L17.793,4.793C18.183,4.402 18.817,4.402 19.207,4.793C19.598,5.183 19.598,5.817 19.207,6.207L13.414,12L19.207,17.793C19.598,18.183 19.598,18.817 19.207,19.207C18.817,19.598 18.183,19.598 17.793,19.207L12,13.414L6.207,19.207C5.817,19.598 5.183,19.598 4.793,19.207C4.402,18.817 4.402,18.183 4.793,17.793L10.586,12L4.793,6.207C4.402,5.817 4.402,5.183 4.793,4.793Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</group>
</vector>

View File

@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="52dp"
android:height="18dp"
android:viewportWidth="52"
android:viewportHeight="18">
<path
android:pathData="M0,9C0,4.029 4.029,0 9,0H43C47.971,0 52,4.029 52,9V16C52,17.105 51.105,18 50,18H9C4.029,18 0,13.971 0,9Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="0"
android:startY="9"
android:endX="52"
android:endY="9"
android:type="linear">
<item android:offset="0" android:color="#FFFFC247"/>
<item android:offset="1" android:color="#FFFF9933"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M13.79,7.92H11.54C11.49,8.45 11.43,8.98 11.33,9.47H13.79V7.92ZM8.43,9.47H10.35C10.46,8.98 10.51,8.46 10.56,7.92H8.43V9.47ZM11.62,6.09H9.34C9.08,6.43 8.79,6.77 8.48,7.09H10.87C11.13,6.8 11.4,6.43 11.62,6.09ZM12.89,12.77H14.22C14.6,12.77 14.67,12.58 14.71,11.43C14.92,11.58 15.31,11.73 15.56,11.79C15.45,13.22 15.2,13.6 14.29,13.6H12.76C11.7,13.6 11.43,13.33 11.43,12.42V10.3H11.11C10.61,11.82 9.54,13.02 7.04,13.71C6.94,13.47 6.71,13.12 6.51,12.93C8.67,12.39 9.63,11.46 10.1,10.3H7.5V7.99C7.35,8.11 7.2,8.23 7.04,8.34C6.89,8.13 6.59,7.8 6.37,7.66C7.72,6.77 8.68,5.56 9.21,4.58L10.23,4.77C10.13,4.96 10.02,5.13 9.89,5.32H12.15L12.29,5.28L12.94,5.69C12.69,6.13 12.33,6.65 11.97,7.09H14.77V10.3H12.4V12.41C12.4,12.72 12.47,12.77 12.89,12.77ZM19.48,7.08H18.19C18.16,7.29 18.13,7.48 18.09,7.68H19.34C19.42,7.49 19.46,7.29 19.48,7.08ZM20.34,5.84V6.43H21.72V5.84H20.34ZM23.87,5.84H22.6V6.43H23.87V5.84ZM21.72,7.68V7.08H20.33C20.32,7.29 20.29,7.48 20.25,7.68H21.72ZM17.84,12.32V9.8C17.59,9.88 17.33,9.95 17.05,10.01C16.97,9.81 16.74,9.46 16.58,9.31C17.77,9.05 18.49,8.72 18.91,8.32H17.14C17.25,7.81 17.37,7.06 17.44,6.44H17.79V6.43H19.5V5.84H17.11V5.2H19.5V4.61H20.34V5.2H21.72V4.61H22.6V5.2H24.72V7.08H22.6V7.68H25.33C25.33,7.68 25.32,7.86 25.31,7.97C25.25,8.63 25.18,8.97 25.02,9.14C24.89,9.29 24.71,9.35 24.5,9.36C24.32,9.37 23.98,9.37 23.6,9.35C23.59,9.17 23.52,8.91 23.44,8.74C23.72,8.78 23.96,8.78 24.06,8.78C24.17,8.78 24.23,8.77 24.29,8.71C24.33,8.65 24.37,8.55 24.4,8.32H22.6V9.37H21.72V8.32H20.01C19.75,8.81 19.28,9.24 18.47,9.58H24.22V12.23H23.28V10.35H18.74V12.32H17.84ZM20.66,10.72H21.59C21.21,12.46 20.22,13.36 16.83,13.78C16.77,13.55 16.59,13.18 16.43,12.99C19.55,12.68 20.34,12.04 20.66,10.72ZM21.19,12.48L21.68,11.85C22.9,12.14 24.59,12.67 25.49,13.07L24.97,13.79C24.13,13.39 22.44,12.81 21.19,12.48ZM26.94,10.46V9.55H35.13V10.46H30.85C30.31,11.17 29.68,11.86 29.03,12.45L33.1,12.17C32.77,11.79 32.41,11.42 32.07,11.11L32.86,10.7C33.74,11.47 34.66,12.53 35.11,13.27L34.27,13.78C34.14,13.55 33.97,13.27 33.75,12.99C28.65,13.39 28.05,13.42 27.62,13.59C27.56,13.38 27.42,12.9 27.3,12.64C27.53,12.59 27.76,12.41 28.06,12.13C28.32,11.91 28.97,11.24 29.58,10.46H26.94ZM29.04,7.64H33.11C32.31,7.13 31.57,6.54 31.02,6C30.45,6.61 29.78,7.15 29.04,7.64ZM31.01,4.59L31.87,4.98C31.78,5.12 31.67,5.26 31.56,5.4C32.5,6.28 34.09,7.28 35.63,7.8C35.43,7.98 35.12,8.36 34.98,8.61C34.43,8.39 33.88,8.11 33.36,7.8V8.5H28.68V7.87C28.17,8.18 27.64,8.46 27.08,8.72C26.95,8.48 26.63,8.08 26.42,7.9C28.33,7.13 30.09,5.9 31.01,4.59ZM38.85,5.84V6.8H43.17V5.84H38.85ZM37.88,5.04H44.2V7.6H37.88V5.04ZM40.44,9.8H41.45V10.71C41.45,11.76 40.93,13.03 37.28,13.86C37.16,13.65 36.88,13.29 36.65,13.09C40.16,12.43 40.44,11.43 40.44,10.69V9.8ZM41.31,12.41L41.77,11.72C42.9,12.07 44.53,12.65 45.39,13.05L44.91,13.85C44.1,13.43 42.49,12.81 41.31,12.41ZM37.5,8.39H44.61V11.93H43.61V9.25H38.47V12.02H37.5V8.39Z"
android:fillColor="#ffffff"/>
</vector>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="8dp"
android:viewportWidth="8"
android:viewportHeight="8">
<path
android:pathData="M6.4395,2C7.0684,2 7.418,2.7274 7.0252,3.2185L4.5856,6.2679C4.2854,6.6432 3.7146,6.6432 3.4144,6.2679L0.9748,3.2185C0.582,2.7274 0.9316,2 1.5605,2H6.4395Z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Button
android:id="@+id/btn_register"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="批量注册"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -279,6 +279,7 @@
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/detail_progressbar"
app:layout_constraintHorizontal_weight="76"
app:layout_constraintStart_toEndOf="@+id/iv_switch"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
@ -313,7 +314,8 @@
app:download_button_show_progress="true"
app:download_button_text_size="@dimen/primary_text_size"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@id/cl_speed_container"
app:layout_constraintHorizontal_weight="76"
app:layout_constraintStart_toEndOf="@+id/localDownloadContainer"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginStart="24dp" />
@ -387,6 +389,167 @@
app:layout_constraintStart_toStartOf="@id/detail_progressbar"
app:layout_constraintTop_toTopOf="@id/detail_progressbar"
tools:text="选择下载你的版本" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_speed_container"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginTop="10dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="144"
app:layout_constraintStart_toEndOf="@id/detail_progressbar"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_speed"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/bg_common_button_fill_gradient_blue"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone">
<View
android:id="@+id/v_more_zone"
android:layout_width="36dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_more_zone"
android:layout_width="8dp"
android:layout_height="8dp"
app:layout_constraintBottom_toBottomOf="@id/v_more_zone"
app:layout_constraintEnd_toEndOf="@id/v_more_zone"
app:layout_constraintStart_toStartOf="@id/v_more_zone"
app:layout_constraintTop_toTopOf="@id/v_more_zone"
app:srcCompat="@drawable/ic_triangle_down"
app:tint="@color/text_aw_primary" />
<View
android:id="@+id/v_divider"
android:layout_width="1dp"
android:layout_height="12dp"
android:background="@color/white_alpha_20"
app:layout_constraintBottom_toBottomOf="@id/v_more_zone"
app:layout_constraintEnd_toStartOf="@id/v_more_zone"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Group
android:id="@+id/g_more_zone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="v_divider,iv_more_zone,v_more_zone" />
<View
android:id="@+id/v_guide_end"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="4dp"
app:layout_constraintStart_toEndOf="@id/v_divider"
app:layout_constraintTop_toTopOf="@id/v_divider" />
<View
android:id="@+id/v_speed_content"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/v_divider"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:gravity="center"
android:orientation="horizontal"
android:paddingHorizontal="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/v_guide_end"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/iv_speed"
android:layout_width="14dp"
android:layout_height="14dp"
app:srcCompat="@drawable/ic_basic_accelerator"
app:tint="@color/text_aw_primary" />
<TextView
android:id="@+id/tv_speed"
style="@style/TextBody2B"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:text="@string/network_acceleration"
android:textColor="@color/text_aw_primary" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/tv_stop_speed"
style="@style/TextBody2B"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="24dp"
android:background="@drawable/bg_common_button_light_fill_gray"
android:gravity="center"
android:maxLines="1"
android:text="@string/stop_speed"
android:textColor="@color/text_secondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/tv_enter_game"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_enter_game"
style="@style/TextBody2B"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:background="@drawable/bg_common_button_light_fill_blue"
android:gravity="center"
android:maxLines="1"
android:text="@string/enter_game"
android:textColor="@color/text_theme"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@id/tv_stop_speed"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Group
android:id="@+id/g_accelerating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="tv_stop_speed,tv_enter_game" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:id="@+id/iv_free_vip_tag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/cl_speed_container"
app:layout_constraintEnd_toEndOf="@id/cl_speed_container"
app:layout_constraintTop_toTopOf="@id/cl_speed_container"
app:srcCompat="@drawable/ic_free_vip_tag" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,34 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:orientation="vertical">
<com.gh.gamecenter.common.view.titlebar.BottomSheetTitleView
android:id="@+id/title_view"
android:layout_width="match_parent"
android:layout_height="48dp"
app:left_style="title"
app:right_icon_color="@color/text_tertiary"
app:right_icon_resource="@drawable/ic_basic_x_18"
app:right_style="icon"
app:title="@string/select_the_acceleration_zone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_zone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/ui_surface"
android:paddingHorizontal="16dp"
android:paddingBottom="16dp"
android:clipToPadding="false"
tools:itemCount="3"
tools:listitem="@layout/recycler_accelerator_zone" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/background_shape_white_radius_8"
android:padding="24dp">
<TextView
android:id="@+id/tv_title"
style="@style/TextHeadline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hint"
android:textColor="@color/text_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_content"
style="@style/TextBody2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:textColor="@color/text_secondary"
android:text="@string/speed_failure_tips"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_title" />
<TextView
android:id="@+id/tv_submit"
style="@style/BtnRegularStyle"
android:layout_width="0dp"
android:layout_marginTop="24dp"
android:textColor="@color/primary_theme"
android:background="@drawable/bg_common_button_light_fill_blue"
android:text="@string/contact_service"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_content" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/background_shape_white_radius_8"
android:padding="24dp">
<TextView
android:id="@+id/tv_title"
style="@style/TextHeadline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hint"
android:textColor="@color/text_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_content"
style="@style/TextBody2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:textColor="@color/text_secondary"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_title" />
<TextView
android:id="@+id/tv_cancel"
style="@style/BtnRegularStyle"
android:layout_width="0dp"
android:layout_marginTop="24dp"
android:background="@drawable/bg_common_button_light_fill_gray"
android:text="@string/not_started_yet"
android:textColor="@color/text_secondary"
android:textSize="14sp"
app:layout_constraintEnd_toStartOf="@id/tv_submit"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_content" />
<TextView
android:id="@+id/tv_submit"
style="@style/BtnRegularStyle"
android:layout_width="0dp"
android:layout_marginStart="12dp"
android:layout_marginTop="24dp"
android:background="@drawable/bg_common_button_fill_blue"
android:text="@string/continue_to_start"
app:layout_goneMarginStart="0dp"
android:textColor="@color/text_aw_primary"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tv_cancel"
app:layout_constraintTop_toBottomOf="@id/tv_content" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/background_shape_white_radius_8"
android:padding="24dp">
<TextView
android:id="@+id/tv_title"
style="@style/TextHeadline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hint"
android:textColor="@color/text_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_content"
style="@style/TextBody2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:textColor="@color/text_secondary"
android:text="@string/enable_vip_tips"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_title" />
<TextView
android:id="@+id/tv_submit"
style="@style/BtnRegularStyle"
android:layout_width="0dp"
android:layout_marginTop="24dp"
android:textColor="@color/text_aw_primary"
android:background="@drawable/bg_common_button_fill_blue"
android:text="@string/go_to_activate"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_content" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="160dp"
android:layout_height="88dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="160dp"
android:layout_height="88dp"
android:background="@drawable/background_shape_white_radius_8">
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:progressDrawable="@drawable/bg_horizontal_progressbar"/>
<TextView
android:id="@+id/tv_progress"
style="@style/TextBody2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:textColor="@color/text_secondary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/progress_bar"
tools:text="加速中...16%" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -132,11 +132,18 @@
tools:visibility="visible" />
</RelativeLayout>
<ImageView
android:id="@+id/iv_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_jump" />
<TextView
android:id="@+id/tv_my_assets"
style="@style/TextButton3"
android:layout_width="72dp"
android:layout_height="28dp"
android:layout_marginEnd="20dp"
android:background="@drawable/bg_common_button_stroke_gray"
android:gravity="center"
android:text="我的资产"
android:textColor="@color/text_secondary"
app:layout_constraintEnd_toEndOf="parent"
tools:layout_editor_absoluteY="133dp" />
</com.gh.common.view.CollapsingMotionLayout>
</com.google.android.material.appbar.AppBarLayout>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="match_parent"
android:gravity="bottom">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_background"
android:layout_width="188dp"
android:layout_height="119dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/bg_accelerator_guide" />
<View
android:id="@+id/v_i_know"
android:layout_width="80dp"
android:layout_height="32dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="18dp"
app:layout_constraintBottom_toBottomOf="@id/iv_background"
app:layout_constraintEnd_toEndOf="@id/iv_background" />
<View
android:id="@+id/v_speed"
android:layout_width="144dp"
android:layout_height="40dp"
android:layout_marginTop="14dp"
android:layout_marginEnd="4dp"
app:layout_constraintEnd_toEndOf="@id/iv_background"
app:layout_constraintTop_toBottomOf="@id/iv_background" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tv_name"
style="@style/TextCaption1"
android:layout_width="match_parent"
android:layout_height="32dp"
android:background="@drawable/bg_shape_f8_radius_8"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:paddingHorizontal="10dp"
android:textColor="@color/text_secondary"
tools:text="选择区服" />

View File

@ -650,5 +650,30 @@
<string name="my_game">我的遊戲</string>
<string name="played">玩過</string>
<string name="reserve">預約</string>
<string name="hint">提示</string>
<string name="speed_without_login_tips">請您完成登入後,在使用加速啟動遊戲</string>
<string name="go_to_login">去登入</string>
<string name="enable_vip_tips">開通會員使用加速功能!</string>
<string name="go_to_activate">去開通</string>
<string name="speed_failure_tips">加速失敗,請聯絡客服</string>
<string name="contact_service">聯絡客服</string>
<string name="speed_replace_game_tips">偵測到您目前正在加速遊戲,繼續啟動將停止原遊戲的加速,並加速啟動【%1$s】是否繼續</string>
<string name="not_started_yet">暫不啟動</string>
<string name="continue_to_start">繼續啟動</string>
<string name="stop_speed_tips">停止加速可能會導致遊戲斷線,是否繼續?</string>
<string name="stop_speed">停止加速</string>
<string name="cancel">取消</string>
<string name="select_the_acceleration_zone">選擇加速區服</string>
<string name="network_acceleration">網路加速</string>
<string name="accelerating_with_progress">加速中...%1$s%%</string>
<string name="enter_game">進入遊戲</string>
<string name="speed_not_installed_tips">偵測到您尚未安裝對應遊戲,請先安裝遊戲後再啟動加速器!</string>
<string name="customer_service_qq_number_has_been_copied">已複製客服QQ號</string>
<string name="real_name_tips">實名提示</string>
<string name="acceleration_service_without_real_name_description">根據相關政策要求,該遊戲需通過認證後才能加速</string>
<string name="go_to_real_name_authentication">前往實名認證</string>
<string name="recharge_without_real_name_description">響應國家號召,本平台僅對成年人提供儲值服務,請先完成實名認證</string>
<string name="recharge_with_minors_description">響應國家號召,保護未成年人健康參與遊戲,本平台暫不對未成年人提供儲值服務</string>
<string name="recharge_with_verifying_description">您實名認證處於【認證中】,暫不能進行充值,請等待完成認證</string>
</resources>

View File

@ -650,4 +650,29 @@
<string name="my_game">我的游戏</string>
<string name="played">玩过</string>
<string name="reserve">预约</string>
<string name="hint">提示</string>
<string name="speed_without_login_tips">请您完成登录后,在使用加速启动游戏</string>
<string name="go_to_login">去登录</string>
<string name="enable_vip_tips">开通会员使用加速功能!</string>
<string name="go_to_activate">去开通</string>
<string name="speed_failure_tips">加速失败,请联系客服</string>
<string name="contact_service">联系客服</string>
<string name="speed_replace_game_tips">检测到您当前正在加速游戏,继续启动将停止原游戏的加速,并加速启动【%1$s】是否继续</string>
<string name="not_started_yet">暂不启动</string>
<string name="continue_to_start">继续启动</string>
<string name="stop_speed_tips">停止加速可能会导致游戏断线,是否继续?</string>
<string name="stop_speed">停止加速</string>
<string name="cancel">取消</string>
<string name="select_the_acceleration_zone">选择加速区服</string>
<string name="network_acceleration">网络加速</string>
<string name="accelerating_with_progress">加速中...%1$s%%</string>
<string name="enter_game">进入游戏</string>
<string name="speed_not_installed_tips">检测到您尚未安装对应游戏,请先安装游戏后再启动加速器!</string>
<string name="customer_service_qq_number_has_been_copied">已复制客服QQ号</string>
<string name="real_name_tips">实名提示</string>
<string name="acceleration_service_without_real_name_description">根据相关政策要求,该游戏需通过认证后才能进行加速</string>
<string name="go_to_real_name_authentication">前往实名认证</string>
<string name="recharge_without_real_name_description">响应国家号召,本平台仅对成年人提供充值服务,请先完成实名认证</string>
<string name="recharge_with_minors_description">响应国家号召,保护未成年人健康参与游戏,本平台暂不对未成年人提供充值服务</string>
<string name="recharge_with_verifying_description">您实名认证处于【认证中】,暂不能进行充值,请等待完成认证</string>
</resources>

View File

@ -20,6 +20,10 @@
android:alpha="0"
motion:framePosition="20"
motion:motionTarget="@+id/iv_arrow" />
<KeyAttribute
android:alpha="0"
motion:framePosition="20"
motion:motionTarget="@+id/tv_my_assets" />
</KeyFrameSet>
</Transition>
@ -97,6 +101,14 @@
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent" />
<Constraint
android:id="@+id/tv_my_assets"
android:layout_width="72dp"
android:layout_height="28dp"
android:layout_marginEnd="20dp"
android:alpha="1"
motion:layout_constraintBottom_toBottomOf="@id/personal_user_icon"
motion:layout_constraintEnd_toEndOf="parent" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
@ -188,5 +200,17 @@
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent" />
<Constraint
android:id="@+id/tv_my_assets"
android:layout_width="72dp"
android:layout_height="28dp"
android:layout_marginEnd="20dp"
android:alpha="0"
android:scaleX="0.875"
android:scaleY="0.875"
android:translationY="57dp"
motion:layout_constraintBottom_toBottomOf="@id/personal_user_icon"
motion:layout_constraintEnd_toEndOf="parent" />
</ConstraintSet>
</MotionScene>

View File

@ -149,6 +149,8 @@ ext {
volcTlsVersion = "1.1.4"
xcrashVersion = "3.1.0"
aliPayVersion = "15.8.17"
acceleratorVersion = "1.0.1"
}
apply from: 'dependencies_vasdk.gradle'

View File

@ -0,0 +1,48 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'kotlin-parcelize'
id 'com.google.devtools.ksp'
}
android {
namespace 'com.gh.gamecenter.accelerator'
compileSdk 34
defaultConfig {
minSdk 22
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
repositories {
flatDir {
dirs 'libs'
}
}
}
buildTypes {
release {
minifyEnabled false
consumerProguardFiles 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
implementation(project(path: ":module_common")) {
exclude group: 'androidx.swiperefreshlayout'
}
implementation(project(path: ":module_core_feature"))
ksp "cn.therouter:apt:${routerVersion}"
implementation "com.lg:accelerator:${acceleratorVersion}"
}

21
feature/accelerator/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,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.gh.gamecenter.accelerator">
<application>
</application>
</manifest>

View File

@ -0,0 +1,242 @@
package com.gh.gamecenter.accelerator.provider
import android.app.Application
import com.gh.gamecenter.core.callback.AccelerateState
import com.gh.gamecenter.core.callback.OnAccelerateListener
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.VipEntity
import com.gh.gamecenter.feature.utils.SentryHelper
import com.lightgame.utils.Utils
import com.qeeyou.qyvpn.QyAccelerator
import com.qeeyou.qyvpn.bean.AccNotifyConfigBean
import com.qeeyou.qyvpn.bean.QyAcctGameInfo
import com.qeeyou.qyvpn.bean.QyGameInfoBean
import com.qeeyou.qyvpn.strategy.QyUnifiedProcessStrategy
import com.qeeyou.qyvpn.utils.OnQyAccelerateListener
import com.qeeyou.qyvpn.utils.QyAccConfig
/**
* 单例模式,可以用户保存一些加速器全局状态信息
*/
@com.therouter.inject.ServiceProvider
class AcceleratorProviderImpl : IAcceleratorProvider {
private var vipEntity: VipEntity? = null
private var _token = ""
// 根据包名回调
private val listeners = hashMapOf<String, OnAccelerateListener>()
// 所有包名都回调(pkgName.isBlank())
private val allListener = mutableSetOf<OnAccelerateListener>()
private var curAcctGameInfo: AcctGameInfo? = null
private val qyListener = object : OnQyAccelerateListener {
override fun onAccCurrentProgress(progress: Int, curGamePkgName: String?, curGameZoneFlag: String?) {
listeners[curGamePkgName]?.onProgress(progress, curGamePkgName, curGameZoneFlag)
allListener.forEach {
it.onProgress(progress, curGamePkgName, curGameZoneFlag)
}
}
override fun onAccCurrentStatus(
status: QyAccelerator.QyStatus,
curGamePkgName: String?,
curGameZoneFlag: String?,
eventCode: Int,
eventMsg: String?,
extraParam: Any?
) {
Utils.log(LOG_TAG, "onAccCurrentStatus:$status -- code:$eventCode -- msg:$eventMsg")
val state = when (status) {
QyAccelerator.QyStatus.AccNormal -> AccelerateState.Normal(eventCode)
QyAccelerator.QyStatus.AccStarting -> AccelerateState.Starting(eventCode)
QyAccelerator.QyStatus.AccSuccess -> AccelerateState.Success(curAcctGameInfo, eventCode)
QyAccelerator.QyStatus.AccFailure -> {
SentryHelper.onEvent(SENTRY_EVENT_ID, KEY_ACC_FAILURE_ERROR, "$eventCode($eventMsg)")
AccelerateState.Failure(eventCode)
}
QyAccelerator.QyStatus.AccOkStopping -> AccelerateState.OkStopping(eventCode)
QyAccelerator.QyStatus.AccErrStopping -> AccelerateState.ErrStopping(eventCode)
}
listeners[curGamePkgName]?.onStateChanged(state)
allListener.forEach {
it.onStateChanged(state)
}
}
override fun onAccEventCallBack(
eventCode: Int,
eventMsg: String?,
curGamePkgName: String?,
curGameZoneFlag: String?
) = Unit
override fun onAccExtraInfoEvent(
eventFlag: QyAccelerator.QyExtra,
extraInfo: Any?,
curGamePkgName: String?,
curGameZoneFlag: String?
) = ""
}
override fun init(application: Application, version: String) {
Utils.log(LOG_TAG, "init:$version")
val qyAccConfigBuilder = QyAccConfig.Builder().setAppId("QyAccSdk")
.setDebug(false)
.setAppVersion(version)
.setServerAddressEnv(QyAccConfig.ServerAddressEnv.Release)
.build()
QyAccelerator.getInstance().init(application, qyAccConfigBuilder, QyAccelerator.QyAccStrategy.HaloRing)
QyAccelerator.getInstance().bindQyAccRelatedListener(qyListener)
}
override fun setQyUserToken(token: String, callback: ((Boolean) -> Unit)?) {
Utils.log(LOG_TAG, "setQyUserToken")
// if (_token == token) {
// // 避免外部多次设置相同的token
// return
// }
println("kayn -->setQyUserToken:$token")
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.onEvent(SENTRY_EVENT_ID, KEY_SET_TOKEN_ERROR_MESSAGE, errMsg)
}
callback?.invoke(isSuccess)
})
}
override fun setVipEntity(vip: Any) {
Utils.log(LOG_TAG, "setVipEntity:$vip")
if (vip is VipEntity) {
if (vipEntity != vip) {
vipEntity = vip
listeners.values.forEach {
it.onVipStatusChanged(vip.isNewUser, vip.vipStatus)
}
allListener.forEach {
it.onVipStatusChanged(vip.isNewUser, vip.vipStatus)
}
}
}
}
override fun isVip(): Boolean = vipEntity?.vipStatus ?: false
override fun isNewUser(): Boolean = vipEntity?.isNewUser ?: false
override fun isPaidUser(): Boolean = isVip() && !(vipEntity?.isTryVip ?: false)
override fun getMemberType(): String = when {
isPaidUser() -> MEMBER_TYPE_PAID_MEMBER
isVip() -> MEMBER_TYPE_FREE_MEMBER
else -> MEMBER_TYPE_NONE_MEMBER
}
override fun deleteQyUserToken(): Boolean {
Utils.log(LOG_TAG, "deleteQyUserToken")
if (isCurAccSuccess()) {
stopQyGameAccelerate("退出登录!")
}
// 退出登录的时候才会调用
val isDeleted = QyAccelerator.getInstance().delQyUserToken()
_token = ""
listeners.clear()
allListener.clear()
vipEntity = null
return isDeleted
}
override fun startQyGameAccelerate(accGameInfo: Any) {
Utils.log(LOG_TAG, "startQyGameAccelerate:$accGameInfo")
if (accGameInfo is AcctGameInfo) {
val qyAcctGameInfo = QyAcctGameInfo(
accGameInfo.accGamePkgName,
accGameInfo.zoneInfo.id.toString(),
accNotifyConfig = AccNotifyConfigBean(
notifyGameName = accGameInfo.notifyGameName,
notifyGameTxtTitle = accGameInfo.notifyGameTxtTitle,
notifyGameSmallLogo = accGameInfo.notifyGameSmallLogo,
notifyGameLargeLogo = accGameInfo.notifyGameLargeLogo,
notifyClickParam = accGameInfo.notifyClickParam
)
)
curAcctGameInfo = accGameInfo
QyAccelerator.getInstance().startQyGameAccelerate(qyAcctGameInfo)
}
}
override fun stopQyGameAccelerate(appLayerCallStopMsg: String?): Boolean {
Utils.log(LOG_TAG, "stopQyGameAccelerate:$appLayerCallStopMsg")
return QyAccelerator.getInstance().stopQyGameAccelerate(appLayerCallStopMsg)
}
override fun bindAccRelatedListener(pkgName: String, listener: OnAccelerateListener) {
if (pkgName.isBlank()) {
allListener.add(listener)
} else {
listeners[pkgName] = listener
}
}
override fun unBindAccRelatedListener(listener: OnAccelerateListener) {
listeners.values.remove(listener)
allListener.remove(listener)
}
override fun loadQyGameZoneData(packageName: String, callback: (List<Any>) -> Unit) {
Utils.log(LOG_TAG, "loadQyGameZoneData:$packageName")
QyAccelerator.getInstance()
.loadQyGameZoneData(
packageName,
object : QyUnifiedProcessStrategy.OnLoadQyGameZoneDataCallBack {
override fun loadQyGameZoneData(
isSuccess: Boolean,
errCode: Int?,
errMsg: String?,
gameZoneMap: MutableMap<String, List<QyGameInfoBean.Game.ZoneInfo?>?>?
) {
if (!gameZoneMap.isNullOrEmpty()) {
val zoneList = arrayListOf<AcctGameInfo>()
gameZoneMap.iterator().forEach {
it.value?.forEach { value ->
zoneList.add(
AcctGameInfo(
it.key,
AcctGameInfo.ZoneInfo(value?.id ?: 0, value?.cn_name, value?.en_name)
)
)
}
}
callback(zoneList)
} else {
Utils.log(LOG_TAG, "区服数据为空")
callback(listOf())
}
}
})
}
override fun isCurAccSuccess(): Boolean {
return QyAccelerator.getInstance().isCurAccSuccess()
}
companion object {
private const val LOG_TAG = "AcceleratorProviderImpl"
private const val MEMBER_TYPE_PAID_MEMBER = "付费会员"
private const val MEMBER_TYPE_FREE_MEMBER = "免费会员"
private const val MEMBER_TYPE_NONE_MEMBER = "非会员"
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"
}
}

View File

@ -0,0 +1,7 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.Assistantandroid2" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your dark theme here. -->
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
</style>
</resources>

View File

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

View File

@ -0,0 +1,42 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'kotlin-parcelize'
id 'com.google.devtools.ksp'
}
android {
namespace 'com.game.gamecenter.alipay'
compileSdk 34
defaultConfig {
minSdk 22
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "com.alipay.sdk:alipaysdk-android:$aliPayVersion"
implementation(project(path: ":module_common")) {
exclude group: 'androidx.swiperefreshlayout'
}
implementation(project(path: ":module_core_feature"))
ksp "cn.therouter:apt:${routerVersion}"
}

21
feature/ali_pay/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,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<application android:networkSecurityConfig="@xml/network_security_config">
</application>
</manifest>

View File

@ -0,0 +1,44 @@
package com.game.gamecenter.alipay
import android.app.Activity
import com.alipay.sdk.app.PayTask
import com.gh.gamecenter.core.provider.IAliPayProvider
import com.gh.gamecenter.feature.eventbus.EBPayState
import io.reactivex.Single
@com.therouter.inject.ServiceProvider
class AliPayProviderImpl : IAliPayProvider {
override fun sendRequest(activity: Activity, orderNo: String): Single<Int> {
return Single.create {
val pay = PayTask(activity)
val result = pay.payV2(orderNo, true)
val statusCode = result[KEY_RESULT_STATUS]
val resultCode = when (statusCode) {
PAY_RESULT_CODE_SUCCESS, PAY_RESULT_CODE_HANDLING, PAY_RESULT_CODE_UNKNOWN -> 0
PAY_RESULT_CODE_CANCEL -> 1
else -> 2
}
it.onSuccess(resultCode)
}
}
companion object {
// 订单支付成功。
private const val PAY_RESULT_CODE_SUCCESS = "9000"
// 正在处理中,支付结果未知(有可能已经支付成功),请查询商家订单列表中订单的支付状态
private const val PAY_RESULT_CODE_HANDLING = "8000"
// 支付结果未知(有可能已经支付成功),请查询商家订单列表中订单的支付状态
private const val PAY_RESULT_CODE_UNKNOWN = "6004"
private const val PAY_RESULT_CODE_CANCEL = "6001"
private const val KEY_RESULT_STATUS = "resultStatus"
private const val DELAY_DURATION = 1000L
}
}

View File

@ -0,0 +1,78 @@
package com.game.gamecenter.alipay
import android.app.Activity
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import android.webkit.CookieManager
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.LinearLayout
import com.alipay.sdk.app.PayTask
class H5PayActivity : Activity() {
private var _webView: WebView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val layout = LinearLayout(applicationContext)
val params =
LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)
layout.orientation = LinearLayout.VERTICAL
setContentView(layout)
_webView = WebView(applicationContext)
params.weight = 1F
_webView?.let {
it.visibility = View.VISIBLE
layout.addView(it, params)
initWebView(it)
}
}
private fun initWebView(webView: WebView) {
with(webView.settings) {
javaScriptEnabled = true
javaScriptCanOpenWindowsAutomatically = true
CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true)
domStorageEnabled = true
}
with(webView) {
webViewClient = MyWebViewClient()
loadUrl("url")
}
}
private inner class MyWebViewClient : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
if (!(url.startsWith("http") || url.startsWith("https"))) {
return true
}
/**
* 推荐采用的新的二合一接口(payInterceptorWithUrl),只需调用一次
*/
val task = PayTask(this@H5PayActivity)
val isIntercepted = task.payInterceptorWithUrl(url, true) { result ->
val url = result.getReturnUrl()
if (!TextUtils.isEmpty(url)) {
this@H5PayActivity.runOnUiThread {
view.loadUrl(url)
}
}
}
/**
* 判断是否成功拦截
* 若成功拦截则无需继续加载该URL否则继续加载
*/
if (!isIntercepted) {
view.loadUrl(url)
}
return true
}
}
}

View File

@ -0,0 +1,7 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.Assistantandroid2" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your dark theme here. -->
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
</style>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

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

View File

@ -0,0 +1,9 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.Assistantandroid2" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.Assistantandroid2" parent="Base.Theme.Assistantandroid2" />
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>

View File

@ -0,0 +1,40 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'kotlin-parcelize'
id 'com.google.devtools.ksp'
}
android {
namespace 'com.gh.gamecenter.wechat.pay'
compileSdk 34
defaultConfig {
minSdk 22
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation(project(path: ":module_common")) {
exclude group: 'androidx.swiperefreshlayout'
}
implementation(project(path: ":module_core_feature"))
ksp "cn.therouter:apt:${routerVersion}"
}

21
feature/wechat_pay/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,17 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.gh.gamecenter.wechat.pay">
<uses-permission android:name="android.permission.INTERNET"/>
<application android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name="${applicationId}.wxapi.WXPayEntryActivity"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
</activity>
</application>
</manifest>

View File

@ -0,0 +1,56 @@
package com.gh.gamecenter
import com.gh.gamecenter.core.HaloApp
import com.gh.gamecenter.core.provider.IConfigProvider
import com.gh.gamecenter.core.provider.IWechatPayProvider
import com.gh.gamecenter.feature.entity.OrderEntity
import com.gh.gamecenter.feature.entity.WechatPayEntity
import com.tencent.mm.opensdk.modelpay.PayReq
import com.tencent.mm.opensdk.openapi.IWXAPI
import com.tencent.mm.opensdk.openapi.WXAPIFactory
import com.therouter.TheRouter
/**
* 这里必须使用单例模式,需要保存 orderNo以便后续查询订单使用
*/
@com.therouter.inject.ServiceProvider
class WechatPayProviderImpl : IWechatPayProvider {
private var api: IWXAPI
private var _orderNo: String = ""
override val orderNo: String
get() = _orderNo
private var _order: OrderEntity? = null
override val order: Any?
get() = _order
init {
val config = TheRouter.get(IConfigProvider::class.java)
api = WXAPIFactory.createWXAPI(HaloApp.getInstance().applicationContext, config?.getWechatAppId(), false)
}
override fun payRequest(order: Any, payRequest: Any) {
if (order is OrderEntity) {
_order = order
}
if (payRequest is WechatPayEntity) {
_orderNo = payRequest.orderNo
val request = PayReq().apply {
appId = payRequest.appId
partnerId = payRequest.partnerId
prepayId = payRequest.prepayId
packageValue = payRequest.packageValue
nonceStr = payRequest.nonceStr
timeStamp = payRequest.timestamp
sign = payRequest.sign
}
api.sendReq(request)
}
}
override fun clear() {
_orderNo = ""
_order = null
}
}

View File

@ -0,0 +1,88 @@
package com.gh.gamecenter.wxapi
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import com.gh.gamecenter.WechatPayProviderImpl
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.core.provider.IConfigProvider
import com.gh.gamecenter.core.provider.IWechatPayProvider
import com.gh.gamecenter.core.provider.IWechatPayResultProvider
import com.gh.gamecenter.feature.entity.OrderEntity
import com.gh.gamecenter.feature.eventbus.EBPayState
import com.tencent.mm.opensdk.constants.ConstantsAPI
import com.tencent.mm.opensdk.modelbase.BaseReq
import com.tencent.mm.opensdk.modelbase.BaseResp
import com.tencent.mm.opensdk.openapi.IWXAPI
import com.tencent.mm.opensdk.openapi.IWXAPIEventHandler
import com.tencent.mm.opensdk.openapi.WXAPIFactory
import com.therouter.TheRouter
class WXPayEntryActivity : Activity(), IWXAPIEventHandler {
private lateinit var api: IWXAPI
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val config = TheRouter.get(IConfigProvider::class.java)
api = WXAPIFactory.createWXAPI(this, config!!.getWechatAppId(), false)
api.handleIntent(intent, this)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
setIntent(intent)
api.handleIntent(intent, this)
}
override fun onReq(reqeust: BaseReq?) {
}
override fun onResp(response: BaseResp) {
if (response.type == ConstantsAPI.COMMAND_PAY_BY_WX) {
val payProvider = TheRouter.get(IWechatPayProvider::class.java)
val order = payProvider?.order as? OrderEntity
when (response.errCode) {
PAY_RESULT_SUCCESSFULLY -> {
if (payProvider is WechatPayProviderImpl) {
val nonceStr = payProvider.orderNo
TheRouter.get(IWechatPayResultProvider::class.java)?.onPayComplete(nonceStr, order)
}
}
PAY_RESULT_FAILURE -> {
SensorsBridge.trackMemberRechargeResult(
PAYMENT_TYPE_WECHAT,
order?.setMenuName ?: "",
order?.paymentAmount ?: "",
RECHARGE_RESULT_FAILURE
)
org.greenrobot.eventbus.EventBus.getDefault().post(EBPayState.PayFail)
}
PAY_RESULT_CANCEL -> {
SensorsBridge.trackMemberRechargeResult(
PAYMENT_TYPE_WECHAT,
order?.setMenuName ?: "",
order?.paymentAmount ?: "",
RECHARGE_RESULT_CANCEL
)
org.greenrobot.eventbus.EventBus.getDefault().post(EBPayState.PayCancel)
}
}
payProvider?.clear()
finish()
}
}
companion object {
private const val PAY_RESULT_SUCCESSFULLY = 0
private const val PAY_RESULT_FAILURE = -1
private const val PAY_RESULT_CANCEL = -2
private const val PAYMENT_TYPE_WECHAT = "微信"
private const val RECHARGE_RESULT_CANCEL = "充值取消"
private const val RECHARGE_RESULT_FAILURE = "充值失败"
}
}

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">光環助手</string>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">光环助手</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>

View File

@ -299,6 +299,10 @@ public class Constants {
// 腾讯企点地址
public static final String TENCENT_QIDIAN_ADDRESS = "https://admin.qidian.qq.com/template/blue/mp/menu/qr-code-jump.html?linkType=0&env=ol&kfuin=2355094296&fid=457&key=c76dcb2e3d582b6ffbfb5bb22cde85ff&cate=1&source=&isLBS=&isCustomEntry=&type=16&ftype=1&_type=wpa&qidian=true";
// 企点qq连接
public static final String QQ_QIDIAN_ADDRESS = "https://wpa1.qq.com/XqJqVq2d?_type=wpa&qidian=true";
//版规声明
public static final String FORUM_REGULATIONS_NEWS_ID = "5f4db9cc34d44d01b92fd670";
@ -345,6 +349,10 @@ public class Constants {
//游戏单管理规范
public static final String GAME_COLLECTION_RULE = "https://and-static.ghzs66.com/page/privacy_policies/game_collection.html";
// 我的资产页面
public static final String MY_ASSETS_DEV = "https://dev-and-static.ghzs.com/web/qiyou-acc/index.html";
public static final String MY_ASSETS = "https://and-static.ghzs.com/web/qiyou-acc/index.html";
public static final String SP_IS_DEV_ENV = "is_dev_env";
public static final String[] REPORT_LIST = new String[]{"垃圾广告营销", "恶意攻击谩骂", "淫秽色情信息", "违法有害信息", "其他原因"};
@ -482,4 +490,9 @@ public class Constants {
public static final String SP_VIDEO_COVER_CACHE = "sp_video_cover_cache";
public static final String SP_HAS_SHOW_ACCELERATION_GUIDE_LAYER = "sp_has_show_acceleration_guide_layer";
public static final String SP_MEMBER_RECHARGE_BUTTON_CLICK = "sp_member_recharge_button_click";
public static final String SP_MEMBER_PAYMENT_BUTTON_CLICK = "sp_member_payment_button_click";
}

View File

@ -369,4 +369,10 @@ public class EntranceConsts {
public static final String KEY_UID = "uid";
public static final String KEY_DISPLAY_TYPE = "display_type";
public static final String KEY_ACCT_GAME_INFO = "acct_game_info";
public static final String KEY_IS_NEED_RECORD = "is_need_record";
public static final String KEY_HAS_MULTI_ZONE = "has_multi_zone";
public static final String KEY_SPEED_TYPE = "speed_type";
public static final String KEY_ACCELERATOR_REQUEST = "accelerator_request";
}

View File

@ -76,7 +76,7 @@ object SensorsBridge {
private const val KEY_GAME_FORUM_TYPE = "game_forum_type"
private const val KEY_STAY_LENGTH = "stay_length"
private const val KEY_SHARE_TYPE = "share_type"
private const val KEY_IS_FIRST_TIME = "\$is_first_time"
const val KEY_IS_FIRST_TIME = "\$is_first_time"
private const val KEY_TAB_TYPE = "tab_type"
private const val KEY_BANNER_NAME = "banner_name"
private const val KEY_TOPPING_TYPE = "topping_type"
@ -123,6 +123,14 @@ object SensorsBridge {
private const val KEY_WECHAT_REMIND = "wechat_remind"
private const val KEY_MESSAGE_REMIND = "message_remind"
private const val KEY_AUTOMATIC_DOWNLOAD = "automatic_download"
private const val KEY_PACKAGE_NAME = "package_name"
private const val KEY_MEMBER_TYPE = "member_type"
private const val KEY_DISTRICT_SERVER = "district_server"
private const val KEY_SCENE_TYPE = "scene_type"
private const val KEY_SET_MENU_NAME = "set_menu_name"
private const val KEY_PAYMENT_AMOUNT = "payment_amount"
private const val KEY_PAYMENT_TYPE = "payment_type"
const val EVENT_NAME = "event_name"
private const val EVENT_GAME_DETAIL_PAGE_TAB_SELECT = "GameDetailPageTabSelect"
private const val EVENT_GAME_DETAIL_PAGE_TAG_CLICK = "GameDetailPageGameTagClick"
@ -307,6 +315,23 @@ object SensorsBridge {
private const val EVENT_APPOINTMENT_GAME_ONLINE_DIALOG_CLICK = "AppointmentGameOnlineDialogClick"
private const val EVENT_JIGUANG_PUSH_CLICK = "JiGuangPushClick"
private const val EVENT_NETWORK_ACCELERATION_GUIDANCE_DIAGRAM_SHOW = "NetworkAccelerationGuidanceDiagramShow"
private const val EVENT_NETWORK_ACCELERATION_BUTTON_CLICK = "NetworkAccelerationButtonClick"
private const val EVENT_NETWORK_ACCELERATION_START_UP = "NetworkAccelerationStartup"
private const val EVENT_NETWORK_ACCELERATION_START_UP_RESULT = "NetworkAccelerationStartupResult"
private const val EVENT_NETWORK_ACCELERATION_OTHER_BUTTON_CLICK = "NetworkAccelerationOtherButtonClick"
private const val EVENT_MEMBERSHIP_ACTIVATION_DIALOG_SHOW = "MembershipActivationDialogShow"
private const val EVENT_MEMBERSHIP_ACTIVATION_DIALOG_CLICK = "MembershipActivationDialogClick"
private const val EVENT_NETWORK_ACCELERATION_FAILURE_DIALOG_SHOW = "NetworkAccelerationFailureDialogShow"
private const val EVENT_NETWORK_ACCELERATION_FAILURE_DIALOG_CLICK = "NetworkAccelerationFailureDialogClick"
private const val EVENT_NETWORK_ACCELERATION_CONFLICT_DIALOG_SHOW = "NetworkAccelerationConflictDialogShow"
private const val EVENT_NETWORK_ACCELERATION_CONFLICT_DIALOG_CLICK = "NetworkAccelerationConflictDialogClick"
private const val EVENT_MY_ASSETS_PAGE_SHOW = "MyAssetsPageShow"
const val EVENT_MEMBER_RECHARGE_BUTTON_CLICK = "MemberRechargeButtonClick"
private const val EVENT_MEMBER_PAYMENT_BUTTON_CLICK = "MemberPaymentButtonClick"
private const val EVENT_MEMBER_RECHARGE_RESULT = "MemberRechargeResult"
private const val EVENT_MY_ASSETS_PAGE_CONTACT_CUSTOMER_SERVICE_CLICK = "MyAssetsPageContactCustomerServiceClick"
private var mIsSensorsEnabled = false
private val mSensor by lazy {
@ -5002,6 +5027,7 @@ object SensorsBridge {
}
trackEvent(EVENT_APPOINTMENT_GAME_ONLINE_DIALOG_CLICK, json)
}
/**
* 事件IDSearchDiscoveryClick
* 事件名称:搜索发现点击事件
@ -5050,4 +5076,341 @@ object SensorsBridge {
}
trackEvent(EVENT_JIGUANG_PUSH_CLICK, json)
}
/**
* 事件IDNetworkAccelerationGuidanceDiagramShow
* 事件名称:网络加速引导图展示事件
* 触发时机:网络加速引导图展示触发
*/
fun trackNetworkAccelerationGuidanceDiagramShow(
packageName: String,
gameId: String,
gameName: String
) {
val json = json {
KEY_PACKAGE_NAME to packageName
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
}
trackEvent(EVENT_NETWORK_ACCELERATION_GUIDANCE_DIAGRAM_SHOW, json)
}
/**
* 事件IDNetworkAccelerationButtonClick
* 事件名称:网络加速按钮点击事件
* 触发时机:用户点击网络加速按钮时触发上报以及点击时的具体场景以及用户的相关属性
*/
fun trackNetworkAccelerationButtonClick(
packageName: String,
gameId: String,
gameName: String,
memberType: String,
districtServer: String,
sceneType: String,
sourceEntrance: String
) {
val json = json {
KEY_PACKAGE_NAME to packageName
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_MEMBER_TYPE to memberType
KEY_DISTRICT_SERVER to districtServer
KEY_SCENE_TYPE to sceneType
KEY_SOURCE_ENTRANCE to sourceEntrance
}
trackEvent(EVENT_NETWORK_ACCELERATION_BUTTON_CLICK, json)
}
/**
* 事件IDNetworkAccelerationStartup
* 事件名称:网络加速启动事件
* 触发时机调用奇游SDK进行加速时进行上报
*/
fun trackNetworkAccelerationStartup(
packageName: String,
gameId: String,
gameName: String,
memberType: String,
districtServer: String,
sourceEntrance: String
) {
val json = json {
KEY_PACKAGE_NAME to packageName
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_MEMBER_TYPE to memberType
KEY_DISTRICT_SERVER to districtServer
KEY_SOURCE_ENTRANCE to sourceEntrance
}
trackEvent(EVENT_NETWORK_ACCELERATION_START_UP, json)
}
/**
* 事件IDNetworkAccelerationStartupResult
* 事件名称:网络加速启动结果事件
* 触发时机调用奇游SDK进行加速时成功\失败时触发上报失败是需上报具体的code码
*/
fun trackNetworkAccelerationStartupResult(
packageName: String,
gameId: String,
gameName: String,
memberType: String,
districtServer: String,
result: String,
sourceEntrance: String
) {
val json = json {
KEY_PACKAGE_NAME to packageName
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_MEMBER_TYPE to memberType
KEY_DISTRICT_SERVER to districtServer
KEY_RESULT to result
KEY_SOURCE_ENTRANCE to sourceEntrance
}
trackEvent(EVENT_NETWORK_ACCELERATION_START_UP_RESULT, json)
}
/**
* 事件IDNetworkAccelerationOtherButtonClick
* 事件名称:网络加速其他按钮点击事件
* 触发时机:点击【进入游戏\停止加速】时,触发上报
*/
fun trackNetworkAccelerationOtherButtonClick(
packageName: String,
gameId: String,
gameName: String,
memberType: String,
buttonName: String,
sourceEntrance: String
) {
val json = json {
KEY_PACKAGE_NAME to packageName
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_MEMBER_TYPE to memberType
KEY_BUTTON_NAME to buttonName
KEY_SOURCE_ENTRANCE to sourceEntrance
}
trackEvent(EVENT_NETWORK_ACCELERATION_OTHER_BUTTON_CLICK, json)
}
/**
* 事件IDMembershipActivationDialogShow
* 事件名称:会员开通提示弹窗展示事件
* 触发时机:会员提示弹窗展示时触发上报
*/
fun trackMembershipActivationDialogShow(
packageName: String,
gameId: String,
gameName: String,
sourceEntrance: String
) {
val json = json {
KEY_PACKAGE_NAME to packageName
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_SOURCE_ENTRANCE to sourceEntrance
}
trackEvent(EVENT_MEMBERSHIP_ACTIVATION_DIALOG_SHOW, json)
}
/**
* 事件IDMembershipActivationDialogClick
* 事件名称:会员开通提示弹窗点击事件
* 触发时机:用户点击会员提示弹窗的按钮【去开通】时触发上报
*/
fun trackMembershipActivationDialogClick(
packageName: String,
gameId: String,
gameName: String,
sourceEntrance: String
) {
val json = json {
KEY_PACKAGE_NAME to packageName
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_SOURCE_ENTRANCE to sourceEntrance
}
trackEvent(EVENT_MEMBERSHIP_ACTIVATION_DIALOG_CLICK, json)
}
/**
* 事件IDNetworkAccelerationFailureDialogShow
* 事件名称:加速失败提示弹窗展示事件
* 触发时机:加速失败提示弹窗展示时触发上报
*/
fun trackNetworkAccelerationFailureDialogShow(
packageName: String,
gameId: String,
gameName: String,
sourceEntrance: String
) {
val json = json {
KEY_PACKAGE_NAME to packageName
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_SOURCE_ENTRANCE to sourceEntrance
}
trackEvent(EVENT_NETWORK_ACCELERATION_FAILURE_DIALOG_SHOW, json)
}
/**
* 事件IDNetworkAccelerationFailureDialogClick
* 事件名称:加速失败提示弹窗点击事件
* 触发时机:用户点击加速失败提示弹窗的按钮【联系客服】时触发上报
*/
fun trackNetworkAccelerationFailureDialogClick(
packageName: String,
gameId: String,
gameName: String,
sourceEntrance: String
) {
val json = json {
KEY_PACKAGE_NAME to packageName
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_SOURCE_ENTRANCE to sourceEntrance
}
trackEvent(EVENT_NETWORK_ACCELERATION_FAILURE_DIALOG_CLICK, json)
}
/**
* 事件IDNetworkAccelerationConflictDialogShow
* 事件名称:加速冲突提示弹窗展示事件
* 触发时机:加速冲突提示弹窗展示时触发上报
*/
fun trackNetworkAccelerationConflictDialogShow(
packageName: String,
gameId: String,
gameName: String,
sourceEntrance: String
) {
val json = json {
KEY_PACKAGE_NAME to packageName
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_SOURCE_ENTRANCE to sourceEntrance
}
trackEvent(EVENT_NETWORK_ACCELERATION_CONFLICT_DIALOG_SHOW, json)
}
/**
* 事件IDNetworkAccelerationConflictDialogClick
* 事件名称:加速冲突提示弹窗点击事件
* 触发时机:用户点击加速冲突提示弹窗的按钮【暂不启动】\【继续启动】时触发上报
*/
fun trackNetworkAccelerationConflictDialogClick(
packageName: String,
gameId: String,
gameName: String,
buttonName: String,
sourceEntrance: String
) {
val json = json {
KEY_PACKAGE_NAME to packageName
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_BUTTON_NAME to buttonName
KEY_SOURCE_ENTRANCE to sourceEntrance
}
trackEvent(EVENT_NETWORK_ACCELERATION_CONFLICT_DIALOG_CLICK, json)
}
/**
* 事件IDMyAssetsPageShow
* 事件名称:我的资产页面展示事件
* 触发时机:进入我的资产页面时触发上报
*/
fun trackMyAssetsPageShow(
packageName: String,
gameId: String,
gameName: String,
sourceEntrance: String
) {
val json = json {
KEY_PACKAGE_NAME to packageName
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_SOURCE_ENTRANCE to sourceEntrance
}
trackEvent(EVENT_MY_ASSETS_PAGE_SHOW, json)
}
/**
* 事件IDMemberRechargeButtonClick
* 事件名称:会员充值按钮点击事件
* 触发时机:用户点击立即支付按钮时触发上报
* note:这个事件目前有 web 调用调用原生方法上报
*/
fun trackMemberRechargeButtonClick(
isFirstTime: Boolean,
setMenuName: String,
paymentAmount: String
) {
val json = json {
KEY_IS_FIRST_TIME to isFirstTime
KEY_SET_MENU_NAME to setMenuName
KEY_PAYMENT_AMOUNT to paymentAmount
}
trackEvent(EVENT_MEMBER_RECHARGE_BUTTON_CLICK, json)
}
/**
* 事件IDMemberPaymentButtonClick
* 事件名称:会员充值按钮点击事件
* 触发时机:用户点击立即支付按钮时触发上报
*/
fun trackMemberPaymentButtonClick(
isFirstTime: Boolean,
paymentType: String,
setMenuName: String,
paymentAmount: String
) {
val json = json {
KEY_IS_FIRST_TIME to isFirstTime
KEY_SET_MENU_NAME to setMenuName
KEY_PAYMENT_AMOUNT to paymentAmount
KEY_PAYMENT_TYPE to paymentType
}
trackEvent(EVENT_MEMBER_PAYMENT_BUTTON_CLICK, json)
}
/**
* 事件IDMemberRechargeResult
* 事件名称:会员充值结果返回事件
* 触发时机:支付的回调结果返回时触发上报
*/
fun trackMemberRechargeResult(
paymentType: String,
setMenuName: String,
paymentAmount: String,
result: String
) {
val json = json {
KEY_PAYMENT_TYPE to paymentType
KEY_SET_MENU_NAME to setMenuName
KEY_PAYMENT_AMOUNT to paymentAmount
KEY_RESULT to result
}
trackEvent(EVENT_MEMBER_RECHARGE_RESULT, json)
}
/**
* 事件IDMyAssetsPageContactCustomerServiceClick
* 事件名称:我的资产联系客服点击事件
* 触发时机:用户在【我的资产】点击联系客服时触发上报
*/
fun trackMyAssetsPageContactCustomerServiceClick() {
trackEvent(EVENT_MY_ASSETS_PAGE_CONTACT_CUSTOMER_SERVICE_CLICK)
}
fun trackSensorsAnalyticsFromWeb(eventName: String, hashmap: HashMap<String, Any>) {
val json = json {
hashmap.iterator().forEach { (key, value) ->
key to value
}
}
trackEvent(eventName, json)
}
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.gh.gamecenter.common.view.StatusBarView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/primary_theme_20"/>
<FrameLayout
android:id="@id/layout_activity_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -5,6 +5,8 @@
<color name="primary_theme">#2888E0</color>
<!---10 -->
<color name="primary_theme_10">#1A2888E0</color>
<!---20 -->
<color name="primary_theme_20">#332888E0</color>
<!---30 -->
<color name="primary_theme_30">#4D2888E0</color>
<!---70 -->

View File

@ -5,6 +5,8 @@
<color name="primary_theme">#2496FF</color>
<!---10 -->
<color name="primary_theme_10">#1A2496FF</color>
<!---20 -->
<color name="primary_theme_20">#332496FF</color>
<!---30 -->
<color name="primary_theme_30">#4D2496FF</color>
<!---70 -->

View File

@ -0,0 +1,41 @@
package com.gh.gamecenter.core.callback
interface OnAccelerateListener {
fun onStateChanged(state: AccelerateState)
fun onProgress(progress: Int, curGamePkgName: String?, curGameZoneFlag: String?)
fun onVipStatusChanged(isNewUser: Boolean, isVip: Boolean)
}
sealed class AccelerateState(val code: Int) {
// token 过期
val isTokenExpired: Boolean
get() = code == ACCT_CODE_TOKEN_EXPIRED
val isTokenEmpty: Boolean
get() = code == ACCT_CODE_TOKEN_EMPTY
val isSkipError: Boolean
get() = skipErrorCodes.any { it == code }
data class Normal(private val _code: Int) : AccelerateState(_code)
data class Starting(private val _code: Int) : AccelerateState(_code)
data class Success(val acctGameInfo: Any?, private val _code: Int) : AccelerateState(_code)
data class Failure(private val _code: Int) : AccelerateState(_code)
data class OkStopping(private val _code: Int) : AccelerateState(_code)
data class ErrStopping(private val _code: Int) : AccelerateState(_code)
companion object {
private const val ACCT_CODE_TOKEN_EMPTY = 102
private const val ACCT_CODE_TOKEN_EXPIRED = 200 // token过期
private const val ACCT_CODE_PERMISSION_DENIED = 290 // 用户拒绝权限
private val skipErrorCodes = arrayOf(ACCT_CODE_PERMISSION_DENIED)
}
}

View File

@ -0,0 +1,37 @@
package com.gh.gamecenter.core.provider
import android.app.Application
import com.gh.gamecenter.core.callback.OnAccelerateListener
import com.therouter.inject.Singleton
@Singleton
interface IAcceleratorProvider {
fun init(application: Application, version: String)
fun setQyUserToken(token: String, callback: ((Boolean) -> Unit)? = null)
fun setVipEntity(vip: Any)
fun isNewUser(): Boolean
fun isVip(): Boolean
fun isPaidUser(): Boolean
fun getMemberType(): String
fun deleteQyUserToken(): Boolean
fun startQyGameAccelerate(accGameInfo: Any)
fun stopQyGameAccelerate(appLayerCallStopMsg: String? = null): Boolean
fun bindAccRelatedListener(pkgName: String, listener: OnAccelerateListener)
fun unBindAccRelatedListener(listener: OnAccelerateListener)
fun loadQyGameZoneData(packageName: String, callback: (List<Any>) -> Unit)
fun isCurAccSuccess(): Boolean
}

Some files were not shown because too many files have changed in this diff Show More