@haykmkrtich/react-native-patriot-native 1.0.5 → 1.1.0

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
@@ -9,7 +9,7 @@
9
9
  ## ✨ What's New in v1.0.5
10
10
 
11
11
  - 🔍 **Device Detection**: New `getConnectedWatchProperties()` function
12
- - 📱 **Multi-Platform Support**: Detect WearOS, Fitbit, and other wearable devices
12
+ - 📱 **Multi-Platform Support**: Detect WearOS, Fitbit, and other wearable devices
13
13
  - 🏷️ **Device Information**: Get name, platform, type, and unique ID
14
14
  - 🛡️ **Enhanced Error Handling**: Improved disconnection detection
15
15
 
@@ -112,7 +112,7 @@ dependencies {
112
112
  ### WearOS Development Best Practices
113
113
 
114
114
  > ⚠️ **Important**: For Google Play Console compliance, create **two applications** with identical package names:
115
- > - 📱 Mobile companion app (React Native)
115
+ > - 📱 Mobile companion app (React Native)
116
116
  > - ⌚ WearOS watch face app
117
117
 
118
118
  This ensures proper functionality and distribution through Google Play Store.
@@ -201,4 +201,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
201
201
 
202
202
  Made with ❤️ for the React Native community
203
203
 
204
- </div>
204
+ </div>
@@ -1,24 +1,54 @@
1
-
2
1
  apply plugin: 'com.android.library'
3
2
  apply plugin: 'com.facebook.react'
4
3
 
5
4
  android {
6
- compileSdkVersion 34
5
+ compileSdk 35
7
6
  namespace "com.patriotnative"
7
+
8
8
  defaultConfig {
9
- minSdkVersion 21
10
- targetSdkVersion 34
9
+ minSdk 24
10
+ targetSdk 35
11
+
12
+ ndk {
13
+ abiFilters "arm64-v8a", "x86_64"
14
+ }
11
15
  }
12
16
 
13
17
  compileOptions {
14
- sourceCompatibility JavaVersion.VERSION_11
15
- targetCompatibility JavaVersion.VERSION_11
18
+ sourceCompatibility JavaVersion.VERSION_17
19
+ targetCompatibility JavaVersion.VERSION_17
16
20
  }
21
+
22
+ packaging {
23
+ pickFirst "**/libc++_shared.so"
24
+ pickFirst "**/libjsc.so"
25
+ }
26
+
27
+ publishing {
28
+ singleVariant("release") {
29
+ withSourcesJar()
30
+ }
31
+ }
32
+
33
+ sourceSets {
34
+ main {
35
+ if (project.hasProperty("newArchEnabled") && project.property("newArchEnabled") == "true") {
36
+ java.srcDirs += ["src/newarch/java"]
37
+ } else {
38
+ java.srcDirs += ["src/oldarch/java"]
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ react {
45
+ libraryName = "PatriotNative"
46
+ codegenJavaPackageName = "com.patriotnative"
17
47
  }
18
48
 
19
49
  dependencies {
20
- implementation "com.facebook.react:react-android:+"
21
- implementation 'androidx.annotation:annotation:1.3.0'
22
- implementation 'com.google.android.gms:play-services-wearable:18.1.0'
50
+ implementation "com.facebook.react:react-android"
51
+ implementation 'androidx.annotation:annotation:1.7.0'
52
+ implementation 'com.google.android.gms:play-services-wearable:18.2.0'
23
53
  implementation 'androidx.wear:wear-remote-interactions:1.0.0'
24
54
  }
@@ -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,9 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
+
4
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
5
+ <uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
6
+
7
+ <application android:extractNativeLibs="false" />
8
+
9
+ </manifest>
@@ -0,0 +1,47 @@
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 getConnectedDevices(Promise promise);
39
+
40
+ @ReactMethod
41
+ @DoNotStrip
42
+ public abstract void isAppInstalledOnWatch(String packageName, Promise promise);
43
+
44
+ @ReactMethod
45
+ @DoNotStrip
46
+ public abstract void sendMessageToWatch(String nodeId, String path, String data, Promise promise);
47
+ }
@@ -1,25 +1,29 @@
1
-
2
- package com.patriotnative;
3
-
4
- import com.facebook.react.ReactPackage;
5
- import com.facebook.react.bridge.NativeModule;
6
- import com.facebook.react.bridge.ReactApplicationContext;
7
- import com.facebook.react.uimanager.ViewManager;
8
-
9
- import java.util.ArrayList;
10
- import java.util.Collections;
11
- import java.util.List;
12
-
13
- public class PatriotNativePackage implements ReactPackage {
14
- @Override
15
- public List<NativeModule> createNativeModules(ReactApplicationContext context) {
16
- List<NativeModule> modules = new ArrayList<>();
17
- modules.add(new PatriotNativeModule(context));
18
- return modules;
19
- }
20
-
21
- @Override
22
- public List<ViewManager> createViewManagers(ReactApplicationContext context) {
23
- return Collections.emptyList();
24
- }
25
- }
1
+ package com.patriotnative;
2
+
3
+ import androidx.annotation.NonNull;
4
+
5
+ import com.facebook.react.ReactPackage;
6
+ import com.facebook.react.bridge.NativeModule;
7
+ import com.facebook.react.bridge.ReactApplicationContext;
8
+ import com.facebook.react.uimanager.ViewManager;
9
+
10
+ import java.util.ArrayList;
11
+ import java.util.Collections;
12
+ import java.util.List;
13
+
14
+ public class PatriotNativePackage implements ReactPackage {
15
+
16
+ @NonNull
17
+ @Override
18
+ public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
19
+ List<NativeModule> modules = new ArrayList<>();
20
+ modules.add(new PatriotNativeModule(reactContext));
21
+ return modules;
22
+ }
23
+
24
+ @NonNull
25
+ @Override
26
+ public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
27
+ return Collections.emptyList();
28
+ }
29
+ }
@@ -0,0 +1,201 @@
1
+ package com.patriotnative;
2
+
3
+ import android.content.Context;
4
+ import android.widget.Toast;
5
+
6
+ import androidx.annotation.NonNull;
7
+
8
+ import com.facebook.react.bridge.Arguments;
9
+ import com.facebook.react.bridge.Promise;
10
+ import com.facebook.react.bridge.ReactApplicationContext;
11
+ import com.facebook.react.bridge.ReactMethod;
12
+ import com.facebook.react.bridge.WritableArray;
13
+ import com.facebook.react.bridge.WritableMap;
14
+
15
+ import com.google.android.gms.wearable.CapabilityClient;
16
+ import com.google.android.gms.wearable.CapabilityInfo;
17
+ import com.google.android.gms.wearable.Node;
18
+ import com.google.android.gms.wearable.NodeClient;
19
+ import com.google.android.gms.wearable.Wearable;
20
+ import com.google.android.gms.tasks.Task;
21
+ import com.google.android.gms.tasks.Tasks;
22
+
23
+ import java.util.Set;
24
+ import java.util.concurrent.ExecutionException;
25
+
26
+ /**
27
+ * NewArch implementation of PatriotNative (TurboModule) for React Native 0.77+
28
+ */
29
+ public class PatriotNativeModule extends NativePatriotNativeSpec {
30
+ private static final String NAME = "PatriotNative";
31
+ private static final String PLAY_STORE_APP_URI = "market://details?id=";
32
+
33
+ private final ReactApplicationContext reactContext;
34
+
35
+ public PatriotNativeModule(ReactApplicationContext reactContext) {
36
+ super(reactContext);
37
+ this.reactContext = reactContext;
38
+ }
39
+
40
+ @Override
41
+ @NonNull
42
+ public String getName() {
43
+ return NAME;
44
+ }
45
+
46
+ private Context getContext() {
47
+ Context context = getCurrentActivity();
48
+ if (context == null) {
49
+ context = getReactApplicationContext();
50
+ }
51
+ return context;
52
+ }
53
+
54
+ @Override
55
+ @ReactMethod
56
+ public void installWatchface(String packageName, Promise promise) {
57
+ try {
58
+ Context context = getContext();
59
+
60
+ NodeClient nodeClient = Wearable.getNodeClient(context);
61
+ Task<Set<Node>> nodesTask = nodeClient.getConnectedNodes();
62
+ Set<Node> nodes = Tasks.await(nodesTask);
63
+
64
+ if (nodes.isEmpty()) {
65
+ promise.reject("NO_NODES", "No connected WearOS devices found");
66
+ return;
67
+ }
68
+
69
+ String appUri = PLAY_STORE_APP_URI + packageName;
70
+ boolean installationSent = false;
71
+
72
+ for (Node node : nodes) {
73
+ try {
74
+ Task<Integer> sendTask = Wearable.getMessageClient(context)
75
+ .sendMessage(node.getId(), "/install_watchface", appUri.getBytes());
76
+ Tasks.await(sendTask);
77
+ installationSent = true;
78
+ } catch (Exception e) {
79
+ continue;
80
+ }
81
+ }
82
+
83
+ if (installationSent) {
84
+ if (getCurrentActivity() != null) {
85
+ final Context finalContext = context;
86
+ getCurrentActivity().runOnUiThread(() ->
87
+ Toast.makeText(finalContext, "Check your watch for installation prompt", Toast.LENGTH_LONG).show()
88
+ );
89
+ }
90
+ promise.resolve(null);
91
+ } else {
92
+ promise.reject("INSTALL_FAILED", "Failed to send installation request to any connected device");
93
+ }
94
+
95
+ } catch (ExecutionException | InterruptedException e) {
96
+ promise.reject("INSTALL_FAILED", "Failed to communicate with WearOS device: " + e.getMessage());
97
+ } catch (Exception e) {
98
+ promise.reject("INSTALL_FAILED", "Unexpected error: " + e.getMessage());
99
+ }
100
+ }
101
+
102
+ @Override
103
+ @ReactMethod
104
+ public void getConnectedDevices(Promise promise) {
105
+ try {
106
+ Context context = getContext();
107
+
108
+ NodeClient nodeClient = Wearable.getNodeClient(context);
109
+ Task<Set<Node>> nodesTask = nodeClient.getConnectedNodes();
110
+ Set<Node> nodes = Tasks.await(nodesTask);
111
+
112
+ CapabilityClient capabilityClient = Wearable.getCapabilityClient(context);
113
+
114
+ WritableArray devicesArray = Arguments.createArray();
115
+
116
+ for (Node node : nodes) {
117
+ WritableMap deviceMap = Arguments.createMap();
118
+ deviceMap.putString("id", node.getId());
119
+ deviceMap.putString("displayName", node.getDisplayName());
120
+ deviceMap.putBoolean("isNearby", node.isNearby());
121
+ deviceMap.putString("type", "watch");
122
+
123
+ try {
124
+ Task<CapabilityInfo> capabilityTask = capabilityClient.getCapability(
125
+ "wear_app_runtime", CapabilityClient.FILTER_REACHABLE);
126
+ CapabilityInfo capabilityInfo = Tasks.await(capabilityTask);
127
+
128
+ boolean isWearOS = false;
129
+ for (Node capableNode : capabilityInfo.getNodes()) {
130
+ if (capableNode.getId().equals(node.getId())) {
131
+ isWearOS = true;
132
+ break;
133
+ }
134
+ }
135
+ deviceMap.putString("platform", isWearOS ? "wearOS" : "unknown");
136
+ } catch (Exception e) {
137
+ deviceMap.putString("platform", "wearOS");
138
+ }
139
+
140
+ devicesArray.pushMap(deviceMap);
141
+ }
142
+
143
+ promise.resolve(devicesArray);
144
+
145
+ } catch (ExecutionException | InterruptedException e) {
146
+ promise.reject("DETECTION_FAILED", "Failed to get connected devices: " + e.getMessage());
147
+ } catch (Exception e) {
148
+ promise.reject("DETECTION_FAILED", "Unexpected error: " + e.getMessage());
149
+ }
150
+ }
151
+
152
+ @Override
153
+ @ReactMethod
154
+ public void isAppInstalledOnWatch(String packageName, Promise promise) {
155
+ try {
156
+ Context context = getContext();
157
+
158
+ CapabilityClient capabilityClient = Wearable.getCapabilityClient(context);
159
+ Task<CapabilityInfo> capabilityTask = capabilityClient.getCapability(
160
+ packageName, CapabilityClient.FILTER_ALL);
161
+ CapabilityInfo capabilityInfo = Tasks.await(capabilityTask);
162
+
163
+ Set<Node> capableNodes = capabilityInfo.getNodes();
164
+
165
+ WritableMap result = Arguments.createMap();
166
+ result.putBoolean("isInstalled", !capableNodes.isEmpty());
167
+
168
+ WritableArray nodeIds = Arguments.createArray();
169
+ for (Node node : capableNodes) {
170
+ nodeIds.pushString(node.getId());
171
+ }
172
+ result.putArray("installedOnNodes", nodeIds);
173
+
174
+ promise.resolve(result);
175
+
176
+ } catch (ExecutionException | InterruptedException e) {
177
+ promise.reject("CHECK_FAILED", "Failed to check app installation: " + e.getMessage());
178
+ } catch (Exception e) {
179
+ promise.reject("CHECK_FAILED", "Unexpected error: " + e.getMessage());
180
+ }
181
+ }
182
+
183
+ @Override
184
+ @ReactMethod
185
+ public void sendMessageToWatch(String nodeId, String path, String data, Promise promise) {
186
+ try {
187
+ Context context = getContext();
188
+
189
+ Task<Integer> sendTask = Wearable.getMessageClient(context)
190
+ .sendMessage(nodeId, path, data.getBytes());
191
+ Tasks.await(sendTask);
192
+
193
+ promise.resolve(null);
194
+
195
+ } catch (ExecutionException | InterruptedException e) {
196
+ promise.reject("MESSAGE_FAILED", "Failed to send message: " + e.getMessage());
197
+ } catch (Exception e) {
198
+ promise.reject("MESSAGE_FAILED", "Unexpected error: " + e.getMessage());
199
+ }
200
+ }
201
+ }
@@ -0,0 +1,198 @@
1
+ package com.patriotnative;
2
+
3
+ import android.content.Context;
4
+ import android.widget.Toast;
5
+
6
+ import androidx.annotation.NonNull;
7
+
8
+ import com.facebook.react.bridge.Arguments;
9
+ import com.facebook.react.bridge.Promise;
10
+ import com.facebook.react.bridge.ReactApplicationContext;
11
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
12
+ import com.facebook.react.bridge.ReactMethod;
13
+ import com.facebook.react.bridge.WritableArray;
14
+ import com.facebook.react.bridge.WritableMap;
15
+
16
+ import com.google.android.gms.wearable.CapabilityClient;
17
+ import com.google.android.gms.wearable.CapabilityInfo;
18
+ import com.google.android.gms.wearable.Node;
19
+ import com.google.android.gms.wearable.NodeClient;
20
+ import com.google.android.gms.wearable.Wearable;
21
+ import com.google.android.gms.tasks.Task;
22
+ import com.google.android.gms.tasks.Tasks;
23
+
24
+ import java.util.Set;
25
+ import java.util.concurrent.ExecutionException;
26
+
27
+ /**
28
+ * OldArch implementation of PatriotNative (legacy bridge) for React Native < 0.77
29
+ */
30
+ public class PatriotNativeModule extends ReactContextBaseJavaModule {
31
+ private static final String NAME = "PatriotNative";
32
+ private static final String PLAY_STORE_APP_URI = "market://details?id=";
33
+
34
+ private final ReactApplicationContext reactContext;
35
+
36
+ public PatriotNativeModule(ReactApplicationContext reactContext) {
37
+ super(reactContext);
38
+ this.reactContext = reactContext;
39
+ }
40
+
41
+ @Override
42
+ @NonNull
43
+ public String getName() {
44
+ return NAME;
45
+ }
46
+
47
+ private Context getContext() {
48
+ Context context = getCurrentActivity();
49
+ if (context == null) {
50
+ context = getReactApplicationContext();
51
+ }
52
+ return context;
53
+ }
54
+
55
+ @ReactMethod
56
+ public void installWatchface(String packageName, Promise promise) {
57
+ try {
58
+ Context context = getContext();
59
+
60
+ NodeClient nodeClient = Wearable.getNodeClient(context);
61
+ Task<Set<Node>> nodesTask = nodeClient.getConnectedNodes();
62
+ Set<Node> nodes = Tasks.await(nodesTask);
63
+
64
+ if (nodes.isEmpty()) {
65
+ promise.reject("NO_NODES", "No connected WearOS devices found");
66
+ return;
67
+ }
68
+
69
+ String appUri = PLAY_STORE_APP_URI + packageName;
70
+ boolean installationSent = false;
71
+
72
+ for (Node node : nodes) {
73
+ try {
74
+ Task<Integer> sendTask = Wearable.getMessageClient(context)
75
+ .sendMessage(node.getId(), "/install_watchface", appUri.getBytes());
76
+ Tasks.await(sendTask);
77
+ installationSent = true;
78
+ } catch (Exception e) {
79
+ continue;
80
+ }
81
+ }
82
+
83
+ if (installationSent) {
84
+ if (getCurrentActivity() != null) {
85
+ final Context finalContext = context;
86
+ getCurrentActivity().runOnUiThread(() ->
87
+ Toast.makeText(finalContext, "Check your watch for installation prompt", Toast.LENGTH_LONG).show()
88
+ );
89
+ }
90
+ promise.resolve(null);
91
+ } else {
92
+ promise.reject("INSTALL_FAILED", "Failed to send installation request to any connected device");
93
+ }
94
+
95
+ } catch (ExecutionException | InterruptedException e) {
96
+ promise.reject("INSTALL_FAILED", "Failed to communicate with WearOS device: " + e.getMessage());
97
+ } catch (Exception e) {
98
+ promise.reject("INSTALL_FAILED", "Unexpected error: " + e.getMessage());
99
+ }
100
+ }
101
+
102
+ @ReactMethod
103
+ public void getConnectedDevices(Promise promise) {
104
+ try {
105
+ Context context = getContext();
106
+
107
+ NodeClient nodeClient = Wearable.getNodeClient(context);
108
+ Task<Set<Node>> nodesTask = nodeClient.getConnectedNodes();
109
+ Set<Node> nodes = Tasks.await(nodesTask);
110
+
111
+ CapabilityClient capabilityClient = Wearable.getCapabilityClient(context);
112
+
113
+ WritableArray devicesArray = Arguments.createArray();
114
+
115
+ for (Node node : nodes) {
116
+ WritableMap deviceMap = Arguments.createMap();
117
+ deviceMap.putString("id", node.getId());
118
+ deviceMap.putString("displayName", node.getDisplayName());
119
+ deviceMap.putBoolean("isNearby", node.isNearby());
120
+ deviceMap.putString("type", "watch");
121
+
122
+ try {
123
+ Task<CapabilityInfo> capabilityTask = capabilityClient.getCapability(
124
+ "wear_app_runtime", CapabilityClient.FILTER_REACHABLE);
125
+ CapabilityInfo capabilityInfo = Tasks.await(capabilityTask);
126
+
127
+ boolean isWearOS = false;
128
+ for (Node capableNode : capabilityInfo.getNodes()) {
129
+ if (capableNode.getId().equals(node.getId())) {
130
+ isWearOS = true;
131
+ break;
132
+ }
133
+ }
134
+ deviceMap.putString("platform", isWearOS ? "wearOS" : "unknown");
135
+ } catch (Exception e) {
136
+ deviceMap.putString("platform", "wearOS");
137
+ }
138
+
139
+ devicesArray.pushMap(deviceMap);
140
+ }
141
+
142
+ promise.resolve(devicesArray);
143
+
144
+ } catch (ExecutionException | InterruptedException e) {
145
+ promise.reject("DETECTION_FAILED", "Failed to get connected devices: " + e.getMessage());
146
+ } catch (Exception e) {
147
+ promise.reject("DETECTION_FAILED", "Unexpected error: " + e.getMessage());
148
+ }
149
+ }
150
+
151
+ @ReactMethod
152
+ public void isAppInstalledOnWatch(String packageName, Promise promise) {
153
+ try {
154
+ Context context = getContext();
155
+
156
+ CapabilityClient capabilityClient = Wearable.getCapabilityClient(context);
157
+ Task<CapabilityInfo> capabilityTask = capabilityClient.getCapability(
158
+ packageName, CapabilityClient.FILTER_ALL);
159
+ CapabilityInfo capabilityInfo = Tasks.await(capabilityTask);
160
+
161
+ Set<Node> capableNodes = capabilityInfo.getNodes();
162
+
163
+ WritableMap result = Arguments.createMap();
164
+ result.putBoolean("isInstalled", !capableNodes.isEmpty());
165
+
166
+ WritableArray nodeIds = Arguments.createArray();
167
+ for (Node node : capableNodes) {
168
+ nodeIds.pushString(node.getId());
169
+ }
170
+ result.putArray("installedOnNodes", nodeIds);
171
+
172
+ promise.resolve(result);
173
+
174
+ } catch (ExecutionException | InterruptedException e) {
175
+ promise.reject("CHECK_FAILED", "Failed to check app installation: " + e.getMessage());
176
+ } catch (Exception e) {
177
+ promise.reject("CHECK_FAILED", "Unexpected error: " + e.getMessage());
178
+ }
179
+ }
180
+
181
+ @ReactMethod
182
+ public void sendMessageToWatch(String nodeId, String path, String data, Promise promise) {
183
+ try {
184
+ Context context = getContext();
185
+
186
+ Task<Integer> sendTask = Wearable.getMessageClient(context)
187
+ .sendMessage(nodeId, path, data.getBytes());
188
+ Tasks.await(sendTask);
189
+
190
+ promise.resolve(null);
191
+
192
+ } catch (ExecutionException | InterruptedException e) {
193
+ promise.reject("MESSAGE_FAILED", "Failed to send message: " + e.getMessage());
194
+ } catch (Exception e) {
195
+ promise.reject("MESSAGE_FAILED", "Unexpected error: " + e.getMessage());
196
+ }
197
+ }
198
+ }
package/index.ts CHANGED
@@ -1,16 +1,85 @@
1
+ import { NativeModules, Platform } from 'react-native';
1
2
 
2
- import { NativeModules } from 'react-native';
3
+ // Import TurboModule spec for RN 0.77+ compatibility
4
+ let PatriotNativeModule: any;
3
5
 
4
- type PatriotNativeType = {
5
- installWatchface(packageName: string): Promise<void>;
6
- getConnectedWatchProperties(): Promise<{ id: string; displayName: string; isNearby: boolean; type: string; platform: string }>;
7
- };
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
+ }
8
13
 
9
- const { PatriotNative } = NativeModules as { PatriotNative: PatriotNativeType };
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
+ }
10
22
 
11
- if (!PatriotNative) {
12
- throw new Error('PatriotNative module is not linked properly.');
23
+ export interface ConnectedDevice {
24
+ id: string;
25
+ displayName: string;
26
+ isNearby: boolean;
27
+ type: string;
28
+ platform: string;
13
29
  }
14
30
 
15
- export const installWatchface = PatriotNative.installWatchface;
16
- export const getConnectedWatchProperties = PatriotNative.getConnectedWatchProperties;
31
+ export interface AppInstallStatus {
32
+ isInstalled: boolean;
33
+ installedOnNodes: string[];
34
+ }
35
+
36
+ /**
37
+ * Install a watch face on connected WearOS devices.
38
+ */
39
+ export const installWatchface = (packageName: string): Promise<void> => {
40
+ if (Platform.OS !== 'android') {
41
+ return Promise.reject(new Error('PatriotNative is only supported on Android'));
42
+ }
43
+ return PatriotNativeModule.installWatchface(packageName);
44
+ };
45
+
46
+ /**
47
+ * Get all connected WearOS devices with full details (id, name, proximity, type, platform).
48
+ * Returns an empty array if no devices are connected.
49
+ */
50
+ export const getConnectedDevices = (): Promise<ConnectedDevice[]> => {
51
+ if (Platform.OS !== 'android') {
52
+ return Promise.reject(new Error('PatriotNative is only supported on Android'));
53
+ }
54
+ return PatriotNativeModule.getConnectedDevices();
55
+ };
56
+
57
+ /**
58
+ * Check if a specific app/capability is installed on connected watches.
59
+ */
60
+ export const isAppInstalledOnWatch = (packageName: string): Promise<AppInstallStatus> => {
61
+ if (Platform.OS !== 'android') {
62
+ return Promise.reject(new Error('PatriotNative is only supported on Android'));
63
+ }
64
+ return PatriotNativeModule.isAppInstalledOnWatch(packageName);
65
+ };
66
+
67
+ /**
68
+ * Send a custom message to a specific watch node.
69
+ */
70
+ export const sendMessageToWatch = (nodeId: string, path: string, data: string = ''): Promise<void> => {
71
+ if (Platform.OS !== 'android') {
72
+ return Promise.reject(new Error('PatriotNative is only supported on Android'));
73
+ }
74
+ return PatriotNativeModule.sendMessageToWatch(nodeId, path, data);
75
+ };
76
+
77
+ // Backward compatibility
78
+ /** @deprecated Use getConnectedDevices() instead. Returns the first device or { isDisconnected: true }. */
79
+ export const getConnectedWatchProperties = async (): Promise<ConnectedDevice & { isDisconnected?: boolean }> => {
80
+ const devices = await getConnectedDevices();
81
+ if (devices.length === 0) {
82
+ return { isDisconnected: true } as any;
83
+ }
84
+ return devices[0];
85
+ };
package/package.json CHANGED
@@ -1,12 +1,20 @@
1
1
  {
2
2
  "name": "@haykmkrtich/react-native-patriot-native",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
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": [
11
+ "react-native",
12
+ "wearos",
13
+ "watchface",
14
+ "16kb-pages",
15
+ "rn-0.77",
16
+ "turbomodule"
17
+ ],
10
18
  "author": "Hayk Mkrtich",
11
19
  "license": "MIT",
12
20
  "repository": {
@@ -15,5 +23,25 @@
15
23
  },
16
24
  "publishConfig": {
17
25
  "access": "public"
26
+ },
27
+ "peerDependencies": {
28
+ "react-native": ">=0.60.0"
29
+ },
30
+ "engines": {
31
+ "node": ">=16.0.0"
32
+ },
33
+ "react-native": {
34
+ "android": {
35
+ "sourceDir": "android",
36
+ "packageImportPath": "import com.patriotnative.PatriotNativePackage;"
37
+ }
38
+ },
39
+ "codegenConfig": {
40
+ "name": "PatriotNative",
41
+ "type": "modules",
42
+ "jsSrcsDir": "src",
43
+ "android": {
44
+ "javaPackageName": "com.patriotnative"
45
+ }
18
46
  }
19
47
  }
@@ -0,0 +1,16 @@
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 Spec extends TurboModule {
10
+ installWatchface(packageName: string): Promise<void>;
11
+ getConnectedDevices(): Promise<Object>;
12
+ isAppInstalledOnWatch(packageName: string): Promise<Object>;
13
+ sendMessageToWatch(nodeId: string, path: string, data: string): Promise<void>;
14
+ }
15
+
16
+ export default TurboModuleRegistry.getEnforcing<Spec>('PatriotNative');
@@ -1,116 +0,0 @@
1
-
2
- package com.patriotnative;
3
-
4
- import android.content.Intent;
5
- import android.net.Uri;
6
- import android.os.Handler;
7
- import android.os.Looper;
8
- import android.widget.Toast;
9
-
10
- import androidx.annotation.NonNull;
11
- import androidx.wear.remote.interactions.RemoteActivityHelper;
12
-
13
- import com.facebook.react.bridge.ReactApplicationContext;
14
- import com.facebook.react.bridge.ReactContextBaseJavaModule;
15
- import com.facebook.react.bridge.ReactMethod;
16
- import com.facebook.react.bridge.Promise;
17
-
18
- import com.google.android.gms.tasks.Task;
19
- import com.google.android.gms.tasks.Tasks;
20
- import com.google.android.gms.wearable.Node;
21
- import com.google.android.gms.wearable.Wearable;
22
-
23
- import java.util.List;
24
- import java.util.concurrent.Executors;
25
- import com.facebook.react.bridge.WritableMap;
26
- import com.facebook.react.bridge.Arguments;
27
-
28
- public class PatriotNativeModule extends ReactContextBaseJavaModule {
29
- private final ReactApplicationContext reactContext;
30
-
31
- public PatriotNativeModule(ReactApplicationContext context) {
32
- super(context);
33
- this.reactContext = context;
34
- }
35
-
36
- @NonNull
37
- @Override
38
- public String getName() {
39
- return "PatriotNative";
40
- }
41
-
42
- @ReactMethod
43
- public void installWatchface(String packageName, Promise promise) {
44
- new Thread(() -> {
45
- try {
46
- Task<List<Node>> nodeListTask = Wearable.getNodeClient(reactContext).getConnectedNodes();
47
- List<Node> nodes = Tasks.await(nodeListTask);
48
-
49
- if (nodes.isEmpty()) {
50
- showToast("Watch not connected");
51
- promise.reject("NO_NODES", "No connected WearOS device found.");
52
- return;
53
- }
54
-
55
- for (Node node : nodes) {
56
- Intent intent = new Intent(Intent.ACTION_VIEW);
57
- intent.addCategory(Intent.CATEGORY_BROWSABLE);
58
- intent.setData(Uri.parse("market://details?id=" + packageName));
59
-
60
- RemoteActivityHelper helper = new RemoteActivityHelper(reactContext, Executors.newSingleThreadExecutor());
61
- helper.startRemoteActivity(intent, node.getId());
62
- }
63
-
64
- showToast("Check your watch");
65
- promise.resolve(null);
66
- } catch (Exception e) {
67
- promise.reject("INSTALL_FAILED", e.getMessage());
68
- }
69
- }).start();
70
- }
71
-
72
- @ReactMethod
73
- public void getConnectedWatchProperties(Promise promise) {
74
- Executors.newSingleThreadExecutor().execute(() -> {
75
- try {
76
- List<Node> nodes = Tasks.await(Wearable.getNodeClient(reactContext).getConnectedNodes());
77
- if (nodes == null || nodes.isEmpty()) {
78
- WritableMap result = Arguments.createMap();
79
- result.putBoolean("isDisconnected", true);
80
- promise.resolve(result);
81
- return;
82
- }
83
-
84
- for (Node node : nodes) {
85
- String displayName = node.getDisplayName().toLowerCase();
86
-
87
- // Простой фильтр: часы по ключевым словам
88
- if (displayName.contains("watch") || displayName.contains("fitbit") || displayName.contains("wear")) {
89
- WritableMap map = Arguments.createMap();
90
- map.putString("id", node.getId());
91
- map.putString("displayName", node.getDisplayName());
92
- map.putBoolean("isNearby", node.isNearby());
93
- map.putString("type", "watch");
94
- map.putString("platform", displayName.contains("fitbit") ? "fitbit" : "wearOS");
95
-
96
- promise.resolve(map);
97
- return;
98
- }
99
- }
100
-
101
- // Если ни одно устройство не подошло
102
- WritableMap result = Arguments.createMap();
103
- result.putBoolean("isDisconnected", true);
104
- promise.resolve(result);
105
- } catch (Exception e) {
106
- promise.reject("WATCH_DETECTION_FAILED", e.getMessage());
107
- }
108
- });
109
- }
110
-
111
-
112
- private void showToast(String msg) {
113
- new Handler(Looper.getMainLooper()).post(() ->
114
- Toast.makeText(reactContext, msg, Toast.LENGTH_SHORT).show());
115
- }
116
- }