diff --git a/src/components/modals/webApp/MoreAppsTabContent.module.scss b/src/components/modals/webApp/MoreAppsTabContent.module.scss index 05e8cefeb..d6d1267d9 100644 --- a/src/components/modals/webApp/MoreAppsTabContent.module.scss +++ b/src/components/modals/webApp/MoreAppsTabContent.module.scss @@ -32,7 +32,7 @@ justify-content: center; align-items: center; gap: 0.125rem; - grid-template-columns: repeat(5, 1fr); + grid-template-columns: repeat(auto-fit, minmax(4rem, 1fr)); padding: 0.125rem; padding-bottom: 1.25em; } diff --git a/src/components/modals/webApp/WebAppModal.module.scss b/src/components/modals/webApp/WebAppModal.module.scss index 0f5d13ade..4cfa68d3b 100644 --- a/src/components/modals/webApp/WebAppModal.module.scss +++ b/src/components/modals/webApp/WebAppModal.module.scss @@ -210,7 +210,6 @@ display: flex; align-items: center; height: 100%; - flex-grow: 1; overflow-x: auto; overflow-y: hidden; @@ -233,6 +232,12 @@ } } +.tool-bar { + flex-grow: 1; + display: flex; + justify-content: flex-end; +} + .tab-button-wrapper { display: flex; margin-left: -0.5rem; @@ -362,8 +367,12 @@ height: 1.75rem !important; } +.more-apps-button { + margin-right: auto; +} + .fullscreenButton { - margin-right: 0.5rem; + margin-inline: 0.5rem; } .tab-close-button { diff --git a/src/components/modals/webApp/WebAppModal.tsx b/src/components/modals/webApp/WebAppModal.tsx index 976762d86..c922336a4 100644 --- a/src/components/modals/webApp/WebAppModal.tsx +++ b/src/components/modals/webApp/WebAppModal.tsx @@ -9,7 +9,7 @@ import { getActions, getGlobal, withGlobal } from '../../../global'; import type { ApiAttachBot, ApiChat, ApiUser } from '../../../api/types'; import type { TabState } from '../../../global/types'; -import type { ThemeKey } from '../../../types'; +import type { Point, Size, ThemeKey } from '../../../types'; import type { WebApp, WebAppOutboundEvent } from '../../../types/webapp'; import { RESIZE_HANDLE_CLASS_NAME } from '../../../config'; @@ -61,6 +61,8 @@ type StateProps = { bot?: ApiUser; attachBot?: ApiAttachBot; theme?: ThemeKey; + cachedSize?: Size; + cachedPosition?: Point; }; const PROLONG_INTERVAL = 45000; // 45s @@ -76,6 +78,8 @@ const WebAppModal: FC = ({ bot, attachBot, theme, + cachedSize, + cachedPosition, }) => { const { closeActiveWebApp, @@ -88,11 +92,11 @@ const WebAppModal: FC = ({ updateWebApp, openMoreAppsTab, closeMoreAppsTab, + updateMiniAppCachedPosition, + updateMiniAppCachedSize, } = getActions(); - const [getMaximizedStateSize, setMaximizedStateSize] = useSignal( - { width: DEFAULT_MAXIMIZED_STATE_SIZE.width, height: DEFAULT_MAXIMIZED_STATE_SIZE.height }, - ); + const [getMaximizedStateSize, setMaximizedStateSize] = useSignal(cachedSize || DEFAULT_MAXIMIZED_STATE_SIZE); function getSize() { if (modal?.modalState === 'fullScreen') return windowSize.get(); @@ -173,6 +177,7 @@ const WebAppModal: FC = ({ isResizing, style: draggableStyle, size, + position, } = useDraggable( ref, headerRef, @@ -180,8 +185,21 @@ const WebAppModal: FC = ({ getSize(), isFullScreen, getMinimumSize(), + cachedPosition, ); + const x = position?.x; + const y = position?.y; + useEffect(() => { + if (!isDragging && x !== undefined && y !== undefined) { + updateMiniAppCachedPosition({ position: { x, y } }); + } + }, [isDragging, x, y]); + + useEffect(() => { + if (!isDragging && size && isMaximizedState) { updateMiniAppCachedSize({ size }); } + }, [isDragging, isMaximizedState, size]); + const currentSize = size || getSize(); const currentWidth = currentSize.width; @@ -544,7 +562,6 @@ const WebAppModal: FC = ({ ) ))} {isMoreAppsTabActive && renderMoreAppsTab()} - {!isMoreAppsTabActive && renderMoreAppsButton()} ); } @@ -585,32 +602,38 @@ const WebAppModal: FC = ({ {renderTabs()} {renderMoreMenu()} - +
+ {!isMoreAppsTabActive && renderMoreAppsButton()} - )} - round - color="translucent" - size="tiny" - onClick={handleCollapseClick} - > - - + + +
); } @@ -705,12 +728,16 @@ export default memo(withGlobal( const bot = activeBotId ? selectUser(global, activeBotId) : undefined; const chat = selectCurrentChat(global); const theme = selectTheme(global); + const cachedPosition = global.settings.miniAppsCachedPosition; + const cachedSize = global.settings.miniAppsCachedSize; return { attachBot, bot, chat, theme, + cachedPosition, + cachedSize, }; }, )(WebAppModal)); diff --git a/src/components/modals/webApp/WebAppModalTabContent.module.scss b/src/components/modals/webApp/WebAppModalTabContent.module.scss index 6b4dea72d..e9d3075c8 100644 --- a/src/components/modals/webApp/WebAppModalTabContent.module.scss +++ b/src/components/modals/webApp/WebAppModalTabContent.module.scss @@ -3,6 +3,11 @@ .root { height: 100%; width: 100%; + position: relative; + display: flex; + flex-direction: column; + padding: 0; + z-index: 0; } .multi-tab { @@ -84,13 +89,21 @@ .secondary-button, .main-button { + --color-transition: 0.15s; + --transform-transition: 0.25s ease-in-out; + flex-grow: 1; margin: 0.5rem; transform: translateY(100%); opacity: 0; - transition-property: background-color, color, transform, margin-inline, flex-grow, opacity, filter; - transition-duration: 0.25s; - transition-timing-function: ease-in-out; + transition: + background-color var(--color-transition), + color var(--color-transition), + opacity var(--color-transition), + filter var(--color-transition), + transform var(--transform-transition), + margin-inline var(--transform-transition), + flex-grow var(--transform-transition); &.visible { transform: translateY(0); @@ -127,10 +140,18 @@ &.one-row { align-items: center; height: 4rem; + + @media (max-width: 600px) { + height: 3.5rem; + } } &.two-rows { height: 7.75rem; + + @media (max-width: 600px) { + height: 6.9375rem; + } } &.left-to-right { diff --git a/src/components/modals/webApp/hooks/useWebAppFrame.ts b/src/components/modals/webApp/hooks/useWebAppFrame.ts index c17fe2d43..ed4304397 100644 --- a/src/components/modals/webApp/hooks/useWebAppFrame.ts +++ b/src/components/modals/webApp/hooks/useWebAppFrame.ts @@ -32,7 +32,7 @@ const SCROLLBAR_STYLE = `* { }`; const RELOAD_TIMEOUT = 500; -const SAFE_AREA_HEIGHT = 3.675 * REM; +const FULLSCREEN_BUTTONS_AREA_HEIGHT = 3.675 * REM; const useWebAppFrame = ( ref: React.RefObject, @@ -129,15 +129,13 @@ const useWebAppFrame = ( if (!ref.current) { return; } - const { height } = ref.current.getBoundingClientRect(); - const safeAreaHeight = isFullscreen ? SAFE_AREA_HEIGHT : 0; sendEvent({ eventType: 'safe_area_changed', eventData: { left: 0, right: 0, top: 0, - bottom: height - safeAreaHeight, + bottom: 0, }, }); @@ -146,7 +144,7 @@ const useWebAppFrame = ( eventData: { left: 0, right: 0, - top: safeAreaHeight, + top: isFullscreen ? FULLSCREEN_BUTTONS_AREA_HEIGHT : 0, bottom: 0, }, }); @@ -328,7 +326,6 @@ const useWebAppFrame = ( && lastFrameSizeRef.current.height === height && !lastFrameSizeRef.current.isResizing) return; lastFrameSizeRef.current = { width, height, isResizing }; sendViewport(isResizing); - sendSafeArea(); }, [sendViewport, sendSafeArea, windowSize]); useEffect(() => { diff --git a/src/global/actions/ui/bots.ts b/src/global/actions/ui/bots.ts index dc2acfa93..b3717bb4f 100644 --- a/src/global/actions/ui/bots.ts +++ b/src/global/actions/ui/bots.ts @@ -117,6 +117,34 @@ addActionHandler('changeWebAppModalState', (global, actions, payload): ActionRet return replaceWebAppModalState(global, state, tabId); }); +addActionHandler('updateMiniAppCachedPosition', (global, actions, payload): ActionReturnType => { + const { position } = payload; + + global = { + ...global, + settings: { + ...global.settings, + miniAppsCachedPosition: position, + }, + }; + + return global; +}); + +addActionHandler('updateMiniAppCachedSize', (global, actions, payload): ActionReturnType => { + const { size } = payload; + + global = { + ...global, + settings: { + ...global.settings, + miniAppsCachedSize: size, + }, + }; + + return global; +}); + addActionHandler('setWebAppPaymentSlug', (global, actions, payload): ActionReturnType => { const { tabId = getCurrentTabId() } = payload; const activeWebApp = selectActiveWebApp(global, tabId); diff --git a/src/global/cache.ts b/src/global/cache.ts index 49a793c19..8fffbc248 100644 --- a/src/global/cache.ts +++ b/src/global/cache.ts @@ -632,7 +632,7 @@ function omitLocalMedia(message: ApiMessage): ApiMessage { function reduceSettings(global: T): GlobalState['settings'] { const { - byKey, themes, performance, botVerificationShownPeerIds, + byKey, themes, performance, botVerificationShownPeerIds, miniAppsCachedPosition, miniAppsCachedSize, } = global.settings; return { @@ -642,6 +642,8 @@ function reduceSettings(global: T): GlobalState['settings privacy: {}, notifyExceptions: {}, botVerificationShownPeerIds, + miniAppsCachedPosition, + miniAppsCachedSize, }; } diff --git a/src/global/types/actions.ts b/src/global/types/actions.ts index 1f7447932..cd593a273 100644 --- a/src/global/types/actions.ts +++ b/src/global/types/actions.ts @@ -73,10 +73,12 @@ import type { NewChatMembersProgress, PaymentStep, PerformanceType, + Point, ProfileTabType, ScrollTargetPosition, SettingsScreens, SharedMediaType, + Size, StarGiftInfo, StarsTransactionType, StoryViewerOrigin, @@ -2040,7 +2042,12 @@ export interface ActionPayloads { changeWebAppModalState: { state: WebAppModalStateType; } & WithTabId; - + updateMiniAppCachedPosition: { + position: Point; + }; + updateMiniAppCachedSize: { + size: Size; + }; // Misc refreshLangPackFromCache: { langCode: string; diff --git a/src/global/types/globalState.ts b/src/global/types/globalState.ts index 2d27ae81c..6b8dfce89 100644 --- a/src/global/types/globalState.ts +++ b/src/global/types/globalState.ts @@ -55,7 +55,9 @@ import type { IThemeSettings, NotifyException, PerformanceType, + Point, ServiceNotification, + Size, StarGiftCategory, StarsSubscriptions, StarsTransactionHistory, @@ -404,6 +406,8 @@ export type GlobalState = { paidReactionPrivacy?: boolean; languages?: ApiLanguage[]; botVerificationShownPeerIds: string[]; + miniAppsCachedPosition?: Point; + miniAppsCachedSize?: Size; }; push?: { diff --git a/src/hooks/useDraggable.ts b/src/hooks/useDraggable.ts index 000650d50..7cac2072b 100644 --- a/src/hooks/useDraggable.ts +++ b/src/hooks/useDraggable.ts @@ -3,17 +3,14 @@ import { useEffect, useSignal, useState, } from '../lib/teact/teact'; +import type { Point, Size } from '../types'; + import { RESIZE_HANDLE_SELECTOR } from '../config'; import buildStyle from '../util/buildStyle'; import { captureEvents } from '../util/captureEvents'; import useFlag from './useFlag'; import useLastCallback from './useLastCallback'; -export interface Size { - width: number; - height: number; -} - export enum ResizeHandleType { Top, Bottom, @@ -41,11 +38,6 @@ const resizeHandleSelectorsMap: Record(undefined); + const [elementCurrentPosition, setElementCurrentPosition] = useState(cachedPosition); const [elementCurrentSize, setElementCurrentSize] = useState(undefined); const [getElementPositionOnStartTransform, setElementPositionOnStartTransform] = useSignal({ x: 0, y: 0 }); @@ -211,7 +204,7 @@ export default function useDraggable( const adjustPositionWithinBounds = useLastCallback(() => { if (isFullscreen) return; - const position = !wasElementShown ? getCenteredPosition() : elementCurrentPosition; + const position = !wasElementShown && !cachedPosition ? getCenteredPosition() : elementCurrentPosition; if (!elementCurrentSize || !position) return; const newPosition = ensurePositionInVisibleArea(position.x, position.y); updateCurrentPosition(newPosition); diff --git a/src/types/index.ts b/src/types/index.ts index 1778757db..cba7be714 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -629,6 +629,16 @@ export type ConfettiParams = OptionalCombine<{ height?: number; }>; +export interface Size { + width: number; + height: number; +} + +export interface Point { + x: number; + y: number; +} + export type WebPageMediaSize = 'large' | 'small'; export type StarGiftCategory = number | 'all' | 'limited' | 'stock';