From 16d34a71377696945e38bffe830c4b398be9d638 Mon Sep 17 00:00:00 2001
From: Audric Ackermann <audric@loki.network>
Date: Wed, 14 Jul 2021 10:47:23 +1000
Subject: [PATCH] moved conversations reducer to createSlice

---
 ts/components/ConversationListItem.tsx        |   2 +-
 ts/components/MessageSearchResult.tsx         |   4 +-
 .../conversation/ConversationHeader.tsx       |  13 +-
 ts/components/conversation/Message.tsx        |   4 +-
 ts/components/conversation/MessageDetail.tsx  |   2 +-
 ts/components/session/ActionsPanel.tsx        |  10 +-
 .../session/LeftPaneMessageSection.tsx        |   4 +-
 ts/components/session/SessionInboxView.tsx    |   6 +-
 .../conversation/SessionConversation.tsx      |   2 +-
 .../conversation/SessionMessagesList.tsx      |   2 +-
 .../conversation/SessionRightPanel.tsx        |   5 +-
 ts/interactions/conversationInteractions.ts   |  13 +-
 ts/models/conversation.ts                     |  12 +-
 ts/receiver/closedGroups.ts                   |   7 +-
 .../conversations/ConversationController.ts   |  10 +-
 ts/state/createStore.ts                       |   4 +-
 ts/state/ducks/conversationScreen.tsx         |  79 ---
 ts/state/ducks/conversations.ts               | 568 +++++++-----------
 ts/state/ducks/search.ts                      |  75 ++-
 ts/state/reducer.ts                           |   9 +-
 ts/state/selectors/conversationScreen.ts      |  38 --
 ts/state/selectors/conversations.ts           |  26 +
 ts/state/smart/SessionConversation.tsx        |   6 +-
 23 files changed, 347 insertions(+), 554 deletions(-)
 delete mode 100644 ts/state/ducks/conversationScreen.tsx
 delete mode 100644 ts/state/selectors/conversationScreen.ts

diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx
index 81fbdd683..5a4e7e21c 100644
--- a/ts/components/ConversationListItem.tsx
+++ b/ts/components/ConversationListItem.tsx
@@ -243,7 +243,7 @@ const ConversationListItem = (props: Props) => {
       <div
         role="button"
         onClick={() => {
-          dispatch(openConversationExternal(conversationId));
+          dispatch(openConversationExternal({ id: conversationId }));
         }}
         onContextMenu={(e: any) => {
           contextMenu.show({
diff --git a/ts/components/MessageSearchResult.tsx b/ts/components/MessageSearchResult.tsx
index 1787c1801..3faf761bc 100644
--- a/ts/components/MessageSearchResult.tsx
+++ b/ts/components/MessageSearchResult.tsx
@@ -82,7 +82,7 @@ const AvatarItem = (props: { from: FindAndFormatContactType }) => {
   );
 };
 export const MessageSearchResult = (props: Props) => {
-  const { from, id, isSelected, conversationId, receivedAt, snippet, to } = props;
+  const { from, id: messageId, isSelected, conversationId, receivedAt, snippet, to } = props;
 
   const dispatch = useDispatch();
 
@@ -94,7 +94,7 @@ export const MessageSearchResult = (props: Props) => {
     <div
       role="button"
       onClick={() => {
-        dispatch(openConversationExternal(conversationId, id));
+        dispatch(openConversationExternal({ id: conversationId, messageId }));
       }}
       className={classNames(
         'module-message-search-result',
diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx
index 333a6fb35..52c073430 100644
--- a/ts/components/conversation/ConversationHeader.tsx
+++ b/ts/components/conversation/ConversationHeader.tsx
@@ -14,20 +14,19 @@ import {
   getConversationHeaderProps,
   getConversationHeaderTitleProps,
   getSelectedConversation,
+  getSelectedMessageIds,
+  isMessageDetailView,
+  isMessageSelectionMode,
 } from '../../state/selectors/conversations';
 import { useDispatch, useSelector } from 'react-redux';
 import { useMembersAvatars } from '../../hooks/useMembersAvatar';
+
+import { deleteMessagesById } from '../../interactions/conversationInteractions';
 import {
   closeMessageDetailsView,
   openRightPanel,
   resetSelectedMessageIds,
-} from '../../state/ducks/conversationScreen';
-import {
-  getSelectedMessageIds,
-  isMessageDetailView,
-  isMessageSelectionMode,
-} from '../../state/selectors/conversationScreen';
-import { deleteMessagesById } from '../../interactions/conversationInteractions';
+} from '../../state/ducks/conversations';
 
 export interface TimerOption {
   name: string;
diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx
index 1f9c5973c..133bff565 100644
--- a/ts/components/conversation/Message.tsx
+++ b/ts/components/conversation/Message.tsx
@@ -42,12 +42,12 @@ import autoBind from 'auto-bind';
 import { AudioPlayerWithEncryptedFile } from './H5AudioPlayer';
 import { ClickToTrustSender } from './message/ClickToTrustSender';
 import { getMessageById } from '../../data/data';
-import { showMessageDetailsView } from '../../state/ducks/conversationScreen';
 import { deleteMessagesById } from '../../interactions/conversationInteractions';
 import { getSelectedMessage } from '../../state/selectors/search';
 import { connect } from 'react-redux';
 import { StateType } from '../../state/reducer';
-import { getSelectedMessageIds, isMessageSelected } from '../../state/selectors/conversationScreen';
+import { getSelectedMessageIds } from '../../state/selectors/conversations';
+import { showMessageDetailsView } from '../../state/ducks/conversations';
 
 // Same as MIN_WIDTH in ImageGrid.tsx
 const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;
diff --git a/ts/components/conversation/MessageDetail.tsx b/ts/components/conversation/MessageDetail.tsx
index 74682aaae..d8bebfa56 100644
--- a/ts/components/conversation/MessageDetail.tsx
+++ b/ts/components/conversation/MessageDetail.tsx
@@ -8,8 +8,8 @@ import { Message } from './Message';
 import { MessageRegularProps } from '../../models/messageType';
 import { deleteMessagesById } from '../../interactions/conversationInteractions';
 import { useSelector } from 'react-redux';
-import { getMessageDetailsViewProps } from '../../state/selectors/conversationScreen';
 import { ContactPropsMessageDetail } from '../../state/ducks/conversations';
+import { getMessageDetailsViewProps } from '../../state/selectors/conversations';
 
 const AvatarItem = (props: { contact: ContactPropsMessageDetail }) => {
   const { avatarPath, phoneNumber, name, profileName } = props.contact;
diff --git a/ts/components/session/ActionsPanel.tsx b/ts/components/session/ActionsPanel.tsx
index bae63749c..c94a8d1bc 100644
--- a/ts/components/session/ActionsPanel.tsx
+++ b/ts/components/session/ActionsPanel.tsx
@@ -37,7 +37,11 @@ import { getOpenGroupManager } from '../../opengroup/opengroupV2/OpenGroupManage
 import { forceRefreshRandomSnodePool } from '../../session/snode_api/snodePool';
 import { getSwarmPollingInstance } from '../../session/snode_api';
 import { DURATION } from '../../session/constants';
-import { actions as conversationActions } from '../../state/ducks/conversations';
+import {
+  actions as conversationActions,
+  conversationChanged,
+  conversationRemoved,
+} from '../../state/ducks/conversations';
 import { editProfileModal, onionPathModal } from '../../state/ducks/modalDialog';
 import { uploadOurAvatar } from '../../interactions/conversationInteractions';
 import { ModalContainer } from './ModalContainer';
@@ -173,9 +177,9 @@ const removeAllV1OpenGroups = async () => {
       window.log.info(`deleting v1convo : ${v1Convo.id}`);
       getConversationController().unsafeDelete(v1Convo);
       if (window.inboxStore) {
-        window.inboxStore?.dispatch(conversationActions.conversationRemoved(v1Convo.id));
+        window.inboxStore?.dispatch(conversationRemoved(v1Convo.id));
         window.inboxStore?.dispatch(
-          conversationActions.conversationChanged(v1Convo.id, v1Convo.getProps())
+          conversationChanged({ id: v1Convo.id, data: v1Convo.getProps() })
         );
       }
     } catch (e) {
diff --git a/ts/components/session/LeftPaneMessageSection.tsx b/ts/components/session/LeftPaneMessageSection.tsx
index acd15c412..f2ea3701c 100644
--- a/ts/components/session/LeftPaneMessageSection.tsx
+++ b/ts/components/session/LeftPaneMessageSection.tsx
@@ -319,7 +319,7 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
         pubkeyorOns,
         ConversationTypeEnum.PRIVATE
       );
-      window.inboxStore?.dispatch(openConversationExternal(pubkeyorOns));
+      window.inboxStore?.dispatch(openConversationExternal({ id: pubkeyorOns }));
       this.handleToggleOverlay(undefined);
     } else {
       // this might be an ONS, validate the regex first
@@ -339,7 +339,7 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
           resolvedSessionID,
           ConversationTypeEnum.PRIVATE
         );
-        window.inboxStore?.dispatch(openConversationExternal(resolvedSessionID));
+        window.inboxStore?.dispatch(openConversationExternal({ id: resolvedSessionID }));
         this.handleToggleOverlay(undefined);
       } catch (e) {
         window?.log?.warn('failed to resolve ons name', pubkeyorOns, e);
diff --git a/ts/components/session/SessionInboxView.tsx b/ts/components/session/SessionInboxView.tsx
index 2c0a42add..ff9a2bb87 100644
--- a/ts/components/session/SessionInboxView.tsx
+++ b/ts/components/session/SessionInboxView.tsx
@@ -23,7 +23,6 @@ import { SessionMainPanel } from '../SessionMainPanel';
 import { PersistGate } from 'redux-persist/integration/react';
 import { persistStore } from 'redux-persist';
 import { TimerOptionsArray, TimerOptionsState } from '../../state/ducks/timerOptions';
-import { initialConversationScreen } from '../../state/ducks/conversationScreen';
 
 // Workaround: A react component's required properties are filtering up through connect()
 //   https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
@@ -103,6 +102,10 @@ export class SessionInboxView extends React.Component<any, State> {
       conversations: {
         conversationLookup: makeLookup(fullFilledConversations, 'id'),
         messages: [],
+        showRightPanel: false,
+        messageDetailProps: undefined,
+        selectedMessageIds: [],
+        selectedConversation: undefined,
       },
       user: {
         ourNumber: UserUtils.getOurPubKeyStrFromCache(),
@@ -118,7 +121,6 @@ export class SessionInboxView extends React.Component<any, State> {
       timerOptions: {
         timerOptions,
       },
-      conversationScreen: initialConversationScreen,
     };
 
     this.store = createStore(initialState);
diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx
index 20eaca802..5644e0ffd 100644
--- a/ts/components/session/conversation/SessionConversation.tsx
+++ b/ts/components/session/conversation/SessionConversation.tsx
@@ -22,6 +22,7 @@ import {
   fetchMessagesForConversation,
   PropsForMessage,
   ReduxConversationType,
+  resetSelectedMessageIds,
   SortedMessageModelProps,
 } from '../../../state/ducks/conversations';
 import { MessageView } from '../../MainViewController';
@@ -34,7 +35,6 @@ import { getDecryptedMediaUrl } from '../../../session/crypto/DecryptedAttachmen
 import { updateMentionsMembers } from '../../../state/ducks/mentionsInput';
 import { sendDataExtractionNotification } from '../../../session/messages/outgoing/controlMessage/DataExtractionNotificationMessage';
 
-import { resetSelectedMessageIds } from '../../../state/ducks/conversationScreen';
 interface State {
   unreadCount: number;
 
diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx
index f1e6e43ea..6043972e1 100644
--- a/ts/components/session/conversation/SessionMessagesList.tsx
+++ b/ts/components/session/conversation/SessionMessagesList.tsx
@@ -26,11 +26,11 @@ import autoBind from 'auto-bind';
 import { ConversationTypeEnum } from '../../../models/conversation';
 import { DataExtractionNotification } from '../../conversation/DataExtractionNotification';
 import { StateType } from '../../../state/reducer';
-import { getSelectedMessageIds } from '../../../state/selectors/conversationScreen';
 import { connect } from 'react-redux';
 import {
   getSelectedConversation,
   getSelectedConversationKey,
+  getSelectedMessageIds,
 } from '../../../state/selectors/conversations';
 
 interface State {
diff --git a/ts/components/session/conversation/SessionRightPanel.tsx b/ts/components/session/conversation/SessionRightPanel.tsx
index 3a670dbb6..38f0e6a1b 100644
--- a/ts/components/session/conversation/SessionRightPanel.tsx
+++ b/ts/components/session/conversation/SessionRightPanel.tsx
@@ -38,10 +38,9 @@ import { MediaItemType } from '../../LightboxGallery';
 import useInterval from 'react-use/lib/useInterval';
 import { useDispatch, useSelector } from 'react-redux';
 import { getTimerOptions } from '../../../state/selectors/timerOptions';
-import { closeRightPanel } from '../../../state/ducks/conversationScreen';
-import { isRightPanelShowing } from '../../../state/selectors/conversationScreen';
-import { getSelectedConversation } from '../../../state/selectors/conversations';
+import { getSelectedConversation, isRightPanelShowing } from '../../../state/selectors/conversations';
 import { useMembersAvatars } from '../../../hooks/useMembersAvatar';
+import { closeRightPanel } from '../../../state/ducks/conversations';
 
 type Props = {
   memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts
index 17a297732..110a916ba 100644
--- a/ts/interactions/conversationInteractions.ts
+++ b/ts/interactions/conversationInteractions.ts
@@ -32,14 +32,17 @@ import {
   lastAvatarUploadTimestamp,
   removeAllMessagesInConversation,
 } from '../data/data';
-import { conversationReset, SortedMessageModelProps } from '../state/ducks/conversations';
+import {
+  conversationReset,
+  resetSelectedMessageIds,
+  SortedMessageModelProps,
+} from '../state/ducks/conversations';
 import { getDecryptedMediaUrl } from '../session/crypto/DecryptedAttachmentsManager';
 import { IMAGE_JPEG } from '../types/MIME';
 import { FSv2 } from '../fileserver';
 import { fromBase64ToArray, toHex } from '../session/utils/String';
 import { SessionButtonColor } from '../components/session/SessionButton';
 import { perfEnd, perfStart } from '../session/utils/Performance';
-import { resetSelectedMessageIds } from '../state/ducks/conversationScreen';
 
 export const getCompleteUrlForV2ConvoId = async (convoId: string) => {
   if (convoId.match(openGroupV2ConversationIdRegex)) {
@@ -286,11 +289,7 @@ export function showChangeNickNameByConvoId(conversationId: string) {
 export async function deleteMessagesByConvoIdNoConfirmation(conversationId: string) {
   const conversation = getConversationController().get(conversationId);
   await removeAllMessagesInConversation(conversationId);
-  window.inboxStore?.dispatch(
-    conversationReset({
-      conversationKey: conversationId,
-    })
-  );
+  window.inboxStore?.dispatch(conversationReset(conversationId));
 
   // destroy message keeps the active timestamp set so the
   // conversation still appears on the conversation list but is empty
diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts
index 4505dd990..1b9d83d42 100644
--- a/ts/models/conversation.ts
+++ b/ts/models/conversation.ts
@@ -23,6 +23,7 @@ import {
 import { fromArrayBufferToBase64, fromBase64ToArrayBuffer } from '../session/utils/String';
 import {
   actions as conversationActions,
+  conversationChanged,
   LastMessageStatusType,
   MessageModelProps,
   ReduxConversationType,
@@ -202,7 +203,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
     this.typingRefreshTimer = null;
     this.typingPauseTimer = null;
 
-    window.inboxStore?.dispatch(conversationActions.conversationChanged(this.id, this.getProps()));
+    window.inboxStore?.dispatch(conversationChanged({ id: this.id, data: this.getProps() }));
   }
 
   public idForLogging() {
@@ -885,9 +886,12 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
 
   public triggerUIRefresh() {
     window.inboxStore?.dispatch(
-      conversationActions.conversationChanged(this.id, {
-        ...this.getProps(),
-        isSelected: false,
+      conversationChanged({
+        id: this.id,
+        data: {
+          ...this.getProps(),
+          isSelected: false,
+        },
       })
     );
   }
diff --git a/ts/receiver/closedGroups.ts b/ts/receiver/closedGroups.ts
index b9c5d6073..100e9d755 100644
--- a/ts/receiver/closedGroups.ts
+++ b/ts/receiver/closedGroups.ts
@@ -31,7 +31,10 @@ import { forceSyncConfigurationNowIfNeeded } from '../session/utils/syncUtils';
 import { getMessageController } from '../session/messages';
 import { ClosedGroupEncryptionPairReplyMessage } from '../session/messages/outgoing/controlMessage/group/ClosedGroupEncryptionPairReplyMessage';
 import { queueAllCachedFromSource } from './receiver';
-import { actions as conversationActions } from '../state/ducks/conversations';
+import {
+  actions as conversationActions,
+  openConversationExternal,
+} from '../state/ducks/conversations';
 import { getSwarmPollingInstance } from '../session/snode_api';
 import { MessageModel } from '../models/message';
 
@@ -952,7 +955,7 @@ export async function createClosedGroup(groupName: string, members: Array<string
 
   await forceSyncConfigurationNowIfNeeded();
 
-  window.inboxStore?.dispatch(conversationActions.openConversationExternal(groupPublicKey));
+  window.inboxStore?.dispatch(openConversationExternal({ id: groupPublicKey }));
 }
 
 /**
diff --git a/ts/session/conversations/ConversationController.ts b/ts/session/conversations/ConversationController.ts
index 5d249c1b4..82804d8cf 100644
--- a/ts/session/conversations/ConversationController.ts
+++ b/ts/session/conversations/ConversationController.ts
@@ -118,7 +118,10 @@ export class ConversationController {
     conversation.initialPromise.then(async () => {
       if (window?.inboxStore) {
         window.inboxStore?.dispatch(
-          conversationActions.conversationAdded(conversation.id, conversation.getProps())
+          conversationActions.conversationAdded({
+            id: conversation.id,
+            data: conversation.getProps(),
+          })
         );
       }
       if (!conversation.isPublic()) {
@@ -245,7 +248,10 @@ export class ConversationController {
       if (window?.inboxStore) {
         window.inboxStore?.dispatch(conversationActions.conversationRemoved(conversation.id));
         window.inboxStore?.dispatch(
-          conversationActions.conversationChanged(conversation.id, conversation.getProps())
+          conversationActions.conversationChanged({
+            id: conversation.id,
+            data: conversation.getProps(),
+          })
         );
       }
       window.log.info(`deleteContact !isPrivate, convo removed from store: ${id}`);
diff --git a/ts/state/createStore.ts b/ts/state/createStore.ts
index 196ed439f..93240e04c 100644
--- a/ts/state/createStore.ts
+++ b/ts/state/createStore.ts
@@ -1,7 +1,7 @@
 import promise from 'redux-promise-middleware';
 import { createLogger } from 'redux-logger';
 import { configureStore } from '@reduxjs/toolkit';
-import { reducer as allReducers } from './reducer';
+import { rootReducer } from './reducer';
 import { persistReducer } from 'redux-persist';
 
 // tslint:disable-next-line: no-submodule-imports match-default-export-name
@@ -32,7 +32,7 @@ export const persistConfig = {
   whitelist: ['userConfig'],
 };
 
-const persistedReducer = persistReducer(persistConfig, allReducers);
+const persistedReducer = persistReducer(persistConfig, rootReducer);
 
 // Exclude logger if we're in production mode
 const disableLogging = env === 'production' || true; // ALWAYS TURNED OFF
diff --git a/ts/state/ducks/conversationScreen.tsx b/ts/state/ducks/conversationScreen.tsx
deleted file mode 100644
index c7e015a97..000000000
--- a/ts/state/ducks/conversationScreen.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import { createSlice, PayloadAction } from '@reduxjs/toolkit';
-import { MessagePropsDetails } from './conversations';
-
-export type ConversationScreenState = {
-  messageDetailProps: MessagePropsDetails | undefined;
-  showRightPanel: boolean;
-  selectedMessageIds: Array<string>;
-};
-
-export const initialConversationScreen: ConversationScreenState = {
-  messageDetailProps: undefined,
-  showRightPanel: false,
-  selectedMessageIds: [],
-};
-
-/**
- * This slice is the one holding the layout of the Conversation Screen of the app
- */
-const conversationScreenSlice = createSlice({
-  name: 'conversationScreen',
-  initialState: initialConversationScreen,
-  reducers: {
-    showMessageDetailsView(
-      state: ConversationScreenState,
-      action: PayloadAction<MessagePropsDetails>
-    ) {
-      // force the right panel to be hidden when showing message detail view
-      return { ...state, messageDetailProps: action.payload, showRightPanel: false };
-    },
-
-    closeMessageDetailsView(state: ConversationScreenState) {
-      return { ...state, messageDetailProps: undefined };
-    },
-
-    openRightPanel(state: ConversationScreenState) {
-      return { ...state, showRightPanel: true };
-    },
-    closeRightPanel(state: ConversationScreenState) {
-      return { ...state, showRightPanel: false };
-    },
-    addMessageIdToSelection(state: ConversationScreenState, action: PayloadAction<string>) {
-      if (state.selectedMessageIds.some(id => id === action.payload)) {
-        return state;
-      }
-      return { ...state, selectedMessageIds: [...state.selectedMessageIds, action.payload] };
-    },
-    removeMessageIdFromSelection(state: ConversationScreenState, action: PayloadAction<string>) {
-      const index = state.selectedMessageIds.findIndex(id => id === action.payload);
-
-      if (index === -1) {
-        return state;
-      }
-      return { ...state, selectedMessageIds: state.selectedMessageIds.splice(index, 1) };
-    },
-    toggleSelectedMessageId(state: ConversationScreenState, action: PayloadAction<string>) {
-      const index = state.selectedMessageIds.findIndex(id => id === action.payload);
-
-      if (index === -1) {
-        return { ...state, selectedMessageIds: [...state.selectedMessageIds, action.payload] };
-      }
-      return { ...state, selectedMessageIds: state.selectedMessageIds.splice(index, 1) };
-    },
-    resetSelectedMessageIds(state: ConversationScreenState) {
-      return { ...state, selectedMessageIds: [] };
-    },
-  },
-});
-
-// destructures
-const { actions, reducer } = conversationScreenSlice;
-export const {
-  showMessageDetailsView,
-  closeMessageDetailsView,
-  openRightPanel,
-  closeRightPanel,
-  addMessageIdToSelection,
-  resetSelectedMessageIds,
-} = actions;
-export const defaultConversationScreenReducer = reducer;
diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts
index 535a4c9e0..368029324 100644
--- a/ts/state/ducks/conversations.ts
+++ b/ts/state/ducks/conversations.ts
@@ -1,9 +1,8 @@
 import _, { omit } from 'lodash';
 
 import { Constants } from '../../session';
-import { createAsyncThunk } from '@reduxjs/toolkit';
+import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
 import { getConversationController } from '../../session/conversations';
-import { MessageModel } from '../../models/message';
 import { getMessagesByConversation } from '../../data/data';
 import {
   ConversationNotificationSettingType,
@@ -231,6 +230,9 @@ export type ConversationsStateType = {
   conversationLookup: ConversationLookupType;
   selectedConversation?: string;
   messages: Array<SortedMessageModelProps>;
+  messageDetailProps: MessagePropsDetails | undefined;
+  showRightPanel: boolean;
+  selectedMessageIds: Array<string>;
 };
 
 async function getMessages(
@@ -338,246 +340,15 @@ export const fetchMessagesForConversation = createAsyncThunk(
   }
 );
 
-// Actions
-
-type ConversationAddedActionType = {
-  type: 'CONVERSATION_ADDED';
-  payload: {
-    id: string;
-    data: ReduxConversationType;
-  };
-};
-type ConversationChangedActionType = {
-  type: 'CONVERSATION_CHANGED';
-  payload: {
-    id: string;
-    data: ReduxConversationType;
-  };
-};
-type ConversationRemovedActionType = {
-  type: 'CONVERSATION_REMOVED';
-  payload: {
-    id: string;
-  };
-};
-export type RemoveAllConversationsActionType = {
-  type: 'CONVERSATIONS_REMOVE_ALL';
-  payload: null;
-};
-export type MessageExpiredActionType = {
-  type: 'MESSAGE_EXPIRED';
-  payload: {
-    messageId: string;
-    conversationKey: string;
-  };
-};
-export type MessageChangedActionType = {
-  type: 'MESSAGE_CHANGED';
-  payload: MessageModelProps;
-};
-export type MessagesChangedActionType = {
-  type: 'MESSAGES_CHANGED';
-  payload: Array<MessageModelProps>;
-};
-export type MessageAddedActionType = {
-  type: 'MESSAGE_ADDED';
-  payload: {
-    conversationKey: string;
-    messageModelProps: MessageModelProps;
-  };
-};
-export type MessageDeletedActionType = {
-  type: 'MESSAGE_DELETED';
-  payload: {
-    conversationKey: string;
-    messageId: string;
-  };
-};
-export type ConversationResetActionType = {
-  type: 'CONVERSATION_RESET';
-  payload: {
-    conversationKey: string;
-  };
-};
-export type SelectedConversationChangedActionType = {
-  type: 'SELECTED_CONVERSATION_CHANGED';
-  payload: {
-    id: string;
-    messageId?: string;
-  };
-};
-
-export type FetchMessagesForConversationType = {
-  type: 'messages/fetchByConversationKey/fulfilled';
-  payload: {
-    conversationKey: string;
-    messages: Array<MessageModelProps>;
-  };
-};
-
-export type ConversationActionType =
-  | ConversationAddedActionType
-  | ConversationChangedActionType
-  | ConversationRemovedActionType
-  | ConversationResetActionType
-  | RemoveAllConversationsActionType
-  | MessageExpiredActionType
-  | MessageAddedActionType
-  | MessageDeletedActionType
-  | MessageChangedActionType
-  | MessagesChangedActionType
-  | SelectedConversationChangedActionType
-  | SelectedConversationChangedActionType
-  | FetchMessagesForConversationType;
-
-// Action Creators
-
-export const actions = {
-  conversationAdded,
-  conversationChanged,
-  conversationRemoved,
-  removeAllConversations,
-  messageExpired,
-  messageAdded,
-  messageDeleted,
-  conversationReset,
-  messageChanged,
-  messagesChanged,
-  fetchMessagesForConversation,
-  openConversationExternal,
-};
-
-function conversationAdded(id: string, data: ReduxConversationType): ConversationAddedActionType {
-  return {
-    type: 'CONVERSATION_ADDED',
-    payload: {
-      id,
-      data,
-    },
-  };
-}
-function conversationChanged(
-  id: string,
-  data: ReduxConversationType
-): ConversationChangedActionType {
-  return {
-    type: 'CONVERSATION_CHANGED',
-    payload: {
-      id,
-      data,
-    },
-  };
-}
-function conversationRemoved(id: string): ConversationRemovedActionType {
-  return {
-    type: 'CONVERSATION_REMOVED',
-    payload: {
-      id,
-    },
-  };
-}
-function removeAllConversations(): RemoveAllConversationsActionType {
-  return {
-    type: 'CONVERSATIONS_REMOVE_ALL',
-    payload: null,
-  };
-}
-
-function messageExpired({
-  conversationKey,
-  messageId,
-}: {
-  conversationKey: string;
-  messageId: string;
-}): MessageExpiredActionType {
-  return {
-    type: 'MESSAGE_EXPIRED',
-    payload: {
-      conversationKey,
-      messageId,
-    },
-  };
-}
-
-function messageChanged(messageModelProps: MessageModelProps): MessageChangedActionType {
-  return {
-    type: 'MESSAGE_CHANGED',
-    payload: messageModelProps,
-  };
-}
-
-function messagesChanged(messageModelsProps: Array<MessageModelProps>): MessagesChangedActionType {
-  return {
-    type: 'MESSAGES_CHANGED',
-    payload: messageModelsProps,
-  };
-}
-
-function messageAdded({
-  conversationKey,
-  messageModelProps,
-}: {
-  conversationKey: string;
-  messageModelProps: MessageModelProps;
-}): MessageAddedActionType {
-  return {
-    type: 'MESSAGE_ADDED',
-    payload: {
-      conversationKey,
-      messageModelProps,
-    },
-  };
-}
-
-function messageDeleted({
-  conversationKey,
-  messageId,
-}: {
-  conversationKey: string;
-  messageId: string;
-}): MessageDeletedActionType {
-  return {
-    type: 'MESSAGE_DELETED',
-    payload: {
-      conversationKey,
-      messageId,
-    },
-  };
-}
-
-export function conversationReset({
-  conversationKey,
-}: {
-  conversationKey: string;
-}): ConversationResetActionType {
-  return {
-    type: 'CONVERSATION_RESET',
-    payload: {
-      conversationKey,
-    },
-  };
-}
-
-export function openConversationExternal(
-  id: string,
-  messageId?: string
-): SelectedConversationChangedActionType {
-  window?.log?.info(`openConversationExternal with convoId: ${id}; messageId: ${messageId}`);
-  return {
-    type: 'SELECTED_CONVERSATION_CHANGED',
-    payload: {
-      id,
-      messageId,
-    },
-  };
-}
-
 // Reducer
 
 function getEmptyState(): ConversationsStateType {
   return {
     conversationLookup: {},
     messages: [],
+    messageDetailProps: undefined,
+    showRightPanel: false,
+    selectedMessageIds: [],
   };
 }
 
@@ -607,7 +378,13 @@ function sortMessages(
   return messagesSorted;
 }
 
-function handleMessageAdded(state: ConversationsStateType, action: MessageAddedActionType) {
+function handleMessageAdded(
+  state: ConversationsStateType,
+  action: PayloadAction<{
+    conversationKey: string;
+    messageModelProps: MessageModelProps;
+  }>
+) {
   const { messages } = state;
   const { conversationKey, messageModelProps: addedMessageProps } = action.payload;
   if (conversationKey === state.selectedConversation) {
@@ -631,9 +408,7 @@ function handleMessageAdded(state: ConversationsStateType, action: MessageAddedA
   return state;
 }
 
-function handleMessageChanged(state: ConversationsStateType, action: MessageChangedActionType) {
-  const { payload } = action;
-
+function handleMessageChanged(state: ConversationsStateType, payload: MessageModelProps) {
   const messageInStoreIndex = state?.messages?.findIndex(
     m => m.propsForMessage.id === payload.propsForMessage.id
   );
@@ -661,15 +436,10 @@ function handleMessageChanged(state: ConversationsStateType, action: MessageChan
   return state;
 }
 
-function handleMessagesChanged(state: ConversationsStateType, action: MessagesChangedActionType) {
-  const { payload } = action;
-
+function handleMessagesChanged(state: ConversationsStateType, payload: Array<MessageModelProps>) {
   payload.forEach(element => {
     // tslint:disable-next-line: no-parameter-reassignment
-    state = handleMessageChanged(state, {
-      payload: element,
-      type: 'MESSAGE_CHANGED',
-    });
+    state = handleMessageChanged(state, element);
   });
 
   return state;
@@ -677,7 +447,10 @@ function handleMessagesChanged(state: ConversationsStateType, action: MessagesCh
 
 function handleMessageExpiredOrDeleted(
   state: ConversationsStateType,
-  action: MessageDeletedActionType | MessageExpiredActionType
+  action: PayloadAction<{
+    messageId: string;
+    conversationKey: string;
+  }>
 ) {
   const { conversationKey, messageId } = action.payload;
   if (conversationKey === state.selectedConversation) {
@@ -709,11 +482,8 @@ function handleMessageExpiredOrDeleted(
   return state;
 }
 
-function handleConversationReset(
-  state: ConversationsStateType,
-  action: ConversationResetActionType
-) {
-  const { conversationKey } = action.payload;
+function handleConversationReset(state: ConversationsStateType, action: PayloadAction<string>) {
+  const conversationKey = action.payload;
   if (conversationKey === state.selectedConversation) {
     // just empty the list of messages
     return {
@@ -724,110 +494,220 @@ function handleConversationReset(
   return state;
 }
 
-// tslint:disable: cyclomatic-complexity
-// tslint:disable: max-func-body-length
-export function reducer(
-  state: ConversationsStateType = getEmptyState(),
-  action: ConversationActionType
-): ConversationsStateType {
-  if (action.type === 'CONVERSATION_ADDED') {
-    const { payload } = action;
-    const { id, data } = payload;
-    const { conversationLookup } = state;
+const conversationsSlice = createSlice({
+  name: 'conversations',
+  initialState: getEmptyState(),
+  reducers: {
+    showMessageDetailsView(
+      state: ConversationsStateType,
+      action: PayloadAction<MessagePropsDetails>
+    ) {
+      // force the right panel to be hidden when showing message detail view
+      return { ...state, messageDetailProps: action.payload, showRightPanel: false };
+    },
 
-    return {
-      ...state,
-      conversationLookup: {
-        ...conversationLookup,
-        [id]: data,
-      },
-    };
-  }
-  if (action.type === 'CONVERSATION_CHANGED') {
-    const { payload } = action;
-    const { id, data } = payload;
-    const { conversationLookup, selectedConversation } = state;
-
-    const existing = conversationLookup[id];
-    // In the change case we only modify the lookup if we already had that conversation
-    if (!existing) {
-      return state;
-    }
+    closeMessageDetailsView(state: ConversationsStateType) {
+      return { ...state, messageDetailProps: undefined };
+    },
 
-    return {
-      ...state,
-      selectedConversation,
-      conversationLookup: {
-        ...conversationLookup,
-        [id]: data,
-      },
-    };
-  }
-  if (action.type === 'CONVERSATION_REMOVED') {
-    const { payload } = action;
-    const { id } = payload;
-    const { conversationLookup, selectedConversation } = state;
-    return {
-      ...state,
-      conversationLookup: omit(conversationLookup, [id]),
-      selectedConversation: selectedConversation === id ? undefined : selectedConversation,
-    };
-  }
-  if (action.type === 'CONVERSATIONS_REMOVE_ALL') {
-    return getEmptyState();
-  }
+    openRightPanel(state: ConversationsStateType) {
+      return { ...state, showRightPanel: true };
+    },
+    closeRightPanel(state: ConversationsStateType) {
+      return { ...state, showRightPanel: false };
+    },
+    addMessageIdToSelection(state: ConversationsStateType, action: PayloadAction<string>) {
+      if (state.selectedMessageIds.some(id => id === action.payload)) {
+        return state;
+      }
+      return { ...state, selectedMessageIds: [...state.selectedMessageIds, action.payload] };
+    },
+    removeMessageIdFromSelection(state: ConversationsStateType, action: PayloadAction<string>) {
+      const index = state.selectedMessageIds.findIndex(id => id === action.payload);
+
+      if (index === -1) {
+        return state;
+      }
+      return { ...state, selectedMessageIds: state.selectedMessageIds.splice(index, 1) };
+    },
+    toggleSelectedMessageId(state: ConversationsStateType, action: PayloadAction<string>) {
+      const index = state.selectedMessageIds.findIndex(id => id === action.payload);
+
+      if (index === -1) {
+        return { ...state, selectedMessageIds: [...state.selectedMessageIds, action.payload] };
+      }
+      return { ...state, selectedMessageIds: state.selectedMessageIds.splice(index, 1) };
+    },
+    resetSelectedMessageIds(state: ConversationsStateType) {
+      return { ...state, selectedMessageIds: [] };
+    },
 
-  if (action.type === 'SELECTED_CONVERSATION_CHANGED') {
-    const { payload } = action;
-    const { id } = payload;
-    const oldSelectedConversation = state.selectedConversation;
-    const newSelectedConversation = id;
+    conversationAdded(
+      state: ConversationsStateType,
+      action: PayloadAction<{
+        id: string;
+        data: ReduxConversationType;
+      }>
+    ) {
+      const { conversationLookup } = state;
 
-    if (newSelectedConversation !== oldSelectedConversation) {
-      // empty the message list
       return {
         ...state,
-        messages: [],
-        selectedConversation: id,
+        conversationLookup: {
+          ...conversationLookup,
+          [action.payload.id]: action.payload.data,
+        },
       };
-    }
-    return {
-      ...state,
-      selectedConversation: id,
-    };
-  }
+    },
+    conversationChanged(
+      state: ConversationsStateType,
+      action: PayloadAction<{
+        id: string;
+        data: ReduxConversationType;
+      }>
+    ) {
+      const { payload } = action;
+      const { id, data } = payload;
+      const { conversationLookup, selectedConversation } = state;
+
+      const existing = conversationLookup[id];
+      // In the change case we only modify the lookup if we already had that conversation
+      if (!existing) {
+        return state;
+      }
 
-  // this is called once the messages are loaded from the db for the currently selected conversation
-  if (action.type === fetchMessagesForConversation.fulfilled.type) {
-    const { messagesProps, conversationKey } = action.payload as FetchedMessageResults;
-    // double check that this update is for the shown convo
-    if (conversationKey === state.selectedConversation) {
       return {
         ...state,
-        messages: messagesProps,
+        selectedConversation,
+        conversationLookup: {
+          ...conversationLookup,
+          [id]: data,
+        },
       };
-    }
-    return state;
-  }
+    },
 
-  if (action.type === 'MESSAGE_CHANGED') {
-    return handleMessageChanged(state, action);
-  }
+    conversationRemoved(
+      state: ConversationsStateType,
+      action: PayloadAction<{
+        id: string;
+      }>
+    ) {
+      const { payload } = action;
+      const { id } = payload;
+      const { conversationLookup, selectedConversation } = state;
+      return {
+        ...state,
+        conversationLookup: omit(conversationLookup, [id]),
+        selectedConversation: selectedConversation === id ? undefined : selectedConversation,
+      };
+    },
 
-  if (action.type === 'MESSAGES_CHANGED') {
-    return handleMessagesChanged(state, action);
-  }
+    removeAllConversations() {
+      return getEmptyState();
+    },
 
-  if (action.type === 'MESSAGE_ADDED') {
-    return handleMessageAdded(state, action);
-  }
-  if (action.type === 'MESSAGE_EXPIRED' || action.type === 'MESSAGE_DELETED') {
-    return handleMessageExpiredOrDeleted(state, action);
-  }
+    messageAdded(
+      state: ConversationsStateType,
+      action: PayloadAction<{
+        conversationKey: string;
+        messageModelProps: MessageModelProps;
+      }>
+    ) {
+      return handleMessageAdded(state, action);
+    },
 
-  if (action.type === 'CONVERSATION_RESET') {
-    return handleConversationReset(state, action);
-  }
+    messageChanged(state: ConversationsStateType, action: PayloadAction<MessageModelProps>) {
+      return handleMessageChanged(state, action.payload);
+    },
+    messagesChanged(
+      state: ConversationsStateType,
+      action: PayloadAction<Array<MessageModelProps>>
+    ) {
+      return handleMessagesChanged(state, action.payload);
+    },
 
-  return state;
-}
+    messageExpired(
+      state: ConversationsStateType,
+      action: PayloadAction<{
+        messageId: string;
+        conversationKey: string;
+      }>
+    ) {
+      return handleMessageExpiredOrDeleted(state, action);
+    },
+
+    messageDeleted(
+      state: ConversationsStateType,
+      action: PayloadAction<{
+        messageId: string;
+        conversationKey: string;
+      }>
+    ) {
+      return handleMessageExpiredOrDeleted(state, action);
+    },
+
+    conversationReset(state: ConversationsStateType, action: PayloadAction<string>) {
+      return handleConversationReset(state, action);
+    },
+
+    openConversationExternal(
+      state: ConversationsStateType,
+      action: PayloadAction<{
+        id: string;
+        messageId?: string;
+      }>
+    ) {
+      if (state.selectedConversation === action.payload.id) {
+        return state;
+      }
+      state.showRightPanel = false;
+      state.messageDetailProps = undefined;
+      state.selectedMessageIds = [];
+      state.selectedConversation = action.payload.id;
+      state.messages = [];
+      return state;
+    },
+  },
+  extraReducers: (builder: any) => {
+    // Add reducers for additional action types here, and handle loading state as needed
+    builder.addCase(
+      fetchMessagesForConversation.fulfilled,
+      (state: ConversationsStateType, action: any) => {
+        // this is called once the messages are loaded from the db for the currently selected conversation
+        const { messagesProps, conversationKey } = action.payload as FetchedMessageResults;
+        // double check that this update is for the shown convo
+        if (conversationKey === state.selectedConversation) {
+          return {
+            ...state,
+            messages: messagesProps,
+          };
+        }
+        return state;
+      }
+    );
+  },
+});
+
+// destructures
+export const { actions, reducer } = conversationsSlice;
+export const {
+  // conversation and messages list
+  conversationAdded,
+  conversationChanged,
+  conversationRemoved,
+  removeAllConversations,
+  messageExpired,
+  messageAdded,
+  messageDeleted,
+  conversationReset,
+  messageChanged,
+  messagesChanged,
+  openConversationExternal,
+  // layout stuff
+  showMessageDetailsView,
+  closeMessageDetailsView,
+  openRightPanel,
+  closeRightPanel,
+  addMessageIdToSelection,
+  resetSelectedMessageIds,
+} = actions;
diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts
index 463142266..3719a32d5 100644
--- a/ts/state/ducks/search.ts
+++ b/ts/state/ducks/search.ts
@@ -6,11 +6,9 @@ import { searchConversations, searchMessages } from '../../../ts/data/data';
 import { makeLookup } from '../../util/makeLookup';
 
 import {
-  MessageExpiredActionType,
+  openConversationExternal,
   PropsForSearchResults,
   ReduxConversationType,
-  RemoveAllConversationsActionType,
-  SelectedConversationChangedActionType,
 } from './conversations';
 import { PubKey } from '../../session/types';
 import { MessageModel } from '../../models/message';
@@ -64,10 +62,7 @@ type ClearSearchActionType = {
 export type SEARCH_TYPES =
   | SearchResultsFulfilledActionType
   | UpdateSearchTermActionType
-  | ClearSearchActionType
-  | MessageExpiredActionType
-  | RemoveAllConversationsActionType
-  | SelectedConversationChangedActionType;
+  | ClearSearchActionType;
 
 // Action Creators
 
@@ -332,39 +327,39 @@ export function reducer(state: SearchStateType | undefined, action: SEARCH_TYPES
     };
   }
 
-  if (action.type === 'CONVERSATIONS_REMOVE_ALL') {
-    return getEmptyState();
-  }
-
-  if (action.type === 'SELECTED_CONVERSATION_CHANGED') {
-    const { payload } = action;
-    const { messageId } = payload;
-
-    if (!messageId) {
-      return state;
-    }
-
-    return {
-      ...state,
-      selectedMessage: messageId,
-    };
-  }
-
-  if (action.type === 'MESSAGE_EXPIRED') {
-    const { messages, messageLookup } = state;
-    if (!messages.length) {
-      return state;
-    }
-
-    const { payload } = action;
-    const { messageId } = payload;
-
-    return {
-      ...state,
-      messages: reject(messages, message => messageId === message.id),
-      messageLookup: omit(messageLookup, ['id']),
-    };
-  }
+  // if (action.type === 'CONVERSATIONS_REMOVE_ALL') {
+  //   return getEmptyState();
+  // }
+
+  // if (action.type === openConversationExternal.name) {
+  //   const { payload } = action;
+  //   const { messageId } = payload;
+
+  //   if (!messageId) {
+  //     return state;
+  //   }
+
+  //   return {
+  //     ...state,
+  //     selectedMessage: messageId,
+  //   };
+  // }
+
+  // if (action.type === 'MESSAGE_EXPIRED') {
+  //   const { messages, messageLookup } = state;
+  //   if (!messages.length) {
+  //     return state;
+  //   }
+
+  //   const { payload } = action;
+  //   const { messageId } = payload;
+
+  //   return {
+  //     ...state,
+  //     messages: reject(messages, message => messageId === message.id),
+  //     messageLookup: omit(messageLookup, ['id']),
+  //   };
+  // }
 
   return state;
 }
diff --git a/ts/state/reducer.ts b/ts/state/reducer.ts
index 8760c5cc8..b75f2e52d 100644
--- a/ts/state/reducer.ts
+++ b/ts/state/reducer.ts
@@ -10,10 +10,7 @@ import {
   defaultMentionsInputReducer as mentionsInput,
   MentionsInputState,
 } from './ducks/mentionsInput';
-import {
-  ConversationScreenState,
-  defaultConversationScreenReducer as conversationScreen,
-} from './ducks/conversationScreen';
+
 import { defaultOnionReducer as onionPaths, OnionState } from './ducks/onion';
 import { modalReducer as modals, ModalState } from './ducks/modalDialog';
 import { userConfigReducer as userConfig, UserConfigState } from './ducks/userConfig';
@@ -31,7 +28,6 @@ export type StateType = {
   modals: ModalState;
   userConfig: UserConfigState;
   timerOptions: TimerOptionsState;
-  conversationScreen: ConversationScreenState;
 };
 
 export const reducers = {
@@ -46,10 +42,9 @@ export const reducers = {
   modals,
   userConfig,
   timerOptions,
-  conversationScreen,
 };
 
 // Making this work would require that our reducer signature supported AnyAction, not
 //   our restricted actions
 // @ts-ignore
-export const reducer = combineReducers(reducers);
+export const rootReducer = combineReducers(reducers);
diff --git a/ts/state/selectors/conversationScreen.ts b/ts/state/selectors/conversationScreen.ts
deleted file mode 100644
index 8e633f247..000000000
--- a/ts/state/selectors/conversationScreen.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { createSelector } from 'reselect';
-
-import { StateType } from '../reducer';
-import { ConversationScreenState } from '../ducks/conversationScreen';
-import { MessagePropsDetails } from '../ducks/conversations';
-
-export const getConversationScreenState = (state: StateType): ConversationScreenState =>
-  state.conversationScreen;
-
-export const isMessageDetailView = createSelector(
-  getConversationScreenState,
-  (state: ConversationScreenState): boolean => state.messageDetailProps !== undefined
-);
-
-export const getMessageDetailsViewProps = createSelector(
-  getConversationScreenState,
-  (state: ConversationScreenState): MessagePropsDetails | undefined => state.messageDetailProps
-);
-
-export const isRightPanelShowing = createSelector(
-  getConversationScreenState,
-  (state: ConversationScreenState): boolean => state.showRightPanel
-);
-
-export const isMessageSelectionMode = createSelector(
-  getConversationScreenState,
-  (state: ConversationScreenState): boolean => state.selectedMessageIds.length > 0
-);
-
-export const getSelectedMessageIds = createSelector(
-  getConversationScreenState,
-  (state: ConversationScreenState): Array<string> => state.selectedMessageIds
-);
-
-export const isMessageSelected = (messageId: string) =>
-  createSelector(getConversationScreenState, (state: ConversationScreenState): boolean =>
-    state.selectedMessageIds.includes(messageId)
-  );
diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts
index d755da206..17717f69a 100644
--- a/ts/state/selectors/conversations.ts
+++ b/ts/state/selectors/conversations.ts
@@ -4,6 +4,7 @@ import { StateType } from '../reducer';
 import {
   ConversationLookupType,
   ConversationsStateType,
+  MessagePropsDetails,
   ReduxConversationType,
   SortedMessageModelProps,
 } from '../ducks/conversations';
@@ -257,3 +258,28 @@ export const getNumberOfPinnedConversations = createSelector(getConversations, (
   const values = Object.values(state.conversationLookup);
   return values.filter(conversation => conversation.isPinned).length;
 });
+
+export const isMessageDetailView = createSelector(
+  getConversations,
+  (state: ConversationsStateType): boolean => state.messageDetailProps !== undefined
+);
+
+export const getMessageDetailsViewProps = createSelector(
+  getConversations,
+  (state: ConversationsStateType): MessagePropsDetails | undefined => state.messageDetailProps
+);
+
+export const isRightPanelShowing = createSelector(
+  getConversations,
+  (state: ConversationsStateType): boolean => state.showRightPanel
+);
+
+export const isMessageSelectionMode = createSelector(
+  getConversations,
+  (state: ConversationsStateType): boolean => state.selectedMessageIds.length > 0
+);
+
+export const getSelectedMessageIds = createSelector(
+  getConversations,
+  (state: ConversationsStateType): Array<string> => state.selectedMessageIds
+);
diff --git a/ts/state/smart/SessionConversation.tsx b/ts/state/smart/SessionConversation.tsx
index f8a31cef3..50eebefdd 100644
--- a/ts/state/smart/SessionConversation.tsx
+++ b/ts/state/smart/SessionConversation.tsx
@@ -7,13 +7,11 @@ import {
   getMessagesOfSelectedConversation,
   getSelectedConversation,
   getSelectedConversationKey,
-} from '../selectors/conversations';
-import { getOurNumber } from '../selectors/user';
-import {
   getSelectedMessageIds,
   isMessageDetailView,
   isRightPanelShowing,
-} from '../selectors/conversationScreen';
+} from '../selectors/conversations';
+import { getOurNumber } from '../selectors/user';
 
 const mapStateToProps = (state: StateType) => {
   return {