Merge branch 'dev' into merge_dev_to_dev-5.33.0

# Conflicts:
#	app/src/main/java/com/gh/common/databind/BindingAdapters.java
#	app/src/main/java/com/gh/gamecenter/cloudarchive/CloudArchiveManagerActivity.kt
#	app/src/main/java/com/gh/gamecenter/cloudarchive/CloudArchiveManagerViewModel.kt
#	app/src/main/java/com/gh/gamecenter/gamedetail/GameDetailFragment.kt
#	app/src/main/java/com/gh/gamecenter/gamedetail/fuli/kaifu/ServersDetailReportViewHolder.kt
#	app/src/main/java/com/gh/gamecenter/gamedetail/fuli/kaifu/ServersDetailViewHolder.kt
#	app/src/main/java/com/gh/vspace/VHelper.kt
#	app/src/main/res/layout/dialog_servers_calendear_detail.xml
#	app/src/main/res/layout/dialog_servers_calendear_detail_item.xml
#	app/src/main/res/layout/dialog_servers_calendear_detail_report.xml
#	module_common/src/main/res/drawable/download_button_normal_style.xml
This commit is contained in:
叶子维
2023-11-01 14:38:45 +08:00
295 changed files with 9926 additions and 3027 deletions

View File

@ -22,6 +22,7 @@ import androidx.annotation.StringRes;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.ActionMenuView;
import androidx.appcompat.widget.Toolbar;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
@ -59,7 +60,7 @@ public abstract class ToolBarActivity extends BaseActivity implements ToolbarCon
protected TextView mAdLabelTv;
protected LinearLayout mTitleContainer;
protected ConstraintLayout mTitleContainer;
protected LinearLayout mIconTitleContainer;

View File

@ -74,10 +74,14 @@ public class Constants {
public static final String EXTRA_DOWNLOAD_TYPE = "extra_download_type";
public static final String SILENT_UPDATE = "静默更新";
public static final String SILENT_DOWNLOAD = "silent_download"; // 静默下载(不需要显示在下载管理里)
public static final String SIMULATOR_DOWNLOAD = "下载模拟器";
public static final String SIMULATOR_GAME = "simulator_game";
public static final String SIMULATOR = "simulator";
public static final String SMOOTH_GAME = "smooth_game"; // 畅玩类型的游戏
public static final String VGAME = "smooth_game"; // 畅玩类型的游戏
public static final String DUAL_DOWNLOAD_VGAME = "dual_download_vgame"; // 双下载模式,触发的时候是畅玩
public static final String DUAL_DOWNLOAD_LOCAL = "dual_download_local"; // 双下载模式,触发的时候是普通游戏
public static final String VSPACE_32_DOWNLOAD_ONLY = "vspace_32_download_only"; // 仅下载32位畅玩助手不跳转安装
public static final String GAME_NAME = "game_name";
public static final String GAME_CATEGORY = "game_category"; // 游戏类型
@ -489,4 +493,8 @@ public class Constants {
public static final String SP_SHOW_COMMUNITY_HOME_VIDEO_GUIDE = "show_community_home_video_guide";
public static final String SP_COMMUNITY_HOME_VIDEO_LOTTIE_LAST_PLAY_TIME = "community_home_video_lottie_last_play_time";
public static final String SP_SERVERS_CALENDAR_BY_APP = "servers_calendar_by_app";
public static final String SP_SERVERS_CALENDAR_BY_WECHAT = "servers_calendar_by_wechat";
}

View File

@ -228,6 +228,7 @@ public class EntranceConsts {
public static final String KEY_ARTICLE_OPEN_IN_NEW_PAGE = "openArticleInNewPage";
public static final String KEY_ONLY_CREATE_DRAFT = "onlyCreateDraft";
public static final String KEY_KAIFU_SELECT_TIME = "kaifuSelectTime";
public static final String KEY_KAIFU_TIME = "kaifuTime";
public static final String KEY_POSTER_PATH = "posterPath";
public static final String KEY_BLACK_THEME = "blackTheme";
public static final String KEY_FROM_LOGIN = "fromLogin";
@ -306,4 +307,10 @@ public class EntranceConsts {
public static final String KEY_SHOW_DOWNLOAD_MENU = "show_download_menu";
public static final String KEY_IS_FROM_MAIN_WRAPPER = "is_from_main_wrapper";
public static final String KEY_IS_FROM_HOME_TOOLBAR_WRAPPER = "is_from_home_toolbar_wrapper";
public static final String KEY_SHOW_REMIND = "show_remind";
public static final String KEY_CALENDAR_YEAR = "calendar_year";
public static final String KEY_CALENDAR_MONTH = "calendar_month";
public static final String KEY_CALENDAR_DAY = "calendar_day";
public static final String KEY_SERVER_CALENDAR_ID = "server_calendar_id";
}

View File

@ -90,6 +90,10 @@ class ExposureEntity(
var country: String = "",
var province: String = "",
var city: String = "",
@SerializedName("ad_space_id")
var adSpaceId: String = "", // 广告位ID
@SerializedName("game_ad_source_id")
var gameAdSourceId: String = "", // 广告源计划ID
) : Parcelable {
fun setContainerInfo(id: String?, type: String?) {

View File

@ -0,0 +1,54 @@
package com.gh.gamecenter.common.livedata
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.lang.reflect.Field
import java.lang.reflect.Method
/**
* “非粘性”状态的MutableLiveData支持用于事件传递可避免数据倒灌等情况发生
* 主要特点是observe方法被调用时不会回调onChanged事件
*/
class NonStickyMutableLiveData<T> : MutableLiveData<T> {
constructor(): super()
constructor(value: T): super(value)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
hook(observer)
super.observe(owner, observer)
}
/**
* 通过反射保持Observer的mLastVersion值与LiveData的值相同, 实现LiveData的“非粘性”状态
* @param observer observer实例
*/
private fun hook(observer: Observer<*>) {
val classLiveData = LiveData::class.java
val fieldObservers: Field = classLiveData.getDeclaredField("mObservers")
fieldObservers.isAccessible = true
val objectObservers: Any = fieldObservers.get(this) ?: return
val classObservers: Class<*> = objectObservers.javaClass
val methodGet: Method = classObservers.getDeclaredMethod("get", Any::class.java)
methodGet.isAccessible = true
val objectWrapperEntry: Any = methodGet.invoke(objectObservers, observer) ?: return
var objectWrapper: Any? = null
if (objectWrapperEntry is Map.Entry<*, *>) {
objectWrapper = objectWrapperEntry.value
}
if (objectWrapper == null) {
throw NullPointerException("Wrapper can not be bull!")
}
val classObserverWrapper: Class<*> = objectWrapper.javaClass.superclass ?: return
val fieldLastVersion: Field = classObserverWrapper.getDeclaredField("mLastVersion")
fieldLastVersion.isAccessible = true
//get livedata's version
val fieldVersion: Field = classLiveData.getDeclaredField("mVersion")
fieldVersion.isAccessible = true
val objectVersion: Any = fieldVersion.get(this) ?: return
//set wrapper's version
fieldLastVersion.set(objectWrapper, objectVersion)
}
}

View File

@ -1061,8 +1061,37 @@ fun DownloadEntity.isSimulatorGame(): Boolean {
return getMetaExtra(Constants.SIMULATOR_GAME).isNotEmpty()
}
fun DownloadEntity.isVGame(): Boolean {
return Constants.SMOOTH_GAME == getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE)
fun DownloadEntity.asVGame(): Boolean {
return Constants.VGAME == getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE)
|| Constants.DUAL_DOWNLOAD_VGAME == getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE)
}
/**
* 下载时触发的是双下载模式里的畅玩下载
*/
fun DownloadEntity.isVGameDownloadInDualDownloadMode(): Boolean {
return Constants.DUAL_DOWNLOAD_VGAME == getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE)
}
/**
* 下载时触发的是双下载模式里的普通下载
*/
fun DownloadEntity.isLocalDownloadInDualDownloadMode(): Boolean {
return Constants.DUAL_DOWNLOAD_LOCAL == getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE)
}
/**
* 设置触发时的下载模式为双下载模式里的普通下载
*/
fun DownloadEntity.setLocalDownloadModeInDualDownloadMode() {
addMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE, Constants.DUAL_DOWNLOAD_LOCAL)
}
/**
* 设置触发时的下载模式为双下载模式里的畅玩下载
*/
fun DownloadEntity.setVGameDownloadModeInDualDownloadMode() {
addMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE, Constants.DUAL_DOWNLOAD_VGAME)
}
/**
@ -1448,7 +1477,9 @@ fun ImageView.dimOnDarkMode() {
* 修复WebView初次加载重置深色模式
*/
fun WebView.fixUiModeIfNeeded() {
val configuration = context.resources.configuration
val topContext = if (context is Activity) context else CurrentActivityHolder.getCurrentActivity() ?: return
val configuration = topContext.resources.configuration
val configurationNightMode = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
val appCompatNightMode = AppCompatDelegate.getDefaultNightMode()
@ -1466,9 +1497,9 @@ fun WebView.fixUiModeIfNeeded() {
if (newUiModeConfiguration != null) {
@Suppress("DEPRECATION")
context.resources.updateConfiguration(
topContext.resources.updateConfiguration(
Configuration().apply { uiMode = newUiModeConfiguration },
context.resources.displayMetrics
topContext.resources.displayMetrics
)
}
}

View File

@ -55,6 +55,9 @@ object SensorsBridge {
private const val KEY_SEARCH_TYPE = "search_type"
private const val KEY_SEARCH_RESULT = "search_result"
private const val KEY_IS_NOT_PROMPT = "is_not_prompt"
private const val KEY_SESSION_MESSAGE_TYPE = "session_message_type"
private const val KEY_REMINDER_TYPE = "reminder_type"
private const val KEY_MESSAGE_TYPE = "message_type"
private const val EVENT_GAME_DETAIL_PAGE_TAB_SELECT = "GameDetailPageTabSelect"
private const val EVENT_GAME_DETAIL_PAGE_TAG_CLICK = "GameDetailPageGameTagClick"
@ -117,6 +120,15 @@ object SensorsBridge {
private const val EVENT_SEARCH_RESULT_RETURN = "SearchResultReturn"
private const val EVENT_SEARCH_RESULT_CLICK = "SearchResultClick"
private const val EVENT_ARTICLE_SEARCH_TAB_CLICK = "ArticleSearchTabClick"
private const val EVENT_MESSAGE_RECEIVE = "MessageReceive"
private const val EVENT_MESSAGE_CENTER_CLICK = "MessageCenterClick"
private const val EVENT_MESSAGE_SESSION_CLICK = "MessageSessionClick"
private const val EVENT_MESSAGE_ITEM_CLICK = "MessageItemClick"
private const val EVENT_MESSAGE_ITEM_LINK_CLICK = "MessageItemLinkClick"
private const val EVENT_LAUNCH_SERVER_SUBSCRIBE_CLICK = "LaunchServerSubscribeClick"
private const val EVENT_LAUNCH_SERVER_SUBSCRIBE_CANCEL_CLICK = "LaunchServerSubscribeCancelClick"
private const val EVENT_LAUNCH_SERVER_REMINDER_CLICK = "LaunchServerReminderClick"
private const val EVENT_LAUNCH_SERVER_REMINDER_CANCEL_CLICK ="LaunchServerReminderCancelClick"
private var mIsSensorsEnabled = false
@ -1949,4 +1961,202 @@ object SensorsBridge {
trackEvent(EVENT_INSTALL_GAME_FINISH, json)
}
/**
* 事件IDMessageReceive
* 事件名称:消息接收事件
* @param gameId 游戏ID
* @param gameName 游戏名称
* @param sessionMessageType 会话类型: 游戏消息、系统消息、客服消息、互动消息、运营消息
* @param messageType 消息类型
* @see EVENT_MESSAGE_RECEIVE
*/
@JvmStatic
fun trackMessageReceive(
gameId: String,
gameName: String,
sessionMessageType: String,
messageType: String
) {
val json = json {
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_SESSION_MESSAGE_TYPE to sessionMessageType
KEY_MESSAGE_TYPE to messageType
}
trackEvent(EVENT_MESSAGE_RECEIVE, json)
}
/**
* 事件IDMessageCenterClick
* 事件名称:消息中心点击事件
* @see EVENT_MESSAGE_CENTER_CLICK
*/
@JvmStatic
fun trackMessageCenterClick() {
val json = json {}
trackEvent(EVENT_MESSAGE_CENTER_CLICK, json)
}
/**
* 事件IDMessageSessionClick
* 事件名称:消息会话点击事件
* @param gameId 游戏ID
* @param gameName 游戏名称
* @param sessionMessageType 会话类型: 游戏消息、系统消息、客服消息、互动消息、运营消息
* @see EVENT_MESSAGE_SESSION_CLICK
*/
@JvmStatic
fun trackMessageSessionClick(
gameId: String,
gameName: String,
sessionMessageType: String
) {
val json = json {
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_SESSION_MESSAGE_TYPE to sessionMessageType
}
trackEvent(EVENT_MESSAGE_SESSION_CLICK, json)
}
/**
* 事件IDMessageItemClick
* 事件名称:消息点击事件
* @param gameId 游戏ID
* @param gameName 游戏名称
* @param sessionMessageType 会话类型: 游戏消息、系统消息、客服消息、互动消息、运营消息
* @param messageType 消息类型
* @see EVENT_MESSAGE_ITEM_CLICK
*/
@JvmStatic
fun trackMessageItemClick(
gameId: String,
gameName: String,
sessionMessageType: String,
messageType: String
) {
val json = json {
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_SESSION_MESSAGE_TYPE to sessionMessageType
KEY_MESSAGE_TYPE to messageType
}
trackEvent(EVENT_MESSAGE_ITEM_CLICK, json)
}
/**
* 事件IDMessageItemLinkClick
* 事件名称:消息点击事件
* @param gameId 游戏ID
* @param gameName 游戏名称
* @param sessionMessageType 会话类型: 游戏消息、系统消息、客服消息、互动消息、运营消息
* @param messageType 消息类型
* @see EVENT_MESSAGE_ITEM_LINK_CLICK
*/
@JvmStatic
fun trackMessageItemLinkClick(
gameId: String,
gameName: String,
sessionMessageType: String,
messageType: String
) {
val json = json {
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_SESSION_MESSAGE_TYPE to sessionMessageType
KEY_MESSAGE_TYPE to messageType
}
trackEvent(EVENT_MESSAGE_ITEM_LINK_CLICK, json)
}
/**
* 事件IDLaunchServerSubscribeClick
* 事件名称:加入开服订阅事件
* @param gameId 游戏ID
* @param gameName 游戏名称
* @see EVENT_LAUNCH_SERVER_SUBSCRIBE_CLICK
*/
@JvmStatic
fun trackLaunchServerSubscribeClick(
gameId: String,
gameName: String
) {
val json = json {
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
}
trackEvent(EVENT_LAUNCH_SERVER_SUBSCRIBE_CLICK, json)
}
/**
* 事件IDLaunchServerSubscribeCancelClick
* 事件名称:取消开服订阅事件
* @param gameId 游戏ID
* @param gameName 游戏名称
* @see EVENT_LAUNCH_SERVER_SUBSCRIBE_CANCEL_CLICK
*/
@JvmStatic
fun trackLaunchServerSubscribeCancelClick(
gameId: String,
gameName: String
) {
val json = json {
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
}
trackEvent(EVENT_LAUNCH_SERVER_SUBSCRIBE_CANCEL_CLICK, json)
}
/**
* 事件IDLaunchServerReminderClick
* 事件名称:添加开服提醒事件
* @param gameId 游戏ID
* @param gameName 游戏名称
* @param reminderType 提醒类型: 已知服、未知服
* @see EVENT_LAUNCH_SERVER_REMINDER_CLICK
*/
@JvmStatic
fun trackLaunchServerReminderClick(
gameId: String,
gameName: String,
reminderType: String
) {
val json = json {
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_REMINDER_TYPE to reminderType
}
trackEvent(EVENT_LAUNCH_SERVER_REMINDER_CLICK, json)
}
/**
* 事件IDLaunchServerReminderCancelClick
* 事件名称:游戏安装完成事件
* @param gameId 游戏ID
* @param gameName 游戏名称
* @see EVENT_LAUNCH_SERVER_REMINDER_CANCEL_CLICK
*/
@JvmStatic
fun trackLaunchServerReminderCancelClick(
gameId: String,
gameName: String,
reminderType: String
) {
val json = json {
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
KEY_REMINDER_TYPE to reminderType
}
trackEvent(EVENT_LAUNCH_SERVER_REMINDER_CANCEL_CLICK, json)
}
}

View File

@ -0,0 +1,136 @@
package com.gh.gamecenter.common.utils;
import android.content.Context;
import android.content.Intent;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData;
import com.tencent.mm.opensdk.modelbase.BaseReq;
import com.tencent.mm.opensdk.modelbase.BaseResp;
import com.tencent.mm.opensdk.openapi.IWXAPI;
import com.tencent.mm.opensdk.openapi.IWXAPIEventHandler;
import com.tencent.mm.opensdk.openapi.WXAPIFactory;
import com.tencent.mm.opensdk.utils.ILog;
import java.util.Collections;
import java.util.List;
/**
* 通用微信SDK代理工厂类
* 1. 构造通用微信SDK代理类实例
* 2. 处理WXEntryActivity的结果回调
*/
public final class WXAPIProxyFactory {
private WXAPIProxyFactory() {
}
private static final MutableLiveData<BaseResp> mRespLiveData = new MutableLiveData<>();
public static <T extends BaseReq, R extends BaseResp> WXAPIProxy<T, R> createWXAPI(Context context, String appid) {
final String transaction = String.valueOf(System.currentTimeMillis());
return createWXAPI(context, appid, new WXHandler<T, R>() {
@Override
public void handleOnReq(T req) {
req.transaction = transaction;
}
@Override
public R handleOnResp(BaseResp resp) {
if (transaction.equals(resp.transaction)) {
return (R) resp;
}
return null;
}
});
}
public static <T extends BaseReq, R extends BaseResp> WXAPIProxy<T, R> createWXAPI(Context context, String appid, WXHandler<T, R> handler) {
return new WXAPIProxy<>(context, appid, handler);
}
public static MutableLiveData<BaseResp> getLiveData() {
return mRespLiveData;
}
public final static class WXAPIProxy<T extends BaseReq, R extends BaseResp> {
private final IWXAPI mWxAPI;
private WXHandler<T, R> mHandler;
private final MediatorLiveData<R> mLiveData = new MediatorLiveData<R>() {{
addSource(mRespLiveData, input -> {
R resp = mHandler.handleOnResp(input);
if (resp != null) {
mLiveData.setValue(resp);
}
});
}};
private WXAPIProxy(Context context, String appid, WXHandler<T, R> handler) {
mHandler = handler;
mWxAPI = WXAPIFactory.createWXAPI(context, appid);
}
public boolean registerApp(String s) {
return mWxAPI.registerApp(s);
}
public boolean registerApp(String s, long l) {
return mWxAPI.registerApp(s, l);
}
public void unregisterApp() {
mWxAPI.unregisterApp();
}
public boolean handleIntent(Intent var1, IWXAPIEventHandler var2) {
return mWxAPI.handleIntent(var1, var2);
}
public boolean isWXAppInstalled() {
return mWxAPI.isWXAppInstalled();
}
public int getWXAppSupportAPI() {
return mWxAPI.getWXAppSupportAPI();
}
public boolean openWXApp() {
return mWxAPI.openWXApp();
}
public boolean sendReq(T req) {
mHandler.handleOnReq(req);
return mWxAPI.sendReq(req);
}
public boolean sendResp(R resp) {
return mWxAPI.sendResp(resp);
}
public void detach() {
mWxAPI.detach();
}
public void setLogImpl(ILog var1) {
mWxAPI.setLogImpl(var1);
}
public LiveData<R> getLiveData() {
return mLiveData;
}
}
public interface WXHandler<T extends BaseReq, R extends BaseResp> {
void handleOnReq(T req);
R handleOnResp(BaseResp resp);
}
}

View File

@ -1,82 +0,0 @@
package com.gh.gamecenter.common.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import androidx.annotation.Nullable;
import com.gh.gamecenter.common.R;
public class LongPressView extends View {
private int mLastMotionX, mLastMotionY;
// 是否移动了
private boolean isMoved;
// 是否释放了
private boolean isReleased;
// 计数器防止多次点击导致最后一次形成longpress的时间变短
private int mCounter;
// 长按的runnable
private Runnable mLongPressRunnable;
// 移动的阈值
private static final int TOUCH_SLOP = 60;
private int longPressTimeout = ViewConfiguration.getLongPressTimeout();
public LongPressView(Context context) {
super(context);
}
public LongPressView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public LongPressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LongPressViewStyle);
longPressTimeout = ta.getInteger(R.styleable.LongPressViewStyle_timeout, longPressTimeout);
ta.recycle();
}
mLongPressRunnable = () -> {
mCounter--;
if (mCounter > 0 || isReleased || isMoved)
return;
performLongClick();// 回调长按事件
};
}
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastMotionX = x;
mLastMotionY = y;
mCounter++;
isReleased = false;
isMoved = false;
postDelayed(mLongPressRunnable, longPressTimeout);
break;
case MotionEvent.ACTION_MOVE:
if (isMoved)
break;
if (Math.abs(mLastMotionX - x) > TOUCH_SLOP
|| Math.abs(mLastMotionY - y) > TOUCH_SLOP) {
isMoved = true;
}
break;
case MotionEvent.ACTION_UP:
isReleased = true;
break;
}
return true;
}
}