Compare commits

...

4 Commits

Author SHA1 Message Date
cef4526b35 stash 2025-04-08 17:33:06 +08:00
0e9301f4dc chore: unify dependency version 2025-04-07 17:14:29 +08:00
8436b2e3aa feat: init sample page 2025-04-07 17:05:26 +08:00
c57af24811 feat: revamped apk manager 2025-03-13 11:01:51 +08:00
32 changed files with 1480 additions and 31 deletions

View File

@ -515,6 +515,7 @@ dependencies {
}
implementation(project(':feature:media_select'))
implementation(project(':feature:apk_manager'))
implementation(project(":module_va_api"))
implementation(project(":va-archive-common"))

View File

@ -83,29 +83,9 @@
tv.danmaku.ijk.media.exo2,
pl.droidsonroids.gif,
com.lzf.easyfloat,
com.airbnb.lottie.compose,
androidx.compose.ui.platform,
androidx.compose.material.icons,
androidx.activity.compose,
androidx.compose.ui.tooling,
androidx.compose.ui.tooling.data,
androidx.compose.material.ripple,
androidx.compose.foundation,
androidx.compose.animation,
androidx.compose.foundation.layout,
androidx.compose.ui.text,
androidx.compose.ui.graphics,
androidx.compose.ui.unit,
androidx.compose.ui.util,
androidx.compose.ui.geometry,
androidx.compose.runtime.saveable,
androidx.compose.animation.core,
androidx.constraintlayout.compose,
androidx.compose.ui.test.manifest,
com.bytedance.sdk.openadsdk,
com.bykv.vk.openvk,
com.bytedance.tools,
androidx.compose.ui.tooling.preview,
com.tencent.qqmini,
com.tencent.qqmini.minigame.external,
com.tencent.qqmini.minigame.opensdk,

View File

@ -23,7 +23,7 @@ ext {
constraintLayout = "2.1.3"
databinding = "3.4.1"
recyclerView = "1.1.0"
lifeCycle = "2.5.1"
lifeCycle = "2.6.2"
lifeCycleExtensions = "2.2.0"
asynclayoutinflater = "1.0.0"
room = "2.6.1"
@ -120,10 +120,11 @@ ext {
autoServiceVersion = "1.0-rc7"
routerVersion = "1.2.2"
composeVersion = "1.2.1"
activityComposeVersion = "1.6.0"
composeVersion = "1.6.8"
activityComposeVersion = "1.7.2"
composeCompilerVersion = "1.5.14"
constraintlayoutCompose = "1.0.1"
composeMaterial3Version = "1.2.1"
constraintlayoutCompose = "1.1.1"
sensorsDataVersion = "6.8.0"

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

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,100 @@
if (isRelease.toBoolean()) {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
apply plugin: 'org.jetbrains.kotlin.android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-parcelize'
apply plugin: 'com.google.devtools.ksp'
android {
compileSdk rootProject.ext.compileSdkVersion
defaultConfig {
if (!isRelease.toBoolean()) {
applicationId "com.gh.gamecenter.apkmanager.compose"
multiDexEnabled true
buildConfigField "String", "API_HOST", "\"${API_HOST}\""
}
minSdk 21
targetSdk rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
}
sourceSets {
main {
if (isRelease.toBoolean()) {
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
exclude 'manifest/**'
}
resources {
exclude 'values/styles.xml'
exclude 'values-night/styles.xml'
}
} else {
java {
srcDirs = ['src/main/java', "src/apk/java"]
}
manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
}
}
}
buildTypes {
debug {
if (!isRelease.toBoolean()) {
buildConfigField "String", "DEV_API_HOST", "\"${DEV_API_HOST}\""
}
}
release {
consumerProguardFiles 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion composeCompilerVersion
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
dependencies {
if (!isRelease.toBoolean()) {
implementation "androidx.multidex:multidex:$multiDex"
}
ksp "cn.therouter:apt:${routerVersion}"
kapt "com.google.auto.service:auto-service:${autoServiceVersion}"
implementation(project(path: ":module_common")) {
exclude group: 'androidx.swiperefreshlayout'
}
implementation(project(path: ":module_core_feature")){
exclude group: 'androidx.swiperefreshlayout'
}
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifeCycle"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifeCycle"
implementation "androidx.activity:activity-compose:$activityComposeVersion"
implementation "androidx.compose.ui:ui:$composeVersion"
implementation "androidx.compose.ui:ui-tooling-preview:$composeVersion"
implementation "androidx.compose.material:material:$composeVersion"
implementation "androidx.compose.material3:material3-android:$composeMaterial3Version"
}

21
feature/apk_manager/proguard-rules.pro vendored Normal file
View File

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

View File

@ -0,0 +1,59 @@
package com.gh.gamecenter.apkmanager.compose
import androidx.multidex.MultiDexApplication
import com.facebook.common.logging.FLog
import com.facebook.imagepipeline.core.ImagePipelineConfig
import com.gh.gamecenter.common.utils.ImageUtils.isFrescoInitialized
import com.gh.gamecenter.core.iinterface.IApplication
import com.github.piasy.biv.BigImageViewer
import com.github.piasy.biv.loader.fresco.FrescoImageLoader
import java.util.*
class ApkManagerApp : MultiDexApplication() {
private val mApplicationList = ServiceLoader.load(IApplication::class.java, this.javaClass.classLoader)
override fun onCreate() {
super.onCreate()
initArouter()
mApp = this
for (application in mApplicationList) {
application.onCreate(this)
}
initFresco()
}
private fun initArouter() {
if (BuildConfig.DEBUG) { // 这两行必须写在init之前否则这些配置在init过程中将无效
// 可以直接删掉本行 ARouter.openLog() // 打印日志
// 可以直接删掉本行 ARouter.openDebug() // 开启调试模式(如果在InstantRun模式下运行必须开启调试模式线上版本需要关闭,否则有安全风险)
}
// 可以直接删掉本行 ARouter.init(this) // 尽可能早推荐在Application中初始化
}
fun initFresco() {
// 初始化 Fresco(BigImageViewer 已包含Fresco)
if (!isFrescoInitialized()) {
val pipelineConfigBuilder = ImagePipelineConfig.newBuilder(this)
try {
BigImageViewer.initialize(
FrescoImageLoader.with(
this,
pipelineConfigBuilder.build()
)
)
} catch (e: Throwable) {
e.printStackTrace()
}
FLog.setMinimumLoggingLevel(FLog.VERBOSE)
}
}
companion object {
private lateinit var mApp: ApkManagerApp
fun getInstance(): ApkManagerApp {
return mApp
}
}
}

View File

@ -0,0 +1,106 @@
package com.gh.gamecenter.apkmanager.compose.provider
import android.app.Activity
import android.app.Application
import android.content.Context
import com.gh.gamecenter.core.provider.IAppProvider
import com.gh.gamecenter.core.provider.IFlavorProvider
@com.therouter.inject.ServiceProvider
class AppProviderImpl : IAppProvider {
override fun getAppName(): String {
return "Compose App"
}
override fun getGid(): String {
return ""
}
override fun refreshGid() {
// Do nothing
}
override fun getOaid(): String {
return ""
}
override fun getChannel(): String {
return ""
}
override fun setChannel(channel: String) {
// Do nothing
}
override fun getUserAgent(): String {
return ""
}
override fun getServerUserMark(): String {
return ""
}
override fun getDeviceRamSize(): Long {
return 0L
}
override fun getTemporaryLocalDeviceId(): String {
return ""
}
override fun getFlavorProvider(): IFlavorProvider {
return object : IFlavorProvider {
override fun getChannelStr(application: Application): String {
return ""
}
override fun init(application: Application, activity: Activity, activateRatio: Int) {
// do nothing
}
override fun logEvent(content: String) {
// do nothing
}
override fun logCoreEvent() {
// do nothing
}
}
}
override fun getFlavor(): String {
return "internal"
}
override fun isUserAcceptPrivacyPolicy(context: Context): Boolean {
return true
}
override fun put(key: String, any: Any) {
// do nothing
}
override fun get(key: String, isRemove: Boolean): Any? {
return null
}
override fun getIsBrandNewInstall(): Boolean {
return false
}
override fun getAppVersion(): String {
return ""
}
override fun getPluginVersion(): String {
return ""
}
override fun setDisableSplashAdTemporarily(isDisable: Boolean) {
// do nothing
}
override fun initImageLoaderIfNeeded() {
// do nothing
}
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.gh.gamecenter.apkmanager.compose">
<application>
<activity
android:name=".ApkManagerActivity"
android:exported="false"
android:screenOrientation="portrait"
android:theme="@style/Theme.Assistantandroid" />
</application>
</manifest>

View File

@ -0,0 +1,27 @@
package com.gh.gamecenter.apkmanager.compose
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import com.gh.gamecenter.apkmanager.ui.AppManagerScreen
import com.gh.gamecenter.apkmanager.ui.theme.CustomAppTheme
class ApkManagerActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CustomAppTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
AppManagerScreen()
}
}
}
}
}

View File

@ -0,0 +1,45 @@
package com.gh.gamecenter.apkmanager.compose
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
}
}
}

View File

@ -0,0 +1,17 @@
package com.gh.gamecenter.apkmanager.data.model
data class InstalledApp(
val id: String,
val name: String,
val version: String,
val size: String,
val sizeBytes: Long,
val iconUrl: String,
val isSelected: Boolean = false
)
data class StorageInfo(
val totalApps: Int,
val totalSize: String,
val totalSizeBytes: Long
)

View File

@ -0,0 +1,121 @@
package com.gh.gamecenter.apkmanager.data.repository
import com.gh.gamecenter.apkmanager.data.model.InstalledApp
import com.gh.gamecenter.apkmanager.data.model.StorageInfo
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
class AppRepository {
// 模拟安装的应用列表
private val _installedApps = MutableStateFlow(
listOf(
InstalledApp(
id = "1",
name = "Standoff 2",
version = "V0.32.2",
size = "86.03MB",
sizeBytes = 86_030_000,
iconUrl = "https://and-static.ghzs.com/image/game/icon/2023/03/30/64252858bc8ef8264b07a0af.jpeg"
),
InstalledApp(
id = "2",
name = "Roblox安装器",
version = "V2.656.109",
size = "225.26MB",
sizeBytes = 225_260_000,
iconUrl = "https://and-static.ghzs.com/image/game/icon/2023/03/30/64252858bc8ef8264b07a0af.jpeg"
),
InstalledApp(
id = "3",
name = "Beat Kongfu",
version = "V9.1",
size = "27.22MB",
sizeBytes = 27_220_000,
iconUrl = "https://and-static.ghzs.com/image/game/icon/2023/03/30/64252858bc8ef8264b07a0af.jpeg"
),
InstalledApp(
id = "4",
name = "光环助手",
version = "V5.38.4",
size = "26.43MB",
sizeBytes = 26_430_000,
iconUrl = "https://and-static.ghzs.com/image/game/icon/2023/03/30/64252858bc8ef8264b07a0af.jpeg"
),
InstalledApp(
id = "5",
name = "光环助手",
version = "V5.38.5",
size = "27.20MB",
sizeBytes = 27_200_000,
iconUrl = "https://and-static.ghzs.com/image/game/icon/2023/03/30/64252858bc8ef8264b07a0af.jpeg"
),
InstalledApp(
id = "6",
name = "光环助手",
version = "V5.38.6",
size = "27.28MB",
sizeBytes = 27_280_000,
iconUrl = "https://and-static.ghzs.com/image/game/icon/2023/03/30/64252858bc8ef8264b07a0af.jpeg"
)
)
)
val installedApps: Flow<List<InstalledApp>> = _installedApps
val storageInfo: Flow<StorageInfo> = _installedApps.map { apps ->
val totalSizeBytes = apps.sumOf { it.sizeBytes }
val totalSize = formatSize(totalSizeBytes)
StorageInfo(
totalApps = apps.size,
totalSize = totalSize,
totalSizeBytes = totalSizeBytes
)
}
fun toggleAppSelection(appId: String) {
_installedApps.update { appList ->
appList.map { app ->
if (app.id == appId) app.copy(isSelected = !app.isSelected) else app
}
}
}
fun selectAllApps(selected: Boolean) {
_installedApps.update { appList ->
appList.map { app -> app.copy(isSelected = selected) }
}
}
fun getSelectedApps(): Flow<List<InstalledApp>> {
return _installedApps.map { appList -> appList.filter { it.isSelected } }
}
fun getSelectedAppsCount(): Flow<Int> {
return _installedApps.map { appList -> appList.count { it.isSelected } }
}
fun getSelectedAppsSize(): Flow<String> {
return _installedApps.map { appList ->
val totalBytes = appList.filter { it.isSelected }.sumOf { it.sizeBytes }
formatSize(totalBytes)
}
}
fun deleteSelectedApps() {
_installedApps.update { appList -> appList.filter { !it.isSelected } }
}
private fun formatSize(bytes: Long): String {
val kb = bytes / 1024.0
val mb = kb / 1024.0
val gb = mb / 1024.0
return when {
gb >= 1.0 -> String.format("%.2fGB", gb)
mb >= 1.0 -> String.format("%.2fMB", mb)
else -> String.format("%.2fKB", kb)
}
}
}

View File

@ -0,0 +1,28 @@
package com.gh.gamecenter.apkmanager.domain
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
data class PermissionResult(val isGranted: Boolean, val shouldShowRationale: Boolean)
class CheckStoragePermissionUseCase(private val context: Context) {
fun execute(): Flow<PermissionResult> = flow {
val permission = Manifest.permission.READ_EXTERNAL_STORAGE
val permissionGranted = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
if (permissionGranted) {
emit(PermissionResult(isGranted = true, shouldShowRationale = false)) // Permission already granted
} else {
val shouldShowRationale = ActivityCompat.shouldShowRequestPermissionRationale(context as Activity, permission)
emit(PermissionResult(false, shouldShowRationale)) // Permission not granted, signal to UI
}
}
}

View File

@ -0,0 +1,380 @@
package com.gh.gamecenter.apkmanager.ui
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Share
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.lifecycle.viewmodel.compose.viewModel
import com.gh.gamecenter.apkmanager.data.model.InstalledApp
@Composable
fun AppManagerScreen(
viewModel: AppManagerViewModel = viewModel(),
) {
val uiState by viewModel.uiState.collectAsState()
// 权限请求对话框
if (!uiState.permissionResult.isGranted) {
AlertDialog(
onDismissRequest = { },
title = { Text("需要文件管理权限") },
text = { Text("为了扫描和清理安装包,需要获取文件管理权限。请允许应用访问所有文件。") },
confirmButton = {
TextButton(
onClick = {
// requestStoragePermission()
}
) {
Text("去设置")
}
},
dismissButton = {
TextButton(onClick = { }) {
Text("取消")
}
}
)
}
Scaffold { paddingValues ->
if (uiState.isLoading) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
}
} else {
// 使用 Column 包装内容和底部按钮
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.background(Color(0xFFF5F5F5))
) {
// 应用列表 - 使用权重确保它填充剩余空间
LazyColumn(
modifier = Modifier
.weight(1f)
.fillMaxWidth()
) {
items(uiState.apps) { app ->
AppItem(
app = app,
onSelectApp = { viewModel.handleIntent(AppManagerIntent.SelectApp(app.id)) },
onOpenDetails = {
viewModel.handleIntent(
AppManagerIntent.OpenAppDetails(
app.id
)
)
},
onShare = { viewModel.handleIntent(AppManagerIntent.ShareApp(app.id)) },
onInstall = { viewModel.handleIntent(AppManagerIntent.InstallApp(app.id)) }
)
}
}
// 底部清理按钮 - 直接作为 Column 的一部分,而不是使用 bottomBar
CleanupButton(
selectedCount = uiState.selectedCount,
selectedSize = uiState.selectedSize,
onCleanup = { viewModel.handleIntent(AppManagerIntent.DeleteSelectedApps) }
)
}
}
}
}
@Composable
fun CleanupButton(
selectedCount: Int,
selectedSize: String,
onCleanup: () -> Unit,
) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color.White)
.padding(16.dp)
) {
if (selectedCount > 0) {
Button(
onClick = onCleanup,
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary,
contentColor = Color.White
),
shape = RoundedCornerShape(4.dp)
) {
Text(
text = "一键删除(${selectedCount}个,释放${selectedSize}",
modifier = Modifier.padding(vertical = 8.dp)
)
}
} else {
Button(
onClick = { /* 不可点击 */ },
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = Color.LightGray,
contentColor = Color.White
),
shape = RoundedCornerShape(4.dp),
enabled = false
) {
Text(
text = "请选择要删除的应用",
modifier = Modifier.padding(vertical = 8.dp)
)
}
}
}
}
data class ContextMenuItem(
val title: String,
val icon: ImageVector,
val onClick: () -> Unit,
)
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun AppItem(
app: InstalledApp,
onSelectApp: () -> Unit,
onOpenDetails: () -> Unit,
onShare: () -> Unit,
onInstall: () -> Unit,
) {
var showDialog by remember { mutableStateOf(false) }
// 创建上下文菜单项
val menuItems = listOf(
ContextMenuItem(
title = "打开游戏详情页",
icon = Icons.Outlined.Info,
onClick = {
onOpenDetails()
showDialog = false
}
),
ContextMenuItem(
title = "分享",
icon = Icons.Outlined.Share,
onClick = {
onShare()
showDialog = false
}
),
ContextMenuItem(
title = "安装",
icon = Icons.Outlined.Settings,
onClick = {
onInstall()
showDialog = false
}
)
)
// 显示对话框
if (showDialog) {
AppOptionsDialog(
appName = app.name,
menuItems = menuItems,
onDismiss = { showDialog = false }
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.background(Color.White)
.combinedClickable(
onClick = { onSelectApp() },
onLongClick = { showDialog = true }
)
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 应用信息
Column(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp)
) {
Text(
text = app.name,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = "版本: ${app.version} | ${app.size}",
color = Color.Gray,
style = MaterialTheme.typography.bodyMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = "未安装",
color = Color.Red,
style = MaterialTheme.typography.labelSmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
Spacer(modifier = Modifier.width(16.dp))
// 选择状态 - 使用截图中的圆形选择样式
Box(
modifier = Modifier
.size(20.dp)
.clip(CircleShape)
.background(
if (app.isSelected) MaterialTheme.colorScheme.primary
else Color(0xFFE0E0E0)
),
contentAlignment = Alignment.Center
) {
if (app.isSelected) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = "已选择",
tint = Color.White,
modifier = Modifier.size(14.dp)
)
}
}
}
// 分隔线
Spacer(
modifier = Modifier
.fillMaxWidth()
.height(1.dp)
.background(Color(0xFFEEEEEE))
)
}
@Composable
fun AppOptionsDialog(
appName: String,
menuItems: List<ContextMenuItem>,
onDismiss: () -> Unit,
) {
Dialog(onDismissRequest = onDismiss) {
Surface(
shape = RoundedCornerShape(8.dp),
color = Color.White
) {
Column(
modifier = Modifier.padding(vertical = 8.dp)
) {
// 标题
Text(
text = appName,
style = MaterialTheme.typography.titleMedium.copy(
fontWeight = FontWeight.Bold,
fontSize = 18.sp
),
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp, horizontal = 16.dp)
)
// 菜单项
Column {
menuItems.forEach { item ->
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { item.onClick() }
.padding(vertical = 16.dp, horizontal = 24.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = item.icon,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = item.title,
style = MaterialTheme.typography.bodyLarge,
color = Color.Black
)
}
}
}
// 取消按钮
Text(
text = "取消",
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center,
color = Color.Gray,
modifier = Modifier
.fillMaxWidth()
.clickable { onDismiss() }
.padding(vertical = 16.dp)
)
}
}
}
}

View File

@ -0,0 +1,133 @@
package com.gh.gamecenter.apkmanager.ui
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.gh.gamecenter.apkmanager.data.model.InstalledApp
import com.gh.gamecenter.apkmanager.data.repository.AppRepository
import com.gh.gamecenter.apkmanager.domain.CheckStoragePermissionUseCase
import com.gh.gamecenter.apkmanager.domain.PermissionResult
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class AppManagerViewModel(
private val checkStoragePermissionUseCase: CheckStoragePermissionUseCase) : ViewModel() {
private val _permissionState = MutableStateFlow<PermissionResult?>(null)
private val repository = AppRepository()
private val _uiState = MutableStateFlow(AppManagerUiState())
val uiState: StateFlow<AppManagerUiState> = combine(
_uiState,
repository.installedApps,
repository.getSelectedAppsCount(),
repository.getSelectedAppsSize(),
repository.storageInfo
) { state, apps, selectedCount, selectedSize, storageInfo ->
state.copy(
apps = apps,
selectedCount = selectedCount,
selectedSize = selectedSize,
totalCount = storageInfo.totalApps,
totalSize = storageInfo.totalSize,
isLoading = false,
)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = AppManagerUiState(isLoading = true)
)
init {
viewModelScope.launch {
checkStoragePermissionUseCase.execute()
.collect { result ->
_permissionState.value = result
}
}
}
// 处理用户操作
fun handleIntent(intent: AppManagerIntent) {
when (intent) {
is AppManagerIntent.SelectApp -> toggleAppSelection(intent.appId)
is AppManagerIntent.SelectAllApps -> selectAllApps(intent.selected)
is AppManagerIntent.DeleteSelectedApps -> deleteSelectedApps()
is AppManagerIntent.UpdateSearchQuery -> updateSearchQuery(intent.query)
// 新增的长按菜单操作
is AppManagerIntent.OpenAppDetails -> openAppDetails(intent.appId)
is AppManagerIntent.ShareApp -> shareApp(intent.appId)
is AppManagerIntent.InstallApp -> installApp(intent.appId)
}
}
private fun toggleAppSelection(appId: String) {
repository.toggleAppSelection(appId)
}
private fun selectAllApps(selected: Boolean) {
repository.selectAllApps(selected)
}
private fun deleteSelectedApps() {
viewModelScope.launch {
repository.deleteSelectedApps()
}
}
private fun updateSearchQuery(query: String) {
_uiState.update { it.copy(searchQuery = query) }
}
// 新增的长按菜单操作实现
private fun openAppDetails(appId: String) {
// 在真实应用中,这里应该通过 Intents 打开系统的应用详情页面
Log.d("AppManagerViewModel", "打开应用详情页: $appId")
// 如果需要跨层级调用,可以通过 UseCase 实现
}
private fun shareApp(appId: String) {
// 在真实应用中,这里应该通过 Intents 分享应用
Log.d("AppManagerViewModel", "分享应用: $appId")
// 如果需要跨层级调用,可以通过 UseCase 实现
}
private fun installApp(appId: String) {
// 在真实应用中,这里应该启动安装操作
Log.d("AppManagerViewModel", "安装应用: $appId")
// 如果需要跨层级调用,可以通过 UseCase 实现
}
}
data class AppManagerUiState(
val apps: List<InstalledApp> = emptyList(),
val selectedCount: Int = 0,
val selectedSize: String = "0",
val totalCount: Int = 0,
val totalSize: String = "0",
val searchQuery: String = "",
val permissionResult: PermissionResult = PermissionResult(
isGranted = false,
shouldShowRationale = true
),
val isLoading: Boolean = false,
)
sealed class AppManagerIntent {
data class SelectApp(val appId: String) : AppManagerIntent()
data class SelectAllApps(val selected: Boolean) : AppManagerIntent()
data object DeleteSelectedApps : AppManagerIntent()
data class UpdateSearchQuery(val query: String) : AppManagerIntent()
// 新增的长按菜单操作
data class OpenAppDetails(val appId: String) : AppManagerIntent()
data class ShareApp(val appId: String) : AppManagerIntent()
data class InstallApp(val appId: String) : AppManagerIntent()
}

View File

@ -0,0 +1,61 @@
package com.gh.gamecenter.apkmanager.ui.theme
import androidx.compose.ui.graphics.Color
// Light Theme Colors
val md_theme_light_primary = Color(0xFF0061A4)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFD1E4FF)
val md_theme_light_onPrimaryContainer = Color(0xFF001D36)
val md_theme_light_secondary = Color(0xFF535F70)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFD7E3F7)
val md_theme_light_onSecondaryContainer = Color(0xFF101C2B)
val md_theme_light_tertiary = Color(0xFF6B5778)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFF2DAFF)
val md_theme_light_onTertiaryContainer = Color(0xFF251431)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFDFCFF)
val md_theme_light_onBackground = Color(0xFF1A1C1E)
val md_theme_light_surface = Color(0xFFFDFCFF)
val md_theme_light_onSurface = Color(0xFF1A1C1E)
val md_theme_light_surfaceVariant = Color(0xFFDFE2EB)
val md_theme_light_onSurfaceVariant = Color(0xFF43474E)
val md_theme_light_outline = Color(0xFF73777F)
// Dark Theme Colors
val md_theme_dark_primary = Color(0xFF9ECAFF)
val md_theme_dark_onPrimary = Color(0xFF003258)
val md_theme_dark_primaryContainer = Color(0xFF00497D)
val md_theme_dark_onPrimaryContainer = Color(0xFFD1E4FF)
val md_theme_dark_secondary = Color(0xFFBBC7DB)
val md_theme_dark_onSecondary = Color(0xFF253140)
val md_theme_dark_secondaryContainer = Color(0xFF3B4858)
val md_theme_dark_onSecondaryContainer = Color(0xFFD7E3F7)
val md_theme_dark_tertiary = Color(0xFFD7BDE4)
val md_theme_dark_onTertiary = Color(0xFF3B2948)
val md_theme_dark_tertiaryContainer = Color(0xFF523F5F)
val md_theme_dark_onTertiaryContainer = Color(0xFFF2DAFF)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF1A1C1E)
val md_theme_dark_onBackground = Color(0xFFE2E2E6)
val md_theme_dark_surface = Color(0xFF1A1C1E)
val md_theme_dark_onSurface = Color(0xFFE2E2E6)
val md_theme_dark_surfaceVariant = Color(0xFF43474E)
val md_theme_dark_onSurfaceVariant = Color(0xFFC3C7CF)
val md_theme_dark_outline = Color(0xFF8D9199)
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View File

@ -0,0 +1,122 @@
package com.gh.gamecenter.apkmanager.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Shapes
import androidx.compose.material3.Typography
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
// 截图中的颜色风格
private val LightColorPalette = lightColorScheme(
primary = Color(0xFF2196F3), // 蓝色主题色
onPrimary = Color.White,
primaryContainer = Color(0xFFE3F2FD),
onPrimaryContainer = Color(0xFF1976D2),
secondary = Color(0xFF2196F3),
onSecondary = Color.White,
secondaryContainer = Color(0xFFBBDEFB),
onSecondaryContainer = Color(0xFF1976D2),
tertiary = Color(0xFF2196F3),
onTertiary = Color.White,
background = Color(0xFFF5F5F5), // 浅灰色背景
onBackground = Color(0xFF212121), // 深灰色文字
surface = Color.White, // 白色卡片背景
onSurface = Color(0xFF212121), // 深灰色文字
surfaceVariant = Color(0xFFEEEEEE), // 浅灰色变体表面
onSurfaceVariant = Color(0xFF757575) // 中灰色文字
)
private val DarkColorPalette = darkColorScheme(
primary = Color(0xFF90CAF9), // 浅蓝色主题色 (暗色模式)
onPrimary = Color.Black,
primaryContainer = Color(0xFF0D47A1),
onPrimaryContainer = Color(0xFFBBDEFB),
secondary = Color(0xFF90CAF9),
onSecondary = Color.Black,
secondaryContainer = Color(0xFF0D47A1),
onSecondaryContainer = Color(0xFFBBDEFB),
tertiary = Color(0xFF90CAF9),
onTertiary = Color.Black,
background = Color(0xFF121212), // 暗色背景
onBackground = Color(0xFFEEEEEE), // 浅灰色文字
surface = Color(0xFF1E1E1E), // 暗色卡片背景
onSurface = Color(0xFFEEEEEE), // 浅灰色文字
surfaceVariant = Color(0xFF2D2D2D), // 暗灰色变体表面
onSurfaceVariant = Color(0xFFBDBDBD) // 浅灰色文字
)
// 更扁平化的形状
val CustomShapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(4.dp),
large = RoundedCornerShape(8.dp)
)
// 更简洁的文字风格
val CustomTypography = Typography(
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Bold,
fontSize = 18.sp
),
titleMedium = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 16.sp
),
titleSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 14.sp
),
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 14.sp
),
bodyMedium = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 13.sp
),
bodySmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 12.sp,
color = Color(0xFF757575)
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 11.sp,
color = Color(0xFFE53935) // 红色标签(例如未安装)
)
)
@Composable
fun CustomAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
MaterialTheme(
colorScheme = colors,
typography = CustomTypography,
shapes = CustomShapes,
content = content
)
}

View File

@ -0,0 +1,99 @@
package com.gh.gamecenter.apkmanager.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val LightColorScheme = lightColorScheme(
primary = md_theme_light_primary,
onPrimary = md_theme_light_onPrimary,
primaryContainer = md_theme_light_primaryContainer,
onPrimaryContainer = md_theme_light_onPrimaryContainer,
secondary = md_theme_light_secondary,
onSecondary = md_theme_light_onSecondary,
secondaryContainer = md_theme_light_secondaryContainer,
onSecondaryContainer = md_theme_light_onSecondaryContainer,
tertiary = md_theme_light_tertiary,
onTertiary = md_theme_light_onTertiary,
tertiaryContainer = md_theme_light_tertiaryContainer,
onTertiaryContainer = md_theme_light_onTertiaryContainer,
error = md_theme_light_error,
errorContainer = md_theme_light_errorContainer,
onError = md_theme_light_onError,
onErrorContainer = md_theme_light_onErrorContainer,
background = md_theme_light_background,
onBackground = md_theme_light_onBackground,
surface = md_theme_light_surface,
onSurface = md_theme_light_onSurface,
surfaceVariant = md_theme_light_surfaceVariant,
onSurfaceVariant = md_theme_light_onSurfaceVariant,
outline = md_theme_light_outline
)
private val DarkColorScheme = darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
secondaryContainer = md_theme_dark_secondaryContainer,
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
tertiary = md_theme_dark_tertiary,
onTertiary = md_theme_dark_onTertiary,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
errorContainer = md_theme_dark_errorContainer,
onError = md_theme_dark_onError,
onErrorContainer = md_theme_dark_onErrorContainer,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
surfaceVariant = md_theme_dark_surfaceVariant,
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
outline = md_theme_dark_outline
)
@Composable
fun DemoTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@ -0,0 +1,34 @@
package com.gh.gamecenter.apkmanager.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.gh.gamecenter.apkmanager.compose">
<application
android:name=".ApkManagerApp"
android:allowBackup="true"
android:icon="@mipmap/logo"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.Assistantandroid">
<meta-data
android:name="io.sentry.auto-init"
android:value="false" /> <!-- 不让 sentry 读取系统事件 -->
<meta-data
android:name="io.sentry.breadcrumbs.system-events"
android:value="false" />
<activity
android:name=".ApkManagerActivity"
android:exported="true"
android:screenOrientation="portrait"
android:theme="@style/Theme.Assistantandroid">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,22 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- extends common style -->
<style name="AppCompatTheme.APP">
<item name="colorAccent">@color/primary_theme</item>
<item name="colorPrimary">@color/primary_theme</item>
<!-- 导航栏颜色(还包括列表下拉沉浸色 等等)-->
<item name="colorPrimaryDark">@color/ui_background</item>
<!-- 设置沉浸栏颜色 5.0以上有效 -->
<item name="android:windowLightStatusBar">true</item>
<item name="android:actionModeBackground">@color/primary_theme</item>
<item name="actionModeBackground">@color/primary_theme</item>
<item name="android:windowBackground">@color/ui_background</item>
<item name="android:navigationIcon">@drawable/ic_bar_back</item>
<item name="navigationIcon">@drawable/ic_bar_back</item>
<item name="android:homeAsUpIndicator">@drawable/ic_bar_back</item>
<item name="homeAsUpIndicator">@drawable/ic_bar_back</item>
</style>
</resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Assistantandroid" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="android:statusBarColor">@color/ui_surface</item>
</style>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Compose APK</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Compose APK</string>
</resources>

View File

@ -0,0 +1,22 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- extends common style -->
<style name="AppCompatTheme.APP">
<item name="colorAccent">@color/primary_theme</item>
<item name="colorPrimary">@color/primary_theme</item>
<!-- 导航栏颜色(还包括列表下拉沉浸色 等等)-->
<item name="colorPrimaryDark">@color/system_bar</item>
<!-- 设置沉浸栏颜色 5.0以上有效 -->
<item name="android:windowLightStatusBar">true</item>
<item name="android:actionModeBackground">@color/primary_theme</item>
<item name="actionModeBackground">@color/primary_theme</item>
<item name="android:windowBackground">@color/ui_background</item>
<item name="android:navigationIcon">@drawable/ic_bar_back</item>
<item name="navigationIcon">@drawable/ic_bar_back</item>
<item name="android:homeAsUpIndicator">@drawable/ic_bar_back</item>
<item name="homeAsUpIndicator">@drawable/ic_bar_back</item>
</style>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Assistantandroid" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="android:statusBarColor">@color/ui_surface</item>
</style>
</resources>

View File

@ -8,10 +8,6 @@ android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
if (!isRelease.toBoolean()) {
applicationId "com.gh.floatingwindow"
multiDexEnabled true
}
minSdk rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode

View File

@ -13,7 +13,7 @@
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#Wed Jul 19 10:16:09 CST 2017
org.gradle.jvmargs=-Xmx4096m -XX\:MaxPermSize\=2048m -XX\:+HeapDumpOnOutOfMemoryError -Dfile.encoding\=UTF-8
org.gradle.jvmargs=-Xmx4096m -XX\:+HeapDumpOnOutOfMemoryError -Dfile.encoding\=UTF-8
#开启gradle并行编译
org.gradle.parallel=true
#开启守护进程
@ -95,5 +95,5 @@ android.databinding.incremental=true
android.injected.testOnly = false
# 动态配置插件
isRelease = true
isRelease = false
pluginBasePath=vasdk/

View File

@ -69,7 +69,7 @@ class SelectGameViewModel(application: Application) : AndroidViewModel(applicati
Observable
.create<Any?> { emitter: ObservableEmitter<Any?> ->
mApkList.clear()
val pm: PackageManager = getApplication<Application?>().applicationContext.packageManager
val pm: PackageManager = getApplication<Application>().applicationContext.packageManager
val packageUtilsConfig = TheRouter.get(IPackageUtilsProvider::class.java)
val installedPackages =
packageUtilsConfig?.getInstalledPackages(getApplication(), 0) ?: listOf()

View File

@ -39,6 +39,7 @@ def coreModules = [
':feature:xapk-installer',
':feature:media_select',
':feature:route_doc',
':feature:apk_manager',
':module_internal_test',
':module_va_api',
]