Overview • Features • Architecture • Structure • Installation • Platform Channels • Developer
Zyro Browser is a premium, high-performance mobile web browser for Android built with Flutter, featuring a futuristic Cyber-Bento design language. It combines a high-fidelity Flutter client with a dedicated Node.js/FFmpeg media microservice — offering native ad blocking, local internet usage analytics, smart video download, background audio playback, home screen web app installation, page QR sharing, per-site permission management, full-page screenshot capture with PDF export, and a sandboxed extension ecosystem.
✨ Zyro is not a WebView wrapper with a thin UI shell. It is a complete, vertically integrated browser stack — from the Android foreground service layer through the Flutter extension engine to the Node.js stream extraction pipeline.
Zyro is under active development. Most features are fully functional and stable. One exception:
- Video downloads currently require the Node.js/FFmpeg backend to be self-hosted (running on
localhostor your local network). It is not yet deployed to a public server, so downloads won't work out-of-the-box if you just install the APK without also running the backend yourself. See Installation & Running to set it up locally.Everything else — browsing, tabs, ad blocking, screenshots, permissions, background playback, web app install, page QR, extensions — works as documented below.
| Capability | Description |
|---|---|
| 🎨 Cyber-Bento Design | Glassmorphic, futuristic UI with Outfit typography and a deep indigo/teal/cyan palette |
| 🛡️ Native Ad Blocking | Multi-layer request interceptor with 35+ rules blocking ads, trackers, beacons, and popunders |
| 📊 Ad Block Analytics | Persistent per-domain blocked-request counters with daily reset and lifetime totals |
| 📈 Usage Analytics | Local-only estimated internet usage by domain with today, monthly, and all-time views |
| 🎵 Background Playback | Uninterrupted audio/video via Android MediaSession foreground service with lock-screen controls |
| 🎬 Smart Video Detection | JavaScript-based detection across YouTube, Vimeo, Facebook, Instagram, Twitter/X, Dailymotion |
| 📥 Media Download Pipeline | yt-dlp metadata extraction → adaptive stream download → FFmpeg merge → static file hosting |
| 📸 Screenshot Pro | Floating capture button with viewport screenshot, full-page scrolling stitch, PNG save, A4 PDF export |
| 📱 Web App Installer | Install any website as an Android home screen shortcut with manifest parsing and shortcut lifecycle sync |
| ▣ Page QR Code | Address-bar QR action generates a compact scannable QR popup for the current page URL |
| 🔐 Website Permissions | Per-site allow/ask/block controls for Camera, Microphone, Location, Notifications, and Clipboard |
| 🔌 Extension Ecosystem | Sandboxed extension registry with install/uninstall/enable/disable lifecycle and persistent state |
| 🗂️ Tab Groups | Full grouped tab management alongside standalone tabs with session persistence |
| 🕵️ Incognito Mode | Global incognito mode with dedicated theme and session isolation |
| 🛠️ Dev Tools Extension | Built-in element inspector, console viewer, network logger, and storage explorer |
🧭 Browser Core
| Feature | Status |
|---|---|
flutter_inappwebview-powered WebView |
✅ Implemented |
| Multi-tab management (standalone + grouped) | ✅ Implemented |
| Tab session persistence across restarts | ✅ Implemented |
| Tab groups with color labels | ✅ Implemented |
| Incognito mode (global toggle) | ✅ Implemented |
| Undo tab close (5-second window) | ✅ Implemented |
| Desktop mode per tab | ✅ Implemented |
| Find-in-page | ✅ Implemented |
| Smart address bar (search vs. URL routing) | ✅ Implemented |
| Popup window / new tab handling | ✅ Implemented |
| History tracking | ✅ Implemented |
| Bookmark system | ✅ Implemented |
| Reading list | ✅ Implemented (in-memory only) |
| Favorites | ✅ Implemented (in-memory only) |
| Share page | ✅ Implemented (share_plus) |
| Address-bar Page QR Code | ✅ Implemented (qr_flutter) |
📈 Internet Usage Analytics
| Feature | Status |
|---|---|
| WebView request observation | ✅ Implemented |
Local domain normalization (m., www. grouping) |
✅ Implemented |
| Estimated bytes by website/domain | ✅ Implemented |
| Today, this-month, and all-time totals | ✅ Implemented |
| Top websites sorted by highest usage | ✅ Implemented |
| Domain rows with favicon, usage, percentage, and progress bar | ✅ Implemented |
Persistent storage via SharedPreferences |
✅ Implemented |
| Clear usage data with confirmation | ✅ Implemented |
| Settings entry: Usage | ✅ Implemented |
| Local-only privacy note | ✅ Implemented |
🛡️ Ad Blocker
| Feature | Status |
|---|---|
| URL-pattern request interception (35+ rules) | ✅ Implemented |
| Tracker / beacon / analytics blocking | ✅ Implemented |
| Google Ads / DoubleClick / Syndication blocking | ✅ Implemented |
| Facebook pixel / GTM / Google Analytics blocking | ✅ Implemented |
| AppNexus, PubMatic, Criteo, Taboola, Outbrain | ✅ Implemented |
| Pop-under / pop-up blocking | ✅ Implemented |
| YouTube ad cosmetic injection | ✅ Implemented |
| Generic cosmetic filter injection | ✅ Implemented |
| Per-domain blocked-request analytics | ✅ Implemented |
| Today-blocked counter with daily reset | ✅ Implemented |
| Lifetime total blocked counter | ✅ Implemented |
| Ad block analytics dashboard (Settings UI) | ✅ Implemented |
| Toggle ad blocker per extension | ✅ Implemented |
📸 Screenshot Pro
| Feature | Status |
|---|---|
| Floating screenshot FAB on browser | ✅ Implemented |
| Visible viewport screenshot | ✅ Implemented |
| Full-page scrolling stitch capture (max 16000px height) | ✅ Implemented |
| Scroll-behavior override during capture | ✅ Implemented |
| Scroll-position restoration after capture | ✅ Implemented |
| PNG save to device storage (screenshots subfolder) | ✅ Implemented |
| Multi-page A4 PDF export | ✅ Implemented |
Native WebView PDF export via zyro/screenshot_pro channel |
✅ Implemented |
| Progress dialog during capture | ✅ Implemented |
| Persistent enable/disable toggle | ✅ Implemented |
📱 Web App Installer
| Feature | Status |
|---|---|
| Web App Manifest detection from DOM | ✅ Implemented |
| Manifest icon download & local caching | ✅ Implemented |
| Bitmap decode + normalization (blank/too-small fallback) | ✅ Implemented |
| Android home screen shortcut pinning (API 26+) | ✅ Implemented |
| Shortcut intent routing back to browser | ✅ Implemented |
| Shortcut URL validation (http/https only) | ✅ Implemented |
| Installed app list (Zyro Apps) | ✅ Implemented |
| Pinned shortcut ID synchronization | ✅ Implemented |
| Shortcut launch handling on cold start | ✅ Implemented |
| Shortcut launch handling on warm resume | ✅ Implemented |
| Shortcut sync against pinned Android shortcuts | ✅ Implemented |
▣ Page QR Code
| Feature | Status |
|---|---|
| QR icon in the address bar | ✅ Implemented |
Current-page URL validation (http/https only) |
✅ Implemented |
| Compact QR popup with domain and URL | ✅ Implemented |
High-contrast QR rendering via qr_flutter |
✅ Implemented |
| Invalid/internal URL snackbar | ✅ Implemented |
🔐 Website Permissions Manager
| Permission | Status |
|---|---|
| Camera | ✅ Implemented |
| Microphone | ✅ Implemented |
Location (locationWhenInUse) |
✅ Implemented |
| Notifications | ✅ Implemented |
| Clipboard (read) | ✅ Implemented |
| Allow / Ask every time / Block per domain | ✅ Implemented |
Persistent storage via SharedPreferences |
✅ Implemented |
| Android runtime permission escalation | ✅ Implemented |
| Per-site permission management UI | ✅ Implemented |
| Per-permission category browser | ✅ Implemented |
🎵 Background Media Playback
| Feature | Status |
|---|---|
Android foreground service (startForegroundService) |
✅ Implemented |
MediaSession with transport controls |
✅ Implemented |
| Lock-screen play / pause / next / previous | ✅ Implemented |
| Seek via lock-screen progress bar | ✅ Implemented |
PARTIAL_WAKE_LOCK (30 min) |
✅ Implemented |
| Notification channel (Importance: Low, no badge) | ✅ Implemented |
| Media state sync (title, website, duration, position) | ✅ Implemented |
| JavaScript bridge: play/pause/next/prev/seek | ✅ Implemented |
| Background player extension toggle | ✅ Implemented |
| Service auto-stop on tab close | ✅ Implemented |
📥 Video Download Engine
| Feature | Status |
|---|---|
| Real-time video detection via JavaScript DOM polling | ✅ Implemented |
| YouTube, Vimeo, Facebook, Instagram, Twitter/X, Dailymotion | ✅ Implemented |
Direct .mp4, .mkv, .webm URL detection |
✅ Implemented |
yt-dlp metadata extraction (youtube-dl-exec) |
✅ Implemented |
| Adaptive format selection (separate video + audio) | ✅ Implemented |
| Progressive stream detection (video+audio combined) | ✅ Implemented |
| FFmpeg video+audio stream merge | ✅ Implemented |
| FFmpeg audio-only → 320kbps MP3 conversion | ✅ Implemented |
| Background download task with UUID tracking | ✅ Implemented |
| Real-time task status polling | ✅ Implemented |
| Playlist download rejection | ✅ Implemented |
| Floating download HUD | ✅ Implemented |
| Quality selector bottom sheet | ✅ Implemented |
| Download library screen | ✅ Implemented |
| Video height validation after download | ✅ Implemented |
| URL sanitizer (strips tracking params) | ✅ Implemented |
Android DownloadManager integration |
✅ Implemented |
| MediaStore device storage save | ✅ Implemented |
| Local in-app video player | ✅ Implemented |
🔌 Extension Ecosystem
| Extension | ID | Default State |
|---|---|---|
| Ad Blocker & Downloader | ad_blocker_downloader |
Installed + Enabled |
| Dev Tools | dev_tools |
Installed, Disabled |
| Background Player | background_player |
Installed, Disabled |
| Dark Reader | dark_mode |
Available (not installed) |
| KeyGen | password_gen |
Available (not installed) |
✨ Live data-flow animation — glowing dots travel the connector lines to show requests, messages, and files moving through the system in real time.
┌──────────────────────────────────────────────────────────────────────┐
│ Android Device │
│ │
│ ┌─────────────────── Flutter Application ───────────────────────┐ │
│ │ │ │
│ │ Cyber-Bento UI Extension Engine │ │
│ │ (Widgets/Screens) (AdBlock, BG Player, Dev Tools) │ │
│ │ │ │
│ │ ─────────────────── Core Layer ───────────────────────── │ │
│ │ TabManager │ WebViewWrapper │ BrowserDataManager │ │
│ │ ExtensionManager │ ScriptEngine │ │
│ │ │ │
│ │ ──────────────── Feature Modules ──────────────────────── │ │
│ │ Screenshot Pro │ Web Apps │ Permissions │ Usage Analytics │ │
│ │ Video Downloader │ Download Library │ Settings │ Page QR │ │
│ └──────────────────────────┬────────────────────────────────────┘ │
│ │ MethodChannels │
│ ┌──────────────────────────▼────────────────────────────────────┐ │
│ │ Native Kotlin (MainActivity.kt) │ │
│ │ zyro/downloads │ zyro/screenshot_pro │ │
│ │ zyro/web_apps │ zyro/background_player │ │
│ └────────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌────────────────────────────▼──────────────────────────────────┐ │
│ │ BackgroundPlayerService.kt (Android Foreground Service) │ │
│ │ MediaSession │ WakeLock │ NotificationChannel │ │
│ └───────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
│ HTTP (localhost:3000)
┌───────────────────▼──────────────────────────────────────────────────┐
│ Node.js Media Microservice │
│ Express │ youtube-dl-exec (yt-dlp) │ fluent-ffmpeg │ uuid │
│ │
│ POST /api/video/metadata → VideoExtractor → yt-dlp │
│ POST /api/video/download → FormatSelector → DownloadManager │
│ → MergeService (FFmpeg) │
│ GET /api/video/status/:id → Task state polling │
│ GET /downloads/:file → Static file serving │
└──────────────────────────────────────────────────────────────────────┘
zyro/
│
├── 📱 zyro-frontend/ Flutter Android application
│ ├── lib/
│ │ ├── main.dart App entry; MultiProvider setup
│ │ ├── core/
│ │ │ ├── tab_manager.dart Multi-tab + group session manager
│ │ │ ├── webview_wrapper.dart WebView config, JS bridge, intercepts
│ │ │ ├── extension_manager.dart Extension registry + lifecycle
│ │ │ ├── browser_data_manager.dart History, bookmarks, downloads, DL polling
│ │ │ ├── globals.dart Global navigator/scaffold keys
│ │ │ ├── constants/app_assets.dart Asset path constants
│ │ │ ├── models/
│ │ │ │ ├── tab_model.dart Tab state (url, title, favicon, scroll...)
│ │ │ │ ├── extension_model.dart Extension definition + lifecycle state
│ │ │ │ ├── bookmark_item.dart
│ │ │ │ ├── history_item.dart
│ │ │ │ ├── download_item.dart
│ │ │ │ └── link_metadata.dart Long-press link context model
│ │ │ ├── services/
│ │ │ │ ├── tab_session_storage_service.dart
│ │ │ │ ├── extension_storage_service.dart
│ │ │ │ └── extension_notification_service.dart
│ │ │ └── theme/
│ │ │ ├── app_colors.dart Color palette (light/dark)
│ │ │ ├── app_theme.dart MaterialApp themes
│ │ │ ├── theme_controller.dart
│ │ │ └── theme_storage_service.dart
│ │ ├── engine/
│ │ │ ├── hooks.dart BrowserHooks interface
│ │ │ └── script_engine.dart JS injection coordinator
│ │ ├── app/
│ │ │ ├── screens/
│ │ │ │ ├── browser_main.dart Main browser scaffold
│ │ │ │ ├── tab_switcher.dart Tab manager UI + groups
│ │ │ │ ├── history_screen.dart
│ │ │ │ ├── bookmarks_screen.dart
│ │ │ │ ├── extensions_screen.dart
│ │ │ │ └── local_video_player_screen.dart
│ │ │ └── widgets/
│ │ │ ├── cyber_menu.dart Cyber-Bento navigation drawer
│ │ │ ├── glass_app_bar.dart Glassmorphic URL/search bar
│ │ │ ├── glass_container.dart
│ │ │ └── link_context_menu_sheet.dart
│ │ └── features/
│ │ ├── splash/screens/splash_screen.dart
│ │ ├── screenshot_pro/
│ │ │ ├── controllers/screenshot_pro_controller.dart
│ │ │ ├── models/screenshot_capture_result.dart
│ │ │ ├── services/
│ │ │ │ ├── screenshot_capture_service.dart (viewport PNG)
│ │ │ │ ├── full_page_capture_service.dart (scroll stitch)
│ │ │ │ ├── screenshot_pdf_export_service.dart (A4 PDF export)
│ │ │ │ ├── pdf_export_service.dart
│ │ │ │ └── screenshot_pro_settings_service.dart
│ │ │ ├── screens/screenshot_pro_sheet.dart
│ │ │ └── widgets/
│ │ │ ├── screenshot_floating_button.dart
│ │ │ ├── screenshot_options_sheet.dart
│ │ │ ├── screenshot_option_tile.dart
│ │ │ └── capture_progress_dialog.dart
│ │ ├── web_apps/
│ │ │ ├── controllers/web_app_installer_controller.dart
│ │ │ └── services/web_app_shortcut_channel.dart
│ │ ├── permissions/
│ │ │ ├── controllers/website_permissions_controller.dart
│ │ │ ├── models/
│ │ │ │ ├── permission_enums.dart
│ │ │ │ └── website_permission_rule.dart
│ │ │ ├── services/
│ │ │ │ ├── website_permission_manager.dart
│ │ │ │ ├── website_permission_storage_service.dart
│ │ │ │ └── domain_normalizer.dart
│ │ │ ├── screens/
│ │ │ │ ├── website_permissions_screen.dart
│ │ │ │ └── permission_category_screen.dart
│ │ │ └── widgets/
│ │ │ ├── permission_request_dialog.dart
│ │ │ ├── permission_site_tile.dart
│ │ │ ├── permission_status_selector.dart
│ │ │ └── permission_summary_card.dart
│ │ ├── usage/
│ │ │ ├── controllers/usage_controller.dart
│ │ │ ├── models/
│ │ │ │ ├── usage_entry.dart
│ │ │ │ └── usage_period.dart
│ │ │ ├── services/
│ │ │ │ ├── domain_usage_normalizer.dart
│ │ │ │ ├── usage_format_service.dart
│ │ │ │ ├── usage_storage_service.dart
│ │ │ │ └── usage_tracking_service.dart
│ │ │ ├── screens/usage_screen.dart
│ │ │ └── widgets/
│ │ │ ├── usage_period_filter.dart
│ │ │ ├── usage_progress_bar.dart
│ │ │ ├── usage_summary_card.dart
│ │ │ └── usage_website_tile.dart
│ │ ├── extensions/
│ │ │ ├── ad_blocker/
│ │ │ │ ├── models/ad_block_stats_model.dart
│ │ │ │ ├── services/
│ │ │ │ │ ├── ad_block_service.dart
│ │ │ │ │ ├── ad_block_rule_engine.dart (35+ regex rules)
│ │ │ │ │ ├── ad_block_stats_service.dart (analytics)
│ │ │ │ │ ├── youtube_ad_blocker_service.dart
│ │ │ │ │ └── cosmetic_filter_injector.dart
│ │ │ │ └── widgets/ad_block_settings_stats_widget.dart
│ │ │ ├── background_player/
│ │ │ │ ├── background_player_service.dart
│ │ │ │ └── platform/background_player_channel.dart
│ │ │ ├── dev_tools/
│ │ │ │ ├── dev_tools_controller.dart
│ │ │ │ ├── dev_tools_extension.dart
│ │ │ │ ├── dev_tools_models.dart
│ │ │ │ ├── dev_tools_service.dart
│ │ │ │ └── widgets/
│ │ │ ├── floating_videos/ [Scaffolded — not yet implemented]
│ │ │ └── widgets/extension_overview_dialog.dart
│ │ ├── video_downloader/
│ │ │ ├── controllers/download_controller.dart
│ │ │ ├── models/
│ │ │ │ ├── current_playing_video.dart
│ │ │ │ ├── download_request.dart
│ │ │ │ ├── downloaded_video.dart
│ │ │ │ └── video_format.dart
│ │ │ ├── services/
│ │ │ │ ├── video_detection_service.dart
│ │ │ │ ├── download_api_service.dart
│ │ │ │ ├── format_mapper_service.dart
│ │ │ │ ├── local_storage_service.dart
│ │ │ │ ├── media_store_service.dart
│ │ │ │ └── url_sanitizer_service.dart
│ │ │ └── widgets/
│ │ │ ├── floating_download_button.dart
│ │ │ └── quality_selector_sheet.dart
│ │ ├── download_library/screens/downloads_screen.dart
│ │ ├── video_player/screens/
│ │ └── settings/screens/
│ │ ├── settings_screen.dart
│ │ └── developer_info_screen.dart
│ ├── assets/logo.png
│ └── android/app/src/main/kotlin/com/example/zyro/
│ ├── MainActivity.kt Platform channel hub
│ ├── BackgroundPlayerService.kt Android foreground media service
│ └── BackgroundPlayerConfirmActivity.kt
│
└── ⚙️ zyro-backend/ Node.js media microservice
├── src/
│ ├── server.js Express entry (port 3000)
│ ├── routes/download.routes.js
│ ├── controllers/download.controller.js
│ ├── services/
│ │ ├── videoExtractor.service.js (yt-dlp metadata extraction)
│ │ ├── formatSelector.service.js (adaptive format selection)
│ │ ├── downloadManager.service.js (stream download executor)
│ │ ├── merge.service.js (FFmpeg merge + MP3 conversion)
│ │ ├── fileManager.service.js (paths, dirs, verification)
│ │ └── urlSanitizer.service.js (URL cleaning)
│ └── middleware/errorHandler.js
├── downloads/ Completed files (static)
├── temp/ FFmpeg workspace
└── package.json
Zyro follows a feature-first, provider-driven architecture:
main.dart → MultiProvider → ZyroApp → SplashScreen → BrowserMainScreen
│
├── ThemeController theme mode persistence
├── TabManager tabs + groups + session
├── BrowserDataManager history, bookmarks, downloads
├── ExtensionManager extension registry + lifecycle
├── DownloadController download state + polling
├── DevToolsController dev tools state
├── AdBlockStatsService ad block analytics
├── ScreenshotProController screenshot enable/expand state
├── WebAppInstallerController installed apps + shortcut sync
├── WebsitePermissionsController per-site permission rules
└── UsageTrackingService local usage estimates by domain
State Management: Flutter Provider / ChangeNotifier throughout.
Persistence: SharedPreferences for all state (tabs, extensions, theme, permissions, ad block stats, screenshot settings, web apps, usage analytics).
Navigation: Named navigatorKey with globalScaffoldKey for cross-context snackbar delivery.
Font: Outfit (Google Fonts) via GoogleFonts.outfit().
Icons: lucide_icons for consistent iconography.
All Flutter↔Android communication is via MethodChannel in MainActivity.kt:
| Channel | Direction | Methods |
|---|---|---|
zyro/downloads |
Flutter→Native | enqueueDownload, queryDownload |
zyro/screenshot_pro |
Flutter→Native | exportWebViewPdf |
zyro/web_apps |
Bidirectional | pinWebAppShortcut, getPinnedShortcutIds, getInitialShortcutUrl ↙ webAppShortcutLaunched |
zyro/background_player |
Bidirectional | startService, updateState, stopService ↙ play, pause, next, previous, seekTo |
| Token | Light | Dark |
|---|---|---|
| Background | #F8FAFC |
#0B0F19 |
| Surface | #F1F5F9 |
#161F30 |
| Card | #FFFFFF |
#1E293B |
| Primary | #4F46E5 Indigo |
#6366F1 Indigo |
| Secondary | #0D9488 Teal |
#14B8A6 Teal |
| Accent | #06B6D4 Cyan |
#22D3EE Cyan |
| Danger | #EF4444 |
#EF4444 |
| Component | Description |
|---|---|
GlassAppBar |
Glassmorphic address/search bar with smart URL/search routing |
PageQrCodeDialog |
Compact current-page QR popup launched from the address bar |
CyberMenu |
Slide-out Bento navigation drawer with logo, quick actions, and nav links |
GlassContainer |
Reusable frosted-glass surface primitive |
TabSwitcherScreen |
Full-screen tab manager with group support and undo close |
LinkContextMenuSheet |
Long-press sheet: open, new tab, copy, share, download, inspect |
FloatingDownloadButton |
Animated FAB shown when a downloadable video is detected |
ScreenshotFloatingButton |
Expandable FAB with mini-actions for viewport and full-page capture |
QualitySelectorSheet |
Format/quality picker bottom sheet for video downloads |
- Light Theme — Clean white/slate with indigo primary accents
- Dark Theme — Deep navy/slate with indigo/teal/cyan neon accents
- Incognito Theme — Forced dark mode with session isolation
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/video/metadata |
Extract video metadata + format list via yt-dlp |
POST |
/api/video/download |
Start async download task; returns taskId |
GET |
/api/video/status/:taskId |
Poll task state and progress |
GET |
/downloads/:filename |
Serve completed media file statically |
GET |
/ |
Health check |
extracting → downloading_video → downloading_audio → merging → completed
↘ failed
| Mode | Input | Operation | Output |
|---|---|---|---|
| Video + Audio | Separate adaptive streams | -c:v copy -c:a aac -shortest |
.mp4 / .webm |
| Audio Only | Audio stream | -acodec libmp3lame -ab 320k |
.mp3 |
| Progressive | Single stream (video+audio) | Direct download, no FFmpeg | .mp4 |
User taps Screenshot FAB
│
├──▶ Viewport Screenshot
│ controller.takeScreenshot() → PNG bytes
│ MediaStoreService.getSaveDirectoryPath('screenshots')
│ File.writeAsBytes() → SnackBar notification
│
└──▶ Full Page Capture
├── Save current scrollY
├── Inject scroll-behavior:auto style override
├── Calculate scroll positions (viewport-step increments)
├── For each offset: scroll → wait 420ms → takeScreenshot()
├── img.decodePng() → stitch: img.copyCrop() + compositeImage()
├── Restore scrollY + remove style override
└── Save stitched PNG
│
└──▶ PDF Export (optional)
├── Calculate A4 printable area + scale
├── Slice image into page-height strips
├── pw.Document().addPage() per strip
└── File.writeAsBytes(pdf.save())
Page load → WebAppInstallerController detects manifest
├── Fetch <link rel="manifest"> via JS evaluation
├── HTTP GET manifest.json → parse name, icons, start_url, scope
├── Select best icon → HTTP download → local file cache
└── Bitmap decode + normalize (blank/too-small detection)
User taps "Add to Home Screen"
└── zyro/web_apps → pinWebAppShortcut(id, name, url, iconPath)
└── MainActivity.kt:
├── ShortcutManager.isRequestPinShortcutSupported
├── BitmapFactory.decodeFile(iconPath)
├── normalizeShortcutBitmap() — resize + round-rect clip
├── ShortcutInfo.Builder → setIntent(openWebAppAction)
└── ShortcutManager.requestPinShortcut()
Shortcut tap on home screen
└── MainActivity.onCreate / onNewIntent
├── handleWebAppShortcutIntent() → extract web_app_url
├── pendingWebAppUrl cached until Flutter engine ready
└── webAppChannel.invokeMethod("webAppShortcutLaunched", {url})
└── WebAppShortcutLaunchBridge → TabManager.openUrl()
WebView shouldInterceptRequest(url)
└── AdBlockService.interceptRequest(url, requestType, sourceUrl)
├── [extension disabled] → return null (allow)
└── AdBlockRuleEngine.match(url)
├── Normalize to lowercase; extract sourceDomain
├── Iterate 35+ compiled RegExp rules
├── [matched] → AdBlockStatsService.recordBlockedEvent(url)
│ ├── domainBlockedCounts[domain]++
│ ├── totalBlocked++ / todayBlocked++
│ └── SharedPreferences save (async)
└── [not matched] → return null (allow)
WebView onPageStarted / onPageFinished / onUrlChanged
└── ScriptEngine → AdBlockService.getInjectedScripts(url)
├── [youtube.com] → YouTubeAdBlockerService.cosmeticScript
└── [other] → CosmeticFilterInjector.cosmeticScript
WebView shouldInterceptRequest(url)
├── AdBlockService.interceptRequest(url)
├── [blocked] → do not count usage
└── [allowed] → UsageTrackingService.observeRequest(url, sourceUrl)
├── DomainUsageNormalizer.normalize(url)
│ └── www.youtube.com / m.youtube.com → youtube.com
├── Content-Length if available, otherwise safe byte estimate
├── De-duplicate same URL estimate within the current minute
├── UsageEntry.record()
│ ├── totalBytes
│ ├── monthlyBytes
│ ├── todayBytes
│ ├── requestCount
│ └── lastVisitedAt
└── UsageStorageService → SharedPreferences
Settings → Usage
├── Total data this month
├── Total data today
├── Today / This Month / All Time filters
├── Top domains sorted by usage
└── Clear usage data confirmation
Usage values are estimates when response sizes are not exposed by the WebView request callback. Downloads and media requests observed inside the WebView are attributed to their source/request domain; internal app routes, local files, extension assets, screenshots, QR generation, and app UI assets are skipped.
Privacy note: Usage Analytics are stored locally on your device and are used only to show your browsing data usage inside Zyro.
Flutter BackgroundPlayerService.dart
└── BackgroundPlayerChannel → zyro/background_player → startService
└── BackgroundPlayerService.kt (Android)
├── PowerManager.WakeLock (30 min, PARTIAL_WAKE_LOCK)
├── NotificationChannel (IMPORTANCE_LOW)
└── MediaSession("ZyroMediaSession")
├── setMetadata(title, website, duration)
├── setPlaybackState(position)
└── Callback: onPlay/onPause/onNext/onPrev/onSeekTo
→ onMediaAction → invokeMethod → Flutter
→ JS: media.play() / media.pause() /
nextBtn.click() / media.currentTime
VideoDetectionService.detectionScript → DOM polls <video> elements
└── videoDetected event → Flutter → FloatingDownloadButton appears
User ▼ → QualitySelectorSheet → selects format + mode
DownloadController → POST /api/video/download
└── Node.js:
├── yt-dlp: extractMetadata(url) → formats[]
├── formatSelector.selectFormats(formatId, mode)
├── [audio] downloadStream → convertToMp3 (FFmpeg)
├── [progressive] downloadStream (no merge)
└── [adaptive] downloadStream(video) + downloadStream(audio)
→ mergeStreams (FFmpeg)
→ verifyVideoHeight()
▼ task.state = 'completed'
Flutter polls GET /api/video/status/:taskId
└── BrowserDataManager → zyro/downloads → Android DownloadManager → MediaStore
WebViewWrapper onPermissionRequest(origin, resources[])
└── WebsitePermissionManager.resolve(context, origin, permissionType)
├── DomainNormalizer.normalize(origin) → domain
├── WebsitePermissionsController.ruleFor(domain, type)
├── [block] → return false (deny silently)
├── [allow] → requestAndroidPermission → return result
└── [null/ask] → PermissionRequestDialog.show()
└── User: Allow / Ask / Block
→ WebsitePermissionsController.upsert(rule)
→ SharedPreferences
→ requestAndroidPermission(type)
| Setting | Value |
|---|---|
| Engine | flutter_inappwebview v6.1.5 |
| JavaScript | Enabled |
| JS Handlers | videoStateUpdate, mediaProgress, devToolsLog |
| Request Interception | shouldOverrideUrlLoading + shouldInterceptRequest |
| Page Lifecycle Hooks | onPageStarted, onProgressChanged, onPageFinished, onUpdateVisitedHistory |
| Popup Handling | onCreateWindow → new tab |
| Permission Handling | onPermissionRequest → WebsitePermissionManager |
| Context Menu | onContextMenuActionItemClicked → LinkContextMenuSheet |
| User Agent | Default; per-tab desktop mode toggle |
| Requirement | Version |
|---|---|
| Flutter SDK | ^3.10 (Dart ^3.10.7) |
| Android SDK | minSdk 21, targetSdk 34+ |
| Node.js | 18+ |
| FFmpeg | Latest stable — must be on system PATH |
| yt-dlp | Auto-installed via youtube-dl-exec npm package |
| Device | Android physical/emulator with USB Debugging |
git clone https://github.com/Ashish6298/zyro.git
cd zyrocd zyro-backend
npm install
npm run dev
# Zyro Downloader Backend running on port 3000FFmpeg must be on
PATH. Verify withffmpeg -version.
cd zyro-frontend
flutter pub get
flutter runBackend URL: On a physical device, update
DownloadApiServicebase URL fromlocalhostto your development machine's LAN IP address.
cd zyro-frontend
flutter build apk --release
# Output: build/app/outputs/flutter-apk/app-release.apk
|
|
|
| Feature | Implementation |
|---|---|
| Incognito mode | Global toggle — separate ThemeMode, session not persisted |
| Ad/tracker blocking | 35+ regex rules at WebView request interception level |
| Per-site permissions | Granular allow/ask/block per domain per resource type |
| Usage analytics | Stored locally on device via SharedPreferences; never sent to a server |
| Download URL sanitization | Strip UTM/tracking params before yt-dlp extraction |
| Playlist download rejection | Reject YouTube playlist URLs without v= parameter |
| Video height validation | FFmpeg-verified output resolution after merge |
| Shortcut URL validation | Only http:// and https:// accepted for web app shortcuts |
| Layer | Package / Technology | Role |
|---|---|---|
| Flutter | flutter_inappwebview ^6.1.5 |
WebView engine |
| Flutter | provider ^6.1.2 |
State management |
| Flutter | google_fonts ^6.2.1 |
Typography (Outfit) |
| Flutter | lucide_icons ^0.257.0 |
Icon set |
| Flutter | shared_preferences ^2.5.5 |
Local persistence |
| Flutter | path_provider ^2.1.2 |
File system paths |
| Flutter | permission_handler ^11.4.0 |
Android permissions |
| Flutter | image ^4.8.0 |
PNG decode/encode/stitch |
| Flutter | pdf ^3.11.3 |
PDF document generation |
| Flutter | qr_flutter 4.1.0 |
Address-bar current-page QR rendering |
| Flutter | http ^1.2.1 |
HTTP client (manifest/icon fetch) |
| Flutter | url_launcher ^6.2.5 |
External URL launch |
| Flutter | share_plus ^7.2.2 |
Native share sheet |
| Flutter | youtube_explode_dart ^2.2.2 |
YouTube utilities |
| Flutter | uuid ^4.3.3 |
UUID generation |
| Flutter | video_player ^2.9.2 |
In-app video playback |
| Android Kotlin | MediaSession |
Lock-screen media controls |
| Android Kotlin | ShortcutManager |
Home screen shortcut pinning |
| Android Kotlin | DownloadManager |
System download integration |
| Android Kotlin | PowerManager.WakeLock |
CPU awake during background play |
| Node.js | express ^4.19.2 |
REST API server |
| Node.js | youtube-dl-exec ^3.0.2 |
yt-dlp wrapper |
| Node.js | fluent-ffmpeg ^2.1.3 |
FFmpeg merge + MP3 conversion |
| Node.js | uuid ^9.0.1 |
Task ID generation |
| Node.js | cors ^2.8.5 |
CORS for Flutter client |
Items below are not currently implemented.
- Floating Videos / PiP — directory scaffolded, no implementation yet
- Dark Reader extension — registered in registry, script not yet active
- KeyGen extension — registered in registry, not yet active
- iOS support — Flutter project scaffolded; native channels are Android-only
- Reading list & Favorites persistence (currently in-memory only)
- Bookmarks import / export
- Extension SDK for third-party extensions
- Backend rate limiting and authentication
- iOS: Native channel implementations (
BackgroundPlayerService,ShortcutManager,DownloadManager) are Android-specific. The app will not function correctly on iOS. - Floating Videos (PiP):
floating_videosdirectory is scaffolded but contains no implementation files. - Reading List & Favorites: In-memory only — resets on app restart.
- Backend Reachability: Physical devices must point
DownloadApiServiceto the LAN IP instead oflocalhost. - yt-dlp Maintenance: Depends on yt-dlp to parse platforms. Frequent YouTube changes may temporarily break extraction.
- FFmpeg PATH:
fluent-ffmpegrequires FFmpeg on the systemPATH; fails silently if absent.
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature-name - Commit your changes
- Push and open a Pull Request against
main
Please follow the existing feature-first directory structure and document any new platform channel methods in this README.
Website Vault is a local-first organization feature that gives every website/domain its own vault for important saved material. Users can keep website-related pages, links, screenshots, PDFs, downloads, notes, receipts, invoices, tracking pages, order pages, and other useful references grouped by domain instead of manually searching across downloads, history, bookmarks, and screenshots.
| Capability | Description |
|---|---|
| Quick Actions entry | Website Vault is available from the existing Quick Actions page with the same Cyber-Bento visual language |
| Domain dashboard | Lists websites/domains with saved item counts, latest activity, storage usage, favicons, and category chips |
| Domain details | Shows saved vault items grouped by Screenshots, PDFs, Links, Downloads, Notes, Receipts/Invoices, and Pages |
| Search and filtering | Search by domain, title, URL, tags, note text, and saved item name; filter items by type inside a domain |
| Save current page | Save the active page/link directly into the current website vault |
| Screenshot Pro integration | Saved screenshots and PDFs can be associated with the source website vault |
| Download integration | Downloaded files can be linked to the source website when the source URL is available |
| Manual entries | Add notes and useful links directly from the Website Vault screen |
| Item actions | Open, Share, Rename, Delete, and Copy Link where applicable |
| Safe deletion | Deleting a vault item removes only the vault entry unless the user explicitly confirms local file deletion |
Website Vault uses WebsiteVaultItem, WebsiteVaultDomainSummary, and WebsiteVaultType models under lib/features/website_vault/models/.
Metadata is persisted locally through the app's existing local persistence approach, and vault files are organized under a clear domain-based folder structure such as:
Zyro/Vault/<domain>/
Website Vault is stored locally on your device. Vault metadata and saved files are not uploaded to a server.
This project is licensed under the MIT License — see the LICENSE file for full details.
You're free to use, modify, and distribute this code, provided the original copyright notice is retained.
"Passionate about building modern applications, browser technologies, and user-focused digital products."