From 738dfd3b4d3dc746e019537e8ee59342b5e9c55b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=99=A8?= Date: Tue, 4 Mar 2025 10:44:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:https://jira.shanqu.cc/browse/GHZSCY-6976?= =?UTF-8?q?=20=E5=A5=87=E6=B8=B8=E5=8A=A0=E9=80=9FSDK=E6=8E=A5=E5=85=A5?= =?UTF-8?q?=E2=80=94=E5=AE=A2=E6=88=B7=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 14 +- app/src/main/AndroidManifest.xml | 14 +- .../main/java/com/gh/common/DefaultJsApi.kt | 102 ++++- .../gh/common/chain/PackageCheckHandler.kt | 11 +- .../IAcceleratorDataHolderProviderImpl.kt | 18 + .../provider/WechatPayResultProviderImpl.kt | 54 +++ .../gh/common/util/DetailDownloadUtils.java | 13 +- .../java/com/gh/common/util/DirectUtils.kt | 23 +- .../com/gh/common/util/DownloadItemUtils.kt | 143 +++++-- .../main/java/com/gh/common/util/GameUtils.kt | 24 +- .../gh/common/util/TempCertificationUtils.kt | 9 + .../com/gh/gamecenter/SplashScreenActivity.kt | 5 +- .../adapter/viewholder/DetailViewHolder.kt | 79 +++- .../CloudArchiveManagerActivity.kt | 2 +- .../eventbus/EBStartupAcceleration.kt | 5 + .../gamedetail/AcceleratorZoneViewModel.kt | 13 + .../gamedetail/GameDetailViewModel.kt | 89 +++- .../gamedetail/GameDetailWrapperFragment.kt | 127 ++++-- .../StartingAcceleratorViewModel.kt | 71 ++++ .../gamedetail/accelerator/AccelerationDao.kt | 49 +++ .../accelerator/AccelerationDataBase.kt | 93 +++++ .../accelerator/AcceleratorGuideView.kt | 182 +++++++++ .../GameDetailAcceleratorUiHelper.kt | 368 +++++++++++++++++ .../accelerator/UserVerifyDialogUtils.kt | 54 +++ .../AcceleratorCheckInstallInterceptor.kt | 28 ++ .../accelerator/chain/AcceleratorClient.kt | 29 ++ .../chain/AcceleratorLoginInterceptor.kt | 18 + .../AcceleratorPackageCheckInterceptor.kt | 20 + .../chain/AcceleratorRealNameInterceptor.kt | 26 ++ .../chain/AcceleratorStateInterceptor.kt | 52 +++ .../accelerator/chain/AcceleratorValidator.kt | 71 ++++ .../chain/AcceleratorVipInterceptor.kt | 31 ++ .../dialog/AcceleratorDialogFragment.kt | 260 ++++++++++++ .../AcceleratorReplaceDialogFragment.kt | 116 ++++++ .../dialog/AcceleratorZoneDialogFragment.kt | 213 ++++++++++ .../StartingAcceleratorDialogFragment.kt | 280 +++++++++++++ .../home/custom/CustomPageViewModel.kt | 5 +- .../home/custom/adapter/CustomPageAdapter.kt | 18 +- .../adapter/CustomRecentAcceleratorAdapter.kt | 84 ++++ .../home/custom/model/CustomPageData.kt | 7 +- .../home/custom/model/CustomPageItem.kt | 31 +- .../home/custom/model/CustomPageRepository.kt | 43 +- .../custom/model/CustomPageShareRepository.kt | 33 +- .../CustomRecentAcceleratorViewHolder.kt | 113 ++++++ .../gamecenter/libao/LibaoDetailFragment.java | 2 +- .../newsdetail/NewsDetailFragment.java | 1 + .../personal/HaloPersonalFragment.kt | 68 +++- .../retrofit/service/ApiService.java | 31 ++ .../room/converter/AcctGameInfoConverter.kt | 44 ++ .../search/SearchGameFirstItemViewHolder.kt | 10 +- .../search/SearchGameIndexAdapter.kt | 17 +- .../search/SearchGameIndexItemViewHolder.kt | 23 +- .../search/SearchGameResultAdapter.kt | 253 +++++++++++- .../search/SearchGameResultFragment.kt | 71 +++- .../search/SearchGameResultRepository.kt | 12 + .../search/SearchGameResultViewModel.kt | 11 +- .../viewmodel/SearchTabActivityViewModel.kt | 11 + .../main/java/com/halo/assistant/HaloApp.java | 8 + .../accelerator/AccelerationUseCase.kt | 183 +++++++++ .../assistant/accelerator/MyAssetsActivity.kt | 48 +++ .../repository/AccelerationRepository.kt | 223 ++++++++++ .../repository/AcceleratorDataHolder.kt | 120 ++++++ .../halo/assistant/fragment/WebFragment.kt | 218 +++++++++- .../halo/assistant/fragment/WebViewModel.kt | 14 + .../drawable-night/bg_accelerator_guide.xml | 144 +++++++ .../res/drawable/bg_accelerator_guide.xml | 125 ++++++ .../bg_accelerator_guide_download.xml | 7 + .../ic_accelerator_recently_played.xml | 42 ++ .../res/drawable/ic_basic_accelerator.xml | 10 + app/src/main/res/drawable/ic_basic_x.xml | 14 + app/src/main/res/drawable/ic_basic_x_18.xml | 14 + app/src/main/res/drawable/ic_free_vip_tag.xml | 24 ++ .../res/drawable/ic_free_vip_tag_detail.xml | 24 ++ .../res/drawable/ic_new_function_text.xml | 21 + .../main/res/drawable/ic_triangle_down.xml | 9 + .../main/res/layout/detail_download_item.xml | 165 +++++++- .../dialog_fragment_accelerator_zone.xml | 34 ++ .../layout/dialog_fragment_speed_failure.xml | 49 +++ .../dialog_fragment_speed_replace_game.xml | 65 +++ .../dialog_fragment_speed_without_login.xml | 49 +++ .../dialog_fragment_starting_accelerator.xml | 37 ++ .../res/layout/fragment_halo_personal.xml | 17 +- .../layout/item_home_recent_vgame_custom.xml | 3 +- .../res/layout/item_home_vgame_refactor.xml | 8 +- .../layout/layout_accelerator_guide_page.xml | 40 ++ .../res/layout/recycler_accelerator_zone.xml | 14 + .../res/layout/search_game_first_item.xml | 2 +- .../res/layout/search_game_index_item.xml | 2 +- app/src/main/res/layout/search_game_item.xml | 362 +++++++++++++++++ app/src/main/res/values-zh-rTW/strings.xml | 30 ++ app/src/main/res/values/strings.xml | 30 ++ app/src/main/res/xml/personal_scene.xml | 24 ++ dependencies.gradle | 6 +- feature/accelerator/build.gradle | 48 +++ feature/accelerator/proguard-rules.pro | 21 + .../accelerator/src/main/AndroidManifest.xml | 9 + .../provider/AcceleratorProviderImpl.kt | 173 ++++++++ .../src/main/res/values-zh-rTW/themes.xml | 7 + .../src/main/res/values/strings.xml | 3 + feature/ali_pay/build.gradle | 42 ++ feature/ali_pay/proguard-rules.pro | 21 + feature/ali_pay/src/main/AndroidManifest.xml | 13 + .../gamecenter/alipay/AliPayProviderImpl.kt | 44 ++ .../game/gamecenter/alipay/H5PayActivity.kt | 78 ++++ .../src/main/res/values-night/themes.xml | 7 + .../ali_pay/src/main/res/values/colors.xml | 5 + .../ali_pay/src/main/res/values/strings.xml | 3 + .../ali_pay/src/main/res/values/themes.xml | 9 + .../main/res/xml/network_security_config.xml | 4 + .../main/java/com/lg/SentryProviderImpl.kt | 45 +- feature/wechat_pay/build.gradle | 40 ++ feature/wechat_pay/proguard-rules.pro | 21 + .../wechat_pay/src/main/AndroidManifest.xml | 17 + .../gh/gamecenter/WechatPayProviderImpl.kt | 56 +++ .../gh/gamecenter/wxapi/WXPayEntryActivity.kt | 88 ++++ .../src/main/res/values-zh-rTW/strings.xml | 3 + .../src/main/res/values/strings.xml | 3 + .../main/res/xml/network_security_config.xml | 4 + .../gamecenter/common/constant/Constants.java | 13 + .../common/constant/EntranceConsts.java | 6 + .../gamecenter/common/constant/RouteConsts.kt | 1 + .../gamecenter/common/utils/SensorsBridge.kt | 384 +++++++++++++++++- .../src/main/res/layout/activity_member.xml | 16 + .../src/main/res/values-night/colors.xml | 2 + module_common/src/main/res/values/colors.xml | 2 + .../core/callback/OnAccelerateListener.kt | 43 ++ .../IAcceleratorDataHolderProvider.kt | 8 + .../core/provider/IAcceleratorProvider.kt | 28 ++ .../core/provider/IAliPayProvider.kt | 9 + .../core/provider/ISentryProvider.kt | 3 +- .../core/provider/IWechatPayProvider.kt | 12 + .../core/provider/IWechatPayResultProvider.kt | 6 + .../gamecenter/feature/entity/AcctGameInfo.kt | 35 ++ .../gamecenter/feature/entity/AcctRecord.kt | 17 + .../feature/entity/AcctRecordEntity.kt | 40 ++ .../gamecenter/feature/entity/AliPayEntity.kt | 20 + .../gamecenter/feature/entity/BaseEntity.kt | 8 + .../gamecenter/feature/entity/GameEntity.kt | 37 +- .../gamecenter/feature/entity/OrderEntity.kt | 22 + .../feature/entity/PackageDialogEntity.kt | 1 + .../gh/gamecenter/feature/entity/QyToken.kt | 12 + .../gamecenter/feature/entity/TrialEntity.kt | 15 + .../gh/gamecenter/feature/entity/VipEntity.kt | 22 + .../feature/entity/WechatPayEntity.kt | 50 +++ .../gamecenter/feature/eventbus/EBPayState.kt | 7 + .../gamecenter/feature/utils/SentryHelper.kt | 9 + .../gamecenter/feature/view/DownloadButton.kt | 44 +- .../gamecenter/login/retrofit/ApiService.java | 8 + .../gh/gamecenter/login/user/UserManager.java | 34 +- .../gamecenter/login/user/UserRepository.java | 209 ++++++++-- scripts/build_with_simple_backup.sh | 4 +- settings.gradle | 19 +- 152 files changed, 7435 insertions(+), 254 deletions(-) create mode 100644 app/src/main/java/com/gh/common/provider/IAcceleratorDataHolderProviderImpl.kt create mode 100644 app/src/main/java/com/gh/common/provider/WechatPayResultProviderImpl.kt create mode 100644 app/src/main/java/com/gh/gamecenter/eventbus/EBStartupAcceleration.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/AcceleratorZoneViewModel.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/StartingAcceleratorViewModel.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/AccelerationDao.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/AccelerationDataBase.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/AcceleratorGuideView.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/GameDetailAcceleratorUiHelper.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/UserVerifyDialogUtils.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorCheckInstallInterceptor.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorClient.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorLoginInterceptor.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorPackageCheckInterceptor.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorRealNameInterceptor.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorStateInterceptor.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorValidator.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorVipInterceptor.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/dialog/AcceleratorDialogFragment.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/dialog/AcceleratorReplaceDialogFragment.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/dialog/AcceleratorZoneDialogFragment.kt create mode 100644 app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/dialog/StartingAcceleratorDialogFragment.kt create mode 100644 app/src/main/java/com/gh/gamecenter/home/custom/adapter/CustomRecentAcceleratorAdapter.kt create mode 100644 app/src/main/java/com/gh/gamecenter/home/custom/viewholder/CustomRecentAcceleratorViewHolder.kt create mode 100644 app/src/main/java/com/gh/gamecenter/room/converter/AcctGameInfoConverter.kt create mode 100644 app/src/main/java/com/halo/assistant/accelerator/AccelerationUseCase.kt create mode 100644 app/src/main/java/com/halo/assistant/accelerator/MyAssetsActivity.kt create mode 100644 app/src/main/java/com/halo/assistant/accelerator/repository/AccelerationRepository.kt create mode 100644 app/src/main/java/com/halo/assistant/accelerator/repository/AcceleratorDataHolder.kt create mode 100644 app/src/main/java/com/halo/assistant/fragment/WebViewModel.kt create mode 100644 app/src/main/res/drawable-night/bg_accelerator_guide.xml create mode 100644 app/src/main/res/drawable/bg_accelerator_guide.xml create mode 100644 app/src/main/res/drawable/bg_accelerator_guide_download.xml create mode 100644 app/src/main/res/drawable/ic_accelerator_recently_played.xml create mode 100644 app/src/main/res/drawable/ic_basic_accelerator.xml create mode 100644 app/src/main/res/drawable/ic_basic_x.xml create mode 100644 app/src/main/res/drawable/ic_basic_x_18.xml create mode 100644 app/src/main/res/drawable/ic_free_vip_tag.xml create mode 100644 app/src/main/res/drawable/ic_free_vip_tag_detail.xml create mode 100644 app/src/main/res/drawable/ic_new_function_text.xml create mode 100644 app/src/main/res/drawable/ic_triangle_down.xml create mode 100644 app/src/main/res/layout/dialog_fragment_accelerator_zone.xml create mode 100644 app/src/main/res/layout/dialog_fragment_speed_failure.xml create mode 100644 app/src/main/res/layout/dialog_fragment_speed_replace_game.xml create mode 100644 app/src/main/res/layout/dialog_fragment_speed_without_login.xml create mode 100644 app/src/main/res/layout/dialog_fragment_starting_accelerator.xml create mode 100644 app/src/main/res/layout/layout_accelerator_guide_page.xml create mode 100644 app/src/main/res/layout/recycler_accelerator_zone.xml create mode 100644 app/src/main/res/layout/search_game_item.xml create mode 100644 feature/accelerator/build.gradle create mode 100644 feature/accelerator/proguard-rules.pro create mode 100644 feature/accelerator/src/main/AndroidManifest.xml create mode 100644 feature/accelerator/src/main/java/com/gh/gamecenter/accelerator/provider/AcceleratorProviderImpl.kt create mode 100644 feature/accelerator/src/main/res/values-zh-rTW/themes.xml create mode 100644 feature/accelerator/src/main/res/values/strings.xml create mode 100644 feature/ali_pay/build.gradle create mode 100644 feature/ali_pay/proguard-rules.pro create mode 100644 feature/ali_pay/src/main/AndroidManifest.xml create mode 100644 feature/ali_pay/src/main/java/com/game/gamecenter/alipay/AliPayProviderImpl.kt create mode 100644 feature/ali_pay/src/main/java/com/game/gamecenter/alipay/H5PayActivity.kt create mode 100644 feature/ali_pay/src/main/res/values-night/themes.xml create mode 100644 feature/ali_pay/src/main/res/values/colors.xml create mode 100644 feature/ali_pay/src/main/res/values/strings.xml create mode 100644 feature/ali_pay/src/main/res/values/themes.xml create mode 100644 feature/ali_pay/src/main/res/xml/network_security_config.xml create mode 100644 feature/wechat_pay/build.gradle create mode 100644 feature/wechat_pay/proguard-rules.pro create mode 100644 feature/wechat_pay/src/main/AndroidManifest.xml create mode 100644 feature/wechat_pay/src/main/java/com/gh/gamecenter/WechatPayProviderImpl.kt create mode 100644 feature/wechat_pay/src/main/java/com/gh/gamecenter/wxapi/WXPayEntryActivity.kt create mode 100644 feature/wechat_pay/src/main/res/values-zh-rTW/strings.xml create mode 100644 feature/wechat_pay/src/main/res/values/strings.xml create mode 100644 feature/wechat_pay/src/main/res/xml/network_security_config.xml create mode 100644 module_common/src/main/res/layout/activity_member.xml create mode 100644 module_core/src/main/java/com/gh/gamecenter/core/callback/OnAccelerateListener.kt create mode 100644 module_core/src/main/java/com/gh/gamecenter/core/provider/IAcceleratorDataHolderProvider.kt create mode 100644 module_core/src/main/java/com/gh/gamecenter/core/provider/IAcceleratorProvider.kt create mode 100644 module_core/src/main/java/com/gh/gamecenter/core/provider/IAliPayProvider.kt create mode 100644 module_core/src/main/java/com/gh/gamecenter/core/provider/IWechatPayProvider.kt create mode 100644 module_core/src/main/java/com/gh/gamecenter/core/provider/IWechatPayResultProvider.kt create mode 100644 module_core_feature/src/main/java/com/gh/gamecenter/feature/entity/AcctGameInfo.kt create mode 100644 module_core_feature/src/main/java/com/gh/gamecenter/feature/entity/AcctRecord.kt create mode 100644 module_core_feature/src/main/java/com/gh/gamecenter/feature/entity/AcctRecordEntity.kt create mode 100644 module_core_feature/src/main/java/com/gh/gamecenter/feature/entity/AliPayEntity.kt create mode 100644 module_core_feature/src/main/java/com/gh/gamecenter/feature/entity/BaseEntity.kt create mode 100644 module_core_feature/src/main/java/com/gh/gamecenter/feature/entity/OrderEntity.kt create mode 100644 module_core_feature/src/main/java/com/gh/gamecenter/feature/entity/QyToken.kt create mode 100644 module_core_feature/src/main/java/com/gh/gamecenter/feature/entity/TrialEntity.kt create mode 100644 module_core_feature/src/main/java/com/gh/gamecenter/feature/entity/VipEntity.kt create mode 100644 module_core_feature/src/main/java/com/gh/gamecenter/feature/entity/WechatPayEntity.kt create mode 100644 module_core_feature/src/main/java/com/gh/gamecenter/feature/eventbus/EBPayState.kt diff --git a/app/build.gradle b/app/build.gradle index 514e406611..4434244ad2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 697763562f..1c7a5ba025 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,9 +10,9 @@ - - - + + + @@ -197,7 +197,9 @@ android:name="io.sentry.breadcrumbs.system-events" android:value="false" /> - + @@ -814,6 +816,10 @@ android:name=".video.poster.PosterEditActivity" android:screenOrientation="portrait" /> + + diff --git a/app/src/main/java/com/gh/common/DefaultJsApi.kt b/app/src/main/java/com/gh/common/DefaultJsApi.kt index 185e12e8fe..80f7285323 100644 --- a/app/src/main/java/com/gh/common/DefaultJsApi.kt +++ b/app/src/main/java/com/gh/common/DefaultJsApi.kt @@ -11,7 +11,6 @@ import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentManager -import com.therouter.TheRouter import com.gh.common.exposure.ExposureManager import com.gh.common.util.* import com.gh.common.util.LogUtils @@ -22,7 +21,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 +30,22 @@ 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.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,10 +57,14 @@ 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.accelerator.repository.AccelerationRepository.Companion.PAYMENT_TYPE_ALIPAY +import com.halo.assistant.accelerator.repository.AccelerationRepository.Companion.PAYMENT_TYPE_WECHAT +import com.halo.assistant.accelerator.repository.AcceleratorDataHolder import com.lightgame.download.DataWatcher import com.lightgame.download.DownloadEntity import com.lightgame.download.DownloadStatus.* import com.lightgame.utils.Utils +import com.therouter.TheRouter import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -75,6 +81,7 @@ class DefaultJsApi( private var mBbsId: String? = "", private var mOriginUrl: String? = "", private val mForumName: String? = "", + private val listener: OnWebClickListener? = null ) { companion object { @@ -699,6 +706,78 @@ class DefaultJsApi( } } + @JavascriptInterface + fun preOrderWithAli(json: Any) { + val order = json.toString().toObject() ?: return + trackMemberPaymentButtonClick(order, PAYMENT_TYPE_ALIPAY) + listener?.onPreOrderWithAli(order) + } + + @JavascriptInterface + fun preOrderWithWechat(json: Any) { + val order = json.toString().toObject() ?: 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 { + return AcceleratorDataHolder.instance.getAcceleratingGameId() + } + + @JavascriptInterface + fun stopGameAccelerate() { + listener?.onStopGameAccelerate() + } + + @JavascriptInterface + fun refreshToken(token: Any, handler: CompletionHandler) { + 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>() ?: 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 +894,15 @@ class DefaultJsApi( } } } + + interface OnWebClickListener { + + fun onPreOrderWithAli(order: OrderEntity) + + fun onPreOrderWithWechat(order: OrderEntity) + + fun onStartGameAccelerate(accInfo: AcctRecordEntity.AccInfo) + + fun onStopGameAccelerate() + } } diff --git a/app/src/main/java/com/gh/common/chain/PackageCheckHandler.kt b/app/src/main/java/com/gh/common/chain/PackageCheckHandler.kt index 25b0651c07..72a69cb1c7 100644 --- a/app/src/main/java/com/gh/common/chain/PackageCheckHandler.kt +++ b/app/src/main/java/com/gh/common/chain/PackageCheckHandler.kt @@ -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() + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/provider/IAcceleratorDataHolderProviderImpl.kt b/app/src/main/java/com/gh/common/provider/IAcceleratorDataHolderProviderImpl.kt new file mode 100644 index 0000000000..ebdf875a7b --- /dev/null +++ b/app/src/main/java/com/gh/common/provider/IAcceleratorDataHolderProviderImpl.kt @@ -0,0 +1,18 @@ +package com.gh.common.provider + +import com.gh.gamecenter.core.provider.IAcceleratorDataHolderProvider +import com.gh.gamecenter.feature.entity.VipEntity +import com.halo.assistant.accelerator.repository.AcceleratorDataHolder + +@com.therouter.inject.ServiceProvider +class IAcceleratorDataHolderProviderImpl : IAcceleratorDataHolderProvider { + override fun setVipEntity(vip: Any) { + if (vip is VipEntity) { + AcceleratorDataHolder.instance.setVipEntity(vip) + } + } + + override fun clear() { + AcceleratorDataHolder.instance.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/provider/WechatPayResultProviderImpl.kt b/app/src/main/java/com/gh/common/provider/WechatPayResultProviderImpl.kt new file mode 100644 index 0000000000..213b43e06e --- /dev/null +++ b/app/src/main/java/com/gh/common/provider/WechatPayResultProviderImpl.kt @@ -0,0 +1,54 @@ +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.IWechatPayResultProvider +import com.gh.gamecenter.feature.entity.OrderEntity +import com.gh.gamecenter.feature.entity.VipEntity +import com.gh.gamecenter.feature.eventbus.EBPayState +import com.halo.assistant.accelerator.repository.AccelerationRepository +import com.halo.assistant.accelerator.repository.AcceleratorDataHolder +import org.greenrobot.eventbus.EventBus + +@com.therouter.inject.ServiceProvider +class WechatPayResultProviderImpl : IWechatPayResultProvider { + + private val repository = AccelerationRepository.newInstance() + + @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) + + // 先刷新本地状态,支付成功,肯定是付费会员 + AcceleratorDataHolder.instance.setVipEntity( + VipEntity( + _vipStatus = true, + _isNewUser = false, + _isTryVip = false + ) + ) + + SensorsBridge.trackMemberRechargeResult( + AccelerationRepository.PAYMENT_TYPE_WECHAT, + orderEntity?.setMenuName ?: "", + orderEntity?.paymentAmount ?: "", + AccelerationRepository.RECHARGE_RESULT_SUCCESS + ) + }, { + // 支付失败 + EventBus.getDefault().post(EBPayState.PayFail) + SensorsBridge.trackMemberRechargeResult( + AccelerationRepository.PAYMENT_TYPE_WECHAT, + orderEntity?.setMenuName ?: "", + orderEntity?.paymentAmount ?: "", + AccelerationRepository.RECHARGE_RESULT_FAILURE + ) + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/util/DetailDownloadUtils.java b/app/src/main/java/com/gh/common/util/DetailDownloadUtils.java index 4a5ae06540..ec74f4e060 100644 --- a/app/src/main/java/com/gh/common/util/DetailDownloadUtils.java +++ b/app/src/main/java/com/gh/common/util/DetailDownloadUtils.java @@ -41,7 +41,8 @@ public class DetailDownloadUtils { /** * 更新底部下载区域 - * @param viewHolder 下载区域的包裹 + * + * @param viewHolder 下载区域的包裹 * @param ignoreDownloadEntity 忽略下载实体(往往用于下载异常时) */ public static void updateViewHolder(DetailViewHolder viewHolder, boolean ignoreDownloadEntity) { @@ -53,6 +54,8 @@ public class DetailDownloadUtils { viewHolder.getMultiVersionDownloadTv().setVisibility(View.GONE); } + viewHolder.setSpeedViewsVisible(false); + // 根据预置的配置更新 ViewHolder 的状态 (譬如青少年模式、下载内容为空等) if (updateViewHolderWithPredefinedConfig(viewHolder, gameEntity)) { return; @@ -135,6 +138,12 @@ public class DetailDownloadUtils { showDualDownloadButton, downloadEntity ); + + if(!showVGame){ + String rawBtnText = GameUtils.getDownloadBtnText(viewHolder.getContext(), gameEntity, false, showVGame, PluginLocation.only_game); + viewHolder.checkIfShowSpeedUi(rawBtnText); + } + } else { // 游戏包含多 APK 的情况 viewHolder.getMultiVersionDownloadTv().setText("选择下载你的版本" + (TextUtils.isEmpty(downloadAddWord) ? "" : "-" + downloadAddWord)); @@ -219,7 +228,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) diff --git a/app/src/main/java/com/gh/common/util/DirectUtils.kt b/app/src/main/java/com/gh/common/util/DirectUtils.kt index ab0ea11702..a6ef4bfb07 100644 --- a/app/src/main/java/com/gh/common/util/DirectUtils.kt +++ b/app/src/main/java/com/gh/common/util/DirectUtils.kt @@ -195,7 +195,8 @@ object DirectUtils { "simulator", "teen_mode", "message_center", - "archive" + "archive", + "my_assets", ) fun directToLinkPage( @@ -618,6 +619,8 @@ object DirectUtils { "archive" -> directToCloudArchive(context, linkEntity.link ?: "", linkEntity.text ?: "", "", entrance) + "my_assets" -> navigateToMyAssetsPage(context, entrance) + "" -> { // do nothing } @@ -2315,7 +2318,13 @@ object DirectUtils { // 跳转云存档详情页 @JvmStatic - fun directToCloudArchive(context: Context, gameId: String, gameName: String, configUrl: String = "", entrance: String = "") { + fun directToCloudArchive( + context: Context, + gameId: String, + gameName: String, + configUrl: String = "", + entrance: String = "" + ) { val bundle = Bundle() val gameEntity = GameEntity(id = gameId, name = gameName) bundle.putParcelable(KEY_GAME_ENTITY, gameEntity) @@ -2324,4 +2333,14 @@ object DirectUtils { bundle.putBoolean(KEY_USE_ALTERNATIVE_LAYOUT, true) context.startActivity(ShellActivity.getIntent(context, Type.CLOUD_ARCHIVE, bundle)) } + + @JvmStatic + fun navigateToMyAssetsPage(context: Context, entrance: String?) { + if (CheckLoginUtils.isLogin()) { + TheRouter.build(RouteConsts.activity.myAssetsActivity).navigation(context) + } else { + CheckLoginUtils.checkLogin(context, entrance, null) + } + + } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/util/DownloadItemUtils.kt b/app/src/main/java/com/gh/common/util/DownloadItemUtils.kt index 4cd3abc103..0fb715e969 100644 --- a/app/src/main/java/com/gh/common/util/DownloadItemUtils.kt +++ b/app/src/main/java/com/gh/common/util/DownloadItemUtils.kt @@ -176,7 +176,8 @@ object DownloadItemUtils { pluginLocation: PluginLocation? = PluginLocation.only_game, hideDownloadBtnIfNoAvailableContent: Boolean = false, briefStyle: String? = null, - isShowRecommendStar: Boolean = false + isShowRecommendStar: Boolean = false, + listener: DownloadButton.OnUpdateListener? = null ) { holder.gameDownloadBtn.putObject(gameEntity) @@ -189,7 +190,8 @@ object DownloadItemUtils { holder.gameDownloadBtn, gameEntity, hideDownloadBtnIfNoAvailableContent, - pluginLocation + pluginLocation, + listener ) return } @@ -210,7 +212,8 @@ object DownloadItemUtils { holder.gameDownloadBtn, gameEntity, hideDownloadBtnIfNoAvailableContent, - pluginLocation + pluginLocation, + listener ) } @@ -219,7 +222,8 @@ object DownloadItemUtils { downloadBtn: DownloadButton, gameEntity: GameEntity, hideDownloadBtnIfNoAvailableContent: Boolean = false, - pluginLocation: PluginLocation? = PluginLocation.only_game + pluginLocation: PluginLocation? = PluginLocation.only_game, + listener: DownloadButton.OnUpdateListener? ) { // 控制是否显示下载按钮 downloadBtn.goneIf(context.getString(R.string.app_name) == gameEntity.name) @@ -227,6 +231,7 @@ object DownloadItemUtils { if (SPUtils.getBoolean(Constants.SP_TEENAGER_MODE) || gameEntity.isSpecialDownload()) { downloadBtn.text = "查看" downloadBtn.buttonStyle = DownloadButton.ButtonStyle.TEENAGER_MODE + listener?.completion(downloadBtn.text) return } if (gameEntity.isReservable) { @@ -239,6 +244,7 @@ object DownloadItemUtils { buttonStyle = DownloadButton.ButtonStyle.RESERVED } } + listener?.completion(downloadBtn.text) return } if (RegionSettingHelper.getGameH5DownloadByGameId(gameEntity.id) != null) { @@ -248,6 +254,7 @@ object DownloadItemUtils { setBackgroundResource(com.gh.gamecenter.common.R.drawable.download_button_normal_style) setTextColor(com.gh.gamecenter.common.R.color.white.toColor(context)) } + listener?.completion(downloadBtn.text) return } if (gameEntity.isMiniGame()) { @@ -265,6 +272,7 @@ object DownloadItemUtils { text = context.getString(com.gh.gamecenter.feature.R.string.quick_play) } } + listener?.completion(downloadBtn.text) return } if (gameEntity.getApk().isEmpty() || gameEntity.downloadOffStatus != null) { @@ -296,6 +304,7 @@ object DownloadItemUtils { downloadBtn.isClickable = false } } + listener?.completion(downloadBtn.text) } else if (gameEntity.getApk().size == 1) { // 来自于下载管理的实体快照 val entityFromDownloadManager = DownloadManager.getInstance().getDownloadEntitySnapshot(gameEntity) @@ -354,43 +363,60 @@ object DownloadItemUtils { downloadBtn.apply { when (downloadEntity.status) { DownloadStatus.done -> { - if (downloadEntity.isSimulatorGame() && gameEntity.simulator != null) { - GameUtils.setDownloadBtnStatus(context, gameEntity, downloadBtn, pluginLocation) - } else if (isVGamePreferred) { - buttonStyle = - if (PackagesManager.isCanUpdate( - downloadEntity.gameId, - downloadEntity.packageName, - asVGame = true - ) - ) { - setText(com.gh.gamecenter.feature.R.string.update) - DownloadButton.ButtonStyle.NORMAL - } else { - setText(com.gh.gamecenter.feature.R.string.launch) - DownloadButton.ButtonStyle.LAUNCH_OR_OPEN - } - } else { - val xapkStatus = downloadEntity.meta[XapkInstaller.XAPK_UNZIP_STATUS] - if (XapkUnzipStatus.SUCCESS.name == xapkStatus && isInstalling(downloadEntity.path)) { + val xapkStatus = downloadEntity.meta[XapkInstaller.XAPK_UNZIP_STATUS] + when { + downloadEntity.isSimulatorGame() && gameEntity.simulator != null -> { + GameUtils.setDownloadBtnStatus( + context, + gameEntity, + downloadBtn, + pluginLocation, + listener + ) + } + + isVGamePreferred -> { + buttonStyle = + if (PackagesManager.isCanUpdate( + downloadEntity.gameId, + downloadEntity.packageName, + asVGame = true + ) + ) { + setText(com.gh.gamecenter.feature.R.string.update) + DownloadButton.ButtonStyle.NORMAL + } else { + setText(com.gh.gamecenter.feature.R.string.launch) + DownloadButton.ButtonStyle.LAUNCH_OR_OPEN + } + listener?.completion(downloadBtn.text) + } + + XapkUnzipStatus.SUCCESS.name == xapkStatus && isInstalling(downloadEntity.path) -> { progress = 100 setText(com.gh.gamecenter.feature.R.string.installing) buttonStyle = DownloadButton.ButtonStyle.INSTALL_NORMAL + listener?.completion(downloadBtn.text) return } - if (XapkUnzipStatus.UNZIPPING.name == xapkStatus) { + + XapkUnzipStatus.UNZIPPING.name == xapkStatus -> { val percent = downloadEntity.meta[XapkInstaller.XAPK_UNZIP_PERCENT] progress = (java.lang.Float.valueOf(percent) * 10).toInt() text = "$percent%" buttonStyle = DownloadButton.ButtonStyle.XAPK_UNZIPPING - return - } else if (XapkUnzipStatus.FAILURE.name == xapkStatus) { - setText(com.gh.gamecenter.feature.R.string.install) - buttonStyle = DownloadButton.ButtonStyle.INSTALL_NORMAL + listener?.completion(downloadBtn.text) return } - if (PackagesManager.isInstalled(downloadEntity.packageName) && !downloadEntity.isUpdate) { + XapkUnzipStatus.FAILURE.name == xapkStatus -> { + setText(com.gh.gamecenter.feature.R.string.install) + buttonStyle = DownloadButton.ButtonStyle.INSTALL_NORMAL + listener?.completion(downloadBtn.text) + return + } + + PackagesManager.isInstalled(downloadEntity.packageName) && !downloadEntity.isUpdate -> { // 双下载按钮快速安装时存在已下载的安装包过时,需要重新下载的情况 if (PackagesManager.isCanUpdate( downloadEntity.gameId, @@ -403,9 +429,13 @@ object DownloadItemUtils { buttonStyle = DownloadButton.ButtonStyle.LAUNCH_OR_OPEN setText(com.gh.gamecenter.feature.R.string.launch) } - } else { + listener?.completion(downloadBtn.text) + } + + else -> { buttonStyle = DownloadButton.ButtonStyle.INSTALL_NORMAL setText(com.gh.gamecenter.feature.R.string.install) + listener?.completion(downloadBtn.text) } } buttonStyle = @@ -425,22 +455,24 @@ object DownloadItemUtils { DownloadStatus.overflow -> { buttonStyle = DownloadButton.ButtonStyle.NORMAL setText(com.gh.gamecenter.feature.R.string.resume) + listener?.completion(downloadBtn.text) } DownloadStatus.cancel -> { - GameUtils.setDownloadBtnStatus(context, gameEntity, downloadBtn, pluginLocation) + GameUtils.setDownloadBtnStatus(context, gameEntity, downloadBtn, pluginLocation, listener) } else -> { // do nothing + listener?.completion(downloadBtn.text) } } } } else { - GameUtils.setDownloadBtnStatus(context, gameEntity, downloadBtn, pluginLocation) + GameUtils.setDownloadBtnStatus(context, gameEntity, downloadBtn, pluginLocation, listener) } } else { - GameUtils.setDownloadBtnStatus(context, gameEntity, downloadBtn, pluginLocation) + GameUtils.setDownloadBtnStatus(context, gameEntity, downloadBtn, pluginLocation, listener) } } @@ -510,13 +542,18 @@ object DownloadItemUtils { DownloadStatus.downloading -> { if (isMultiVersion) { holder.gameDownloadBtn.buttonStyle = DownloadButton.ButtonStyle.NORMAL - val darkMode = (holder.gameDownloadTips?.getTag(com.gh.gamecenter.common.R.string.is_dark_mode_on_id) as? Boolean) ?: false + val darkMode = + (holder.gameDownloadTips?.getTag(com.gh.gamecenter.common.R.string.is_dark_mode_on_id) as? Boolean) + ?: false val isDarkModeChanged = DarkModeUtils.isDarkModeOn(context) != darkMode if (holder.gameDownloadTips?.visibility == View.GONE || holder.gameDownloadTips?.isAnimating == false || isDarkModeChanged) { holder.gameDownloadTips?.visibility = View.VISIBLE holder.gameDownloadTips?.setDownloadTipsAnimation(true) } - holder.gameDownloadTips?.setTag(com.gh.gamecenter.common.R.string.is_dark_mode_on_id, DarkModeUtils.isDarkModeOn(context)) + holder.gameDownloadTips?.setTag( + com.gh.gamecenter.common.R.string.is_dark_mode_on_id, + DarkModeUtils.isDarkModeOn(context) + ) } else { holder.gameDownloadTips?.visibility = View.GONE holder.gameDownloadBtn.buttonStyle = DownloadButton.ButtonStyle.DOWNLOADING_NORMAL @@ -961,7 +998,8 @@ object DownloadItemUtils { traceEvent: ExposureEvent? = null, refreshCallback: EmptyCallback? = null ) { - val str = if (downloadBtn is DownloadButton) downloadBtn.text else context.getString(com.gh.gamecenter.feature.R.string.download) + val str = + if (downloadBtn is DownloadButton) downloadBtn.text else context.getString(com.gh.gamecenter.feature.R.string.download) if (gameEntity.getApk().isEmpty()) return val apk = gameEntity.getApk().safelyGetInRelease(0) ?: return @@ -980,7 +1018,16 @@ object DownloadItemUtils { addHandler(CheckDownloadHandler()) } .setProcessEndCallback(gameEntity.id) { asVGame, isSubscribe -> - download(context, gameEntity, downloadBtn, entrance, location, asVGame, isSubscribe as Boolean, traceEvent) + download( + context, + gameEntity, + downloadBtn, + entrance, + location, + asVGame, + isSubscribe as Boolean, + traceEvent + ) } .buildHandlerChain() ?.handleRequest(context, gameEntity, shouldPerformAsVGame) @@ -999,7 +1046,16 @@ object DownloadItemUtils { addHandler(CheckDownloadHandler()) } .setProcessEndCallback(gameEntity.id) { asVGame, isSubscribe -> - download(context, gameEntity, downloadBtn, entrance, location, asVGame, isSubscribe as Boolean, traceEvent) + download( + context, + gameEntity, + downloadBtn, + entrance, + location, + asVGame, + isSubscribe as Boolean, + traceEvent + ) } .buildHandlerChain() ?.handleRequest(context, gameEntity, shouldPerformAsVGame) @@ -1018,7 +1074,16 @@ object DownloadItemUtils { addHandler(CheckDownloadHandler()) } .setProcessEndCallback(gameEntity.id) { asVGame, isSubscribe -> - download(context, gameEntity, downloadBtn, entrance, location, asVGame, isSubscribe as Boolean, traceEvent) + download( + context, + gameEntity, + downloadBtn, + entrance, + location, + asVGame, + isSubscribe as Boolean, + traceEvent + ) } .buildHandlerChain() ?.handleRequest(context, gameEntity, shouldPerformAsVGame) diff --git a/app/src/main/java/com/gh/common/util/GameUtils.kt b/app/src/main/java/com/gh/common/util/GameUtils.kt index 5419d343da..806c15ad73 100644 --- a/app/src/main/java/com/gh/common/util/GameUtils.kt +++ b/app/src/main/java/com/gh/common/util/GameUtils.kt @@ -25,7 +25,10 @@ object GameUtils { /** * 去除与重复sourceList相同的数据 */ - fun removeDuplicateData(sourceList: MutableList?, rawList: MutableList?): MutableList? { + fun removeDuplicateData( + sourceList: MutableList?, + rawList: MutableList? + ): MutableList? { if (sourceList.isNullOrEmpty() || rawList.isNullOrEmpty()) { return rawList } @@ -52,7 +55,8 @@ object GameUtils { context: Context, gameEntity: GameEntity, downloadBtn: DownloadButton, - pluginLocation: PluginLocation? + pluginLocation: PluginLocation?, + listener: DownloadButton.OnUpdateListener? = null ) { // getDownloadBtnText 里包括查询数据库、根据包名读取包体 meta 信息等 lightWeightIoExecutor.execute { @@ -73,6 +77,7 @@ object GameUtils { } else { downloadBtn.buttonStyle = DownloadButton.ButtonStyle.NORMAL } + listener?.completion(downloadBtn.text) } } } @@ -85,11 +90,13 @@ object GameUtils { */ @WorkerThread @JvmStatic - fun getDownloadBtnText(context: Context, - gameEntity: GameEntity, - isFromList: Boolean, - fixedAsVGame: Boolean, - pluginLocation: PluginLocation?): String { + fun getDownloadBtnText( + context: Context, + gameEntity: GameEntity, + isFromList: Boolean, + fixedAsVGame: Boolean, + pluginLocation: PluginLocation? + ): String { if (gameEntity.getApk().size > 1) { return "" } @@ -138,7 +145,8 @@ object GameUtils { } else if (!isFromList) { if (!performAsVGame && gameEntity.isDualBtnModeEnabled() - && downloadEntity?.isVGameDownloadInDualDownloadMode() == true) { + && downloadEntity?.isVGameDownloadInDualDownloadMode() == true + ) { // 下载的任务是由畅玩触发的,并且双下载按钮启用,游戏详情页不需判定为需要安装 downloadEntity = null } else if (performAsVGame && downloadEntity?.isLocalDownloadInDualDownloadMode() == true) { diff --git a/app/src/main/java/com/gh/common/util/TempCertificationUtils.kt b/app/src/main/java/com/gh/common/util/TempCertificationUtils.kt index d9e217f39d..7cac8c17ef 100644 --- a/app/src/main/java/com/gh/common/util/TempCertificationUtils.kt +++ b/app/src/main/java/com/gh/common/util/TempCertificationUtils.kt @@ -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 + } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/SplashScreenActivity.kt b/app/src/main/java/com/gh/gamecenter/SplashScreenActivity.kt index 81c72a3567..d593350b69 100644 --- a/app/src/main/java/com/gh/gamecenter/SplashScreenActivity.kt +++ b/app/src/main/java/com/gh/gamecenter/SplashScreenActivity.kt @@ -14,8 +14,8 @@ 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.therouter.router.Route import com.gh.common.dialog.NewPrivacyPolicyDialogFragment import com.gh.common.util.DeviceTokenUtils import com.gh.common.util.DialogUtils @@ -91,7 +91,8 @@ class SplashScreenActivity : BaseActivity(), ISplashScreen { } else { val spanBuilder = buildSpannedString { append("这个弹窗只会在右上角有环境标签的测试包出现" + - "\n进入应用以后还可以到关于我们页面长按应用图标重新选择") + "\n进入应用以后还可以到关于我们页面长按应用图标重新选择" + ) bold { color(com.gh.gamecenter.common.R.color.text_theme.toColor(this@SplashScreenActivity)) { append("\n点击这里进行预设置渠道") diff --git a/app/src/main/java/com/gh/gamecenter/adapter/viewholder/DetailViewHolder.kt b/app/src/main/java/com/gh/gamecenter/adapter/viewholder/DetailViewHolder.kt index 54fe174f95..f7e95d5636 100644 --- a/app/src/main/java/com/gh/gamecenter/adapter/viewholder/DetailViewHolder.kt +++ b/app/src/main/java/com/gh/gamecenter/adapter/viewholder/DetailViewHolder.kt @@ -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 @@ -44,6 +48,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.GameDetailViewModel +import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper import com.gh.gamecenter.gamedetail.dialog.GamePermissionDialogFragment import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity import com.gh.gamecenter.teenagermode.TeenagerModeActivity.Companion.getIntent @@ -63,7 +68,8 @@ class DetailViewHolder( name: String?, title: String?, val traceEvent: ExposureEvent?, - val isSupportDualButton: Boolean = false, // 是否支持双下载按钮,不支持的时候跟普通列表意义选用优先级高的那个来显示 + val isSupportDualButton: Boolean = false, // 是否支持双下载按钮,不支持的时候跟普通列表意义选用优先级高的那个来显示, + val acceleratorUiHelper: GameDetailAcceleratorUiHelper? = null, // 网速加速,只有游戏详情才有 onDownloadClickAction: ((Boolean) -> Unit)? = null ) { var context: Context @@ -92,6 +98,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) @@ -106,6 +118,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() @@ -162,7 +178,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("游戏详情页") @@ -180,6 +198,33 @@ class DetailViewHolder( gamePermissionDialogFragment?.dismissAllowingStateLoss() } + + fun checkIfShowSpeedUi(rawBtnText: String) { + acceleratorUiHelper?.let { + when { + rawBtnText == "启动" && gameEntity.canSpeed -> { + downloadPb.goneIf(true) + localDownloadButton?.goneIf(true) + it.checkIfShowSpeedUi(true) + } + + rawBtnText == "更新" && gameEntity.canSpeed -> { + it.checkIfShowSpeedUi(true) + } + + else -> { + it.checkIfShowSpeedUi(false) + } + } + } + + } + + fun setSpeedViewsVisible(isVisible: Boolean) { + ivFreeVipTag?.goneIf(!isVisible) + speedContainer?.goneIf(!isVisible) + } + internal class OnDetailDownloadClickListener( private val mViewHolder: DetailViewHolder, private val mEntrance: String?, @@ -286,11 +331,13 @@ class DetailViewHolder( showLandPageAddressDialogIfNeeded() } } + "toast" -> { mViewHolder.viewModel?.performTabSelected(GameDetailTabEntity.TYPE_COMMENT) ToastUtils.toast("该游戏因故暂不提供下载,具体详情可在相关评论中查看,敬请谅解~") showLandPageAddressDialogIfNeeded() } + "third_party" -> { showLandPageAddressDialogIfNeeded() } @@ -339,7 +386,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( @@ -600,8 +659,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) @@ -710,11 +770,16 @@ class DetailViewHolder( private fun performDownloadClickAction() { val buttonText = if (mShowDualDownloadButton && !mAsVGame) { - mViewHolder.localDownloadTitleTv?.text?.ifEmpty { mViewHolder.downloadPb.text.ifEmpty { mViewHolder.overlayTv?.text ?: "" } } + mViewHolder.localDownloadTitleTv?.text?.ifEmpty { + mViewHolder.downloadPb.text.ifEmpty { + mViewHolder.overlayTv?.text ?: "" + } + } } else { mViewHolder.downloadPb.text.ifEmpty { mViewHolder.overlayTv?.text ?: "" } } - val isUpdate = buttonText?.contains(mViewHolder.context.getString(com.gh.gamecenter.feature.R.string.update)) == true + val isUpdate = + buttonText?.contains(mViewHolder.context.getString(com.gh.gamecenter.feature.R.string.update)) == true onDownloadClickAction?.invoke(isUpdate) } } diff --git a/app/src/main/java/com/gh/gamecenter/cloudarchive/CloudArchiveManagerActivity.kt b/app/src/main/java/com/gh/gamecenter/cloudarchive/CloudArchiveManagerActivity.kt index 2c96ea5843..97786122ee 100644 --- a/app/src/main/java/com/gh/gamecenter/cloudarchive/CloudArchiveManagerActivity.kt +++ b/app/src/main/java/com/gh/gamecenter/cloudarchive/CloudArchiveManagerActivity.kt @@ -216,7 +216,7 @@ class CloudArchiveManagerActivity : BaseActivity_TabLayout(), ArchiveLimitSelect private fun initDownloadBtn() { mGameEntity?.let { gameEntity -> - DownloadItemUtils.updateDownloadButton(this, mBinding.downloadBtn, gameEntity) + DownloadItemUtils.updateDownloadButton(this, mBinding.downloadBtn, gameEntity, listener = null) DownloadItemUtils.setOnClickListener(this, mBinding.downloadBtn, gameEntity, 0, null, mEntrance, "") } } diff --git a/app/src/main/java/com/gh/gamecenter/eventbus/EBStartupAcceleration.kt b/app/src/main/java/com/gh/gamecenter/eventbus/EBStartupAcceleration.kt new file mode 100644 index 0000000000..50e0b6ad94 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/eventbus/EBStartupAcceleration.kt @@ -0,0 +1,5 @@ +package com.gh.gamecenter.eventbus + +import com.gh.gamecenter.feature.entity.AcctGameInfo + +class EBStartupAcceleration(val acctGameInfo: AcctGameInfo) \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/AcceleratorZoneViewModel.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/AcceleratorZoneViewModel.kt new file mode 100644 index 0000000000..e1aab2c5cb --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/AcceleratorZoneViewModel.kt @@ -0,0 +1,13 @@ +package com.gh.gamecenter.gamedetail + +import androidx.lifecycle.ViewModel +import com.halo.assistant.accelerator.AccelerationUseCase + +class AcceleratorZoneViewModel : ViewModel() { + + val useCase = AccelerationUseCase() + + override fun onCleared() { + useCase.onClear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailViewModel.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailViewModel.kt index fad10e0cb6..9d935bb95a 100644 --- a/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailViewModel.kt @@ -8,10 +8,7 @@ import android.os.Build import android.text.TextUtils import android.util.SparseBooleanArray import androidx.collection.arrayMapOf -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 @@ -40,15 +37,20 @@ import com.gh.gamecenter.gamedetail.detail.viewholder.GameDetailContentRecommend import com.gh.gamecenter.gamedetail.entity.* import com.gh.gamecenter.livedata.Event import com.gh.gamecenter.login.user.UserManager +import com.gh.gamecenter.login.user.UserRepository import com.gh.gamecenter.manager.PackagesManager import com.gh.gamecenter.retrofit.RetrofitManager import com.gh.vspace.VHelper import com.google.gson.JsonArray import com.google.gson.reflect.TypeToken import com.halo.assistant.HaloApp +import com.halo.assistant.accelerator.repository.AccelerationRepository +import com.halo.assistant.accelerator.repository.AcceleratorDataHolder import com.lightgame.utils.Utils import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import okhttp3.ResponseBody import org.json.JSONObject @@ -64,6 +66,7 @@ class GameDetailViewModel( private val api = RetrofitManager.getInstance().api private val newApi = RetrofitManager.getInstance().newApi + private val accelerationRepository = AccelerationRepository.newInstance() val concernLiveData = MutableLiveData() val gameLiveData = MutableLiveData?>() @@ -127,6 +130,8 @@ class GameDetailViewModel( var isGameInstalled = false private var isGameUpdatable = false + private val compositeDisposable = CompositeDisposable() + init { loadData() } @@ -138,11 +143,6 @@ class GameDetailViewModel( } when { - game != null -> { - gameLiveData.postValue(Resource.success(game)) - loadGameDetailData() - } - gameId != null -> getGameDigest() else -> gameLiveData.postValue(null) } @@ -188,6 +188,13 @@ class GameDetailViewModel( game = response gameLiveData.postValue(Resource.success(game)) loadGameDetailData() + if (game?.canSpeed == true) { + // 如果当前游戏支持加速,则刷新vip状态 + val userId = UserManager.getInstance().userId + if (userId.isNotBlank()) { + UserRepository.getInstance().refreshVipStatus(userId, true) + } + } } override fun onFailure(e: HttpException?) { @@ -196,6 +203,7 @@ class GameDetailViewModel( }) } + /** * 过滤被屏蔽的内容 */ @@ -205,7 +213,10 @@ class GameDetailViewModel( if (ContentBlockedHelper.isContentBlocked(item.type)) { continue } - if (item.type == GameDetailData.TYPE_BASIC_INFO && ContentBlockedHelper.isContentBlocked(ContentBlockedHelper.GAME_TAG)) { + if (item.type == GameDetailData.TYPE_BASIC_INFO && ContentBlockedHelper.isContentBlocked( + ContentBlockedHelper.GAME_TAG + ) + ) { item.linkBasicInfo?.gameTags = arrayListOf() } finalItemList.add(item) @@ -445,7 +456,8 @@ class GameDetailViewModel( data.linkVideoImgArea != null -> { val coverList = transformVideoImgAreaData(data.linkVideoImgArea) if (coverList.isNotEmpty()) { - defaultCoverEntity = coverList.find { coverEntity -> coverEntity.isDefault } ?: coverList.first() + defaultCoverEntity = + coverList.find { coverEntity -> coverEntity.isDefault } ?: coverList.first() coverListLiveData.postValue(coverList) } } @@ -1011,6 +1023,61 @@ class GameDetailViewModel( }) } + private val _lastAcctGame = MutableLiveData() + val lastAcctGame: LiveData = _lastAcctGame + + private val _hasAnyRecord = MutableLiveData() + val hasAnyRecord: LiveData = _hasAnyRecord + + fun loadRecordDataInDatabase(gameId: String) { + accelerationRepository.getLastAcctGameInfoObservable(gameId) + .compose(observableToMain()) + .subscribe(object : Response>() { + override fun onSubscribe(d: Disposable) { + super.onSubscribe(d) + compositeDisposable.add(d) + } + + override fun onResponse(response: List?) { + super.onResponse(response) + _lastAcctGame.value = response?.firstOrNull() + } + + override fun onFailure(e: HttpException?) { + super.onFailure(e) + _lastAcctGame.value = null + } + }) + + + accelerationRepository.getHasAnyAcctRecordObservable() + .compose(observableToMain()) + .subscribe( + object : Response() { + override fun onSubscribe(d: Disposable) { + super.onSubscribe(d) + compositeDisposable.add(d) + } + + override fun onResponse(response: Boolean?) { + super.onResponse(response) + _hasAnyRecord.value = response ?: false + } + + override fun onFailure(e: HttpException?) { + super.onFailure(e) + _hasAnyRecord.value = false + } + }) + + + } + + override fun onCleared() { + super.onCleared() + compositeDisposable.clear() + } + class Factory( private val application: Application, private val gameId: String?, diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailWrapperFragment.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailWrapperFragment.kt index 6166611fed..41ec944c19 100644 --- a/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailWrapperFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailWrapperFragment.kt @@ -66,6 +66,7 @@ import com.gh.gamecenter.feature.entity.SimpleGame import com.gh.gamecenter.feature.eventbus.EBConcernChanged import com.gh.gamecenter.feature.exposure.ExposureEvent import com.gh.gamecenter.feature.utils.ApkActiveUtils +import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper import com.gh.gamecenter.gamedetail.cloudarchive.CloudArchiveFragment import com.gh.gamecenter.gamedetail.detail.GameDetailFragment import com.gh.gamecenter.gamedetail.dialog.GameDetailMoreDialog @@ -95,7 +96,7 @@ import org.greenrobot.eventbus.ThreadMode import retrofit2.HttpException import java.util.* -class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { +class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable { private val binding by lazy { FragmentGameDetailWrapperBinding.inflate(layoutInflater) } private val bodyBinding by lazy { binding.bodyContainer } private val downloadBinding by lazy { binding.detailLlBottom } @@ -103,6 +104,7 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { private lateinit var viewModel: GameDetailViewModel private val packageViewModel: PackageViewModel by lazy { viewModelProvider(PackageViewModel.Factory()) } private val userViewModel: UserViewModel by lazy { viewModelProvider(UserViewModel.Factory(HaloApp.getInstance().application)) } + private val acceleratorUiHelper: GameDetailAcceleratorUiHelper by lazy { GameDetailAcceleratorUiHelper(binding.detailLlBottom) } private var moreMenuItem: MenuItem? = null private var downloadMenuItem: MenuItem? = null @@ -243,6 +245,7 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { name = "游戏详情", title = gameEntity!!.name ?: "", traceEvent = traceEvent, + acceleratorUiHelper = acceleratorUiHelper, isSupportDualButton = true ) { viewModel.isDownloadClick = true @@ -341,6 +344,7 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { "is_download" to viewModel.isDownloadClick }) } + acceleratorUiHelper.clear() } private fun initMenu() { @@ -386,6 +390,7 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { if (gameResource.status == Status.SUCCESS) { viewModel.logHistory(gameResource.data!!) gameEntity = gameResource.data + initAccelerator() showBrowserInstallHintIfNeeded() controlInstallHint() // 添加启动弹窗的相关信息 @@ -407,6 +412,9 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { controlReserveBtn() gameEntity?.run { showVModeIconAtBottomBarIfNeeded(it.smoothRelatedGame, this) } showConcernIconAtBottomBarIfAvailable() + // 底部按钮尺寸已确定,可以显示按钮上的引导页 + acceleratorUiHelper.isButtonSizeDetermined = true + acceleratorUiHelper.showGuideLayerIfNeed() } viewModel.gameDetailTabListLiveData.observeNonNull(viewLifecycleOwner) { if (it.status == Status.SUCCESS) { @@ -429,20 +437,34 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { SensorsBridge.trackEventWithExposureSource( "GameDetailPageShow", traceEvent?.source ?: listOf(ExposureSource(mEntrance)), - "game_id", gameEntity?.id ?: "", - "game_name", gameEntity?.name ?: "", - "download_status", gameEntity?.downloadStatusChinese ?: "", - "cloud_save_tab_status", if (data.find { tabEntity -> tabEntity.type == GameDetailTabEntity.TYPE_ARCHIVE } != null) "开启" else "关闭", - "game_type", gameEntity?.categoryChinese ?: "", - "page_name", getCurrentPageEntity().pageName, - "page_id", getCurrentPageEntity().pageId, - "page_business_id", getCurrentPageEntity().pageBusinessId, - "last_page_name", getLastPageEntity().pageName, - "last_page_id", getLastPageEntity().pageId, - "last_page_business_id", getLastPageEntity().pageBusinessId, - "button_name", buttonName, - "game_schema_type", gameEntity?.gameBitChinese ?: "", - "download_type", gameEntity?.downloadType ?: "", + "game_id", + gameEntity?.id ?: "", + "game_name", + gameEntity?.name ?: "", + "download_status", + gameEntity?.downloadStatusChinese ?: "", + "cloud_save_tab_status", + if (data.find { tabEntity -> tabEntity.type == GameDetailTabEntity.TYPE_ARCHIVE } != null) "开启" else "关闭", + "game_type", + gameEntity?.categoryChinese ?: "", + "page_name", + getCurrentPageEntity().pageName, + "page_id", + getCurrentPageEntity().pageId, + "page_business_id", + getCurrentPageEntity().pageBusinessId, + "last_page_name", + getLastPageEntity().pageName, + "last_page_id", + getLastPageEntity().pageId, + "last_page_business_id", + getLastPageEntity().pageBusinessId, + "button_name", + buttonName, + "game_schema_type", + gameEntity?.gameBitChinese ?: "", + "download_type", + gameEntity?.downloadType ?: "", *(traceEvent?.additional ?: emptyArray()) ) }, 120) @@ -472,7 +494,8 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { val downloadEntitySnapshot = DownloadManager.getInstance().getDownloadEntitySnapshot(gameEntity) if (isSpecialDownloadDialogAvailable(downloadEntitySnapshot) - && downloadEntitySnapshot != null) { + && downloadEntitySnapshot != null + ) { updateSpecialDownloadDialogIcon(true) } binding.expandSpecialDownloadIv.setOnClickListener { @@ -553,6 +576,24 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { } } + private fun initAccelerator() { + gameEntity?.let { game -> + if (game.canSpeed) { + acceleratorUiHelper.initUi(game, requireContext()) + + viewModel.lastAcctGame.observe(viewLifecycleOwner) { + acceleratorUiHelper.setCurrentAcctGameInfo(it) + } + + viewModel.hasAnyRecord.observe(viewLifecycleOwner) { + acceleratorUiHelper.setHasAnyAcctRecord(it) + } + + viewModel.loadRecordDataInDatabase(game.id) + } + } + } + private fun updateArchiveTabUI(hasNew: Boolean) { if (hasNew) { val archivePosition = tabEntityList.indexOfFirst { it.type == GameDetailTabEntity.TYPE_ARCHIVE } @@ -597,7 +638,8 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { } private fun doShowAlertDialog(dialog: GameEntity.Dialog) { - SensorsBridge.trackEvent("GameDetailDialogShow", + SensorsBridge.trackEvent( + "GameDetailDialogShow", "game_id", gameEntity?.id ?: "", "game_name", gameEntity?.name ?: "", "game_type", gameEntity?.categoryChinese ?: "" @@ -609,7 +651,8 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { dialog.confirmButton.text.toString(), dialog.closeButtonText, { - SensorsBridge.trackEvent("GameDetailDialogClick", + SensorsBridge.trackEvent( + "GameDetailDialogClick", "game_id", gameEntity?.id ?: "", "game_name", gameEntity?.name ?: "", "game_type", gameEntity?.categoryChinese ?: "", @@ -622,7 +665,8 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { DirectUtils.directToLinkPage(requireContext(), dialog.confirmButton, mEntrance, "") }, { - SensorsBridge.trackEvent("GameDetailDialogClick", + SensorsBridge.trackEvent( + "GameDetailDialogClick", "game_id", gameEntity?.id ?: "", "game_name", gameEntity?.name ?: "", "game_type", gameEntity?.categoryChinese ?: "", @@ -630,7 +674,8 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { ) }, { - SensorsBridge.trackEvent("GameDetailDialogClick", + SensorsBridge.trackEvent( + "GameDetailDialogClick", "game_id", gameEntity?.id ?: "", "game_name", gameEntity?.name ?: "", "game_type", gameEntity?.categoryChinese ?: "", @@ -654,7 +699,10 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { EntranceConsts.KEY_SCROLL_TO_LIBAO, arguments?.getBoolean(EntranceConsts.KEY_SCROLL_TO_LIBAO) ?: false ) - bundle.putBoolean(EntranceConsts.KEY_SCROLL_TO_SERVER, arguments?.getBoolean(EntranceConsts.KEY_SCROLL_TO_SERVER) ?: false) + bundle.putBoolean( + EntranceConsts.KEY_SCROLL_TO_SERVER, + arguments?.getBoolean(EntranceConsts.KEY_SCROLL_TO_SERVER) ?: false + ) bundle.putBoolean( EntranceConsts.KEY_OPEN_VIDEO_STREAMING, arguments?.getBoolean(EntranceConsts.KEY_OPEN_VIDEO_STREAMING) ?: false @@ -664,19 +712,23 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { fragment = fragment ?: GameDetailFragment() bundle.putAll(arguments) } + GameDetailTabEntity.TYPE_ARCHIVE -> { fragment = fragment ?: CloudArchiveFragment() bundle.putParcelable(EntranceConsts.KEY_GAME, gameEntity ?: GameEntity()) bundle.putString(EntranceConsts.KEY_ARCHIVE_CONFIG_URL, tabEntity.archiveConfigUrl) } + GameDetailTabEntity.TYPE_COMMENT -> { fragment = fragment ?: RatingFragment() bundle.putBoolean(EntranceConsts.KEY_COMMENT_AS_DEFAULT_TAB, skipGameComment) bundle.putBoolean(EntranceConsts.KEY_DIRECT_COMMENT, gameEntity?.directComment ?: false) } + GameDetailTabEntity.TYPE_BBS -> { fragment = fragment ?: Fragment() } + GameDetailTabEntity.TYPE_ZONE -> { val zone = gameEntity?.zone if (zone?.style == "link") { @@ -688,16 +740,19 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { fragment = fragment ?: FuLiFragment() } } + GameDetailTabEntity.TYPE_WEB -> { fragment = fragment ?: WebFragment() bundle.putString(EntranceConsts.KEY_URL, tabEntity.link?.link) } + GameDetailTabEntity.TYPE_GIFT -> { fragment = fragment ?: LibaoListFragment() gameEntity?.let { bundle.putAll(LibaoListFragment.getBundle(it, true)) } } + GameDetailTabEntity.TYPE_CUSTOM_PAGE -> { fragment = fragment ?: CustomPageFragment() bundle.putString(EntranceConsts.KEY_CUSTOM_PAGE_ID, tabEntity.link?.link) @@ -831,7 +886,8 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { val tabItemBinding = GameDetailTabItemBinding.inflate(layoutInflater).apply { tabTitle.text = tabEntity?.name if (tabEntity?.type == GameDetailTabEntity.TYPE_ARCHIVE) { - val hasEnterArchiveGameDetail = SPUtils.getBoolean(SP_HAS_ENTER_ARCHIVE_GAME_DETAIL_PREFIX + (gameEntity?.id ?: "")) + val hasEnterArchiveGameDetail = + SPUtils.getBoolean(SP_HAS_ENTER_ARCHIVE_GAME_DETAIL_PREFIX + (gameEntity?.id ?: "")) val hasNewArchive = viewModel.archiveStatusLiveData.value ?: false newIv.goneIf(hasEnterArchiveGameDetail && !hasNewArchive) { if (i == tabEntityList.size - 1) { @@ -859,9 +915,11 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { override fun onTabSelected(tab: TabLayout.Tab) { updateTabStyle(tab, true) } + override fun onTabUnselected(tab: TabLayout.Tab) { updateTabStyle(tab, false) } + override fun onTabReselected(tab: TabLayout.Tab) { updateTabStyle(tab, true) } @@ -869,9 +927,12 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { } private fun updateTabStyle(tab: TabLayout.Tab, isChecked: Boolean) { - tab.customView?.findViewById(R.id.tab_title)?.setTypeface(if (isChecked) Typeface.DEFAULT_BOLD else Typeface.DEFAULT) + tab.customView?.findViewById(R.id.tab_title) + ?.setTypeface(if (isChecked) Typeface.DEFAULT_BOLD else Typeface.DEFAULT) tab.customView?.findViewById(R.id.tab_title)?.setTextColor( - if (isChecked) com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()) else com.gh.gamecenter.common.R.color.text_tertiary.toColor(requireContext()) + if (isChecked) com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()) else com.gh.gamecenter.common.R.color.text_tertiary.toColor( + requireContext() + ) ) } @@ -1187,7 +1248,8 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { fixedAsVGame = false, pluginLocation = PluginLocation.only_game ) - val isLaunch = (status == getString(com.gh.gamecenter.feature.R.string.launch) || status == getString(com.gh.gamecenter.feature.R.string.open)) + val isLaunch = + (status == getString(com.gh.gamecenter.feature.R.string.launch) || status == getString(com.gh.gamecenter.feature.R.string.open)) if (SPUtils.getBoolean( Constants.SP_SHOULD_SHOW_GAME_DETAIL_INSTALL_GUIDE, false @@ -1447,6 +1509,9 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { } override fun onBackPressed(): Boolean { + if (acceleratorUiHelper.onBack()) { + return true + } return when (val fragment = fragmentsList.getOrNull(bodyBinding.viewPager.currentItem)) { is GameDetailFragment -> { fragment.onBackPressed() @@ -1492,8 +1557,16 @@ class GameDetailWrapperFragment: BaseLazyFragment(), IScrollable { controlReserveBtn() // 更新分割线样式 updateDivider() - binding.detailLlBottom.detailLlBottom.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(requireContext())) - binding.errorReuseToolbar.normalToolbar.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(requireContext())) + binding.detailLlBottom.detailLlBottom.setBackgroundColor( + com.gh.gamecenter.common.R.color.ui_background.toColor( + requireContext() + ) + ) + binding.errorReuseToolbar.normalToolbar.setBackgroundColor( + com.gh.gamecenter.common.R.color.ui_surface.toColor( + requireContext() + ) + ) initSkeleton() skeleton?.hide() } diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/StartingAcceleratorViewModel.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/StartingAcceleratorViewModel.kt new file mode 100644 index 0000000000..df20247058 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/StartingAcceleratorViewModel.kt @@ -0,0 +1,71 @@ +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.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.accelerator.AccelerationUseCase +import com.halo.assistant.accelerator.repository.AcceleratorDataHolder +import io.reactivex.disposables.CompositeDisposable + +class StartingAcceleratorViewModel : ViewModel() { + + private val compositeDisposable = CompositeDisposable() + + val useCase = AccelerationUseCase() + + private val _restartingAcceleratorAction = MutableLiveData>() + val restartingAcceleratorAction: LiveData> = _restartingAcceleratorAction + fun loadAcceleratorToken() { + val userId = UserManager.getInstance().userId + if (userId.isNotBlank()) { + UserRepository.getInstance().setAcceleratorToken(userId) { + _restartingAcceleratorAction.value = Event(it) + } + } + } + + private val _rechargeTrailResult = MutableLiveData>() + val rechargeTrailResult: LiveData> = _rechargeTrailResult + fun rechargeTrial() { + val userId = UserManager.getInstance().userId + useCase.rechargeTrial(userId) + .compose(singleToMain()) + .subscribe(object : BiResponse>() { + override fun onSuccess(data: BaseEntity) { + if (data.data?.result == true) { + // 刷新vip状态 + // 这里先刷新内存数据 + AcceleratorDataHolder.instance.setVipEntity( + VipEntity( + _vipStatus = true, + _isNewUser = false, + _isTryVip = true + ) + ) + + _rechargeTrailResult.value = Event(true) + } else { + _rechargeTrailResult.value = Event(false) + } + } + + override fun onFailure(exception: Exception) { + super.onFailure(exception) + _rechargeTrailResult.value = Event(false) + } + }).let(compositeDisposable::add) + } + + override fun onCleared() { + super.onCleared() + compositeDisposable.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/AccelerationDao.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/AccelerationDao.kt new file mode 100644 index 0000000000..ffb2426d5d --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/AccelerationDao.kt @@ -0,0 +1,49 @@ +package com.gh.gamecenter.gamedetail.accelerator + +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.AcctRecord +import io.reactivex.Observable +import io.reactivex.Single + +@Dao +interface AccelerationDao { + + @Upsert + fun upsertAcctGameInfo(gameInfo: AcctGameInfo): Single + + @Upsert + fun upsertAcctRecord(record: AcctRecord): Single + + /** + * 为什么这里定义成 Observable> 而不是 Observable + * 因为当返回值为 Observable 时,当表中未查询到数据,则不会收到任何回调 + * 使用 Observable>时,为查询到数据时会返回空数组 + */ + @Query("SELECT * FROM AcctGameInfo WHERE gameId = :gameId") + fun getAcctGameInfoByGameIdObservable(gameId: String): Observable> + + @Query("SELECT EXISTS(SELECT 1 FROM AcctGameInfo LIMIT 1)") + fun hasAnyAcctGameInfo(): Single + + @Query("SELECT * FROM AcctGameInfo WHERE gameId IN (:gameIds)") + fun queryAcctGameInfoByGameId(gameIds: List): List + + @Query("SELECT * FROM AcctRecord ORDER BY createTime DESC LIMIT 20") + fun loadAcctRecordsObservable(): Observable> + + @Query("SELECT * FROM AcctRecord ORDER BY createTime DESC LIMIT 20") + fun loadAcctRecords(): List + + /** + * 获取最新的记录(根据createTime排序) + * @return 最新的AcctRecord记录 + */ + @Query("SELECT * FROM AcctRecord ORDER BY createTime DESC LIMIT 1") + fun loadLatestRecord(): Single + + @Query("SELECT EXISTS(SELECT 1 FROM AcctRecord LIMIT 1)") + fun getAnyAcctRecordObservable(): Observable +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/AccelerationDataBase.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/AccelerationDataBase.kt new file mode 100644 index 0000000000..b2e68dabd7 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/AccelerationDataBase.kt @@ -0,0 +1,93 @@ +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 androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import com.gh.gamecenter.feature.entity.AcctGameInfo +import com.gh.gamecenter.feature.entity.AcctRecord +import com.gh.gamecenter.room.converter.AcctGameInfoConverter +import com.halo.assistant.HaloApp + +@Database( + entities = [AcctGameInfo::class, AcctRecord::class], + version = 2, + exportSchema = false +) +@TypeConverters( + AcctGameInfoConverter::class +) +abstract class AccelerationDataBase : RoomDatabase() { + + abstract fun accelerationDao(): AccelerationDao + + 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) + .addMigrations(MIGRATION_1_2) + .allowMainThreadQueries() + .build() + } + + private val MIGRATION_1_2 = object : Migration(1, 2) { + override fun migrate(db: SupportSQLiteDatabase) { + // 删除表 AcctZoneListBean + db.execSQL("DROP TABLE IF EXISTS AcctZoneListBean") + // 创建新表的SQL ,游戏加速成功记录 + createAcctRecordTab(db) + + renamePrimaryKeyWithAcctGameInfoTab(db) + + } + + private fun createAcctRecordTab(db: SupportSQLiteDatabase) { + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS AcctRecord ( + gameId TEXT PRIMARY KEY NOT NULL, + game TEXT NOT NULL, + zoneInfo TEXT NOT NULL, + hasMultiZone INTEGER NOT NULL, + createTime INTEGER NOT NULL + ) + """ + ) + } + + /** + * 重命名表 AcctGameInfo 主键 + * 游戏旧表中没有 gameId 字段,这里将旧表数据直接清空 + */ + private fun renamePrimaryKeyWithAcctGameInfoTab(db: SupportSQLiteDatabase) { + // 1. 创建临时表(包含新的结构) + db.execSQL( + """ + CREATE TABLE IF NOT EXISTS AcctGameInfo_temp ( + gameId TEXT PRIMARY KEY NOT NULL, + accGamePkgName TEXT NOT NULL, + zoneInfo TEXT NOT NULL, + notifyGameName TEXT, + notifyGameTxtTitle TEXT, + notifyGameSmallLogo INTEGER, + notifyGameLargeLogo INTEGER, + notifyClickParam TEXT + ) + """ + ) + + // 2. 删除旧表 + db.execSQL("DROP TABLE AcctGameInfo") + + // 3. 重命名新表 + db.execSQL("ALTER TABLE AcctGameInfo_temp RENAME TO AcctGameInfo") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/AcceleratorGuideView.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/AcceleratorGuideView.kt new file mode 100644 index 0000000000..b2b4d3ddd7 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/AcceleratorGuideView.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/GameDetailAcceleratorUiHelper.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/GameDetailAcceleratorUiHelper.kt new file mode 100644 index 0000000000..d8c7f49776 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/GameDetailAcceleratorUiHelper.kt @@ -0,0 +1,368 @@ +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.databinding.DetailDownloadItemBinding +import com.gh.gamecenter.feature.entity.AcctGameInfo +import com.gh.gamecenter.feature.entity.GameEntity +import com.gh.gamecenter.feature.entity.VipEntity +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.AcceleratorZoneDialogFragment +import com.gh.gamecenter.gamedetail.accelerator.dialog.StartingAcceleratorDialogFragment +import com.halo.assistant.accelerator.repository.AcceleratorDataHolder +import com.therouter.TheRouter + +class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBinding) { + + private var iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java) + + private var guideView: AcceleratorGuideView? = null + + private lateinit var game: GameEntity + + private var isGuideLayerShowing = false + + private var _last: AcctGameInfo? = null + private val last: AcctGameInfo? + get() = _last + + private val hasMultiZone: Boolean + get() = game.serviceArea.size > 1 + + private val isVip: Boolean + get() = AcceleratorDataHolder.instance.isVip + + val isNewUser: Boolean + get() = AcceleratorDataHolder.instance.isNewUser + + private val isInit: Boolean + get() = ::game.isInitialized + + val context: Context + get() = binding.root.context + + private var hasAnyAcctRecord = false + + private val accelerationListener = object : OnAccelerateListener { + override fun onStateChanged(state: AccelerateState) { + when (state) { + is AccelerateState.Success -> { + updateSpeedUi() + + } + + is AccelerateState.Normal -> { + updateSpeedUi() + } + + else -> Unit + } + } + + override fun onProgress(progress: Int, curGamePkgName: String?, curGameZoneFlag: String?) = Unit + + } + + private val onDataHolderListener = object : AcceleratorDataHolder.OnDataHolderListener { + override fun onVipStateChanged(vip: VipEntity) { + showFreeTag() + } + } + + fun initUi(game: GameEntity, context: Context) { + this.game = game + iAcceleratorProvider?.bindAccRelatedListener(game.getUniquePackageName() ?: "", accelerationListener) + AcceleratorDataHolder.instance.addListener(onDataHolderListener) + + binding.vSpeedContent.setOnClickListener { + startAccelerating(context, game, false) + } + binding.vMoreZone.setOnClickListener { + showZoneList(context, game) + } + + binding.tvStopSpeed.setOnClickListener { + SensorsBridge.trackNetworkAccelerationOtherButtonClick( + game.getUniquePackageName() ?: "", + game.id, + game.name ?: "", + AcceleratorDataHolder.instance.memberType, + BUTTON_NAME_STOP_ACCELERATOR, + SOURCE_ENTRANCE_GAME_DETAIL + ) + + AcceleratorDialogFragment.show( + SPEED_STOP, game.getUniquePackageName() ?: "", game.id, game.name ?: "", + SOURCE_ENTRANCE_GAME_DETAIL, context + ) + } + + binding.tvEnterGame.setOnClickListener { + SensorsBridge.trackNetworkAccelerationOtherButtonClick( + game.getUniquePackageName() ?: "", + game.id, + game.name ?: "", + AcceleratorDataHolder.instance.memberType, + BUTTON_NAME_ENTER_GAME, + SOURCE_ENTRANCE_GAME_DETAIL + ) + PackageLauncher.launchApp(context, game, game.getUniquePackageName()) + } + + updateSpeedUi() + } + + fun updateSpeedUi() { + if (!isInit || !showSpeedUi) { + return + } + + val isCurrentGameAccelerating = AcceleratorDataHolder.instance.isCurrentGameAccelerating(game.id) + + when { + isCurrentGameAccelerating -> {// 如果当前游戏正处于加速状态,则需要隐藏当前下载按钮 + binding.detailProgressbar.goneIf(true) + } + + binding.detailProgressbar.text == "更新" -> { // 游戏没有处于加速状态,如果 下载按钮为 “更新” 状态,则需要显示出来 + binding.detailProgressbar.goneIf(false) + } + } + + binding.clSpeed.goneIf(isCurrentGameAccelerating) + binding.gAccelerating.goneIf(!isCurrentGameAccelerating) + binding.gMoreZone.goneIf(!hasMultiZone) + showFreeTag() + binding.tvSpeed.text = if (hasMultiZone) { + last?.zoneName + } else { + null + } ?: R.string.network_acceleration.toResString() + } + + fun checkIfShowSpeedUi(show: Boolean) { + if (!isInit) { + return + } + showSpeedUi = show + binding.clSpeedContainer.goneIf(!showSpeedUi) { + 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)) + // 是否需要展示弹窗 + showGuideLayerIfNeed() + updateSpeedUi() + } + } + + private fun showFreeTag() { + binding.ivFreeVipTag.visibleIf( + showSpeedUi && last == null && !isGuideLayerShowing && + (!CheckLoginUtils.isLogin() || isNewUser) + ) + } + + /** + * 需要同时满足这四个条件,才需要判断是否需要展示弹窗 + */ + var isButtonSizeDetermined = false // 底部下载按钮尺寸是否已确定 + private var isLastLoaded = false // 本地存在区服记录(用户点击过选择区服/启动加速) + private var hasAnyAcctRecordLoaded = false // 已获取:是否存在加速成功的游戏记录 + private var showSpeedUi = false // 显示加速器ui + fun showGuideLayerIfNeed() { + if (isButtonSizeDetermined && hasAnyAcctRecordLoaded && isLastLoaded && showSpeedUi) { + if (shouldShowGuideLayer()) { + isGuideLayerShowing = true + showGuideLayer(context) + } + showFreeTag() + } + + } + + private fun shouldShowGuideLayer() = + !SPUtils.getBoolean(Constants.SP_HAS_SHOW_ACCELERATION_GUIDE_LAYER) && + !hasAnyAcctRecord && + last == null && + !isGuideLayerShowing + + private fun showGuideLayer(context: Context) { + binding.root.post { + val uiListener = object : OnAcceleratorListener { + override fun onStartAccelerator() { + startAccelerating(context, game, true) + } + + override fun onGuideLayerDismiss() { + isGuideLayerShowing = false + showFreeTag() + } + + } + 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) { + SensorsBridge.trackNetworkAccelerationGuidanceDiagramShow( + game.getUniquePackageName() ?: "", + game.id, + game.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 startAccelerating(context: Context, game: GameEntity, isShowing: Boolean) { + if (isInvalidClick()) { + return + } + val lastAcctGameInfo = last + + val memberType = AcceleratorDataHolder.instance.memberType + 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.zoneInfo) + + hasMultiZone -> context.ifLogin("[网络加速]") { + AcceleratorZoneDialogFragment.show( + context, last?.zoneInfo?.id, game.serviceArea, game, + SOURCE_ENTRANCE_GAME_DETAIL + ) + } + + else -> { + val gameInfo = game.serviceArea.firstOrNull() ?: AcctGameInfo.ZoneInfo(0) + doStartAccelerating(context, game, gameInfo) + } + } + } + + private fun showZoneList(context: Context, game: GameEntity) { + if (isInvalidClick()) { + return + } + context.ifLogin("[网络加速]") { + AcceleratorZoneDialogFragment.show( + context, last?.zoneInfo?.id, game.serviceArea, game, + SOURCE_ENTRANCE_GAME_DETAIL + ) + } + } + + private var clickTime = 0L + + private fun doStartAccelerating(context: Context, game: GameEntity, zoneInfo: AcctGameInfo.ZoneInfo) { + + 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, zoneInfo, game, true, hasMultiZone, + SOURCE_ENTRANCE_GAME_DETAIL, + ) + } + }) + } + + fun setCurrentAcctGameInfo(acctGameInfo: AcctGameInfo?) { + isLastLoaded = true + if (last?.zoneInfo?.id != acctGameInfo?.zoneInfo?.id) { + _last = acctGameInfo + updateSpeedUi() + } + showGuideLayerIfNeed() + } + + fun setHasAnyAcctRecord(hasAnyAcctRecord: Boolean) { + hasAnyAcctRecordLoaded = true + this.hasAnyAcctRecord = hasAnyAcctRecord + showGuideLayerIfNeed() + } + + private fun isInvalidClick(): Boolean { + // 300ms内只能调用一次 + val currentTime = System.currentTimeMillis() + val difTime = currentTime - clickTime + if (difTime <= CLICK_DURATION) { + return true + } + clickTime = currentTime + return false + } + + fun clear() { + iAcceleratorProvider?.unBindAccRelatedListener(accelerationListener) + AcceleratorDataHolder.instance.removeListener(onDataHolderListener) + guideView?.dismiss() + } + + + companion object { + + private const val CLICK_DURATION = 300 + + + 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 SOURCE_ENTRANCE_SEARCH = "搜索页" + const val SOURCE_ENTRANCE_RECENTLY_PLAYED = "最近在玩" + const val BUTTON_NAME_ENTER_GAME = "进入游戏" + const val BUTTON_NAME_STOP_ACCELERATOR = "停止加速" + + } + + interface OnAcceleratorListener { + + fun onStartAccelerator() + + fun onGuideLayerDismiss() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/UserVerifyDialogUtils.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/UserVerifyDialogUtils.kt new file mode 100644 index 0000000000..a33fa9a6e9 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/UserVerifyDialogUtils.kt @@ -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 = "", + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorCheckInstallInterceptor.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorCheckInstallInterceptor.kt new file mode 100644 index 0000000000..11578ce9b2 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorCheckInstallInterceptor.kt @@ -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 + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorClient.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorClient.kt new file mode 100644 index 0000000000..6a730f03e0 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorClient.kt @@ -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() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorLoginInterceptor.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorLoginInterceptor.kt new file mode 100644 index 0000000000..ef986b418c --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorLoginInterceptor.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorPackageCheckInterceptor.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorPackageCheckInterceptor.kt new file mode 100644 index 0000000000..df60545e00 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorPackageCheckInterceptor.kt @@ -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) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorRealNameInterceptor.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorRealNameInterceptor.kt new file mode 100644 index 0000000000..17825a1473 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorRealNameInterceptor.kt @@ -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() + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorStateInterceptor.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorStateInterceptor.kt new file mode 100644 index 0000000000..d9c073fc53 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorStateInterceptor.kt @@ -0,0 +1,52 @@ +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.AcceleratorDialogFragment +import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment.Companion.SPEED_CURRENT_ACCELERATING +import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorReplaceDialogFragment +import com.halo.assistant.accelerator.repository.AcceleratorDataHolder +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 + val isCurrentAccelerating = AcceleratorDataHolder.instance.isCurrentGameAccelerating(game.id) + if (isCurrentAccelerating) { + AcceleratorDialogFragment.show( + SPEED_CURRENT_ACCELERATING, + game.getUniquePackageName() ?: "", + game.id, + game.name ?: "", + request.sourceEntrance, + context + ) + } else { + AcceleratorReplaceDialogFragment.show( + context, + game.getUniquePackageName() ?: "", + game.id, + game.name ?: "", + request.sourceEntrance + ) { + if (chain.isValidContext(context)) { + listener?.finished(context) + } + } + } + + } else { + chain.proceed(context, request, listener) + } + + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorValidator.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorValidator.kt new file mode 100644 index 0000000000..c22aff32dc --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorValidator.kt @@ -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, + 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) + } +} + + diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorVipInterceptor.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorVipInterceptor.kt new file mode 100644 index 0000000000..be3f11be1f --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/chain/AcceleratorVipInterceptor.kt @@ -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 + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/dialog/AcceleratorDialogFragment.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/dialog/AcceleratorDialogFragment.kt new file mode 100644 index 0000000000..002a21db30 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/dialog/AcceleratorDialogFragment.kt @@ -0,0 +1,260 @@ +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.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() + SPEED_CURRENT_ACCELERATING -> { + SensorsBridge.trackStopAcceleratingDialogShow(gameId, gameName, pkgName) + CurrentAcceleratingUi() + } + + 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) + DirectUtils.navigateToMyAssetsPage(requireContext(), sourceEntrance) + } + + is SpeedFailureUi -> { + SensorsBridge.trackNetworkAccelerationFailureDialogClick(pkgName, gameId, gameName, sourceEntrance) + context?.let { + DirectUtils.directToWebView(it, Constants.QQ_QIDIAN_ADDRESS, "") + } + } + + is StopSpeedUi, is CurrentAcceleratingUi -> { + 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 + const val SPEED_CURRENT_ACCELERATING = 5 + + 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, SPEED_CURRENT_ACCELERATING) + @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 + } + + class CurrentAcceleratingUi : SpeedDialogUiHelper() { + override val contentResId: Int + get() = R.string.speed_current_game_accelerating + + override val isShowCancelButton: Boolean + get() = true + + override val submitResId: Int + get() = R.string.stop_speed + + override val cancelResId: Int + get() = R.string.cancel + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/dialog/AcceleratorReplaceDialogFragment.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/dialog/AcceleratorReplaceDialogFragment.kt new file mode 100644 index 0000000000..7c07a85849 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/dialog/AcceleratorReplaceDialogFragment.kt @@ -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) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/dialog/AcceleratorZoneDialogFragment.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/dialog/AcceleratorZoneDialogFragment.kt new file mode 100644 index 0000000000..e7ab8ebea3 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/dialog/AcceleratorZoneDialogFragment.kt @@ -0,0 +1,213 @@ +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.gamecenter.R +import com.gh.gamecenter.common.base.fragment.BaseBottomDialogFragment +import com.gh.gamecenter.common.constant.EntranceConsts +import com.gh.gamecenter.common.utils.* +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.AcceleratorZoneViewModel +import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SCENE_TYPE_NO_GUIDE_LAYER +import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorClient +import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorValidator +import com.halo.assistant.accelerator.repository.AcceleratorDataHolder +import kotlin.math.roundToInt + +class AcceleratorZoneDialogFragment : BaseBottomDialogFragment() { + + private val viewModel by viewModels() + + private lateinit var adapter: ZoneAdapter + + private var sourceEntrance = "" + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + sourceEntrance = arguments?.getString(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: "" + } + + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val data = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + arguments?.getParcelableArrayList(EntranceConsts.KEY_DATA, AcctGameInfo.ZoneInfo::class.java) + } else { + arguments?.getParcelableArrayList(EntranceConsts.KEY_DATA) + } ?: listOf() + + val game = arguments?.getParcelable(EntranceConsts.KEY_GAME)!! + val selectedId = arguments?.getInt(KEY_SELECTED_ID) ?: -1 + + 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.setData(selectedId, data) + } + + private fun startAccelerating(game: GameEntity, zoneInfo: AcctGameInfo.ZoneInfo) { + context?.let { + viewModel.useCase.insertAcctGameInfo(AcctGameInfo(game.id, game.getUniquePackageName() ?: "", zoneInfo)) + val acceleratorDataHolder = AcceleratorDataHolder.instance + val memberType = acceleratorDataHolder.memberType + + SensorsBridge.trackNetworkAccelerationButtonClick( + game.getUniquePackageName() ?: "", + game.id, + game.name ?: "", + memberType, + zoneInfo.cnName ?: "", + SCENE_TYPE_NO_GUIDE_LAYER, + sourceEntrance + ) + + val isVip = acceleratorDataHolder.isVip + val isNewUser = acceleratorDataHolder.isNewUser + val request = AcceleratorValidator.Request(isVip, isNewUser, game, sourceEntrance) + AcceleratorClient.newInstance() + .execute(it, request, object : AcceleratorValidator.ValidateListener { + override fun finished(context: Context) { + StartingAcceleratorDialogFragment.show( + context, + zoneInfo, + game, + isNeedRecord = false, + hasMultiZone = true, + sourceEntrance = sourceEntrance + ) + } + + }) + } + + } + + companion object { + private const val KEY_SELECTED_ID = "key_selected_id" + fun show( + context: Context, + selectedId: Int? = null, + data: List, + game: GameEntity, + sourceEntrance: String + ) { + if (context is AppCompatActivity) { + context.supportFragmentManager + } else { + (CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager + }?.let { + val fragment = AcceleratorZoneDialogFragment().apply { + arguments = Bundle().apply { + putParcelableArrayList(EntranceConsts.KEY_DATA, data.toArrayList()) + putParcelable(EntranceConsts.KEY_GAME, game) + putInt(KEY_SELECTED_ID, selectedId ?: -1) + putString(EntranceConsts.KEY_SOURCE_ENTRANCE, sourceEntrance) + } + } + 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.ZoneInfo) -> Unit) : + ListAdapter(diffCallback) { + + private var selectedId = -1 + + fun setData(selectedId: Int, data: List) { + this.selectedId = selectedId + submitList(data) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ZoneViewHolder { + return ZoneViewHolder(parent.toBinding()) + } + + override fun onBindViewHolder(holder: ZoneViewHolder, position: Int, payloads: MutableList) { + 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, item.id) + holder.binding.tvName.text = item.cnName + holder.itemView.setOnClickListener { + click(item) + } + } + + private fun updateSelectedState(holder: ZoneViewHolder, zoneId: Int) { + val (textColorResId, backgroundResId) = if (selectedId == zoneId) { + 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 val diffCallback = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: AcctGameInfo.ZoneInfo, newItem: AcctGameInfo.ZoneInfo): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame( + oldItem: AcctGameInfo.ZoneInfo, + newItem: AcctGameInfo.ZoneInfo + ): Boolean { + return oldItem == newItem + } + + } + } + + class ZoneViewHolder(val binding: RecyclerAcceleratorZoneBinding) : ViewHolder(binding.root) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/dialog/StartingAcceleratorDialogFragment.kt b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/dialog/StartingAcceleratorDialogFragment.kt new file mode 100644 index 0000000000..8e13e6565b --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamedetail/accelerator/dialog/StartingAcceleratorDialogFragment.kt @@ -0,0 +1,280 @@ +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.AcctRecord +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_ENABLE_VIP +import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment.Companion.SPEED_START_FAILURE +import com.gh.gamecenter.livedata.EventObserver +import com.gh.gamecenter.login.user.UserManager +import com.gh.gamecenter.login.user.UserRepository +import com.halo.assistant.accelerator.repository.AcceleratorDataHolder +import com.therouter.TheRouter +import io.reactivex.disposables.CompositeDisposable +import kotlin.math.max + +class StartingAcceleratorDialogFragment : BaseDialogFragment() { + + private val viewModel by viewModels() + + private lateinit var binding: DialogFragmentStartingAcceleratorBinding + + private lateinit var zoneInfo: AcctGameInfo.ZoneInfo + 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 acceleratorDataHolder = AcceleratorDataHolder.instance + + 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()) + // 记录加速成功的游戏 + viewModel.useCase.recordAcctGameInfo(game, zoneInfo, 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 + ) + // 加速失败,上传奇游加速器log + unloadAcceleratorErrorLog(game) + } + } + + if (state.isPermissionExpired) { + // 加速会员过期,刷新本地vip状态 + val userId = UserManager.getInstance().userId + UserRepository.getInstance().refreshVipStatus(userId, true) + // 加速失败,上传奇游加速器log + unloadAcceleratorErrorLog(game) + } + + 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") + } + + } + + private fun unloadAcceleratorErrorLog(game: GameEntity) { + viewModel.useCase.unloadAcceleratorErrorLog(game) + } + + private fun trackNetworkAccelerationStartupResult(result: String) { + SensorsBridge.trackNetworkAccelerationStartupResult( + game.getUniquePackageName() ?: "", + game.id, + game.name ?: "", + acceleratorDataHolder.memberType, + if (hasMultiZone) 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) + zoneInfo = arguments?.getParcelable(EntranceConsts.KEY_ACCT_ZONE_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 = acceleratorDataHolder.isVip + val isNewUser = acceleratorDataHolder.isNewUser + 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) + AcceleratorDataHolder.instance.acctGameRecord = AcctRecord(game.id, game, zoneInfo, hasMultiZone, 0) + iAcceleratorProvider?.startQyGameAccelerate(game.id, game.getUniquePackageName() ?: "", zoneInfo) + if (isNeedRecord) { + viewModel.useCase.insertAcctGameInfo( + AcctGameInfo(game.id, game.getUniquePackageName() ?: "", zoneInfo) + ) + } + if (refreshTokenCount == 0) { + + SensorsBridge.trackNetworkAccelerationStartup( + game.getUniquePackageName() ?: "", + game.id, + game.name ?: "", + acceleratorDataHolder.memberType, + if (hasMultiZone) 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, + zoneInfo: AcctGameInfo.ZoneInfo, + 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_ZONE_INFO, zoneInfo) + 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) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/home/custom/CustomPageViewModel.kt b/app/src/main/java/com/gh/gamecenter/home/custom/CustomPageViewModel.kt index 5733faa506..2650e393d7 100644 --- a/app/src/main/java/com/gh/gamecenter/home/custom/CustomPageViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/home/custom/CustomPageViewModel.kt @@ -84,6 +84,7 @@ class CustomPageViewModel( addSource(repository.recentGamesItemLiveData, ::notifyItemChanged) addSource(repository.customPluginItemLiveData, ::notifyItemChanged) addSource(repository.wechatMiniGameCPMItemLiveData, ::notifyWechatMiniGameCPMSubjectItemChanged) + addSource(repository.customRecentAcceleratorItemLiveData, ::notifyItemChanged) addSource(repository.pkDataListLiveData, ::notifyPKDataChanged) } @@ -415,7 +416,7 @@ class CustomPageViewModel( private fun notifyWGameCPMABatchChanged(gameList: List, subjectId: String, page: Int) { val nextPage: Int - val cpmGameList = if(gameList.isEmpty()) { + val cpmGameList = if (gameList.isEmpty()) { nextPage = 1// 已经到最后一页了,下一页是第一页 subjectChangedMap[SubjectChanged(subjectId, 1)]!! } else { @@ -475,7 +476,7 @@ class CustomPageViewModel( if (!newData.isNullOrEmpty() && newGameList.isNotEmpty()) { // 替换当前专题游戏数据 val position = newData.indexOfFirst { - when(it) { + when (it) { is CustomSubjectItem -> it.data.id == id is CustomSplitSubjectItem -> it.data.id == id else -> false diff --git a/app/src/main/java/com/gh/gamecenter/home/custom/adapter/CustomPageAdapter.kt b/app/src/main/java/com/gh/gamecenter/home/custom/adapter/CustomPageAdapter.kt index 89271acbd5..fa9660ca41 100644 --- a/app/src/main/java/com/gh/gamecenter/home/custom/adapter/CustomPageAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/home/custom/adapter/CustomPageAdapter.kt @@ -20,6 +20,7 @@ import com.gh.gamecenter.game.GameAndPosition import com.gh.gamecenter.home.custom.CustomPageViewModel import com.gh.gamecenter.home.custom.IGameChangedNotifier import com.gh.gamecenter.home.custom.model.* +import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_ACCELERATOR_RECENT_PLAYED import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_AMWAY_WALL import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_COLLECTION_REFRESH_ICON_LANE import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_COLLECTION_REFRESH_ICON_MATRIX @@ -99,27 +100,29 @@ class CustomPageAdapter( is CustomRecentGamesItem -> { if (item.data.isNotEmpty()) { _firstShowPosition.value = index - return } } is CustomRecentWeChatMiniGamesItem -> { if (item.data.isNotEmpty()) { _firstShowPosition.value = index - return } } is CustomRecentQqMiniGamesItem -> { if (item.data.isNotEmpty()) { _firstShowPosition.value = index - return + } + } + + is CustomRecentAcceleratorItem -> { + if (item.data.isNotEmpty()) { + _firstShowPosition.value = index } } else -> { _firstShowPosition.value = index - return } } } @@ -273,6 +276,10 @@ class CustomPageAdapter( CUSTOM_PAGE_ITEM_TYPE_DOUBLE_GAME_WELFARE_CARD -> CustomDoubleWelfareCardViewHolder(viewModel, parent.toBinding()) + CUSTOM_PAGE_ITEM_TYPE_ACCELERATOR_RECENT_PLAYED -> { + CustomRecentAcceleratorViewHolder(viewModel, parent.toBinding()) + } + CUSTOM_PAGE_ITEM_TYPE_FOOTER -> CustomFooterViewHolder(viewModel, parent.toBinding()) CUSTOM_PAGE_ITEM_TYPE_PK -> CustomPKItemViewHolder(viewModel, lifecycleOwner, parent.toBinding()) @@ -467,7 +474,8 @@ class CustomPageAdapter( is CustomGameTestV2Item -> { val positionList = ArrayList() - viewModel.getNewGameTestUserCase(item.link.link ?: "none").getDataListLiveData().value?.forEach { gameDataWrapper -> + viewModel.getNewGameTestUserCase(item.link.link ?: "none") + .getDataListLiveData().value?.forEach { gameDataWrapper -> val gameEntity = gameDataWrapper.gameData if (gameEntity != null) { for (apkEntity in gameEntity.getApk()) { diff --git a/app/src/main/java/com/gh/gamecenter/home/custom/adapter/CustomRecentAcceleratorAdapter.kt b/app/src/main/java/com/gh/gamecenter/home/custom/adapter/CustomRecentAcceleratorAdapter.kt new file mode 100644 index 0000000000..f73bade1be --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/home/custom/adapter/CustomRecentAcceleratorAdapter.kt @@ -0,0 +1,84 @@ +package com.gh.gamecenter.home.custom.adapter + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.gh.gamecenter.common.utils.SensorsBridge +import com.gh.gamecenter.common.utils.toBinding +import com.gh.gamecenter.databinding.ItemHomeVgameBinding +import com.gh.gamecenter.feature.entity.AcctRecord +import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SCENE_TYPE_NO_GUIDE_LAYER +import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SOURCE_ENTRANCE_RECENTLY_PLAYED +import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorClient +import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorValidator +import com.gh.gamecenter.gamedetail.accelerator.dialog.StartingAcceleratorDialogFragment +import com.halo.assistant.accelerator.repository.AcceleratorDataHolder + +class CustomRecentAcceleratorAdapter( + context: Context +) : CustomBaseChildAdapter(context) { + + private val acceleratorDataHolder = AcceleratorDataHolder.instance + + override fun getKey(t: AcctRecord): String { + return t.gameId + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AcceleratorViewHolder = + AcceleratorViewHolder(acceleratorDataHolder, parent.toBinding()) + + override fun onBindViewHolder(holder: AcceleratorViewHolder, position: Int) { + holder.bindView(getItem(position)) + } + + class AcceleratorViewHolder( + private val acceleratorDataHolder: AcceleratorDataHolder, + private var mBinding: ItemHomeVgameBinding + ) : + RecyclerView.ViewHolder(mBinding.root) { + + fun bindView(acctRecord: AcctRecord) { + val game = acctRecord.game + mBinding.gameIconIv.displayGameIcon(game) + + mBinding.maskView.visibility = View.GONE + mBinding.controlTv.visibility = View.GONE + mBinding.progressBar.visibility = View.GONE + mBinding.dotView.visibility = View.GONE + mBinding.updateHintIv.visibility = View.GONE + mBinding.controlTv.visibility = View.GONE + mBinding.progressBar.visibility = View.GONE + + val context = mBinding.root.context + mBinding.root.setOnClickListener { + SensorsBridge.trackNetworkAccelerationButtonClick( + game.getUniquePackageName() ?: "", + game.id, + game.name ?: "", + AcceleratorDataHolder.instance.memberType, + acctRecord.zoneInfo.cnName ?: "", + SCENE_TYPE_NO_GUIDE_LAYER, + SOURCE_ENTRANCE_RECENTLY_PLAYED + ) + val isVip = acceleratorDataHolder.isVip + val isNewUser = acceleratorDataHolder.isNewUser + val request = AcceleratorValidator.Request(isVip, isNewUser, game, SOURCE_ENTRANCE_RECENTLY_PLAYED) + AcceleratorClient.newInstance() + .execute(context, request, object : AcceleratorValidator.ValidateListener { + override fun finished(context: Context) { + StartingAcceleratorDialogFragment.show( + context, + acctRecord.zoneInfo, + game, + true, + acctRecord.hasMultiZone, + SOURCE_ENTRANCE_RECENTLY_PLAYED + ) + } + + }) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageData.kt b/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageData.kt index 9bb1b09ecc..d689f85151 100644 --- a/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageData.kt +++ b/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageData.kt @@ -79,8 +79,9 @@ class CustomPageData( @SerializedName("link_common_collection") val linkCommonCollection: CommonContentCollection? = null, @SerializedName("link_qq_game_recently_played") - val linkQqGameRecentlyPlayed: LinkQqGameRecentlyPlayed? = null, - @SerializedName("link_pk") + val linkQqGameRecentlyPlayed: LinkRecentlyPlayed? = null, + @SerializedName("link_accelerator_recently_played") + val linkAcceleratorRecentlyPlayed: LinkRecentlyPlayed? = null, var linkPK: PKEntity? = null, ) { // 游戏专题 @@ -674,7 +675,7 @@ class CustomPageData( ) } - data class LinkQqGameRecentlyPlayed( + data class LinkRecentlyPlayed( @SerializedName("home") private val _home: String? = null, @SerializedName("more_link") diff --git a/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageItem.kt b/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageItem.kt index 4a8b2625ed..04859ed5f9 100644 --- a/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageItem.kt +++ b/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageItem.kt @@ -9,6 +9,7 @@ import com.gh.gamecenter.entity.AmwayCommentEntity import com.gh.gamecenter.entity.DiscoveryCardEntity import com.gh.gamecenter.entity.HomeItemTestV2Entity import com.gh.gamecenter.entity.SubjectEntity +import com.gh.gamecenter.feature.entity.AcctRecord import com.gh.gamecenter.feature.entity.GameEntity import com.gh.gamecenter.feature.exposure.ExposureEvent import com.gh.vspace.VGameItemData @@ -83,9 +84,11 @@ abstract class CustomPageItem( const val CUSTOM_LINK_TYPE_CWZS_RECENTLY_PLAYED = "cwzs_recently_played" const val CUSTOM_LINK_TYPE_QQ_GAME_RECENTLY_PLAYED = "qq_game_recently_played" const val CUSTOM_LINK_TYPE_WECHAT_GAME_RECENTLY_PLAYED = "wechat_game_recently_played" + const val CUSTOM_LINK_TYPE_ACCELERATOR_RECENTLY_PLAYED = "accelerator_recently_played" const val CUSTOM_LINK_TYPE_QQ_MINI_GAME_COLUMN_DETAIL = ViewPagerFragmentHelper.TYPE_QQ_MINI_GAME_COLUMN const val CUSTOM_LINK_TYPE_WECHAT_MINI_GAME_COLUMN_DETAIL = ViewPagerFragmentHelper.TYPE_WECHAT_GAME_COLUMN - const val CUSTOM_LINK_TYPE_WECHAT_WECHAT_GAME_CPM_COLUMN_DETAIL = ViewPagerFragmentHelper.TYPE_WECHAT_GAME_CPM_COLUMN + const val CUSTOM_LINK_TYPE_WECHAT_WECHAT_GAME_CPM_COLUMN_DETAIL = + ViewPagerFragmentHelper.TYPE_WECHAT_GAME_CPM_COLUMN const val CUSTOM_LINK_TYPE_HALO_RECOMMEND = "halo_recommend" const val CUSTOM_LINK_TYPE_COLUMN_COLLECTION = "column_collection" // 专题合集 const val CUSTOM_LINK_TYPE_GAME_LIST_COLLECTION = "game_list_collection" @@ -176,6 +179,7 @@ abstract class CustomPageItem( const val CUSTOM_PAGE_ITEM_TYPE_SINGLE_GAME_CARD = 36 const val CUSTOM_PAGE_ITEM_TYPE_DOUBLE_GAME_WELFARE_CARD = 37 const val CUSTOM_PAGE_ITEM_TYPE_PK = 38 + const val CUSTOM_PAGE_ITEM_TYPE_ACCELERATOR_RECENT_PLAYED = 39 // 专题样式 to itemType val subjectTypeMap: HashMap = hashMapOf( @@ -651,7 +655,7 @@ data class CustomWeChatMiniGamesCPMSubjectItem( data class CustomRecentQqMiniGamesItem( private val _link: LinkEntity, val data: List, - val linkQqGameRecentlyPlayed: CustomPageData.LinkQqGameRecentlyPlayed, + val linkQqGameRecentlyPlayed: CustomPageData.LinkRecentlyPlayed, private val _position: Int, private val _componentPosition: Int ) : CustomPageItem(_link, _position, _componentPosition) { @@ -671,6 +675,29 @@ data class CustomRecentQqMiniGamesItem( } } +data class CustomRecentAcceleratorItem( + private val _link: LinkEntity, + val data: List, + val moreLink: LinkEntity?, + private val _position: Int, + private val _componentPosition: Int +) : CustomPageItem(_link, _position, _componentPosition) { + + override val itemType: Int + get() = CUSTOM_PAGE_ITEM_TYPE_ACCELERATOR_RECENT_PLAYED + + override fun doAreContentsTheSames(other: CustomPageItem): Boolean { + return other is CustomRecentAcceleratorItem + && position == other.position + && componentPosition == other.componentPosition + && isSameData(other.data) + } + + private fun isSameData(other: List) = + data.size == other.size && + data.joinToString("_") { it.gameId } == other.joinToString("_") { it.gameId } +} + // 插件化区域 data class CustomPluginItem( private val _link: LinkEntity, diff --git a/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageRepository.kt b/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageRepository.kt index 3ebc95dcf8..36e6ed2455 100644 --- a/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageRepository.kt +++ b/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageRepository.kt @@ -22,10 +22,7 @@ import com.gh.gamecenter.entity.DiscoveryCardEntity import com.gh.gamecenter.entity.GameUpdateEntity import com.gh.gamecenter.entity.PullDownPush import com.gh.gamecenter.entity.SubjectEntity -import com.gh.gamecenter.feature.entity.FloatingWindowEntity -import com.gh.gamecenter.feature.entity.GameEntity -import com.gh.gamecenter.feature.entity.PluginLocation -import com.gh.gamecenter.feature.entity.TagStyleEntity +import com.gh.gamecenter.feature.entity.* import com.gh.gamecenter.feature.utils.ApkActiveUtils import com.gh.gamecenter.gamedetail.rating.edit.RatingEditActivity import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_BANNER @@ -57,6 +54,7 @@ import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_ import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_PK import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_QQ_GAME_RECENTLY_PLAYED import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_QQ_MINI_GAME_COLUMN_DETAIL +import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_ACCELERATOR_RECENTLY_PLAYED import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_WECHAT_GAME_RECENTLY_PLAYED import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_WECHAT_MINI_GAME_COLUMN_DETAIL import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_WECHAT_WECHAT_GAME_CPM_COLUMN_DETAIL @@ -103,6 +101,8 @@ class CustomPageRepository private constructor( private var discoverCardItem: CustomDiscoverCardItem? = null + private var recentAcceleratorItem: CustomRecentAcceleratorItem? = null + private var pluginGameList = arrayListOf() private var displayingGameIdSet = hashSetOf() // 当前正在显示的游戏 id 列表 @@ -145,12 +145,20 @@ class CustomPageRepository private constructor( } } + val customRecentAcceleratorItemLiveData: LiveData = + MediatorLiveData().apply { + addSource(shareRepository.recentlyAcctRecord) { + refreshRecentAcceleratorItemIfNeed(it)?.let(this::setValue) + } + } + val wechatMiniGameCPMItemLiveData: LiveData>> = wGameSubjectCPMListUseCase.wechatMiniGameCPMListLiveData val hiddenNotifications: LiveData>> get() = shareRepository.hiddenNotifications + val pkDataListLiveData = MutableLiveData>() private var lastComponentType = "" @@ -213,6 +221,14 @@ class CustomPageRepository private constructor( } } + private fun refreshRecentAcceleratorItemIfNeed(data: List): CustomRecentAcceleratorItem? { + return recentAcceleratorItem?.let { + recentAcceleratorItem = + CustomRecentAcceleratorItem(it.link, data, it.moreLink, it.position, it.componentPosition) + recentAcceleratorItem + } + } + fun loadSuspendedWindow(pageId: String): Single> = remoteDataSource.loadSuspendedWindow(pageId) .map { @@ -494,6 +510,23 @@ class CustomPageRepository private constructor( } } + item.link.type == CUSTOM_LINK_TYPE_ACCELERATOR_RECENTLY_PLAYED -> { + // 最近在玩-加速器 + val acctGameInfos = shareRepository.loadRecentlyAcctGameInfos() + recentAcceleratorItem = CustomRecentAcceleratorItem( + item.link, + acctGameInfos, + item.linkAcceleratorRecentlyPlayed?.moreLink, + pageInfo.position, + pageInfo.componentPosition + ).also { + list.add(it) + } + + pageInfo.positionIncrement() + pageInfo.componentPositionIncrement() + } + item.link.type == "plugin_area" -> { // 插件化区域 pluginItem = @@ -886,7 +919,7 @@ class CustomPageRepository private constructor( COMPONENTS_SUBJECT_TYPE_GAME_HORIZONTAL_SLIDE, COMPONENTS_SUBJECT_TYPE_GALLERY, COMPONENTS_SUBJECT_TYPE_GALLERY_SLIDE - -> { + -> { GameSubstituteRepositoryHelper.replaceGames( gameList = gameSubject.data!!, displayingGameIdSet = displayingGameIdSet, diff --git a/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageShareRepository.kt b/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageShareRepository.kt index e72d5123d9..169936c86f 100644 --- a/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageShareRepository.kt +++ b/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageShareRepository.kt @@ -3,18 +3,21 @@ package com.gh.gamecenter.home.custom.model import androidx.annotation.MainThread import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asLiveData import com.gh.common.filter.RegionSettingHelper import com.gh.common.util.PackageUtils import com.gh.gamecenter.common.constant.Constants import com.gh.gamecenter.common.constant.Constants.SP_HIDDEN_NOTIFICATIONS import com.gh.gamecenter.common.utils.observableToMain +import com.gh.gamecenter.common.utils.singleToMain import com.gh.gamecenter.common.utils.toJson -import com.gh.gamecenter.core.utils.GsonUtils import com.gh.gamecenter.core.utils.SPUtils import com.gh.gamecenter.entity.DiscoveryCardEntity import com.gh.gamecenter.entity.DiscoveryGameCardEntity import com.gh.gamecenter.entity.DiscoveryGameCardLabel +import com.gh.gamecenter.feature.entity.AcctRecord import com.gh.gamecenter.feature.entity.GameEntity +import com.gh.gamecenter.gamedetail.accelerator.AccelerationDataBase import com.gh.gamecenter.retrofit.RetrofitManager import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -144,6 +147,34 @@ class CustomPageShareRepository private constructor() { } } + private val _recentlyAcctRecord = MutableLiveData>() + val recentlyAcctRecord: LiveData> = _recentlyAcctRecord + private fun loadAcctRecordsFlow() { + if (recentlyAcctRecord.value == null) { + AccelerationDataBase.instance.accelerationDao().loadAcctRecordsObservable() + .compose(observableToMain()) + .subscribe({ + _recentlyAcctRecord.value = it + }) { + /* no implement */ + }.let(compositeDisposable::add) + } + } + + /** + * 请注意,这个方法只能在子线程调用 + * 此方法用于与自定义页面同步返回数据使用 + */ + fun loadRecentlyAcctGameInfos(): List { + loadAcctRecordsFlow() + return try { + AccelerationDataBase.instance.accelerationDao().loadAcctRecords() + } catch (e: Exception) { + emptyList() + } + } + + // 这个方法要在用户退出首页时调用 fun onClear() { compositeDisposable.clear() diff --git a/app/src/main/java/com/gh/gamecenter/home/custom/viewholder/CustomRecentAcceleratorViewHolder.kt b/app/src/main/java/com/gh/gamecenter/home/custom/viewholder/CustomRecentAcceleratorViewHolder.kt new file mode 100644 index 0000000000..fab4c4bee5 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/home/custom/viewholder/CustomRecentAcceleratorViewHolder.kt @@ -0,0 +1,113 @@ +package com.gh.gamecenter.home.custom.viewholder + +import android.view.ViewGroup.MarginLayoutParams +import androidx.core.view.updateLayoutParams +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.LayoutParams +import androidx.recyclerview.widget.RecyclerView.OnScrollListener +import com.gh.gamecenter.R +import com.gh.gamecenter.common.utils.dip2px +import com.gh.gamecenter.common.utils.goneIf +import com.gh.gamecenter.common.utils.toResString +import com.gh.gamecenter.common.utils.visibleIf +import com.gh.gamecenter.common.view.GridSpacingItemColorDecoration +import com.gh.gamecenter.databinding.ItemHomeRecentVgameCustomBinding +import com.gh.gamecenter.home.custom.CustomPageViewModel +import com.gh.gamecenter.home.custom.adapter.CustomPageAdapter +import com.gh.gamecenter.home.custom.adapter.CustomRecentAcceleratorAdapter +import com.gh.gamecenter.home.custom.eventlistener.RecentMiniGameItemEventHelper +import com.gh.gamecenter.home.custom.model.CustomPageItem +import com.gh.gamecenter.home.custom.model.CustomRecentAcceleratorItem + +/** + * 最近在玩-加速器快速启动 + */ +class CustomRecentAcceleratorViewHolder( + viewModel: CustomPageViewModel, + var binding: ItemHomeRecentVgameCustomBinding +) : BaseCustomViewHolder(viewModel, binding.root) { + + override val childEventHelper: RecentMiniGameItemEventHelper by lazy { RecentMiniGameItemEventHelper(viewModel) } + + private val adapter by lazy(LazyThreadSafetyMode.NONE) { + CustomRecentAcceleratorAdapter(itemView.context) + } + + init { + binding.recyclerView.itemAnimator = null + binding.recyclerView.addItemDecoration( + GridSpacingItemColorDecoration(binding.root.context, 4, 0, com.gh.gamecenter.common.R.color.transparent) + ) + + binding.recyclerView.addOnScrollListener(object : OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + val layoutManager = binding.recyclerView.layoutManager + if (layoutManager is LinearLayoutManager) { + val scrollToEnd = + layoutManager.findLastCompletelyVisibleItemPosition() == (binding.recyclerView.adapter!!.itemCount - 1) + val item = _item + if (item is CustomRecentAcceleratorItem && item.moreLink != null) { + binding.divider.visibleIf(scrollToEnd) + } + } + } + }) + } + + override fun bindView(item: CustomPageItem) { + super.bindView(item) + binding.moreTv.goneIf(true) + + if (item is CustomRecentAcceleratorItem) { + + if (item.moreLink != null) { + binding.vspaceIv.goneIf(false) + binding.vspaceIv.setImageResource(R.drawable.ic_accelerator_recently_played) + binding.vspaceIv.setOnClickListener { + val link = item.moreLink + viewModel.navigateToLinkPage(item, link, "更多", null) + } + } else { + binding.vspaceIv.goneIf(true) + } + binding.divider.goneIf(item.moreLink == null) + + binding.titleTv.text = R.string.recent_played.toResString() + val entities = item.data + if (entities.isEmpty()) { + binding.root.goneIf(true) + binding.clRoot.updateLayoutParams { + height = if (bindingAdapterPosition == 0) { + 1 + } else { + 0 + } + width = LayoutParams.MATCH_PARENT + if (this is MarginLayoutParams) { + topMargin = 0 + } + } + } else { + binding.clRoot.goneIf(false) + binding.clRoot.updateLayoutParams { + height = 122F.dip2px() + width = LayoutParams.MATCH_PARENT + if (this is MarginLayoutParams) { + topMargin = 16F.dip2px() + } + } + + if (binding.recyclerView.adapter == null) { + binding.recyclerView.adapter = adapter + } + adapter.submitList(entities) + } + + val bindingAdapter = bindingAdapter + if (bindingAdapter is CustomPageAdapter) { + bindingAdapter.updateFirstItemId() + } + } + } +} diff --git a/app/src/main/java/com/gh/gamecenter/libao/LibaoDetailFragment.java b/app/src/main/java/com/gh/gamecenter/libao/LibaoDetailFragment.java index 00e5739e7c..854b061a26 100644 --- a/app/src/main/java/com/gh/gamecenter/libao/LibaoDetailFragment.java +++ b/app/src/main/java/com/gh/gamecenter/libao/LibaoDetailFragment.java @@ -144,7 +144,7 @@ public class LibaoDetailFragment extends ToolbarFragment implements LibaoDetailA private DetailViewHolder getDetailViewHolder() { // 每次获取需要重新创建, 防止数据刷新 - return new DetailViewHolder(mCachedView, null, mGameEntity, false, mEntrance, mName, mTitle, null, false, null); // 下载按钮ViewHolder + return new DetailViewHolder(mCachedView, null, mGameEntity, false, mEntrance, mName, mTitle, null, false, null, null); // 下载按钮ViewHolder } public LibaoEntity getLibaoEntity() { diff --git a/app/src/main/java/com/gh/gamecenter/newsdetail/NewsDetailFragment.java b/app/src/main/java/com/gh/gamecenter/newsdetail/NewsDetailFragment.java index 95f1d8e5bb..67d7675e1b 100644 --- a/app/src/main/java/com/gh/gamecenter/newsdetail/NewsDetailFragment.java +++ b/app/src/main/java/com/gh/gamecenter/newsdetail/NewsDetailFragment.java @@ -194,6 +194,7 @@ public class NewsDetailFragment extends ToolbarFragment { adapter.getTitle(), mExposureEvent, false, + null, null); // 下载按钮ViewHolder } diff --git a/app/src/main/java/com/gh/gamecenter/personal/HaloPersonalFragment.kt b/app/src/main/java/com/gh/gamecenter/personal/HaloPersonalFragment.kt index 38ea7207db..38eb4bd14f 100644 --- a/app/src/main/java/com/gh/gamecenter/personal/HaloPersonalFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/personal/HaloPersonalFragment.kt @@ -118,9 +118,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 +139,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 +193,7 @@ class HaloPersonalFragment : BaseLazyFragment() { } } } + mStubBinding.personalMsg -> { // 优先进入有数字提醒的消息tab,其次是有红点提醒的游戏动态,最后是没有提醒的消息tab val defaultTabIndex = if ((mUnreadViewModel.messageUnreadCountLiveData.value?.message @@ -208,6 +220,7 @@ class HaloPersonalFragment : BaseLazyFragment() { DirectUtils.directToMessageCenter(defaultTabIndex, "我的光环-消息") } + mStubBinding.personalUserIcon, mStubBinding.personalUserName -> { if (mUserInfoEntity != null) { @@ -226,22 +239,20 @@ 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("无", "无", "无", "我的光环") + DirectUtils.navigateToMyAssetsPage(requireContext(), "我的光环-个人主页") } else { NewFlatLogUtils.logHaloSelfClick("用户信息", "立即登录") SensorsBridge.trackHaloSelfClick("用户信息", "立即登录", "", "", "") CheckLoginUtils.checkLogin(context, "我的光环-个人主页", null) } } + mStubBinding.personalBadge -> { NewFlatLogUtils.logHaloSelfClick("用户信息", "我的徽章") SensorsBridge.trackHaloSelfClick("用户信息", "我的徽章", "", "", "") @@ -252,6 +263,7 @@ class HaloPersonalFragment : BaseLazyFragment() { mUserInfoEntity?.icon ) } + mStubBinding.myGameTv -> { if (UserManager.getInstance().isLoggedIn) { NewFlatLogUtils.logHaloSelfClick("常用功能", "我的游戏") @@ -268,6 +280,7 @@ class HaloPersonalFragment : BaseLazyFragment() { CheckLoginUtils.checkLogin(requireContext(), "我的光环-我的游戏") {} } } + mStubBinding.myPostTv -> { if (UserManager.getInstance().isLoggedIn) { NewFlatLogUtils.logHaloSelfClick("常用功能", "我的发布") @@ -284,6 +297,7 @@ class HaloPersonalFragment : BaseLazyFragment() { CheckLoginUtils.checkLogin(requireContext(), "我的光环-我的发布") { } } } + mStubBinding.myGameCollectionTv -> { if (UserManager.getInstance().isLoggedIn) { NewFlatLogUtils.logHaloSelfClick("常用功能", "我的游戏单") @@ -300,6 +314,7 @@ class HaloPersonalFragment : BaseLazyFragment() { CheckLoginUtils.checkLogin(requireContext(), "我的光环-我的游戏单") { } } } + mStubBinding.historyTv -> { NewFlatLogUtils.logHaloSelfClick("常用功能", "浏览记录") SensorsBridge.trackHaloSelfClick( @@ -311,6 +326,7 @@ class HaloPersonalFragment : BaseLazyFragment() { ) startActivity(HistoryActivity.getHistoryIntent(requireContext(), "我的光环-浏览记录")) } + mStubBinding.myCollectionTv -> { if (UserManager.getInstance().isLoggedIn) { NewFlatLogUtils.logHaloSelfClick("常用功能", "我的收藏") @@ -606,7 +622,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 +653,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 +711,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 +738,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 +745,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) @@ -799,7 +830,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 +1011,7 @@ class HaloPersonalFragment : BaseLazyFragment() { MotionEvent.ACTION_DOWN -> { mStubBinding.listRefresh.isEnabled = false } + MotionEvent.ACTION_UP -> { mStubBinding.listRefresh.isEnabled = true } @@ -1084,7 +1119,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 +1199,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())) } } } diff --git a/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java b/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java index 3ff8e20244..0c4d6f656e 100644 --- a/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java +++ b/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java @@ -102,12 +102,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.BigEvent; import com.gh.gamecenter.feature.entity.CommentEntity; import com.gh.gamecenter.feature.entity.CommentnumEntity; @@ -130,9 +132,11 @@ 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.WXSubscribeMsgConfig; +import com.gh.gamecenter.feature.entity.WechatPayEntity; import com.gh.gamecenter.feature.entity.ZoneEntity; import com.gh.gamecenter.gamedetail.entity.GameDetailSetting; import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity; @@ -149,6 +153,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; @@ -3421,6 +3426,7 @@ public interface ApiService { */ @PATCH("app/{module}/diverter_visit_time") Single patchDiverterVisitTime(@Path("module") String module, @Body RequestBody body); + /** * 游戏单搜索(复用 游戏单合集-内容列表 (适用于刷新轮换) 接口实体) */ @@ -3468,4 +3474,29 @@ public interface ApiService { */ @GET("games/{game_id}/libao?is_all_type=true") Single> getGameLibaoList(@Path("game_id") String gameId); + + @POST("pay/wechat/goods/{goodId}/user/{userId}") + Single> preOrderWithWechat(@Path("goodId") String goodId, @Path("userId") String userId, @Query("type") String type); + + @POST("pay/alipay/goods/{goodId}/user/{userId}") + Single> preOrderWithAli(@Path("goodId") String goodId, @Path("userId") String userId, @Query("type") String type); + + @GET("pay/alipay/order/{orderNo}/user/{userId}") + Single> getAliPayResult(@Path("orderNo") String orderNo, @Path("userId") String userId, @Query("type") String type); + + @GET("pay/wechat/order/{orderNo}/user/{userId}") + Single> getWechatPayResult(@Path("orderNo") String orderNo, @Path("userId") String userId, @Query("type") String type); + + @POST("vip/user/{userId}/game") + Single> recordAcctGameInfo(@Path("userId") String userId, @Query("type") String type, @Body RequestBody body); + + @POST("vip/user/{userId}/recharge_trial") + Single> rechargeTrial(@Path("userId") String userId, @Query("type") String type); + + /** + * 上传意见反馈(奇游加速器启动失败) + */ + @Headers({"Content-Type: application/json", "Accept: application/json"}) + @POST("suggestions") + Single uploadAcceleratorErrorLog(@Body RequestBody toRequestBody); } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/room/converter/AcctGameInfoConverter.kt b/app/src/main/java/com/gh/gamecenter/room/converter/AcctGameInfoConverter.kt new file mode 100644 index 0000000000..aac5f443ff --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/room/converter/AcctGameInfoConverter.kt @@ -0,0 +1,44 @@ +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.GameEntity +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(0) + } + + @TypeConverter + fun fromZoneList(zoneList: List): String { + return zoneList.toJson() + } + + @TypeConverter + fun toZoneList(value: String): List { + return value.toObject>() ?: listOf() + } + + @TypeConverter + fun fromGame(game: GameEntity): String { + return game.toJson() + } + + @TypeConverter + fun toGame(value: String): GameEntity? { + return value.toObject() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/search/SearchGameFirstItemViewHolder.kt b/app/src/main/java/com/gh/gamecenter/search/SearchGameFirstItemViewHolder.kt index 3622c08553..4422dc4f42 100644 --- a/app/src/main/java/com/gh/gamecenter/search/SearchGameFirstItemViewHolder.kt +++ b/app/src/main/java/com/gh/gamecenter/search/SearchGameFirstItemViewHolder.kt @@ -7,24 +7,26 @@ import android.view.LayoutInflater import android.view.View import androidx.collection.ArrayMap import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.gh.common.databind.BindingAdapters import com.gh.common.util.DirectUtils import com.gh.common.util.NewFlatLogUtils +import com.gh.gamecenter.SearchType import com.gh.gamecenter.common.exposure.ExposureSource import com.gh.gamecenter.common.utils.* import com.gh.gamecenter.core.utils.HtmlUtils import com.gh.gamecenter.databinding.ItemSearchFirstBannerBinding import com.gh.gamecenter.databinding.ItemSearchFirstCardBinding import com.gh.gamecenter.databinding.SearchGameFirstItemBinding +import com.gh.gamecenter.db.ISearchHistoryDao import com.gh.gamecenter.feature.entity.FirstSetting import com.gh.gamecenter.feature.entity.GameEntity +import com.gh.gamecenter.feature.entity.VipEntity import com.gh.gamecenter.feature.exposure.ExposureEvent import com.gh.gamecenter.feature.exposure.ExposureType import com.gh.gamecenter.feature.game.GameItemViewHolder -import com.gh.gamecenter.SearchType -import com.gh.gamecenter.db.ISearchHistoryDao import com.gh.gamecenter.search.SearchGameResultAdapter.Companion.setItemCLick import com.gh.gamecenter.search.SearchGameResultAdapter.Companion.showContentTag import com.lzf.easyfloat.utils.DisplayUtils @@ -34,14 +36,14 @@ class SearchGameFirstItemViewHolder( private val searchMap: ArrayMap, private val entrance: String, private val sourceEntrance: String, - fragment: Fragment, + private val fragment: Fragment, val binding: SearchGameFirstItemBinding, private val dao: ISearchHistoryDao ) : RecyclerView.ViewHolder(binding.root) { private var contentTag: GameEntity.ContentTag? = null private var game: GameEntity? = null - private var key:String = "" + private var key: String = "" private val bannerBinding by lazy { val inflater = LayoutInflater.from(binding.root.context) diff --git a/app/src/main/java/com/gh/gamecenter/search/SearchGameIndexAdapter.kt b/app/src/main/java/com/gh/gamecenter/search/SearchGameIndexAdapter.kt index 0cbcfb7a09..9021196878 100644 --- a/app/src/main/java/com/gh/gamecenter/search/SearchGameIndexAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/search/SearchGameIndexAdapter.kt @@ -172,9 +172,9 @@ class SearchGameIndexAdapter( binding.topDivider.goneIf(position == 0) binding.gameItemIncluded.root.setPadding( 16F.dip2px(), + 0, 16F.dip2px(), - 16F.dip2px(), - 16F.dip2px() + 0 ) SearchGameIndexItemViewHolder.initServerType(gameEntity, binding.gameItemIncluded) @@ -483,10 +483,21 @@ class SearchGameIndexAdapter( ) } + val viewHolder = GameViewHolder(binding.gameItemIncluded.root).apply { + gameDownloadBtn = binding.gameItemIncluded.downloadBtn + gameDes = binding.gameItemIncluded.gameDes + gameDownloadTips = binding.gameItemIncluded.downloadTipsLottie + multiVersionDownloadTv = binding.gameItemIncluded.multiVersionDownloadTv + gameRating = binding.gameItemIncluded.gameRating + recommendContainer = binding.gameItemIncluded.recommendContainer + recommendTv = binding.gameItemIncluded.recommendTv + recommendIv = binding.gameItemIncluded.recommendIv + recommendStarInfo = binding.gameItemIncluded.recommendStarInfo + } DownloadItemUtils.updateItem( mContext, gameEntity, - GameViewHolder(binding.gameItemIncluded) + viewHolder ) } } else if (holder is SearchHistoryViewHolder) { diff --git a/app/src/main/java/com/gh/gamecenter/search/SearchGameIndexItemViewHolder.kt b/app/src/main/java/com/gh/gamecenter/search/SearchGameIndexItemViewHolder.kt index 3f957ca54c..e92b91bfe6 100644 --- a/app/src/main/java/com/gh/gamecenter/search/SearchGameIndexItemViewHolder.kt +++ b/app/src/main/java/com/gh/gamecenter/search/SearchGameIndexItemViewHolder.kt @@ -11,20 +11,22 @@ import com.gh.gamecenter.common.exposure.ExposureSource import com.gh.gamecenter.common.utils.* import com.gh.gamecenter.common.view.DrawableView import com.gh.gamecenter.databinding.SearchGameIndexItemBinding +import com.gh.gamecenter.databinding.SearchGameItemBinding import com.gh.gamecenter.databinding.SearchSubjectItemBinding import com.gh.gamecenter.db.ISearchHistoryDao import com.gh.gamecenter.entity.SubjectData -import com.gh.gamecenter.feature.databinding.GameItemBinding import com.gh.gamecenter.feature.entity.GameEntity import com.gh.gamecenter.feature.exposure.ExposureEvent import com.gh.gamecenter.subject.SubjectActivity -class SearchGameIndexItemViewHolder(var binding: SearchGameIndexItemBinding) : RecyclerView.ViewHolder(binding.root) { +class SearchGameIndexItemViewHolder( + var binding: SearchGameIndexItemBinding +) : RecyclerView.ViewHolder(binding.root) { var contentTag: GameEntity.ContentTag? = null companion object { - fun initServerType(gameEntity: GameEntity, binding: GameItemBinding) { + fun initServerType(gameEntity: GameEntity, binding: SearchGameItemBinding) { val serverLabel = gameEntity.serverLabel when { gameEntity.test != null -> { @@ -38,7 +40,11 @@ class SearchGameIndexItemViewHolder(var binding: SearchGameIndexItemBinding) : R if (gameEntity.isUseDefaultServerStyle()) { binding.gameKaifuType.background = com.gh.gamecenter.feature.R.drawable.server_label_default_bg.toDrawable(binding.root.context) - binding.gameKaifuType.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(binding.root.context)) + binding.gameKaifuType.setTextColor( + com.gh.gamecenter.common.R.color.text_secondary.toColor( + binding.root.context + ) + ) } else { binding.gameKaifuType.background = DrawableView.getServerDrawable(serverLabel.color) @@ -86,7 +92,14 @@ class SearchSubjectItemViewHolder(var binding: SearchSubjectItemBinding) : Recyc binding.run { subjectRv.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false) - subjectRv.adapter = SearchSubjectAdapter(context, entity.getFilterGame(), entity.columnId, entity.name, "($type-专题)", key) { + subjectRv.adapter = SearchSubjectAdapter( + context, + entity.getFilterGame(), + entity.columnId, + entity.name, + "($type-专题)", + key + ) { dao?.add(key) if (itemData.adConfig != null) { NewFlatLogUtils.logClickGameAd( diff --git a/app/src/main/java/com/gh/gamecenter/search/SearchGameResultAdapter.kt b/app/src/main/java/com/gh/gamecenter/search/SearchGameResultAdapter.kt index 4b15ff5d94..552627a99c 100644 --- a/app/src/main/java/com/gh/gamecenter/search/SearchGameResultAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/search/SearchGameResultAdapter.kt @@ -7,6 +7,7 @@ import android.view.View import android.view.ViewGroup import android.widget.LinearLayout import androidx.collection.ArrayMap +import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import com.gh.ad.AdDelegateHelper @@ -40,14 +41,27 @@ import com.gh.gamecenter.databinding.* import com.gh.gamecenter.db.ISearchHistoryDao import com.gh.gamecenter.eventbus.EBDownloadStatus import com.gh.gamecenter.eventbus.EBSearch -import com.gh.gamecenter.feature.databinding.GameItemBinding +import com.gh.gamecenter.eventbus.EBStartupAcceleration +import com.gh.gamecenter.feature.entity.AcctGameInfo import com.gh.gamecenter.feature.entity.GameEntity +import com.gh.gamecenter.feature.entity.VipEntity import com.gh.gamecenter.feature.exposure.ExposureEvent import com.gh.gamecenter.feature.exposure.ExposureType import com.gh.gamecenter.feature.game.GameItemViewHolder import com.gh.gamecenter.feature.minigame.MiniGameItemHelper +import com.gh.gamecenter.feature.view.DownloadButton +import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.DISTRICT_SERVER_EMPTY +import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.DISTRICT_SERVER_HAVA +import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SCENE_TYPE_NO_GUIDE_LAYER +import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SOURCE_ENTRANCE_SEARCH +import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorClient +import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorValidator +import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorZoneDialogFragment +import com.gh.gamecenter.gamedetail.accelerator.dialog.StartingAcceleratorDialogFragment import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity import com.gh.gamecenter.help.HelpAndFeedbackBridge +import com.gh.gamecenter.minigame.MiniGameSearchResultFragment +import com.halo.assistant.accelerator.repository.AcceleratorDataHolder import com.lightgame.download.DownloadEntity import com.lightgame.utils.Util_System_Keyboard import org.greenrobot.eventbus.EventBus @@ -66,6 +80,7 @@ class SearchGameResultAdapter( private var searchMap: ArrayMap = ArrayMap() private var exposureEventArray: SparseArray? = null private var _recyclerView: RecyclerView? = null + private var gameIdBeingAccelerated = "" var key: String = "" @@ -73,11 +88,15 @@ class SearchGameResultAdapter( private val adIdSet = hashSetOf() // 记录展示过的广告id + private val isNormalGameSearchPage: Boolean + get() = fragment !is MiniGameSearchResultFragment + fun clearAdIdSet() { adIdSet.clear() } override fun setListData(updateData: MutableList?) { + gameIdBeingAccelerated = AcceleratorDataHolder.instance.getAcceleratingGameId() exposureEventArray = SparseArray(updateData?.size ?: 0) // 记录游戏位置 if (updateData != null) { @@ -143,7 +162,15 @@ class SearchGameResultAdapter( ITEM_GAME_FIRST -> { val binding = SearchGameFirstItemBinding.inflate(mLayoutInflater, viewGroup, false) - SearchGameFirstItemViewHolder(type, searchMap, entrance, sourceEntrance, fragment, binding, dao) + SearchGameFirstItemViewHolder( + type, + searchMap, + entrance, + sourceEntrance, + fragment, + binding, + dao + ) } ItemViewType.ITEM_BODY -> { @@ -315,8 +342,10 @@ class SearchGameResultAdapter( override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) { super.onViewAttachedToWindow(holder) - if (holder is SearchGameFirstItemViewHolder) { - holder.onAttach(_recyclerView) + when (holder) { + is SearchGameFirstItemViewHolder -> { + holder.onAttach(_recyclerView) + } } } @@ -381,7 +410,7 @@ class SearchGameResultAdapter( MiniGameItemHelper.setMiniGameUsage(gamePlayCount, gameEntity) officialEntryIv.isVisible = gameEntity.officialEntry } - binding.gameItemIncluded.root.setPadding(16F.dip2px(), 16F.dip2px(), 16F.dip2px(), 16F.dip2px()) + binding.gameItemIncluded.root.setPadding(16F.dip2px(), 0, 16F.dip2px(), 0) SearchGameIndexItemViewHolder.initServerType(gameEntity, binding.gameItemIncluded) val exposureSources = ArrayList() @@ -419,7 +448,6 @@ class SearchGameResultAdapter( sourceEntrance, item.gamePosition ) - } else { bottomDivider.visibility = View.GONE } @@ -508,6 +536,43 @@ class SearchGameResultAdapter( } } + fun notifyItemAndStartupAcceleration(acctGameInfo: EBStartupAcceleration) { + if (isNormalGameSearchPage) { + positionAndPackageMap.keys.forEach { + if (it.contains(acctGameInfo.acctGameInfo.gameId)) { + val position = positionAndPackageMap[it] + if (position != null && mEntityList != null && position < mEntityList.size && mEntityList[position].game != null) { + mEntityList[position].game!!.lastAcctGame = acctGameInfo.acctGameInfo + notifyItemChanged(position) + } + } + } + } + } + + fun notifyItemAccelerationState() { + val lastGameId = gameIdBeingAccelerated + val currentGameId = AcceleratorDataHolder.instance.getAcceleratingGameId() + gameIdBeingAccelerated = currentGameId + + fun refreshAccelerationState(gameId: String) { + positionAndPackageMap.keys.forEach { + if (it.contains(gameId)) { + val position = positionAndPackageMap[it] + if (position != null && mEntityList != null && position < mEntityList.size && mEntityList[position].game != null) { + notifyItemChanged(position) + } + } + } + } + if (lastGameId.isNotBlank()) { + refreshAccelerationState(lastGameId) + } + if (currentGameId.isNotBlank()) { + refreshAccelerationState(currentGameId) + } + } + fun isLoadOver(): Boolean { return mIsOver } @@ -831,7 +896,7 @@ class SearchGameResultAdapter( searchMap: ArrayMap, exposureEvent: ExposureEvent, position: Int, - binding: GameItemBinding, + binding: SearchGameItemBinding, item: SearchItemData, context: Context, adapter: SearchGameResultAdapter @@ -907,14 +972,184 @@ class SearchGameResultAdapter( ) } - val viewHolder = GameViewHolder(binding) + val viewHolder = GameViewHolder(binding.root).apply { + gameDownloadBtn = binding.downloadBtn + gameDes = binding.gameDes + gameDownloadTips = binding.downloadTipsLottie + multiVersionDownloadTv = binding.multiVersionDownloadTv + gameRating = binding.gameRating + recommendContainer = binding.recommendContainer + recommendTv = binding.recommendTv + recommendIv = binding.recommendIv + recommendStarInfo = binding.recommendStarInfo + } - DownloadItemUtils.updateItem(context, gameEntity, viewHolder) + binding.downloadBtn.visibility = View.VISIBLE + DownloadItemUtils.updateItem( + context, + gameEntity, + viewHolder, + listener = object : DownloadButton.OnUpdateListener { + override fun completion(text: String) { + // 如果下载按钮状态为启动,则需要判断是否需要显示加速按钮 + showSpeedButton( + text, + gameEntity, + adapter.listViewModel, + binding + ) + } + }) binding.downloadBtn.putWidgetBusinessName("搜索列表") } } + private fun showSpeedButton( + text: String, + gameEntity: GameEntity, + viewModel: SearchGameResultViewModel, + binding: SearchGameItemBinding + ) { + val isNewUser = AcceleratorDataHolder.instance.isNewUser + if (text == "启动" && !gameEntity.isVGamePreferred() && gameEntity.canSpeed) { + val context = binding.root.context + binding.tvFreeVipTag.goneIf( + AcceleratorDataHolder.instance.hasAcctGameInfoInLocal || + (CheckLoginUtils.isLogin() && !isNewUser) + ) + binding.tvSpeed.goneIf(false) + binding.downloadBtn.goneIf(true) + + val serviceArea = gameEntity.serviceArea + val hasMutualityZone = serviceArea.size > 1 + + val lastAcctGame = gameEntity.lastAcctGame + val isCurrentGameAccelerating = AcceleratorDataHolder.instance.isCurrentGameAccelerating(gameEntity.id) + + if (isCurrentGameAccelerating) { + binding.vSelectRegion.goneIf(true) + binding.tvSelectRegion.goneIf(true) + binding.tvSpeed.setBackgroundResource(com.gh.gamecenter.common.R.drawable.bg_common_button_light_fill_blue) + binding.tvSpeed.text = R.string.accelerating.toResString() + binding.tvSpeed.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context)) + val drawable = ContextCompat.getDrawable(context, R.drawable.ic_basic_accelerator) + drawable?.setTint(com.gh.gamecenter.common.R.color.text_theme.toColor(context)) + binding.tvSpeed.setDrawableStart(drawable) + + } else { + binding.vSelectRegion.goneIf(!hasMutualityZone) + binding.tvSelectRegion.goneIf(!hasMutualityZone) + binding.tvSelectRegion.text = lastAcctGame?.zoneName ?: R.string.select_the_region.toResString() + binding.tvSpeed.setBackgroundResource(com.gh.gamecenter.common.R.drawable.bg_common_button_fill_gradient_blue) + binding.tvSpeed.text = R.string.network_acceleration.toResString() + binding.tvSpeed.setTextColor(com.gh.gamecenter.common.R.color.text_aw_primary.toColor(context)) + val drawable = ContextCompat.getDrawable(context, R.drawable.ic_basic_accelerator) + drawable?.setTint(com.gh.gamecenter.common.R.color.text_aw_primary.toColor(context)) + binding.tvSpeed.setDrawableStart(drawable) + } + + binding.tvSpeed.setOnClickListener { + val districtServer = when { + hasMutualityZone && lastAcctGame != null -> lastAcctGame.zoneName + hasMutualityZone -> DISTRICT_SERVER_EMPTY + else -> DISTRICT_SERVER_HAVA + } + SensorsBridge.trackNetworkAccelerationButtonClick( + gameEntity.getUniquePackageName() ?: "", + gameEntity.id, + gameEntity.name ?: "", + AcceleratorDataHolder.instance.memberType, + districtServer, + SCENE_TYPE_NO_GUIDE_LAYER, + SOURCE_ENTRANCE_SEARCH + ) + if (CheckLoginUtils.isLogin()) { + when { + !hasMutualityZone -> { + // 单区服,直接启动 + val zoneInfo = gameEntity.serviceArea.firstOrNull() ?: AcctGameInfo.ZoneInfo(0) + startAccelerating(gameEntity, context, zoneInfo, false) + } + + lastAcctGame != null -> { + // 多区服,有缓存的加速记录 + startAccelerating(gameEntity, context, lastAcctGame.zoneInfo, true) + } + + else -> { + // 多区服,没有缓存的加速记录 + AcceleratorZoneDialogFragment.show( + context, + null, + gameEntity.serviceArea.toArrayList(), + gameEntity, + SOURCE_ENTRANCE_SEARCH + ) + } + } + + } else { + CheckLoginUtils.checkLogin(context, null, true, "") { + // 登录成功,如果vip还未刷新成功,显示刷新 vip loading + if (AcceleratorDataHolder.instance.vipEntity == null) { + viewModel.showRefreshVipLoading(true) + } + + } + } + } + binding.tvSpeed.isClickable = !isCurrentGameAccelerating + + binding.vSelectRegion.setOnClickListener { + context.ifLogin("[网络加速]") { + AcceleratorZoneDialogFragment.show( + context, + lastAcctGame?.zoneInfo?.id, + gameEntity.serviceArea.toArrayList(), + gameEntity, + SOURCE_ENTRANCE_SEARCH + ) + } + } + + + } else { + binding.tvFreeVipTag.goneIf(true) + binding.tvSpeed.goneIf(true) + binding.vSelectRegion.goneIf(true) + binding.tvSelectRegion.goneIf(true) + } + } + + private fun startAccelerating( + gameEntity: GameEntity, + context: Context, + zoneInfo: AcctGameInfo.ZoneInfo, + hasMultiZone: Boolean + ) { + val vipEntity = AcceleratorDataHolder.instance.vipEntity ?: VipEntity() + val request = AcceleratorValidator.Request( + vipEntity.vipStatus, + vipEntity.isNewUser, + gameEntity, + SOURCE_ENTRANCE_SEARCH + ) + AcceleratorClient.newInstance() + .execute(context, request, object : AcceleratorValidator.ValidateListener { + override fun finished(context: Context) { + StartingAcceleratorDialogFragment.show( + context, + zoneInfo, + gameEntity, + true, + hasMultiZone, + SOURCE_ENTRANCE_SEARCH + ) + } + }) + } + private fun getContentTagView( iconRes: Int, iconUrl: String, diff --git a/app/src/main/java/com/gh/gamecenter/search/SearchGameResultFragment.kt b/app/src/main/java/com/gh/gamecenter/search/SearchGameResultFragment.kt index d7ec9f5265..662db77285 100644 --- a/app/src/main/java/com/gh/gamecenter/search/SearchGameResultFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/search/SearchGameResultFragment.kt @@ -1,5 +1,6 @@ package com.gh.gamecenter.search +import android.app.Dialog import android.os.Bundle import android.text.SpannableString import android.text.Spanned @@ -12,6 +13,7 @@ import android.view.animation.TranslateAnimation import android.widget.TextView import androidx.core.content.ContextCompat import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.gh.common.exposure.ExposureListener import com.gh.common.util.* @@ -27,19 +29,28 @@ import com.gh.gamecenter.common.constant.EntranceConsts import com.gh.gamecenter.common.entity.SuggestType import com.gh.gamecenter.common.utils.toColor import com.gh.gamecenter.common.utils.viewModelProvider +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.DisplayUtils import com.gh.gamecenter.core.utils.MtaHelper import com.gh.gamecenter.databinding.FragmentSearchResultBinding import com.gh.gamecenter.db.SearchHistoryDao import com.gh.gamecenter.eventbus.EBDownloadStatus import com.gh.gamecenter.eventbus.EBPackage +import com.gh.gamecenter.eventbus.EBStartupAcceleration import com.gh.gamecenter.feature.entity.GameEntity +import com.gh.gamecenter.feature.entity.VipEntity import com.gh.gamecenter.help.HelpAndFeedbackBridge +import com.gh.gamecenter.livedata.EventObserver +import com.gh.gamecenter.minigame.MiniGameSearchResultFragment import com.gh.gamecenter.search.viewmodel.SearchTabViewModel import com.halo.assistant.HaloApp +import com.halo.assistant.accelerator.repository.AcceleratorDataHolder import com.lightgame.download.DataWatcher import com.lightgame.download.DownloadEntity import com.lightgame.utils.Util_System_Keyboard +import com.therouter.TheRouter import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -48,6 +59,7 @@ open class SearchGameResultFragment : ListFragment( ownerProducer = { parentFragment ?: this } ) + // 上一次搜索的关键字和时间 private var previousSearchedPair: Pair = Pair("", 0) @@ -64,6 +76,9 @@ open class SearchGameResultFragment : ListFragment setParams(key, type) } + + + + if (this !is MiniGameSearchResultFragment) { + // 只有普通游戏搜索支持加速 + mListViewModel.refreshVipLoading.observe(viewLifecycleOwner, EventObserver { + if (it) { + refreshVipLoadingDialog = DialogUtils.showWaitDialog(requireContext(), "数据刷新中...") + } else { + refreshVipLoadingDialog?.dismiss() + } + }) + + iAcceleratorProvider?.bindAccRelatedListener("", onAccelerateListener) + + AcceleratorDataHolder.instance.addListener(onDataHolderListener) + } + + } private fun handleCloseMenuVisibility(recyclerView: RecyclerView) { @@ -380,7 +435,8 @@ open class SearchGameResultFragment : ListFragment> = repository.rechargeTrial(userId) + + /** + * 上传奇游错误日志 + * 由于错误日志可能没那么快生成,这里等待3s之后,在上报 + */ + @SuppressLint("CheckResult") + fun unloadAcceleratorErrorLog(game: GameEntity) { + Single.just(Unit) + .delay(3, TimeUnit.SECONDS) + .flatMap { + repository.getAcceleratorErrorLog() + }.flatMap { + val packageUtilsConfig = TheRouter.get(IPackageUtilsProvider::class.java) + val appProvider = TheRouter.get(IAppProvider::class.java) + + val body = hashMapOf( + "ghversion" to packageUtilsConfig?.getGhVersionName(), + "channel" to appProvider?.getChannel(), + "type" to Build.MODEL, + "sdk" to Build.VERSION.SDK_INT.toString(), + "version" to Build.VERSION.RELEASE, + "source" to R.string.app_name.toResString(), + "jnfj" to getBase64EncodedIMEI(), + "from" to "", + "game_id" to game.id, + "manufacturer" to Build.MANUFACTURER, + "rom" to MetaUtil.getRom().name + " " + MetaUtil.getRom().versionName, + "suggestion_type" to "加速器失败", + "message" to "加速失败, 游戏ID: ${game.id}, 游戏名: ${game.name ?: ""}", + "log" to it + ).toRequestBody() + + repository.uploadAcceleratorErrorLog(body) + }.subscribe({}, {}) + } + + 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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/halo/assistant/accelerator/MyAssetsActivity.kt b/app/src/main/java/com/halo/assistant/accelerator/MyAssetsActivity.kt new file mode 100644 index 0000000000..40e30d7359 --- /dev/null +++ b/app/src/main/java/com/halo/assistant/accelerator/MyAssetsActivity.kt @@ -0,0 +1,48 @@ +package com.halo.assistant.accelerator + +import android.graphics.Color +import android.os.Bundle +import com.gh.gamecenter.common.base.activity.BaseActivity +import com.gh.gamecenter.common.constant.Constants +import com.gh.gamecenter.common.constant.EntranceConsts.KEY_URL +import com.gh.gamecenter.common.constant.RouteConsts +import com.gh.gamecenter.common.utils.EnvHelper +import com.gh.gamecenter.core.utils.DisplayUtils +import com.halo.assistant.fragment.WebFragment +import com.therouter.router.Route + +@Route( + path = RouteConsts.activity.myAssetsActivity, + description = "我的资产" +) +class MyAssetsActivity : 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 url = if (EnvHelper.isDevEnv) { + Constants.MY_ASSETS_DEV + } else { + Constants.MY_ASSETS + } + val bundle = Bundle().apply { + putString(KEY_URL, url) + } + val containerFragment = supportFragmentManager.findFragmentByTag(WebFragment::class.java.name) + ?: WebFragment().with(bundle) + // 若 placeholder 外层为 RelativeLayout 的话,会出现莫名的偏移 + supportFragmentManager.beginTransaction() + .replace( + com.gh.gamecenter.selector.R.id.layout_activity_content, + containerFragment, + WebFragment::class.java.name + ) + .commitAllowingStateLoss() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/halo/assistant/accelerator/repository/AccelerationRepository.kt b/app/src/main/java/com/halo/assistant/accelerator/repository/AccelerationRepository.kt new file mode 100644 index 0000000000..629e1f0a53 --- /dev/null +++ b/app/src/main/java/com/halo/assistant/accelerator/repository/AccelerationRepository.kt @@ -0,0 +1,223 @@ +package com.halo.assistant.accelerator.repository + +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.gamedetail.accelerator.AccelerationDao +import com.gh.gamecenter.gamedetail.accelerator.AccelerationDataBase +import com.gh.gamecenter.login.user.UserManager +import com.gh.gamecenter.retrofit.RetrofitManager +import com.gh.gamecenter.retrofit.service.ApiService +import com.therouter.TheRouter +import io.reactivex.Flowable +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.schedulers.Schedulers +import okhttp3.RequestBody +import okhttp3.ResponseBody +import org.greenrobot.eventbus.EventBus +import java.util.concurrent.TimeUnit + +/** + * 加速器相关的操作,统一放在这里 + */ +class AccelerationRepository private constructor( + private val newApi: ApiService, + private val api: ApiService, + private val accelerationDao: AccelerationDao, +) { + + fun preOrderWithAli(goodId: String): Single { + val userId = UserManager.getInstance().userId + return newApi.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 { + 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> { + val userId = UserManager.getInstance().userId + return newApi.preOrderWithWechat(goodId, userId, VIP_TYPE) + } + + fun sendWechatPayRequest(order: OrderEntity, payEntity: WechatPayEntity) { + TheRouter.get(IWechatPayProvider::class.java)?.payRequest(order, payEntity) + } + + fun getWechatPayResult(orderNo: String): Single { + 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() { + // 先刷新本地状态,支付成功,肯定是付费会员 + AcceleratorDataHolder.instance.setVipEntity( + VipEntity( + _vipStatus = true, + _isNewUser = false, + _isTryVip = false + ) + ) + } + + @SuppressLint("CheckResult") + fun recordAcctGameInfo(gameId: String, zoneInfo: AcctGameInfo.ZoneInfo, hasMultiZone: Boolean) { + val userId = UserManager.getInstance().userId + val body = + hashMapOf( + "game_id" to gameId, + "service_area_id" to zoneInfo.id, + "service_area" to zoneInfo.cnName, + "is_more_service_area" to hasMultiZone + ).toRequestBody() + RetrofitManager.getInstance().newApi + .recordAcctGameInfo(userId, VIP_TYPE, body) + .subscribeOn(Schedulers.io()) + .subscribe({}, {}) + } + + @SuppressLint("CheckResult") + fun saveAcctRecordToLocal(game: GameEntity, zoneInfo: AcctGameInfo.ZoneInfo, hasMultiZone: Boolean) { + val record = + AcctRecord(game.id, game, zoneInfo, hasMultiZone, System.currentTimeMillis()) + accelerationDao.upsertAcctRecord(record) + .subscribeOn(Schedulers.io()) + .subscribe({}, {}) + + } + + fun rechargeTrial(userId: String): Single> = + newApi.rechargeTrial(userId, VIP_TYPE) + + fun getAcceleratorErrorLog(): Single = Single.create { emitter -> + val logFileStorageList = TheRouter.get(IAcceleratorProvider::class.java)?.getAccLogFileStorageList() + val content = buildString { + logFileStorageList?.forEachIndexed { index, file -> + if (index > 0) { + append("\n") + } + when { + !file.exists() -> { + appendLine("\n---------------\n") + } + + !file.canRead() -> { + appendLine("文件无法读取") + } + + else -> { + file.bufferedReader().use { reader -> + reader.lineSequence().forEach { line -> + appendLine(line) + } + } + } + } + } + } + if (content.isBlank()) { + emitter.onSuccess("log is empty") + } else { + emitter.onSuccess(content) + } + } + + fun uploadAcceleratorErrorLog(body: RequestBody): Single = + api.uploadAcceleratorErrorLog(body) + + fun getLastAcctGameInfoObservable(gameId: String): Observable> = + accelerationDao.getAcctGameInfoByGameIdObservable(gameId) + + + fun getHasAnyAcctRecordObservable() = accelerationDao.getAnyAcctRecordObservable() + + 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 = "微信" + + fun newInstance() = AccelerationRepository( + RetrofitManager.getInstance().newApi, + RetrofitManager.getInstance().api, + AccelerationDataBase.instance.accelerationDao() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/halo/assistant/accelerator/repository/AcceleratorDataHolder.kt b/app/src/main/java/com/halo/assistant/accelerator/repository/AcceleratorDataHolder.kt new file mode 100644 index 0000000000..59b68a3f9b --- /dev/null +++ b/app/src/main/java/com/halo/assistant/accelerator/repository/AcceleratorDataHolder.kt @@ -0,0 +1,120 @@ +package com.halo.assistant.accelerator.repository + +import android.annotation.SuppressLint +import com.gh.common.util.CheckLoginUtils +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.AcctRecord +import com.gh.gamecenter.feature.entity.VipEntity +import com.gh.gamecenter.gamedetail.accelerator.AccelerationDataBase +import com.therouter.TheRouter + +/** + * 单利:保存加速相关的一些全局信息 + * 如:vip状态,加速状态 + */ +class AcceleratorDataHolder { + + private val iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java) + + private val listeners = mutableSetOf() + + private var _hasAcctGameInfoInLocal = false + val hasAcctGameInfoInLocal: Boolean + get() = _hasAcctGameInfoInLocal + + @SuppressLint("CheckResult") + fun init() { + AccelerationDataBase.instance.accelerationDao() + .loadLatestRecord() + .compose(singleToMain()) + .subscribe(object : BiResponse() { + override fun onSuccess(data: AcctRecord) { + acctGameRecord = data + } + }) + AccelerationDataBase.instance.accelerationDao() + .hasAnyAcctGameInfo() + .compose(singleToMain()) + .subscribe(object : BiResponse() { + override fun onSuccess(data: Boolean) { + _hasAcctGameInfoInLocal = data + } + }) + + } + + // 最后一次加速的游戏id(失败或者成功都会记录) + var acctGameRecord: AcctRecord? = null + set(value) { + _hasAcctGameInfoInLocal = true + field = value + } + + fun isCurrentGameAccelerating(gameId: String) = + iAcceleratorProvider?.isCurAccSuccess() ?: false && + acctGameRecord?.gameId == gameId + + fun getAcceleratingGameId() = if (iAcceleratorProvider?.isCurAccSuccess() == true) { + acctGameRecord?.gameId ?: "" + } else { + "" + } + + private var _vipEntity: VipEntity? = null + val vipEntity: VipEntity? + get() = _vipEntity + + val isVip: Boolean + get() = _vipEntity?.vipStatus ?: false + + val isNewUser: Boolean + get() = _vipEntity?.isNewUser ?: false + + val memberType: String + get() = when { + !CheckLoginUtils.isLogin() -> MEMBER_TYPE_NOT_LOGIN + _vipEntity?.vipStatus == true && _vipEntity?.isTryVip == true -> MEMBER_TYPE_FREE_MEMBER + _vipEntity?.vipStatus == true -> MEMBER_TYPE_PAID_MEMBER + else -> MEMBER_TYPE_NONE_MEMBER + } + + fun setVipEntity(vip: VipEntity) { + if (_vipEntity != vip) { + _vipEntity = vip + listeners.forEach { + it.onVipStateChanged(vip) + } + } + } + + fun addListener(listener: OnDataHolderListener) { + listeners.add(listener) + } + + fun removeListener(listener: OnDataHolderListener) { + listeners.remove(listener) + } + + fun clear() { + _vipEntity = null + listeners.clear() + } + + companion object { + private const val MEMBER_TYPE_PAID_MEMBER = "付费会员" + private const val MEMBER_TYPE_FREE_MEMBER = "免费会员" + private const val MEMBER_TYPE_NONE_MEMBER = "非会员" + const val MEMBER_TYPE_NOT_LOGIN = "未登录" + + val instance by lazy { + AcceleratorDataHolder() + } + } + + interface OnDataHolderListener { + + fun onVipStateChanged(vip: VipEntity) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/halo/assistant/fragment/WebFragment.kt b/app/src/main/java/com/halo/assistant/fragment/WebFragment.kt index eeb1bece73..cb0a047b92 100644 --- a/app/src/main/java/com/halo/assistant/fragment/WebFragment.kt +++ b/app/src/main/java/com/halo/assistant/fragment/WebFragment.kt @@ -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.GameDetailWrapperFragment +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.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.accelerator.AccelerationUseCase +import com.halo.assistant.accelerator.repository.AcceleratorDataHolder 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() + private var mBinding: FragmentWebBinding? = null var mMenuShare: MenuItem? = null var mMenuCollect: MenuItem? = null @@ -114,6 +138,19 @@ class WebFragment : LazyFragment(), IScrollable { } } + private val acceleratorDataHolder = AcceleratorDataHolder.instance + + 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 onCreate(savedInstanceState: Bundle?) { updateIsWebViewInstalled() super.onCreate(savedInstanceState) @@ -311,7 +348,125 @@ 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 game = accInfo.game + val hasMultiZone = accInfo.isMoreServiceArea + val zoneInfo = AcctGameInfo.ZoneInfo(accInfo.serviceAreaId, accInfo.serviceArea) + + val memberType = acceleratorDataHolder.memberType + + 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 = acceleratorDataHolder.isVip + 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, + zoneInfo, + game, + false, + hasMultiZone, + SOURCE_ENTRANCE_MY_ASSETS + ) + } + }) + } + + } + + override fun onStopGameAccelerate() { + context?.let { + val acctGameRecord = AcceleratorDataHolder.instance.acctGameRecord + + SensorsBridge.trackNetworkAccelerationOtherButtonClick( + acctGameRecord?.game?.getUniquePackageName() ?: "", + acctGameRecord?.gameId ?: "", + acctGameRecord?.game?.name ?: "", + acceleratorDataHolder.memberType, + BUTTON_NAME_STOP_ACCELERATOR, + SOURCE_ENTRANCE_MY_ASSETS + ) + AcceleratorDialogFragment.show( + SPEED_STOP, + acctGameRecord?.game?.getUniquePackageName() ?: "", + acctGameRecord?.gameId ?: "", + acctGameRecord?.game?.name ?: "", + 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 AccelerationUseCase.UserVerifyState.UnVerified -> { // 未实名 + UserVerifyDialogUtils.showUnVerifiedDialog( + requireContext(), + R.string.archive_dialog_title.toResString(), + it.toast ?: R.string.recharge_without_real_name_description.toResString() + ) + } + + is AccelerationUseCase.UserVerifyState.Minors -> { // 已实名:未成年 + UserVerifyDialogUtils.showMinorsOrVerifyingDialog( + requireContext(), + R.string.archive_dialog_title.toResString(), + it.toast ?: R.string.recharge_with_minors_description.toResString() + ) + } + + is AccelerationUseCase.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 +517,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 +768,42 @@ class WebFragment : LazyFragment(), IScrollable { } } + private fun onAccelerateCallback() { + val gameId = AcceleratorDataHolder.instance.acctGameRecord?.gameId ?: "" + 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 +814,7 @@ class WebFragment : LazyFragment(), IScrollable { removeJavascriptObject("share") removeJavascriptObject("internal") } + TheRouter.get(IAcceleratorProvider::class.java)?.unBindAccRelatedListener(acctListener) } private fun getNewsCommentNum() { @@ -681,6 +887,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? -> } diff --git a/app/src/main/java/com/halo/assistant/fragment/WebViewModel.kt b/app/src/main/java/com/halo/assistant/fragment/WebViewModel.kt new file mode 100644 index 0000000000..c83cf6076a --- /dev/null +++ b/app/src/main/java/com/halo/assistant/fragment/WebViewModel.kt @@ -0,0 +1,14 @@ +package com.halo.assistant.fragment + +import androidx.lifecycle.ViewModel +import com.halo.assistant.accelerator.AccelerationUseCase + +class WebViewModel:ViewModel() { + + val memberUseCase = AccelerationUseCase() + + override fun onCleared() { + super.onCleared() + memberUseCase.onClear() + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-night/bg_accelerator_guide.xml b/app/src/main/res/drawable-night/bg_accelerator_guide.xml new file mode 100644 index 0000000000..a0a828b0b4 --- /dev/null +++ b/app/src/main/res/drawable-night/bg_accelerator_guide.xml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/bg_accelerator_guide.xml b/app/src/main/res/drawable/bg_accelerator_guide.xml new file mode 100644 index 0000000000..c147062275 --- /dev/null +++ b/app/src/main/res/drawable/bg_accelerator_guide.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/bg_accelerator_guide_download.xml b/app/src/main/res/drawable/bg_accelerator_guide_download.xml new file mode 100644 index 0000000000..154bc07aad --- /dev/null +++ b/app/src/main/res/drawable/bg_accelerator_guide_download.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_accelerator_recently_played.xml b/app/src/main/res/drawable/ic_accelerator_recently_played.xml new file mode 100644 index 0000000000..90d5c80367 --- /dev/null +++ b/app/src/main/res/drawable/ic_accelerator_recently_played.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_basic_accelerator.xml b/app/src/main/res/drawable/ic_basic_accelerator.xml new file mode 100644 index 0000000000..75494a77bd --- /dev/null +++ b/app/src/main/res/drawable/ic_basic_accelerator.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_basic_x.xml b/app/src/main/res/drawable/ic_basic_x.xml new file mode 100644 index 0000000000..81f7d48c13 --- /dev/null +++ b/app/src/main/res/drawable/ic_basic_x.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_basic_x_18.xml b/app/src/main/res/drawable/ic_basic_x_18.xml new file mode 100644 index 0000000000..9728a56940 --- /dev/null +++ b/app/src/main/res/drawable/ic_basic_x_18.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_free_vip_tag.xml b/app/src/main/res/drawable/ic_free_vip_tag.xml new file mode 100644 index 0000000000..578b1fd9b7 --- /dev/null +++ b/app/src/main/res/drawable/ic_free_vip_tag.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_free_vip_tag_detail.xml b/app/src/main/res/drawable/ic_free_vip_tag_detail.xml new file mode 100644 index 0000000000..5180ba053c --- /dev/null +++ b/app/src/main/res/drawable/ic_free_vip_tag_detail.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_new_function_text.xml b/app/src/main/res/drawable/ic_new_function_text.xml new file mode 100644 index 0000000000..97084ff2fb --- /dev/null +++ b/app/src/main/res/drawable/ic_new_function_text.xml @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_triangle_down.xml b/app/src/main/res/drawable/ic_triangle_down.xml new file mode 100644 index 0000000000..a63546fbaa --- /dev/null +++ b/app/src/main/res/drawable/ic_triangle_down.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/detail_download_item.xml b/app/src/main/res/layout/detail_download_item.xml index 5a0b10de1d..3e1606c867 100644 --- a/app/src/main/res/layout/detail_download_item.xml +++ b/app/src/main/res/layout/detail_download_item.xml @@ -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="选择下载你的版本" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_fragment_accelerator_zone.xml b/app/src/main/res/layout/dialog_fragment_accelerator_zone.xml new file mode 100644 index 0000000000..bd837b84f3 --- /dev/null +++ b/app/src/main/res/layout/dialog_fragment_accelerator_zone.xml @@ -0,0 +1,34 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_fragment_speed_failure.xml b/app/src/main/res/layout/dialog_fragment_speed_failure.xml new file mode 100644 index 0000000000..a24b768259 --- /dev/null +++ b/app/src/main/res/layout/dialog_fragment_speed_failure.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_fragment_speed_replace_game.xml b/app/src/main/res/layout/dialog_fragment_speed_replace_game.xml new file mode 100644 index 0000000000..c88528246f --- /dev/null +++ b/app/src/main/res/layout/dialog_fragment_speed_replace_game.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_fragment_speed_without_login.xml b/app/src/main/res/layout/dialog_fragment_speed_without_login.xml new file mode 100644 index 0000000000..e45e55fe61 --- /dev/null +++ b/app/src/main/res/layout/dialog_fragment_speed_without_login.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_fragment_starting_accelerator.xml b/app/src/main/res/layout/dialog_fragment_starting_accelerator.xml new file mode 100644 index 0000000000..6a1fe6c0c6 --- /dev/null +++ b/app/src/main/res/layout/dialog_fragment_starting_accelerator.xml @@ -0,0 +1,37 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_halo_personal.xml b/app/src/main/res/layout/fragment_halo_personal.xml index b0e1b8873a..460af079c8 100644 --- a/app/src/main/res/layout/fragment_halo_personal.xml +++ b/app/src/main/res/layout/fragment_halo_personal.xml @@ -132,11 +132,18 @@ tools:visibility="visible" /> - + diff --git a/app/src/main/res/layout/item_home_recent_vgame_custom.xml b/app/src/main/res/layout/item_home_recent_vgame_custom.xml index e16748aaa7..2429247b88 100644 --- a/app/src/main/res/layout/item_home_recent_vgame_custom.xml +++ b/app/src/main/res/layout/item_home_recent_vgame_custom.xml @@ -88,12 +88,11 @@ diff --git a/app/src/main/res/layout/item_home_vgame_refactor.xml b/app/src/main/res/layout/item_home_vgame_refactor.xml index 6b3cf93781..2ed01d520a 100644 --- a/app/src/main/res/layout/item_home_vgame_refactor.xml +++ b/app/src/main/res/layout/item_home_vgame_refactor.xml @@ -2,8 +2,10 @@ + android:layout_width="48dp" + android:layout_height="48dp" + android:clipToPadding="false" + android:clipChildren="false"> + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/recycler_accelerator_zone.xml b/app/src/main/res/layout/recycler_accelerator_zone.xml new file mode 100644 index 0000000000..1c39eba3db --- /dev/null +++ b/app/src/main/res/layout/recycler_accelerator_zone.xml @@ -0,0 +1,14 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/search_game_first_item.xml b/app/src/main/res/layout/search_game_first_item.xml index 2c5c661ac8..2b9f80f761 100644 --- a/app/src/main/res/layout/search_game_first_item.xml +++ b/app/src/main/res/layout/search_game_first_item.xml @@ -16,7 +16,7 @@ + layout="@layout/search_game_item" /> + layout="@layout/search_game_item" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 9ff818721d..24a2263751 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -650,5 +650,35 @@ 我的遊戲 玩過 預約 + 提示 + 請您完成登入後,在使用加速啟動遊戲 + 去登入 + 開通會員使用加速功能! + 去開通 + 加速失敗,請聯絡客服 + 聯絡客服 + 偵測到您目前正在加速遊戲,繼續啟動將停止原遊戲的加速,並加速啟動【%1$s】,是否繼續? + 偵測到您目前遊戲正在加速,停止加速可能會導致遊戲斷線,是否繼續? + 暫不啟動 + 繼續啟動 + 停止加速可能會導致遊戲斷線,是否繼續? + 停止加速 + 取消 + 選擇加速區服 + 網路加速 + 加速中...%1$s%% + 加速中 + 進入遊戲 + 偵測到您尚未安裝對應遊戲,請先安裝遊戲後再啟動加速器! + 已複製客服QQ號 + 實名提示 + 根據相關政策要求,該遊戲需通過認證後才能加速 + 前往實名認證 + 響應國家號召,本平台僅對成年人提供儲值服務,請先完成實名認證 + 響應國家號召,保護未成年人健康參與遊戲,本平台暫不對未成年人提供儲值服務 + 您實名認證處於【認證中】,暫不能進行充值,請等待完成認證 + 選擇加速區服 + 新用戶免費3小時 + 最近在玩 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2a23789e85..2ba003266f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -650,4 +650,34 @@ 我的游戏 玩过 预约 + 提示 + 请您完成登录后,在使用加速启动游戏 + 去登录 + 开通会员使用加速功能! + 去开通 + 加速失败,请联系客服 + 联系客服 + 检测到您当前正在加速游戏,继续启动将停止原游戏的加速,并加速启动【%1$s】,是否继续? + 检测到您当前游戏正在加速,停止加速可能会导致游戏断线,是否继续? + 暂不启动 + 继续启动 + 停止加速可能会导致游戏断线,是否继续? + 停止加速 + 取消 + 选择加速区服 + 网络加速 + 加速中...%1$s%% + 加速中 + 进入游戏 + 检测到您尚未安装对应游戏,请先安装游戏后再启动加速器! + 已复制客服QQ号 + 实名提示 + 根据相关政策要求,该游戏需通过认证后才能进行加速 + 前往实名认证 + 响应国家号召,本平台仅对成年人提供充值服务,请先完成实名认证 + 响应国家号召,保护未成年人健康参与游戏,本平台暂不对未成年人提供充值服务 + 您实名认证处于【认证中】,暂不能进行充值,请等待完成认证 + 选择加速区服 + 新用户免费3小时 + 最近在玩 diff --git a/app/src/main/res/xml/personal_scene.xml b/app/src/main/res/xml/personal_scene.xml index 86328eec5a..5d588c1417 100644 --- a/app/src/main/res/xml/personal_scene.xml +++ b/app/src/main/res/xml/personal_scene.xml @@ -20,6 +20,10 @@ android:alpha="0" motion:framePosition="20" motion:motionTarget="@+id/iv_arrow" /> + @@ -97,6 +101,14 @@ motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintEnd_toEndOf="parent" /> + @@ -188,5 +200,17 @@ android:alpha="0" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintEnd_toEndOf="parent" /> + + \ No newline at end of file diff --git a/dependencies.gradle b/dependencies.gradle index 94f5a85946..34ca3e03c1 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -7,8 +7,8 @@ ext { targetSdkVersion = 30 // 升级targetSdkVersion到 34 时需要根据官方文档补全前台服务的权限类型。比如 NDownloadService,KeepAliveService // application info (每个大版本之间的 versionCode 增加 20) - versionCode = 1130 - versionName = "5.39.0" + versionCode = 1131 + versionName = "5.39.1" applicationId = "com.gh.gamecenter" applicationIdGat = "com.gh.gamecenter.intl" @@ -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' diff --git a/feature/accelerator/build.gradle b/feature/accelerator/build.gradle new file mode 100644 index 0000000000..58a01f5716 --- /dev/null +++ b/feature/accelerator/build.gradle @@ -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}" +} \ No newline at end of file diff --git a/feature/accelerator/proguard-rules.pro b/feature/accelerator/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/feature/accelerator/proguard-rules.pro @@ -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 \ No newline at end of file diff --git a/feature/accelerator/src/main/AndroidManifest.xml b/feature/accelerator/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..e3a95d18ed --- /dev/null +++ b/feature/accelerator/src/main/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/feature/accelerator/src/main/java/com/gh/gamecenter/accelerator/provider/AcceleratorProviderImpl.kt b/feature/accelerator/src/main/java/com/gh/gamecenter/accelerator/provider/AcceleratorProviderImpl.kt new file mode 100644 index 0000000000..8b0201c1bd --- /dev/null +++ b/feature/accelerator/src/main/java/com/gh/gamecenter/accelerator/provider/AcceleratorProviderImpl.kt @@ -0,0 +1,173 @@ +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.QyAcctGameInfo +import com.qeeyou.qyvpn.utils.OnQyAccelerateListener +import com.qeeyou.qyvpn.utils.QyAccConfig +import java.io.File + +/** + * 单例模式,可以用户保存一些加速器全局状态信息 + */ +@com.therouter.inject.ServiceProvider +class AcceleratorProviderImpl : IAcceleratorProvider { + private var vipEntity: VipEntity? = null + private var _token = "" + + // 根据包名回调 + private val listeners = hashMapOf() + + // 所有包名都回调(pkgName.isBlank()) + private val allListener = mutableSetOf() + 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(isDev: Boolean, application: Application, version: String) { + Utils.log(LOG_TAG, "init:$version") + val qyAccConfigBuilder = QyAccConfig.Builder().setAppId("QyAccSdk") + .setDebug(isDev) + .setAppVersion(version) + .setServerAddressEnv(if (isDev) QyAccConfig.ServerAddressEnv.DebugProImg else QyAccConfig.ServerAddressEnv.Release) + .build() + QyAccelerator.getInstance().init(application, qyAccConfigBuilder, QyAccelerator.QyAccStrategy.HaloRing) + QyAccelerator.getInstance().bindQyAccRelatedListener(qyListener) + QyAccelerator.getInstance().switchAccLogCanUpload(true) + } + + override fun setQyUserToken(token: String, callback: ((Boolean) -> Unit)?) { + Utils.log(LOG_TAG, "setQyUserToken") + if (_token == token) { + // 避免外部多次设置相同的token + return + } + QyAccelerator.getInstance().setQyUserToken(token, setResultCallback = { isSuccess, errMsg -> + Utils.log(LOG_TAG, "setQyUserToken:$token --isSuccess:$isSuccess --errMsg:$errMsg") + if (isSuccess) { + _token = token + } else { + // 将setToken错误事件上报的sentry,便于后期分析原因 + SentryHelper.onEventInAllChannel(SENTRY_EVENT_ID, KEY_SET_TOKEN_ERROR_MESSAGE, errMsg) + } + callback?.invoke(isSuccess) + }) + } + + override fun deleteQyUserToken(): Boolean { + 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(gameId: String, pkgName: String, zoneInfo: Any) { + QyAccelerator.getInstance().clearAllAccLogFile() + Utils.log(LOG_TAG, "startQyGameAccelerate:$zoneInfo") + if (zoneInfo is AcctGameInfo.ZoneInfo) { + val qyAcctGameInfo = QyAcctGameInfo( + pkgName, + zoneInfo.id.toString() + ) + curAcctGameInfo = AcctGameInfo(gameId, pkgName, zoneInfo) + 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 isCurAccSuccess(): Boolean { + return QyAccelerator.getInstance().isCurAccSuccess() + } + + override fun getAccLogFileStorageList(): List? { + return QyAccelerator.getInstance().getAccLogFileStorageList() + } + + companion object { + private const val LOG_TAG = "AcceleratorProviderImpl" + private const val SENTRY_EVENT_ID = "ACCELERATOR_SET_TOKEN_ERROR" + private const val KEY_SET_TOKEN_ERROR_MESSAGE = "set_token_error" + private const val KEY_ACC_FAILURE_ERROR = "key_acc_failure_error" + + } + +} \ No newline at end of file diff --git a/feature/accelerator/src/main/res/values-zh-rTW/themes.xml b/feature/accelerator/src/main/res/values-zh-rTW/themes.xml new file mode 100644 index 0000000000..708cb75e4a --- /dev/null +++ b/feature/accelerator/src/main/res/values-zh-rTW/themes.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/feature/accelerator/src/main/res/values/strings.xml b/feature/accelerator/src/main/res/values/strings.xml new file mode 100644 index 0000000000..972aca8425 --- /dev/null +++ b/feature/accelerator/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + accelerator + \ No newline at end of file diff --git a/feature/ali_pay/build.gradle b/feature/ali_pay/build.gradle new file mode 100644 index 0000000000..b783d5f69b --- /dev/null +++ b/feature/ali_pay/build.gradle @@ -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}" +} \ No newline at end of file diff --git a/feature/ali_pay/proguard-rules.pro b/feature/ali_pay/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/feature/ali_pay/proguard-rules.pro @@ -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 \ No newline at end of file diff --git a/feature/ali_pay/src/main/AndroidManifest.xml b/feature/ali_pay/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..0971662520 --- /dev/null +++ b/feature/ali_pay/src/main/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/feature/ali_pay/src/main/java/com/game/gamecenter/alipay/AliPayProviderImpl.kt b/feature/ali_pay/src/main/java/com/game/gamecenter/alipay/AliPayProviderImpl.kt new file mode 100644 index 0000000000..e95a2e063c --- /dev/null +++ b/feature/ali_pay/src/main/java/com/game/gamecenter/alipay/AliPayProviderImpl.kt @@ -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 { + 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 + } +} diff --git a/feature/ali_pay/src/main/java/com/game/gamecenter/alipay/H5PayActivity.kt b/feature/ali_pay/src/main/java/com/game/gamecenter/alipay/H5PayActivity.kt new file mode 100644 index 0000000000..1d17bff91e --- /dev/null +++ b/feature/ali_pay/src/main/java/com/game/gamecenter/alipay/H5PayActivity.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/feature/ali_pay/src/main/res/values-night/themes.xml b/feature/ali_pay/src/main/res/values-night/themes.xml new file mode 100644 index 0000000000..708cb75e4a --- /dev/null +++ b/feature/ali_pay/src/main/res/values-night/themes.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/feature/ali_pay/src/main/res/values/colors.xml b/feature/ali_pay/src/main/res/values/colors.xml new file mode 100644 index 0000000000..c8524cd961 --- /dev/null +++ b/feature/ali_pay/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/feature/ali_pay/src/main/res/values/strings.xml b/feature/ali_pay/src/main/res/values/strings.xml new file mode 100644 index 0000000000..2065f32d80 --- /dev/null +++ b/feature/ali_pay/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + ali_pay + \ No newline at end of file diff --git a/feature/ali_pay/src/main/res/values/themes.xml b/feature/ali_pay/src/main/res/values/themes.xml new file mode 100644 index 0000000000..e291fb4fb9 --- /dev/null +++ b/feature/ali_pay/src/main/res/values/themes.xml @@ -0,0 +1,9 @@ + + + + +