Pin conversations WIP. Pinning functioning and persisting on conversation list.

This commit is contained in:
Warrick Corfe-Tan 2021-06-29 10:52:55 +10:00
parent 4e8dcefa9a
commit 6dd7f34e4d
8 changed files with 73 additions and 15 deletions

View file

@ -22,6 +22,8 @@ import { OutgoingMessageStatus } from './conversation/message/OutgoingMessageSta
import { DefaultTheme, withTheme } from 'styled-components';
import { PubKey } from '../session/types';
import { ConversationType, openConversationExternal } from '../state/ducks/conversations';
import { SessionIcon, SessionIconSize, SessionIconType } from './session/icon';
import { SessionButtonColor } from './session/SessionButton';
export interface ConversationListItemProps extends ConversationType {
index?: number; // used to force a refresh when one conversation is removed on top of the list
@ -63,7 +65,7 @@ class ConversationListItem extends React.PureComponent<Props> {
}
public renderHeader() {
const { unreadCount, mentionedUs, activeAt } = this.props;
const { unreadCount, mentionedUs, activeAt, isPinned } = this.props;
let atSymbol = null;
let unreadCountDiv = null;
@ -81,7 +83,15 @@ class ConversationListItem extends React.PureComponent<Props> {
)}
>
{this.renderUser()}
</div>
{isPinned ?
<SessionIcon
iconType={SessionIconType.Pin}
iconColor={this.props.theme.colors.textColorSubtle}
iconSize={SessionIconSize.Tiny} />
: null
}
{unreadCountDiv}
{atSymbol}
{

View file

@ -1,5 +1,5 @@
import React from 'react';
import _ from 'lodash';
import { AutoSizer, List } from 'react-virtualized';
import { MainViewController } from '../MainViewController';
@ -81,12 +81,17 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
}
public renderRow = ({ index, key, style }: RowRendererParamsType): JSX.Element => {
const { conversations, openConversationExternal } = this.props;
const { openConversationExternal } = this.props;
let { conversations } = this.props;
if (!conversations) {
throw new Error('renderRow: Tried to render without conversations');
}
conversations = _.sortBy([...conversations], (convo) => {
return convo.isPinned ? -1 : 1
});
const conversation = conversations[index];
return (
@ -100,7 +105,8 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
};
public renderList(): JSX.Element | Array<JSX.Element | null> {
const { conversations, openConversationExternal, searchResults } = this.props;
let { conversations } = this.props;
const { openConversationExternal, searchResults } = this.props;
const contacts = searchResults?.contacts || [];
if (searchResults) {

View file

@ -25,6 +25,7 @@ export enum SessionIconType {
Moon = 'moon',
Pause = 'pause',
Pencil = 'pencil',
Pin = 'pin',
Play = 'play',
Plus = 'plus',
Reply = 'reply',
@ -220,6 +221,12 @@ export const icons = {
viewBox: '1 1 21 21',
ratio: 1,
},
[SessionIconType.Pin]: {
path:
'M83.88.451L122.427 39c.603.601.603 1.585 0 2.188l-13.128 13.125c-.602.604-1.586.604-2.187 0l-3.732-3.73-17.303 17.3c3.882 14.621.095 30.857-11.37 42.32-.266.268-.535.529-.808.787-1.004.955-.843.949-1.813-.021L47.597 86.48 0 122.867l36.399-47.584L11.874 50.76c-.978-.98-.896-.826.066-1.837.24-.251.485-.503.734-.753C24.137 36.707 40.376 32.917 54.996 36.8l17.301-17.3-3.733-3.732c-.601-.601-.601-1.585 0-2.188L81.691.451c.604-.601 1.588-.601 2.189 0z',
viewBox: '0 0 122.879 122.867',
ratio: 1,
},
[SessionIconType.Play]: {
path:
'M29.462,15.707c0,1.061-0.562,2.043-1.474,2.583L6.479,30.999c-0.47,0.275-0.998,0.417-1.526,0.417 c-0.513,0-1.026-0.131-1.487-0.396c-0.936-0.534-1.513-1.527-1.513-2.604V2.998c0-1.077,0.578-2.07,1.513-2.605 C4.402-0.139,5.553-0.13,6.479,0.415l21.509,12.709C28.903,13.664,29.462,14.646,29.462,15.707z',

View file

@ -12,6 +12,7 @@ import {
getInviteContactMenuItem,
getLeaveGroupMenuItem,
getMarkAllReadMenuItem,
MenuItemPinConversation
} from './Menu';
export type PropsContextConversationItem = {
@ -46,6 +47,7 @@ export const ConversationListItemContextMenu = (props: PropsContextConversationI
return (
<>
<Menu id={triggerId} animation={animation.fade}>
<MenuItemPinConversation conversationId={conversationId} />
{getBlockMenuItem(isMe, type === ConversationTypeEnum.PRIVATE, isBlocked, conversationId)}
{getCopyMenuItem(isPublic, isGroup, conversationId)}
{getMarkAllReadMenuItem(conversationId)}

View file

@ -4,8 +4,8 @@ import { NotificationForConvoOption, TimerOption } from '../../conversation/Conv
import { Item, Submenu } from 'react-contexify';
import { ConversationNotificationSettingType } from '../../../models/conversation';
import { useDispatch } from 'react-redux';
import { actions as conversationActions } from '../../../state/ducks/conversations';
import {
adminLeaveClosedGroup,
changeNickNameModal,
updateConfirmModal,
} from '../../../state/ducks/modalDialog';
@ -25,6 +25,8 @@ import {
showUpdateGroupNameByConvoId,
unblockConvoById,
} from '../../../interactions/conversationInteractions';
import { purgeStoredState } from 'redux-persist';
import { persistConfig, _purgedStoredState } from '../../../state/createStore';
function showTimerOptions(
isPublic: boolean,
@ -125,6 +127,30 @@ export function getInviteContactMenuItem(
return null;
}
export interface PinConversationMenuItemProps {
conversationId: string;
}
export const MenuItemPinConversation = (props: PinConversationMenuItemProps): JSX.Element | null => {
const { conversationId } = props;
const conversation = getConversationController().get(conversationId).getProps();
const { isPinned } = conversation;
const togglePinConversation = () => {
window.inboxStore?.dispatch(conversationActions.conversationChanged(conversationId,
{
...conversation,
isPinned: !isPinned
}))
if (isPinned) {
// purgeStoredState(persistConfig);
}
}
return <Item onClick={togglePinConversation}>{(isPinned ? 'Unpin' : 'Pin') + ' Conversation'}</Item>
}
export function getDeleteContactMenuItem(
isMe: boolean | undefined,
isGroup: boolean | undefined,
@ -297,7 +323,7 @@ export function getDisappearingMenuItem(
// Remove the && false to make context menu work with RTL support
<Submenu
label={window.i18n('disappearingMessages') as any}
// rtl={isRtlMode && false}
// rtl={isRtlMode && false}
>
{(timerOptions || []).map(item => (
<Item
@ -330,7 +356,7 @@ export function getNotificationForConvoMenuItem(
// Remove the && false to make context menu work with RTL support
<Submenu
label={window.i18n('notificationForConvo') as any}
// rtl={isRtlMode && false}
// rtl={isRtlMode && false}
>
{(notificationForConvoOptions || []).map(item => {
const disabled = item.value === currentNotificationSetting;

View file

@ -408,9 +408,14 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
left: !!this.get('left'),
groupAdmins,
members,
isPinned: this.getIsPinned() || false
};
}
private getIsPinned() {
return window.inboxStore?.getState().conversations.conversationLookup[this.id].isPinned;
}
public async updateGroupAdmins(groupAdmins: Array<string>) {
const existingAdmins = _.uniq(_.sortBy(this.getGroupAdmins()));
const newAdmins = _.uniq(_.sortBy(groupAdmins));
@ -501,9 +506,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
fileName: fileName || null,
thumbnail: thumbnail
? {
...(await loadAttachmentData(thumbnail)),
objectUrl: getAbsoluteAttachmentPath(thumbnail.path),
}
...(await loadAttachmentData(thumbnail)),
objectUrl: getAbsoluteAttachmentPath(thumbnail.path),
}
: null,
};
})
@ -526,9 +531,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
fileName: null,
thumbnail: image
? {
...(await loadAttachmentData(image)),
objectUrl: getAbsoluteAttachmentPath(image.path),
}
...(await loadAttachmentData(image)),
objectUrl: getAbsoluteAttachmentPath(image.path),
}
: null,
};
})

View file

@ -6,6 +6,7 @@ import { persistReducer } from 'redux-persist';
// tslint:disable-next-line: no-submodule-imports match-default-export-name
import storage from 'redux-persist/lib/storage';
import purgeStoredState from 'redux-persist/es/purgeStoredState';
// @ts-ignore
const env = window.getEnvironment();
@ -26,10 +27,10 @@ const logger = createLogger({
logger: directConsole,
});
const persistConfig = {
export const persistConfig = {
key: 'root',
storage,
whitelist: ['userConfig'],
whitelist: ['userConfig', 'conversations'],
};
const persistedReducer = persistReducer(persistConfig, allReducers);

View file

@ -82,6 +82,7 @@ export interface ConversationType {
avatarPath?: string; // absolute filepath to the avatar
groupAdmins?: Array<string>; // admins for closed groups and moderators for open groups
members?: Array<string>; // members for closed groups only
isPinned?: boolean
}
export type ConversationLookupType = {