fix: bugs

This commit is contained in:
AnranYus
2025-10-16 17:32:58 +08:00
parent 289aef4d84
commit f43429806a
15 changed files with 267 additions and 53 deletions
@@ -7,6 +7,7 @@
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:name=".ComfyApp"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
@@ -0,0 +1,17 @@
package moe.uni.comfy.comfyuikmp
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
class ComfyApp: Application() {
companion object{
@SuppressLint("StaticFieldLeak")
lateinit var context: Context
}
override fun onCreate() {
super.onCreate()
context = applicationContext
}
}
@@ -0,0 +1,58 @@
package moe.uni.comfy.comfyuikmp.util
import android.content.ContentValues
import android.graphics.Bitmap
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asAndroidBitmap
import moe.uni.comfy.comfyuikmp.ComfyApp
import java.io.File
actual suspend fun saveImageToLocal(image: ImageBitmap, name: String): Result<Boolean> {
val bitmap = image.asAndroidBitmap()
val imagesCollection =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
} else {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, "$name.png")
put(MediaStore.Images.Media.MIME_TYPE, "image/png")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/ComfyApp")
put(MediaStore.Images.Media.IS_PENDING, 1)
}
}
val resolver = ComfyApp.context.contentResolver
val uri = resolver.insert(imagesCollection, contentValues)
uri?.let {
try {
resolver.openOutputStream(uri)?.use { out ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
} ?: throw kotlinx.io.IOException("OpenOutputStream returned null")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
contentValues.clear()
contentValues.put(MediaStore.Images.Media.IS_PENDING, 0)
resolver.update(uri, contentValues, null, null)
}
return Result.success(true)
} catch (e: Exception) {
// 出错时删除占位条目,避免相册里出现空文件
runCatching { resolver.delete(uri, null, null) }
e.printStackTrace()
return Result.failure(e)
}
}
return Result.failure(kotlinx.io.IOException("Failed to create new MediaStore record."))
}
@@ -4,6 +4,11 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
object NodeType{
const val KSampler = "KSampler"
}
/** /**
* 通用 ComfyUI 节点模型,尽量保持结构宽松以兼容不同节点类型与输入形态。 * 通用 ComfyUI 节点模型,尽量保持结构宽松以兼容不同节点类型与输入形态。
*/ */
@@ -6,12 +6,14 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import moe.uni.comfy.comfyuikmp.data.model.ComfyPrompt import moe.uni.comfy.comfyuikmp.data.model.ComfyPrompt
import moe.uni.comfy.comfyuikmp.data.settings.KeyValueStore import moe.uni.comfy.comfyuikmp.data.settings.KeyValueStore
import moe.uni.comfy.comfyuikmp.data.model.ImageRef
@Serializable @Serializable
data class WorkflowItem( data class WorkflowItem(
val id: String, val id: String,
val name: String, val name: String,
val prompt: ComfyPrompt val prompt: ComfyPrompt,
val cover: ImageRef? = null
) )
class WorkflowRepository { class WorkflowRepository {
@@ -1,16 +1,21 @@
package moe.uni.comfy.comfyuikmp.ui package moe.uni.comfy.comfyuikmp.ui
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.History import androidx.compose.material.icons.filled.History
import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import moe.uni.comfy.comfyuikmp.data.model.NodeType
import moe.uni.comfy.comfyuikmp.di.AppGraph import moe.uni.comfy.comfyuikmp.di.AppGraph
import moe.uni.comfy.comfyuikmp.ui.gallery.GalleryScreen import moe.uni.comfy.comfyuikmp.ui.gallery.GalleryScreen
import moe.uni.comfy.comfyuikmp.ui.generate.GenerateScreen import moe.uni.comfy.comfyuikmp.ui.generate.GenerateScreen
@@ -21,6 +26,7 @@ import moe.uni.comfy.comfyuikmp.ui.navigation.rememberNavController
import moe.uni.comfy.comfyuikmp.ui.settings.SettingsScreen import moe.uni.comfy.comfyuikmp.ui.settings.SettingsScreen
import moe.uni.comfy.comfyuikmp.ui.view.ViewScreen import moe.uni.comfy.comfyuikmp.ui.view.ViewScreen
import moe.uni.comfy.comfyuikmp.util.mainViewModelFactory import moe.uni.comfy.comfyuikmp.util.mainViewModelFactory
import moe.uni.comfy.comfyuikmp.util.saveImageToLocal
import kotlin.random.Random import kotlin.random.Random
import kotlin.time.Clock import kotlin.time.Clock
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
@@ -31,6 +37,7 @@ fun AppRoot(viewModel: AppViewModel = viewModel(factory = mainViewModelFactory))
val hasSaved = remember { AppGraph.workflows.list().isNotEmpty() } val hasSaved = remember { AppGraph.workflows.list().isNotEmpty() }
val startDest = if (hasSaved) Dest.Home else Dest.Settings val startDest = if (hasSaved) Dest.Home else Dest.Settings
val navController = rememberNavController(startDest) val navController = rememberNavController(startDest)
MaterialTheme { MaterialTheme {
val snackbar = remember { SnackbarHostState() } val snackbar = remember { SnackbarHostState() }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -72,11 +79,29 @@ fun AppRoot(viewModel: AppViewModel = viewModel(factory = mainViewModelFactory))
}, },
actions = { actions = {
if (current == Dest.Generate) { if (current == Dest.Generate) {
if (uiState.image!=null){
Icon(Icons.Filled.Save, contentDescription = null, modifier = Modifier.clickable{
uiState.image?.let {
scope.launch {
val result = saveImageToLocal(it,"${uiState.workflowName}_${Random.nextInt()}.png")
if (result.isSuccess){
snackbar.showSnackbar("保存成功")
}else{
snackbar.showSnackbar(result.exceptionOrNull()?.message?:"保存失败")
}
}
}
})
Spacer(Modifier.size(16.dp))
}
Icon( Icon(
Icons.Filled.History, Icons.Filled.History,
contentDescription = null, contentDescription = null,
modifier = Modifier.clickable { navController.navigate(Dest.Gallery) } modifier = Modifier.clickable { navController.navigate(Dest.Gallery) }
) )
Spacer(Modifier.size(16.dp))
} }
} }
) )
@@ -90,7 +115,7 @@ fun AppRoot(viewModel: AppViewModel = viewModel(factory = mainViewModelFactory))
uiState.prompt.let { p -> uiState.prompt.let { p ->
scope.launch { scope.launch {
if (uiState.useRandom) { if (uiState.useRandom) {
p.values.find { it.classType == "KSampler" }?.let { p.values.find { it.classType == NodeType.KSampler }?.let {
val inputs = it.inputs.toMutableMap() val inputs = it.inputs.toMutableMap()
inputs["seed"] = JsonPrimitive(Random.nextInt(0, Int.MAX_VALUE)) inputs["seed"] = JsonPrimitive(Random.nextInt(0, Int.MAX_VALUE))
it.inputs = inputs it.inputs = inputs
@@ -145,7 +170,8 @@ fun AppRoot(viewModel: AppViewModel = viewModel(factory = mainViewModelFactory))
viewModel.useRandomSeed(it) viewModel.useRandomSeed(it)
}, },
onOpenImage = { ref -> viewModel.openImage(ref) } onOpenImage = { ref -> viewModel.openImage(ref) },
onSetCover = { }
) )
} }
@@ -23,6 +23,7 @@ import moe.uni.comfy.comfyuikmp.data.network.createDefaultHttpClient
import moe.uni.comfy.comfyuikmp.data.repository.WorkflowItem import moe.uni.comfy.comfyuikmp.data.repository.WorkflowItem
import moe.uni.comfy.comfyuikmp.di.AppGraph import moe.uni.comfy.comfyuikmp.di.AppGraph
import moe.uni.comfy.comfyuikmp.util.decodeImage import moe.uni.comfy.comfyuikmp.util.decodeImage
import moe.uni.comfy.comfyuikmp.util.saveImageToLocal
class AppViewModel : ViewModel() { class AppViewModel : ViewModel() {
private val _uiState = MutableStateFlow(AppUiState()) private val _uiState = MutableStateFlow(AppUiState())
@@ -145,5 +146,5 @@ data class AppUiState(
val image: ImageBitmap? = null, val image: ImageBitmap? = null,
val progress:ProgressUpdate = ProgressUpdate(0f, ""), val progress:ProgressUpdate = ProgressUpdate(0f, ""),
val prompt: ComfyPrompt = emptyMap(), val prompt: ComfyPrompt = emptyMap(),
val useRandom: Boolean = false val useRandom: Boolean = true
) )
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -23,6 +24,12 @@ import coil3.compose.AsyncImage
import moe.uni.comfy.comfyuikmp.di.AppGraph import moe.uni.comfy.comfyuikmp.di.AppGraph
import moe.uni.comfy.comfyuikmp.ui.AppViewModel import moe.uni.comfy.comfyuikmp.ui.AppViewModel
import moe.uni.comfy.comfyuikmp.ui.navigation.Dest import moe.uni.comfy.comfyuikmp.ui.navigation.Dest
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import moe.uni.comfy.comfyuikmp.util.buildImageUrl
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
@@ -42,23 +49,12 @@ fun GalleryScreen(contentPadding: PaddingValues, viewModel: AppViewModel) {
LazyColumn(contentPadding = contentPadding, modifier = Modifier.padding(horizontal = 16.dp)) { LazyColumn(contentPadding = contentPadding, modifier = Modifier.padding(horizontal = 16.dp)) {
items(history, key = { it.filename }) { img -> items(history, key = { it.filename }) { img ->
val url = buildString { val url = buildImageUrl(img)
append(base)
append("/view?filename=")
append(img.filename)
img.subfolder?.let {
append("&subfolder=")
append(it)
}
img.type?.let {
append("&type=")
append(it)
}
}
Card(modifier = Modifier.fillMaxWidth().aspectRatio(3f / 4f).clickable {
viewModel.openImage(img) Card(modifier = Modifier.fillMaxWidth().aspectRatio(3f / 4f).combinedClickable(
}) { onClick = { viewModel.openImage(img) }
)) {
AsyncImage( AsyncImage(
model = url, model = url,
contentDescription = null, contentDescription = null,
@@ -26,6 +26,7 @@ import androidx.compose.material3.Switch
import androidx.compose.material3.rememberBottomSheetScaffoldState import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.booleanOrNull import kotlinx.serialization.json.booleanOrNull
@@ -36,6 +37,9 @@ import moe.uni.comfy.comfyuikmp.data.model.updateNodeStringInput
import moe.uni.comfy.comfyuikmp.data.model.updateNodePrimitiveInput import moe.uni.comfy.comfyuikmp.data.model.updateNodePrimitiveInput
import moe.uni.comfy.comfyuikmp.data.network.ProgressUpdate import moe.uni.comfy.comfyuikmp.data.network.ProgressUpdate
import moe.uni.comfy.comfyuikmp.data.model.ImageRef import moe.uni.comfy.comfyuikmp.data.model.ImageRef
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import moe.uni.comfy.comfyuikmp.data.model.NodeType
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -49,6 +53,7 @@ fun GenerateScreen(
onPromptChange: (ComfyPrompt) -> Unit, onPromptChange: (ComfyPrompt) -> Unit,
onRandomSwitchChange: (Boolean) -> Unit, onRandomSwitchChange: (Boolean) -> Unit,
onOpenImage: (ImageRef) -> Unit = {}, onOpenImage: (ImageRef) -> Unit = {},
onSetCover: (ImageRef) -> Unit = {},
) { ) {
val state = rememberBottomSheetScaffoldState() val state = rememberBottomSheetScaffoldState()
Scaffold(modifier = Modifier.padding(contentPadding)) { Scaffold(modifier = Modifier.padding(contentPadding)) {
@@ -141,7 +146,8 @@ fun GenerateScreen(
) )
//KSampler //KSampler
if (node.classType == "KSampler" && key.equals("seed", ignoreCase = true)) { if (node.classType == NodeType.KSampler && key.equals("seed", ignoreCase = true)) {
inputEnable = !useRandom
Row(modifier = Modifier.padding(top = 8.dp)) { Row(modifier = Modifier.padding(top = 8.dp)) {
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
Switch(useRandom, onCheckedChange = { Switch(useRandom, onCheckedChange = {
@@ -189,6 +195,7 @@ fun GenerateScreen(
Alignment.Center)) Alignment.Center))
} }
var showSetCover by remember { mutableStateOf(false) }
if (latestImage != null) { if (latestImage != null) {
if (latestImageRef != null) { if (latestImageRef != null) {
Image( Image(
@@ -197,7 +204,10 @@ fun GenerateScreen(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = 32.dp) .padding(top = 32.dp)
.clickable { onOpenImage(latestImageRef) } .combinedClickable(
onClick = { onOpenImage(latestImageRef) },
onLongClick = { showSetCover = true }
)
) )
} else { } else {
Image( Image(
@@ -207,6 +217,21 @@ fun GenerateScreen(
) )
} }
} }
if (showSetCover && latestImageRef != null) {
AlertDialog(
onDismissRequest = { showSetCover = false },
confirmButton = {
Button(onClick = {
onSetCover(latestImageRef)
showSetCover = false
}) { Text("设为封面") }
},
dismissButton = { Button(onClick = { showSetCover = false }) { Text("取消") } },
title = { Text("设置封面") },
text = { Text("将当前图片设为该工作流的封面?") }
)
}
} }
} }
} }
@@ -6,12 +6,16 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,6 +32,10 @@ import androidx.compose.material3.Icon
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.Alignment
import coil3.compose.AsyncImage
import moe.uni.comfy.comfyuikmp.util.buildImageUrl
@Composable @Composable
@@ -49,27 +57,48 @@ fun HomeScreen(
) { Text("新建/导入工作流", modifier = Modifier.padding(16.dp), style = MaterialTheme.typography.titleMedium) } ) { Text("新建/导入工作流", modifier = Modifier.padding(16.dp), style = MaterialTheme.typography.titleMedium) }
} }
items(items) { w -> items(items) { w ->
Card(modifier = Modifier Card(
.padding(horizontal = 16.dp, vertical = 8.dp) modifier = Modifier
.fillMaxWidth() .padding(horizontal = 16.dp, vertical = 8.dp)
.clickable { onPick(w) } .fillMaxWidth()
.clickable { onPick(w) },
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)
) { ) {
Text(w.name, modifier = Modifier.padding(16.dp), style = MaterialTheme.typography.titleMedium) if (w.cover != null) {
Row(modifier = Modifier.padding(horizontal = 8.dp)) { AsyncImage(
IconButton(onClick = { renaming = w; newName = w.name }) { Icon(Icons.Filled.Edit, contentDescription = null, model = buildImageUrl(w.cover),
Modifier.size(24.dp)) } contentDescription = null,
IconButton(onClick = { AppGraph.workflows.delete(w.id); items = repo.list() }) { Icon(Icons.Filled.Delete, contentDescription = null, contentScale = ContentScale.Crop,
Modifier.size(24.dp)) } modifier = Modifier
.fillMaxWidth()
.aspectRatio(4f / 3f)
)
} else {
// 占位封面区域
androidx.compose.foundation.layout.Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(4f / 3f)
)
}
Column(modifier = Modifier.padding(12.dp)) {
Text(w.name, style = MaterialTheme.typography.titleMedium)
Row(modifier = Modifier.padding(top = 4.dp), verticalAlignment = Alignment.CenterVertically) {
IconButton(onClick = { renaming = w; newName = w.name }) { Icon(Icons.Filled.Edit, contentDescription = null,
Modifier.size(24.dp)) }
IconButton(onClick = { AppGraph.workflows.delete(w.id); items = repo.list() }) { Icon(Icons.Filled.Delete, contentDescription = null,
Modifier.size(24.dp)) }
}
} }
} }
} }
} }
val r = renaming if (renaming != null) {
if (r != null) {
AlertDialog(onDismissRequest = { renaming = null }, confirmButton = { AlertDialog(onDismissRequest = { renaming = null }, confirmButton = {
Button(onClick = { Button(onClick = {
AppGraph.workflows.rename(r.id, newName.ifBlank { r.name }) AppGraph.workflows.rename(renaming!!.id, newName.ifBlank { renaming!!.name })
items = repo.list() items = repo.list()
renaming = null renaming = null
}) { Text("确定") } }) { Text("确定") }
@@ -19,24 +19,13 @@ import moe.uni.comfy.comfyuikmp.data.model.ImageRef
import moe.uni.comfy.comfyuikmp.di.AppGraph import moe.uni.comfy.comfyuikmp.di.AppGraph
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import moe.uni.comfy.comfyuikmp.util.buildImageUrl
@Composable @Composable
fun ViewScreen(ref: ImageRef, contentPadding: PaddingValues) { fun ViewScreen(ref: ImageRef, contentPadding: PaddingValues) {
val base = AppGraph.settings.getBaseUrl() ?: "" val base = AppGraph.settings.getBaseUrl() ?: ""
val url = remember(ref, base) { val url = remember(ref, base) {
buildString { buildImageUrl(ref)
append(base)
append("/view?filename=")
append(ref.filename)
ref.subfolder?.let {
append("&subfolder=")
append(it)
}
ref.type?.let {
append("&type=")
append(it)
}
}
} }
val scaleState = remember { mutableFloatStateOf(1f) } val scaleState = remember { mutableFloatStateOf(1f) }
@@ -0,0 +1,27 @@
package moe.uni.comfy.comfyuikmp.util
import androidx.compose.ui.graphics.ImageBitmap
import moe.uni.comfy.comfyuikmp.data.model.ImageRef
import moe.uni.comfy.comfyuikmp.di.AppGraph
fun buildImageUrl(ref: ImageRef): String {
val base = AppGraph.settings.getBaseUrl() ?: ""
return buildString {
append(base)
append("/view?filename=")
append(ref.filename)
ref.subfolder?.let {
append("&subfolder=")
append(it)
}
ref.type?.let {
append("&type=")
append(it)
}
}
}
expect suspend fun saveImageToLocal(image: ImageBitmap, name: String): Result<Boolean>
@@ -19,11 +19,6 @@ actual fun createPlatformHttpClient(configure: HttpClientConfig<*>.() -> Unit):
install(UserAgent) { install(UserAgent) {
agent = "ComfyUIKMP-iOS" agent = "ComfyUIKMP-iOS"
} }
defaultRequest {
url {
protocol = URLProtocol.HTTP
}
}
configure() configure()
} }
} }
@@ -0,0 +1,8 @@
package moe.uni.comfy.comfyuikmp.util
import androidx.compose.ui.graphics.ImageBitmap
import coil3.Bitmap
actual suspend fun saveImageToLocal(image: ImageBitmap, name: String): Result<Boolean> {
return Result.failure(kotlinx.io.IOException("Failed to create new MediaStore record."))
}
+35
View File
@@ -4,12 +4,47 @@
<dict> <dict>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
<key>NSLocalNetworkUsageDescription</key>
<string>需要访问 ComfyUI 服务以生成图像</string>
<key>NSAppTransportSecurity</key> <key>NSAppTransportSecurity</key>
<dict> <dict>
<key>NSAllowsArbitraryLoads</key> <key>NSAllowsArbitraryLoads</key>
<true/> <true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
<key>NSAllowsLocalNetworking</key> <key>NSAllowsLocalNetworking</key>
<true/> <true/>
<key>NSExceptionDomains</key>
<dict>
<!-- 示例:开发期允许局域网 IP 和自签名证书。可按需添加具体域名/IP。 -->
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>127.0.0.1</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<key>10.0.0.0/8</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<key>172.16.0.0/12</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<key>192.168.0.0/16</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict> </dict>
</dict> </dict>
</plist> </plist>