diff --git a/_locales/en/messages.json b/_locales/en/messages.json index fe19c3d27..471ffe52c 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1619,5 +1619,11 @@ "example": "Bob" } } + }, + "blockUser": { + "message": "Block user" + }, + "unblockUser": { + "message": "Unblock user" } } diff --git a/js/models/blockedNumbers.js b/js/models/blockedNumbers.js index 7e1f79914..b31d24e58 100644 --- a/js/models/blockedNumbers.js +++ b/js/models/blockedNumbers.js @@ -86,7 +86,7 @@ return m.get('number'); }, getNumber(number) { - return this.model.find(m => m.number === number); + return this.models.find(m => m.number === number); }, }); diff --git a/js/models/conversations.js b/js/models/conversations.js index 47be7e3e2..d15fea527 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -1,6 +1,6 @@ /* global _: false */ /* global Backbone: false */ -/* global libphonenumber: false */ +/* global BlockedNumberController: false */ /* global ConversationController: false */ /* global libsignal: false */ @@ -147,7 +147,17 @@ isMe() { return this.id === this.ourNumber; }, - + isBlocked() { + return BlockedNumberController.isBlocked(this.id); + }, + block() { + BlockedNumberController.block(this.id); + this.trigger('change'); + }, + unblock() { + BlockedNumberController.unblock(this.id); + this.trigger('change'); + }, async cleanup() { await window.Signal.Types.Conversation.deleteExternalFiles( this.attributes, @@ -280,6 +290,7 @@ unreadCount: this.get('unreadCount') || 0, isSelected: this.isSelected, showFriendRequestIndicator: this.pendingFriendRequest, + isBlocked: this.isBlocked(), lastMessage: { status: this.lastMessageStatus, text: this.lastMessage, diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 35099e78b..6dd0cdca6 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -7,6 +7,7 @@ /* global Signal: false */ /* global storage: false */ /* global Whisper: false */ +/* global BlockNumberConversation: false */ // eslint-disable-next-line func-names (function() { @@ -170,6 +171,7 @@ isVerified: this.model.isVerified(), isKeysPending: this.model.isKeyExchangeCompleted() === false, isMe: this.model.isMe(), + isBlocked: this.model.isBlocked(), isGroup: !this.model.isPrivate(), expirationSettingName, showBackButton: Boolean(this.panels && this.panels.length), @@ -200,6 +202,13 @@ this.resetPanel(); this.updateHeader(); }, + + onBlockUser: () => { + this.model.block(); + }, + onUnblockUser: () => { + this.model.unblock(); + }, }; }; this.titleView = new Whisper.ReactWrapperView({ diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index 5430952bd..d4c1dfe56 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -1831,6 +1831,11 @@ border-left: 4px solid $color-conversation-indigo; } +.module-conversation-list-item--is-blocked { + padding-left: 12px; + border-left: 4px solid $color-conversation-red; +} + .module-conversation-list-item--is-selected { background-color: $color-gray-05; } diff --git a/stylesheets/_theme_dark.scss b/stylesheets/_theme_dark.scss index 190c077a9..87dc3f85e 100644 --- a/stylesheets/_theme_dark.scss +++ b/stylesheets/_theme_dark.scss @@ -1269,6 +1269,10 @@ body.dark-theme { border-left: 4px solid $color-conversation-indigo; } + .module-conversation-list-item--is-blocked { + border-left: 4px solid $color-conversation-red; + } + .module-conversation-list-item--is-selected { background-color: $color-dark-70; } diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx index a2e9cf644..574b626d6 100644 --- a/ts/components/ConversationListItem.tsx +++ b/ts/components/ConversationListItem.tsx @@ -24,6 +24,7 @@ interface Props { text: string; }; showFriendRequestIndicator?: boolean; + isBlocked: boolean; i18n: Localizer; onClick?: () => void; @@ -157,7 +158,7 @@ export class ConversationListItem extends React.Component { } public render() { - const { unreadCount, onClick, isSelected, showFriendRequestIndicator } = this.props; + const { unreadCount, onClick, isSelected, showFriendRequestIndicator, isBlocked } = this.props; return (
{ 'module-conversation-list-item', unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null, isSelected ? 'module-conversation-list-item--is-selected' : null, - showFriendRequestIndicator ? 'module-conversation-list-item--has-friend-request' : null + showFriendRequestIndicator ? 'module-conversation-list-item--has-friend-request' : null, + isBlocked ? 'module-conversation-list-item--is-blocked' : null, )} > {this.renderAvatar()} diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 705973309..49a99a6ed 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -30,6 +30,7 @@ interface Props { color: string; avatarPath?: string; + isBlocked: boolean; isMe: boolean; isGroup: boolean; expirationSettingName?: string; @@ -44,6 +45,9 @@ interface Props { onShowAllMedia: () => void; onShowGroupMembers: () => void; onGoBack: () => void; + + onBlockUser: () => void; + onUnblockUser: () => void; } export class ConversationHeader extends React.Component { @@ -182,6 +186,7 @@ export class ConversationHeader extends React.Component { public renderMenu(triggerId: string) { const { i18n, + isBlocked, isMe, isGroup, onDeleteMessages, @@ -191,10 +196,15 @@ export class ConversationHeader extends React.Component { onShowGroupMembers, onShowSafetyNumber, timerOptions, + onBlockUser, + onUnblockUser, } = this.props; const disappearingTitle = i18n('disappearingMessages') as any; + const blockTitle = isBlocked ? i18n('unblockUser') : i18n('blockUser'); + const blockHandler = isBlocked ? onUnblockUser : onBlockUser; + return ( @@ -223,6 +233,10 @@ export class ConversationHeader extends React.Component { {!isGroup ? ( {i18n('resetSession')} ) : null} + {/* Only show the block on other conversations */} + {!isMe ? ( + {blockTitle} + ) : null} {i18n('deleteMessages')} );