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,170 +0,0 @@
|
|
|
1
|
-
# Android SMB库对比分析与推荐
|
|
2
|
-
|
|
3
|
-
## 当前使用的库:SMBJ
|
|
4
|
-
|
|
5
|
-
### 优点
|
|
6
|
-
|
|
7
|
-
- ✅ 轻量级,专注SMB2/3协议
|
|
8
|
-
- ✅ 现代化设计,良好的性能
|
|
9
|
-
- ✅ 活跃维护,最新版本0.13.0 (2023)
|
|
10
|
-
- ✅ 支持SMB2/3的高级功能
|
|
11
|
-
|
|
12
|
-
### 缺点
|
|
13
|
-
|
|
14
|
-
- ❌ **不支持share枚举** - 这是我们当前的问题
|
|
15
|
-
- ❌ 没有NetShareEnum API
|
|
16
|
-
- ❌ 需要手动测试常见share名称
|
|
17
|
-
|
|
18
|
-
## 备选方案1:JCIFS (CodeLibs版本)
|
|
19
|
-
|
|
20
|
-
### 基本信息
|
|
21
|
-
|
|
22
|
-
```gradle
|
|
23
|
-
implementation 'org.codelibs:jcifs:2.1.39'
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### 优点
|
|
27
|
-
|
|
28
|
-
- ✅ **完整的SMB协议支持** (SMB1/2/3)
|
|
29
|
-
- ✅ **支持网络浏览** - 可能包括share枚举
|
|
30
|
-
- ✅ 成熟的库,有长期历史
|
|
31
|
-
- ✅ 支持Java 17+
|
|
32
|
-
- ✅ 现代化API设计
|
|
33
|
-
|
|
34
|
-
### 缺点
|
|
35
|
-
|
|
36
|
-
- ❌ 体积较大
|
|
37
|
-
- ❌ 需要额外学习新API
|
|
38
|
-
- ❌ 依赖Bouncy Castle加密库
|
|
39
|
-
|
|
40
|
-
### Share枚举支持
|
|
41
|
-
|
|
42
|
-
基于文档分析,JCIFS应该支持类似这样的用法:
|
|
43
|
-
|
|
44
|
-
```java
|
|
45
|
-
// 可能的API (需要验证)
|
|
46
|
-
SmbFile server = new SmbFile("smb://192.168.1.100/", context);
|
|
47
|
-
SmbFile[] shares = server.listFiles(); // 列出所有shares
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## 备选方案2:JCIFS-NG (AgNO3版本)
|
|
51
|
-
|
|
52
|
-
### 基本信息
|
|
53
|
-
|
|
54
|
-
```gradle
|
|
55
|
-
implementation 'eu.agno3.jcifs:jcifs-ng:2.1.9'
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### 优点
|
|
59
|
-
|
|
60
|
-
- ✅ **支持SMB2支持** (2.02协议级别)
|
|
61
|
-
- ✅ 基于原始JCIFS但经过清理和改进
|
|
62
|
-
- ✅ 移除了全局状态,线程安全
|
|
63
|
-
|
|
64
|
-
### 缺点
|
|
65
|
-
|
|
66
|
-
- ❌ **明确弃用了server browsing功能** (2.1版本)
|
|
67
|
-
- ❌ 不再支持share枚举
|
|
68
|
-
- ❌ API与原始JCIFS有差异
|
|
69
|
-
|
|
70
|
-
## 备选方案3:SMB4J
|
|
71
|
-
|
|
72
|
-
### 基本信息
|
|
73
|
-
|
|
74
|
-
```gradle
|
|
75
|
-
implementation 'com.github.smb4j:smb4j:1.0.0'
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### 优点
|
|
79
|
-
|
|
80
|
-
- ✅ 现代化设计
|
|
81
|
-
- ✅ 支持SMB2/3
|
|
82
|
-
|
|
83
|
-
### 缺点
|
|
84
|
-
|
|
85
|
-
- ❌ 相对较新,生态不够成熟
|
|
86
|
-
- ❌ 文档不够完整
|
|
87
|
-
|
|
88
|
-
## 推荐方案:尝试JCIFS (CodeLibs)
|
|
89
|
-
|
|
90
|
-
### 理由
|
|
91
|
-
|
|
92
|
-
1. **完整功能支持** - 作为最全面的SMB库,很可能支持share枚举
|
|
93
|
-
2. **现代维护** - CodeLibs版本是维护最积极的JCIFS分支
|
|
94
|
-
3. **向后兼容** - 支持所有SMB版本,兼容性最好
|
|
95
|
-
|
|
96
|
-
### 实施建议
|
|
97
|
-
|
|
98
|
-
#### 第一步:添加依赖测试
|
|
99
|
-
|
|
100
|
-
```gradle
|
|
101
|
-
dependencies {
|
|
102
|
-
// 当前SMBJ
|
|
103
|
-
implementation 'com.hierynomus:smbj:0.13.0'
|
|
104
|
-
|
|
105
|
-
// 添加JCIFS测试
|
|
106
|
-
implementation 'org.codelibs:jcifs:2.1.39'
|
|
107
|
-
implementation 'org.slf4j:slf4j-nop:2.0.13'
|
|
108
|
-
}
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
#### 第二步:创建JCIFS实现的listShares
|
|
112
|
-
|
|
113
|
-
```kotlin
|
|
114
|
-
import org.codelibs.jcifs.smb.CIFSContext
|
|
115
|
-
import org.codelibs.jcifs.smb.context.SingletonContext
|
|
116
|
-
import org.codelibs.jcifs.smb.impl.SmbFile
|
|
117
|
-
import org.codelibs.jcifs.smb.impl.NtlmPasswordAuthenticator
|
|
118
|
-
|
|
119
|
-
class JcifsShareEnumerator {
|
|
120
|
-
fun listShares(host: String, username: String, password: String, domain: String? = null): List<String> {
|
|
121
|
-
val context = SingletonContext.getInstance()
|
|
122
|
-
val auth = NtlmPasswordAuthenticator(domain, username, password)
|
|
123
|
-
val authContext = context.withCredentials(auth)
|
|
124
|
-
|
|
125
|
-
return try {
|
|
126
|
-
val serverUrl = "smb://$host/"
|
|
127
|
-
val server = SmbFile(serverUrl, authContext)
|
|
128
|
-
|
|
129
|
-
server.listFiles()?.map { share ->
|
|
130
|
-
share.name.removeSuffix("/")
|
|
131
|
-
} ?: emptyList()
|
|
132
|
-
} catch (e: Exception) {
|
|
133
|
-
emptyList()
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
#### 第三步:集成到现有API
|
|
140
|
-
|
|
141
|
-
```kotlin
|
|
142
|
-
// 在SmbClient.kt中添加
|
|
143
|
-
@Synchronized
|
|
144
|
-
fun listSharesJcifs(): List<String> {
|
|
145
|
-
// 使用JCIFS实现
|
|
146
|
-
return JcifsShareEnumerator().listShares(host, username, password, domain)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
@Synchronized
|
|
150
|
-
fun listShares(): List<String> {
|
|
151
|
-
// 首先尝试JCIFS方法
|
|
152
|
-
val jcifsShares = listSharesJcifs()
|
|
153
|
-
if (jcifsShares.isNotEmpty()) {
|
|
154
|
-
return jcifsShares
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// 如果失败,回退到当前的测试方法
|
|
158
|
-
return listSharesViaTesting()
|
|
159
|
-
}
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
## 总结
|
|
163
|
-
|
|
164
|
-
**推荐采用JCIFS (CodeLibs版本)** 作为share枚举的补充方案:
|
|
165
|
-
|
|
166
|
-
1. **混合策略** - 保留SMBJ作为主要SMB客户端,仅使用JCIFS进行share枚举
|
|
167
|
-
2. **向后兼容** - 如果JCIFS枚举失败,回退到当前的测试方法
|
|
168
|
-
3. **最小改动** - 只需要添加一个枚举方法,不需要重写整个SMB客户端
|
|
169
|
-
|
|
170
|
-
这样既能解决share枚举问题,又能保持现有代码的稳定性。
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# Android SMB listShares() 修复说明
|
|
2
|
-
|
|
3
|
-
## 问题描述
|
|
4
|
-
|
|
5
|
-
Android构建失败,错误信息:
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
e: Unresolved reference 'listShares'
|
|
9
|
-
e: Unresolved reference 'name'
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## 根本原因
|
|
13
|
-
|
|
14
|
-
在Android SmbClient.kt中,我尝试使用了SMBJ库中不存在的API:
|
|
15
|
-
|
|
16
|
-
```kotlin
|
|
17
|
-
s.listShares().map { it.name } // ❌ SMBJ Session没有listShares()方法
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## 解决方案
|
|
21
|
-
|
|
22
|
-
由于SMBJ库不提供直接的share枚举API,我实现了一个实用的替代方案:
|
|
23
|
-
|
|
24
|
-
```kotlin
|
|
25
|
-
@Synchronized
|
|
26
|
-
fun listShares(): List<String> {
|
|
27
|
-
val s = session ?: throw IOException("Not authenticated")
|
|
28
|
-
return try {
|
|
29
|
-
// SMBJ doesn't provide direct share enumeration API
|
|
30
|
-
// We implement a practical approach by testing common share names
|
|
31
|
-
val availableShares = mutableListOf<String>()
|
|
32
|
-
|
|
33
|
-
// Common Windows shares
|
|
34
|
-
val windowsShares = listOf("C$", "D$", "E$", "ADMIN$", "IPC$")
|
|
35
|
-
// Common user-defined shares
|
|
36
|
-
val commonShares = listOf("shared", "public", "data", "home", "Users", "files", "documents", "media")
|
|
37
|
-
|
|
38
|
-
val allTestShares = windowsShares + commonShares
|
|
39
|
-
|
|
40
|
-
for (shareName in allTestShares) {
|
|
41
|
-
try {
|
|
42
|
-
// Try to connect briefly to test if share exists
|
|
43
|
-
val testShare = s.connectShare(shareName)
|
|
44
|
-
testShare.close()
|
|
45
|
-
availableShares.add(shareName)
|
|
46
|
-
} catch (e: Exception) {
|
|
47
|
-
// Share doesn't exist, access denied, or other error - ignore
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// If no shares found, suggest some common ones
|
|
52
|
-
if (availableShares.isEmpty()) {
|
|
53
|
-
return listOf("C$", "shared", "public")
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
availableShares
|
|
57
|
-
} catch (e: Exception) {
|
|
58
|
-
// Fallback to common share names if enumeration fails
|
|
59
|
-
listOf("C$", "shared", "public", "data")
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## 实现逻辑
|
|
65
|
-
|
|
66
|
-
1. **测试常见shares**: 尝试连接到常见的Windows系统shares(C$、D$等)和用户定义的shares(shared、public等)
|
|
67
|
-
|
|
68
|
-
2. **快速连接测试**: 对每个share进行快速连接测试,如果成功则添加到可用列表
|
|
69
|
-
|
|
70
|
-
3. **优雅降级**: 如果无法枚举任何share,返回常见的share名称作为建议
|
|
71
|
-
|
|
72
|
-
4. **错误处理**: 如果整个过程失败,返回基本的备用列表
|
|
73
|
-
|
|
74
|
-
## 优势
|
|
75
|
-
|
|
76
|
-
- ✅ **兼容性**: 适用于SMBJ库的限制
|
|
77
|
-
- ✅ **实用性**: 能发现大多数常见的SMB shares
|
|
78
|
-
- ✅ **健壮性**: 有多层备用机制
|
|
79
|
-
- ✅ **性能**: 只测试常见shares,避免大量无效尝试
|
|
80
|
-
|
|
81
|
-
## 限制
|
|
82
|
-
|
|
83
|
-
- 无法发现自定义名称的shares(除非在测试列表中)
|
|
84
|
-
- 依赖于连接测试,可能受权限限制影响
|
|
85
|
-
- 不如真正的RPC share枚举完整
|
|
86
|
-
|
|
87
|
-
## 构建验证
|
|
88
|
-
|
|
89
|
-
修复后Android构建成功:
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
./gradlew app:assembleDebug -x lint -x test
|
|
93
|
-
BUILD SUCCESSFUL in 10s
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## 相关文件
|
|
97
|
-
|
|
98
|
-
- `android/src/main/java/expo/modules/kookit/SmbClient.kt` - 修复的Android实现
|
|
99
|
-
- `ios/ReactNativeKookitModule.swift` - iOS使用SMBClient库的真正listShares API
|
|
100
|
-
- `src/SmbClient.ts` - TypeScript接口保持不变
|
package/API_UNIFICATION.md
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
# iOS与Android API接口统一更新
|
|
2
|
-
|
|
3
|
-
本文档记录了为统一iOS和Android FTP客户端API接口命名所做的更改。
|
|
4
|
-
|
|
5
|
-
## 问题描述
|
|
6
|
-
|
|
7
|
-
之前iOS和Android的FTP API方法命名不一致:
|
|
8
|
-
|
|
9
|
-
### 修改前的命名差异
|
|
10
|
-
|
|
11
|
-
| 功能 | iOS方法名 | Android方法名 |
|
|
12
|
-
| -------------- | --------------------------- | ------------------------------ |
|
|
13
|
-
| 连接 | `ftpConnect` | `ftpClientConnect` |
|
|
14
|
-
| 断开连接 | `ftpDisconnect` | `ftpClientDisconnect` |
|
|
15
|
-
| 文件列表 | `ftpList` | `ftpClientList` |
|
|
16
|
-
| 下载文件 | `ftpDownload` | `ftpClientDownload` |
|
|
17
|
-
| 上传文件 | `ftpUpload` | `ftpClientUpload` |
|
|
18
|
-
| 删除文件 | `ftpDelete` | `ftpClientDelete` |
|
|
19
|
-
| 创建目录 | `ftpCreateDirectory` | `ftpClientCreateDirectory` |
|
|
20
|
-
| 切换目录 | `ftpChangeDirectory` | `ftpClientChangeDirectory` |
|
|
21
|
-
| 获取当前目录 | `ftpGetCurrentDirectory` | `ftpClientGetCurrentDirectory` |
|
|
22
|
-
| 获取客户端状态 | `getFtpClientStatus` (同步) | `getFtpClientStatus` (异步) |
|
|
23
|
-
| 列出所有客户端 | `listFtpClients` (同步) | `listFtpClients` (异步) |
|
|
24
|
-
|
|
25
|
-
## 解决方案
|
|
26
|
-
|
|
27
|
-
统一使用`ftpClient*`命名模式,并确保所有方法都是异步的。
|
|
28
|
-
|
|
29
|
-
### 修改后的统一命名
|
|
30
|
-
|
|
31
|
-
| 功能 | 统一方法名 | 参数 |
|
|
32
|
-
| -------------- | ------------------------------ | --------------------------------------------------------------- |
|
|
33
|
-
| 创建客户端 | `createFtpClient` | `(clientId: String)` |
|
|
34
|
-
| 释放客户端 | `disposeFtpClient` | `(clientId: String)` |
|
|
35
|
-
| 连接 | `ftpClientConnect` | `(clientId: String, config: Object)` |
|
|
36
|
-
| 断开连接 | `ftpClientDisconnect` | `(clientId: String)` |
|
|
37
|
-
| 文件列表 | `ftpClientList` | `(clientId: String, path?: String)` |
|
|
38
|
-
| 下载文件 | `ftpClientDownload` | `(clientId: String, remotePath: String, localPath: String)` |
|
|
39
|
-
| 上传文件 | `ftpClientUpload` | `(clientId: String, localPath: String, remotePath: String)` |
|
|
40
|
-
| 删除文件 | `ftpClientDelete` | `(clientId: String, remotePath: String, isDirectory?: Boolean)` |
|
|
41
|
-
| 创建目录 | `ftpClientCreateDirectory` | `(clientId: String, remotePath: String)` |
|
|
42
|
-
| 切换目录 | `ftpClientChangeDirectory` | `(clientId: String, remotePath: String)` |
|
|
43
|
-
| 获取当前目录 | `ftpClientGetCurrentDirectory` | `(clientId: String)` |
|
|
44
|
-
| 获取客户端状态 | `getFtpClientStatus` | `(clientId: String)` |
|
|
45
|
-
| 列出所有客户端 | `listFtpClients` | `()` |
|
|
46
|
-
|
|
47
|
-
## iOS代码更改详情
|
|
48
|
-
|
|
49
|
-
### 主要FTP操作方法
|
|
50
|
-
|
|
51
|
-
```swift
|
|
52
|
-
// 连接
|
|
53
|
-
AsyncFunction("ftpClientConnect") { (clientId: String, config: [String: Any], promise: Promise) in
|
|
54
|
-
|
|
55
|
-
// 断开连接
|
|
56
|
-
AsyncFunction("ftpClientDisconnect") { (clientId: String, promise: Promise) in
|
|
57
|
-
|
|
58
|
-
// 文件列表
|
|
59
|
-
AsyncFunction("ftpClientList") { (clientId: String, path: String?, promise: Promise) in
|
|
60
|
-
|
|
61
|
-
// 下载文件
|
|
62
|
-
AsyncFunction("ftpClientDownload") { (clientId: String, remotePath: String, localPath: String, promise: Promise) in
|
|
63
|
-
|
|
64
|
-
// 上传文件
|
|
65
|
-
AsyncFunction("ftpClientUpload") { (clientId: String, localPath: String, remotePath: String, promise: Promise) in
|
|
66
|
-
|
|
67
|
-
// 删除文件
|
|
68
|
-
AsyncFunction("ftpClientDelete") { (clientId: String, remotePath: String, isDirectory: Bool?, promise: Promise) in
|
|
69
|
-
|
|
70
|
-
// 创建目录
|
|
71
|
-
AsyncFunction("ftpClientCreateDirectory") { (clientId: String, remotePath: String, promise: Promise) in
|
|
72
|
-
|
|
73
|
-
// 切换目录
|
|
74
|
-
AsyncFunction("ftpClientChangeDirectory") { (clientId: String, remotePath: String, promise: Promise) in
|
|
75
|
-
|
|
76
|
-
// 获取当前目录
|
|
77
|
-
AsyncFunction("ftpClientGetCurrentDirectory") { (clientId: String, promise: Promise) in
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### 客户端管理方法
|
|
81
|
-
|
|
82
|
-
```swift
|
|
83
|
-
// 获取客户端状态 - 改为异步
|
|
84
|
-
AsyncFunction("getFtpClientStatus") { (clientId: String, promise: Promise) in
|
|
85
|
-
guard let ftpClient = self.ftpClients[clientId] else {
|
|
86
|
-
promise.resolve([
|
|
87
|
-
"exists": false,
|
|
88
|
-
"connected": false
|
|
89
|
-
])
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
promise.resolve([
|
|
94
|
-
"exists": true,
|
|
95
|
-
"connected": ftpClient.isConnected
|
|
96
|
-
])
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// 列出所有客户端 - 改为异步
|
|
100
|
-
AsyncFunction("listFtpClients") { (promise: Promise) in
|
|
101
|
-
let clientsInfo = self.ftpClients.mapValues { client in
|
|
102
|
-
[
|
|
103
|
-
"connected": client.isConnected
|
|
104
|
-
]
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
promise.resolve([
|
|
108
|
-
"clients": clientsInfo,
|
|
109
|
-
"count": self.ftpClients.count
|
|
110
|
-
])
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
## 兼容性说明
|
|
115
|
-
|
|
116
|
-
### 完全统一
|
|
117
|
-
|
|
118
|
-
现在iOS和Android的API接口完全统一:
|
|
119
|
-
|
|
120
|
-
- ✅ 方法名称一致
|
|
121
|
-
- ✅ 参数顺序一致
|
|
122
|
-
- ✅ 返回值格式一致
|
|
123
|
-
- ✅ 所有方法都是异步的
|
|
124
|
-
- ✅ 错误处理方式一致
|
|
125
|
-
|
|
126
|
-
### TypeScript类型定义
|
|
127
|
-
|
|
128
|
-
TypeScript模块定义(`ReactNativeKookitModule.ts`)已经使用了统一的命名,无需修改。
|
|
129
|
-
|
|
130
|
-
### FtpClient包装类
|
|
131
|
-
|
|
132
|
-
`FtpClient.ts`中的方法调用已经更新为使用统一的方法名。
|
|
133
|
-
|
|
134
|
-
## 影响范围
|
|
135
|
-
|
|
136
|
-
### 不影响的部分
|
|
137
|
-
|
|
138
|
-
- TypeScript客户端API保持不变
|
|
139
|
-
- 示例应用代码无需修改
|
|
140
|
-
- 用户使用的高级API (`FtpClient` 类) 保持不变
|
|
141
|
-
|
|
142
|
-
### 改进的部分
|
|
143
|
-
|
|
144
|
-
- 跨平台开发时API行为完全一致
|
|
145
|
-
- 调试时方法名称在两个平台上相同
|
|
146
|
-
- 内部native方法调用逻辑统一
|
|
147
|
-
|
|
148
|
-
## 验证结果
|
|
149
|
-
|
|
150
|
-
### 构建测试
|
|
151
|
-
|
|
152
|
-
- ✅ TypeScript编译:0错误
|
|
153
|
-
- ✅ iOS Swift编译:通过
|
|
154
|
-
- ✅ Android Kotlin编译:通过
|
|
155
|
-
|
|
156
|
-
### API一致性验证
|
|
157
|
-
|
|
158
|
-
```typescript
|
|
159
|
-
// 现在这些调用在iOS和Android上完全一致
|
|
160
|
-
await ReactNativeKookitModule.createFtpClient(clientId);
|
|
161
|
-
await ReactNativeKookitModule.ftpClientConnect(clientId, config);
|
|
162
|
-
await ReactNativeKookitModule.ftpClientList(clientId, path);
|
|
163
|
-
await ReactNativeKookitModule.ftpClientDownload(
|
|
164
|
-
clientId,
|
|
165
|
-
remotePath,
|
|
166
|
-
localPath
|
|
167
|
-
);
|
|
168
|
-
// ... 所有其他方法
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
## 总结
|
|
172
|
-
|
|
173
|
-
通过这次统一更新:
|
|
174
|
-
|
|
175
|
-
1. **解决了命名不一致问题**:iOS方法名更新为与Android一致
|
|
176
|
-
2. **统一了异步模式**:所有方法都使用AsyncFunction模式
|
|
177
|
-
3. **简化了维护工作**:两个平台使用相同的API接口
|
|
178
|
-
4. **提高了开发体验**:跨平台开发时无需记忆不同的方法名
|
|
179
|
-
|
|
180
|
-
现在iOS和Android的FTP客户端API完全统一,为开发者提供了一致的跨平台体验。
|
package/EXPO_PLUGIN_README.md
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
# React Native Kookit - Expo Plugin
|
|
2
|
-
|
|
3
|
-
这个插件可以自动修改您的 MainActivity 以支持音量键拦截功能,无需手动编写代码。
|
|
4
|
-
|
|
5
|
-
## 自动安装(推荐)
|
|
6
|
-
|
|
7
|
-
### 1. 安装模块
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install react-native-kookit
|
|
11
|
-
# 或
|
|
12
|
-
yarn add react-native-kookit
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
### 2. 配置 Expo 插件
|
|
16
|
-
|
|
17
|
-
在您的 `app.json` 或 `app.config.js` 中添加插件:
|
|
18
|
-
|
|
19
|
-
```json
|
|
20
|
-
{
|
|
21
|
-
"expo": {
|
|
22
|
-
"plugins": ["react-native-kookit"]
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### 3. 预构建项目
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
npx expo prebuild --clean
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### 4. 构建和运行
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
npx expo run:android
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
就这样!插件会自动修改您的 MainActivity 以支持音量键拦截。
|
|
40
|
-
|
|
41
|
-
## 使用方法
|
|
42
|
-
|
|
43
|
-
```javascript
|
|
44
|
-
import ReactNativeKookit from "react-native-kookit";
|
|
45
|
-
|
|
46
|
-
// 添加监听器
|
|
47
|
-
const subscription = ReactNativeKookit.addListener(
|
|
48
|
-
"onVolumeButtonPressed",
|
|
49
|
-
(event) => {
|
|
50
|
-
console.log("Volume button pressed:", event.key); // "up" 或 "down"
|
|
51
|
-
}
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
// 启用音量键拦截
|
|
55
|
-
ReactNativeKookit.enableVolumeKeyInterception();
|
|
56
|
-
|
|
57
|
-
// 测试:按音量键,应该在控制台看到日志
|
|
58
|
-
|
|
59
|
-
// 清理
|
|
60
|
-
subscription.remove();
|
|
61
|
-
ReactNativeKookit.disableVolumeKeyInterception();
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## 插件做了什么
|
|
65
|
-
|
|
66
|
-
插件会自动:
|
|
67
|
-
|
|
68
|
-
1. **添加必要的导入语句**:
|
|
69
|
-
- `import android.view.KeyEvent`
|
|
70
|
-
- `import expo.modules.kookit.VolumeKeyInterceptActivity`
|
|
71
|
-
- `import expo.modules.kookit.handleVolumeKeyEvent`
|
|
72
|
-
|
|
73
|
-
2. **修改类声明**:
|
|
74
|
-
|
|
75
|
-
```kotlin
|
|
76
|
-
class MainActivity : ReactActivity(), VolumeKeyInterceptActivity
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
3. **添加必要的属性和方法**:
|
|
80
|
-
|
|
81
|
-
```kotlin
|
|
82
|
-
private var volumeKeyListener: ((Int) -> Unit)? = null
|
|
83
|
-
private var isVolumeKeyInterceptEnabled = false
|
|
84
|
-
|
|
85
|
-
override fun setVolumeKeyListener(listener: ((Int) -> Unit)?) {
|
|
86
|
-
volumeKeyListener = listener
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
override fun setVolumeKeyInterceptEnabled(enabled: Boolean) {
|
|
90
|
-
isVolumeKeyInterceptEnabled = enabled
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
|
94
|
-
if (isVolumeKeyInterceptEnabled && handleVolumeKeyEvent(event)) {
|
|
95
|
-
return true
|
|
96
|
-
}
|
|
97
|
-
return super.dispatchKeyEvent(event)
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## 兼容性
|
|
102
|
-
|
|
103
|
-
- ✅ Expo SDK 49+
|
|
104
|
-
- ✅ React Native 0.70+
|
|
105
|
-
- ✅ 支持 Kotlin 和 Java MainActivity
|
|
106
|
-
- ✅ 支持新架构(Fabric)
|
|
107
|
-
|
|
108
|
-
## 手动安装(如果不使用 Expo)
|
|
109
|
-
|
|
110
|
-
如果您不使用 Expo,请参考 [ANDROID_SETUP.md](./ANDROID_SETUP.md) 手动修改 MainActivity。
|
|
111
|
-
|
|
112
|
-
## 故障排除
|
|
113
|
-
|
|
114
|
-
### 插件未生效
|
|
115
|
-
|
|
116
|
-
1. 确保运行了 `npx expo prebuild --clean`
|
|
117
|
-
2. 检查 `android/app/src/main/java/.../MainActivity.kt` 是否包含了修改
|
|
118
|
-
3. 清理并重新构建:`cd android && ./gradlew clean && cd .. && npx expo run:android`
|
|
119
|
-
|
|
120
|
-
### 构建错误
|
|
121
|
-
|
|
122
|
-
1. 确保您的 MainActivity 文件格式正确
|
|
123
|
-
2. 检查是否有语法错误
|
|
124
|
-
3. 如果插件修改失败,可以手动按照 ANDROID_SETUP.md 进行修改
|
|
125
|
-
|
|
126
|
-
## 开发插件
|
|
127
|
-
|
|
128
|
-
如果您需要修改插件,可以:
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
cd plugin
|
|
132
|
-
npm install
|
|
133
|
-
npm run build
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
然后重新运行 `npx expo prebuild --clean`。
|