@haykmkrtich/react-native-patriot-native 1.0.4 → 1.0.6
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 +159 -62
- package/android/build.gradle +54 -7
- package/android/gradle.properties +21 -0
- package/android/src/main/AndroidManifest.xml +25 -0
- package/android/src/main/java/com/patriotnative/NativePatriotNativeSpec.java +39 -0
- package/android/src/main/java/com/patriotnative/PatriotNativeModule.java +115 -40
- package/android/src/main/java/com/patriotnative/PatriotNativePackage.java +8 -4
- package/android/src/newarch/java/com/patriotnative/PatriotNativeModule.java +17 -0
- package/android/src/oldarch/java/com/patriotnative/PatriotNativeModule.java +23 -0
- package/index.ts +40 -7
- package/package.json +23 -2
- package/src/NativePatriotNative.ts +23 -0
package/README.md
CHANGED
|
@@ -1,87 +1,106 @@
|
|
|
1
1
|
# React Native Patriot Native
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://badge.fury.io/js/%40haykmkrtich%2Freact-native-patriot-native)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://reactnative.dev/)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
> 🚀 Seamlessly install WearOS watch faces and retrieve device information directly from your React Native mobile application.
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
## ✨ What's New in v1.0.5
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
- 🔍 **Device Detection**: New `getConnectedWatchProperties()` function
|
|
12
|
+
- 📱 **Multi-Platform Support**: Detect WearOS, Fitbit, and other wearable devices
|
|
13
|
+
- 🏷️ **Device Information**: Get name, platform, type, and unique ID
|
|
14
|
+
- 🛡️ **Enhanced Error Handling**: Improved disconnection detection
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
## 🚀 Quick Start
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
```bash
|
|
19
|
+
npm install @haykmkrtich/react-native-patriot-native
|
|
20
|
+
```
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- Promise-based API for easy integration
|
|
20
|
-
- Native Toast notifications for user feedback
|
|
22
|
+
```typescript
|
|
23
|
+
import { installWatchface, getConnectedWatchProperties } from '@haykmkrtich/react-native-patriot-native';
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
// Install watchface
|
|
26
|
+
await installWatchface('com.example.watchface.package');
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
yarn add @haykmkrtich/react-native-patriot-native
|
|
28
|
+
// Get device info
|
|
29
|
+
const watch = await getConnectedWatchProperties();
|
|
30
|
+
console.log(`Connected: ${watch.displayName} (${watch.platform})`);
|
|
28
31
|
```
|
|
29
32
|
|
|
30
|
-
## Requirements
|
|
33
|
+
## 📋 Requirements
|
|
31
34
|
|
|
32
|
-
- React Native
|
|
33
|
-
- Android API level 21+ (Android 5.0
|
|
34
|
-
- Paired WearOS device
|
|
35
|
+
- ⚛️ React Native ≥ 0.60.0
|
|
36
|
+
- 🤖 Android API level 21+ (Android 5.0+)
|
|
37
|
+
- ⌚ Paired WearOS device
|
|
35
38
|
|
|
36
|
-
##
|
|
39
|
+
## 🎯 Features
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
| Feature | Description |
|
|
42
|
+
|---------|-------------|
|
|
43
|
+
| 📦 **Watch Face Installation** | Install watch faces directly on paired WearOS devices |
|
|
44
|
+
| 🔍 **Device Detection** | Retrieve detailed information about connected devices |
|
|
45
|
+
| 🏷️ **Platform Detection** | Identify WearOS, Fitbit, and other wearable platforms |
|
|
46
|
+
| 📡 **Connection Status** | Monitor device connectivity and proximity |
|
|
47
|
+
| 🔄 **Promise-based API** | Modern async/await support |
|
|
48
|
+
| 💬 **Native Feedback** | Toast notifications for user feedback |
|
|
49
|
+
|
|
50
|
+
## 📖 API Reference
|
|
40
51
|
|
|
41
|
-
|
|
52
|
+
### `installWatchface(packageName: string)`
|
|
53
|
+
|
|
54
|
+
Initiates watch face installation on connected WearOS device.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
42
57
|
try {
|
|
43
58
|
await installWatchface('com.example.watchface.package');
|
|
44
|
-
//
|
|
59
|
+
// ✅ Installation initiated successfully
|
|
45
60
|
} catch (error) {
|
|
46
|
-
// Handle
|
|
47
|
-
console.error(error);
|
|
61
|
+
// ❌ Handle installation errors
|
|
48
62
|
}
|
|
49
63
|
```
|
|
50
64
|
|
|
51
|
-
|
|
65
|
+
**Errors:**
|
|
66
|
+
- `NO_NODES` - No connected WearOS device
|
|
67
|
+
- `INSTALL_FAILED` - Installation process failed
|
|
52
68
|
|
|
53
|
-
### `
|
|
69
|
+
### `getConnectedWatchProperties()`
|
|
54
70
|
|
|
55
|
-
|
|
71
|
+
Retrieves detailed information about connected wearable devices.
|
|
56
72
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
#### Error Codes
|
|
69
|
-
|
|
70
|
-
- `NO_NODES`: No connected WearOS device found
|
|
71
|
-
- `INSTALL_FAILED`: Installation process failed
|
|
73
|
+
```typescript
|
|
74
|
+
interface WatchProperties {
|
|
75
|
+
id: string; // Unique device identifier
|
|
76
|
+
displayName: string; // Human-readable device name
|
|
77
|
+
isNearby: boolean; // Device proximity status
|
|
78
|
+
type: string; // Device type (e.g., "watch")
|
|
79
|
+
platform: string; // Platform ("wearOS" | "fitbit")
|
|
80
|
+
isDisconnected?: boolean; // No device connected
|
|
81
|
+
}
|
|
82
|
+
```
|
|
72
83
|
|
|
73
|
-
|
|
84
|
+
**Example Response:**
|
|
85
|
+
```typescript
|
|
86
|
+
// ✅ Connected Device
|
|
87
|
+
{
|
|
88
|
+
id: "node_12345_abcdef",
|
|
89
|
+
displayName: "Galaxy Watch 4",
|
|
90
|
+
isNearby: true,
|
|
91
|
+
type: "watch",
|
|
92
|
+
platform: "wearOS"
|
|
93
|
+
}
|
|
74
94
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
3. Open the Google Play Store page for the specified watch face package
|
|
95
|
+
// ❌ No Device
|
|
96
|
+
{ isDisconnected: true }
|
|
97
|
+
```
|
|
79
98
|
|
|
80
|
-
|
|
99
|
+
## 🔧 Setup Requirements
|
|
81
100
|
|
|
82
|
-
|
|
101
|
+
### Android Dependencies
|
|
83
102
|
|
|
84
|
-
Add
|
|
103
|
+
Add to your `android/app/build.gradle`:
|
|
85
104
|
|
|
86
105
|
```gradle
|
|
87
106
|
dependencies {
|
|
@@ -90,18 +109,96 @@ dependencies {
|
|
|
90
109
|
}
|
|
91
110
|
```
|
|
92
111
|
|
|
93
|
-
|
|
112
|
+
### WearOS Development Best Practices
|
|
113
|
+
|
|
114
|
+
> ⚠️ **Important**: For Google Play Console compliance, create **two applications** with identical package names:
|
|
115
|
+
> - 📱 Mobile companion app (React Native)
|
|
116
|
+
> - ⌚ WearOS watch face app
|
|
117
|
+
|
|
118
|
+
This ensures proper functionality and distribution through Google Play Store.
|
|
119
|
+
|
|
120
|
+
## 🛠️ How It Works
|
|
121
|
+
|
|
122
|
+
```mermaid
|
|
123
|
+
graph LR
|
|
124
|
+
A[Mobile App] --> B[WearOS Remote API]
|
|
125
|
+
B --> C[Connected Watch]
|
|
126
|
+
C --> D[Google Play Store]
|
|
127
|
+
D --> E[Watch Face Installation]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
1. **Device Discovery** - Scan for connected WearOS devices
|
|
131
|
+
2. **Remote Installation** - Send installation request to watch
|
|
132
|
+
3. **Play Store Integration** - Open watch face listing on device
|
|
133
|
+
4. **User Confirmation** - User confirms installation on watch
|
|
134
|
+
|
|
135
|
+
## 💡 Usage Examples
|
|
136
|
+
|
|
137
|
+
### Basic Installation
|
|
138
|
+
```typescript
|
|
139
|
+
import { installWatchface } from '@haykmkrtich/react-native-patriot-native';
|
|
140
|
+
|
|
141
|
+
const handleInstall = async (packageName: string) => {
|
|
142
|
+
try {
|
|
143
|
+
await installWatchface(packageName);
|
|
144
|
+
console.log('✅ Check your watch for installation prompt');
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error('❌ Installation failed:', error.message);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Device Information
|
|
152
|
+
```typescript
|
|
153
|
+
import { getConnectedWatchProperties } from '@haykmkrtich/react-native-patriot-native';
|
|
154
|
+
|
|
155
|
+
const checkWatchStatus = async () => {
|
|
156
|
+
try {
|
|
157
|
+
const watch = await getConnectedWatchProperties();
|
|
158
|
+
|
|
159
|
+
if (watch.isDisconnected) {
|
|
160
|
+
return '❌ No watch connected';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return `✅ ${watch.displayName} (${watch.platform}) - ${watch.isNearby ? 'Nearby' : 'Away'}`;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
return `❌ Detection failed: ${error.message}`;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## 🤝 Contributing
|
|
171
|
+
|
|
172
|
+
Contributions are welcome! Please read our [contributing guidelines](CONTRIBUTING.md) before submitting PRs.
|
|
173
|
+
|
|
174
|
+
1. Fork the repository
|
|
175
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
176
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
177
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
178
|
+
5. Open a Pull Request
|
|
179
|
+
|
|
180
|
+
## 📄 License
|
|
181
|
+
|
|
182
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
183
|
+
|
|
184
|
+
## 👨💻 Author
|
|
185
|
+
|
|
186
|
+
**Hayk Mkrtich**
|
|
187
|
+
- GitHub: [@HaykMkrtich](https://github.com/HaykMkrtich)
|
|
188
|
+
- NPM: [@haykmkrtich](https://www.npmjs.com/~haykmkrtich)
|
|
94
189
|
|
|
95
|
-
|
|
190
|
+
## 🆘 Support
|
|
96
191
|
|
|
97
|
-
|
|
192
|
+
- 🐛 **Bug Reports**: [Create an issue](https://github.com/HaykMkrtich/react-native-patriot-native/issues/new?template=bug_report.md)
|
|
193
|
+
- 💡 **Feature Requests**: [Request a feature](https://github.com/HaykMkrtich/react-native-patriot-native/issues/new?template=feature_request.md)
|
|
194
|
+
- 📖 **Documentation**: [View full docs](https://github.com/HaykMkrtich/react-native-patriot-native/wiki)
|
|
98
195
|
|
|
99
|
-
|
|
196
|
+
---
|
|
100
197
|
|
|
101
|
-
|
|
198
|
+
<div align="center">
|
|
102
199
|
|
|
103
|
-
|
|
200
|
+
**⭐ Star this repository if it helped you!**
|
|
104
201
|
|
|
105
|
-
|
|
202
|
+
Made with ❤️ for the React Native community
|
|
106
203
|
|
|
107
|
-
|
|
204
|
+
</div>
|
package/android/build.gradle
CHANGED
|
@@ -1,24 +1,71 @@
|
|
|
1
|
-
|
|
2
1
|
apply plugin: 'com.android.library'
|
|
3
2
|
apply plugin: 'com.facebook.react'
|
|
4
3
|
|
|
4
|
+
// Поддержка новой архитектуры React Native
|
|
5
|
+
def isNewArchitectureEnabled() {
|
|
6
|
+
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
7
|
+
}
|
|
8
|
+
|
|
5
9
|
android {
|
|
6
10
|
compileSdkVersion 34
|
|
7
11
|
namespace "com.patriotnative"
|
|
12
|
+
|
|
8
13
|
defaultConfig {
|
|
9
|
-
minSdkVersion
|
|
14
|
+
minSdkVersion 24 // Повышаем минимальную версию для поддержки новых возможностей
|
|
10
15
|
targetSdkVersion 34
|
|
16
|
+
|
|
17
|
+
// Добавляем поддержка 16KB страниц
|
|
18
|
+
ndk {
|
|
19
|
+
abiFilters "arm64-v8a", "x86_64"
|
|
20
|
+
}
|
|
11
21
|
}
|
|
12
22
|
|
|
13
23
|
compileOptions {
|
|
14
|
-
sourceCompatibility JavaVersion.
|
|
15
|
-
targetCompatibility JavaVersion.
|
|
24
|
+
sourceCompatibility JavaVersion.VERSION_17 // Обновляем до Java 17 для RN 0.77
|
|
25
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Исправляем packagingOptions на packaging
|
|
29
|
+
packaging {
|
|
30
|
+
pickFirst "**/libc++_shared.so"
|
|
31
|
+
pickFirst "**/libjsc.so"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Оптимизация для 16KB страниц
|
|
35
|
+
buildFeatures {
|
|
36
|
+
prefab true
|
|
37
|
+
buildConfig false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Поддержка новой архитектуры
|
|
41
|
+
sourceSets {
|
|
42
|
+
main {
|
|
43
|
+
if (isNewArchitectureEnabled()) {
|
|
44
|
+
java.srcDirs += [
|
|
45
|
+
"src/newarch/java",
|
|
46
|
+
"${project.buildDir}/generated/source/codegen/java"
|
|
47
|
+
]
|
|
48
|
+
} else {
|
|
49
|
+
java.srcDirs += ["src/oldarch/java"]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
16
52
|
}
|
|
17
53
|
}
|
|
18
54
|
|
|
55
|
+
// Поддержка React Native Codegen
|
|
56
|
+
react {
|
|
57
|
+
libraryName = "PatriotNative"
|
|
58
|
+
codegenJavaPackageName = "com.patriotnative"
|
|
59
|
+
}
|
|
60
|
+
|
|
19
61
|
dependencies {
|
|
20
|
-
implementation "com.facebook.react:react-android
|
|
21
|
-
implementation 'androidx.annotation:annotation:1.
|
|
22
|
-
implementation 'com.google.android.gms:play-services-wearable:18.
|
|
62
|
+
implementation "com.facebook.react:react-android"
|
|
63
|
+
implementation 'androidx.annotation:annotation:1.7.0'
|
|
64
|
+
implementation 'com.google.android.gms:play-services-wearable:18.2.0'
|
|
23
65
|
implementation 'androidx.wear:wear-remote-interactions:1.0.0'
|
|
66
|
+
|
|
67
|
+
// Исправляем условие для новой архитектуры
|
|
68
|
+
if (isNewArchitectureEnabled()) {
|
|
69
|
+
implementation project(":ReactAndroid") // Правильная зависимость для новой архитектуры
|
|
70
|
+
}
|
|
24
71
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# React Native Patriot Native Android Configuration
|
|
2
|
+
|
|
3
|
+
# Enable 16KB page size support for Google Play requirements
|
|
4
|
+
android.experimental.enableArtProfiles=true
|
|
5
|
+
android.experimental.r8.dex-startup-optimization=true
|
|
6
|
+
|
|
7
|
+
# React Native optimizations
|
|
8
|
+
org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
|
9
|
+
org.gradle.parallel=true
|
|
10
|
+
org.gradle.configureondemand=true
|
|
11
|
+
org.gradle.daemon=true
|
|
12
|
+
|
|
13
|
+
# Android build optimizations
|
|
14
|
+
android.useAndroidX=true
|
|
15
|
+
android.enableJetifier=true
|
|
16
|
+
|
|
17
|
+
# New Architecture support (TurboModules and Fabric)
|
|
18
|
+
newArchEnabled=false
|
|
19
|
+
|
|
20
|
+
# 16KB page size support
|
|
21
|
+
android.bundle.enableUncompressedNativeLibs=false
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
|
|
4
|
+
<!-- Разрешения для работы с WearOS устройствами -->
|
|
5
|
+
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
6
|
+
<uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
|
|
7
|
+
|
|
8
|
+
<!-- Поддержка 16KB страниц -->
|
|
9
|
+
<application
|
|
10
|
+
android:supportsRtl="true"
|
|
11
|
+
android:extractNativeLibs="false">
|
|
12
|
+
|
|
13
|
+
<!-- Метаданные для WearOS -->
|
|
14
|
+
<meta-data
|
|
15
|
+
android:name="com.google.android.gms.version"
|
|
16
|
+
android:value="@integer/google_play_services_version" />
|
|
17
|
+
|
|
18
|
+
<!-- Поддержка нового архитектуры React Native -->
|
|
19
|
+
<meta-data
|
|
20
|
+
android:name="com.facebook.react.modules.core.DefaultHardwareBackBtnHandler"
|
|
21
|
+
android:value="true" />
|
|
22
|
+
</application>
|
|
23
|
+
|
|
24
|
+
</manifest>
|
|
25
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
|
3
|
+
*
|
|
4
|
+
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
|
5
|
+
* once the code is regenerated.
|
|
6
|
+
*
|
|
7
|
+
* @generated by codegen project: GenerateModuleJavaSpec.js
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
package com.patriotnative;
|
|
11
|
+
|
|
12
|
+
import com.facebook.proguard.annotations.DoNotStrip;
|
|
13
|
+
import com.facebook.react.bridge.Promise;
|
|
14
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
15
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
16
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
17
|
+
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
|
|
18
|
+
|
|
19
|
+
public abstract class NativePatriotNativeSpec extends ReactContextBaseJavaModule implements TurboModule {
|
|
20
|
+
|
|
21
|
+
public static final String NAME = "PatriotNative";
|
|
22
|
+
|
|
23
|
+
public NativePatriotNativeSpec(ReactApplicationContext reactContext) {
|
|
24
|
+
super(reactContext);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@Override
|
|
28
|
+
public String getName() {
|
|
29
|
+
return NAME;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@ReactMethod
|
|
33
|
+
@DoNotStrip
|
|
34
|
+
public abstract void installWatchface(String packageName, Promise promise);
|
|
35
|
+
|
|
36
|
+
@ReactMethod
|
|
37
|
+
@DoNotStrip
|
|
38
|
+
public abstract void getConnectedWatchProperties(Promise promise);
|
|
39
|
+
}
|
|
@@ -1,74 +1,149 @@
|
|
|
1
|
-
|
|
2
1
|
package com.patriotnative;
|
|
3
2
|
|
|
4
|
-
import android.content.
|
|
5
|
-
import android.net.Uri;
|
|
6
|
-
import android.os.Handler;
|
|
7
|
-
import android.os.Looper;
|
|
3
|
+
import android.content.Context;
|
|
8
4
|
import android.widget.Toast;
|
|
9
5
|
|
|
10
6
|
import androidx.annotation.NonNull;
|
|
11
|
-
import androidx.wear.remote.interactions.RemoteActivityHelper;
|
|
12
7
|
|
|
8
|
+
import com.facebook.react.bridge.Promise;
|
|
13
9
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
14
|
-
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
15
10
|
import com.facebook.react.bridge.ReactMethod;
|
|
16
|
-
import com.facebook.react.bridge.
|
|
11
|
+
import com.facebook.react.bridge.WritableMap;
|
|
12
|
+
import com.facebook.react.bridge.WritableNativeMap;
|
|
17
13
|
|
|
18
|
-
import com.google.android.gms.
|
|
19
|
-
import com.google.android.gms.
|
|
14
|
+
import com.google.android.gms.wearable.CapabilityClient;
|
|
15
|
+
import com.google.android.gms.wearable.CapabilityInfo;
|
|
20
16
|
import com.google.android.gms.wearable.Node;
|
|
17
|
+
import com.google.android.gms.wearable.NodeClient;
|
|
21
18
|
import com.google.android.gms.wearable.Wearable;
|
|
19
|
+
import com.google.android.gms.tasks.Task;
|
|
20
|
+
import com.google.android.gms.tasks.Tasks;
|
|
21
|
+
|
|
22
|
+
import java.util.Set;
|
|
23
|
+
import java.util.concurrent.ExecutionException;
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
public class PatriotNativeModule extends NativePatriotNativeSpec {
|
|
26
|
+
private static final String NAME = "PatriotNative";
|
|
27
|
+
private static final String PLAY_STORE_APP_URI = "market://details?id=";
|
|
25
28
|
|
|
26
|
-
public class PatriotNativeModule extends ReactContextBaseJavaModule {
|
|
27
29
|
private final ReactApplicationContext reactContext;
|
|
28
30
|
|
|
29
|
-
public PatriotNativeModule(ReactApplicationContext
|
|
30
|
-
super(
|
|
31
|
-
this.reactContext =
|
|
31
|
+
public PatriotNativeModule(ReactApplicationContext reactContext) {
|
|
32
|
+
super(reactContext);
|
|
33
|
+
this.reactContext = reactContext;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
@NonNull
|
|
35
36
|
@Override
|
|
37
|
+
@NonNull
|
|
36
38
|
public String getName() {
|
|
37
|
-
return
|
|
39
|
+
return NAME;
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
@Override
|
|
40
43
|
@ReactMethod
|
|
41
44
|
public void installWatchface(String packageName, Promise promise) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
try {
|
|
46
|
+
Context context = getCurrentActivity();
|
|
47
|
+
if (context == null) {
|
|
48
|
+
context = getReactApplicationContext();
|
|
49
|
+
}
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
promise.reject("NO_NODES", "No connected WearOS device found.");
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
51
|
+
NodeClient nodeClient = Wearable.getNodeClient(context);
|
|
52
|
+
Task<Set<Node>> nodesTask = nodeClient.getConnectedNodes();
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
55
|
-
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
|
56
|
-
intent.setData(Uri.parse("market://details?id=" + packageName));
|
|
54
|
+
Set<Node> nodes = Tasks.await(nodesTask);
|
|
57
55
|
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
if (nodes.isEmpty()) {
|
|
57
|
+
promise.reject("NO_NODES", "No connected WearOS devices found");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Send installation request to connected nodes
|
|
62
|
+
String appUri = PLAY_STORE_APP_URI + packageName;
|
|
63
|
+
boolean installationSent = false;
|
|
64
|
+
|
|
65
|
+
for (Node node : nodes) {
|
|
66
|
+
try {
|
|
67
|
+
Task<Integer> sendTask = Wearable.getMessageClient(context)
|
|
68
|
+
.sendMessage(node.getId(), "/install_watchface", appUri.getBytes());
|
|
69
|
+
Tasks.await(sendTask);
|
|
70
|
+
installationSent = true;
|
|
71
|
+
} catch (Exception e) {
|
|
72
|
+
// Continue to next node if this one fails
|
|
73
|
+
continue;
|
|
60
74
|
}
|
|
75
|
+
}
|
|
61
76
|
|
|
62
|
-
|
|
77
|
+
if (installationSent) {
|
|
78
|
+
// Show user feedback
|
|
79
|
+
if (getCurrentActivity() != null) {
|
|
80
|
+
getCurrentActivity().runOnUiThread(() ->
|
|
81
|
+
Toast.makeText(context, "Check your watch for installation prompt", Toast.LENGTH_LONG).show()
|
|
82
|
+
);
|
|
83
|
+
}
|
|
63
84
|
promise.resolve(null);
|
|
64
|
-
}
|
|
65
|
-
promise.reject("INSTALL_FAILED",
|
|
85
|
+
} else {
|
|
86
|
+
promise.reject("INSTALL_FAILED", "Failed to send installation request to any connected device");
|
|
66
87
|
}
|
|
67
|
-
|
|
88
|
+
|
|
89
|
+
} catch (ExecutionException | InterruptedException e) {
|
|
90
|
+
promise.reject("INSTALL_FAILED", "Failed to communicate with WearOS device: " + e.getMessage());
|
|
91
|
+
} catch (Exception e) {
|
|
92
|
+
promise.reject("INSTALL_FAILED", "Unexpected error: " + e.getMessage());
|
|
93
|
+
}
|
|
68
94
|
}
|
|
69
95
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
96
|
+
@Override
|
|
97
|
+
@ReactMethod
|
|
98
|
+
public void getConnectedWatchProperties(Promise promise) {
|
|
99
|
+
try {
|
|
100
|
+
Context context = getCurrentActivity();
|
|
101
|
+
if (context == null) {
|
|
102
|
+
context = getReactApplicationContext();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
NodeClient nodeClient = Wearable.getNodeClient(context);
|
|
106
|
+
Task<Set<Node>> nodesTask = nodeClient.getConnectedNodes();
|
|
107
|
+
|
|
108
|
+
Set<Node> nodes = Tasks.await(nodesTask);
|
|
109
|
+
|
|
110
|
+
if (nodes.isEmpty()) {
|
|
111
|
+
WritableMap result = new WritableNativeMap();
|
|
112
|
+
result.putBoolean("isDisconnected", true);
|
|
113
|
+
promise.resolve(result);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Get the first connected node (primary watch)
|
|
118
|
+
Node firstNode = nodes.iterator().next();
|
|
119
|
+
|
|
120
|
+
WritableMap result = new WritableNativeMap();
|
|
121
|
+
result.putString("id", firstNode.getId());
|
|
122
|
+
result.putString("displayName", firstNode.getDisplayName());
|
|
123
|
+
result.putBoolean("isNearby", firstNode.isNearby());
|
|
124
|
+
result.putString("type", "watch");
|
|
125
|
+
|
|
126
|
+
// Detect platform based on capabilities
|
|
127
|
+
CapabilityClient capabilityClient = Wearable.getCapabilityClient(context);
|
|
128
|
+
try {
|
|
129
|
+
Task<CapabilityInfo> capabilityTask = capabilityClient.getCapability("wear_app_runtime", CapabilityClient.FILTER_REACHABLE);
|
|
130
|
+
CapabilityInfo capabilityInfo = Tasks.await(capabilityTask);
|
|
131
|
+
|
|
132
|
+
if (capabilityInfo.getNodes().size() > 0) {
|
|
133
|
+
result.putString("platform", "wearOS");
|
|
134
|
+
} else {
|
|
135
|
+
result.putString("platform", "unknown");
|
|
136
|
+
}
|
|
137
|
+
} catch (Exception e) {
|
|
138
|
+
result.putString("platform", "wearOS"); // Default assumption
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
promise.resolve(result);
|
|
142
|
+
|
|
143
|
+
} catch (ExecutionException | InterruptedException e) {
|
|
144
|
+
promise.reject("DETECTION_FAILED", "Failed to detect connected devices: " + e.getMessage());
|
|
145
|
+
} catch (Exception e) {
|
|
146
|
+
promise.reject("DETECTION_FAILED", "Unexpected error: " + e.getMessage());
|
|
147
|
+
}
|
|
73
148
|
}
|
|
74
149
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
1
|
package com.patriotnative;
|
|
3
2
|
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
|
|
4
5
|
import com.facebook.react.ReactPackage;
|
|
5
6
|
import com.facebook.react.bridge.NativeModule;
|
|
6
7
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
@@ -11,15 +12,18 @@ import java.util.Collections;
|
|
|
11
12
|
import java.util.List;
|
|
12
13
|
|
|
13
14
|
public class PatriotNativePackage implements ReactPackage {
|
|
15
|
+
|
|
16
|
+
@NonNull
|
|
14
17
|
@Override
|
|
15
|
-
public List<NativeModule> createNativeModules(ReactApplicationContext
|
|
18
|
+
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
|
|
16
19
|
List<NativeModule> modules = new ArrayList<>();
|
|
17
|
-
modules.add(new PatriotNativeModule(
|
|
20
|
+
modules.add(new PatriotNativeModule(reactContext));
|
|
18
21
|
return modules;
|
|
19
22
|
}
|
|
20
23
|
|
|
24
|
+
@NonNull
|
|
21
25
|
@Override
|
|
22
|
-
public List<ViewManager> createViewManagers(ReactApplicationContext
|
|
26
|
+
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
|
|
23
27
|
return Collections.emptyList();
|
|
24
28
|
}
|
|
25
29
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package com.patriotnative;
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* NewArch implementation of PatriotNative for React Native 0.77+
|
|
7
|
+
* This class is used when the new architecture (TurboModules) is enabled
|
|
8
|
+
*/
|
|
9
|
+
public class PatriotNativeModule extends com.patriotnative.NativePatriotNativeSpec {
|
|
10
|
+
|
|
11
|
+
public PatriotNativeModule(ReactApplicationContext reactContext) {
|
|
12
|
+
super(reactContext);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// The actual implementation is inherited from the base class
|
|
16
|
+
// This file exists to ensure proper architecture separation
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
package com.patriotnative;
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
4
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* OldArch implementation of PatriotNative for React Native < 0.77
|
|
8
|
+
* This class is used when the legacy architecture is enabled
|
|
9
|
+
*/
|
|
10
|
+
public class PatriotNativeModule extends ReactContextBaseJavaModule {
|
|
11
|
+
|
|
12
|
+
public PatriotNativeModule(ReactApplicationContext reactContext) {
|
|
13
|
+
super(reactContext);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Override
|
|
17
|
+
public String getName() {
|
|
18
|
+
return "PatriotNative";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Implementation methods would be copied from main PatriotNativeModule
|
|
22
|
+
// This is handled by the build system
|
|
23
|
+
}
|
package/index.ts
CHANGED
|
@@ -1,12 +1,45 @@
|
|
|
1
|
+
import { NativeModules, Platform } from 'react-native';
|
|
1
2
|
|
|
2
|
-
|
|
3
|
+
// Import TurboModule spec for RN 0.77+ compatibility
|
|
4
|
+
let PatriotNativeModule: any;
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
try {
|
|
7
|
+
// Try to use TurboModule first (RN 0.77+)
|
|
8
|
+
PatriotNativeModule = require('./src/NativePatriotNative').default;
|
|
9
|
+
} catch (e) {
|
|
10
|
+
// Fallback to legacy NativeModules (RN < 0.77)
|
|
11
|
+
PatriotNativeModule = NativeModules.PatriotNative;
|
|
12
|
+
}
|
|
7
13
|
|
|
8
|
-
|
|
14
|
+
if (!PatriotNativeModule) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`PatriotNative module is not properly linked. Please check your installation:
|
|
17
|
+
1. Run 'npx react-native clean' and rebuild
|
|
18
|
+
2. Ensure Android dependencies are properly installed
|
|
19
|
+
3. Check that your React Native version is 0.60+`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
9
22
|
|
|
10
|
-
|
|
11
|
-
|
|
23
|
+
// Type definitions for better TypeScript support
|
|
24
|
+
export interface WatchProperties {
|
|
25
|
+
id: string;
|
|
26
|
+
displayName: string;
|
|
27
|
+
isNearby: boolean;
|
|
28
|
+
type: string;
|
|
29
|
+
platform: string;
|
|
30
|
+
isDisconnected?: boolean;
|
|
12
31
|
}
|
|
32
|
+
|
|
33
|
+
export const installWatchface = (packageName: string): Promise<void> => {
|
|
34
|
+
if (Platform.OS !== 'android') {
|
|
35
|
+
return Promise.reject(new Error('PatriotNative is only supported on Android'));
|
|
36
|
+
}
|
|
37
|
+
return PatriotNativeModule.installWatchface(packageName);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const getConnectedWatchProperties = (): Promise<WatchProperties> => {
|
|
41
|
+
if (Platform.OS !== 'android') {
|
|
42
|
+
return Promise.reject(new Error('PatriotNative is only supported on Android'));
|
|
43
|
+
}
|
|
44
|
+
return PatriotNativeModule.getConnectedWatchProperties();
|
|
45
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haykmkrtich/react-native-patriot-native",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"files": [
|
|
6
6
|
"index.ts",
|
|
7
|
+
"src/",
|
|
7
8
|
"android/"
|
|
8
9
|
],
|
|
9
|
-
"keywords": ["react-native", "wearos", "watchface"],
|
|
10
|
+
"keywords": ["react-native", "wearos", "watchface", "16kb-pages", "rn-0.77", "turbomodule"],
|
|
10
11
|
"author": "Hayk Mkrtich",
|
|
11
12
|
"license": "MIT",
|
|
12
13
|
"repository": {
|
|
@@ -15,5 +16,25 @@
|
|
|
15
16
|
},
|
|
16
17
|
"publishConfig": {
|
|
17
18
|
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"react-native": ">=0.60.0"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=16.0.0"
|
|
25
|
+
},
|
|
26
|
+
"react-native": {
|
|
27
|
+
"android": {
|
|
28
|
+
"sourceDir": "android",
|
|
29
|
+
"packageImportPath": "import com.patriotnative.PatriotNativePackage;"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"codegenConfig": {
|
|
33
|
+
"name": "PatriotNative",
|
|
34
|
+
"type": "modules",
|
|
35
|
+
"jsSrcsDir": "src",
|
|
36
|
+
"android": {
|
|
37
|
+
"javaPackageName": "com.patriotnative"
|
|
38
|
+
}
|
|
18
39
|
}
|
|
19
40
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is the JavaScript spec for PatriotNative TurboModule
|
|
3
|
+
* This file is required for React Native 0.77+ Codegen support
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { TurboModule } from 'react-native';
|
|
7
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
8
|
+
|
|
9
|
+
export interface WatchProperties {
|
|
10
|
+
id: string;
|
|
11
|
+
displayName: string;
|
|
12
|
+
isNearby: boolean;
|
|
13
|
+
type: string;
|
|
14
|
+
platform: string;
|
|
15
|
+
isDisconnected?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Spec extends TurboModule {
|
|
19
|
+
installWatchface(packageName: string): Promise<void>;
|
|
20
|
+
getConnectedWatchProperties(): Promise<WatchProperties>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default TurboModuleRegistry.getEnforcing<Spec>('PatriotNative');
|