From 3268d3e6eb7a64b98a1a663bc1f8c3f007028273 Mon Sep 17 00:00:00 2001 From: Jamie Kyle <113370520+jamiebuilds-signal@users.noreply.github.com> Date: Thu, 10 Aug 2023 15:16:51 -0700 Subject: [PATCH] Update call tab design based on feedback --- _locales/en/messages.json | 2 +- stylesheets/_mixins.scss | 1 + stylesheets/_modules.scss | 21 ++- stylesheets/_variables.scss | 1 + stylesheets/components/CallsTab.scss | 18 +- stylesheets/components/ContextMenu.scss | 1 - .../components/ConversationDetails.scss | 11 +- stylesheets/components/ConversationPanel.scss | 1 + stylesheets/components/GroupDescription.scss | 1 + stylesheets/components/NavSidebar.scss | 5 + stylesheets/components/NavTabs.scss | 40 ++++- stylesheets/components/SearchInput.scss | 2 + stylesheets/components/Stories.scss | 1 - .../components/StoriesSettingsModal.scss | 100 +++++++++-- stylesheets/components/StoryListItem.scss | 21 ++- ts/components/Avatar.tsx | 1 + ts/components/CallsList.tsx | 75 +++++--- ts/components/CallsNewCall.tsx | 78 ++++---- ts/components/CallsTab.tsx | 9 +- ts/components/LeftPane.tsx | 169 +++++++++--------- ts/components/NavTabs.tsx | 137 ++++++++------ ts/components/SendStoryModal.tsx | 21 ++- ts/components/StoriesSettingsModal.tsx | 22 +-- ts/components/StoryCreator.tsx | 5 +- ts/components/Tooltip.tsx | 53 +++++- .../ConversationDetails.tsx | 5 +- ts/sql/Server.ts | 7 +- ts/state/ducks/callHistory.ts | 26 ++- ts/state/selectors/message.ts | 5 + ts/state/smart/StoriesSettingsModal.tsx | 4 +- ts/state/smart/StoryCreator.tsx | 3 +- ts/util/lint/exceptions.json | 44 +++-- 32 files changed, 601 insertions(+), 289 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index f7c2a71b076..e2bb9a58c66 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -6684,7 +6684,7 @@ "description": "Calls Tab > Calls List > Search Input > Placeholder" }, "icu:CallsList__ToggleFilterByMissedLabel": { - "messageformat": "Toggle filter by missed", + "messageformat": "Filter by missed", "description": "Calls Tab > Calls List > Toggle search filter by missed > Accessibility label" }, "icu:CallsList__ToggleFilterByMissed__RoleDescription": { diff --git a/stylesheets/_mixins.scss b/stylesheets/_mixins.scss index 8d3525c94bd..60e8fa5c1b8 100644 --- a/stylesheets/_mixins.scss +++ b/stylesheets/_mixins.scss @@ -868,6 +868,7 @@ $rtl-icon-map: ( } @mixin NavTabs__Scroller { + padding-bottom: 8px; @include scrollbar; &::-webkit-scrollbar-thumb { @include light-theme { diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index b40a4b5a9f1..a826e710389 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -6974,7 +6974,8 @@ button.module-image__border-overlay:focus { content: ''; display: block; height: 0; - margin-inline-start: -6px; + /* stylelint-disable-next-line liberty/use-logical-spec */ + margin-left: -6px; margin-top: -6px; position: absolute; width: 0; @@ -6993,12 +6994,15 @@ button.module-image__border-overlay:focus { &[data-placement='right'] { .module-tooltip-arrow { - inset-inline-start: 0; + /* stylelint-disable-next-line liberty/use-logical-spec */ + left: 0; } .module-tooltip-arrow::after { - inset-inline-start: -6px; - border-inline-end-color: var(--tooltip-background-color); + /* stylelint-disable-next-line liberty/use-logical-spec */ + left: -6px; + /* stylelint-disable-next-line liberty/use-logical-spec */ + border-right-color: var(--tooltip-background-color); } } @@ -7015,12 +7019,15 @@ button.module-image__border-overlay:focus { &[data-placement='left'] { .module-tooltip-arrow { - inset-inline-end: 0; + /* stylelint-disable-next-line liberty/use-logical-spec */ + right: 0; } .module-tooltip-arrow::after { - inset-inline-end: -12px; - border-inline-start-color: var(--tooltip-background-color); + /* stylelint-disable-next-line liberty/use-logical-spec */ + right: -12px; + /* stylelint-disable-next-line liberty/use-logical-spec */ + border-left-color: var(--tooltip-background-color); } } } diff --git a/stylesheets/_variables.scss b/stylesheets/_variables.scss index 79ad8f81c6c..254beff441d 100644 --- a/stylesheets/_variables.scss +++ b/stylesheets/_variables.scss @@ -286,4 +286,5 @@ $z-index-above-context-menu: 126; $NavTabs__width: 80px; // These values are 'block' specific to coordinate with the NavSidebar__Header $NavTabs__Item__blockPadding: 2px; +$NavTabs__Toggle__blockPadding: 8px; $NavTabs__ItemButton__blockPadding: 10px; diff --git a/stylesheets/components/CallsTab.scss b/stylesheets/components/CallsTab.scss index a1d69c57629..a302b2868d2 100644 --- a/stylesheets/components/CallsTab.scss +++ b/stylesheets/components/CallsTab.scss @@ -52,6 +52,8 @@ width: 100%; height: 100%; padding-block: 80px; + padding-inline: 24px; + user-select: none; } .CallsTab__ClearCallHistoryIcon { @@ -183,8 +185,8 @@ .CallsList__LoadingAvatar { display: block; - width: 32px; - height: 32px; + width: 36px; + height: 36px; border-radius: 9999px; } @@ -206,10 +208,20 @@ font-weight: bold; } -.CallsList__ItemCallInfo--missed { +.CallsList__ItemCallInfo { + @include font-body-1; +} + +// Override .ListTile__subtitle so ellipsis is correct color +.CallsList__Item--missed .ListTile__subtitle { color: $color-accent-red; } +// Override .ListTile +.ListTile.CallsList__ItemTile { + padding-block: 12px; +} + .CallsList__Item--selected .CallsList__ItemTile { @include light-theme { background-color: $color-gray-15; diff --git a/stylesheets/components/ContextMenu.scss b/stylesheets/components/ContextMenu.scss index d88882d36ee..dfb60db83a9 100644 --- a/stylesheets/components/ContextMenu.scss +++ b/stylesheets/components/ContextMenu.scss @@ -99,7 +99,6 @@ &__popper--single-item &__option { padding-block: 12px; - padding-inline: 6px; } &__divider { diff --git a/stylesheets/components/ConversationDetails.scss b/stylesheets/components/ConversationDetails.scss index aa6365793e3..35f862cf68c 100644 --- a/stylesheets/components/ConversationDetails.scss +++ b/stylesheets/components/ConversationDetails.scss @@ -30,6 +30,7 @@ justify-content: center; padding-bottom: 8px; padding-top: 12px; + user-select: text; } &__subtitle { @@ -41,6 +42,11 @@ @include dark-theme { color: $color-gray-25; } + + &__about, + &__phone-number { + user-select: text; + } } &__root--editable &__title { @@ -513,11 +519,6 @@ } } -.ConversationDetails__CallHistoryGroup__header { - @include font-title-2; - margin-block: 24px 16px; -} - .ConversationDetails__CallHistoryGroup__List { list-style: none; margin: 0; diff --git a/stylesheets/components/ConversationPanel.scss b/stylesheets/components/ConversationPanel.scss index 006df9ccd8f..0621ea41455 100644 --- a/stylesheets/components/ConversationPanel.scss +++ b/stylesheets/components/ConversationPanel.scss @@ -20,6 +20,7 @@ &__body { margin-top: calc(#{$header-height} + var(--title-bar-drag-area-height)); + padding-inline: 24px; } &__header { diff --git a/stylesheets/components/GroupDescription.scss b/stylesheets/components/GroupDescription.scss index c4833d0b73e..d7494e8d7e0 100644 --- a/stylesheets/components/GroupDescription.scss +++ b/stylesheets/components/GroupDescription.scss @@ -7,6 +7,7 @@ -webkit-line-clamp: 2; display: -webkit-box; overflow: hidden; + user-select: text; a { @include light-theme { diff --git a/stylesheets/components/NavSidebar.scss b/stylesheets/components/NavSidebar.scss index 4462258ce92..c70139dc235 100644 --- a/stylesheets/components/NavSidebar.scss +++ b/stylesheets/components/NavSidebar.scss @@ -125,6 +125,11 @@ @include sr-only; } +.NavSidebar .module-SearchInput__container { + // override .module-SearchInput__container + margin: 0; +} + .NavSidebar__Content { flex: 1 1 0%; min-height: 0; diff --git a/stylesheets/components/NavTabs.scss b/stylesheets/components/NavTabs.scss index 1bc74923540..c64efd7428b 100644 --- a/stylesheets/components/NavTabs.scss +++ b/stylesheets/components/NavTabs.scss @@ -1,6 +1,10 @@ // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +$NavTabs__ToggleButton__blockPadding: 4px; +$NavTabs__ItemIcon__size: 20px; +$NavTabs__ProfileAvatar__size: 28px; + // This effectively wraps the entire app .NavTabs__Container { position: relative; @@ -14,6 +18,7 @@ display: flex; flex-shrink: 0; flex-direction: column; + align-items: center; width: $NavTabs__width; height: 100%; padding-top: var(--title-bar-drag-area-height); @@ -43,6 +48,12 @@ // Handled by .NavTabs__ItemButton outline: none; } + &.NavTabs__Toggle { + padding-block: calc( + $NavTabs__Item__blockPadding + $NavTabs__ItemButton__blockPadding - + $NavTabs__ToggleButton__blockPadding + ); + } } .NavTabs__ItemButton { @@ -50,7 +61,7 @@ display: flex; align-items: center; justify-content: center; - padding: $NavTabs__ItemButton__blockPadding; + padding-block: $NavTabs__ItemButton__blockPadding; border-radius: 8px; .NavTabs__Item:hover &, .NavTabs__Item:focus-visible & { @@ -73,6 +84,21 @@ background: $color-gray-62; } } + + .NavTabs__Toggle & { + width: fit-content; + padding: $NavTabs__ToggleButton__blockPadding; + margin-block: 0; + margin-inline: auto; + border-radius: 4px; + } + .NavTabs__Item--Profile & { + // Normalize for the size difference of the avatar vs sidebar icons + padding-block: calc( + $NavTabs__ItemButton__blockPadding - + (($NavTabs__ProfileAvatar__size - $NavTabs__ItemIcon__size) / 2) + ); + } } .NavTabs__ItemContent { @@ -107,8 +133,8 @@ .NavTabs__ItemIcon { display: block; - width: 20px; - height: 20px; + width: $NavTabs__ItemIcon__size; + height: $NavTabs__ItemIcon__size; } @mixin NavTabs__Icon($icon) { @@ -157,10 +183,18 @@ } .NavTabs__TabList { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; flex: 1; } .NavTabs__Misc { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; padding-bottom: 8px; } diff --git a/stylesheets/components/SearchInput.scss b/stylesheets/components/SearchInput.scss index 13a482bc786..07e9bb45bd2 100644 --- a/stylesheets/components/SearchInput.scss +++ b/stylesheets/components/SearchInput.scss @@ -5,6 +5,8 @@ &__container { position: relative; flex: 1 0 0; + margin-inline: 16px; + margin-bottom: 8px; } &__icon { diff --git a/stylesheets/components/Stories.scss b/stylesheets/components/Stories.scss index b649f4861d8..a72335d335b 100644 --- a/stylesheets/components/Stories.scss +++ b/stylesheets/components/Stories.scss @@ -83,7 +83,6 @@ flex-direction: column; flex: 1; overflow-y: overlay; - padding-block: 0; padding-inline: 16px; &--empty { diff --git a/stylesheets/components/StoriesSettingsModal.scss b/stylesheets/components/StoriesSettingsModal.scss index 66b9c52e533..8218c489afb 100644 --- a/stylesheets/components/StoriesSettingsModal.scss +++ b/stylesheets/components/StoriesSettingsModal.scss @@ -45,7 +45,13 @@ &::before { @include rounded-corners; background: inherit; - border: 1.5px solid $color-gray-60; + border: 1.5px solid; + @include light-theme { + border-color: $color-gray-25; + } + @include dark-theme { + border-color: $color-gray-60; + } content: ''; display: block; height: 20px; @@ -80,7 +86,12 @@ &__viewers { display: flex; @include font-body-2; - color: $color-gray-25; + @include light-theme { + color: $color-gray-60; + } + @include dark-theme { + color: $color-gray-25; + } } &__left { @@ -96,13 +107,23 @@ align-items: center; width: 32px; height: 32px; - background: $color-gray-75; + @include light-theme { + background: $color-gray-15; + } + @include dark-theme { + background: $color-gray-75; + } &::after { - @include color-svg($svg, $color-white); content: ''; height: 20px; width: 20px; + @include light-theme { + @include color-svg($svg, $color-black); + } + @include dark-theme { + @include color-svg($svg, $color-white); + } } } @@ -126,10 +147,22 @@ &__delete { @include button-reset; - @include color-svg('../images/icons/v3/trash/trash.svg', $color-gray-25); height: 20px; width: 20px; visibility: hidden; + + @include light-theme { + @include color-svg( + '../images/icons/v3/trash/trash.svg', + $color-gray-45 + ); + } + @include dark-theme { + @include color-svg( + '../images/icons/v3/trash/trash.svg', + $color-gray-25 + ); + } } &:hover &__delete { @@ -139,8 +172,14 @@ &__divider { width: 100%; - border: 0 solid $color-gray-65; + border: 0 solid; border-top-width: 1px; + @include light-theme { + border-color: $color-gray-15; + } + @include dark-theme { + border-color: $color-gray-65; + } } &__input__container { @@ -150,8 +189,13 @@ &__visibility { @include font-subtitle; - color: $color-gray-25; margin-top: 10px; + @include light-theme { + color: $color-gray-60; + } + @include dark-theme { + color: $color-gray-25; + } } &__title { @@ -161,9 +205,14 @@ &__description { @include font-subtitle; - color: $color-gray-25; margin-top: 0px; margin-bottom: 16px; + @include light-theme { + color: $color-gray-60; + } + @include dark-theme { + color: $color-gray-25; + } } &__listHeader { @@ -215,7 +264,12 @@ } &__checkbox-description { - color: $color-gray-25; + @include light-theme { + color: $color-gray-60; + } + @include dark-theme { + color: $color-gray-25; + } } &__conversation-list { @@ -226,11 +280,21 @@ &__disclaimer { @include font-subtitle; - color: $color-gray-25; + @include light-theme { + color: $color-gray-60; + } + @include dark-theme { + color: $color-gray-25; + } &__learn-more { @include button-reset; - color: $color-gray-05; + @include light-theme { + color: $color-gray-90; + } + @include dark-theme { + color: $color-gray-05; + } } } @@ -242,8 +306,13 @@ &__stories-off-text { flex: 1; - color: $color-gray-25; @include font-subtitle; + @include light-theme { + color: $color-gray-60; + } + @include dark-theme { + color: $color-gray-25; + } } } @@ -271,7 +340,12 @@ &__members_help { @include font-body-2; - color: $color-gray-25; + @include light-theme { + color: $color-gray-60; + } + @include dark-theme { + color: $color-gray-25; + } } &__remove_group { diff --git a/stylesheets/components/StoryListItem.scss b/stylesheets/components/StoryListItem.scss index f06e4dfdb03..f1902e3d261 100644 --- a/stylesheets/components/StoryListItem.scss +++ b/stylesheets/components/StoryListItem.scss @@ -167,16 +167,16 @@ } } - &__icon { - @mixin StoryListItem__Icon($path) { - @include light-theme { - @include color-svg($path, $color-black); - } - @include dark-theme { - @include color-svg($path, $color-white); - } + @mixin StoryListItem__Icon($path) { + @include light-theme { + @include color-svg($path, $color-black); } + @include dark-theme { + @include color-svg($path, $color-white); + } + } + &__icon { &--chat { @include StoryListItem__Icon('../images/icons/v3/open/open-compact.svg'); } @@ -203,9 +203,8 @@ margin-inline-start: 2px; vertical-align: middle; width: 16px; - @include color-svg( - '../images/icons/v3/chevron/chevron-right-compact-bold.svg', - $color-white + @include StoryListItem__Icon( + '../images/icons/v3/chevron/chevron-right-compact-bold.svg' ); } } diff --git a/ts/components/Avatar.tsx b/ts/components/Avatar.tsx index 8493e906f8b..e4b23022dde 100644 --- a/ts/components/Avatar.tsx +++ b/ts/components/Avatar.tsx @@ -38,6 +38,7 @@ export enum AvatarSize { TWENTY = 20, TWENTY_EIGHT = 28, THIRTY_TWO = 32, + THIRTY_SIX = 36, FORTY_EIGHT = 48, FIFTY_TWO = 52, EIGHTY = 80, diff --git a/ts/components/CallsList.tsx b/ts/components/CallsList.tsx index 563bfadea69..7f764097beb 100644 --- a/ts/components/CallsList.tsx +++ b/ts/components/CallsList.tsx @@ -40,6 +40,9 @@ import { Intl } from './Intl'; import { NavSidebarSearchHeader } from './NavSidebar'; import { SizeObserver } from '../hooks/useSizeObserver'; import { formatCallHistoryGroup } from '../util/callDisposition'; +import { CallsNewCallButton } from './CallsNewCall'; +import { Tooltip, TooltipPlacement } from './Tooltip'; +import { Theme } from '../util/theme'; function Timestamp({ i18n, @@ -100,6 +103,7 @@ const defaultPendingState: SearchState = { }; type CallsListProps = Readonly<{ + hasActiveCall: boolean; getCallHistoryGroupsCount: ( options: CallHistoryFilterOptions ) => Promise; @@ -110,6 +114,8 @@ type CallsListProps = Readonly<{ getConversation: (id: string) => ConversationType | void; i18n: LocalizerType; selectedCallHistoryGroup: CallHistoryGroup | null; + onOutgoingAudioCallInConversation: (conversationId: string) => void; + onOutgoingVideoCallInConversation: (conversationId: string) => void; onSelectCallHistoryGroup: ( conversationId: string, selectedCallHistoryGroup: CallHistoryGroup @@ -117,15 +123,18 @@ type CallsListProps = Readonly<{ }>; function rowHeight() { - return ListTile.heightCompact; + return ListTile.heightFull; } export function CallsList({ + hasActiveCall, getCallHistoryGroupsCount, getCallHistoryGroups, getConversation, i18n, selectedCallHistoryGroup, + onOutgoingAudioCallInConversation, + onOutgoingVideoCallInConversation, onSelectCallHistoryGroup, }: CallsListProps): JSX.Element { const infiniteLoaderRef = useRef(null); @@ -270,6 +279,7 @@ export function CallsList({ title={ } + subtitleMaxLines={1} subtitle={ } @@ -306,6 +316,7 @@ export function CallsList({ style={style} className={classNames('CallsList__Item', { 'CallsList__Item--selected': isSelected, + 'CallsList__Item--missed': wasMissed, })} > } trailing={ - { + if (item.type === CallType.Audio) { + onOutgoingAudioCallInConversation(conversation.id); + } else { + onOutgoingVideoCallInConversation(conversation.id); + } + }} /> } title={ @@ -341,12 +357,9 @@ export function CallsList({ } + subtitleMaxLines={1} subtitle={ - + {item.children.length > 1 ? `(${item.children.length}) ` : ''} {statusText} ·{' '} @@ -360,10 +373,13 @@ export function CallsList({ ); }, [ + hasActiveCall, searchState, getConversation, selectedCallHistoryGroup, onSelectCallHistoryGroup, + onOutgoingAudioCallInConversation, + onOutgoingVideoCallInConversation, i18n, ] ); @@ -402,21 +418,28 @@ export function CallsList({ onClear={handleSearchInputClear} value={queryInput} /> - + + {hasEmptyResults && ( diff --git a/ts/components/CallsNewCall.tsx b/ts/components/CallsNewCall.tsx index ec3dfc6e29b..1c29aa9ea22 100644 --- a/ts/components/CallsNewCall.tsx +++ b/ts/components/CallsNewCall.tsx @@ -16,11 +16,11 @@ import { strictAssert } from '../util/assert'; import { UserText } from './UserText'; import { Avatar, AvatarSize } from './Avatar'; import { Intl } from './Intl'; -import type { ActiveCallStateType } from '../state/ducks/calling'; import { SizeObserver } from '../hooks/useSizeObserver'; +import { CallType } from '../types/CallDisposition'; type CallsNewCallProps = Readonly<{ - activeCall: ActiveCallStateType | undefined; + hasActiveCall: boolean; allConversations: ReadonlyArray; i18n: LocalizerType; onSelectConversation: (conversationId: string) => void; @@ -33,8 +33,39 @@ type Row = | { kind: 'header'; title: string } | { kind: 'conversation'; conversation: ConversationType }; +export function CallsNewCallButton({ + callType, + hasActiveCall, + onClick, +}: { + callType: CallType; + hasActiveCall: boolean; + onClick: () => void; +}): JSX.Element { + return ( + + ); +} + export function CallsNewCall({ - activeCall, + hasActiveCall, allConversations, i18n, onSelectConversation, @@ -146,8 +177,6 @@ export function CallsNewCall({ ); } - const callButtonsDisabled = activeCall != null; - return (
{item.conversation.type === 'direct' && ( - + /> )} - + />
} onClick={() => { @@ -207,7 +225,7 @@ export function CallsNewCall({ [ rows, i18n, - activeCall, + hasActiveCall, onSelectConversation, onOutgoingAudioCallInConversation, onOutgoingVideoCallInConversation, diff --git a/ts/components/CallsTab.tsx b/ts/components/CallsTab.tsx index 4aeb011e8f8..3621e0388bd 100644 --- a/ts/components/CallsTab.tsx +++ b/ts/components/CallsTab.tsx @@ -198,18 +198,25 @@ export function CallsTab({ {sidebarView === CallsTabSidebarView.CallsListView && ( )} {sidebarView === CallsTabSidebarView.NewCallView && ( (null); + const measureSize = useSizeObserver(measureRef); + + const widthBreakpoint = getNavSidebarWidthBreakpoint( + measureSize?.width ?? preferredWidthFromStorage + ); const commonDialogProps = { i18n, @@ -548,7 +552,7 @@ export function LeftPane({ navTabsCollapsed={navTabsCollapsed} onToggleNavTabsCollapse={toggleNavTabsCollapse} preferredLeftPaneWidth={preferredWidthFromStorage} - requiresFullWidth={false} + requiresFullWidth={modeSpecificProps.mode !== LeftPaneMode.Inbox} savePreferredLeftPaneWidth={savePreferredLeftPaneWidth} actions={ <> @@ -603,91 +607,88 @@ export function LeftPane({ showChooseGroupMembers, })} - - {helper.getSearchInput({ - clearConversationSearch, - clearSearch, - i18n, - onChangeComposeSearchTerm: event => { - setComposeSearchTerm(event.target.value); - }, - updateSearchTerm, - showConversation, - })} - + {(widthBreakpoint === WidthBreakpoint.Wide || + modeSpecificProps.mode !== LeftPaneMode.Inbox) && ( + + {helper.getSearchInput({ + clearConversationSearch, + clearSearch, + i18n, + onChangeComposeSearchTerm: event => { + setComposeSearchTerm(event.target.value); + }, + updateSearchTerm, + showConversation, + })} + + )}
{dialogs.map(({ key, dialog }) => ( {dialog} ))}
{preRowsNode && {preRowsNode}} - - {(ref, size) => ( -
-
-
- { - switch (disabledReason) { - case undefined: - toggleConversationInChooseMembers(conversationId); - break; - case ContactCheckboxDisabledReason.AlreadyAdded: - case ContactCheckboxDisabledReason.MaximumContactsSelected: - // These are no-ops. - break; - default: - throw missingCaseError(disabledReason); - } - }} - showUserNotFoundModal={showUserNotFoundModal} - setIsFetchingUUID={setIsFetchingUUID} - lookupConversationWithoutServiceId={ - lookupConversationWithoutServiceId - } - showConversation={showConversation} - blockConversation={blockConversation} - onSelectConversation={onSelectConversation} - onOutgoingAudioCallInConversation={ - onOutgoingAudioCallInConversation - } - onOutgoingVideoCallInConversation={ - onOutgoingVideoCallInConversation - } - removeConversation={ - isContactManagementEnabled - ? removeConversation - : undefined - } - renderMessageSearchResult={renderMessageSearchResult} - rowCount={helper.getRowCount()} - scrollBehavior={scrollBehavior} - scrollToRowIndex={rowIndexToScrollTo} - scrollable={isScrollable} - shouldRecomputeRowHeights={shouldRecomputeRowHeights} - showChooseGroupMembers={showChooseGroupMembers} - theme={theme} - /> -
-
+
+
+
+ { + switch (disabledReason) { + case undefined: + toggleConversationInChooseMembers(conversationId); + break; + case ContactCheckboxDisabledReason.AlreadyAdded: + case ContactCheckboxDisabledReason.MaximumContactsSelected: + // These are no-ops. + break; + default: + throw missingCaseError(disabledReason); + } + }} + showUserNotFoundModal={showUserNotFoundModal} + setIsFetchingUUID={setIsFetchingUUID} + lookupConversationWithoutServiceId={ + lookupConversationWithoutServiceId + } + showConversation={showConversation} + blockConversation={blockConversation} + onSelectConversation={onSelectConversation} + onOutgoingAudioCallInConversation={ + onOutgoingAudioCallInConversation + } + onOutgoingVideoCallInConversation={ + onOutgoingVideoCallInConversation + } + removeConversation={ + isContactManagementEnabled ? removeConversation : undefined + } + renderMessageSearchResult={renderMessageSearchResult} + rowCount={helper.getRowCount()} + scrollBehavior={scrollBehavior} + scrollToRowIndex={rowIndexToScrollTo} + scrollable={isScrollable} + shouldRecomputeRowHeights={shouldRecomputeRowHeights} + showChooseGroupMembers={showChooseGroupMembers} + theme={theme} + />
- )} - +
+
{footerContents && (
{footerContents}
)} diff --git a/ts/components/NavTabs.tsx b/ts/components/NavTabs.tsx index 022b01a8257..06089dee99f 100644 --- a/ts/components/NavTabs.tsx +++ b/ts/components/NavTabs.tsx @@ -15,27 +15,38 @@ import { AvatarPopup } from './AvatarPopup'; import { handleOutsideClick } from '../util/handleOutsideClick'; import type { UnreadStats } from '../state/selectors/conversations'; import { NavTab } from '../state/ducks/nav'; +import { Tooltip, TooltipPlacement } from './Tooltip'; +import { Theme } from '../util/theme'; type NavTabProps = Readonly<{ + i18n: LocalizerType; badge?: ReactNode; iconClassName: string; id: NavTab; label: string; }>; -function NavTabsItem({ badge, iconClassName, id, label }: NavTabProps) { +function NavTabsItem({ i18n, badge, iconClassName, id, label }: NavTabProps) { + const isRTL = i18n.getLocaleDirection() === 'rtl'; return ( {label} - - - - {badge && {badge}} + + + + + {badge && {badge}} + - + ); } @@ -59,23 +70,30 @@ export function NavTabsToggle({ function handleToggle() { onToggleNavTabsCollapse(!navTabsCollapsed); } + const label = navTabsCollapsed + ? i18n('icu:NavTabsToggle__showTabs') + : i18n('icu:NavTabsToggle__hideTabs'); + const isRTL = i18n.getLocaleDirection() === 'rtl'; return ( ); } @@ -127,6 +145,8 @@ export function NavTabs({ onNavTabSelected(key as NavTab); } + const isRTL = i18n.getLocaleDirection() === 'rtl'; + const [targetElement, setTargetElement] = useState(null); const [popperElement, setPopperElement] = useState(null); const [portalElement, setPortalElement] = useState(null); @@ -202,6 +222,7 @@ export function NavTabs({ onSelectionChange={handleSelectionChange} > {storiesEnabled && ( - - - - {i18n('icu:NavTabs__ItemLabel--Settings')} + + + + + {i18n('icu:NavTabs__ItemLabel--Settings')} + - + {showAvatarPopup && portalElement != null && diff --git a/ts/components/SendStoryModal.tsx b/ts/components/SendStoryModal.tsx index 73a12eb16c5..b89ca1b0b0c 100644 --- a/ts/components/SendStoryModal.tsx +++ b/ts/components/SendStoryModal.tsx @@ -10,6 +10,7 @@ import { filterAndSortConversationsByRecent } from '../util/filterAndSortConvers import type { ConversationType } from '../state/ducks/conversations'; import type { ConversationWithStoriesType } from '../state/selectors/conversations'; import type { LocalizerType } from '../types/Util'; +import { ThemeType } from '../types/Util'; import type { PreferredBadgeSelectorType } from '../state/selectors/badges'; import type { PropsType as StoriesSettingsModalPropsType } from './StoriesSettingsModal'; import { @@ -34,7 +35,6 @@ import { MY_STORY_ID, getStoryDistributionListName } from '../types/Stories'; import type { RenderModalPage, ModalPropsType } from './Modal'; import { PagedModal, ModalPage } from './Modal'; import { StoryDistributionListName } from './StoryDistributionListName'; -import { Theme } from '../util/theme'; import { isNotNil } from '../util/isNotNil'; import { StoryImage } from './StoryImage'; import type { AttachmentType } from '../types/Attachment'; @@ -42,6 +42,7 @@ import { useConfirmDiscard } from '../hooks/useConfirmDiscard'; import { getStoryBackground } from '../util/getStoryBackground'; import { makeObjectUrl, revokeObjectUrl } from '../types/VisualAttachment'; import { UserText } from './UserText'; +import { Theme } from '../util/theme'; export type PropsType = { draftAttachment: AttachmentType; @@ -70,6 +71,7 @@ export type PropsType = { conversationIds: Array ) => unknown; signalConnections: Array; + theme: ThemeType; toggleGroupsForStorySend: (cids: Array) => Promise; mostRecentActiveStoryTimestampByGroupOrDistributionList: Record< string, @@ -141,6 +143,7 @@ export function SendStoryModal({ onViewersUpdated, setMyStoriesToAllSignalConnections, signalConnections, + theme, toggleGroupsForStorySend, mostRecentActiveStoryTimestampByGroupOrDistributionList, toggleSignalConnectionsModal, @@ -402,6 +405,7 @@ export function SendStoryModal({ setPage={setPage} setSelectedContacts={setSelectedContacts} toggleSignalConnectionsModal={toggleSignalConnectionsModal} + theme={theme} onBackButtonClick={() => confirmDiscardIf(selectedContacts.length > 0, () => setListIdToEdit(undefined) @@ -485,6 +489,7 @@ export function SendStoryModal({ } selectedContacts={selectedContacts} setSelectedContacts={setSelectedContacts} + theme={theme} /> ); } else if (page === Page.ChooseGroups) { @@ -700,7 +705,7 @@ export function SendStoryModal({ placement: 'bottom', strategy: 'absolute', }} - theme={Theme.Dark} + theme={theme === ThemeType.dark ? Theme.Dark : Theme.Light} >
{callHistoryGroup && ( - -

- {formatDate(i18n, callHistoryGroup.timestamp)} -

+
    {callHistoryGroup.children.map(child => { return ( diff --git a/ts/sql/Server.ts b/ts/sql/Server.ts index ce788472b9f..778d2dda14c 100644 --- a/ts/sql/Server.ts +++ b/ts/sql/Server.ts @@ -157,6 +157,7 @@ import { callHistoryGroupSchema, CallHistoryFilterStatus, callHistoryDetailsSchema, + CallDirection, } from '../types/CallDisposition'; type ConversationRow = Readonly<{ @@ -3347,6 +3348,7 @@ async function getCallHistory( const MISSED = sqlConstant(DirectCallStatus.Missed); const DELETED = sqlConstant(DirectCallStatus.Deleted); +const INCOMING = sqlConstant(CallDirection.Incoming); const FOUR_HOURS_IN_MS = sqlConstant(4 * 60 * 60 * 1000); function getCallHistoryGroupDataSync( @@ -3405,7 +3407,10 @@ function getCallHistoryGroupDataSync( const filterClause = status === CallHistoryFilterStatus.All ? sqlFragment`status IS NOT ${DELETED}` - : sqlFragment`status IS ${MISSED} AND status IS NOT ${DELETED}`; + : sqlFragment` + direction IS ${INCOMING} AND + status IS ${MISSED} AND status IS NOT ${DELETED} + `; const offsetLimit = limit > 0 ? sqlFragment`LIMIT ${limit} OFFSET ${offset}` : sqlFragment``; diff --git a/ts/state/ducks/callHistory.ts b/ts/state/ducks/callHistory.ts index 223747a4757..cf904a737ae 100644 --- a/ts/state/ducks/callHistory.ts +++ b/ts/state/ducks/callHistory.ts @@ -11,6 +11,8 @@ import type { ToastActionType } from './toast'; import { showToast } from './toast'; import { ToastType } from '../../types/Toast'; import type { CallHistoryDetails } from '../../types/CallDisposition'; +import * as log from '../../logging/log'; +import * as Errors from '../../types/errors'; export type CallHistoryState = ReadonlyDeep<{ // This informs the app that underlying call history data has changed. @@ -19,19 +21,19 @@ export type CallHistoryState = ReadonlyDeep<{ }>; const CALL_HISTORY_CACHE = 'callHistory/CACHE'; -const CALL_HISTORY_CLEAR = 'callHistory/CLEAR'; +const CALL_HISTORY_RESET = 'callHistory/RESET'; export type CallHistoryCache = ReadonlyDeep<{ type: typeof CALL_HISTORY_CACHE; payload: CallHistoryDetails; }>; -export type CallHistoryClear = ReadonlyDeep<{ - type: typeof CALL_HISTORY_CLEAR; +export type CallHistoryReset = ReadonlyDeep<{ + type: typeof CALL_HISTORY_RESET; }>; export type CallHistoryAction = ReadonlyDeep< - CallHistoryCache | CallHistoryClear + CallHistoryCache | CallHistoryReset >; export function getEmptyState(): CallHistoryState { @@ -52,12 +54,18 @@ function clearAllCallHistory(): ThunkAction< void, RootStateType, unknown, - CallHistoryClear | ToastActionType + CallHistoryReset | ToastActionType > { return async dispatch => { - await clearCallHistoryDataAndSync(); - dispatch({ type: CALL_HISTORY_CLEAR }); - dispatch(showToast({ toastType: ToastType.CallHistoryCleared })); + try { + await clearCallHistoryDataAndSync(); + dispatch(showToast({ toastType: ToastType.CallHistoryCleared })); + } catch (error) { + log.error('Error clearing call history', Errors.toLogFormat(error)); + } finally { + // Just force a reset, even if the clear failed. + dispatch({ type: CALL_HISTORY_RESET }); + } }; } @@ -75,7 +83,7 @@ export function reducer( action: CallHistoryAction ): CallHistoryState { switch (action.type) { - case CALL_HISTORY_CLEAR: + case CALL_HISTORY_RESET: return { ...state, edition: state.edition + 1, callHistoryByCallId: {} }; case CALL_HISTORY_CACHE: return { diff --git a/ts/state/selectors/message.ts b/ts/state/selectors/message.ts index 726f85be861..66b04e6ee07 100644 --- a/ts/state/selectors/message.ts +++ b/ts/state/selectors/message.ts @@ -1328,6 +1328,11 @@ export function getPropsForCallHistory( }: GetPropsForCallHistoryOptions ): CallingNotificationType { const { callId } = message; + if (callId == null && 'callHistoryDetails' in message) { + log.error( + 'getPropsForCallHistory: Found callHistoryDetails, but no callId' + ); + } strictAssert(callId != null, 'getPropsForCallHistory: Missing callId'); const callHistory = callHistorySelector(callId); strictAssert( diff --git a/ts/state/smart/StoriesSettingsModal.tsx b/ts/state/smart/StoriesSettingsModal.tsx index c5b2c19841b..783b8992be5 100644 --- a/ts/state/smart/StoriesSettingsModal.tsx +++ b/ts/state/smart/StoriesSettingsModal.tsx @@ -15,7 +15,7 @@ import { getMe, } from '../selectors/conversations'; import { getDistributionListsWithMembers } from '../selectors/storyDistributionLists'; -import { getIntl } from '../selectors/user'; +import { getIntl, getTheme } from '../selectors/user'; import { getPreferredBadgeSelector } from '../selectors/badges'; import { getHasStoryViewReceiptSetting } from '../selectors/items'; import { useGlobalModalActions } from '../ducks/globalModals'; @@ -49,6 +49,7 @@ export function SmartStoriesSettingsModal(): JSX.Element | null { const groupStories = useSelector(getGroupStories); const getConversationByUuid = useSelector(getConversationByUuidSelector); + const theme = useSelector(getTheme); return ( diff --git a/ts/state/smart/StoryCreator.tsx b/ts/state/smart/StoryCreator.tsx index 9239edbc5e0..08caa52e8e8 100644 --- a/ts/state/smart/StoryCreator.tsx +++ b/ts/state/smart/StoryCreator.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import type { LocalizerType } from '../../types/Util'; +import { ThemeType, type LocalizerType } from '../../types/Util'; import type { StateType } from '../reducer'; import { LinkPreviewSourceType } from '../../types/LinkPreview'; import { SmartCompositionTextArea } from './CompositionTextArea'; @@ -140,6 +140,7 @@ export function SmartStoryCreator(): JSX.Element | null { setMyStoriesToAllSignalConnections={setMyStoriesToAllSignalConnections} signalConnections={signalConnections} skinTone={skinTone} + theme={ThemeType.dark} toggleGroupsForStorySend={toggleGroupsForStorySend} toggleSignalConnectionsModal={toggleSignalConnectionsModal} /> diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index a9f7431b23a..9a6f2db3bca 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -2334,6 +2334,14 @@ "reasonCategory": "usageTrusted", "updated": "2021-07-30T16:57:33.618Z" }, + { + "rule": "React-useRef", + "path": "ts/components/LeftPane.tsx", + "line": " const measureRef = useRef(null);", + "reasonCategory": "usageTrusted", + "updated": "2023-08-09T21:48:42.602Z", + "reasonDetail": "" + }, { "rule": "React-useRef", "path": "ts/components/LeftPaneSearchInput.tsx", @@ -2395,7 +2403,7 @@ "rule": "React-useRef", "path": "ts/components/Modal.tsx", "line": " const bodyRef = useRef(null);", - "reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", + "reasonCategory": "usageTrusted", "updated": "2023-07-25T21:55:26.191Z", "reasonDetail": "" }, @@ -2403,7 +2411,7 @@ "rule": "React-useRef", "path": "ts/components/Modal.tsx", "line": " const bodyInnerRef = useRef(null);", - "reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", + "reasonCategory": "usageTrusted", "updated": "2023-07-25T21:55:26.191Z", "reasonDetail": "" }, @@ -2556,7 +2564,7 @@ "rule": "React-useRef", "path": "ts/components/TextAttachment.tsx", "line": " const ref = useRef(null);", - "reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", + "reasonCategory": "usageTrusted", "updated": "2023-07-25T21:55:26.191Z", "reasonDetail": "" }, @@ -2588,6 +2596,14 @@ "reasonCategory": "usageTrusted", "updated": "2021-07-30T16:57:33.618Z" }, + { + "rule": "React-useRef", + "path": "ts/components/Tooltip.tsx", + "line": " const timeoutRef = useRef();", + "reasonCategory": "usageTrusted", + "updated": "2023-08-10T00:23:35.320Z", + "reasonDetail": "" + }, { "rule": "React-createRef", "path": "ts/components/conversation/ConversationHeader.tsx", @@ -2806,7 +2822,7 @@ "rule": "React-useRef", "path": "ts/hooks/useSizeObserver.tsx", "line": " const sizeRef = useRef(null);", - "reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", + "reasonCategory": "usageTrusted", "updated": "2023-07-25T21:55:26.191Z", "reasonDetail": "" }, @@ -2814,7 +2830,7 @@ "rule": "React-useRef", "path": "ts/hooks/useSizeObserver.tsx", "line": " const onSizeChangeRef = useRef(onSizeChange);", - "reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", + "reasonCategory": "usageTrusted", "updated": "2023-07-25T21:55:26.191Z", "reasonDetail": "" }, @@ -2822,7 +2838,7 @@ "rule": "React-useRef", "path": "ts/hooks/useSizeObserver.tsx", "line": " const ref = useRef();", - "reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", + "reasonCategory": "usageTrusted", "updated": "2023-07-25T21:55:26.191Z", "reasonDetail": "" }, @@ -2830,7 +2846,7 @@ "rule": "React-useRef", "path": "ts/hooks/useSizeObserver.tsx", "line": " * const scrollerRef = useRef()", - "reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", + "reasonCategory": "usageTrusted", "updated": "2023-07-25T21:55:26.191Z", "reasonDetail": "" }, @@ -2838,7 +2854,7 @@ "rule": "React-useRef", "path": "ts/hooks/useSizeObserver.tsx", "line": " * const scrollerInnerRef = useRef()", - "reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", + "reasonCategory": "usageTrusted", "updated": "2023-07-25T21:55:26.191Z", "reasonDetail": "" }, @@ -2846,7 +2862,7 @@ "rule": "React-useRef", "path": "ts/hooks/useSizeObserver.tsx", "line": " const scrollRef = useRef(null);", - "reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", + "reasonCategory": "usageTrusted", "updated": "2023-07-25T21:55:26.191Z", "reasonDetail": "" }, @@ -2854,7 +2870,7 @@ "rule": "React-useRef", "path": "ts/hooks/useSizeObserver.tsx", "line": " const onScrollChangeRef = useRef(onScrollChange);", - "reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", + "reasonCategory": "usageTrusted", "updated": "2023-07-25T21:55:26.191Z", "reasonDetail": "" }, @@ -2862,7 +2878,7 @@ "rule": "React-useRef", "path": "ts/quill/formatting/menu.tsx", "line": " const fadeOutTimerRef = React.useRef();", - "reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", + "reasonCategory": "usageTrusted", "updated": "2023-08-02T19:01:24.771Z", "reasonDetail": "We need a persistent timer to know when to remove after fade-out" }, @@ -2870,7 +2886,7 @@ "rule": "React-useRef", "path": "ts/quill/formatting/menu.tsx", "line": " const hoverTimerRef = React.useRef();", - "reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", + "reasonCategory": "usageTrusted", "updated": "2023-08-02T19:01:24.771Z", "reasonDetail": "We need a persistent timer to track long-hovers" }, @@ -2930,7 +2946,7 @@ "rule": "React-useRef", "path": "ts/components/CallsList.tsx", "line": " const infiniteLoaderRef = useRef(null);", - "reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", + "reasonCategory": "usageTrusted", "updated": "2023-08-02T00:21:37.858Z", "reasonDetail": "" }, @@ -2938,7 +2954,7 @@ "rule": "React-useRef", "path": "ts/components/CallsList.tsx", "line": " const listRef = useRef(null);", - "reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted", + "reasonCategory": "usageTrusted", "updated": "2023-08-02T00:21:37.858Z", "reasonDetail": "" }