diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 0a932bd..d3071fd 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -23,6 +23,7 @@ kotlin { iosTarget.binaries.framework { baseName = "ComposeApp" isStatic = true + linkerOpts("-framework", "Photos") } } @@ -94,4 +95,3 @@ android { dependencies { debugImplementation(libs.compose.uiTooling) } - diff --git a/composeApp/src/androidMain/kotlin/moe/uni/comfy_kmp/data/ImageSaveUtils.android.kt b/composeApp/src/androidMain/kotlin/moe/uni/comfy_kmp/data/ImageSaveUtils.android.kt new file mode 100644 index 0000000..4caa794 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/moe/uni/comfy_kmp/data/ImageSaveUtils.android.kt @@ -0,0 +1,10 @@ +package moe.uni.comfy_kmp.data + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +actual suspend fun saveImageToGallery(bytes: ByteArray, filenameHint: String): String { + return withContext(Dispatchers.IO) { + saveToTemp(bytes, filenameHint) + } +} diff --git a/composeApp/src/commonMain/kotlin/moe/uni/comfy_kmp/data/ImageSaveUtils.kt b/composeApp/src/commonMain/kotlin/moe/uni/comfy_kmp/data/ImageSaveUtils.kt new file mode 100644 index 0000000..229d1a5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/moe/uni/comfy_kmp/data/ImageSaveUtils.kt @@ -0,0 +1,3 @@ +package moe.uni.comfy_kmp.data + +expect suspend fun saveImageToGallery(bytes: ByteArray, filenameHint: String): String diff --git a/composeApp/src/commonMain/kotlin/moe/uni/comfy_kmp/ui/screens/WorkflowRunScreen.kt b/composeApp/src/commonMain/kotlin/moe/uni/comfy_kmp/ui/screens/WorkflowRunScreen.kt index d2d829c..9b31f01 100644 --- a/composeApp/src/commonMain/kotlin/moe/uni/comfy_kmp/ui/screens/WorkflowRunScreen.kt +++ b/composeApp/src/commonMain/kotlin/moe/uni/comfy_kmp/ui/screens/WorkflowRunScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer @@ -14,6 +15,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -25,14 +27,11 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.Stop import androidx.compose.material3.BottomSheetScaffold -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton import androidx.compose.material3.SheetValue import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar @@ -49,6 +48,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.dp import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.rememberScreenModel @@ -78,7 +79,7 @@ import moe.uni.comfy_kmp.data.extractImagesFromHistory import moe.uni.comfy_kmp.data.extractPromptObject import moe.uni.comfy_kmp.data.parsePromptNodes import moe.uni.comfy_kmp.data.saveCoverImage -import moe.uni.comfy_kmp.data.saveToTemp +import moe.uni.comfy_kmp.data.saveImageToGallery import moe.uni.comfy_kmp.data.updateNodeInput import moe.uni.comfy_kmp.di.LocalAppContainer import moe.uni.comfy_kmp.network.ComfyApiClient @@ -127,92 +128,123 @@ data class WorkflowRunScreen(val workflowId: String) : Screen { ) val comfyColors = MaterialTheme.comfyColors + val sheetPeekHeight = 120.dp - BottomSheetScaffold( - scaffoldState = scaffoldState, - sheetPeekHeight = 120.dp, - sheetContainerColor = comfyColors.cardBackground, - sheetContentColor = MaterialTheme.colorScheme.onSurface, - sheetShape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp), - sheetDragHandle = { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Spacer(modifier = Modifier.height(12.dp)) - Box( - modifier = Modifier - .width(40.dp) - .height(4.dp) - .clip(RoundedCornerShape(2.dp)) - .background(MaterialTheme.colorScheme.outline.copy(alpha = 0.4f)) - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = "节点状态 (${model.nodeStates.size})", - style = MaterialTheme.typography.titleSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Spacer(modifier = Modifier.height(8.dp)) - } - }, - sheetContent = { - NodeListSheet( - nodes = model.orderedNodeStates, - randomSeedNodes = model.randomSeedNodes, - modelChoices = model.modelChoices, - loadingModels = model.loadingModels, - onInputChange = { nodeId, field, value -> model.updateNodeInput(nodeId, field, value) }, - onRandomSeedToggle = { nodeId, enabled -> model.setRandomSeedMode(nodeId, enabled) }, - onRandomizeSeed = { nodeId -> model.randomizeSeed(nodeId) }, - onLoadModels = { nodeId -> model.loadModelsForNode(nodeId) }, - modifier = Modifier.navigationBarsPadding() - ) - }, - topBar = { - WorkflowRunTopBar( - onBack = { navigator.pop() }, - status = model.executionStatus, - statusText = model.statusText, - progress = model.progress, - progressText = model.progressText, - isRunning = model.running, - onStart = { model.runWorkflow() }, - onInterrupt = { model.interrupt() } - ) - }, - containerColor = MaterialTheme.colorScheme.background - ) { padding -> - Box( - modifier = Modifier - .fillMaxSize() - .padding(padding) - .background( - Brush.verticalGradient( - colors = listOf( - comfyColors.gradientStart, - comfyColors.gradientEnd - ) + BoxWithConstraints(modifier = Modifier.fillMaxSize()) { + val density = LocalDensity.current + val layoutHeightPx = with(density) { maxHeight.toPx() } + val peekHeightPx = with(density) { sheetPeekHeight.toPx() } + + BottomSheetScaffold( + scaffoldState = scaffoldState, + sheetPeekHeight = sheetPeekHeight, + sheetContainerColor = comfyColors.cardBackground, + sheetContentColor = MaterialTheme.colorScheme.onSurface, + sheetShape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp), + sheetDragHandle = { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(12.dp)) + Box( + modifier = Modifier + .width(40.dp) + .height(4.dp) + .clip(RoundedCornerShape(2.dp)) + .background(MaterialTheme.colorScheme.outline.copy(alpha = 0.4f)) ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "节点状态 (${model.nodeStates.size})", + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Spacer(modifier = Modifier.height(8.dp)) + } + }, + sheetContent = { + NodeListSheet( + nodes = model.orderedNodeStates, + randomSeedNodes = model.randomSeedNodes, + modelChoices = model.modelChoices, + loadingModels = model.loadingModels, + onInputChange = { nodeId, field, value -> model.updateNodeInput(nodeId, field, value) }, + onRandomSeedToggle = { nodeId, enabled -> model.setRandomSeedMode(nodeId, enabled) }, + onRandomizeSeed = { nodeId -> model.randomizeSeed(nodeId) }, + onLoadModels = { nodeId -> model.loadModelsForNode(nodeId) }, + modifier = Modifier.navigationBarsPadding() ) - ) { - ImageGallery( - images = model.galleryImages, - onImageClick = { index -> - model.getPreviewImage(index)?.let { preview -> - navigator.push( - ImagePreviewScreen( - preview.filename, - preview.bytes, - onSave = { model.saveImage(index) } + }, + topBar = { + WorkflowRunTopBar( + onBack = { navigator.pop() }, + status = model.executionStatus, + statusText = model.statusText, + progress = model.progress, + progressText = model.progressText + ) + }, + containerColor = MaterialTheme.colorScheme.background + ) { padding -> + val layoutDirection = LocalLayoutDirection.current + val startPadding = padding.calculateLeftPadding(layoutDirection) + val endPadding = padding.calculateRightPadding(layoutDirection) + val contentPadding = PaddingValues( + start = startPadding, + top = padding.calculateTopPadding(), + end = endPadding, + bottom = 0.dp + ) + val sheetOffsetPx = runCatching { + scaffoldState.bottomSheetState.requireOffset() + }.getOrElse { (layoutHeightPx - peekHeightPx).coerceAtLeast(0f) } + val sheetHeightPx = (layoutHeightPx - sheetOffsetPx).coerceAtLeast(0f) + val sheetHeightOffset = with(density) { sheetHeightPx.toDp() } + + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.verticalGradient( + colors = listOf( + comfyColors.gradientStart, + comfyColors.gradientEnd ) ) - } - }, - onSaveClick = { index -> model.saveImage(index) }, - onSetCoverClick = { index -> model.setCoverImage(index) }, - modifier = Modifier.fillMaxSize() - ) + ) + ) { + ImageGallery( + images = model.galleryImages, + onImageClick = { index -> + model.getPreviewImage(index)?.let { preview -> + navigator.push( + ImagePreviewScreen( + preview.filename, + preview.bytes, + onSave = { model.saveImage(index) } + ) + ) + } + }, + onSaveClick = { index -> model.saveImage(index) }, + onSetCoverClick = { index -> model.setCoverImage(index) }, + modifier = Modifier + .fillMaxSize() + .padding(contentPadding) + ) + + WorkflowRunFab( + isRunning = model.running, + onStart = { model.runWorkflow() }, + onInterrupt = { model.interrupt() }, + modifier = Modifier + .align(Alignment.BottomEnd) + .navigationBarsPadding() + .padding(end = ComfySpacing.lg, bottom = ComfySpacing.lg) + .offset(y = -sheetHeightOffset) + ) + } } } } @@ -230,10 +262,7 @@ private fun WorkflowRunTopBar( status: ExecutionStatus, statusText: String, progress: Float?, - progressText: String?, - isRunning: Boolean, - onStart: () -> Unit, - onInterrupt: () -> Unit + progressText: String? ) { Column( modifier = Modifier @@ -250,43 +279,6 @@ private fun WorkflowRunTopBar( ) } }, - actions = { - AnimatedContent( - targetState = isRunning, - transitionSpec = { fadeIn() togetherWith fadeOut() } - ) { running -> - if (running) { - OutlinedButton( - onClick = onInterrupt, - colors = ButtonDefaults.outlinedButtonColors( - contentColor = MaterialTheme.colorScheme.error - ), - modifier = Modifier.padding(end = ComfySpacing.sm) - ) { - Icon( - imageVector = Icons.Default.Stop, - contentDescription = null, - modifier = Modifier.size(18.dp) - ) - Spacer(modifier = Modifier.width(ComfySpacing.xs)) - Text("中断") - } - } else { - FilledTonalButton( - onClick = onStart, - modifier = Modifier.padding(end = ComfySpacing.sm) - ) { - Icon( - imageVector = Icons.Default.PlayArrow, - contentDescription = null, - modifier = Modifier.size(18.dp) - ) - Spacer(modifier = Modifier.width(ComfySpacing.xs)) - Text("开始执行") - } - } - } - }, colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.surface ) @@ -301,6 +293,51 @@ private fun WorkflowRunTopBar( } } +@Composable +private fun WorkflowRunFab( + isRunning: Boolean, + onStart: () -> Unit, + onInterrupt: () -> Unit, + modifier: Modifier = Modifier +) { + Box(modifier = modifier) { + AnimatedContent( + targetState = isRunning, + transitionSpec = { fadeIn() togetherWith fadeOut() } + ) { running -> + if (running) { + ExtendedFloatingActionButton( + onClick = onInterrupt, + icon = { + Icon( + imageVector = Icons.Default.Stop, + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + }, + text = { Text("中断") }, + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.onErrorContainer + ) + } else { + ExtendedFloatingActionButton( + onClick = onStart, + icon = { + Icon( + imageVector = Icons.Default.PlayArrow, + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + }, + text = { Text("开始执行") }, + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer + ) + } + } + } +} + @Composable private fun NodeListSheet( nodes: List, @@ -719,8 +756,8 @@ private class WorkflowRunScreenModel( val bytes = imageBytes[ref.filename] ?: return screenModelScope.launch { try { - val path = saveToTemp(bytes, ref.filename) - statusText = "已保存: $path" + val location = saveImageToGallery(bytes, ref.filename) + statusText = "已保存: $location" } catch (e: Exception) { statusText = "保存失败: ${e.message}" } diff --git a/composeApp/src/iosMain/kotlin/moe/uni/comfy_kmp/data/ImageSaveUtils.ios.kt b/composeApp/src/iosMain/kotlin/moe/uni/comfy_kmp/data/ImageSaveUtils.ios.kt new file mode 100644 index 0000000..8e93cbe --- /dev/null +++ b/composeApp/src/iosMain/kotlin/moe/uni/comfy_kmp/data/ImageSaveUtils.ios.kt @@ -0,0 +1,74 @@ +@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) + +package moe.uni.comfy_kmp.data + +import kotlinx.cinterop.addressOf +import kotlinx.cinterop.usePinned +import kotlinx.coroutines.suspendCancellableCoroutine +import platform.Foundation.NSData +import platform.Foundation.dataWithBytes +import platform.Photos.PHAccessLevelAddOnly +import platform.Photos.PHAssetChangeRequest +import platform.Photos.PHAuthorizationStatusAuthorized +import platform.Photos.PHAuthorizationStatusLimited +import platform.Photos.PHAuthorizationStatusNotDetermined +import platform.Photos.PHPhotoLibrary +import platform.UIKit.UIImage +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +actual suspend fun saveImageToGallery(bytes: ByteArray, filenameHint: String): String { + return suspendCancellableCoroutine { continuation -> + val image: UIImage? = UIImage(data = bytes.toNSData()) + if (image == null) { + continuation.resumeWithException(IllegalArgumentException("Unable to decode image")) + return@suspendCancellableCoroutine + } + + fun finishSuccess() { + if (continuation.isActive) { + continuation.resume("Photos") + } + } + + fun finishError(message: String) { + if (continuation.isActive) { + continuation.resumeWithException(IllegalStateException(message)) + } + } + + fun saveToAlbum() { + PHPhotoLibrary.sharedPhotoLibrary().performChanges({ + PHAssetChangeRequest.creationRequestForAssetFromImage(image) + }, completionHandler = { success, error -> + if (success) { + finishSuccess() + } else { + val details = error?.localizedDescription?.let { ": $it" }.orEmpty() + finishError("Failed to save to Photos$details") + } + }) + } + + val status = PHPhotoLibrary.authorizationStatusForAccessLevel(PHAccessLevelAddOnly) + when (status) { + PHAuthorizationStatusAuthorized, PHAuthorizationStatusLimited -> saveToAlbum() + PHAuthorizationStatusNotDetermined -> { + PHPhotoLibrary.requestAuthorizationForAccessLevel(PHAccessLevelAddOnly) { newStatus -> + if (newStatus == PHAuthorizationStatusAuthorized || newStatus == PHAuthorizationStatusLimited) { + saveToAlbum() + } else { + finishError("Photo Library access not granted") + } + } + } + else -> finishError("Photo Library access not granted") + } + } +} + +private fun ByteArray.toNSData(): NSData { + return usePinned { pinned -> + NSData.dataWithBytes(pinned.addressOf(0), size.toULong()) + } +} diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 4fc6c19..4771516 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -167,71 +167,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ - BD38431153976DCA04A5BCBF /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReferenceAnchor = 6DFFEFC5344B9751F9B09211 /* Configuration */; - baseConfigurationReferenceRelativePath = Config.xcconfig; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 18.2; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; 220243254E599B629BB97E0D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReferenceAnchor = 6DFFEFC5344B9751F9B09211 /* Configuration */; @@ -299,7 +234,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; - DEVELOPMENT_TEAM = "${TEAM_ID}"; + DEVELOPMENT_TEAM = NLHG3FFX4L; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = iosApp/Info.plist; @@ -312,12 +247,78 @@ "$(inherited)", "@executable_path/Frameworks", ); + PRODUCT_BUNDLE_IDENTIFIER = moe.uni.comfyui.ios; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; + BD38431153976DCA04A5BCBF /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReferenceAnchor = 6DFFEFC5344B9751F9B09211 /* Configuration */; + baseConfigurationReferenceRelativePath = Config.xcconfig; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; C41926DAA27AEF778FA38896 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -327,7 +328,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; - DEVELOPMENT_TEAM = "${TEAM_ID}"; + DEVELOPMENT_TEAM = NLHG3FFX4L; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = iosApp/Info.plist; @@ -340,6 +341,7 @@ "$(inherited)", "@executable_path/Frameworks", ); + PRODUCT_BUNDLE_IDENTIFIER = moe.uni.comfyui.ios; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -349,15 +351,6 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 9EF34B5F98F775D07666BB98 /* Build configuration list for PBXProject "iosApp" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - BD38431153976DCA04A5BCBF /* Debug */, - 220243254E599B629BB97E0D /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 365A766E3A8766A34A980614 /* Build configuration list for PBXNativeTarget "iosApp" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -367,7 +360,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 9EF34B5F98F775D07666BB98 /* Build configuration list for PBXProject "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BD38431153976DCA04A5BCBF /* Debug */, + 220243254E599B629BB97E0D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 04FFB4DE676F9A629B413B44 /* Project object */; -} \ No newline at end of file +} diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist index 99d16fa..614cda3 100644 --- a/iosApp/iosApp/Info.plist +++ b/iosApp/iosApp/Info.plist @@ -1,8 +1,12 @@ - - CADisableMinimumFrameDurationOnPhone - - + + CADisableMinimumFrameDurationOnPhone + + NSPhotoLibraryAddUsageDescription + Allow access to save generated images to Photos. + NSPhotoLibraryUsageDescription + Allow access to save generated images to Photos. +