import { RefreshRounded, StorageOutlined } from '@mui/icons-material' import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Divider, IconButton, LinearProgress, List, ListItem, ListItemText, Typography, alpha, styled, } from '@mui/material' import { useLockFn } from 'ahooks' import dayjs from 'dayjs' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { updateProxyProvider } from 'tauri-plugin-mihomo-api' import { useAppRefreshers, useProxiesData } from '@/providers/app-data-context' import { syncRuntimeProviders } from '@/services/cmds' import { mutate } from '@/services/mutate' import { showNotice } from '@/services/notice-service' import parseTraffic from '@/utils/parse-traffic' const TypeBox = styled(Box)<{ component?: React.ElementType }>(({ theme }) => ({ display: 'inline-block', border: '1px solid #ccc', borderColor: alpha(theme.palette.secondary.main, 0.5), color: alpha(theme.palette.secondary.main, 0.8), borderRadius: 4, fontSize: 10, marginRight: '4px', padding: '0 2px', lineHeight: 1.25, })) const parseExpire = (expire?: number) => { if (!expire) return '-' return dayjs(expire * 1000).format('YYYY-MM-DD') } export const ProviderButton = () => { const { t } = useTranslation() const [open, setOpen] = useState(false) const { proxyView } = useProxiesData() const { refreshProxy } = useAppRefreshers() const [updating, setUpdating] = useState>({}) const providers = proxyView?.providers ?? [] const providerUnavailable = proxyView?.providerState === 'unavailable' const updateProvider = useLockFn(async (name: string) => { try { setUpdating((prev) => ({ ...prev, [name]: true })) await mutate(() => updateProxyProvider(name), { id: `update-proxy-provider-${name}`, errorNotice: false, }) await refreshProxy() void mutate(() => syncRuntimeProviders(), { id: 'sync-runtime-providers', errorNotice: false, }) showNotice.success( 'proxies.feedback.notifications.provider.updateSuccess', { name, }, ) } catch (err) { showNotice.error('proxies.feedback.notifications.provider.updateFailed', { name, message: String(err), }) } finally { setUpdating((prev) => ({ ...prev, [name]: false })) } }) const updateAllProviders = useLockFn(async () => { try { const allProviders = providers.map(({ name }) => name) if (allProviders.length === 0) { showNotice.info('proxies.feedback.notifications.provider.none') return } const newUpdating = allProviders.reduce( (acc, key) => { acc[key] = true return acc }, {} as Record, ) setUpdating(newUpdating) for (const name of allProviders) { try { await mutate(() => updateProxyProvider(name), { id: `update-proxy-provider-${name}`, errorNotice: false, }) setUpdating((prev) => ({ ...prev, [name]: false })) } catch (err) { console.error(`更新 ${name} 失败`, err) } } await refreshProxy() void mutate(() => syncRuntimeProviders(), { id: 'sync-runtime-providers', errorNotice: false, }) showNotice.success('proxies.feedback.notifications.provider.allUpdated') } catch (err) { showNotice.error('proxies.feedback.notifications.provider.genericError', { message: String(err), }) } finally { setUpdating({}) } }) const handleClose = () => { setOpen(false) } if (providers.length === 0 && !providerUnavailable) return null return ( <> {t('proxies.page.provider.title')} {providers.map((provider) => { const key = provider.name const updatedAt = provider.updatedAt ? dayjs(provider.updatedAt) : null const isUpdating = updating[key] const sub = provider.subscriptionInfo const hasSubInfo = !!sub const upload = sub?.upload || 0 const download = sub?.download || 0 const total = sub?.total || 0 const expire = sub?.expire || 0 const progress = total > 0 ? Math.min( Math.round(((download + upload) * 100) / total) + 1, 100, ) : 0 return ( { const bgcolor = mode === 'light' ? '#ffffff' : '#24252f' const hoverColor = mode === 'light' ? alpha(primary.main, 0.1) : alpha(primary.main, 0.2) return { backgroundColor: bgcolor, '&:hover': { backgroundColor: hoverColor, }, } }, ]} > {key} {provider.proxyRecordIds.length} {provider.vehicleType} {t('shared.labels.updateAt')}: {updatedAt?.fromNow() ?? '-'} } secondary={ <> {hasSubInfo && ( <> {parseTraffic(upload + download)} /{' '} {parseTraffic(total)} {parseExpire(expire)} 0 ? 1 : 0, }} /> )} } /> { updateProvider(key) }} disabled={isUpdating} sx={{ animation: isUpdating ? 'spin 1s linear infinite' : 'none', '@keyframes spin': { '0%': { transform: 'rotate(0deg)' }, '100%': { transform: 'rotate(360deg)' }, }, }} title={t('proxies.page.provider.actions.update')} aria-label={t('proxies.page.provider.actions.update')} > ) })} ) }