Merge branch 'clearnet' into reactions_fixes

pull/2445/head
William Grant 3 years ago
commit 80d726659c

@ -1,4 +1,4 @@
import React, { useContext, useLayoutEffect, useState } from 'react'; import React, { useContext, useLayoutEffect } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import { getQuotedMessageToAnimate } from '../../state/selectors/conversations'; import { getQuotedMessageToAnimate } from '../../state/selectors/conversations';
@ -35,19 +35,28 @@ const LastSeenText = styled.div`
color: var(--color-last-seen-indicator); color: var(--color-last-seen-indicator);
`; `;
export const SessionLastSeenIndicator = (props: { messageId: string }) => { export const SessionLastSeenIndicator = (props: {
messageId: string;
didScroll: boolean;
setDidScroll: (scroll: boolean) => void;
}) => {
// if this unread-indicator is not unique it's going to cause issues // if this unread-indicator is not unique it's going to cause issues
const [didScroll, setDidScroll] = useState(false);
const quotedMessageToAnimate = useSelector(getQuotedMessageToAnimate); const quotedMessageToAnimate = useSelector(getQuotedMessageToAnimate);
const scrollToLoadedMessage = useContext(ScrollToLoadedMessageContext); const scrollToLoadedMessage = useContext(ScrollToLoadedMessageContext);
// if this unread-indicator is rendered, const { messageId, didScroll, setDidScroll } = props;
// we want to scroll here only if the conversation was not opened to a specific message
/**
* If this unread-indicator is rendered, we want to scroll here only if:
* 1. the conversation was not opened to a specific message (quoted message)
* 2. we already scrolled to this unread banner once for this convo https://github.com/oxen-io/session-desktop/issues/2308
*
* To achieve 2. we store the didScroll state in the parent and track the last rendered conversation in it.
*/
useLayoutEffect(() => { useLayoutEffect(() => {
if (!quotedMessageToAnimate && !didScroll) { if (!quotedMessageToAnimate && !didScroll) {
scrollToLoadedMessage(props.messageId, 'unread-indicator'); scrollToLoadedMessage(messageId, 'unread-indicator');
setDidScroll(true); setDidScroll(true);
} else if (quotedMessageToAnimate) { } else if (quotedMessageToAnimate) {
setDidScroll(true); setDidScroll(true);

@ -1,4 +1,4 @@
import React, { useLayoutEffect } from 'react'; import React, { useLayoutEffect, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey'; import useKey from 'react-use/lib/useKey';
@ -15,6 +15,7 @@ import {
import { import {
getOldBottomMessageId, getOldBottomMessageId,
getOldTopMessageId, getOldTopMessageId,
getSelectedConversationKey,
getSortedMessagesTypesOfSelectedConversation, getSortedMessagesTypesOfSelectedConversation,
} from '../../state/selectors/conversations'; } from '../../state/selectors/conversations';
import { GroupUpdateMessage } from './message/message-item/GroupUpdateMessage'; import { GroupUpdateMessage } from './message/message-item/GroupUpdateMessage';
@ -32,6 +33,8 @@ function isNotTextboxEvent(e: KeyboardEvent) {
return (e?.target as any)?.type === undefined; return (e?.target as any)?.type === undefined;
} }
let previousRenderedConvo: string | undefined;
export const SessionMessagesList = (props: { export const SessionMessagesList = (props: {
scrollAfterLoadMore: ( scrollAfterLoadMore: (
messageIdToScrollTo: string, messageIdToScrollTo: string,
@ -43,6 +46,9 @@ export const SessionMessagesList = (props: {
onEndPressed: () => void; onEndPressed: () => void;
}) => { }) => {
const messagesProps = useSelector(getSortedMessagesTypesOfSelectedConversation); const messagesProps = useSelector(getSortedMessagesTypesOfSelectedConversation);
const convoKey = useSelector(getSelectedConversationKey);
const [didScroll, setDidScroll] = useState(false);
const oldTopMessageId = useSelector(getOldTopMessageId); const oldTopMessageId = useSelector(getOldTopMessageId);
const oldBottomMessageId = useSelector(getOldBottomMessageId); const oldBottomMessageId = useSelector(getOldBottomMessageId);
@ -84,12 +90,22 @@ export const SessionMessagesList = (props: {
} }
}); });
if (didScroll && previousRenderedConvo !== convoKey) {
setDidScroll(false);
previousRenderedConvo = convoKey;
}
return ( return (
<> <>
{messagesProps.map(messageProps => { {messagesProps.map(messageProps => {
const messageId = messageProps.message.props.messageId; const messageId = messageProps.message.props.messageId;
const unreadIndicator = messageProps.showUnreadIndicator ? ( const unreadIndicator = messageProps.showUnreadIndicator ? (
<SessionLastSeenIndicator key={`unread-indicator-${messageId}`} messageId={messageId} /> <SessionLastSeenIndicator
key={'unread-indicator'}
messageId={messageId}
didScroll={didScroll}
setDidScroll={setDidScroll}
/>
) : null; ) : null;
const dateBreak = const dateBreak =
@ -100,24 +116,22 @@ export const SessionMessagesList = (props: {
messageId={messageId} messageId={messageId}
/> />
) : null; ) : null;
const componentToMerge = [dateBreak, unreadIndicator];
if (messageProps.message?.messageType === 'group-notification') { if (messageProps.message?.messageType === 'group-notification') {
const msgProps = messageProps.message.props as PropsForGroupUpdate; const msgProps = messageProps.message.props as PropsForGroupUpdate;
return [<GroupUpdateMessage key={messageId} {...msgProps} />, dateBreak, unreadIndicator]; return [<GroupUpdateMessage key={messageId} {...msgProps} />, ...componentToMerge];
} }
if (messageProps.message?.messageType === 'group-invitation') { if (messageProps.message?.messageType === 'group-invitation') {
const msgProps = messageProps.message.props as PropsForGroupInvitation; const msgProps = messageProps.message.props as PropsForGroupInvitation;
return [<GroupInvitation key={messageId} {...msgProps} />, dateBreak, unreadIndicator]; return [<GroupInvitation key={messageId} {...msgProps} />, ...componentToMerge];
} }
if (messageProps.message?.messageType === 'message-request-response') { if (messageProps.message?.messageType === 'message-request-response') {
const msgProps = messageProps.message.props as PropsForMessageRequestResponse; const msgProps = messageProps.message.props as PropsForMessageRequestResponse;
return [ return [<MessageRequestResponse key={messageId} {...msgProps} />, ...componentToMerge];
<MessageRequestResponse key={messageId} {...msgProps} />,
dateBreak,
unreadIndicator,
];
} }
if (messageProps.message?.messageType === 'data-extraction') { if (messageProps.message?.messageType === 'data-extraction') {
@ -125,28 +139,27 @@ export const SessionMessagesList = (props: {
return [ return [
<DataExtractionNotification key={messageId} {...msgProps} />, <DataExtractionNotification key={messageId} {...msgProps} />,
dateBreak, ...componentToMerge,
unreadIndicator,
]; ];
} }
if (messageProps.message?.messageType === 'timer-notification') { if (messageProps.message?.messageType === 'timer-notification') {
const msgProps = messageProps.message.props as PropsForExpirationTimer; const msgProps = messageProps.message.props as PropsForExpirationTimer;
return [<TimerNotification key={messageId} {...msgProps} />, dateBreak, unreadIndicator]; return [<TimerNotification key={messageId} {...msgProps} />, ...componentToMerge];
} }
if (messageProps.message?.messageType === 'call-notification') { if (messageProps.message?.messageType === 'call-notification') {
const msgProps = messageProps.message.props as PropsForCallNotification; const msgProps = messageProps.message.props as PropsForCallNotification;
return [<CallNotification key={messageId} {...msgProps} />, dateBreak, unreadIndicator]; return [<CallNotification key={messageId} {...msgProps} />, ...componentToMerge];
} }
if (!messageProps) { if (!messageProps) {
return null; return null;
} }
return [<Message messageId={messageId} key={messageId} />, dateBreak, unreadIndicator]; return [<Message messageId={messageId} key={messageId} />, ...componentToMerge];
})} })}
</> </>
); );

@ -477,10 +477,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
); );
} }
public async getUnread() {
return Data.getUnreadByConversation(this.id);
}
public async getUnreadCount() { public async getUnreadCount() {
const unreadCount = await Data.getUnreadCountByConversation(this.id); const unreadCount = await Data.getUnreadCountByConversation(this.id);
@ -1891,6 +1887,10 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
: null; : null;
} }
private async getUnread() {
return Data.getUnreadByConversation(this.id);
}
/** /**
* *
* @returns The open group conversationId this conversation originated from * @returns The open group conversationId this conversation originated from

@ -94,6 +94,7 @@ import {
} from '../session/apis/open_group_api/sogsv3/knownBlindedkeys'; } from '../session/apis/open_group_api/sogsv3/knownBlindedkeys';
import { QUOTED_TEXT_MAX_LENGTH } from '../session/constants'; import { QUOTED_TEXT_MAX_LENGTH } from '../session/constants';
import { ReactionList } from '../types/Reaction'; import { ReactionList } from '../types/Reaction';
import { getAttachmentMetadata } from '../types/message/initializeAttachmentMetadata';
// tslint:disable: cyclomatic-complexity // tslint:disable: cyclomatic-complexity
/** /**
@ -780,6 +781,12 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
const quoteWithData = await loadQuoteData(this.get('quote')); const quoteWithData = await loadQuoteData(this.get('quote'));
const previewWithData = await loadPreviewData(this.get('preview')); const previewWithData = await loadPreviewData(this.get('preview'));
const { hasAttachments, hasVisualMediaAttachments, hasFileAttachments } = getAttachmentMetadata(
this
);
this.set({ hasAttachments, hasVisualMediaAttachments, hasFileAttachments });
await this.commit();
const conversation = this.getConversation(); const conversation = this.getConversation();
let attachmentPromise; let attachmentPromise;

@ -579,6 +579,12 @@ function updateToSessionSchemaVersion20(currentVersion: number, db: BetterSqlite
console.log(`updateToSessionSchemaVersion${targetVersion}: starting...`); console.log(`updateToSessionSchemaVersion${targetVersion}: starting...`);
db.transaction(() => { db.transaction(() => {
// First we want to drop the column friendRequestStatus if it is there, otherwise the transaction fails
const rows = db.pragma(`table_info(${CONVERSATIONS_TABLE});`);
if (rows.some((m: any) => m.name === 'friendRequestStatus')) {
console.info('found column friendRequestStatus. Dropping it');
db.exec(`ALTER TABLE ${CONVERSATIONS_TABLE} DROP COLUMN friendRequestStatus;`);
}
// looking for all private conversations, with a nickname set // looking for all private conversations, with a nickname set
const rowsToUpdate = db const rowsToUpdate = db
.prepare( .prepare(
@ -917,6 +923,13 @@ function updateToSessionSchemaVersion27(currentVersion: number, db: BetterSqlite
// tslint:disable-next-line: max-func-body-length // tslint:disable-next-line: max-func-body-length
db.transaction(() => { db.transaction(() => {
// First we want to drop the column friendRequestStatus if it is there, otherwise the transaction fails
const rows = db.pragma(`table_info(${CONVERSATIONS_TABLE});`);
if (rows.some((m: any) => m.name === 'friendRequestStatus')) {
console.info('found column friendRequestStatus. Dropping it');
db.exec(`ALTER TABLE ${CONVERSATIONS_TABLE} DROP COLUMN friendRequestStatus;`);
}
// We want to replace all the occurrences of the sogs server ip url (116.203.70.33 || http://116.203.70.33 || https://116.203.70.33) by its hostname: https://open.getsession.org // We want to replace all the occurrences of the sogs server ip url (116.203.70.33 || http://116.203.70.33 || https://116.203.70.33) by its hostname: https://open.getsession.org
// This includes change the conversationTable, the openGroupRooms tables and every single message associated with them. // This includes change the conversationTable, the openGroupRooms tables and every single message associated with them.
// Because the conversationId is used to link messages to conversation includes the ip/url in it... // Because the conversationId is used to link messages to conversation includes the ip/url in it...
@ -1157,17 +1170,12 @@ function updateToSessionSchemaVersion28(currentVersion: number, db: BetterSqlite
if (currentVersion >= targetVersion) { if (currentVersion >= targetVersion) {
return; return;
} }
console.log(`updateToSessionSchemaVersion${targetVersion}: starting...`); console.log(`updateToSessionSchemaVersion${targetVersion}: starting...`);
// some very old databases have the column friendRequestStatus still there but we are not using it anymore. So drop it if we find it.
db.transaction(() => { db.transaction(() => {
const rows = db.pragma(`table_info(${CONVERSATIONS_TABLE});`); // Keeping this empty migration because some people updated to this already, even if it is not needed anymore
if (rows.some((m: any) => m.name === 'friendRequestStatus')) {
console.info('found column friendRequestStatus. Dropping it');
db.exec(`ALTER TABLE ${CONVERSATIONS_TABLE} DROP COLUMN friendRequestStatus;`);
}
writeSessionSchemaVersion(targetVersion, db); writeSessionSchemaVersion(targetVersion, db);
console.log('... done');
})(); })();
console.log(`updateToSessionSchemaVersion${targetVersion}: success!`); console.log(`updateToSessionSchemaVersion${targetVersion}: success!`);

@ -420,6 +420,7 @@ function getConversationCount() {
// tslint:disable-next-line: max-func-body-length // tslint:disable-next-line: max-func-body-length
// tslint:disable-next-line: cyclomatic-complexity // tslint:disable-next-line: cyclomatic-complexity
// tslint:disable-next-line: max-func-body-length
function saveConversation(data: ConversationAttributes, instance?: BetterSqlite3.Database) { function saveConversation(data: ConversationAttributes, instance?: BetterSqlite3.Database) {
const formatted = assertValidConversationAttributes(data); const formatted = assertValidConversationAttributes(data);
@ -458,10 +459,13 @@ function saveConversation(data: ConversationAttributes, instance?: BetterSqlite3
conversationIdOrigin, conversationIdOrigin,
} = formatted; } = formatted;
// shorten the last message as we never need more than 60 chars (and it bloats the redux/ipc calls uselessly const maxLength = 300;
// shorten the last message as we never need more than `maxLength` chars (and it bloats the redux/ipc calls uselessly.
const shortenedLastMessage = const shortenedLastMessage =
isString(lastMessage) && lastMessage.length > 60 ? lastMessage.substring(60) : lastMessage; isString(lastMessage) && lastMessage.length > maxLength
? lastMessage.substring(0, maxLength)
: lastMessage;
assertGlobalInstanceOrInstance(instance) assertGlobalInstanceOrInstance(instance)
.prepare( .prepare(
`INSERT OR REPLACE INTO ${CONVERSATIONS_TABLE} ( `INSERT OR REPLACE INTO ${CONVERSATIONS_TABLE} (
@ -1090,7 +1094,7 @@ function getUnreadByConversation(conversationId: string) {
conversationId, conversationId,
}); });
return rows; return map(rows, row => jsonToObject(row.json));
} }
function getUnreadCountByConversation(conversationId: string) { function getUnreadCountByConversation(conversationId: string) {

@ -366,6 +366,7 @@ type FetchedTopMessageResults = {
conversationKey: string; conversationKey: string;
messagesProps: Array<MessageModelPropsWithoutConvoProps>; messagesProps: Array<MessageModelPropsWithoutConvoProps>;
oldTopMessageId: string | null; oldTopMessageId: string | null;
newMostRecentMessageIdInConversation: string | null;
} | null; } | null;
export const fetchTopMessagesForConversation = createAsyncThunk( export const fetchTopMessagesForConversation = createAsyncThunk(
@ -379,6 +380,7 @@ export const fetchTopMessagesForConversation = createAsyncThunk(
}): Promise<FetchedTopMessageResults> => { }): Promise<FetchedTopMessageResults> => {
// no need to load more top if we are already at the top // no need to load more top if we are already at the top
const oldestMessage = await Data.getOldestMessageInConversation(conversationKey); const oldestMessage = await Data.getOldestMessageInConversation(conversationKey);
const mostRecentMessage = await Data.getLastMessageInConversation(conversationKey);
if (!oldestMessage || oldestMessage.id === oldTopMessageId) { if (!oldestMessage || oldestMessage.id === oldTopMessageId) {
window.log.info('fetchTopMessagesForConversation: we are already at the top'); window.log.info('fetchTopMessagesForConversation: we are already at the top');
@ -393,6 +395,7 @@ export const fetchTopMessagesForConversation = createAsyncThunk(
conversationKey, conversationKey,
messagesProps, messagesProps,
oldTopMessageId, oldTopMessageId,
newMostRecentMessageIdInConversation: mostRecentMessage?.id || null,
}; };
} }
); );
@ -845,7 +848,12 @@ const conversationsSlice = createSlice({
return { ...state, areMoreMessagesBeingFetched: false }; return { ...state, areMoreMessagesBeingFetched: false };
} }
// this is called once the messages are loaded from the db for the currently selected conversation // this is called once the messages are loaded from the db for the currently selected conversation
const { messagesProps, conversationKey, oldTopMessageId } = action.payload; const {
messagesProps,
conversationKey,
oldTopMessageId,
newMostRecentMessageIdInConversation,
} = action.payload;
// double check that this update is for the shown convo // double check that this update is for the shown convo
if (conversationKey === state.selectedConversation) { if (conversationKey === state.selectedConversation) {
return { return {
@ -853,6 +861,7 @@ const conversationsSlice = createSlice({
oldTopMessageId, oldTopMessageId,
messages: messagesProps, messages: messagesProps,
areMoreMessagesBeingFetched: false, areMoreMessagesBeingFetched: false,
mostRecentMessageId: newMostRecentMessageIdInConversation,
}; };
} }
return state; return state;

@ -172,11 +172,12 @@ export const hasSelectedConversationIncomingMessages = createSelector(
} }
); );
const getFirstUnreadMessageId = createSelector(getConversations, (state: ConversationsStateType): export const getFirstUnreadMessageId = createSelector(
| string getConversations,
| undefined => { (state: ConversationsStateType): string | undefined => {
return state.firstUnreadMessageId; return state.firstUnreadMessageId;
}); }
);
export const getConversationHasUnread = createSelector(getFirstUnreadMessageId, unreadId => { export const getConversationHasUnread = createSelector(getFirstUnreadMessageId, unreadId => {
return Boolean(unreadId); return Boolean(unreadId);
@ -215,10 +216,11 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector(
? messageTimestamp ? messageTimestamp
: undefined; : undefined;
const common = { showUnreadIndicator: isFirstUnread, showDateBreak };
if (msg.propsForDataExtractionNotification) { if (msg.propsForDataExtractionNotification) {
return { return {
showUnreadIndicator: isFirstUnread, ...common,
showDateBreak,
message: { message: {
messageType: 'data-extraction', messageType: 'data-extraction',
props: { ...msg.propsForDataExtractionNotification, messageId: msg.propsForMessage.id }, props: { ...msg.propsForDataExtractionNotification, messageId: msg.propsForMessage.id },
@ -228,8 +230,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector(
if (msg.propsForMessageRequestResponse) { if (msg.propsForMessageRequestResponse) {
return { return {
showUnreadIndicator: isFirstUnread, ...common,
showDateBreak,
message: { message: {
messageType: 'message-request-response', messageType: 'message-request-response',
props: { ...msg.propsForMessageRequestResponse, messageId: msg.propsForMessage.id }, props: { ...msg.propsForMessageRequestResponse, messageId: msg.propsForMessage.id },
@ -239,8 +240,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector(
if (msg.propsForGroupInvitation) { if (msg.propsForGroupInvitation) {
return { return {
showUnreadIndicator: isFirstUnread, ...common,
showDateBreak,
message: { message: {
messageType: 'group-invitation', messageType: 'group-invitation',
props: { ...msg.propsForGroupInvitation, messageId: msg.propsForMessage.id }, props: { ...msg.propsForGroupInvitation, messageId: msg.propsForMessage.id },
@ -250,8 +250,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector(
if (msg.propsForGroupUpdateMessage) { if (msg.propsForGroupUpdateMessage) {
return { return {
showUnreadIndicator: isFirstUnread, ...common,
showDateBreak,
message: { message: {
messageType: 'group-notification', messageType: 'group-notification',
props: { ...msg.propsForGroupUpdateMessage, messageId: msg.propsForMessage.id }, props: { ...msg.propsForGroupUpdateMessage, messageId: msg.propsForMessage.id },
@ -261,8 +260,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector(
if (msg.propsForTimerNotification) { if (msg.propsForTimerNotification) {
return { return {
showUnreadIndicator: isFirstUnread, ...common,
showDateBreak,
message: { message: {
messageType: 'timer-notification', messageType: 'timer-notification',
props: { ...msg.propsForTimerNotification, messageId: msg.propsForMessage.id }, props: { ...msg.propsForTimerNotification, messageId: msg.propsForMessage.id },
@ -272,8 +270,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector(
if (msg.propsForCallNotification) { if (msg.propsForCallNotification) {
return { return {
showUnreadIndicator: isFirstUnread, ...common,
showDateBreak,
message: { message: {
messageType: 'call-notification', messageType: 'call-notification',
props: { props: {

Loading…
Cancel
Save