Compare commits
1 Commits
feat/insta
...
feat/cloud
| Author | SHA1 | Date | |
|---|---|---|---|
| 718ed2d209 |
@ -538,6 +538,10 @@ dependencies {
|
||||
if(!gradle.ext.excludeOptionalModules || gradle.ext.enableWechatPay){
|
||||
implementation(project(":feature:wechat_pay"))
|
||||
}
|
||||
|
||||
if(!gradle.ext.excludeOptionalModules || gradle.ext.enableCloudGame){
|
||||
implementation(project(":feature:cloud_game"))
|
||||
}
|
||||
}
|
||||
|
||||
File propFile = file('sign.properties')
|
||||
|
||||
@ -18,6 +18,7 @@ import com.gh.gamecenter.common.base.GlobalActivityManager
|
||||
import com.gh.gamecenter.common.provider.IHelpAndFeedbackProvider
|
||||
import com.gh.gamecenter.common.utils.PackageFlavorHelper
|
||||
import com.gh.gamecenter.core.provider.IPushProvider
|
||||
import com.gh.gamecenter.feature.cloudgame.CloudGameHelper
|
||||
import com.gh.gamecenter.login.utils.QuickLoginHelper
|
||||
import com.gh.gamecenter.login.view.LoginActivity
|
||||
import com.gh.gamecenter.va.VCore
|
||||
@ -106,6 +107,8 @@ class GlobalActivityLifecycleObserver : Application.ActivityLifecycleCallbacks {
|
||||
|
||||
if (PackageFlavorHelper.IS_TEST_FLAVOR && activity is AppCompatActivity) {
|
||||
DarkModeSwitchHelper.showDarkModeSwitchFloatingView(activity)
|
||||
|
||||
CloudGameHelper.showCloudGameFloat(activity)
|
||||
}
|
||||
|
||||
if (activity is AppCompatActivity
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
android:layout_height="112dp"
|
||||
android:background="@color/ui_surface"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
tools:showIn="@layout/activity_main">
|
||||
tools:showIn="@layout/activity_cloudgame">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
1
feature/cloud_game/.gitignore
vendored
Normal file
1
feature/cloud_game/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
||||
40
feature/cloud_game/build.gradle
Normal file
40
feature/cloud_game/build.gradle
Normal file
@ -0,0 +1,40 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(path: ":module_common")) {
|
||||
exclude group: 'androidx.swiperefreshlayout'
|
||||
}
|
||||
implementation(project(':module_core_feature')) {
|
||||
exclude group: 'androidx.swiperefreshlayout'
|
||||
}
|
||||
|
||||
implementation "com.tencent.tcr:tcrsdk-full:3.23.0"
|
||||
implementation "com.tencent.tcr:tcr-gamepad:2.2.4"
|
||||
implementation "com.lg:easyfloat:2.0.4-fix_proguard"
|
||||
}
|
||||
21
feature/cloud_game/proguard-rules.pro
vendored
Normal file
21
feature/cloud_game/proguard-rules.pro
vendored
Normal 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
|
||||
10
feature/cloud_game/src/main/AndroidManifest.xml
Normal file
10
feature/cloud_game/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.gh.gamecenter.cloudgame">
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name="com.gh.gamecenter.feature.cloudgame.CloudGameActivity"
|
||||
android:screenOrientation="landscape" />
|
||||
</application>
|
||||
</manifest>
|
||||
1
feature/cloud_game/src/main/assets/lol_5v5.cfg
Normal file
1
feature/cloud_game/src/main/assets/lol_5v5.cfg
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,500 @@
|
||||
package com.gh.gamecenter.feature.cloudgame
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.isVisible
|
||||
import com.gh.gamecenter.cloudgame.R
|
||||
import com.gh.gamecenter.cloudgame.databinding.ActivityCloudgameBinding
|
||||
import com.gh.gamecenter.core.AppExecutor
|
||||
import com.gh.gamecenter.core.AppExecutor.ioExecutor
|
||||
import com.gh.gamecenter.feature.cloudgame.OkHelper.obtainServerSession
|
||||
import com.tencent.tcr.sdk.api.AsyncCallback
|
||||
import com.tencent.tcr.sdk.api.CustomDataChannel
|
||||
import com.tencent.tcr.sdk.api.TcrSdk
|
||||
import com.tencent.tcr.sdk.api.TcrSession
|
||||
import com.tencent.tcr.sdk.api.TcrSessionConfig
|
||||
import com.tencent.tcr.sdk.api.data.CursorState
|
||||
import com.tencent.tcr.sdk.api.data.ScreenConfig
|
||||
import com.tencent.tcr.sdk.api.data.StatsInfo
|
||||
import com.tencent.tcr.sdk.api.data.VideoStreamConfig
|
||||
import com.tencent.tcr.sdk.api.view.MobileTouchListener
|
||||
import com.tencent.tcr.sdk.api.view.PcClickListener
|
||||
import com.tencent.tcr.sdk.api.view.PcTouchListener
|
||||
import com.tencent.tcr.sdk.api.view.TcrRenderView
|
||||
import com.tencent.tcr.sdk.api.view.TcrRenderView.TcrRenderViewType
|
||||
import com.tencent.tcr.sdk.api.view.TcrRenderView.VideoRotation
|
||||
import com.tencent.tcrgamepad.GamepadManager
|
||||
import com.tencent.tcrgamepad.GamepadManager.OnEditListener
|
||||
import com.tencent.tcrgui.keyboard.KeyboardView
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.text.DecimalFormat
|
||||
|
||||
/**
|
||||
* 该类演示了如何初始化TcrSdk,创建会话、请求远端会话、启动云应用,并将云应用画面展示到界面上的基础流程。<br></br>
|
||||
*
|
||||
* 要使用TcrSdk,你需要先调用[TcrSdk.init]接口初始化TcrSdk,<br></br>
|
||||
* 在[AsyncCallback.onSuccess] 回调以后才能做进一步操作,例如创建[TcrSession]以及[TcrRenderView]。
|
||||
*
|
||||
* 在启动会话[TcrSession.start]后才可以与云端实例进行交互。<br></br>
|
||||
* 具体的流程如下:
|
||||
*
|
||||
* <pre>
|
||||
* `┌──────────────┐ ┌────────────┐ ┌────────────┐ ┌───────────┐
|
||||
* │ MainActivity │ │ TcrSession │ │ App Server │ │ Cloud Api │
|
||||
* └──────┬───────┘ └──────┬─────┘ └──────┬─────┘ └─────┬─────┘
|
||||
* │ setObserver() │ │ │
|
||||
* ├────────────────►│ │ │
|
||||
* │ ├─────┐ │ │
|
||||
* │ │ │ │ │
|
||||
* │ Event.INITED │◄────┘ │ │
|
||||
* │◄────────────────┤ │ │
|
||||
* │ │ │ │
|
||||
* │ startGame(clientSession) │ │
|
||||
* ├─────────────────┬─────────────►│ │
|
||||
* │ │ │ tryLock │
|
||||
* │ │ ├─────────────►│
|
||||
* │ │ │ │
|
||||
* │ │ │createSession │
|
||||
* │ │ ├─────────────►│
|
||||
* │ │ │ │
|
||||
* │ onSuccess(serverSession) │ │
|
||||
* │◄────────────────┬──────────────┤ │
|
||||
* │ │ │ │
|
||||
* │ start() │ │ │
|
||||
* ├────────────────►│ │ │
|
||||
* │ │ │ │
|
||||
* SDK调用流程 后台交互流程
|
||||
` *
|
||||
</pre> *
|
||||
*
|
||||
* 1.调用[TcrSdk.init]初始化SDK,初始化成功后创建TcrSession,
|
||||
* 并通过[TcrSessionConfig.Builder.observer]将Observer设置好,通过Observer的回调拿到TcrSession对外的通知<br></br>
|
||||
* 2.通过Observer的回调得到TcrSession初始化成功[TcrSession.Event.STATE_INITED]的事件后,将事件传递的数据解析为clientSession<br></br>
|
||||
* 3.调用业务后台接口, 将`clientSession`传递给云端实例,并获取`serverSession`。<br></br>
|
||||
* 4.拿到`serverSession`之后,调用[TcrSession.start]启动会话。<br></br>
|
||||
* 5.当会话启动成功之后用户便可以和云端实例进行交互。<br></br>
|
||||
*
|
||||
* 详细的TcrSdk接口如何使用,请参考文档<br></br>
|
||||
* 业务后台的搭建,请参考链接<br></br>
|
||||
* @see [TcrSdK API](https://tencentyun.github.io/cloudgame-android-sdk/tcrsdk/index.html)
|
||||
*
|
||||
* @see [搭建业务后台](https://github.com/tencentyun/gs-server-demo)
|
||||
*/
|
||||
class CloudGameActivity : Activity() {
|
||||
private val mDf = DecimalFormat("#.##")
|
||||
|
||||
// 渲染视图
|
||||
private var mRenderView: TcrRenderView? = null
|
||||
|
||||
// 云渲染会话
|
||||
private var mTcrSession: TcrSession? = null
|
||||
|
||||
// 创建的数据通道
|
||||
private var mCustomDataChannel: CustomDataChannel? = null
|
||||
|
||||
// 云端横竖屏信息
|
||||
private var mScreenConfig: ScreenConfig? = null
|
||||
|
||||
// 视频流分辨率信息
|
||||
private var mVideoStreamConfig: VideoStreamConfig? = null
|
||||
|
||||
// 记录云端屏幕配置是否发生变化
|
||||
private var mScreenConfigChanged = false
|
||||
|
||||
// 记录视频分辨率是否发生变化
|
||||
private var mVideoStreamConfigChanged = false
|
||||
|
||||
// 游戏手柄管理器
|
||||
private var gamepadManager: GamepadManager? = null
|
||||
|
||||
// 键盘管理器
|
||||
private var keyboardView: KeyboardView? = null
|
||||
|
||||
private var isCursorEnabled = true
|
||||
|
||||
private val binding by lazy { ActivityCloudgameBinding.inflate(layoutInflater) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
initWindow()
|
||||
setContentView(binding.root)
|
||||
|
||||
initView()
|
||||
}
|
||||
|
||||
private fun initWindow() {
|
||||
// 不显示标题栏
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
// 全屏展示
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||
)
|
||||
// 屏幕常亮
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
|
||||
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||
)
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
binding.run {
|
||||
joystickBtn.setOnClickListener {
|
||||
if (joystickContainer.isVisible) {
|
||||
joystickContainer.visibility = View.GONE
|
||||
} else {
|
||||
if (mTcrSession == null) return@setOnClickListener
|
||||
|
||||
if (gamepadManager == null) {
|
||||
gamepadManager = GamepadManager(this@CloudGameActivity, mTcrSession)
|
||||
joystickContainer.addView(gamepadManager)
|
||||
|
||||
val customGamePadCfg =
|
||||
readConfigFile(this@CloudGameActivity, "lol_5v5.cfg")
|
||||
gamepadManager?.showGamepad(customGamePadCfg)
|
||||
gamepadManager?.setEditListener(OnEditListener { isChanged: Boolean, newCfg: String ->
|
||||
if (isChanged) {
|
||||
gamepadManager?.showGamepad(newCfg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
joystickContainer.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
joystickEditBtn.setOnClickListener {
|
||||
if (mTcrSession == null || gamepadManager == null) return@setOnClickListener
|
||||
|
||||
val customGamePadCfg =
|
||||
readConfigFile(this@CloudGameActivity, "lol_5v5.cfg")
|
||||
gamepadManager?.editGamepad(customGamePadCfg)
|
||||
}
|
||||
|
||||
keyboardBtn.setOnClickListener {
|
||||
if (keyboardContainer.isVisible) {
|
||||
keyboardContainer.visibility = View.GONE
|
||||
} else {
|
||||
if (mTcrSession == null) return@setOnClickListener
|
||||
|
||||
if (keyboardView == null) {
|
||||
keyboardView = KeyboardView(this@CloudGameActivity, mTcrSession)
|
||||
binding.keyboardContainer.addView(keyboardView)
|
||||
}
|
||||
|
||||
keyboardContainer.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
cursorBtn.setOnClickListener {
|
||||
if (mTcrSession == null) return@setOnClickListener
|
||||
|
||||
if (isCursorEnabled) {
|
||||
mRenderView?.setOnTouchListener(null)
|
||||
} else {
|
||||
setTouchHandler(
|
||||
mTcrSession!!,
|
||||
mRenderView!!,
|
||||
PC_GAME
|
||||
)
|
||||
}
|
||||
|
||||
isCursorEnabled = !isCursorEnabled
|
||||
}
|
||||
|
||||
connectBtn.setOnClickListener {
|
||||
// 初始化TcrSdk,初始化成功后创建TcrSession
|
||||
TcrSdk.getInstance().init(this@CloudGameActivity, null, object : AsyncCallback<Void?> {
|
||||
override fun onSuccess(result: Void?) {
|
||||
Log.i(TAG, "init SDK success")
|
||||
showToast("sdk 初始化成功", Toast.LENGTH_SHORT)
|
||||
// 为TcrSession创建配置参数对象。参考https://tencentyun.github.io/cloudgame-android-sdk/tcrsdk/com/tencent/tcr/sdk/api/config/TcrSessionConfig.Builder.html
|
||||
val tcrSessionConfig = TcrSessionConfig.builder()
|
||||
.observer(mSessionEventObserver)
|
||||
.idleThreshold(30000)
|
||||
.build()
|
||||
// 创建会话对象
|
||||
mTcrSession = TcrSdk.getInstance().createTcrSession(tcrSessionConfig)
|
||||
if (mTcrSession == null) {
|
||||
Log.e(TAG, "mTcrSession = null")
|
||||
showToast("创建TcrSession失败,请查看日志", Toast.LENGTH_SHORT)
|
||||
return
|
||||
}
|
||||
// 创建和初始化渲染视图
|
||||
runOnUiThread { initTcrRenderView() }
|
||||
}
|
||||
|
||||
override fun onFailure(code: Int, msg: String) {
|
||||
val errorMsg = "init SDK failed:$code msg:$msg"
|
||||
Log.e(TAG, errorMsg)
|
||||
showToast(errorMsg, Toast.LENGTH_LONG)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
disconnectBtn.setOnClickListener {
|
||||
AppExecutor.ioExecutor.execute {
|
||||
OkHelper.stopServerSession("rbmk")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化渲染视图
|
||||
*/
|
||||
private fun initTcrRenderView() {
|
||||
// 创建渲染视图
|
||||
mRenderView = TcrSdk.getInstance()
|
||||
.createTcrRenderView(this@CloudGameActivity, mTcrSession!!, TcrRenderViewType.SURFACE)
|
||||
if (mRenderView == null) {
|
||||
Log.e(TAG, "mRenderView = null")
|
||||
showToast("创建TcrRenderView失败,请查看日志", Toast.LENGTH_SHORT)
|
||||
return
|
||||
}
|
||||
// 将渲染视图添加到界面上
|
||||
(findViewById<View>(R.id.render_view_parent) as FrameLayout).addView(mRenderView)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过http请求业务后台并获取ServerSession,拿到ServerSession后启动会话<br></br>
|
||||
* 如果您需要启动云应用请调用CloudRenderBiz.getInstance().startProject()<br></br>
|
||||
* 如果您需要启动云游戏请调用CloudRenderBiz.getInstance().startGame()<br></br>
|
||||
* <br></br>
|
||||
* 无论启动的是云应用还是云游戏,都需要接入方准备相应的业务后台环境。<br></br>
|
||||
* 云渲染团队提供了一个测试环境,可通过[TcrTestEnv]工具进行调用,方便您做客户端SDK接入测试。<br></br>
|
||||
*/
|
||||
private fun requestServerSession(clientSession: String) {
|
||||
Log.i(TAG, "init session success:$clientSession")
|
||||
|
||||
ioExecutor.execute {
|
||||
val session = obtainServerSession(clientSession, "rbmk")
|
||||
if (!TextUtils.isEmpty(session)) {
|
||||
mTcrSession!!.start(session)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转屏幕方向, 以便本地的屏幕方向和云端保持一致<br></br>
|
||||
* 注意: 请确保Manifest中的Activity有android:configChanges="orientation|screenSize"配置, 避免Activity因旋转而被销毁.<br></br>
|
||||
*/
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
private fun updateOrientation() {
|
||||
Log.i(TAG, "updateOrientation:" + mScreenConfig!!.orientation)
|
||||
if (mScreenConfig!!.orientation == "portrait") {
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
} else if (mScreenConfig!!.orientation == "landscape") {
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据云端屏幕配置旋转视频画面
|
||||
*/
|
||||
private fun updateRotation() {
|
||||
if (!mScreenConfigChanged || !mVideoStreamConfigChanged) {
|
||||
Log.w(
|
||||
TAG, ("updateRotation failed,mScreenConfigChanged=" + mScreenConfigChanged
|
||||
+ " mVideoStreamConfigChanged=" + mScreenConfigChanged)
|
||||
)
|
||||
return
|
||||
}
|
||||
if (mVideoStreamConfig!!.width > mVideoStreamConfig!!.height) {
|
||||
if (mScreenConfig!!.orientation == "portrait") {
|
||||
mRenderView!!.setVideoRotation(VideoRotation.ROTATION_90)
|
||||
} else {
|
||||
mRenderView!!.setVideoRotation(VideoRotation.ROTATION_0)
|
||||
}
|
||||
} else {
|
||||
if (mScreenConfig!!.orientation == "landscape") {
|
||||
mRenderView!!.setVideoRotation(VideoRotation.ROTATION_270)
|
||||
} else {
|
||||
mRenderView!!.setVideoRotation(VideoRotation.ROTATION_0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为不同的云端实例设置处理器
|
||||
*
|
||||
*
|
||||
* 对于云端PC应用可以使用[PcTouchListener]
|
||||
* (云端PC应用在支持windows多点触摸时可使用[MobileTouchListener])
|
||||
*
|
||||
* 对于手游要使用[MobileTouchListener]
|
||||
*/
|
||||
private fun setTouchHandler(session: TcrSession, renderView: TcrRenderView, gameType: Int) {
|
||||
when (gameType) {
|
||||
MOBILE_GAME -> renderView.setOnTouchListener(MobileTouchListener(session))
|
||||
PC_GAME -> {
|
||||
val pcTouchListener = PcTouchListener(session)
|
||||
pcTouchListener.zoomHandler.setZoomRatio(1f, 5f)
|
||||
renderView.setOnTouchListener(pcTouchListener)
|
||||
pcTouchListener.setShortClickListener(PcClickListener(session))
|
||||
}
|
||||
|
||||
else -> Log.e(TAG, "UNKNOWN DeviceMode!!")
|
||||
}
|
||||
}
|
||||
|
||||
private fun showToast(msg: String, duration: Int) {
|
||||
runOnUiThread {
|
||||
Toast.makeText(this@CloudGameActivity, msg, duration)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 观察TcrSession通知出的各类事件,处理各类事件通知的消息和数据
|
||||
*
|
||||
* @see TcrSession.Event
|
||||
*/
|
||||
private val mSessionEventObserver = TcrSession.Observer { event, eventData ->
|
||||
when (event) {
|
||||
TcrSession.Event.STATE_INITED -> {
|
||||
// 回调数据中拿到client session并请求ServerSession
|
||||
val clientSession = eventData as String
|
||||
requestServerSession(clientSession)
|
||||
}
|
||||
|
||||
TcrSession.Event.STATE_CONNECTED -> {
|
||||
// 连接成功后设置操作模式
|
||||
// 与云端的交互需在此事件回调后开始调用接口
|
||||
runOnUiThread {
|
||||
setTouchHandler(
|
||||
mTcrSession!!,
|
||||
mRenderView!!, PC_GAME
|
||||
)
|
||||
}
|
||||
createCustomDataChannel()
|
||||
}
|
||||
|
||||
TcrSession.Event.STATE_RECONNECTING -> showToast("重连中...", Toast.LENGTH_LONG)
|
||||
TcrSession.Event.STATE_CLOSED -> {
|
||||
showToast("会话关闭", Toast.LENGTH_SHORT)
|
||||
finish()
|
||||
}
|
||||
|
||||
TcrSession.Event.SCREEN_CONFIG_CHANGE -> {
|
||||
mScreenConfig = eventData as ScreenConfig
|
||||
updateOrientation()
|
||||
mScreenConfigChanged = true
|
||||
updateRotation()
|
||||
}
|
||||
|
||||
TcrSession.Event.VIDEO_STREAM_CONFIG_CHANGED -> {
|
||||
mVideoStreamConfig = eventData as VideoStreamConfig
|
||||
mVideoStreamConfigChanged = true
|
||||
updateRotation()
|
||||
}
|
||||
|
||||
TcrSession.Event.CLIENT_STATS -> {
|
||||
val statsInfo = eventData as StatsInfo
|
||||
runOnUiThread {
|
||||
binding.statsValue.text =
|
||||
(" fps: " + statsInfo.fps + " bitrate: " + mDf.format(
|
||||
statsInfo.bitrate / 1024.0 / 1024.0
|
||||
)
|
||||
+ "Mb/s rtt: " + statsInfo.rtt + "ms")
|
||||
}
|
||||
}
|
||||
|
||||
TcrSession.Event.CURSOR_STATE_CHANGE -> {
|
||||
val cursorState = eventData as CursorState
|
||||
Log.i(
|
||||
TAG,
|
||||
"cursor showing state changed, $cursorState"
|
||||
)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建自定义数据通道
|
||||
*/
|
||||
private fun createCustomDataChannel() {
|
||||
if (mTcrSession == null) {
|
||||
return
|
||||
}
|
||||
// 10000为数据通道端口,请替换为你们自己业务的端口
|
||||
mCustomDataChannel =
|
||||
mTcrSession!!.createCustomDataChannel(10000, object : CustomDataChannel.Observer {
|
||||
override fun onConnected(port: Int) {
|
||||
val msg = "Your message"
|
||||
mCustomDataChannel!!.send(ByteBuffer.wrap(msg.toByteArray(StandardCharsets.UTF_8)))
|
||||
Log.i(
|
||||
TAG,
|
||||
"onConnected() send data to port $port: $msg"
|
||||
)
|
||||
}
|
||||
|
||||
override fun onError(port: Int, code: Int, msg: String) {
|
||||
Log.e(TAG, "onError() $port msg:$msg")
|
||||
}
|
||||
|
||||
override fun onMessage(port: Int, data: ByteBuffer) {
|
||||
Log.i(
|
||||
TAG,
|
||||
"onMessage() port=" + port + " data=" + StandardCharsets.UTF_8.decode(data)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (mTcrSession != null) return
|
||||
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (mTcrSession != null) {
|
||||
mTcrSession!!.release()
|
||||
}
|
||||
if (mRenderView != null) {
|
||||
mRenderView!!.release()
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun readConfigFile(context: Context, fileName: String): String? {
|
||||
try {
|
||||
val am = context.assets
|
||||
val `is` = am.open(fileName)
|
||||
val isr = InputStreamReader(`is`, StandardCharsets.UTF_8)
|
||||
val input = CharArray(`is`.available())
|
||||
isr.read(input)
|
||||
isr.close()
|
||||
`is`.close()
|
||||
return String(input)
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"readConfigFile failed:$e"
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "CloudGameActivity"
|
||||
|
||||
private const val MOBILE_GAME = 1
|
||||
private const val PC_GAME = 2
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
package com.gh.gamecenter.feature.cloudgame
|
||||
|
||||
import android.content.Intent
|
||||
import android.view.Gravity
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.gh.gamecenter.cloudgame.R
|
||||
import com.gh.gamecenter.common.utils.PackageFlavorHelper
|
||||
import com.gh.gamecenter.common.utils.dip2px
|
||||
import com.lzf.easyfloat.EasyFloat
|
||||
import com.lzf.easyfloat.enums.ShowPattern
|
||||
import com.lzf.easyfloat.enums.SidePattern
|
||||
|
||||
object CloudGameHelper {
|
||||
|
||||
fun showCloudGameFloat(activity: AppCompatActivity) {
|
||||
if (PackageFlavorHelper.IS_TEST_FLAVOR) {
|
||||
EasyFloat.with(activity)
|
||||
.setLayout(R.layout.layout_cloud_window)
|
||||
.setTag("cloud_game_float")
|
||||
.setAnimator(null)
|
||||
.setGravity(Gravity.TOP.xor(Gravity.END), 0, 250F.dip2px())
|
||||
.setSidePattern(SidePattern.RESULT_SIDE)
|
||||
.setDragEnable(true)
|
||||
.setShowPattern(ShowPattern.CURRENT_ACTIVITY)
|
||||
.registerCallback {
|
||||
createResult { _, _, view ->
|
||||
val tv = view?.findViewById<TextView>(R.id.iconTv)
|
||||
tv?.text = "☁\uFE0F\uD83C\uDFAE"
|
||||
view?.setOnClickListener {
|
||||
// Handle click event
|
||||
activity.startActivity(Intent(activity, CloudGameActivity::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package com.gh.gamecenter.feature.cloudgame
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import com.gh.gamecenter.core.iinterface.IApplication
|
||||
import com.google.auto.service.AutoService
|
||||
|
||||
@AutoService(IApplication::class)
|
||||
class HaloApp : IApplication {
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
override fun onCreate(application: Application) {
|
||||
mApp = application
|
||||
}
|
||||
|
||||
override fun onLowMemory() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
companion object {
|
||||
private lateinit var mApp: Application
|
||||
|
||||
@JvmStatic
|
||||
fun getInstance(): Application {
|
||||
return mApp
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
package com.gh.gamecenter.feature.cloudgame
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import com.gh.gamecenter.common.utils.EnvHelper
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import org.json.JSONObject
|
||||
|
||||
object OkHelper {
|
||||
|
||||
@WorkerThread
|
||||
fun obtainServerSession(clientSession: String, userId: String): String {
|
||||
val client = OkHttpClient()
|
||||
|
||||
// Define the JSON body as a string
|
||||
val json = """
|
||||
{
|
||||
"user_id": "${userId}",
|
||||
"client_session": "${clientSession}",
|
||||
"application_id": "app-attadogx"
|
||||
}
|
||||
"""
|
||||
|
||||
// Create a RequestBody with the JSON and the appropriate content type
|
||||
val mediaType = "application/json; charset=utf-8".toMediaType()
|
||||
val requestBody = json.toRequestBody(mediaType)
|
||||
|
||||
// Create the Request object
|
||||
val request = Request.Builder()
|
||||
.url(EnvHelper.getNewHost() + "tencent/car/start_app")
|
||||
.post(requestBody)
|
||||
.build()
|
||||
|
||||
try {
|
||||
val response = client.newCall(request).execute()
|
||||
if (response.isSuccessful) {
|
||||
val resultString = response.body?.string() ?: ""
|
||||
|
||||
if (resultString.isNotEmpty()) {
|
||||
return JSONObject(resultString).optString("server_session", "")
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
} else {
|
||||
"Request failed with code: ${response.code}" // Handle error case
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
"Error: ${e.message}" // Handle exceptions
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun stopServerSession(userId: String): String {
|
||||
val client = OkHttpClient()
|
||||
|
||||
// Define the JSON body as a string
|
||||
val json = """
|
||||
{
|
||||
"user_id": "${userId}"
|
||||
}
|
||||
"""
|
||||
|
||||
// Create a RequestBody with the JSON and the appropriate content type
|
||||
val mediaType = "application/json; charset=utf-8".toMediaType()
|
||||
val requestBody = json.toRequestBody(mediaType)
|
||||
|
||||
// Create the Request object
|
||||
val request = Request.Builder()
|
||||
.url(EnvHelper.getNewHost() + "tencent/car/stop_app")
|
||||
.post(requestBody)
|
||||
.build()
|
||||
|
||||
try {
|
||||
val response = client.newCall(request).execute()
|
||||
if (response.isSuccessful) {
|
||||
val resultString = response.body?.string() ?: ""
|
||||
|
||||
if (resultString.isNotEmpty()) {
|
||||
return JSONObject(resultString).optString("server_session", "")
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
} else {
|
||||
"Request failed with code: ${response.code}" // Handle error case
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
"Error: ${e.message}" // Handle exceptions
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- First Stroke Layer -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="4dp" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/black" /> <!-- Second stroke color -->
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<!-- Second Stroke Layer -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="4dp" />
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@color/primary_theme" /> <!-- First stroke color -->
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<!-- Transparent Content Layer -->
|
||||
<item
|
||||
android:bottom="4dp"
|
||||
android:left="4dp"
|
||||
android:right="4dp"
|
||||
android:top="4dp">
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="2dp" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</layer-list>
|
||||
@ -0,0 +1,17 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h48v48h-48z"/>
|
||||
<path
|
||||
android:fillAlpha="0.6"
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M24,24m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"/>
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M18,17.608C18,16.038 19.728,15.08 21.06,15.913L31.286,22.304C32.54,23.087 32.54,24.913 31.286,25.696L21.06,32.088C19.728,32.92 18,31.962 18,30.392V17.608Z"/>
|
||||
</group>
|
||||
</vector>
|
||||
112
feature/cloud_game/src/main/res/layout/activity_cloudgame.xml
Normal file
112
feature/cloud_game/src/main/res/layout/activity_cloudgame.xml
Normal file
@ -0,0 +1,112 @@
|
||||
<?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:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/render_view_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/rightController"
|
||||
app:layout_constraintStart_toEndOf="@id/leftController" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/keyboardContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="bottom"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/rightController"
|
||||
app:layout_constraintStart_toEndOf="@id/leftController" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/joystickContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="bottom"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/rightController"
|
||||
app:layout_constraintStart_toEndOf="@id/leftController" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingEnd="10dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/leftController"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/connectBtn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="启动" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/disconnectBtn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="关闭" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/rightController"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/keyboardBtn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="键盘" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/joystickBtn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="手柄" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/joystickEditBtn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="编辑手柄" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/cursorBtn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="鼠标" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
147
feature/cloud_game/src/main/res/layout/fragment_local_media.xml
Normal file
147
feature/cloud_game/src/main/res/layout/fragment_local_media.xml
Normal file
@ -0,0 +1,147 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.gh.gamecenter.common.view.MaterializedConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/ui_background_fixed_dark"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.gh.gamecenter.common.view.StatusBarView
|
||||
android:id="@+id/statusBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/ui_surface_fixed_dark" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/normal_toolbar_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/appbar_height"
|
||||
app:layout_constraintTop_toBottomOf="@id/statusBar">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/normal_toolbar"
|
||||
style="@style/Base_ToolbarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/ui_surface_fixed_dark"
|
||||
app:contentInsetEnd="0dp"
|
||||
app:contentInsetStartWithNavigation="0dp"
|
||||
app:navigationIcon="@null">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/backContainer"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/backBtn"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:srcCompat="@drawable/ic_function_close" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/normal_title"
|
||||
style="@style/toolbar_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:textColor="@color/text_aw_primary"
|
||||
android:textSize="16sp"
|
||||
tools:text="12345" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/arrowIv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginLeft="-12dp"
|
||||
android:layout_toRightOf="@+id/normal_title"
|
||||
android:padding="16dp"
|
||||
app:srcCompat="@drawable/ic_pin_down" />
|
||||
</RelativeLayout>
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/ui_background_fixed_dark"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/normal_toolbar_container">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginRight="2dp">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/list_refresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list_rv"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<include
|
||||
android:id="@+id/pieceMediaControl"
|
||||
layout="@layout/piece_media_control" />
|
||||
|
||||
<com.gh.gamecenter.common.view.NavigationBarView
|
||||
android:id="@+id/navigationBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/ui_surface_fixed_dark" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/reuse_ll_loading"
|
||||
layout="@layout/reuse_loading"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true" />
|
||||
|
||||
<include
|
||||
android:id="@+id/reuse_no_connection"
|
||||
layout="@layout/reuse_no_connection" />
|
||||
|
||||
<include
|
||||
android:id="@+id/reuse_none_data"
|
||||
layout="@layout/reuse_none_data" />
|
||||
|
||||
<include
|
||||
android:id="@+id/reuse_data_exception"
|
||||
layout="@layout/reuse_data_exception" />
|
||||
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/layout_activity_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</com.gh.gamecenter.common.view.MaterializedConstraintLayout>
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardCornerRadius="100dp"
|
||||
app:cardBackgroundColor="@color/secondary_yellow">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/iconTv"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
3
feature/cloud_game/src/main/res/values-night/themes.xml
Normal file
3
feature/cloud_game/src/main/res/values-night/themes.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
</resources>
|
||||
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
|
||||
</resources>
|
||||
10
feature/cloud_game/src/main/res/values/colors.xml
Normal file
10
feature/cloud_game/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
3
feature/cloud_game/src/main/res/values/strings.xml
Normal file
3
feature/cloud_game/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">SimpleDemo</string>
|
||||
</resources>
|
||||
16
feature/cloud_game/src/main/res/values/themes.xml
Normal file
16
feature/cloud_game/src/main/res/values/themes.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.SimpleDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
@ -18,6 +18,7 @@ gradle.ext.enableQuickLogin = gradle.startParameter.projectProperties.get('ENABL
|
||||
gradle.ext.enableAccelerator = gradle.startParameter.projectProperties.get('ENABLE_ACCELERATOR')?.toBoolean() ?: false
|
||||
gradle.ext.enableWechatPay = gradle.startParameter.projectProperties.get('ENABLE_WECHAT_PAY')?.toBoolean() ?: false
|
||||
gradle.ext.enableAliPay = gradle.startParameter.projectProperties.get('ENABLE_ALI_PAY')?.toBoolean() ?: false
|
||||
gradle.ext.enableCloudGame = gradle.startParameter.projectProperties.get('ENABLE_CLOUD_GAME')?.toBoolean() ?: false
|
||||
|
||||
// 是否启用路由文档输出
|
||||
gradle.ext.enableRouteDoc = gradle.startParameter.projectProperties.get('ENABLE_ROUTE_DOC')?.toBoolean() ?: false
|
||||
@ -68,7 +69,8 @@ def optionalModules = [
|
||||
':feature:ali_pay',
|
||||
':module_message',
|
||||
':module_sensors_data',
|
||||
':module_va_impl'
|
||||
':module_va_impl',
|
||||
':feature:cloud_game',
|
||||
]
|
||||
|
||||
// 定义 va 可选模块
|
||||
@ -164,4 +166,7 @@ if (gradle.ext.excludeOptionalModules == false) { // 默认全部模块都包含
|
||||
if (gradle.ext.enableAliPay) {
|
||||
include ':feature:ali_pay'
|
||||
}
|
||||
if (gradle.ext.enableCloudGame) {
|
||||
include ':feature:cloud_game'
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user