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 +22 -1
- package/TTS_CHECKLIST.md +256 -0
- package/android/src/main/java/expo/modules/kookit/ReactNativeKookitModule.kt +284 -0
- package/build/ReactNativeKookit.types.d.ts +11 -0
- package/build/ReactNativeKookit.types.d.ts.map +1 -1
- package/build/ReactNativeKookit.types.js.map +1 -1
- package/build/ReactNativeKookitModule.d.ts +37 -1
- package/build/ReactNativeKookitModule.d.ts.map +1 -1
- package/build/ReactNativeKookitModule.js.map +1 -1
- package/ios/ReactNativeKookitModule.swift +143 -0
- package/package.json +1 -1
- package/ANDROID_BUILD_FIX.md +0 -117
- package/ANDROID_FTP_UPDATE.md +0 -161
- package/ANDROID_SETUP.md +0 -188
- package/ANDROID_SMB_LIBRARY_COMPARISON.md +0 -170
- package/ANDROID_SMB_LISTSHARES_FIX.md +0 -100
- package/API_UNIFICATION.md +0 -180
- package/EXPO_PLUGIN_README.md +0 -136
- package/FTP_CLIENT_API.md +0 -301
- package/FTP_FILE_LIST_ENHANCEMENT.md +0 -186
- package/FTP_FILE_OPERATIONS.md +0 -0
- package/FTP_README.md +0 -322
- package/README_NEW.md +0 -143
- package/RELEASE_CHECKLIST.md +0 -115
- package/SMB_CLIENT_STATIC_METHODS.md +0 -175
- package/SMB_COMPLETE_GUIDE.md +0 -308
- package/SMB_HYBRID_IMPLEMENTATION.md +0 -160
- package/SMB_IMPLEMENTATION_SUMMARY.md +0 -229
- package/SMB_USAGE.md +0 -275
- package/TROUBLESHOOTING.md +0 -132
- /package/{ANDROID_PARAMETER_FIX.md → CHANGELOG_CONTENT_URI.md} +0 -0
- /package/{FTP_EXAMPLE_IMPLEMENTATION.md → IMPLEMENTATION_SUMMARY.md} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactNativeKookitModule.d.ts","sourceRoot":"","sources":["../src/ReactNativeKookitModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,EACL,6BAA6B,EAC7B,mBAAmB,EACnB,WAAW,EACX,mBAAmB,EACnB,WAAW,
|
|
1
|
+
{"version":3,"file":"ReactNativeKookitModule.d.ts","sourceRoot":"","sources":["../src/ReactNativeKookitModule.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactNativeKookitModule.js","sourceRoot":"","sources":["../src/ReactNativeKookitModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ReactNativeKookitModule.js","sourceRoot":"","sources":["../src/ReactNativeKookitModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AA+OzD,yDAAyD;AACzD,eAAe,mBAAmB,CAChC,mBAAmB,CACpB,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\n\nimport {\n ReactNativeKookitModuleEvents,\n FtpConnectionConfig,\n FtpFileInfo,\n SmbConnectionConfig,\n SmbFileInfo,\n TtsSynthesizeOptions,\n TtsSynthesizeResult,\n} from \"./ReactNativeKookit.types\";\n\ndeclare class ReactNativeKookitModule extends NativeModule<ReactNativeKookitModuleEvents> {\n PI: number;\n\n /**\n * Returns a hello world string\n */\n hello(): string;\n\n /**\n * Test async function that sends a change event\n */\n setValueAsync(value: string): Promise<void>;\n\n /**\n * Enables volume key interception.\n * On Android, your MainActivity must implement VolumeKeyInterceptActivity interface.\n * On iOS, this works automatically.\n *\n * @throws Error if MainActivity doesn't implement VolumeKeyInterceptActivity on Android\n */\n enableVolumeKeyInterception(): void;\n\n /**\n * Disables volume key interception\n */\n disableVolumeKeyInterception(): void;\n\n // New FTP Client API Methods\n\n /**\n * Create a new FTP client instance\n * @param clientId Unique identifier for the FTP client\n * @returns Promise that resolves with client creation result\n */\n createFtpClient(clientId: string): Promise<{ clientId: string }>;\n\n /**\n * Connect FTP client to server\n * @param clientId FTP client identifier\n * @param config FTP connection configuration\n * @returns Promise that resolves when connected\n */\n ftpClientConnect(\n clientId: string,\n config: FtpConnectionConfig\n ): Promise<void>;\n\n /**\n * Disconnect FTP client from server\n * @param clientId FTP client identifier\n * @returns Promise that resolves when disconnected\n */\n ftpClientDisconnect(clientId: string): Promise<void>;\n\n /**\n * Dispose FTP client and clean up resources\n * @param clientId FTP client identifier\n * @returns Promise that resolves when disposed\n */\n disposeFtpClient(clientId: string): Promise<void>;\n\n /**\n * List files and directories\n * @param clientId FTP client identifier\n * @param path Optional path to list (defaults to current directory)\n * @returns Promise that resolves with array of file information\n */\n ftpClientList(clientId: string, path?: string): Promise<FtpFileInfo[]>;\n\n /**\n * Download a file from FTP server\n * @param clientId FTP client identifier\n * @param remotePath Remote file path on FTP server\n * @param localPath Local file path to save the downloaded file\n * @returns Promise that resolves when download is complete\n */\n ftpClientDownload(\n clientId: string,\n remotePath: string,\n localPath: string\n ): Promise<void>;\n\n /**\n * Upload a file to FTP server\n * @param clientId FTP client identifier\n * @param localPath Local file path to upload\n * @param remotePath Remote file path on FTP server\n * @returns Promise that resolves when upload is complete\n */\n ftpClientUpload(\n clientId: string,\n localPath: string,\n remotePath: string\n ): Promise<void>;\n\n /**\n * Delete a file or directory on FTP server\n * @param clientId FTP client identifier\n * @param remotePath Remote file or directory path to delete\n * @param isDirectory Whether the path is a directory (default: false)\n * @returns Promise that resolves when deletion is complete\n */\n ftpClientDelete(\n clientId: string,\n remotePath: string,\n isDirectory?: boolean\n ): Promise<void>;\n\n /**\n * Create a directory on FTP server\n * @param clientId FTP client identifier\n * @param remotePath Remote directory path to create\n * @returns Promise that resolves when directory is created\n */\n ftpClientCreateDirectory(clientId: string, remotePath: string): Promise<void>;\n\n /**\n * Change current working directory on FTP server\n * @param clientId FTP client identifier\n * @param remotePath Remote directory path to change to\n * @returns Promise that resolves when directory is changed\n */\n ftpClientChangeDirectory(clientId: string, remotePath: string): Promise<void>;\n\n /**\n * Get current working directory on FTP server\n * @param clientId FTP client identifier\n * @returns Promise that resolves with current directory path\n */\n ftpClientGetCurrentDirectory(clientId: string): Promise<string>;\n\n /**\n * Get FTP client status\n * @param clientId FTP client identifier\n * @returns Client status information\n */\n getFtpClientStatus(\n clientId: string\n ): Promise<{ exists: boolean; connected: boolean }>;\n\n /**\n * List all FTP clients\n * @returns Object containing all clients and their status\n */\n listFtpClients(): Promise<{\n clients: Record<string, { connected: boolean }>;\n count: number;\n }>;\n\n // SMB Client API\n\n createSmbClient(clientId: string): Promise<{ clientId: string }>;\n smbClientConnect(\n clientId: string,\n config: SmbConnectionConfig\n ): Promise<void>;\n smbClientConnectShare(clientId: string, shareName: string): Promise<void>;\n smbClientDisconnect(clientId: string): Promise<void>;\n disposeSmbClient(clientId: string): Promise<void>;\n smbClientList(clientId: string, path?: string): Promise<SmbFileInfo[]>;\n smbClientDownload(\n clientId: string,\n remotePath: string,\n localPath: string\n ): Promise<void>;\n smbClientUpload(\n clientId: string,\n localPath: string,\n remotePath: string\n ): Promise<void>;\n smbClientDelete(\n clientId: string,\n remotePath: string,\n isDirectory?: boolean\n ): Promise<void>;\n smbClientCreateDirectory(clientId: string, remotePath: string): Promise<void>;\n getSmbClientStatus(\n clientId: string\n ): Promise<{ exists: boolean; connected: boolean }>;\n listSmbClients(): Promise<{\n clients: Record<string, { connected: boolean }>;\n count: number;\n }>;\n\n /**\n * Copy a file from content:// URI to local cache directory.\n * This method uses Android's ContentResolver to read the file stream,\n * bypassing permission restrictions when receiving content URIs from other apps.\n *\n * @param contentUri The content:// URI received from another app (e.g., via Intent)\n * @param fileName Optional filename for the destination file. If not provided, tries to extract from URI\n * @returns Promise that resolves with the local file path (file://)\n * @platform android\n */\n copyContentUriToCache(\n contentUri: string,\n fileName?: string\n ): Promise<{\n localPath: string;\n fileName: string;\n mimeType?: string;\n size?: number;\n }>;\n\n /**\n * Get metadata about a content:// URI without copying the file.\n *\n * @param contentUri The content:// URI to query\n * @returns Promise that resolves with file metadata\n * @platform android\n */\n getContentUriMetadata(contentUri: string): Promise<{\n displayName?: string;\n mimeType?: string;\n size?: number;\n }>;\n\n /**\n * Synthesize speech from text and save to audio file.\n * The audio file will be saved in the cache directory under 'tts' folder.\n *\n * @param options TTS synthesis options including text, voiceId, language, pitch, and rate\n * @returns Promise that resolves with file path and duration of the generated audio\n */\n synthesizeSpeech(options: TtsSynthesizeOptions): Promise<TtsSynthesizeResult>;\n}\n\n// This call loads the native module object from the JSI.\nexport default requireNativeModule<ReactNativeKookitModule>(\n \"ReactNativeKookit\"\n);\n"]}
|
|
@@ -1022,6 +1022,8 @@ public class ReactNativeKookitModule: Module {
|
|
|
1022
1022
|
private var previousVolume: Float = 0.0
|
|
1023
1023
|
private var ftpClients: [String: FtpClient] = [:] // Store multiple FTP clients by ID
|
|
1024
1024
|
private var smbClients: [String: SmbClient] = [:] // Store multiple SMB clients by ID
|
|
1025
|
+
private var speechSynthesizer: AVSpeechSynthesizer?
|
|
1026
|
+
private var currentSynthesisTask: Task<String, Error>?
|
|
1025
1027
|
|
|
1026
1028
|
// Each module class must implement the definition function. The definition consists of components
|
|
1027
1029
|
// that describes the module's functionality and behavior.
|
|
@@ -1547,6 +1549,47 @@ public class ReactNativeKookitModule: Module {
|
|
|
1547
1549
|
}
|
|
1548
1550
|
}
|
|
1549
1551
|
|
|
1552
|
+
// TTS (Text-to-Speech) Function
|
|
1553
|
+
AsyncFunction("synthesizeSpeech") { (options: [String: Any], promise: Promise) in
|
|
1554
|
+
Task {
|
|
1555
|
+
do {
|
|
1556
|
+
let text = options["text"] as? String ?? ""
|
|
1557
|
+
if text.isEmpty {
|
|
1558
|
+
promise.reject("TTS_ERROR", "Text is required")
|
|
1559
|
+
return
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
let voiceId = options["voiceId"] as? String
|
|
1563
|
+
let language = options["language"] as? String ?? "en-US"
|
|
1564
|
+
let pitch = options["pitch"] as? Double ?? 1.0
|
|
1565
|
+
let rate = options["rate"] as? Double ?? 1.0
|
|
1566
|
+
|
|
1567
|
+
// Initialize synthesizer if needed
|
|
1568
|
+
if self.speechSynthesizer == nil {
|
|
1569
|
+
self.speechSynthesizer = AVSpeechSynthesizer()
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
let filePath = try await self.synthesizeSpeechToFile(
|
|
1573
|
+
text: text,
|
|
1574
|
+
voiceId: voiceId,
|
|
1575
|
+
language: language,
|
|
1576
|
+
pitch: Float(pitch),
|
|
1577
|
+
rate: Float(rate)
|
|
1578
|
+
)
|
|
1579
|
+
|
|
1580
|
+
// Estimate duration
|
|
1581
|
+
let duration = self.estimateDuration(text: text, rate: Float(rate))
|
|
1582
|
+
|
|
1583
|
+
promise.resolve([
|
|
1584
|
+
"filePath": filePath,
|
|
1585
|
+
"duration": duration
|
|
1586
|
+
])
|
|
1587
|
+
} catch {
|
|
1588
|
+
promise.reject("TTS_SYNTHESIS_ERROR", error.localizedDescription)
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1550
1593
|
// Enables the module to be used as a native view. Definition components that are accepted as part of
|
|
1551
1594
|
// the view definition: Prop, Events.
|
|
1552
1595
|
View(ReactNativeKookitView.self) {
|
|
@@ -1640,6 +1683,106 @@ public class ReactNativeKookitModule: Module {
|
|
|
1640
1683
|
}
|
|
1641
1684
|
}
|
|
1642
1685
|
}
|
|
1686
|
+
|
|
1687
|
+
// MARK: - TTS Helper Methods
|
|
1688
|
+
|
|
1689
|
+
private func synthesizeSpeechToFile(text: String, voiceId: String?, language: String, pitch: Float, rate: Float) async throws -> String {
|
|
1690
|
+
return try await withCheckedThrowingContinuation { continuation in
|
|
1691
|
+
guard let synthesizer = self.speechSynthesizer else {
|
|
1692
|
+
continuation.resume(throwing: NSError(domain: "TTS", code: -1, userInfo: [NSLocalizedDescriptionKey: "Speech synthesizer not initialized"]))
|
|
1693
|
+
return
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
let utterance = AVSpeechUtterance(string: text)
|
|
1697
|
+
|
|
1698
|
+
// Set voice
|
|
1699
|
+
if let voiceId = voiceId {
|
|
1700
|
+
utterance.voice = AVSpeechSynthesisVoice(identifier: voiceId)
|
|
1701
|
+
} else {
|
|
1702
|
+
// Try to find voice by language
|
|
1703
|
+
let voices = AVSpeechSynthesisVoice.speechVoices()
|
|
1704
|
+
if let voice = voices.first(where: { $0.language.hasPrefix(language) }) {
|
|
1705
|
+
utterance.voice = voice
|
|
1706
|
+
} else {
|
|
1707
|
+
utterance.voice = AVSpeechSynthesisVoice(language: language)
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
// Set pitch and rate
|
|
1712
|
+
utterance.pitchMultiplier = pitch
|
|
1713
|
+
utterance.rate = rate * AVSpeechUtteranceDefaultSpeechRate
|
|
1714
|
+
|
|
1715
|
+
// Create TTS cache directory
|
|
1716
|
+
let cacheDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
|
1717
|
+
let ttsDir = cacheDir.appendingPathComponent("tts")
|
|
1718
|
+
|
|
1719
|
+
do {
|
|
1720
|
+
try FileManager.default.createDirectory(at: ttsDir, withIntermediateDirectories: true, attributes: nil)
|
|
1721
|
+
} catch {
|
|
1722
|
+
continuation.resume(throwing: error)
|
|
1723
|
+
return
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// Generate unique filename
|
|
1727
|
+
let fileName = "tts_\(UUID().uuidString).caf"
|
|
1728
|
+
let outputURL = ttsDir.appendingPathComponent(fileName)
|
|
1729
|
+
|
|
1730
|
+
// Write utterance to file using AVSpeechSynthesizer.write
|
|
1731
|
+
Task {
|
|
1732
|
+
do {
|
|
1733
|
+
var audioBuffers: [AVAudioPCMBuffer] = []
|
|
1734
|
+
|
|
1735
|
+
synthesizer.write(utterance) { buffer in
|
|
1736
|
+
if let buffer = buffer as? AVAudioPCMBuffer {
|
|
1737
|
+
audioBuffers.append(buffer)
|
|
1738
|
+
} else if buffer == nil {
|
|
1739
|
+
// Writing is complete when buffer is nil
|
|
1740
|
+
Task {
|
|
1741
|
+
do {
|
|
1742
|
+
// Write all buffers to file
|
|
1743
|
+
try self.writeBuffersToFile(buffers: audioBuffers, url: outputURL)
|
|
1744
|
+
continuation.resume(returning: outputURL.path)
|
|
1745
|
+
} catch {
|
|
1746
|
+
continuation.resume(throwing: error)
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
private func writeBuffersToFile(buffers: [AVAudioPCMBuffer], url: URL) throws {
|
|
1757
|
+
guard let firstBuffer = buffers.first else {
|
|
1758
|
+
throw NSError(domain: "TTS", code: -1, userInfo: [NSLocalizedDescriptionKey: "No audio data generated"])
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// Create audio file with settings from the buffer
|
|
1762
|
+
let settings: [String: Any] = [
|
|
1763
|
+
AVFormatIDKey: kAudioFormatLinearPCM,
|
|
1764
|
+
AVSampleRateKey: firstBuffer.format.sampleRate,
|
|
1765
|
+
AVNumberOfChannelsKey: firstBuffer.format.channelCount,
|
|
1766
|
+
AVLinearPCMBitDepthKey: 16,
|
|
1767
|
+
AVLinearPCMIsFloatKey: false,
|
|
1768
|
+
AVLinearPCMIsBigEndianKey: false
|
|
1769
|
+
]
|
|
1770
|
+
|
|
1771
|
+
let audioFile = try AVAudioFile(forWriting: url, settings: settings)
|
|
1772
|
+
|
|
1773
|
+
// Write all buffers to file
|
|
1774
|
+
for buffer in buffers {
|
|
1775
|
+
try audioFile.write(from: buffer)
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
private func estimateDuration(text: String, rate: Float) -> Double {
|
|
1780
|
+
// Average speaking rate is about 150 words per minute
|
|
1781
|
+
let words = text.split(separator: " ").count
|
|
1782
|
+
let baseMinutes = Double(words) / 150.0
|
|
1783
|
+
let durationSeconds = (baseMinutes * 60.0) / Double(rate)
|
|
1784
|
+
return durationSeconds
|
|
1785
|
+
}
|
|
1643
1786
|
}
|
|
1644
1787
|
|
|
1645
1788
|
// Helper class to handle FTP progress callbacks
|
package/package.json
CHANGED
package/ANDROID_BUILD_FIX.md
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
# Android 构建修复总结
|
|
2
|
-
|
|
3
|
-
## 问题诊断与解决
|
|
4
|
-
|
|
5
|
-
### 遇到的问题
|
|
6
|
-
|
|
7
|
-
Android 构建失败,主要错误包括:
|
|
8
|
-
|
|
9
|
-
1. `Unresolved reference 'SMB2CreateDisposition'`
|
|
10
|
-
2. `Unresolved reference 'SMB2ShareAccess'`
|
|
11
|
-
3. `Unresolved reference 'isDirectory'`
|
|
12
|
-
4. `Unresolved reference 'msftyp'`
|
|
13
|
-
5. `FileAttributes.contains()` 方法使用错误
|
|
14
|
-
|
|
15
|
-
### 解决方案
|
|
16
|
-
|
|
17
|
-
#### 1. 修正 SMBJ 库的 import 包名
|
|
18
|
-
|
|
19
|
-
```kotlin
|
|
20
|
-
// 修正前 (错误)
|
|
21
|
-
import com.hierynomus.msfscc.SMB2CreateDisposition
|
|
22
|
-
import com.hierynomus.msfscc.SMB2ShareAccess
|
|
23
|
-
|
|
24
|
-
// 修正后 (正确)
|
|
25
|
-
import com.hierynomus.mssmb2.SMB2CreateDisposition
|
|
26
|
-
import com.hierynomus.mssmb2.SMB2ShareAccess
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
#### 2. 移除不存在的包
|
|
30
|
-
|
|
31
|
-
```kotlin
|
|
32
|
-
// 移除了这个不存在的 import
|
|
33
|
-
import com.hierynomus.msftyp.FileInformationClass
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
#### 3. 修正 FileAttributes 的目录检查方式
|
|
37
|
-
|
|
38
|
-
```kotlin
|
|
39
|
-
// 修正前 (错误)
|
|
40
|
-
val isDir = info.fileAttributes.contains(FileAttributes.FILE_ATTRIBUTE_DIRECTORY)
|
|
41
|
-
|
|
42
|
-
// 修正后 (正确)
|
|
43
|
-
val isDir = info.fileAttributes and FileAttributes.FILE_ATTRIBUTE_DIRECTORY.value != 0L
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### 最终的正确 import 语句
|
|
47
|
-
|
|
48
|
-
```kotlin
|
|
49
|
-
package expo.modules.kookit
|
|
50
|
-
|
|
51
|
-
import com.hierynomus.msdtyp.AccessMask
|
|
52
|
-
import com.hierynomus.msfscc.fileinformation.FileIdBothDirectoryInformation
|
|
53
|
-
import com.hierynomus.msfscc.FileAttributes
|
|
54
|
-
import com.hierynomus.mssmb2.SMB2CreateDisposition
|
|
55
|
-
import com.hierynomus.mssmb2.SMB2ShareAccess
|
|
56
|
-
import com.hierynomus.smbj.SMBClient
|
|
57
|
-
import com.hierynomus.smbj.auth.AuthenticationContext
|
|
58
|
-
import com.hierynomus.smbj.connection.Connection
|
|
59
|
-
import com.hierynomus.smbj.session.Session
|
|
60
|
-
import com.hierynomus.smbj.share.DiskShare
|
|
61
|
-
import com.hierynomus.smbj.share.File
|
|
62
|
-
import kotlinx.coroutines.Dispatchers
|
|
63
|
-
import kotlinx.coroutines.withContext
|
|
64
|
-
import java.io.FileOutputStream
|
|
65
|
-
import java.io.FileInputStream
|
|
66
|
-
import java.io.IOException
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## 构建结果
|
|
70
|
-
|
|
71
|
-
✅ **Android 构建成功**
|
|
72
|
-
|
|
73
|
-
- 构建时间: 2分39秒
|
|
74
|
-
- 任务执行: 37 executed, 301 up-to-date
|
|
75
|
-
- 状态: BUILD SUCCESSFUL
|
|
76
|
-
|
|
77
|
-
## 技术要点
|
|
78
|
-
|
|
79
|
-
### SMBJ 库的正确使用
|
|
80
|
-
|
|
81
|
-
1. **SMB2 协议相关类**: 位于 `com.hierynomus.mssmb2` 包中
|
|
82
|
-
2. **文件系统常量**: 位于 `com.hierynomus.msfscc` 包中
|
|
83
|
-
3. **文件属性检查**: 使用位运算而不是集合的 contains 方法
|
|
84
|
-
|
|
85
|
-
### 目录检查的正确方式
|
|
86
|
-
|
|
87
|
-
```kotlin
|
|
88
|
-
// SMBJ 库中文件属性是使用位标志 (bit flags)
|
|
89
|
-
val isDirectory = fileAttributes and FILE_ATTRIBUTE_DIRECTORY.value != 0L
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## 验证状态
|
|
93
|
-
|
|
94
|
-
### ✅ 已验证的功能
|
|
95
|
-
|
|
96
|
-
- **TypeScript 编译**: 无错误
|
|
97
|
-
- **iOS Swift 编译**: 无错误
|
|
98
|
-
- **Android Kotlin 编译**: 无错误
|
|
99
|
-
- **Android 构建**: 成功
|
|
100
|
-
- **依赖管理**: SMBJ 0.13.0 + SLF4J-nop 2.0.13
|
|
101
|
-
|
|
102
|
-
### 🔄 待验证的功能
|
|
103
|
-
|
|
104
|
-
- iOS 构建 (需要 Xcode 环境)
|
|
105
|
-
- 实际 SMB 服务器连接测试
|
|
106
|
-
- 跨平台功能一致性验证
|
|
107
|
-
|
|
108
|
-
## 下一步
|
|
109
|
-
|
|
110
|
-
1. **iOS 构建验证**: 在 Xcode 中构建 iOS 项目
|
|
111
|
-
2. **功能测试**: 连接真实的 SMB 服务器进行功能测试
|
|
112
|
-
3. **性能测试**: 大文件传输和进度事件验证
|
|
113
|
-
4. **错误处理测试**: 网络异常和认证失败场景
|
|
114
|
-
|
|
115
|
-
## 总结
|
|
116
|
-
|
|
117
|
-
Android SMB 实现已经完全修复并可以正常构建。所有的 SMBJ 库集成问题都已解决,代码符合 SMBJ 库的 API 规范。整个 SMB 客户端现在可以在 Android 平台上正常工作。
|
package/ANDROID_FTP_UPDATE.md
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
# Android FTP Client Implementation Update
|
|
2
|
-
|
|
3
|
-
This document summarizes the updates made to the Android implementation to support the new object-oriented FTP client API.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
The Android implementation has been updated to support multiple FTP client instances, matching the iOS implementation and providing a consistent cross-platform API.
|
|
8
|
-
|
|
9
|
-
## Key Changes
|
|
10
|
-
|
|
11
|
-
### 1. ReactNativeKookitModule.kt Updates
|
|
12
|
-
|
|
13
|
-
#### New Client Management
|
|
14
|
-
|
|
15
|
-
- Added `ftpClients` HashMap to manage multiple FTP client instances
|
|
16
|
-
- Each client is identified by a unique `clientId` string
|
|
17
|
-
- Clients can be created, used, and disposed independently
|
|
18
|
-
|
|
19
|
-
#### New Native Methods
|
|
20
|
-
|
|
21
|
-
**Client Lifecycle:**
|
|
22
|
-
|
|
23
|
-
- `createFtpClient(clientId: String)` - Create a new FTP client instance
|
|
24
|
-
- `disposeFtpClient(clientId: String)` - Dispose client and clean up resources
|
|
25
|
-
- `getFtpClientStatus(clientId: String)` - Get client status (exists, connected)
|
|
26
|
-
- `listFtpClients()` - List all active clients with their status
|
|
27
|
-
|
|
28
|
-
**Connection Management:**
|
|
29
|
-
|
|
30
|
-
- `ftpClientConnect(clientId: String, config: Map<String, Any>)` - Connect specific client
|
|
31
|
-
- `ftpClientDisconnect(clientId: String)` - Disconnect specific client
|
|
32
|
-
|
|
33
|
-
**File Operations (all require clientId):**
|
|
34
|
-
|
|
35
|
-
- `ftpClientList(clientId: String, path: String?)` - List files/directories
|
|
36
|
-
- `ftpClientDownload(clientId: String, remotePath: String, localPath: String)` - Download file
|
|
37
|
-
- `ftpClientUpload(clientId: String, localPath: String, remotePath: String)` - Upload file
|
|
38
|
-
- `ftpClientDelete(clientId: String, remotePath: String, isDirectory: Boolean?)` - Delete file/directory
|
|
39
|
-
- `ftpClientCreateDirectory(clientId: String, remotePath: String)` - Create directory
|
|
40
|
-
- `ftpClientChangeDirectory(clientId: String, remotePath: String)` - Change directory
|
|
41
|
-
- `ftpClientGetCurrentDirectory(clientId: String)` - Get current directory
|
|
42
|
-
|
|
43
|
-
#### Event System Updates
|
|
44
|
-
|
|
45
|
-
All FTP events now include `clientId` to identify which client triggered the event:
|
|
46
|
-
|
|
47
|
-
- `onFtpProgress` - Includes `clientId`, `transferred`, `total`, `percentage`
|
|
48
|
-
- `onFtpComplete` - Includes `clientId`
|
|
49
|
-
- `onFtpError` - Includes `clientId`, `error`
|
|
50
|
-
|
|
51
|
-
### 2. FtpClient.kt Updates
|
|
52
|
-
|
|
53
|
-
#### New Methods
|
|
54
|
-
|
|
55
|
-
- `isConnected(): Boolean` - Public method to check connection status
|
|
56
|
-
- Enhanced connection state management
|
|
57
|
-
|
|
58
|
-
#### Existing Features Maintained
|
|
59
|
-
|
|
60
|
-
- UTF-8 encoding support for Chinese characters
|
|
61
|
-
- Passive mode FTP connections
|
|
62
|
-
- Progress callbacks for upload/download operations
|
|
63
|
-
- Comprehensive error handling
|
|
64
|
-
- Multi-line FTP response parsing
|
|
65
|
-
|
|
66
|
-
## Architecture Benefits
|
|
67
|
-
|
|
68
|
-
### Resource Management
|
|
69
|
-
|
|
70
|
-
- Each client manages its own connection independently
|
|
71
|
-
- Proper cleanup when clients are disposed
|
|
72
|
-
- No interference between multiple concurrent connections
|
|
73
|
-
|
|
74
|
-
### Scalability
|
|
75
|
-
|
|
76
|
-
- Support for multiple FTP servers simultaneously
|
|
77
|
-
- Each client can connect to different servers
|
|
78
|
-
- Independent authentication and configuration per client
|
|
79
|
-
|
|
80
|
-
### Error Isolation
|
|
81
|
-
|
|
82
|
-
- Errors in one client don't affect others
|
|
83
|
-
- Per-client event handling
|
|
84
|
-
- Individual client status tracking
|
|
85
|
-
|
|
86
|
-
## Usage Pattern
|
|
87
|
-
|
|
88
|
-
### Android Native Layer
|
|
89
|
-
|
|
90
|
-
```kotlin
|
|
91
|
-
// Create client
|
|
92
|
-
createFtpClient("client1")
|
|
93
|
-
|
|
94
|
-
// Connect
|
|
95
|
-
ftpClientConnect("client1", config)
|
|
96
|
-
|
|
97
|
-
// Use client
|
|
98
|
-
ftpClientList("client1", "/")
|
|
99
|
-
ftpClientDownload("client1", "/remote/file.txt", "/local/file.txt")
|
|
100
|
-
|
|
101
|
-
// Clean up
|
|
102
|
-
disposeFtpClient("client1")
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Event Handling
|
|
106
|
-
|
|
107
|
-
```kotlin
|
|
108
|
-
// Events now include clientId for identification
|
|
109
|
-
sendEvent("onFtpProgress", mapOf(
|
|
110
|
-
"clientId" to clientId,
|
|
111
|
-
"transferred" to transferred,
|
|
112
|
-
"total" to total,
|
|
113
|
-
"percentage" to percentage
|
|
114
|
-
))
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## Backward Compatibility
|
|
118
|
-
|
|
119
|
-
The old global FTP methods (`ftpConnect`, `ftpDisconnect`, etc.) are maintained for backward compatibility, but the new client-based API is recommended for new development.
|
|
120
|
-
|
|
121
|
-
## Error Handling
|
|
122
|
-
|
|
123
|
-
All new methods include comprehensive error handling:
|
|
124
|
-
|
|
125
|
-
- Client not found errors when invalid `clientId` is provided
|
|
126
|
-
- Connection state validation before operations
|
|
127
|
-
- Proper exception propagation to JavaScript layer
|
|
128
|
-
- Resource cleanup on errors
|
|
129
|
-
|
|
130
|
-
## Testing
|
|
131
|
-
|
|
132
|
-
The implementation has been tested with:
|
|
133
|
-
|
|
134
|
-
- ✅ TypeScript compilation (0 errors)
|
|
135
|
-
- ✅ Multiple concurrent clients
|
|
136
|
-
- ✅ Client lifecycle management
|
|
137
|
-
- ✅ Event system with client identification
|
|
138
|
-
- ✅ Error handling and resource cleanup
|
|
139
|
-
|
|
140
|
-
## Migration from Old API
|
|
141
|
-
|
|
142
|
-
### Old Android Pattern:
|
|
143
|
-
|
|
144
|
-
```kotlin
|
|
145
|
-
ftpConnect(config)
|
|
146
|
-
ftpList(path)
|
|
147
|
-
ftpDownload(remotePath, localPath)
|
|
148
|
-
ftpDisconnect()
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### New Android Pattern:
|
|
152
|
-
|
|
153
|
-
```kotlin
|
|
154
|
-
createFtpClient(clientId)
|
|
155
|
-
ftpClientConnect(clientId, config)
|
|
156
|
-
ftpClientList(clientId, path)
|
|
157
|
-
ftpClientDownload(clientId, remotePath, localPath)
|
|
158
|
-
disposeFtpClient(clientId)
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
The new API provides better resource management, supports multiple concurrent connections, and maintains consistency with the iOS implementation.
|
package/ANDROID_SETUP.md
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
# Android Setup Guide for react-native-kookit
|
|
2
|
-
|
|
3
|
-
This guide will help you properly set up volume key interception on Android.
|
|
4
|
-
|
|
5
|
-
## Why is this needed?
|
|
6
|
-
|
|
7
|
-
Unlike iOS, Android requires manual implementation in your MainActivity to intercept volume key events. This is because Android's key event handling is managed at the Activity level.
|
|
8
|
-
|
|
9
|
-
## Step-by-Step Setup
|
|
10
|
-
|
|
11
|
-
### 1. Locate your MainActivity file
|
|
12
|
-
|
|
13
|
-
Your MainActivity should be located at:
|
|
14
|
-
|
|
15
|
-
- `android/app/src/main/java/[your/package/path]/MainActivity.kt` (Kotlin)
|
|
16
|
-
- `android/app/src/main/java/[your/package/path]/MainActivity.java` (Java)
|
|
17
|
-
|
|
18
|
-
### 2. Update your MainActivity
|
|
19
|
-
|
|
20
|
-
Choose the appropriate implementation based on your language:
|
|
21
|
-
|
|
22
|
-
#### Kotlin Implementation
|
|
23
|
-
|
|
24
|
-
```kotlin
|
|
25
|
-
package com.yourapp.yourpackage // Replace with your actual package
|
|
26
|
-
|
|
27
|
-
import android.os.Build
|
|
28
|
-
import android.os.Bundle
|
|
29
|
-
import android.view.KeyEvent
|
|
30
|
-
|
|
31
|
-
import com.facebook.react.ReactActivity
|
|
32
|
-
import com.facebook.react.ReactActivityDelegate
|
|
33
|
-
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
|
|
34
|
-
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
|
35
|
-
|
|
36
|
-
import expo.modules.ReactActivityDelegateWrapper
|
|
37
|
-
import expo.modules.kookit.VolumeKeyInterceptActivity
|
|
38
|
-
import expo.modules.kookit.handleVolumeKeyEvent
|
|
39
|
-
|
|
40
|
-
class MainActivity : ReactActivity(), VolumeKeyInterceptActivity {
|
|
41
|
-
private var volumeKeyListener: ((Int) -> Unit)? = null
|
|
42
|
-
private var isVolumeKeyInterceptEnabled = false
|
|
43
|
-
|
|
44
|
-
override fun getMainComponentName(): String = "main"
|
|
45
|
-
|
|
46
|
-
override fun createReactActivityDelegate(): ReactActivityDelegate {
|
|
47
|
-
return ReactActivityDelegateWrapper(
|
|
48
|
-
this,
|
|
49
|
-
BuildConfig.IS_NEW_ARCHITECTURE_ENABLED,
|
|
50
|
-
object : DefaultReactActivityDelegate(
|
|
51
|
-
this,
|
|
52
|
-
mainComponentName,
|
|
53
|
-
fabricEnabled
|
|
54
|
-
){})
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Volume key interception implementation - REQUIRED
|
|
58
|
-
override fun setVolumeKeyListener(listener: ((Int) -> Unit)?) {
|
|
59
|
-
volumeKeyListener = listener
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
override fun setVolumeKeyInterceptEnabled(enabled: Boolean) {
|
|
63
|
-
isVolumeKeyInterceptEnabled = enabled
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
|
67
|
-
if (isVolumeKeyInterceptEnabled && handleVolumeKeyEvent(event)) {
|
|
68
|
-
return true
|
|
69
|
-
}
|
|
70
|
-
return super.dispatchKeyEvent(event)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
#### Java Implementation
|
|
76
|
-
|
|
77
|
-
```java
|
|
78
|
-
package com.yourapp.yourpackage; // Replace with your actual package
|
|
79
|
-
|
|
80
|
-
import android.view.KeyEvent;
|
|
81
|
-
|
|
82
|
-
import com.facebook.react.ReactActivity;
|
|
83
|
-
import com.facebook.react.ReactActivityDelegate;
|
|
84
|
-
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
|
|
85
|
-
import com.facebook.react.defaults.DefaultReactActivityDelegate;
|
|
86
|
-
|
|
87
|
-
import expo.modules.ReactActivityDelegateWrapper;
|
|
88
|
-
import expo.modules.kookit.VolumeKeyInterceptActivity;
|
|
89
|
-
import expo.modules.kookit.VolumeKeyInterceptActivityKt;
|
|
90
|
-
import kotlin.Unit;
|
|
91
|
-
import kotlin.jvm.functions.Function1;
|
|
92
|
-
|
|
93
|
-
public class MainActivity extends ReactActivity implements VolumeKeyInterceptActivity {
|
|
94
|
-
private Function1<Integer, Unit> volumeKeyListener;
|
|
95
|
-
private boolean isVolumeKeyInterceptEnabled = false;
|
|
96
|
-
|
|
97
|
-
@Override
|
|
98
|
-
protected String getMainComponentName() {
|
|
99
|
-
return "main";
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
@Override
|
|
103
|
-
protected ReactActivityDelegate createReactActivityDelegate() {
|
|
104
|
-
return new ReactActivityDelegateWrapper(
|
|
105
|
-
this,
|
|
106
|
-
BuildConfig.IS_NEW_ARCHITECTURE_ENABLED,
|
|
107
|
-
new DefaultReactActivityDelegate(this, getMainComponentName(), DefaultNewArchitectureEntryPoint.getFabricEnabled())
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Volume key interception implementation - REQUIRED
|
|
112
|
-
@Override
|
|
113
|
-
public void setVolumeKeyListener(Function1<Integer, Unit> listener) {
|
|
114
|
-
this.volumeKeyListener = listener;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
@Override
|
|
118
|
-
public void setVolumeKeyInterceptEnabled(boolean enabled) {
|
|
119
|
-
this.isVolumeKeyInterceptEnabled = enabled;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
@Override
|
|
123
|
-
public boolean dispatchKeyEvent(KeyEvent event) {
|
|
124
|
-
if (isVolumeKeyInterceptEnabled && VolumeKeyInterceptActivityKt.handleVolumeKeyEvent(this, event)) {
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
return super.dispatchKeyEvent(event);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## Key Points
|
|
133
|
-
|
|
134
|
-
1. **Package Name**: Make sure to replace `com.yourapp.yourpackage` with your actual package name
|
|
135
|
-
2. **Interface Implementation**: Your MainActivity MUST implement `VolumeKeyInterceptActivity`
|
|
136
|
-
3. **Required Methods**: You must implement all three methods:
|
|
137
|
-
- `setVolumeKeyListener`
|
|
138
|
-
- `setVolumeKeyInterceptEnabled`
|
|
139
|
-
- `dispatchKeyEvent` (override)
|
|
140
|
-
4. **Import Statements**: Make sure all import statements are correct
|
|
141
|
-
|
|
142
|
-
## Common Issues
|
|
143
|
-
|
|
144
|
-
### "Unresolved reference" errors
|
|
145
|
-
|
|
146
|
-
Make sure you have these imports:
|
|
147
|
-
|
|
148
|
-
```kotlin
|
|
149
|
-
import expo.modules.kookit.VolumeKeyInterceptActivity
|
|
150
|
-
import expo.modules.kookit.handleVolumeKeyEvent
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### Volume keys not working
|
|
154
|
-
|
|
155
|
-
1. Check that your MainActivity implements `VolumeKeyInterceptActivity`
|
|
156
|
-
2. Verify that `dispatchKeyEvent` is properly overridden
|
|
157
|
-
3. Make sure you're calling `enableVolumeKeyInterception()` from JavaScript
|
|
158
|
-
|
|
159
|
-
### Build errors
|
|
160
|
-
|
|
161
|
-
1. Clean and rebuild your project: `cd android && ./gradlew clean && cd .. && npx react-native run-android`
|
|
162
|
-
2. Make sure your package name in MainActivity matches your app's package structure
|
|
163
|
-
|
|
164
|
-
## Testing
|
|
165
|
-
|
|
166
|
-
After setup, test with this JavaScript code:
|
|
167
|
-
|
|
168
|
-
```javascript
|
|
169
|
-
import ReactNativeKookit from "react-native-kookit";
|
|
170
|
-
|
|
171
|
-
// Add listener
|
|
172
|
-
const subscription = ReactNativeKookit.addListener(
|
|
173
|
-
"onVolumeButtonPressed",
|
|
174
|
-
(event) => {
|
|
175
|
-
console.log("Volume button pressed:", event.key);
|
|
176
|
-
}
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
// Enable interception
|
|
180
|
-
ReactNativeKookit.enableVolumeKeyInterception();
|
|
181
|
-
|
|
182
|
-
// Test by pressing volume up/down buttons
|
|
183
|
-
// You should see logs in Metro console
|
|
184
|
-
|
|
185
|
-
// Clean up when done
|
|
186
|
-
subscription.remove();
|
|
187
|
-
ReactNativeKookit.disableVolumeKeyInterception();
|
|
188
|
-
```
|