feat: use priority for hidden and pinned conversation

This commit is contained in:
Audric Ackermann 2023-04-03 12:03:23 +10:00
parent 027bd46ff7
commit f3975b545a
26 changed files with 247 additions and 239 deletions

View file

@ -66,9 +66,12 @@ export const OverlayMessage = () => {
if (!convo.isActive() || !convo.isApproved() || convo.isHidden()) {
// bump the timestamp only if we were not active before
if (!convo.isActive()) {
convo.set({ active_at: Date.now(), isApproved: true, hidden: false });
convo.set({ active_at: Date.now() });
}
convo.set({ isApproved: true, hidden: false });
await convo.unhideIfNeeded(false);
// TODO find a way to make this not a hack to show it in the list
convo.set({ isApproved: true });
await convo.commit();
}

View file

@ -183,7 +183,7 @@ export const PinConversationMenuItem = (): JSX.Element | null => {
const conversation = getConversationController().get(conversationId);
const togglePinConversation = async () => {
await conversation?.setIsPinned(!isPinned);
await conversation?.togglePinned();
};
const menuText = isPinned ? window.i18n('unpinConversation') : window.i18n('pinConversation');

View file

@ -1,4 +1,4 @@
import { isEmpty, pick } from 'lodash';
import { isEmpty, isNumber, pick } from 'lodash';
import { useSelector } from 'react-redux';
import { ConversationModel } from '../models/conversation';
import { PubKey } from '../session/types';
@ -141,7 +141,12 @@ export function useExpireTimer(convoId?: string) {
export function useIsPinned(convoId?: string) {
const convoProps = useConversationPropsById(convoId);
return Boolean(convoProps && convoProps.isPinned);
return Boolean(
convoProps &&
isNumber(convoProps.priority) &&
isFinite(convoProps.priority) &&
convoProps.priority > 0
);
}
export function useIsApproved(convoId?: string) {

View file

@ -97,6 +97,7 @@ import { Reactions } from '../util/reactions';
import { Registration } from '../util/registration';
import { Storage } from '../util/storage';
import {
CONVERSATION_PRIORITIES,
ConversationAttributes,
ConversationNotificationSetting,
ConversationTypeEnum,
@ -263,7 +264,8 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
public isHidden() {
return Boolean(this.get('hidden'));
const priority = this.get('priority') || CONVERSATION_PRIORITIES.default;
return this.isPrivate() && priority === CONVERSATION_PRIORITIES.hidden;
}
public async cleanup() {
@ -280,7 +282,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public getConversationModelProps(): ReduxConversationType {
const isPublic = this.isPublic();
const zombies = this.isClosedGroup() ? this.get('zombies') : [];
const ourNumber = UserUtils.getOurPubKeyStrFromCache();
const avatarPath = this.getAvatarPath();
const isPrivate = this.isPrivate();
@ -289,9 +290,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const weAreModerator = this.isModerator(ourNumber); // only used for sogs
const isMe = this.isMe();
const isTyping = !!this.typingTimer;
const isKickedFromGroup = !!this.get('isKickedFromGroup');
const left = !!this.get('left');
const currentNotificationSetting = this.get('triggerNotificationsFor');
const priority = this.get('priority');
// To reduce the redux store size, only set fields which cannot be undefined.
// For instance, a boolean can usually be not set if false, etc
@ -299,10 +300,16 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
id: this.id as string,
activeAt: this.get('active_at'),
type: this.get('type'),
isHidden: !!this.get('hidden'),
isMarkedUnread: !!this.get('markedAsUnread'),
};
if (isFinite(priority) && priority !== CONVERSATION_PRIORITIES.default) {
toRet.priority = priority;
}
if (this.get('markedAsUnread')) {
toRet.isMarkedUnread = !!this.get('markedAsUnread');
}
if (isPrivate) {
toRet.isPrivate = true;
}
@ -333,6 +340,13 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
toRet.avatarPath = avatarPath;
}
if (
currentNotificationSetting &&
currentNotificationSetting !== ConversationNotificationSetting[0]
) {
toRet.currentNotificationSetting = currentNotificationSetting;
}
const foundContact = SessionUtilContact.getContactCached(this.id);
const foundCommunity = SessionUtilUserGroups.getCommunityByConvoIdCached(this.id);
const foundLegacyGroup = SessionUtilUserGroups.getLegacyGroupCached(this.id);
@ -360,10 +374,8 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
toRet.isApproved = foundContact.approved;
}
toRet.isHidden = foundContact.hidden;
if (foundContact.priority > 0) {
toRet.isPinned = true;
if (foundContact.priority) {
toRet.priority = foundContact.priority;
}
if (foundContact.expirationTimerSeconds > 0) {
@ -389,19 +401,13 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
if (this.get('isApproved')) {
toRet.isApproved = this.get('isApproved');
}
if (!this.get('active_at')) {
toRet.isHidden = true;
}
if (this.get('isPinned')) {
toRet.isPinned = true;
}
if (this.get('expireTimer')) {
toRet.expireTimer = this.get('expireTimer');
}
}
// -- Handle the group fields from the wrapper and the database --
if (foundLegacyGroup) {
toRet.members = foundLegacyGroup.members.map(m => m.pubkeyHex) || [];
toRet.groupAdmins =
@ -411,15 +417,32 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
: foundLegacyGroup.name;
toRet.expireTimer = foundLegacyGroup.disappearingTimerSeconds;
if (foundLegacyGroup.priority > 0) {
toRet.isPinned = Boolean(foundLegacyGroup.priority > 0);
if (foundLegacyGroup.priority) {
toRet.priority = foundLegacyGroup.priority;
} // TODO grab the details from the db if we do not have an entry in the wrapper
}
if (this.isClosedGroup()) {
if (this.get('isKickedFromGroup')) {
toRet.isKickedFromGroup = !!this.get('isKickedFromGroup');
}
if (this.get('left')) {
toRet.left = !!this.get('left');
}
const zombies = this.get('zombies') || [];
if (zombies?.length) {
toRet.zombies = uniq(zombies);
}
}
// -- Handle the communities fields from the wrapper and the database --
if (foundCommunity) {
if (foundCommunity.priority > 0) {
toRet.isPinned = true;
if (foundCommunity.priority) {
toRet.priority = foundCommunity.priority;
}
// TODO grab the details from the db if we do not have an entry in the wrapper
// TODO should we just not rely on the db values?
}
if (foundVolatileInfo) {
@ -428,6 +451,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
}
// -- Handle the field stored only in memory for all types of conversation--
const inMemoryConvoInfo = inMemoryConvoInfos.get(this.id);
if (inMemoryConvoInfo) {
if (inMemoryConvoInfo.unreadCount) {
@ -438,24 +462,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
}
if (isKickedFromGroup) {
toRet.isKickedFromGroup = isKickedFromGroup;
}
if (left) {
toRet.left = left;
}
if (
currentNotificationSetting &&
currentNotificationSetting !== ConversationNotificationSetting[0]
) {
toRet.currentNotificationSetting = currentNotificationSetting;
}
if (zombies && zombies.length) {
toRet.zombies = uniq(zombies);
}
// -- Handle the last message status, if needed --
const lastMessageText = this.get('lastMessage');
if (lastMessageText && lastMessageText.length) {
const lastMessageStatus = this.get('lastMessageStatus');
@ -601,10 +608,8 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
// we are trying to send a message to someone. If that convo is hidden in the list, make sure it is not
if (this.isHidden()) {
this.set({ hidden: false });
await this.commit();
}
this.unhideIfNeeded(true);
// an OpenGroupV2 message is just a visible message
const chatMessageParams: VisibleMessageParams = {
body,
@ -1432,22 +1437,76 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return Array.isArray(groupModerators) && groupModerators.includes(pubKey);
}
public async setIsPinned(value: boolean, shouldCommit: boolean = true) {
const valueForced = Boolean(value);
if (valueForced !== Boolean(this.isPinned())) {
/**
* When receiving a shared config message, we need to apply the change after the merge happened to our database.
* This is done with this function.
* There are other actions to change the priority from the UI (or from )
* @param priority
* @param shouldCommit
*/
public async setPriorityFromWrapper(
priority: number,
shouldCommit: boolean = true
): Promise<boolean> {
if (priority !== this.get('priority')) {
this.set({
isPinned: valueForced,
priority,
});
if (shouldCommit) {
await this.commit();
}
return true;
}
return false;
}
/**
* Toggle the pinned state of a conversation.
* Any conversation can be pinned and the higher the priority, the higher it will be in the list.
* Note: Currently, we do not have an order in the list of pinned conversation, but the libsession util wrapper can handle the order.
*/
public async togglePinned(shouldCommit: boolean = true) {
this.set({ priority: this.isPinned() ? 0 : 1 });
if (shouldCommit) {
await this.commit();
}
return true;
}
/**
* Force the priority to be -1 (PRIORITY_DEFAULT_HIDDEN) so this conversation is hidden in the list. Currently only works for private chats.
*/
public async setHidden(shouldCommit: boolean = true) {
if (!this.isPrivate()) {
return;
}
const priority = this.get('priority');
if (priority >= CONVERSATION_PRIORITIES.default) {
this.set({ priority: CONVERSATION_PRIORITIES.hidden });
if (shouldCommit) {
await this.commit();
}
}
}
/**
* Reset the priority of this conversation to 0 if it was < 0, but keep anything > 0 as is.
* So if the conversation was pinned, we keep it pinned with its current priority.
* A pinned cannot be hidden, as the it is all based on the same priority values.
*/
public async unhideIfNeeded(shouldCommit: boolean = true) {
const priority = this.get('priority');
if (isFinite(priority) && priority < CONVERSATION_PRIORITIES.default) {
this.set({ priority: CONVERSATION_PRIORITIES.default });
if (shouldCommit) {
await this.commit();
}
}
}
public async markAsUnread(forcedValue: boolean, shouldCommit: boolean = true) {
if (!!forcedValue !== !!this.get('markedAsUnread')) {
if (!!forcedValue !== this.isMarkedUnread()) {
this.set({
markedAsUnread: !!forcedValue,
});
@ -1627,7 +1686,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
public isPinned() {
return Boolean(this.get('isPinned'));
const priority = this.get('priority');
return isFinite(priority) && priority > CONVERSATION_PRIORITIES.default;
}
public didApproveMe() {
@ -1638,10 +1699,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return Boolean(this.get('isApproved'));
}
public getTitle() {
return this.getNicknameOrRealUsernameOrPlaceholder();
}
/**
* For a private convo, returns the loki profilename if set, or a shortened
* version of the contact pubkey.
@ -1773,7 +1830,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
message: friendRequestText ? friendRequestText : message.getNotificationText(),
messageId,
messageSentAt,
title: friendRequestText ? '' : convo.getTitle(),
title: friendRequestText ? '' : convo.getNicknameOrRealUsernameOrPlaceholder(),
});
}

View file

@ -95,13 +95,12 @@ export interface ConversationAttributes {
members: Array<string>; // groups only members are all members for this group. zombies excluded (not used for communities)
groupAdmins: Array<string>; // for sogs and closed group: the unique admins of that group
isPinned: boolean;
priority: number; // -1 = hidden (contact and NTS only), 0 = normal, 1 = pinned
isApproved: boolean; // if we sent a message request or sent a message to this contact, we approve them. If isApproved & didApproveMe, a message request becomes a contact
didApproveMe: boolean; // if our message request was approved already (or they've sent us a message request/message themselves). If isApproved & didApproveMe, a message request becomes a contact
markedAsUnread: boolean; // Force the conversation as unread even if all the messages are read. Used to highlight a conversation the user wants to check again later, synced.
hidden: boolean; // hides a conversation, but keep it the history and nicknames, etc. Currently only supported for contacts
}
/**
@ -128,12 +127,29 @@ export const fillConvoAttributesWithDefaults = (
triggerNotificationsFor: 'all', // if the settings is not set in the db, this is the default
isTrustedForAttachmentDownload: false, // we don't trust a contact until we say so
isPinned: false,
isApproved: false,
didApproveMe: false,
isKickedFromGroup: false,
left: false,
hidden: true,
priority: CONVERSATION_PRIORITIES.default,
markedAsUnread: false,
});
};
/**
* Priorities have a weird behavior.
* * 0 always means unpinned and not hidden.
* * -1 always means hidden.
* * anything over 0 means pinned with the higher priority the better. (No sorting currently implemented)
*
* When our local user pins a conversation we should use 1 as the priority.
* When we get an update from the libsession util wrapper, we should trust the value and set it locally as is.
* So if we get 100 as priority, we set the conversation priority to 100.
* If we get -20 as priority we set it as is, even if our current client does not understand what that means.
*
*/
export const CONVERSATION_PRIORITIES = {
default: 0,
hidden: -1,
pinned: 1, // anything over 0 means pinned, but when our local users pins a conversation, we set the priority to 1
};

View file

@ -1220,7 +1220,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
avatarPath: contactModel ? contactModel.getAvatarPath() : null,
name: contactModel?.getRealSessionUsername() || null,
profileName,
title: contactModel?.getTitle() || null,
title: contactModel?.getNicknameOrRealUsernameOrPlaceholder() || null,
isMe,
};
}

View file

@ -2,6 +2,7 @@ import { difference, omit, pick } from 'lodash';
import {
ConversationAttributes,
ConversationAttributesWithNotSavedOnes,
CONVERSATION_PRIORITIES,
} from '../models/conversationAttributes';
import * as BetterSqlite3 from 'better-sqlite3';
@ -51,7 +52,6 @@ const allowedKeysFormatRowOfConversation = [
'members',
'zombies',
'isTrustedForAttachmentDownload',
'isPinned',
'isApproved',
'didApproveMe',
'mentionedUs',
@ -74,7 +74,7 @@ const allowedKeysFormatRowOfConversation = [
'displayNameInProfile',
'conversationIdOrigin',
'markedAsUnread',
'hidden',
'priority',
];
// tslint:disable: cyclomatic-complexity
export function formatRowOfConversation(
@ -121,13 +121,12 @@ export function formatRowOfConversation(
// sqlite stores boolean as integer. to clean thing up we force the expected boolean fields to be boolean
convo.isTrustedForAttachmentDownload = Boolean(convo.isTrustedForAttachmentDownload);
convo.isPinned = Boolean(convo.isPinned);
convo.isApproved = Boolean(convo.isApproved);
convo.didApproveMe = Boolean(convo.didApproveMe);
convo.isKickedFromGroup = Boolean(convo.isKickedFromGroup);
convo.left = Boolean(convo.left);
convo.markedAsUnread = Boolean(convo.markedAsUnread);
convo.hidden = Boolean(convo.hidden);
convo.priority = convo.priority || CONVERSATION_PRIORITIES.default;
if (!convo.conversationIdOrigin) {
convo.conversationIdOrigin = undefined;
@ -169,7 +168,6 @@ const allowedKeysOfConversationAttributes = [
'members',
'zombies',
'isTrustedForAttachmentDownload',
'isPinned',
'isApproved',
'didApproveMe',
'isKickedFromGroup',
@ -190,7 +188,7 @@ const allowedKeysOfConversationAttributes = [
'displayNameInProfile',
'conversationIdOrigin',
'markedAsUnread',
'hidden',
'priority',
];
/**

View file

@ -6,7 +6,10 @@ import {
UserConfigWrapperInsideWorker,
UserGroupsWrapperInsideWorker,
} from 'session_util_wrapper';
import { ConversationAttributes } from '../../models/conversationAttributes';
import {
CONVERSATION_PRIORITIES,
ConversationAttributes,
} from '../../models/conversationAttributes';
import { HexKeyPair } from '../../receiver/keypairs';
import { fromHexToArray } from '../../session/utils/String';
import {
@ -1216,8 +1219,7 @@ function insertContactIntoContactWrapper(
const dbApproved = !!contact.isApproved || false;
const dbApprovedMe = !!contact.didApproveMe || false;
const dbBlocked = blockedNumbers.includes(contact.id);
const hidden = contact.hidden || false;
const isPinned = contact.isPinned;
const priority = contact.priority || 0;
const expirationTimerSeconds = contact.expireTimer || 0;
const wrapperContact = getContactInfoFromDBValues({
@ -1229,8 +1231,7 @@ function insertContactIntoContactWrapper(
dbNickname: contact.nickname || undefined,
dbProfileKey: contact.profileKey || undefined,
dbProfileUrl: contact.avatarPointer || undefined,
isPinned,
hidden,
priority,
expirationTimerSeconds,
});
@ -1254,8 +1255,7 @@ function insertContactIntoContactWrapper(
dbNickname: undefined,
dbProfileKey: undefined,
dbProfileUrl: undefined,
isPinned: false,
hidden,
priority: CONVERSATION_PRIORITIES.default,
expirationTimerSeconds: 0,
})
);
@ -1295,12 +1295,12 @@ function insertContactIntoContactWrapper(
}
function insertCommunityIntoWrapper(
community: { id: string; isPinned: boolean },
community: { id: string; priority: number },
userGroupConfigWrapper: UserGroupsWrapperInsideWorker,
volatileConfigWrapper: ConvoInfoVolatileWrapperInsideWorker,
db: BetterSqlite3.Database
) {
const isPinned = community.isPinned;
const priority = community.priority;
const convoId = community.id; // the id of a conversation has the prefix, the serverUrl and the roomToken already present, but not the pubkey
const roomDetails = sqlNode.getV2OpenGroupRoom(convoId, db);
@ -1330,7 +1330,7 @@ function insertCommunityIntoWrapper(
);
const wrapperComm = getCommunityInfoFromDBValues({
fullUrl,
isPinned,
priority,
});
try {
@ -1364,21 +1364,13 @@ function insertCommunityIntoWrapper(
function insertLegacyGroupIntoWrapper(
legacyGroup: Pick<
ConversationAttributes,
'hidden' | 'id' | 'isPinned' | 'expireTimer' | 'displayNameInProfile'
'id' | 'priority' | 'expireTimer' | 'displayNameInProfile'
> & { members: string; groupAdmins: string }, // members and groupAdmins are still stringified here
userGroupConfigWrapper: UserGroupsWrapperInsideWorker,
volatileInfoConfigWrapper: ConvoInfoVolatileWrapperInsideWorker,
db: BetterSqlite3.Database
) {
const {
isPinned,
id,
hidden,
expireTimer,
groupAdmins,
members,
displayNameInProfile,
} = legacyGroup;
const { priority, id, expireTimer, groupAdmins, members, displayNameInProfile } = legacyGroup;
const latestEncryptionKeyPairHex = sqlNode.getLatestClosedGroupEncryptionKeyPair(
legacyGroup.id,
@ -1387,8 +1379,7 @@ function insertLegacyGroupIntoWrapper(
const wrapperLegacyGroup = getLegacyGroupInfoFromDBValues({
id,
hidden,
isPinned,
priority,
expireTimer,
groupAdmins,
members,
@ -1455,14 +1446,6 @@ function updateToSessionSchemaVersion30(currentVersion: number, db: BetterSqlite
* Create a table to store our sharedConfigMessage dumps
*/
db.transaction(() => {
// when deleting a contact we now mark it as 'hidden' rather than overriding the `active_at` field.
// by default, conversation are hidden
db.exec(
`ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN hidden INTEGER NOT NULL DEFAULT ${toSqliteBoolean(
true
)};
`
);
// drop unused readCapability & uploadCapability columns. Also move `writeCapability` to memory only value.
db.exec(`
ALTER TABLE ${CONVERSATIONS_TABLE} DROP COLUMN readCapability; -- stored in a redux slice now
@ -1471,6 +1454,7 @@ function updateToSessionSchemaVersion30(currentVersion: number, db: BetterSqlite
ALTER TABLE ${CONVERSATIONS_TABLE} DROP COLUMN subscriberCount; -- stored in a redux slice now
ALTER TABLE ${CONVERSATIONS_TABLE} DROP COLUMN groupModerators; -- stored in a redux slice now
ALTER TABLE ${CONVERSATIONS_TABLE} RENAME COLUMN isPinned TO priority; -- isPinned was 0 for false and 1 for true, which matches our way of handling the priority
ALTER TABLE ${CONVERSATIONS_TABLE} DROP COLUMN is_medium_group; -- a medium group starts with 05 and has a type of group. We cache everything renderer side so there is no need for that field
`);
@ -1478,18 +1462,13 @@ function updateToSessionSchemaVersion30(currentVersion: number, db: BetterSqlite
db.exec(`
ALTER TABLE unprocessed DROP COLUMN serverTimestamp;
`);
// mark every "active" private chats as not hidden
db.prepare(
`UPDATE ${CONVERSATIONS_TABLE} SET
hidden = ${toSqliteBoolean(false)}
WHERE type = 'private' AND active_at > 0 AND (didApproveMe OR isApproved);`
).run({});
// mark every not private chats (groups or communities) as not hidden (even if a group was left or we were kicked, we want it visible in the app)
// after the rename of isPinned to priority, we also need to hide any conversation that
// TODO do we need to update the conversation priority to hidden for some for those ( like the non active and non approved/didApproveMe?)
db.prepare(
`UPDATE ${CONVERSATIONS_TABLE} SET
hidden = ${toSqliteBoolean(false)}
WHERE type <> 'private' AND active_at > 0;`
priority = ${CONVERSATION_PRIORITIES.hidden}
WHERE type = 'private' AND active_at IS NULL;`
).run({});
db.exec(`CREATE TABLE ${CONFIG_DUMP_TABLE}(

View file

@ -436,13 +436,12 @@ function saveConversation(data: ConversationAttributes): SaveConversationReturn
avatarImageId,
triggerNotificationsFor,
isTrustedForAttachmentDownload,
isPinned,
isApproved,
didApproveMe,
avatarInProfile,
displayNameInProfile,
conversationIdOrigin,
hidden,
priority,
markedAsUnread,
} = formatted;
@ -486,13 +485,12 @@ function saveConversation(data: ConversationAttributes): SaveConversationReturn
avatarImageId,
triggerNotificationsFor,
isTrustedForAttachmentDownload: toSqliteBoolean(isTrustedForAttachmentDownload),
isPinned: toSqliteBoolean(isPinned),
priority,
isApproved: toSqliteBoolean(isApproved),
didApproveMe: toSqliteBoolean(didApproveMe),
avatarInProfile,
displayNameInProfile,
conversationIdOrigin,
hidden,
markedAsUnread: toSqliteBoolean(markedAsUnread),
});

View file

@ -156,8 +156,8 @@ async function handleContactsUpdate(result: IncomingConfResult): Promise<Incomin
changes = true;
}
if (Boolean(wrapperConvo.hidden) !== Boolean(contactConvo.isHidden())) {
contactConvo.set({ hidden: !!wrapperConvo.hidden });
if (wrapperConvo.priority !== contactConvo.get('priority')) {
contactConvo.set({ priority: wrapperConvo.priority });
changes = true;
}
@ -176,13 +176,6 @@ async function handleContactsUpdate(result: IncomingConfResult): Promise<Incomin
changes = true;
}
//TODO priority means more than just isPinned but has an order logic in it too
const shouldBePinned = wrapperConvo.priority > 0;
if (shouldBePinned !== Boolean(contactConvo.isPinned())) {
await contactConvo.setIsPinned(shouldBePinned, false);
changes = true;
}
const convoBlocked = wrapperConvo.blocked || false;
await BlockedNumberController.setBlocked(wrapperConvo.id, convoBlocked);
@ -277,12 +270,8 @@ async function handleCommunitiesUpdate() {
if (fromWrapper && communityConvo) {
let changes = false;
//TODO priority means more than just isPinned but has an order logic in it too
const shouldBePinned = fromWrapper.priority > 0;
if (shouldBePinned !== Boolean(communityConvo.isPinned())) {
await communityConvo.setIsPinned(shouldBePinned, false);
changes = true;
}
changes =
(await communityConvo.setPriorityFromWrapper(fromWrapper.priority, false)) || changes;
// make sure to write the changes to the database now as the `AvatarDownloadJob` below might take some time before getting run
if (changes) {
@ -368,13 +357,10 @@ async function handleLegacyGroupUpdate(latestEnvelopeTimestamp: number) {
await ClosedGroup.updateOrCreateClosedGroup(groupDetails);
let changes = false;
if (legacyGroupConvo.isPinned() !== fromWrapper.priority > 0) {
await legacyGroupConvo.setIsPinned(fromWrapper.priority > 0, false);
changes = true;
}
if (!!legacyGroupConvo.isHidden() !== !!fromWrapper.hidden) {
legacyGroupConvo.set({ hidden: !!fromWrapper.hidden });
let changes = await legacyGroupConvo.setPriorityFromWrapper(fromWrapper.priority, false);
if (legacyGroupConvo.get('priority') !== fromWrapper.priority) {
legacyGroupConvo.set({ priority: fromWrapper.priority });
changes = true;
}
if (legacyGroupConvo.get('expireTimer') !== fromWrapper.disappearingTimerSeconds) {

View file

@ -642,10 +642,10 @@ async function handleMessageRequestResponse(
conversationToApprove.set({
active_at: mostRecentActiveAt,
hidden: false,
isApproved: true,
didApproveMe: true,
});
await conversationToApprove.unhideIfNeeded(false);
if (convosToMerge.length) {
// merge fields we care by hand

View file

@ -267,14 +267,15 @@ async function handleRegularMessage(
const conversationActiveAt = conversation.get('active_at');
if (
!conversationActiveAt ||
conversation.get('hidden') ||
conversation.isHidden() ||
(message.get('sent_at') || 0) > conversationActiveAt
) {
conversation.set({
active_at: message.get('sent_at'),
hidden: false, // a new message was received for that conversation. If it was not it should not be hidden anymore
lastMessage: message.getNotificationText(),
});
// a new message was received for that conversation. If it was not it should not be hidden anymore
await conversation.unhideIfNeeded(false);
}
if (rawDataMessage.profileKey) {

View file

@ -8,7 +8,10 @@ import { OpenGroupServerPoller } from './OpenGroupServerPoller';
import autoBind from 'auto-bind';
import _, { clone, isEqual } from 'lodash';
import { ConversationTypeEnum } from '../../../../models/conversationAttributes';
import {
CONVERSATION_PRIORITIES,
ConversationTypeEnum,
} from '../../../../models/conversationAttributes';
import { SessionUtilUserGroups } from '../../../utils/libsession/libsession_utils_user_groups';
import { openGroupV2GetRoomInfoViaOnionV4 } from '../sogsv3/sogsV3RoomInfos';
import { UserGroupsWrapperActions } from '../../../../webworker/workers/browser/libsession_worker_interface';
@ -222,7 +225,7 @@ export class OpenGroupManagerV2 {
displayNameInProfile: updatedRoom.roomName,
isApproved: true,
didApproveMe: true,
hidden: false,
priority: CONVERSATION_PRIORITIES.default,
isTrustedForAttachmentDownload: true, // we always trust attachments when sent to an opengroup
});
await conversation.commit();

View file

@ -8,7 +8,7 @@ import { getSwarmFor } from '../apis/snode_api/snodePool';
import { PubKey } from '../types';
import { deleteAllMessagesByConvoIdNoConfirmation } from '../../interactions/conversationInteractions';
import { ConversationTypeEnum } from '../../models/conversationAttributes';
import { CONVERSATION_PRIORITIES, ConversationTypeEnum } from '../../models/conversationAttributes';
import { leaveClosedGroup } from '../group/closed-group';
import { ConfigurationSync } from '../utils/job_runners/jobs/ConfigurationSyncJob';
import { SessionUtilContact } from '../utils/libsession/libsession_utils_contacts';
@ -224,9 +224,8 @@ export class ConversationController {
// so the conversation still exists (needed for that user's profile in groups) but is not shown on the list of conversation.
// We also keep the messages for now, as turning a contact as hidden might just be a temporary thing
window.log.info(`deleteContact isPrivate, marking as hidden: ${id}`);
conversation.set({
hidden: true,
priority: CONVERSATION_PRIORITIES.hidden,
});
// we currently do not wish to reset the approved/approvedMe state when marking a private conversation as hidden
// await conversation.setIsApproved(false, false);

View file

@ -220,13 +220,7 @@ export async function updateOrCreateClosedGroup(details: GroupInfo) {
const updates: Pick<
ConversationAttributes,
| 'type'
| 'members'
| 'displayNameInProfile'
| 'active_at'
| 'left'
| 'lastJoinedTimestamp'
| 'hidden'
'type' | 'members' | 'displayNameInProfile' | 'active_at' | 'left' | 'lastJoinedTimestamp'
> = {
displayNameInProfile: details.name,
members: details.members,
@ -234,10 +228,10 @@ export async function updateOrCreateClosedGroup(details: GroupInfo) {
active_at: details.activeAt ? details.activeAt : 0,
left: details.activeAt ? false : true,
lastJoinedTimestamp: details.activeAt && weWereJustAdded ? Date.now() : details.activeAt || 0,
hidden: false,
};
conversation.set(updates);
await conversation.unhideIfNeeded(false);
const isBlocked = details.blocked || false;
if (conversation.isClosedGroup()) {

View file

@ -503,7 +503,7 @@ export async function USER_callRecipient(recipient: string) {
window.log.info('Sending preOffer message to ', ed25519Str(recipient));
const calledConvo = getConversationController().get(recipient);
calledConvo.set('active_at', Date.now()); // addSingleOutgoingMessage does the commit for us on the convo
calledConvo.set('hidden', false);
await calledConvo.unhideIfNeeded(false);
weAreCallerOnCurrentCall = true;
await calledConvo?.addSingleOutgoingMessage({
@ -852,7 +852,8 @@ export async function USER_acceptIncomingCallRequest(fromSender: string) {
const networkTimestamp = GetNetworkTime.getNowWithNetworkOffset();
const callerConvo = getConversationController().get(fromSender);
callerConvo.set('active_at', networkTimestamp);
callerConvo.set('hidden', false);
await callerConvo.unhideIfNeeded(false);
await callerConvo?.addSingleIncomingMessage({
source: UserUtils.getOurPubKeyStrFromCache(),
sent_at: networkTimestamp,
@ -1189,7 +1190,7 @@ async function addMissedCallMessage(callerPubkey: string, sentAt: number) {
if (incomingCallConversation.isActive() || incomingCallConversation.isHidden()) {
incomingCallConversation.set('active_at', GetNetworkTime.getNowWithNetworkOffset());
incomingCallConversation.set('hidden', false);
await incomingCallConversation.unhideIfNeeded(false);
}
await incomingCallConversation?.addSingleIncomingMessage({

View file

@ -81,8 +81,7 @@ async function insertContactFromDBIntoWrapperAndRefresh(id: string): Promise<voi
const dbApproved = !!foundConvo.get('isApproved') || false;
const dbApprovedMe = !!foundConvo.get('didApproveMe') || false;
const dbBlocked = !!foundConvo.isBlocked() || false;
const hidden = foundConvo.get('hidden') || false;
const isPinned = foundConvo.get('isPinned');
const priority = foundConvo.get('priority') || 0;
const expirationTimerSeconds = foundConvo.get('expireTimer') || 0;
const wrapperContact = getContactInfoFromDBValues({
@ -94,8 +93,7 @@ async function insertContactFromDBIntoWrapperAndRefresh(id: string): Promise<voi
dbNickname,
dbProfileKey,
dbProfileUrl,
isPinned,
hidden,
priority,
expirationTimerSeconds,
});

View file

@ -102,7 +102,7 @@ async function insertGroupsFromDBIntoWrapperAndRefresh(convoId: string): Promise
);
const wrapperComm = getCommunityInfoFromDBValues({
isPinned: !!foundConvo.get('isPinned'),
priority: foundConvo.get('priority'),
fullUrl,
});
@ -124,12 +124,11 @@ async function insertGroupsFromDBIntoWrapperAndRefresh(convoId: string): Promise
const encryptionKeyPair = await Data.getLatestClosedGroupEncryptionKeyPair(convoId);
const wrapperLegacyGroup = getLegacyGroupInfoFromDBValues({
id: foundConvo.id,
isPinned: !!foundConvo.get('isPinned'),
priority: foundConvo.get('priority'),
members: foundConvo.get('members') || [],
groupAdmins: foundConvo.get('groupAdmins') || [],
expireTimer: foundConvo.get('expireTimer'),
displayNameInProfile: foundConvo.get('displayNameInProfile'),
hidden: false, // TODOLATER we do not handle hidden yet for groups
encPubkeyHex: encryptionKeyPair?.publicHex || '',
encSeckeyHex: encryptionKeyPair?.privateHex || '',
});

View file

@ -245,7 +245,6 @@ export interface ReduxConversationType {
isTyping?: boolean;
isBlocked?: boolean;
isHidden: boolean;
isKickedFromGroup?: boolean;
left?: boolean;
avatarPath?: string | null; // absolute filepath to the avatar
@ -258,7 +257,7 @@ export interface ReduxConversationType {
*/
currentNotificationSetting?: ConversationNotificationSettingType;
isPinned?: boolean;
priority?: number; // undefined means 0
isInitialFetchingInProgress?: boolean;
isApproved?: boolean;
didApproveMe?: boolean;

View file

@ -231,10 +231,12 @@ const collator = new Intl.Collator();
export const _getConversationComparator = (testingi18n?: LocalizerType) => {
return (left: ReduxConversationType, right: ReduxConversationType): number => {
// Pin is the first criteria to check
if (left.isPinned && !right.isPinned) {
const leftPriority = left.priority || 0;
const rightPriority = right.priority || 0;
if (leftPriority > rightPriority) {
return -1;
}
if (!left.isPinned && right.isPinned) {
if (rightPriority > leftPriority) {
return 1;
}
// Then if none is pinned, check other criteria
@ -277,7 +279,7 @@ export const _getLeftPaneLists = (
conversation.type === ConversationTypeEnum.PRIVATE &&
conversation.isApproved &&
!conversation.isBlocked &&
!conversation.isHidden
(conversation.priority || 0) >= 0 // filtering non-hidden conversation
) {
directConversations.push(conversation);
}

View file

@ -3,7 +3,10 @@ import { expect } from 'chai';
import { from_hex, from_string } from 'libsodium-wrappers-sumo';
import Sinon from 'sinon';
import { ConversationModel } from '../../../../models/conversation';
import { ConversationTypeEnum } from '../../../../models/conversationAttributes';
import {
CONVERSATION_PRIORITIES,
ConversationTypeEnum,
} from '../../../../models/conversationAttributes';
import { UserUtils } from '../../../../session/utils';
import { SessionUtilContact } from '../../../../session/utils/libsession/libsession_utils_contacts';
@ -349,7 +352,7 @@ describe('libsession_contacts', () => {
new ConversationModel({
...validArgs,
type: ConversationTypeEnum.PRIVATE,
hidden: true,
priority: CONVERSATION_PRIORITIES.hidden,
} as any)
)
).to.be.eq(true);

View file

@ -2,7 +2,10 @@ import { expect } from 'chai';
import Sinon from 'sinon';
import { ConversationModel } from '../../../../models/conversation';
import { ConversationTypeEnum } from '../../../../models/conversationAttributes';
import {
CONVERSATION_PRIORITIES,
ConversationTypeEnum,
} from '../../../../models/conversationAttributes';
import { UserUtils } from '../../../../session/utils';
import { SessionUtilUserGroups } from '../../../../session/utils/libsession/libsession_utils_user_groups';
@ -97,7 +100,7 @@ describe('libsession_groups', () => {
SessionUtilUserGroups.isUserGroupToStoreInWrapper(
new ConversationModel({
...validLegacyGroupArgs,
hidden: true,
priority: CONVERSATION_PRIORITIES.hidden,
})
)
).to.be.eq(true);

View file

@ -3,6 +3,7 @@
import { expect } from 'chai';
import { describe } from 'mocha';
import {
CONVERSATION_PRIORITIES,
ConversationAttributes,
fillConvoAttributesWithDefaults,
} from '../../../../models/conversationAttributes';
@ -178,20 +179,20 @@ describe('fillConvoAttributesWithDefaults', () => {
});
});
describe('isPinned', () => {
it('initialize isPinned if not given', () => {
describe('priority', () => {
it('initialize priority if not given', () => {
expect(fillConvoAttributesWithDefaults({} as ConversationAttributes)).to.have.deep.property(
'isPinned',
false
'priority',
0
);
});
it('do not override isPinned if given', () => {
it('do not override priority if given', () => {
expect(
fillConvoAttributesWithDefaults({
isPinned: true,
priority: CONVERSATION_PRIORITIES.pinned,
} as ConversationAttributes)
).to.have.deep.property('isPinned', true);
).to.have.deep.property('priority', 1);
});
});

View file

@ -32,29 +32,6 @@ describe('formatRowOfConversation', () => {
});
});
describe('isPinned', () => {
it('initialize isPinned if they are not given', () => {
expect(formatRowOfConversation({}, 'test', 0, false)).to.have.deep.property(
'isPinned',
false
);
});
it('do not override isPinned if they are set in the row as integer: true', () => {
expect(formatRowOfConversation({ isPinned: 1 }, 'test', 0, false)).to.have.deep.property(
'isPinned',
true
);
});
it('do not override isPinned if they are set in the row as integer: false', () => {
expect(formatRowOfConversation({ isPinned: 0 }, 'test', 0, false)).to.have.deep.property(
'isPinned',
false
);
});
});
describe('isApproved', () => {
it('initialize isApproved if they are not given', () => {
expect(formatRowOfConversation({}, 'test', 0, false)).to.have.deep.property(

View file

@ -1,5 +1,8 @@
import { assert } from 'chai';
import { ConversationTypeEnum } from '../../../../models/conversationAttributes';
import {
CONVERSATION_PRIORITIES,
ConversationTypeEnum,
} from '../../../../models/conversationAttributes';
import { ConversationLookupType } from '../../../../state/ducks/conversations';
import {
@ -37,8 +40,7 @@ describe('state/selectors/conversations', () => {
lastMessage: undefined,
members: [],
expireTimer: 0,
isPinned: false,
isHidden: false,
priority: CONVERSATION_PRIORITIES.default,
},
id2: {
id: 'id2',
@ -63,8 +65,7 @@ describe('state/selectors/conversations', () => {
lastMessage: undefined,
members: [],
expireTimer: 0,
isPinned: false,
isHidden: false,
priority: CONVERSATION_PRIORITIES.default,
},
id3: {
id: 'id3',
@ -89,8 +90,7 @@ describe('state/selectors/conversations', () => {
lastMessage: undefined,
members: [],
expireTimer: 0,
isPinned: false,
isHidden: false,
priority: CONVERSATION_PRIORITIES.default,
},
id4: {
id: 'id4',
@ -116,8 +116,7 @@ describe('state/selectors/conversations', () => {
expireTimer: 0,
lastMessage: undefined,
members: [],
isPinned: false,
isHidden: false,
priority: CONVERSATION_PRIORITIES.default,
},
id5: {
id: 'id5',
@ -143,8 +142,7 @@ describe('state/selectors/conversations', () => {
groupAdmins: [],
lastMessage: undefined,
members: [],
isPinned: false,
isHidden: false,
priority: CONVERSATION_PRIORITIES.default,
},
};
const comparator = _getConversationComparator(i18n);
@ -186,9 +184,8 @@ describe('state/selectors/conversations', () => {
groupAdmins: [],
lastMessage: undefined,
members: [],
isPinned: false,
priority: CONVERSATION_PRIORITIES.default,
isPublic: false,
isHidden: false,
},
id2: {
id: 'id2',
@ -214,9 +211,9 @@ describe('state/selectors/conversations', () => {
groupAdmins: [],
lastMessage: undefined,
members: [],
isPinned: false,
priority: CONVERSATION_PRIORITIES.default,
isPublic: false,
isHidden: false,
},
id3: {
id: 'id3',
@ -242,9 +239,8 @@ describe('state/selectors/conversations', () => {
groupAdmins: [],
lastMessage: undefined,
members: [],
isPinned: true,
priority: CONVERSATION_PRIORITIES.pinned,
isPublic: false,
isHidden: false,
},
id4: {
id: 'id4',
@ -269,9 +265,8 @@ describe('state/selectors/conversations', () => {
groupAdmins: [],
lastMessage: undefined,
members: [],
isPinned: true,
priority: CONVERSATION_PRIORITIES.pinned,
isPublic: false,
isHidden: false,
},
id5: {
id: 'id5',
@ -297,9 +292,8 @@ describe('state/selectors/conversations', () => {
groupAdmins: [],
lastMessage: undefined,
members: [],
isPinned: false,
priority: CONVERSATION_PRIORITIES.default,
isPublic: false,
isHidden: false,
},
};
const comparator = _getConversationComparator(i18n);

View file

@ -113,8 +113,7 @@ export function getContactInfoFromDBValues({
dbBlocked,
dbName,
dbNickname,
hidden,
isPinned,
priority,
dbProfileUrl,
dbProfileKey,
expirationTimerSeconds,
@ -123,10 +122,9 @@ export function getContactInfoFromDBValues({
dbApproved: boolean;
dbApprovedMe: boolean;
dbBlocked: boolean;
hidden: boolean;
dbNickname: string | undefined;
dbName: string | undefined;
isPinned: boolean;
priority: number;
dbProfileUrl: string | undefined;
dbProfileKey: string | undefined;
expirationTimerSeconds: number | undefined;
@ -136,8 +134,7 @@ export function getContactInfoFromDBValues({
approved: !!dbApproved,
approvedMe: !!dbApprovedMe,
blocked: !!dbBlocked,
hidden: !!hidden,
priority: !!isPinned ? 1 : 0, // TODOLATER the priority handling is not that simple
priority,
nickname: dbNickname,
name: dbName,
expirationTimerSeconds:
@ -168,15 +165,15 @@ export function getContactInfoFromDBValues({
* It is created in this file so we can reuse it during the migration (node side), and from the renderer side
*/
export function getCommunityInfoFromDBValues({
isPinned,
priority,
fullUrl,
}: {
isPinned: boolean;
priority: number;
fullUrl: string;
}) {
const community = {
fullUrl,
priority: !!isPinned ? 1 : 0, // TODOLATER the priority handling is not that simple
priority,
};
return community;
@ -200,18 +197,14 @@ function maybeArrayJSONtoArray(arr: string | Array<string>): Array<string> {
export function getLegacyGroupInfoFromDBValues({
id,
hidden,
isPinned,
priority,
members: maybeMembers,
displayNameInProfile,
expireTimer,
encPubkeyHex,
encSeckeyHex,
groupAdmins: maybeAdmins,
}: Pick<
ConversationAttributes,
'hidden' | 'id' | 'isPinned' | 'displayNameInProfile' | 'expireTimer'
> & {
}: Pick<ConversationAttributes, 'id' | 'priority' | 'displayNameInProfile' | 'expireTimer'> & {
encPubkeyHex: string;
encSeckeyHex: string;
members: string | Array<string>;
@ -229,9 +222,8 @@ export function getLegacyGroupInfoFromDBValues({
const legacyGroup: LegacyGroupInfo = {
pubkeyHex: id,
disappearingTimerSeconds: !expireTimer ? 0 : expireTimer,
hidden: !!hidden,
name: displayNameInProfile || '',
priority: !!isPinned ? 1 : 0, // TODOLATER the priority handling is not that simple
priority: priority || 0,
members: wrappedMembers,
encPubkey: !isEmpty(encPubkeyHex) ? from_hex(encPubkeyHex) : new Uint8Array(),
encSeckey: !isEmpty(encSeckeyHex) ? from_hex(encSeckeyHex) : new Uint8Array(),