Skip to content

Commit 7bb4be2

Browse files
feat: Implement animation on notification delete
* feat: Implement animation on notification delete using LayoutAnimation react-native * refactor: updated delete animation logic * fix: updated test case * fix: update delete animation logic * fix: updated test cases
1 parent dc9c94c commit 7bb4be2

File tree

5 files changed

+48
-18
lines changed

5 files changed

+48
-18
lines changed

src/components/card.tsx

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import React, { useState, type ReactElement, useMemo, useEffect } from 'react';
2-
import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
1+
import React, { useState, type ReactElement, useMemo, useEffect, useRef } from 'react';
2+
import { Animated, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
33

44
import type { NotificationCardProps } from '../types';
55
import { CommonUtils, useSiren } from '../utils';
6+
import { eventTypes, events } from '../utils/constants';
67
import CloseIcon from './closeIcon';
78
import TimerIcon from './timerIcon';
89

@@ -44,6 +45,8 @@ const Card = (props: NotificationCardProps): ReactElement => {
4445
const { hideAvatar, disableAutoMarkAsRead, hideDelete = false } = cardProps;
4546
const { markAsRead } = useSiren();
4647

48+
const opacity = useRef(new Animated.Value(1)).current;
49+
4750
const emptyState = () => {
4851
return darkMode ? require('../assets/emptyDark.png') : require('../assets/emptyLight.png');
4952
};
@@ -86,6 +89,22 @@ const Card = (props: NotificationCardProps): ReactElement => {
8689
);
8790
}, [styles, darkMode, imageSource]);
8891

92+
const onDeleteItem = async (): Promise<void> => {
93+
94+
const isSuccess = await onDelete(notification.id, false);
95+
96+
if (isSuccess)
97+
Animated.timing(opacity, {
98+
toValue: 0.1,
99+
duration: 300,
100+
useNativeDriver: true
101+
}).start(() => {
102+
const payload = { id: notification.id, action: eventTypes.DELETE_ITEM };
103+
104+
PubSub.publish(events.NOTIFICATION_LIST_EVENT, JSON.stringify(payload));
105+
});
106+
};
107+
89108
return (
90109
<TouchableOpacity
91110
onPress={cardClick}
@@ -100,15 +119,15 @@ const Card = (props: NotificationCardProps): ReactElement => {
100119
notification?.isRead && style.transparent
101120
]}
102121
/>
103-
<View style={[style.cardContainer, styles.cardContainer]}>
122+
<Animated.View style={[style.cardContainer, styles.cardContainer, { opacity }]}>
104123
{!hideAvatar && renderAvatar}
105124
<View style={style.cardContentContainer}>
106125
<View style={style.cardFooterRow}>
107126
<Text numberOfLines={2} style={[styles.cardTitle, style.cardTitle]}>
108127
{notification.message?.header}
109128
</Text>
110129
{!hideDelete && (
111-
<CloseIcon onDelete={onDelete} notification={notification} styles={styles} />
130+
<CloseIcon onDelete={onDeleteItem} notification={notification} styles={styles} />
112131
)}
113132
</View>
114133
{Boolean(notification.message?.subHeader) && (
@@ -126,7 +145,7 @@ const Card = (props: NotificationCardProps): ReactElement => {
126145
</Text>
127146
</View>
128147
</View>
129-
</View>
148+
</Animated.View>
130149
</TouchableOpacity>
131150
);
132151
};
@@ -139,7 +158,7 @@ const style = StyleSheet.create({
139158
},
140159
cardContainer: {
141160
width: '100%',
142-
flexDirection: 'row',
161+
flexDirection: 'row'
143162
},
144163
cardIconContainer: {
145164
paddingLeft: 6,

src/components/sirenInbox.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,20 @@ const SirenInbox = (props: SirenInboxProps): ReactElement => {
305305
);
306306
};
307307

308-
const onDelete = async (id: string): Promise<void> => {
308+
const onDelete = async (id: string, shouldUpdateList: boolean): Promise<boolean> => {
309+
let isSuccess = false;
310+
309311
if (!disableCardDelete.current) {
310312
disableCardDelete.current = true;
311-
const response = await deleteNotification(id);
312313

314+
const response = await deleteNotification(id, shouldUpdateList);
315+
316+
if (response?.data) isSuccess = true;
313317
processError(response?.error);
314318
disableCardDelete.current = false;
315319
}
320+
321+
return isSuccess;
316322
};
317323

318324
const onPressClearAll = async (): Promise<void> => {

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ export type NotificationCardProps = {
202202
cardProps: CardProps;
203203
darkMode: boolean;
204204
styles: Partial<SirenStyleProps>;
205-
onDelete: (id: string) => void;
205+
onDelete: (id: string, shouldUpdateList: boolean) => Promise<boolean>;
206206
};
207207

208208
/**

src/utils/sirenHook.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ const useSiren = () => {
4141
return { error: errorMap.SIREN_OBJECT_NOT_FOUND };
4242
};
4343

44-
const deleteNotification = async (id: string) => {
44+
const deleteNotification = async (id: string, shouldUpdateList: boolean = true) => {
4545
if (siren)
4646
if (id?.length > 0) {
4747
const response = await siren?.deleteNotificationById(id);
4848

49-
if (response?.data) {
49+
if (response?.data && shouldUpdateList) {
5050
const payload = { id, action: eventTypes.DELETE_ITEM };
5151

5252
PubSub.publish(events.NOTIFICATION_LIST_EVENT, JSON.stringify(payload));

tests/components/card.test.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { fireEvent, render } from '@testing-library/react-native';
2+
import { act, fireEvent, render, waitFor } from '@testing-library/react-native';
33
import Card from '../../src/components/card';
44
import type { SirenStyleProps } from '../../src/types';
55
import type { NotificationDataType } from '@sirenapp/js-sdk/dist/esm/types';
@@ -39,7 +39,8 @@ describe('Card Component', () => {
3939
onCardClick={onCardClickMock}
4040
onDelete={onDeleteMock}
4141
notification={notification}
42-
cardProps={{ hideAvatar: false, showMedia: true }}
42+
darkMode
43+
cardProps={{ hideAvatar: false }}
4344
styles={customStyles}
4445
/>
4546
);
@@ -66,7 +67,7 @@ describe('Card Component', () => {
6667
expect(onCardClickMock).toHaveBeenCalledWith(notification);
6768
});
6869

69-
it('should invoke onDelete callback with correct notification id when delete button is pressed', () => {
70+
it('should invoke onDelete callback with correct notification id when delete button is pressed', async () => {
7071
const onCardClickMock = jest.fn();
7172
const onDeleteMock = jest.fn();
7273

@@ -75,12 +76,16 @@ describe('Card Component', () => {
7576
onCardClick={onCardClickMock}
7677
onDelete={onDeleteMock}
7778
notification={notification}
78-
cardProps={{ hideAvatar: false, showMedia: true }}
79+
darkMode
80+
cardProps={{ hideAvatar: false}}
7981
styles={customStyles}
8082
/>
8183
);
82-
83-
fireEvent.press(getByTestId('delete-button'));
84-
expect(onDeleteMock).toHaveBeenCalledWith(notification.id);
84+
await act(async () => {
85+
fireEvent.press(getByTestId('delete-button'));
86+
await waitFor(() => {
87+
expect(onDeleteMock).toHaveBeenCalledWith(notification.id, false);
88+
});
89+
});
8590
});
8691
});

0 commit comments

Comments
 (0)