Skip to content

Commit 6e48457

Browse files
feat: sync app with watch
1 parent 47d7020 commit 6e48457

File tree

10 files changed

+207
-11
lines changed

10 files changed

+207
-11
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { NativeModules } from 'react-native';
2+
3+
const { WatchBridge } = NativeModules;
4+
5+
export async function checkWatch() {
6+
const status = await WatchBridge.getWatchStatus();
7+
8+
return status;
9+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { NativeModules } from 'react-native';
2+
3+
const { WatchBridge } = NativeModules;
4+
5+
export async function syncWatchOSQuickReplies(replies: string[]) {
6+
if (!Array.isArray(replies)) return false;
7+
8+
try {
9+
const success: boolean = await WatchBridge.syncQuickReplies(replies);
10+
11+
return success;
12+
} catch (e) {
13+
console.error('Failed to send quick replies', e);
14+
return false;
15+
}
16+
}

app/views/UserWatchOSQuickRepliesView/index.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { FormTextInput } from '../../containers/TextInput';
1010
import Chip from '../../containers/Chip';
1111
import { useUserPreferences } from '../../lib/methods/userPreferences';
1212
import { WATCHOS_QUICKREPLIES } from '../../lib/constants/keys';
13+
import { syncWatchOSQuickReplies } from '../../lib/methods/WatchOSQuickReplies/syncReplies';
14+
import { checkWatch } from '../../lib/methods/WatchOSQuickReplies/getWatchStatus';
1315

1416
interface IUserPreferencesViewProps {
1517
navigation: NativeStackNavigationProp<ProfileStackParamList, 'UserPreferencesView'>;
@@ -25,6 +27,16 @@ const UserPreferencesView = ({ navigation }: IUserPreferencesViewProps): JSX.Ele
2527
});
2628
}, [navigation]);
2729

30+
useEffect(() => {
31+
const load = async () => {
32+
const status = await checkWatch();
33+
console.log(status);
34+
const result = await syncWatchOSQuickReplies(quickreplies ?? []);
35+
console.log(result);
36+
};
37+
load();
38+
}, [quickreplies]);
39+
2840
const removeQuickReply = (reply: string) => {
2941
const newReplies = quickreplies?.filter(quickreply => quickreply !== reply);
3042
setQuickreplies(newReplies);

ios/Podfile.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ PODS:
232232
- libwebp/sharpyuv (1.5.0)
233233
- libwebp/webp (1.5.0):
234234
- libwebp/sharpyuv
235-
- MobileCrypto (0.2.0):
235+
- MobileCrypto (0.2.1):
236236
- DoubleConversion
237237
- glog
238238
- hermes-engine
@@ -3082,7 +3082,7 @@ SPEC CHECKSUMS:
30823082
libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7
30833083
libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f
30843084
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
3085-
MobileCrypto: 60a1e43e26a9d6851ae2aa7294b8041c9e9220b7
3085+
MobileCrypto: a424494b2f45bec9dbe60e3f6d16a40aedefe7b7
30863086
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
30873087
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
30883088
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
@@ -3185,7 +3185,7 @@ SPEC CHECKSUMS:
31853185
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
31863186
TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654
31873187
WatermelonDB: 4c846c8cb94eef3cba90fa034d15310163226703
3188-
Yoga: dfabf1234ccd5ac41d1b1d43179f024366ae9831
3188+
Yoga: 2a3a4c38a8441b6359d5e5914d35db7b2b67aebd
31893189
ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5
31903190

31913191
PODFILE CHECKSUM: 199f6fbbe6fb415c822cca992e6152000ac55b3e

ios/RocketChat Watch App/Loaders/WatchSession.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ protocol WatchSessionProtocol {
77
/// Default WatchSession protocol implementation.
88
final class WatchSession: NSObject, WatchSessionProtocol, WCSessionDelegate {
99
private let session: WCSession
10-
10+
11+
@Storage(.quickReplies, defaultValue: [])
12+
private var quickReplies: [String]?
13+
1114
init(session: WCSession) {
1215
self.session = session
1316
super.init()
@@ -46,6 +49,21 @@ final class WatchSession: NSObject, WatchSessionProtocol, WCSessionDelegate {
4649
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
4750

4851
}
52+
53+
// quick replies
54+
func session(
55+
_ session: WCSession,
56+
didReceiveApplicationContext applicationContext: [String : Any]
57+
) {
58+
print(applicationContext)
59+
60+
if let replies = applicationContext["quickReplies"] as? [String] {
61+
quickReplies = replies
62+
print("QUICK REPLIES STORED:", replies)
63+
} else {
64+
print("quickReplies key missing")
65+
}
66+
}
4967
}
5068

5169
/// Retry decorator for WatchSession protocol.

ios/RocketChat Watch App/Storage.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Foundation
22

33
enum StorageKey: String {
44
case currentServer = "current_server"
5+
case quickReplies = "quick_replies"
56
}
67

78
@propertyWrapper

ios/RocketChat Watch App/Views/MessageComposerView.swift

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ struct MessageComposerView: View {
55

66
let room: Room
77
let onSend: (String) -> Void
8+
9+
@Storage(.quickReplies, defaultValue: [])
10+
private var quickReplies: [String]?
811

912
var body: some View {
1013
if room.isReadOnly {
@@ -17,9 +20,52 @@ struct MessageComposerView: View {
1720
Spacer()
1821
}
1922
} else {
20-
TextField("Message", text: $message)
21-
.submitLabel(.send)
22-
.onSubmit(send)
23+
VStack(alignment: .leading, spacing: 4) {
24+
TextField("Message", text: $message)
25+
.submitLabel(.send)
26+
.onSubmit(send)
27+
28+
if let replies = quickReplies, !replies.isEmpty {
29+
ScrollView(.horizontal, showsIndicators: false) {
30+
HStack(spacing: 6) {
31+
ForEach(replies, id: \.self) { reply in
32+
if #available(watchOS 26.0, *) {
33+
Button {
34+
message = reply
35+
send()
36+
} label: {
37+
Text(reply)
38+
.font(.caption2)
39+
.foregroundStyle(.primary)
40+
.padding(.horizontal, 10)
41+
.padding(.vertical, 4)
42+
}
43+
.buttonStyle(.plain)
44+
.glassEffect(.regular)
45+
.clipShape(Capsule())
46+
} else {
47+
Button {
48+
message = reply
49+
send()
50+
} label: {
51+
Text(reply)
52+
.font(.caption2)
53+
.foregroundStyle(.primary)
54+
.padding(.horizontal, 10)
55+
.padding(.vertical, 6)
56+
.background(
57+
Capsule()
58+
.fill(Color.white.opacity(0.12))
59+
)
60+
}
61+
.buttonStyle(.borderedProminent)
62+
}
63+
}
64+
}
65+
}
66+
}
67+
}
68+
2369
}
2470
}
2571

ios/RocketChatRN.xcodeproj/project.pbxproj

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
05F9D701BF644C25192B8E79 /* Pods_defaults_RocketChatRN.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A46AABC73B7E9703E69AF850 /* Pods_defaults_RocketChatRN.framework */; };
1212
0C6E2DE448364EA896869ADF /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B37C79D9BD0742CE936B6982 /* libc++.tbd */; };
1313
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
14+
169F81322F2BDBB700DF8EA5 /* WatchBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 169F812E2F2BDBB700DF8EA5 /* WatchBridge.swift */; };
15+
169F81372F2BFC4000DF8EA5 /* WatchBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 169F81362F2BFC4000DF8EA5 /* WatchBridge.m */; };
16+
169F81382F2BFC4000DF8EA5 /* WatchBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 169F81362F2BFC4000DF8EA5 /* WatchBridge.m */; };
17+
169F81392F2BFCE600DF8EA5 /* WatchBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 169F812E2F2BDBB700DF8EA5 /* WatchBridge.swift */; };
1418
1E01C81C2511208400FEF824 /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E01C81B2511208400FEF824 /* URL+Extensions.swift */; };
1519
1E01C8212511301400FEF824 /* PushResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E01C8202511301400FEF824 /* PushResponse.swift */; };
1620
1E01C8252511303100FEF824 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E01C8242511303100FEF824 /* Notification.swift */; };
@@ -469,6 +473,8 @@
469473
13B07F961A680F5B00A75B9A /* Rocket.Chat Experimental.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Rocket.Chat Experimental.app"; sourceTree = BUILT_PRODUCTS_DIR; };
470474
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = RocketChatRN/Images.xcassets; sourceTree = "<group>"; };
471475
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = RocketChatRN/Info.plist; sourceTree = "<group>"; };
476+
169F812E2F2BDBB700DF8EA5 /* WatchBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchBridge.swift; sourceTree = "<group>"; };
477+
169F81362F2BFC4000DF8EA5 /* WatchBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WatchBridge.m; sourceTree = "<group>"; };
472478
194D9A8897F4A486C2C6F89A /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-NotificationService/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
473479
1E01C81B2511208400FEF824 /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = "<group>"; };
474480
1E01C8202511301400FEF824 /* PushResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushResponse.swift; sourceTree = "<group>"; };
@@ -728,8 +734,10 @@
728734
66C2701E2EBBCB780062725F /* SecureStorage.h */,
729735
66C2701F2EBBCB780062725F /* SecureStorage.m */,
730736
7ACFE7D82DDE48760090D9BC /* AppDelegate.swift */,
737+
169F812E2F2BDBB700DF8EA5 /* WatchBridge.swift */,
731738
A48B46D72D3FFBD200945489 /* A11yFlowModule.h */,
732739
A48B46D82D3FFBD200945489 /* A11yFlowModule.m */,
740+
169F81362F2BFC4000DF8EA5 /* WatchBridge.m */,
733741
7A8B30742BCD9D3F00146A40 /* SSLPinning.h */,
734742
7A8B30752BCD9D3F00146A40 /* SSLPinning.mm */,
735743
65B9A7192AFC24190088956F /* ringtone.mp3 */,
@@ -1825,7 +1833,7 @@
18251833
inputFileListPaths = (
18261834
);
18271835
inputPaths = (
1828-
"$TARGET_BUILD_DIR/$INFOPLIST_PATH",
1836+
$TARGET_BUILD_DIR/$INFOPLIST_PATH,
18291837
);
18301838
name = "Upload source maps to Bugsnag";
18311839
outputFileListPaths = (
@@ -1845,7 +1853,7 @@
18451853
inputFileListPaths = (
18461854
);
18471855
inputPaths = (
1848-
"$TARGET_BUILD_DIR/$INFOPLIST_PATH",
1856+
$TARGET_BUILD_DIR/$INFOPLIST_PATH,
18491857
);
18501858
name = "Upload source maps to Bugsnag";
18511859
outputFileListPaths = (
@@ -2051,10 +2059,12 @@
20512059
1E0426E6251A5467008F022C /* RoomType.swift in Sources */,
20522060
1E76CBCD25152C2C0067298C /* MessageType.swift in Sources */,
20532061
1E76CBC925152C1F0067298C /* PushResponse.swift in Sources */,
2062+
169F81382F2BFC4000DF8EA5 /* WatchBridge.m in Sources */,
20542063
1E76CBD325152C770067298C /* Encryption.swift in Sources */,
20552064
1E76CBC825152C070067298C /* RocketChat.swift in Sources */,
20562065
1E76CBD725152C840067298C /* HTTPMethod.swift in Sources */,
20572066
1E76CBC725152BFF0067298C /* Payload.swift in Sources */,
2067+
169F81322F2BDBB700DF8EA5 /* WatchBridge.swift in Sources */,
20582068
1E76CBD225152C730067298C /* Data+Extensions.swift in Sources */,
20592069
1E76CBD125152C710067298C /* Date+Extensions.swift in Sources */,
20602070
1E76CBD425152C790067298C /* Database.swift in Sources */,
@@ -2330,11 +2340,13 @@
23302340
7AAB3E1F257E6A6E00707CF6 /* RocketChat.swift in Sources */,
23312341
7AAB3E20257E6A6E00707CF6 /* HTTPMethod.swift in Sources */,
23322342
7AAB3E21257E6A6E00707CF6 /* Payload.swift in Sources */,
2343+
169F81392F2BFCE600DF8EA5 /* WatchBridge.swift in Sources */,
23332344
7AAB3E23257E6A6E00707CF6 /* Data+Extensions.swift in Sources */,
23342345
7AAB3E24257E6A6E00707CF6 /* Date+Extensions.swift in Sources */,
23352346
7AAB3E25257E6A6E00707CF6 /* Database.swift in Sources */,
23362347
7ACFE7D92DDE48760090D9BC /* AppDelegate.swift in Sources */,
23372348
1E9A71752B59F36E00477BA2 /* ClientSSL.swift in Sources */,
2349+
169F81372F2BFC4000DF8EA5 /* WatchBridge.m in Sources */,
23382350
A48B46DA2D3FFBD200945489 /* A11yFlowModule.m in Sources */,
23392351
3F56D232A9EBA1C9C749F160 /* MMKVBridge.mm in Sources */,
23402352
7AACF8AE2C94B28B0082844E /* DecryptedContent.swift in Sources */,
@@ -2593,7 +2605,7 @@
25932605
"$(inherited)",
25942606
"$(SRCROOT)/../node_modules/rn-extensions-share/ios/**",
25952607
"$(SRCROOT)/../node_modules/react-native-firebase/ios/RNFirebase/**",
2596-
"$PODS_CONFIGURATION_BUILD_DIR/Firebase",
2608+
$PODS_CONFIGURATION_BUILD_DIR/Firebase,
25972609
);
25982610
INFOPLIST_FILE = ShareRocketChatRN/Info.plist;
25992611
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
@@ -2669,7 +2681,7 @@
26692681
"$(inherited)",
26702682
"$(SRCROOT)/../node_modules/rn-extensions-share/ios/**",
26712683
"$(SRCROOT)/../node_modules/react-native-firebase/ios/RNFirebase/**",
2672-
"$PODS_CONFIGURATION_BUILD_DIR/Firebase",
2684+
$PODS_CONFIGURATION_BUILD_DIR/Firebase,
26732685
);
26742686
INFOPLIST_FILE = ShareRocketChatRN/Info.plist;
26752687
IPHONEOS_DEPLOYMENT_TARGET = 15.1;

ios/WatchBridge.m

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#import <React/RCTBridgeModule.h>
2+
3+
@interface RCT_EXTERN_MODULE(WatchBridge, NSObject)
4+
5+
RCT_EXTERN_METHOD(
6+
getWatchStatus:(RCTPromiseResolveBlock)resolve
7+
rejecter:(RCTPromiseRejectBlock)reject
8+
)
9+
10+
RCT_EXTERN_METHOD(
11+
syncQuickReplies:(NSArray *)replies
12+
resolver:(RCTPromiseResolveBlock)resolve
13+
rejecter:(RCTPromiseRejectBlock)reject
14+
)
15+
16+
@end

ios/WatchBridge.swift

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import Foundation
2+
import React
3+
import WatchConnectivity
4+
5+
@objc(WatchBridge)
6+
final class WatchBridge: NSObject {
7+
8+
@objc(syncQuickReplies:resolver:rejecter:)
9+
func syncQuickReplies(
10+
_ replies: [String],
11+
resolver resolve: RCTPromiseResolveBlock,
12+
rejecter reject: RCTPromiseRejectBlock
13+
) {
14+
print("WatchBridge.syncData called")
15+
print("Value:", replies)
16+
17+
guard WCSession.isSupported() else {
18+
resolve(false)
19+
return
20+
}
21+
22+
let session = WCSession.default
23+
24+
guard session.isPaired, session.isWatchAppInstalled else {
25+
resolve(false)
26+
return
27+
}
28+
29+
do {
30+
try session.updateApplicationContext([
31+
"quickReplies": replies
32+
])
33+
resolve(true)
34+
} catch {
35+
reject("SYNC_FAILED", error.localizedDescription, error)
36+
}
37+
}
38+
39+
@objc(getWatchStatus:rejecter:)
40+
func getWatchStatus(
41+
resolver resolve: RCTPromiseResolveBlock,
42+
rejecter reject: RCTPromiseRejectBlock
43+
) {
44+
guard WCSession.isSupported() else {
45+
resolve([
46+
"isSupported": false,
47+
"isPaired": false,
48+
"isWatchAppInstalled": false
49+
])
50+
return
51+
}
52+
53+
let session = WCSession.default
54+
55+
resolve([
56+
"isSupported": true,
57+
"isPaired": session.isPaired,
58+
"isWatchAppInstalled": session.isWatchAppInstalled
59+
])
60+
}
61+
}
62+
63+
extension WatchBridge: RCTBridgeModule {
64+
static func moduleName() -> String! { "WatchBridge" }
65+
static func requiresMainQueueSetup() -> Bool { false }
66+
}

0 commit comments

Comments
 (0)