@haykmkrtich/react-native-patriot-native 1.0.6 → 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,50 +1,39 @@
1
1
  apply plugin: 'com.android.library'
2
2
  apply plugin: 'com.facebook.react'
3
3
 
4
- // Поддержка новой архитектуры React Native
5
- def isNewArchitectureEnabled() {
6
- return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
7
- }
8
-
9
4
  android {
10
- compileSdkVersion 34
5
+ compileSdk 35
11
6
  namespace "com.patriotnative"
12
7
 
13
8
  defaultConfig {
14
- minSdkVersion 24 // Повышаем минимальную версию для поддержки новых возможностей
15
- targetSdkVersion 34
9
+ minSdk 24
10
+ targetSdk 35
16
11
 
17
- // Добавляем поддержка 16KB страниц
18
12
  ndk {
19
13
  abiFilters "arm64-v8a", "x86_64"
20
14
  }
21
15
  }
22
16
 
23
17
  compileOptions {
24
- sourceCompatibility JavaVersion.VERSION_17 // Обновляем до Java 17 для RN 0.77
18
+ sourceCompatibility JavaVersion.VERSION_17
25
19
  targetCompatibility JavaVersion.VERSION_17
26
20
  }
27
21
 
28
- // Исправляем packagingOptions на packaging
29
22
  packaging {
30
23
  pickFirst "**/libc++_shared.so"
31
24
  pickFirst "**/libjsc.so"
32
25
  }
33
26
 
34
- // Оптимизация для 16KB страниц
35
- buildFeatures {
36
- prefab true
37
- buildConfig false
27
+ publishing {
28
+ singleVariant("release") {
29
+ withSourcesJar()
30
+ }
38
31
  }
39
32
 
40
- // Поддержка новой архитектуры
41
33
  sourceSets {
42
34
  main {
43
- if (isNewArchitectureEnabled()) {
44
- java.srcDirs += [
45
- "src/newarch/java",
46
- "${project.buildDir}/generated/source/codegen/java"
47
- ]
35
+ if (project.hasProperty("newArchEnabled") && project.property("newArchEnabled") == "true") {
36
+ java.srcDirs += ["src/newarch/java"]
48
37
  } else {
49
38
  java.srcDirs += ["src/oldarch/java"]
50
39
  }
@@ -52,7 +41,6 @@ android {
52
41
  }
53
42
  }
54
43
 
55
- // Поддержка React Native Codegen
56
44
  react {
57
45
  libraryName = "PatriotNative"
58
46
  codegenJavaPackageName = "com.patriotnative"
@@ -63,9 +51,4 @@ dependencies {
63
51
  implementation 'androidx.annotation:annotation:1.7.0'
64
52
  implementation 'com.google.android.gms:play-services-wearable:18.2.0'
65
53
  implementation 'androidx.wear:wear-remote-interactions:1.0.0'
66
-
67
- // Исправляем условие для новой архитектуры
68
- if (isNewArchitectureEnabled()) {
69
- implementation project(":ReactAndroid") // Правильная зависимость для новой архитектуры
70
- }
71
54
  }
@@ -1,25 +1,9 @@
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
-
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>
@@ -1,39 +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 getConnectedWatchProperties(Promise promise);
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 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,29 +1,29 @@
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
- }
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
+ }
@@ -1,17 +1,201 @@
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
- }
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
+ }
@@ -1,23 +1,198 @@
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
- }
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
@@ -13,23 +13,29 @@ try {
13
13
 
14
14
  if (!PatriotNativeModule) {
15
15
  throw new Error(
16
- `PatriotNative module is not properly linked. Please check your installation:
16
+ `PatriotNative module is not properly linked. Please check your installation:
17
17
  1. Run 'npx react-native clean' and rebuild
18
18
  2. Ensure Android dependencies are properly installed
19
19
  3. Check that your React Native version is 0.60+`
20
20
  );
21
21
  }
22
22
 
23
- // Type definitions for better TypeScript support
24
- export interface WatchProperties {
23
+ export interface ConnectedDevice {
25
24
  id: string;
26
25
  displayName: string;
27
26
  isNearby: boolean;
28
27
  type: string;
29
28
  platform: string;
30
- isDisconnected?: boolean;
31
29
  }
32
30
 
31
+ export interface AppInstallStatus {
32
+ isInstalled: boolean;
33
+ installedOnNodes: string[];
34
+ }
35
+
36
+ /**
37
+ * Install a watch face on connected WearOS devices.
38
+ */
33
39
  export const installWatchface = (packageName: string): Promise<void> => {
34
40
  if (Platform.OS !== 'android') {
35
41
  return Promise.reject(new Error('PatriotNative is only supported on Android'));
@@ -37,9 +43,43 @@ export const installWatchface = (packageName: string): Promise<void> => {
37
43
  return PatriotNativeModule.installWatchface(packageName);
38
44
  };
39
45
 
40
- export const getConnectedWatchProperties = (): Promise<WatchProperties> => {
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> => {
41
61
  if (Platform.OS !== 'android') {
42
62
  return Promise.reject(new Error('PatriotNative is only supported on Android'));
43
63
  }
44
- return PatriotNativeModule.getConnectedWatchProperties();
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];
45
85
  };
package/package.json CHANGED
@@ -1,13 +1,20 @@
1
1
  {
2
2
  "name": "@haykmkrtich/react-native-patriot-native",
3
- "version": "1.0.6",
3
+ "version": "1.1.0",
4
4
  "main": "index.ts",
5
5
  "files": [
6
6
  "index.ts",
7
7
  "src/",
8
8
  "android/"
9
9
  ],
10
- "keywords": ["react-native", "wearos", "watchface", "16kb-pages", "rn-0.77", "turbomodule"],
10
+ "keywords": [
11
+ "react-native",
12
+ "wearos",
13
+ "watchface",
14
+ "16kb-pages",
15
+ "rn-0.77",
16
+ "turbomodule"
17
+ ],
11
18
  "author": "Hayk Mkrtich",
12
19
  "license": "MIT",
13
20
  "repository": {
@@ -1,23 +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 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');
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,149 +0,0 @@
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.Promise;
9
- import com.facebook.react.bridge.ReactApplicationContext;
10
- import com.facebook.react.bridge.ReactMethod;
11
- import com.facebook.react.bridge.WritableMap;
12
- import com.facebook.react.bridge.WritableNativeMap;
13
-
14
- import com.google.android.gms.wearable.CapabilityClient;
15
- import com.google.android.gms.wearable.CapabilityInfo;
16
- import com.google.android.gms.wearable.Node;
17
- import com.google.android.gms.wearable.NodeClient;
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;
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=";
28
-
29
- private final ReactApplicationContext reactContext;
30
-
31
- public PatriotNativeModule(ReactApplicationContext reactContext) {
32
- super(reactContext);
33
- this.reactContext = reactContext;
34
- }
35
-
36
- @Override
37
- @NonNull
38
- public String getName() {
39
- return NAME;
40
- }
41
-
42
- @Override
43
- @ReactMethod
44
- public void installWatchface(String packageName, Promise promise) {
45
- try {
46
- Context context = getCurrentActivity();
47
- if (context == null) {
48
- context = getReactApplicationContext();
49
- }
50
-
51
- NodeClient nodeClient = Wearable.getNodeClient(context);
52
- Task<Set<Node>> nodesTask = nodeClient.getConnectedNodes();
53
-
54
- Set<Node> nodes = Tasks.await(nodesTask);
55
-
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;
74
- }
75
- }
76
-
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
- }
84
- promise.resolve(null);
85
- } else {
86
- promise.reject("INSTALL_FAILED", "Failed to send installation request to any connected device");
87
- }
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
- }
94
- }
95
-
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
- }
148
- }
149
- }