完成多平台拍照软件
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
mul/BBIT_AI/data/image/raw/
|
||||||
|
mul/BBIT_AI/data/image/ai/
|
||||||
@@ -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)…
|
|
||||||
@@ -14,7 +14,7 @@ kotlin {
|
|||||||
androidTarget {
|
androidTarget {
|
||||||
@OptIn(ExperimentalKotlinGradlePluginApi::class)
|
@OptIn(ExperimentalKotlinGradlePluginApi::class)
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
jvmTarget.set(JvmTarget.JVM_11)
|
jvmTarget.set(JvmTarget.JVM_21)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +36,8 @@ kotlin {
|
|||||||
implementation(compose.components.uiToolingPreview)
|
implementation(compose.components.uiToolingPreview)
|
||||||
implementation(libs.androidx.lifecycle.viewmodel)
|
implementation(libs.androidx.lifecycle.viewmodel)
|
||||||
implementation(libs.androidx.lifecycle.runtimeCompose)
|
implementation(libs.androidx.lifecycle.runtimeCompose)
|
||||||
|
// 通用ViewModel
|
||||||
|
implementation("org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.9.1")
|
||||||
}
|
}
|
||||||
commonTest.dependencies {
|
commonTest.dependencies {
|
||||||
implementation(libs.kotlin.test)
|
implementation(libs.kotlin.test)
|
||||||
@@ -43,6 +45,8 @@ kotlin {
|
|||||||
desktopMain.dependencies {
|
desktopMain.dependencies {
|
||||||
implementation(compose.desktop.currentOs)
|
implementation(compose.desktop.currentOs)
|
||||||
implementation(libs.kotlinx.coroutinesSwing)
|
implementation(libs.kotlinx.coroutinesSwing)
|
||||||
|
// 摄像头
|
||||||
|
implementation("org.bytedeco:javacv-platform:1.5.9")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,8 +73,8 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.activity.compose.setContent
|
|||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -18,7 +19,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview(widthDp = 1280, heightDp = 800)
|
||||||
@Composable
|
@Composable
|
||||||
fun AppAndroidPreview() {
|
fun AppAndroidPreview() {
|
||||||
App()
|
App()
|
||||||
|
|||||||
@@ -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()
|
|
||||||
+33
@@ -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<String> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
+12
@@ -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
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -1,44 +1,28 @@
|
|||||||
package com.bbitcn.bbit_ai
|
package com.bbitcn.bbit_ai
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.animation.core.Spring
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.animation.core.spring
|
||||||
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.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import org.jetbrains.compose.resources.painterResource
|
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
import bbit_ai.composeapp.generated.resources.Res
|
import com.bbitcn.bbit_ai.ui.mainFunction.MainScreen
|
||||||
import bbit_ai.composeapp.generated.resources.compose_multiplatform
|
|
||||||
|
|
||||||
|
val M = Modifier
|
||||||
|
.animateContentSize(
|
||||||
|
animationSpec = spring(
|
||||||
|
dampingRatio = Spring.DampingRatioLowBouncy,
|
||||||
|
stiffness = Spring.StiffnessLow
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview()
|
||||||
fun App() {
|
fun App() {
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
var showContent by remember { mutableStateOf(false) }
|
MainScreen()
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.bbitcn.bbit_ai
|
|
||||||
|
|
||||||
class Greeting {
|
|
||||||
private val platform = getPlatform()
|
|
||||||
|
|
||||||
fun greet(): String {
|
|
||||||
return "Hello, ${platform.name}!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.bbitcn.bbit_ai
|
|
||||||
|
|
||||||
interface Platform {
|
|
||||||
val name: String
|
|
||||||
}
|
|
||||||
|
|
||||||
expect fun getPlatform(): Platform
|
|
||||||
+200
@@ -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 <T> 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 <T> doInIoThread(
|
||||||
|
loadingTips: String = "正在加载中",
|
||||||
|
showDialog: Boolean = true,
|
||||||
|
onError: (Throwable) -> Unit = { },
|
||||||
|
onFinish: () -> Unit = { },
|
||||||
|
doInIO: suspend () -> T,
|
||||||
|
) {
|
||||||
|
doInIoThreadThenUI(loadingTips, showDialog, onError, doInIO, onFinish) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> 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<String, Job>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 放弃旧任务,执行新任务
|
||||||
|
*
|
||||||
|
* @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<Unit> { 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
package com.bbitcn.bbit_ai.base
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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<String>
|
||||||
|
suspend fun takePhoto(): ImageBitmap?
|
||||||
|
|
||||||
|
fun saveImageBitmapToFile(image: ImageBitmap)
|
||||||
|
fun loadImage(filePath: String): ImageBitmap?
|
||||||
|
}
|
||||||
|
|
||||||
|
expect fun getMyCamera(): MyCamera
|
||||||
+226
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
+81
@@ -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<String>())
|
||||||
|
val cameraList = _cameraList.asStateFlow()
|
||||||
|
private val _analyticsList = MutableStateFlow(listOf<SaveData>())
|
||||||
|
val analyticsList = _analyticsList.asStateFlow()
|
||||||
|
private val _curDetail = MutableStateFlow<SaveData?>(null)
|
||||||
|
val curDetail = _curDetail.asStateFlow()
|
||||||
|
private val _picRaw = MutableStateFlow<ImageBitmap?>(null)
|
||||||
|
val picRaw = _picRaw.asStateFlow()
|
||||||
|
private val _picAi = MutableStateFlow<ImageBitmap?>(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<SaveData> {
|
||||||
|
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]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
+123
@@ -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<ImageBitmap?>(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<String> {
|
||||||
|
val availableCameras = mutableListOf<String>()
|
||||||
|
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()
|
||||||
|
}
|
||||||
+40
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
|
||||||
@@ -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
|
||||||
-10
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="deploymentTargetSelector">
|
|
||||||
<selectionStates>
|
|
||||||
<SelectionState runConfigName="composeApp">
|
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
|
||||||
</SelectionState>
|
|
||||||
</selectionStates>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-176
@@ -1,176 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="AutoImportSettings">
|
|
||||||
<option name="autoReloadType" value="NONE" />
|
|
||||||
</component>
|
|
||||||
<component name="ChangeListManager">
|
|
||||||
<list default="true" id="7e5f85d3-00cd-41d5-89b4-71b4a3ca4deb" name="Changes" comment="">
|
|
||||||
<change afterPath="$PROJECT_DIR$/../../ktor/src/main/kotlin/ink/snowflake/server/utils/bin" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../../ktor/src/main/kotlin/ink/snowflake/server/Application.kt" beforeDir="false" afterPath="$PROJECT_DIR$/../../ktor/src/main/kotlin/ink/snowflake/server/Application.kt" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../../ktor/src/main/kotlin/ink/snowflake/server/database/ImageDao.kt" beforeDir="false" afterPath="$PROJECT_DIR$/../../ktor/src/main/kotlin/ink/snowflake/server/database/ImageDao.kt" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../../ktor/src/main/kotlin/ink/snowflake/server/route/ImageAnalytics.kt" beforeDir="false" afterPath="$PROJECT_DIR$/../../ktor/src/main/kotlin/ink/snowflake/server/route/ImageAnalytics.kt" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../../ktor/src/main/kotlin/ink/snowflake/server/route/Main.kt" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../../ktor/src/main/kotlin/ink/snowflake/server/route/VideoAnalytics.kt" beforeDir="false" afterPath="$PROJECT_DIR$/../../ktor/src/main/kotlin/ink/snowflake/server/route/VideoAnalytics.kt" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/../../lefthook.yml" beforeDir="false" />
|
|
||||||
</list>
|
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
|
||||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
|
||||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
|
||||||
</component>
|
|
||||||
<component name="ClangdSettings">
|
|
||||||
<option name="formatViaClangd" value="false" />
|
|
||||||
</component>
|
|
||||||
<component name="ExecutionTargetManager" SELECTED_TARGET="device_and_snapshot_combo_box_target[DeviceId(pluginId=LocalEmulator, isTemplate=false, identifier=path=C:\Users\BBIT\.android\avd\Pixel_Fold_API_35.avd)]" />
|
|
||||||
<component name="ExternalProjectsData">
|
|
||||||
<projectState path="$PROJECT_DIR$">
|
|
||||||
<ProjectState />
|
|
||||||
</projectState>
|
|
||||||
</component>
|
|
||||||
<component name="ExternalProjectsManager">
|
|
||||||
<system id="GRADLE">
|
|
||||||
<state>
|
|
||||||
<projects_view>
|
|
||||||
<tree_state>
|
|
||||||
<expand>
|
|
||||||
<path>
|
|
||||||
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
|
|
||||||
<item name="BbitAI" type="f1a62948:ProjectNode" />
|
|
||||||
</path>
|
|
||||||
</expand>
|
|
||||||
<select />
|
|
||||||
</tree_state>
|
|
||||||
</projects_view>
|
|
||||||
</state>
|
|
||||||
</system>
|
|
||||||
</component>
|
|
||||||
<component name="Git.Settings">
|
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/../.." />
|
|
||||||
</component>
|
|
||||||
<component name="GradleScriptDefinitionsStorage" workingDir="$PROJECT_DIR$" gradleHome="$USER_HOME$/.gradle/wrapper/dists/gradle-8.9-bin/78qddjpeqn5v6yec3xb8kv9ca/gradle-8.9" javaHome="C:/Program Files/Java/java21" />
|
|
||||||
<component name="ProjectColorInfo">{
|
|
||||||
"associatedIndex": 4
|
|
||||||
}</component>
|
|
||||||
<component name="ProjectId" id="2yOafc6E4a44F9oYGcoL3xqrvuQ" />
|
|
||||||
<component name="ProjectViewState">
|
|
||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
|
||||||
<option name="showLibraryContents" value="true" />
|
|
||||||
</component>
|
|
||||||
<component name="PropertiesComponent"><![CDATA[{
|
|
||||||
"keyToString": {
|
|
||||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
|
||||||
"RunOnceActivity.cidr.known.project.marker": "true",
|
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
|
||||||
"RunOnceActivity.readMode.enableVisualFormatting": "true",
|
|
||||||
"cf.first.check.clang-format": "false",
|
|
||||||
"cidr.known.project.marker": "true",
|
|
||||||
"git-widget-placeholder": "master",
|
|
||||||
"kotlin-language-version-configured": "true",
|
|
||||||
"last_opened_file_path": "D:/Mine/ICP/mul/BbitAI",
|
|
||||||
"node.js.detected.package.eslint": "true",
|
|
||||||
"node.js.detected.package.tslint": "true",
|
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
|
||||||
"nodejs_package_manager_path": "npm",
|
|
||||||
"vue.rearranger.settings.migration": "true"
|
|
||||||
}
|
|
||||||
}]]></component>
|
|
||||||
<component name="RunManager">
|
|
||||||
<configuration name="composeApp" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
|
|
||||||
<module name="BbitAI.composeApp" />
|
|
||||||
<option name="ANDROID_RUN_CONFIGURATION_SCHEMA_VERSION" value="1" />
|
|
||||||
<option name="DEPLOY" value="true" />
|
|
||||||
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
|
||||||
<option name="DEPLOY_AS_INSTANT" value="false" />
|
|
||||||
<option name="ARTIFACT_NAME" value="" />
|
|
||||||
<option name="PM_INSTALL_OPTIONS" value="" />
|
|
||||||
<option name="ALL_USERS" value="false" />
|
|
||||||
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
|
|
||||||
<option name="ALLOW_ASSUME_VERIFIED" value="false" />
|
|
||||||
<option name="CLEAR_APP_STORAGE" value="false" />
|
|
||||||
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
|
|
||||||
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
|
||||||
<option name="MODE" value="default_activity" />
|
|
||||||
<option name="RESTORE_ENABLED" value="false" />
|
|
||||||
<option name="RESTORE_FILE" value="" />
|
|
||||||
<option name="RESTORE_FRESH_INSTALL_ONLY" value="false" />
|
|
||||||
<option name="CLEAR_LOGCAT" value="false" />
|
|
||||||
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
|
||||||
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
|
||||||
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
|
|
||||||
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
|
|
||||||
<option name="DEBUGGER_TYPE" value="Auto" />
|
|
||||||
<Auto>
|
|
||||||
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
|
||||||
<option name="SHOW_STATIC_VARS" value="true" />
|
|
||||||
<option name="WORKING_DIR" value="" />
|
|
||||||
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
|
||||||
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
|
||||||
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
|
||||||
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
|
||||||
</Auto>
|
|
||||||
<Hybrid>
|
|
||||||
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
|
||||||
<option name="SHOW_STATIC_VARS" value="true" />
|
|
||||||
<option name="WORKING_DIR" value="" />
|
|
||||||
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
|
||||||
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
|
||||||
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
|
||||||
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
|
||||||
</Hybrid>
|
|
||||||
<Java>
|
|
||||||
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
|
||||||
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
|
||||||
</Java>
|
|
||||||
<Native>
|
|
||||||
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
|
||||||
<option name="SHOW_STATIC_VARS" value="true" />
|
|
||||||
<option name="WORKING_DIR" value="" />
|
|
||||||
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
|
||||||
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
|
||||||
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
|
||||||
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
|
||||||
</Native>
|
|
||||||
<Profilers>
|
|
||||||
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
|
||||||
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
|
||||||
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
|
||||||
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
|
|
||||||
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
|
||||||
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
|
||||||
</Profilers>
|
|
||||||
<option name="DEEP_LINK" value="" />
|
|
||||||
<option name="ACTIVITY" value="" />
|
|
||||||
<option name="ACTIVITY_CLASS" value="" />
|
|
||||||
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
|
|
||||||
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
|
|
||||||
<method v="2">
|
|
||||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
|
||||||
</method>
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
<component name="SharedIndexes">
|
|
||||||
<attachedChunks>
|
|
||||||
<set>
|
|
||||||
<option value="bundled-jdk-9823dce3aa75-0db3c2423d63-intellij.indexing.shared.core-IU-252.16512.17" />
|
|
||||||
<option value="bundled-js-predefined-d6986cc7102b-cf89dd401a8c-JavaScript-IU-252.16512.17" />
|
|
||||||
</set>
|
|
||||||
</attachedChunks>
|
|
||||||
</component>
|
|
||||||
<component name="TaskManager">
|
|
||||||
<task active="true" id="Default" summary="Default task">
|
|
||||||
<changelist id="7e5f85d3-00cd-41d5-89b4-71b4a3ca4deb" name="Changes" comment="" />
|
|
||||||
<created>1749708177539</created>
|
|
||||||
<option name="number" value="Default" />
|
|
||||||
<option name="presentableId" value="Default" />
|
|
||||||
<updated>1749708177539</updated>
|
|
||||||
<workItem from="1749708178704" duration="1510000" />
|
|
||||||
<workItem from="1750208347521" duration="284000" />
|
|
||||||
</task>
|
|
||||||
<servers />
|
|
||||||
</component>
|
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
|
||||||
<option name="version" value="3" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Reference in New Issue
Block a user