diff --git a/Frontend/Electron/main.cjs b/Frontend/Electron/main.cjs index f191424..ca35819 100644 --- a/Frontend/Electron/main.cjs +++ b/Frontend/Electron/main.cjs @@ -346,6 +346,44 @@ app.whenReady().then(async () => { } }); + // Flatpak update check + ipcMain.handle('check-flatpak-update', async () => { + const isFlatpak = fs.existsSync('/.flatpak-info') || !!process.env.FLATPAK_ID; + if (!isFlatpak) return { isFlatpak: false }; + + try { + const yaml = await httpGet('https://gitea.moyettes.com/Moyettes/DiscordClone/releases/download/latest/latest-linux.yml'); + if (!yaml) return { isFlatpak: true, updateAvailable: false }; + + const versionMatch = yaml.match(/^version:\s*(.+)$/m); + if (!versionMatch) return { isFlatpak: true, updateAvailable: false }; + + const latestVersion = versionMatch[1].trim(); + const currentVersion = app.getVersion(); + + // Semver comparison: determine if update exists and its severity + const latest = latestVersion.split('.').map(Number); + const current = currentVersion.split('.').map(Number); + let updateAvailable = false; + let updateType = 'patch'; // 'major', 'minor', or '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 { isFlatpak: true, updateAvailable, updateType, latestVersion, currentVersion }; + } catch (err) { + console.error('Flatpak update check error:', err.message); + return { isFlatpak: true, updateAvailable: false }; + } + }); + ipcMain.handle('open-external', async (event, url) => { await shell.openExternal(url); }); diff --git a/Frontend/Electron/preload.cjs b/Frontend/Electron/preload.cjs index 5b9c222..3f11439 100644 --- a/Frontend/Electron/preload.cjs +++ b/Frontend/Electron/preload.cjs @@ -32,6 +32,10 @@ contextBridge.exposeInMainWorld('appSettings', { set: (key, value) => ipcRenderer.invoke('set-setting', key, value), }); +contextBridge.exposeInMainWorld('updateAPI', { + checkFlatpakUpdate: () => ipcRenderer.invoke('check-flatpak-update'), +}); + contextBridge.exposeInMainWorld('sessionPersistence', { save: (data) => ipcRenderer.invoke('save-session', data), load: () => ipcRenderer.invoke('load-session'), diff --git a/Frontend/Electron/src/components/TitleBar.jsx b/Frontend/Electron/src/components/TitleBar.jsx index 1c4aa9c..7949f04 100644 --- a/Frontend/Electron/src/components/TitleBar.jsx +++ b/Frontend/Electron/src/components/TitleBar.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import { TitleBarUpdateIcon } from './UpdateBanner'; const TitleBar = () => { return ( @@ -26,6 +27,7 @@ const TitleBar = () => {
Discord Clone
+ +
+ + ); +} + +export function TitleBarUpdateIcon() { + const update = useUpdateCheck(); + + if (!update) return null; + + const handleClick = () => { + window.cryptoAPI?.openExternal(RELEASE_URL); + }; + + return ( + + ); +} diff --git a/Frontend/Electron/src/index.css b/Frontend/Electron/src/index.css index 1f6902b..180dfe4 100644 --- a/Frontend/Electron/src/index.css +++ b/Frontend/Electron/src/index.css @@ -2816,6 +2816,63 @@ body { width: 200px; } +/* ============================================ + FORCED UPDATE MODAL (Flatpak) + ============================================ */ +.forced-update-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.85); + display: flex; + align-items: center; + justify-content: center; + z-index: 10002; +} + +.forced-update-modal { + background-color: var(--bg-secondary); + border-radius: 8px; + padding: 32px; + width: 400px; + max-width: 90vw; + text-align: center; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); +} + +.forced-update-modal h2 { + color: var(--header-primary); + font-size: 20px; + font-weight: 700; + margin: 0 0 8px; +} + +.forced-update-modal p { + color: var(--header-secondary); + font-size: 14px; + margin: 0 0 24px; + line-height: 1.4; +} + +.forced-update-btn { + background-color: var(--brand-experiment); + color: white; + border: none; + border-radius: 3px; + padding: 10px 24px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + font-family: inherit; + transition: background-color 0.15s; +} + +.forced-update-btn:hover { + background-color: var(--brand-experiment-hover); +} + .drag-overlay-category { padding: 8px 12px; background-color: var(--bg-secondary); diff --git a/Frontend/Electron/src/main.jsx b/Frontend/Electron/src/main.jsx index 2ea8ff5..2191711 100644 --- a/Frontend/Electron/src/main.jsx +++ b/Frontend/Electron/src/main.jsx @@ -8,6 +8,7 @@ import './index.css'; import { ThemeProvider } from './contexts/ThemeContext'; import { VoiceProvider } from './contexts/VoiceContext'; +import { UpdateProvider } from './components/UpdateBanner'; import TitleBar from './components/TitleBar'; const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL); @@ -15,14 +16,16 @@ const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL); ReactDOM.createRoot(document.getElementById('root')).render( - - - - - - - - + + + + + + + + + + , );