├── .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 |
11 |
12 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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")
--------------------------------------------------------------------------------