add ourPrimary convo to redux and update the actionPanel with it

pull/1381/head
Audric Ackermann 4 years ago
parent c203303c71
commit 1503d83f3a
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -613,7 +613,7 @@
confirmDialog.render(); confirmDialog.render();
}; };
window.showEditProfileDialog = async callback => { window.showEditProfileDialog = async () => {
const ourNumber = window.storage.get('primaryDevicePubKey'); const ourNumber = window.storage.get('primaryDevicePubKey');
const conversation = await ConversationController.getOrCreateAndWait( const conversation = await ConversationController.getOrCreateAndWait(
ourNumber, ourNumber,
@ -642,7 +642,6 @@
if (appView) { if (appView) {
appView.showEditProfileDialog({ appView.showEditProfileDialog({
callback,
profileName: displayName, profileName: displayName,
pubkey: ourNumber, pubkey: ourNumber,
avatarPath, avatarPath,
@ -706,26 +705,35 @@
url, url,
}); });
newAvatarPath = upgraded.path; newAvatarPath = upgraded.path;
// Replace our temporary image with the attachment pointer from the server:
conversation.set('avatar', null);
conversation.setLokiProfile({
displayName: newName,
avatar: newAvatarPath,
});
} else {
// do not update the avatar if it did not change
conversation.setLokiProfile({
displayName: newName,
});
} }
// Replace our temporary image with the attachment pointer from the server: conversation.commit();
conversation.set('avatar', null);
conversation.setLokiProfile({
displayName: newName,
avatar: newAvatarPath,
});
// inform all your registered public servers // inform all your registered public servers
// could put load on all the servers // could put load on all the servers
// if they just keep changing their names without sending messages // if they just keep changing their names without sending messages
// so we could disable this here // so we could disable this here
// or least it enable for the quickest response // or least it enable for the quickest response
window.lokiPublicChatAPI.setProfileName(newName); window.lokiPublicChatAPI.setProfileName(newName);
window
.getConversations() if (avatar) {
.filter(convo => convo.isPublic() && !convo.isRss()) window
.forEach(convo => .getConversations()
convo.trigger('ourAvatarChanged', { url, profileKey }) .filter(convo => convo.isPublic() && !convo.isRss())
); .forEach(convo =>
convo.trigger('ourAvatarChanged', { url, profileKey })
);
}
}, },
}); });
} }
@ -938,13 +946,6 @@
}); });
Whisper.events.on('onShowUserDetails', async ({ userPubKey }) => { Whisper.events.on('onShowUserDetails', async ({ userPubKey }) => {
const isMe = userPubKey === textsecure.storage.user.getNumber();
if (isMe) {
Whisper.events.trigger('onEditProfile');
return;
}
const conversation = await ConversationController.getOrCreateAndWait( const conversation = await ConversationController.getOrCreateAndWait(
userPubKey, userPubKey,
'private' 'private'

@ -1895,8 +1895,11 @@
await this.commit(); await this.commit();
} }
// if set to null, it will show a placeholder with color and first letter // a user cannot remove an avatar. Only change it
await this.setProfileAvatar({ path: newProfile.avatar }); // if you change this behavior, double check all setLokiProfile calls (especially the one in EditProfileDialog)
if (newProfile.avatar) {
await this.setProfileAvatar({ path: newProfile.avatar });
}
await this.updateProfileName(); await this.updateProfileName();
}, },
@ -2386,7 +2389,6 @@
getAvatarPath() { getAvatarPath() {
const avatar = this.get('avatar') || this.get('profileAvatar'); const avatar = this.get('avatar') || this.get('profileAvatar');
if (typeof avatar === 'string') { if (typeof avatar === 'string') {
return avatar; return avatar;
} }

@ -8,10 +8,9 @@
Whisper.EditProfileDialogView = Whisper.View.extend({ Whisper.EditProfileDialogView = Whisper.View.extend({
className: 'loki-dialog modal', className: 'loki-dialog modal',
initialize({ profileName, avatarPath, pubkey, onOk, callback }) { initialize({ profileName, avatarPath, pubkey, onOk }) {
this.close = this.close.bind(this); this.close = this.close.bind(this);
this.callback = callback;
this.profileName = profileName; this.profileName = profileName;
this.pubkey = pubkey; this.pubkey = pubkey;
this.avatarPath = avatarPath; this.avatarPath = avatarPath;
@ -25,7 +24,6 @@
className: 'edit-profile-dialog', className: 'edit-profile-dialog',
Component: window.Signal.Components.EditProfileDialog, Component: window.Signal.Components.EditProfileDialog,
props: { props: {
callback: this.callback,
onOk: this.onOk, onOk: this.onOk,
onClose: this.close, onClose: this.close,
profileName: this.profileName, profileName: this.profileName,

@ -26,7 +26,6 @@ declare global {
} }
interface Props { interface Props {
callback: any;
i18n: any; i18n: any;
profileName: string; profileName: string;
avatarPath: string; avatarPath: string;
@ -318,17 +317,10 @@ export class EditProfileDialog extends React.Component<Props, State> {
this.props.onOk(newName, avatar); this.props.onOk(newName, avatar);
this.setState( this.setState({
{ mode: 'default',
mode: 'default', setProfileName: this.state.profileName,
setProfileName: this.state.profileName, });
},
() => {
// Update settings in dialog complete;
// now callback to reloadactions panel avatar
this.props.callback(this.state.avatar);
}
);
} }
private closeDialog() { private closeDialog() {

@ -29,6 +29,7 @@ export type RowRendererParamsType = {
}; };
interface Props { interface Props {
ourPrimaryConversation: ConversationType;
conversations: Array<ConversationListItemPropsType>; conversations: Array<ConversationListItemPropsType>;
contacts: Array<ConversationType>; contacts: Array<ConversationType>;
@ -90,14 +91,15 @@ export class LeftPane extends React.Component<Props> {
} }
public render(): JSX.Element { public render(): JSX.Element {
const ourPrimaryConversation = this.props.ourPrimaryConversation;
return ( return (
<SessionTheme theme={this.props.theme}> <SessionTheme theme={this.props.theme}>
<div className="module-left-pane-session"> <div className="module-left-pane-session">
<ActionsPanel <ActionsPanel
selectedSection={this.props.focusedSection} selectedSection={this.props.focusedSection}
onSectionSelected={this.handleSectionSelected} onSectionSelected={this.handleSectionSelected}
conversations={this.props.conversations}
unreadMessageCount={this.props.unreadMessageCount} unreadMessageCount={this.props.unreadMessageCount}
ourPrimaryConversation={ourPrimaryConversation}
/> />
<div className="module-left-pane">{this.renderSection()}</div> <div className="module-left-pane">{this.renderSection()}</div>
</div> </div>

@ -1,12 +1,13 @@
import React from 'react'; import React from 'react';
import { connect, useDispatch } from 'react-redux'; import { connect } from 'react-redux';
import { SessionIconButton, SessionIconSize, SessionIconType } from './icon'; import { SessionIconButton, SessionIconSize, SessionIconType } from './icon';
import { Avatar } from '../Avatar'; import { Avatar } from '../Avatar';
import { PropsData as ConversationListItemPropsType } from '../ConversationListItem'; import { removeItemById } from '../../../js/modules/data';
import { createOrUpdateItem, getItemById } from '../../../js/modules/data';
import { APPLY_THEME } from '../../state/ducks/theme';
import { darkTheme, lightTheme } from '../../state/ducks/SessionTheme'; import { darkTheme, lightTheme } from '../../state/ducks/SessionTheme';
import { SessionToastContainer } from './SessionToastContainer'; import { SessionToastContainer } from './SessionToastContainer';
import { mapDispatchToProps } from '../../state/actions';
import { ConversationType } from '../../state/ducks/conversations';
import { noop } from 'lodash';
// tslint:disable-next-line: no-import-side-effect no-submodule-imports // tslint:disable-next-line: no-import-side-effect no-submodule-imports
export enum SectionType { export enum SectionType {
@ -18,104 +19,22 @@ export enum SectionType {
Moon, Moon,
} }
interface State {
avatarPath: string;
}
interface Props { interface Props {
onSectionSelected: any; onSectionSelected: any;
selectedSection: SectionType; selectedSection: SectionType;
conversations: Array<ConversationListItemPropsType> | undefined;
unreadMessageCount: number; unreadMessageCount: number;
dispatch?: any; ourPrimaryConversation: ConversationType;
applyTheme?: any;
} }
class ActionsPanelPrivate extends React.Component<Props, State> { class ActionsPanelPrivate extends React.Component<Props> {
private ourConversation: any;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.state = {
avatarPath: '',
};
this.editProfileHandle = this.editProfileHandle.bind(this); this.editProfileHandle = this.editProfileHandle.bind(this);
this.refreshAvatarCallback = this.refreshAvatarCallback.bind(this); // we consider people had the time to upgrade, so remove this id from the db
} // it was used to display a dialog when we added the light mode auto-enabled
void removeItemById('hasSeenLightModeDialog');
public componentDidMount() {
// tslint:disable-next-line: no-backbone-get-set-outside-model
const ourNumber = window.storage.get('primaryDevicePubKey');
void window.ConversationController.getOrCreateAndWait(
ourNumber,
'private'
).then((conversation: any) => {
this.setState({
avatarPath: conversation.getAvatarPath(),
});
// When our primary device updates its avatar, we will need for a message sync to know about that.
// Once we get the avatar update, we need to refresh this react component.
// So we listen to changes on our profile avatar and use the updated avatarPath (done on message received).
this.ourConversation = conversation;
this.ourConversation.on(
'change',
() => {
this.refreshAvatarCallback(this.ourConversation);
},
'refreshAvatarCallback'
);
void this.showLightThemeDialogIfNeeded();
});
}
public async showLightThemeDialogIfNeeded() {
const currentTheme = window.Events.getThemeSetting(); // defaults to light on new registration
if (currentTheme !== 'light') {
const message = 'Light Mode';
const messageSub =
'Whoops, who left the lights on?</br></br>\
Thats right, Session has a spiffy new light mode! Take the fresh new color palette for a spin its now the default mode.</br></br>\
Want to go back to the dark side? Just tap the moon symbol in the lower left corner of the app to switch modes.';
const hasSeenLightMode = await getItemById('hasSeenLightModeDialog');
if (hasSeenLightMode?.value === true) {
// if hasSeen is set and true, we have nothing to do
return;
}
// force light them right now, then ask for permission
await window.Events.setThemeSetting('light');
window.confirmationDialog({
message,
messageSub,
resolve: async () => {
const data = {
id: 'hasSeenLightModeDialog',
value: true,
};
void createOrUpdateItem(data);
},
okTheme: 'default primary',
hideCancel: true,
sessionIcon: SessionIconType.Sun,
iconSize: SessionIconSize.Max,
});
}
}
public refreshAvatarCallback(conversation: any) {
if (conversation.changed?.profileAvatar) {
this.setState({
avatarPath: conversation.getAvatarPath(),
});
}
}
public componentWillUnmount() {
if (this.ourConversation) {
this.ourConversation.off('change', null, 'refreshAvatarCallback');
}
} }
public Section = ({ public Section = ({
@ -143,10 +62,7 @@ class ActionsPanelPrivate extends React.Component<Props, State> {
const newThemeObject = const newThemeObject =
updatedTheme === 'dark' ? darkTheme : lightTheme; updatedTheme === 'dark' ? darkTheme : lightTheme;
this.props.dispatch({ this.props.applyTheme(newThemeObject);
type: APPLY_THEME,
payload: newThemeObject,
});
} else { } else {
onSelect(type); onSelect(type);
} }
@ -204,11 +120,7 @@ class ActionsPanelPrivate extends React.Component<Props, State> {
}; };
public editProfileHandle() { public editProfileHandle() {
window.showEditProfileDialog((avatar: any) => { window.showEditProfileDialog(noop);
this.setState({
avatarPath: avatar,
});
});
} }
public render(): JSX.Element { public render(): JSX.Element {
@ -224,7 +136,7 @@ class ActionsPanelPrivate extends React.Component<Props, State> {
<div className="module-left-pane__sections-container"> <div className="module-left-pane__sections-container">
<this.Section <this.Section
type={SectionType.Profile} type={SectionType.Profile}
avatarPath={this.state.avatarPath} avatarPath={this.props.ourPrimaryConversation.avatarPath}
isSelected={isProfilePageSelected} isSelected={isProfilePageSelected}
onSelect={this.handleSectionSelect} onSelect={this.handleSectionSelect}
/> />
@ -260,4 +172,6 @@ class ActionsPanelPrivate extends React.Component<Props, State> {
}; };
} }
export const ActionsPanel = connect()(ActionsPanelPrivate); const smart = connect(null, mapDispatchToProps);
export const ActionsPanel = smart(ActionsPanelPrivate);

@ -15,7 +15,6 @@ import {
// Workaround: A react component's required properties are filtering up through connect() // Workaround: A react component's required properties are filtering up through connect()
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363 // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
const FilteredLeftPane = SmartLeftPane as any; const FilteredLeftPane = SmartLeftPane as any;
const FilteredSessionConversation = SmartSessionConversation as any;
type Props = { type Props = {
focusedSection: number; focusedSection: number;
@ -27,8 +26,6 @@ type State = {
isExpired: boolean; isExpired: boolean;
}; };
// tslint:disable: react-a11y-img-has-alt
export class SessionInboxView extends React.Component<Props, State> { export class SessionInboxView extends React.Component<Props, State> {
private store: any; private store: any;
@ -111,7 +108,7 @@ export class SessionInboxView extends React.Component<Props, State> {
private renderSessionConversation() { private renderSessionConversation() {
return ( return (
<div className="session-conversation"> <div className="session-conversation">
<FilteredSessionConversation /> <SmartSessionConversation />
</div> </div>
); );
} }

@ -43,10 +43,7 @@ interface Props {
onDeleteSelectedMessages: () => Promise<void>; onDeleteSelectedMessages: () => Promise<void>;
} }
export class SessionMessagesList extends React.Component< export class SessionMessagesList extends React.Component<Props, State> {
Props,
State
> {
private readonly messagesEndRef: React.RefObject<HTMLDivElement>; private readonly messagesEndRef: React.RefObject<HTMLDivElement>;
private readonly messageContainerRef: React.RefObject<any>; private readonly messageContainerRef: React.RefObject<any>;

@ -4,15 +4,19 @@ import { actions as search } from './ducks/search';
import { actions as conversations } from './ducks/conversations'; import { actions as conversations } from './ducks/conversations';
import { actions as user } from './ducks/user'; import { actions as user } from './ducks/user';
import { actions as sections } from './ducks/section'; import { actions as sections } from './ducks/section';
import { actions as theme } from './ducks/theme';
const actions = {
...search,
...conversations,
...user,
// ...messages,
...sections,
};
export function mapDispatchToProps(dispatch: Dispatch): Object { export function mapDispatchToProps(dispatch: Dispatch): Object {
return { ...bindActionCreators(actions, dispatch) }; return {
...bindActionCreators(
{
...search,
...conversations,
...user,
...theme,
...sections,
},
dispatch
),
};
} }

@ -75,6 +75,7 @@ export type ConversationType = {
isBlocked: boolean; isBlocked: boolean;
isKickedFromGroup: boolean; isKickedFromGroup: boolean;
leftGroup: boolean; leftGroup: boolean;
avatarPath?: string; // absolute filepath to the avatar
}; };
export type ConversationLookupType = { export type ConversationLookupType = {
[key: string]: ConversationType; [key: string]: ConversationType;

@ -2,7 +2,6 @@ import { omit, reject } from 'lodash';
import { normalize } from '../../types/PhoneNumber'; import { normalize } from '../../types/PhoneNumber';
import { AdvancedSearchOptions, SearchOptions } from '../../types/Search'; import { AdvancedSearchOptions, SearchOptions } from '../../types/Search';
import { trigger } from '../../shims/events';
import { getMessageModel } from '../../shims/Whisper'; import { getMessageModel } from '../../shims/Whisper';
import { cleanSearchTerm } from '../../util/cleanSearchTerm'; import { cleanSearchTerm } from '../../util/cleanSearchTerm';
import { searchConversations, searchMessages } from '../../../js/modules/data'; import { searchConversations, searchMessages } from '../../../js/modules/data';

@ -2,12 +2,17 @@ import { SectionType } from '../../components/session/ActionsPanel';
export const FOCUS_SECTION = 'FOCUS_SECTION'; export const FOCUS_SECTION = 'FOCUS_SECTION';
const focusSection = (section: SectionType) => { type FocusSectionActionType = {
type: 'FOCUS_SECTION';
payload: SectionType;
};
function focusSection(section: SectionType): FocusSectionActionType {
return { return {
type: FOCUS_SECTION, type: FOCUS_SECTION,
payload: section, payload: section,
}; };
}; }
export const actions = { export const actions = {
focusSection, focusSection,

@ -29,3 +29,7 @@ export const reducer = (
return state; return state;
} }
}; };
export const actions = {
applyTheme,
};

@ -30,6 +30,12 @@ export const getSelectedConversation = createSelector(
} }
); );
export const getOurPrimaryConversation = createSelector(
getConversations,
(state: ConversationsStateType): ConversationType =>
state.conversationLookup[window.storage.get('primaryDevicePubKey')]
);
function getConversationTitle( function getConversationTitle(
conversation: ConversationType, conversation: ConversationType,
options: { i18n: LocalizerType; ourRegionCode: string } options: { i18n: LocalizerType; ourRegionCode: string }

@ -1,5 +1,4 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { mapDispatchToProps } from '../actions';
import { LeftPane } from '../../components/LeftPane'; import { LeftPane } from '../../components/LeftPane';
import { StateType } from '../reducer'; import { StateType } from '../reducer';
@ -10,7 +9,11 @@ import {
getRegionCode, getRegionCode,
getUserNumber, getUserNumber,
} from '../selectors/user'; } from '../selectors/user';
import { getLeftPaneLists } from '../selectors/conversations'; import {
getLeftPaneLists,
getOurPrimaryConversation,
} from '../selectors/conversations';
import { mapDispatchToProps } from '../actions';
// Workaround: A react component's required properties are filtering up through connect() // Workaround: A react component's required properties are filtering up through connect()
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363 // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
@ -23,6 +26,7 @@ const mapStateToProps = (state: StateType) => {
const searchResults = showSearch ? getSearchResults(state) : undefined; const searchResults = showSearch ? getSearchResults(state) : undefined;
return { return {
...lists, ...lists,
ourPrimaryConversation: getOurPrimaryConversation(state), // used in actionPanel
searchTerm: getQuery(state), searchTerm: getQuery(state),
regionCode: getRegionCode(state), regionCode: getRegionCode(state),
ourNumber: getUserNumber(state), ourNumber: getUserNumber(state),

Loading…
Cancel
Save