/** * 第 8 章配套代码 · 自定义 CameraModule(4 步法完整示例) * * 演示如何从 0 到 1 接入一个原生能力: * ① commonMain 定义 expect class * ② androidMain 写 actual 实现 * ③ iosMain 写 actual 实现 * ④ Application 注册 + 业务调用 * * 真实工程会更复杂(权限、回调、错误处理),这里聚焦"通信骨架"。 */ // ═══════════════════════════════════════════════════════════════════ // 步骤 ① commonMain:定义 expect class // 文件位置:shared/src/commonMain/kotlin/com/example/kuikly/modules/CameraModule.kt // ═══════════════════════════════════════════════════════════════════ package com.example.kuikly.modules import com.tencent.kuikly.core.module.Module expect class CameraModule() : Module { /** * 调起系统相机拍照 * @param onComplete 拍照完成回调(path 为本地图片绝对路径,null = 用户取消或失败) */ fun takePhoto(onComplete: (path: String?) -> Unit) /** * 从相册选图(多选) * @param maxCount 最多张数 * @param onComplete 回调(选中的图片路径列表,空列表 = 取消) */ fun pickFromAlbum(maxCount: Int = 1, onComplete: (paths: List) -> Unit) /** * 检查相机权限 */ fun hasPermission(): Boolean companion object { const val MODULE_NAME = "Camera" } } // ═══════════════════════════════════════════════════════════════════ // 步骤 ② androidMain:Android 实现 // 文件位置:shared/src/androidMain/kotlin/com/example/kuikly/modules/CameraModule.kt // ═══════════════════════════════════════════════════════════════════ /* package com.example.kuikly.modules import android.Manifest import android.app.Activity import android.content.Intent import android.content.pm.PackageManager import android.provider.MediaStore import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import com.example.kuikly.app.appContext import com.tencent.kuikly.android.KuiklyActivity import com.tencent.kuikly.core.module.Module import java.io.File actual class CameraModule actual constructor() : Module() { actual fun takePhoto(onComplete: (String?) -> Unit) { val activity = currentActivity() as? KuiklyActivity ?: run { onComplete(null); return } if (!hasPermission()) { requestCameraPermission(activity) { granted -> if (granted) takePhoto(onComplete) else onComplete(null) } return } val tempFile = File(activity.cacheDir, "kuikly_photo_${System.currentTimeMillis()}.jpg") val authority = "${activity.packageName}.fileprovider" val uri = FileProvider.getUriForFile(activity, authority, tempFile) val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) .putExtra(MediaStore.EXTRA_OUTPUT, uri) activity.launchForResult(intent) { resultCode, _ -> if (resultCode == Activity.RESULT_OK && tempFile.exists()) { onComplete(tempFile.absolutePath) } else { onComplete(null) } } } actual fun pickFromAlbum(maxCount: Int, onComplete: (List) -> Unit) { val activity = currentActivity() as? KuiklyActivity ?: run { onComplete(emptyList()); return } val intent = Intent(Intent.ACTION_PICK) .setType("image/*") .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, maxCount > 1) activity.launchForResult(intent) { code, data -> if (code != Activity.RESULT_OK || data == null) { onComplete(emptyList()); return@launchForResult } val paths = mutableListOf() data.clipData?.let { clip -> for (i in 0 until clip.itemCount.coerceAtMost(maxCount)) { paths += copyUriToCache(clip.getItemAt(i).uri) } } ?: data.data?.let { paths += copyUriToCache(it) } onComplete(paths) } } actual fun hasPermission(): Boolean { return ContextCompat.checkSelfPermission( appContext, Manifest.permission.CAMERA ) == PackageManager.PERMISSION_GRANTED } actual companion object { actual const val MODULE_NAME = "Camera" } } */ // ═══════════════════════════════════════════════════════════════════ // 步骤 ③ iosMain:iOS 实现(伪代码) // 文件位置:shared/src/iosMain/kotlin/com/example/kuikly/modules/CameraModule.kt // ═══════════════════════════════════════════════════════════════════ /* package com.example.kuikly.modules import platform.UIKit.* import platform.AVFoundation.* import platform.Foundation.* actual class CameraModule actual constructor() : Module() { actual fun takePhoto(onComplete: (String?) -> Unit) { if (!hasPermission()) { requestCameraPermission { granted -> if (granted) takePhoto(onComplete) else onComplete(null) } return } val picker = UIImagePickerController().apply { sourceType = UIImagePickerControllerSourceTypeCamera delegate = createDelegate(onComplete) } currentVC()?.presentViewController(picker, animated = true, completion = null) } actual fun pickFromAlbum(maxCount: Int, onComplete: (List) -> Unit) { // 用 PHPickerViewController(iOS 14+)实现多选相册 } actual fun hasPermission(): Boolean { val status = AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo) return status == AVAuthorizationStatusAuthorized } actual companion object { actual const val MODULE_NAME = "Camera" } } */ // ═══════════════════════════════════════════════════════════════════ // 步骤 ④ 注册 + 业务调用(commonMain 业务侧) // ═══════════════════════════════════════════════════════════════════ /* // (a) Application 启动时注册(Android) class KuiklyApplication : Application() { override fun onCreate() { super.onCreate() ModuleRegistry.register(CameraModule.MODULE_NAME) { CameraModule() } } } // (b) 业务 Pager 调用 @Page("PublishPage") internal class PublishPage : Pager() { private var photoPath by observable(null) override fun body(): ViewBuilder { val ctx = this return { View { attr { height(200f); allCenter() backgroundColor(Color(0xFFE0E0E0L)) borderRadius(12f); margin(16f) } event { click { acquireModule(CameraModule.MODULE_NAME) .takePhoto { path -> ctx.photoPath = path } } } if (ctx.photoPath == null) { Text { attr { text("📷 点击拍照"); fontSize(16f) } } } else { Image { attr { src(ctx.photoPath!!); flex(1f); resizeCover() } } } } } } } */