feat: haptics feedback in app notificaiton (#5507)
* create redux for inAppFeedback * add the clear inAppFeedback, haptic feedback to room view * added haptics feedback to room view * add the user preference to in app vibration and the value * minor tweak * yarn prettier-lint
This commit is contained in:
parent
501e42196d
commit
e06aaf1cc0
|
@ -97,3 +97,4 @@ export const VIDEO_CONF = createRequestTypes('VIDEO_CONF', [
|
||||||
'SET_CALLING'
|
'SET_CALLING'
|
||||||
]);
|
]);
|
||||||
export const SUPPORTED_VERSIONS = createRequestTypes('SUPPORTED_VERSIONS', ['SET']);
|
export const SUPPORTED_VERSIONS = createRequestTypes('SUPPORTED_VERSIONS', ['SET']);
|
||||||
|
export const IN_APP_FEEDBACK = createRequestTypes('IN_APP_FEEDBACK', ['SET', 'REMOVE', 'CLEAR']);
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { Action } from 'redux';
|
||||||
|
|
||||||
|
import { IN_APP_FEEDBACK } from './actionsTypes';
|
||||||
|
|
||||||
|
interface IInAppFeedbackAction {
|
||||||
|
msgId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TInAppFeedbackAction = IInAppFeedbackAction & Action;
|
||||||
|
|
||||||
|
export function setInAppFeedback(msgId: string): TInAppFeedbackAction {
|
||||||
|
return {
|
||||||
|
type: IN_APP_FEEDBACK.SET,
|
||||||
|
msgId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeInAppFeedback(msgId: string): TInAppFeedbackAction {
|
||||||
|
return {
|
||||||
|
type: IN_APP_FEEDBACK.REMOVE,
|
||||||
|
msgId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearInAppFeedback(): Action {
|
||||||
|
return {
|
||||||
|
type: IN_APP_FEEDBACK.CLEAR
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
import React, { ElementType, memo, useEffect } from 'react';
|
import React, { ElementType, memo, useEffect } from 'react';
|
||||||
import { Easing, Notifier, NotifierRoot } from 'react-native-notifier';
|
import { Easing, Notifier, NotifierRoot } from 'react-native-notifier';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
import NotifierComponent, { INotifierComponent } from './NotifierComponent';
|
import NotifierComponent, { INotifierComponent } from './NotifierComponent';
|
||||||
import EventEmitter from '../../lib/methods/helpers/events';
|
import EventEmitter from '../../lib/methods/helpers/events';
|
||||||
import Navigation from '../../lib/navigation/appNavigation';
|
import Navigation from '../../lib/navigation/appNavigation';
|
||||||
import { getActiveRoute } from '../../lib/methods/helpers/navigation';
|
import { getActiveRoute } from '../../lib/methods/helpers/navigation';
|
||||||
import { useAppSelector } from '../../lib/hooks';
|
import { useAppSelector } from '../../lib/hooks';
|
||||||
|
import { setInAppFeedback } from '../../actions/inAppFeedback';
|
||||||
|
|
||||||
export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp';
|
export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp';
|
||||||
|
|
||||||
|
@ -15,6 +17,8 @@ const InAppNotification = memo(() => {
|
||||||
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background'
|
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const show = (
|
const show = (
|
||||||
notification: INotifierComponent['notification'] & {
|
notification: INotifierComponent['notification'] & {
|
||||||
customComponent?: ElementType;
|
customComponent?: ElementType;
|
||||||
|
@ -30,7 +34,13 @@ const InAppNotification = memo(() => {
|
||||||
const state = Navigation.navigationRef.current?.getRootState();
|
const state = Navigation.navigationRef.current?.getRootState();
|
||||||
const route = getActiveRoute(state);
|
const route = getActiveRoute(state);
|
||||||
if (payload?.rid || notification.customNotification) {
|
if (payload?.rid || notification.customNotification) {
|
||||||
if (payload?.rid === subscribedRoom || route?.name === 'JitsiMeetView' || payload?.message?.t === 'videoconf') return;
|
if (route?.name === 'JitsiMeetView' || payload?.message?.t === 'videoconf') return;
|
||||||
|
|
||||||
|
if (payload?.rid === subscribedRoom) {
|
||||||
|
const msgId = payload._id;
|
||||||
|
dispatch(setInAppFeedback(msgId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Notifier.showNotification({
|
Notifier.showNotification({
|
||||||
showEasing: Easing.inOut(Easing.quad),
|
showEasing: Easing.inOut(Easing.quad),
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { TActionPermissions } from '../../actions/permissions';
|
||||||
import { TActionEnterpriseModules } from '../../actions/enterpriseModules';
|
import { TActionEnterpriseModules } from '../../actions/enterpriseModules';
|
||||||
import { TActionVideoConf } from '../../actions/videoConf';
|
import { TActionVideoConf } from '../../actions/videoConf';
|
||||||
import { TActionSupportedVersions } from '../../actions/supportedVersions';
|
import { TActionSupportedVersions } from '../../actions/supportedVersions';
|
||||||
|
import { TInAppFeedbackAction } from '../../actions/inAppFeedback';
|
||||||
// REDUCERS
|
// REDUCERS
|
||||||
import { IActiveUsers } from '../../reducers/activeUsers';
|
import { IActiveUsers } from '../../reducers/activeUsers';
|
||||||
import { IApp } from '../../reducers/app';
|
import { IApp } from '../../reducers/app';
|
||||||
|
@ -40,6 +41,7 @@ import { IVideoConf } from '../../reducers/videoConf';
|
||||||
import { TActionUsersRoles } from '../../actions/usersRoles';
|
import { TActionUsersRoles } from '../../actions/usersRoles';
|
||||||
import { TUsersRoles } from '../../reducers/usersRoles';
|
import { TUsersRoles } from '../../reducers/usersRoles';
|
||||||
import { ISupportedVersionsState } from '../../reducers/supportedVersions';
|
import { ISupportedVersionsState } from '../../reducers/supportedVersions';
|
||||||
|
import { IInAppFeedbackState } from '../../reducers/inAppFeedback';
|
||||||
|
|
||||||
export interface IApplicationState {
|
export interface IApplicationState {
|
||||||
settings: TSettingsState;
|
settings: TSettingsState;
|
||||||
|
@ -66,6 +68,7 @@ export interface IApplicationState {
|
||||||
videoConf: IVideoConf;
|
videoConf: IVideoConf;
|
||||||
usersRoles: TUsersRoles;
|
usersRoles: TUsersRoles;
|
||||||
supportedVersions: ISupportedVersionsState;
|
supportedVersions: ISupportedVersionsState;
|
||||||
|
inAppFeedback: IInAppFeedbackState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TApplicationActions = TActionActiveUsers &
|
export type TApplicationActions = TActionActiveUsers &
|
||||||
|
@ -87,4 +90,5 @@ export type TApplicationActions = TActionActiveUsers &
|
||||||
TActionEnterpriseModules &
|
TActionEnterpriseModules &
|
||||||
TActionVideoConf &
|
TActionVideoConf &
|
||||||
TActionUsersRoles &
|
TActionUsersRoles &
|
||||||
TActionSupportedVersions;
|
TActionSupportedVersions &
|
||||||
|
TInAppFeedbackAction;
|
||||||
|
|
|
@ -794,6 +794,8 @@
|
||||||
"You_dont_have_permission_to_perform_this_action": "You don’t have permission to perform this action. Check with a workspace administrator.",
|
"You_dont_have_permission_to_perform_this_action": "You don’t have permission to perform this action. Check with a workspace administrator.",
|
||||||
"Jump_to_message": "Jump to message",
|
"Jump_to_message": "Jump to message",
|
||||||
"Missed_call": "Missed call",
|
"Missed_call": "Missed call",
|
||||||
|
"In_app_message_notifications": "In app message notifications",
|
||||||
|
"Vibrate": "Vibrate",
|
||||||
"Recording_audio_in_progress": "Recording audio message",
|
"Recording_audio_in_progress": "Recording audio message",
|
||||||
"Bold": "Bold",
|
"Bold": "Bold",
|
||||||
"Italic": "Italic",
|
"Italic": "Italic",
|
||||||
|
|
|
@ -793,5 +793,7 @@
|
||||||
"Pinned_a_message": "Fixou uma mensagem:",
|
"Pinned_a_message": "Fixou uma mensagem:",
|
||||||
"You_dont_have_permission_to_perform_this_action": "Você não tem permissão para realizar esta ação. Verifique com um administrador do espaço de trabalho.",
|
"You_dont_have_permission_to_perform_this_action": "Você não tem permissão para realizar esta ação. Verifique com um administrador do espaço de trabalho.",
|
||||||
"Jump_to_message": "Ir para mensagem",
|
"Jump_to_message": "Ir para mensagem",
|
||||||
"Missed_call": "Chamada perdida"
|
"Missed_call": "Chamada perdida",
|
||||||
|
"In_app_message_notifications": "Notificações de mensagens in-app",
|
||||||
|
"Vibrate": "Vibrar"
|
||||||
}
|
}
|
|
@ -1 +1,2 @@
|
||||||
export const NOTIFICATION_PRESENCE_CAP = 'NOTIFICATION_PRESENCE_CAP';
|
export const NOTIFICATION_PRESENCE_CAP = 'NOTIFICATION_PRESENCE_CAP';
|
||||||
|
export const NOTIFICATION_IN_APP_VIBRATION = 'NOTIFICATION_IN_APP_VIBRATION';
|
||||||
|
|
|
@ -29,7 +29,6 @@ class UserPreferences {
|
||||||
|
|
||||||
getBool(key: string): boolean | null {
|
getBool(key: string): boolean | null {
|
||||||
try {
|
try {
|
||||||
console.log(this.mmkv.getBool(key));
|
|
||||||
return this.mmkv.getBool(key) ?? null;
|
return this.mmkv.getBool(key) ?? null;
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { removeInAppFeedback, setInAppFeedback, clearInAppFeedback } from '../actions/inAppFeedback';
|
||||||
|
import { mockedStore } from './mockedStore';
|
||||||
|
import { initialState } from './inAppFeedback';
|
||||||
|
|
||||||
|
describe('test inAppFeedback reducer', () => {
|
||||||
|
it('should return initial state', () => {
|
||||||
|
const state = mockedStore.getState().inAppFeedback;
|
||||||
|
expect(state).toEqual(initialState);
|
||||||
|
});
|
||||||
|
|
||||||
|
const msgId01 = 'msgId01';
|
||||||
|
const msgId02 = 'msgId02';
|
||||||
|
it('should return modified store after setInAppFeedback', () => {
|
||||||
|
const resultExpected = { [msgId01]: msgId01, [msgId02]: msgId02 };
|
||||||
|
mockedStore.dispatch(setInAppFeedback(msgId01));
|
||||||
|
mockedStore.dispatch(setInAppFeedback(msgId02));
|
||||||
|
const state = mockedStore.getState().inAppFeedback;
|
||||||
|
expect(state).toEqual(resultExpected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return modified store after removeInAppFeedback', () => {
|
||||||
|
const resultExpected = { [msgId02]: msgId02 };
|
||||||
|
mockedStore.dispatch(removeInAppFeedback(msgId01));
|
||||||
|
const state = mockedStore.getState().inAppFeedback;
|
||||||
|
expect(state).toEqual(resultExpected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty store after clearInAppFeedback', () => {
|
||||||
|
mockedStore.dispatch(clearInAppFeedback());
|
||||||
|
const state = mockedStore.getState().inAppFeedback;
|
||||||
|
expect(state).toEqual(initialState);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { IN_APP_FEEDBACK } from '../actions/actionsTypes';
|
||||||
|
import { TApplicationActions } from '../definitions';
|
||||||
|
|
||||||
|
export interface IInAppFeedbackState {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialState: IInAppFeedbackState = {};
|
||||||
|
|
||||||
|
export default function activeUsers(state = initialState, action: TApplicationActions): IInAppFeedbackState {
|
||||||
|
switch (action.type) {
|
||||||
|
case IN_APP_FEEDBACK.SET:
|
||||||
|
const { msgId } = action;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[msgId]: msgId
|
||||||
|
};
|
||||||
|
case IN_APP_FEEDBACK.REMOVE:
|
||||||
|
const newState = { ...state };
|
||||||
|
delete newState[action.msgId];
|
||||||
|
return newState;
|
||||||
|
case IN_APP_FEEDBACK.CLEAR:
|
||||||
|
return initialState;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import roles from './roles';
|
||||||
import videoConf from './videoConf';
|
import videoConf from './videoConf';
|
||||||
import usersRoles from './usersRoles';
|
import usersRoles from './usersRoles';
|
||||||
import supportedVersions from './supportedVersions';
|
import supportedVersions from './supportedVersions';
|
||||||
|
import inAppFeedback from './inAppFeedback';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
settings,
|
settings,
|
||||||
|
@ -49,5 +50,6 @@ export default combineReducers({
|
||||||
roles,
|
roles,
|
||||||
videoConf,
|
videoConf,
|
||||||
usersRoles,
|
usersRoles,
|
||||||
supportedVersions
|
supportedVersions,
|
||||||
|
inAppFeedback
|
||||||
});
|
});
|
||||||
|
|
|
@ -147,7 +147,7 @@ const ChangeAvatarView = () => {
|
||||||
payload: { url: response.path, data: `data:image/jpeg;base64,${response.data}`, service: 'upload' }
|
payload: { url: response.path, data: `data:image/jpeg;base64,${response.data}`, service: 'upload' }
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if(error?.code !== "E_PICKER_CANCELLED") {
|
if (error?.code !== 'E_PICKER_CANCELLED') {
|
||||||
log(error);
|
log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,7 +139,7 @@ class DirectoryView extends React.Component<IDirectoryViewProps, IDirectoryViewS
|
||||||
} else if (type === 'teams') {
|
} else if (type === 'teams') {
|
||||||
logEvent(events.DIRECTORY_SEARCH_TEAMS);
|
logEvent(events.DIRECTORY_SEARCH_TEAMS);
|
||||||
}
|
}
|
||||||
this.toggleDropdown()
|
this.toggleDropdown();
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleWorkspace = () => {
|
toggleWorkspace = () => {
|
||||||
|
|
|
@ -111,13 +111,7 @@ export const RoomInfoButtons = ({
|
||||||
iconName='ignore'
|
iconName='ignore'
|
||||||
showIcon={!!renderBlockUser}
|
showIcon={!!renderBlockUser}
|
||||||
/>
|
/>
|
||||||
<BaseButton
|
<BaseButton onPress={handleReportUser} label={i18n.t('Report')} iconName='warning' showIcon={!!renderReportUser} danger />
|
||||||
onPress={handleReportUser}
|
|
||||||
label={i18n.t('Report')}
|
|
||||||
iconName='warning'
|
|
||||||
showIcon={!!renderReportUser}
|
|
||||||
danger
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,6 +22,7 @@ export interface IRoomViewProps extends IActionSheetProvider, IBaseScreen<ChatsS
|
||||||
transferLivechatGuestPermission?: string[]; // TODO: Check if its the correct type
|
transferLivechatGuestPermission?: string[]; // TODO: Check if its the correct type
|
||||||
viewCannedResponsesPermission?: string[]; // TODO: Check if its the correct type
|
viewCannedResponsesPermission?: string[]; // TODO: Check if its the correct type
|
||||||
livechatAllowManualOnHold?: boolean;
|
livechatAllowManualOnHold?: boolean;
|
||||||
|
inAppFeedback?: { [key: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TStateAttrsUpdate = keyof IRoomViewState;
|
export type TStateAttrsUpdate = keyof IRoomViewState;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { Q } from '@nozbe/watermelondb';
|
||||||
import { dequal } from 'dequal';
|
import { dequal } from 'dequal';
|
||||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
import * as Haptics from 'expo-haptics';
|
||||||
|
|
||||||
import { getRoutingConfig } from '../../lib/services/restApi';
|
import { getRoutingConfig } from '../../lib/services/restApi';
|
||||||
import Touch from '../../containers/Touch';
|
import Touch from '../../containers/Touch';
|
||||||
|
@ -62,7 +63,14 @@ import {
|
||||||
TGetCustomEmoji,
|
TGetCustomEmoji,
|
||||||
RoomType
|
RoomType
|
||||||
} from '../../definitions';
|
} from '../../definitions';
|
||||||
import { E2E_MESSAGE_TYPE, E2E_STATUS, MESSAGE_TYPE_ANY_LOAD, MessageTypeLoad, themes } from '../../lib/constants';
|
import {
|
||||||
|
E2E_MESSAGE_TYPE,
|
||||||
|
E2E_STATUS,
|
||||||
|
MESSAGE_TYPE_ANY_LOAD,
|
||||||
|
MessageTypeLoad,
|
||||||
|
themes,
|
||||||
|
NOTIFICATION_IN_APP_VIBRATION
|
||||||
|
} from '../../lib/constants';
|
||||||
import { ModalStackParamList } from '../../stacks/MasterDetailStack/types';
|
import { ModalStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||||
import {
|
import {
|
||||||
callJitsi,
|
callJitsi,
|
||||||
|
@ -90,6 +98,8 @@ import audioPlayer from '../../lib/methods/audioPlayer';
|
||||||
import { IListContainerRef, TListRef } from './List/definitions';
|
import { IListContainerRef, TListRef } from './List/definitions';
|
||||||
import { getMessageById } from '../../lib/database/services/Message';
|
import { getMessageById } from '../../lib/database/services/Message';
|
||||||
import { getThreadById } from '../../lib/database/services/Thread';
|
import { getThreadById } from '../../lib/database/services/Thread';
|
||||||
|
import { clearInAppFeedback, removeInAppFeedback } from '../../actions/inAppFeedback';
|
||||||
|
import UserPreferences from '../../lib/methods/userPreferences';
|
||||||
import { IRoomViewProps, IRoomViewState } from './definitions';
|
import { IRoomViewProps, IRoomViewState } from './definitions';
|
||||||
import { roomAttrsUpdate, stateAttrsUpdate } from './constants';
|
import { roomAttrsUpdate, stateAttrsUpdate } from './constants';
|
||||||
|
|
||||||
|
@ -197,8 +207,9 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
const { navigation, dispatch } = this.props;
|
||||||
const { selectedMessages } = this.state;
|
const { selectedMessages } = this.state;
|
||||||
const { navigation } = this.props;
|
dispatch(clearInAppFeedback());
|
||||||
this.mounted = true;
|
this.mounted = true;
|
||||||
this.didMountInteraction = InteractionManager.runAfterInteractions(() => {
|
this.didMountInteraction = InteractionManager.runAfterInteractions(() => {
|
||||||
const { isAuthenticated } = this.props;
|
const { isAuthenticated } = this.props;
|
||||||
|
@ -302,6 +313,8 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
async componentWillUnmount() {
|
async componentWillUnmount() {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
dispatch(clearInAppFeedback());
|
||||||
this.mounted = false;
|
this.mounted = false;
|
||||||
this.unsubscribe();
|
this.unsubscribe();
|
||||||
if (this.didMountInteraction && this.didMountInteraction.cancel) {
|
if (this.didMountInteraction && this.didMountInteraction.cancel) {
|
||||||
|
@ -1224,10 +1237,31 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
Navigation.navigate('CannedResponsesListView', { rid: room.rid });
|
Navigation.navigate('CannedResponsesListView', { rid: room.rid });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
hapticFeedback = (msgId: string) => {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
dispatch(removeInAppFeedback(msgId));
|
||||||
|
const notificationInAppVibration = UserPreferences.getBool(NOTIFICATION_IN_APP_VIBRATION);
|
||||||
|
if (notificationInAppVibration || notificationInAppVibration === null) {
|
||||||
|
try {
|
||||||
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
||||||
|
} catch {
|
||||||
|
// Do nothing: Haptic is unavailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
renderItem = (item: TAnyMessageModel, previousItem: TAnyMessageModel, highlightedMessage?: string) => {
|
renderItem = (item: TAnyMessageModel, previousItem: TAnyMessageModel, highlightedMessage?: string) => {
|
||||||
const { room, lastOpen, canAutoTranslate } = this.state;
|
const { room, lastOpen, canAutoTranslate } = this.state;
|
||||||
const { user, Message_GroupingPeriod, Message_TimeFormat, useRealName, baseUrl, Message_Read_Receipt_Enabled, theme } =
|
const {
|
||||||
this.props;
|
user,
|
||||||
|
Message_GroupingPeriod,
|
||||||
|
Message_TimeFormat,
|
||||||
|
useRealName,
|
||||||
|
baseUrl,
|
||||||
|
Message_Read_Receipt_Enabled,
|
||||||
|
theme,
|
||||||
|
inAppFeedback
|
||||||
|
} = this.props;
|
||||||
const { action, selectedMessages } = this.state;
|
const { action, selectedMessages } = this.state;
|
||||||
let dateSeparator = null;
|
let dateSeparator = null;
|
||||||
let showUnreadSeparator = false;
|
let showUnreadSeparator = false;
|
||||||
|
@ -1254,6 +1288,9 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
||||||
};
|
};
|
||||||
content = <LoadMore rid={room.rid} t={room.t as RoomType} loaderId={item.id} type={item.t} runOnRender={runOnRender()} />;
|
content = <LoadMore rid={room.rid} t={room.t as RoomType} loaderId={item.id} type={item.t} runOnRender={runOnRender()} />;
|
||||||
} else {
|
} else {
|
||||||
|
if (inAppFeedback?.[item.id]) {
|
||||||
|
this.hapticFeedback(item.id);
|
||||||
|
}
|
||||||
content = (
|
content = (
|
||||||
<Message
|
<Message
|
||||||
item={item}
|
item={item}
|
||||||
|
@ -1458,7 +1495,8 @@ const mapStateToProps = (state: IApplicationState) => ({
|
||||||
Hide_System_Messages: state.settings.Hide_System_Messages as string[],
|
Hide_System_Messages: state.settings.Hide_System_Messages as string[],
|
||||||
transferLivechatGuestPermission: state.permissions['transfer-livechat-guest'],
|
transferLivechatGuestPermission: state.permissions['transfer-livechat-guest'],
|
||||||
viewCannedResponsesPermission: state.permissions['view-canned-responses'],
|
viewCannedResponsesPermission: state.permissions['view-canned-responses'],
|
||||||
livechatAllowManualOnHold: state.settings.Livechat_allow_manual_on_hold as boolean
|
livechatAllowManualOnHold: state.settings.Livechat_allow_manual_on_hold as boolean,
|
||||||
|
inAppFeedback: state.inAppFeedback
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withDimensions(withTheme(withSafeAreaInsets(withActionSheet(RoomView)))));
|
export default connect(mapStateToProps)(withDimensions(withTheme(withSafeAreaInsets(withActionSheet(RoomView)))));
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useEffect, useLayoutEffect, useState } from 'react';
|
import React, { useEffect, useLayoutEffect, useState } from 'react';
|
||||||
|
import { Switch } from 'react-native';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
|
||||||
|
@ -14,8 +15,11 @@ import { Services } from '../../lib/services';
|
||||||
import { useAppSelector } from '../../lib/hooks';
|
import { useAppSelector } from '../../lib/hooks';
|
||||||
import ListPicker from './ListPicker';
|
import ListPicker from './ListPicker';
|
||||||
import log from '../../lib/methods/helpers/log';
|
import log from '../../lib/methods/helpers/log';
|
||||||
|
import { useUserPreferences } from '../../lib/methods';
|
||||||
|
import { NOTIFICATION_IN_APP_VIBRATION, SWITCH_TRACK_COLOR } from '../../lib/constants';
|
||||||
|
|
||||||
const UserNotificationPreferencesView = () => {
|
const UserNotificationPreferencesView = () => {
|
||||||
|
const [inAppVibration, setInAppVibration] = useUserPreferences<boolean>(NOTIFICATION_IN_APP_VIBRATION, true);
|
||||||
const [preferences, setPreferences] = useState({} as INotificationPreferences);
|
const [preferences, setPreferences] = useState({} as INotificationPreferences);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
@ -58,6 +62,10 @@ const UserNotificationPreferencesView = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleInAppVibration = () => {
|
||||||
|
setInAppVibration(!inAppVibration);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView testID='user-notification-preference-view'>
|
<SafeAreaView testID='user-notification-preference-view'>
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
|
@ -92,6 +100,18 @@ const UserNotificationPreferencesView = () => {
|
||||||
<List.Info info='Push_Notifications_Alert_Info' />
|
<List.Info info='Push_Notifications_Alert_Info' />
|
||||||
</List.Section>
|
</List.Section>
|
||||||
|
|
||||||
|
<List.Section title='In_app_message_notifications'>
|
||||||
|
<List.Separator />
|
||||||
|
<List.Item
|
||||||
|
title='Vibrate'
|
||||||
|
testID='user-notification-preference-view-in-app-vibration'
|
||||||
|
right={() => (
|
||||||
|
<Switch value={inAppVibration} trackColor={SWITCH_TRACK_COLOR} onValueChange={toggleInAppVibration} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<List.Separator />
|
||||||
|
</List.Section>
|
||||||
|
|
||||||
<List.Section title='Email'>
|
<List.Section title='Email'>
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
<ListPicker
|
<ListPicker
|
||||||
|
|
Loading…
Reference in New Issue