├── .gitignore ├── LICENSE.txt ├── README.md ├── build.gradle.kts ├── composeApp ├── build.gradle.kts └── src │ ├── commonMain │ ├── kotlin │ │ └── com │ │ │ └── nukrs │ │ │ └── windroid │ │ │ ├── Main.kt │ │ │ ├── core │ │ │ └── service │ │ │ │ ├── DeviceService.kt │ │ │ │ └── RebootService.kt │ │ │ ├── data │ │ │ └── model │ │ │ │ └── Models.kt │ │ │ └── ui │ │ │ ├── AppTray.kt │ │ │ ├── components │ │ │ ├── AboutButton.kt │ │ │ ├── AdbToolPanel.kt │ │ │ ├── CloseConfirmDialog.kt │ │ │ ├── DeviceOverviewPanel.kt │ │ │ ├── FastbootToolPanel.kt │ │ │ ├── FileChooser.kt │ │ │ ├── StatusBar.kt │ │ │ ├── ThemeToggleButton.kt │ │ │ └── ToolCard.kt │ │ │ ├── screens │ │ │ └── MainScreen.kt │ │ │ └── theme │ │ │ ├── Theme.kt │ │ │ └── ThemeManager.kt │ └── resources │ │ └── tray_icon.svg │ └── desktopMain │ ├── kotlin │ └── com │ │ └── nukrs │ │ └── windroid │ │ └── ui │ │ └── TrayManager.kt │ └── resources │ ├── color │ └── Macchiato.json │ ├── icon.ico │ ├── icon.svg │ ├── logo │ ├── Lenovo.png │ ├── Motorola.png │ ├── OnePlus.png │ ├── Oppo.png │ ├── Redmi.png │ ├── Samsung.jpg │ ├── Sony.png │ ├── Vivo.png │ ├── Xiaomi.png │ ├── google.png │ ├── iqoo.png │ ├── meizu.png │ └── realme.png │ └── platform-tools │ ├── AdbWinApi.dll │ ├── AdbWinUsbApi.dll │ ├── NOTICE.txt │ ├── adb.exe │ ├── etc1tool.exe │ ├── fastboot.exe │ ├── hprof-conv.exe │ ├── libwinpthread-1.dll │ ├── make_f2fs.exe │ ├── make_f2fs_casefold.exe │ ├── mke2fs.conf │ ├── mke2fs.exe │ ├── package.xml │ ├── source.properties │ └── sqlite3.exe ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .kotlin 3 | .gradle 4 | **/build/ 5 | xcuserdata 6 | !src/**/build/ 7 | local.properties 8 | .idea 9 | .DS_Store 10 | captures 11 | .externalNativeBuild 12 | .cxx 13 | *.xcodeproj/* 14 | !*.xcodeproj/project.pbxproj 15 | !*.xcodeproj/xcshareddata/ 16 | !*.xcodeproj/project.xcworkspace/ 17 | !*.xcworkspace/contents.xcworkspacedata 18 | **/xcshareddata/WorkspaceSettings.xcsettings 19 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Nukrs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WinDroid Toolbox 2 | 3 | 一个功能强大的Android刷机工具箱,使用Kotlin Multiplatform和Jetpack Compose for Desktop构建。 4 | 5 | ## 功能特性 6 | 7 | ### 🔧 刷机工具 8 | - **Fastboot刷机**: 支持刷入系统镜像、Recovery、Bootloader等 9 | - **ADB工具**: Android调试桥,用于设备调试和文件传输 10 | image 11 | image 12 | image 13 | 14 | 15 | 16 | 17 | ### 📱 设备管理 18 | - 自动检测连接的Android设备 19 | - 实时显示设备信息(型号、制造商、Android版本等) 20 | - 设备状态监控(Bootloader状态、Root状态等) 21 | - 高级重启模式 22 | 23 | ### 🎨 用户界面 24 | - 现代化界面 25 | - 支持深色/浅色主题 26 | - 响应式布局设计 27 | - 实时操作日志显示 28 | 29 | ## 技术栈 30 | 31 | - **Kotlin Multiplatform**: 跨平台开发 32 | - **Jetpack Compose for Desktop**: 现代化UI框架 33 | - **Material Design 3**: 设计系统 34 | 35 | ## 系统要求 36 | 37 | - Windows 10/11 (x64) 38 | - Java 17+ 39 | 40 | ## 安装使用 41 | 42 | ### 从源码构建 43 | 44 | 1. 克隆仓库 45 | ```bash 46 | git clone https://github.com/Nukrs/WinDroid-ToolBox.git 47 | cd WinDroid-ToolBox 48 | ``` 49 | 50 | 2. 构建应用 51 | ```bash 52 | ./gradlew packageDistributionForCurrentOS 53 | ``` 54 | 55 | 3. 运行应用 56 | ```bash 57 | ./gradlew run 58 | ``` 59 | 60 | ### 预编译版本 61 | 62 | 从[Releases页面](https://github.com/Nukrs/WinDroid-ToolBox/releases)下载安装包。 63 | 64 | ## 配置说明 65 | 66 | 首次运行时,请在设置中配置以下工具路径: 67 | 68 | - **ADB路径**: Android Debug Bridge工具 69 | - **Fastboot路径**: Fastboot工具 70 | 71 | ## 使用指南 72 | 73 | ### 1. 连接设备 74 | - 启用开发者选项和USB调试 75 | - 使用USB线连接设备到电脑 76 | - 在设备上授权USB调试 77 | 78 | ### 2. 选择工具 79 | - 在左侧工具列表中选择需要的功能 80 | - 查看右侧详情面板中的说明 81 | 82 | ### 3. 执行操作 83 | - 按照界面提示进行操作 84 | - 在日志区域查看执行进度 85 | 86 | ## 安全提示 87 | 88 | ⚠️ **重要警告**: 89 | - 刷机有风险,操作前请备份重要数据 90 | - 确保使用正确的固件文件 91 | - 刷机过程中请勿断开设备连接 92 | - 本工具仅供学习和研究使用 93 | 94 | ## 开发计划 95 | 96 | - [ ] 固件下载和管理功能 97 | - [ ] 批量操作支持 98 | - [ ] 插件系统 99 | - [ ] 多语言支持 100 | 101 | ## 贡献指南 102 | 103 | 欢迎提交Issue和Pull Request! 104 | 105 | ## 许可证 106 | 107 | 本项目采用MIT许可证 - 查看[LICENSE](LICENSE)文件了解详情。 108 | 109 | ## 免责声明 110 | 111 | 本软件仅供教育和研究目的使用。使用本软件进行的任何操作所造成的设备损坏或数据丢失,开发者不承担任何责任。请在使用前仔细阅读相关文档并备份重要数据。 112 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | // this is necessary to avoid the plugins to be loaded multiple times 3 | // in each subproject's classloader 4 | alias(libs.plugins.composeHotReload) apply false 5 | alias(libs.plugins.composeMultiplatform) apply false 6 | alias(libs.plugins.composeCompiler) apply false 7 | alias(libs.plugins.kotlinMultiplatform) apply false 8 | } 9 | 10 | allprojects { 11 | repositories { 12 | google() 13 | mavenCentral() 14 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 15 | } 16 | } -------------------------------------------------------------------------------- /composeApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat 2 | 3 | plugins { 4 | alias(libs.plugins.kotlinMultiplatform) 5 | alias(libs.plugins.composeMultiplatform) 6 | alias(libs.plugins.composeCompiler) 7 | alias(libs.plugins.composeHotReload) 8 | alias(libs.plugins.kotlinx.serialization) 9 | } 10 | 11 | kotlin { 12 | jvm("desktop") 13 | 14 | sourceSets { 15 | val desktopMain by getting 16 | 17 | commonMain.dependencies { 18 | implementation(compose.runtime) 19 | implementation(compose.foundation) 20 | implementation(compose.material3) 21 | implementation(compose.ui) 22 | implementation(compose.components.resources) 23 | implementation(compose.components.uiToolingPreview) 24 | implementation(compose.materialIconsExtended) 25 | implementation(libs.androidx.lifecycle.viewmodel) 26 | implementation(libs.androidx.lifecycle.runtimeCompose) 27 | implementation(libs.kotlinx.serialization.json) 28 | implementation(libs.ktor.client.core) 29 | implementation(libs.ktor.client.cio) 30 | implementation(libs.ktor.client.logging) 31 | implementation(libs.slf4j.api) 32 | implementation(libs.logback.classic) 33 | } 34 | commonTest.dependencies { 35 | implementation(libs.kotlin.test) 36 | } 37 | desktopMain.dependencies { 38 | implementation(compose.desktop.currentOs) 39 | implementation(libs.kotlinx.coroutinesSwing) 40 | } 41 | } 42 | } 43 | 44 | 45 | compose.desktop { 46 | application { 47 | mainClass = "com.nukrs.windroid.MainKt" 48 | 49 | nativeDistributions { 50 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) 51 | packageName = "WinDroid Toolbox" 52 | packageVersion = "1.0.0" 53 | description = "Android设备管理工具箱" 54 | copyright = "© 2024 WinDroid Toolbox. All rights reserved." 55 | vendor = "WinDroid Team" 56 | 57 | windows { 58 | iconFile.set(project.file("src/desktopMain/resources/icon.ico")) 59 | menuGroup = "WinDroid Toolbox" 60 | upgradeUuid = "61DAB35E-17CB-43B8-B24D-A9C57C7C6A9E" 61 | 62 | // 创建快捷方式和菜单项 63 | shortcut = true // 创建桌面快捷方式 64 | menu = true // 创建开始菜单项 65 | console = false // 不显示控制台窗口 66 | 67 | // 安装包详细信息 68 | packageName = "WinDroid Toolbox" 69 | 70 | // JVM 参数 71 | jvmArgs += listOf( 72 | "-Dfile.encoding=UTF-8", 73 | "-Dsun.java2d.uiScale=1.0" 74 | ) 75 | } 76 | 77 | linux { 78 | iconFile.set(project.file("src/desktopMain/resources/icon.png")) 79 | } 80 | 81 | macOS { 82 | iconFile.set(project.file("src/desktopMain/resources/icon.icns")) 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/nukrs/windroid/Main.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid 2 | 3 | import androidx.compose.runtime.* 4 | import androidx.compose.ui.res.painterResource 5 | import androidx.compose.ui.window.Window 6 | import androidx.compose.ui.window.WindowState 7 | import androidx.compose.ui.window.application 8 | import androidx.compose.ui.window.rememberWindowState 9 | import androidx.compose.ui.unit.dp 10 | import com.nukrs.windroid.ui.AppTray 11 | import com.nukrs.windroid.ui.components.CloseConfirmDialog 12 | import com.nukrs.windroid.ui.theme.WinDroidTheme 13 | import com.nukrs.windroid.ui.screens.MainScreen 14 | 15 | fun main() = application { 16 | var isWindowVisible by remember { mutableStateOf(true) } 17 | var showCloseDialog by remember { mutableStateOf(false) } 18 | val windowState = rememberWindowState(width = 1200.dp, height = 800.dp) 19 | 20 | // 系统托盘 21 | AppTray( 22 | onShowWindow = { 23 | isWindowVisible = true 24 | }, 25 | onExit = { 26 | exitApplication() 27 | } 28 | ) 29 | 30 | if (isWindowVisible) { 31 | Window( 32 | onCloseRequest = { 33 | showCloseDialog = true 34 | }, 35 | title = "WinDroid Toolbox - Android刷机工具箱", 36 | icon = painterResource("tray_icon.svg"), 37 | state = windowState, 38 | visible = isWindowVisible 39 | ) { 40 | WinDroidTheme { 41 | MainScreen() 42 | 43 | // 关闭确认对话框 44 | if (showCloseDialog) { 45 | CloseConfirmDialog( 46 | onDismiss = { 47 | showCloseDialog = false 48 | }, 49 | onMinimizeToTray = { 50 | showCloseDialog = false 51 | isWindowVisible = false 52 | }, 53 | onExit = { 54 | exitApplication() 55 | } 56 | ) 57 | } 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/nukrs/windroid/core/service/DeviceService.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid.core.service 2 | 3 | import com.nukrs.windroid.data.model.DeviceInfo 4 | import com.nukrs.windroid.data.model.Tuple4 5 | import kotlinx.coroutines.flow.MutableStateFlow 6 | import kotlinx.coroutines.flow.StateFlow 7 | import kotlinx.coroutines.flow.asStateFlow 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.withContext 10 | import java.io.File 11 | import java.util.concurrent.TimeUnit 12 | 13 | class DeviceService { 14 | private val _deviceInfo = MutableStateFlow(DeviceInfo()) 15 | val deviceInfo: StateFlow = _deviceInfo.asStateFlow() 16 | 17 | private val _isScanning = MutableStateFlow(false) 18 | val isScanning: StateFlow = _isScanning.asStateFlow() 19 | 20 | private val _connectedDevices = MutableStateFlow>(emptyList()) 21 | val connectedDevices: StateFlow> = _connectedDevices.asStateFlow() 22 | 23 | // Fastboot设备列表 24 | private val _fastbootDevices = MutableStateFlow>(emptyList()) 25 | val fastbootDevices: StateFlow> = _fastbootDevices.asStateFlow() 26 | 27 | // ADB工具路径 28 | private val adbPath = getAdbPath() 29 | 30 | // Fastboot工具路径 31 | private val fastbootPath = getFastbootPath() 32 | 33 | private fun getFastbootPath(): String { 34 | // 首先尝试使用项目内置的Fastboot 35 | val resourcesPath = System.getProperty("compose.application.resources.dir") ?: "" 36 | val bundledFastboot = File(resourcesPath, "platform-tools/fastboot.exe") 37 | 38 | return if (bundledFastboot.exists()) { 39 | bundledFastboot.absolutePath 40 | } else { 41 | // 回退到系统PATH中的Fastboot 42 | "fastboot" 43 | } 44 | } 45 | 46 | private fun getAdbPath(): String { 47 | // 首先尝试使用项目内置的ADB 48 | val resourcesPath = System.getProperty("compose.application.resources.dir") ?: "" 49 | val bundledAdb = File(resourcesPath, "platform-tools/adb.exe") 50 | 51 | return if (bundledAdb.exists()) { 52 | bundledAdb.absolutePath 53 | } else { 54 | // 回退到系统PATH中的ADB 55 | "adb" 56 | } 57 | } 58 | 59 | suspend fun scanForDevices() { 60 | _isScanning.value = true 61 | try { 62 | val output = executeAdbCommand("devices") 63 | val devices = parseDevicesOutput(output) 64 | _connectedDevices.value = devices 65 | 66 | // 如果有设备连接,获取第一个设备的详细信息 67 | if (devices.isNotEmpty()) { 68 | val deviceInfo = getDeviceInfo(devices.first()) 69 | if (deviceInfo != null) { 70 | _deviceInfo.value = deviceInfo 71 | } 72 | } else { 73 | _deviceInfo.value = DeviceInfo() 74 | } 75 | } catch (e: Exception) { 76 | println("扫描设备时出错: ${e.message}") 77 | _connectedDevices.value = emptyList() 78 | _deviceInfo.value = DeviceInfo() 79 | } finally { 80 | _isScanning.value = false 81 | } 82 | } 83 | 84 | /** 85 | * 扫描Fastboot设备 86 | */ 87 | suspend fun scanForFastbootDevices() { 88 | _isScanning.value = true 89 | try { 90 | val output = executeFastbootCommand("devices") 91 | val devices = parseFastbootDevicesOutput(output) 92 | _fastbootDevices.value = devices 93 | println("检测到Fastboot设备: $devices") 94 | } catch (e: Exception) { 95 | println("扫描Fastboot设备时出错: ${e.message}") 96 | _fastbootDevices.value = emptyList() 97 | } finally { 98 | _isScanning.value = false 99 | } 100 | } 101 | 102 | private fun parseFastbootDevicesOutput(output: String): List { 103 | return output.lines() 104 | .filter { it.isNotBlank() && it.contains("\t") } 105 | .map { line -> 106 | line.split("\t")[0].trim() 107 | } 108 | .filter { it.isNotEmpty() } 109 | } 110 | 111 | private fun parseDevicesOutput(output: String): List { 112 | return output.lines() 113 | .drop(1) // 跳过第一行 "List of devices attached" 114 | .filter { it.isNotBlank() && it.contains("\t") } 115 | .map { line -> 116 | line.split("\t")[0].trim() 117 | } 118 | .filter { it.isNotEmpty() } 119 | } 120 | 121 | private fun parseCpuBrandAndModel(rawCpuModel: String): Pair { 122 | val model = rawCpuModel.lowercase() 123 | 124 | return when { 125 | // 高通 126 | model.contains("qualcomm") || 127 | model.contains("snapdragon") || 128 | model.contains("qcom") || 129 | model.contains("msm") || 130 | model.contains("sdm") || 131 | model.contains("sm") || 132 | model.contains("kona") || 133 | model.contains("lahaina") || 134 | model.contains("taro") || 135 | model.contains("kalama") || 136 | model.contains("pineapple") -> { 137 | val brand = "高通" 138 | val chipModel = when { 139 | model.contains("snapdragon") -> { 140 | // 提取骁龙型号 141 | val snapdragonRegex = "snapdragon\\s*(\\d+)".toRegex() 142 | val match = snapdragonRegex.find(model) 143 | if (match != null) { 144 | "骁龙 ${match.groupValues[1]}" 145 | } else { 146 | "骁龙处理器" 147 | } 148 | } 149 | model.contains("msm") -> { 150 | // MSM系列 151 | val msmRegex = "msm(\\d+)".toRegex() 152 | val match = msmRegex.find(model) 153 | if (match != null) { 154 | val msmNumber = match.groupValues[1] 155 | val snapdragonModel = mapMsmToSnapdragon(msmNumber) 156 | if (snapdragonModel.isNotEmpty()) { 157 | "骁龙 $snapdragonModel" 158 | } else { 159 | "MSM$msmNumber" 160 | } 161 | } else { 162 | "MSM处理器" 163 | } 164 | } 165 | model.contains("sdm") -> { 166 | // SDM系列 167 | val sdmRegex = "sdm(\\d+)".toRegex() 168 | val match = sdmRegex.find(model) 169 | if (match != null) { 170 | val sdmNumber = match.groupValues[1] 171 | "骁龙 $sdmNumber" 172 | } else { 173 | "SDM处理器" 174 | } 175 | } 176 | model.contains("sm") -> { 177 | // SM系列 178 | val smRegex = "sm(\\d+)".toRegex() 179 | val match = smRegex.find(model) 180 | if (match != null) { 181 | val smNumber = match.groupValues[1] 182 | val snapdragonModel = mapSmToSnapdragon(smNumber) 183 | if (snapdragonModel.isNotEmpty()) { 184 | "骁龙 $snapdragonModel" 185 | } else { 186 | "SM$smNumber" 187 | } 188 | } else { 189 | "SM处理器" 190 | } 191 | } 192 | // 代号识别 193 | model.contains("sun") -> "骁龙 8 Elite Gen 1 (sun)" 194 | model.contains("pineapple") -> "骁龙 8 Gen 3 (pineapple)" 195 | model.contains("kona") -> "骁龙 865 (kona)" 196 | model.contains("lahaina") -> "骁龙 888 (lahaina)" 197 | model.contains("taro") -> "骁龙 8 Gen 1 (taro)" 198 | model.contains("kalama") -> "骁龙 8 Gen 2 (kalama)" 199 | model.contains("qcom") -> { 200 | // 如果只有qcom,尝试获取更多信息 201 | "高通处理器" 202 | } 203 | else -> "处理器" 204 | } 205 | Pair(brand, chipModel) 206 | } 207 | 208 | // 联发科处理器 209 | model.contains("mediatek") || model.contains("mtk") || model.contains("mt") -> { 210 | val brand = "联发科" 211 | val chipModel = when { 212 | model.contains("dimensity") -> { 213 | // 天玑系列 214 | val dimensityRegex = "dimensity\\s*(\\d+)".toRegex() 215 | val match = dimensityRegex.find(model) 216 | if (match != null) { 217 | "天玑 ${match.groupValues[1]}" 218 | } else { 219 | "天玑处理器" 220 | } 221 | } 222 | model.contains("helio") -> { 223 | // Helio系列 224 | val helioRegex = "helio\\s*([a-z]\\d+)".toRegex() 225 | val match = helioRegex.find(model) 226 | if (match != null) { 227 | "Helio ${match.groupValues[1].uppercase()}" 228 | } else { 229 | "Helio处理器" 230 | } 231 | } 232 | model.contains("mt") -> { 233 | // MT系列 234 | val mtRegex = "mt(\\d+)".toRegex() 235 | val match = mtRegex.find(model) 236 | if (match != null) { 237 | "MT${match.groupValues[1]}" 238 | } else { 239 | "MT处理器" 240 | } 241 | } 242 | else -> "处理器" 243 | } 244 | Pair(brand, chipModel) 245 | } 246 | 247 | // 华为海思 248 | model.contains("hisilicon") || model.contains("kirin") -> { 249 | val brand = "华为海思" 250 | val chipModel = when { 251 | model.contains("kirin") -> { 252 | val kirinRegex = "kirin\\s*(\\d+)".toRegex() 253 | val match = kirinRegex.find(model) 254 | if (match != null) { 255 | "麒麟 ${match.groupValues[1]}" 256 | } else { 257 | "麒麟处理器" 258 | } 259 | } 260 | else -> "处理器" 261 | } 262 | Pair(brand, chipModel) 263 | } 264 | 265 | // 三星Exynos 266 | model.contains("exynos") -> { 267 | val brand = "三星" 268 | val exynosRegex = "exynos\\s*(\\d+)".toRegex() 269 | val match = exynosRegex.find(model) 270 | val chipModel = if (match != null) { 271 | "Exynos ${match.groupValues[1]}" 272 | } else { 273 | "Exynos处理器" 274 | } 275 | Pair(brand, chipModel) 276 | } 277 | 278 | // 紫光展锐 279 | model.contains("unisoc") || model.contains("spreadtrum") -> { 280 | val brand = "紫光展锐" 281 | val chipModel = when { 282 | model.contains("tiger") -> "Tiger处理器" 283 | model.contains("sc") -> { 284 | val scRegex = "sc(\\d+)".toRegex() 285 | val match = scRegex.find(model) 286 | if (match != null) { 287 | "SC${match.groupValues[1]}" 288 | } else { 289 | "SC处理器" 290 | } 291 | } 292 | else -> "处理器" 293 | } 294 | Pair(brand, chipModel) 295 | } 296 | 297 | // 其他或未知 298 | else -> { 299 | // 尝试从原始字符串中提取有用信息 300 | val cleanModel = rawCpuModel.replace(Regex("[^a-zA-Z0-9\\s]"), "").trim() 301 | if (cleanModel.isNotEmpty() && cleanModel != "未知处理器") { 302 | Pair("未知品牌", cleanModel) 303 | } else { 304 | Pair("未知品牌", "未知型号") 305 | } 306 | } 307 | } 308 | } 309 | 310 | // MSM编号到骁龙型号的映射 311 | private fun mapMsmToSnapdragon(msmNumber: String): String { 312 | return when (msmNumber) { 313 | "8998" -> "835" 314 | "8996" -> "820/821" 315 | "8994" -> "810" 316 | "8992" -> "808" 317 | "8974" -> "801" 318 | "8960" -> "S4 Pro" 319 | "8660" -> "S3" 320 | "8255" -> "S2" 321 | "7227" -> "S1" 322 | else -> "" 323 | } 324 | } 325 | 326 | // SM编号到骁龙型号的映射 327 | private fun mapSmToSnapdragon(smNumber: String): String { 328 | return when (smNumber) { 329 | "8750" -> "8 Elite Gen 1" 330 | "8650" -> "8 Gen 3" 331 | "8550" -> "8 Gen 2" 332 | "8475" -> "8+ Gen 1" 333 | "8450" -> "8 Gen 1" 334 | "8350" -> "888" 335 | "8250" -> "865" 336 | "8150" -> "855" 337 | "7325" -> "778G" 338 | "7225" -> "750G" 339 | "6375" -> "695" 340 | "6350" -> "690" 341 | else -> "" 342 | } 343 | } 344 | 345 | suspend fun getDeviceInfo(deviceId: String): DeviceInfo? { 346 | return try { 347 | val model = executeAdbCommand("-s $deviceId shell getprop ro.product.model").trim() 348 | val manufacturer = executeAdbCommand("-s $deviceId shell getprop ro.product.manufacturer").trim() 349 | val androidVersion = executeAdbCommand("-s $deviceId shell getprop ro.build.version.release").trim() 350 | val serialNumber = executeAdbCommand("-s $deviceId shell getprop ro.serialno").trim() 351 | val buildNumber = executeAdbCommand("-s $deviceId shell getprop ro.build.display.id").trim() 352 | val securityPatch = executeAdbCommand("-s $deviceId shell getprop ro.build.version.security_patch").trim() 353 | 354 | // 检查Bootloader状态 355 | val bootloaderStatus = try { 356 | val unlockStatus = executeAdbCommand("-s $deviceId shell getprop ro.boot.verifiedbootstate").trim() 357 | when (unlockStatus.lowercase()) { 358 | "orange" -> "已解锁" 359 | "yellow" -> "部分解锁" 360 | "green" -> "已锁定" 361 | else -> "未知" 362 | } 363 | } catch (e: Exception) { 364 | "未知" 365 | } 366 | 367 | // 检查Root状态 368 | val isRooted = try { 369 | val suResult = executeAdbCommand("-s $deviceId shell which su").trim() 370 | suResult.isNotEmpty() && !suResult.contains("not found") 371 | } catch (e: Exception) { 372 | false 373 | } 374 | 375 | // 获取开机时间 376 | val uptime = try { 377 | val uptimeSeconds = executeAdbCommand("-s $deviceId shell cat /proc/uptime").trim().split(" ")[0].toDouble() 378 | val hours = (uptimeSeconds / 3600).toInt() 379 | val minutes = ((uptimeSeconds % 3600) / 60).toInt() 380 | "${hours}小时${minutes}分钟" 381 | } catch (e: Exception) { 382 | "未知" 383 | } 384 | 385 | // 获取存储信息 386 | val (totalStorage, availableStorage, usedStorage, storagePercentage) = try { 387 | val dfOutput = executeAdbCommand("-s $deviceId shell df /data").trim() 388 | val lines = dfOutput.lines() 389 | if (lines.size >= 2) { 390 | val dataLine = lines[1].split("\\s+".toRegex()) 391 | if (dataLine.size >= 4) { 392 | val totalKb = dataLine[1].toLong() 393 | val usedKb = dataLine[2].toLong() 394 | val availableKb = dataLine[3].toLong() 395 | 396 | val totalGb = totalKb / 1024.0 / 1024.0 397 | val usedGb = usedKb / 1024.0 / 1024.0 398 | val availableGb = availableKb / 1024.0 / 1024.0 399 | val percentage = (usedGb / totalGb * 100).toInt() 400 | 401 | val total = String.format("%.1f GB", totalGb) 402 | val used = String.format("%.1f GB", usedGb) 403 | val available = String.format("%.1f GB", availableGb) 404 | 405 | Tuple4(total, available, used, percentage) 406 | } else { 407 | Tuple4("未知", "未知", "未知", 0) 408 | } 409 | } else { 410 | Tuple4("未知", "未知", "未知", 0) 411 | } 412 | } catch (e: Exception) { 413 | Tuple4("未知", "未知", "未知", 0) 414 | } 415 | 416 | // 获取安装的应用数量 417 | val installedApps = try { 418 | val pmOutput = executeAdbCommand("-s $deviceId shell pm list packages").trim() 419 | pmOutput.lines().count { it.startsWith("package:") } 420 | } catch (e: Exception) { 421 | 0 422 | } 423 | 424 | // 获取CPU信息 425 | val cpuInfo = try { 426 | val cpuInfoOutput = executeAdbCommand("-s $deviceId shell cat /proc/cpuinfo").trim() 427 | println("CPU信息原始输出: $cpuInfoOutput") // 调试信息 428 | val lines = cpuInfoOutput.lines() 429 | 430 | // 优先从系统属性获取平台代号信息 431 | val platformProp = executeAdbCommand("-s $deviceId shell getprop ro.board.platform").trim() 432 | val hardwareProp = executeAdbCommand("-s $deviceId shell getprop ro.hardware").trim() 433 | val chipsetProp = executeAdbCommand("-s $deviceId shell getprop ro.chipname").trim() 434 | 435 | println("硬件属性: $hardwareProp") // 调试信息 436 | println("平台属性: $platformProp") // 调试信息 437 | println("芯片属性: $chipsetProp") // 调试信息 438 | 439 | // 尝试获取CPU型号 440 | val modelLine = lines.find { 441 | it.startsWith("model name") || 442 | it.startsWith("Processor") || 443 | it.startsWith("Hardware") || 444 | it.startsWith("cpu model") || 445 | it.startsWith("Model") 446 | } 447 | 448 | // 优先使用平台代号,其次是硬件属性,最后是CPU信息 449 | val rawCpuModel = when { 450 | platformProp.isNotEmpty() && platformProp != "unknown" -> platformProp 451 | hardwareProp.isNotEmpty() && hardwareProp != "unknown" -> hardwareProp 452 | chipsetProp.isNotEmpty() && chipsetProp != "unknown" -> chipsetProp 453 | modelLine != null -> modelLine.substringAfter(":").trim() 454 | else -> "未知处理器" 455 | } 456 | 457 | println("提取的CPU型号: $rawCpuModel") // 调试信息 458 | 459 | // 解析处理器品牌和型号 460 | val (brand, model) = parseCpuBrandAndModel(rawCpuModel) 461 | 462 | // 获取CPU核心数 463 | val coreCount = lines.count { it.startsWith("processor") } 464 | 465 | // 获取CPU频率 466 | val maxFreq = try { 467 | val freqOutput = executeAdbCommand("-s $deviceId shell cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq").trim() 468 | val freqMhz = freqOutput.toLongOrNull()?.div(1000) ?: 0 469 | if (freqMhz > 0) { 470 | val freqGhz = freqMhz / 1000.0 471 | String.format("%.1f GHz", freqGhz) 472 | } else { 473 | "" 474 | } 475 | } catch (e: Exception) { 476 | "" 477 | } 478 | 479 | // 组合CPU信息 480 | buildString { 481 | append("$brand $model") 482 | if (coreCount > 0) { 483 | append(" (${coreCount}核)") 484 | } 485 | if (maxFreq.isNotEmpty()) { 486 | append(" @ $maxFreq") 487 | } 488 | } 489 | } catch (e: Exception) { 490 | println("获取CPU信息失败: ${e.message}") // 调试信息 491 | "未知" 492 | } 493 | 494 | // 获取RAM信息 495 | val ramInfo = try { 496 | val memInfoOutput = executeAdbCommand("-s $deviceId shell cat /proc/meminfo").trim() 497 | val totalMemLine = memInfoOutput.lines().find { it.startsWith("MemTotal:") } 498 | if (totalMemLine != null) { 499 | val totalKb = totalMemLine.split("\\s+".toRegex())[1].toLong() 500 | val totalGb = totalKb / 1024 / 1024 501 | "${totalGb}GB" 502 | } else { 503 | "未知" 504 | } 505 | } catch (e: Exception) { 506 | "未知" 507 | } 508 | 509 | // 获取电池电量 510 | val batteryLevel = try { 511 | val batteryOutput = executeAdbCommand("-s $deviceId shell dumpsys battery").trim() 512 | val levelLine = batteryOutput.lines().find { it.contains("level:") } 513 | if (levelLine != null) { 514 | val level = levelLine.substringAfter("level:").trim() 515 | "$level%" 516 | } else { 517 | "未知" 518 | } 519 | } catch (e: Exception) { 520 | "未知" 521 | } 522 | 523 | DeviceInfo( 524 | model = model.ifEmpty { "未知型号" }, 525 | manufacturer = manufacturer.ifEmpty { "未知厂商" }, 526 | androidVersion = androidVersion.ifEmpty { "未知版本" }, 527 | serialNumber = serialNumber.ifEmpty { deviceId }, 528 | buildNumber = buildNumber.ifEmpty { "未知" }, 529 | bootloaderStatus = bootloaderStatus, 530 | isRooted = isRooted, 531 | isConnected = true, 532 | uptime = uptime, 533 | totalStorage = totalStorage, 534 | availableStorage = availableStorage, 535 | usedStorage = usedStorage, 536 | storagePercentage = storagePercentage, 537 | installedApps = installedApps, 538 | securityPatch = securityPatch.ifEmpty { "未知" }, 539 | cpuInfo = cpuInfo, 540 | ramInfo = ramInfo, 541 | batteryLevel = batteryLevel 542 | ) 543 | } catch (e: Exception) { 544 | println("获取设备信息时出错: ${e.message}") 545 | null 546 | } 547 | } 548 | 549 | suspend fun rebootToFastboot(deviceId: String): Boolean { 550 | return try { 551 | executeAdbCommand("-s $deviceId shell su -c 'reboot bootloader'") 552 | true 553 | } catch (e: Exception) { 554 | println("重启到Fastboot失败: ${e.message}") 555 | false 556 | } 557 | } 558 | 559 | suspend fun rebootToRecovery(deviceId: String): Boolean { 560 | return try { 561 | executeAdbCommand("-s $deviceId shell su -c 'reboot recovery'") 562 | true 563 | } catch (e: Exception) { 564 | println("重启到Recovery失败: ${e.message}") 565 | false 566 | } 567 | } 568 | 569 | suspend fun rebootToSystem(deviceId: String): Boolean { 570 | return try { 571 | executeAdbCommand("-s $deviceId shell su -c 'reboot'") 572 | true 573 | } catch (e: Exception) { 574 | println("重启到系统失败: ${e.message}") 575 | false 576 | } 577 | } 578 | 579 | /** 580 | * 执行自定义ADB命令 581 | * @param command ADB命令(不包含adb前缀) 582 | * @return 命令执行结果 583 | */ 584 | suspend fun executeCustomCommand(command: String): String { 585 | return try { 586 | executeAdbCommand(command) 587 | } catch (e: Exception) { 588 | "错误: ${e.message}" 589 | } 590 | } 591 | 592 | /** 593 | * 执行自定义Fastboot命令 594 | * @param command Fastboot命令(不包含fastboot前缀) 595 | * @return 命令执行结果 596 | */ 597 | suspend fun executeCustomFastbootCommand(command: String): String { 598 | return try { 599 | executeFastbootCommand(command) 600 | } catch (e: Exception) { 601 | "错误: ${e.message}" 602 | } 603 | } 604 | 605 | private suspend fun executeFastbootCommand(command: String): String = withContext(Dispatchers.IO) { 606 | try { 607 | val fullCommand = "$fastbootPath $command" 608 | println("执行Fastboot命令: $fullCommand") 609 | 610 | val process = ProcessBuilder() 611 | .command(fullCommand.split(" ")) 612 | .redirectErrorStream(true) 613 | .start() 614 | 615 | val result = process.inputStream.bufferedReader().readText() 616 | val exitCode = process.waitFor(30, TimeUnit.SECONDS) 617 | 618 | if (!exitCode) { 619 | process.destroyForcibly() 620 | throw RuntimeException("命令执行超时") 621 | } 622 | 623 | if (process.exitValue() != 0) { 624 | throw RuntimeException("命令执行失败: $result") 625 | } 626 | 627 | result 628 | } catch (e: Exception) { 629 | throw RuntimeException("执行Fastboot命令失败: ${e.message}", e) 630 | } 631 | } 632 | 633 | private suspend fun executeAdbCommand(command: String): String = withContext(Dispatchers.IO) { 634 | try { 635 | val fullCommand = "$adbPath $command" 636 | println("执行命令: $fullCommand") 637 | 638 | val process = ProcessBuilder() 639 | .command(fullCommand.split(" ")) 640 | .redirectErrorStream(true) 641 | .start() 642 | 643 | val result = process.inputStream.bufferedReader().readText() 644 | val exitCode = process.waitFor(30, TimeUnit.SECONDS) 645 | 646 | if (!exitCode) { 647 | process.destroyForcibly() 648 | throw RuntimeException("命令执行超时") 649 | } 650 | 651 | if (process.exitValue() != 0) { 652 | throw RuntimeException("命令执行失败: $result") 653 | } 654 | 655 | result 656 | } catch (e: Exception) { 657 | throw RuntimeException("执行ADB命令失败: ${e.message}", e) 658 | } 659 | } 660 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/nukrs/windroid/core/service/RebootService.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid.core.service 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.withContext 5 | import java.io.BufferedReader 6 | import java.io.InputStreamReader 7 | 8 | /** 9 | * 重启服务 - 处理设备的高级重启选项 10 | */ 11 | class RebootService { 12 | 13 | enum class RebootMode(val command: String, val displayName: String, val description: String) { 14 | NORMAL("reboot", "正常重启", "重启到系统"), 15 | RECOVERY("reboot recovery", "Recovery模式", "重启到Recovery恢复模式"), 16 | BOOTLOADER("reboot bootloader", "Bootloader模式", "重启到Fastboot/Download模式"), 17 | FASTBOOT("reboot fastboot", "Fastboot模式", "重启到Fastboot模式"), 18 | DOWNLOAD("reboot download", "Download模式", "重启到Download模式"), 19 | SAFE_MODE("reboot safemode", "安全模式", "重启到安全模式"), 20 | POWER_OFF("reboot -p", "关机", "关闭设备电源") 21 | } 22 | 23 | /** 24 | * 检查设备是否已Root 25 | */ 26 | suspend fun checkRootStatus(deviceId: String): Boolean { 27 | return withContext(Dispatchers.IO) { 28 | try { 29 | val process = ProcessBuilder("adb", "-s", deviceId, "shell", "su", "-c", "id") 30 | .redirectErrorStream(true) 31 | .start() 32 | 33 | val reader = BufferedReader(InputStreamReader(process.inputStream)) 34 | val output = reader.readText() 35 | reader.close() 36 | 37 | val exitCode = process.waitFor() 38 | 39 | // 如果命令成功执行且输出包含uid=0,说明有root权限 40 | exitCode == 0 && output.contains("uid=0") 41 | } catch (e: Exception) { 42 | println("检查Root状态失败: ${e.message}") 43 | false 44 | } 45 | } 46 | } 47 | 48 | /** 49 | * 执行重启命令 50 | */ 51 | suspend fun executeReboot(deviceId: String, mode: RebootMode): RebootResult { 52 | return withContext(Dispatchers.IO) { 53 | try { 54 | // 所有重启命令都使用su权限执行 55 | val command = listOf("adb", "-s", deviceId, "shell", "su", "-c", mode.command) 56 | 57 | println("执行重启命令: ${command.joinToString(" ")}") 58 | 59 | val process = ProcessBuilder(command) 60 | .redirectErrorStream(true) 61 | .start() 62 | 63 | val reader = BufferedReader(InputStreamReader(process.inputStream)) 64 | val output = reader.readText() 65 | reader.close() 66 | 67 | val exitCode = process.waitFor() 68 | 69 | if (exitCode == 0) { 70 | RebootResult.Success(mode, "重启命令执行成功") 71 | } else { 72 | val errorMsg = if (output.contains("not found") || output.contains("su:")) { 73 | "需要Root权限才能执行此操作" 74 | } else { 75 | "重启命令执行失败: $output" 76 | } 77 | RebootResult.Error(mode, errorMsg) 78 | } 79 | } catch (e: Exception) { 80 | println("执行重启命令失败: ${e.message}") 81 | RebootResult.Error(mode, "执行重启命令时发生错误: ${e.message}") 82 | } 83 | } 84 | } 85 | 86 | /** 87 | * 获取可用的重启选项(根据设备状态) 88 | */ 89 | suspend fun getAvailableRebootModes(deviceId: String): List { 90 | val isRooted = checkRootStatus(deviceId) 91 | 92 | return if (isRooted) { 93 | // Root设备可以使用所有重启选项 94 | RebootMode.values().toList() 95 | } else { 96 | // 非Root设备无法使用任何重启选项(因为现在都需要su权限) 97 | emptyList() 98 | } 99 | } 100 | } 101 | 102 | /** 103 | * 重启操作结果 104 | */ 105 | sealed class RebootResult { 106 | data class Success(val mode: RebootService.RebootMode, val message: String) : RebootResult() 107 | data class Error(val mode: RebootService.RebootMode, val error: String) : RebootResult() 108 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/nukrs/windroid/data/model/Models.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid.data.model 2 | 3 | import androidx.compose.ui.graphics.vector.ImageVector 4 | import kotlinx.serialization.Serializable 5 | 6 | // 辅助数据类 7 | data class Tuple4(val first: A, val second: B, val third: C, val fourth: D) 8 | 9 | @Serializable 10 | data class FlashingTool( 11 | val id: String, 12 | val name: String, 13 | val description: String, 14 | @kotlinx.serialization.Transient 15 | val icon: ImageVector? = null, 16 | val category: String, 17 | val isEnabled: Boolean = true, 18 | val version: String = "1.0.0" 19 | ) 20 | 21 | @Serializable 22 | data class DeviceInfo( 23 | val model: String = "未连接", 24 | val manufacturer: String = "未知", 25 | val androidVersion: String = "未知", 26 | val buildNumber: String = "未知", 27 | val bootloaderStatus: String = "未知", 28 | val isRooted: Boolean = false, 29 | val serialNumber: String = "未知", 30 | val isConnected: Boolean = false, 31 | // 新增字段 32 | val uptime: String = "未知", 33 | val totalStorage: String = "未知", 34 | val availableStorage: String = "未知", 35 | val usedStorage: String = "未知", 36 | val storagePercentage: Int = 0, 37 | val installedApps: Int = 0, 38 | val securityPatch: String = "未知", 39 | val cpuInfo: String = "未知", 40 | val ramInfo: String = "未知", 41 | val batteryLevel: String = "未知" 42 | ) 43 | 44 | @Serializable 45 | data class FlashingTask( 46 | val id: String, 47 | val toolId: String, 48 | val deviceId: String, 49 | val status: TaskStatus, 50 | val progress: Float = 0f, 51 | val logs: List = emptyList(), 52 | val startTime: Long = System.currentTimeMillis(), 53 | val endTime: Long? = null 54 | ) 55 | 56 | enum class TaskStatus { 57 | PENDING, 58 | RUNNING, 59 | SUCCESS, 60 | FAILED, 61 | CANCELLED 62 | } 63 | 64 | @Serializable 65 | data class AppSettings( 66 | val adbPath: String = "", 67 | val fastbootPath: String = "", 68 | val odinPath: String = "", 69 | val spFlashToolPath: String = "", 70 | val autoDetectDevices: Boolean = true, 71 | val enableLogging: Boolean = true, 72 | val logLevel: String = "INFO", 73 | val theme: String = "SYSTEM" 74 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/nukrs/windroid/ui/AppTray.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid.ui 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.window.ApplicationScope 5 | 6 | @Composable 7 | expect fun ApplicationScope.AppTray( 8 | onShowWindow: () -> Unit, 9 | onExit: () -> Unit 10 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/nukrs/windroid/ui/components/AboutButton.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid.ui.components 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.foundation.shape.RoundedCornerShape 5 | import androidx.compose.material.icons.Icons 6 | import androidx.compose.material.icons.filled.Info 7 | import androidx.compose.material.icons.filled.Language 8 | import androidx.compose.material.icons.filled.Code 9 | import androidx.compose.material.icons.filled.Chat 10 | import androidx.compose.material3.* 11 | import androidx.compose.runtime.* 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.platform.LocalUriHandler 15 | import androidx.compose.ui.text.font.FontWeight 16 | import androidx.compose.ui.unit.dp 17 | import androidx.compose.ui.window.Dialog 18 | 19 | /** 20 | * 关于按钮组件 21 | * 显示应用信息和相关链接 22 | */ 23 | @Composable 24 | fun AboutButton( 25 | modifier: Modifier = Modifier 26 | ) { 27 | var showDialog by remember { mutableStateOf(false) } 28 | 29 | IconButton( 30 | onClick = { showDialog = true }, 31 | modifier = modifier 32 | ) { 33 | Icon( 34 | imageVector = Icons.Default.Info, 35 | contentDescription = "关于", 36 | tint = MaterialTheme.colorScheme.onSurface 37 | ) 38 | } 39 | 40 | if (showDialog) { 41 | AboutDialog( 42 | onDismiss = { showDialog = false } 43 | ) 44 | } 45 | } 46 | 47 | @Composable 48 | private fun AboutDialog( 49 | onDismiss: () -> Unit 50 | ) { 51 | val uriHandler = LocalUriHandler.current 52 | 53 | Dialog(onDismissRequest = onDismiss) { 54 | Card( 55 | modifier = Modifier 56 | .fillMaxWidth() 57 | .padding(16.dp), 58 | shape = RoundedCornerShape(16.dp), 59 | elevation = CardDefaults.cardElevation(defaultElevation = 8.dp) 60 | ) { 61 | Column( 62 | modifier = Modifier 63 | .fillMaxWidth() 64 | .padding(24.dp), 65 | horizontalAlignment = Alignment.CenterHorizontally 66 | ) { 67 | // 应用图标和标题 68 | Icon( 69 | imageVector = Icons.Default.Info, 70 | contentDescription = null, 71 | modifier = Modifier.size(48.dp), 72 | tint = MaterialTheme.colorScheme.primary 73 | ) 74 | 75 | Spacer(modifier = Modifier.height(16.dp)) 76 | 77 | Text( 78 | text = "WinDroid Toolbox", 79 | style = MaterialTheme.typography.headlineSmall, 80 | fontWeight = FontWeight.Bold, 81 | color = MaterialTheme.colorScheme.onSurface 82 | ) 83 | 84 | Text( 85 | text = "版本 1.0.0", 86 | style = MaterialTheme.typography.bodyMedium, 87 | color = MaterialTheme.colorScheme.onSurfaceVariant, 88 | modifier = Modifier.padding(top = 4.dp) 89 | ) 90 | 91 | Spacer(modifier = Modifier.height(24.dp)) 92 | 93 | Text( 94 | text = "一个功能强大的 Android 工具箱", 95 | style = MaterialTheme.typography.bodyMedium, 96 | color = MaterialTheme.colorScheme.onSurfaceVariant, 97 | modifier = Modifier.padding(bottom = 24.dp) 98 | ) 99 | 100 | // 链接按钮 101 | Column( 102 | modifier = Modifier.fillMaxWidth(), 103 | verticalArrangement = Arrangement.spacedBy(8.dp) 104 | ) { 105 | // 官网链接 106 | OutlinedButton( 107 | onClick = { 108 | uriHandler.openUri("https://nukrs.com") 109 | }, 110 | modifier = Modifier.fillMaxWidth() 111 | ) { 112 | Icon( 113 | imageVector = Icons.Default.Language, 114 | contentDescription = null, 115 | modifier = Modifier.size(18.dp) 116 | ) 117 | Spacer(modifier = Modifier.width(8.dp)) 118 | Text("访问官网 (nukrs.com)") 119 | } 120 | 121 | // GitHub 链接 122 | OutlinedButton( 123 | onClick = { 124 | uriHandler.openUri("https://github.com/Nukrs/WinDroid-ToolBox") 125 | }, 126 | modifier = Modifier.fillMaxWidth() 127 | ) { 128 | Icon( 129 | imageVector = Icons.Default.Code, 130 | contentDescription = null, 131 | modifier = Modifier.size(18.dp) 132 | ) 133 | Spacer(modifier = Modifier.width(8.dp)) 134 | Text("查看源码 (GitHub)") 135 | } 136 | 137 | // Telegram 链接 138 | OutlinedButton( 139 | onClick = { 140 | uriHandler.openUri("https://t.me/windroidtoolbox") 141 | }, 142 | modifier = Modifier.fillMaxWidth() 143 | ) { 144 | Icon( 145 | imageVector = Icons.Default.Chat, 146 | contentDescription = null, 147 | modifier = Modifier.size(18.dp) 148 | ) 149 | Spacer(modifier = Modifier.width(8.dp)) 150 | Text("加入群组 (Telegram)") 151 | } 152 | } 153 | 154 | Spacer(modifier = Modifier.height(24.dp)) 155 | 156 | // 关闭按钮 157 | TextButton( 158 | onClick = onDismiss, 159 | modifier = Modifier.align(Alignment.End) 160 | ) { 161 | Text("关闭") 162 | } 163 | } 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/nukrs/windroid/ui/components/AdbToolPanel.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid.ui.components 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.foundation.lazy.LazyColumn 5 | import androidx.compose.foundation.lazy.LazyRow 6 | import androidx.compose.foundation.lazy.items 7 | import androidx.compose.foundation.rememberScrollState 8 | import androidx.compose.foundation.verticalScroll 9 | import androidx.compose.material.icons.Icons 10 | import androidx.compose.material.icons.filled.* 11 | import androidx.compose.material3.* 12 | import androidx.compose.runtime.* 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.text.font.FontFamily 16 | import androidx.compose.ui.text.font.FontWeight 17 | import androidx.compose.ui.unit.dp 18 | import com.nukrs.windroid.core.service.DeviceService 19 | import kotlinx.coroutines.launch 20 | 21 | /** 22 | * ADB工具面板 23 | * 提供完整的ADB功能界面 24 | */ 25 | @Composable 26 | fun AdbToolPanel( 27 | deviceService: DeviceService, 28 | modifier: Modifier = Modifier 29 | ) { 30 | var selectedDevice by remember { mutableStateOf(null) } 31 | var commandOutput by remember { mutableStateOf("") } 32 | var isExecuting by remember { mutableStateOf(false) } 33 | var customCommand by remember { mutableStateOf("") } 34 | 35 | val connectedDevices by deviceService.connectedDevices.collectAsState() 36 | val scope = rememberCoroutineScope() 37 | 38 | // 每次打开ADB页面时自动扫描设备 39 | LaunchedEffect(Unit) { 40 | commandOutput += "=== ADB工具已启动 ===\n" 41 | commandOutput += "正在自动扫描设备...\n" 42 | deviceService.scanForDevices() 43 | commandOutput += "设备扫描完成\n" 44 | } 45 | 46 | Card( 47 | modifier = modifier.fillMaxSize(), 48 | elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) 49 | ) { 50 | Column( 51 | modifier = Modifier 52 | .fillMaxSize() 53 | .padding(24.dp) 54 | ) { 55 | // 标题区域 56 | Row( 57 | verticalAlignment = Alignment.CenterVertically, 58 | modifier = Modifier.padding(bottom = 24.dp) 59 | ) { 60 | Icon( 61 | imageVector = Icons.Default.DeveloperMode, 62 | contentDescription = null, 63 | modifier = Modifier.size(32.dp), 64 | tint = MaterialTheme.colorScheme.primary 65 | ) 66 | Spacer(modifier = Modifier.width(12.dp)) 67 | Text( 68 | text = "ADB 调试工具", 69 | style = MaterialTheme.typography.headlineMedium, 70 | fontWeight = FontWeight.Bold 71 | ) 72 | } 73 | 74 | Row( 75 | modifier = Modifier.fillMaxSize() 76 | ) { 77 | // 左侧控制面板 78 | Column( 79 | modifier = Modifier 80 | .weight(1f) 81 | .padding(end = 16.dp) 82 | ) { 83 | // 设备选择区域 84 | DeviceSelectionSection( 85 | devices = connectedDevices, 86 | selectedDevice = selectedDevice, 87 | onDeviceSelected = { selectedDevice = it }, 88 | onRefreshDevices = { 89 | scope.launch { 90 | deviceService.scanForDevices() 91 | } 92 | } 93 | ) 94 | 95 | Spacer(modifier = Modifier.height(24.dp)) 96 | 97 | // 自定义命令区域 98 | CustomCommandSection( 99 | customCommand = customCommand, 100 | onCommandChange = { customCommand = it }, 101 | selectedDevice = selectedDevice, 102 | deviceService = deviceService, 103 | isExecuting = isExecuting, 104 | onExecutionStart = { isExecuting = true }, 105 | onExecutionEnd = { isExecuting = false }, 106 | onOutputUpdate = { output -> 107 | commandOutput += "$output\n" 108 | } 109 | ) 110 | } 111 | 112 | // 右侧输出区域 113 | Card( 114 | modifier = Modifier 115 | .weight(1f) 116 | .fillMaxHeight(), 117 | colors = CardDefaults.cardColors( 118 | containerColor = MaterialTheme.colorScheme.surfaceVariant 119 | ) 120 | ) { 121 | Column( 122 | modifier = Modifier 123 | .fillMaxSize() 124 | .padding(16.dp) 125 | ) { 126 | Row( 127 | modifier = Modifier.fillMaxWidth(), 128 | horizontalArrangement = Arrangement.SpaceBetween, 129 | verticalAlignment = Alignment.CenterVertically 130 | ) { 131 | Text( 132 | text = "命令输出", 133 | style = MaterialTheme.typography.titleMedium, 134 | fontWeight = FontWeight.Medium 135 | ) 136 | 137 | IconButton( 138 | onClick = { commandOutput = "" } 139 | ) { 140 | Icon( 141 | imageVector = Icons.Default.Clear, 142 | contentDescription = "清空输出", 143 | tint = MaterialTheme.colorScheme.onSurfaceVariant 144 | ) 145 | } 146 | } 147 | 148 | Spacer(modifier = Modifier.height(8.dp)) 149 | 150 | Card( 151 | modifier = Modifier.fillMaxSize(), 152 | colors = CardDefaults.cardColors( 153 | containerColor = MaterialTheme.colorScheme.surface 154 | ) 155 | ) { 156 | Text( 157 | text = if (commandOutput.isEmpty()) "等待命令执行..." else commandOutput, 158 | modifier = Modifier 159 | .fillMaxSize() 160 | .padding(12.dp) 161 | .verticalScroll(rememberScrollState()), 162 | style = MaterialTheme.typography.bodySmall, 163 | fontFamily = FontFamily.Monospace, 164 | color = if (commandOutput.isEmpty()) 165 | MaterialTheme.colorScheme.onSurfaceVariant 166 | else 167 | MaterialTheme.colorScheme.onSurface 168 | ) 169 | } 170 | } 171 | } 172 | } 173 | } 174 | } 175 | } 176 | 177 | @Composable 178 | private fun DeviceSelectionSection( 179 | devices: List, 180 | selectedDevice: String?, 181 | onDeviceSelected: (String?) -> Unit, 182 | onRefreshDevices: () -> Unit 183 | ) { 184 | Card( 185 | colors = CardDefaults.cardColors( 186 | containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f) 187 | ) 188 | ) { 189 | Column( 190 | modifier = Modifier 191 | .fillMaxWidth() 192 | .padding(16.dp) 193 | ) { 194 | Row( 195 | modifier = Modifier.fillMaxWidth(), 196 | horizontalArrangement = Arrangement.SpaceBetween, 197 | verticalAlignment = Alignment.CenterVertically 198 | ) { 199 | Text( 200 | text = "连接的设备", 201 | style = MaterialTheme.typography.titleMedium, 202 | fontWeight = FontWeight.Medium 203 | ) 204 | 205 | IconButton(onClick = onRefreshDevices) { 206 | Icon( 207 | imageVector = Icons.Default.Refresh, 208 | contentDescription = "刷新设备列表" 209 | ) 210 | } 211 | } 212 | 213 | Spacer(modifier = Modifier.height(12.dp)) 214 | 215 | if (devices.isEmpty()) { 216 | Text( 217 | text = "未检测到设备", 218 | style = MaterialTheme.typography.bodyMedium, 219 | color = MaterialTheme.colorScheme.onSurfaceVariant 220 | ) 221 | } else { 222 | LazyColumn( 223 | modifier = Modifier.heightIn(max = 120.dp), 224 | verticalArrangement = Arrangement.spacedBy(4.dp) 225 | ) { 226 | items(devices) { device -> 227 | FilterChip( 228 | onClick = { 229 | onDeviceSelected(if (selectedDevice == device) null else device) 230 | }, 231 | label = { Text(device) }, 232 | selected = selectedDevice == device, 233 | leadingIcon = { 234 | Icon( 235 | imageVector = Icons.Default.PhoneAndroid, 236 | contentDescription = null, 237 | modifier = Modifier.size(16.dp) 238 | ) 239 | } 240 | ) 241 | } 242 | } 243 | } 244 | } 245 | } 246 | } 247 | 248 | 249 | @Composable 250 | private fun CustomCommandSection( 251 | customCommand: String, 252 | onCommandChange: (String) -> Unit, 253 | selectedDevice: String?, 254 | deviceService: DeviceService, 255 | isExecuting: Boolean, 256 | onExecutionStart: () -> Unit, 257 | onExecutionEnd: () -> Unit, 258 | onOutputUpdate: (String) -> Unit 259 | ) { 260 | val scope = rememberCoroutineScope() 261 | 262 | Card( 263 | colors = CardDefaults.cardColors( 264 | containerColor = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.3f) 265 | ) 266 | ) { 267 | Column( 268 | modifier = Modifier 269 | .fillMaxWidth() 270 | .padding(16.dp) 271 | ) { 272 | Text( 273 | text = "ADB命令执行", 274 | style = MaterialTheme.typography.titleMedium, 275 | fontWeight = FontWeight.Medium, 276 | modifier = Modifier.padding(bottom = 12.dp) 277 | ) 278 | 279 | OutlinedTextField( 280 | value = customCommand, 281 | onValueChange = onCommandChange, 282 | label = { Text("ADB命令 (不需要输入adb前缀)") }, 283 | placeholder = { Text("例如: devices, shell, install app.apk") }, 284 | modifier = Modifier.fillMaxWidth(), 285 | enabled = !isExecuting, 286 | singleLine = false, 287 | maxLines = 3 288 | ) 289 | 290 | Spacer(modifier = Modifier.height(12.dp)) 291 | 292 | Button( 293 | onClick = { 294 | if (customCommand.isNotBlank()) { 295 | scope.launch { 296 | onExecutionStart() 297 | try { 298 | // 检查是否是shell命令 299 | if (customCommand.trim().lowercase() == "shell") { 300 | onOutputUpdate("=== 打开ADB Shell ===") 301 | onOutputUpdate("正在启动PowerShell窗口执行 adb shell...") 302 | 303 | // 构建命令 304 | val shellCommand = if (selectedDevice != null) { 305 | "adb -s $selectedDevice shell" 306 | } else { 307 | "adb shell" 308 | } 309 | 310 | try { 311 | // 在新的PowerShell窗口中执行adb shell 312 | val processBuilder = ProcessBuilder( 313 | "powershell.exe", 314 | "-Command", 315 | "Start-Process powershell -ArgumentList '-NoExit', '-Command', '$shellCommand'" 316 | ) 317 | processBuilder.start() 318 | onOutputUpdate("PowerShell窗口已打开,您可以在新窗口中进行交互式操作") 319 | onOutputUpdate("提示: 输入 'exit' 退出shell") 320 | } catch (e: Exception) { 321 | onOutputUpdate("打开PowerShell失败: ${e.message}") 322 | } 323 | } else { 324 | // 普通命令执行 325 | onOutputUpdate("=== 执行ADB命令 ===") 326 | onOutputUpdate("命令: adb $customCommand") 327 | 328 | val result = deviceService.executeCustomCommand(customCommand) 329 | onOutputUpdate("输出:") 330 | onOutputUpdate(result) 331 | } 332 | } catch (e: Exception) { 333 | onOutputUpdate("错误: ${e.message}") 334 | } finally { 335 | onExecutionEnd() 336 | } 337 | } 338 | } 339 | }, 340 | enabled = customCommand.isNotBlank() && !isExecuting, 341 | modifier = Modifier.fillMaxWidth() 342 | ) { 343 | if (isExecuting) { 344 | CircularProgressIndicator( 345 | modifier = Modifier.size(16.dp), 346 | strokeWidth = 2.dp 347 | ) 348 | Spacer(modifier = Modifier.width(8.dp)) 349 | Text("执行中...") 350 | } else { 351 | Icon( 352 | imageVector = if (customCommand.trim().lowercase() == "shell") { 353 | Icons.Default.Terminal 354 | } else { 355 | Icons.Default.PlayArrow 356 | }, 357 | contentDescription = null, 358 | modifier = Modifier.size(16.dp) 359 | ) 360 | Spacer(modifier = Modifier.width(8.dp)) 361 | Text( 362 | if (customCommand.trim().lowercase() == "shell") { 363 | "打开Shell窗口" 364 | } else { 365 | "执行命令" 366 | } 367 | ) 368 | } 369 | } 370 | 371 | // 添加常用命令快捷按钮 372 | Spacer(modifier = Modifier.height(12.dp)) 373 | 374 | Text( 375 | text = "常用命令", 376 | style = MaterialTheme.typography.labelMedium, 377 | color = MaterialTheme.colorScheme.onSurfaceVariant, 378 | modifier = Modifier.padding(bottom = 8.dp) 379 | ) 380 | 381 | LazyRow( 382 | horizontalArrangement = Arrangement.spacedBy(8.dp) 383 | ) { 384 | val commonCommands = listOf( 385 | "devices" to "设备列表", 386 | "shell" to "Shell", 387 | "logcat" to "日志", 388 | "install" to "安装", 389 | "uninstall" to "卸载" 390 | ) 391 | 392 | items(commonCommands) { (command, label) -> 393 | FilterChip( 394 | onClick = { onCommandChange(command) }, 395 | label = { Text(label) }, 396 | selected = false, 397 | enabled = !isExecuting 398 | ) 399 | } 400 | } 401 | } 402 | } 403 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/nukrs/windroid/ui/components/CloseConfirmDialog.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid.ui.components 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.material.icons.Icons 5 | import androidx.compose.material.icons.filled.Close 6 | import androidx.compose.material.icons.filled.Minimize 7 | import androidx.compose.material.icons.filled.Warning 8 | import androidx.compose.material3.* 9 | import androidx.compose.runtime.* 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.text.font.FontWeight 13 | import androidx.compose.ui.unit.dp 14 | import androidx.compose.ui.window.Dialog 15 | import androidx.compose.ui.window.DialogProperties 16 | 17 | @Composable 18 | fun CloseConfirmDialog( 19 | onDismiss: () -> Unit, 20 | onMinimizeToTray: () -> Unit, 21 | onExit: () -> Unit 22 | ) { 23 | Dialog( 24 | onDismissRequest = onDismiss, 25 | properties = DialogProperties( 26 | dismissOnBackPress = true, 27 | dismissOnClickOutside = true 28 | ) 29 | ) { 30 | Card( 31 | modifier = Modifier 32 | .width(400.dp) 33 | .padding(16.dp), 34 | elevation = CardDefaults.cardElevation(defaultElevation = 8.dp) 35 | ) { 36 | Column( 37 | modifier = Modifier.padding(24.dp), 38 | horizontalAlignment = Alignment.CenterHorizontally 39 | ) { 40 | // 图标 41 | Icon( 42 | imageVector = Icons.Default.Warning, 43 | contentDescription = null, 44 | modifier = Modifier.size(48.dp), 45 | tint = MaterialTheme.colorScheme.primary 46 | ) 47 | 48 | Spacer(modifier = Modifier.height(16.dp)) 49 | 50 | // 标题 51 | Text( 52 | text = "关闭应用程序", 53 | style = MaterialTheme.typography.headlineSmall, 54 | fontWeight = FontWeight.Bold 55 | ) 56 | 57 | Spacer(modifier = Modifier.height(8.dp)) 58 | 59 | // 描述 60 | Text( 61 | text = "您希望如何处理应用程序?", 62 | style = MaterialTheme.typography.bodyMedium, 63 | color = MaterialTheme.colorScheme.onSurfaceVariant 64 | ) 65 | 66 | Spacer(modifier = Modifier.height(24.dp)) 67 | 68 | // 按钮组 69 | Column( 70 | modifier = Modifier.fillMaxWidth(), 71 | verticalArrangement = Arrangement.spacedBy(8.dp) 72 | ) { 73 | // 最小化到托盘按钮 74 | Button( 75 | onClick = onMinimizeToTray, 76 | modifier = Modifier.fillMaxWidth(), 77 | colors = ButtonDefaults.buttonColors( 78 | containerColor = MaterialTheme.colorScheme.primary 79 | ) 80 | ) { 81 | Icon( 82 | imageVector = Icons.Default.Minimize, 83 | contentDescription = null, 84 | modifier = Modifier.size(18.dp) 85 | ) 86 | Spacer(modifier = Modifier.width(8.dp)) 87 | Text("最小化到系统托盘") 88 | } 89 | 90 | // 完全退出按钮 91 | OutlinedButton( 92 | onClick = onExit, 93 | modifier = Modifier.fillMaxWidth(), 94 | colors = ButtonDefaults.outlinedButtonColors( 95 | contentColor = MaterialTheme.colorScheme.error 96 | ) 97 | ) { 98 | Icon( 99 | imageVector = Icons.Default.Close, 100 | contentDescription = null, 101 | modifier = Modifier.size(18.dp) 102 | ) 103 | Spacer(modifier = Modifier.width(8.dp)) 104 | Text("完全退出应用") 105 | } 106 | 107 | // 取消按钮 108 | TextButton( 109 | onClick = onDismiss, 110 | modifier = Modifier.fillMaxWidth() 111 | ) { 112 | Text("取消") 113 | } 114 | } 115 | } 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/nukrs/windroid/ui/components/DeviceOverviewPanel.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid.ui.components 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.* 6 | import androidx.compose.foundation.lazy.LazyColumn 7 | import androidx.compose.foundation.shape.RoundedCornerShape 8 | import androidx.compose.material.icons.Icons 9 | import androidx.compose.material.icons.filled.* 10 | import androidx.compose.material3.* 11 | import androidx.compose.runtime.* 12 | import androidx.compose.runtime.collectAsState 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.graphics.Color 16 | import androidx.compose.ui.graphics.vector.ImageVector 17 | import androidx.compose.ui.layout.ContentScale 18 | import androidx.compose.ui.res.painterResource 19 | import androidx.compose.ui.text.font.FontWeight 20 | import androidx.compose.ui.unit.dp 21 | import com.nukrs.windroid.data.model.DeviceInfo 22 | import com.nukrs.windroid.core.service.DeviceService 23 | import com.nukrs.windroid.core.service.RebootService 24 | import com.nukrs.windroid.ui.theme.MacchiatoColors 25 | import kotlinx.coroutines.delay 26 | import kotlinx.coroutines.GlobalScope 27 | import kotlinx.coroutines.launch 28 | 29 | @Composable 30 | fun DeviceOverviewPanel( 31 | modifier: Modifier = Modifier, 32 | deviceService: DeviceService = remember { DeviceService() } 33 | ) { 34 | val deviceInfo by deviceService.deviceInfo.collectAsState() 35 | val connectedDevices by deviceService.connectedDevices.collectAsState() 36 | val isScanning by deviceService.isScanning.collectAsState() 37 | var isRefreshing by remember { mutableStateOf(false) } 38 | 39 | // 自动刷新设备信息 40 | LaunchedEffect(Unit) { 41 | deviceService.scanForDevices() 42 | while (true) { 43 | delay(5 * 60 * 1000L) // 每5分钟刷新一次 44 | deviceService.scanForDevices() 45 | } 46 | } 47 | 48 | val refreshDeviceInfo: () -> Unit = { 49 | GlobalScope.launch { 50 | isRefreshing = true 51 | try { 52 | // 重新扫描设备 53 | deviceService.scanForDevices() 54 | // 等待扫描完成 55 | delay(500) 56 | 57 | // 如果有连接的设备,重新获取设备详细信息 58 | val currentDevices = deviceService.connectedDevices.value 59 | if (currentDevices.isNotEmpty()) { 60 | val deviceInfo = deviceService.getDeviceInfo(currentDevices.first()) 61 | if (deviceInfo != null) { 62 | // 这里设备信息会通过 StateFlow 自动更新UI 63 | println("设备信息已刷新: ${deviceInfo.model}") 64 | } 65 | } 66 | } catch (e: Exception) { 67 | println("刷新设备信息失败: ${e.message}") 68 | } finally { 69 | isRefreshing = false 70 | } 71 | } 72 | } 73 | 74 | Card( 75 | modifier = modifier.fillMaxSize().padding(16.dp), 76 | elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), 77 | colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) 78 | ) { 79 | Column( 80 | modifier = Modifier.fillMaxSize().padding(24.dp) 81 | ) { 82 | // 标题栏 83 | Row( 84 | modifier = Modifier.fillMaxWidth(), 85 | horizontalArrangement = Arrangement.SpaceBetween, 86 | verticalAlignment = Alignment.CenterVertically 87 | ) { 88 | Text( 89 | text = "设备概览", 90 | style = MaterialTheme.typography.headlineMedium, 91 | fontWeight = FontWeight.Bold, 92 | color = MaterialTheme.colorScheme.primary 93 | ) 94 | 95 | IconButton( 96 | onClick = refreshDeviceInfo, 97 | enabled = !isRefreshing 98 | ) { 99 | Icon( 100 | imageVector = Icons.Default.Refresh, 101 | contentDescription = "刷新设备信息", 102 | tint = if (isRefreshing) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface 103 | ) 104 | } 105 | } 106 | 107 | Spacer(modifier = Modifier.height(16.dp)) 108 | 109 | if (isScanning) { 110 | Box( 111 | modifier = Modifier.fillMaxSize(), 112 | contentAlignment = Alignment.Center 113 | ) { 114 | Column( 115 | horizontalAlignment = Alignment.CenterHorizontally 116 | ) { 117 | CircularProgressIndicator() 118 | Spacer(modifier = Modifier.height(16.dp)) 119 | Text("正在获取设备信息...") 120 | } 121 | } 122 | } else if (!deviceInfo.isConnected || connectedDevices.isEmpty()) { 123 | Box( 124 | modifier = Modifier.fillMaxSize(), 125 | contentAlignment = Alignment.Center 126 | ) { 127 | Column( 128 | horizontalAlignment = Alignment.CenterHorizontally 129 | ) { 130 | Icon( 131 | imageVector = Icons.Default.PhoneAndroid, 132 | contentDescription = "未检测到设备", 133 | modifier = Modifier.size(64.dp), 134 | tint = MaterialTheme.colorScheme.outline 135 | ) 136 | Spacer(modifier = Modifier.height(16.dp)) 137 | Text( 138 | text = "未检测到设备", 139 | style = MaterialTheme.typography.titleLarge, 140 | color = MaterialTheme.colorScheme.outline 141 | ) 142 | Text( 143 | text = "请连接Android设备并开启USB调试", 144 | style = MaterialTheme.typography.bodyMedium, 145 | color = MaterialTheme.colorScheme.outline 146 | ) 147 | } 148 | } 149 | } else { 150 | LazyColumn( 151 | modifier = Modifier.fillMaxSize(), 152 | verticalArrangement = Arrangement.spacedBy(16.dp) 153 | ) { 154 | item { 155 | DeviceHeaderCard(deviceInfo) 156 | } 157 | 158 | item { 159 | SystemInfoCard(deviceInfo, connectedDevices) 160 | } 161 | 162 | item { 163 | StorageInfoCard(deviceInfo) 164 | } 165 | 166 | item { 167 | SecurityInfoCard(deviceInfo) 168 | } 169 | 170 | item { 171 | HardwareInfoCard(deviceInfo) 172 | } 173 | } 174 | } 175 | } 176 | } 177 | } 178 | 179 | @Composable 180 | private fun DeviceHeaderCard(deviceInfo: DeviceInfo) { 181 | Card( 182 | modifier = Modifier.fillMaxWidth(), 183 | colors = CardDefaults.cardColors( 184 | containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.8f) 185 | ), 186 | elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) 187 | ) { 188 | Row( 189 | modifier = Modifier.fillMaxWidth().padding(20.dp), 190 | verticalAlignment = Alignment.CenterVertically 191 | ) { 192 | // 设备Logo 193 | val logoResource = getDeviceLogo(deviceInfo.manufacturer, deviceInfo.model) 194 | if (logoResource != null) { 195 | Surface( 196 | modifier = Modifier.size(56.dp), // 增大Logo尺寸 197 | shape = RoundedCornerShape(16.dp), // 增大圆角 198 | color = Color.White.copy(alpha = 0.9f) 199 | ) { 200 | Image( 201 | painter = painterResource(logoResource), 202 | contentDescription = "${deviceInfo.manufacturer} Logo", 203 | modifier = Modifier 204 | .fillMaxSize() 205 | .padding(6.dp), 206 | contentScale = ContentScale.Fit 207 | ) 208 | } 209 | Spacer(modifier = Modifier.width(16.dp)) 210 | } else { 211 | // 通用设备图标 212 | Surface( 213 | modifier = Modifier.size(56.dp), 214 | shape = RoundedCornerShape(16.dp), 215 | color = MaterialTheme.colorScheme.primary.copy(alpha = 0.2f) 216 | ) { 217 | Box( 218 | modifier = Modifier.fillMaxSize(), 219 | contentAlignment = Alignment.Center 220 | ) { 221 | Icon( 222 | imageVector = Icons.Default.PhoneAndroid, 223 | contentDescription = "设备", 224 | modifier = Modifier.size(28.dp), 225 | tint = MaterialTheme.colorScheme.primary 226 | ) 227 | } 228 | } 229 | Spacer(modifier = Modifier.width(16.dp)) 230 | } 231 | 232 | Column(modifier = Modifier.weight(1f)) { 233 | Text( 234 | text = deviceInfo.model, 235 | style = MaterialTheme.typography.headlineSmall, 236 | fontWeight = FontWeight.Bold, 237 | color = MaterialTheme.colorScheme.onPrimaryContainer 238 | ) 239 | Text( 240 | text = deviceInfo.manufacturer, 241 | style = MaterialTheme.typography.bodyLarge, 242 | color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.8f) 243 | ) 244 | Text( 245 | text = "Android ${deviceInfo.androidVersion}", 246 | style = MaterialTheme.typography.bodyMedium, 247 | color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.6f) 248 | ) 249 | } 250 | } 251 | } 252 | } 253 | 254 | @Composable 255 | private fun SystemInfoCard(deviceInfo: DeviceInfo, connectedDevices: List) { 256 | val rebootService = remember { RebootService() } 257 | var showRebootMenu by remember { mutableStateOf(false) } 258 | var availableRebootModes by remember { mutableStateOf>(emptyList()) } 259 | var isLoadingRebootModes by remember { mutableStateOf(false) } 260 | 261 | // 获取当前设备ID 262 | val currentDeviceId = connectedDevices.firstOrNull() ?: "" 263 | 264 | // 加载可用的重启选项 265 | LaunchedEffect(currentDeviceId) { 266 | if (currentDeviceId.isNotEmpty()) { 267 | isLoadingRebootModes = true 268 | availableRebootModes = rebootService.getAvailableRebootModes(currentDeviceId) 269 | isLoadingRebootModes = false 270 | } 271 | } 272 | 273 | InfoCard( 274 | title = "系统信息", 275 | icon = Icons.Default.Info 276 | ) { 277 | // 高级重启选项按钮 278 | Row( 279 | modifier = Modifier.fillMaxWidth(), 280 | horizontalArrangement = Arrangement.End 281 | ) { 282 | Box { 283 | IconButton( 284 | onClick = { showRebootMenu = true }, 285 | enabled = !isLoadingRebootModes && availableRebootModes.isNotEmpty() 286 | ) { 287 | Icon( 288 | imageVector = Icons.Default.PowerSettingsNew, 289 | contentDescription = "高级重启选项", 290 | tint = if (availableRebootModes.isNotEmpty()) 291 | MaterialTheme.colorScheme.primary 292 | else 293 | MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f), 294 | modifier = Modifier.size(20.dp) 295 | ) 296 | } 297 | 298 | // 重启选项下拉菜单 299 | DropdownMenu( 300 | expanded = showRebootMenu, 301 | onDismissRequest = { showRebootMenu = false } 302 | ) { 303 | availableRebootModes.forEach { mode -> 304 | DropdownMenuItem( 305 | text = { 306 | Column { 307 | Text( 308 | text = mode.displayName, 309 | style = MaterialTheme.typography.bodyMedium, 310 | fontWeight = FontWeight.Medium 311 | ) 312 | Text( 313 | text = mode.description, 314 | style = MaterialTheme.typography.bodySmall, 315 | color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f) 316 | ) 317 | } 318 | }, 319 | onClick = { 320 | showRebootMenu = false 321 | // 执行重启操作 322 | if (currentDeviceId.isNotEmpty()) { 323 | // 在协程中执行重启操作 324 | kotlinx.coroutines.GlobalScope.launch { 325 | try { 326 | rebootService.executeReboot(currentDeviceId, mode) 327 | } catch (e: Exception) { 328 | println("重启操作失败: ${e.message}") 329 | } 330 | } 331 | } 332 | }, 333 | leadingIcon = { 334 | Icon( 335 | imageVector = when (mode) { 336 | RebootService.RebootMode.NORMAL -> Icons.Default.Refresh 337 | RebootService.RebootMode.RECOVERY -> Icons.Default.Build 338 | RebootService.RebootMode.BOOTLOADER -> Icons.Default.DeveloperMode 339 | RebootService.RebootMode.FASTBOOT -> Icons.Default.FlashOn 340 | RebootService.RebootMode.DOWNLOAD -> Icons.Default.Download 341 | RebootService.RebootMode.SAFE_MODE -> Icons.Default.Security 342 | RebootService.RebootMode.POWER_OFF -> Icons.Default.PowerOff 343 | }, 344 | contentDescription = mode.displayName, 345 | modifier = Modifier.size(18.dp) 346 | ) 347 | } 348 | ) 349 | } 350 | } 351 | } 352 | } 353 | 354 | // Root权限提示 355 | if (availableRebootModes.isEmpty()) { 356 | Text( 357 | text = "需要Root权限", 358 | style = MaterialTheme.typography.labelSmall, 359 | color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f), 360 | modifier = Modifier.padding(bottom = 8.dp) 361 | ) 362 | } 363 | 364 | // 系统运行状态 365 | SystemStatusSection(deviceInfo) 366 | 367 | Spacer(modifier = Modifier.height(12.dp)) 368 | 369 | // CPU信息 370 | ProcessorInfoRow(deviceInfo.cpuInfo) 371 | 372 | Spacer(modifier = Modifier.height(12.dp)) 373 | 374 | // 系统详细信息 375 | InfoRow("构建版本", deviceInfo.buildNumber, Icons.Default.Build) 376 | InfoRow("安全补丁", deviceInfo.securityPatch, Icons.Default.Security) 377 | InfoRow("序列号", deviceInfo.serialNumber, Icons.Default.Tag) 378 | InfoRow("已安装应用", "${deviceInfo.installedApps} 个", Icons.Default.Apps) 379 | } 380 | } 381 | 382 | @Composable 383 | private fun SystemStatusSection(deviceInfo: DeviceInfo) { 384 | Card( 385 | modifier = Modifier.fillMaxWidth(), 386 | colors = CardDefaults.cardColors( 387 | containerColor = Color(0xFF8AADF4).copy(alpha = 0.1f) // Macchiato blue background 388 | ), 389 | elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) 390 | ) { 391 | Column( 392 | modifier = Modifier.fillMaxWidth().padding(12.dp) 393 | ) { 394 | Row( 395 | modifier = Modifier.fillMaxWidth(), 396 | verticalAlignment = Alignment.CenterVertically 397 | ) { 398 | Icon( 399 | imageVector = Icons.Default.Schedule, 400 | contentDescription = "运行状态", 401 | modifier = Modifier.size(18.dp), 402 | tint = Color(0xFF8AADF4) // Macchiato blue 403 | ) 404 | Spacer(modifier = Modifier.width(8.dp)) 405 | Text( 406 | text = "系统运行状态", 407 | style = MaterialTheme.typography.bodyMedium, 408 | fontWeight = FontWeight.SemiBold, 409 | color = MaterialTheme.colorScheme.onSurface 410 | ) 411 | } 412 | 413 | Spacer(modifier = Modifier.height(8.dp)) 414 | 415 | Row( 416 | modifier = Modifier.fillMaxWidth(), 417 | horizontalArrangement = Arrangement.SpaceBetween, 418 | verticalAlignment = Alignment.CenterVertically 419 | ) { 420 | Column { 421 | Text( 422 | text = "开机时间", 423 | style = MaterialTheme.typography.bodySmall, 424 | color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f) 425 | ) 426 | Text( 427 | text = deviceInfo.uptime, 428 | style = MaterialTheme.typography.bodyLarge, 429 | fontWeight = FontWeight.Medium, 430 | color = MaterialTheme.colorScheme.onSurface 431 | ) 432 | } 433 | 434 | // 运行状态指示器 435 | Surface( 436 | color = Color(0xFFA6DA95), // Macchiato green 437 | shape = RoundedCornerShape(16.dp) 438 | ) { 439 | Row( 440 | modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp), 441 | verticalAlignment = Alignment.CenterVertically 442 | ) { 443 | Box( 444 | modifier = Modifier 445 | .size(8.dp) 446 | .background( 447 | Color.White, 448 | shape = RoundedCornerShape(4.dp) 449 | ) 450 | ) 451 | Spacer(modifier = Modifier.width(6.dp)) 452 | Text( 453 | text = "运行中", 454 | style = MaterialTheme.typography.labelSmall, 455 | fontWeight = FontWeight.Medium, 456 | color = Color.White 457 | ) 458 | } 459 | } 460 | } 461 | } 462 | } 463 | } 464 | 465 | @Composable 466 | private fun StorageInfoCard(deviceInfo: DeviceInfo) { 467 | InfoCard( 468 | title = "存储信息", 469 | icon = Icons.Default.Storage 470 | ) { 471 | InfoRow("总存储", deviceInfo.totalStorage, Icons.Default.Storage) 472 | InfoRow("可用空间", deviceInfo.availableStorage, Icons.Default.FolderOpen) 473 | InfoRow("已用空间", deviceInfo.usedStorage, Icons.Default.Folder) 474 | } 475 | } 476 | 477 | @Composable 478 | private fun SecurityInfoCard(deviceInfo: DeviceInfo) { 479 | InfoCard( 480 | title = "安全状态", 481 | icon = Icons.Default.Security 482 | ) { 483 | InfoRow("Bootloader", deviceInfo.bootloaderStatus, Icons.Default.Lock) 484 | InfoRow( 485 | "Root状态", 486 | if (deviceInfo.isRooted) "已Root" else "未Root", 487 | if (deviceInfo.isRooted) Icons.Default.Warning else Icons.Default.CheckCircle 488 | ) 489 | } 490 | } 491 | 492 | @Composable 493 | private fun HardwareInfoCard(deviceInfo: DeviceInfo) { 494 | InfoCard( 495 | title = "硬件信息", 496 | icon = Icons.Default.Memory 497 | ) { 498 | InfoRow("内存", deviceInfo.ramInfo, Icons.Default.Memory) 499 | InfoRow("电池电量", deviceInfo.batteryLevel, Icons.Default.BatteryFull) 500 | } 501 | } 502 | 503 | @Composable 504 | private fun ProcessorInfoRow(cpuInfo: String) { 505 | Card( 506 | modifier = Modifier.fillMaxWidth(), 507 | colors = CardDefaults.cardColors( 508 | containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f) 509 | ), 510 | elevation = CardDefaults.cardElevation(defaultElevation = 0.dp) 511 | ) { 512 | Column( 513 | modifier = Modifier.fillMaxWidth().padding(12.dp) 514 | ) { 515 | Row( 516 | modifier = Modifier.fillMaxWidth(), 517 | verticalAlignment = Alignment.CenterVertically 518 | ) { 519 | // 处理器品牌图标 520 | val (brandIcon, brandColor) = getProcessorBrandInfo(cpuInfo) 521 | Icon( 522 | imageVector = brandIcon, 523 | contentDescription = "处理器", 524 | modifier = Modifier.size(20.dp), 525 | tint = brandColor 526 | ) 527 | Spacer(modifier = Modifier.width(8.dp)) 528 | Text( 529 | text = "处理器", 530 | style = MaterialTheme.typography.bodyMedium, 531 | fontWeight = FontWeight.SemiBold, 532 | color = MaterialTheme.colorScheme.onPrimaryContainer 533 | ) 534 | } 535 | 536 | Spacer(modifier = Modifier.height(8.dp)) 537 | 538 | // 处理器详细信息 539 | Text( 540 | text = cpuInfo, 541 | style = MaterialTheme.typography.bodyLarge, 542 | fontWeight = FontWeight.Medium, 543 | color = MaterialTheme.colorScheme.onPrimaryContainer 544 | ) 545 | 546 | // 如果包含品牌信息,显示品牌标签 547 | val brandLabel = getBrandLabel(cpuInfo) 548 | if (brandLabel.isNotEmpty()) { 549 | Spacer(modifier = Modifier.height(6.dp)) 550 | Surface( 551 | color = getBrandColor(cpuInfo).copy(alpha = 0.2f), 552 | shape = RoundedCornerShape(12.dp) 553 | ) { 554 | Text( 555 | text = brandLabel, 556 | modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp), 557 | style = MaterialTheme.typography.labelSmall, 558 | fontWeight = FontWeight.Medium, 559 | color = getBrandColor(cpuInfo) 560 | ) 561 | } 562 | } 563 | } 564 | } 565 | } 566 | 567 | @Composable 568 | fun InfoCard( 569 | title: String, 570 | icon: ImageVector, 571 | content: @Composable ColumnScope.() -> Unit 572 | ) { 573 | Card( 574 | modifier = Modifier.fillMaxWidth(), 575 | colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant) 576 | ) { 577 | Column( 578 | modifier = Modifier.fillMaxWidth().padding(16.dp) 579 | ) { 580 | Row( 581 | verticalAlignment = Alignment.CenterVertically, 582 | modifier = Modifier.padding(bottom = 12.dp) 583 | ) { 584 | Icon( 585 | imageVector = icon, 586 | contentDescription = title, 587 | tint = MaterialTheme.colorScheme.primary, 588 | modifier = Modifier.size(20.dp) 589 | ) 590 | Spacer(modifier = Modifier.width(8.dp)) 591 | Text( 592 | text = title, 593 | style = MaterialTheme.typography.titleMedium, 594 | fontWeight = FontWeight.SemiBold, 595 | color = MaterialTheme.colorScheme.onSurfaceVariant 596 | ) 597 | } 598 | content() 599 | } 600 | } 601 | } 602 | 603 | @Composable 604 | fun InfoRow( 605 | label: String, 606 | value: String, 607 | icon: ImageVector 608 | ) { 609 | Row( 610 | modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), 611 | verticalAlignment = Alignment.CenterVertically 612 | ) { 613 | Icon( 614 | imageVector = icon, 615 | contentDescription = label, 616 | modifier = Modifier.size(16.dp), 617 | tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f) 618 | ) 619 | Spacer(modifier = Modifier.width(8.dp)) 620 | Text( 621 | text = label, 622 | style = MaterialTheme.typography.bodyMedium, 623 | color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.8f), 624 | modifier = Modifier.weight(1f) 625 | ) 626 | Text( 627 | text = value, 628 | style = MaterialTheme.typography.bodyMedium, 629 | fontWeight = FontWeight.Medium, 630 | color = MaterialTheme.colorScheme.onSurfaceVariant 631 | ) 632 | } 633 | } 634 | 635 | // 辅助函数 636 | fun getProcessorBrandInfo(cpuInfo: String): Pair { 637 | return when { 638 | cpuInfo.contains("高通", ignoreCase = true) -> 639 | Pair(Icons.Default.Memory, Color(0xFFA6DA95)) // Macchiato green 640 | cpuInfo.contains("联发科", ignoreCase = true) -> 641 | Pair(Icons.Default.Memory, Color(0xFF8AADF4)) // Macchiato blue 642 | cpuInfo.contains("华为海思", ignoreCase = true) -> 643 | Pair(Icons.Default.Memory, Color(0xFFF5A97F)) // Macchiato peach 644 | cpuInfo.contains("三星", ignoreCase = true) -> 645 | Pair(Icons.Default.Memory, Color(0xFFC6A0F6)) // Macchiato mauve 646 | cpuInfo.contains("紫光展锐", ignoreCase = true) -> 647 | Pair(Icons.Default.Memory, Color(0xFF8BD5CA)) // Macchiato teal 648 | else -> 649 | Pair(Icons.Default.Memory, Color(0xFFA5ADCB)) // Macchiato subtext0 650 | } 651 | } 652 | 653 | fun getBrandLabel(cpuInfo: String): String { 654 | return when { 655 | cpuInfo.contains("高通", ignoreCase = true) -> "Qualcomm" 656 | cpuInfo.contains("联发科", ignoreCase = true) -> "MediaTek" 657 | cpuInfo.contains("华为海思", ignoreCase = true) -> "HiSilicon" 658 | cpuInfo.contains("三星", ignoreCase = true) -> "Samsung" 659 | cpuInfo.contains("紫光展锐", ignoreCase = true) -> "UNISOC" 660 | else -> "" 661 | } 662 | } 663 | 664 | fun getBrandColor(cpuInfo: String): Color { 665 | return when { 666 | cpuInfo.contains("高通", ignoreCase = true) -> Color(0xFFA6DA95) // Macchiato green 667 | cpuInfo.contains("联发科", ignoreCase = true) -> Color(0xFF8AADF4) // Macchiato blue 668 | cpuInfo.contains("华为海思", ignoreCase = true) -> Color(0xFFF5A97F) // Macchiato peach 669 | cpuInfo.contains("三星", ignoreCase = true) -> Color(0xFFC6A0F6) // Macchiato mauve 670 | cpuInfo.contains("紫光展锐", ignoreCase = true) -> Color(0xFF8BD5CA) // Macchiato teal 671 | else -> Color(0xFFA5ADCB) // Macchiato subtext0 672 | } 673 | } 674 | 675 | // 获取设备Logo资源 676 | fun getDeviceLogo(manufacturer: String, model: String): String? { 677 | val manufacturerLower = manufacturer.lowercase() 678 | val modelLower = model.lowercase() 679 | 680 | return when { 681 | manufacturerLower.contains("oneplus") -> "logo/OnePlus.png" 682 | manufacturerLower.contains("samsung") -> "logo/Samsung.jpg" 683 | manufacturerLower.contains("xiaomi") -> "logo/Xiaomi.png" 684 | //gemini逻辑好怪 685 | manufacturerLower.contains("redmi") || modelLower.contains("redmi") -> "logo/Redmi.png" 686 | manufacturerLower.contains("oppo") -> "logo/Oppo.png" 687 | manufacturerLower.contains("vivo") -> "logo/Vivo.png" 688 | manufacturerLower.contains("realme") -> "logo/realme.png" 689 | manufacturerLower.contains("iqoo") || modelLower.contains("iqoo") -> "logo/iqoo.png" 690 | manufacturerLower.contains("lenovo") -> "logo/Lenovo.png" 691 | manufacturerLower.contains("motorola") -> "logo/Motorola.png" 692 | manufacturerLower.contains("sony") -> "logo/Sony.png" 693 | manufacturerLower.contains("meizu") -> "logo/meizu.png" 694 | manufacturerLower.contains("google") || modelLower.contains("pixel") -> "logo/google.png" 695 | else -> "logo/google.png" 696 | } 697 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/nukrs/windroid/ui/components/FileChooser.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid.ui.components 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.foundation.lazy.LazyColumn 5 | import androidx.compose.foundation.lazy.LazyRow 6 | import androidx.compose.foundation.lazy.items 7 | import androidx.compose.material.icons.Icons 8 | import androidx.compose.material.icons.filled.* 9 | import androidx.compose.material3.* 10 | import androidx.compose.runtime.* 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.text.font.FontWeight 14 | import androidx.compose.ui.unit.dp 15 | import androidx.compose.ui.window.Dialog 16 | import androidx.compose.ui.window.DialogProperties 17 | import kotlinx.coroutines.launch 18 | import java.io.File 19 | import javax.swing.JFileChooser 20 | import javax.swing.filechooser.FileNameExtensionFilter 21 | 22 | /** 23 | * 文件选择器对话框 24 | */ 25 | @Composable 26 | fun FileChooserDialog( 27 | title: String = "选择文件", 28 | initialDirectory: String = System.getProperty("user.home"), 29 | fileExtensions: List = emptyList(), // 例如: listOf("img", "bin", "zip") 30 | onFileSelected: (String) -> Unit, 31 | onDismiss: () -> Unit 32 | ) { 33 | var currentDirectory by remember { mutableStateOf(File(initialDirectory)) } 34 | var selectedFile by remember { mutableStateOf(null) } 35 | val scope = rememberCoroutineScope() 36 | 37 | // 获取系统所有可用的磁盘驱动器 38 | val availableDrives = remember { 39 | File.listRoots().toList() 40 | } 41 | 42 | Dialog( 43 | onDismissRequest = onDismiss, 44 | properties = DialogProperties( 45 | dismissOnBackPress = true, 46 | dismissOnClickOutside = false 47 | ) 48 | ) { 49 | Card( 50 | modifier = Modifier 51 | .fillMaxWidth(0.85f) 52 | .fillMaxHeight(0.85f), 53 | elevation = CardDefaults.cardElevation(defaultElevation = 8.dp) 54 | ) { 55 | Column( 56 | modifier = Modifier 57 | .fillMaxSize() 58 | .padding(16.dp) 59 | ) { 60 | // 标题栏 61 | Row( 62 | modifier = Modifier.fillMaxWidth(), 63 | horizontalArrangement = Arrangement.SpaceBetween, 64 | verticalAlignment = Alignment.CenterVertically 65 | ) { 66 | Text( 67 | text = title, 68 | style = MaterialTheme.typography.titleLarge, 69 | fontWeight = FontWeight.Bold 70 | ) 71 | 72 | IconButton(onClick = onDismiss) { 73 | Icon( 74 | imageVector = Icons.Default.Close, 75 | contentDescription = "关闭" 76 | ) 77 | } 78 | } 79 | 80 | Spacer(modifier = Modifier.height(8.dp)) 81 | 82 | // 磁盘驱动器选择 83 | Card( 84 | colors = CardDefaults.cardColors( 85 | containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.3f) 86 | ) 87 | ) { 88 | Column( 89 | modifier = Modifier 90 | .fillMaxWidth() 91 | .padding(12.dp) 92 | ) { 93 | Text( 94 | text = "磁盘驱动器", 95 | style = MaterialTheme.typography.labelMedium, 96 | fontWeight = FontWeight.Medium, 97 | color = MaterialTheme.colorScheme.secondary 98 | ) 99 | Spacer(modifier = Modifier.height(8.dp)) 100 | 101 | LazyRow( 102 | horizontalArrangement = Arrangement.spacedBy(8.dp) 103 | ) { 104 | items(availableDrives) { drive -> 105 | DriveItem( 106 | drive = drive, 107 | isSelected = currentDirectory.absolutePath.startsWith(drive.absolutePath), 108 | onClick = { 109 | currentDirectory = drive 110 | selectedFile = null 111 | } 112 | ) 113 | } 114 | } 115 | } 116 | } 117 | 118 | Spacer(modifier = Modifier.height(8.dp)) 119 | 120 | // 当前路径显示 121 | Card( 122 | colors = CardDefaults.cardColors( 123 | containerColor = MaterialTheme.colorScheme.surfaceVariant 124 | ) 125 | ) { 126 | Row( 127 | modifier = Modifier 128 | .fillMaxWidth() 129 | .padding(12.dp), 130 | verticalAlignment = Alignment.CenterVertically 131 | ) { 132 | Icon( 133 | imageVector = Icons.Default.Folder, 134 | contentDescription = null, 135 | modifier = Modifier.size(20.dp) 136 | ) 137 | Spacer(modifier = Modifier.width(8.dp)) 138 | Text( 139 | text = currentDirectory.absolutePath, 140 | style = MaterialTheme.typography.bodyMedium 141 | ) 142 | } 143 | } 144 | 145 | Spacer(modifier = Modifier.height(8.dp)) 146 | 147 | // 导航按钮 148 | Row( 149 | modifier = Modifier.fillMaxWidth(), 150 | horizontalArrangement = Arrangement.spacedBy(8.dp) 151 | ) { 152 | // 上级目录按钮 153 | OutlinedButton( 154 | onClick = { 155 | currentDirectory.parentFile?.let { parent -> 156 | currentDirectory = parent 157 | selectedFile = null 158 | } 159 | }, 160 | enabled = currentDirectory.parentFile != null 161 | ) { 162 | Icon( 163 | imageVector = Icons.Default.ArrowUpward, 164 | contentDescription = null, 165 | modifier = Modifier.size(16.dp) 166 | ) 167 | Spacer(modifier = Modifier.width(4.dp)) 168 | Text("上级目录") 169 | } 170 | 171 | // 主目录按钮 172 | OutlinedButton( 173 | onClick = { 174 | currentDirectory = File(System.getProperty("user.home")) 175 | selectedFile = null 176 | } 177 | ) { 178 | Icon( 179 | imageVector = Icons.Default.Home, 180 | contentDescription = null, 181 | modifier = Modifier.size(16.dp) 182 | ) 183 | Spacer(modifier = Modifier.width(4.dp)) 184 | Text("主目录") 185 | } 186 | 187 | // 桌面按钮 188 | OutlinedButton( 189 | onClick = { 190 | val desktop = File(System.getProperty("user.home"), "Desktop") 191 | if (desktop.exists()) { 192 | currentDirectory = desktop 193 | selectedFile = null 194 | } 195 | } 196 | ) { 197 | Icon( 198 | imageVector = Icons.Default.Computer, 199 | contentDescription = null, 200 | modifier = Modifier.size(16.dp) 201 | ) 202 | Spacer(modifier = Modifier.width(4.dp)) 203 | Text("桌面") 204 | } 205 | } 206 | 207 | Spacer(modifier = Modifier.height(8.dp)) 208 | 209 | // 文件列表 210 | Card( 211 | modifier = Modifier 212 | .fillMaxWidth() 213 | .weight(1f), 214 | colors = CardDefaults.cardColors( 215 | containerColor = MaterialTheme.colorScheme.surface 216 | ) 217 | ) { 218 | LazyColumn( 219 | modifier = Modifier 220 | .fillMaxSize() 221 | .padding(8.dp), 222 | verticalArrangement = Arrangement.spacedBy(4.dp) 223 | ) { 224 | val files = try { 225 | currentDirectory.listFiles()?.sortedWith(compareBy { !it.isDirectory }.thenBy { it.name.lowercase() }) ?: emptyList() 226 | } catch (e: Exception) { 227 | emptyList() 228 | } 229 | 230 | items(files) { file -> 231 | FileItem( 232 | file = file, 233 | isSelected = selectedFile == file, 234 | fileExtensions = fileExtensions, 235 | onClick = { 236 | if (file.isDirectory) { 237 | currentDirectory = file 238 | selectedFile = null 239 | } else { 240 | selectedFile = file 241 | } 242 | } 243 | ) 244 | } 245 | } 246 | } 247 | 248 | Spacer(modifier = Modifier.height(16.dp)) 249 | 250 | // 选中文件显示 251 | if (selectedFile != null) { 252 | Card( 253 | colors = CardDefaults.cardColors( 254 | containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.3f) 255 | ) 256 | ) { 257 | Row( 258 | modifier = Modifier 259 | .fillMaxWidth() 260 | .padding(12.dp), 261 | verticalAlignment = Alignment.CenterVertically 262 | ) { 263 | Icon( 264 | imageVector = Icons.Default.InsertDriveFile, 265 | contentDescription = null, 266 | modifier = Modifier.size(20.dp), 267 | tint = MaterialTheme.colorScheme.primary 268 | ) 269 | Spacer(modifier = Modifier.width(8.dp)) 270 | Column(modifier = Modifier.weight(1f)) { 271 | Text( 272 | text = "已选择: ${selectedFile!!.name}", 273 | style = MaterialTheme.typography.bodyMedium, 274 | fontWeight = FontWeight.Medium 275 | ) 276 | Text( 277 | text = "大小: ${formatFileSize(selectedFile!!.length())}", 278 | style = MaterialTheme.typography.bodySmall, 279 | color = MaterialTheme.colorScheme.onSurfaceVariant 280 | ) 281 | } 282 | } 283 | } 284 | 285 | Spacer(modifier = Modifier.height(16.dp)) 286 | } 287 | 288 | // 底部按钮 289 | Column( 290 | modifier = Modifier.fillMaxWidth(), 291 | verticalArrangement = Arrangement.spacedBy(8.dp) 292 | ) { 293 | // 系统文件选择器按钮 294 | OutlinedButton( 295 | onClick = { 296 | scope.launch { 297 | try { 298 | val fileChooser = JFileChooser().apply { 299 | currentDirectory = currentDirectory 300 | dialogTitle = title 301 | fileSelectionMode = JFileChooser.FILES_ONLY 302 | 303 | // 设置文件过滤器 304 | if (fileExtensions.isNotEmpty()) { 305 | val description = "支持的文件 (${fileExtensions.joinToString(", ") { "*.$it" }})" 306 | val filter = FileNameExtensionFilter(description, *fileExtensions.toTypedArray()) 307 | fileFilter = filter 308 | } 309 | } 310 | 311 | val result = fileChooser.showOpenDialog(null) 312 | if (result == JFileChooser.APPROVE_OPTION) { 313 | val selectedSystemFile = fileChooser.selectedFile 314 | onFileSelected(selectedSystemFile.absolutePath) 315 | } 316 | } catch (e: Exception) { 317 | println("系统文件选择器错误: ${e.message}") 318 | } 319 | } 320 | }, 321 | modifier = Modifier.fillMaxWidth() 322 | ) { 323 | Icon( 324 | imageVector = Icons.Default.FolderOpen, 325 | contentDescription = null, 326 | modifier = Modifier.size(16.dp) 327 | ) 328 | Spacer(modifier = Modifier.width(8.dp)) 329 | Text("使用系统文件选择器") 330 | } 331 | 332 | // 主要操作按钮 333 | Row( 334 | modifier = Modifier.fillMaxWidth(), 335 | horizontalArrangement = Arrangement.spacedBy(8.dp) 336 | ) { 337 | OutlinedButton( 338 | onClick = onDismiss, 339 | modifier = Modifier.weight(1f) 340 | ) { 341 | Text("取消") 342 | } 343 | 344 | Button( 345 | onClick = { 346 | selectedFile?.let { file -> 347 | onFileSelected(file.absolutePath) 348 | } 349 | }, 350 | enabled = selectedFile != null, 351 | modifier = Modifier.weight(1f) 352 | ) { 353 | Text("确定") 354 | } 355 | } 356 | } 357 | } 358 | } 359 | } 360 | } 361 | 362 | @Composable 363 | private fun DriveItem( 364 | drive: File, 365 | isSelected: Boolean, 366 | onClick: () -> Unit 367 | ) { 368 | val driveLetter = drive.absolutePath.replace("\\", "") 369 | val driveInfo = try { 370 | val totalSpace = drive.totalSpace 371 | val freeSpace = drive.freeSpace 372 | val usedSpace = totalSpace - freeSpace 373 | val usagePercent = if (totalSpace > 0) (usedSpace * 100 / totalSpace).toInt() else 0 374 | 375 | Triple(formatFileSize(totalSpace), formatFileSize(freeSpace), usagePercent) 376 | } catch (e: Exception) { 377 | Triple("未知", "未知", 0) 378 | } 379 | 380 | Card( 381 | onClick = onClick, 382 | colors = CardDefaults.cardColors( 383 | containerColor = if (isSelected) { 384 | MaterialTheme.colorScheme.primaryContainer 385 | } else { 386 | MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.7f) 387 | } 388 | ), 389 | modifier = Modifier.width(120.dp) 390 | ) { 391 | Column( 392 | modifier = Modifier 393 | .fillMaxWidth() 394 | .padding(12.dp), 395 | horizontalAlignment = Alignment.CenterHorizontally 396 | ) { 397 | Icon( 398 | imageVector = when { 399 | driveLetter.startsWith("C") -> Icons.Default.Storage 400 | driveLetter.contains("USB") || driveLetter.contains("移动") -> Icons.Default.Usb 401 | else -> Icons.Default.Storage 402 | }, 403 | contentDescription = null, 404 | modifier = Modifier.size(24.dp), 405 | tint = if (isSelected) { 406 | MaterialTheme.colorScheme.primary 407 | } else { 408 | MaterialTheme.colorScheme.onSurfaceVariant 409 | } 410 | ) 411 | 412 | Spacer(modifier = Modifier.height(4.dp)) 413 | 414 | Text( 415 | text = driveLetter, 416 | style = MaterialTheme.typography.labelLarge, 417 | fontWeight = FontWeight.Medium, 418 | color = if (isSelected) { 419 | MaterialTheme.colorScheme.onPrimaryContainer 420 | } else { 421 | MaterialTheme.colorScheme.onSurfaceVariant 422 | } 423 | ) 424 | 425 | Text( 426 | text = driveInfo.first, 427 | style = MaterialTheme.typography.bodySmall, 428 | color = MaterialTheme.colorScheme.onSurfaceVariant 429 | ) 430 | 431 | Text( 432 | text = "可用: ${driveInfo.second}", 433 | style = MaterialTheme.typography.bodySmall, 434 | color = MaterialTheme.colorScheme.onSurfaceVariant 435 | ) 436 | 437 | // 使用进度条显示磁盘使用情况 438 | if (driveInfo.third > 0) { 439 | Spacer(modifier = Modifier.height(4.dp)) 440 | LinearProgressIndicator( 441 | progress = { driveInfo.third / 100f }, 442 | modifier = Modifier 443 | .fillMaxWidth() 444 | .height(4.dp), 445 | color = when { 446 | driveInfo.third > 90 -> MaterialTheme.colorScheme.error 447 | driveInfo.third > 75 -> MaterialTheme.colorScheme.tertiary 448 | else -> MaterialTheme.colorScheme.primary 449 | }, 450 | ) 451 | } 452 | } 453 | } 454 | } 455 | 456 | @Composable 457 | private fun FileItem( 458 | file: File, 459 | isSelected: Boolean, 460 | fileExtensions: List, 461 | onClick: () -> Unit 462 | ) { 463 | val isValidFile = if (file.isFile && fileExtensions.isNotEmpty()) { 464 | val extension = file.extension.lowercase() 465 | fileExtensions.any { it.lowercase() == extension } 466 | } else { 467 | true 468 | } 469 | 470 | Card( 471 | onClick = onClick, 472 | colors = CardDefaults.cardColors( 473 | containerColor = when { 474 | isSelected -> MaterialTheme.colorScheme.primaryContainer 475 | !isValidFile && file.isFile -> MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f) 476 | else -> MaterialTheme.colorScheme.surface 477 | } 478 | ), 479 | modifier = Modifier.fillMaxWidth() 480 | ) { 481 | Row( 482 | modifier = Modifier 483 | .fillMaxWidth() 484 | .padding(12.dp), 485 | verticalAlignment = Alignment.CenterVertically 486 | ) { 487 | Icon( 488 | imageVector = when { 489 | file.isDirectory -> Icons.Default.Folder 490 | file.extension.lowercase() in listOf("img", "bin", "zip", "tar", "gz") -> Icons.Default.Archive 491 | file.extension.lowercase() in listOf("txt", "log", "md") -> Icons.Default.Description 492 | file.extension.lowercase() in listOf("jpg", "jpeg", "png", "gif", "bmp") -> Icons.Default.Image 493 | else -> Icons.Default.InsertDriveFile 494 | }, 495 | contentDescription = null, 496 | modifier = Modifier.size(20.dp), 497 | tint = when { 498 | isSelected -> MaterialTheme.colorScheme.primary 499 | !isValidFile && file.isFile -> MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f) 500 | file.isDirectory -> MaterialTheme.colorScheme.secondary 501 | else -> MaterialTheme.colorScheme.onSurfaceVariant 502 | } 503 | ) 504 | 505 | Spacer(modifier = Modifier.width(12.dp)) 506 | 507 | Column(modifier = Modifier.weight(1f)) { 508 | Text( 509 | text = file.name, 510 | style = MaterialTheme.typography.bodyMedium, 511 | fontWeight = if (isSelected) FontWeight.Medium else FontWeight.Normal, 512 | color = when { 513 | isSelected -> MaterialTheme.colorScheme.onPrimaryContainer 514 | !isValidFile && file.isFile -> MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f) 515 | else -> MaterialTheme.colorScheme.onSurface 516 | } 517 | ) 518 | 519 | if (file.isFile) { 520 | Text( 521 | text = formatFileSize(file.length()), 522 | style = MaterialTheme.typography.bodySmall, 523 | color = MaterialTheme.colorScheme.onSurfaceVariant.copy( 524 | alpha = if (!isValidFile) 0.5f else 1f 525 | ) 526 | ) 527 | } else { 528 | Text( 529 | text = "文件夹", 530 | style = MaterialTheme.typography.bodySmall, 531 | color = MaterialTheme.colorScheme.secondary 532 | ) 533 | } 534 | } 535 | 536 | if (!isValidFile && file.isFile) { 537 | Icon( 538 | imageVector = Icons.Default.Block, 539 | contentDescription = "不支持的文件类型", 540 | modifier = Modifier.size(16.dp), 541 | tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f) 542 | ) 543 | } 544 | } 545 | } 546 | } 547 | 548 | /** 549 | * 格式化文件大小 550 | */ 551 | private fun formatFileSize(bytes: Long): String { 552 | if (bytes < 1024) return "$bytes B" 553 | val kb = bytes / 1024.0 554 | if (kb < 1024) return "%.1f KB".format(kb) 555 | val mb = kb / 1024.0 556 | if (mb < 1024) return "%.1f MB".format(mb) 557 | val gb = mb / 1024.0 558 | if (gb < 1024) return "%.1f GB".format(gb) 559 | val tb = gb / 1024.0 560 | return "%.1f TB".format(tb) 561 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/nukrs/windroid/ui/components/StatusBar.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid.ui.components 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.material.icons.Icons 5 | import androidx.compose.material.icons.filled.Circle 6 | import androidx.compose.material3.* 7 | import androidx.compose.runtime.* 8 | import androidx.compose.ui.Alignment 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.graphics.Color 11 | import androidx.compose.ui.unit.dp 12 | 13 | @Composable 14 | fun StatusBar() { 15 | var deviceStatus by remember { mutableStateOf("未连接设备") } 16 | var connectionStatus by remember { mutableStateOf(false) } 17 | 18 | Surface( 19 | modifier = Modifier.fillMaxWidth(), 20 | color = MaterialTheme.colorScheme.surfaceVariant, 21 | tonalElevation = 2.dp 22 | ) { 23 | Row( 24 | modifier = Modifier 25 | .fillMaxWidth() 26 | .padding(horizontal = 16.dp, vertical = 8.dp), 27 | horizontalArrangement = Arrangement.SpaceBetween, 28 | verticalAlignment = Alignment.CenterVertically 29 | ) { 30 | // 设备连接状态 31 | Row( 32 | verticalAlignment = Alignment.CenterVertically 33 | ) { 34 | Icon( 35 | imageVector = Icons.Default.Circle, 36 | contentDescription = null, 37 | modifier = Modifier.size(8.dp), 38 | tint = if (connectionStatus) Color.Green else Color.Red 39 | ) 40 | Spacer(modifier = Modifier.width(8.dp)) 41 | Text( 42 | text = deviceStatus, 43 | style = MaterialTheme.typography.bodySmall 44 | ) 45 | } 46 | 47 | // ADB/Fastboot状态 48 | Row( 49 | verticalAlignment = Alignment.CenterVertically, 50 | horizontalArrangement = Arrangement.spacedBy(16.dp) 51 | ) { 52 | StatusIndicator( 53 | label = "ADB", 54 | isActive = false 55 | ) 56 | StatusIndicator( 57 | label = "Fastboot", 58 | isActive = false 59 | ) 60 | } 61 | 62 | // 版本信息 63 | Text( 64 | text = "v1.0.0", 65 | style = MaterialTheme.typography.bodySmall, 66 | color = MaterialTheme.colorScheme.onSurfaceVariant 67 | ) 68 | } 69 | } 70 | } 71 | 72 | @Composable 73 | private fun StatusIndicator( 74 | label: String, 75 | isActive: Boolean 76 | ) { 77 | Row( 78 | verticalAlignment = Alignment.CenterVertically 79 | ) { 80 | Text( 81 | text = label, 82 | style = MaterialTheme.typography.bodySmall 83 | ) 84 | Spacer(modifier = Modifier.width(4.dp)) 85 | Icon( 86 | imageVector = Icons.Default.Circle, 87 | contentDescription = null, 88 | modifier = Modifier.size(6.dp), 89 | tint = if (isActive) Color.Green else Color.Gray 90 | ) 91 | } 92 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/nukrs/windroid/ui/components/ThemeToggleButton.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid.ui.components 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.material.icons.Icons 5 | import androidx.compose.material.icons.filled.DarkMode 6 | import androidx.compose.material.icons.filled.LightMode 7 | import androidx.compose.material3.* 8 | import androidx.compose.runtime.* 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.unit.dp 12 | import com.nukrs.windroid.ui.theme.ThemeManager 13 | 14 | /** 15 | * 主题切换按钮 16 | * 用于在日/夜主题之间切换 17 | */ 18 | @Composable 19 | fun ThemeToggleButton( 20 | modifier: Modifier = Modifier 21 | ) { 22 | val isDarkTheme by ThemeManager.isDarkTheme 23 | 24 | IconButton( 25 | onClick = { ThemeManager.toggleTheme() }, 26 | modifier = modifier 27 | ) { 28 | Icon( 29 | imageVector = if (isDarkTheme) Icons.Default.LightMode else Icons.Default.DarkMode, 30 | contentDescription = if (isDarkTheme) "切换到浅色主题" else "切换到深色主题", 31 | tint = MaterialTheme.colorScheme.onSurface 32 | ) 33 | } 34 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/nukrs/windroid/ui/components/ToolCard.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid.ui.components 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.material3.* 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Alignment 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.unit.dp 10 | import com.nukrs.windroid.data.model.FlashingTool 11 | 12 | @Composable 13 | fun ToolCard( 14 | tool: FlashingTool, 15 | isSelected: Boolean = false, 16 | onClick: () -> Unit 17 | ) { 18 | Card( 19 | modifier = Modifier 20 | .fillMaxWidth() 21 | .clickable { onClick() }, 22 | elevation = CardDefaults.cardElevation( 23 | defaultElevation = if (isSelected) 8.dp else 2.dp 24 | ), 25 | colors = CardDefaults.cardColors( 26 | containerColor = if (isSelected) 27 | MaterialTheme.colorScheme.primaryContainer 28 | else 29 | MaterialTheme.colorScheme.surface 30 | ) 31 | ) { 32 | Row( 33 | modifier = Modifier 34 | .fillMaxWidth() 35 | .padding(16.dp), 36 | verticalAlignment = Alignment.CenterVertically 37 | ) { 38 | tool.icon?.let { icon -> 39 | Icon( 40 | imageVector = icon, 41 | contentDescription = null, 42 | modifier = Modifier.size(24.dp), 43 | tint = if (isSelected) 44 | MaterialTheme.colorScheme.onPrimaryContainer 45 | else 46 | MaterialTheme.colorScheme.primary 47 | ) 48 | Spacer(modifier = Modifier.width(12.dp)) 49 | } 50 | 51 | Column( 52 | modifier = Modifier.weight(1f) 53 | ) { 54 | Text( 55 | text = tool.name, 56 | style = MaterialTheme.typography.titleMedium, 57 | color = if (isSelected) 58 | MaterialTheme.colorScheme.onPrimaryContainer 59 | else 60 | MaterialTheme.colorScheme.onSurface 61 | ) 62 | Text( 63 | text = tool.category, 64 | style = MaterialTheme.typography.bodySmall, 65 | color = if (isSelected) 66 | MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.7f) 67 | else 68 | MaterialTheme.colorScheme.onSurfaceVariant 69 | ) 70 | } 71 | 72 | if (!tool.isEnabled) { 73 | Badge { 74 | Text("禁用") 75 | } 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/nukrs/windroid/ui/screens/MainScreen.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid.ui.screens 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.foundation.lazy.LazyColumn 5 | import androidx.compose.foundation.lazy.items 6 | import androidx.compose.material.icons.Icons 7 | import androidx.compose.material.icons.filled.* 8 | import androidx.compose.material3.* 9 | import androidx.compose.runtime.* 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.graphics.vector.ImageVector 13 | import androidx.compose.ui.unit.dp 14 | import com.nukrs.windroid.ui.components.ToolCard 15 | import com.nukrs.windroid.ui.components.StatusBar 16 | import com.nukrs.windroid.ui.components.DeviceOverviewPanel 17 | import com.nukrs.windroid.ui.components.ThemeToggleButton 18 | import com.nukrs.windroid.ui.components.AboutButton 19 | import com.nukrs.windroid.ui.components.AdbToolPanel 20 | import com.nukrs.windroid.ui.components.FastbootToolPanel 21 | import com.nukrs.windroid.data.model.FlashingTool 22 | import com.nukrs.windroid.core.service.DeviceService 23 | 24 | @OptIn(ExperimentalMaterial3Api::class) 25 | @Composable 26 | fun MainScreen() { 27 | var selectedTool by remember { mutableStateOf(null) } 28 | var selectedView by remember { mutableStateOf("overview") } // 默认显示概览 29 | val deviceService = remember { DeviceService() } 30 | 31 | Column( 32 | modifier = Modifier.fillMaxSize() 33 | ) { 34 | // 顶部应用栏 35 | TopAppBar( 36 | title = { Text("WinDroid Toolbox") }, 37 | actions = { 38 | ThemeToggleButton() 39 | AboutButton() 40 | }, 41 | colors = TopAppBarDefaults.topAppBarColors( 42 | containerColor = MaterialTheme.colorScheme.primaryContainer 43 | ) 44 | ) 45 | 46 | Row( 47 | modifier = Modifier.fillMaxSize() 48 | ) { 49 | // 左侧导航面板 50 | Column( 51 | modifier = Modifier 52 | .weight(1f) 53 | .padding(16.dp) 54 | ) { 55 | // 概览按钮 56 | NavigationCard( 57 | title = "设备概览", 58 | icon = Icons.Default.Dashboard, 59 | isSelected = selectedView == "overview", 60 | onClick = { 61 | selectedView = "overview" 62 | selectedTool = null 63 | } 64 | ) 65 | 66 | Spacer(modifier = Modifier.height(16.dp)) 67 | 68 | Text( 69 | text = "刷机工具", 70 | style = MaterialTheme.typography.headlineSmall, 71 | modifier = Modifier.padding(bottom = 16.dp) 72 | ) 73 | 74 | LazyColumn( 75 | verticalArrangement = Arrangement.spacedBy(8.dp) 76 | ) { 77 | items(getFlashingTools()) { tool -> 78 | ToolCard( 79 | tool = tool, 80 | isSelected = selectedTool == tool, 81 | onClick = { 82 | selectedTool = tool 83 | selectedView = "tool" 84 | } 85 | ) 86 | } 87 | } 88 | } 89 | 90 | // 右侧详情面板 91 | Column( 92 | modifier = Modifier 93 | .weight(2f) 94 | .padding(16.dp) 95 | ) { 96 | when (selectedView) { 97 | "overview" -> { 98 | DeviceOverviewPanel(deviceService = deviceService) 99 | } 100 | "tool" -> { 101 | if (selectedTool != null) { 102 | ToolDetailPanel(tool = selectedTool!!) 103 | } else { 104 | DeviceOverviewPanel(deviceService = deviceService) 105 | } 106 | } 107 | else -> { 108 | DeviceOverviewPanel(deviceService = deviceService) 109 | } 110 | } 111 | } 112 | } 113 | 114 | // 底部状态栏 115 | StatusBar() 116 | } 117 | } 118 | 119 | @Composable 120 | fun NavigationCard( 121 | title: String, 122 | icon: ImageVector, 123 | isSelected: Boolean, 124 | onClick: () -> Unit 125 | ) { 126 | Card( 127 | modifier = Modifier 128 | .fillMaxWidth() 129 | .height(60.dp), 130 | onClick = onClick, 131 | colors = CardDefaults.cardColors( 132 | containerColor = if (isSelected) { 133 | MaterialTheme.colorScheme.primaryContainer 134 | } else { 135 | MaterialTheme.colorScheme.surface 136 | } 137 | ), 138 | elevation = CardDefaults.cardElevation( 139 | defaultElevation = if (isSelected) 8.dp else 2.dp 140 | ) 141 | ) { 142 | Row( 143 | modifier = Modifier 144 | .fillMaxSize() 145 | .padding(16.dp), 146 | verticalAlignment = Alignment.CenterVertically 147 | ) { 148 | Icon( 149 | imageVector = icon, 150 | contentDescription = title, 151 | tint = if (isSelected) { 152 | MaterialTheme.colorScheme.primary 153 | } else { 154 | MaterialTheme.colorScheme.onSurface 155 | }, 156 | modifier = Modifier.size(24.dp) 157 | ) 158 | Spacer(modifier = Modifier.width(12.dp)) 159 | Text( 160 | text = title, 161 | style = MaterialTheme.typography.titleMedium, 162 | color = if (isSelected) { 163 | MaterialTheme.colorScheme.onPrimaryContainer 164 | } else { 165 | MaterialTheme.colorScheme.onSurface 166 | } 167 | ) 168 | } 169 | } 170 | } 171 | 172 | @Composable 173 | fun ToolDetailPanel(tool: FlashingTool) { 174 | // 如果是ADB工具,显示专门的ADB面板 175 | if (tool.id == "adb") { 176 | AdbToolPanel( 177 | deviceService = remember { DeviceService() }, 178 | modifier = Modifier.fillMaxSize() 179 | ) 180 | } else if (tool.id == "fastboot") { 181 | // 如果是Fastboot工具,显示专门的Fastboot面板 182 | FastbootToolPanel( 183 | deviceService = remember { DeviceService() }, 184 | modifier = Modifier.fillMaxSize() 185 | ) 186 | } else { 187 | // 其他工具显示通用面板 188 | Card( 189 | modifier = Modifier.fillMaxSize(), 190 | elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) 191 | ) { 192 | Column( 193 | modifier = Modifier 194 | .fillMaxSize() 195 | .padding(24.dp) 196 | ) { 197 | Row( 198 | verticalAlignment = Alignment.CenterVertically, 199 | modifier = Modifier.padding(bottom = 16.dp) 200 | ) { 201 | tool.icon?.let { icon -> 202 | Icon( 203 | imageVector = icon, 204 | contentDescription = null, 205 | modifier = Modifier.size(32.dp), 206 | tint = MaterialTheme.colorScheme.primary 207 | ) 208 | Spacer(modifier = Modifier.width(12.dp)) 209 | } 210 | Text( 211 | text = tool.name, 212 | style = MaterialTheme.typography.headlineMedium 213 | ) 214 | } 215 | 216 | Text( 217 | text = tool.description, 218 | style = MaterialTheme.typography.bodyLarge, 219 | modifier = Modifier.padding(bottom = 24.dp) 220 | ) 221 | 222 | // 功能按钮 223 | Row( 224 | horizontalArrangement = Arrangement.spacedBy(12.dp) 225 | ) { 226 | Button( 227 | onClick = { /* TODO: 实现功能 */ }, 228 | modifier = Modifier.weight(1f) 229 | ) { 230 | Text("开始${tool.name}") 231 | } 232 | 233 | OutlinedButton( 234 | onClick = { /* TODO: 打开设置 */ }, 235 | modifier = Modifier.weight(1f) 236 | ) { 237 | Text("设置") 238 | } 239 | } 240 | 241 | Spacer(modifier = Modifier.height(24.dp)) 242 | 243 | // 操作日志区域 244 | Card( 245 | modifier = Modifier.fillMaxSize(), 246 | colors = CardDefaults.cardColors( 247 | containerColor = MaterialTheme.colorScheme.surfaceVariant 248 | ) 249 | ) { 250 | Column( 251 | modifier = Modifier 252 | .fillMaxSize() 253 | .padding(16.dp) 254 | ) { 255 | Text( 256 | text = "操作日志", 257 | style = MaterialTheme.typography.titleMedium, 258 | modifier = Modifier.padding(bottom = 8.dp) 259 | ) 260 | 261 | Text( 262 | text = "等待操作...", 263 | style = MaterialTheme.typography.bodyMedium, 264 | color = MaterialTheme.colorScheme.onSurfaceVariant 265 | ) 266 | } 267 | } 268 | } 269 | } 270 | } 271 | } 272 | 273 | private fun getFlashingTools(): List { 274 | return listOf( 275 | FlashingTool( 276 | id = "fastboot", 277 | name = "Fastboot刷机", 278 | description = "使用Fastboot命令刷入系统镜像、Recovery等", 279 | icon = Icons.Default.FlashOn, 280 | category = "刷机工具" 281 | ), 282 | FlashingTool( 283 | id = "adb", 284 | name = "ADB工具", 285 | description = "Android调试桥,用于设备调试和文件传输", 286 | icon = Icons.Default.DeveloperMode, 287 | category = "调试工具" 288 | ) 289 | ) 290 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/nukrs/windroid/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid.ui.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material3.* 5 | import androidx.compose.runtime.* 6 | import androidx.compose.ui.graphics.Color 7 | 8 | // Macchiato 主题配色 9 | object MacchiatoColors { 10 | val rosewater = Color(0xFFF4DBD6) 11 | val flamingo = Color(0xFFF0C6C6) 12 | val pink = Color(0xFFF5BDE6) 13 | val mauve = Color(0xFFC6A0F6) 14 | val red = Color(0xFFED8796) 15 | val maroon = Color(0xFFEE99A0) 16 | val peach = Color(0xFFF5A97F) 17 | val yellow = Color(0xFFEED49F) 18 | val green = Color(0xFFA6DA95) 19 | val teal = Color(0xFF8BD5CA) 20 | val sky = Color(0xFF91D7E3) 21 | val sapphire = Color(0xFF7DC4E4) 22 | val blue = Color(0xFF8AADF4) 23 | val lavender = Color(0xFFB7BDF8) 24 | val text = Color(0xFFCAD3F5) 25 | val subtext1 = Color(0xFFB8C0E0) 26 | val subtext0 = Color(0xFFA5ADCB) 27 | val overlay2 = Color(0xFF939AB7) 28 | val overlay1 = Color(0xFF8087A2) 29 | val overlay0 = Color(0xFF6E738D) 30 | val surface2 = Color(0xFF5B6078) 31 | val surface1 = Color(0xFF494D64) 32 | val surface0 = Color(0xFF363A4F) 33 | val base = Color(0xFF24273A) 34 | val mantle = Color(0xFF1E2030) 35 | val crust = Color(0xFF181926) 36 | } 37 | 38 | // Macchiato 暗色主题 39 | private val MacchiatoDarkColorScheme = darkColorScheme( 40 | primary = MacchiatoColors.blue, 41 | secondary = MacchiatoColors.mauve, 42 | tertiary = MacchiatoColors.pink, 43 | background = MacchiatoColors.base, 44 | surface = MacchiatoColors.surface0, 45 | surfaceVariant = MacchiatoColors.surface1, 46 | primaryContainer = MacchiatoColors.surface2, 47 | secondaryContainer = MacchiatoColors.surface1, 48 | tertiaryContainer = MacchiatoColors.surface0, 49 | error = MacchiatoColors.red, 50 | errorContainer = MacchiatoColors.surface0, 51 | onPrimary = MacchiatoColors.base, // 改为深色背景 52 | onSecondary = MacchiatoColors.base, // 改为深色背景 53 | onTertiary = MacchiatoColors.base, // 改为深色背景 54 | onBackground = MacchiatoColors.text, 55 | onSurface = MacchiatoColors.text, 56 | onSurfaceVariant = MacchiatoColors.subtext1, 57 | onPrimaryContainer = MacchiatoColors.text, 58 | onSecondaryContainer = MacchiatoColors.text, 59 | onTertiaryContainer = MacchiatoColors.text, 60 | onError = MacchiatoColors.base, // 改为深色背景 61 | onErrorContainer = MacchiatoColors.text, 62 | outline = MacchiatoColors.overlay0, 63 | outlineVariant = MacchiatoColors.surface2, 64 | scrim = MacchiatoColors.crust, 65 | inverseSurface = MacchiatoColors.surface2, // 改为深色表面 66 | inverseOnSurface = MacchiatoColors.text, // 保持浅色文字 67 | inversePrimary = MacchiatoColors.blue, 68 | surfaceTint = MacchiatoColors.blue 69 | ) 70 | 71 | // Macchiato 浅色主题(优化对比度) 72 | private val MacchiatoLightColorScheme = lightColorScheme( 73 | primary = Color(0xFF0066CC), // 更深的蓝色,提高对比度 74 | secondary = Color(0xFF7B4397), // 更深的紫色 75 | tertiary = Color(0xFFD63384), // 更深的粉色 76 | background = Color(0xFFFAFAFA), // 纯白背景 77 | surface = Color(0xFFFFFFFF), // 白色表面 78 | surfaceVariant = Color(0xFFF5F5F5), // 浅灰色变体 79 | primaryContainer = Color(0xFFE3F2FD), // 浅蓝色容器 80 | secondaryContainer = Color(0xFFF3E5F5), // 浅紫色容器 81 | tertiaryContainer = Color(0xFFFCE4EC), // 浅粉色容器 82 | error = Color(0xFFD32F2F), // 深红色错误 83 | errorContainer = Color(0xFFFFEBEE), // 浅红色错误容器 84 | onPrimary = Color(0xFFFFFFFF), // 主色上的白色文字 85 | onSecondary = Color(0xFFFFFFFF), // 次色上的白色文字 86 | onTertiary = Color(0xFFFFFFFF), // 第三色上的白色文字 87 | onBackground = Color(0xFF1A1A1A), // 背景上的深色文字 88 | onSurface = Color(0xFF1A1A1A), // 表面上的深色文字 89 | onSurfaceVariant = Color(0xFF424242), // 表面变体上的深灰色文字 90 | onPrimaryContainer = Color(0xFF0D47A1), // 主色容器上的深蓝色文字 91 | onSecondaryContainer = Color(0xFF4A148C), // 次色容器上的深紫色文字 92 | onTertiaryContainer = Color(0xFF880E4F), // 第三色容器上的深粉色文字 93 | onError = Color(0xFFFFFFFF), // 错误色上的白色文字 94 | onErrorContainer = Color(0xFFB71C1C), // 错误容器上的深红色文字 95 | outline = Color(0xFFBDBDBD), // 边框颜色 96 | outlineVariant = Color(0xFFE0E0E0), // 边框变体颜色 97 | scrim = Color(0xFF000000), // 遮罩颜色 98 | inverseSurface = Color(0xFF2E2E2E), // 反色表面 99 | inverseOnSurface = Color(0xFFF5F5F5), // 反色表面上的文字 100 | inversePrimary = MacchiatoColors.blue, // 反色主色 101 | surfaceTint = Color(0xFF0066CC) // 表面着色 102 | ) 103 | 104 | @Composable 105 | fun WinDroidTheme( 106 | content: @Composable () -> Unit 107 | ) { 108 | val isDarkTheme by ThemeManager.isDarkTheme 109 | 110 | val colorScheme = when { 111 | isDarkTheme -> MacchiatoDarkColorScheme 112 | else -> MacchiatoLightColorScheme 113 | } 114 | 115 | MaterialTheme( 116 | colorScheme = colorScheme, 117 | typography = Typography(), 118 | content = content 119 | ) 120 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/com/nukrs/windroid/ui/theme/ThemeManager.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid.ui.theme 2 | 3 | import androidx.compose.runtime.* 4 | 5 | /** 6 | * 主题管理器 7 | * 用于管理应用的日/夜主题状态 8 | */ 9 | object ThemeManager { 10 | private var _isDarkTheme = mutableStateOf(true) // 默认为暗色主题 11 | val isDarkTheme: State = _isDarkTheme 12 | 13 | /** 14 | * 切换主题 15 | */ 16 | fun toggleTheme() { 17 | _isDarkTheme.value = !_isDarkTheme.value 18 | } 19 | 20 | /** 21 | * 设置主题 22 | * @param isDark 是否为暗色主题 23 | */ 24 | fun setTheme(isDark: Boolean) { 25 | _isDarkTheme.value = isDark 26 | } 27 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/resources/tray_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/com/nukrs/windroid/ui/TrayManager.kt: -------------------------------------------------------------------------------- 1 | package com.nukrs.windroid.ui 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.graphics.painter.Painter 5 | import androidx.compose.ui.res.painterResource 6 | import androidx.compose.ui.window.ApplicationScope 7 | import androidx.compose.ui.window.Tray 8 | 9 | @Composable 10 | actual fun ApplicationScope.AppTray( 11 | onShowWindow: () -> Unit, 12 | onExit: () -> Unit 13 | ) { 14 | val icon: Painter = painterResource("tray_icon.svg") 15 | 16 | Tray( 17 | icon = icon, 18 | tooltip = "WinDroid Toolbox" 19 | ) { 20 | Item( 21 | text = "Show Window", 22 | onClick = onShowWindow 23 | ) 24 | Separator() 25 | Item( 26 | text = "Exit", 27 | onClick = onExit 28 | ) 29 | } 30 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/color/Macchiato.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme": "Macchiato", 3 | "colors": { 4 | "rosewater": { 5 | "hex": "#f4dbd6", 6 | "rgb": "rgb(244, 219, 214)", 7 | "hsl": "hsl(10deg, 58%, 90%)" 8 | }, 9 | "flamingo": { 10 | "hex": "#f0c6c6", 11 | "rgb": "rgb(240, 198, 198)", 12 | "hsl": "hsl(0deg, 58%, 86%)" 13 | }, 14 | "pink": { 15 | "hex": "#f5bde6", 16 | "rgb": "rgb(245, 189, 230)", 17 | "hsl": "hsl(316deg, 74%, 85%)" 18 | }, 19 | "mauve": { 20 | "hex": "#c6a0f6", 21 | "rgb": "rgb(198, 160, 246)", 22 | "hsl": "hsl(267deg, 83%, 80%)" 23 | }, 24 | "red": { 25 | "hex": "#ed8796", 26 | "rgb": "rgb(237, 135, 150)", 27 | "hsl": "hsl(351deg, 74%, 73%)" 28 | }, 29 | "maroon": { 30 | "hex": "#ee99a0", 31 | "rgb": "rgb(238, 153, 160)", 32 | "hsl": "hsl(355deg, 71%, 77%)" 33 | }, 34 | "peach": { 35 | "hex": "#f5a97f", 36 | "rgb": "rgb(245, 169, 127)", 37 | "hsl": "hsl(21deg, 86%, 73%)" 38 | }, 39 | "yellow": { 40 | "hex": "#eed49f", 41 | "rgb": "rgb(238, 212, 159)", 42 | "hsl": "hsl(40deg, 70%, 78%)" 43 | }, 44 | "green": { 45 | "hex": "#a6da95", 46 | "rgb": "rgb(166, 218, 149)", 47 | "hsl": "hsl(105deg, 48%, 72%)" 48 | }, 49 | "teal": { 50 | "hex": "#8bd5ca", 51 | "rgb": "rgb(139, 213, 202)", 52 | "hsl": "hsl(171deg, 47%, 69%)" 53 | }, 54 | "sky": { 55 | "hex": "#91d7e3", 56 | "rgb": "rgb(145, 215, 227)", 57 | "hsl": "hsl(189deg, 59%, 73%)" 58 | }, 59 | "sapphire": { 60 | "hex": "#7dc4e4", 61 | "rgb": "rgb(125, 196, 228)", 62 | "hsl": "hsl(199deg, 66%, 69%)" 63 | }, 64 | "blue": { 65 | "hex": "#8aadf4", 66 | "rgb": "rgb(138, 173, 244)", 67 | "hsl": "hsl(220deg, 83%, 75%)" 68 | }, 69 | "lavender": { 70 | "hex": "#b7bdf8", 71 | "rgb": "rgb(183, 189, 248)", 72 | "hsl": "hsl(234deg, 82%, 85%)" 73 | }, 74 | "text": { 75 | "hex": "#cad3f5", 76 | "rgb": "rgb(202, 211, 245)", 77 | "hsl": "hsl(227deg, 68%, 88%)" 78 | }, 79 | "subtext1": { 80 | "hex": "#b8c0e0", 81 | "rgb": "rgb(184, 192, 224)", 82 | "hsl": "hsl(228deg, 39%, 80%)" 83 | }, 84 | "subtext0": { 85 | "hex": "#a5adcb", 86 | "rgb": "rgb(165, 173, 203)", 87 | "hsl": "hsl(227deg, 27%, 72%)" 88 | }, 89 | "overlay2": { 90 | "hex": "#939ab7", 91 | "rgb": "rgb(147, 154, 183)", 92 | "hsl": "hsl(228deg, 20%, 65%)" 93 | }, 94 | "overlay1": { 95 | "hex": "#8087a2", 96 | "rgb": "rgb(128, 135, 162)", 97 | "hsl": "hsl(228deg, 15%, 57%)" 98 | }, 99 | "overlay0": { 100 | "hex": "#6e738d", 101 | "rgb": "rgb(110, 115, 141)", 102 | "hsl": "hsl(230deg, 12%, 49%)" 103 | }, 104 | "surface2": { 105 | "hex": "#5b6078", 106 | "rgb": "rgb(91, 96, 120)", 107 | "hsl": "hsl(230deg, 14%, 41%)" 108 | }, 109 | "surface1": { 110 | "hex": "#494d64", 111 | "rgb": "rgb(73, 77, 100)", 112 | "hsl": "hsl(231deg, 16%, 34%)" 113 | }, 114 | "surface0": { 115 | "hex": "#363a4f", 116 | "rgb": "rgb(54, 58, 79)", 117 | "hsl": "hsl(230deg, 19%, 26%)" 118 | }, 119 | "base": { 120 | "hex": "#24273a", 121 | "rgb": "rgb(36, 39, 58)", 122 | "hsl": "hsl(232deg, 23%, 18%)" 123 | }, 124 | "mantle": { 125 | "hex": "#1e2030", 126 | "rgb": "rgb(30, 32, 48)", 127 | "hsl": "hsl(233deg, 23%, 15%)" 128 | }, 129 | "crust": { 130 | "hex": "#181926", 131 | "rgb": "rgb(24, 25, 38)", 132 | "hsl": "hsl(236deg, 23%, 12%)" 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/icon.ico -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | WinDroid 53 | -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/logo/Lenovo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/logo/Lenovo.png -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/logo/Motorola.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/logo/Motorola.png -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/logo/OnePlus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/logo/OnePlus.png -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/logo/Oppo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/logo/Oppo.png -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/logo/Redmi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/logo/Redmi.png -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/logo/Samsung.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/logo/Samsung.jpg -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/logo/Sony.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/logo/Sony.png -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/logo/Vivo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/logo/Vivo.png -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/logo/Xiaomi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/logo/Xiaomi.png -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/logo/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/logo/google.png -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/logo/iqoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/logo/iqoo.png -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/logo/meizu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/logo/meizu.png -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/logo/realme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/logo/realme.png -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/platform-tools/AdbWinApi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/platform-tools/AdbWinApi.dll -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/platform-tools/AdbWinUsbApi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/platform-tools/AdbWinUsbApi.dll -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/platform-tools/NOTICE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/platform-tools/NOTICE.txt -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/platform-tools/adb.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/platform-tools/adb.exe -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/platform-tools/etc1tool.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/platform-tools/etc1tool.exe -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/platform-tools/fastboot.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/platform-tools/fastboot.exe -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/platform-tools/hprof-conv.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/platform-tools/hprof-conv.exe -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/platform-tools/libwinpthread-1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/platform-tools/libwinpthread-1.dll -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/platform-tools/make_f2fs.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/platform-tools/make_f2fs.exe -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/platform-tools/make_f2fs_casefold.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/platform-tools/make_f2fs_casefold.exe -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/platform-tools/mke2fs.conf: -------------------------------------------------------------------------------- 1 | [defaults] 2 | base_features = sparse_super,large_file,filetype,dir_index,ext_attr 3 | default_mntopts = acl,user_xattr 4 | enable_periodic_fsck = 0 5 | blocksize = 4096 6 | inode_size = 256 7 | inode_ratio = 16384 8 | reserved_ratio = 1.0 9 | 10 | [fs_types] 11 | ext3 = { 12 | features = has_journal 13 | } 14 | ext4 = { 15 | features = has_journal,extent,huge_file,dir_nlink,extra_isize,uninit_bg 16 | inode_size = 256 17 | } 18 | ext4dev = { 19 | features = has_journal,extent,huge_file,flex_bg,inline_data,64bit,dir_nlink,extra_isize 20 | inode_size = 256 21 | options = test_fs=1 22 | } 23 | small = { 24 | blocksize = 1024 25 | inode_size = 128 26 | inode_ratio = 4096 27 | } 28 | floppy = { 29 | blocksize = 1024 30 | inode_size = 128 31 | inode_ratio = 8192 32 | } 33 | big = { 34 | inode_ratio = 32768 35 | } 36 | huge = { 37 | inode_ratio = 65536 38 | } 39 | news = { 40 | inode_ratio = 4096 41 | } 42 | largefile = { 43 | inode_ratio = 1048576 44 | blocksize = -1 45 | } 46 | largefile4 = { 47 | inode_ratio = 4194304 48 | blocksize = -1 49 | } 50 | hurd = { 51 | blocksize = 4096 52 | inode_size = 128 53 | } 54 | -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/platform-tools/mke2fs.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/platform-tools/mke2fs.exe -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/platform-tools/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Terms and Conditions 4 | 5 | This is the Android Software Development Kit License Agreement 6 | 7 | 1. Introduction 8 | 9 | 1.1 The Android Software Development Kit (referred to in the License Agreement as the "SDK" and specifically including the Android system files, packaged APIs, and Google APIs add-ons) is licensed to you subject to the terms of the License Agreement. The License Agreement forms a legally binding contract between you and Google in relation to your use of the SDK. 10 | 11 | 1.2 "Android" means the Android software stack for devices, as made available under the Android Open Source Project, which is located at the following URL: http://source.android.com/, as updated from time to time. 12 | 13 | 1.3 A "compatible implementation" means any Android device that (i) complies with the Android Compatibility Definition document, which can be found at the Android compatibility website (http://source.android.com/compatibility) and which may be updated from time to time; and (ii) successfully passes the Android Compatibility Test Suite (CTS). 14 | 15 | 1.4 "Google" means Google Inc., a Delaware corporation with principal place of business at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States. 16 | 17 | 18 | 2. Accepting the License Agreement 19 | 20 | 2.1 In order to use the SDK, you must first agree to the License Agreement. You may not use the SDK if you do not accept the License Agreement. 21 | 22 | 2.2 By clicking to accept, you hereby agree to the terms of the License Agreement. 23 | 24 | 2.3 You may not use the SDK and may not accept the License Agreement if you are a person barred from receiving the SDK under the laws of the United States or other countries, including the country in which you are resident or from which you use the SDK. 25 | 26 | 2.4 If you are agreeing to be bound by the License Agreement on behalf of your employer or other entity, you represent and warrant that you have full legal authority to bind your employer or such entity to the License Agreement. If you do not have the requisite authority, you may not accept the License Agreement or use the SDK on behalf of your employer or other entity. 27 | 28 | 29 | 3. SDK License from Google 30 | 31 | 3.1 Subject to the terms of the License Agreement, Google grants you a limited, worldwide, royalty-free, non-assignable, non-exclusive, and non-sublicensable license to use the SDK solely to develop applications for compatible implementations of Android. 32 | 33 | 3.2 You may not use this SDK to develop applications for other platforms (including non-compatible implementations of Android) or to develop another SDK. You are of course free to develop applications for other platforms, including non-compatible implementations of Android, provided that this SDK is not used for that purpose. 34 | 35 | 3.3 You agree that Google or third parties own all legal right, title and interest in and to the SDK, including any Intellectual Property Rights that subsist in the SDK. "Intellectual Property Rights" means any and all rights under patent law, copyright law, trade secret law, trademark law, and any and all other proprietary rights. Google reserves all rights not expressly granted to you. 36 | 37 | 3.4 You may not use the SDK for any purpose not expressly permitted by the License Agreement. Except to the extent required by applicable third party licenses, you may not copy (except for backup purposes), modify, adapt, redistribute, decompile, reverse engineer, disassemble, or create derivative works of the SDK or any part of the SDK. 38 | 39 | 3.5 Use, reproduction and distribution of components of the SDK licensed under an open source software license are governed solely by the terms of that open source software license and not the License Agreement. 40 | 41 | 3.6 You agree that the form and nature of the SDK that Google provides may change without prior notice to you and that future versions of the SDK may be incompatible with applications developed on previous versions of the SDK. You agree that Google may stop (permanently or temporarily) providing the SDK (or any features within the SDK) to you or to users generally at Google's sole discretion, without prior notice to you. 42 | 43 | 3.7 Nothing in the License Agreement gives you a right to use any of Google's trade names, trademarks, service marks, logos, domain names, or other distinctive brand features. 44 | 45 | 3.8 You agree that you will not remove, obscure, or alter any proprietary rights notices (including copyright and trademark notices) that may be affixed to or contained within the SDK. 46 | 47 | 48 | 4. Use of the SDK by You 49 | 50 | 4.1 Google agrees that it obtains no right, title or interest from you (or your licensors) under the License Agreement in or to any software applications that you develop using the SDK, including any intellectual property rights that subsist in those applications. 51 | 52 | 4.2 You agree to use the SDK and write applications only for purposes that are permitted by (a) the License Agreement and (b) any applicable law, regulation or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries). 53 | 54 | 4.3 You agree that if you use the SDK to develop applications for general public users, you will protect the privacy and legal rights of those users. If the users provide you with user names, passwords, or other login information or personal information, you must make the users aware that the information will be available to your application, and you must provide legally adequate privacy notice and protection for those users. If your application stores personal or sensitive information provided by users, it must do so securely. If the user provides your application with Google Account information, your application may only use that information to access the user's Google Account when, and for the limited purposes for which, the user has given you permission to do so. 55 | 56 | 4.4 You agree that you will not engage in any activity with the SDK, including the development or distribution of an application, that interferes with, disrupts, damages, or accesses in an unauthorized manner the servers, networks, or other properties or services of any third party including, but not limited to, Google or any mobile communications carrier. 57 | 58 | 4.5 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any data, content, or resources that you create, transmit or display through Android and/or applications for Android, and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so. 59 | 60 | 4.6 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any breach of your obligations under the License Agreement, any applicable third party contract or Terms of Service, or any applicable law or regulation, and for the consequences (including any loss or damage which Google or any third party may suffer) of any such breach. 61 | 62 | 5. Your Developer Credentials 63 | 64 | 5.1 You agree that you are responsible for maintaining the confidentiality of any developer credentials that may be issued to you by Google or which you may choose yourself and that you will be solely responsible for all applications that are developed under your developer credentials. 65 | 66 | 6. Privacy and Information 67 | 68 | 6.1 In order to continually innovate and improve the SDK, Google may collect certain usage statistics from the software including but not limited to a unique identifier, associated IP address, version number of the software, and information on which tools and/or services in the SDK are being used and how they are being used. Before any of this information is collected, the SDK will notify you and seek your consent. If you withhold consent, the information will not be collected. 69 | 70 | 6.2 The data collected is examined in the aggregate to improve the SDK and is maintained in accordance with Google's Privacy Policy. 71 | 72 | 73 | 7. Third Party Applications 74 | 75 | 7.1 If you use the SDK to run applications developed by a third party or that access data, content or resources provided by a third party, you agree that Google is not responsible for those applications, data, content, or resources. You understand that all data, content or resources which you may access through such third party applications are the sole responsibility of the person from which they originated and that Google is not liable for any loss or damage that you may experience as a result of the use or access of any of those third party applications, data, content, or resources. 76 | 77 | 7.2 You should be aware the data, content, and resources presented to you through such a third party application may be protected by intellectual property rights which are owned by the providers (or by other persons or companies on their behalf). You may not modify, rent, lease, loan, sell, distribute or create derivative works based on these data, content, or resources (either in whole or in part) unless you have been specifically given permission to do so by the relevant owners. 78 | 79 | 7.3 You acknowledge that your use of such third party applications, data, content, or resources may be subject to separate terms between you and the relevant third party. In that case, the License Agreement does not affect your legal relationship with these third parties. 80 | 81 | 82 | 8. Using Android APIs 83 | 84 | 8.1 Google Data APIs 85 | 86 | 8.1.1 If you use any API to retrieve data from Google, you acknowledge that the data may be protected by intellectual property rights which are owned by Google or those parties that provide the data (or by other persons or companies on their behalf). Your use of any such API may be subject to additional Terms of Service. You may not modify, rent, lease, loan, sell, distribute or create derivative works based on this data (either in whole or in part) unless allowed by the relevant Terms of Service. 87 | 88 | 8.1.2 If you use any API to retrieve a user's data from Google, you acknowledge and agree that you shall retrieve data only with the user's explicit consent and only when, and for the limited purposes for which, the user has given you permission to do so. If you use the Android Recognition Service API, documented at the following URL: https://developer.android.com/reference/android/speech/RecognitionService, as updated from time to time, you acknowledge that the use of the API is subject to the Data Processing Addendum for Products where Google is a Data Processor, which is located at the following URL: https://privacy.google.com/businesses/gdprprocessorterms/, as updated from time to time. By clicking to accept, you hereby agree to the terms of the Data Processing Addendum for Products where Google is a Data Processor. 89 | 90 | 91 | 9. Terminating the License Agreement 92 | 93 | 9.1 The License Agreement will continue to apply until terminated by either you or Google as set out below. 94 | 95 | 9.2 If you want to terminate the License Agreement, you may do so by ceasing your use of the SDK and any relevant developer credentials. 96 | 97 | 9.3 Google may at any time, terminate the License Agreement with you if: (A) you have breached any provision of the License Agreement; or (B) Google is required to do so by law; or (C) the partner with whom Google offered certain parts of SDK (such as APIs) to you has terminated its relationship with Google or ceased to offer certain parts of the SDK to you; or (D) Google decides to no longer provide the SDK or certain parts of the SDK to users in the country in which you are resident or from which you use the service, or the provision of the SDK or certain SDK services to you by Google is, in Google's sole discretion, no longer commercially viable. 98 | 99 | 9.4 When the License Agreement comes to an end, all of the legal rights, obligations and liabilities that you and Google have benefited from, been subject to (or which have accrued over time whilst the License Agreement has been in force) or which are expressed to continue indefinitely, shall be unaffected by this cessation, and the provisions of paragraph 14.7 shall continue to apply to such rights, obligations and liabilities indefinitely. 100 | 101 | 102 | 10. DISCLAIMER OF WARRANTIES 103 | 104 | 10.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE SDK IS AT YOUR SOLE RISK AND THAT THE SDK IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM GOOGLE. 105 | 106 | 10.2 YOUR USE OF THE SDK AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE SDK IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF DATA THAT RESULTS FROM SUCH USE. 107 | 108 | 10.3 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. 109 | 110 | 111 | 11. LIMITATION OF LIABILITY 112 | 113 | 11.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU UNDER ANY THEORY OF LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES THAT MAY BE INCURRED BY YOU, INCLUDING ANY LOSS OF DATA, WHETHER OR NOT GOOGLE OR ITS REPRESENTATIVES HAVE BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING. 114 | 115 | 116 | 12. Indemnification 117 | 118 | 12.1 To the maximum extent permitted by law, you agree to defend, indemnify and hold harmless Google, its affiliates and their respective directors, officers, employees and agents from and against any and all claims, actions, suits or proceedings, as well as any and all losses, liabilities, damages, costs and expenses (including reasonable attorneys fees) arising out of or accruing from (a) your use of the SDK, (b) any application you develop on the SDK that infringes any copyright, trademark, trade secret, trade dress, patent or other intellectual property right of any person or defames any person or violates their rights of publicity or privacy, and (c) any non-compliance by you with the License Agreement. 119 | 120 | 121 | 13. Changes to the License Agreement 122 | 123 | 13.1 Google may make changes to the License Agreement as it distributes new versions of the SDK. When these changes are made, Google will make a new version of the License Agreement available on the website where the SDK is made available. 124 | 125 | 126 | 14. General Legal Terms 127 | 128 | 14.1 The License Agreement constitutes the whole legal agreement between you and Google and governs your use of the SDK (excluding any services which Google may provide to you under a separate written agreement), and completely replaces any prior agreements between you and Google in relation to the SDK. 129 | 130 | 14.2 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in the License Agreement (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google. 131 | 132 | 14.3 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of the License Agreement is invalid, then that provision will be removed from the License Agreement without affecting the rest of the License Agreement. The remaining provisions of the License Agreement will continue to be valid and enforceable. 133 | 134 | 14.4 You acknowledge and agree that each member of the group of companies of which Google is the parent shall be third party beneficiaries to the License Agreement and that such other companies shall be entitled to directly enforce, and rely upon, any provision of the License Agreement that confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to the License Agreement. 135 | 136 | 14.5 EXPORT RESTRICTIONS. THE SDK IS SUBJECT TO UNITED STATES EXPORT LAWS AND REGULATIONS. YOU MUST COMPLY WITH ALL DOMESTIC AND INTERNATIONAL EXPORT LAWS AND REGULATIONS THAT APPLY TO THE SDK. THESE LAWS INCLUDE RESTRICTIONS ON DESTINATIONS, END USERS AND END USE. 137 | 138 | 14.6 The rights granted in the License Agreement may not be assigned or transferred by either you or Google without the prior written approval of the other party. Neither you nor Google shall be permitted to delegate their responsibilities or obligations under the License Agreement without the prior written approval of the other party. 139 | 140 | 14.7 The License Agreement, and your relationship with Google under the License Agreement, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from the License Agreement. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction. 141 | 142 | 143 | January 16, 2019 144 | 145 | 146 | 147 | 35 148 | 0 149 | 2 150 | 151 | Android SDK Platform-Tools 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/platform-tools/source.properties: -------------------------------------------------------------------------------- 1 | Pkg.UserSrc=false 2 | Pkg.Revision=35.0.2 3 | -------------------------------------------------------------------------------- /composeApp/src/desktopMain/resources/platform-tools/sqlite3.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/composeApp/src/desktopMain/resources/platform-tools/sqlite3.exe -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #Kotlin 2 | kotlin.code.style=official 3 | kotlin.daemon.jvmargs=-Xmx3072M 4 | 5 | #Gradle 6 | org.gradle.jvmargs=-Xmx3072M -Dfile.encoding=UTF-8 7 | org.gradle.configuration-cache=true 8 | org.gradle.caching=true -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | androidx-lifecycle = "2.9.1" 3 | composeHotReload = "1.0.0-alpha11" 4 | composeMultiplatform = "1.8.2" 5 | junit = "4.13.2" 6 | kotlin = "2.2.0" 7 | kotlinx-coroutines = "1.10.2" 8 | kotlinx-serialization = "1.7.3" 9 | ktor = "3.0.2" 10 | slf4j = "2.0.16" 11 | logback = "1.5.12" 12 | 13 | [libraries] 14 | kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } 15 | kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } 16 | junit = { module = "junit:junit", version.ref = "junit" } 17 | androidx-lifecycle-viewmodel = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-lifecycle" } 18 | androidx-lifecycle-runtimeCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } 19 | kotlinx-coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } 20 | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } 21 | ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } 22 | ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } 23 | ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } 24 | slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } 25 | logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } 26 | 27 | [plugins] 28 | composeHotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "composeHotReload" } 29 | composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } 30 | composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 31 | kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 32 | kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nukrs/WinDroid-ToolBox/8fd5d30b1f0a6827267efaf206ad6c6a1a1a37a7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "WinDroidToolbox" 2 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 3 | 4 | pluginManagement { 5 | repositories { 6 | google { 7 | mavenContent { 8 | includeGroupAndSubgroups("androidx") 9 | includeGroupAndSubgroups("com.android") 10 | includeGroupAndSubgroups("com.google") 11 | } 12 | } 13 | mavenCentral() 14 | gradlePluginPortal() 15 | } 16 | } 17 | 18 | dependencyResolutionManagement { 19 | repositories { 20 | google { 21 | mavenContent { 22 | includeGroupAndSubgroups("androidx") 23 | includeGroupAndSubgroups("com.android") 24 | includeGroupAndSubgroups("com.google") 25 | } 26 | } 27 | mavenCentral() 28 | } 29 | } 30 | 31 | plugins { 32 | id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" 33 | } 34 | 35 | include(":composeApp") --------------------------------------------------------------------------------