diff --git a/apps/browser/package.json b/apps/browser/package.json index 4514c76..a081ba9 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -86,6 +86,7 @@ } }, "dependencies": { + "lucide-react": "^0.546.0", "react": "^18.3.1", "react-dom": "^18.3.1" } diff --git a/apps/browser/src/main/ipc-handlers.ts b/apps/browser/src/main/ipc-handlers.ts index 8a60d70..dfaca7f 100644 --- a/apps/browser/src/main/ipc-handlers.ts +++ b/apps/browser/src/main/ipc-handlers.ts @@ -36,6 +36,7 @@ export class IPCHandlers { this.registerWebContentsHandlers(); this.registerThemeHandlers(); this.registerOrientationHandlers(); + this.registerAppHandlers(); } /** @@ -349,4 +350,13 @@ export class IPCHandlers { return this.windowManager.toggleOrientation(); }); } + + /** + * Register app-related handlers + */ + private registerAppHandlers(): void { + ipcMain.handle("get-app-version", () => { + return app.getVersion(); + }); + } } diff --git a/apps/browser/src/preload.ts b/apps/browser/src/preload.ts index 2aba092..c00dc23 100644 --- a/apps/browser/src/preload.ts +++ b/apps/browser/src/preload.ts @@ -48,6 +48,15 @@ contextBridge.exposeInMainWorld("electronAPI", { return () => ipcRenderer.removeAllListeners("fullscreen-mode-changed"); }, + // Settings listener + onOpenSettings: (callback: () => void) => { + ipcRenderer.on("open-settings", callback); + return () => ipcRenderer.removeListener("open-settings", callback); + }, + + // App version + getAppVersion: () => ipcRenderer.invoke("get-app-version"), + // Tab management APIs tabs: { getAll: () => ipcRenderer.invoke("tabs-get-all"), diff --git a/apps/browser/src/renderer/app.tsx b/apps/browser/src/renderer/app.tsx index 56f6a34..d80a82e 100644 --- a/apps/browser/src/renderer/app.tsx +++ b/apps/browser/src/renderer/app.tsx @@ -3,6 +3,7 @@ import { useState, useEffect, useRef } from "react"; import TopBar from "./components/top-bar"; import PhoneFrame from "./components/phone-frame"; import TabOverview from "./components/tab-overview"; +import Settings from "./components/settings"; function App() { const [_time, setTime] = useState("9:41"); @@ -16,6 +17,7 @@ function App() { "portrait" ); const [showTabOverview, setShowTabOverview] = useState(false); + const [showSettings, setShowSettings] = useState(false); const [tabCount, setTabCount] = useState(1); const [isFullscreen, setIsFullscreen] = useState(false); const webContainerRef = useRef(null); @@ -73,6 +75,19 @@ function App() { }; }, []); + // Listen for settings open request + useEffect(() => { + const cleanup = window.electronAPI?.onOpenSettings(() => { + setShowSettings(true); + // Hide WebContentsView when showing settings + window.electronAPI?.webContents.setVisible(false); + }); + + return () => { + if (cleanup) cleanup(); + }; + }, []); + // Track tab count useEffect(() => { // Get initial tab count @@ -515,6 +530,35 @@ function App() { window.electronAPI?.webContents.reload(); }; + const handleCloseSettings = () => { + setShowSettings(false); + + // Set bounds before showing view + if (webContainerRef.current) { + const rect = webContainerRef.current.getBoundingClientRect(); + const statusBarHeight = 58; + const statusBarWidth = 58; + + window.electronAPI?.webContents.setBounds({ + x: Math.round( + rect.x + (orientation === "landscape" ? statusBarWidth : 0) + ), + y: Math.round( + rect.y + (orientation === "landscape" ? 0 : statusBarHeight) + ), + width: Math.round( + rect.width - (orientation === "landscape" ? statusBarWidth : 0) + ), + height: Math.round( + rect.height - (orientation === "landscape" ? 0 : statusBarHeight) + ), + }); + } + + // Show WebContentsView when closing settings + window.electronAPI?.webContents.setVisible(true); + }; + return (
+ showSettings ? ( + + ) : ( + + ) } />
diff --git a/apps/browser/src/renderer/components/settings.tsx b/apps/browser/src/renderer/components/settings.tsx new file mode 100644 index 0000000..7b6ec04 --- /dev/null +++ b/apps/browser/src/renderer/components/settings.tsx @@ -0,0 +1,327 @@ +import { useState, useEffect } from "react"; +import { Info, Globe, ChevronRight, ChevronLeft } from "lucide-react"; + +interface SettingsProps { + theme: "light" | "dark"; + orientation: "portrait" | "landscape"; + onClose: () => void; +} + +interface SettingsSection { + id: string; + title: string; + items: SettingsItem[]; +} + +interface SettingsItem { + id: string; + label: string; + value?: string; + icon?: React.ReactNode; + hasDetail?: boolean; + onClick?: () => void; +} + +function Settings({ theme, orientation, onClose }: SettingsProps) { + const [currentView, setCurrentView] = useState<"main" | "about">("main"); + const [appVersion, setAppVersion] = useState("0.0.0"); + + useEffect(() => { + // Get app version + window.electronAPI?.getAppVersion().then((version: string) => { + setAppVersion(version); + }); + }, []); + + const isDark = theme === "dark"; + + const settingsSections: SettingsSection[] = [ + { + id: "general", + title: "General", + items: [ + { + id: "about", + label: "About", + value: "Aka Browser", + icon: , + hasDetail: true, + onClick: () => setCurrentView("about"), + }, + ], + }, + ]; + + const renderMainView = () => ( + <> + {/* Header */} +
+

+ Settings +

+ +
+ + {/* Settings List */} +
+
+ {settingsSections.map((section) => ( +
+ {/* Section Title */} +
+ {section.title} +
+ + {/* Section Items */} +
+ {section.items.map((item, index) => ( +
+ {index > 0 && ( +
+ )} + +
+ ))} +
+
+ ))} +
+
+ + ); + + const renderAboutView = () => ( + <> + {/* Header */} +
+ +

+ About +

+
+
+ + {/* About Content */} +
+
+ {/* App Icon and Name */} +
+
+ +
+

+ Aka Browser +

+

+ Version {appVersion} +

+
+ + {/* Info Section */} +
+
+ Information +
+
+
+
+ + Name + + + Aka Browser + +
+
+
+
+
+ + Version + + + {appVersion} + +
+
+
+
+
+ + Description + + + A lightweight, elegant web browser + +
+
+
+
+
+
+ + ); + + return ( +
{ + // Only close if clicking the background + if (e.target === e.currentTarget) { + onClose(); + } + }} + > + {currentView === "main" ? renderMainView() : renderAboutView()} +
+ ); +} + +export default Settings; diff --git a/apps/browser/src/types/electron-api.d.ts b/apps/browser/src/types/electron-api.d.ts index eff96e0..f17eba2 100644 --- a/apps/browser/src/types/electron-api.d.ts +++ b/apps/browser/src/types/electron-api.d.ts @@ -55,6 +55,12 @@ export interface ElectronAPI { callback: (isFullscreen: boolean) => void ) => () => void; + // Settings listener + onOpenSettings: (callback: () => void) => () => void; + + // App version + getAppVersion: () => Promise; + // Tab management APIs tabs: { getAll: () => Promise; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 54c9cd5..f9ab8f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: apps/browser: dependencies: + lucide-react: + specifier: ^0.546.0 + version: 0.546.0(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -1652,6 +1655,11 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + lucide-react@0.546.0: + resolution: {integrity: sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} @@ -4094,6 +4102,10 @@ snapshots: lru-cache@7.18.3: {} + lucide-react@0.546.0(react@18.3.1): + dependencies: + react: 18.3.1 + magic-string@0.30.19: dependencies: '@jridgewell/sourcemap-codec': 1.5.5