Stories: Follow-up (#3758)
This commit is contained in:
parent
1de3f4811e
commit
a5e29bd32e
@ -16,7 +16,7 @@ import {
|
||||
} from '../../global/helpers';
|
||||
import { formatRelativeTime } from '../../util/dateFormat';
|
||||
import { getServerTime } from '../../util/serverTime';
|
||||
import { selectChat, selectTabState } from '../../global/selectors';
|
||||
import { selectChat, selectIsCurrentUserPremium, selectTabState } from '../../global/selectors';
|
||||
import captureKeyboardListeners from '../../util/captureKeyboardListeners';
|
||||
|
||||
import useAppLayout, { getIsMobile } from '../../hooks/useAppLayout';
|
||||
@ -44,6 +44,7 @@ import MenuItem from '../ui/MenuItem';
|
||||
import DropdownMenu from '../ui/DropdownMenu';
|
||||
import Skeleton from '../ui/Skeleton';
|
||||
import StoryCaption from './StoryCaption';
|
||||
import AvatarList from '../common/AvatarList';
|
||||
|
||||
import styles from './StoryViewer.module.scss';
|
||||
|
||||
@ -75,13 +76,14 @@ interface StateProps {
|
||||
viewersExpirePeriod: number;
|
||||
isChatExist?: boolean;
|
||||
areChatSettingsLoaded?: boolean;
|
||||
isCurrentUserPremium?: boolean;
|
||||
}
|
||||
|
||||
const VIDEO_MIN_READY_STATE = 4;
|
||||
const SPACEBAR_CODE = 32;
|
||||
|
||||
const PRIMARY_VIDEO_MIME = 'video/mp4; codecs="hvc1"';
|
||||
const SECONDARY_VIDEO_MIME = 'video/mp4; codecs="avc1.64001E"';
|
||||
const PRIMARY_VIDEO_MIME = 'video/mp4; codecs=hvc1.1.6.L63.00';
|
||||
const SECONDARY_VIDEO_MIME = 'video/mp4; codecs=avc1.64001E';
|
||||
|
||||
function Story({
|
||||
isSelf,
|
||||
@ -101,6 +103,7 @@ function Story({
|
||||
isChatExist,
|
||||
areChatSettingsLoaded,
|
||||
getIsAnimating,
|
||||
isCurrentUserPremium,
|
||||
onDelete,
|
||||
onClose,
|
||||
onReport,
|
||||
@ -148,7 +151,13 @@ function Story({
|
||||
true,
|
||||
);
|
||||
const areViewsExpired = Boolean(
|
||||
isSelf && isLoadedStory && (story!.date + viewersExpirePeriod) < getServerTime(),
|
||||
isSelf && !isCurrentUserPremium && isLoadedStory && (story!.date + viewersExpirePeriod) < getServerTime(),
|
||||
);
|
||||
const canCopyLink = Boolean(
|
||||
isLoadedStory
|
||||
&& story.isPublic
|
||||
&& userId !== storyChangelogUserId
|
||||
&& user?.usernames?.length,
|
||||
);
|
||||
const canShare = Boolean(
|
||||
isLoadedStory
|
||||
@ -275,11 +284,10 @@ function Story({
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSelf || isDeletedStory || areViewsExpired) return;
|
||||
if (story && 'recentViewerIds' in story && story.recentViewerIds?.length) return;
|
||||
|
||||
// Refresh recent viewers list on new stories each view
|
||||
// Refresh recent viewers list each time
|
||||
loadStorySeenBy({ storyId });
|
||||
}, [isDeletedStory, areViewsExpired, isSelf, story, storyId]);
|
||||
}, [isDeletedStory, areViewsExpired, isSelf, storyId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@ -582,7 +590,7 @@ function Story({
|
||||
onOpen={handlePauseStory}
|
||||
onClose={handlePlayStory}
|
||||
>
|
||||
<MenuItem icon="copy" onClick={handleCopyStoryLink}>{lang('CopyLink')}</MenuItem>
|
||||
{canCopyLink && <MenuItem icon="copy" onClick={handleCopyStoryLink}>{lang('CopyLink')}</MenuItem>}
|
||||
{canPinToProfile && (
|
||||
<MenuItem icon="save-story" onClick={handlePinClick}>{lang('StorySave')}</MenuItem>
|
||||
)}
|
||||
@ -597,11 +605,17 @@ function Story({
|
||||
);
|
||||
}
|
||||
|
||||
function renderRecentViewers() {
|
||||
// No need for expensive global updates on chats and users, so we avoid them
|
||||
const recentViewers = useMemo(() => {
|
||||
const { users: { byId: usersById } } = getGlobal();
|
||||
|
||||
const { recentViewerIds, viewsCount } = story as ApiStory;
|
||||
const recentViewerIds = story && 'recentViewerIds' in story ? story.recentViewerIds : undefined;
|
||||
if (!recentViewerIds) return undefined;
|
||||
|
||||
return recentViewerIds.map((id) => usersById[id]).filter(Boolean);
|
||||
}, [story]);
|
||||
|
||||
function renderRecentViewers() {
|
||||
const { viewsCount } = story as ApiStory;
|
||||
|
||||
if (!viewsCount) {
|
||||
return (
|
||||
@ -620,14 +634,12 @@ function Story({
|
||||
)}
|
||||
onClick={handleOpenStorySeenBy}
|
||||
>
|
||||
{!areViewsExpired && recentViewerIds?.map((viewerId) => (
|
||||
<Avatar
|
||||
key={`viewer-${viewerId}`}
|
||||
{!areViewsExpired && Boolean(recentViewers?.length) && (
|
||||
<AvatarList
|
||||
size="small"
|
||||
peer={usersById[viewerId]}
|
||||
className={styles.recentViewer}
|
||||
peers={recentViewers}
|
||||
/>
|
||||
))}
|
||||
)}
|
||||
|
||||
<span className={styles.recentViewersCount}>{lang('Views', viewsCount, 'i')}</span>
|
||||
</div>
|
||||
@ -780,6 +792,7 @@ export default memo(withGlobal<OwnProps>((global, {
|
||||
orderedIds: isArchivedStories ? archiveIds : (isPrivateStories ? pinnedIds : orderedIds),
|
||||
isMuted,
|
||||
isSelf: currentUserId === userId,
|
||||
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||
shouldForcePause,
|
||||
storyChangelogUserId: appConfig!.storyChangelogUserId,
|
||||
viewersExpirePeriod: appConfig!.storyExpirePeriod + appConfig!.storyViewersExpirePeriod,
|
||||
|
||||
@ -42,6 +42,10 @@ function StoryToggler({
|
||||
const lang = useLang();
|
||||
|
||||
const users = useMemo(() => {
|
||||
if (orderedUserIds.length === 1) {
|
||||
return [usersById[orderedUserIds[0]]];
|
||||
}
|
||||
|
||||
return orderedUserIds
|
||||
.map((id) => usersById[id])
|
||||
.filter((user) => user && user.id !== currentUserId)
|
||||
|
||||
@ -646,17 +646,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.recentViewer {
|
||||
z-index: 3;
|
||||
}
|
||||
.recentViewer + .recentViewer {
|
||||
margin-left: -0.5rem;
|
||||
z-index: 2;
|
||||
}
|
||||
.recentViewer + .recentViewer + .recentViewer {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.recentViewersCount {
|
||||
margin-inline-start: 0.5rem;
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import { disableDirectTextInput, enableDirectTextInput } from '../../util/direct
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
import { dispatchPriorityPlaybackEvent } from '../../hooks/usePriorityPlaybackCheck';
|
||||
|
||||
import ShowTransition from '../ui/ShowTransition';
|
||||
import Button from '../ui/Button';
|
||||
@ -57,8 +58,10 @@ function StoryViewer({
|
||||
}
|
||||
|
||||
disableDirectTextInput();
|
||||
const stopPriorityPlayback = dispatchPriorityPlaybackEvent();
|
||||
|
||||
return () => {
|
||||
stopPriorityPlayback();
|
||||
enableDirectTextInput();
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
import React, { memo, useEffect, useMemo } from '../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../global';
|
||||
|
||||
import { selectStorySeenBy, selectTabState, selectUserStory } from '../../global/selectors';
|
||||
import {
|
||||
selectIsCurrentUserPremium,
|
||||
selectStorySeenBy,
|
||||
selectTabState,
|
||||
selectUserStory,
|
||||
} from '../../global/selectors';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { formatDateAtTime } from '../../util/dateFormat';
|
||||
import { getServerTime } from '../../util/serverTime';
|
||||
@ -26,6 +31,7 @@ interface StateProps {
|
||||
viewsCount?: number;
|
||||
seenByDates?: Record<string, number>;
|
||||
viewersExpirePeriod: number;
|
||||
isCurrentUserPremium?: boolean;
|
||||
}
|
||||
const CLOSE_ANIMATION_DURATION = 100;
|
||||
|
||||
@ -35,6 +41,7 @@ function StoryViewers({
|
||||
viewsCount,
|
||||
viewersExpirePeriod,
|
||||
seenByDates,
|
||||
isCurrentUserPremium,
|
||||
}: StateProps) {
|
||||
const {
|
||||
loadStorySeenBy, openChat, closeStorySeenBy, closeStoryViewer,
|
||||
@ -43,7 +50,7 @@ function StoryViewers({
|
||||
const lang = useLang();
|
||||
|
||||
const isOpen = Boolean(storyId);
|
||||
const isExpired = Boolean(storyDate) && (storyDate + viewersExpirePeriod) < getServerTime();
|
||||
const isExpired = !isCurrentUserPremium && Boolean(storyDate) && (storyDate + viewersExpirePeriod) < getServerTime();
|
||||
const renderingSeenByDates = useCurrentOrPrev(seenByDates, true);
|
||||
const renderingIsExpired = usePrevious(isExpired) || isExpired;
|
||||
const renderingViewsCount = useCurrentOrPrev(viewsCount, true);
|
||||
@ -58,7 +65,7 @@ function StoryViewers({
|
||||
|
||||
return result;
|
||||
}, [renderingIsExpired, renderingSeenByDates]);
|
||||
const isLoading = !renderingIsExpired && (!memberIds || memberIds.length === 0);
|
||||
const isLoading = !isCurrentUserPremium && !renderingIsExpired && (!memberIds || memberIds.length === 0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!storyId || seenByDates || renderingIsExpired) {
|
||||
@ -99,6 +106,11 @@ function StoryViewers({
|
||||
{renderText(lang('ExpiredViewsStub'), ['simple_markdown', 'emoji'])}
|
||||
</div>
|
||||
)}
|
||||
{isCurrentUserPremium && Boolean(!memberIds?.length) && (
|
||||
<div className={styles.expiredText}>
|
||||
{lang('ServerErrorViewers')}
|
||||
</div>
|
||||
)}
|
||||
{memberIds?.map((userId) => (
|
||||
<ListItem
|
||||
key={userId}
|
||||
@ -142,5 +154,6 @@ export default memo(withGlobal((global) => {
|
||||
viewersExpirePeriod: appConfig!.storyExpirePeriod + appConfig!.storyViewersExpirePeriod,
|
||||
storyDate,
|
||||
viewsCount,
|
||||
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||
};
|
||||
})(StoryViewers));
|
||||
|
||||
@ -134,7 +134,7 @@ const SearchInput: FC<OwnProps> = ({
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
<Transition
|
||||
name="zoomFade"
|
||||
name="fade"
|
||||
shouldCleanup
|
||||
activeKey={Number(isLoading)}
|
||||
className="icon-container"
|
||||
|
||||
@ -42,8 +42,9 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
case 'updateUser': {
|
||||
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
||||
if (update.id === global.currentUserId && update.user.isPremium !== selectIsCurrentUserPremium(global)) {
|
||||
// TODO Do not display modal if premium is bought from another device
|
||||
if (update.user.isPremium) actions.openPremiumModal({ isSuccess: true, tabId });
|
||||
if (update.user.isPremium && global.byTabId[tabId].premiumModal) {
|
||||
actions.openPremiumModal({ isSuccess: true, tabId });
|
||||
}
|
||||
|
||||
// Reset translation cache cause premium provides additional formatting
|
||||
global = {
|
||||
|
||||
@ -356,7 +356,10 @@ function updateOrderedStoriesUserIds<T extends GlobalState>(global: T, updateUse
|
||||
}, { active: [], archived: [] });
|
||||
|
||||
function sort(userId: string) {
|
||||
return currentUserId === userId ? Infinity : byUserId[userId].lastUpdatedAt;
|
||||
const PREMIUM_PRIORITY = 1e12;
|
||||
const isPremium = selectUser(global, userId)?.isPremium;
|
||||
const lastUpdated = byUserId[userId].lastUpdatedAt || 0;
|
||||
return currentUserId === userId ? Infinity : (lastUpdated + (isPremium ? PREMIUM_PRIORITY : 0));
|
||||
}
|
||||
|
||||
newOrderedUserIds.archived = orderBy(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user