desktop: Releases > Send for review (App Store submission state, builds by number, Submit / Resubmit / in-app purchases; Play versionCodes by number, Send to production, managed publishing, console plans for countries and Send changes for review), plan panel before every owner dialog with streamed progress; App info console steps for every console plan form with verify; Catalog price-change owner actions and the declined state; Data safety deletion answer; TestFlight create missing groups; Android SDK in Dependencies; upload progress in job toasts; boot check send-for-review cases on D1 and D3 state

This commit is contained in:
CRTkafa 2026-10-12 12:07:59 +08:00
parent 33eb24a3dc
commit 2b101f0fd8
22 changed files with 1527 additions and 57 deletions

View file

@ -2,7 +2,7 @@
// into the app data folder in one click (checksum or signature verified, progress, cancel); the rest link out.
// Store work needs none of them. Sources and licenses: docs/desktop.md.
import { useCallback, useEffect, useState } from 'react';
import { api, type DownloadInfo, errorText, newJob, onDownload, type ToolStatus } from '../api';
import { type AndroidSdkLookup, api, type DownloadInfo, errorText, type LocalTools, newJob, onDownload, type ToolStatus } from '../api';
import { t } from '../i18n';
import { bytes } from '../lib/format';
import { shortPath } from '../paths';
@ -23,6 +23,7 @@ interface Dl { job: string; stage: string; received: number; total: number | nul
export function Dependencies() {
const d = useData();
const [tools, setTools] = useState<ToolStatus[] | null>(d.sidecar?.tools?.tools ?? null);
const [sdk, setSdk] = useState<AndroidSdkLookup | null>(d.sidecar?.tools?.androidSdk ?? null);
const [info, setInfo] = useState<DownloadInfo[]>([]);
const [error, setError] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
@ -31,8 +32,8 @@ export function Dependencies() {
const refresh = useCallback(async () => {
setBusy(true); setError(null);
try {
const [s, i] = await Promise.all([api.call<{ tools: ToolStatus[] }>('system.tools'), api.toolsInfo()]);
setTools(s.tools); setInfo(i);
const [s, i] = await Promise.all([api.call<LocalTools>('system.tools'), api.toolsInfo()]);
setTools(s.tools); setInfo(i); setSdk(s.androidSdk ?? null);
} catch (e) { setError(errorText(e)); } finally { setBusy(false); }
}, []);
useEffect(() => { refresh(); }, [refresh]);
@ -113,6 +114,22 @@ export function Dependencies() {
</div>
)}
</Card>
{sdk && (
<Card title={t('deps.sdkTitle')} icon="phone" sub={t('deps.sdkSub')} id="android-sdk"
actions={sdk.path ? <Chip tone="ok">{t('deps.installed')}</Chip> : <><Chip tone="warn">{t('deps.missing')}</Chip><button className="btn btn-sm" onClick={() => open('https://developer.android.com/studio')}><Icon name="external" />{t('deps.getIt')}</button></>}>
{sdk.path ? (
<dl className="kv">
<dt>{t('deps.sdkPath')}</dt><dd className="mono-id selectable" title={sdk.path}>{shortPath(sdk.path, d.info?.home)}</dd>
<dt>{t('deps.sdkFrom')}</dt><dd className="small">{sdk.source}</dd>
</dl>
) : (
<div className="stack" style={{ gap: 6 }}>
<span className="small">{t('deps.sdkNone')}</span>
<ul className="note-list small">{sdk.checked.map((c, i) => <li key={i} className="selectable">{d.info?.home ? c.split(d.info.home).join("~") : c}</li>)}</ul>
</div>
)}
</Card>
)}
<div className="grid g2">
{info.map(m => (
<Card key={m.tool} title={m.tool === 'chrome' ? 'Chrome for Testing' : `ffmpeg ${m.version ?? ''}`} icon={m.tool === 'chrome' ? 'globe' : 'image'} sub={m.installed ? t('deps.inAppData', { folder: m.folder }) : t('deps.notDownloaded')}>