Compare commits
1 Commits
dev
...
feat/cloud
| Author | SHA1 | Date | |
|---|---|---|---|
| 718ed2d209 |
@ -538,6 +538,10 @@ dependencies {
|
|||||||
if(!gradle.ext.excludeOptionalModules || gradle.ext.enableWechatPay){
|
if(!gradle.ext.excludeOptionalModules || gradle.ext.enableWechatPay){
|
||||||
implementation(project(":feature:wechat_pay"))
|
implementation(project(":feature:wechat_pay"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!gradle.ext.excludeOptionalModules || gradle.ext.enableCloudGame){
|
||||||
|
implementation(project(":feature:cloud_game"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File propFile = file('sign.properties')
|
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.provider.IHelpAndFeedbackProvider
|
||||||
import com.gh.gamecenter.common.utils.PackageFlavorHelper
|
import com.gh.gamecenter.common.utils.PackageFlavorHelper
|
||||||
import com.gh.gamecenter.core.provider.IPushProvider
|
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.utils.QuickLoginHelper
|
||||||
import com.gh.gamecenter.login.view.LoginActivity
|
import com.gh.gamecenter.login.view.LoginActivity
|
||||||
import com.gh.gamecenter.va.VCore
|
import com.gh.gamecenter.va.VCore
|
||||||
@ -106,6 +107,8 @@ class GlobalActivityLifecycleObserver : Application.ActivityLifecycleCallbacks {
|
|||||||
|
|
||||||
if (PackageFlavorHelper.IS_TEST_FLAVOR && activity is AppCompatActivity) {
|
if (PackageFlavorHelper.IS_TEST_FLAVOR && activity is AppCompatActivity) {
|
||||||
DarkModeSwitchHelper.showDarkModeSwitchFloatingView(activity)
|
DarkModeSwitchHelper.showDarkModeSwitchFloatingView(activity)
|
||||||
|
|
||||||
|
CloudGameHelper.showCloudGameFloat(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activity is AppCompatActivity
|
if (activity is AppCompatActivity
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
android:layout_height="112dp"
|
android:layout_height="112dp"
|
||||||
android:background="@color/ui_surface"
|
android:background="@color/ui_surface"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
tools:showIn="@layout/activity_main">
|
tools:showIn="@layout/activity_cloudgame">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="wrap_content"
|
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.enableAccelerator = gradle.startParameter.projectProperties.get('ENABLE_ACCELERATOR')?.toBoolean() ?: false
|
||||||
gradle.ext.enableWechatPay = gradle.startParameter.projectProperties.get('ENABLE_WECHAT_PAY')?.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.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
|
gradle.ext.enableRouteDoc = gradle.startParameter.projectProperties.get('ENABLE_ROUTE_DOC')?.toBoolean() ?: false
|
||||||
@ -68,7 +69,8 @@ def optionalModules = [
|
|||||||
':feature:ali_pay',
|
':feature:ali_pay',
|
||||||
':module_message',
|
':module_message',
|
||||||
':module_sensors_data',
|
':module_sensors_data',
|
||||||
':module_va_impl'
|
':module_va_impl',
|
||||||
|
':feature:cloud_game',
|
||||||
]
|
]
|
||||||
|
|
||||||
// 定义 va 可选模块
|
// 定义 va 可选模块
|
||||||
@ -164,4 +166,7 @@ if (gradle.ext.excludeOptionalModules == false) { // 默认全部模块都包含
|
|||||||
if (gradle.ext.enableAliPay) {
|
if (gradle.ext.enableAliPay) {
|
||||||
include ':feature:ali_pay'
|
include ':feature:ali_pay'
|
||||||
}
|
}
|
||||||
|
if (gradle.ext.enableCloudGame) {
|
||||||
|
include ':feature:cloud_game'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user