From 1aa67280fe8dcfac8b466c56025287b0cddd4114 Mon Sep 17 00:00:00 2001 From: BBIT-Kai <2911862937@qq.com> Date: Wed, 13 Aug 2025 09:17:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=A4=9A=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E6=8B=8D=E7=85=A7=E8=BD=AF=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + mul/BBIT_AI/README.md | 11 - mul/BBIT_AI/composeApp/build.gradle.kts | 10 +- .../kotlin/com/bbitcn/bbit_ai/MainActivity.kt | 3 +- .../com/bbitcn/bbit_ai/Platform.android.kt | 9 - .../bbit_ai/plantform/MyCamera.android.kt | 33 +++ .../ui/mainFunction/MainScreen.android.kt | 12 + .../kotlin/com/bbitcn/bbit_ai/App.kt | 48 ++-- .../kotlin/com/bbitcn/bbit_ai/Greeting.kt | 9 - .../kotlin/com/bbitcn/bbit_ai/Platform.kt | 7 - .../com/bbitcn/bbit_ai/base/BaseViewModel.kt | 200 ++++++++++++++++ .../bbit_ai/base/CustomMaterialComponents.kt | 1 + .../com/bbitcn/bbit_ai/model/SaveData.kt | 10 + .../com/bbitcn/bbit_ai/plantform/MyCamera.kt | 16 ++ .../bbit_ai/ui/mainFunction/MainScreen.kt | 226 ++++++++++++++++++ .../bbit_ai/ui/mainFunction/MainViewModel.kt | 81 +++++++ .../com/bbitcn/bbit_ai/utils/DataFileUtil.kt | 54 +++++ .../com/bbitcn/bbit_ai/utils/MyUtils.kt | 13 + .../bbit_ai/plantform/MyCamera.desktop.kt | 123 ++++++++++ .../ui/mainFunction/MainScreen.desktop.kt | 40 ++++ .../kotlin/com/bbitcn/bbit_ai/Platform.jvm.kt | 7 - mul/BBIT_AI/data/data.txt | 9 + mul/BbitAI/.idea/deploymentTargetSelector.xml | 10 - mul/BbitAI/.idea/workspace.xml | 176 -------------- 24 files changed, 845 insertions(+), 265 deletions(-) create mode 100644 .gitignore delete mode 100644 mul/BBIT_AI/README.md delete mode 100644 mul/BBIT_AI/composeApp/src/androidMain/kotlin/com/bbitcn/bbit_ai/Platform.android.kt create mode 100644 mul/BBIT_AI/composeApp/src/androidMain/kotlin/com/bbitcn/bbit_ai/plantform/MyCamera.android.kt create mode 100644 mul/BBIT_AI/composeApp/src/androidMain/kotlin/com/bbitcn/bbit_ai/ui/mainFunction/MainScreen.android.kt delete mode 100644 mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/Greeting.kt delete mode 100644 mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/Platform.kt create mode 100644 mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/base/BaseViewModel.kt create mode 100644 mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/base/CustomMaterialComponents.kt create mode 100644 mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/model/SaveData.kt create mode 100644 mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/plantform/MyCamera.kt create mode 100644 mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/ui/mainFunction/MainScreen.kt create mode 100644 mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/ui/mainFunction/MainViewModel.kt create mode 100644 mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/utils/DataFileUtil.kt create mode 100644 mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/utils/MyUtils.kt create mode 100644 mul/BBIT_AI/composeApp/src/desktopMain/java/com/bbitcn/bbit_ai/plantform/MyCamera.desktop.kt create mode 100644 mul/BBIT_AI/composeApp/src/desktopMain/java/com/bbitcn/bbit_ai/ui/mainFunction/MainScreen.desktop.kt delete mode 100644 mul/BBIT_AI/composeApp/src/desktopMain/kotlin/com/bbitcn/bbit_ai/Platform.jvm.kt create mode 100644 mul/BBIT_AI/data/data.txt delete mode 100644 mul/BbitAI/.idea/deploymentTargetSelector.xml delete mode 100644 mul/BbitAI/.idea/workspace.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4be8d40 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +mul/BBIT_AI/data/image/raw/ +mul/BBIT_AI/data/image/ai/ diff --git a/mul/BBIT_AI/README.md b/mul/BBIT_AI/README.md deleted file mode 100644 index d0c8e67..0000000 --- a/mul/BBIT_AI/README.md +++ /dev/null @@ -1,11 +0,0 @@ -This is a Kotlin Multiplatform project targeting Android, Desktop. - -* `/composeApp` is for code that will be shared across your Compose Multiplatform applications. - It contains several subfolders: - - `commonMain` is for code that’s common for all targets. - - Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name. - For example, if you want to use Apple’s CoreCrypto for the iOS part of your Kotlin app, - `iosMain` would be the right folder for such calls. - - -Learn more about [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html)… \ No newline at end of file diff --git a/mul/BBIT_AI/composeApp/build.gradle.kts b/mul/BBIT_AI/composeApp/build.gradle.kts index 8aad16c..7434a2d 100644 --- a/mul/BBIT_AI/composeApp/build.gradle.kts +++ b/mul/BBIT_AI/composeApp/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { androidTarget { @OptIn(ExperimentalKotlinGradlePluginApi::class) compilerOptions { - jvmTarget.set(JvmTarget.JVM_11) + jvmTarget.set(JvmTarget.JVM_21) } } @@ -36,6 +36,8 @@ kotlin { implementation(compose.components.uiToolingPreview) implementation(libs.androidx.lifecycle.viewmodel) implementation(libs.androidx.lifecycle.runtimeCompose) + // 通用ViewModel + implementation("org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.9.1") } commonTest.dependencies { implementation(libs.kotlin.test) @@ -43,6 +45,8 @@ kotlin { desktopMain.dependencies { implementation(compose.desktop.currentOs) implementation(libs.kotlinx.coroutinesSwing) + // 摄像头 + implementation("org.bytedeco:javacv-platform:1.5.9") } } } @@ -69,8 +73,8 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } } diff --git a/mul/BBIT_AI/composeApp/src/androidMain/kotlin/com/bbitcn/bbit_ai/MainActivity.kt b/mul/BBIT_AI/composeApp/src/androidMain/kotlin/com/bbitcn/bbit_ai/MainActivity.kt index 6aa28c4..6996d96 100644 --- a/mul/BBIT_AI/composeApp/src/androidMain/kotlin/com/bbitcn/bbit_ai/MainActivity.kt +++ b/mul/BBIT_AI/composeApp/src/androidMain/kotlin/com/bbitcn/bbit_ai/MainActivity.kt @@ -6,6 +6,7 @@ import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -18,7 +19,7 @@ class MainActivity : ComponentActivity() { } } -@Preview +@Preview(widthDp = 1280, heightDp = 800) @Composable fun AppAndroidPreview() { App() diff --git a/mul/BBIT_AI/composeApp/src/androidMain/kotlin/com/bbitcn/bbit_ai/Platform.android.kt b/mul/BBIT_AI/composeApp/src/androidMain/kotlin/com/bbitcn/bbit_ai/Platform.android.kt deleted file mode 100644 index a2128e1..0000000 --- a/mul/BBIT_AI/composeApp/src/androidMain/kotlin/com/bbitcn/bbit_ai/Platform.android.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.bbitcn.bbit_ai - -import android.os.Build - -class AndroidPlatform : Platform { - override val name: String = "Android ${Build.VERSION.SDK_INT}" -} - -actual fun getPlatform(): Platform = AndroidPlatform() \ No newline at end of file diff --git a/mul/BBIT_AI/composeApp/src/androidMain/kotlin/com/bbitcn/bbit_ai/plantform/MyCamera.android.kt b/mul/BBIT_AI/composeApp/src/androidMain/kotlin/com/bbitcn/bbit_ai/plantform/MyCamera.android.kt new file mode 100644 index 0000000..a580f78 --- /dev/null +++ b/mul/BBIT_AI/composeApp/src/androidMain/kotlin/com/bbitcn/bbit_ai/plantform/MyCamera.android.kt @@ -0,0 +1,33 @@ +package com.bbitcn.bbit_ai.plantform.CommonInterface + +import androidx.compose.ui.graphics.ImageBitmap + +actual class MyCamera { + actual fun stopCamera() { + TODO("Not yet implemented") + } + + actual fun listAvailableCameras(): List { + return List(3) { "Camera $it" } // 假设有 3 个摄像头 + } + + actual suspend fun startCamera(index: Int) { + TODO("Not yet implemented") + } + + actual suspend fun takePhoto(): ImageBitmap? { + TODO("Not yet implemented") + } + + actual fun saveImageBitmapToFile(image: ImageBitmap) { + TODO("Not yet implemented") + } + + actual fun loadImage(filePath: String): ImageBitmap? { + TODO("Not yet implemented") + } +} + +actual fun getMyCamera(): MyCamera { + return com.bbitcn.bbit_ai.plantform.CommonInterface.MyCamera() +} \ No newline at end of file diff --git a/mul/BBIT_AI/composeApp/src/androidMain/kotlin/com/bbitcn/bbit_ai/ui/mainFunction/MainScreen.android.kt b/mul/BBIT_AI/composeApp/src/androidMain/kotlin/com/bbitcn/bbit_ai/ui/mainFunction/MainScreen.android.kt new file mode 100644 index 0000000..170f8a3 --- /dev/null +++ b/mul/BBIT_AI/composeApp/src/androidMain/kotlin/com/bbitcn/bbit_ai/ui/mainFunction/MainScreen.android.kt @@ -0,0 +1,12 @@ +package com.bbitcn.bbit_ai.ui.mainFunction + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bbitcn.bbit_ai.plantform.CommonInterface.MyCamera + +@Composable +actual fun CameraPreview( + modifier: Modifier, + controller: MyCamera +) { +} \ No newline at end of file diff --git a/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/App.kt b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/App.kt index 072846d..83ec651 100644 --- a/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/App.kt +++ b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/App.kt @@ -1,44 +1,28 @@ package com.bbitcn.bbit_ai -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.safeContentPadding -import androidx.compose.material3.Button +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.* -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.ui.tooling.preview.Preview -import bbit_ai.composeapp.generated.resources.Res -import bbit_ai.composeapp.generated.resources.compose_multiplatform +import com.bbitcn.bbit_ai.ui.mainFunction.MainScreen + + +val M = Modifier + .animateContentSize( + animationSpec = spring( + dampingRatio = Spring.DampingRatioLowBouncy, + stiffness = Spring.StiffnessLow + ) + ) @Composable -@Preview +@Preview() fun App() { MaterialTheme { - var showContent by remember { mutableStateOf(false) } - Column( - modifier = Modifier - .safeContentPadding() - .fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Button(onClick = { showContent = !showContent }) { - Text("Click me!") - } - AnimatedVisibility(showContent) { - val greeting = remember { Greeting().greet() } - Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { - Image(painterResource(Res.drawable.compose_multiplatform), null) - Text("Compose: $greeting") - } - } - } + MainScreen() } -} \ No newline at end of file +} diff --git a/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/Greeting.kt b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/Greeting.kt deleted file mode 100644 index 2c5349e..0000000 --- a/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/Greeting.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.bbitcn.bbit_ai - -class Greeting { - private val platform = getPlatform() - - fun greet(): String { - return "Hello, ${platform.name}!" - } -} \ No newline at end of file diff --git a/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/Platform.kt b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/Platform.kt deleted file mode 100644 index a97f45d..0000000 --- a/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/Platform.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.bbitcn.bbit_ai - -interface Platform { - val name: String -} - -expect fun getPlatform(): Platform \ No newline at end of file diff --git a/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/base/BaseViewModel.kt b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/base/BaseViewModel.kt new file mode 100644 index 0000000..3a548b6 --- /dev/null +++ b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/base/BaseViewModel.kt @@ -0,0 +1,200 @@ +package com.bbitcn.f8.pad.base + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext +import java.net.SocketException +import kotlin.coroutines.cancellation.CancellationException + +open class BaseViewModel : ViewModel() { + + + protected fun doInUIThread(task: () -> Unit) { + viewModelScope.launch { + withContext(Dispatchers.Main) { + task() + } + } + } + + protected fun doInIoThreadThenUI( + loadingTips: String = "正在加载中", + showDialog: Boolean = true, + onError: (Throwable) -> Unit = { }, + onIO: suspend () -> T, + onFinish: () -> Unit = { }, + onUI: (T) -> Unit, + ) { + viewModelScope.launch { + val result = kotlin.runCatching { + withContext(Dispatchers.IO) { + if (showDialog) { +// Toasty.showLoadingDialog(loadingTips) + } + onIO() + } + } + + if (showDialog) { +// hideLoadingDialog() + } + + withContext(Dispatchers.Main) { + result.onSuccess { data -> + onUI(data) + }.onFailure { exception -> + // ✅ 如果是协程取消,不处理,只记录日志 + if (exception is CancellationException || exception.cause is SocketException && exception.cause?.message?.contains( + "Socket closed" + ) == true + ) { +// MyLog.test("协程被取消:${exception.javaClass.simpleName},message=${exception.message}") + return@onFailure + } + // 其他异常继续处理 + exception.printStackTrace() + onError(exception) + exception.message?.let { +// Toasty.error(it) + } + }.also { + // ✅ 最终执行的操作 + onFinish() + } + } + } + } + + + fun doInIoThread( + loadingTips: String = "正在加载中", + showDialog: Boolean = true, + onError: (Throwable) -> Unit = { }, + onFinish: () -> Unit = { }, + doInIO: suspend () -> T, + ) { + doInIoThreadThenUI(loadingTips, showDialog, onError, doInIO, onFinish) { } + } + + fun doInIoThreadNoDialog( + onError: (Throwable) -> Unit = { }, + task: suspend () -> T, + ) { + doInIoThread(showDialog = false, doInIO = task, onError = onError) + } + + /** + * 在IO线程中执行任务,可选择是否显示加载对话框 + */ + fun doInIoThreadWith(showLoading: Boolean, loadingTips: String, function: suspend () -> Unit) { + if (showLoading) { + doInIoThread(loadingTips) { function() } + } else { + doInIoThreadNoDialog { function() } + } + } + + /** + * 启动一个无限轮询任务 + * + * @param pollingInterval 轮询间隔时间(单位:秒) + * @param pollingTask 轮询任务的挂起函数 + */ + fun polling(intervalSeconds: Long, task: suspend () -> Unit) { + viewModelScope.launch { + withContext(Dispatchers.IO) { +// MyLog.test("开始轮询任务,间隔:$intervalSeconds 秒") + while (true) { + task() + delay(intervalSeconds * 1000L) // 转换秒为毫秒 + } + } + } + } + + /** + * 延迟开始轮询任务 + */ + suspend fun delayPolling(delaySeconds: Long, intervalSeconds: Long, task: suspend () -> Unit) { + delay(delaySeconds * 1000L) + polling(intervalSeconds, task) + } + + private val taskMap = mutableMapOf() + + /** + * 放弃旧任务,执行新任务 + * + * @param key 任务的唯一标识符 + * @param block 任务的挂起函数,必须使用协程,不能开启新的协程,否则无法取消任务 + */ + fun launchTaskNewFirst( + key: String, + block: suspend () -> Unit + ) { + taskMap[key]?.cancel() // 取消旧任务 + val job = viewModelScope.launch { + withContext(Dispatchers.IO) { + try { + block() + } catch (e: Exception) { + e.printStackTrace() + } + } + } + taskMap[key] = job + } + + /** + * 有新任务时,取消。优先执行旧任务直到完成 + * + * @param key 任务的唯一标识符 + * @param block 任务函数,可以自由新建协程,但一定要在任务完成时调用 onFinished 回调,否则会导致后续任务永远无法执行 + */ + @OptIn(ExperimentalCoroutinesApi::class) + fun launchTaskOldFirst( + key: String, + block: (onFinished: () -> Unit) -> Unit + ) { + val existing = taskMap[key] + if (existing?.isActive == true) { + return + } + val job = viewModelScope.launch { + try { + suspendCancellableCoroutine { continuation -> + block { + if (continuation.isActive) { + continuation.resume(Unit) { cause -> + println("resume 后任务被取消: $cause") + } + } + taskMap.remove(key) + } + } + } catch (e: Exception) { + e.printStackTrace() + taskMap.remove(key) + } + } + taskMap[key] = job + } + + override fun onCleared() { + super.onCleared() + taskMap.values.forEach { it.cancel() } + taskMap.clear() + } + +} diff --git a/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/base/CustomMaterialComponents.kt b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/base/CustomMaterialComponents.kt new file mode 100644 index 0000000..41c702f --- /dev/null +++ b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/base/CustomMaterialComponents.kt @@ -0,0 +1 @@ +package com.bbitcn.bbit_ai.base diff --git a/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/model/SaveData.kt b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/model/SaveData.kt new file mode 100644 index 0000000..d6a5028 --- /dev/null +++ b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/model/SaveData.kt @@ -0,0 +1,10 @@ +package com.bbitcn.bbit_ai.model + +data class SaveData( + val id: String, + val projectName: String, + val createTime: String, + val rawImagePath: String, + val aiImagePath: String, + val extraJson: String +) \ No newline at end of file diff --git a/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/plantform/MyCamera.kt b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/plantform/MyCamera.kt new file mode 100644 index 0000000..e2fcda2 --- /dev/null +++ b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/plantform/MyCamera.kt @@ -0,0 +1,16 @@ +package com.bbitcn.bbit_ai.plantform.CommonInterface + +import androidx.compose.ui.graphics.ImageBitmap + + +expect class MyCamera { + suspend fun startCamera(index: Int = 0) + fun stopCamera() + fun listAvailableCameras(): List + suspend fun takePhoto(): ImageBitmap? + + fun saveImageBitmapToFile(image: ImageBitmap) + fun loadImage(filePath: String): ImageBitmap? +} + +expect fun getMyCamera(): MyCamera diff --git a/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/ui/mainFunction/MainScreen.kt b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/ui/mainFunction/MainScreen.kt new file mode 100644 index 0000000..bd51c65 --- /dev/null +++ b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/ui/mainFunction/MainScreen.kt @@ -0,0 +1,226 @@ +package com.bbitcn.bbit_ai.ui.mainFunction + +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeContentPadding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Button +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import bbit_ai.composeapp.generated.resources.Res +import bbit_ai.composeapp.generated.resources.compose_multiplatform +import com.bbitcn.bbit_ai.plantform.CommonInterface.MyCamera +import com.bbitcn.bbit_ai.M +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.ui.tooling.preview.Preview + +// commonMain +@Composable +expect fun CameraPreview(modifier: Modifier = M, controller: MyCamera) + +@Preview +@Composable +fun MainScreen( + mainViewModel: MainViewModel = viewModel(), +) { + Row( + modifier = Modifier + .safeContentPadding() + .padding(10.dp) + .fillMaxSize(), + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + var expanded by remember { mutableStateOf(false) } + val cameraList by mainViewModel.cameraList.collectAsState() + var selectedIndex by remember { mutableStateOf(-1) } + val detail by mainViewModel.curDetail.collectAsState() + LaunchedEffect(cameraList) { + if (cameraList.isNotEmpty() && selectedIndex == -1) { + selectedIndex = 0 // 默认选择第一个摄像头 + mainViewModel.startCamera(selectedIndex) // 启动默认摄像头 + } + } + + Column(modifier = M.width(250.dp), horizontalAlignment = Alignment.CenterHorizontally) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = "摄像头:", + modifier = M.padding(end = 10.dp), + style = MaterialTheme.typography.titleMedium + ) + Column { + Text( + text = if (cameraList.isEmpty() || selectedIndex == -1) "未检测到摄像头" else cameraList[selectedIndex], + modifier = M + .padding(10.dp) + .clickable { + expanded = true + }, + style = MaterialTheme.typography.titleMedium + ) + DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { + cameraList.forEachIndexed { index, label -> + DropdownMenuItem(text = { + Text(label) + }, onClick = { + selectedIndex = index + expanded = false + }) + } + } + } + } + Row(verticalAlignment = Alignment.CenterVertically) { + // 刷新摄像头按钮 + Button( + modifier = M.padding(2.5.dp), + onClick = { mainViewModel.refreshCameraList() } + ) { + Text("刷新") + } + Button( + modifier = M.padding(2.5.dp), onClick = { + mainViewModel.startCamera(selectedIndex) + }) { + Text("连接") + } + Button( + modifier = M.padding(2.5.dp), onClick = { + mainViewModel.takePhots() + }) { + Text("拍照") + } + } + Row(verticalAlignment = Alignment.CenterVertically) { + // 刷新摄像头按钮 + Button( + modifier = M.padding(2.5.dp), + onClick = { mainViewModel.initData() } + ) { + Text("初始化数据") + } + Button( + modifier = M.padding(2.5.dp), onClick = { + mainViewModel.refreshDataList() + }) { + Text("刷新列表") + } + } + HorizontalDivider(modifier = M.padding(5.dp)) + val list by mainViewModel.analyticsList.collectAsState() + LazyColumn(modifier = M.weight(1f)) { + items(list) { + Button(modifier = M.padding(5.dp).animateItem(), onClick = { + mainViewModel.setCurDetail(it) + }) { + Column { + Text( + text = it.projectName, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.Bold, + fontSize = MaterialTheme.typography.bodyLarge.fontSize, + modifier = M.padding(2.5.dp) + ) + Text( + text = it.createTime, + maxLines = 1, + fontSize = MaterialTheme.typography.bodySmall.fontSize, + modifier = M.padding(2.5.dp) + ) + } + } + } + } + HorizontalDivider(modifier = M.padding(5.dp)) + Box(modifier = M.fillMaxWidth().height(50.dp)) { + Text( + text = detail?.extraJson ?: "", + modifier = M.padding(5.dp).fillMaxSize(), + style = MaterialTheme.typography.bodyMedium + ) + } + } + CameraPreview(M.fillMaxHeight().weight(2f).widthIn(min = 600.dp), mainViewModel.controller) + Column( + modifier = M.weight(1f).fillMaxHeight(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + val picRaw by mainViewModel.picRaw.collectAsState() + if (picRaw == null) { + Column( + Modifier.fillMaxWidth().weight(1f), + horizontalAlignment = Alignment.CenterHorizontally + ) { + MyArea() + } + } else { + Image( + modifier = Modifier.fillMaxWidth().weight(1f), + bitmap = picRaw!!, + contentDescription = null, + contentScale = ContentScale.FillHeight + ) + } + val picAi by mainViewModel.picAi.collectAsState() + if (picAi == null) { + Column( + Modifier.fillMaxWidth().weight(1f), + horizontalAlignment = Alignment.CenterHorizontally + ) { + MyArea() + } + } else { + Image( + modifier = Modifier.fillMaxWidth().weight(1f), + bitmap = picAi!!, + contentDescription = null, + contentScale = ContentScale.FillHeight + ) + } + } + } +} + +@Composable +fun MyArea(modifier: Modifier = M) { + Image( + modifier = modifier.border( + width = 1.dp, + color = MaterialTheme.colorScheme.primary + ), + painter = painterResource(Res.drawable.compose_multiplatform), + contentDescription = null + ) +} + diff --git a/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/ui/mainFunction/MainViewModel.kt b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/ui/mainFunction/MainViewModel.kt new file mode 100644 index 0000000..fc1ff9d --- /dev/null +++ b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/ui/mainFunction/MainViewModel.kt @@ -0,0 +1,81 @@ +package com.bbitcn.bbit_ai.ui.mainFunction + +import androidx.compose.ui.graphics.ImageBitmap +import com.bbitcn.bbit_ai.model.SaveData +import com.bbitcn.bbit_ai.plantform.CommonInterface.getMyCamera +import com.bbitcn.bbit_ai.utils.DataFileUtil +import com.bbitcn.f8.pad.base.BaseViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import java.io.File + +class MainViewModel : BaseViewModel() { + + val controller = getMyCamera() + private val _cameraList = MutableStateFlow(listOf()) + val cameraList = _cameraList.asStateFlow() + private val _analyticsList = MutableStateFlow(listOf()) + val analyticsList = _analyticsList.asStateFlow() + private val _curDetail = MutableStateFlow(null) + val curDetail = _curDetail.asStateFlow() + private val _picRaw = MutableStateFlow(null) + val picRaw = _picRaw.asStateFlow() + private val _picAi = MutableStateFlow(null) + val picAi = _picAi.asStateFlow() + + init { + refreshCameraList() + refreshDataList { + if (_curDetail.value == null && _analyticsList.value.isNotEmpty()) { + setCurDetail(_analyticsList.value.first()) + } + } + } + + fun refreshCameraList() { + doInIoThread { + _cameraList.value = controller.listAvailableCameras() // 获取摄像头列表(见下方) + } + } + + fun startCamera(index: Int = 0) { + launchTaskNewFirst("启动摄像头") { + controller.startCamera(index) + } + } + + fun takePhots() { + doInIoThread { + controller.takePhoto()?.let { + controller.saveImageBitmapToFile(it) + refreshDataList() + } + } + } + + fun initData() { + doInIoThread { + DataFileUtil.init() // 初始化数据文件 + refreshDataList() + _curDetail.value = null // 清空当前详情 + _picRaw.value = null // 清空图片预览 + _picAi.value = null // 清空图片预览 + } + } + + fun refreshDataList(onFinish: () -> Unit = {}) { + doInIoThread { + _analyticsList.value = DataFileUtil.readAll() + onFinish() + } + } + + fun setCurDetail(it: SaveData) { + doInIoThread { + _curDetail.value = it + _picRaw.value = controller.loadImage(it.rawImagePath) + _picAi.value = controller.loadImage(it.aiImagePath) + } + } + +} \ No newline at end of file diff --git a/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/utils/DataFileUtil.kt b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/utils/DataFileUtil.kt new file mode 100644 index 0000000..8b50f02 --- /dev/null +++ b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/utils/DataFileUtil.kt @@ -0,0 +1,54 @@ +package com.bbitcn.bbit_ai.utils + +import com.bbitcn.bbit_ai.model.SaveData +import java.io.File + +object DataFileUtil { + + private val dataDir = File("data") + private val dataFile = File(dataDir, "data.txt") + val rawImage = File(dataDir,"image/raw") + val aiImage = File(dataDir,"image/ai") + + fun init() { + if (!dataDir.exists()) { + dataDir.mkdirs() + } + if (!rawImage.exists()) { + rawImage.mkdirs() + } + if (!aiImage.exists()) { + aiImage.mkdirs() + } + dataFile.writeText("") // 清空内容 + } + + fun addData( + info: SaveData + ) { + if (!dataDir.exists()) dataDir.mkdirs() + val line = info.id + "\t" + + info.projectName + "\t" + + info.createTime + "\t" + + info.rawImagePath + "\t" + + info.aiImagePath + "\t" + + info.extraJson + dataFile.appendText(line + "\n") + } + + fun readAll(): List { + if (!dataFile.exists()) return emptyList() + return dataFile.readLines().mapNotNull { line -> + val parts = line.split("\t") + if (parts.size < 6) return@mapNotNull null + SaveData( + id = parts[0], + projectName = parts[1], + createTime = parts[2], + rawImagePath = parts[3], + aiImagePath = parts[4], + extraJson = parts[5] + ) + } + } +} diff --git a/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/utils/MyUtils.kt b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/utils/MyUtils.kt new file mode 100644 index 0000000..6307a52 --- /dev/null +++ b/mul/BBIT_AI/composeApp/src/commonMain/kotlin/com/bbitcn/bbit_ai/utils/MyUtils.kt @@ -0,0 +1,13 @@ +package com.bbitcn.bbit_ai.utils + +object MyUtils { + fun getCurrentTime(): String { + val currentTime = System.currentTimeMillis() + return java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(java.util.Date(currentTime)) + } + + fun getCurrentDate(): String { + val currentDate = System.currentTimeMillis() + return java.text.SimpleDateFormat("yyyy-MM-dd").format(java.util.Date(currentDate)) + } +} \ No newline at end of file diff --git a/mul/BBIT_AI/composeApp/src/desktopMain/java/com/bbitcn/bbit_ai/plantform/MyCamera.desktop.kt b/mul/BBIT_AI/composeApp/src/desktopMain/java/com/bbitcn/bbit_ai/plantform/MyCamera.desktop.kt new file mode 100644 index 0000000..27dd744 --- /dev/null +++ b/mul/BBIT_AI/composeApp/src/desktopMain/java/com/bbitcn/bbit_ai/plantform/MyCamera.desktop.kt @@ -0,0 +1,123 @@ +package com.bbitcn.bbit_ai.plantform.CommonInterface + +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asSkiaBitmap +import androidx.compose.ui.graphics.toComposeImageBitmap +import com.bbitcn.bbit_ai.model.SaveData +import com.bbitcn.bbit_ai.utils.DataFileUtil +import com.bbitcn.bbit_ai.utils.MyUtils +import kotlinx.coroutines.flow.MutableStateFlow +import org.bytedeco.javacv.Java2DFrameConverter +import org.bytedeco.javacv.OpenCVFrameGrabber +import org.bytedeco.opencv.opencv_videoio.VideoCapture +import org.jetbrains.skia.Image +import java.awt.image.BufferedImage +import java.io.ByteArrayOutputStream +import java.io.File +import javax.imageio.ImageIO +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid + +actual class MyCamera { + val frameFlow = MutableStateFlow(null) + private var running = false + private var curCameraId = 0 + + actual suspend fun startCamera(index: Int) { + curCameraId = index + running = false + val grabber = OpenCVFrameGrabber(index) + try { + grabber.start() + val converter = Java2DFrameConverter() + running = true + while (running) { + val frame = grabber.grab() ?: continue + val image = converter.getBufferedImage(frame) ?: continue + frameFlow.value = image.toComposeImageBitmap() + } + } catch (e: Exception) { + println("Camera grab error: ${e.message}") + } finally { + grabber.stop() + grabber.release() + } + } + + actual fun stopCamera() { + running = false + } + + private fun BufferedImage.toComposeImageBitmap(): ImageBitmap { + val bytes = ByteArrayOutputStream().use { + ImageIO.write(this, "png", it) + it.toByteArray() + } + val bitmap = Image.makeFromEncoded(bytes).toComposeImageBitmap() + return bitmap + } + + actual fun listAvailableCameras(): List { + val availableCameras = mutableListOf() + for (i in 0..3) { + val capture = VideoCapture(i) + if (capture.isOpened) { + availableCameras.add("Camera $i") + capture.release() + } + } + return availableCameras + } + + actual suspend fun takePhoto(): ImageBitmap? { + return frameFlow.value + } + + @OptIn(ExperimentalUuidApi::class) + actual fun saveImageBitmapToFile(image: ImageBitmap) { + val bufferedImage = image.toBufferedImage() + val imageName = "photo_${System.currentTimeMillis()}.png" + val uuid = Uuid.random().toString() + DataFileUtil.addData( + SaveData( + Uuid.random().toString(), + imageName, + MyUtils.getCurrentTime(), + DataFileUtil.rawImage.absolutePath + File.separator + imageName, + DataFileUtil.aiImage.absolutePath + File.separator + imageName, + uuid + "额外信息" + ) + ) + ImageIO.write( + bufferedImage, + "png", + File(DataFileUtil.rawImage, imageName) + ) + ImageIO.write( + bufferedImage, + "png", + File(DataFileUtil.aiImage, imageName) + ) + } + + actual fun loadImage(filePath: String): ImageBitmap? { + return try { + val bytes = File(filePath).readBytes() + val skiaImage = Image.makeFromEncoded(bytes) + skiaImage.toComposeImageBitmap() + } catch (e: Exception) { + println("Image decode error: ${e.message}") + null + } + } + + fun ImageBitmap.toBufferedImage(): BufferedImage { + val skiaImage = Image.makeFromBitmap(this.asSkiaBitmap()) + val bytes = skiaImage.encodeToData()?.bytes ?: throw Exception("Encode failed") + return ImageIO.read(bytes.inputStream()) + } +} + +actual fun getMyCamera(): MyCamera { + return MyCamera() +} \ No newline at end of file diff --git a/mul/BBIT_AI/composeApp/src/desktopMain/java/com/bbitcn/bbit_ai/ui/mainFunction/MainScreen.desktop.kt b/mul/BBIT_AI/composeApp/src/desktopMain/java/com/bbitcn/bbit_ai/ui/mainFunction/MainScreen.desktop.kt new file mode 100644 index 0000000..15e3c75 --- /dev/null +++ b/mul/BBIT_AI/composeApp/src/desktopMain/java/com/bbitcn/bbit_ai/ui/mainFunction/MainScreen.desktop.kt @@ -0,0 +1,40 @@ +package com.bbitcn.bbit_ai.ui.mainFunction + +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import com.bbitcn.bbit_ai.M +import com.bbitcn.bbit_ai.plantform.CommonInterface.MyCamera + + +@Composable +actual fun CameraPreview( + modifier: Modifier, + controller: MyCamera +) { + val imageBitmap = controller.frameFlow.collectAsState() + // 显示图像 + Box( + modifier = modifier + .border( + width = 1.dp, + color = androidx.compose.material3.MaterialTheme.colorScheme.primary + ) + .fillMaxSize() + ) { + imageBitmap.value?.let { + Image( + modifier = M.fillMaxSize(), + bitmap = it, + contentDescription = null, + contentScale = ContentScale.FillHeight + ) + } + } +} \ No newline at end of file diff --git a/mul/BBIT_AI/composeApp/src/desktopMain/kotlin/com/bbitcn/bbit_ai/Platform.jvm.kt b/mul/BBIT_AI/composeApp/src/desktopMain/kotlin/com/bbitcn/bbit_ai/Platform.jvm.kt deleted file mode 100644 index 6745cb7..0000000 --- a/mul/BBIT_AI/composeApp/src/desktopMain/kotlin/com/bbitcn/bbit_ai/Platform.jvm.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.bbitcn.bbit_ai - -class JVMPlatform: Platform { - override val name: String = "Java ${System.getProperty("java.version")}" -} - -actual fun getPlatform(): Platform = JVMPlatform() \ No newline at end of file diff --git a/mul/BBIT_AI/data/data.txt b/mul/BBIT_AI/data/data.txt new file mode 100644 index 0000000..1a676ad --- /dev/null +++ b/mul/BBIT_AI/data/data.txt @@ -0,0 +1,9 @@ +66f278f6-a1a1-41fb-aa87-b5885782b9a6 photo_1751267939500.png 2025-06-30 15:18:59 D:\Mine\ICP\mul\BBIT_AI\data\image\raw\photo_1751267939500.png D:\Mine\ICP\mul\BBIT_AI\data\image\raw\photo_1751267939500.png 4edc5ef4-f1ad-4250-be2e-99463e78613e额外信息 +157c219b-96ed-4081-9e56-bf051b53da74 photo_1751267946751.png 2025-06-30 15:19:06 D:\Mine\ICP\mul\BBIT_AI\data\image\raw\photo_1751267946751.png D:\Mine\ICP\mul\BBIT_AI\data\image\raw\photo_1751267946751.png 0226f898-e628-4aee-964d-dc9dde13a6a2额外信息 +60f3d84c-46a0-4f47-aebd-5d0a65614d77 photo_1751267947206.png 2025-06-30 15:19:07 D:\Mine\ICP\mul\BBIT_AI\data\image\raw\photo_1751267947206.png D:\Mine\ICP\mul\BBIT_AI\data\image\raw\photo_1751267947206.png 3769748f-1c68-4cb9-a064-c83a27ecf43f额外信息 +0af57555-38e3-4873-961e-2e4b1cd34e7e photo_1751267947678.png 2025-06-30 15:19:07 D:\Mine\ICP\mul\BBIT_AI\data\image\raw\photo_1751267947678.png D:\Mine\ICP\mul\BBIT_AI\data\image\raw\photo_1751267947678.png 7e4f4846-006f-4b83-b921-25d51e44be70额外信息 +7199513b-2afc-4bb8-ac6c-1d5a547f9c48 photo_1751267949120.png 2025-06-30 15:19:09 D:\Mine\ICP\mul\BBIT_AI\data\image\raw\photo_1751267949120.png D:\Mine\ICP\mul\BBIT_AI\data\image\raw\photo_1751267949120.png 611e90ff-a314-43b8-9c01-2b311aad6360额外信息 +f50ad935-f96f-4706-b40b-5c8086b50c65 photo_1751267949749.png 2025-06-30 15:19:09 D:\Mine\ICP\mul\BBIT_AI\data\image\raw\photo_1751267949749.png D:\Mine\ICP\mul\BBIT_AI\data\image\raw\photo_1751267949749.png 272353f8-dd0b-4fe9-8c46-a9b98c5f7f5d额外信息 +3ce6ce04-6202-4632-a9b9-ec179f3c98d9 photo_1751268000111.png 2025-06-30 15:20:00 D:\Mine\ICP\mul\BBIT_AI\data\image\raw\photo_1751268000111.png D:\Mine\ICP\mul\BBIT_AI\data\image\raw\photo_1751268000111.png 8af58088-d5fc-4bcc-9746-a2105296807f额外信息 +b319048d-8394-4506-a989-e955ddd91097 photo_1751268002014.png 2025-06-30 15:20:02 D:\Mine\ICP\mul\BBIT_AI\data\image\raw\photo_1751268002014.png D:\Mine\ICP\mul\BBIT_AI\data\image\raw\photo_1751268002014.png be0efdcd-2293-4282-b59f-3590cb99dc6a额外信息 +e4b3e02d-3a8f-4c70-ab54-f88f31eeebbf photo_1751268003292.png 2025-06-30 15:20:03 D:\Mine\ICP\mul\BBIT_AI\data\image\raw\bfca3a998659ec179a76ac99666b529.jpg D:\Mine\ICP\mul\BBIT_AI\data\image\ai\bfca3a998659ec179a76ac99666b529.jpg 检测结果:14 \ No newline at end of file diff --git a/mul/BbitAI/.idea/deploymentTargetSelector.xml b/mul/BbitAI/.idea/deploymentTargetSelector.xml deleted file mode 100644 index d0b0930..0000000 --- a/mul/BbitAI/.idea/deploymentTargetSelector.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/mul/BbitAI/.idea/workspace.xml b/mul/BbitAI/.idea/workspace.xml deleted file mode 100644 index 2ffedc0..0000000 --- a/mul/BbitAI/.idea/workspace.xml +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -