Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"abac_removed_user_from_the_room": "was removed by ABAC",
"ABAC_room_attributes": "Room attributes",
"accept": "Accept",
"WatchOS_Quick_Replies_Description":"Enter Quick Replies to Display on Apple Watch",
"Accessibility": "Accessibility",
"Accessibility_and_Appearance": "Accessibility & appearance",
"Accessibility_statement": "Accessibility statement",
Expand All @@ -33,6 +34,7 @@
"Add_server": "Add workspace",
"Add_thread_reply": "Add thread reply",
"Add_users": "Add users",
"Add_Quick_Reply":"Add Quick Reply",
"added__roomName__to_this_team": "added #{{roomName}} to this team",
"Added__username__to_this_team": "added @{{user_added}} to this team",
"Admin_Panel": "Admin panel",
Expand Down Expand Up @@ -977,6 +979,7 @@
"Waiting_for_answer": "Waiting for answer",
"Waiting_for_network": "Waiting for network...",
"Waiting_for_server_connection": "Waiting for server connection",
"WatchOS_Quick_Replies":"WatchOS Quick Replies",
"Websocket_disabled": "Websocket is disabled for this workspace.\n{{contact}}",
"What_are_you_doing_right_now": "What are you doing right now?",
"Whats_the_password_for_your_certificate": "What's the password for your certificate?",
Expand Down
1 change: 1 addition & 0 deletions app/lib/constants/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY';
export const TOKEN_KEY = 'reactnativemeteor_usertoken';
export const CURRENT_SERVER = 'currentServer';
export const CERTIFICATE_KEY = 'RC_CERTIFICATE_KEY';
export const WATCHOS_QUICKREPLIES = 'RC_WATCHOS_QUICKREPIES';
9 changes: 9 additions & 0 deletions app/lib/methods/WatchOSQuickReplies/getWatchStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { NativeModules } from 'react-native';

const { WatchBridge } = NativeModules;

export async function checkWatch() {
const status = await WatchBridge.getWatchStatus();

return status;
}
16 changes: 16 additions & 0 deletions app/lib/methods/WatchOSQuickReplies/syncReplies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { NativeModules } from 'react-native';

const { WatchBridge } = NativeModules;

export async function syncWatchOSQuickReplies(replies: string[]) {
if (!Array.isArray(replies)) return false;

try {
const success: boolean = await WatchBridge.syncQuickReplies(replies);

return success;
} catch (e) {
console.error('Failed to send quick replies', e);
return false;
}
}
13 changes: 13 additions & 0 deletions app/lib/methods/userPreferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,19 @@ class UserPreferences {
this.mmkv.set(key, value);
}

getArray<T = unknown>(key: string): T[] | null {
try {
const jsonString = this.mmkv.getString(key);
return jsonString ? (JSON.parse(jsonString) as T[]) : null;
} catch {
return null;
}
}

setArray<T>(key: string, value: T[]): void {
this.mmkv.set(key, JSON.stringify(value));
}

getAllKeys(): string[] {
return this.mmkv.getAllKeys();
}
Expand Down
2 changes: 2 additions & 0 deletions app/stacks/InsideStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import CannedResponseDetail from '../views/CannedResponseDetail';
import ProfileView from '../views/ProfileView';
import UserPreferencesView from '../views/UserPreferencesView';
import UserNotificationPrefView from '../views/UserNotificationPreferencesView';
import UserWatchOSQuickRepliesView from '../views/UserWatchOSQuickRepliesView';
import ChangePasswordView from '../views/ChangePasswordView';
// Display Preferences View
import DisplayPrefsView from '../views/DisplayPrefsView';
Expand Down Expand Up @@ -171,6 +172,7 @@ const ProfileStackNavigator = () => {
<ProfileStack.Screen name='UserPreferencesView' component={UserPreferencesView} />
<ProfileStack.Screen name='ChangeAvatarView' component={ChangeAvatarView} />
<ProfileStack.Screen name='UserNotificationPrefView' component={UserNotificationPrefView} />
<ProfileStack.Screen name='UserWatchOSQuickRepliesView' component={UserWatchOSQuickRepliesView} />
<ProfileStack.Screen name='PushTroubleshootView' component={PushTroubleshootView} />
<ProfileStack.Screen name='PickerView' component={PickerView} />
</ProfileStack.Navigator>
Expand Down
1 change: 1 addition & 0 deletions app/stacks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ export type ProfileStackParamList = {
ProfileView: undefined;
UserPreferencesView: undefined;
UserNotificationPrefView: undefined;
UserWatchOSQuickRepliesView: undefined;
PushTroubleshootView: undefined;
ChangeAvatarView: {
context: TChangeAvatarViewContext;
Expand Down
10 changes: 10 additions & 0 deletions app/views/UserPreferencesView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ const UserPreferencesView = ({ navigation }: IUserPreferencesViewProps): JSX.Ele
/>
<List.Separator />
</List.Section>
<List.Section title='WatchOS_Quick_Replies'>
<List.Separator />
<List.Item
title='WatchOS_Quick_Replies'
onPress={() => navigateToScreen('UserWatchOSQuickRepliesView')}
showActionIndicator
testID='preferences-view-watchos-quickreplies'
/>
<List.Separator />
</List.Section>
</List.Container>
</SafeAreaView>
);
Expand Down
80 changes: 80 additions & 0 deletions app/views/UserWatchOSQuickRepliesView/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { type NativeStackNavigationProp } from '@react-navigation/native-stack';
import React, { useEffect, useState } from 'react';
import { ScrollView } from 'react-native-gesture-handler';

import I18n from '../../i18n';
import SafeAreaView from '../../containers/SafeAreaView';
import * as List from '../../containers/List';
import { type ProfileStackParamList } from '../../stacks/types';
import { FormTextInput } from '../../containers/TextInput';
import Chip from '../../containers/Chip';
import { useUserPreferences } from '../../lib/methods/userPreferences';
import { WATCHOS_QUICKREPLIES } from '../../lib/constants/keys';
import { syncWatchOSQuickReplies } from '../../lib/methods/WatchOSQuickReplies/syncReplies';
import { checkWatch } from '../../lib/methods/WatchOSQuickReplies/getWatchStatus';

interface IUserPreferencesViewProps {
navigation: NativeStackNavigationProp<ProfileStackParamList, 'UserPreferencesView'>;
}

const UserPreferencesView = ({ navigation }: IUserPreferencesViewProps): JSX.Element => {
const [quickreplies, setQuickreplies] = useUserPreferences<string[]>(WATCHOS_QUICKREPLIES, []);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const [quickreplies, setQuickreplies] = useUserPreferences<string[]>(WATCHOS_QUICKREPLIES, []);
const [quickReplies, setQuickReplies] = useUserPreferences<string[]>(WATCHOS_QUICKREPLIES, []);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a setting on Admin/Mobile like ScreenLock section.

Admin should look like this:

  • Apple Watch
    • Quick actions: string

The string should be comma separated and the default ones should be:

  • OK
  • Yes
  • No
  • On my way
  • Will follow up shortly

const [input, setInput] = useState<string>('');

useEffect(() => {
navigation.setOptions({
title: I18n.t('Preferences')
});
}, [navigation]);

useEffect(() => {
const load = async () => {
const status = await checkWatch();
console.log(status);
const result = await syncWatchOSQuickReplies(quickreplies ?? []);
console.log(result);
};
load();
}, [quickreplies]);

const removeQuickReply = (reply: string) => {
const newReplies = quickreplies?.filter(quickreply => quickreply !== reply);
setQuickreplies(newReplies);
};

const addQuickReply = () => {
const value = input.trim();
if (!value) return;
if (!quickreplies?.includes(input.trim())) setQuickreplies([...(quickreplies ?? []), value]);
setInput('');
};

return (
<SafeAreaView testID='preferences-view'>
<List.Container>
<List.Section title='WatchOS_Quick_Replies'>
<>
{quickreplies && quickreplies.length !== 0 && (
<ScrollView horizontal style={{ marginVertical: 8, paddingHorizontal: 4 }}>
{quickreplies.map((reply, index) => (
<Chip key={index} text={reply} onPress={() => removeQuickReply(reply)} />
))}
</ScrollView>
)}
</>
<List.Separator />
<FormTextInput
value={input}
onChangeText={text => setInput(text)}
placeholder={I18n.t('Add_Quick_Reply')}
onSubmitEditing={addQuickReply}
/>
<List.Separator />
<List.Info info='WatchOS_Quick_Replies_Description' />
</List.Section>
</List.Container>
</SafeAreaView>
);
};

export default UserPreferencesView;
6 changes: 3 additions & 3 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ PODS:
- libwebp/sharpyuv (1.5.0)
- libwebp/webp (1.5.0):
- libwebp/sharpyuv
- MobileCrypto (0.2.0):
- MobileCrypto (0.2.1):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -3082,7 +3082,7 @@ SPEC CHECKSUMS:
libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7
libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
MobileCrypto: 60a1e43e26a9d6851ae2aa7294b8041c9e9220b7
MobileCrypto: a424494b2f45bec9dbe60e3f6d16a40aedefe7b7
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
Expand Down Expand Up @@ -3185,7 +3185,7 @@ SPEC CHECKSUMS:
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654
WatermelonDB: 4c846c8cb94eef3cba90fa034d15310163226703
Yoga: dfabf1234ccd5ac41d1b1d43179f024366ae9831
Yoga: 2a3a4c38a8441b6359d5e5914d35db7b2b67aebd
ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5

PODFILE CHECKSUM: 199f6fbbe6fb415c822cca992e6152000ac55b3e
Expand Down
20 changes: 19 additions & 1 deletion ios/RocketChat Watch App/Loaders/WatchSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ protocol WatchSessionProtocol {
/// Default WatchSession protocol implementation.
final class WatchSession: NSObject, WatchSessionProtocol, WCSessionDelegate {
private let session: WCSession


@Storage(.quickReplies, defaultValue: [])
private var quickReplies: [String]?

init(session: WCSession) {
self.session = session
super.init()
Expand Down Expand Up @@ -46,6 +49,21 @@ final class WatchSession: NSObject, WatchSessionProtocol, WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {

}

// quick replies
func session(
_ session: WCSession,
didReceiveApplicationContext applicationContext: [String : Any]
) {
print(applicationContext)

if let replies = applicationContext["quickReplies"] as? [String] {
quickReplies = replies
print("QUICK REPLIES STORED:", replies)
} else {
print("quickReplies key missing")
}
}
}

/// Retry decorator for WatchSession protocol.
Expand Down
1 change: 1 addition & 0 deletions ios/RocketChat Watch App/Storage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Foundation

enum StorageKey: String {
case currentServer = "current_server"
case quickReplies = "quick_replies"
}

@propertyWrapper
Expand Down
52 changes: 49 additions & 3 deletions ios/RocketChat Watch App/Views/MessageComposerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ struct MessageComposerView: View {

let room: Room
let onSend: (String) -> Void

@Storage(.quickReplies, defaultValue: [])
private var quickReplies: [String]?

var body: some View {
if room.isReadOnly {
Expand All @@ -17,9 +20,52 @@ struct MessageComposerView: View {
Spacer()
}
} else {
TextField("Message", text: $message)
.submitLabel(.send)
.onSubmit(send)
VStack(alignment: .leading, spacing: 4) {
TextField("Message", text: $message)
.submitLabel(.send)
.onSubmit(send)

if let replies = quickReplies, !replies.isEmpty {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 6) {
ForEach(replies, id: \.self) { reply in
if #available(watchOS 26.0, *) {
Button {
message = reply
send()
} label: {
Text(reply)
.font(.caption2)
.foregroundStyle(.primary)
.padding(.horizontal, 10)
.padding(.vertical, 4)
}
.buttonStyle(.plain)
.glassEffect(.regular)
.clipShape(Capsule())
} else {
Button {
message = reply
send()
} label: {
Text(reply)
.font(.caption2)
.foregroundStyle(.primary)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(
Capsule()
.fill(Color.white.opacity(0.12))
)
}
.buttonStyle(.borderedProminent)
}
}
}
}
}
}

}
}

Expand Down
Loading