Skip to content

Commit

Permalink
feat: Add conversation typing state (#626)
Browse files Browse the repository at this point in the history
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
  • Loading branch information
muhsin-k and iamsivin committed Feb 22, 2023
1 parent 4bb0529 commit 3faf785
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 56 deletions.
44 changes: 1 addition & 43 deletions src/actions/conversation.js
@@ -1,7 +1,4 @@
import {
SET_CONVERSATION_DETAILS,
ADD_OR_UPDATE_USER_TYPING_IN_CONVERSATION,
} from '../constants/actions';
import { SET_CONVERSATION_DETAILS } from '../constants/actions';

import axios from '../helpers/APIHelper';
import { getInboxAgents } from './inbox';
Expand All @@ -22,45 +19,6 @@ export const getConversationDetails =
} catch {}
};

export const addUserTypingToConversation =
({ conversation, user }) =>
async (dispatch, getState) => {
const { id: conversationId } = conversation;
const { conversationTypingUsers } = await getState().conversation;
const records = conversationTypingUsers[conversationId] || [];
const hasUserRecordAlready = !!records.filter(
record => record.id === user.id && record.type === user.type,
).length;
if (!hasUserRecordAlready) {
dispatch({
type: ADD_OR_UPDATE_USER_TYPING_IN_CONVERSATION,
payload: {
conversationId,
users: [...records, user],
},
});
}
};

export const removeUserFromTypingConversation =
({ conversation, user }) =>
async (dispatch, getState) => {
const { id: conversationId } = conversation;
const { conversationTypingUsers } = await getState().conversation;
const records = conversationTypingUsers[conversationId] || [];
const updatedUsers = records.filter(
record => record.id !== user.id || record.type !== user.type,
);

dispatch({
type: ADD_OR_UPDATE_USER_TYPING_IN_CONVERSATION,
payload: {
conversationId,
users: updatedUsers,
},
});
};

export const toggleTypingStatus =
({ conversationId, typingStatus }) =>
async dispatch => {
Expand Down
16 changes: 6 additions & 10 deletions src/helpers/ActionCable.js
Expand Up @@ -8,12 +8,9 @@ import {
} from 'reducer/conversationSlice';

import conversationActions from 'reducer/conversationSlice.action';
import {
addUserTypingToConversation,
removeUserFromTypingConversation,
} from '../actions/conversation';
import { addOrUpdateActiveUsers } from '../actions/auth';
import { store } from '../store';
import { addUserToTyping, destroyUserFromTyping } from 'reducer/conversationTypingSlice';

class ActionCableConnector extends BaseActionCableConnector {
constructor(pubsubToken, webSocketUrl, accountId, userId) {
Expand Down Expand Up @@ -84,28 +81,27 @@ class ActionCableConnector extends BaseActionCableConnector {
};

onTypingOn = ({ conversation, user }) => {
//TODO: Move this to typingSlice
const conversationId = conversation.id;

this.clearTimer(conversationId);

store.dispatch(
addUserTypingToConversation({
conversation,
addUserToTyping({
conversationId,
user,
}),
);
this.initTimer({ conversation, user });
};

onTypingOff = ({ conversation, user }) => {
//TODO: Move this to typingSlice
const conversationId = conversation.id;

this.clearTimer(conversationId);

store.dispatch(
removeUserFromTypingConversation({
conversation,
destroyUserFromTyping({
conversationId,
user,
}),
);
Expand Down
36 changes: 36 additions & 0 deletions src/reducer/conversationTypingSlice.js
@@ -0,0 +1,36 @@
import { createSlice, createEntityAdapter } from '@reduxjs/toolkit';

const conversationTypingAdapter = createEntityAdapter();

const typingSlice = createSlice({
name: 'conversationTypingStatus',
initialState: conversationTypingAdapter.getInitialState({
records: {},
}),
reducers: {
addUserToTyping: (state, action) => {
const { conversationId, user } = action.payload;
const records = state.records[conversationId] || [];
const hasUserRecordAlready = !!records.filter(
record => record.id === user.id && record.type === user.type,
).length;

if (!hasUserRecordAlready) {
state.records[conversationId] = [...records, user];
}
},
destroyUserFromTyping: (state, action) => {
const { conversationId, user } = action.payload;
const records = state.entities[conversationId] || [];
const updatedRecords = records.filter(
record => record.id !== user.id || record.type !== user.type,
);
state.records[conversationId] = updatedRecords;
},
},
});
export const { addUserToTyping, destroyUserFromTyping } = typingSlice.actions;

export const selectAllTypingUsers = state => state.conversationTypingStatus.records;

export default typingSlice.reducer;
2 changes: 2 additions & 0 deletions src/reducer/index.js
Expand Up @@ -8,6 +8,7 @@ import notification from './notification';
import agent from './agent';
import cannedResponseSlice from './cannedResponseSlice';
import conversationSlice from './conversationSlice';
import conversationTypingSlice from './conversationTypingSlice';

export const rootReducer = combineReducers({
auth,
Expand All @@ -18,6 +19,7 @@ export const rootReducer = combineReducers({
agent,
cannedResponses: cannedResponseSlice,
conversations: conversationSlice,
conversationTypingStatus: conversationTypingSlice,
});

// export default (state, action) =>
Expand Down
61 changes: 61 additions & 0 deletions src/reducer/tests/conversationTypingSlice.spec.js
@@ -0,0 +1,61 @@
import conversationTypingSlice, {
selectAllTypingUsers,
addUserToTyping,
destroyUserFromTyping,
} from '../conversationTypingSlice';

describe('conversationTypingSlice', () => {
describe('reducers', () => {
const initialState = { entities: {}, ids: [], loading: false, records: {} };

it('Add typing users to store', () => {
expect(
conversationTypingSlice(
initialState,
addUserToTyping({ conversationId: 1, user: { id: 1, type: 'user' } }),
),
).toEqual({
entities: {},
ids: [],
loading: false,
records: {
1: [{ id: 1, type: 'user' }],
},
});
});

it('Remove typing users from store', () => {
expect(
conversationTypingSlice(
{ entities: {}, ids: [], loading: false, records: { 1: [{ id: 1, type: 'user' }] } },
destroyUserFromTyping({ conversationId: 1, user: { id: 1, type: 'user' } }),
),
).toEqual({
entities: {},
ids: [],
loading: false,
records: {
1: [],
},
});
});
});

describe('selectors', () => {
it('selects all typing users', () => {
const state = {
conversationTypingStatus: {
entities: {},
ids: [],
loading: false,
records: {
1: [{ id: 1, type: 'user' }],
},
},
};
expect(selectAllTypingUsers(state)).toEqual({
1: [{ id: 1, type: 'user' }],
});
});
});
});
3 changes: 2 additions & 1 deletion src/screens/ChatScreen/ChatScreen.js
Expand Up @@ -15,6 +15,7 @@ import { openURL } from 'helpers/UrlHelper';
import { markNotificationAsRead } from 'actions/notification';
import { getGroupedConversation, findUniqueMessages } from 'helpers';
import { actions as CannedResponseActions } from 'reducer/cannedResponseSlice';
import { selectAllTypingUsers } from 'reducer/conversationTypingSlice';
import {
clearConversation,
selectors as conversationSelectors,
Expand All @@ -38,7 +39,7 @@ const propTypes = {

const ChatScreenComponent = ({ eva: { style }, navigation, route }) => {
const dispatch = useDispatch();
const conversationTypingUsers = useSelector(state => state.conversation.conversationTypingUsers);
const conversationTypingUsers = useSelector(selectAllTypingUsers);

const isFetching = useSelector(selectMessagesLoading);
const isAllMessagesFetched = useSelector(selectAllMessagesFetched);
Expand Down
Expand Up @@ -16,6 +16,7 @@ import ConversationItem from '../ConversationItem/ConversationItem';
import ConversationEmptyMessage from '../ConversationEmptyMessage/ConversationEmptyMessage';
import i18n from 'i18n';
import createStyles from './ConversationList.style';
import { selectAllTypingUsers } from 'reducer/conversationTypingSlice';

const propTypes = {
assigneeType: PropTypes.string,
Expand Down Expand Up @@ -46,8 +47,7 @@ const ConversationList = ({
const [refreshing, setRefreshing] = useState(false);
const userId = useSelector(store => store.auth.user.id);
const navigation = useNavigation();
const conversationTypingUsers = useSelector(state => state.conversation.conversationTypingUsers);

const conversationTypingUsers = useSelector(selectAllTypingUsers);
const filters = {
assigneeType,
conversationStatus,
Expand Down

0 comments on commit 3faf785

Please sign in to comment.