Merge remote-tracking branch 'origin/dev' into dev-5.31.0

# Conflicts:
#	app/src/main/java/com/gh/gamecenter/fragment/MainWrapperFragment.java
#	app/src/main/java/com/gh/gamecenter/game/horizontal/GameHorizontalAdapter.kt
This commit is contained in:
chenjuntao
2023-08-25 16:38:27 +08:00
137 changed files with 4807 additions and 375 deletions

View File

@ -348,6 +348,9 @@ dependencies {
implementation(project(':feature:csj_ad'))
// implementation(project(':feature:beizi_startup_ad'))
implementation(project(':feature:xapk-installer'))
implementation(project(':feature:qq_game')) {
exclude group: 'androidx.swiperefreshlayout'
}
}
File propFile = file('sign.properties')

View File

@ -78,7 +78,11 @@
androidx.constraintlayout.compose,
androidx.compose.ui.test.manifest,
com.bytedance.sdk.openadsdk,
androidx.compose.ui.tooling.preview" />
androidx.compose.ui.tooling.preview,
com.tencent.qqmini,
com.tencent.qqmini.minigame.external,
com.tencent.qqmini.minigame.opensdk,
com.tencent.qqmini.union.ad" />
<!-- 去掉 SDK 一些流氓权限 -->
<uses-permission
@ -741,6 +745,18 @@
android:name="com.gh.gamecenter.gamecollection.hotlist.GameCollectionHotListActivity"
android:screenOrientation="portrait" />
<activity
android:name="com.gh.gamecenter.qgame.QGameHomeWrapperActivity"
android:screenOrientation="portrait" />
<activity
android:name="com.gh.gamecenter.qgame.QGameSearchActivity"
android:screenOrientation="portrait" />
<activity
android:name="com.gh.gamecenter.qgame.QGameSubjectActivity"
android:screenOrientation="portrait" />
<!-- <activity-->
<!-- android:name="${applicationId}.douyinapi.DouYinEntryActivity"-->

View File

@ -1,18 +1,20 @@
package com.gh.base
import android.graphics.Typeface
import android.os.Bundle
import android.text.TextUtils
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import com.gh.download.DownloadManager
import com.gh.gamecenter.DownloadManagerActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.ToolBarActivity
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.SPUtils.getBoolean
import com.gh.gamecenter.entity.GameUpdateEntity
import com.gh.gamecenter.eventbus.EBDownloadStatus
@ -58,6 +60,7 @@ abstract class DownloadToolbarActivity : ToolBarActivity() {
}
val downloadMenuView = mActionMenuView.menu.findItem(R.id.menu_download).actionView
mDownloadCountHint = downloadMenuView?.findViewById(R.id.menu_download_count_hint)
mDownloadCountHint?.typeface = Typeface.createFromAsset(assets, "fonts/d_din_bold_only_number.ttf")
}
override fun onMenuItemClick(item: MenuItem?): Boolean {
@ -74,19 +77,27 @@ abstract class DownloadToolbarActivity : ToolBarActivity() {
if (mDownloadCountHint == null) return
val count = DownloadManager.getInstance().getDownloadOrUpdateCount(updateList)
if (count != null) {
mDownloadCountHint!!.visibility = View.VISIBLE
mDownloadCountHint!!.text = count
val params = mDownloadCountHint!!.layoutParams
if (TextUtils.isEmpty(count)) {
params.width = DisplayUtils.dip2px(6f)
params.height = DisplayUtils.dip2px(6f)
} else {
params.width = DisplayUtils.dip2px(12f)
params.height = DisplayUtils.dip2px(12f)
}
mDownloadCountHint!!.layoutParams = params
mDownloadCountHint?.visibility = View.VISIBLE
mDownloadCountHint?.text = count
val params = mDownloadCountHint?.layoutParams
params?.width = if (count.isEmpty()) 6F.dip2px() else ConstraintLayout.LayoutParams.WRAP_CONTENT
params?.height = if (count.isEmpty()) 6F.dip2px() else 14F.dip2px()
(params as? ViewGroup.MarginLayoutParams)?.setMargins(
0,
if (count.isEmpty()) 0 else (-4F).dip2px(),
if (count.isEmpty()) (-4F).dip2px() else (-8F).dip2px(),
0
)
mDownloadCountHint?.setPadding(
if (count.isEmpty()) 0 else 4F.dip2px(),
0,
if (count.isEmpty()) 0 else 4F.dip2px(),
0
)
mDownloadCountHint?.minWidth = if (count.isEmpty()) 0 else 14F.dip2px()
mDownloadCountHint?.layoutParams = params
} else {
mDownloadCountHint!!.visibility = View.GONE
mDownloadCountHint?.visibility = View.GONE
}
}

View File

@ -19,6 +19,10 @@ class AppProviderImpl : IAppProvider {
return HaloApp.getInstance().getString(R.string.app_name)
}
override fun getAppVersion(): String {
return BuildConfig.VERSION_NAME
}
override fun getGid(): String {
return HaloApp.getInstance().gid ?: ""
}

View File

@ -115,6 +115,10 @@ class DirectProviderImpl : IDirectProvider {
DirectUtils.directToWinOrderDetail(context, orderId, activityId)
}
override fun directToQGame(context: Context) {
return DirectUtils.directToQGameHome(context)
}
override fun init(context: Context?) {
// Do nothing
}

View File

@ -1,5 +1,6 @@
package com.gh.common.util
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
@ -29,9 +30,9 @@ import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.EntranceConsts.*
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.*
import com.gh.gamecenter.common.entity.Display
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.provider.IQGameProvider
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.discovery.DiscoveryActivity
@ -71,6 +72,9 @@ import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity
import com.gh.gamecenter.qa.questions.newdetail.NewQuestionDetailActivity
import com.gh.gamecenter.qa.subject.CommunitySubjectActivity
import com.gh.gamecenter.qa.video.detail.ForumVideoDetailActivity
import com.gh.gamecenter.qgame.QGameHomeWrapperActivity
import com.gh.gamecenter.qgame.QGameSearchActivity
import com.gh.gamecenter.qgame.QGameViewModel
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.servers.GameServerTestActivity
import com.gh.gamecenter.servers.GameServersActivity
@ -94,7 +98,6 @@ import org.greenrobot.eventbus.EventBus
import retrofit2.HttpException
import java.net.URLEncoder
import java.util.*
import kotlin.collections.ArrayList
import kotlin.math.roundToInt
/**
@ -422,6 +425,8 @@ object DirectUtils {
)
)
"qq_mini_game_column" -> directToQGameHome(context)
"" -> {
// do nothing
}
@ -711,11 +716,12 @@ object DirectUtils {
id: String,
subjectName: String? = "",
entrance: String? = null,
exposureEvent: ExposureEvent? = null
exposureEvent: ExposureEvent? = null,
isQQMiniGame: Boolean = false,
) {
if (id.isEmpty()) return
val bundle = Bundle()
val subjectData = SubjectData(subjectId = id, subjectName = subjectName, isOrder = false)
val subjectData = SubjectData(subjectId = id, subjectName = subjectName, isOrder = false, isQQMiniGame = isQQMiniGame)
bundle.putString(KEY_ENTRANCE, entrance ?: ENTRANCE_BROWSER)
bundle.putString(KEY_TO, SubjectActivity::class.java.name)
bundle.putParcelable(EntranceConsts.KEY_SUBJECT_DATA, subjectData)
@ -2024,6 +2030,61 @@ object DirectUtils {
)
}
@JvmStatic
fun directToQGameHome(context: Context) {
context.startActivity(QGameHomeWrapperActivity.getIntent(context))
}
@JvmStatic
fun directToQGameSearch(
context: Context,
hint: String,
sourceEntrance: String
) {
context.startActivity(QGameSearchActivity.getIntent(context, hint, sourceEntrance))
}
@SuppressLint("CheckResult")
@JvmStatic
fun directToQGameById(
activity: Activity,
qqGameId: String
) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
ToastUtils.toast("该游戏仅支持安卓5.0及以上设备")
return
}
CheckLoginUtils.checkLogin(
activity, null, true, "QQ小游戏-秒开"
) {
val userToken = UserManager.getInstance().token
val userId = UserManager.getInstance().userId
val userName = UserManager.getInstance().userInfoEntity?.name ?: "unknown"
val qGameProvider = ARouter
.getInstance()
.build(RouteConsts.provider.qGame)
.navigation() as IQGameProvider<*>
qGameProvider.setLoginInfo(activity, userId, userName, userToken)
qGameProvider.launchGame(activity, qqGameId) { _, _ ->
RetrofitManager
.getInstance()
.newApi
.postQGamePlay(qqGameId, userId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
QGameViewModel.notifyQGameSubjectUpdate() // 通知QQ小游戏首页列表刷新
},
{}
) // 秒玩记录上报
}
}
}
@JvmStatic
fun directToMessageCenter(defaultTabIndex: Int) {
ARouter.getInstance().build(RouteConsts.activity.messageWrapperActivity)

View File

@ -1,5 +1,6 @@
package com.gh.common.util
import android.annotation.SuppressLint
import android.content.Context
import android.os.Message
import android.text.TextUtils
@ -7,6 +8,7 @@ import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.collection.ArrayMap
import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.common.chain.*
import com.gh.common.constant.Config
import com.gh.common.dialog.DeviceRemindDialog
@ -26,19 +28,24 @@ import com.gh.download.server.BrowserInstallHelper
import com.gh.gamecenter.R
import com.gh.gamecenter.WebActivity
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.callback.CancelListener
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.provider.IQGameProvider
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.PluginLocation
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.dialog.GamePermissionDialogFragment
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.teenagermode.TeenagerModeActivity
import com.gh.vspace.VHelper
import com.lightgame.download.DownloadConfig
@ -237,6 +244,24 @@ object DownloadItemUtils {
}
return
}
if (gameEntity.isQQMiniGame()) {
val isQQMiniGameOffShelve = gameEntity.qqMiniGameAppStatus == 1 // QQ小游戏是否下架
if (isQQMiniGameOffShelve) {
downloadBtn.apply {
isClickable = false
text = context.getString(R.string.off_shelve)
buttonStyle = DownloadButton.ButtonStyle.NONE
}
} else {
downloadBtn.apply {
isClickable = true
setBackgroundResource(R.drawable.download_button_normal_style)
setTextColor(R.color.white.toColor(context))
text = context.getString(R.string.quick_play)
}
}
return
}
if (gameEntity.getApk().isEmpty() || gameEntity.downloadOffStatus != null) {
val h5LinkEntity = gameEntity.h5Link
val offStatus = gameEntity.downloadOffStatus
@ -747,6 +772,15 @@ object DownloadItemUtils {
}
return
}
if (gameEntity.isQQMiniGame()) {
downloadBtn.setOnClickListener {
NewFlatLogUtils.logQGameClick(gameEntity.qqMiniGameAppId, gameEntity.name)
GlobalActivityManager.currentActivity?.let { activity ->
DirectUtils.directToQGameById(activity, gameEntity.qqMiniGameAppId)
}
}
return
}
if (gameEntity.getApk().size == 0 && gameEntity.h5Link != null) {
downloadBtn.setOnClickListener {
allStateClickCallback?.onCallback()

View File

@ -2400,4 +2400,15 @@ object NewFlatLogUtils {
}
log(json)
}
@JvmStatic
fun logQGameClick(qqGameId: String, qqGameName: String?) {
val json = json {
KEY_EVENT to "qq_game_click"
"qq_game_id" to qqGameId
"qq_game_name" to qqGameName ?: ""
parseAndPutMeta().invoke(this)
}
log(json)
}
}

View File

@ -7,12 +7,14 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PermissionInfo;
import android.content.pm.Signature;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.NonNull;
@ -29,6 +31,7 @@ import com.gh.gamecenter.BuildConfig;
import com.gh.gamecenter.common.constant.Constants;
import com.gh.gamecenter.common.utils.ExtensionsKt;
import com.gh.gamecenter.common.utils.PackageFlavorHelper;
import com.gh.gamecenter.common.utils.PermissionHelper;
import com.gh.gamecenter.core.utils.MD5Utils;
import com.gh.gamecenter.core.utils.SentryHelper;
import com.gh.gamecenter.feature.entity.ApkEntity;
@ -74,6 +77,9 @@ public class PackageUtils {
private static final String TAG = "PackageUtils";
// 设备是否支持禁用获取已安装应用列表。-1 代表支持情况未知0 代表不支持, 1 代表支持
private static int mIsSupportGetInstalledListPermission = -1;
public static String getInstallPackageInfoSourceDir(String packageName) {
try {
return HaloApp.getInstance().getApplication().getPackageManager().getPackageInfo(packageName,
@ -966,18 +972,78 @@ public class PackageUtils {
return new ArrayList<>(mInstalledPackageList);
}
Utils.log(TAG, "调用系统 API 获取新的已安装应用列表");
// 是否需要调用系统 API 获取新的已安装应用列表
boolean shouldGetNewInstalledPackagedList = false;
// 当前设备是否支持限制获取已安装应用列表的功能
if (isSupportGetInstalledAppsPermission(context)) {
Utils.log(TAG, "当前设备支持限制获取已安装应用列表的功能");
// 当前设备是否支持禁用了获取已安装应用列表
if (!PermissionHelper.isGetInstalledListPermissionDisabled(context)) {
Utils.log(TAG, "当前设备没有限制获取已安装应用列表的功能");
shouldGetNewInstalledPackagedList = true;
} else {
Utils.log(TAG, "当前设备已限制获取已安装应用列表的功能");
}
} else {
Utils.log(TAG, "当前设备不支持限制获取已安装应用列表的功能");
shouldGetNewInstalledPackagedList = true;
}
if (shouldGetNewInstalledPackagedList) {
mLastInstalledPackageListTime = System.currentTimeMillis();
mInstalledPackageList = getInstalledPackagesInternal(context, flags);
}
if (mInstalledPackageList == null) {
mInstalledPackageList = new ArrayList<>();
}
mLastInstalledPackageListTime = System.currentTimeMillis();
mInstalledPackageList = getInstalledPackagesInternal(context, flags);
return mInstalledPackageList;
}
public static boolean isSupportGetInstalledAppsPermission(Context context) {
// 若存在缓存,直接返回缓存结果。为 0 代表不支持,为 1 代表支持
if (mIsSupportGetInstalledListPermission != -1) {
return mIsSupportGetInstalledListPermission != 0;
}
try {
// 根据官方提供的方法来判定是否支持限制获取已安装应用列表
int flag = Settings.Secure.getInt(context.getContentResolver(), "oem_installed_apps_runtime_permission_enable", 0);
if (flag == 1) {
mIsSupportGetInstalledListPermission = 1;
return true;
}
// 部分未升级的手机没有上面配置项,有定义下面危险权限也认为是支持设备软件列表管控
PackageManager packageManager = context.getPackageManager();
PermissionInfo permissionInfo = packageManager.getPermissionInfo("com.android.permission.GET_INSTALLED_APPS", 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS) {
mIsSupportGetInstalledListPermission = 1;
return true;
} else {
mIsSupportGetInstalledListPermission = 0;
return false;
}
} else {
mIsSupportGetInstalledListPermission = 0;
return false;
}
} catch (NameNotFoundException e) {
mIsSupportGetInstalledListPermission = 0;
return false;
}
}
/**
* 在5.1系统手机使用PackageManager获取已安装应用容易发生Package manager has died异常
* https://stackoverflow.com/questions/13235793/transactiontoolargeeception-when-trying-tÏo-get-a-list-of-applications-installed/30062632#30062632
*/
private static List<PackageInfo> getInstalledPackagesInternal(Context context, int flags) {
Utils.log(TAG, "调用系统 API 获取已安装应用列表");
final PackageManager pm = context.getPackageManager();
try {
return pm.getInstalledPackages(flags);

View File

@ -277,7 +277,11 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
pendingSessionInfo.updateStatus(XapkPendingSessionInfo.STATUS_INSTALL_CANCELED)
} else if (sessionInfo.progress <= 0.8F) {
AppExecutor.ioExecutor.execute {
installer.abandonSession(sessionInfo.sessionId)
try {
installer.abandonSession(sessionInfo.sessionId)
} catch (_: Exception) {
// 有概率抛SecurityException这里只要直接catch不做处理即可
}
}
pendingSessionInfo.updateStatus(XapkPendingSessionInfo.STATUS_INSTALL_CANCELED)
}

View File

@ -16,6 +16,7 @@ import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_GAME_COLLECT
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_INVOKE_ONLY;
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_LIBAO;
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_QQ;
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_QQ_GAME;
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_QQ_GROUP;
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_QQ_QUN;
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_QUESTION;
@ -49,19 +50,25 @@ import android.util.Base64;
import androidx.annotation.Nullable;
import com.alibaba.android.arouter.launcher.ARouter;
import com.gh.common.util.CheckLoginUtils;
import com.gh.common.util.DirectUtils;
import com.gh.common.util.DownloadItemUtils;
import com.gh.common.util.EntranceUtils;
import com.gh.gamecenter.common.base.activity.BaseActivity;
import com.gh.gamecenter.common.constant.EntranceConsts;
import com.gh.gamecenter.common.constant.RouteConsts;
import com.gh.gamecenter.common.entity.CommunityEntity;
import com.gh.gamecenter.common.entity.LinkEntity;
import com.gh.gamecenter.common.entity.SimpleGameEntity;
import com.gh.gamecenter.core.provider.IQGameProvider;
import com.gh.gamecenter.core.utils.GsonUtils;
import com.gh.gamecenter.core.utils.ToastUtils;
import com.gh.gamecenter.entity.SubjectRecommendEntity;
import com.gh.gamecenter.entity.VideoLinkEntity;
import com.gh.gamecenter.feature.utils.PlatformUtils;
import com.gh.gamecenter.login.entity.UserInfoEntity;
import com.gh.gamecenter.login.user.UserManager;
import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel;
import com.gh.gamecenter.video.videomanager.VideoManagerActivity;
import com.gh.vspace.shortcut.OnCreateShortcutResult;
@ -72,6 +79,14 @@ import com.lightgame.config.CommonDebug;
import com.lightgame.utils.Utils;
import com.muugi.shortcut.core.Executor;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.function.Function;
import kotlin.Unit;
import kotlin.jvm.functions.Function2;
/**
* Created by LGT on 2016/11/16.
* 链接跳转用
@ -123,7 +138,7 @@ public class SkipActivity extends BaseActivity {
DirectUtils.directToGameDetail(this, path, ENTRANCE_BROWSER, "true".equals(uri.getQueryParameter("auto_download")), to, null);
break;
case HOST_COLUMN:
DirectUtils.directToSubject(this, path, uri.getQueryParameter(KEY_NAME), ENTRANCE_BROWSER, null);
DirectUtils.directToSubject(this, path, uri.getQueryParameter(KEY_NAME), ENTRANCE_BROWSER, null, false);
break;
case HOST_SUGGESTION:
String platform = uri.getQueryParameter(KEY_PLATFORM);
@ -406,6 +421,14 @@ public class SkipActivity extends BaseActivity {
case HOST_GAME_COLLECTION_SQUARE:
DirectUtils.directToGameCollectionSquare(this, ENTRANCE_BROWSER, "", "", "", "", "", null);
break;
case HOST_QQ_GAME:
String extJson = uri.getQueryParameter("ext");
try {
JSONObject extJsonObject = new JSONObject(extJson);
String qqGameId = extJsonObject.optString("aid");
DirectUtils.directToQGameById(this, qqGameId);
} catch (JSONException ignored) {}
break;
default:
EntranceUtils.jumpActivity(this, new Bundle()); // 跳转至首页
return;

View File

@ -16,6 +16,7 @@ class SubjectData(
var tagType: String? = "", // 游戏Item 标签类型
var briefStyle: String = "",
var showSuffix: Boolean = true,
var isQQMiniGame: Boolean = false,
var requireUpdateSetting: Boolean = false, // 多专题页面需要专题页面自行获取专题配置
var isAdData: Boolean = false,

View File

@ -96,7 +96,10 @@ data class SubjectEntity(
var position: Int = -1,
// 本地字段,用来标记在外部页面中的序号,仅用于曝光记录,具体细节可见 https://git.ghzs.com/pm/halo-app-issues/-/issues/1087
var outerSequence: Int = -1
var outerSequence: Int = -1,
@SerializedName("is_qq_column")
var isQQColumn: Boolean = false
) : Parcelable {
@IgnoredOnParcel

View File

@ -1,6 +1,9 @@
package com.gh.gamecenter.fragment
import android.graphics.Typeface
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import com.gh.common.util.DataCollectionUtils
import com.gh.common.util.DirectUtils
import com.gh.common.util.LogUtils
@ -15,8 +18,12 @@ import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.SentryHelper
import com.gh.gamecenter.databinding.FragmentHomeGameWrapperBinding
import com.gh.gamecenter.entity.GameUpdateEntity
import com.gh.gamecenter.entity.SubjectRecommendEntity
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.packagehelper.PackageViewModel
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import kotlin.math.roundToInt
class HomeGameWrapperFragment : HomeTabWrapperFragment() {
@ -80,6 +87,8 @@ class HomeGameWrapperFragment : HomeTabWrapperFragment() {
DataCollectionUtils.uploadClick(activity, "下载图标", "主页")
startActivity(DownloadManagerActivity.getDownloadMangerIntent(requireContext(), "游戏库"))
}
mBinding?.menuDownloadCountHint?.typeface =
Typeface.createFromAsset(requireContext().assets, "fonts/d_din_bold_only_number.ttf")
}
}
@ -113,16 +122,38 @@ class HomeGameWrapperFragment : HomeTabWrapperFragment() {
}
mPackageViewModel?.filterSameUpdateLiveData?.observe(this) {
val count = DownloadManager.getInstance().getDownloadOrUpdateCount(it)
val params = mBinding?.menuDownloadCountHint?.layoutParams
params?.width = if (count.isNullOrEmpty()) 6F.dip2px() else 12F.dip2px()
params?.height = if (count.isNullOrEmpty()) 6F.dip2px() else 12F.dip2px()
mBinding?.menuDownloadCountHint?.layoutParams = params
mBinding?.menuDownloadCountHint?.goneIf(count == null)
mBinding?.menuDownloadCountHint?.text = count.toString()
setDownloadHint(it)
}
}
private fun setDownloadHint(updateList: List<GameUpdateEntity>) {
val count = DownloadManager.getInstance().getDownloadOrUpdateCount(updateList)
val params = mBinding?.menuDownloadCountHint?.layoutParams
params?.width = if (count.isNullOrEmpty()) 6F.dip2px() else ConstraintLayout.LayoutParams.WRAP_CONTENT
params?.height = if (count.isNullOrEmpty()) 6F.dip2px() else 14F.dip2px()
(params as ViewGroup.MarginLayoutParams).setMargins(
0,
if (count.isNullOrEmpty()) 0 else (-4F).dip2px(),
if (count.isNullOrEmpty()) (-4F).dip2px() else (-8F).dip2px(),
0
)
mBinding?.menuDownloadCountHint?.setPadding(
if (count.isNullOrEmpty()) 0 else 4F.dip2px(),
0,
if (count.isNullOrEmpty()) 0 else 4F.dip2px(),
0
)
mBinding?.menuDownloadCountHint?.minWidth = if (count.isNullOrEmpty()) 0 else 14F.dip2px()
mBinding?.menuDownloadCountHint?.layoutParams = params
mBinding?.menuDownloadCountHint?.goneIf(count == null)
mBinding?.menuDownloadCountHint?.text = count.toString()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(status: EBDownloadStatus?) {
mPackageViewModel?.filterSameUpdateLiveData?.value?.let { setDownloadHint(it) }
}
override fun getLastTabRightMargin(): Int = 26F.dip2px()
override fun logTabSelected(tabEntity: SubjectRecommendEntity, position: Int) {

View File

@ -2,6 +2,7 @@ package com.gh.gamecenter.fragment;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Menu;
@ -13,6 +14,7 @@ import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
@ -207,21 +209,40 @@ public class SearchToolbarFragment extends BaseLazyFragment implements View.OnCl
mUnreadViewModel = ViewModelProviders.of(this,
new MessageUnreadViewModel.Factory(HaloApp.getInstance().getApplication())).get(MessageUnreadViewModel.class);
mUnreadViewModel.getMessageCenterUnreadCountLiveData().observe(this, this::setMessageUnread);
mDownloadHintTv.setTypeface(Typeface.createFromAsset(requireContext().getAssets(), "fonts/d_din_bold_only_number.ttf"));
mMessageUnread.setTypeface(Typeface.createFromAsset(requireContext().getAssets(), "fonts/d_din_bold_only_number.ttf"));
}
private void setMessageUnread(int unreadCount) {
mMessageUnread.setVisibility(unreadCount == 0 ? View.GONE : View.VISIBLE);
ViewGroup.LayoutParams params = mMessageUnread.getLayoutParams();
if (unreadCount == -1) {
mMessageUnread.setText("");
params.width = DisplayUtils.dip2px(6);
params.height = DisplayUtils.dip2px(6);
setHintLayoutParams(mMessageUnread, true);
} else {
BindingAdapters.setMessageUnread(mMessageUnread, unreadCount);
params.width = DisplayUtils.dip2px(12);
params.height = DisplayUtils.dip2px(12);
setHintLayoutParams(mMessageUnread, false);
}
mMessageUnread.setLayoutParams(params);
}
private void setHintLayoutParams(TextView textView, boolean isRedDot) {
ViewGroup.LayoutParams params = textView.getLayoutParams();
if (isRedDot) {
params.width = DisplayUtils.dip2px(6F);
params.height = DisplayUtils.dip2px(6F);
((ViewGroup.MarginLayoutParams) params).rightMargin = DisplayUtils.dip2px(-4F);
((ViewGroup.MarginLayoutParams) params).topMargin = 0;
textView.setPadding(0, 0, 0, 0);
textView.setMinWidth(0);
} else {
params.width = RelativeLayout.LayoutParams.WRAP_CONTENT;
params.height = DisplayUtils.dip2px(14F);
((ViewGroup.MarginLayoutParams) params).rightMargin = DisplayUtils.dip2px(-8F);
((ViewGroup.MarginLayoutParams) params).topMargin = DisplayUtils.dip2px(-4F);
textView.setPadding(DisplayUtils.dip2px(4F), 0, DisplayUtils.dip2px(4F), 0);
textView.setMinWidth(DisplayUtils.dip2px(14F));
}
textView.setLayoutParams(params);
}
@Override
@ -343,15 +364,11 @@ public class SearchToolbarFragment extends BaseLazyFragment implements View.OnCl
mDownloadHintTv.setVisibility(View.VISIBLE);
mDownloadHintTv.setText(count);
ViewGroup.LayoutParams params = mDownloadHintTv.getLayoutParams();
if (TextUtils.isEmpty(count)) {
params.width = DisplayUtils.dip2px(6);
params.height = DisplayUtils.dip2px(6);
setHintLayoutParams(mDownloadHintTv, true);
} else {
params.width = DisplayUtils.dip2px(12);
params.height = DisplayUtils.dip2px(12);
setHintLayoutParams(mDownloadHintTv, false);
}
mDownloadHintTv.setLayoutParams(params);
} else {
mDownloadHintTv.setVisibility(View.GONE);
}

View File

@ -0,0 +1,117 @@
package com.gh.gamecenter.game
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import com.gh.common.util.GameSubstituteRepositoryHelper
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.entity.ExposureEntity
import com.gh.gamecenter.core.iinterface.IOffsetable
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.entity.SubjectRecommendEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.game.data.GameItemData
import com.gh.gamecenter.home.BlankDividerViewHolder
import com.gh.gamecenter.home.test_v2.HomeGameTestV2DownloadStateUpdateHelper
abstract class BaseGameViewModel(application: Application, open var blockData: SubjectRecommendEntity?) : AndroidViewModel(application), IOffsetable {
private var mOffsetMap: HashMap<Int, Int> = hashMapOf()
protected val mItemDataListCache: MutableList<GameItemData> = ArrayList()
val loadStatus = MutableLiveData<LoadStatus>()
val commandScrollTop = MutableLiveData<Any>()
var itemDataList: MediatorLiveData<List<GameItemData>> = MediatorLiveData()
var entrance: String = ""
var positionAndPackageMap = HashMap<String, Int>() // key: packageName + position, value: position
private var mHomeGameTestV2DownloadStateUpdateHelper: HomeGameTestV2DownloadStateUpdateHelper? = null
override fun getOffset(position: Int) = mOffsetMap[position] ?: 0
override fun updateOffset(position: Int, offset: Int) {
mOffsetMap[position] = offset
}
override fun resetOffset() {
mOffsetMap.clear()
}
fun initData() {
onInitData()
}
fun isGameRepo(): Boolean {
return blockData?.name?.contains("游戏库") == true
}
fun setHomeGameTestV2DownloadStateUpdateHelper(helper: HomeGameTestV2DownloadStateUpdateHelper) {
mHomeGameTestV2DownloadStateUpdateHelper = helper
mHomeGameTestV2DownloadStateUpdateHelper?.setOnGameListAddCallback { position, gameEntities ->
gameEntities.forEach {
addGamePositionAndPackage(it, position)
}
}
}
protected fun addGamePositionAndPackage(game: GameEntity, position: Int) {
var packages = ""
for (apkEntity in game.getApk()) {
packages += apkEntity.packageName
}
positionAndPackageMap[packages + position] = position
game.gameLocation = GameEntity.GameLocation.INDEX
game.setEntryMap(DownloadManager.getInstance().getEntryMap(game.name))
}
protected fun addGamePositionAndPackage(game: GameEntity) {
var packages = ""
for (apkEntity in game.getApk()) {
packages += apkEntity.packageName
}
positionAndPackageMap[packages + (mItemDataListCache.size - 1)] = mItemDataListCache.size - 1
game.gameLocation = GameEntity.GameLocation.INDEX
game.setEntryMap(DownloadManager.getInstance().getEntryMap(game.name))
}
protected fun appendAdditionalInfoToSubjectGame(subject: SubjectEntity, outerPosition: Int) {
subject.outerSequence = outerPosition
subject.data?.let {
for ((index, game) in it.withIndex()) {
// 开测表不是专题不需要补充专题 id
if (subject.tag != "test") {
game.subjectId = subject.id
}
game.sequence = index
game.containerId = blockData?.link
game.containerType = ExposureEntity.BLOCK_ID
game.outerSequence = outerPosition
}
}
}
/**
* 获取填充间距的空白 item
*/
protected fun getBlankSpacingItem() =
GameItemData().apply { blankDivider = BlankDividerViewHolder.DEFAULT_BLANK_HEIGHT * 2 }
protected abstract fun onInitData()
abstract fun getSubjectList()
abstract fun replaceRefreshData(itemData: GameItemData): Boolean
abstract fun changeSubjectGame(subjectId: String)
abstract fun refreshDiscoverCardData()
abstract fun changeGameCollectionRefresh(collectionId: String, isRefreshClick: Boolean): Boolean
}

View File

@ -11,6 +11,7 @@ import com.ethanhua.skeleton.Skeleton
import com.ethanhua.skeleton.ViewSkeletonScreen
import com.gh.common.exposure.ExposureListener
import com.gh.common.util.DialogUtils
import com.gh.common.util.HomeBottomBarHelper
import com.gh.common.xapk.XapkInstaller
import com.gh.common.xapk.XapkUnzipStatus
import com.gh.download.DownloadManager
@ -44,9 +45,9 @@ import com.lightgame.download.DownloadEntity
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class GameFragment : LazyFragment() {
open class GameFragment : LazyFragment() {
private lateinit var mViewModel: GameViewModel
private lateinit var mViewModel: BaseGameViewModel
private lateinit var mHomeGameTestV2ViewModel: HomeGameTestV2ViewModel
private lateinit var mBinding: FragmentGameBinding
@ -74,11 +75,7 @@ class GameFragment : LazyFragment() {
override fun getRealLayoutId() = R.layout.fragment_game
override fun onFragmentFirstVisible() {
val factory = GameViewModel.Factory(
HaloApp.getInstance().application,
arguments?.getParcelable(EntranceConsts.KEY_BLOCK_DATA)
)
mViewModel = viewModelProvider(factory)
mViewModel = provideViewModel()
mViewModel.entrance = mEntrance
//新游开测模块
mHomeGameTestV2ViewModel = viewModelProvider()
@ -364,4 +361,12 @@ class GameFragment : LazyFragment() {
mBinding.gameList.setBackgroundColor(R.color.background_white.toColor(requireContext()))
}
}
protected open fun provideViewModel(): BaseGameViewModel {
val factory = GameViewModel.Factory(
HaloApp.getInstance().application,
arguments?.getParcelable(EntranceConsts.KEY_BLOCK_DATA)
)
return viewModelProvider(factory)
}
}

View File

@ -25,6 +25,7 @@ import com.gh.gamecenter.R
import com.gh.gamecenter.adapter.ImagePagerAdapter
import com.gh.gamecenter.adapter.viewholder.*
import com.gh.gamecenter.category.CategoryDirectoryActivity
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.callback.OnViewClickListener
import com.gh.gamecenter.common.constant.EntranceConsts
@ -82,6 +83,7 @@ import com.gh.gamecenter.home.test_v2.HomeGameTestV2GameListRvAdapter
import com.gh.gamecenter.home.test_v2.HomeGameTestV2ViewModel
import com.gh.gamecenter.home.test_v2.HomeItemGameTestV2ViewHolder
import com.gh.gamecenter.home.video.ScrollCalculatorHelper
import com.gh.gamecenter.qgame.QGameHorizontalSlideListViewHolder
import com.gh.gamecenter.servers.gametest2.GameServerTestV2Activity
import com.gh.gamecenter.subject.SubjectActivity
import com.lightgame.adapter.BaseRecyclerAdapter
@ -89,7 +91,7 @@ import com.lightgame.download.DownloadEntity
class GameFragmentAdapter(
context: Context,
model: GameViewModel,
model: BaseGameViewModel,
private val mLifecycleOwner: LifecycleOwner,
private val mHomeGameTestV2ViewModel: HomeGameTestV2ViewModel,
private val mBasicExposureSource: ArrayList<ExposureSource>,
@ -97,7 +99,7 @@ class GameFragmentAdapter(
private val mScrollCalculatorHelper: ScrollCalculatorHelper? = null
) : BaseRecyclerAdapter<ViewHolder>(context), IExposable {
private val mViewModel: GameViewModel = model
private val mViewModel: BaseGameViewModel = model
private var mLoadStatus: LoadStatus? = null
@ -132,6 +134,7 @@ class GameFragmentAdapter(
if (itemData.image != null) return ItemViewType.GAME_IMAGE
if (itemData.horizontalColumn != null) return ItemViewType.GAME_SUBJECT
if (itemData.horizontalSlide != null) return ItemViewType.GAME_SUBJECT_SLIDE
if (itemData.qqHorizontalSlide != null) return ItemViewType.QQ_GAME_SUBJECT_SLIDE
if (itemData.imageSlide != null) return ItemViewType.IMAGE_SLIDE_ITEM
if (itemData.verticalSlide != null) return ItemViewType.VERTICAL_SLIDE_ITEM
if (itemData.columnCollection != null) return ItemViewType.COLUMN_COLLECTION
@ -177,6 +180,7 @@ class GameFragmentAdapter(
ItemViewType.GAME_SUBJECT -> GameHorizontalListViewHolder(parent.toBinding())
ItemViewType.GAME_SUBJECT_SLIDE -> GameHorizontalSlideListViewHolder(parent.toBinding())
ItemViewType.QQ_GAME_SUBJECT_SLIDE -> QGameHorizontalSlideListViewHolder(parent.toBinding())
ItemViewType.GAME_NORMAL -> GameItemViewHolder(parent.toBinding())
ItemViewType.GAME_IMAGE -> GameImageViewHolder(parent.toBinding())
ItemViewType.COLUMN_HEADER -> GameHeadViewHolder(parent.toBinding())
@ -223,6 +227,7 @@ class GameFragmentAdapter(
is FooterViewHolder -> bindFooterView(holder)
is GameImageViewHolder -> bindGameImageView(holder, position)
is GameHorizontalListViewHolder -> bindGameHorizontalListView(holder, position)
is QGameHorizontalSlideListViewHolder -> bindQGameHorizontalSlideListView(holder, position)
is GameHorizontalSlideListViewHolder -> bindGameHorizontalSlideListView(holder, position)
is GameImageSlideViewHolder -> bindGameImageSlide(holder, position)
is GameVerticalSlideViewHolder -> bindVerticalSlide(holder, position)
@ -267,7 +272,7 @@ class GameFragmentAdapter(
holder.cell.binding?.root?.setOnClickListener {
setPageSwitchData()
DirectUtils.directToSubject(
it.context, gallery.id ?: "", gallery.name, "(游戏-专题)"
it.context, gallery.id ?: "", gallery.name, "(游戏-专题)", null, gallery.isQQColumn
)
NewLogUtils.logColumnPictureClick(
"显示图集", gallery.name ?: "", gallery.id ?: "", gallery.id ?: "", "column",
@ -513,17 +518,24 @@ class GameFragmentAdapter(
val clickClosure: (Int, GameEntity) -> Unit = { _, gameEntity ->
val subjectData = gameEntity.subjectData
DataCollectionUtils.uploadClick(mContext, subjectData?.name + "-列表", "游戏-专题", gameEntity.name)
GameDetailActivity.startGameDetailActivity(
mContext, gameEntity,
StringUtils.buildString(
"(游戏-专题:",
subjectData?.name,
"-列表[",
(subjectData?.position).toString(),
"])"
),
traceEvent = gameEntity.exposureEvent
)
if (gameEntity.isQQMiniGame()) {
NewFlatLogUtils.logQGameClick(gameEntity.qqMiniGameAppId, gameEntity.name)
GlobalActivityManager.currentActivity?.let {
DirectUtils.directToQGameById(it, gameEntity.qqMiniGameAppId)
}
} else {
GameDetailActivity.startGameDetailActivity(
mContext, gameEntity,
StringUtils.buildString(
"(游戏-专题:",
subjectData?.name,
"-列表[",
(subjectData?.position).toString(),
"])"
),
traceEvent = gameEntity.exposureEvent
)
}
}
val verticalSlide = mItemDataList[position].verticalSlide!!
@ -689,6 +701,64 @@ class GameFragmentAdapter(
}
}
private fun bindQGameHorizontalSlideListView(holder: QGameHorizontalSlideListViewHolder, position: Int) {
val subject = mItemDataList[position].qqHorizontalSlide
holder.bindHorizontalSlideList(subject!!)
holder.binding.moreTv.setOnClickListener {
val buttonType = when (subject.home) {
"change" -> "换一批"
"more" -> "更多"
else -> "全部"
}
val moreLink = subject.moreLink
NewLogUtils.logColumnListClickButton(
buttonType, subject.name ?: "", subject.id ?: "", moreLink?.type ?: "",
moreLink?.name ?: "", "版块", mViewModel.blockData?.name ?: ""
)
when (subject.home) {
"change" -> {
MtaHelper.onEvent("游戏专题", "换一批", subject.name)
holder.binding.moreTv.visibility = View.VISIBLE
mViewModel.changeSubjectGame(subject.id!!)
}
"more" -> {
setPageSwitchData()
subject.moreLink?.let { link ->
DirectUtils.directToLinkPage(it.context, link, "(板块)", "(游戏-专题:" + subject.name + "-全部)")
}
}
else -> {
setPageSwitchData()
if (subject.indexRightTopLink != null) {
val link = subject.indexRightTopLink!!
// ugly ugly ugly as hell
if (holder.binding.moreTv.text.contains("更多")) {
LogUtils.logServerTestClickMoreEvent(link.text, subject.remark, link.type, link.link)
} else {
LogUtils.logServerTestClickAllEvent(link.text, subject.remark)
}
DirectUtils.directToLinkPage(mContext, link, "(游戏-专题:" + subject.name + "-全部)", "")
} else {
SubjectActivity.startSubjectActivity(
mContext,
subject.id,
subject.getFilterName(),
subject.isOrder,
mBasicExposureSource,
"(游戏-专题:" + subject.name + "-全部)",
subject.isQQColumn
)
}
MtaHelper.onEvent("游戏专题", "全部", subject.name)
}
}
}
}
private fun bindGameImageView(holder: GameImageViewHolder, position: Int) {
val entity = mItemDataList[position].image
holder.bindImage(entity!!)
@ -1127,10 +1197,17 @@ class GameFragmentAdapter(
!gameEntity.isPluggable,
subjectData.briefStyle
)
holder.itemView.setOnClickListener {
DataCollectionUtils.uploadClick(mContext, subjectData.name + "-列表", "游戏-专题", gameEntity.name)
if (gameEntity.isPluggable) {
if (gameEntity.isQQMiniGame()) {
GlobalActivityManager.currentActivity?.let {
NewFlatLogUtils.logQGameClick(gameEntity.qqMiniGameAppId, gameEntity.name)
DirectUtils.directToQGameById(it, gameEntity.qqMiniGameAppId)
}
} else if (gameEntity.isPluggable) {
GameDetailActivity.startGameDetailActivity(
mContext,
gameEntity.id,
@ -1283,14 +1360,26 @@ class GameFragmentAdapter(
}
DirectUtils.directToLinkPage(mContext, link, "(游戏-专题:" + column.name + "-全部)", "")
} else {
SubjectActivity.startSubjectActivity(
mContext,
column.id,
column.getFilterName(),
column.isOrder,
mBasicExposureSource,
"(游戏-专题:" + column.name + "-全部)"
)
if (column.isQQColumn) {
SubjectActivity.startSubjectActivity(
mContext,
column.id,
column.getFilterName(),
column.isOrder,
mBasicExposureSource,
"(游戏-专题:" + column.name + "-全部)",
column.isQQColumn
)
} else {
SubjectActivity.startSubjectActivity(
mContext,
column.id,
column.getFilterName(),
column.isOrder,
mBasicExposureSource,
"(游戏-专题:" + column.name + "-全部)"
)
}
}
MtaHelper.onEvent("游戏专题", "全部", column.name)
}

View File

@ -7,7 +7,6 @@ import androidx.collection.ArrayMap
import androidx.lifecycle.*
import com.gh.common.filter.RegionSettingHelper
import com.gh.common.util.*
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
@ -19,7 +18,6 @@ import com.gh.gamecenter.common.utils.countOccurrences
import com.gh.gamecenter.common.utils.debugOnly
import com.gh.gamecenter.common.utils.observableToMain
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.core.iinterface.IOffsetable
import com.gh.gamecenter.core.utils.RandomUtils
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.core.utils.UrlFilterUtils
@ -32,7 +30,6 @@ import com.gh.gamecenter.game.rank.RankCollectionAdapter
import com.gh.gamecenter.gamecollection.square.GameCollectionListItemData
import com.gh.gamecenter.home.BlankDividerViewHolder
import com.gh.gamecenter.home.LegacyHomeFragmentAdapterAssistant
import com.gh.gamecenter.home.test_v2.HomeGameTestV2DownloadStateUpdateHelper
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
@ -44,17 +41,13 @@ import retrofit2.HttpException
import kotlin.collections.set
import kotlin.math.roundToInt
class GameViewModel(application: Application, var blockData: SubjectRecommendEntity?) : AndroidViewModel(application),
IOffsetable {
private var mHomeGameTestV2DownloadStateUpdateHelper: HomeGameTestV2DownloadStateUpdateHelper? = null
class GameViewModel(application: Application, override var blockData: SubjectRecommendEntity?) : BaseGameViewModel(application, blockData) {
private var mSlideList = ArrayList<LinkEntity>() // 轮播图
private var mNavigationList = ArrayList<GameNavigationEntity>() // 导航栏
private var mSubjectList: MutableList<SubjectEntity> = ArrayList() // 专题
private var mSubjectDigestList = ArrayList<SubjectRecommendEntity>() // 专题入口
private val mItemDataListCache: MutableList<GameItemData> = ArrayList()
private val mSubjectChangedMap: ArrayMap<String, List<GameEntity>> = ArrayMap() // 存储换一换的数据
private val mSubjectRefreshMap: ArrayMap<String, MutableList<GameEntity>> = ArrayMap() // 存储专题刷新数据
private val mRefreshGameCollectionMap: ArrayMap<String, List<HomeGameCollectionEntity>> = ArrayMap() // 存储刷新轮换游戏单的数据
@ -71,15 +64,6 @@ class GameViewModel(application: Application, var blockData: SubjectRecommendEnt
private val mSensitiveApi = RetrofitManager.getInstance().api
private var mOffsetMap: HashMap<Int, Int> = hashMapOf()
var entrance: String = ""
var itemDataList: MediatorLiveData<List<GameItemData>> = MediatorLiveData()
val loadStatus = MutableLiveData<LoadStatus>()
val commandScrollTop = MutableLiveData<Any>()
var positionAndPackageMap = HashMap<String, Int>() // key: packageName + position, value: position
// 所有专题里的所有游戏 ID 的集合,供替换时排重用
private var mSubjectGameIdList = hashSetOf<String>()
@ -97,7 +81,7 @@ class GameViewModel(application: Application, var blockData: SubjectRecommendEnt
/**
* 初始化加载数据
*/
fun initData() {
override fun onInitData() {
mSubjectPage = 1
mSlideList = arrayListOf()
@ -188,7 +172,7 @@ class GameViewModel(application: Application, var blockData: SubjectRecommendEnt
})
}
fun getSubjectList() {
override fun getSubjectList() {
if (mIsLoading || loadStatus.value == LoadStatus.LIST_OVER) return
mIsLoading = true
@ -311,7 +295,7 @@ class GameViewModel(application: Application, var blockData: SubjectRecommendEnt
}
// 目前只需替换横向专题和横向滑动专题
fun replaceRefreshData(itemData: GameItemData): Boolean {
override fun replaceRefreshData(itemData: GameItemData): Boolean {
val horizontalColumn = itemData.horizontalColumn
if (horizontalColumn != null) {
return replaceHorizontalSubjectRefreshData(horizontalColumn)
@ -378,7 +362,7 @@ class GameViewModel(application: Application, var blockData: SubjectRecommendEnt
return true
}
fun changeSubjectGame(subjectId: String) {
override fun changeSubjectGame(subjectId: String) {
val list = mSubjectChangedMap[subjectId]
if (list != null) {
initRandomGame(subjectId, ArrayList(list))
@ -512,7 +496,7 @@ class GameViewModel(application: Application, var blockData: SubjectRecommendEnt
}
}
fun refreshDiscoverCardData() {
override fun refreshDiscoverCardData() {
if (mDiscoveryGameCard != null) {
val paramsMap = if (SPUtils.getBoolean(Constants.SP_DISCOVER_FORCE_REFRESH)) {
mapOf("refresh" to "true")
@ -598,7 +582,7 @@ class GameViewModel(application: Application, var blockData: SubjectRecommendEnt
}
}
fun changeGameCollectionRefresh(collectionId: String, isRefreshClick: Boolean): Boolean {
override fun changeGameCollectionRefresh(collectionId: String, isRefreshClick: Boolean): Boolean {
val page = (mRefreshGameCollectionPageMap[collectionId] ?: 1) + 1
return if (page != 0) {
getGameCollectionRefresh(collectionId, page, isRefreshClick)
@ -1125,67 +1109,6 @@ class GameViewModel(application: Application, var blockData: SubjectRecommendEnt
itemDataList.postValue(mItemDataListCache)
}
private fun addGamePositionAndPackage(game: GameEntity, position: Int) {
var packages = ""
for (apkEntity in game.getApk()) {
packages += apkEntity.packageName
}
positionAndPackageMap[packages + position] = position
game.gameLocation = GameEntity.GameLocation.INDEX
game.setEntryMap(DownloadManager.getInstance().getEntryMap(game.name))
}
private fun addGamePositionAndPackage(game: GameEntity) {
var packages = ""
for (apkEntity in game.getApk()) {
packages += apkEntity.packageName
}
positionAndPackageMap[packages + (mItemDataListCache.size - 1)] = mItemDataListCache.size - 1
game.gameLocation = GameEntity.GameLocation.INDEX
game.setEntryMap(DownloadManager.getInstance().getEntryMap(game.name))
}
fun setHomeGameTestV2DownloadStateUpdateHelper(helper: HomeGameTestV2DownloadStateUpdateHelper) {
mHomeGameTestV2DownloadStateUpdateHelper = helper
mHomeGameTestV2DownloadStateUpdateHelper?.setOnGameListAddCallback { position, gameEntities ->
gameEntities.forEach {
addGamePositionAndPackage(it, position)
}
}
}
/**
* 获取填充间距的空白 item
*/
private fun getBlankSpacingItem() =
GameItemData().apply { blankDivider = BlankDividerViewHolder.DEFAULT_BLANK_HEIGHT * 2 }
override fun getOffset(position: Int) = mOffsetMap[position] ?: 0
override fun updateOffset(position: Int, offset: Int) {
mOffsetMap[position] = offset
}
override fun resetOffset() {
mOffsetMap.clear()
}
private fun appendAdditionalInfoToSubjectGame(subject: SubjectEntity, outerPosition: Int) {
subject.outerSequence = outerPosition
subject.data?.let {
for ((index, game) in it.withIndex()) {
// 开测表不是专题不需要补充专题 id
if (subject.tag != "test") {
game.subjectId = subject.id
}
game.sequence = index
game.containerId = blockData?.link
game.containerType = ExposureEntity.BLOCK_ID
game.outerSequence = outerPosition
}
}
}
private fun appendAdditionalInfoToRankSubjectGame(subject: SubjectEntity, outerPosition: Int) {
subject.columns.forEach {
it.data?.forEachIndexed { index, game ->
@ -1201,13 +1124,6 @@ class GameViewModel(application: Application, var blockData: SubjectRecommendEnt
}
}
/**
* 是否显示为游戏库
*/
fun isGameRepo(): Boolean {
return blockData?.name?.contains("游戏库") == true
}
override fun onCleared() {
super.onCleared()

View File

@ -17,6 +17,7 @@ class GameItemData : ExposureItemData {
var navigationList: List<GameNavigationEntity>? = null
var verticalSlide: SubjectEntity? = null
var horizontalSlide: SubjectEntity? = null
var qqHorizontalSlide: SubjectEntity? = null
var imageSlide: GameEntity? = null
var columnCollection: SubjectEntity? = null
var commonLinkCollection: SubjectEntity? = null

View File

@ -5,18 +5,21 @@ import android.view.ViewGroup
import androidx.activity.ComponentActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureSource
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.databinding.GameGallerySlideItemBinding
import com.gh.gamecenter.databinding.ItemWithinGameGallerySlideBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureSource
import com.lightgame.adapter.BaseRecyclerAdapter
class GameGallerySlideViewHolder(val binding: GameGallerySlideItemBinding) : BaseRecyclerViewHolder<Any>(binding.root) {
@ -131,12 +134,19 @@ class GameGallerySlideViewHolder(val binding: GameGallerySlideItemBinding) : Bas
fun bindView(gameEntity: GameEntity) {
binding.iconIv.displayGameIcon(gameEntity)
binding.iconIv.setOnClickListener {
GameDetailActivity.startGameDetailActivity(
binding.root.context,
gameEntity.id,
mEntrance,
gameEntity.exposureEvent
)
if (gameEntity.isQQMiniGame()) {
NewFlatLogUtils.logQGameClick(gameEntity.qqMiniGameAppId, gameEntity.name)
GlobalActivityManager.currentActivity?.let {
DirectUtils.directToQGameById(it, gameEntity.qqMiniGameAppId)
}
} else {
GameDetailActivity.startGameDetailActivity(
binding.root.context,
gameEntity.id,
mEntrance,
gameEntity.exposureEvent
)
}
}
}
}

View File

@ -2,10 +2,12 @@ package com.gh.gamecenter.game.gallery
import android.content.Context
import android.view.View
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureSource
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.utils.safelyGetInRelease
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.view.AsyncCell
@ -37,20 +39,36 @@ class GameGalleryViewHolder(val cell: GameGalleryItemCell) :
)
for ((index, gameIcon) in gameIconList.withIndex()) {
val gameEntity = subjectEntity.data?.safelyGetInRelease(index) ?: continue
val dataSize = subjectEntity.data?.size ?: 0
if (index >= dataSize) {
gameIcon.visibility = View.GONE
} else {
val gameEntity = subjectEntity.data?.safelyGetInRelease(index) ?: continue
gameEntity.subjectId = subjectEntity.id
gameEntity.subjectId = subjectEntity.id
gameIcon.rotation = 35F
gameIcon.displayGameIcon(gameEntity)
gameIcon.visibility = View.VISIBLE
gameIcon.rotation = 35F
gameIcon.displayGameIcon(gameEntity)
runOnIoThread(true) {
val exposureEvent = ExposureEvent.createEventWithSourceConcat(
gameEntity,
basicExposureSource,
listOf(ExposureSource("专题", "${subjectEntity.name}-图集"))
)
exposureClosure.invoke(exposureEvent)
if (subjectEntity.isQQColumn) {
gameIcon.setOnClickListener {
GlobalActivityManager.currentActivity?.let {
DirectUtils.directToQGameById(it, gameEntity.qqMiniGameAppId)
}
}
} else {
gameIcon.setOnClickListener(null)
}
runOnIoThread(true) {
val exposureEvent = ExposureEvent.createEventWithSourceConcat(
gameEntity,
basicExposureSource,
listOf(ExposureSource("专题", "${subjectEntity.name}-图集"))
)
exposureClosure.invoke(exposureEvent)
}
}
}

View File

@ -3,14 +3,19 @@ package com.gh.gamecenter.game.horizontal
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.DataCollectionUtils
import com.gh.common.util.DownloadItemUtils
import com.gh.common.util.NewLogUtils
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.common.util.*
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toPx
import com.gh.gamecenter.core.provider.IQGameProvider
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
@ -55,6 +60,8 @@ class GameHorizontalAdapter(
val size = mSubjectEntity.data!!.size - getIndex()
return if (type == GameHorizontalListType.GameDetailHorizontalType) {
size
} else if (type == GameHorizontalListType.QGameSubjectHorizontalType){
mSubjectEntity.data!!.size
} else {
when {
size < 4 -> size
@ -123,12 +130,19 @@ class GameHorizontalAdapter(
)
}
GameDetailActivity.startGameDetailActivity(
mContext,
gameEntity.id,
entranceResult,
exposureEventList?.get(position)
)
if (gameEntity.isQQMiniGame()) {
NewFlatLogUtils.logQGameClick(gameEntity.qqMiniGameAppId, gameEntity.name)
GlobalActivityManager.currentActivity?.let {
DirectUtils.directToQGameById(it, gameEntity.qqMiniGameAppId)
}
} else {
GameDetailActivity.startGameDetailActivity(
mContext,
gameEntity.id,
entranceResult,
exposureEventList?.get(position)
)
}
}
if (mSubjectEntity.showDownload && type != GameHorizontalListType.GameDetailHorizontalType) {
@ -202,4 +216,6 @@ class GameHorizontalAdapter(
sealed class GameHorizontalListType {
object GameDetailHorizontalType : GameHorizontalListType()
object SubjectHorizontalType : GameHorizontalListType()
object QGameSubjectHorizontalType : GameHorizontalListType()
}

View File

@ -17,7 +17,16 @@ class GameHorizontalListViewHolder(val binding: GameHorizontalListBinding) :
if (subjectAdapter == null) {
binding.recyclerView.setPadding(5F.dip2px(), 8F.dip2px(), 5F.dip2px(), 8F.dip2px())
binding.recyclerView.layoutManager = GridLayoutManager(context, 4)
subjectAdapter = GameHorizontalAdapter(context, subjectEntity)
subjectAdapter = GameHorizontalAdapter(
context,
subjectEntity,
if (subjectEntity.isQQColumn) {
GameHorizontalListType.QGameSubjectHorizontalType
}
else {
GameHorizontalListType.SubjectHorizontalType
}
)
(binding.recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
binding.recyclerView.adapter = subjectAdapter
binding.recyclerView.isNestedScrollingEnabled = false

View File

@ -2,12 +2,18 @@ package com.gh.gamecenter.game.horizontal
import android.content.Context
import android.view.ViewGroup
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.common.util.DownloadItemUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.provider.IQGameProvider
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.databinding.GameHorizontalItemBinding
import com.gh.gamecenter.entity.SubjectEntity
@ -67,19 +73,26 @@ class GameHorizontalSlideAdapter(
}
holder.bindGameHorizontalItem(gameEntity, mSubjectEntity)
holder.itemView.setOnClickListener {
val exposureEvent = exposureEventList?.safelyGetInRelease(position)
if (exposureEvent != null) {
GameDetailActivity.startGameDetailActivity(
mContext,
gameEntity.id,
StringUtils.buildString("(游戏-专题:", mSubjectEntity.name, "-列表[", (position + 1).toString(), "])"),
traceEvent = exposureEvent
)
if (gameEntity.isQQMiniGame()) {
NewFlatLogUtils.logQGameClick(gameEntity.qqMiniGameAppId, gameEntity.name)
GlobalActivityManager.currentActivity?.let {
DirectUtils.directToQGameById(it, gameEntity.qqMiniGameAppId)
}
} else {
GameDetailActivity.startGameDetailActivity(
mContext, gameEntity.id,
StringUtils.buildString("(游戏-专题:", mSubjectEntity.name, "-列表[", (position + 1).toString(), "])")
)
val exposureEvent = exposureEventList?.safelyGetInRelease(position)
if (exposureEvent != null) {
GameDetailActivity.startGameDetailActivity(
mContext,
gameEntity.id,
StringUtils.buildString("(游戏-专题:", mSubjectEntity.name, "-列表[", (position + 1).toString(), "])"),
traceEvent = exposureEvent
)
} else {
GameDetailActivity.startGameDetailActivity(
mContext, gameEntity.id,
StringUtils.buildString("(游戏-专题:", mSubjectEntity.name, "-列表[", (position + 1).toString(), "])")
)
}
}
}

View File

@ -5,12 +5,18 @@ import android.graphics.drawable.Drawable
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.common.databind.BindingAdapters
import com.gh.common.util.DownloadItemUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.AsyncUi
import com.gh.gamecenter.core.provider.IQGameProvider
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.GameSubjectData

View File

@ -7,6 +7,7 @@ import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.databinding.GameVerticalSlideItemBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.entity.SubjectEntity
import kotlin.math.min
class GameVerticalSlideViewHolder(val binding: GameVerticalSlideItemBinding) :
BaseRecyclerViewHolder<Any>(binding.root) {
@ -17,7 +18,7 @@ class GameVerticalSlideViewHolder(val binding: GameVerticalSlideItemBinding) :
transparentBackground: Boolean = false,
): SpanCountPagerSnapHelper {
val context = binding.root.context
val spanCount = verticalSlide.list
val spanCount = min(verticalSlide.list, verticalSlide.data!!.size)
val snapHelper = SpanCountPagerSnapHelper(spanCount, true)
binding.recyclerView.run {
clearOnScrollListeners()

View File

@ -5,6 +5,7 @@ import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.GradientDrawable
import android.os.*
import android.text.SpannableStringBuilder
@ -403,7 +404,7 @@ class GameDetailFragment : ToolbarFragment(), IScrollable {
mGameEntity,
mNewGameDetailEntity?.shortId ?: "",
mShowConcernOnMenu,
mNewGameDetailEntity!!.me.isGameConcerned
mNewGameDetailEntity!!.me?.isGameConcerned ?: false
)
MtaHelper.onEvent("游戏详情_新", "更多按钮", mViewModel.game?.name ?: "")
}
@ -1152,7 +1153,7 @@ class GameDetailFragment : ToolbarFragment(), IScrollable {
private fun updateGameDetailTopArea(updateUserRelatedViewOnly: Boolean = false) {
if (updateUserRelatedViewOnly) {
updateConcernMenuIcon(mNewGameDetailEntity!!.me.isGameConcerned)
updateConcernMenuIcon(mNewGameDetailEntity!!.me?.isGameConcerned ?: false)
return
}
@ -1167,7 +1168,7 @@ class GameDetailFragment : ToolbarFragment(), IScrollable {
}
}
updateConcernMenuIcon(mNewGameDetailEntity!!.me.isGameConcerned)
updateConcernMenuIcon(mNewGameDetailEntity!!.me?.isGameConcerned ?: false)
mBaseHandler.postDelayed({
mGameEntity?.gameType = mNewGameDetailEntity?.tagStyle?.joinToString("-") { it.name } ?: ""
mBodyBinding.gamedetailGametag.setTags(mNewGameDetailEntity?.tagStyle!!)
@ -1705,7 +1706,7 @@ class GameDetailFragment : ToolbarFragment(), IScrollable {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(changed: EBConcernChanged) {
if (mNewGameDetailEntity != null && mGameEntity != null && changed.isSingle && changed.gameId == mGameEntity!!.id) {
mNewGameDetailEntity!!.me.isGameConcerned = changed.isConcern
mNewGameDetailEntity!!.me?.isGameConcerned = changed.isConcern
updateGameDetailTopArea()
}
}
@ -1739,7 +1740,7 @@ class GameDetailFragment : ToolbarFragment(), IScrollable {
}
mDownloadBinding.ivConcern.setOnClickListener {
ifLogin("游戏详情-[关注]") {
if (mNewGameDetailEntity != null && mNewGameDetailEntity!!.me.isGameConcerned) {
if (mNewGameDetailEntity != null && mNewGameDetailEntity!!.me?.isGameConcerned == true) {
DialogHelper.showCancelDialog(requireContext(), {
mViewModel.concernCommand(false)
})
@ -1826,18 +1827,28 @@ class GameDetailFragment : ToolbarFragment(), IScrollable {
val downloadMenuView = mBodyBinding.toolbar.menu.findItem(R.id.menu_download).actionView
mDownloadCountHint = downloadMenuView?.findViewById(R.id.menu_download_count_hint)
mDownloadCountHint?.typeface =
Typeface.createFromAsset(requireContext().assets, "fonts/d_din_bold_only_number.ttf")
}
private fun updateDownloadCountHint(updateList: List<GameUpdateEntity>?) {
val count = DownloadManager.getInstance().getDownloadOrUpdateCount(updateList)
val params = mDownloadCountHint?.layoutParams
if (TextUtils.isEmpty(count)) {
params?.width = DisplayUtils.dip2px(6f)
params?.height = DisplayUtils.dip2px(6f)
} else {
params?.width = DisplayUtils.dip2px(12f)
params?.height = DisplayUtils.dip2px(12f)
}
params?.width = if (count.isNullOrEmpty()) 6F.dip2px() else ConstraintLayout.LayoutParams.WRAP_CONTENT
params?.height = if (count.isNullOrEmpty()) 6F.dip2px() else 14F.dip2px()
(params as? ViewGroup.MarginLayoutParams)?.setMargins(
0,
if (count.isNullOrEmpty()) 0 else (-4F).dip2px(),
if (count.isNullOrEmpty()) (-4F).dip2px() else (-8F).dip2px(),
0
)
mDownloadCountHint?.setPadding(
if (count.isNullOrEmpty()) 0 else 4F.dip2px(),
0,
if (count.isNullOrEmpty()) 0 else 4F.dip2px(),
0
)
mDownloadCountHint?.minWidth = if (count.isNullOrEmpty()) 0 else 14F.dip2px()
mDownloadCountHint?.layoutParams = params
mDownloadCountHint?.goneIf(count == null)

View File

@ -19,7 +19,7 @@ class NewGameDetailEntity(
@SerializedName("top_video")
var topVideo: Video? = null,
@SerializedName("me")
var me: MeEntity = MeEntity(),
var me: MeEntity? = null,
var event: BigEvent? = null,//游戏大事件
@SerializedName("detail_dialogs")
var detailDialogs: ArrayList<GameEntity.Dialog> = arrayListOf(),

View File

@ -6,13 +6,16 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.common.util.CheckLoginUtils
import com.gh.gamecenter.common.utils.throwExceptionInDebug
import com.gh.gamecenter.entity.*
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.feature.entity.GameDetailServer
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.MeEntity
import com.gh.gamecenter.feature.entity.ServerCalendarEntity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.retrofit.RetrofitManager
import io.reactivex.Single
import io.reactivex.SingleEmitter
@ -22,12 +25,11 @@ import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.ArrayList
class ServersCalendarViewModel(
application: Application,
val game: GameEntity,
val gameServer: GameDetailServer,
val meEntity: MeEntity?
var meEntity: MeEntity?
) : AndroidViewModel(application) {
val calendarLiveData: MutableLiveData<List<CalendarEntity>> = MutableLiveData()
@ -50,6 +52,11 @@ class ServersCalendarViewModel(
} else {
loadServerData(false)
}
// 登录状态下,且用户信息为空时尝试请求接口判断是否为兼职
if (meEntity == null && CheckLoginUtils.isLogin()) {
requestPartTimeInfo()
}
}
@SuppressLint("CheckResult")
@ -69,6 +76,27 @@ class ServersCalendarViewModel(
})
}
@SuppressLint("CheckResult")
private fun requestPartTimeInfo() {
val requestMap = hashMapOf<String, List<String>>()
requestMap["game"] = arrayListOf(game.id)
RetrofitManager.getInstance().api.getUserRelatedInfoByPost(UserManager.getInstance().userId, requestMap.toRequestBody())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<UnifiedUserTrendEntity>() {
override fun onSuccess(data: UnifiedUserTrendEntity) {
data.game?.let {
if (data.game.isNullOrEmpty()) return@let
for (game in it) {
meEntity = game.me
break
}
}
}
})
}
// private fun checkExistCurSerer() {
// isExistCurServer = true // 此参数控制是否显示当月开服表(无论有没有开服信息),为防止需求变更先设死这个参数
// val curDate = System.currentTimeMillis()

View File

@ -27,6 +27,7 @@ import com.gh.gamecenter.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.common.view.OffsetLinearLayoutManager
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.provider.IFloatingWindowProvider
import com.gh.gamecenter.core.provider.IQGameProvider
import com.gh.gamecenter.core.provider.ICsjAdProvider
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.core.utils.MtaHelper

View File

@ -7,10 +7,12 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.DirectUtils
import com.gh.common.util.DownloadItemUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.RandomUtils
import com.gh.gamecenter.databinding.HomeGameItemBinding
@ -92,12 +94,19 @@ class HomeGameItemViewHolder(val binding: HomeGameItemBinding) : BaseRecyclerVie
}
}
holder.itemView.setOnClickListener {
GameDetailActivity.startGameDetailActivity(
binding.root.context,
game.id,
"(${entrance}-游戏[" + game.name + "])",
exposureEvent
)
if (game.isQQMiniGame()) {
NewFlatLogUtils.logQGameClick(game.qqMiniGameAppId, game.name)
GlobalActivityManager.currentActivity?.let {
DirectUtils.directToQGameById(it, game.qqMiniGameAppId)
}
} else {
GameDetailActivity.startGameDetailActivity(
binding.root.context,
game.id,
"(${entrance}-游戏[" + game.name + "])",
exposureEvent
)
}
if (holder.binding.autoVideoView.isInPlayingState) {
holder.binding.autoVideoView.uploadVideoStreamingPlaying(
"游戏详情-播放点击",

View File

@ -3,10 +3,13 @@ package com.gh.gamecenter.personal
import android.annotation.SuppressLint
import android.content.Intent
import android.database.sqlite.SQLiteException
import android.graphics.Typeface
import android.os.*
import android.text.TextUtils
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.RelativeLayout
import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat
@ -57,6 +60,7 @@ import com.tencent.connect.common.Constants
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import splitties.views.dsl.core.endMargin
import java.lang.ref.WeakReference
import java.util.concurrent.TimeUnit
import kotlin.math.abs
@ -426,10 +430,18 @@ class HaloPersonalFragment : BaseLazyFragment() {
mStubBinding.loginMessageHint.text = ""
params.width = 6F.dip2px()
params.height = 6F.dip2px()
(params as ViewGroup.MarginLayoutParams).topMargin = 0
params.endMargin = (-4F).dip2px()
mStubBinding.loginMessageHint.setPadding(0, 0, 0, 0)
mStubBinding.loginMessageHint.minWidth = 0
} else {
BindingAdapters.setMessageUnread(mStubBinding.loginMessageHint, it)
params.width = 12F.dip2px()
params.height = 12F.dip2px()
params.width = RelativeLayout.LayoutParams.WRAP_CONTENT
params.height = 14F.dip2px()
(params as ViewGroup.MarginLayoutParams).topMargin = (-4F).dip2px()
params.endMargin = (-8F).dip2px()
mStubBinding.loginMessageHint.setPadding(4F.dip2px(), 0, 4F.dip2px(), 0)
mStubBinding.loginMessageHint.minWidth = 14F.dip2px()
}
mStubBinding.loginMessageHint.layoutParams = params
} else {
@ -570,6 +582,8 @@ class HaloPersonalFragment : BaseLazyFragment() {
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, "fonts/d_din_bold_only_number.ttf")
mStubBinding.motionLayout.setOnClickListener(this)
mStubBinding.toolbarContainer.setOnClickListener(this)

View File

@ -0,0 +1,23 @@
package com.gh.gamecenter.qgame
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.entity.SubjectRecommendEntity
import com.gh.gamecenter.game.GameFragment
import com.halo.assistant.HaloApp
/**
* QQ小游戏-专题页面
*/
class QGameFragment : GameFragment() {
/**
* 只替换ViewModelUI显示与GameFragment完全相同
*/
override fun provideViewModel(): QGameViewModel {
val factory = QGameViewModel.Factory(
HaloApp.getInstance().application,
SubjectRecommendEntity(text = "QQ小游戏")
)
return viewModelProvider(factory)
}
}

View File

@ -0,0 +1,37 @@
package com.gh.gamecenter.qgame
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.gh.gamecenter.common.base.activity.ToolBarActivity
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.updateStatusBarColor
import com.gh.gamecenter.forum.search.ForumOrUserSearchActivity
import com.gh.gamecenter.setting.R
/**
* QQ小游戏-首页
*/
class QGameHomeWrapperActivity : ToolBarActivity() {
override fun provideNormalIntent(): Intent {
return getTargetIntent(this, QGameHomeWrapperActivity::class.java, QGameHomeWrapperFragment::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
updateStatusBarColor(R.color.background_white, R.color.background_white)
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
updateStatusBarColor(R.color.background_white, R.color.background_white)
}
companion object {
fun getIntent(context: Context): Intent {
val intent = Intent(context, QGameHomeWrapperActivity::class.java)
return intent
}
}
}

View File

@ -0,0 +1,34 @@
package com.gh.gamecenter.qgame
import android.os.Bundle
import android.view.View
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.SearchActivity
import com.gh.gamecenter.SearchType
import com.gh.gamecenter.common.base.fragment.ToolbarFragment
import com.gh.gamecenter.databinding.FragmentQgameHomeBinding
class QGameHomeWrapperFragment : ToolbarFragment() {
private lateinit var viewBinding: FragmentQgameHomeBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setNavigationTitle(getString(R.string.qgame_title))
val contentFragment = childFragmentManager.findFragmentById(R.id.wrapper_content) ?: QGameFragment()
childFragmentManager.beginTransaction().replace(R.id.wrapper_content, contentFragment).commitAllowingStateLoss()
}
override fun getInflatedLayout(): View =
FragmentQgameHomeBinding.inflate(layoutInflater).also { viewBinding = it }.root
override fun initView(view: View?) {
super.initView(view)
viewBinding.searchBar.root.setOnClickListener {
DirectUtils.directToQGameSearch(it.context, "请输入小游戏关键词", "QQ小游戏")
}
}
override fun getLayoutId(): Int = 0
}

View File

@ -0,0 +1,168 @@
package com.gh.gamecenter.qgame
import android.content.Context
import android.os.Build
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
import com.gh.common.util.DirectUtils
import com.gh.common.util.DownloadItemUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.baselist.DiffUtilAdapter
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.visibleIf
import com.gh.gamecenter.common.view.GridSpacingItemColorDecoration
import com.gh.gamecenter.databinding.ItemHomeRecentVgameBinding
import com.gh.gamecenter.databinding.ItemHomeVgameBinding
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
class QGameHorizontalSlideListViewHolder(var binding: ItemHomeRecentVgameBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindHorizontalSlideList(subject: SubjectEntity) {
binding.vspaceIv.visibility = View.GONE
binding.divider.visibility = View.GONE
if (subject.showName) {
binding.titleTv.visibility = View.VISIBLE
binding.titleTv.text = subject.getFilterName()
} else {
binding.titleTv.visibility = View.GONE
}
val text = if ("change" == subject.home) {
"换一批"
} else {
when (subject.home) {
"more" -> "更多"
"hide" -> ""
else -> "全部"
}
}
binding.moreTv.text = text
if (subject.indexRightTop != null && subject.indexRightTop != "none") {
// 开测表用到的
binding.moreTv.visibility = View.VISIBLE
if (subject.indexRightTop == "all") {
binding.moreTv.text = "全部"
} else {
binding.moreTv.text = "更多"
}
} else if (text == "全部"
&& subject.data != null
&& subject.data!!.size >= (subject.more ?: 0)
) {
binding.moreTv.visibility = View.GONE
} else if (subject.home == "hide") {
binding.moreTv.visibility = View.GONE
} else {
binding.moreTv.visibility = View.VISIBLE
}
if (binding.moreTv.visibility == View.VISIBLE && (binding.moreTv.text == "更多" || binding.moreTv.text == "全部")) {
binding.moreTv.visibility = View.VISIBLE
}
val subjectAdapter = if (binding.recyclerView.adapter == null) {
val layoutManager =
LinearLayoutManager(binding.root.context, RecyclerView.HORIZONTAL, false)
binding.recyclerView.layoutManager = layoutManager
binding.recyclerView.itemAnimator = null
val adapter = QGameHorizontalSlideAdapter(binding.root.context)
binding.recyclerView.adapter = adapter
binding.recyclerView.addItemDecoration(
GridSpacingItemColorDecoration(binding.root.context, 4, 0, R.color.transparent)
)
binding.recyclerView.addOnScrollListener(object : OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dx == 0) {
val scrollToEnd =
layoutManager.findLastCompletelyVisibleItemPosition() == (binding.recyclerView.adapter!!.itemCount - 1)
// 当游戏数量超过一屏时需去除列表右侧的分隔线去除后列表最后一个游戏图标与卡片右侧的边距应为16dp
if (scrollToEnd) {
binding.recyclerViewContainer.post {
binding.recyclerViewContainer.layoutParams =
(binding.recyclerViewContainer.layoutParams as MarginLayoutParams).apply {
rightMargin = 10F.dip2px()
}
}
binding.divider.visibility = View.VISIBLE
} else {
binding.recyclerViewContainer.post {
binding.recyclerViewContainer.layoutParams =
(binding.recyclerViewContainer.layoutParams as MarginLayoutParams).apply {
rightMargin = 0
}
}
binding.divider.visibility = View.GONE
}
}
}
})
adapter
} else {
binding.recyclerView.adapter as QGameHorizontalSlideAdapter
}
subjectAdapter.submitList(subject.data)
}
}
class QGameHorizontalSlideAdapter(context: Context) : DiffUtilAdapter<GameEntity>(context) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): QGameHorizontalSlideViewHolder {
return QGameHorizontalSlideViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as QGameHorizontalSlideViewHolder).bindView(mDataList[position])
}
override fun getItemCount(): Int {
return mDataList.size
}
override fun areItemsTheSame(oldItem: GameEntity?, newItem: GameEntity?): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: GameEntity?, newItem: GameEntity?): Boolean {
return oldItem == newItem
}
class QGameHorizontalSlideViewHolder(private var mBinding: ItemHomeVgameBinding) :
RecyclerView.ViewHolder(mBinding.root) {
fun bindView(entity: GameEntity) {
mBinding.gameIconIv.displayGameIcon(entity)
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
mBinding.root.setOnClickListener {
NewFlatLogUtils.logQGameClick(entity.qqMiniGameAppId, entity.name)
GlobalActivityManager.currentActivity?.let {
DirectUtils.directToQGameById(it, entity.qqMiniGameAppId)
}
}
}
}
}

View File

@ -0,0 +1,64 @@
package com.gh.gamecenter.qgame
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.gh.gamecenter.DisplayType
import com.gh.gamecenter.SearchActivity
import com.gh.gamecenter.SearchType
import com.gh.gamecenter.common.base.activity.ToolBarActivity
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.login.R
import com.gh.gamecenter.search.SearchGameIndexFragment
import com.lightgame.utils.Util_System_Keyboard
/**
* QQ小游戏-搜索页面
*/
class QGameSearchActivity : SearchActivity() {
override fun updateDisplayType(type: DisplayType) {
val transaction = supportFragmentManager.beginTransaction()
when(type) {
DisplayType.GAME_DETAIL -> {
val digestListFragment =
supportFragmentManager.findFragmentByTag(QGameSearchResultFragment::class.java.name) as? QGameSearchResultFragment
?: QGameSearchResultFragment()
digestListFragment.setParams(mSearchKey ?: "", mSearchType.value)
transaction.replace(com.gh.gamecenter.R.id.search_result, digestListFragment, SearchGameIndexFragment::class.java.name)
}
else -> {
AppExecutor.uiExecutor.executeWithDelay({
Util_System_Keyboard.showSoftKeyboard(this)
}, 100)
}
}
mDisplayType = type
transaction.commitAllowingStateLoss()
}
override fun handleBackPressed(): Boolean {
Util_System_Keyboard.hideSoftKeyboard(this)
finish()
return true
}
companion object {
@JvmStatic
fun getIntent(
context: Context,
hint: String,
sourceEntrance: String
): Intent {
val intent = Intent(context, QGameSearchActivity::class.java)
intent.putExtra(EntranceConsts.KEY_HINT, hint)
intent.putExtra(EntranceConsts.KEY_SOURCE_ENTRANCE, sourceEntrance)
return intent
}
}
}

View File

@ -0,0 +1,25 @@
package com.gh.gamecenter.qgame
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.search.SearchGameResultFragment
import com.gh.gamecenter.search.SearchGameResultViewModel
import com.halo.assistant.HaloApp
/**
* QQ小游戏-搜索结果页面
*/
class QGameSearchResultFragment : SearchGameResultFragment() {
/**
* 仅替换搜索游戏的API搜索结果页面样式与首页的完全一致
*/
override fun provideListViewModel(): SearchGameResultViewModel {
val factory = SearchGameResultViewModel.Factory(
HaloApp.getInstance().application,
mKey,
true,
QGameSearchResultRepository()
)
return viewModelProvider(factory)
}
}

View File

@ -0,0 +1,31 @@
package com.gh.gamecenter.qgame
import com.gh.gamecenter.common.utils.EnvHelper
import com.gh.gamecenter.entity.SearchSubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.gh.gamecenter.search.ISearchGameResultRepository
import io.reactivex.Observable
import java.net.URLEncoder
class QGameSearchResultRepository(
private val api: ApiService = RetrofitManager.getInstance().newApi
) : ISearchGameResultRepository {
override fun getSearchGame(
key: String?,
page: Int
): Observable<List<GameEntity>> {
// 可能会有特殊字符,需要 encode 处理
val encodedKey = URLEncoder.encode(key, "utf-8")
return api.getSearchQGame(
EnvHelper.getNewHost() + "games/qq_mini/columns/game_search?keyword=" +
encodedKey + "&page=" + page + "&page_size=20"
)
}
override fun getSearchSubject(key: String?, page: Int): Observable<List<SearchSubjectEntity>> {
return Observable.just(emptyList())
}
}

View File

@ -0,0 +1,17 @@
package com.gh.gamecenter.qgame
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.gh.gamecenter.subject.ISubjectListRepository
import io.reactivex.Single
class QGameSubjectListRepository(
private val api: ApiService = RetrofitManager.getInstance().newApi
) : ISubjectListRepository {
override fun getColumn(column_id: String?, page: Int, sort: String?, order: String?): Single<MutableList<GameEntity>> {
return api.getQGameColumn(column_id, order, page, 20)
}
}

View File

@ -0,0 +1,25 @@
package com.gh.gamecenter.qgame
import androidx.lifecycle.viewmodel.viewModelFactory
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.gh.gamecenter.subject.ISubjectRepository
import io.reactivex.Observable
import okhttp3.MediaType
import okhttp3.ResponseBody
class QGameSubjectRepository(
private val api: ApiService = RetrofitManager.getInstance().newApi
) : ISubjectRepository {
override fun getColumnSettings(column_id: String?): Observable<SubjectSettingEntity> {
return api.getQGameColumnSettings(column_id)
}
override fun getSubjectName(column_id: String?): Observable<ResponseBody> {
return Observable.just(ResponseBody.create(MediaType.parse("application/json"), "{\"name\": \"专题\"}"))
}
}

View File

@ -0,0 +1,4 @@
package com.gh.gamecenter.qgame
class QGameSubjectUpdateEvent {
}

View File

@ -0,0 +1,583 @@
package com.gh.gamecenter.qgame
import android.app.Application
import android.content.Context
import android.text.TextUtils
import androidx.collection.ArrayMap
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.common.constant.Config
import com.gh.common.filter.RegionSettingHelper
import com.gh.common.util.GameSubstituteRepositoryHelper
import com.gh.common.util.GameUtils
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.core.provider.IQGameProvider
import com.gh.gamecenter.core.utils.RandomUtils
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.entity.SubjectRecommendEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.GameSubjectData
import com.gh.gamecenter.feature.utils.ApkActiveUtils
import com.gh.gamecenter.game.BaseGameViewModel
import com.gh.gamecenter.game.GameViewModel
import com.gh.gamecenter.game.data.GameItemData
import com.gh.gamecenter.game.rank.RankCollectionAdapter
import com.gh.gamecenter.home.BlankDividerViewHolder
import com.gh.gamecenter.home.LegacyHomeFragmentAdapterAssistant
import com.gh.gamecenter.retrofit.RetrofitManager
import com.lightgame.utils.Utils
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import retrofit2.HttpException
import kotlin.math.roundToInt
class QGameViewModel(application: Application, blockData: SubjectRecommendEntity?) :
BaseGameViewModel(application, blockData) {
private val mSensitiveApi = RetrofitManager.getInstance().newApi
private var mSubjectList: MutableList<SubjectEntity> = ArrayList() // 专题
private val mSubjectChangedMap: ArrayMap<String, List<GameEntity>> = ArrayMap() // 存储换一换的数据
// 所有专题里的所有游戏 ID 的集合,供替换时排重用
private var mSubjectGameIdList = hashSetOf<String>()
private var mIsLoading = false
init {
initQQMiniGameSDK(application)
initData()
EventBus.getDefault().register(this)// 为了减少对原有逻辑的影响把EventBus注册在这里
}
override fun onInitData() {
mSubjectList = arrayListOf()
loadStatus.postValue(LoadStatus.INIT_LOADING)
getQGameSubjectList()
}
override fun changeSubjectGame(subjectId: String) {
mSensitiveApi
.getSubjectQGame(subjectId)
.map(RegionSettingHelper.filterGame)
.map(ApkActiveUtils.filterMapperList)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<List<GameEntity>>() {
override fun onResponse(response: List<GameEntity>?) {
if (response != null) {
mSubjectChangedMap[subjectId] = response
initRandomGame(subjectId, java.util.ArrayList(response))
}
}
override fun onFailure(e: HttpException?) {
Utils.toast(getApplication(), "网络异常")
}
})
}
private fun getQGameSubjectList() {
if (mIsLoading) return
mSensitiveApi
.subjectQGameList
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<List<SubjectEntity>>() {
override fun onResponse(response: List<SubjectEntity>?) {
if (response != null) {
when {
response.isEmpty() -> {
loadStatus.postValue(LoadStatus.LIST_OVER)
}
else -> {
mSubjectList.addAll(response)
loadStatus.postValue(LoadStatus.LIST_OVER)
transformItemData()
}
}
}
mIsLoading = false
}
override fun onFailure(e: HttpException?) {
loadStatus.postValue(LoadStatus.LIST_FAILED)
mIsLoading = false
}
})
}
// 随机产生专题数据(换一换)
private fun initRandomGame(subjectId: String, sourceList: List<GameEntity>?) {
var rawList: MutableList<GameEntity>? = null
for (entity in mSubjectList) {
if (entity.id == subjectId) rawList = entity.data
}
if (rawList == null) return
var sourceList = sourceList
var size = rawList.size // 判断是否有大图
var i = 0
while (i < rawList.size) { //删除原数据,排除大图
if (TextUtils.isEmpty(rawList[i].image)) {
rawList.removeAt(i)
i--
} else {
size--
}
i++
}
if (size * 2 <= sourceList!!.size) {
sourceList = GameUtils.removeDuplicateData(rawList, sourceList)//排除重复
}
val indexes = RandomUtils.getRandomArray(size, sourceList!!.size)
for (index in indexes) {
val gameEntity = sourceList[index]
rawList.add(gameEntity)
}
transformItemData()
}
private fun transformItemData() {
mItemDataListCache.clear()
positionAndPackageMap.clear()
val iterator = mSubjectList.iterator()
while (iterator.hasNext()) {
val item = iterator.next()
// 双列卡片专题过滤掉无封面图游戏
if (item.type == "game_double_card") {
item.data = item.data?.filter { it.columnImage.isNotBlank() }?.toMutableList()
// 游戏数量小于2个不显示专题所以直接去掉
if ((item.data?.size ?: 0) < 2) {
iterator.remove()
}
}
//过滤掉游戏数量小于2个的视频横屏滑动专题
if (item.type == "game_video_horizontal_slide") {
if ((item.data?.size ?: 0) < 2) {
iterator.remove()
}
}
}
// 专题 "type": "image/game_vertical/game_horizontal"
for ((index, subjectEntity) in mSubjectList.withIndex()) {
var containsImageBeforeHead = false // 在专题名上面是否有大图
val data = subjectEntity.data
// 这个 for 循环主要功能是用来标识替换已安装的游戏
if (!data.isNullOrEmpty()) {
for (game in data) {
mSubjectGameIdList.add(game.id)
// 应用专题是否显示游戏名后缀的配置
game.shouldShowNameSuffix = subjectEntity.showSuffix
}
}
if (!data.isNullOrEmpty()) {
subjectEntity.relatedColumnId?.let {
GameSubstituteRepositoryHelper.replaceGames(data, mSubjectGameIdList, it, false)
}
}
val shouldShowDivider = subjectEntity.type == "game" || subjectEntity.type == "video"
// 是否不显示专题标题
val hideSubjectName = !subjectEntity.showName
// 判断是否添加顶部间距
// 顶部 & 显示了分割线的 & 隐藏了专题标题的 item 不添加顶部间距
if (index != 0
&& !shouldShowDivider
&& !hideSubjectName
&& !(subjectEntity.type == "game_list_collection" && subjectEntity.style == LegacyHomeFragmentAdapterAssistant.GAME_COLLECTION_VERTICAL_REFRESH_STYLE)
) {
mItemDataListCache.add(getBlankSpacingItem())
}
if (shouldShowDivider) {
mItemDataListCache.add(GameItemData().apply { lineDivider = GameViewModel.DEFAULT_DIVIDER })
}
if (!data.isNullOrEmpty() && !data[0].image.isNullOrEmpty() && subjectEntity.type != "column_collection") {
val itemDataImage = GameItemData()
val gameEntity = data[0]
var isBigImageWithGame = false
if (gameEntity.id.isNullOrEmpty()) gameEntity.id = gameEntity.link ?: ""
appendAdditionalInfoToSubjectGame(subjectEntity, index)
// 图片专题样式(后台操作->只显示(滑动))
if (data[0].type == "column" && subjectEntity.type == "image_slide") {
itemDataImage.imageSlide = gameEntity
gameEntity.games?.let {
for ((i, g) in it.withIndex()) {
g.sequence = i
g.outerSequence = index
}
}
} else {
itemDataImage.image = gameEntity
itemDataImage.image?.subjectData =
GameSubjectData(id = subjectEntity.id, name = subjectEntity.name, isOrder = false)
if (gameEntity.type == "game") {
isBigImageWithGame = true
}
}
containsImageBeforeHead = true
mItemDataListCache.add(itemDataImage)
// 确保下载按钮更新
if (isBigImageWithGame) {
addGamePositionAndPackage(gameEntity)
}
mItemDataListCache.add(getBlankSpacingItem()) // 图片风格 item 后需要空白间距
if (subjectEntity.type == "image" || subjectEntity.type == "image_slide") continue
}
// 专题名
if (!hideSubjectName
&& (subjectEntity.type != "column_collection"
&& subjectEntity.type != "column_test_v2"
&& subjectEntity.type != "community_article"
&& subjectEntity.type != "question"
&& subjectEntity.type != "bbs_video"
&& subjectEntity.type != "news"
&& subjectEntity.type != "game"
&& subjectEntity.type != "video"
&& subjectEntity.type != "game_explore"
&& subjectEntity.type != "game_list_collection"
&& subjectEntity.type != "qq_game_horizontal_slide"
&& subjectEntity.type != "gallery")
|| (subjectEntity.type == "column_collection" && subjectEntity.style != "top")
) {
val itemDataHead = GameItemData()
// 专题名称与大图间需要额外的空白间距
if (containsImageBeforeHead) {
mItemDataListCache.add(getBlankSpacingItem())
}
appendAdditionalInfoToSubjectGame(subjectEntity, index)
itemDataHead.columnHead = subjectEntity
mItemDataListCache.add(itemDataHead)
}
// 裁剪排行榜数据数量
if (subjectEntity.type == "column_collection" && subjectEntity.style == "top") {
subjectEntity.columns.let { columns ->
for (column in columns) {
column.data =
ArrayList(column.data?.take(RankCollectionAdapter.MAX_RANK_ITEM_COUNT) ?: listOf())
}
}
}
if (!data.isNullOrEmpty()) {
if (subjectEntity.type == "game_vertical_slide") {
val itemVerticalSlide = GameItemData()
itemVerticalSlide.verticalSlide = subjectEntity
mItemDataListCache.add(itemVerticalSlide)
appendAdditionalInfoToSubjectGame(subjectEntity, index)
for (i in 0 until data.size) {
val game = data[i]
if (!game.image.isNullOrEmpty()) continue
game.subjectData = GameSubjectData(
id = subjectEntity.id,
name = subjectEntity.name,
tag = subjectEntity.tag,
position = i + if (data[0].image.isNullOrEmpty()) 1 else 0,
isOrder = subjectEntity.isOrder,
briefStyle = subjectEntity.briefStyle,
isShowSuffix = subjectEntity.showSuffix
)
addGamePositionAndPackage(game)
}
continue
}
}
if (subjectEntity.type == "game_horizontal_slide") {
val itemHorizontalSlide = GameItemData()
itemHorizontalSlide.horizontalSlide = subjectEntity
// 开测表数据,把全部转化为右上角跳转
if (subjectEntity.indexRightTop == "all") {
subjectEntity.indexRightTopLink =
LinkEntity(text = subjectEntity.name, link = subjectEntity.id, type = "column_test")
}
appendAdditionalInfoToSubjectGame(subjectEntity, index)
mItemDataListCache.add(itemHorizontalSlide)
continue
}
// QQ小游戏-横屏滑动
if (subjectEntity.type == "qq_game_horizontal_slide") {
val itemQQHorizontalSlide = GameItemData()
itemQQHorizontalSlide.qqHorizontalSlide = subjectEntity
appendAdditionalInfoToSubjectGame(subjectEntity, index)
mItemDataListCache.add(itemQQHorizontalSlide)
continue
}
if (subjectEntity.type == "game_horizontal") {
val itemDataSubject = GameItemData()
itemDataSubject.horizontalColumn = subjectEntity
appendAdditionalInfoToSubjectGame(subjectEntity, index)
mItemDataListCache.add(itemDataSubject)
continue
}
if (subjectEntity.type == "column_collection" && subjectEntity.style != "top") {
val itemColumnCollection = GameItemData()
itemColumnCollection.columnCollection = subjectEntity
appendAdditionalInfoToSubjectGame(subjectEntity, index)
mItemDataListCache.add(itemColumnCollection)
continue
}
if (subjectEntity.type == "common_collection") {
if (subjectEntity.homePageStyle == "1-2") {
// 样式为一行两个的时候把数据拆分为若干个一行两个的小item根据 offset 来在原数据中的获取数据
var lineCount = (((subjectEntity.commonCollectionList?.size ?: 0).toDouble()) / 2).roundToInt()
if (!TextUtils.isEmpty(subjectEntity.verticalLine)) {
val serverSideLineCount = subjectEntity.verticalLine.toInt()
if (lineCount == 0 || serverSideLineCount < lineCount) {
lineCount = serverSideLineCount
}
}
for (i in 0 until lineCount) {
if (i == 0) {
mItemDataListCache.add(GameItemData().apply { blankDivider = 12F })
} else {
if (subjectEntity.style == "img-list") {
mItemDataListCache.add(GameItemData().apply { blankDivider = 12F })
}
}
val itemDataSubject = GameItemData()
itemDataSubject.commonLinkCollection12 = subjectEntity
appendAdditionalInfoToSubjectGame(subjectEntity, index)
itemDataSubject.offset = i * 2
mItemDataListCache.add(itemDataSubject)
}
} else {
val itemCommonCollection = GameItemData()
itemCommonCollection.commonLinkCollection = subjectEntity
appendAdditionalInfoToSubjectGame(subjectEntity, index)
mItemDataListCache.add(itemCommonCollection)
}
continue
}
if (subjectEntity.type == "gallery") {
val itemItemData = GameItemData()
itemItemData.gallery = subjectEntity
appendAdditionalInfoToSubjectGame(subjectEntity, index)
// 小于3就不要滚动了
if (subjectEntity.data != null && subjectEntity.data!!.size > 3) {
for ((index, game) in subjectEntity.data!!.withIndex()) {
game.sequence = index
}
mItemDataListCache.add(itemItemData)
}
continue
}
if (subjectEntity.type == "gallery" || subjectEntity.type == "gallery_slide") {
val itemItemData = GameItemData()
itemItemData.gallerySlide = subjectEntity
appendAdditionalInfoToSubjectGame(subjectEntity, index)
// 小于3就不要滚动了
if (subjectEntity.data != null && subjectEntity.data!!.size > 3) {
for ((index, game) in subjectEntity.data!!.withIndex()) {
game.sequence = index
}
mItemDataListCache.add(itemItemData)
}
continue
}
if (subjectEntity.type == "game_double_card") {
val gameCount = subjectEntity.data?.size ?: continue
for (i in 0 until gameCount) {
if (i % 2 == 0) {
if (i == 0) {
mItemDataListCache.add(GameItemData().apply { blankDivider = 12F })
}
val itemDataSubject = GameItemData()
itemDataSubject.doubleCardColumn = subjectEntity
itemDataSubject.offset = i
mItemDataListCache.add(itemDataSubject)
}
}
mItemDataListCache.add(GameItemData().apply { blankDivider = 12F })
continue
}
if (subjectEntity.type == "community_article"
|| subjectEntity.type == "question"
|| subjectEntity.type == "bbs_video"
|| subjectEntity.type == "news"
) {
val itemDataSubject = GameItemData()
itemDataSubject.bigImageRecommend = subjectEntity
mItemDataListCache.add(itemDataSubject)
continue
}
if (subjectEntity.type == "game_video_horizontal_slide") {
val itemItemData = GameItemData().apply {
horizontalSlideVideo = subjectEntity
}
mItemDataListCache.add(itemItemData)
itemItemData.horizontalSlideVideo?.data?.forEach {
addGamePositionAndPackage(it)
}
continue
}
if (!data.isNullOrEmpty()) {
for (i in 0 until data.size) {
val game = data[i]
if (!game.image.isNullOrEmpty()) continue
// 第一个普通游戏顶部需要增加半高的填充间距
if (i == 0) {
mItemDataListCache.add(GameItemData().apply {
blankDivider = BlankDividerViewHolder.DEFAULT_BLANK_HEIGHT
})
}
game.subjectData = GameSubjectData(
id = subjectEntity.id,
name = subjectEntity.name,
tag = subjectEntity.tag,
position = i + if (data[0].image.isNullOrEmpty()) 1 else 0,
isOrder = subjectEntity.isOrder,
briefStyle = subjectEntity.briefStyle,
isShowSuffix = subjectEntity.showSuffix
)
val itemDataGame = GameItemData()
itemDataGame.game = game
mItemDataListCache.add(itemDataGame)
addGamePositionAndPackage(game)
}
}
if (subjectEntity.type == "column_test_v2") {
//新游开测 标题和全部按钮
val head = GameItemData()
val rightTopLink = subjectEntity.testV2Data?.rightTop?.link
val indexRightTop = subjectEntity.testV2Data?.rightTop?.text
head.columnHead =
SubjectEntity(
type = subjectEntity.type,
name = "新游开测",
indexRightTop = indexRightTop,
indexRightTopLink = rightTopLink
)
mItemDataListCache.add(head)
//新游开测 条目
val item = GameItemData()
item.gameTestV2Entity = subjectEntity.testV2Data
mItemDataListCache.add(item)
continue
}
if (subjectEntity.type == "game" || subjectEntity.type == "video") {
val itemItemData = GameItemData()
itemItemData.attachGame = subjectEntity
appendAdditionalInfoToSubjectGame(subjectEntity, index)
mItemDataListCache.add(itemItemData)
subjectEntity.linkGame?.let {
addGamePositionAndPackage(it)
}
continue
}
appendAdditionalInfoToSubjectGame(subjectEntity, index)
}
itemDataList.postValue(mItemDataListCache)
}
override fun getSubjectList() {
loadStatus.postValue(LoadStatus.LIST_OVER)
}
override fun onCleared() {
super.onCleared()
EventBus.getDefault().unregister(this)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onQGameSubjectUpdate(event: QGameSubjectUpdateEvent) {
onInitData()
}
override fun replaceRefreshData(itemData: GameItemData): Boolean = false
override fun refreshDiscoverCardData() = Unit
override fun changeGameCollectionRefresh(collectionId: String, isRefreshClick: Boolean): Boolean = false
class Factory(val mApplication: Application, val blockData: SubjectRecommendEntity?) :
ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return QGameViewModel(mApplication, blockData) as T
}
}
companion object {
private var isQQMiniSDKInit = false
/**
* QQ小游戏SDK初始化
*/
private fun initQQMiniGameSDK(context: Context) {
if (isQQMiniSDKInit) return
val provider = ARouter
.getInstance()
.build(RouteConsts.provider.qGame)
.navigation() as IQGameProvider<*>
provider.init(context, Config.WECHAT_APPID, Config.TENCENT_APPID)
isQQMiniSDKInit = true
}
fun notifyQGameSubjectUpdate() {
EventBus.getDefault().post(QGameSubjectUpdateEvent())
}
}
}

View File

@ -5,6 +5,7 @@ import android.content.Context;
import com.gh.common.constant.Config;
import com.gh.gamecenter.BuildConfig;
import com.gh.gamecenter.common.retrofit.BaseRetrofitManager;
import com.gh.gamecenter.common.utils.EnvHelper;
import com.gh.gamecenter.retrofit.service.ApiService;
import com.gh.gamecenter.retrofit.service.VApiService;
import com.halo.assistant.HaloApp;

View File

@ -1343,14 +1343,14 @@ public interface ApiService {
*/
@POST("question/{question_id}:set-top")
Observable<ResponseBody> topCommunityQuestion(@Path("question_id") String questionId,
@Body RequestBody body);
@Body RequestBody body);
/**
* 提问帖-取消置顶
*/
@POST("question/{question_id}:cancel-top")
Observable<ResponseBody> cancelTopCommunityQuestion(@Path("question_id") String questionId,
@Body RequestBody body);
@Body RequestBody body);
/**
* 视频帖-置顶
@ -3143,6 +3143,45 @@ public interface ApiService {
@GET("blocks/{block_id}/tabs")
Single<List<SubjectRecommendEntity>> getBlockTab(@Path("block_id") String blockId);
/**
* QQ小游戏-专题列表
*/
@GET("games/qq_mini/columns")
Observable<List<SubjectEntity>> getSubjectQGameList();
/**
* QQ小游戏-专题-换一换
*/
@GET("games/qq_mini/columns/{column_id}/games?page=1&page_size=30")
Observable<List<GameEntity>> getSubjectQGame(@Path("column_id") String columnId);
/**
* QQ小游戏-专题游戏列表
*/
@GET("games/qq_mini/columns/{column_id}/games")
Single<List<GameEntity>> getQGameColumn(@Path("column_id") String columnId,
@Query("filter") String order,
@Query("page") int page,
@Query("page_size") int pageSize);
/**
* QQ小游戏-搜索
*/
@GET
Observable<List<GameEntity>> getSearchQGame(@Url String url);
/**
* QQ小游戏-游玩记录
*/
@POST("/games/qq_mini/{appid}/user/{user_id}")
Single<ResponseBody> postQGamePlay(@Path("appid") String qqGameId, @Path("user_id") String userId);
/**
* QQ小游戏-专题数据详情
*/
@GET("/games/qq_mini/columns/{column_id}/setting")
Observable<SubjectSettingEntity> getQGameColumnSettings(@Path("column_id") String column_id);
/**
* 获取广告配置
*/

View File

@ -0,0 +1,11 @@
package com.gh.gamecenter.search
import com.gh.gamecenter.entity.SearchSubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import io.reactivex.Observable
interface ISearchGameResultRepository {
fun getSearchGame(key: String?, page: Int): Observable<List<GameEntity>>
fun getSearchSubject(key: String?, page: Int): Observable<List<SearchSubjectEntity>>
}

View File

@ -67,7 +67,8 @@ class SearchGameIndexFragment : ListFragment<GameEntity, SearchGameResultViewMod
SearchGameResultViewModel.Factory(
HaloApp.getInstance(),
mKey,
false
false,
SearchGameResultRepository()
)
)

View File

@ -49,6 +49,7 @@ import kotlin.collections.MutableList
import kotlin.collections.forEach
import kotlin.collections.isNotEmpty
import kotlin.collections.set
import com.gh.gamecenter.common.base.GlobalActivityManager
class SearchGameResultAdapter(
context: Context,
@ -451,15 +452,21 @@ class SearchGameResultAdapter(
EventBus.getDefault().post(EBSearch("click", gameEntity.id, gameEntity.name))
}
GameDetailActivity.startGameDetailActivity(
mContext, gameEntity,
StringUtils.buildString(
entrance, "+(搜索-列表[", key, "=", type, "=",
(holder.adapterPosition + 1).toString(), "])"
),
traceEvent = exposureEvent
)
if (gameEntity.isQQMiniGame()) {
GlobalActivityManager.currentActivity?.let {
DirectUtils.directToQGameById(it, gameEntity.qqMiniGameAppId)
}
} else {
GameDetailActivity.startGameDetailActivity(
mContext, gameEntity,
StringUtils.buildString(
entrance, "+(搜索-列表[", key, "=", type, "=",
(holder.adapterPosition + 1).toString(), "])"
),
traceEvent = exposureEvent
)
// mDao.add(gameEntity.nameWithoutSuffix)
}
LogUtils.uploadSearchClick(
"search_click",

View File

@ -40,7 +40,7 @@ import com.lightgame.utils.Util_System_Keyboard
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class SearchGameResultFragment : ListFragment<GameEntity, SearchGameResultViewModel>() {
open class SearchGameResultFragment : ListFragment<GameEntity, SearchGameResultViewModel>() {
private var mAdapter: SearchGameResultAdapter? = null
private var mExposureListener: ExposureListener? = null
@ -49,7 +49,7 @@ class SearchGameResultFragment : ListFragment<GameEntity, SearchGameResultViewMo
private var mIsFirstLoad = true
private val mBinding by lazy { FragmentSearchResultBinding.inflate(layoutInflater) }
private var mKey: String = ""
protected var mKey: String = ""
private var mType: String = ""
private var mLoadingAnimation = false
private var mScrollIng = false
@ -72,7 +72,14 @@ class SearchGameResultFragment : ListFragment<GameEntity, SearchGameResultViewMo
override fun getInflatedLayout() = mBinding.root
override fun provideListViewModel() =
viewModelProvider<SearchGameResultViewModel>(SearchGameResultViewModel.Factory(HaloApp.getInstance(), mKey, true))
viewModelProvider<SearchGameResultViewModel>(
SearchGameResultViewModel.Factory(
HaloApp.getInstance(),
mKey,
true,
SearchGameResultRepository()
)
)
override fun provideListAdapter(): SearchGameResultAdapter {
if (mAdapter == null) {
@ -264,7 +271,12 @@ class SearchGameResultFragment : ListFragment<GameEntity, SearchGameResultViewMo
R.id.container_menu_close -> {
if (mScrollIng) return
LogUtils.uploadSearchGame("open_floating_window", "搜索页", mKey, SearchType.fromString(mType).toChinese())
LogUtils.uploadSearchGame(
"open_floating_window",
"搜索页",
mKey,
SearchType.fromString(mType).toChinese()
)
showOpenMenuView()
}
@ -282,7 +294,12 @@ class SearchGameResultFragment : ListFragment<GameEntity, SearchGameResultViewMo
R.id.seek_game_btn -> {
Util_System_Keyboard.hideSoftKeyboard(requireActivity())
MtaHelper.onEvent("游戏搜索", "求游戏", mKey)
LogUtils.uploadSearchGame("ask_more_games", " 搜索页-悬浮按钮", mKey, SearchType.fromString(mType).toChinese())
LogUtils.uploadSearchGame(
"ask_more_games",
" 搜索页-悬浮按钮",
mKey,
SearchType.fromString(mType).toChinese()
)
HelpAndFeedbackBridge.startSuggestionActivity(context, SuggestType.gameCollect, "", "求游戏:$mKey")
showCloseMenuView()
}
@ -290,7 +307,12 @@ class SearchGameResultFragment : ListFragment<GameEntity, SearchGameResultViewMo
R.id.seek_function_btn -> {
Util_System_Keyboard.hideSoftKeyboard(requireActivity())
MtaHelper.onEvent("游戏搜索", "求功能", mKey)
LogUtils.uploadSearchGame("ask_more_func", " 搜索页-悬浮按钮", mKey, SearchType.fromString(mType).toChinese())
LogUtils.uploadSearchGame(
"ask_more_func",
" 搜索页-悬浮按钮",
mKey,
SearchType.fromString(mType).toChinese()
)
HelpAndFeedbackBridge.startSuggestionActivity(context, SuggestType.functionSuggest, "", "求功能:$mKey")
showCloseMenuView()
}

View File

@ -0,0 +1,34 @@
package com.gh.gamecenter.search
import com.gh.common.constant.Config
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.entity.SearchSubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.halo.assistant.HaloApp
import io.reactivex.Observable
import java.net.URLEncoder
class SearchGameResultRepository(
private val mApi: ApiService = RetrofitManager.getInstance().api
) : ISearchGameResultRepository {
override fun getSearchGame(
key: String?,
page: Int
): Observable<List<GameEntity>> {
// 可能会有特殊字符,需要 encode 处理
val encodedKey = URLEncoder.encode(key, "utf-8")
return mApi.getSearchGame(
Config.API_HOST + "games:search?keyword=" +
encodedKey + "&view=digest&page=" + page +
"&channel=" + HaloApp.getInstance().channel +
"&version=" + BuildConfig.VERSION_NAME
)
}
override fun getSearchSubject(key: String?, page: Int): Observable<List<SearchSubjectEntity>> {
return mApi.getSearchSubject(key, page)
}
}

View File

@ -11,23 +11,20 @@ import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.baselist.LoadParams
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.entity.SearchSubjectEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import com.gh.gamecenter.feature.entity.GameEntity
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.json.JSONException
import org.json.JSONObject
import java.net.URLEncoder
class SearchGameResultViewModel(
application: Application,
private var mSearchKey: String?,
private var mIsManuallySearch: Boolean) : ListViewModel<GameEntity, SearchItemData>(application) {
private var mIsManuallySearch: Boolean,
private val repository: ISearchGameResultRepository) : ListViewModel<GameEntity, SearchItemData>(application) {
private var mApi = RetrofitManager.getInstance().api
private var mPage = 0
private var mSearchSubjects = arrayListOf<SearchSubjectEntity>()
@ -61,7 +58,7 @@ class SearchGameResultViewModel(
@SuppressLint("CheckResult")
private fun decorateListAndPost(list: MutableList<GameEntity>) {
var itemDataList = ArrayList(list.map { SearchItemData(game = it) })
mApi.getSearchSubject(mSearchKey, mPage)
repository.getSearchSubject(mSearchKey, mPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ mutableList ->
@ -150,24 +147,18 @@ class SearchGameResultViewModel(
}
override fun provideDataObservable(page: Int): Observable<List<GameEntity>> {
// 可能会有特殊字符,需要 encode 处理
val encodedKey = URLEncoder.encode(mSearchKey, "utf-8")
mPage = page
return mApi.getSearchGame(
Config.API_HOST + "games:search?keyword=" +
encodedKey + "&view=digest&page=" + page +
"&channel=" + HaloApp.getInstance().channel +
"&version=" + BuildConfig.VERSION_NAME
)
return repository.getSearchGame(mSearchKey, page)
}
class Factory(
private val mApplication: Application,
private val mSearchKey: String?,
private val mIsManuallySearch: Boolean
private val mIsManuallySearch: Boolean,
private val repository: ISearchGameResultRepository
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SearchGameResultViewModel(mApplication, mSearchKey, mIsManuallySearch) as T
return SearchGameResultViewModel(mApplication, mSearchKey, mIsManuallySearch, repository) as T
}
}

View File

@ -0,0 +1,14 @@
package com.gh.gamecenter.subject
import com.gh.gamecenter.feature.entity.GameEntity
import io.reactivex.Single
interface ISubjectListRepository {
fun getColumn(
column_id: String?,
page: Int,
sort: String?,
order: String?,
): Single<MutableList<GameEntity>>
}

View File

@ -0,0 +1,12 @@
package com.gh.gamecenter.subject
import com.gh.gamecenter.entity.SubjectSettingEntity
import io.reactivex.Observable
import okhttp3.ResponseBody
interface ISubjectRepository {
fun getColumnSettings(column_id: String?): Observable<SubjectSettingEntity>
fun getSubjectName(column_id: String?): Observable<ResponseBody>
}

View File

@ -11,7 +11,7 @@ import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.entity.SubjectData
import com.gh.gamecenter.feature.exposure.ExposureSource
class SubjectActivity : DownloadToolbarActivity() {
open class SubjectActivity : DownloadToolbarActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -51,12 +51,14 @@ class SubjectActivity : DownloadToolbarActivity() {
name: String?,
isOrder: Boolean,
exposureSourceList: ArrayList<ExposureSource>? = null,
entrance: String?) {
entrance: String?,
isQQMiniGame: Boolean = false
) {
MtaHelper.onEvent("详情页面", "专题详情", name)
val bundle = Bundle()
bundle.putString(EntranceConsts.KEY_ENTRANCE, entrance)
val subjectData = SubjectData(subjectId = id, subjectName = name, isOrder = isOrder)
val subjectData = SubjectData(subjectId = id, subjectName = name, isOrder = isOrder, isQQMiniGame = isQQMiniGame)
bundle.putParcelable(EntranceConsts.KEY_SUBJECT_DATA, subjectData)
bundle.putString(EntranceConsts.KEY_ENTRANCE, entrance)
if (exposureSourceList != null) {

View File

@ -17,6 +17,7 @@ import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.adapter.viewholder.GameImageViewHolder
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.gamecenter.common.entity.LinkEntity
@ -213,21 +214,27 @@ class SubjectAdapter(
holder.itemView.setOnClickListener {
DataCollectionUtils.uploadClick(mContext, "列表", subjectData.subjectName, gameEntity.name)
GameDetailActivity.startGameDetailActivity(
mContext,
gameEntity,
StringUtils.buildString(
mEntrance,
"+(",
subjectData.subjectName,
":列表[", subjectData.filter,
"=",
if (subjectData.sort.contains("publish")) "最新"
else "最热",
"=", (humanReadablePosition).toString(), "])"
),
traceEvent = exposureEvent
)
if (gameEntity.isQQMiniGame()) {
GlobalActivityManager.currentActivity?.let {
DirectUtils.directToQGameById(it, gameEntity.qqMiniGameAppId)
}
} else {
GameDetailActivity.startGameDetailActivity(
mContext,
gameEntity,
StringUtils.buildString(
mEntrance,
"+(",
subjectData.subjectName,
":列表[", subjectData.filter,
"=",
if (subjectData.sort.contains("publish")) "最新"
else "最热",
"=", (humanReadablePosition).toString(), "])"
),
traceEvent = exposureEvent
)
}
}
DownloadItemUtils.setOnClickListener(

View File

@ -15,7 +15,7 @@ import com.gh.gamecenter.subject.tab.SubjectTabFragment
import com.gh.gamecenter.subject.tile.SubjectTileFragment
import com.halo.assistant.HaloApp
class SubjectFragment : LazyFragment() {
open class SubjectFragment : LazyFragment() {
private var mBinding: FragmentSubjectBinding? = null
private var mViewModel: SubjectViewModel? = null
@ -29,11 +29,8 @@ class SubjectFragment : LazyFragment() {
override fun onFragmentFirstVisible() {
initMenu(R.menu.menu_download)
val factory = SubjectViewModel.Factory(
HaloApp.getInstance().application,
arguments?.getParcelable(EntranceConsts.KEY_SUBJECT_DATA)
)
mViewModel = ViewModelProviders.of(this, factory).get(SubjectViewModel::class.java)
mViewModel = provideViewModel()
super.onFragmentFirstVisible()
@ -97,6 +94,14 @@ class SubjectFragment : LazyFragment() {
transaction.commitAllowingStateLoss()
}
protected open fun provideViewModel(): SubjectViewModel {
val factory = SubjectViewModel.Factory(
HaloApp.getInstance().application,
arguments?.getParcelable(EntranceConsts.KEY_SUBJECT_DATA),
)
return ViewModelProviders.of(this, factory).get(SubjectViewModel::class.java)
}
companion object {
const val LAST_PAGE_DATA = "last_page_data"
}

View File

@ -10,19 +10,19 @@ import com.gh.common.xapk.XapkInstaller
import com.gh.common.xapk.XapkUnzipStatus
import com.gh.download.DownloadManager
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.baselist.LazyListFragment
import com.gh.gamecenter.common.baselist.LoadType
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.common.view.SpacingItemDecoration
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.entity.SubjectData
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.GameEntity
import com.halo.assistant.HaloApp
import com.lightgame.OnTitleClickListener
import com.lightgame.download.DataWatcher

View File

@ -0,0 +1,15 @@
package com.gh.gamecenter.subject
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import io.reactivex.Single
class SubjectListRepository(
private val api: ApiService = RetrofitManager.getInstance().api
) : ISubjectListRepository {
override fun getColumn(column_id: String?, page: Int, sort: String?, order: String?): Single<MutableList<GameEntity>> {
return api.getColumn(column_id, sort, order, page)
}
}

View File

@ -14,6 +14,7 @@ import com.gh.gamecenter.entity.SubjectData
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.feature.entity.TagStyleEntity
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.qgame.QGameSubjectListRepository
import com.gh.gamecenter.retrofit.RetrofitManager
import io.reactivex.Observable
import io.reactivex.Single
@ -33,14 +34,16 @@ class SubjectListViewModel(
var lastPageDataMap: HashMap<String, String>? = null
private val repository = if (subjectData.isQQMiniGame) QGameSubjectListRepository() else SubjectListRepository()
override fun provideDataObservable(page: Int): Observable<List<GameEntity>>? = null
override fun provideDataSingle(page: Int): Single<MutableList<GameEntity>> {
return RetrofitManager.getInstance().api.getColumn(
return repository.getColumn(
subjectData.subjectId,
page,
subjectData.sort,
if (subjectData.filter.isEmpty()) "type:全部" else subjectData.filter,
page
)
}

View File

@ -0,0 +1,20 @@
package com.gh.gamecenter.subject
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import io.reactivex.Observable
import okhttp3.ResponseBody
class SubjectRepository(
private val api: ApiService = RetrofitManager.getInstance().api
) : ISubjectRepository {
override fun getColumnSettings(column_id: String?): Observable<SubjectSettingEntity> {
return api.getColumnSettings(column_id)
}
override fun getSubjectName(column_id: String?): Observable<ResponseBody> {
return api.getSubjectName(column_id)
}
}

View File

@ -7,21 +7,26 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.common.constant.Config
import com.gh.gamecenter.entity.SubjectData
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.common.retrofit.JSONObjectResponse
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.entity.SubjectData
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.qgame.QGameSubjectRepository
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.json.JSONObject
import retrofit2.HttpException
class SubjectViewModel(application: Application, var subjectData: SubjectData?) : AndroidViewModel(application) {
class SubjectViewModel(
application: Application,
var subjectData: SubjectData?
) : AndroidViewModel(application) {
val subjectNameLD = MutableLiveData<String>()
val subjectSettingLD = MutableLiveData<SubjectSettingEntity>()
private val repository = if (subjectData?.isQQMiniGame == true) QGameSubjectRepository() else SubjectRepository()
init {
initData()
}
@ -40,7 +45,7 @@ class SubjectViewModel(application: Application, var subjectData: SubjectData?)
}
private fun loadSubjectName() {
RetrofitManager.getInstance().api
repository
.getSubjectName(subjectData?.subjectId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -65,7 +70,7 @@ class SubjectViewModel(application: Application, var subjectData: SubjectData?)
}
private fun loadSubjectType() {
RetrofitManager.getInstance().api
repository
.getColumnSettings(subjectData?.subjectId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -90,7 +95,10 @@ class SubjectViewModel(application: Application, var subjectData: SubjectData?)
}
class Factory(private val mApplication: Application, private val subjectData: SubjectData?) :
class Factory(
private val mApplication: Application,
private val subjectData: SubjectData?
) :
ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SubjectViewModel(mApplication, subjectData) as T

View File

@ -55,6 +55,7 @@ import com.gh.gamecenter.common.utils.ImageUtils;
import com.gh.gamecenter.common.utils.PackageFlavorHelper;
import com.gh.gamecenter.core.AppExecutor;
import com.gh.gamecenter.core.iinterface.IApplication;
import com.gh.gamecenter.core.provider.IFlavorProvider;
import com.gh.gamecenter.core.utils.DisplayUtils;
import com.gh.gamecenter.core.utils.GsonUtils;
import com.gh.gamecenter.core.utils.SPUtils;
@ -63,7 +64,6 @@ import com.gh.gamecenter.fragment.MainWrapperRepository;
import com.gh.gamecenter.login.user.UserRepository;
import com.gh.gamecenter.oaid.OAIDHelper;
import com.gh.gamecenter.packagehelper.PackageRepository;
import com.gh.gamecenter.core.provider.IFlavorProvider;
import com.gh.gamecenter.provider.FlavorProviderImp;
import com.gh.gamecenter.receiver.ActivitySkipReceiver;
import com.gh.gamecenter.receiver.DownloadReceiver;

View File

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

View File

@ -185,7 +185,8 @@
android:layout_height="48dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
app:layout_constraintEnd_toEndOf="parent">
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/personal_msg"
@ -196,17 +197,20 @@
<TextView
android:id="@+id/login_message_hint"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_alignTop="@id/personal_msg"
android:layout_alignParentRight="true"
android:layout_marginRight="8dp"
android:background="@drawable/oval_hint_red_stroke_bg"
android:layout_width="wrap_content"
android:layout_height="14dp"
android:layout_alignTop="@+id/personal_msg"
android:layout_alignEnd="@+id/personal_msg"
android:layout_alignRight="@+id/personal_msg"
android:layout_marginTop="-4dp"
android:layout_marginEnd="-8dp"
android:background="@drawable/bg_red_stroke_radius_999"
android:gravity="center"
android:includeFontPadding="false"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:textColor="@color/white"
android:textSize="8sp"
android:textStyle="bold"
tools:text="12" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -81,19 +81,16 @@
<TextView
android:id="@+id/menu_download_count_hint"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_alignTop="@+id/menu_download_iv"
android:layout_marginEnd="4dp"
android:background="@drawable/oval_hint_red_stroke_bg"
android:layout_width="wrap_content"
android:layout_height="14dp"
android:background="@drawable/bg_red_stroke_radius_999"
android:gravity="center"
android:includeFontPadding="false"
android:textColor="@color/white"
android:textSize="8sp"
android:textStyle="bold"
android:visibility="visible"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/menu_download_iv" />
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@+id/menu_download_iv"
app:layout_constraintTop_toTopOf="@+id/menu_download_iv" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:focusableInTouchMode="true">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/game_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.gh.gamecenter.common.view.TouchSlopRecyclerView
android:id="@+id/game_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/background_white"
android:clipToPadding="false" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<include
android:id="@+id/reuse_no_connection"
layout="@layout/reuse_no_connection" />
<FrameLayout
android:id="@+id/game_skeleton_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<include
android:id="@+id/search_bar"
layout="@layout/layout_qgame_search_bar" />
<FrameLayout
android:id="@+id/wrapper_content"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/search_bar"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/home_actionbar"
android:layout_width="match_parent"
android:layout_height="@dimen/appbar_height"
@ -47,7 +48,7 @@
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="102dp"
android:layout_marginRight="100dp"
android:layout_marginTop="9dp"
android:layout_marginBottom="9dp"
android:layout_weight="1"
@ -89,13 +90,12 @@
android:src="@drawable/toolbar_search" />
<RelativeLayout
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:id="@+id/actionbar_rl_download"
android:layout_width="36dp"
android:layout_width="40dp"
android:layout_height="@dimen/appbar_height"
android:layout_marginRight="52dp">
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/actionbar_notification"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/actionbar_download"
@ -106,29 +106,36 @@
<TextView
android:id="@+id/action_tip"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_alignTop="@id/actionbar_download"
android:layout_alignParentRight="true"
android:layout_marginRight="2dp"
android:background="@drawable/oval_hint_red_stroke_bg"
android:layout_width="wrap_content"
android:layout_height="14dp"
android:layout_alignTop="@+id/actionbar_download"
android:layout_alignEnd="@+id/actionbar_download"
android:layout_alignRight="@+id/actionbar_download"
android:layout_marginTop="-4dp"
android:layout_marginEnd="-8dp"
android:background="@drawable/bg_red_stroke_radius_999"
android:gravity="center"
android:includeFontPadding="false"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:textColor="@color/white"
android:textSize="8sp"
android:textStyle="bold"
android:visibility="gone" />
android:visibility="gone"
tools:minWidth="14dp"
tools:text="99+"
tools:visibility="visible" />
</RelativeLayout>
<RelativeLayout
app:layout_constraintTop_toTopOf="parent"
android:id="@+id/actionbar_notification"
android:layout_width="40dp"
android:layout_height="@dimen/appbar_height"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:id="@+id/actionbar_notification"
android:layout_width="36dp"
android:layout_height="@dimen/appbar_height"
android:layout_marginRight="12dp">
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/actionbar_message"
@ -140,17 +147,23 @@
<TextView
android:id="@+id/message_unread_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/actionbar_message"
android:layout_alignParentRight="true"
android:layout_marginRight="2dp"
android:background="@drawable/oval_hint_red_stroke_bg"
android:layout_height="14dp"
android:layout_alignTop="@+id/actionbar_message"
android:layout_alignEnd="@+id/actionbar_message"
android:layout_alignRight="@+id/actionbar_message"
android:layout_marginTop="-4dp"
android:layout_marginEnd="-8dp"
android:background="@drawable/bg_red_stroke_radius_999"
android:gravity="center"
android:includeFontPadding="false"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:textColor="@color/white"
android:textSize="8dp"
android:textStyle="bold"
android:visibility="gone" />
android:textSize="8sp"
android:visibility="gone"
tools:minWidth="14dp"
tools:visibility="visible"
tools:text="9" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -21,6 +21,7 @@
app:srcCompat="@drawable/ic_vgame_recent_hint" />
<TextView
android:id="@+id/titleTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
@ -41,6 +42,7 @@
android:drawablePadding="4dp"
android:includeFontPadding="false"
android:text="更多"
android:visibility="gone"
android:textColor="@color/theme_font"
android:textSize="@dimen/secondary_size"
app:drawableEndCompat="@drawable/ic_home_head_arrow"

View File

@ -0,0 +1,68 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/background_white"
android:paddingLeft="16dp"
android:paddingTop="12dp"
android:paddingRight="16dp"
android:paddingBottom="12dp"
app:layout_scrollFlags="scroll">
<View
android:id="@+id/search_background"
android:layout_width="0dp"
android:layout_height="32dp"
android:background="@drawable/actionbar_search_bg"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/tv_search"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/et_search"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@null"
android:hint="请输入小游戏关键词"
android:imeOptions="actionSearch"
android:paddingStart="36dp"
android:paddingLeft="36dp"
android:paddingEnd="40dp"
android:paddingRight="40dp"
android:singleLine="true"
android:gravity="center_vertical"
android:textColor="@color/text_title"
android:textColorHint="@color/text_body"
android:textCursorDrawable="@drawable/cursor_color"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@id/search_background"
app:layout_constraintLeft_toLeftOf="@id/search_background"
app:layout_constraintRight_toRightOf="@id/search_background"
app:layout_constraintTop_toTopOf="@id/search_background" />
<ImageView
android:id="@+id/searchIv"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_centerVertical="true"
android:layout_marginStart="12dp"
android:layout_marginLeft="12dp"
app:srcCompat="@drawable/toolbar_search_icon"
app:layout_constraintBottom_toBottomOf="@id/search_background"
app:layout_constraintLeft_toLeftOf="@id/search_background"
app:layout_constraintTop_toTopOf="@id/search_background" />
<TextView
android:id="@+id/tv_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:text="搜索"
android:textColor="@color/theme_font"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@id/search_background"
app:layout_constraintLeft_toRightOf="@id/search_background"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/search_background" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -8,26 +8,23 @@
android:id="@+id/menu_download_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/toolbar_download"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/toolbar_download" />
<TextView
android:id="@+id/menu_download_count_hint"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_alignTop="@id/menu_download_iv"
android:layout_marginRight="8dp"
android:background="@drawable/oval_hint_red_stroke_bg"
android:layout_width="wrap_content"
android:layout_height="14dp"
android:background="@drawable/bg_red_stroke_radius_999"
android:gravity="center"
android:includeFontPadding="false"
android:textColor="@color/white"
android:textSize="8sp"
android:textStyle="bold"
android:visibility="visible"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/menu_download_iv" />
app:layout_constraintEnd_toEndOf="@+id/menu_download_iv"
app:layout_constraintTop_toTopOf="@+id/menu_download_iv" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -469,4 +469,5 @@
<string name="update_all_has_land_page_address_dialog_content">部分游戏下载资源由第三方提供,此类游戏无法自动更新,请手动点击游戏【更新】按钮前往第三方网址更新游戏</string>
<string name="update_all_has_land_page_address_dialog_confirm">我知道了</string>
<string name="miui_open_adb_hint">请查看关闭教程,先开启开发者模式</string>
<string name="qgame_title">QQ小游戏</string>
</resources>

View File

@ -137,4 +137,9 @@ ext {
documentfile = "1.0.1"
csjVersion = "5.4.1.6"
csjVersion = "5.4.1.6"
qGameVersion = "1.57.14"
qGameAdVersion = "4.520.1390"
}

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

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,72 @@
apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-parcelize'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
minSdk rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
buildConfigField "String", "API_HOST", "\"${API_HOST}\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
sourceSets {
main {
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
exclude 'manifest/**'
}
}
}
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.name)
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildTypes {
release {
minifyEnabled false
consumerProguardFiles 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
kapt "com.alibaba:arouter-compiler:$arouterVersion"
implementation(project(path: ":module_common")) {
exclude group: 'androidx.swiperefreshlayout'
}
implementation(project(':feature:realname-window'))
implementation "com.tencent.qqmini:minigame:${qGameVersion}"
implementation ("com.tencent.qqmini:minigame_external:${qGameVersion}") {
exclude group: 'com.squareup.okhttp3'
}
implementation "com.tencent.qqmini:minigame_open_sdk_all:${qGameVersion}"
// 优量汇广告 SDK https://developers.adnet.qq.com/doc/android/union/union_version
implementation "com.qq.e.union:union:${qGameAdVersion}"
implementation "com.tencent.qqmini:extra_union_ad:${qGameVersion}"
}

100
feature/qq_game/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,100 @@
# 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
# Keep class members annotated with @MiniKepp
# 保护代码中的注解不被混淆
-keepattributes *Annotation*
-keep,allowobfuscation @interface com.tencent.qqmini.sdk.annotation.MiniKeep
-keep @com.tencent.qqmini.sdk.annotation.MiniKeep class *
-keepclassmembers @com.tencent.qqmini.sdk.annotation.MiniKeep class ** {
public <methods>; <fields>;
}
-keepclassmembers class * {
@com.tencent.qqmini.sdk.annotation.MiniKeep *;
}
# Keep class members annotated with @JsEvent
-keep,allowobfuscation @interface com.tencent.qqmini.sdk.annotation.JsEvent
-keepclassmembers class * {
@com.tencent.qqmini.sdk.annotation.JsEvent *;
}
# Keep minigame triton
-keep interface com.tencent.mobileqq.triton.** { *; }
-keep class com.tencent.mobileqq.triton.** { *; }
-keep @interface com.tencent.mobileqq.triton.jni.TTNativeModule, com.tencent.mobileqq.triton.jni.TTNativeCall
-keep @com.tencent.mobileqq.triton.jni.TTNativeModule class ** {
@com.tencent.mobileqq.triton.jni.TTNativeCall <methods>;
@com.tencent.mobileqq.triton.jni.TTNativeCall <fields>;
}
-keep @interface io.github.landerlyoung.jenny.NativeClass, io.github.landerlyoung.jenny.NativeFieldProxy, io.github.landerlyoung.jenny.NativeMethodProxy
-keep @io.github.landerlyoung.jenny.NativeClass class ** {
@io.github.landerlyoung.jenny.NativeFieldProxy <fields>;
@io.github.landerlyoung.jenny.NativeMethodProxy <methods>;
}
# Keep minigame sdk
-keep class * extends com.tencent.qqmini.sdk.launcher.ui.MiniActivity
-keep class com.tencent.qqmini.sdk.core.generated.** { *; }
-keep class com.tencent.qqmini.sdk.launcher.** { *; }
-keep class com.tencent.qqmini.sdk.MiniSDK { *; }
-keep class com.tencent.qqmini.sdk.MiniSDK$* { *; }
-keep class com.tencent.qqmini.sdk.BuildConfig { *; }
-keep class com.tencent.qqmini.sdk.annotation.** {* ;}
-keep class com.tencent.qqmini.sdk.utils.MiniSDKConst$AdConst{*;}
-keep class com.tencent.qqmini.sdk.receiver.** {* ;}
-keepclassmembers class com.tencent.qqmini.sdk.** {
@android.webkit.JavascriptInterface <methods>;
}
-keep class com.tencent.qqmini.sdk.core.proxy.service.ChannelProxyDefault { *; }
# protocol: should keep field name because reflection
-keep class cooperation.** { *; }
-keep class com.tencent.mobileqq.pb.MessageMicro { *; }
-keepclassmembers class * extends com.tencent.mobileqq.pb.MessageMicro {
<fields>;
}
# extra_ad
-keep class com.tencent.qqmini.ad.** {* ;}
# ad
-keep class com.qq.e.** {* ;}
# extraad模块直接引用了协议先keep
-keep class NS_MINI_AD.** { *; }
-keep class NS_QWEB_PROTOCAL.** { *; }
-keep class com.tencent.mobileqq.pb.PBStringField {*;}
-keep class com.tencent.qqmini.sdk.request.ProtoBufRequest {
public *;
}
# 微信互联登录
-keep class com.tencent.mm.opensdk.** {
*;
}
-keep class com.tencent.wxop.** {
*;
}
-keep class com.tencent.mm.sdk.** {
*;
}

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.gh.gamecenter.qqgame">
<uses-permission android:name="android.permission.INTERNET" />
<application>
<activity android:name="com.gh.gamecenter.qgame.WXDelegateEntryActivity" />
<!-- QQ小游戏广告相关页面默认进程为光环客户端进程这里将进程修改为QQ小游戏的进程解决关闭广告页面未跳转回QQ小游戏的问题 -->
<activity
android:name="com.qq.e.ads.ADActivity"
android:taskAffinity=".mini1" />
<activity
android:name="com.qq.e.ads.PortraitADActivity"
android:taskAffinity=".mini1" />
<activity
android:name="com.qq.e.ads.LandscapeADActivity"
android:taskAffinity=".mini1" />
<!-- 用于激励视频可选广告的竖屏透明背景activity -->
<activity
android:name="com.qq.e.ads.RewardvideoPortraitADActivity"
android:taskAffinity=".mini1" />
<!-- 用于激励视频可选广告的横屏透明背景activity -->
<activity
android:name="com.qq.e.ads.RewardvideoLandscapeADActivity"
android:taskAffinity=".mini1" />
<activity
android:name="com.qq.e.ads.DialogActivity"
android:taskAffinity=".mini1" />
<!-- QQ小游戏广告相关页面默认进程为光环客户端进程这里将进程修改为QQ小游戏的进程解决关闭广告页面未跳转回QQ小游戏的问题 -->
</application>
</manifest>

View File

@ -0,0 +1,66 @@
package com.gh.gamecenter.qgame
import android.app.Application
import android.content.res.Configuration
import android.util.Log
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.iinterface.IApplication
import com.gh.gamecenter.core.provider.IPackageUtilsProvider
import com.gh.gamecenter.core.provider.IRealNameProvider
import com.google.auto.service.AutoService
@AutoService(IApplication::class)
class HaloApp : IApplication {
override fun attachBaseContext() {
}
override fun onCreate(application: Application) {
MiniGameWebViewUtils.handleWebViewDir(application)
doOnlyOnMiniProcess(application) {
val provider = ARouter
.getInstance()
.build(RouteConsts.provider.realName)
.navigation() as IRealNameProvider
provider.init(application)
}
}
override fun onLowMemory() {
}
override fun onTerminate() {
}
override fun onTrimMemory(level: Int) {
}
override fun onConfigurationChanged(newConfig: Configuration) {
}
private fun doOnlyOnMiniProcess(application: Application, callback: () -> Unit) {
val processName = getProcessName(application)
val processNameSplits = processName.split(":")
if (processNameSplits.size < 2) return
val processNameSuffix = processNameSplits[1]
if (processNameSuffix.startsWith("mini")) {
Log.d("HaloApp", "doOnlyOnMiniProcess -> $processName")
callback.invoke()
}
}
private fun getProcessName(application: Application): String {
val packageUtilsConfig =
ARouter.getInstance().build(RouteConsts.provider.packageUtils).navigation() as? IPackageUtilsProvider
return packageUtilsConfig?.obtainProcessName(application) ?: application.packageName
}
}

View File

@ -0,0 +1,51 @@
package com.gh.gamecenter.qgame;
import android.util.Log;
import androidx.annotation.Keep;
import com.tencent.qqmini.sdk.launcher.core.proxy.LogProxy;
@Keep
public class LogProxyImpl extends LogProxy {
@Override
public void log(int logLevel, String tag, String msg, Throwable t) {
switch (logLevel) {
case com.tencent.qqmini.sdk.launcher.log.Log.DEBUG:
if (t == null) {
Log.d(tag, msg);
} else {
Log.d(tag, msg, t);
}
break;
case com.tencent.qqmini.sdk.launcher.log.Log.INFO:
if (t == null) {
Log.i(tag, msg);
} else {
Log.i(tag, msg, t);
}
break;
case com.tencent.qqmini.sdk.launcher.log.Log.WARN:
if (t == null) {
Log.w(tag, msg);
} else {
Log.w(tag, msg, t);
}
break;
case com.tencent.qqmini.sdk.launcher.log.Log.ERROR:
if (t == null) {
Log.e(tag, msg);
} else {
Log.e(tag, msg, t);
}
break;
default:
if (t == null) {
Log.v(tag, msg);
} else {
Log.v(tag, msg, t);
}
break;
}
}
}

View File

@ -0,0 +1,30 @@
package com.gh.gamecenter.qgame
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.provider.IRealNameProvider
import com.tencent.qqmini.sdk.launcher.AppLoaderFactory
import com.tencent.qqmini.sdk.launcher.core.proxy.MiniCustomizedProxy
import com.tencent.qqmini.sdk.launcher.model.AppState
import com.tencent.qqmini.sdk.launcher.model.MiniAppInfo
class MiniCustomizedProxyImpl : MiniCustomizedProxy() {
override fun onAppStateChange(info: MiniAppInfo, appState: Int) {
val provider = ARouter
.getInstance()
.build(RouteConsts.provider.realName)
.navigation() as IRealNameProvider
when(appState) {
AppState.STATE_SHOW -> provider.setAppInfo(info.appId, info.name) {
QGameHelper.stopMiniApp(AppLoaderFactory.g().context, info, true)
}
AppState.STATE_HIDE -> {}
AppState.STATE_STOP -> {}
}
}
}

View File

@ -0,0 +1,72 @@
package com.gh.gamecenter.qgame;
import androidx.annotation.Keep;
import com.alibaba.android.arouter.launcher.ARouter;
import com.gh.gamecenter.common.constant.RouteConsts;
import com.gh.gamecenter.common.utils.PackageFlavorHelper;
import com.gh.gamecenter.core.provider.IAppProvider;
import com.tencent.qqmini.minigame.external.proxy.IMiniGameChannelInfoProxy;
import java.util.Map;
@Keep
public class MiniGameChannelInfoProxyImpl extends IMiniGameChannelInfoProxy {
/**
* 接入方在小游戏平台上注册的PlatformID。
*/
@Override
public String getPlatformId() {
return "2042";
}
/**
* 获取宿主APP的名称
*
* @return 应用名
*/
@Override
public String getAppName() {
return "光环助手";
}
/**
* 获取宿主APP的版本信息
*
* @return 版本号
*/
@Override
public String getAppVersion() {
IAppProvider appProvider = (IAppProvider) ARouter.getInstance().build(RouteConsts.provider.app).navigation();
return appProvider.getAppVersion();
}
/**
* 判断宿主APP是否为debug版本debug版log输出比较全但是对性能有影响建议上线前检查。
*
* @return true: debug版本; false: release版本
*/
@Override
public boolean isDebugVersion() {
return PackageFlavorHelper.IS_TEST_FLAVOR;
}
/**
* 腾讯广告的AppId如果以前申请过建议填写会针对当前应用出广告。
* 如果没有也不影响广告功能。
*/
@Override
public String getAmsAppId() {
return "1203549148";
}
/**
* 扩展字段,会透传给到接入方后台,接入方可以自定义。
* @return 接入方自定义内容
*/
@Override
public Map<String, String> getExtInfo() {
return null;
}
}

View File

@ -0,0 +1,24 @@
package com.gh.gamecenter.qgame;
import android.content.Context;
import androidx.annotation.Nullable;
import com.tencent.qqmini.minigame.opensdk.proxy.IWXRequestListener;
import com.tencent.qqmini.minigame.opensdk.proxy.MiniGameOpenSdkProxy;
import com.tencent.qqmini.minigame.opensdk.wx.WXHttpsRequest;
public class MiniGameOpenSdkProxyImpl implements MiniGameOpenSdkProxy {
private static final String TAG = "MiniGameOpenSdkProxyDefault";
private static final String wxAppId = "wx3ffd0785fad18396";
private static final String appSecret = "368b49e8471857575a033b206218f9fb";
public void wxAuth(Context context, String code, @Nullable IWXRequestListener listener) {
WXHttpsRequest.wxAuth(context, wxAppId, code, appSecret, listener);
}
public void wxRefreshToken(Context context, @Nullable IWXRequestListener listener) {
WXHttpsRequest.wxRefreshToken(context, wxAppId, listener);
}
}

View File

@ -0,0 +1,21 @@
package com.gh.gamecenter.qgame;
import android.content.Context;
import androidx.annotation.Nullable;
import com.tencent.qqmini.minigame.opensdk.proxy.MimiGameOpenSdkInfoProxyImpl;
import com.tencent.qqmini.sdk.launcher.core.proxy.AsyncResult;
import com.tencent.qqmini.sdk.launcher.model.MiniAppInfo;
public class MiniGameProxyImpl extends MimiGameOpenSdkInfoProxyImpl {
@Override
public boolean needCheckAntiAddictionToken() {
return false;
}
@Override
public boolean handleTokenInvalid(Context context, @Nullable MiniAppInfo miniAppInfo, int i, @Nullable AsyncResult asyncResult) {
return super.handleTokenInvalid(context, miniAppInfo, i, asyncResult);
}
}

View File

@ -0,0 +1,64 @@
package com.gh.gamecenter.qgame
import android.annotation.TargetApi
import android.app.Application
import android.content.Context
import android.os.Build
import android.webkit.WebView
import java.io.File
import java.io.RandomAccessFile
object MiniGameWebViewUtils {
fun handleWebViewDir(application: Application) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
return
}
// Android P需要手动设置Data文件夹所在的进程否则会触发以下异常
// java.lang.RuntimeException: Using WebView from more than one process at once with the same data directory is not supported.
try {
val processName = Application.getProcessName()
val suffix = if (application.packageName != processName) {//判断不等于默认进程名称
val process = processName.ifEmpty { application.packageName }
WebView.setDataDirectorySuffix(process)
"_$process"
} else ""
tryLockOrRecreateFile(application,suffix)
} catch (e: Exception) {
e.printStackTrace()
}
}
@TargetApi(Build.VERSION_CODES.P)
private fun tryLockOrRecreateFile(context: Context, suffix: String) {
val sb = context.dataDir.absolutePath +
"/app_webview"+suffix+"/webview_data.lock"
val file = File(sb)
if (file.exists()) {
try {
val tryLock = RandomAccessFile(file, "rw").channel.tryLock()
if (tryLock != null) {
tryLock.close()
} else {
createFile(file, file.delete());
}
} catch (e: Exception) {
e.printStackTrace()
val deleted = if (file.exists()) {
file.delete()
} else false
createFile(file, deleted)
}
}
}
private fun createFile(file: File, deleted: Boolean){
try {
if (deleted && !file.exists()) {
file.createNewFile()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}

View File

@ -0,0 +1,103 @@
package com.gh.gamecenter.qgame
import android.app.Activity
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.ResultReceiver
import com.gh.gamecenter.core.utils.ToastUtils
import com.google.gson.Gson
import com.tencent.qqmini.minigame.opensdk.OpenSdkLoginManager
import com.tencent.qqmini.minigame.opensdk.config.OpenSdkConfig
import com.tencent.qqmini.sdk.MiniSDK
import com.tencent.qqmini.sdk.launcher.model.AccountInfo
import com.tencent.qqmini.sdk.launcher.model.ExtParams
import com.tencent.qqmini.sdk.launcher.model.LaunchParam
import com.tencent.qqmini.sdk.launcher.model.MiniAppInfo
import com.tencent.tauth.Tencent
/**
* QQ 游戏管理类
*/
internal object QGameHelper {
fun init(context: Context, wxAppId: String, qqAppId: String) {
// 若 Android 系统版本低于 5.0 不用初始化
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return
}
MiniSDK.init(context)
OpenSdkLoginManager.init(
OpenSdkConfig.Builder()
.setUseOauth(false)
.setQqOpenAppId(qqAppId) // QQ互联平台AppId若不想接入QQ互联则不设置
.setWxOpenAppId(wxAppId) // 微信互联平台AppId若不想接入微信互联则不设置
.setIsDebugEnv(true)
.build()
)
Tencent.setIsPermissionGranted(true)
}
/**
* 授权完成后设置用户帐号信息
*/
fun setLoginInfo(context: Context, userId: String, userName: String, token: String) {
MiniSDK.setLoginInfo(context, AccountInfo(userId, userName, token.toByteArray()), null)
}
fun launchGame(
activity: Activity,
gameId: String,
resultCallback: (resultCode: Int, resultData: Bundle?) -> Unit
) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
ToastUtils.toast("该游戏仅支持安卓5.0及以上设备")
return
}
val extParam = ExtParams().apply {
customInfo = "" // 心跳透传字段
resultReceiver = object : ResultReceiver(Handler(Looper.getMainLooper())) {
override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
resultCallback.invoke(resultCode, resultData)
}
}
}
MiniSDK.startMiniAppById(activity, gameId, extParam)
}
fun launchGameByLink(
activity: Activity,
gameLink: String,
resultCallback: (resultCode: Int, resultData: Bundle?) -> Unit
) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
ToastUtils.toast("该游戏仅支持安卓5.0及以上设备")
return
}
val extParam = ExtParams().apply {
customInfo = "" // 心跳透传字段
resultReceiver = object : ResultReceiver(Handler(Looper.getMainLooper())) {
override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
resultCallback.invoke(resultCode, resultData)
}
}
}
MiniSDK.startMiniAppByLink(activity, gameLink, extParam)
}
fun stopAllMiniApp(context: Context, force: Boolean) {
MiniSDK.stopAllMiniApp(context, force)
}
fun stopMiniApp(context: Context, info: MiniAppInfo, force: Boolean) {
MiniSDK.stopMiniApp(context, info, force)
}
}

View File

@ -0,0 +1,78 @@
package com.gh.gamecenter.qgame
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.alibaba.android.arouter.facade.annotation.Route
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.provider.IQGameProvider
import com.tencent.mm.opensdk.modelmsg.SendAuth
import com.tencent.qqmini.minigame.opensdk.wx.BaseWXEntryActivity
import com.tencent.qqmini.sdk.launcher.AppLoaderFactory
import com.tencent.qqmini.sdk.launcher.model.ShareData
@Route(path = RouteConsts.provider.qGame, name = "QQ小游戏暴露服务")
class QGameProviderImpl : IQGameProvider<SendAuth.Resp> {
override fun init(context: Context, wxAppId: String, qqAppId: String) {
QGameHelper.init(context, wxAppId, qqAppId)
}
override fun init(context: Context?) {
// do nothing
}
override fun setLoginInfo(context: Context, userId: String, userName: String, token: String) {
QGameHelper.setLoginInfo(context, userId, userName, token)
}
override fun launchGame(
activity: Activity,
qqGameId: String,
resultCallback: (resultCode: Int, resultData: Bundle?) -> Unit
) {
QGameHelper.launchGame(activity, qqGameId, resultCallback)
}
override fun launchGameByLink(
activity: Activity,
qqGameLink: String,
resultCallback: (resultCode: Int, resultData: Bundle?) -> Unit
) {
QGameHelper.launchGameByLink(activity, qqGameLink, resultCallback)
}
override fun onWechatLogin(context: Context, resp: SendAuth.Resp) {
// 这里用 WXDelegateEntryActivity 类做了个中转,把数据传递到了底层的 Activity
val bundle = Bundle()
resp.toBundle(bundle)
val intent = Intent(context, WXDelegateEntryActivity::class.java)
intent.putExtras(bundle)
context.startActivity(intent)
}
/**
* 微信分享结果回调
* @param resultType 结果 0 代表失败, -1 代表取消, 1 代表成功
*/
override fun onWechatShare(context: Context, resultType: Int) {
val tempShareData = BaseWXEntryActivity.sShareData
BaseWXEntryActivity.sShareData = null
if (tempShareData != null) {
when (resultType) {
-1 -> tempShareData.notifyShareResult(context, ShareData.ShareResult.CANCEL)
0 -> tempShareData.notifyShareResult(context, ShareData.ShareResult.FAIL)
1 -> tempShareData.notifyShareResult(context, ShareData.ShareResult.SUCCESS)
}
}
}
override fun stopAllMiniApp(force: Boolean) {
QGameHelper.stopAllMiniApp(AppLoaderFactory.g().context, force)
}
}

View File

@ -0,0 +1,146 @@
package com.gh.gamecenter.qgame;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.Keep;
import com.tencent.connect.share.QzoneShare;
import com.tencent.qqmini.minigame.opensdk.share.OpenSdkShareHelper;
import com.tencent.qqmini.sdk.MiniSDK;
import com.tencent.qqmini.sdk.annotation.ProxyService;
import com.tencent.qqmini.sdk.launcher.core.proxy.ShareProxy;
import com.tencent.qqmini.sdk.launcher.model.ShareData;
import com.tencent.qqmini.sdk.launcher.ui.MoreItem;
import com.tencent.qqmini.sdk.launcher.ui.MoreItemList;
import com.tencent.qqmini.sdk.manager.LoginManager;
import com.tencent.qqmini.sdk.widget.MiniToast;
import java.util.ArrayList;
@Keep
@ProxyService(proxy = ShareProxy.class)
public class ShareProxyImpl extends ShareProxy {
private static final String TAG = "ShareProxyImpl";
// 扩展按钮的ID需要设置为[100, 200]这个区间中的值,否则,添加无效。
public static final int OTHER_MORE_ITEM_1 = 101;
public static final int OTHER_MORE_ITEM_2 = 102;
private final OpenSdkShareHelper mShareHelper = new OpenSdkShareHelper();
/**
* 获取默认的分享目标,即默认的宿主分享渠道。
*/
@Override
public int getDefaultShareTarget() {
int loginType = LoginManager.getInstance().getOpenSdkLoginInfo().getLoginType();
switch (loginType) {
case MiniSDK.LoginType.LOGIN_FROM_WX:
return ShareData.ShareTarget.WECHAT_FRIEND;
default:
return ShareData.ShareTarget.QQ;
}
}
/**
* 在share之前回调
* 可以用于决定分享的target是否可进行分享行为
*/
@Override
public boolean isShareTargetAvailable(Context context, int shareTarget) {
return mShareHelper.isShareTargetAvailable(context, shareTarget);
}
@Override
public ArrayList<MoreItem> createMoreItems(MoreItemList.Builder builder) {
// 自行调整顺序。不支持删除关于,举报。
builder
.addShareQQ("QQ", com.tencent.qqmini.sdk.R.drawable.mini_sdk_channel_qq)
.addShareQzone("QQ空间", com.tencent.qqmini.sdk.R.drawable.mini_sdk_channel_qzone)
.addShareWxFriends("微信好友", com.tencent.qqmini.sdk.R.drawable.mini_sdk_channel_wx_friend)
.addShareWxMoments("微信朋友圈", com.tencent.qqmini.sdk.R.drawable.mini_sdk_channel_wx_moment)
.addRestart("重启小程序", com.tencent.qqmini.sdk.R.drawable.mini_sdk_restart_miniapp)
.addSettings("设置", com.tencent.qqmini.sdk.R.drawable.mini_sdk_about);
return builder.build();
}
/**
* 分享
* @param activity
* @param shareData 分享数据
*/
@Override
public void share(Activity activity, ShareData shareData) {
switch (shareData.shareTarget) {
case ShareData.ShareTarget.QQ:
case ShareData.ShareTarget.SHARE_CHAT:
case ShareData.ShareTarget.QQ_DIRECTLY:
case ShareData.ShareTarget.FRIEND_LIST:
mShareHelper.shareToQQ(activity, shareData);
break;
case ShareData.ShareTarget.QZONE:
mShareHelper.shareToQZone(activity, shareData);
break;
case ShareData.ShareTarget.WECHAT_FRIEND:
mShareHelper.shareToWxSession(activity, shareData);
break;
case ShareData.ShareTarget.WECHAT_MOMENTS:
mShareHelper.shareToWxTimeline(activity, shareData);
break;
default:
break;
}
if (MoreItem.isValidExtendedItemId(shareData.shareTarget)) {
shareToOther(activity, shareData);
}
}
/**
* 启动第三方分享结果的返回
* 这里wx分享不会回调这里需要渠道自己在IWXAPIEventHandler#onResp根据rsp处理
*/
@Override
public void onShareActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "requestCode=" + requestCode + ",resultCode=" + resultCode);
mShareHelper.onShareActivityResult(requestCode, resultCode, data);
}
/**
* 调用第三方分享
*/
public void shareToOther(Activity activity, ShareData shareData) {
switch (shareData.shareItemId) {
case OTHER_MORE_ITEM_1:
if (shareData.shareInMiniProcess) {
activity.runOnUiThread(() -> MiniToast.makeText(activity, "模拟第三方实现", Toast.LENGTH_LONG).show());
shareToOtherItem1(activity, shareData);
}
break;
case OTHER_MORE_ITEM_2:
default:
MiniToast.makeText(activity, "待第三方实现", Toast.LENGTH_LONG).show();
break;
}
}
private void shareToOtherItem1(Activity activity, ShareData shareData) {
Bundle params = new Bundle();
params.putInt(QzoneShare.SHARE_TO_QZONE_KEY_TYPE, QzoneShare.SHARE_TO_QZONE_TYPE_IMAGE_TEXT );
params.putString(QzoneShare.SHARE_TO_QQ_TITLE, shareData.title);//必填
params.putString(QzoneShare.SHARE_TO_QQ_SUMMARY, shareData.summary);//选填
params.putString(QzoneShare.SHARE_TO_QQ_TARGET_URL, shareData.targetUrl);//必填
ArrayList<String> imageUrlList = new ArrayList<>();
imageUrlList.add(shareData.sharePicPath);
params.putStringArrayList(QzoneShare.SHARE_TO_QQ_IMAGE_URL, imageUrlList);
}
}

View File

@ -0,0 +1,25 @@
package com.gh.gamecenter.qgame
import android.os.Bundle
import androidx.annotation.Keep
import com.tencent.mm.opensdk.modelmsg.SendAuth
import com.tencent.qqmini.minigame.opensdk.wx.BaseWXEntryActivity
import com.tencent.qqmini.minigame.opensdk.wx.WXLoginHelper
@Keep
internal class WXDelegateEntryActivity : BaseWXEntryActivity() {
override fun onCreate(p0: Bundle?) {
// 将数据转移给 BaseWXEntryActivity 来规避需要继承 BaseWXEntryActivity 的问题
// TODO 第二次会无法登录,可能某些地方还需要处理
val resp = SendAuth.Resp()
resp.fromBundle(intent.extras)
WXLoginHelper.loginState = resp.state
onResp(resp)
super.onCreate(p0)
finish()
}
}

View File

@ -0,0 +1,27 @@
package com.tencent.qqmini.sdk.core.generated;
import androidx.annotation.Keep;
import com.gh.gamecenter.qgame.LogProxyImpl;
import com.gh.gamecenter.qgame.MiniCustomizedProxyImpl;
import com.gh.gamecenter.qgame.MiniGameChannelInfoProxyImpl;
import com.gh.gamecenter.qgame.MiniGameOpenSdkProxyImpl;
import com.gh.gamecenter.qgame.MiniGameProxyImpl;
import com.gh.gamecenter.qgame.ShareProxyImpl;
import java.util.HashMap;
import java.util.Map;
@Keep
public final class ExtProxyServiceScope {
public static final Map<Class, Class> PROXY_SERVICES = new HashMap<>();
static {
PROXY_SERVICES.put(com.tencent.qqmini.sdk.launcher.core.proxy.ShareProxy.class, ShareProxyImpl.class);
PROXY_SERVICES.put(com.tencent.qqmini.minigame.external.proxy.IMiniGameChannelInfoProxy.class, MiniGameChannelInfoProxyImpl.class);
PROXY_SERVICES.put(com.tencent.qqmini.sdk.launcher.core.proxy.LogProxy.class, LogProxyImpl.class);
PROXY_SERVICES.put(com.tencent.qqmini.sdk.launcher.core.proxy.MiniGameProxy.class, MiniGameProxyImpl.class);
PROXY_SERVICES.put(com.tencent.qqmini.minigame.opensdk.proxy.MiniGameOpenSdkProxy.class, MiniGameOpenSdkProxyImpl.class);
PROXY_SERVICES.put(com.tencent.qqmini.sdk.launcher.core.proxy.MiniCustomizedProxy.class, MiniCustomizedProxyImpl.class);
}
}

1
feature/realname-window/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,43 @@
apply plugin: "com.android.library"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "kotlin-kapt"
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
minSdk rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
buildFeatures {
viewBinding true
}
kotlinOptions {
jvmTarget = "1.8"
}
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.name)
}
}
}
dependencies {
kapt "com.alibaba:arouter-compiler:$arouterVersion"
implementation(project(path: ":module_common")) {
exclude group: 'androidx.swiperefreshlayout'
}
}

View File

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

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lg.realname">
</manifest>

View File

@ -0,0 +1,199 @@
package com.lg.realname
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.text.TextUtils
import android.view.*
import com.gh.gamecenter.common.eventbus.EBFloatWindow
import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.utils.setTextChangedListener
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.core.utils.DisplayUtils
import com.lg.realname.databinding.DialogInputRealNameBinding
import com.lg.realname.retrofit.RetrofitManager
import com.lg.realname.utils.NewFlatLogUtils
import com.lg.realname.utils.SensorsLogUtils
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.EventBus
import org.json.JSONObject
import retrofit2.HttpException
class InputRealNameDialog(context: Context) : Dialog(context) {
private val mBinding by lazy { DialogInputRealNameBinding.inflate(LayoutInflater.from(context)) }
private var mCallBack: ((realName: String, idCard: String, isMinor: Boolean) -> Unit)? = null
private var mQQGameId : String = ""
private var mQQGameName = "1.0"
private var mInputCount = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
setCancelable(false)
setCanceledOnTouchOutside(false)
setContentView(mBinding.root)
initUI()
SensorsLogUtils.logEvent("HaloFunVerificationDialogShow")
}
override fun onStart() {
super.onStart()
val width = ViewGroup.LayoutParams.MATCH_PARENT
val height = ViewGroup.LayoutParams.MATCH_PARENT
window?.setGravity(Gravity.CENTER)
window?.setType(WindowManager.LayoutParams.TYPE_APPLICATION_PANEL)
window?.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
window?.decorView?.systemUiVisibility =
View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
window?.setLayout(width, height)
}
//动态去修改提交认证按钮的高度去强制更新editText的内容 在某些(小米安卓12 MIUI13)机型下横屏输入的情况下 键盘是半屏的 输入的时候editText没有即时更新内容
private fun changeContinueTvHeight() {
if (DisplayUtils.getScreenWidth() > DisplayUtils.getScreenHeight()) {
mInputCount++
val param = mBinding.continueTv.layoutParams
if (mInputCount % 2 == 0) {
param.height = DisplayUtils.dip2px(40F)
} else {
param.height = DisplayUtils.dip2px(39.5F)
}
mBinding.continueTv.layoutParams = param
}
}
private fun initUI() {
mBinding.apply {
contentTv.text = context.getString(R.string.input_content)
realNameEt.setTextChangedListener { realName, _, _, _ ->
changeContinueTvHeight()
clearRealNameIv.visibility = if (realName.isEmpty()) View.GONE else View.VISIBLE
updateSubmitStatus()
}
idCardEt.setTextChangedListener { idCard, _, _, _ ->
changeContinueTvHeight()
clearIdCardIv.visibility = if (idCard.isEmpty()) View.GONE else View.VISIBLE
updateSubmitStatus()
}
clearRealNameIv.setOnClickListener {
realNameEt.setText("")
}
clearIdCardIv.setOnClickListener {
idCardEt.setText("")
}
continueTv.setOnClickListener {
SensorsLogUtils.logEvent("HaloFunVerificationDialogClick")
checkInput()
}
realNameEt.setOnFocusChangeListener { _, isFoucs ->
if (isFoucs) {
postDismissFloatWindow()
}
}
idCardEt.setOnFocusChangeListener { _, isFoucs ->
if (isFoucs) {
postDismissFloatWindow()
}
}
this.root.setOnClickListener {
postDismissFloatWindow()
}
}
updateSubmitStatus()
}
private fun postDismissFloatWindow(){
EventBus.getDefault().post(EBFloatWindow())
}
private fun checkInput() {
val realName = mBinding.realNameEt.text.toString()
val idCard = mBinding.idCardEt.text.toString()
if (!TextUtils.isEmpty(realName) && !TextUtils.isEmpty(idCard)) {
NewFlatLogUtils.logCwCertificationDialogClick(mQQGameId, mQQGameName)
postCerfication(realName, idCard)
}
}
//实名认证
@SuppressLint("CheckResult")
private fun postCerfication(realName: String, idCard: String) {
val json = json {
"id_card" to json {
"id" to idCard
"name" to realName
}
}
RetrofitManager.getInstance().api.postCertification(json.toRequestBody())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ success ->
val result = JSONObject(success.string())
dismiss()
val isMinor = result.getBoolean("minor")
mCallBack?.invoke(realName, idCard, isMinor)
SensorsLogUtils.logEvent(
"HaloFunVerificationResult",
"attestation_result",
if (isMinor) "未成年人" else "成年人"
)
}, { error ->
NewFlatLogUtils.logCwCertificationResult(mQQGameId, "认证失败")
val errorBody = (error as HttpException).response()?.errorBody()
val errorResult = JSONObject(errorBody?.string())
TipBottomDialog.show(context, false, errorResult.optString("toast", "认证失败"))
SensorsLogUtils.logEvent(
"HaloFunVerificationResult",
"defeated_reason",
errorResult.optString("toast", "1"),
"attestation_result",
"认证失败"
)
})
}
private fun updateSubmitStatus() {
if (!TextUtils.isEmpty(mBinding.realNameEt.text) && !TextUtils.isEmpty(mBinding.idCardEt.text) && mBinding.idCardEt.text.length == 18) {
mBinding.continueTv.isEnabled = true
mBinding.continueTv.alpha = 1f
} else {
mBinding.continueTv.isEnabled = false
mBinding.continueTv.alpha = 0.3f
}
}
companion object {
@JvmStatic
fun show(
context: Context,
qqGameId:String,
qqGameName:String,
callBack: ((realName: String, idCard: String, isMinor: Boolean) -> Unit)?
): Dialog {
val dialog = InputRealNameDialog(context)
dialog.mQQGameId = qqGameId
dialog.mQQGameName = qqGameName
dialog.mCallBack = callBack
dialog.show()
return dialog
}
}
}

View File

@ -0,0 +1,111 @@
package com.lg.realname
import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.TextPaint
import android.text.style.ClickableSpan
import android.view.*
import androidx.core.content.ContextCompat
import com.lg.realname.databinding.DialogRealNameBinding
class RealNameDialog(context: Context) : Dialog(context) {
private val mBinding by lazy { DialogRealNameBinding.inflate(LayoutInflater.from(context)) }
private var isMplay = false
private var mCallBack: ((isPlay: Boolean) -> Unit)? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
setContentView(mBinding.root)
setCancelable(false)
setCanceledOnTouchOutside(false)
initUI()
}
override fun onStart() {
super.onStart()
val width = ViewGroup.LayoutParams.MATCH_PARENT
val height = ViewGroup.LayoutParams.MATCH_PARENT
window?.setGravity(Gravity.CENTER)
window?.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
window?.setLayout(width, height)
}
private fun initUI() {
val spannString = if (isMplay) SpannableStringBuilder(
context.getString(R.string.real_name_content_play)
) else SpannableStringBuilder(
context.getString(R.string.real_name_content)
)
val start = if (!isMplay) 12 else 13
spannString.setSpan(object : ClickableSpan() {
override fun updateDrawState(tp: TextPaint) {
super.updateDrawState(tp)
tp.color =
ContextCompat.getColor(context, R.color.color_1383EB)
tp.isUnderlineText = false
}
override fun onClick(tv: View) {
}
}, 4, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
spannString.setSpan(object : ClickableSpan() {
override fun updateDrawState(tp: TextPaint) {
super.updateDrawState(tp)
tp.color =
ContextCompat.getColor(context, R.color.color_1383EB)
tp.isUnderlineText = false
}
override fun onClick(tv: View) {
}
}, spannString.length - start, spannString.length - 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
mBinding.apply {
contentTv.text = spannString
if (isMplay) {
backGameTv.text = "进入游戏"
backGameTv.setTextColor(Color.WHITE)
backGameTv.setBackgroundResource(R.drawable.bg_blue_radius_20)
backGameTv.setOnClickListener {
dismiss()
mCallBack?.invoke(true)
}
} else {
backGameTv.setOnClickListener {
mCallBack?.invoke(false)
}
}
}
}
companion object {
@JvmStatic
fun show(
context: Context,
isPlay: Boolean,
callBack: ((isPlay: Boolean) -> Unit)?
): Dialog {
val dialog = RealNameDialog(context)
dialog.isMplay = isPlay
dialog.mCallBack = callBack
dialog.show()
return dialog
}
}
}

View File

@ -0,0 +1,302 @@
package com.lg.realname
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.app.Application.ActivityLifecycleCallbacks
import android.app.Dialog
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Base64
import android.view.WindowManager
import com.gh.gamecenter.common.utils.tryWithDefaultCatch
import com.gh.gamecenter.core.AppExecutor
import com.lg.realname.retrofit.RetrofitManager
import com.lg.realname.utils.NewFlatLogUtils
import com.lg.realname.utils.SensorsLogUtils
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.json.JSONObject
import java.lang.ref.WeakReference
import java.util.*
// Application 的 context 不用管泄漏警告
@SuppressLint("StaticFieldLeak")
object RealNameHelper : ActivityLifecycleCallbacks {
private var timer = Timer()
private var timerTask: TimerTask? = null
private var mIsValid = false
private var mIsAdult = false//未成年人为false 成年人true
private var mExitClosure: (() -> Unit)? = null
private var mIsSuccessDialogShowed = false
private var mApplication: Application? = null
private var mIsDialogShown = false
private var mCurrentActivityRef = WeakReference<Activity>(null)
private var mIsCertificated = false
private var mLastIsHealthyTime = false // 上一次是否是健康时间段
private var currentRealNameDialog: Dialog? = null
private var currentInputRealNameDialog: Dialog? = null
private var mIsAgoShowRealNameDialog = false // 是否已经弹起过RealNameDialog
private var mStartAppShowHeath: Boolean = false//是否是在启动游戏的时候弹出健康时间弹窗
private var mSubmitCertification: Boolean = false//是否是在提交认证的时候触发健康时间弹窗
private var mQQGameId: String = ""
private var mQQGameName: String = ""
private var mIsAgoShowInputRealNameDialog = false // 是否已经弹起过实名认证弹窗InputRealNameDialog
@JvmStatic
fun init(application: Application) {
mApplication = application
application.registerActivityLifecycleCallbacks(this)
validateRealNameInfo(application)
}
fun setAppInfo(qqGameId: String, qqGameName: String, exitClosure: (() -> Unit)) {
if (mQQGameId != qqGameId || !mIsDialogShown) {
startGameShowDialog()
mIsDialogShown = true
}
mQQGameId = qqGameId
mQQGameName = qqGameName
mExitClosure = exitClosure
}
override fun onActivityCreated(p0: Activity, p1: Bundle?) {
mStartAppShowHeath = true
mLastIsHealthyTime = false
mIsAgoShowRealNameDialog = false
mIsAgoShowInputRealNameDialog = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
p0.window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
}
//获取是否在健康时间段内
@SuppressLint("CheckResult")
fun checkHealthyTime() {
RetrofitManager.getInstance().newApi.postHealthyTimeCheck()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ success ->
val result = JSONObject(success.string())
if (!result.getBoolean("is_healthy_time")) {//不在健康时间段
showSuccessRealNameDialog(mCurrentActivityRef.get(), false)
mLastIsHealthyTime = false
} else {
showSuccessRealNameDialog(mCurrentActivityRef.get(), true)
mLastIsHealthyTime = true
}
}, {
})
}
override fun onActivityStarted(p0: Activity) {
}
override fun onActivityResumed(p0: Activity) {
mCurrentActivityRef = WeakReference(p0)
}
private fun startGameShowDialog(){
AppExecutor.uiExecutor.executeWithDelay({
if (mIsValid) {
if (!mIsSuccessDialogShowed) {
val activity = mCurrentActivityRef.get()
if (activity != null) {
showSuccessDialog(activity, "光环快速实名认证成功,正在开始游戏~")
NewFlatLogUtils.logCwCertificationToastShow(mQQGameId)
}
}
} else {
val activity = mCurrentActivityRef.get()
if (activity != null) {
if (mIsCertificated) {//已经认证
showSuccessDialogOrScheduleHealthyCheck(activity)
} else {
showInputRealNameDialog(activity)
}
}
}
}, 1500)
}
private fun showInputRealNameDialog(activity: Activity) {
if (mIsAgoShowInputRealNameDialog){
return
}
tryWithDefaultCatch {
currentInputRealNameDialog?.dismiss()
}
currentInputRealNameDialog =
InputRealNameDialog.show(activity, mQQGameId, mQQGameName) { realName, idCard, isMinor ->
mIsAdult = !isMinor
mSubmitCertification = true
mStartAppShowHeath = false
mIsCertificated = true
NewFlatLogUtils.logCwCertificationResult(mQQGameId, if (isMinor) "未成年人" else "成年人")
syncCertification(realName, idCard)
showSuccessDialogOrScheduleHealthyCheck(activity)
}
NewFlatLogUtils.logCwCertificationDialogShow(mQQGameId, mQQGameName)
mIsAgoShowInputRealNameDialog = true
}
private fun showSuccessDialogOrScheduleHealthyCheck(activity: Activity) {
if (mIsAdult) {//如果是成年人
if (!mIsSuccessDialogShowed){
showSuccessDialog(activity, "光环实名认证成功,正在开始游戏~")
}
} else {//未成年人
timerTask?.cancel()//取消上一次的任务
timerTask = MyTimerTask()
//这里需要每一分钟请求一次当前时间是否是健康时间段
timer.schedule(timerTask, 0, 1 * 60 * 1000)
}
}
class MyTimerTask : TimerTask() {
override fun run() {
checkHealthyTime()
}
}
@SuppressLint("NewApi")
private fun syncCertification(realName: String, idCard: String) {
if (mApplication != null) {
val resolver = mApplication!!.contentResolver
val insertUri = Uri.parse("content://com.gh.gamecenter.provider/sync_certification")
val values = ContentValues()
values.put("real_name", Base64.encodeToString(realName.toByteArray(), Base64.DEFAULT))
values.put("id_card", Base64.encodeToString(idCard.toByteArray(), Base64.DEFAULT))
resolver.insert(insertUri, values)
}
}
override fun onActivityPaused(p0: Activity) {
mCurrentActivityRef.clear()
mStartAppShowHeath = false
}
override fun onActivityStopped(p0: Activity) {
}
override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {
}
override fun onActivityDestroyed(p0: Activity) {
currentRealNameDialog?.dismiss()
mIsSuccessDialogShowed = false
}
private fun validateRealNameInfo(context: Context) {
// 检查光环实名
val uri = Uri.parse("content://com.gh.gamecenter.provider/certification")
val cursor = context.contentResolver?.query(
uri,
null,
null,
null,
null
)
if (cursor == null) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("ghzhushou://invoke_only"))
if (context !is Activity) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
}
if (cursor != null && cursor.count > 0) {
cursor.moveToFirst()
do {
if (cursor.getInt(1) == 1) {
mIsCertificated = true
if (cursor.getInt(2) == 1) {
//如果是成年人 那肯定是认证了???
mIsValid = true
mIsAdult = true
}
}
} while (cursor.moveToNext())
cursor.close()
}
}
private fun showSuccessRealNameDialog(activity: Activity?, isPlay: Boolean) {
if (activity != null) {
if (isPlay == mLastIsHealthyTime && mIsAgoShowRealNameDialog) {
return
}
tryWithDefaultCatch {
currentRealNameDialog?.dismiss()
}
// 羊了个羊游戏在广告页面退出游戏会导致应用进程再次被唤起,这里做一个弹窗延时
if (!isPlay && activity.packageName.equals("com.hlys.hcrcq.ylgy.gzgw27")
&& activity.localClassName.equals("com.jygame.ui.SplashActivity")) {
return
}
currentRealNameDialog = RealNameDialog.show(activity, isPlay) { play ->
if (play) {
//进入游戏
uploadHealthTipsEvent(activity, isPlay, "进入游戏")
mSubmitCertification = false
mStartAppShowHeath = false
} else {
//退出游戏
uploadHealthTipsEvent(activity, isPlay, "退出游戏")
mExitClosure?.invoke()
}
}
uploadHealthTipsEvent(activity, isPlay, "")
mIsAgoShowRealNameDialog = true
}
}
private fun uploadHealthTipsEvent(activity: Activity, isPlay: Boolean, operation: String) {
var entrance = ""
val heath = if (isPlay) "健康时间" else "非健康时间"
if (mStartAppShowHeath) {
entrance = "启动游戏"
} else if (mSubmitCertification) {
entrance = "提交认证"
mStartAppShowHeath = false
} else {
entrance = "未在健康时间段"
mSubmitCertification = false
mStartAppShowHeath = false
}
NewFlatLogUtils.logCwHeathTipShow(mQQGameId, heath, entrance, operation)
SensorsLogUtils.logEvent(
"HaloFunHealthTipsShow",
"dialog_type", if (isPlay) "健康时间内" else "非健康时间",
"result", operation,
"source_entrance", entrance
)
}
private fun showSuccessDialog(activity: Activity, message: String) {
TipBottomDialog.show(activity, true, message)
mIsSuccessDialogShowed = true
}
}

View File

@ -0,0 +1,66 @@
package com.lg.realname
import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.*
import com.gh.gamecenter.common.utils.tryWithDefaultCatch
import com.gh.gamecenter.core.utils.DisplayUtils
import com.lg.realname.databinding.DialogTipBinding
class TipBottomDialog(context: Context) : Dialog(context) {
private val mBinding by lazy { DialogTipBinding.inflate(LayoutInflater.from(context)) }
private var mShouldShowIcon = false
private var mContentMessage: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
window?.setGravity(Gravity.BOTTOM)
setCanceledOnTouchOutside(false)
setContentView(mBinding.root)
initUI()
mBinding.root.postDelayed({
tryWithDefaultCatch {
dismiss()
}
}, 3000)
}
override fun onStart() {
super.onStart()
val width = ViewGroup.LayoutParams.WRAP_CONTENT
val height = ViewGroup.LayoutParams.WRAP_CONTENT
window?.setLayout(width, height)
}
private fun initUI() {
mBinding.tipTv.text = mContentMessage
if (!mShouldShowIcon) {
mBinding.tipIconIv.visibility = View.GONE
}
}
companion object {
@JvmStatic
fun show(
context: Context,
showIcon: Boolean = true,
contentMessage: String
) {
val dialog = TipBottomDialog(context)
dialog.mShouldShowIcon = showIcon
dialog.mContentMessage = contentMessage
var params = dialog.window?.attributes
params?.y = DisplayUtils.dip2px(40F)
dialog.show()
}
}
}

View File

@ -0,0 +1,22 @@
package com.lg.realname.provider
import android.app.Application
import android.content.Context
import com.alibaba.android.arouter.facade.annotation.Route
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.provider.IRealNameProvider
import com.lg.realname.RealNameHelper
@Route(path = RouteConsts.provider.realName, name = "实名认证暴露服务")
class RealNameProviderImpl : IRealNameProvider {
override fun init(application: Application) {
RealNameHelper.init(application)
}
override fun setAppInfo(qqGameId: String, qqGameName: String, exitClosure: (() -> Unit)) {
RealNameHelper.setAppInfo(qqGameId, qqGameName, exitClosure)
}
override fun init(context: Context?) {}
}

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