react-native-kookit 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,27 @@
1
1
  # react-native-kookit
2
2
 
3
- A React Native module for intercepting volume button presses on both iOS and Android platforms.
3
+ A comprehensive React Native/Expo module with multiple utilities for Android and iOS.
4
+
5
+ ## Features
6
+
7
+ - 🎵 **Volume Key Interception** - Capture volume button presses on both iOS and Android
8
+ - 📁 **FTP Client** - Full-featured FTP client with upload/download capabilities
9
+ - 🗂️ **SMB Client** - Access SMB/CIFS network shares
10
+ - 📱 **Content URI Handler** (Android) - Handle `content://` URIs from other apps, bypassing permission restrictions
11
+
12
+ ### Content URI Handler
13
+
14
+ The Content URI Handler solves a common Android problem: when receiving files via "Open with" or "Share" intents, apps receive `content://` URIs that often trigger `SecurityException: Permission Denial` errors.
15
+
16
+ **Key Benefits:**
17
+
18
+ - ✅ Bypass permission restrictions using Android's ContentResolver
19
+ - ✅ No `READ_EXTERNAL_STORAGE` permission needed
20
+ - ✅ Works with all third-party apps and file managers
21
+ - ✅ Get file metadata (name, size, MIME type)
22
+ - ✅ Copy to app cache directory for further processing
23
+
24
+ 📖 [**View Content URI Handler Documentation**](./docs/CONTENT_URI_HANDLER.md)
4
25
 
5
26
  # API documentation
6
27
 
@@ -0,0 +1,256 @@
1
+ # TTS 功能实现检查清单
2
+
3
+ ## ✅ 已完成的任务
4
+
5
+ ### 1. TypeScript 类型定义
6
+
7
+ - [x] 在 `src/ReactNativeKookit.types.ts` 中添加 `TtsSynthesizeOptions` 类型
8
+ - [x] 在 `src/ReactNativeKookit.types.ts` 中添加 `TtsSynthesizeResult` 类型
9
+ - [x] 类型包含所有必需和可选参数
10
+ - [x] 添加了清晰的注释说明
11
+
12
+ ### 2. TypeScript 模块接口
13
+
14
+ - [x] 在 `src/ReactNativeKookitModule.ts` 中导入 TTS 类型
15
+ - [x] 声明 `synthesizeSpeech` 方法
16
+ - [x] 添加 JSDoc 注释
17
+ - [x] 正确的类型签名和返回值
18
+
19
+ ### 3. Android 原生实现
20
+
21
+ - [x] 导入必要的 Android TTS 相关类
22
+ - [x] 添加 TTS 实例变量
23
+ - [x] 实现 `synthesizeSpeech` AsyncFunction
24
+ - [x] TTS 初始化逻辑(异步等待)
25
+ - [x] 语言设置和验证
26
+ - [x] 语音ID支持(可选)
27
+ - [x] 音调和语速设置
28
+ - [x] 创建缓存目录 `tts`
29
+ - [x] 生成唯一文件名(UUID)
30
+ - [x] 合成音频到 WAV 文件
31
+ - [x] 实现 `parseLocale()` 辅助函数
32
+ - [x] 实现 `estimateDuration()` 辅助函数
33
+ - [x] 在 `onDestroy()` 中清理 TTS 资源
34
+ - [x] 完善的错误处理
35
+
36
+ ### 4. iOS 原生实现
37
+
38
+ - [x] 添加 `AVSpeechSynthesizer` 实例变量
39
+ - [x] 实现 `synthesizeSpeech` AsyncFunction
40
+ - [x] 初始化语音合成器
41
+ - [x] 语言设置和语音选择
42
+ - [x] 支持语音ID(可选)
43
+ - [x] 音调和语速设置
44
+ - [x] 创建缓存目录 `tts`
45
+ - [x] 生成唯一文件名(UUID)
46
+ - [x] 使用 `AVSpeechSynthesizer.write()` 生成音频
47
+ - [x] 实现 `synthesizeSpeechToFile()` 核心方法
48
+ - [x] 实现 `writeBuffersToFile()` 音频写入方法
49
+ - [x] 实现 `estimateDuration()` 辅助函数
50
+ - [x] 使用 async/await 处理异步操作
51
+ - [x] 完善的错误处理
52
+
53
+ ### 5. 导出和集成
54
+
55
+ - [x] `src/index.ts` 已通过 `export *` 自动导出 TTS 类型
56
+ - [x] 模块可以直接使用
57
+
58
+ ### 6. 示例代码
59
+
60
+ - [x] 创建 `example/TtsExample.tsx` 完整示例
61
+ - [x] 包含文本输入界面
62
+ - [x] 语言选择功能
63
+ - [x] 音调和语速调整
64
+ - [x] 错误处理展示
65
+ - [x] 结果显示
66
+ - [x] 友好的用户界面
67
+
68
+ ### 7. 文档
69
+
70
+ - [x] 创建 `docs/TTS_FEATURE.md` 完整文档
71
+ - API 说明
72
+ - 使用示例
73
+ - 平台详情
74
+ - 支持的语言
75
+ - 参数指南
76
+ - 错误处理
77
+ - 最佳实践
78
+ - 故障排除
79
+
80
+ - [x] 创建 `docs/TTS_QUICK_START.md` 快速入门
81
+ - 快速示例
82
+ - 常用语言代码
83
+ - 文件位置说明
84
+
85
+ - [x] 创建 `docs/TTS_IMPLEMENTATION_SUMMARY.md` 实现总结
86
+ - 技术细节
87
+ - 文件清单
88
+ - 后续改进建议
89
+
90
+ - [x] 创建 `docs/TTS_README_SECTION.md` README片段
91
+ - 可直接添加到主README
92
+
93
+ ### 8. 测试和验证
94
+
95
+ - [x] TypeScript 编译通过
96
+ - [x] 无编译错误
97
+ - [x] 类型检查通过
98
+ - [x] 代码构建成功 (`npm run build`)
99
+
100
+ ## 📋 功能特性验证
101
+
102
+ ### 核心功能
103
+
104
+ - [x] 文本转语音合成
105
+ - [x] 支持自定义语言
106
+ - [x] 支持音调调整(0.5-2.0)
107
+ - [x] 支持语速调整(0.5-2.0)
108
+ - [x] 可选语音ID
109
+ - [x] 返回文件路径
110
+ - [x] 返回预估时长
111
+
112
+ ### 平台支持
113
+
114
+ - [x] Android 实现(TextToSpeech API)
115
+ - [x] iOS 实现(AVSpeechSynthesizer)
116
+ - [x] 跨平台统一API
117
+
118
+ ### 文件管理
119
+
120
+ - [x] 自动创建缓存目录
121
+ - [x] 唯一文件命名(UUID)
122
+ - [x] Android: WAV 格式
123
+ - [x] iOS: CAF 格式
124
+
125
+ ### 开发体验
126
+
127
+ - [x] TypeScript 完整类型支持
128
+ - [x] Promise-based API
129
+ - [x] 清晰的错误消息
130
+ - [x] 详细的文档
131
+ - [x] 实用的示例
132
+
133
+ ## 📁 文件清单
134
+
135
+ ### 修改的文件(5个)
136
+
137
+ 1. ✅ `src/ReactNativeKookit.types.ts` - TTS类型定义
138
+ 2. ✅ `src/ReactNativeKookitModule.ts` - TTS方法声明
139
+ 3. ✅ `android/src/main/java/expo/modules/kookit/ReactNativeKookitModule.kt` - Android实现
140
+ 4. ✅ `ios/ReactNativeKookitModule.swift` - iOS实现
141
+ 5. ✅ `src/index.ts` - 导出(已有 export \*)
142
+
143
+ ### 新增的文件(5个)
144
+
145
+ 1. ✅ `example/TtsExample.tsx` - 完整示例应用
146
+ 2. ✅ `docs/TTS_FEATURE.md` - 完整文档
147
+ 3. ✅ `docs/TTS_QUICK_START.md` - 快速入门
148
+ 4. ✅ `docs/TTS_IMPLEMENTATION_SUMMARY.md` - 实现总结
149
+ 5. ✅ `docs/TTS_README_SECTION.md` - README片段
150
+
151
+ ## 🎯 API 使用示例
152
+
153
+ ### 基础使用
154
+
155
+ ```typescript
156
+ const result = await ReactNativeKookitModule.synthesizeSpeech({
157
+ text: "Hello World",
158
+ });
159
+ ```
160
+
161
+ ### 完整参数
162
+
163
+ ```typescript
164
+ const result = await ReactNativeKookitModule.synthesizeSpeech({
165
+ text: "你好世界",
166
+ language: "zh-CN",
167
+ voiceId: "optional-voice-id",
168
+ pitch: 1.2,
169
+ rate: 0.9,
170
+ });
171
+ ```
172
+
173
+ ### 返回值
174
+
175
+ ```typescript
176
+ {
177
+ filePath: "/path/to/cache/tts/tts_uuid.wav", // Android
178
+ // or
179
+ filePath: "/path/to/cache/tts/tts_uuid.caf", // iOS
180
+ duration: 2.5 // seconds
181
+ }
182
+ ```
183
+
184
+ ## ✨ 代码质量
185
+
186
+ - [x] 遵循项目代码风格
187
+ - [x] 使用 TypeScript 严格类型
188
+ - [x] 完善的错误处理
189
+ - [x] 清晰的变量命名
190
+ - [x] 详细的代码注释
191
+ - [x] 模块化设计
192
+ - [x] 资源清理(Android onDestroy)
193
+
194
+ ## 🔍 测试状态
195
+
196
+ - [x] TypeScript 编译成功
197
+ - [x] 无语法错误
198
+ - [x] 无类型错误
199
+ - [x] Build 构建成功
200
+ - ⏳ 运行时测试(需要在设备上测试)
201
+
202
+ ## 📦 输出文件
203
+
204
+ ### Android
205
+
206
+ - **格式**: WAV (PCM)
207
+ - **位置**: `{cacheDir}/tts/`
208
+ - **命名**: `tts_{uuid}.wav`
209
+ - **API**: `TextToSpeech.synthesizeToFile()`
210
+
211
+ ### iOS
212
+
213
+ - **格式**: CAF (Core Audio Format)
214
+ - **位置**: `{cachesDirectory}/tts/`
215
+ - **命名**: `tts_{uuid}.caf`
216
+ - **API**: `AVSpeechSynthesizer.write()`
217
+
218
+ ## 🌍 语言支持
219
+
220
+ 已文档化的常用语言:
221
+
222
+ - 英语(en-US, en-GB, en-AU)
223
+ - 中文(zh-CN, zh-TW, zh-HK)
224
+ - 西班牙语(es-ES, es-MX)
225
+ - 法语(fr-FR)
226
+ - 德语(de-DE)
227
+ - 日语(ja-JP)
228
+ - 韩语(ko-KR)
229
+ - 意大利语(it-IT)
230
+ - 葡萄牙语(pt-BR)
231
+ - 俄语(ru-RU)
232
+ - 阿拉伯语(ar-SA)
233
+
234
+ ## 🚀 下一步
235
+
236
+ 建议的后续步骤:
237
+
238
+ 1. 在真实设备上测试 Android 版本
239
+ 2. 在真实设备上测试 iOS 版本
240
+ 3. 测试多种语言
241
+ 4. 验证音频文件质量
242
+ 5. 测试边界情况(长文本、特殊字符等)
243
+ 6. 考虑添加单元测试
244
+
245
+ ## ✅ 总结
246
+
247
+ TTS 功能已完全实现,包括:
248
+
249
+ - ✅ TypeScript 类型和接口定义
250
+ - ✅ Android 原生实现
251
+ - ✅ iOS 原生实现
252
+ - ✅ 完整的示例代码
253
+ - ✅ 详细的文档
254
+ - ✅ 编译通过,无错误
255
+
256
+ **状态**: 🎉 实现完成,可以使用!
@@ -11,6 +11,17 @@ import expo.modules.kotlin.exception.Exceptions
11
11
  import expo.modules.kotlin.Promise
12
12
  import kotlinx.coroutines.*
13
13
  import java.net.URL
14
+ import android.net.Uri
15
+ import android.provider.OpenableColumns
16
+ import android.database.Cursor
17
+ import java.io.File
18
+ import java.io.FileOutputStream
19
+ import java.io.InputStream
20
+ import android.speech.tts.TextToSpeech
21
+ import android.speech.tts.UtteranceProgressListener
22
+ import android.os.Bundle
23
+ import java.util.Locale
24
+ import java.util.UUID
14
25
 
15
26
  class ReactNativeKookitModule : Module() {
16
27
  private var isVolumeListenerEnabled = false
@@ -20,6 +31,8 @@ class ReactNativeKookitModule : Module() {
20
31
  private val ftpClients = mutableMapOf<String, FtpClient>()
21
32
  private val smbClients = mutableMapOf<String, SmbClient>()
22
33
  private val moduleScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
34
+ private var textToSpeech: TextToSpeech? = null
35
+ private var ttsInitialized = false
23
36
 
24
37
  // Each module class must implement the definition function. The definition consists of components
25
38
  // that describes the module's functionality and behavior.
@@ -723,6 +736,204 @@ class ReactNativeKookitModule : Module() {
723
736
  }
724
737
  }
725
738
 
739
+ // ContentResolver functions for handling content:// URIs
740
+ AsyncFunction("copyContentUriToCache") { contentUri: String, fileName: String?, promise: Promise ->
741
+ moduleScope.launch(Dispatchers.IO) {
742
+ try {
743
+ val context = appContext.reactContext ?: throw Exception("React context is not available")
744
+ val uri = Uri.parse(contentUri)
745
+
746
+ if (uri.scheme != "content") {
747
+ throw Exception("Only content:// URIs are supported. Received: ${uri.scheme}://")
748
+ }
749
+
750
+ // Get metadata from ContentResolver
751
+ val metadata = getContentUriMetadataInternal(context, uri)
752
+
753
+ // Determine file name
754
+ val finalFileName = fileName ?: metadata["displayName"] as? String
755
+ ?: "file_${System.currentTimeMillis()}"
756
+
757
+ // Create destination file in cache directory
758
+ val cacheDir = context.cacheDir
759
+ val destFile = File(cacheDir, finalFileName)
760
+
761
+ // Copy file using ContentResolver InputStream
762
+ var inputStream: InputStream? = null
763
+ var outputStream: FileOutputStream? = null
764
+ var totalBytes = 0L
765
+
766
+ try {
767
+ inputStream = context.contentResolver.openInputStream(uri)
768
+ ?: throw Exception("Failed to open input stream for URI: $contentUri")
769
+
770
+ outputStream = FileOutputStream(destFile)
771
+ val buffer = ByteArray(8192)
772
+ var bytesRead: Int
773
+
774
+ while (inputStream.read(buffer).also { bytesRead = it } != -1) {
775
+ outputStream.write(buffer, 0, bytesRead)
776
+ totalBytes += bytesRead
777
+ }
778
+
779
+ outputStream.flush()
780
+
781
+ // Build result
782
+ val result = mapOf(
783
+ "localPath" to destFile.absolutePath,
784
+ "fileName" to finalFileName,
785
+ "mimeType" to metadata["mimeType"],
786
+ "size" to totalBytes
787
+ )
788
+
789
+ withContext(Dispatchers.Main) { promise.resolve(result) }
790
+ } finally {
791
+ inputStream?.close()
792
+ outputStream?.close()
793
+ }
794
+ } catch (e: Exception) {
795
+ withContext(Dispatchers.Main) {
796
+ promise.reject("COPY_CONTENT_URI_ERROR", "Failed to copy content URI: ${e.message}", e)
797
+ }
798
+ }
799
+ }
800
+ }
801
+
802
+ AsyncFunction("getContentUriMetadata") { contentUri: String, promise: Promise ->
803
+ moduleScope.launch(Dispatchers.IO) {
804
+ try {
805
+ val context = appContext.reactContext ?: throw Exception("React context is not available")
806
+ val uri = Uri.parse(contentUri)
807
+
808
+ if (uri.scheme != "content") {
809
+ throw Exception("Only content:// URIs are supported. Received: ${uri.scheme}://")
810
+ }
811
+
812
+ val metadata = getContentUriMetadataInternal(context, uri)
813
+ withContext(Dispatchers.Main) { promise.resolve(metadata) }
814
+ } catch (e: Exception) {
815
+ withContext(Dispatchers.Main) {
816
+ promise.reject("GET_CONTENT_URI_METADATA_ERROR", "Failed to get metadata: ${e.message}", e)
817
+ }
818
+ }
819
+ }
820
+ }
821
+
822
+ // TTS (Text-to-Speech) Function
823
+ AsyncFunction("synthesizeSpeech") { options: Map<String, Any?>, promise: Promise ->
824
+ moduleScope.launch(Dispatchers.IO) {
825
+ try {
826
+ val context = appContext.reactContext ?: throw Exception("React context is not available")
827
+
828
+ val text = options["text"] as? String ?: throw Exception("Text is required")
829
+ val voiceId = options["voiceId"] as? String
830
+ val language = options["language"] as? String ?: "en-US"
831
+ val pitch = (options["pitch"] as? Double)?.toFloat() ?: 1.0f
832
+ val rate = (options["rate"] as? Double)?.toFloat() ?: 1.0f
833
+
834
+ // Initialize TTS if not already initialized
835
+ if (textToSpeech == null) {
836
+ val latch = CompletableDeferred<Boolean>()
837
+ withContext(Dispatchers.Main) {
838
+ textToSpeech = TextToSpeech(context) { status ->
839
+ if (status == TextToSpeech.SUCCESS) {
840
+ ttsInitialized = true
841
+ latch.complete(true)
842
+ } else {
843
+ latch.completeExceptionally(Exception("TTS initialization failed"))
844
+ }
845
+ }
846
+ }
847
+ latch.await()
848
+ }
849
+
850
+ // Set language
851
+ val locale = parseLocale(language)
852
+ val result = textToSpeech?.setLanguage(locale)
853
+ if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
854
+ throw Exception("Language not supported: $language")
855
+ }
856
+
857
+ // Set voice if specified
858
+ if (voiceId != null) {
859
+ val voices = textToSpeech?.voices
860
+ val voice = voices?.find { it.name == voiceId }
861
+ if (voice != null) {
862
+ textToSpeech?.voice = voice
863
+ }
864
+ }
865
+
866
+ // Set pitch and rate
867
+ textToSpeech?.setPitch(pitch)
868
+ textToSpeech?.setSpeechRate(rate)
869
+
870
+ // Create TTS cache directory
871
+ val ttsDir = File(context.cacheDir, "tts")
872
+ if (!ttsDir.exists()) {
873
+ ttsDir.mkdirs()
874
+ }
875
+
876
+ // Generate unique filename
877
+ val fileName = "tts_${UUID.randomUUID()}.wav"
878
+ val outputFile = File(ttsDir, fileName)
879
+
880
+ // Synthesize to file
881
+ val utteranceId = UUID.randomUUID().toString()
882
+ val synthesisLatch = CompletableDeferred<String>()
883
+
884
+ withContext<Unit>(Dispatchers.Main) {
885
+ textToSpeech?.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
886
+ override fun onStart(uId: String?) {}
887
+
888
+ override fun onDone(id: String?) {
889
+ if (id == utteranceId) {
890
+ synthesisLatch.complete(outputFile.absolutePath)
891
+ }
892
+ }
893
+
894
+ @Deprecated("Deprecated in Java", ReplaceWith("onError(String)"))
895
+ override fun onError(uId: String?) {
896
+ synthesisLatch.completeExceptionally(Exception("TTS synthesis failed"))
897
+ }
898
+
899
+ override fun onError(uId: String?, errorCode: Int) {
900
+ synthesisLatch.completeExceptionally(Exception("TTS synthesis failed with code: $errorCode"))
901
+ }
902
+ })
903
+
904
+ val params = Bundle().apply {
905
+ putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId)
906
+ }
907
+
908
+ @Suppress("DEPRECATION")
909
+ textToSpeech?.synthesizeToFile(text, params, outputFile, utteranceId)
910
+ }
911
+
912
+ val filePath = synthesisLatch.await()
913
+
914
+ // Get file duration (approximate based on text length and rate)
915
+ val duration = estimateDuration(text, rate)
916
+
917
+ withContext(Dispatchers.Main) {
918
+ promise.resolve(mapOf(
919
+ "filePath" to filePath,
920
+ "duration" to duration
921
+ ))
922
+ }
923
+ } catch (e: Exception) {
924
+ withContext(Dispatchers.Main) {
925
+ promise.reject("TTS_SYNTHESIS_ERROR", "Failed to synthesize speech: ${e.message}", e)
926
+ }
927
+ }
928
+ }
929
+ }
930
+
931
+ // Cleanup when module is destroyed
932
+ OnDestroy {
933
+ textToSpeech?.shutdown()
934
+ textToSpeech = null
935
+ }
936
+
726
937
  // Enables the module to be used as a native view. Definition components that are accepted as part of
727
938
  // the view definition: Prop, Events.
728
939
  View(ReactNativeKookitView::class) {
@@ -789,4 +1000,77 @@ class ReactNativeKookitModule : Module() {
789
1000
  // Context might be lost, ignore
790
1001
  }
791
1002
  }
1003
+
1004
+ /**
1005
+ * Internal helper to query content URI metadata using ContentResolver
1006
+ */
1007
+ private fun getContentUriMetadataInternal(context: Context, uri: Uri): Map<String, Any?> {
1008
+ var cursor: Cursor? = null
1009
+ try {
1010
+ cursor = context.contentResolver.query(
1011
+ uri,
1012
+ arrayOf(
1013
+ OpenableColumns.DISPLAY_NAME,
1014
+ OpenableColumns.SIZE
1015
+ ),
1016
+ null,
1017
+ null,
1018
+ null
1019
+ )
1020
+
1021
+ var displayName: String? = null
1022
+ var size: Long? = null
1023
+
1024
+ cursor?.use {
1025
+ if (it.moveToFirst()) {
1026
+ val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
1027
+ val sizeIndex = it.getColumnIndex(OpenableColumns.SIZE)
1028
+
1029
+ if (nameIndex != -1) {
1030
+ displayName = it.getString(nameIndex)
1031
+ }
1032
+
1033
+ if (sizeIndex != -1 && !it.isNull(sizeIndex)) {
1034
+ size = it.getLong(sizeIndex)
1035
+ }
1036
+ }
1037
+ }
1038
+
1039
+ // Get MIME type
1040
+ val mimeType = context.contentResolver.getType(uri)
1041
+
1042
+ return mapOf(
1043
+ "displayName" to displayName,
1044
+ "mimeType" to mimeType,
1045
+ "size" to size
1046
+ )
1047
+ } catch (e: Exception) {
1048
+ throw Exception("Failed to query content URI metadata: ${e.message}", e)
1049
+ } finally {
1050
+ cursor?.close()
1051
+ }
1052
+ }
1053
+
1054
+ /**
1055
+ * Parse locale string (e.g., "en-US", "zh-CN") to Locale object
1056
+ */
1057
+ private fun parseLocale(localeString: String): Locale {
1058
+ val parts = localeString.split("-", "_")
1059
+ return when (parts.size) {
1060
+ 1 -> Locale(parts[0])
1061
+ 2 -> Locale(parts[0], parts[1])
1062
+ else -> Locale(parts[0], parts[1], parts[2])
1063
+ }
1064
+ }
1065
+
1066
+ /**
1067
+ * Estimate audio duration based on text length and speech rate
1068
+ * This is a rough estimation: average speaking rate is about 150 words per minute
1069
+ */
1070
+ private fun estimateDuration(text: String, rate: Float): Double {
1071
+ val wordCount = text.split("\\s+".toRegex()).size
1072
+ val baseMinutes = wordCount / 150.0
1073
+ val durationSeconds = (baseMinutes * 60.0) / rate
1074
+ return durationSeconds
1075
+ }
792
1076
  }
@@ -96,4 +96,15 @@ export type SmbEventPayload = {
96
96
  onComplete?: () => void;
97
97
  onError?: (error: string) => void;
98
98
  };
99
+ export type TtsSynthesizeOptions = {
100
+ text: string;
101
+ voiceId?: string;
102
+ language?: string;
103
+ pitch?: number;
104
+ rate?: number;
105
+ };
106
+ export type TtsSynthesizeResult = {
107
+ filePath: string;
108
+ duration?: number;
109
+ };
99
110
  //# sourceMappingURL=ReactNativeKookit.types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReactNativeKookit.types.d.ts","sourceRoot":"","sources":["../src/ReactNativeKookit.types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ReactNativeKookit.types.d.ts","sourceRoot":"","sources":["../src/ReactNativeKookit.types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ReactNativeKookit.types.js","sourceRoot":"","sources":["../src/ReactNativeKookit.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { StyleProp, ViewStyle } from \"react-native\";\n\nexport type OnLoadEventPayload = {\n url: string;\n};\n\nexport type VolumeKeyEventPayload = {\n key: \"up\" | \"down\";\n};\n\nexport type FtpConnectionConfig = {\n host: string;\n port?: number;\n username: string;\n password: string;\n passive?: boolean;\n timeout?: number;\n};\n\nexport type FtpFileInfo = {\n name: string;\n isDirectory: boolean;\n size: number;\n lastModified: string;\n permissions?: string;\n};\n\nexport type FtpProgressInfo = {\n transferred: number;\n total: number;\n percentage: number;\n};\n\nexport type FtpProgressEvent = FtpProgressInfo & {\n clientId: string;\n};\n\nexport type FtpCompleteEvent = {\n clientId: string;\n};\n\nexport type FtpErrorEvent = {\n clientId: string;\n error: string;\n};\n\nexport type FtpEventPayload = {\n onProgress?: (progress: FtpProgressInfo) => void;\n onComplete?: () => void;\n onError?: (error: string) => void;\n};\n\nexport type ReactNativeKookitModuleEvents = {\n onChange: (params: ChangeEventPayload) => void;\n onVolumeButtonPressed: (params: VolumeKeyEventPayload) => void;\n onFtpProgress: (params: FtpProgressEvent) => void;\n onFtpComplete: (params: FtpCompleteEvent) => void;\n onFtpError: (params: FtpErrorEvent) => void;\n // SMB events (reserved for future file operations parity)\n onSmbProgress: (params: SmbProgressEvent) => void;\n onSmbComplete: (params: SmbCompleteEvent) => void;\n onSmbError: (params: SmbErrorEvent) => void;\n};\n\nexport type ChangeEventPayload = {\n value: string;\n};\n\nexport type ReactNativeKookitViewProps = {\n url: string;\n onLoad: (event: { nativeEvent: OnLoadEventPayload }) => void;\n style?: StyleProp<ViewStyle>;\n};\n\n// SMB API parity with FTP (OOP new-client style)\nexport type SmbConnectionConfig = {\n host: string;\n port?: number; // default 445\n username: string;\n password: string;\n domain?: string; // optional Windows domain or workgroup\n share?: string; // optional default share to open\n timeout?: number; // ms\n};\n\nexport type SmbFileInfo = {\n name: string;\n isDirectory: boolean;\n size: number;\n lastModified?: string;\n attributes?: string;\n};\n\nexport type SmbProgressInfo = {\n transferred: number;\n total: number;\n percentage: number;\n};\n\nexport type SmbProgressEvent = SmbProgressInfo & {\n clientId: string;\n};\n\nexport type SmbCompleteEvent = {\n clientId: string;\n};\n\nexport type SmbErrorEvent = {\n clientId: string;\n error: string;\n};\n\nexport type SmbEventPayload = {\n onProgress?: (progress: SmbProgressInfo) => void;\n onComplete?: () => void;\n onError?: (error: string) => void;\n};\n"]}
1
+ {"version":3,"file":"ReactNativeKookit.types.js","sourceRoot":"","sources":["../src/ReactNativeKookit.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { StyleProp, ViewStyle } from \"react-native\";\n\nexport type OnLoadEventPayload = {\n url: string;\n};\n\nexport type VolumeKeyEventPayload = {\n key: \"up\" | \"down\";\n};\n\nexport type FtpConnectionConfig = {\n host: string;\n port?: number;\n username: string;\n password: string;\n passive?: boolean;\n timeout?: number;\n};\n\nexport type FtpFileInfo = {\n name: string;\n isDirectory: boolean;\n size: number;\n lastModified: string;\n permissions?: string;\n};\n\nexport type FtpProgressInfo = {\n transferred: number;\n total: number;\n percentage: number;\n};\n\nexport type FtpProgressEvent = FtpProgressInfo & {\n clientId: string;\n};\n\nexport type FtpCompleteEvent = {\n clientId: string;\n};\n\nexport type FtpErrorEvent = {\n clientId: string;\n error: string;\n};\n\nexport type FtpEventPayload = {\n onProgress?: (progress: FtpProgressInfo) => void;\n onComplete?: () => void;\n onError?: (error: string) => void;\n};\n\nexport type ReactNativeKookitModuleEvents = {\n onChange: (params: ChangeEventPayload) => void;\n onVolumeButtonPressed: (params: VolumeKeyEventPayload) => void;\n onFtpProgress: (params: FtpProgressEvent) => void;\n onFtpComplete: (params: FtpCompleteEvent) => void;\n onFtpError: (params: FtpErrorEvent) => void;\n // SMB events (reserved for future file operations parity)\n onSmbProgress: (params: SmbProgressEvent) => void;\n onSmbComplete: (params: SmbCompleteEvent) => void;\n onSmbError: (params: SmbErrorEvent) => void;\n};\n\nexport type ChangeEventPayload = {\n value: string;\n};\n\nexport type ReactNativeKookitViewProps = {\n url: string;\n onLoad: (event: { nativeEvent: OnLoadEventPayload }) => void;\n style?: StyleProp<ViewStyle>;\n};\n\n// SMB API parity with FTP (OOP new-client style)\nexport type SmbConnectionConfig = {\n host: string;\n port?: number; // default 445\n username: string;\n password: string;\n domain?: string; // optional Windows domain or workgroup\n share?: string; // optional default share to open\n timeout?: number; // ms\n};\n\nexport type SmbFileInfo = {\n name: string;\n isDirectory: boolean;\n size: number;\n lastModified?: string;\n attributes?: string;\n};\n\nexport type SmbProgressInfo = {\n transferred: number;\n total: number;\n percentage: number;\n};\n\nexport type SmbProgressEvent = SmbProgressInfo & {\n clientId: string;\n};\n\nexport type SmbCompleteEvent = {\n clientId: string;\n};\n\nexport type SmbErrorEvent = {\n clientId: string;\n error: string;\n};\n\nexport type SmbEventPayload = {\n onProgress?: (progress: SmbProgressInfo) => void;\n onComplete?: () => void;\n onError?: (error: string) => void;\n};\n\n// TTS (Text-to-Speech) API\nexport type TtsSynthesizeOptions = {\n text: string;\n voiceId?: string; // Voice identifier (platform-specific)\n language?: string; // Language code (e.g., 'en-US', 'zh-CN')\n pitch?: number; // Pitch (0.5 - 2.0, default: 1.0)\n rate?: number; // Speech rate (0.5 - 2.0, default: 1.0)\n};\n\nexport type TtsSynthesizeResult = {\n filePath: string; // Path to the generated audio file\n duration?: number; // Duration in seconds (if available)\n};\n"]}
@@ -1,5 +1,5 @@
1
1
  import { NativeModule } from "expo";
2
- import { ReactNativeKookitModuleEvents, FtpConnectionConfig, FtpFileInfo, SmbConnectionConfig, SmbFileInfo } from "./ReactNativeKookit.types";
2
+ import { ReactNativeKookitModuleEvents, FtpConnectionConfig, FtpFileInfo, SmbConnectionConfig, SmbFileInfo, TtsSynthesizeOptions, TtsSynthesizeResult } from "./ReactNativeKookit.types";
3
3
  declare class ReactNativeKookitModule extends NativeModule<ReactNativeKookitModuleEvents> {
4
4
  PI: number;
5
5
  /**
@@ -141,6 +141,42 @@ declare class ReactNativeKookitModule extends NativeModule<ReactNativeKookitModu
141
141
  }>;
142
142
  count: number;
143
143
  }>;
144
+ /**
145
+ * Copy a file from content:// URI to local cache directory.
146
+ * This method uses Android's ContentResolver to read the file stream,
147
+ * bypassing permission restrictions when receiving content URIs from other apps.
148
+ *
149
+ * @param contentUri The content:// URI received from another app (e.g., via Intent)
150
+ * @param fileName Optional filename for the destination file. If not provided, tries to extract from URI
151
+ * @returns Promise that resolves with the local file path (file://)
152
+ * @platform android
153
+ */
154
+ copyContentUriToCache(contentUri: string, fileName?: string): Promise<{
155
+ localPath: string;
156
+ fileName: string;
157
+ mimeType?: string;
158
+ size?: number;
159
+ }>;
160
+ /**
161
+ * Get metadata about a content:// URI without copying the file.
162
+ *
163
+ * @param contentUri The content:// URI to query
164
+ * @returns Promise that resolves with file metadata
165
+ * @platform android
166
+ */
167
+ getContentUriMetadata(contentUri: string): Promise<{
168
+ displayName?: string;
169
+ mimeType?: string;
170
+ size?: number;
171
+ }>;
172
+ /**
173
+ * Synthesize speech from text and save to audio file.
174
+ * The audio file will be saved in the cache directory under 'tts' folder.
175
+ *
176
+ * @param options TTS synthesis options including text, voiceId, language, pitch, and rate
177
+ * @returns Promise that resolves with file path and duration of the generated audio
178
+ */
179
+ synthesizeSpeech(options: TtsSynthesizeOptions): Promise<TtsSynthesizeResult>;
144
180
  }
145
181
  declare const _default: ReactNativeKookitModule;
146
182
  export default _default;