Redesign the download table, finish download dialog

This commit is contained in:
2026-03-19 10:01:39 +03:00
Unverified
parent 7f69e38e71
commit da76f9df74
12 changed files with 350 additions and 136 deletions
@@ -1,113 +0,0 @@
import type { ReactNode } from "react";
import { StyledDialog } from "./StyledDialog";
import { MaterialButton, MaterialIcon } from "@/utils/material";
import { OS_CONFIG, type DownloadOs } from "@/core/downloads/os";
import styles from "./css/download-dialog.module.scss";
interface DownloadDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
os: DownloadOs;
}
function AndroidInstructions(): ReactNode {
return (
<div className={styles.section}>
<h3 className={styles.sectionTitle}>Установка на Android</h3>
<p className={styles.text}>
Вы скачали APK-файл FromChat. Чтобы установить приложение:
</p>
<ul className={styles.list}>
<li>Откройте загруженный APK-файл из шторки уведомлений или файлового менеджера.</li>
<li>
Если появится запрос &quot;Разрешить установку из неизвестных источников&quot; дайте
разрешение для браузера, из которого вы скачивали APK.
</li>
<li>
Google Play Protect может предупредить о неизвестном приложении. Если вы доверяете FromChat,
нажмите &quot;Подробнее&quot; &quot;Всё равно установить&quot; (или аналогичную кнопку).
</li>
<li>Дождитесь завершения установки и откройте FromChat из списка приложений.</li>
</ul>
</div>
);
}
function IosInstructions(): ReactNode {
return (
<div className={styles.section}>
<h3 className={styles.sectionTitle}>Установка на iOS</h3>
<p className={styles.text}>
Эта сборка не распространяется через App Store или TestFlight. Чтобы установить FromChat на iPhone
или iPad, потребуется один из вариантов сторонней установки:
</p>
<ul className={styles.list}>
<li>
<strong>TrollStore</strong>: постоянная установка приложений из IPA-файлов. Требуется поддерживаемая
версия iOS и настройка TrollStore на устройстве.
</li>
<li>
<strong>Джейлбрейк</strong>: установка через менеджер пакетов (Sileo, Cydia и т.п.) или напрямую
из файлового менеджера, если у вас уже есть джейлбрейк.
</li>
<li>
<strong>Другие сервисы сайдлоада</strong>: сторонние инструменты, которые подписывают IPA-файл
вашим сертификатом разработчика или временным сертификатом.
</li>
</ul>
<p className={styles.text}>
К сожалению, простого и официально поддерживаемого пути установки для iOS здесь нет именно поэтому я
бы сам iPhone не покупал 😄
</p>
</div>
);
}
function renderInstructions(os: DownloadOs): ReactNode {
if (os === "android") {
return <AndroidInstructions />;
}
if (os === "ios") {
return <IosInstructions />;
}
return null;
}
export function DownloadDialog({ open, onOpenChange, os }: DownloadDialogProps) {
const osInfo = OS_CONFIG[os];
return (
<StyledDialog
open={open}
onOpenChange={onOpenChange}
className={styles.downloadDialog}
contentClassName={styles.downloadDialogContent}
afterChildren={
<div className={styles.actions}>
<MaterialButton variant="filled" onClick={() => onOpenChange(false)}>
Закрыть
</MaterialButton>
</div>
}
>
<div className={styles.body}>
<div className={styles.header}>
<div className={styles.iconWrapper}>
<MaterialIcon name="download" className={styles.icon} />
</div>
<div className={styles.titleBlock}>
<h2 className={styles.title}>Thanks for downloading</h2>
<p className={styles.subtitle}>
FromChat для&nbsp;
<span className={styles.osName}>{osInfo.label}</span>
</p>
</div>
</div>
{renderInstructions(os)}
</div>
</StyledDialog>
);
}
+5 -11
View File
@@ -40,7 +40,6 @@ export function SplitButton({
top?: number;
bottom?: number;
left: number;
transform: string;
maxHeight: number;
} | null>(null);
const { width: windowWidth, height: windowHeight } = useWindowSize();
@@ -58,9 +57,8 @@ export function SplitButton({
const menuWidth = menuEl?.offsetWidth ?? 220;
const menuHeight = menuEl?.offsetHeight ?? 320;
const anchorX = rect.left + rect.width / 2;
const anchorCenterX = rect.left + rect.width / 2;
let left: number;
let transform: string;
let top: number | undefined;
let bottom: number | undefined;
let maxHeight: number;
@@ -82,18 +80,15 @@ export function SplitButton({
maxHeight = Math.max(100, availableBelow);
}
if (anchorX - menuWidth / 2 < EDGE_PAD) {
if (anchorCenterX - menuWidth / 2 < EDGE_PAD) {
left = EDGE_PAD;
transform = "none";
} else if (anchorX + menuWidth / 2 > vw - EDGE_PAD) {
} else if (anchorCenterX + menuWidth / 2 > vw - EDGE_PAD) {
left = vw - menuWidth - EDGE_PAD;
transform = "none";
} else {
left = anchorX;
transform = "translateX(-50%)";
left = anchorCenterX - menuWidth / 2;
}
setMenuPosition({ top, bottom, left, transform, maxHeight });
setMenuPosition({ top, bottom, left, maxHeight });
}, []);
const closeMenu = useCallback(() => {
@@ -245,7 +240,6 @@ export function SplitButton({
? { bottom: menuPosition.bottom }
: { top: menuPosition.top }),
left: menuPosition.left,
transform: menuPosition.transform,
maxHeight: menuPosition.maxHeight,
overflowY: "auto",
}}
@@ -1,114 +0,0 @@
@use "../../../css/material" as *;
@use "../../../css/colors" as *;
@use "sass:color";
.downloadDialog {
color: $color-dark-on-surface;
}
.downloadDialogContent {
padding: 24px 24px 16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.body {
display: flex;
flex-direction: column;
gap: 16px;
}
.header {
display: flex;
align-items: flex-start;
gap: 16px;
}
.iconWrapper {
width: 40px;
height: 40px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
background-color: $color-dark-primary-container;
color: $color-dark-on-primary-container;
}
.icon {
font-size: 22px;
}
.titleBlock {
display: flex;
flex-direction: column;
gap: 4px;
}
.title {
margin: 0;
font-size: 20px;
font-weight: 600;
}
.subtitle {
margin: 0;
font-size: 14px;
color: $color-dark-on-surface-variant;
}
.osName {
font-weight: 600;
}
.section {
display: flex;
flex-direction: column;
gap: 8px;
}
.sectionTitle {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.text {
margin: 0;
font-size: 14px;
line-height: 1.6;
color: $color-dark-on-surface-variant;
}
.list {
margin: 0;
padding-left: 20px;
font-size: 14px;
line-height: 1.6;
color: $color-dark-on-surface-variant;
li + li {
margin-top: 4px;
}
}
.actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 12px 24px 20px;
border-top: 1px solid rgba($color-dark-outline-variant, 0.4);
background-color: color.mix($color-dark-surface-container, $color-dark-surface-container-low, 60%);
}
@media (max-width: 480px) {
.downloadDialogContent {
padding: 20px 16px 12px;
}
.actions {
padding-inline: 16px;
}
}
@@ -224,4 +224,8 @@ $menu-padding: 8px;
scrollbar-width: thin;
scrollbar-color: rgba($color-dark-on-surface, 0.25) transparent;
mdui-list {
padding: 0;
}
}