diff --git a/packages/platform-web/src/index.js b/packages/platform-web/src/index.js index 8152220..d746465 100644 --- a/packages/platform-web/src/index.js +++ b/packages/platform-web/src/index.js @@ -44,4 +44,53 @@ const webPlatform = { }, }; +// Detect Android/Capacitor and enable native APK updates +if (window.Capacitor?.isNativePlatform?.()) { + const YAML_URL = 'https://gitea.moyettes.com/Moyettes/DiscordClone/releases/download/latest/latest-android.yml'; + const APK_URL = 'https://gitea.moyettes.com/Moyettes/DiscordClone/releases/download/latest/app-release.apk'; + const AppUpdater = window.Capacitor.Plugins.AppUpdater; + + webPlatform.updates = { + async checkUpdate() { + try { + const response = await fetch(YAML_URL); + if (!response.ok) return { updateAvailable: false }; + const yaml = await response.text(); + + const versionMatch = yaml.match(/^version:\s*(.+)$/m); + if (!versionMatch) return { updateAvailable: false }; + const latestVersion = versionMatch[1].trim(); + + const { version: currentVersion } = await AppUpdater.getVersion(); + + const latest = latestVersion.split('.').map(Number); + const current = currentVersion.split('.').map(Number); + let updateAvailable = false; + let updateType = 'patch'; + for (let i = 0; i < Math.max(latest.length, current.length); i++) { + const l = latest[i] || 0; + const c = current[i] || 0; + if (l > c) { + updateAvailable = true; + updateType = i === 0 ? 'major' : i === 1 ? 'minor' : 'patch'; + break; + } + if (l < c) break; + } + + return { updateAvailable, updateType, latestVersion, currentVersion, apkUrl: APK_URL }; + } catch { + return { updateAvailable: false }; + } + }, + async installUpdate() { + await AppUpdater.downloadAndInstall({ url: APK_URL }); + }, + onDownloadProgress(callback) { + AppUpdater.addListener('downloadProgress', callback); + }, + }; + webPlatform.features.hasNativeUpdates = true; +} + export default webPlatform; diff --git a/packages/shared/src/components/UpdateBanner.jsx b/packages/shared/src/components/UpdateBanner.jsx index 84ad847..f9ae1ac 100644 --- a/packages/shared/src/components/UpdateBanner.jsx +++ b/packages/shared/src/components/UpdateBanner.jsx @@ -35,9 +35,31 @@ export function UpdateProvider({ children }) { } function ForcedUpdateModal({ latestVersion }) { - const { links } = usePlatform(); - const handleDownload = () => { - links.openExternal(RELEASE_URL); + const { links, updates } = usePlatform(); + const [downloading, setDownloading] = useState(false); + const [progress, setProgress] = useState(0); + const [error, setError] = useState(null); + + useEffect(() => { + if (!downloading || !updates?.onDownloadProgress) return; + updates.onDownloadProgress(({ percent }) => { + setProgress(percent); + }); + }, [downloading]); + + const handleDownload = async () => { + if (updates?.installUpdate) { + setDownloading(true); + setError(null); + try { + await updates.installUpdate(); + } catch (e) { + setError('Download failed. Please try again.'); + setDownloading(false); + } + } else { + links.openExternal(RELEASE_URL); + } }; return ( @@ -47,30 +69,65 @@ function ForcedUpdateModal({ latestVersion }) {
A new version (v{latestVersion}) is available. This update is required to continue using the app.
- + {downloading ? ( +{error}
} ); } export function TitleBarUpdateIcon() { - const { links } = usePlatform(); + const { links, updates } = usePlatform(); const update = useUpdateCheck(); + const [downloading, setDownloading] = useState(false); + const [progress, setProgress] = useState(0); + + useEffect(() => { + if (!downloading || !updates?.onDownloadProgress) return; + updates.onDownloadProgress(({ percent }) => { + setProgress(percent); + }); + }, [downloading]); if (!update) return null; - const handleClick = () => { - links.openExternal(RELEASE_URL); + const handleClick = async () => { + if (updates?.installUpdate && !downloading) { + setDownloading(true); + try { + await updates.installUpdate(); + } catch { + setDownloading(false); + } + } else if (!downloading) { + links.openExternal(RELEASE_URL); + } }; + const label = downloading + ? `Downloading update... ${progress}%` + : `Update available: v${update.latestVersion}`; + return (