From 64f8b3038e670241d75e7148f5f9e1482f86dca7 Mon Sep 17 00:00:00 2001 From: Ujwal223 Date: Thu, 29 Oct 2026 21:45:03 +0545 Subject: [PATCH] Release Focusgram V3: - NEW: AD BLOCKER!! - NEW: Custom App Icons. - NEW: Search Story Viewer List. - NEW: Custom Fonts in Bio. - NEW: Floating bottom Navigation Bar. - NEW: Remove Stories Tray. - NEW: Fingerprint Support and more.... See CHANGELOG.md for Full Changelog. --- .github/workflows/codeql.yml | 65 + .github/workflows/native-release.yml | 8 +- .github/workflows/release-apk.yml | 91 +- .gitignore | 40 +- .metadata | 30 - CHANGELOG.md | 52 +- CONTRIBUTING.md | 72 + Ported/.gitignore | 35 - .../assets/scripts/reel_metadata_extractor.js | 167 - .../main/assets/scripts/stealth_instagram.js | 227 -- .../feature/diagnostics/DebugMenuScreen.kt | 178 - .../focusgram/feature/history/ReelHistory.kt | 212 -- .../feature/screentime/ScreenTimeService.kt | 295 -- .../focusgram/feature/wellness/BaitEngine.kt | 99 - .../feature/wellness/BaitMeButton.kt | 177 - .../feature/wellness/BaitMeFullScreen.kt | 214 -- .../focusgram/feature/wellness/CreditStore.kt | 53 - .../com/ujwal/focusgram/BaitEngineTest.kt | 33 - .../com/ujwal/focusgram/CreditStoreTest.kt | 64 - .../ujwal/focusgram/EffortFrictionGateTest.kt | 46 - .../focusgram/NavigationContainmentTest.kt | 68 - .../ujwal/focusgram/UserScriptAssetsTest.kt | 42 - README.md | 302 +- SECURITY.md | 57 + analysis_options.yaml | 32 - android/.gitignore | 16 - android/NOTICE | 190 - android/app/build.gradle.kts | 80 - android/app/proguard-rules.pro | 37 - android/app/src/debug/AndroidManifest.xml | 7 - android/app/src/main/AndroidManifest.xml | 74 - .../com/ujwal/focusgram/MainActivity.kt | 5 - .../drawable-hdpi/ic_launcher_foreground.png | Bin 12888 -> 0 bytes .../drawable-mdpi/ic_launcher_foreground.png | Bin 6670 -> 0 bytes .../res/drawable-v21/launch_background.xml | 12 - .../drawable-xhdpi/ic_launcher_foreground.png | Bin 20664 -> 0 bytes .../ic_launcher_foreground.png | Bin 41138 -> 0 bytes .../ic_launcher_foreground.png | Bin 66281 -> 0 bytes .../main/res/drawable/launch_background.xml | 12 - .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3577 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 1907 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 5569 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 10729 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 17094 -> 0 bytes .../app/src/main/res/values-night/styles.xml | 18 - android/app/src/main/res/values/colors.xml | 4 - android/app/src/main/res/values/styles.xml | 18 - .../main/res/xml/network_security_config.xml | 15 - android/app/src/profile/AndroidManifest.xml | 8 - android/build.gradle.kts | 24 - .../reports/problems/problems-report.html | 663 ---- android/gradle.properties | 6 - .../gradle/wrapper/gradle-wrapper.properties | 5 - android/popup.json | 14 +- android/settings.gradle.kts | 26 - {Ported/app => app}/build.gradle.kts | 50 +- {Ported/app => app}/proguard-rules.pro | 0 .../app => app}/src/main/AndroidManifest.xml | 28 +- .../main/assets/adi-registration.properties | 1 + .../main/assets/extensions/ublock/LICENSE.txt | 0 .../main/assets/extensions/ublock/README.md | 0 .../assets/extensions/ublock/background.js | 48 +- .../main/assets/extensions/ublock/content.js | 46 +- .../extensions/ublock/focusgram_content.js | 759 +++- .../main/assets/extensions/ublock/ghost.js | 0 .../assets/extensions/ublock/manifest.json | 5 +- .../extensions/ublock}/media_downloader.js | 110 +- {Ported/app => app}/src/main/assets/omni.ja | Bin .../src/main/assets/reelhistory/bg.js | 0 .../src/main/assets/reelhistory/manifest.json | 0 .../src/main/assets/reelhistory/reels.js | 109 +- .../src/main/assets/reellock/bg.js | 6 +- .../src/main/assets/reellock/manifest.json | 0 .../src/main/assets/reellock/reellock.js | 19 +- .../main/assets/scripts/autoplay_blocker.js | 32 +- .../src/main/assets/scripts/content_hider.js | 144 +- .../main/assets/scripts/fetch_interceptor.js | 116 +- .../src/main/assets/scripts/ghost.js | 0 .../src/main/assets/scripts/grayscale.js | 13 +- .../main/assets/scripts/media_downloader.js | 1006 ++++++ .../src/main/assets/scripts/native_feel.js | 104 +- .../assets/scripts/reel_metadata_extractor.js | 162 + .../assets/scripts/spa_navigation_monitor.js | 13 +- .../main/assets/scripts/stealth_instagram.js | 358 ++ .../com/ujwal/focusgram/FocusGramApp.kt | 13 +- .../com/ujwal/focusgram/MainActivity.kt | 18 +- .../com/ujwal/focusgram/core/database/Daos.kt | 0 .../ujwal/focusgram/core/database/Entities.kt | 16 +- .../core/database/FocusGramDatabase.kt | 5 +- .../datastore/LegacyFlutterDataMigrator.kt | 21 +- .../core/datastore/SettingsRepository.kt | 147 +- .../focusgram/core/engine/ExtensionManager.kt | 28 +- .../engine/FocusGramNavigationDelegate.kt | 40 +- .../engine/FocusGramPermissionDelegate.kt | 11 +- .../core/engine/FocusGramPromptDelegate.kt | 7 +- .../core/engine/GeckoResultExtensions.kt | 6 +- .../core/engine/GeckoRuntimeManager.kt | 10 +- .../core/engine/GeckoSessionManager.kt | 56 +- .../core/engine/UserScriptManager.kt | 78 +- .../focusgram/core/model/FocusSettings.kt | 42 +- .../ujwal/focusgram/core/model/IslandMode.kt | 34 +- .../core/security/EncryptedStorage.kt | 22 +- .../ui/components/CupertinoWheelPicker.kt | 95 +- .../core/ui/components/FocusGramComponents.kt | 34 +- .../ui/components/IOSSettingsComponents.kt | 115 +- .../core/ui/components/SkeletonScreen.kt | 7 +- .../ujwal/focusgram/core/ui/theme/Color.kt | 11 +- .../ujwal/focusgram/core/ui/theme/Motion.kt | 14 +- .../ujwal/focusgram/core/ui/theme/Shape.kt | 1 - .../ujwal/focusgram/core/ui/theme/Theme.kt | 0 .../com/ujwal/focusgram/core/ui/theme/Type.kt | 0 .../focusgram/core/utils/AppIconManager.kt | 58 +- .../focusgram/core/utils/HapticHelper.kt | 18 +- .../core/utils/HistoricalIconManager.kt | 11 +- .../core/utils/MediaDownloadHelper.kt | 24 +- .../focusgram/core/utils/TimeFormatters.kt | 35 + .../feature/applock/AppLockScreen.kt | 9 +- .../feature/applock/AppLockSettingsPage.kt | 3 + .../feature/applock/AppLockSetupScreen.kt | 5 +- .../feature/applock/ChatLockScreen.kt | 13 +- .../feature/diagnostics/DebugMenuScreen.kt | 334 ++ .../focusgram/feature/history/ReelHistory.kt | 360 ++ .../feature/history/ReelMessageDelegate.kt | 4 + .../feature/history/ReelsHistoryRepository.kt | 9 +- .../feature/history/ReelsHistoryScreen.kt | 224 +- .../feature/navigation/InstagramIcons.kt | 18 +- .../feature/navigation/NativeBottomNavBar.kt | 140 +- .../feature/navigation/ReelsTopBar.kt | 8 +- .../feature/notice/RemoteNoticeService.kt | 113 + .../feature/onboarding/OnboardingScreen.kt | 1000 ++++++ .../feature/screentime/ScreenTimeScreen.kt | 610 ++-- .../feature/screentime/ScreenTimeService.kt | 437 +++ .../feature/session/AppSessionPicker.kt | 9 +- .../feature/session/DailyLimitGateScreen.kt | 301 ++ .../feature/session/SessionExhaustedScreen.kt | 49 +- .../focusgram/feature/session/SessionModal.kt | 3 +- .../feature/settings/SettingsScreen.kt | 809 +++-- .../snapshots/SnapshotManagerScreen.kt | 5 +- .../focusgram/feature/update/UpdateBanner.kt | 236 ++ .../feature/update/UpdateCheckerService.kt | 159 + .../focusgram/feature/update/UpdateInfo.kt | 18 + .../feature/webview/GeckoViewContainer.kt | 11 +- .../feature/wellness/AdsterraAdScreen.kt | 110 +- .../feature/wellness/BreathGateScreen.kt | 6 +- .../feature/wellness/CooldownGateScreen.kt | 56 +- .../wellness/DisciplineMantraRepository.kt | 214 ++ .../feature/wellness/EffortFrictionGate.kt | 86 +- .../wellness/GuardrailsDashboardScreen.kt | 42 +- .../feature/wellness/TimerFallbackScreen.kt | 8 +- .../com/ujwal/focusgram/ui/FocusGramApp.kt | 654 +++- .../drawable-hdpi/ic_launcher_foreground.png | Bin .../drawable-mdpi/ic_launcher_foreground.png | Bin .../res/drawable-v21/launch_background.xml | 0 .../drawable-xhdpi/ic_launcher_foreground.png | Bin .../ic_launcher_foreground.png | Bin .../ic_launcher_fg_abstract.png | Bin .../ic_launcher_fg_forest.png | Bin .../ic_launcher_fg_golden.png | Bin .../drawable-xxxhdpi/ic_launcher_fg_metal.png | Bin .../ic_launcher_fg_minimalist.png | Bin .../ic_launcher_fg_polaroid.png | Bin .../ic_launcher_fg_redesign.png | Bin .../ic_launcher_fg_scribble.png | Bin .../ic_launcher_fg_vibrant.png | Bin .../ic_launcher_fg_viewfinder.png | Bin .../ic_launcher_foreground.png | Bin .../main/res/drawable/ic_chat_lock_closed.xml | 0 .../main/res/drawable/ic_chat_lock_open.xml | 0 .../res/drawable/ic_credit_downloader.png | Bin .../main/res/drawable/ic_credit_geckoview.xml | 0 .../main/res/drawable/ic_credit_iconify.xml | 0 .../drawable/ic_credit_jetpack_compose.xml | 0 .../main/res/drawable/ic_credit_sqlite.xml | 0 .../main/res/drawable/ic_credit_ublock.xml | 0 .../main/res/drawable/ic_download_fill.xml | 0 .../res/drawable/ic_launcher_bg_instagram.xml | 0 .../res/drawable/ic_launcher_fg_all_white.xml | 0 .../res/drawable/ic_launcher_fg_classic.xml | 0 .../res/drawable/ic_launcher_fg_discreet.xml | 0 .../ic_launcher_fg_instagram_official.xml | 0 .../main/res/drawable/ic_launcher_fg_mono.xml | 0 .../main/res/drawable/ic_launcher_fg_oled.xml | 0 .../drawable/ic_launcher_fg_pitch_black.xml | 0 .../src/main/res/drawable/ic_notification.xml | 0 .../res/mipmap-anydpi-v26/ic_launcher.xml | 0 .../ic_launcher_abstract.xml | 0 .../ic_launcher_all_white.xml | 0 .../mipmap-anydpi-v26/ic_launcher_classic.xml | 0 .../ic_launcher_discreet.xml | 0 .../mipmap-anydpi-v26/ic_launcher_forest.xml | 0 .../mipmap-anydpi-v26/ic_launcher_golden.xml | 0 .../ic_launcher_instagram_official.xml | 0 .../mipmap-anydpi-v26/ic_launcher_metal.xml | 0 .../ic_launcher_minimalist.xml | 0 .../mipmap-anydpi-v26/ic_launcher_mono.xml | 0 .../mipmap-anydpi-v26/ic_launcher_oled.xml | 0 .../ic_launcher_pitch_black.xml | 0 .../ic_launcher_polaroid.xml | 0 .../ic_launcher_redesign.xml | 0 .../ic_launcher_scribble.xml | 0 .../mipmap-anydpi-v26/ic_launcher_vibrant.xml | 0 .../ic_launcher_viewfinder.xml | 0 .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../mipmap-xxxhdpi/ic_launcher_abstract.png | Bin .../res/mipmap-xxxhdpi/ic_launcher_forest.png | Bin .../res/mipmap-xxxhdpi/ic_launcher_golden.png | Bin .../res/mipmap-xxxhdpi/ic_launcher_metal.png | Bin .../mipmap-xxxhdpi/ic_launcher_minimalist.png | Bin .../mipmap-xxxhdpi/ic_launcher_polaroid.png | Bin .../mipmap-xxxhdpi/ic_launcher_redesign.png | Bin .../mipmap-xxxhdpi/ic_launcher_scribble.png | Bin .../mipmap-xxxhdpi/ic_launcher_vibrant.png | Bin .../mipmap-xxxhdpi/ic_launcher_viewfinder.png | Bin .../src/main/res/values/colors.xml | 0 .../src/main/res/values/strings.xml | 0 .../src/main/res/values/themes.xml | 0 .../src/main/res/xml/file_paths.xml | 0 .../main/res/xml/network_security_config.xml | 12 + .../com/ujwal/focusgram/AdsterraAdFlowTest.kt | 37 +- .../ujwal/focusgram/AppLockSecurityTest.kt | 8 +- .../focusgram/ArchitectureIntegrityTest.kt | 21 +- .../focusgram/AuthNavBarSuppressionTest.kt | 81 + .../focusgram/BlockDmReelsSwipingTest.kt | 5 +- .../com/ujwal/focusgram/BreathGateTest.kt | 2 - .../ujwal/focusgram/ChatLockAndAppIconTest.kt | 14 +- .../com/ujwal/focusgram/CooldownGateTest.kt | 4 +- .../ujwal/focusgram/EffortFrictionGateTest.kt | 84 + .../FocusFeaturesEnhancementsTest.kt | 310 +- .../com/ujwal/focusgram/FocusSettingsTest.kt | 20 +- .../com/ujwal/focusgram/IslandModeTest.kt | 8 - .../focusgram/LegacyMigrationLogicTest.kt | 0 .../focusgram/NavigationContainmentTest.kt | 107 + .../ujwal/focusgram/OnboardingScreenTest.kt | 46 + .../focusgram/ReelLockExtensionAssetsTest.kt | 0 .../ujwal/focusgram/ReelsHistoryLogicTest.kt | 91 +- .../ujwal/focusgram/ScreenTimeTrackerTest.kt | 10 +- .../ujwal/focusgram/SnapshotsEntityTest.kt | 0 .../TwoTierLimitsAndEnhancementsTest.kt | 313 ++ .../focusgram/UBlockExtensionAssetsTest.kt | 0 .../com/ujwal/focusgram/UpdateCheckerTest.kt | 32 + .../ujwal/focusgram/UserScriptAssetsTest.kt | 64 + assets/images/app-demo.png | Bin 410926 -> 874151 bytes build-release.sh | 53 + Ported/build.gradle.kts => build.gradle.kts | 0 devtools_options.yaml | 3 - Ported/gradle.properties => gradle.properties | 0 {Ported/gradle => gradle}/libs.versions.toml | 0 .../wrapper/gradle-wrapper.jar | Bin .../wrapper/gradle-wrapper.properties | 0 Ported/gradlew => gradlew | 0 Ported/gradlew.bat => gradlew.bat | 0 lib/features/loading/skeleton_screen.dart | 532 --- .../native_nav/native_bottom_nav.dart | 161 - .../preloader/instagram_preloader.dart | 68 - .../reels_history/reels_history_screen.dart | 252 -- .../reels_history/reels_history_service.dart | 170 - .../screen_time/screen_time_screen.dart | 307 -- .../update_checker/update_banner.dart | 128 - .../update_checker_service.dart | 106 - lib/focus_settings.dart | 17 - lib/main.dart | 242 -- lib/screens/adsterra_ad_screen.dart | 320 -- lib/screens/app_lock_screen.dart | 311 -- lib/screens/app_lock_settings_page.dart | 225 -- lib/screens/app_lock_setup_screen.dart | 151 - lib/screens/app_session_picker.dart | 226 -- lib/screens/bait_me_button.dart | 266 -- lib/screens/bait_me_full_screen.dart | 254 -- lib/screens/breath_gate_screen.dart | 147 - lib/screens/cooldown_gate_screen.dart | 170 - lib/screens/debug_menu_screen.dart | 342 -- lib/screens/effort_friction_gate.dart | 325 -- lib/screens/extras_settings_page.dart | 188 - lib/screens/ghost_mode_submenu_page.dart | 163 - lib/screens/guardrails_page.dart | 458 --- lib/screens/level_panel_screen.dart | 537 --- lib/screens/main_webview_page.dart | 3144 ----------------- lib/screens/offline_feed_viewer.dart | 91 - lib/screens/onboarding_page.dart | 436 --- lib/screens/reel_player_overlay.dart | 134 - lib/screens/session_modal.dart | 143 - lib/screens/settings_page.dart | 1424 -------- lib/screens/snapshot_manager_screen.dart | 329 -- lib/screens/timer_fallback_screen.dart | 198 -- lib/scripts/autoplay_blocker.dart | 258 -- lib/scripts/content_disabling.dart | 648 ---- lib/scripts/core_injection.dart | 587 --- lib/scripts/dm_keyboard_fix.dart | 15 - lib/scripts/focus_scripts.dart | 463 --- lib/scripts/grayscale.dart | 48 - lib/scripts/haptic_bridge.dart | 11 - lib/scripts/native_feel.dart | 68 - lib/scripts/reel_metadata_extractor.dart | 118 - lib/scripts/scroll_smoothing.dart | 12 - lib/scripts/spa_navigation_monitor.dart | 31 - lib/scripts/ui_hider.dart | 263 -- lib/scripts/video_downloader.dart | 355 -- .../adblock_content_blocker_loader.dart | 430 --- lib/services/app_lock_service.dart | 185 - lib/services/bait_engine.dart | 173 - lib/services/credit_store.dart | 134 - lib/services/focusgram_router.dart | 13 - lib/services/injection_controller.dart | 104 - lib/services/injection_manager.dart | 530 --- lib/services/level_service.dart | 414 --- lib/services/navigation_guard.dart | 68 - lib/services/notification_service.dart | 172 - lib/services/remote_popup_service.dart | 82 - lib/services/screen_time_service.dart | 106 - lib/services/session_manager.dart | 636 ---- lib/services/settings_service.dart | 894 ----- lib/services/snapshot_service.dart | 123 - lib/utils/discipline_challenge.dart | 641 ---- .../script_engine_v2_overlay.dart | 141 - .../script_registry_v2_overlay.dart | 77 - lib/widgets/medium_rect_banner.dart | 79 - lib/widgets/native_ad_banner.dart | 83 - lib/widgets/remote_popup_handler.dart | 36 - pubspec.lock | 1122 ------ pubspec.yaml | 83 - ...settings.gradle.kts => settings.gradle.kts | 0 ...main_webview_page_media_download_test.dart | 65 - test/services/adblocker_test.dart | 90 - test/services/app_lock_service_test.dart | 76 - test/services/dm_ghost_injection_test.dart | 93 - test/services/focusgram_router_test.dart | 66 - .../ghost_mode_focus_scripts_test.dart | 74 - test/services/ghost_mode_settings_test.dart | 80 - .../ghost_mode_url_patterns_test.dart | 249 -- test/services/injection_manager_test.dart | 155 - test/services/level_service_test.dart | 71 - test/services/navigation_guard_test.dart | 193 - test/services/reels_blocker_test.dart | 56 - test/services/screen_time_service_test.dart | 69 - test/services/session_extension_test.dart | 76 - test/services/session_manager_test.dart | 293 -- test/services/settings_service_test.dart | 105 - v2/MainActivity.kt | 84 - v2/PHASE1_DEPS.yaml | 66 - v2/ad_blocker_dom.js | 98 - v2/assets/scripts/ad_blocker_dom.js | 100 - v2/assets/scripts/autoplay_blocker.js | 83 - v2/assets/scripts/content_hider.js | 304 -- v2/assets/scripts/fetch_interceptor.js | 281 -- v2/assets/scripts/ghost_mode.js | 256 -- v2/assets/scripts/theme_detector.js | 89 - v2/channel_registry.dart | 63 - v2/content_hider.js | 166 - v2/fetch_interceptor.js | 233 -- v2/ghost_mode.js | 179 - v2/ghost_mode_script.dart | 286 -- v2/ghost_mode_service.dart | 233 -- v2/instagram_webview.dart | 201 -- v2/main.dart | 58 - v2/script_engine.dart | 230 -- v2/script_registry.dart | 87 - v2/system_ui_manager.dart | 80 - v2/theme_detector.js | 89 - v2/webview_config.dart | 117 - 364 files changed, 9796 insertions(+), 31958 deletions(-) create mode 100644 .github/workflows/codeql.yml delete mode 100644 .metadata create mode 100644 CONTRIBUTING.md delete mode 100644 Ported/.gitignore delete mode 100644 Ported/app/src/main/assets/scripts/reel_metadata_extractor.js delete mode 100644 Ported/app/src/main/assets/scripts/stealth_instagram.js delete mode 100644 Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/diagnostics/DebugMenuScreen.kt delete mode 100644 Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelHistory.kt delete mode 100644 Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeService.kt delete mode 100644 Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BaitEngine.kt delete mode 100644 Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BaitMeButton.kt delete mode 100644 Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BaitMeFullScreen.kt delete mode 100644 Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/CreditStore.kt delete mode 100644 Ported/app/src/test/kotlin/com/ujwal/focusgram/BaitEngineTest.kt delete mode 100644 Ported/app/src/test/kotlin/com/ujwal/focusgram/CreditStoreTest.kt delete mode 100644 Ported/app/src/test/kotlin/com/ujwal/focusgram/EffortFrictionGateTest.kt delete mode 100644 Ported/app/src/test/kotlin/com/ujwal/focusgram/NavigationContainmentTest.kt delete mode 100644 Ported/app/src/test/kotlin/com/ujwal/focusgram/UserScriptAssetsTest.kt create mode 100644 SECURITY.md delete mode 100644 analysis_options.yaml delete mode 100644 android/.gitignore delete mode 100644 android/NOTICE delete mode 100644 android/app/build.gradle.kts delete mode 100644 android/app/proguard-rules.pro delete mode 100644 android/app/src/debug/AndroidManifest.xml delete mode 100644 android/app/src/main/AndroidManifest.xml delete mode 100644 android/app/src/main/kotlin/com/ujwal/focusgram/MainActivity.kt delete mode 100644 android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png delete mode 100644 android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png delete mode 100644 android/app/src/main/res/drawable-v21/launch_background.xml delete mode 100644 android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png delete mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png delete mode 100644 android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png delete mode 100644 android/app/src/main/res/drawable/launch_background.xml delete mode 100644 android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 android/app/src/main/res/values-night/styles.xml delete mode 100644 android/app/src/main/res/values/colors.xml delete mode 100644 android/app/src/main/res/values/styles.xml delete mode 100644 android/app/src/main/res/xml/network_security_config.xml delete mode 100644 android/app/src/profile/AndroidManifest.xml delete mode 100644 android/build.gradle.kts delete mode 100644 android/build/reports/problems/problems-report.html delete mode 100644 android/gradle.properties delete mode 100644 android/gradle/wrapper/gradle-wrapper.properties delete mode 100644 android/settings.gradle.kts rename {Ported/app => app}/build.gradle.kts (76%) rename {Ported/app => app}/proguard-rules.pro (100%) rename {Ported/app => app}/src/main/AndroidManifest.xml (95%) create mode 100644 app/src/main/assets/adi-registration.properties rename {Ported/app => app}/src/main/assets/extensions/ublock/LICENSE.txt (100%) rename {Ported/app => app}/src/main/assets/extensions/ublock/README.md (100%) rename {Ported/app => app}/src/main/assets/extensions/ublock/background.js (88%) rename {Ported/app => app}/src/main/assets/extensions/ublock/content.js (82%) rename {Ported/app => app}/src/main/assets/extensions/ublock/focusgram_content.js (85%) rename {Ported/app => app}/src/main/assets/extensions/ublock/ghost.js (100%) rename {Ported/app => app}/src/main/assets/extensions/ublock/manifest.json (91%) rename {Ported/app/src/main/assets/scripts => app/src/main/assets/extensions/ublock}/media_downloader.js (92%) rename {Ported/app => app}/src/main/assets/omni.ja (100%) rename {Ported/app => app}/src/main/assets/reelhistory/bg.js (100%) rename {Ported/app => app}/src/main/assets/reelhistory/manifest.json (100%) rename {Ported/app => app}/src/main/assets/reelhistory/reels.js (81%) rename {Ported/app => app}/src/main/assets/reellock/bg.js (80%) rename {Ported/app => app}/src/main/assets/reellock/manifest.json (100%) rename {Ported/app => app}/src/main/assets/reellock/reellock.js (81%) rename {Ported/app => app}/src/main/assets/scripts/autoplay_blocker.js (73%) rename {Ported/app => app}/src/main/assets/scripts/content_hider.js (87%) rename {Ported/app => app}/src/main/assets/scripts/fetch_interceptor.js (91%) rename {Ported/app => app}/src/main/assets/scripts/ghost.js (100%) rename {Ported/app => app}/src/main/assets/scripts/grayscale.js (81%) create mode 100644 app/src/main/assets/scripts/media_downloader.js rename {Ported/app => app}/src/main/assets/scripts/native_feel.js (87%) create mode 100644 app/src/main/assets/scripts/reel_metadata_extractor.js rename {Ported/app => app}/src/main/assets/scripts/spa_navigation_monitor.js (76%) create mode 100644 app/src/main/assets/scripts/stealth_instagram.js rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/FocusGramApp.kt (85%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/MainActivity.kt (78%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/database/Daos.kt (100%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/database/Entities.kt (74%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/database/FocusGramDatabase.kt (86%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/datastore/LegacyFlutterDataMigrator.kt (87%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/datastore/SettingsRepository.kt (81%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/engine/ExtensionManager.kt (94%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramNavigationDelegate.kt (86%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramPermissionDelegate.kt (84%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramPromptDelegate.kt (93%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoResultExtensions.kt (73%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoRuntimeManager.kt (87%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoSessionManager.kt (87%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/engine/UserScriptManager.kt (65%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/model/FocusSettings.kt (67%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/model/IslandMode.kt (52%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/security/EncryptedStorage.kt (90%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/ui/components/CupertinoWheelPicker.kt (80%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/ui/components/FocusGramComponents.kt (88%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/ui/components/IOSSettingsComponents.kt (70%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/ui/components/SkeletonScreen.kt (97%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Color.kt (77%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Motion.kt (87%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Shape.kt (89%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Theme.kt (100%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Type.kt (100%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/utils/AppIconManager.kt (83%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/utils/HapticHelper.kt (88%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/utils/HistoricalIconManager.kt (92%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/core/utils/MediaDownloadHelper.kt (95%) create mode 100644 app/src/main/kotlin/com/ujwal/focusgram/core/utils/TimeFormatters.kt rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockScreen.kt (98%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockSettingsPage.kt (98%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockSetupScreen.kt (98%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/applock/ChatLockScreen.kt (96%) create mode 100644 app/src/main/kotlin/com/ujwal/focusgram/feature/diagnostics/DebugMenuScreen.kt create mode 100644 app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelHistory.kt rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelMessageDelegate.kt (90%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelsHistoryRepository.kt (92%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelsHistoryScreen.kt (56%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/navigation/InstagramIcons.kt (88%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/navigation/NativeBottomNavBar.kt (84%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/navigation/ReelsTopBar.kt (94%) create mode 100644 app/src/main/kotlin/com/ujwal/focusgram/feature/notice/RemoteNoticeService.kt create mode 100644 app/src/main/kotlin/com/ujwal/focusgram/feature/onboarding/OnboardingScreen.kt rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeScreen.kt (55%) create mode 100644 app/src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeService.kt rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/session/AppSessionPicker.kt (92%) create mode 100644 app/src/main/kotlin/com/ujwal/focusgram/feature/session/DailyLimitGateScreen.kt rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/session/SessionExhaustedScreen.kt (84%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/session/SessionModal.kt (98%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/settings/SettingsScreen.kt (81%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/snapshots/SnapshotManagerScreen.kt (97%) create mode 100644 app/src/main/kotlin/com/ujwal/focusgram/feature/update/UpdateBanner.kt create mode 100644 app/src/main/kotlin/com/ujwal/focusgram/feature/update/UpdateCheckerService.kt create mode 100644 app/src/main/kotlin/com/ujwal/focusgram/feature/update/UpdateInfo.kt rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/webview/GeckoViewContainer.kt (91%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/wellness/AdsterraAdScreen.kt (83%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BreathGateScreen.kt (96%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/wellness/CooldownGateScreen.kt (83%) create mode 100644 app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/DisciplineMantraRepository.kt rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/wellness/EffortFrictionGate.kt (71%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/wellness/GuardrailsDashboardScreen.kt (76%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/feature/wellness/TimerFallbackScreen.kt (95%) rename {Ported/app => app}/src/main/kotlin/com/ujwal/focusgram/ui/FocusGramApp.kt (73%) rename {Ported/app => app}/src/main/res/drawable-hdpi/ic_launcher_foreground.png (100%) rename {Ported/app => app}/src/main/res/drawable-mdpi/ic_launcher_foreground.png (100%) rename {Ported/app => app}/src/main/res/drawable-v21/launch_background.xml (100%) rename {Ported/app => app}/src/main/res/drawable-xhdpi/ic_launcher_foreground.png (100%) rename {Ported/app => app}/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png (100%) rename {Ported/app => app}/src/main/res/drawable-xxxhdpi/ic_launcher_fg_abstract.png (100%) rename {Ported/app => app}/src/main/res/drawable-xxxhdpi/ic_launcher_fg_forest.png (100%) rename {Ported/app => app}/src/main/res/drawable-xxxhdpi/ic_launcher_fg_golden.png (100%) rename {Ported/app => app}/src/main/res/drawable-xxxhdpi/ic_launcher_fg_metal.png (100%) rename {Ported/app => app}/src/main/res/drawable-xxxhdpi/ic_launcher_fg_minimalist.png (100%) rename {Ported/app => app}/src/main/res/drawable-xxxhdpi/ic_launcher_fg_polaroid.png (100%) rename {Ported/app => app}/src/main/res/drawable-xxxhdpi/ic_launcher_fg_redesign.png (100%) rename {Ported/app => app}/src/main/res/drawable-xxxhdpi/ic_launcher_fg_scribble.png (100%) rename {Ported/app => app}/src/main/res/drawable-xxxhdpi/ic_launcher_fg_vibrant.png (100%) rename {Ported/app => app}/src/main/res/drawable-xxxhdpi/ic_launcher_fg_viewfinder.png (100%) rename {Ported/app => app}/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png (100%) rename {Ported/app => app}/src/main/res/drawable/ic_chat_lock_closed.xml (100%) rename {Ported/app => app}/src/main/res/drawable/ic_chat_lock_open.xml (100%) rename {Ported/app => app}/src/main/res/drawable/ic_credit_downloader.png (100%) rename {Ported/app => app}/src/main/res/drawable/ic_credit_geckoview.xml (100%) rename {Ported/app => app}/src/main/res/drawable/ic_credit_iconify.xml (100%) rename {Ported/app => app}/src/main/res/drawable/ic_credit_jetpack_compose.xml (100%) rename {Ported/app => app}/src/main/res/drawable/ic_credit_sqlite.xml (100%) rename {Ported/app => app}/src/main/res/drawable/ic_credit_ublock.xml (100%) rename {Ported/app => app}/src/main/res/drawable/ic_download_fill.xml (100%) rename {Ported/app => app}/src/main/res/drawable/ic_launcher_bg_instagram.xml (100%) rename {Ported/app => app}/src/main/res/drawable/ic_launcher_fg_all_white.xml (100%) rename {Ported/app => app}/src/main/res/drawable/ic_launcher_fg_classic.xml (100%) rename {Ported/app => app}/src/main/res/drawable/ic_launcher_fg_discreet.xml (100%) rename {Ported/app => app}/src/main/res/drawable/ic_launcher_fg_instagram_official.xml (100%) rename {Ported/app => app}/src/main/res/drawable/ic_launcher_fg_mono.xml (100%) rename {Ported/app => app}/src/main/res/drawable/ic_launcher_fg_oled.xml (100%) rename {Ported/app => app}/src/main/res/drawable/ic_launcher_fg_pitch_black.xml (100%) rename {Ported/app => app}/src/main/res/drawable/ic_notification.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_abstract.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_all_white.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_classic.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_discreet.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_forest.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_golden.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_instagram_official.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_metal.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_minimalist.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_mono.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_oled.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_pitch_black.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_polaroid.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_redesign.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_scribble.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_vibrant.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-anydpi-v26/ic_launcher_viewfinder.xml (100%) rename {Ported/app => app}/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename {Ported/app => app}/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename {Ported/app => app}/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename {Ported/app => app}/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {Ported/app => app}/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {Ported/app => app}/src/main/res/mipmap-xxxhdpi/ic_launcher_abstract.png (100%) rename {Ported/app => app}/src/main/res/mipmap-xxxhdpi/ic_launcher_forest.png (100%) rename {Ported/app => app}/src/main/res/mipmap-xxxhdpi/ic_launcher_golden.png (100%) rename {Ported/app => app}/src/main/res/mipmap-xxxhdpi/ic_launcher_metal.png (100%) rename {Ported/app => app}/src/main/res/mipmap-xxxhdpi/ic_launcher_minimalist.png (100%) rename {Ported/app => app}/src/main/res/mipmap-xxxhdpi/ic_launcher_polaroid.png (100%) rename {Ported/app => app}/src/main/res/mipmap-xxxhdpi/ic_launcher_redesign.png (100%) rename {Ported/app => app}/src/main/res/mipmap-xxxhdpi/ic_launcher_scribble.png (100%) rename {Ported/app => app}/src/main/res/mipmap-xxxhdpi/ic_launcher_vibrant.png (100%) rename {Ported/app => app}/src/main/res/mipmap-xxxhdpi/ic_launcher_viewfinder.png (100%) rename {Ported/app => app}/src/main/res/values/colors.xml (100%) rename {Ported/app => app}/src/main/res/values/strings.xml (100%) rename {Ported/app => app}/src/main/res/values/themes.xml (100%) rename {Ported/app => app}/src/main/res/xml/file_paths.xml (100%) rename {Ported/app => app}/src/main/res/xml/network_security_config.xml (52%) rename {Ported/app => app}/src/test/kotlin/com/ujwal/focusgram/AdsterraAdFlowTest.kt (65%) rename {Ported/app => app}/src/test/kotlin/com/ujwal/focusgram/AppLockSecurityTest.kt (88%) rename {Ported/app => app}/src/test/kotlin/com/ujwal/focusgram/ArchitectureIntegrityTest.kt (64%) create mode 100644 app/src/test/kotlin/com/ujwal/focusgram/AuthNavBarSuppressionTest.kt rename {Ported/app => app}/src/test/kotlin/com/ujwal/focusgram/BlockDmReelsSwipingTest.kt (94%) rename {Ported/app => app}/src/test/kotlin/com/ujwal/focusgram/BreathGateTest.kt (91%) rename {Ported/app => app}/src/test/kotlin/com/ujwal/focusgram/ChatLockAndAppIconTest.kt (94%) rename {Ported/app => app}/src/test/kotlin/com/ujwal/focusgram/CooldownGateTest.kt (89%) create mode 100644 app/src/test/kotlin/com/ujwal/focusgram/EffortFrictionGateTest.kt rename {Ported/app => app}/src/test/kotlin/com/ujwal/focusgram/FocusFeaturesEnhancementsTest.kt (61%) rename {Ported/app => app}/src/test/kotlin/com/ujwal/focusgram/FocusSettingsTest.kt (69%) rename {Ported/app => app}/src/test/kotlin/com/ujwal/focusgram/IslandModeTest.kt (94%) rename {Ported/app => app}/src/test/kotlin/com/ujwal/focusgram/LegacyMigrationLogicTest.kt (100%) create mode 100644 app/src/test/kotlin/com/ujwal/focusgram/NavigationContainmentTest.kt create mode 100644 app/src/test/kotlin/com/ujwal/focusgram/OnboardingScreenTest.kt rename {Ported/app => app}/src/test/kotlin/com/ujwal/focusgram/ReelLockExtensionAssetsTest.kt (100%) rename {Ported/app => app}/src/test/kotlin/com/ujwal/focusgram/ReelsHistoryLogicTest.kt (50%) rename {Ported/app => app}/src/test/kotlin/com/ujwal/focusgram/ScreenTimeTrackerTest.kt (84%) rename {Ported/app => app}/src/test/kotlin/com/ujwal/focusgram/SnapshotsEntityTest.kt (100%) create mode 100644 app/src/test/kotlin/com/ujwal/focusgram/TwoTierLimitsAndEnhancementsTest.kt rename {Ported/app => app}/src/test/kotlin/com/ujwal/focusgram/UBlockExtensionAssetsTest.kt (100%) create mode 100644 app/src/test/kotlin/com/ujwal/focusgram/UpdateCheckerTest.kt create mode 100644 app/src/test/kotlin/com/ujwal/focusgram/UserScriptAssetsTest.kt create mode 100644 build-release.sh rename Ported/build.gradle.kts => build.gradle.kts (100%) delete mode 100644 devtools_options.yaml rename Ported/gradle.properties => gradle.properties (100%) rename {Ported/gradle => gradle}/libs.versions.toml (100%) rename {Ported/gradle => gradle}/wrapper/gradle-wrapper.jar (100%) rename {Ported/gradle => gradle}/wrapper/gradle-wrapper.properties (100%) rename Ported/gradlew => gradlew (100%) rename Ported/gradlew.bat => gradlew.bat (100%) delete mode 100644 lib/features/loading/skeleton_screen.dart delete mode 100644 lib/features/native_nav/native_bottom_nav.dart delete mode 100644 lib/features/preloader/instagram_preloader.dart delete mode 100644 lib/features/reels_history/reels_history_screen.dart delete mode 100644 lib/features/reels_history/reels_history_service.dart delete mode 100644 lib/features/screen_time/screen_time_screen.dart delete mode 100644 lib/features/update_checker/update_banner.dart delete mode 100644 lib/features/update_checker/update_checker_service.dart delete mode 100644 lib/focus_settings.dart delete mode 100644 lib/main.dart delete mode 100644 lib/screens/adsterra_ad_screen.dart delete mode 100644 lib/screens/app_lock_screen.dart delete mode 100644 lib/screens/app_lock_settings_page.dart delete mode 100644 lib/screens/app_lock_setup_screen.dart delete mode 100644 lib/screens/app_session_picker.dart delete mode 100644 lib/screens/bait_me_button.dart delete mode 100644 lib/screens/bait_me_full_screen.dart delete mode 100644 lib/screens/breath_gate_screen.dart delete mode 100644 lib/screens/cooldown_gate_screen.dart delete mode 100644 lib/screens/debug_menu_screen.dart delete mode 100644 lib/screens/effort_friction_gate.dart delete mode 100644 lib/screens/extras_settings_page.dart delete mode 100644 lib/screens/ghost_mode_submenu_page.dart delete mode 100644 lib/screens/guardrails_page.dart delete mode 100644 lib/screens/level_panel_screen.dart delete mode 100644 lib/screens/main_webview_page.dart delete mode 100644 lib/screens/offline_feed_viewer.dart delete mode 100644 lib/screens/onboarding_page.dart delete mode 100644 lib/screens/reel_player_overlay.dart delete mode 100644 lib/screens/session_modal.dart delete mode 100644 lib/screens/settings_page.dart delete mode 100644 lib/screens/snapshot_manager_screen.dart delete mode 100644 lib/screens/timer_fallback_screen.dart delete mode 100644 lib/scripts/autoplay_blocker.dart delete mode 100644 lib/scripts/content_disabling.dart delete mode 100644 lib/scripts/core_injection.dart delete mode 100644 lib/scripts/dm_keyboard_fix.dart delete mode 100644 lib/scripts/focus_scripts.dart delete mode 100644 lib/scripts/grayscale.dart delete mode 100644 lib/scripts/haptic_bridge.dart delete mode 100644 lib/scripts/native_feel.dart delete mode 100644 lib/scripts/reel_metadata_extractor.dart delete mode 100644 lib/scripts/scroll_smoothing.dart delete mode 100644 lib/scripts/spa_navigation_monitor.dart delete mode 100644 lib/scripts/ui_hider.dart delete mode 100644 lib/scripts/video_downloader.dart delete mode 100644 lib/services/adblock/adblock_content_blocker_loader.dart delete mode 100644 lib/services/app_lock_service.dart delete mode 100644 lib/services/bait_engine.dart delete mode 100644 lib/services/credit_store.dart delete mode 100644 lib/services/focusgram_router.dart delete mode 100644 lib/services/injection_controller.dart delete mode 100644 lib/services/injection_manager.dart delete mode 100644 lib/services/level_service.dart delete mode 100644 lib/services/navigation_guard.dart delete mode 100644 lib/services/notification_service.dart delete mode 100644 lib/services/remote_popup_service.dart delete mode 100644 lib/services/screen_time_service.dart delete mode 100644 lib/services/session_manager.dart delete mode 100644 lib/services/settings_service.dart delete mode 100644 lib/services/snapshot_service.dart delete mode 100644 lib/utils/discipline_challenge.dart delete mode 100644 lib/v2_integration/script_engine_v2_overlay.dart delete mode 100644 lib/v2_integration/script_registry_v2_overlay.dart delete mode 100644 lib/widgets/medium_rect_banner.dart delete mode 100644 lib/widgets/native_ad_banner.dart delete mode 100644 lib/widgets/remote_popup_handler.dart delete mode 100644 pubspec.lock delete mode 100644 pubspec.yaml rename Ported/settings.gradle.kts => settings.gradle.kts (100%) delete mode 100644 test/screens/main_webview_page_media_download_test.dart delete mode 100644 test/services/adblocker_test.dart delete mode 100644 test/services/app_lock_service_test.dart delete mode 100644 test/services/dm_ghost_injection_test.dart delete mode 100644 test/services/focusgram_router_test.dart delete mode 100644 test/services/ghost_mode_focus_scripts_test.dart delete mode 100644 test/services/ghost_mode_settings_test.dart delete mode 100644 test/services/ghost_mode_url_patterns_test.dart delete mode 100644 test/services/injection_manager_test.dart delete mode 100644 test/services/level_service_test.dart delete mode 100644 test/services/navigation_guard_test.dart delete mode 100644 test/services/reels_blocker_test.dart delete mode 100644 test/services/screen_time_service_test.dart delete mode 100644 test/services/session_extension_test.dart delete mode 100644 test/services/session_manager_test.dart delete mode 100644 test/services/settings_service_test.dart delete mode 100644 v2/MainActivity.kt delete mode 100644 v2/PHASE1_DEPS.yaml delete mode 100644 v2/ad_blocker_dom.js delete mode 100644 v2/assets/scripts/ad_blocker_dom.js delete mode 100644 v2/assets/scripts/autoplay_blocker.js delete mode 100644 v2/assets/scripts/content_hider.js delete mode 100644 v2/assets/scripts/fetch_interceptor.js delete mode 100644 v2/assets/scripts/ghost_mode.js delete mode 100644 v2/assets/scripts/theme_detector.js delete mode 100644 v2/channel_registry.dart delete mode 100644 v2/content_hider.js delete mode 100644 v2/fetch_interceptor.js delete mode 100644 v2/ghost_mode.js delete mode 100644 v2/ghost_mode_script.dart delete mode 100644 v2/ghost_mode_service.dart delete mode 100644 v2/instagram_webview.dart delete mode 100644 v2/main.dart delete mode 100644 v2/script_engine.dart delete mode 100644 v2/script_registry.dart delete mode 100644 v2/system_ui_manager.dart delete mode 100644 v2/theme_detector.js delete mode 100644 v2/webview_config.dart diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..39cb507 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,65 @@ +name: "CodeQL Analysis & Code Scanning" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '30 4 * * 1' + +permissions: + actions: read + contents: read + security-events: write + +jobs: + analyze: + name: Analyze (Kotlin & Java) + runs-on: ubuntu-latest + timeout-minutes: 120 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Java 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "21" + + - name: Set up Android SDK + uses: android-actions/setup-android@v4 + + - name: Install Android Platform & Build Tools + shell: bash + run: | + set -euo pipefail + sdkmanager \ + "platform-tools" \ + "platforms;android-35" \ + "build-tools;35.0.0" + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-version: wrapper + cache-read-only: false + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: 'java-kotlin' + queries: +security-extended,security-and-quality + + - name: Build Application for Analysis + shell: bash + run: | + chmod +x gradlew + ./gradlew :app:assembleDebug --no-daemon + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:java-kotlin" diff --git a/.github/workflows/native-release.yml b/.github/workflows/native-release.yml index b0a974c..8bb01b1 100644 --- a/.github/workflows/native-release.yml +++ b/.github/workflows/native-release.yml @@ -50,25 +50,23 @@ jobs: - name: Grant execute permission for gradlew shell: bash - run: chmod +x Ported/gradlew + run: chmod +x gradlew - name: Run Unit Tests - working-directory: Ported run: ./gradlew :app:testDebugUnitTest --no-daemon - name: Assemble Release APKs - working-directory: Ported run: ./gradlew :app:assembleRelease --no-daemon - name: Display APK Sizes shell: bash run: | echo "=== Built Release APKs ===" - ls -lh Ported/app/build/outputs/apk/release/*.apk + ls -lh app/build/outputs/apk/release/*.apk - name: Upload Release APKs uses: actions/upload-artifact@v4 with: name: focusgram-native-release-apks - path: Ported/app/build/outputs/apk/release/*.apk + path: app/build/outputs/apk/release/*.apk if-no-files-found: error diff --git a/.github/workflows/release-apk.yml b/.github/workflows/release-apk.yml index b6a4862..83553b8 100644 --- a/.github/workflows/release-apk.yml +++ b/.github/workflows/release-apk.yml @@ -1,13 +1,19 @@ name: Build APK and Create GitHub Release + on: workflow_dispatch: inputs: version: - description: "Release version without leading v. Example: 1.0.0. Leave empty to read the top CHANGELOG heading." + description: "Release version without leading v. Example: 2.1.0. Leave empty to read the top CHANGELOG heading." required: false type: string + push: + tags: + - "v*.*.*" + permissions: contents: write + jobs: release: runs-on: ubuntu-latest @@ -16,18 +22,16 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up Java 17 - uses: actions/setup-java@v5 + + - name: Set up Java 21 + uses: actions/setup-java@v4 with: distribution: temurin - java-version: "17" + java-version: "21" + - name: Set up Android SDK uses: android-actions/setup-android@v4 - - name: Set up Flutter - uses: subosito/flutter-action@v2 - with: - channel: stable - cache: true + - name: Install required Android SDK packages shell: bash run: | @@ -35,61 +39,86 @@ jobs: sdkmanager \ "platform-tools" \ "platforms;android-35" \ - "build-tools;34.0.0" \ - "build-tools;35.0.0" \ - - name: Get dependencies - run: flutter pub get + "build-tools;35.0.0" + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-version: wrapper + cache-read-only: false + + - name: Grant execute permission for gradlew + shell: bash + run: chmod +x gradlew + - name: Resolve version and tag id: meta shell: bash run: | set -euo pipefail - INPUT_VERSION="${{ github.event.inputs.version }}" - if [[ -n "${INPUT_VERSION}" ]]; then - VERSION="${INPUT_VERSION#v}" + if [[ "${GITHUB_REF:-}" == refs/tags/* ]]; then + TAG="${GITHUB_REF#refs/tags/}" + VERSION="${TAG#v}" + elif [[ -n "${{ github.event.inputs.version }}" ]]; then + VERSION="${{ github.event.inputs.version }}" + VERSION="${VERSION#v}" + TAG="v${VERSION}" else VERSION="$(python3 .github/scripts/get_version.py)" + TAG="v${VERSION}" fi - TAG="v${VERSION}" echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + - name: Extract release notes from CHANGELOG.md shell: bash env: VERSION: ${{ steps.meta.outputs.version }} run: python3 .github/scripts/get_notes.py - - name: Build release APK - run: flutter build apk --release - - name: Rename APK - run: mv build/app/outputs/flutter-apk/app-release.apk build/app/outputs/flutter-apk/focusgram-release.apk - - name: Upload APK artifact + + - name: Run Unit Tests + run: ./gradlew :app:testDebugUnitTest --no-daemon + + - name: Build release APKs (with ABI splits) + run: ./gradlew :app:assembleRelease --no-daemon + + - name: Display Generated APKs + shell: bash + run: | + echo "=== Built Release APKs ===" + ls -lh app/build/outputs/apk/release/*.apk + + - name: Upload APK artifacts uses: actions/upload-artifact@v4 with: name: focusgram-apk-${{ steps.meta.outputs.tag }} - path: build/app/outputs/flutter-apk/focusgram-release.apk + path: app/build/outputs/apk/release/*.apk if-no-files-found: error - - name: Create Git tag + + - name: Create Git tag (workflow_dispatch only) shell: bash + if: github.event_name == 'workflow_dispatch' env: TAG: ${{ steps.meta.outputs.tag }} run: | set -euo pipefail git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then - echo "Tag already exists on remote: ${TAG}" - exit 1 + if ! git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then + git tag -a "${TAG}" -m "Release ${TAG}" + git push origin "${TAG}" + else + echo "Tag ${TAG} already exists on remote." fi - git tag -a "${TAG}" -m "Release ${TAG}" - git push origin "${TAG}" + - name: Create GitHub Release uses: softprops/action-gh-release@v3 with: tag_name: ${{ steps.meta.outputs.tag }} name: FocusGram ${{ steps.meta.outputs.tag }} body_path: release_notes.md - files: build/app/outputs/flutter-apk/focusgram-release.apk + files: app/build/outputs/apk/release/*.apk draft: false prerelease: false env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 89b4097..e90bddc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ # ============================================================================== -# FocusGram Root .gitignore -# Covers both Flutter Legacy app and Native Kotlin app in Ported/ +# FocusGram Native Android (Kotlin + GeckoView + Jetpack Compose) .gitignore # ============================================================================== # ------------------------------------------------------------------------------ @@ -29,7 +28,7 @@ Thumbs.db .atom/ .history .svn/ -.swiftpm/ +.navigation/ # ------------------------------------------------------------------------------ # 2. Secrets, Environment, and Keystores @@ -42,11 +41,10 @@ Thumbs.db *.key key.properties **/key.properties -android/key.properties upload-keystore.jks # ------------------------------------------------------------------------------ -# 3. Android & Gradle Build Artifacts (Root and Ported/) +# 3. Android & Gradle Build Artifacts # ------------------------------------------------------------------------------ # Gradle & Android user cache files .gradle/ @@ -66,12 +64,7 @@ local.properties # Build directories & outputs build/ **/build/ -/build/ -/android/app/debug -/android/app/profile -/android/app/release -Ported/build/ -Ported/app/build/ +app/build/ *.apk *.aab *.ap_ @@ -86,31 +79,8 @@ captures/ .buildlog/ # ------------------------------------------------------------------------------ -# 4. Flutter & Dart Artifacts (Preserved Legacy App) +# 4. Multi-Agent Governance & Documentation (Keep untracked from Git if needed) # ------------------------------------------------------------------------------ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.pub/ -.pub-cache/ -/coverage/ -.flutter/ -**/doc/api/ -**/ios/Flutter/.last_build_id -app.*.symbols -app.*.map.json - -# ------------------------------------------------------------------------------ -# 5. Project Migration & Local Tools (Internal / Scratch) -# ------------------------------------------------------------------------------ -migrate_working_dir/ -PRD.md -.reasonix/ .agents/ system/ Documents/ -Ghostify/ -Ghostify -inspo/ -Instagram-Downloader/ - diff --git a/.metadata b/.metadata deleted file mode 100644 index 00b74be..0000000 --- a/.metadata +++ /dev/null @@ -1,30 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: "3b62efc2a3da49882f43c372e0bc53daef7295a6" - channel: "stable" - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 - base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 - - platform: android - create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 - base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6 - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d47183..d6b13d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,22 +1,44 @@ -## FocusGram 2.1.0 +## FocusGram 3.0.0 ### What's new -- NEW: Startup Page - choose which page to launch on app launch. -- NEW: App lock and DM Lock. -- NEW: Bait me button in Focus Control. -- NEW: Interactive Level based system for unlocking features. -- NEW: Effort Friction Mode. -- NEW: Strict and fully working Ghost Mode. +- NEW: AD BLOCKER!! +- NEW: Custom App Icons. +- NEW: Lock Specific Chats. +- NEW: Block Reels Scrolling in DM Feed. +- NEW: Block Video Autoplay. +- NEW: Search Story Viewer List. +- NEW: Custom Fonts in Bio. +- NEW: Floating bottom Navigation Bar. +- NEW: Remove Stories Tray. +- NEW: Improved Ghost Mode. +- NEW: Compelete UI Revamp. +- NEW: Fingerprint Support +- NEW: Live Notifications That shows session timer. +- NEW: Reels Counter (Track how many and how long have you been watching Reels). +- NEW: Reels History. + and more.... ### Bug fixes -- Fixed: Greyscale mode used to turn off when app was restarted. -- Fixed: Images in posts containing multiple images werent getting unblurred when tapped. -- Fixed: You could send message as "Ghost" in GHost mode (Ghost's cant talk with real people ðŸĪŠ). -- Fixed: Reduced duplicate/spam notifications by improving notification bridge IDs. -- Fixed: Download media button (rarely) opened random media rather than desired one. -- Fixed: Reel Session could be started despite quota being finished. -- Perfomance Optimizations -- A lof of other Minor fixes. +- Removed: Leveling System, Credits, Baitme etc. +- Removed: Top bar with Focusgram Logo. +- Fixed: You couldn't send messages when in Ghost mode. +- Fixed: Captions & Audio Banner were Cutted off. +- Fixed: Blur state of the homepage and reels couldn't be changed once set. +- Fixed: Story tray wasn't being hidden after enabling the option (feature didn't exist but its reference did in some descriptions) +- Fixed: App felt Laggy +- Fixed: A lot of other bugs & added few more to fix later. + +### Uncloned Instagram Package Edition + +Looking to replace the official Instagram app directly on your phone? Download the uncloned edition (`com.instagram.android` package name) directly from our distribution channel: +- 🔗 **Download Uncloned Edition (`com.instagram.android`):** [https://focusgram-app.netlify.app/](https://focusgram-app.netlify.app/) + +[Note: this webpage contains ads] + +FEW CHANGES IN USER INTERACTION: +1. Long Press the Profile Icon to open Focusgram Settings (The app looks a LOT CLeaner) +2. Double tap any icon on nav bar to refresh the page. +If more Confusions, Don't hesitate to create an Issue. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4e578e6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,72 @@ +# Contributing to FocusGram + +Thank you for your interest in contributing to FocusGram! FocusGram is a free, open-source digital wellness tool built under the **GNU Affero General Public License v3.0 (AGPL-3.0)**. + +--- + +## Architecture Principles + +Every pull request must uphold these non-negotiable rules: + +1. **Local-Only Operation:** + FocusGram never sends user data off-device. No telemetry, no analytics, no third-party network SDKs, and no remote crash loggers. +2. **The Login Flow is a Black Box:** + Never write code or content scripts that read, intercept, or modify authentication form fields, credentials, cookies, or 2FA tokens. +3. **No Automation of Instagram Actions:** + FocusGram is an intentionality tool, not an automation bot. Do not contribute auto-likers, auto-followers, auto-scrollers, or mass-DM tools. +4. **Clean Web Sandboxing:** + Web content manipulation is strictly client-side via Mozilla GeckoView content scripts (`app/src/main/assets/scripts/` or `app/src/main/assets/extensions/ublock/`). Never inline large raw JavaScript strings directly in Kotlin source files. +5. **FOSS Dependency Licensing:** + All dependencies must be compatible with AGPL-3.0 (e.g. Apache 2.0, MIT, BSD, MPL 2.0, LGPL/GPL). + +--- + +## Development Environment Setup + +### Prerequisites + +- **OS:** Linux, macOS, or Windows +- **JDK:** OpenJDK 21 (Temurin or Adoptium recommended) +- **Android SDK:** API Level 26 minimum, API 35 target, Build-Tools 35.0.0 +- **Build System:** Gradle (bundled wrapper `./gradlew`) + +### Build Commands + +```bash +# Clone the repository +git clone https://github.com/Ujwal223/FocusGram.git +cd FocusGram + +# Run unit tests +./gradlew :app:testDebugUnitTest + +# Build debug APK +./gradlew :app:assembleDebug + +# Build standard release APKs (with ABI splits) +./gradlew :app:assembleRelease + +# Build uncloned Instagram package edition (com.instagram.android) +./gradlew :app:assembleRelease -PuseInstaPackage=true + +# Or use the interactive build script +./build-release.sh +``` + +--- + +## Code Style & Standards + +- **Kotlin:** Idiomatic Kotlin 2.0+ following official Kotlin code style. +- **Compose:** Jetpack Compose Material 3 using Unidirectional Data Flow (UDF), `StateFlow`, and coroutines. +- **Storage:** Use Jetpack DataStore Preferences for simple preferences and AndroidX Room for relational persistence (session snapshots, screen time records, reel history). +- **Comments:** Follow maintainer-voice commenting. Explain *why* something is designed the way it is, warn about platform traps, and delete restatements of code. Avoid decorative ASCII art or divider lines. + +--- + +## Submitting Pull Requests + +1. Fork the repository and create a branch with a descriptive name (`git checkout -b feature/your-feature`). +2. Run `./gradlew :app:testDebugUnitTest` to ensure all tests pass. +3. Keep diffs minimal, focused, and free of drive-by refactors. +4. Open a Pull Request referencing any relevant issues. diff --git a/Ported/.gitignore b/Ported/.gitignore deleted file mode 100644 index 9dd8e46..0000000 --- a/Ported/.gitignore +++ /dev/null @@ -1,35 +0,0 @@ -# ============================================================================== -# FocusGram Native Android (Ported/) .gitignore -# ============================================================================== - -# Gradle & Android user cache -.gradle/ -.android/ -build/ -app/build/ - -# Machine-specific SDK & local configurations -local.properties - -# Android Studio / IntelliJ -.idea/ -*.iml -.navigation/ -captures/ -.cxx/ - -# Keystores & Signing Secrets -*.jks -*.keystore -*.key -key.properties - -# OS & Logs -.DS_Store -Thumbs.db -*.log -*.hprof - -# Others -Ghostify/ -Instagram-Downloader/ diff --git a/Ported/app/src/main/assets/scripts/reel_metadata_extractor.js b/Ported/app/src/main/assets/scripts/reel_metadata_extractor.js deleted file mode 100644 index 450686b..0000000 --- a/Ported/app/src/main/assets/scripts/reel_metadata_extractor.js +++ /dev/null @@ -1,167 +0,0 @@ -/** - * FocusGram — Reels Metadata Extractor UserScript - * - * Extracts author, title, and thumbnail URL for watched Reels directly from client-side DOM - * (Open Graph meta tags, JSON-LD structured data, document titles). - * - * LEGAL & ARCHITECTURE COMPLIANCE: - * - 100% Client-side DOM parsing only. - * - ZERO video media is downloaded or cached to disk. - * - ZERO network calls or external API calls. - * - All metadata remains strictly on-device in Room SQLite. - */ - -'use strict'; - -(function () { - if (window.__fgReelExtractorInitialized) return; - window.__fgReelExtractorInitialized = true; - - let lastExtractedUrl = ''; - - function extractMetadata() { - const currentUrl = window.location.href; - - // Verify this is an Instagram Reel URL - if (!currentUrl.includes('/reels/') && !currentUrl.includes('/reel/')) { - return null; - } - - // Avoid re-extracting for the identical URL - if (lastExtractedUrl === currentUrl) { - return null; - } - - let title = ''; - let thumbnailUrl = ''; - let author = ''; - - // 1. Check Open Graph tags - const ogTitle = document.querySelector('meta[property="og:title"]'); - const ogImage = document.querySelector('meta[property="og:image"]'); - if (ogTitle && ogTitle.content) title = ogTitle.content.trim(); - if (ogImage && ogImage.content) thumbnailUrl = ogImage.content.trim(); - - // 2. Fallback to document title - if (!title && document.title) { - title = document.title.replace(' on Instagram', '').replace('â€Ē Instagram photos and videos', '').trim(); - } - - // Parse author if available in title (format: "Author (@handle) on Instagram: ...") - if (title) { - const authorMatch = title.match(/^([^(@:]+)(?:\s*\((@[^)]+)\))?/); - if (authorMatch) { - author = (authorMatch[2] || authorMatch[1]).trim(); - } - } - - // 3. Fallback to JSON-LD structured data - if (!thumbnailUrl) { - const jsonLdElements = document.querySelectorAll('script[type="application/ld+json"]'); - jsonLdElements.forEach(script => { - try { - const data = JSON.parse(script.textContent); - if (data && data.image) { - if (Array.isArray(data.image) && data.image.length > 0) { - thumbnailUrl = data.image[0]; - } else if (typeof data.image === 'string') { - thumbnailUrl = data.image; - } else if (data.image.url) { - thumbnailUrl = data.image.url; - } - } - if (!title && data.caption) { - title = data.caption; - } - } catch (_) {} - }); - } - - // 4. Fallback to Twitter card image - if (!thumbnailUrl) { - const twitterImage = document.querySelector('meta[name="twitter:image"]'); - if (twitterImage && twitterImage.content) { - thumbnailUrl = twitterImage.content.trim(); - } - } - - // 5. Fallback to video element poster and DOM author - if (!thumbnailUrl) { - const videoEl = document.querySelector('video[poster]') || document.querySelector('video'); - if (videoEl && videoEl.getAttribute('poster')) { - thumbnailUrl = videoEl.getAttribute('poster'); - } - } - if (!author) { - const authorEl = document.querySelector('a[role="link"][tabindex="0"] span') || - document.querySelector('header a') || - document.querySelector('div[role="dialog"] header a'); - if (authorEl && authorEl.textContent) { - author = authorEl.textContent.trim(); - } - } - - // If neither title nor thumbnail is ready, DOM may still be rendering - if (!thumbnailUrl && !title) { - return null; - } - - lastExtractedUrl = currentUrl; - - return { - url: currentUrl, - title: title || 'Instagram Reel', - author: author || 'Instagram Creator', - thumbnailUrl: thumbnailUrl || '', - timestamp: Date.now() - }; - } - - function extractAndBroadcast() { - const data = extractMetadata(); - if (!data) return; - - // Dispatch DOM event for native page bridge / observers - try { - window.dispatchEvent(new CustomEvent('focusgram:reelmetadata', { - detail: data - })); - } catch (_) {} - - // Send via WebExtension messaging if available - try { - if (typeof browser !== 'undefined' && browser.runtime && browser.runtime.sendMessage) { - browser.runtime.sendMessage({ - type: 'ReelMetadata', - payload: data - }).catch(() => {}); - } - } catch (_) {} - - console.log('[FocusGram] Reel metadata extracted on-device:', data.url); - } - - // Initial check - extractAndBroadcast(); - - // Listen to route changes from spa_navigation_monitor - window.addEventListener('focusgram:routechange', () => { - setTimeout(extractAndBroadcast, 400); - }); - - // Observe DOM mutations for asynchronously loaded OG meta tags - let debounceTimer = null; - const observer = new MutationObserver(() => { - if (window.location.href.includes('/reel')) { - clearTimeout(debounceTimer); - debounceTimer = setTimeout(extractAndBroadcast, 500); - } - }); - - observer.observe(document.documentElement || document.body, { - childList: true, - subtree: true - }); - - console.log('[FocusGram] Reels Metadata Extractor active.'); -})(); diff --git a/Ported/app/src/main/assets/scripts/stealth_instagram.js b/Ported/app/src/main/assets/scripts/stealth_instagram.js deleted file mode 100644 index 09b9e13..0000000 --- a/Ported/app/src/main/assets/scripts/stealth_instagram.js +++ /dev/null @@ -1,227 +0,0 @@ -/** - * FocusGram — Stealth Privacy Engine - * - * Intercepts client-side network calls and DOM visibility signals on instagram.com to provide: - * 1. Anonymous Story Viewing (suppressing story view and reel seen mutations) - * 2. Incognito Direct Messages (blocking read receipts and seen acknowledgements) - * 3. Invisible Typing (suppressing typing indicators in chats) - * 4. Visibility spoofing for story viewer containers - */ - -'use strict'; - -(function () { - if (window.__FG_STEALTH_INITIALIZED__) return; - window.__FG_STEALTH_INITIALIZED__ = true; - - const DEFAULT_CONFIG = { - enabled: false, - igStory: true, - igSeen: true, - igTyping: true - }; - - window.__FOCUSGRAM_GHOST_CONFIG__ = Object.assign({}, DEFAULT_CONFIG, window.__FOCUSGRAM_GHOST_CONFIG__ || {}); - - const PATTERNS = { - igTyping: [ - "indicate_activity", - "typing_indicator", - "activity_indicator", - "is_typing", - "direct_v2/threads/broadcast/typing", - "direct_v2/threads/typing", - "sendtypingindicator", - "send_typing_indicator", - "typing_on", - "is_composing", - "update_activity_status", - "activity_status" - ], - igSeen: [ - "mark_read", - "mark_seen", - "thread_seen", - "directmarkasseen", - "markasseen", - "directthreadmarkitemsseen", - "polarisdirectmarkasseenmutation", - "directseenmutation", - "usepolarismarkthreadseenmutation", - "useigdmarkthreadasreadmutation" - ], - igStory: [ - "storiesupdateseenmutation", - "polarisstoriesseenmutation", - "usepolarisstoriesv3seenmutation", - "reelmediaseen", - "storiesupdateseen", - "seenstoriesupdatemutation", - "polarisapireelseenmutation", - "xdt_mark_story_reel_seen", - "26997980659837802", - "polarisapiforcestoryseenmutation", - "xdt_api__v1__stories__reel__seen", - "9647304595318258", - "api/v1/stories/reel/seen", - "stories/reel/seen", - "mark_story_seen", - "update_seen_for_reel", - "reel_seen", - "stories_update_seen", - "mark_story_read" - ] - }; - - function shouldBlock(data, url, method) { - const config = window.__FOCUSGRAM_GHOST_CONFIG__; - if (!config || !config.enabled) return null; - - const urlString = String(url || "").toLowerCase(); - const bodyText = (typeof data === "string" ? data : "").toLowerCase(); - const text = `${bodyText} ${urlString}`; - - // Never block actual message sending or media uploads - if (urlString.includes("threads/broadcast/text") || - urlString.includes("direct_v2/threads/broadcast") || - urlString.includes("broadcast/media") || - urlString.includes("action=send_message") || - urlString.includes("configure")) { - return null; - } - - if (config.igTyping && PATTERNS.igTyping.some(p => text.includes(p))) { - return "IG_TYPING"; - } - - if (config.igStory && PATTERNS.igStory.some(p => text.includes(p))) { - return "IG_STORY"; - } - - if (config.igSeen && PATTERNS.igSeen.some(p => text.includes(p))) { - return "IG_SEEN"; - } - - return null; - } - - function createMockPayload(blockType, url, bodyText) { - const combined = `${bodyText} ${url}`.toLowerCase(); - const isGraphQL = combined.includes("/graphql") || combined.includes("fb_api_req_friendly_name") || combined.includes("doc_id"); - - if (!isGraphQL) { - return { status: "ok", blocked: blockType }; - } - - if (blockType === "IG_STORY") { - if (combined.includes("xdt_api__v1__stories__reel__seen") || - combined.includes("polarisapiforcestoryseenmutation") || - combined.includes("9647304595318258")) { - return { - data: { - xdt_api__v1__stories__reel__seen: { - __typename: "XDTEmptyRecord" - } - } - }; - } - return { - data: { - xdt_mark_story_reel_seen: { - __typename: "XDTMarkSeenResponse" - } - } - }; - } - - return { data: {} }; - } - - // 1. Intercept Fetch API - const originalFetch = window.fetch; - window.fetch = async function (input, init) { - const url = typeof input === "string" ? input : (input && input.url ? input.url : ""); - const method = (init && init.method) || (input && input.method) || "GET"; - const body = (init && init.body) || ""; - - let bodyText = ""; - try { - if (typeof body === "string") { - bodyText = body; - } else if (body instanceof URLSearchParams) { - bodyText = body.toString(); - } - } catch (_) {} - - const blockType = shouldBlock(bodyText, url, method); - if (blockType) { - console.log(`[FocusGram Stealth] Filtered fetch: ${blockType} -> ${url}`); - const payload = createMockPayload(blockType, url, bodyText); - return new Response(JSON.stringify(payload), { - status: 200, - headers: { "Content-Type": "application/json" } - }); - } - - return originalFetch.apply(this, arguments); - }; - - // 2. Intercept XMLHttpRequest - const originalXhrOpen = XMLHttpRequest.prototype.open; - const originalXhrSend = XMLHttpRequest.prototype.send; - - XMLHttpRequest.prototype.open = function (method, url) { - this._fgStealthUrl = url; - this._fgStealthMethod = method; - return originalXhrOpen.apply(this, arguments); - }; - - XMLHttpRequest.prototype.send = function (body) { - let bodyText = ""; - try { - if (typeof body === "string") bodyText = body; - } catch (_) {} - - const blockType = shouldBlock(bodyText, this._fgStealthUrl, this._fgStealthMethod); - if (blockType) { - console.log(`[FocusGram Stealth] Filtered XHR: ${blockType} -> ${this._fgStealthUrl}`); - const payload = createMockPayload(blockType, this._fgStealthUrl, bodyText); - Object.defineProperty(this, 'readyState', { value: 4 }); - Object.defineProperty(this, 'status', { value: 200 }); - Object.defineProperty(this, 'responseText', { value: JSON.stringify(payload) }); - setTimeout(() => { - this.dispatchEvent(new Event('readystatechange')); - this.dispatchEvent(new Event('load')); - }, 10); - return; - } - - return originalXhrSend.apply(this, arguments); - }; - - // 3. Visibility and Focus Spoofing (Prevents story seen triggers during passive browsing) - const originalHasFocus = document.hasFocus.bind(document); - document.hasFocus = function () { - const config = window.__FOCUSGRAM_GHOST_CONFIG__; - if (config && config.enabled && config.igStory && window.location.pathname.startsWith('/stories/')) { - return false; - } - return originalHasFocus(); - }; - - // 4. Listen for realtime configuration updates - window.addEventListener("message", (event) => { - if (!event.data || event.data.source !== "FOCUSGRAM_HOST") return; - if (event.data.type === "FOCUSGRAM_UPDATE" || event.data.type === "STEALTH_UPDATE") { - const ghost = event.data.ghost || event.data.config || {}; - Object.assign(window.__FOCUSGRAM_GHOST_CONFIG__, { - enabled: ghost.enabled !== undefined ? ghost.enabled : window.__FOCUSGRAM_GHOST_CONFIG__.enabled, - igStory: ghost.igStory !== undefined ? ghost.igStory : window.__FOCUSGRAM_GHOST_CONFIG__.igStory, - igSeen: ghost.igSeen !== undefined ? ghost.igSeen : window.__FOCUSGRAM_GHOST_CONFIG__.igSeen, - igTyping: ghost.igTyping !== undefined ? ghost.igTyping : window.__FOCUSGRAM_GHOST_CONFIG__.igTyping - }); - } - }); - - console.log("[FocusGram] Stealth Privacy Engine active."); -})(); diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/diagnostics/DebugMenuScreen.kt b/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/diagnostics/DebugMenuScreen.kt deleted file mode 100644 index 932bd07..0000000 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/diagnostics/DebugMenuScreen.kt +++ /dev/null @@ -1,178 +0,0 @@ -package com.ujwal.focusgram.feature.diagnostics - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBars -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import com.ujwal.focusgram.core.datastore.SettingsRepository -import com.ujwal.focusgram.core.model.FocusSettings -import com.ujwal.focusgram.core.ui.components.FocusGramCard -import com.ujwal.focusgram.core.ui.components.FocusGramSectionHeader -import com.ujwal.focusgram.core.ui.components.FocusGramTopBar -import com.ujwal.focusgram.core.ui.theme.AlertRed -import com.ujwal.focusgram.core.ui.theme.DarkCard -import com.ujwal.focusgram.core.ui.theme.DarkCardElevated -import com.ujwal.focusgram.core.ui.theme.FocusGold -import com.ujwal.focusgram.core.ui.theme.FocusGreen -import com.ujwal.focusgram.core.ui.theme.InstagramBlue -import com.ujwal.focusgram.core.ui.theme.PureBlack -import com.ujwal.focusgram.core.ui.theme.TextPrimary -import com.ujwal.focusgram.core.ui.theme.TextSecondary -import kotlinx.coroutines.launch - -/** - * DebugMenuScreen - * Hidden developer diagnostics panel: - * - Level & XP override - * - Session credit balances mock - * - Hard state reset - */ -@Composable -fun DebugMenuScreen( - repository: SettingsRepository, - onBack: () -> Unit, - modifier: Modifier = Modifier -) { - val settings by repository.settingsFlow.collectAsState(initial = FocusSettings()) - val scope = rememberCoroutineScope() - - Column( - modifier = modifier - .fillMaxSize() - .background(PureBlack) - .padding(WindowInsets.statusBars.asPaddingValues()) - ) { - FocusGramTopBar( - title = "Developer Diagnostics", - onBack = onBack - ) - - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 16.dp), - verticalArrangement = Arrangement.spacedBy(14.dp) - ) { - item { - FocusGramCard( - modifier = Modifier.fillMaxWidth(), - backgroundColor = DarkCardElevated - ) { - Column(modifier = Modifier.padding(16.dp)) { - Text( - text = "Current State", - style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), - color = FocusGold - ) - Spacer(modifier = Modifier.height(6.dp)) - Text( - text = "Level: ${settings.userLevel} · XP: ${settings.userXP}\n" + - "Reels Min: ${settings.reelsMinutes} · Insta Min: ${settings.instaMinutes}\n" + - "Ads Today: ${settings.adsWatchedToday} · AppLock: ${settings.appLockEnabled}", - style = MaterialTheme.typography.bodyMedium, - color = TextPrimary - ) - } - } - } - - item { - FocusGramSectionHeader(title = "Level & XP Grants") - } - - item { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Button( - onClick = { scope.launch { repository.addXP(20) } }, - modifier = Modifier.weight(1f), - colors = ButtonDefaults.buttonColors(containerColor = InstagramBlue) - ) { - Text("+20 XP") - } - Button( - onClick = { scope.launch { repository.addXP(100) } }, - modifier = Modifier.weight(1f), - colors = ButtonDefaults.buttonColors(containerColor = FocusGreen) - ) { - Text("+100 XP (Lvl Up)") - } - } - } - - item { - FocusGramSectionHeader(title = "Credit Balances") - } - - item { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Button( - onClick = { scope.launch { repository.addReelsMinutes(15) } }, - modifier = Modifier.weight(1f), - colors = ButtonDefaults.buttonColors(containerColor = FocusGold) - ) { - Text("+15m Reels") - } - Button( - onClick = { scope.launch { repository.addInstaMinutes(30) } }, - modifier = Modifier.weight(1f), - colors = ButtonDefaults.buttonColors(containerColor = InstagramBlue) - ) { - Text("+30m Insta") - } - } - } - - item { - FocusGramSectionHeader(title = "Danger Zone") - } - - item { - OutlinedButton( - onClick = { - scope.launch { - repository.deductReelsMinute() - repository.deductInstaMinute() - } - }, - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(12.dp) - ) { - Text("Drain 1 Minute Each", color = AlertRed) - } - } - - item { - Spacer(modifier = Modifier.height(30.dp)) - } - } - } -} diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelHistory.kt b/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelHistory.kt deleted file mode 100644 index e983a4d..0000000 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelHistory.kt +++ /dev/null @@ -1,212 +0,0 @@ -package com.ujwal.focusgram.feature.history - -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import org.json.JSONArray -import org.json.JSONObject -import java.io.File -import java.util.concurrent.Executors - -data class ReelEntry( - val code: String, - val author: String, // may be empty if no source had it - val caption: String, // empty is normal, many reels have no caption - val lastSeen: Long, // epoch millis - val views: Int, -) { - val url: String get() = "https://www.instagram.com/reel/${code.removePrefix("reel_")}/" -} - -class ReelHistory(private val file: File) { - private val lock = Any() - private val io = Executors.newSingleThreadExecutor() - private val sessionCodes = HashSet() - private var entries: List = load() // newest first - - var onNewReelWatched: ((String) -> Unit)? = null - - private fun getTodayStartMillis(): Long { - val cal = java.util.Calendar.getInstance().apply { - set(java.util.Calendar.HOUR_OF_DAY, 0) - set(java.util.Calendar.MINUTE, 0) - set(java.util.Calendar.SECOND, 0) - set(java.util.Calendar.MILLISECOND, 0) - } - return cal.timeInMillis - } - - private fun calculateTodayCount(list: List): Int { - val start = getTodayStartMillis() - return list.count { it.lastSeen >= start } - } - - private val _items = MutableStateFlow(entries) - val items: StateFlow> = _items - - private val _sessionCount = MutableStateFlow(0) - val sessionCount: StateFlow = _sessionCount // unique reels since app start - - private val _todayCount = MutableStateFlow(calculateTodayCount(entries)) - val todayCount: StateFlow = _todayCount // unique reels watched today - - companion object { - private val IGNORED_CODES = setOf( - "home", "explore", "reels", "reel", "direct", "accounts", - "stories", "p", "about", "legal", "videos", "audio", "clips", - "trending", "foryou", "following", "live" - ) - private val GENERIC_AUTHORS = setOf( - "home", "explore", "reels", "reel", "direct", "instagram", - "instagram creator", "instagram user", "instagram video", "creator", "user", "" - ) - private val GENERIC_CAPTIONS = setOf( - "instagram reel", "reel", "reels", "instagram", "instagram video", "video", "" - ) - } - - private fun sanitizeCaption(rawCaption: String, cleanAuthor: String): String { - val clean = rawCaption.trim() - val lower = clean.lowercase() - val isGeneric = lower.isBlank() || - lower in GENERIC_CAPTIONS || - lower.startsWith("instagram reel") || - lower.startsWith("reel by @") || - lower.startsWith("reel by ") || - (cleanAuthor.isNotBlank() && lower == cleanAuthor.lowercase()) || - lower.endsWith("· original audio") || - lower.endsWith("original audio") - return if (isGeneric) "" else clean - } - - private fun isCaptionInvalid(caption: String, author: String): Boolean { - return sanitizeCaption(caption, author).isBlank() - } - - fun onWatched(code: String, author: String, caption: String): Int = synchronized(lock) { - val cleanCode = code.trim().removePrefix("/").removeSuffix("/") - val lowerCode = cleanCode.lowercase() - if (cleanCode.length < 5 || lowerCode in IGNORED_CODES) { - return _sessionCount.value - } - val finalCode = if (cleanCode.startsWith("reel_")) cleanCode.removePrefix("reel_") else cleanCode - if (finalCode.length < 5 || finalCode.lowercase() in IGNORED_CODES) { - return _sessionCount.value - } - - val cleanAuthor = author.trim().removePrefix("@") - val lowerAuthor = cleanAuthor.lowercase() - val isGenericAuthor = lowerAuthor in GENERIC_AUTHORS || lowerAuthor.startsWith("instagram") - val sanitizedAuthor = if (isGenericAuthor) "" else cleanAuthor - - val sanitizedCaption = sanitizeCaption(caption, sanitizedAuthor) - - val isNew = sessionCodes.add(finalCode) - val old = entries.firstOrNull { it.code == finalCode } - - val resolvedAuthor = sanitizedAuthor.ifEmpty { old?.author ?: "" } - val resolvedCaption = sanitizedCaption.ifEmpty { - val oldCaption = old?.caption ?: "" - if (!isCaptionInvalid(oldCaption, resolvedAuthor)) oldCaption else "" - } - - val updated = ReelEntry( - code = finalCode, - author = resolvedAuthor, - caption = resolvedCaption, - lastSeen = System.currentTimeMillis(), - views = (old?.views ?: 0) + 1, - ) - commit(listOf(updated) + entries.filter { it.code != finalCode }) - _sessionCount.value = sessionCodes.size - if (isNew) { - onNewReelWatched?.invoke(finalCode) - } - sessionCodes.size - } - - fun onMeta(code: String, author: String, caption: String) { - synchronized(lock) { - val cleanCode = if (code.startsWith("reel_")) code.removePrefix("reel_") else code - if (entries.none { it.code == cleanCode }) return - - val cleanAuthor = author.trim().removePrefix("@") - val lowerAuthor = cleanAuthor.lowercase() - val isGenericAuthor = lowerAuthor in GENERIC_AUTHORS || lowerAuthor.startsWith("instagram") - val sanitizedAuthor = if (isGenericAuthor) "" else cleanAuthor - - val sanitizedCaption = sanitizeCaption(caption, sanitizedAuthor) - - commit(entries.map { - if (it.code == cleanCode) { - val resolvedAuthor = sanitizedAuthor.ifEmpty { it.author } - val currentCaptionInvalid = isCaptionInvalid(it.caption, resolvedAuthor) - val resolvedCaption = if (sanitizedCaption.isNotEmpty()) { - sanitizedCaption - } else if (!currentCaptionInvalid) { - it.caption - } else { - "" - } - it.copy( - author = resolvedAuthor, - caption = resolvedCaption - ) - } else it - }) - } - } - - fun delete(code: String) = synchronized(lock) { commit(entries.filter { it.code != code }) } - - fun clear() = synchronized(lock) { commit(emptyList()) } - - fun resetSession() = synchronized(lock) { - sessionCodes.clear() - _sessionCount.value = 0 - } - - private fun commit(list: List) { - entries = list.take(500) - _items.value = entries - _todayCount.value = calculateTodayCount(entries) - val snapshot = entries - io.execute { save(snapshot) } - } - - private fun save(list: List) { - try { - val arr = JSONArray() - list.forEach { - arr.put( - JSONObject() - .put("code", it.code) - .put("author", it.author) - .put("caption", it.caption) - .put("lastSeen", it.lastSeen) - .put("views", it.views) - ) - } - val tmp = File(file.parentFile, file.name + ".tmp") - tmp.writeText(arr.toString()) - tmp.renameTo(file) - } catch (_: Exception) {} - } - - private fun load(): List = try { - if (!file.exists()) emptyList() else { - val arr = JSONArray(file.readText()) - (0 until arr.length()).map { - val o = arr.getJSONObject(it) - ReelEntry( - o.getString("code"), - o.optString("author"), - o.optString("caption"), - o.optLong("lastSeen"), - o.optInt("views", 1) - ) - } - } - } catch (_: Exception) { - emptyList() - } -} diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeService.kt b/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeService.kt deleted file mode 100644 index ed294da..0000000 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeService.kt +++ /dev/null @@ -1,295 +0,0 @@ -package com.ujwal.focusgram.feature.screentime - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.app.Service -import android.content.Context -import android.content.Intent -import android.os.Build -import android.os.IBinder -import android.util.Log -import androidx.core.app.NotificationCompat -import androidx.core.app.ServiceCompat -import android.content.pm.ServiceInfo -import com.ujwal.focusgram.FocusGramApp -import com.ujwal.focusgram.MainActivity -import com.ujwal.focusgram.R -import com.ujwal.focusgram.core.database.ScreenTimeRecord -import com.ujwal.focusgram.core.database.SessionRecord -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale - -/** - * ScreenTimeService - * Android Foreground Service tracking active Instagram usage, updating persistent notifications, - * draining credit minutes, and triggering session limit gates. - */ -class ScreenTimeService : Service() { - - private val serviceScope = CoroutineScope(Dispatchers.Default) - private var trackingJob: Job? = null - - private var sessionStartTime = 0L - private var sessionSeconds = 0 - private var sessionType = "INSTAGRAM" // "INSTAGRAM" or "REELS" - private var isReelsSession = false - - override fun onCreate() { - super.onCreate() - createNotificationChannel() - val initialNotification = buildNotification("Focus Session Active", "Tracking active session time...") - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - ServiceCompat.startForeground( - this, - NOTIFICATION_ID, - initialNotification, - ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE - ) - } else { - startForeground(NOTIFICATION_ID, initialNotification) - } - } catch (e: Exception) { - Log.e(TAG, "Primary startForeground failed in onCreate: ${e.message}", e) - try { - startForeground(NOTIFICATION_ID, initialNotification) - } catch (fallbackEx: Exception) { - Log.e(TAG, "Fallback startForeground failed: ${fallbackEx.message}", fallbackEx) - stopSelf() - } - } - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - when (intent?.action) { - ACTION_START -> { - isReelsSession = intent.getBooleanExtra(EXTRA_IS_REELS, false) - sessionType = if (isReelsSession) "REELS" else "INSTAGRAM" - startTracking() - } - ACTION_UPDATE_ROUTE -> { - val wasReels = isReelsSession - isReelsSession = intent.getBooleanExtra(EXTRA_IS_REELS, false) - sessionType = if (isReelsSession) "REELS" else "INSTAGRAM" - if (wasReels != isReelsSession && trackingJob?.isActive == true) { - val minutes = sessionSeconds / 60 - val secs = sessionSeconds % 60 - val timeStr = String.format("%02d:%02d", minutes, secs) - val title = if (isReelsSession) "Reels Focus: $timeStr" else "Instagram Focus: $timeStr" - updateNotification(title, "Discipline active. Stay intentional.") - } - } - ACTION_STOP -> { - stopTracking() - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - stopForeground(STOP_FOREGROUND_REMOVE) - } else { - @Suppress("DEPRECATION") - stopForeground(true) - } - } catch (_: Exception) {} - stopSelf() - } - } - return START_NOT_STICKY - } - - private fun startTracking() { - if (trackingJob?.isActive == true) return - - sessionStartTime = System.currentTimeMillis() - sessionSeconds = 0 - - updateNotification("Focus Session Active", "Tracking active session time...") - - trackingJob = serviceScope.launch { - val app = FocusGramApp.instance - val db = app.database - val settingsRepo = app.settingsRepository - val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US) - - var secondsAccumulator = 0 - - while (isActive) { - delay(1000L) - sessionSeconds++ - secondsAccumulator++ - - // Update notification every 5 seconds - if (sessionSeconds % 5 == 0) { - val minutes = sessionSeconds / 60 - val secs = sessionSeconds % 60 - val timeStr = String.format("%02d:%02d", minutes, secs) - val title = if (isReelsSession) "Reels Focus: $timeStr" else "Instagram Focus: $timeStr" - updateNotification(title, "Discipline active. Stay intentional.") - } - - // Every 60 seconds: Drain credits & check daily limits - if (secondsAccumulator >= 60) { - // Drain credit if applicable - if (isReelsSession) { - settingsRepo.deductReelsMinute() - } else { - settingsRepo.deductInstaMinute() - } - - // Check daily limit - val settings = settingsRepo.settingsFlow.first() - val today = dateFormat.format(Date()) - val todayRecord = db.screenTimeDao().getForDateSync(today) - val todayTotalSeconds = todayRecord?.seconds ?: 0 - val dailyLimitSeconds = settings.dailyLimitMinutes * 60 - - if (dailyLimitSeconds in 1..todayTotalSeconds) { - Log.i(TAG, "Daily screen time limit reached ($dailyLimitSeconds s). Triggering session limit broadcast.") - val limitIntent = Intent(ACTION_LIMIT_EXCEEDED).apply { - setPackage(packageName) - } - sendBroadcast(limitIntent) - } - - secondsAccumulator = 0 - } - } - } - } - - private fun stopTracking() { - trackingJob?.cancel() - trackingJob = null - - val endTime = System.currentTimeMillis() - val duration = ((endTime - sessionStartTime) / 1000).toInt() - - if (duration > 5) { - serviceScope.launch { - try { - val app = FocusGramApp.instance - // Insert completed session record - app.database.sessionRecordDao().insert( - SessionRecord( - sessionType = sessionType, - startTime = sessionStartTime, - endTime = endTime, - durationSeconds = duration, - completed = true - ) - ) - } catch (e: Exception) { - Log.e(TAG, "Error saving session record on stop: ${e.message}", e) - } - } - } - } - - private fun createNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = NotificationChannel( - CHANNEL_ID, - "FocusGram Session Timer", - NotificationManager.IMPORTANCE_LOW - ).apply { - description = "Shows active Instagram usage session duration and limits." - setShowBadge(false) - } - val manager = getSystemService(NotificationManager::class.java) - manager.createNotificationChannel(channel) - } - } - - private fun buildNotification(title: String, content: String): Notification { - val launchIntent = Intent(this, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_SINGLE_TOP - } - val pendingIntent = PendingIntent.getActivity( - this, - 0, - launchIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - - return NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle(title) - .setContentText(content) - .setSmallIcon(R.drawable.ic_notification) - .setContentIntent(pendingIntent) - .setOngoing(true) - .setSilent(true) - .setPriority(NotificationCompat.PRIORITY_LOW) - .build() - } - - private fun updateNotification(title: String, content: String) { - try { - val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - manager.notify(NOTIFICATION_ID, buildNotification(title, content)) - } catch (e: Exception) { - Log.w(TAG, "Failed to update notification: ${e.message}") - } - } - - override fun onDestroy() { - stopTracking() - super.onDestroy() - } - - override fun onBind(intent: Intent?): IBinder? = null - - companion object { - private const val TAG = "ScreenTimeService" - const val CHANNEL_ID = "focusgram_session_timer" - const val NOTIFICATION_ID = 1001 - - const val ACTION_START = "com.ujwal.focusgram.action.START_SESSION" - const val ACTION_STOP = "com.ujwal.focusgram.action.STOP_SESSION" - const val ACTION_UPDATE_ROUTE = "com.ujwal.focusgram.action.UPDATE_ROUTE" - const val ACTION_LIMIT_EXCEEDED = "com.ujwal.focusgram.action.LIMIT_EXCEEDED" - const val EXTRA_IS_REELS = "extra_is_reels" - - fun startSession(context: Context, isReels: Boolean = false) { - try { - val intent = Intent(context, ScreenTimeService::class.java).apply { - action = ACTION_START - putExtra(EXTRA_IS_REELS, isReels) - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - context.startForegroundService(intent) - } else { - context.startService(intent) - } - } catch (e: Exception) { - Log.e(TAG, "Failed to startSession: ${e.message}", e) - } - } - - fun updateRoute(context: Context, isReels: Boolean) { - val intent = Intent(context, ScreenTimeService::class.java).apply { - action = ACTION_UPDATE_ROUTE - putExtra(EXTRA_IS_REELS, isReels) - } - try { - context.startService(intent) - } catch (e: Exception) { - Log.w(TAG, "Failed to send route update to ScreenTimeService: ${e.message}") - } - } - - fun stopSession(context: Context) { - val intent = Intent(context, ScreenTimeService::class.java).apply { - action = ACTION_STOP - } - context.startService(intent) - } - } -} diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BaitEngine.kt b/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BaitEngine.kt deleted file mode 100644 index 07776b2..0000000 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BaitEngine.kt +++ /dev/null @@ -1,99 +0,0 @@ -package com.ujwal.focusgram.feature.wellness - -import android.content.Context -import android.util.Log -import com.ujwal.focusgram.core.datastore.SettingsRepository -import java.util.Random - -/** - * BaitOutcome - * Possible outcomes of activating the gamified "Bait Me" button. - */ -enum class BaitOutcome(val label: String, val subtext: String) { - OPEN_AD_SITE_AND_RESET("ðŸ’ļ Session Reset!", "All session credits have been reset. Better luck next time."), - ADD_TEN_MINUTES("⏰ +10 Minutes!", "You earned 10 extra minutes. Use them wisely!"), - REDUCE_SESSION_TIME("âģ Time Deducted!", "The Bait Me took 1 to 5 minutes away!"), - INCREASE_COOLDOWN("🧊 Cooldown Increased!", "Cooldown period extended by 10 minutes."), - END_REEL_SESSION("🎎 Reel Session Ended!", "Your reel session has been cut short."), - END_APP_SESSION("ðŸ“ą App Session Ended!", "Your Instagram session has been ended.") -} - -/** - * BaitEngine - * Weighted random gamification engine that rewards or penalizes risky app interactions. - */ -class BaitEngine( - private val context: Context, - private val settingsRepository: SettingsRepository -) { - private val random = Random() - private var lastActivationTime = 0L - - val isOnCooldown: Boolean - get() = (System.currentTimeMillis() - lastActivationTime) < COOLDOWN_MILLIS - - val remainingCooldownMinutes: Int - get() { - val elapsed = System.currentTimeMillis() - lastActivationTime - val remaining = (COOLDOWN_MILLIS - elapsed) / (1000 * 60) - return remaining.toInt().coerceAtLeast(0) - } - - fun roll(): BaitOutcome { - val r = random.nextInt(100) - return when { - r < 30 -> BaitOutcome.OPEN_AD_SITE_AND_RESET - r < 50 -> BaitOutcome.ADD_TEN_MINUTES - r < 65 -> BaitOutcome.REDUCE_SESSION_TIME - r < 80 -> BaitOutcome.INCREASE_COOLDOWN - r < 90 -> BaitOutcome.END_REEL_SESSION - else -> BaitOutcome.END_APP_SESSION - } - } - - suspend fun activate( - onResetSession: () -> Unit, - onEndSession: () -> Unit, - onOpenUrl: (String) -> Unit - ): BaitOutcome { - val outcome = roll() - lastActivationTime = System.currentTimeMillis() - - Log.i(TAG, "Bait Me activated. Rolled outcome: ${outcome.name}") - - when (outcome) { - BaitOutcome.OPEN_AD_SITE_AND_RESET -> { - onResetSession() - onOpenUrl(AD_WEBSITE_URL) - } - BaitOutcome.ADD_TEN_MINUTES -> { - settingsRepository.addInstaMinutes(10) - } - BaitOutcome.REDUCE_SESSION_TIME -> { - val deduction = 1 + random.nextInt(5) - for (i in 0 until deduction) { - settingsRepository.deductInstaMinute() - } - } - BaitOutcome.INCREASE_COOLDOWN -> { - settingsRepository.setCooldownMinutes(25) - } - BaitOutcome.END_REEL_SESSION -> { - onResetSession() - } - BaitOutcome.END_APP_SESSION -> { - onEndSession() - } - } - - return outcome - } - - companion object { - private const val TAG = "BaitEngine" - private const val COOLDOWN_MILLIS = 30 * 60 * 1000L // 30 minutes - - const val AD_WEBSITE_URL = - "https://www.effectivecpmnetwork.com/qbwsaqj5?key=e547ad0035c9e857ba0ee18506a45f13" - } -} diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BaitMeButton.kt b/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BaitMeButton.kt deleted file mode 100644 index 61050d3..0000000 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BaitMeButton.kt +++ /dev/null @@ -1,177 +0,0 @@ -package com.ujwal.focusgram.feature.wellness - -import android.view.HapticFeedbackConstants -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Star -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.rotate -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.ujwal.focusgram.core.ui.theme.AlertRed -import com.ujwal.focusgram.core.ui.theme.DarkCard -import com.ujwal.focusgram.core.ui.theme.FocusGold -import com.ujwal.focusgram.core.ui.theme.FocusGreen -import com.ujwal.focusgram.core.ui.theme.PureBlack -import com.ujwal.focusgram.core.ui.theme.TextMuted -import com.ujwal.focusgram.core.ui.theme.TextPrimary -import com.ujwal.focusgram.core.ui.theme.TextSecondary -import kotlinx.coroutines.launch - -/** - * BaitMeButton - * Floating gamified overlay button with rotation animation, haptics, and cooldown badge. - */ -@Composable -fun BaitMeButton( - baitEngine: BaitEngine, - userLevel: Int, - onResetSession: () -> Unit, - onEndSession: () -> Unit, - onOpenUrl: (String) -> Unit, - modifier: Modifier = Modifier -) { - if (userLevel < 3) return // Unlocks at Level 3 - - val scope = rememberCoroutineScope() - val rotation = remember { Animatable(0f) } - var isSpinning by remember { mutableStateOf(false) } - var activeOutcome by remember { mutableStateOf(null) } - val view = LocalView.current - val isOnCooldown = baitEngine.isOnCooldown - - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier - ) { - Box(contentAlignment = Alignment.Center) { - Box( - modifier = Modifier - .size(46.dp) - .clip(CircleShape) - .background( - if (isOnCooldown) DarkCard else FocusGold.copy(alpha = 0.18f) - ) - .border( - 2.dp, - if (isOnCooldown) TextMuted else FocusGold, - CircleShape - ) - .rotate(rotation.value) - .clickable(enabled = !isOnCooldown && !isSpinning) { - view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - isSpinning = true - scope.launch { - rotation.animateTo( - targetValue = rotation.value + 1080f, - animationSpec = tween(durationMillis = 1200) - ) - val outcome = baitEngine.activate( - onResetSession = onResetSession, - onEndSession = onEndSession, - onOpenUrl = onOpenUrl - ) - isSpinning = false - activeOutcome = outcome - view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) - } - }, - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = Icons.Default.Star, - contentDescription = "Bait Me", - tint = if (isOnCooldown) TextMuted else FocusGold, - modifier = Modifier.size(22.dp) - ) - } - - // Cooldown indicator badge - if (isOnCooldown) { - Box( - modifier = Modifier - .align(Alignment.BottomEnd) - .clip(RoundedCornerShape(6.dp)) - .background(PureBlack) - .padding(horizontal = 4.dp, vertical = 1.dp) - ) { - Text( - text = "${baitEngine.remainingCooldownMinutes}m", - style = MaterialTheme.typography.labelSmall.copy( - fontSize = 8.sp, - fontWeight = FontWeight.Bold - ), - color = FocusGold - ) - } - } - } - - Spacer(modifier = Modifier.height(2.dp)) - - Text( - text = "Bait Me", - style = MaterialTheme.typography.labelSmall.copy(fontSize = 9.sp), - color = if (isOnCooldown) TextMuted else TextSecondary - ) - } - - // Outcome Result Dialog - activeOutcome?.let { outcome -> - AlertDialog( - onDismissRequest = { activeOutcome = null }, - title = { - Text( - text = outcome.label, - style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold), - color = if (outcome == BaitOutcome.ADD_TEN_MINUTES) FocusGreen else AlertRed - ) - }, - text = { - Text( - text = outcome.subtext, - style = MaterialTheme.typography.bodyMedium, - color = TextSecondary - ) - }, - containerColor = DarkCard, - confirmButton = { - Button( - onClick = { activeOutcome = null }, - colors = ButtonDefaults.buttonColors(containerColor = FocusGold) - ) { - Text("OK", color = PureBlack, fontWeight = FontWeight.Bold) - } - } - ) - } -} diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BaitMeFullScreen.kt b/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BaitMeFullScreen.kt deleted file mode 100644 index ad14d83..0000000 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BaitMeFullScreen.kt +++ /dev/null @@ -1,214 +0,0 @@ -package com.ujwal.focusgram.feature.wellness - -import android.view.HapticFeedbackConstants -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.CheckCircle -import androidx.compose.material.icons.filled.Star -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.rotate -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.ujwal.focusgram.core.ui.components.FocusGramCard -import com.ujwal.focusgram.core.ui.theme.AlertRed -import com.ujwal.focusgram.core.ui.theme.DarkCard -import com.ujwal.focusgram.core.ui.theme.DarkCardElevated -import com.ujwal.focusgram.core.ui.theme.FocusGold -import com.ujwal.focusgram.core.ui.theme.FocusGreen -import com.ujwal.focusgram.core.ui.theme.PureBlack -import com.ujwal.focusgram.core.ui.theme.TextMuted -import com.ujwal.focusgram.core.ui.theme.TextPrimary -import com.ujwal.focusgram.core.ui.theme.TextSecondary -import kotlinx.coroutines.launch - -/** - * BaitMeFullScreen - * Fullscreen gamification spin wheel screen. - */ -@Composable -fun BaitMeFullScreen( - baitEngine: BaitEngine, - onClose: () -> Unit, - onResetSession: () -> Unit, - onEndSession: () -> Unit, - onOpenUrl: (String) -> Unit, - modifier: Modifier = Modifier -) { - val scope = rememberCoroutineScope() - val rotation = remember { Animatable(0f) } - var isSpinning by remember { mutableStateOf(false) } - var done by remember { mutableStateOf(false) } - var lastOutcome by remember { mutableStateOf(null) } - val view = LocalView.current - - Box( - modifier = modifier - .fillMaxSize() - .background(PureBlack) - .padding(28.dp), - contentAlignment = Alignment.Center - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth() - ) { - Text( - text = if (done) "ðŸŽē Result!" else "ðŸŽē Bait Me Wheel", - style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.Bold), - color = TextPrimary - ) - - Spacer(modifier = Modifier.height(8.dp)) - - Text( - text = if (done) { - lastOutcome?.subtext ?: "" - } else { - "Test your discipline luck. Risk session minutes for higher rewards!" - }, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium, - color = TextSecondary - ) - - Spacer(modifier = Modifier.height(36.dp)) - - // Big animated wheel icon - Box( - modifier = Modifier - .size(130.dp) - .clip(CircleShape) - .background( - if (done) FocusGreen.copy(alpha = 0.15f) else FocusGold.copy(alpha = 0.15f) - ) - .border( - 3.dp, - if (done) FocusGreen else FocusGold, - CircleShape - ) - .rotate(rotation.value), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = if (done) Icons.Default.CheckCircle else Icons.Default.Star, - contentDescription = null, - tint = if (done) FocusGreen else FocusGold, - modifier = Modifier.size(60.dp) - ) - } - - Spacer(modifier = Modifier.height(36.dp)) - - // Outcome card if completed - if (done && lastOutcome != null) { - FocusGramCard( - modifier = Modifier.fillMaxWidth(), - backgroundColor = DarkCardElevated - ) { - Column( - modifier = Modifier.padding(20.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = lastOutcome!!.label, - style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold), - color = if (lastOutcome == BaitOutcome.ADD_TEN_MINUTES) FocusGreen else AlertRed - ) - Spacer(modifier = Modifier.height(6.dp)) - Text( - text = lastOutcome!!.subtext, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium, - color = TextSecondary - ) - } - } - Spacer(modifier = Modifier.height(24.dp)) - } - - // Main CTA Button - Button( - onClick = { - if (done) { - onClose() - } else if (!isSpinning) { - view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - isSpinning = true - scope.launch { - rotation.animateTo( - targetValue = rotation.value + 1800f, - animationSpec = tween(durationMillis = 1800) - ) - val outcome = baitEngine.activate( - onResetSession = onResetSession, - onEndSession = onEndSession, - onOpenUrl = onOpenUrl - ) - isSpinning = false - done = true - lastOutcome = outcome - view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) - } - } - }, - modifier = Modifier - .fillMaxWidth() - .height(54.dp), - colors = ButtonDefaults.buttonColors( - containerColor = if (done) FocusGreen else FocusGold - ), - shape = RoundedCornerShape(16.dp), - enabled = !isSpinning - ) { - Text( - text = when { - isSpinning -> "Rolling..." - done -> "Done — Close" - else -> "ðŸŽē Spin the Wheel!" - }, - style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), - color = PureBlack - ) - } - - if (!done) { - Spacer(modifier = Modifier.height(14.dp)) - TextButton(onClick = onClose) { - Text("Not now", color = TextMuted) - } - } - } - } -} diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/CreditStore.kt b/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/CreditStore.kt deleted file mode 100644 index 48a5839..0000000 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/CreditStore.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.ujwal.focusgram.feature.wellness - -import com.ujwal.focusgram.core.datastore.SettingsRepository -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale - -/** - * CreditStore - * Manages earned focus time balances (reels and Instagram minutes) - * acquired through mindful discipline challenges and rewarded ads. - */ -class CreditStore(private val settingsRepository: SettingsRepository) { - - val reelsMinutes: Flow = settingsRepository.settingsFlow.map { it.reelsMinutes } - val instaMinutes: Flow = settingsRepository.settingsFlow.map { it.instaMinutes } - val adsWatchedToday: Flow = settingsRepository.settingsFlow.map { it.adsWatchedToday } - val adsWatchedAllTime: Flow = settingsRepository.settingsFlow.map { it.adsWatchedAllTime } - val userLevel: Flow = settingsRepository.settingsFlow.map { it.userLevel } - val userXP: Flow = settingsRepository.settingsFlow.map { it.userXP } - - val canWatchAdToday: Flow = settingsRepository.settingsFlow.map { - val today = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date()) - if (it.lastAdDate != today) true else it.adsWatchedToday < MAX_DAILY_ADS - } - - suspend fun recordRewardedAd(isReels: Boolean = false) { - val today = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date()) - if (isReels) { - settingsRepository.addReelsMinutes(MINUTES_PER_AD) - } else { - settingsRepository.addInstaMinutes(MINUTES_PER_AD) - } - settingsRepository.recordRewardedAd(todayKey = today, minutesEarned = 0) - settingsRepository.addXP(XP_PER_AD) - } - - suspend fun deductActiveMinute(isReels: Boolean) { - if (isReels) { - settingsRepository.deductReelsMinute() - } else { - settingsRepository.deductInstaMinute() - } - } - - companion object { - const val MINUTES_PER_AD = 2 - const val MAX_DAILY_ADS = 5 - const val XP_PER_AD = 25 - } -} diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/BaitEngineTest.kt b/Ported/app/src/test/kotlin/com/ujwal/focusgram/BaitEngineTest.kt deleted file mode 100644 index aff7b53..0000000 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/BaitEngineTest.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.ujwal.focusgram - -import com.ujwal.focusgram.feature.wellness.BaitOutcome -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull -import org.junit.Assert.assertTrue -import org.junit.Test - -/** - * BaitEngineTest - * Validates weighted random mechanics and outcome labels. - */ -class BaitEngineTest { - - @Test - fun testBaitOutcomeValues() { - val outcomes = BaitOutcome.entries - assertEquals(6, outcomes.size) - - outcomes.forEach { outcome -> - assertNotNull(outcome.label) - assertTrue(outcome.label.isNotEmpty()) - assertNotNull(outcome.subtext) - assertTrue(outcome.subtext.isNotEmpty()) - } - } - - @Test - fun testPositiveOutcomesExist() { - val positive = BaitOutcome.ADD_TEN_MINUTES - assertTrue(positive.label.contains("+10 Minutes")) - } -} diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/CreditStoreTest.kt b/Ported/app/src/test/kotlin/com/ujwal/focusgram/CreditStoreTest.kt deleted file mode 100644 index 13b8860..0000000 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/CreditStoreTest.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.ujwal.focusgram - -import com.ujwal.focusgram.feature.wellness.CreditStore -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test - -/** - * CreditStoreTest - * Validates economy constants, credit calculations, and daily limits for rewarded ads. - */ -class CreditStoreTest { - - @Test - fun testEconomyConstants() { - assertEquals(2, CreditStore.MINUTES_PER_AD) - assertEquals(5, CreditStore.MAX_DAILY_ADS) - assertEquals(25, CreditStore.XP_PER_AD) - } - - @Test - fun testDailyAdLimitPredicate() { - val today = "2026-10-10" - - // If lastAdDate is not today, user can watch ad regardless of count - val differentDayLastAd = "2026-10-09" - val canWatchNewDay = differentDayLastAd != today || 5 < CreditStore.MAX_DAILY_ADS - assertTrue(canWatchNewDay) - - // If today and under max limit (e.g. 4 ads watched), user can watch - val canWatchUnderLimit = today != today || 4 < CreditStore.MAX_DAILY_ADS - assertTrue(canWatchUnderLimit) - - // If today and at max limit (5 ads watched), user cannot watch - val canWatchAtLimit = today != today || 5 < CreditStore.MAX_DAILY_ADS - assertFalse(canWatchAtLimit) - } - - @Test - fun testMinutesAccumulationMath() { - var reelsMinutes = 0 - var instaMinutes = 0 - - // Watch 3 ads for reels (+2 mins each) - repeat(3) { - reelsMinutes += CreditStore.MINUTES_PER_AD - } - assertEquals(6, reelsMinutes) - - // Watch 2 ads for Instagram (+2 mins each) - repeat(2) { - instaMinutes += CreditStore.MINUTES_PER_AD - } - assertEquals(4, instaMinutes) - - // Consume 1 minute from each - reelsMinutes = (reelsMinutes - 1).coerceAtLeast(0) - instaMinutes = (instaMinutes - 1).coerceAtLeast(0) - - assertEquals(5, reelsMinutes) - assertEquals(3, instaMinutes) - } -} diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/EffortFrictionGateTest.kt b/Ported/app/src/test/kotlin/com/ujwal/focusgram/EffortFrictionGateTest.kt deleted file mode 100644 index 9b8cc3e..0000000 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/EffortFrictionGateTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.ujwal.focusgram - -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test - -/** - * EffortFrictionGateTest - * Validates typing discipline challenge mechanics, word counting, and completion criteria. - */ -class EffortFrictionGateTest { - - private val sampleMantra = - "I choose to control my focus and not let algorithmic feeds govern my attention. Real life happens outside this screen, and my precious time deserves deliberate purpose and unwavering disciplined presence." - - @Test - fun testSampleMantraWordCount() { - val words = sampleMantra.trim().split("\\s+".toRegex()) - assertTrue("Mantra should have at least 25 words", words.size >= 25) - } - - @Test - fun testWordCountProgressCalculation() { - val targetCount = 30 - - val input10Words = "one two three four five six seven eight nine ten" - val words10 = input10Words.trim().split("\\s+".toRegex()).filter { it.isNotEmpty() } - assertEquals(10, words10.size) - - val progress = (words10.size.toFloat() / targetCount).coerceIn(0f, 1f) - assertEquals(10f / 30f, progress, 0.001f) - - val isTargetReached = words10.size >= targetCount - assertFalse(isTargetReached) - } - - @Test - fun testCompletionCondition() { - val targetCount = 30 - val fullInput = (1..35).joinToString(" ") { "word$it" } - val words = fullInput.trim().split("\\s+".toRegex()).filter { it.isNotEmpty() } - - assertTrue(words.size >= targetCount) - } -} diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/NavigationContainmentTest.kt b/Ported/app/src/test/kotlin/com/ujwal/focusgram/NavigationContainmentTest.kt deleted file mode 100644 index 1789254..0000000 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/NavigationContainmentTest.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.ujwal.focusgram - -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test - -/** - * NavigationContainmentTest - * Validates domain whitelisting, authentication domain support, and external escaping defense. - */ -class NavigationContainmentTest { - - private val allowedHosts = listOf( - "instagram.com", - "instagr.am", - "cdninstagram.com", - "fbcdn.net", - "facebook.com", - "m.facebook.com", - "accountscenter.instagram.com", - "accountscenter.facebook.com" - ) - - private fun isAllowedHost(host: String): Boolean { - val lower = host.lowercase() - return allowedHosts.any { allowed -> - lower == allowed || lower.endsWith(".$allowed") - } - } - - @Test - fun testAllowedInstagramHosts() { - assertTrue(isAllowedHost("instagram.com")) - assertTrue(isAllowedHost("www.instagram.com")) - assertTrue(isAllowedHost("help.instagram.com")) - assertTrue(isAllowedHost("about.instagram.com")) - assertTrue(isAllowedHost("instagr.am")) - assertTrue(isAllowedHost("scontent.cdninstagram.com")) - assertTrue(isAllowedHost("static.xx.fbcdn.net")) - } - - @Test - fun testAllowedAuthHosts() { - assertTrue(isAllowedHost("facebook.com")) - assertTrue(isAllowedHost("m.facebook.com")) - assertTrue(isAllowedHost("accountscenter.instagram.com")) - assertTrue(isAllowedHost("accountscenter.facebook.com")) - } - - @Test - fun testDisallowedExternalHosts() { - assertFalse(isAllowedHost("google.com")) - assertFalse(isAllowedHost("youtube.com")) - assertFalse(isAllowedHost("tiktok.com")) - assertFalse(isAllowedHost("malicious-fake-instagram.com")) - assertFalse(isAllowedHost("notinstagram.com")) - assertFalse(isAllowedHost("x.com")) - } - - @Test - fun testInternalSchemesAllowed() { - val internalSchemes = listOf("resource", "about", "data", "blob", "javascript") - internalSchemes.forEach { scheme -> - val isInternal = scheme == "resource" || scheme == "about" || scheme == "data" || scheme == "blob" || scheme == "javascript" - assertTrue(isInternal) - } - } -} diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/UserScriptAssetsTest.kt b/Ported/app/src/test/kotlin/com/ujwal/focusgram/UserScriptAssetsTest.kt deleted file mode 100644 index 53ae63e..0000000 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/UserScriptAssetsTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.ujwal.focusgram - -import org.junit.Assert.assertNotNull -import org.junit.Assert.assertTrue -import org.junit.Test -import java.io.File - -/** - * UserScriptAssetsTest - * Verifies that all required client-side injection scripts exist in assets/scripts/ - * and contain valid code structure. - */ -class UserScriptAssetsTest { - - private val expectedScripts = listOf( - "content_hider.js", - "autoplay_blocker.js", - "grayscale.js", - "spa_navigation_monitor.js", - "native_feel.js", - "reel_metadata_extractor.js" - ) - - @Test - fun testAllUserScriptsExistAndNonEmpty() { - // Path relative to execution root or app directory - val baseAssetsDir = File("src/main/assets/scripts") - val altAssetsDir = File("app/src/main/assets/scripts") - val assetsDir = if (baseAssetsDir.exists()) baseAssetsDir else altAssetsDir - - assertTrue("assets/scripts directory must exist", assetsDir.exists()) - - expectedScripts.forEach { scriptName -> - val scriptFile = File(assetsDir, scriptName) - assertTrue("Script file $scriptName must exist", scriptFile.exists()) - assertTrue("Script file $scriptName must not be empty", scriptFile.length() > 0) - - val content = scriptFile.readText() - assertTrue("$scriptName should contain function or IIFE", content.contains("function") || content.contains("=>")) - } - } -} diff --git a/README.md b/README.md index 1428df3..05cee90 100644 --- a/README.md +++ b/README.md @@ -1,171 +1,195 @@ +
+ [ This APK Doesn't require the "Advanced Flow" to install on Android Devices. ] +
-FocusGram +FocusGram Logo # FocusGram -**Use social media on your terms.** +**Digital wellness & intentionality layer for Instagram on Android** -[![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL_3.0-blue.svg)](LICENSE) -[![Version](https://img.shields.io/badge/version-2.1.0-white)](https://flutter.dev) -[![Downloads](https://img.shields.io/github/downloads/ujwal223/focusgram/total?label=downloads&color=blue&cacheSeconds=30)](https://github.com/ujwal223/focusgram/releases) +An open-source Android application built with 100% native Kotlin and Jetpack Compose that loads Instagram inside Mozilla GeckoView with embedded WebExtensions (uBlock Origin), client-side content filters, and hardware-backed wellness controls. - - Download FocusGram on Uptodown - - -[Download APK](https://github.com/ujwal223/focusgram/releases) · [View Changelog](CHANGELOG.md) · [Report a Bug](https://github.com/ujwal223/focusgram/issues/new) +[![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL_3.0-blue.svg?style=for-the-badge)](LICENSE) +[![Android](https://img.shields.io/badge/Android-8.0%2B-3DDC84.svg?style=for-the-badge&logo=android&logoColor=white)](https://developer.android.com) +[![Release](https://img.shields.io/github/v/release/Ujwal223/FocusGram?style=for-the-badge&color=0969da)](https://github.com/Ujwal223/FocusGram/releases) +[![Downloads](https://img.shields.io/github/downloads/Ujwal223/FocusGram/total?style=for-the-badge&color=fa8900)](https://github.com/Ujwal223/FocusGram/releases) +[![Uptodown](https://img.shields.io/badge/Uptodown-Download-00A859?style=for-the-badge&logo=android&logoColor=white)](https://focusgram.en.uptodown.com/android) +[![Support](https://img.shields.io/badge/Support-BuyMeMomo-FFDD00.svg?style=for-the-badge&logo=buymeacoffee&logoColor=black)](https://buymemomo.com/ujwal) +
---- - -Most people don't want to completely quit Instagram but control its usage (i.e They want to check their messages, post a story, and leave) without losing many hours to Reels and distracting content they never meant to watch. - -FocusGram is an Android-only app that loads the Instagram website with the distracting parts removed and with Extra features. No private APIs. No data collection. Just a cleaner way to use a platform you already use. - -> FocusGram is free and always will be. If it's saved you some time, show your support by buying me a momo 👉👈. -> -> [![Buy Me a Momo](https://img.shields.io/badge/-%F0%9F%A5%9F%20Buy%20Me%20a%20Momo-FF6B35?style=for-the-badge&labelColor=1a1a1a)](https://buymemomo.com/ujwal) - -FocusGram App Screenshots --- -## What it does +## The Problem & The Solution -**Focus tools** +

+ FocusGram App Demo +

-- Block Reels entirely, or allow them in timed sessions (1–30 min) with daily limits and cooldowns -- Minimal Mode strips everything down to Feed and DMs -- Hide ALL feed posts entirely. +Modern social platforms are engineered around algorithmic feedback loops, autoplaying short-form videos, and recommendation feeds designed to maximize screen time at the expense of intentionality. Most users do not want to quit Instagram entirely—they want to message friends, reply to stories, or share moments without losing hours to an endless Reel rabbit hole. -**Content filtering** +**FocusGram solves this at the browser engine level:** +Instead of modifying Instagram's proprietary binary or relying on unofficial reverse-engineered APIs, FocusGram embeds a sandboxed **Mozilla GeckoView browser engine** (`org.mozilla.geckoview:geckoview-omni`) paired with an embedded **uBlock Origin** WebExtension. All ad-blocking, element suppression, and session guardrails operate strictly client-side on the rendered web DOM. -- Hide the Explore tab or Reels tab individually -- Disable Explore and blur posts, videos on feed entirely -- Click to unblur feed posts -- Disable Reels entirely -- Disable scrolling of home feed - -**Habit tools** - -- Screen Time Dashboard: daily usage, 7-day chart, weekly average -- Grayscale Mode: reduces the visual pull of colour; can be scheduled by time of day -- Session intentions: optionally set a reason before opening the app -- Reel & App Quota: Allocate only certain time for reels and/or instagram - -**Other Features** - -- Lock the app and/or your private messages. -- See other's message without sending seen indicator* -- Choose which page to launch when app is opened. -- Choose pause time before opening app (mindfulness gate). -- Save media on your local device. ---- - -## Installation - -### Direct download -1. Go to the [Releases](https://github.com/ujwal223/focusgram/releases) page -2. Download `focusgram-release.apk` -3. Open the file and allow "Install from unknown sources" if prompted - -### Uptodown -1. Go to the [FocusGram on Uptodown](https://focusgram.en.uptodown.com/android) page -2. Click "Get the Latest Version" -3. Click "Download" -4. Open the file and allow "Install from unknown sources" if prompted - ---- - -## Privacy - -FocusGram has no access to your Instagram account credentials. It loads `instagram.com` inside a standard Android WebView and your login goes directly to Meta's servers, the same as any mobile browser. - -Our app has: -- No analytics -- No crash reporting -- No third-party SDKs -- No Logging -- No data leaves your device - ---- - -## Frequently asked questions - -**Will this get my account banned?**
-Unlikely. FocusGram's traffic is indistinguishable from someone using Instagram in Chrome. It does not use Instagram's private API, does not automate any actions, and does not intercept credentials. - -**Is this a mod of Instagram's app?**
-No. FocusGram is a separate app that loads `instagram.com` in a WebView. It does not modify Instagram's APK or use any of Meta's proprietary code. - -**How do i support this project?**
-You can support this project by donating here: [Donate](https://buymemomo.com/ujwal) - ---- - -## Building from source - -
-Technical details and build instructions - -### Requirements -- Flutter stable channel (3.38+) -- Android SDK -- NDK 28.2.12676356 -- Android SDK cmdline tools 20 -- Android build tools 34 and 35 -- JDK 17 (Eclipse Adoptium 17.0.17+) - -### Build -```bash -flutter pub get -flutter build apk --release +``` +┌─────────────────────────────────────────────────────────────────┐ +│ FocusGram Android Shell │ +├─────────────────┮───────────────────────────────┮───────────────â”Ī +│ Wellness │ Guardrails │ App Lock │ +│ [Screen Time] │ [Timed Reel Session] │ [Biometrics] │ +├─────────────────â”ī───────────────────────────────â”ī───────────────â”Ī +│ Jetpack Compose Native UI Shell │ +├─────────────────────────────────────────────────────────────────â”Ī +│ Mozilla GeckoView Sandboxed Browser Engine │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ Embedded uBlock Origin Extension + Content UserScripts │ │ +│ │ (Ad blocking, Autoplay suppression, Element blurring) │ │ +│ └─────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + â–ē + Meta Official Web Servers (HTTPS) ``` -### Architecture -FocusGram uses a standard Android System WebView to load `instagram.com`. All features are implemented client-side via: -- JavaScript injection (autoplay blocking, metadata extraction, SPA navigation monitoring) -- CSS injection (element hiding, grayscale, scroll behaviour) -- URL interception via NavigationDelegate (Reels blocking, Explore blocking) +--- -### Stack +## Core Capabilities -| | | -|---|---| -| Framework | Flutter (Dart) | -| WebView | flutter_inappwebview (Apache 2.0) | -| Storage | shared_preferences | -| License | AGPL-3.0 | - -
+ + + + + + + + + +
+

Focus Guardrails

+
    +
  • Timed Reel Sessions: Watch Reels in structured blocks (1–30 min) before a gentle pause.
  • +
  • Daily Screen Time Limits: Set strict daily quotas for overall usage.
  • +
  • Mindfulness Breath Gate: A 10-second reflection pause before entering the app.
  • +
  • Cooldown Periods: Enforce mandatory break intervals between sessions.
  • +
+
+

Calm Feeds & Content Control

+
    +
  • Blur Explore & Feed: Keep recommendation grids blurred until tapped.
  • +
  • Autoplay Blocker: Prevents intrusive video playback from hijacking attention.
  • +
  • Minimal Mode: Strips unnecessary clutter down to essential DMs and Feed.
  • +
  • Grayscale Filter: Eliminates vibrant dopamine colors on demand or on schedule.
  • +
+
+

Privacy & Security Locks

+
    +
  • Biometric & PIN App Lock: Secure the entire app behind Android Keystore authentication.
  • +
  • Individual Chat Lock: Lock specific sensitive direct message conversations with separate PINs.
  • +
  • Link Sanitizer: Strips fbclid and tracking parameters before sharing.
  • +
  • Zero Data Transmission: No telemetry, no analytics, no third-party network SDKs.
  • +
+
+

Native Android Experience

+
    +
  • Modern Apple-Style Onboarding: Elegant guided setup explaining permissions and boundaries.
  • +
  • Update Checker: In-app GitHub release notifications with one-tap downloads.
  • +
  • Dynamic Launcher Icons: Custom app icons including OLED Pitch Black, Abstract, and Classic.
  • +
  • Reel History & Local Snapshots: Save and revisit content stored completely offline in Room DB.
  • +
+
--- -## Legal disclaimer +## Architecture -FocusGram is an independent, free, and open-source productivity tool licensed under AGPL-3.0. It is not affiliated with, endorsed by, or associated with Meta Platforms, Inc. or Instagram in any way. +FocusGram is built natively for Android using standard FOSS components: -**How it works:** FocusGram embeds a standard Android System WebView that loads `instagram.com`; the same website accessible in any mobile browser. All user-facing features are implemented exclusively via client-side modifications and are never transmitted to or processed by Meta's servers. - -**What we do not do:** -- Use/Alter Instagram's or Meta's private APIs -- Intercept, read, log, or store user credentials, session data, or any sensitive content -- Modify any server-side Meta or Instagram services -- Scrape, harvest, or collect any user data -- Claim ownership of any Meta or Instagram trademarks, logos, or intellectual property — any branding visible within the app is served directly from `instagram.com` and remains the property of Meta Platforms, Inc. - -Using FocusGram is functionally equivalent to accessing Instagram through a mobile web browser with a content blocker extension. By using FocusGram, you acknowledge that you remain bound by Instagram's own Terms of Service. - -For legal concerns, contact `notujwal@proton.me` before taking any other action. +| Layer | Implementation | Purpose | +| :--- | :--- | :--- | +| **User Interface** | Jetpack Compose (Material 3) | Declarative UI, Cupertino-style controls, OLED black styling | +| **Web Engine** | Mozilla GeckoView Omni (`MPL-2.0`) | Full web standards compliance, standards-isolated browsing sandbox | +| **Ad Blocking** | Bundled uBlock Origin WebExtension | Hardware-accelerated cosmetic and network ad filtering | +| **Local Storage** | AndroidX Room & DataStore | Relational database for screen time & DataStore preferences | +| **Security** | AndroidX Security Crypto & Biometrics | Keystore-backed AES-256 MasterKey and fingerprint authentication | +| **Concurrency** | Kotlin Coroutines & Flow | Asynchronous I/O and reactive state pipelines | --- -## License +## Quick Start -Copyright ÂĐ 2025-2026 Ujwal Chapagain +### Installation -Licensed under the [GNU Affero General Public License v3.0](LICENSE). You are free to use, modify, and distribute this software under the same terms. +- **Direct APK**: Download the latest release from [GitHub Releases](https://github.com/Ujwal223/FocusGram/releases). Available in architecture splits (`arm64-v8a`, `armeabi-v7a`, `x86_64`) and Universal APK. +- **Uptodown**: Download verified builds on [FocusGram on Uptodown](https://focusgram.en.uptodown.com/android). +- **Uncloned Edition (`com.instagram.android`)**: Replace the official Instagram app directly on your device via our [Uncloned Distribution Channel](https://ujwal223.github.io/FocusGram/uncloned/). -FocusGram is built and maintained by [Ujwal Chapagain](https://github.com/Ujwal223) under AGPL-3.0, Thanks for Reading README. +--- + +### Building from Source + +#### Prerequisites +- Android 8.0+ (API Level 26+) +- JDK 21 (Temurin / Adoptium recommended) +- Android SDK Command-line Tools (Build-Tools 35.0.0) + +#### Build Commands + +```bash +# Clone repository +git clone https://github.com/Ujwal223/FocusGram.git +cd FocusGram + +# Run unit tests +./gradlew :app:testDebugUnitTest + +# Build debug APK +./gradlew :app:assembleDebug + +# Build standard release APKs (Universal & ABI splits) +./gradlew :app:assembleRelease + +# Build uncloned edition with official package name (com.instagram.android) +./gradlew :app:assembleRelease -PuseInstaPackage=true + +# Or launch the interactive build selector +./build-release.sh +``` + +--- + +## Security & Privacy Boundary + +> [!IMPORTANT] +> **Why FocusGram Presents Instagram's Web Login Screen:** +> Meta does not offer public OAuth or user-generated app passwords for third-party consumer apps. To connect to Instagram securely without risking account bans or scraping violations, FocusGram loads the official web login directly from `https://www.instagram.com/` inside an isolated Mozilla GeckoView browser session. + +- **Zero Credential Interception**: The login interface is rendered inside GeckoView as a sealed black box. FocusGram's Kotlin codebase cannot inspect, intercept, or log passwords or 2FA codes. +- **Strictly Local Storage**: All settings, screen time stats, and session records stay encrypted on your phone. +- **No Third-Party SDKs**: Zero Google Firebase, zero analytics, zero crash reporting services, and zero network calls from Kotlin to external servers. + +For detailed security architecture, threat model explanations, and vulnerability disclosure instructions, see **[SECURITY.md](SECURITY.md)**. +For contribution guidelines and development standards, see **[CONTRIBUTING.md](CONTRIBUTING.md)**. + +--- + +## Frequently Asked Questions + +**Will this get my Instagram account banned?** +No. FocusGram's web traffic is identical to accessing Instagram inside a standard Firefox or Chrome browser on Android. FocusGram does not use Instagram's private internal API, does not automate actions, and does not scrape content. + +**Can I use FocusGram alongside the official Instagram app?** +Yes! The default release uses package name `com.ujwal.focusgram` so it installs side-by-side with Instagram. If you wish to replace the official app completely so system links open in FocusGram automatically, build or download the Uncloned Edition (`com.instagram.android`). + +--- + +## License & Support + +Copyright ÂĐ 2025–2026 [Ujwal Chapagain](https://github.com/Ujwal223). +Licensed under the [GNU Affero General Public License v3.0 (AGPL-3.0)](LICENSE). + +If FocusGram has helped you regain control of your attention, support continuous open-source development: + +[![Buy Me a Momo](https://img.shields.io/badge/-%F0%9F%A5%9F%20Buy%20Me%20a%20Momo-FF6B35?style=for-the-badge&labelColor=1a1a1a)](https://buymemomo.com/ujwal) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..dc7bdf9 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,57 @@ +# Security Policy + +FocusGram takes user security and privacy seriously. Because FocusGram operates as an intentional digital wellness layer on top of Instagram, preserving trust and technical boundaries is critical. + +--- + +## Supported Versions + +Only the latest major/minor release receives active security patches. + +| Version | Supported | +| ------- | ------------------ | +| 3.x.x | ☑ïļ | +| < 2.1.0 | x | + +--- + +## Authentication Architecture & Threat Model + +### Why FocusGram Presents Instagram's Web Login Screen + +Users often ask why FocusGram shows a login screen requesting their primary Instagram credentials instead of a standard OAuth login or a scoped app-specific password. + +1. **Meta Does Not Provide Consumer OAuth or App Passwords:** + Meta does not allow third-party consumer apps to access feeds, stories, or direct messages through an official OAuth API. Furthermore, Meta does not offer user-generated app-specific passwords or personal API tokens for Instagram accounts. +2. **The GeckoView Sandbox (Black Box Isolation):** + When you log in to Instagram inside FocusGram, the login page is loaded directly from official Meta servers (`https://www.instagram.com/accounts/login/`) inside an isolated **Mozilla GeckoView browser engine**. +3. **Zero Credential Access:** + FocusGram's Kotlin codebase does not intercept, inspect, listen to, or log form inputs, password fields, or two-factor authentication (2FA) tokens. The login flow is a sealed black box: your credentials flow directly between your device's browser engine and Meta's servers over TLS (HTTPS). +4. **Session Cookie Isolation:** + Authentication cookies are stored inside Mozilla GeckoView's isolated sandboxed profile directory on your device. FocusGram does not transmit or synchronize these cookies to any external server. + +--- + +## Core Security Invariants + +1. **Zero External Data Transmission:** + FocusGram contains no analytics SDKs, no tracking beacons, no crash reporting services, and no developer telemetry. Android Kotlin code makes zero network requests to external servers. All web traffic flows strictly through GeckoView to Meta's official domains. +2. **Client-Side Scripting Boundaries:** + Content modifications (such as hiding Explore or blurring Reels) occur via strictly scoped client-side user scripts and WebExtensions (bundled uBlock Origin). Content scripts are strictly restricted to layout and presentation adjustments; they never touch password inputs, authentication headers, or private user credentials. +3. **Hardware-Backed Local Storage:** + Biometric credentials and PINs configured for App Lock and Chat Lock are verified locally using AndroidX BiometricPrompt and Android Keystore (AES-256-GCM). PIN hashes never leave the device. +4. **Link Sanitization:** + FocusGram strips tracking parameters (such as `fbclid`, `igsh`, and UTM parameters) before links are shared or opened externally. + +--- + +## Reporting a Vulnerability + +If you discover a security vulnerability or privacy flaw within FocusGram: + +1. **Do not open a public GitHub issue.** +2. Report the vulnerability privately via **[GitHub Private Vulnerability Reporting](https://github.com/Ujwal223/FocusGram/security/advisories/new)** or by emailing `notujwal@proton.me`. +3. Provide a clear description of the vulnerability, reproduction steps, and potential impact. +4. We will acknowledge receipt within 48 hours and work with you to coordinate a release before public disclosure. + +### NOTE: Focusgram & its developer are incapable of providing any bounties. \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml deleted file mode 100644 index 9353b22..0000000 --- a/analysis_options.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -analyzer: - exclude: - - v2/** - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore deleted file mode 100644 index 9fc28a3..0000000 --- a/android/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java -.cxx/ - -# Remember to never publicly share your keystore. -# See https://flutter.dev/to/reference-keystore -key.properties -**/*.keystore -**/*.jks -upload-keystore.jks - diff --git a/android/NOTICE b/android/NOTICE deleted file mode 100644 index 06a9081..0000000 --- a/android/NOTICE +++ /dev/null @@ -1,190 +0,0 @@ - - Copyright (c) 2005-2014, The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts deleted file mode 100644 index a447891..0000000 --- a/android/app/build.gradle.kts +++ /dev/null @@ -1,80 +0,0 @@ -import java.util.Properties - -plugins { - id("com.android.application") - id("kotlin-android") - // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. - id("dev.flutter.flutter-gradle-plugin") -} - -android { - namespace = "com.ujwal.focusgram" - compileSdk = flutter.compileSdkVersion - buildToolsVersion = "35.0.0" - ndkVersion = flutter.ndkVersion - - val keystorePropertiesFile = rootProject.file("key.properties") - val keystoreProperties = Properties() - if (keystorePropertiesFile.exists()) { - keystoreProperties.load(keystorePropertiesFile.inputStream()) - } - - signingConfigs { - create("release") { - keyAlias = keystoreProperties["keyAlias"] as String? - keyPassword = keystoreProperties["keyPassword"] as String? - storeFile = keystoreProperties["storeFile"]?.let { file(it) } - storePassword = keystoreProperties["storePassword"] as String? - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - isCoreLibraryDesugaringEnabled = true - } - - kotlinOptions { - jvmTarget = "17" - } - - defaultConfig { - applicationId = "com.ujwal.focusgram" - // You can update the following values to match your application needs. - // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = 24 - targetSdk = 35 - versionCode = 4 - versionName = "2.1.0" - } - - buildTypes { - release { - if (keystorePropertiesFile.exists()) { - signingConfig = signingConfigs.getByName("release") - } else { - signingConfig = signingConfigs.getByName("debug") - } - - // Fix for release crash: Apply proguard rules - isMinifyEnabled = true - isShrinkResources = true - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } - - // Narrow exclusions to only the specific modules that cause conflicts, - // not entire Google/Firebase groups (which would block AdMob & Firebase). - configurations.all { - exclude(group = "com.google.android.play", module = "core") - exclude(group = "com.google.android.play", module = "core-common") - } -} - -dependencies { - coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") -} - -flutter { - source = "../.." -} diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro deleted file mode 100644 index ea309ba..0000000 --- a/android/app/proguard-rules.pro +++ /dev/null @@ -1,37 +0,0 @@ --keep class io.flutter.** { *; } --keep class io.flutter.plugins.** { *; } --keep class com.pichillilorenzo.flutter_inappwebview.** { *; } --keep class **.GeneratedPluginRegistrant { *; } - -# Strip Google Play Core (Flutter engine bundles these unnecessarily for F-Droid) --dontwarn com.google.android.play.core.** --dontwarn com.google.android.play.core.splitinstall.** --dontwarn com.google.android.play.core.tasks.** --assumenosideeffects class com.google.android.play.core.** { *; } --assumenosideeffects class com.google.android.play.core.splitinstall.** { *; } --assumenosideeffects class com.google.android.play.core.tasks.** { *; } - -# Flutter Wrapper --keep class io.flutter.app.** { *; } --keep class io.flutter.plugin.** { *; } --keep class io.flutter.util.** { *; } --keep class io.flutter.view.** { *; } --keep class io.flutter.** { *; } --keep class io.flutter.plugins.** { *; } - -# flutter_local_notifications --keep class com.dexterous.flutterlocalnotifications.** { *; } --keep class com.google.firebase.messaging.** { *; } --dontwarn com.google.firebase.messaging.** - -# webview_flutter --keep class io.flutter.plugins.webviewflutter.** { *; } - -# Keystore and common --keep class com.ujwal.focusgram.** { *; } - -# Avoid stripping JS bridge names --keepattributes JavascriptInterface --keepclassmembers class * { - @android.webkit.JavascriptInterface ; -} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 399f698..0000000 --- a/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index fcde977..0000000 --- a/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/app/src/main/kotlin/com/ujwal/focusgram/MainActivity.kt b/android/app/src/main/kotlin/com/ujwal/focusgram/MainActivity.kt deleted file mode 100644 index 445ab01..0000000 --- a/android/app/src/main/kotlin/com/ujwal/focusgram/MainActivity.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.ujwal.focusgram - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity : FlutterActivity() diff --git a/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 9accd002a60ba442a3e87aa71fffdd6a151483b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12888 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 diff --git a/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png deleted file mode 100644 index 625081a7b7533ea4631633550563121d5aedf014..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6670 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 KE7r(cMf?wLZ - - - - - - - diff --git a/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index 9c5d6357f18032a5a9dc12ad68329fc81d67d021..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20664 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 diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 341e1427eb379918f73cd6dd4fb96e9781b21faa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41138 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

N$Sy^j^He}D6 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 diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml deleted file mode 100644 index 304732f..0000000 --- a/android/app/src/main/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 5f349f7..0000000 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index f9dcc18328218f31462ec26f6bda86767134f307..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3577 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 diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 7352438da17ee8c95262ec87b34e7e3954f3336d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1907 zcmV-(2aNcMP)7Mr{I3X`@2a{D3F|A@LjH2Y!NI_yYim z9{>@A1V}9;NYEnjNLomgrcKhSjUgcqw|*zK*Iuu^&zXJ9%spsk-1RzkqP4xjs^p`c z-Me>Y@BMzx-gD=ik?|Qfd7}la*ERt*+BN|;+BN|;+BN|;+NuMZeoZ4Oe#A= z%PuwhNX$^Na2vuXtas{l0WfdiX;#g-xI ztPOy1m(;rDqzKf0gTR>107eVq*@Dxp@1Onw1sFm;hXgY&&I&H=W8kN8t1O5PGT?4xa?4WI&d5IG7HHq(kP$LZ1Pd z>u_=d4z9zk7vN6jF!s&_uAZMoN+SrDXCH6fdP>|Kd=Y?nC;(EZ@$14DI7W=XWEr(5 zj={Yx0!TUlqk<6#Tr&`gW)XU49G%HLb{?$7=_?7u@&xh)kMg>vZ;ScZuO0#bV^kmh zfz=`g|KVrBw{}><&EPFdSb z4iR9WAkfq$Y%dT2E_YX@x+(@B&K-7(fjgZ`&*k!no_^aF&lp+`Y=@sT;guRH~&7opr3L$Z)ZZC^WN83>Y&s8>co(J^#6W&^Asoq_%T01Pz; zUCCLF^tU%a+#ZA!@FIV00-@)7;jeQeap4x4_qSv8Ht* zX>FAd1mW{Q_PXKeZUZak5kLEH1Ul*v+8eQ!P;GBBCZ;rNFt^{exc`<=aaxB-ReEK>g+#nu=5Fn9U_nqPee>NP^m?sh;`aOd^Qh#cF6xl8?)$d;bagJf1r zu%;!nbaaN`{$9l2JP$3UV5WZrktZI-)GyCi%SriY2$BPcyu1fjPu&C=3|t1f4==x$ zl#N;aWKL+BwRp`$j_hX1e7m+IcY7Ghz&KI^lPD%KXgRPGJe@+YvjOR70w4VL8j5Mn z+BQFT{1NyX&9p)?M2cFz5pVU{CB@B7htc z+Fw0_)cZFOd-n$PY@rl5CUl_Hhn|sOndYN0z zy4ZYWW(s)g=cCp(3~)%PnI8!u)anD6VGxg=l)QlxW=SdX1teel6KW10fTOP)%=v&H z(!F2FDjJ8c#PR+g#!$%E{|d`xuV}MVKV6Nc)or6hx-l{QW!ACd9NeD0Ler5ra}nu_ zAHvtw3U@;QTxM1QFLCpEg<%)-+%D?PgOPz;T*dV5d8qS6>ke+#b4=5Q#ni|vAkr#O zvPJ+{IY5HOKR(36TTW6i^i?zSGL#(B*M^a{DHonzl^2?1Vu$&y>1xV%zEZW)t-$S`Wz|g^Oas+ah_0t tZ9bjeXxjwXXxjwXXxjwXX#30q{15jo3yz$8W(NQO002ovPDHLkV1k+=m_Gmj diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 2182cb535b8ed32d460e000febdbd51c080dcbbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5569 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 Kp;pc^;(q|r3S(dZ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 46afce488d40c781a7724c6f487f110c9fb5dae7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10729 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 diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml deleted file mode 100644 index 06952be..0000000 --- a/android/app/src/main/res/values-night/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml deleted file mode 100644 index beab31f..0000000 --- a/android/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #000000 - \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml deleted file mode 100644 index cb1ef88..0000000 --- a/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/android/app/src/main/res/xml/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml deleted file mode 100644 index 91fb43d..0000000 --- a/android/app/src/main/res/xml/network_security_config.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - instagram.com - cdninstagram.com - fbcdn.net - - diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 4d800c3..0000000 --- a/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - diff --git a/android/build.gradle.kts b/android/build.gradle.kts deleted file mode 100644 index dbee657..0000000 --- a/android/build.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -val newBuildDir: Directory = - rootProject.layout.buildDirectory - .dir("../../build") - .get() -rootProject.layout.buildDirectory.value(newBuildDir) - -subprojects { - val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) - project.layout.buildDirectory.value(newSubprojectBuildDir) -} -subprojects { - project.evaluationDependsOn(":app") -} - -tasks.register("clean") { - delete(rootProject.layout.buildDirectory) -} diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html deleted file mode 100644 index 701b94c..0000000 --- a/android/build/reports/problems/problems-report.html +++ /dev/null @@ -1,663 +0,0 @@ - - - - - - - - - - - - - Gradle Configuration Cache - - - -
- -
- Loading... -
- - - - - - diff --git a/android/gradle.properties b/android/gradle.properties deleted file mode 100644 index b7532de..0000000 --- a/android/gradle.properties +++ /dev/null @@ -1,6 +0,0 @@ -org.gradle.jvmargs=-Xmx3G -XX:MaxMetaspaceSize=1G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError -android.useAndroidX=true -# This builtInKotlin flag was added automatically by Flutter migrator -android.builtInKotlin=false -# This newDsl flag was added automatically by Flutter migrator -android.newDsl=false diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index e4ef43f..0000000 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/android/popup.json b/android/popup.json index 8320a7e..a2d78d7 100644 --- a/android/popup.json +++ b/android/popup.json @@ -1,10 +1,10 @@ { - "show": false, - "id": "popup_005", - "header": "FOCUSGRAM UPDATE DISCONTINUITION!!", - "body": "Due to NO Support from community, it is being difficult for me to maintain this project, due to which, Next coming update might be the LAST update for FocusGram. THANKS FOR UNDERSTANDING! ", - "max_shows": 6, - "button_text": "OKAY..." + "show": true, + "id": "popup_006", + "header": "App Update Notice!", + "body": "New App Update is available, Please update to the latest version to enjoy new features and improvements.", + "max_shows": 4, + "button_text": "OK BRO!" -} +} \ No newline at end of file diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts deleted file mode 100644 index ca7fe06..0000000 --- a/android/settings.gradle.kts +++ /dev/null @@ -1,26 +0,0 @@ -pluginManagement { - val flutterSdkPath = - run { - val properties = java.util.Properties() - file("local.properties").inputStream().use { properties.load(it) } - val flutterSdkPath = properties.getProperty("flutter.sdk") - require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } - flutterSdkPath - } - - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.11.1" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false -} - -include(":app") diff --git a/Ported/app/build.gradle.kts b/app/build.gradle.kts similarity index 76% rename from Ported/app/build.gradle.kts rename to app/build.gradle.kts index 5eea104..eb8dfbc 100644 --- a/Ported/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,5 @@ +import java.util.Properties + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) @@ -5,16 +7,42 @@ plugins { alias(libs.plugins.ksp) } +val useInstaPackage: Boolean = run { + val cliFlag = project.findProperty("useInstaPackage") as? String + if (cliFlag != null) return@run cliFlag.toBoolean() + + val targetPkg = project.findProperty("targetPackage") as? String + if (targetPkg != null) return@run (targetPkg == "insta") + + val envFlag = System.getenv("FOCUSGRAM_USE_INSTA_PACKAGE") + if (envFlag != null) return@run envFlag.toBoolean() + + val localPropsFile = rootProject.file("local.properties") + if (localPropsFile.exists()) { + val p = Properties() + localPropsFile.inputStream().use { p.load(it) } + p.getProperty("focusgram.use_insta_package")?.toBoolean() ?: false + } else { + false + } +} + +val targetAppId = if (useInstaPackage) "com.instagram.android" else "com.ujwal.focusgram" +val targetAppName = if (useInstaPackage) "Instagram" else "FocusGram" +val apkPrefix = if (useInstaPackage) "FocusGram-uncloned-insta" else "FocusGram" + android { namespace = "com.ujwal.focusgram" compileSdk = 35 defaultConfig { - applicationId = "com.ujwal.focusgram" + applicationId = targetAppId minSdk = 26 targetSdk = 35 - versionCode = 200 - versionName = "2.0.0" + versionCode = 300 + versionName = "3.0.0" + + manifestPlaceholders["appLabel"] = targetAppName testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -93,6 +121,21 @@ android { ) } } + + testOptions { + unitTests.isReturnDefaultValues = true + } + + applicationVariants.all { + val variant = this + outputs.all { + val output = this as? com.android.build.gradle.internal.api.BaseVariantOutputImpl + if (output != null) { + val abi = output.getFilter(com.android.build.OutputFile.ABI) ?: "universal" + output.outputFileName = "$apkPrefix-v${variant.versionName}-$abi-${variant.buildType.name}.apk" + } + } + } } dependencies { @@ -134,6 +177,7 @@ dependencies { // Testing & Debug testImplementation(libs.junit) + testImplementation("org.json:json:20240303") androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(platform(libs.androidx.compose.bom)) diff --git a/Ported/app/proguard-rules.pro b/app/proguard-rules.pro similarity index 100% rename from Ported/app/proguard-rules.pro rename to app/proguard-rules.pro diff --git a/Ported/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml similarity index 95% rename from Ported/app/src/main/AndroidManifest.xml rename to app/src/main/AndroidManifest.xml index 12f0510..0689dd6 100644 --- a/Ported/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -53,7 +53,7 @@ android:allowBackup="false" android:hardwareAccelerated="true" android:icon="@mipmap/ic_launcher" - android:label="@string/app_name" + android:label="${appLabel}" android:networkSecurityConfig="@xml/network_security_config" android:roundIcon="@mipmap/ic_launcher" android:supportsRtl="true" @@ -99,7 +99,7 @@ android:enabled="true" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher" - android:label="@string/app_name"> + android:label="${appLabel}"> @@ -113,7 +113,7 @@ android:enabled="false" android:icon="@mipmap/ic_launcher_redesign" android:roundIcon="@mipmap/ic_launcher_redesign" - android:label="@string/app_name"> + android:label="${appLabel}"> @@ -127,7 +127,7 @@ android:enabled="false" android:icon="@mipmap/ic_launcher_pitch_black" android:roundIcon="@mipmap/ic_launcher_pitch_black" - android:label="@string/app_name"> + android:label="${appLabel}"> @@ -141,7 +141,7 @@ android:enabled="false" android:icon="@mipmap/ic_launcher_all_white" android:roundIcon="@mipmap/ic_launcher_all_white" - android:label="@string/app_name"> + android:label="${appLabel}"> @@ -156,7 +156,7 @@ android:enabled="false" android:icon="@mipmap/ic_launcher_abstract" android:roundIcon="@mipmap/ic_launcher_abstract" - android:label="@string/app_name"> + android:label="${appLabel}"> @@ -170,7 +170,7 @@ android:enabled="false" android:icon="@mipmap/ic_launcher_forest" android:roundIcon="@mipmap/ic_launcher_forest" - android:label="@string/app_name"> + android:label="${appLabel}"> @@ -184,7 +184,7 @@ android:enabled="false" android:icon="@mipmap/ic_launcher_golden" android:roundIcon="@mipmap/ic_launcher_golden" - android:label="@string/app_name"> + android:label="${appLabel}"> @@ -198,7 +198,7 @@ android:enabled="false" android:icon="@mipmap/ic_launcher_metal" android:roundIcon="@mipmap/ic_launcher_metal" - android:label="@string/app_name"> + android:label="${appLabel}"> @@ -212,7 +212,7 @@ android:enabled="false" android:icon="@mipmap/ic_launcher_scribble" android:roundIcon="@mipmap/ic_launcher_scribble" - android:label="@string/app_name"> + android:label="${appLabel}"> @@ -227,7 +227,7 @@ android:enabled="false" android:icon="@mipmap/ic_launcher_polaroid" android:roundIcon="@mipmap/ic_launcher_polaroid" - android:label="@string/app_name"> + android:label="${appLabel}"> @@ -241,7 +241,7 @@ android:enabled="false" android:icon="@mipmap/ic_launcher_viewfinder" android:roundIcon="@mipmap/ic_launcher_viewfinder" - android:label="@string/app_name"> + android:label="${appLabel}"> @@ -255,7 +255,7 @@ android:enabled="false" android:icon="@mipmap/ic_launcher_minimalist" android:roundIcon="@mipmap/ic_launcher_minimalist" - android:label="@string/app_name"> + android:label="${appLabel}"> @@ -269,7 +269,7 @@ android:enabled="false" android:icon="@mipmap/ic_launcher_vibrant" android:roundIcon="@mipmap/ic_launcher_vibrant" - android:label="@string/app_name"> + android:label="${appLabel}"> diff --git a/app/src/main/assets/adi-registration.properties b/app/src/main/assets/adi-registration.properties new file mode 100644 index 0000000..f4bc2ba --- /dev/null +++ b/app/src/main/assets/adi-registration.properties @@ -0,0 +1 @@ +COQOQ33MWZHJAAAAAAAAAAAAAA \ No newline at end of file diff --git a/Ported/app/src/main/assets/extensions/ublock/LICENSE.txt b/app/src/main/assets/extensions/ublock/LICENSE.txt similarity index 100% rename from Ported/app/src/main/assets/extensions/ublock/LICENSE.txt rename to app/src/main/assets/extensions/ublock/LICENSE.txt diff --git a/Ported/app/src/main/assets/extensions/ublock/README.md b/app/src/main/assets/extensions/ublock/README.md similarity index 100% rename from Ported/app/src/main/assets/extensions/ublock/README.md rename to app/src/main/assets/extensions/ublock/README.md diff --git a/Ported/app/src/main/assets/extensions/ublock/background.js b/app/src/main/assets/extensions/ublock/background.js similarity index 88% rename from Ported/app/src/main/assets/extensions/ublock/background.js rename to app/src/main/assets/extensions/ublock/background.js index 531d023..70d181e 100644 --- a/Ported/app/src/main/assets/extensions/ublock/background.js +++ b/app/src/main/assets/extensions/ublock/background.js @@ -1,10 +1,6 @@ /** - * FocusGram Privacy & Content Blocker Engine — WebExtension Background Script - * - * Provides: - * 1. Network-level interception of Instagram ads & telemetry (toggled dynamically via settings). - * 2. Native messaging bridge connecting GeckoView Android host with in-page content scripts. - * 3. Dispatcher for SPA navigation, stealth updates, Reels metadata, and UI events. + * Background script routing ad/telemetry blocking, native host messaging, + * and content script coordination for web requests and settings updates. */ 'use strict'; @@ -27,6 +23,8 @@ let cachedSettings = { ghostHideTypingIndicator: true, unblurMode: 'tap', videoDownloadEnabled: false, + storySearchEnabled: true, + customBioFontsEnabled: true, chatLockMode: 'individual', chatLockAll: false, lockedChatIds: [], @@ -63,21 +61,25 @@ const BLOCKED_URL_PATTERNS = [ const GHOST_BLOCKED_URL_PATTERNS = [ "*://*.instagram.com/api/v1/stories/reel/seen*", + "*://*.instagram.com/stories/reel/seen*", + "*://*.instagram.com/api/v1/stories/update_seen*", "*://*.instagram.com/api/v1/direct_v2/threads/*/items/*/seen*", "*://*.instagram.com/api/v1/direct_v2/threads/broadcast/typing*", - "*://*.instagram.com/api/v1/direct_v2/threads/typing*" + "*://*.instagram.com/api/v1/direct_v2/threads/typing*", + "*://*.instagram.com/api/v1/direct_v2/threads/seen*", + "*://*.instagram.com/api/v1/direct_v2/threads/*/seen*", + "*://*.instagram.com/api/v1/direct_v2/threads/*/mark_seen*", + "*://*.instagram.com/api/v1/direct_v2/threads/*/mark_read*" ]; -// Network-level interception: ONLY active when noAds is enabled in settings if (typeof browser !== 'undefined' && browser.webRequest && browser.webRequest.onBeforeRequest) { browser.webRequest.onBeforeRequest.addListener( function (details) { if (cachedSettings.noAds === false || !userIsAuthenticated) { - // User turned ad blocker off or user is not logged in: do not cancel requests return { cancel: false }; } const url = (details.url || '').toLowerCase(); - // NEVER block anything on or related to authentication, login, signup, or security checks + // Preserve auth flows and challenge endpoints without interception. if (url.includes('/accounts/') || url.includes('/login') || url.includes('/signup') || url.includes('facebook.net') || url.includes('facebook.com') || url.includes('/ajax/bz') || url.includes('/api/v1/web/accounts/') || url.includes('/api/v1/accounts/')) { @@ -91,16 +93,18 @@ if (typeof browser !== 'undefined' && browser.webRequest && browser.webRequest.o ); console.log('[Content Blocker] Network interception active across', BLOCKED_URL_PATTERNS.length, 'patterns.'); - // Ghost Mode WebRequest Level Interception browser.webRequest.onBeforeRequest.addListener( function (details) { if (!cachedSettings.ghostModeEnabled) return { cancel: false }; const url = (details.url || '').toLowerCase(); - if (cachedSettings.ghostHideStorySeen && url.includes('stories/reel/seen')) { + if (url.includes('/broadcast/text') || url.includes('/broadcast/media')) { + return { cancel: false }; + } + if (cachedSettings.ghostHideStorySeen && (url.includes('stories/reel/seen') || url.includes('story/seen') || url.includes('update_seen'))) { console.log('[Ghost Mode] Blocked story seen network call:', details.url); return { cancel: true }; } - if (cachedSettings.ghostHideDirectSeen && url.includes('/seen')) { + if (cachedSettings.ghostHideDirectSeen && (url.includes('/seen') || url.includes('mark_seen') || url.includes('mark_read'))) { console.log('[Ghost Mode] Blocked direct seen network call:', details.url); return { cancel: true }; } @@ -114,10 +118,8 @@ if (typeof browser !== 'undefined' && browser.webRequest && browser.webRequest.o ["blocking"] ); - // --- Story Viewer List Interception (Client-Side Search) --- - // Passively captures the response of Instagram's own web client when opening viewers list. - // Zero automated/extra requests — uses web client's authenticated response. - const storyViewersCache = new Map(); // mediaId -> Array<{pk, username, fullName, avatar, hasLiked}> + // Passively capture viewers list responses without issuing external requests. + const storyViewersCache = new Map(); browser.webRequest.onBeforeRequest.addListener( function (details) { @@ -127,7 +129,7 @@ if (typeof browser !== 'undefined' && browser.webRequest && browser.webRequest.o const mediaId = match[1]; - // Immediately broadcast detection when request is SENT so search bar appears right away! + // Broadcast early phase so UI surfaces the search bar before payload arrives. broadcastToTabs({ action: 'storyViewersDetected', phase: 'loading', @@ -142,7 +144,7 @@ if (typeof browser !== 'undefined' && browser.webRequest && browser.webRequest.o filter.ondata = function (event) { chunks.push(event.data); - filter.write(event.data); // Transparently stream data to Instagram web client + filter.write(event.data); }; filter.onstop = async function () { @@ -215,7 +217,6 @@ if (typeof browser !== 'undefined' && browser.webRequest && browser.webRequest.o ); } -// Establish native messaging port with FocusGram Android host let nativePort = null; const pendingNativeMessages = []; @@ -301,7 +302,6 @@ function broadcastToTabs(message) { } } -// Listen for messages from in-page content scripts if (typeof browser !== 'undefined' && browser.runtime && browser.runtime.onMessage) { browser.runtime.onMessage.addListener((message, sender, sendResponse) => { if (!message) return; @@ -311,8 +311,10 @@ if (typeof browser !== 'undefined' && browser.runtime && browser.runtime.onMessa } if (message.action === 'getSettings') { - sendResponse({ settings: cachedSettings }); - return true; + if (typeof sendResponse === 'function') { + sendResponse({ settings: cachedSettings }); + } + return Promise.resolve({ settings: cachedSettings }); } if (message.action === 'getStoryViewers') { diff --git a/Ported/app/src/main/assets/extensions/ublock/content.js b/app/src/main/assets/extensions/ublock/content.js similarity index 82% rename from Ported/app/src/main/assets/extensions/ublock/content.js rename to app/src/main/assets/extensions/ublock/content.js index 134d1f7..1f91306 100644 --- a/Ported/app/src/main/assets/extensions/ublock/content.js +++ b/app/src/main/assets/extensions/ublock/content.js @@ -1,8 +1,6 @@ /** - * FocusGram — Cosmetic Filtering & DOM Scriptlet - * - * Injects CSS cosmetic filtering rules and monitors dynamic SPA DOM - * insertions to eliminate sponsored posts and ad banners when enabled. + * Cosmetic filtering and scriptlet injections. + * Collapses sponsored cards, hides ad banners, and skips sponsored reel units. */ 'use strict'; @@ -17,15 +15,10 @@ if (path.includes('/accounts/edit')) { return false; } - if (path.startsWith('/accounts') || path.startsWith('/login') || path.startsWith('/signup')) { + if (path.startsWith('/accounts') || path.startsWith('/login') || path.startsWith('/signup') || path.startsWith('/two_factor') || path.startsWith('/challenge')) { return true; } - const cookies = document.cookie || ''; - const hasAuthCookie = cookies.includes('sessionid=') || cookies.includes('ds_user_id='); - if ((path === '/' || path === '') && !hasAuthCookie) { - return true; - } - if (document.getElementById('loginForm') || document.querySelector('form[action*="login"], input[name="password"]')) { + if (document.getElementById('loginForm') || document.querySelector('form[action*="login"], form input[type="password"]')) { return true; } } catch (_) {} @@ -35,11 +28,11 @@ if (isAuthPage()) { const existing = document.getElementById(COSMETIC_STYLE_ID); if (existing) existing.remove(); - return; // NEVER run on auth, login, or signup pages + return; } const CSS_RULES = ` - /* Hide sponsored feed articles without breaking Infinite Scroll IntersectionObserver or feed containers */ + /* Maintain 1px height so IntersectionObserver viewport triggers continue firing for infinite scroll */ div[data-ad-preview], div[data-testid="post_ad"], article:has(a[href*="/ads/"]), @@ -92,12 +85,16 @@ const STRICT_AD_CTAS = [ 'shop now', 'install now', 'apply now', - 'book now', 'order now', 'get offer' + 'book now', 'order now', 'get offer', 'learn more' ]; + function findReelCardForVideo(video) { + if (!video) return null; + return video.closest('[data-fg-reel-card="1"]') || video.closest('article'); + } + function filterDynamicSponsoredPosts() { if (!adBlockingEnabled) return; - // Strictly target individual article posts to avoid hiding high-level feed containers const articles = document.querySelectorAll('article'); for (let i = 0; i < articles.length; i++) { const article = articles[i]; @@ -105,7 +102,6 @@ let isAd = false; - // Check post header only (Sponsored, Paid partnership) const headerElements = article.querySelectorAll('header span, header [aria-label*="Sponsored" i], header [aria-label="Ad" i]'); for (let j = 0; j < headerElements.length; j++) { if (headerElements[j].children.length > 2) continue; @@ -118,7 +114,6 @@ } } - // Check CTA button text specifically on buttons (excluding FocusGram download buttons) if (!isAd) { const buttons = article.querySelectorAll('div[role="button"]:not([data-fg-x-act]):not([data-fg-download-btn]):not(.fg-x-act-btn), a[role="button"]:not([data-fg-x-act]):not([data-fg-download-btn]):not(.fg-x-act-btn), button'); for (let k = 0; k < buttons.length; k++) { @@ -130,7 +125,6 @@ } } - // Check ad-specific links (strictly /ads/, /ad_, ads.instagram.com) if (!isAd) { if (article.querySelector('a[href*="/ads/"], a[href*="/ad_"], a[href*="ads.instagram.com"]')) { isAd = true; @@ -150,24 +144,23 @@ } } - // Filter Reels video ads on reels surface without ever breaking layout const videos = document.querySelectorAll('video'); for (let i = 0; i < videos.length; i++) { const video = videos[i]; - const card = video.closest('[data-fg-reel-card="1"]') || video.closest('article'); + const card = findReelCardForVideo(video); if (!card || card.dataset.adFiltered || card.dataset.fgReelAd) continue; - let isSponsored = !!card.querySelector('a[href*="/ads/"], a[href*="/ad_"], a[href*="ads.instagram.com"], a[href*="/about/ads"]') || - !!card.querySelector('[aria-label*="Sponsored" i], [aria-label="Ad" i], [data-ad-preview]'); + let isSponsored = !!card.querySelector('a[href*="/ads/"], a[href*="/ad_"], a[href*="ads.instagram.com"], a[href*="/about/ads"], a[href*="facebook.com/ads"]') || + !!card.querySelector('[aria-label*="Sponsored" i], [aria-label="Ad" i], [aria-label*="advertisement" i], [data-ad-preview], [data-ad-id]'); if (!isSponsored) { const badges = card.querySelectorAll('span, div[role="button"], button'); for (let s = 0; s < badges.length; s++) { const el = badges[s]; if (el.children.length > 2) continue; - const raw = (el.textContent || '').trim(); - if (!raw || raw.length > 15) continue; - if (/^[\u{1F300}-\u{1F9FF}\s]*ad$/iu.test(raw) || /^sponsored$/i.test(raw) || /^paid\s+partnership$/i.test(raw)) { + const raw = (el.textContent || '').trim().toLowerCase(); + if (!raw || raw.length > 25) continue; + if (/^[\u{1F300}-\u{1F9FF}\s]*ad$/iu.test(raw) || raw === 'sponsored' || raw === 'paid partnership' || raw === 'advertisement') { if (!el.closest('span[dir="auto"]')) { isSponsored = true; break; @@ -184,7 +177,6 @@ card.style.setProperty('visibility', 'hidden', 'important'); card.style.setProperty('pointer-events', 'none', 'important'); - // Skip ad reel automatically if currently visible const r = card.getBoundingClientRect(); if (r.top >= -100 && r.top <= window.innerHeight * 0.8) { const scroller = card.closest('[data-fg-reel-scroller]') || document.querySelector('[data-fg-reel-scroller="1"]'); @@ -198,7 +190,6 @@ } } - // Dynamic MutationObserver for SPA scrolling const observer = new MutationObserver(() => { if (adBlockingEnabled) { filterDynamicSponsoredPosts(); @@ -225,7 +216,6 @@ init(); } - // Listen for settings changes from background if (typeof browser !== 'undefined' && browser.runtime && browser.runtime.onMessage) { browser.runtime.onMessage.addListener((msg) => { if (msg && msg.action === 'updateSettings' && msg.settings) { diff --git a/Ported/app/src/main/assets/extensions/ublock/focusgram_content.js b/app/src/main/assets/extensions/ublock/focusgram_content.js similarity index 85% rename from Ported/app/src/main/assets/extensions/ublock/focusgram_content.js rename to app/src/main/assets/extensions/ublock/focusgram_content.js index e6d0f1c..f8e0cf9 100644 --- a/Ported/app/src/main/assets/extensions/ublock/focusgram_content.js +++ b/app/src/main/assets/extensions/ublock/focusgram_content.js @@ -1,21 +1,12 @@ /** - * FocusGram In-Page Controller & Stealth Privacy Engine - * - * Injected as a WebExtension content script across instagram.com at document_start. - * Features: - * 1. Stealth Mode: Main-world fetch and XHR interceptors for Story views, DM Read Receipts, and Typing indicators. - * 2. Bottom Bar Trimming: Hides Instagram's web bottom navigation bar and adds margin for the native app bar. - * 3. Dynamic Margins: Removes margin when keyboard/inputs are active so comments and DM input boxes are never blocked. - * 4. Top Logo Trigger: Intercepts clicks and long-presses on the Instagram logo to show FocusGram Settings. - * 5. Focus/Typing Bridge: Notifies native Android host to hide/show the floating bottom navigation bar. - * 6. SPA Navigation: Provides instant client-side route transitions without page reloads. - * 7. Digital Wellness Guardrails: Dynamic CSS filtering for Stories, Reels, Explore blur, Likes, and Suggested posts. + * In-page WebExtension controller injected across web sessions at document_start. + * Handles stealth request filtering, navigation bar trimming, viewport margin offsets, + * client-side chat locking, and dynamic wellness guardrails. */ 'use strict'; (function () { - // Senior Blueprint: Direct content script communication with WebExtension background script function postToNative(message) { if (!message) return; try { @@ -32,19 +23,19 @@ function isAuthPage() { try { + const host = (window.location.hostname || '').toLowerCase(); + if (host.includes('facebook.com') || host.includes('accountscenter.')) { + return true; + } const path = (window.location.pathname || '').toLowerCase(); if (path.includes('/accounts/edit')) { return false; } - if (path.startsWith('/accounts') || path.startsWith('/login') || path.startsWith('/signup')) { + if (path.startsWith('/accounts') || path.startsWith('/login') || path.startsWith('/signup') || + path.startsWith('/two_factor') || path.startsWith('/challenge') || path.startsWith('/onetap')) { return true; } - const cookies = document.cookie || ''; - const hasAuthCookie = cookies.includes('sessionid=') || cookies.includes('ds_user_id='); - if ((path === '/' || path === '') && !hasAuthCookie) { - return true; - } - if (document.getElementById('loginForm') || document.querySelector('form[action*="login"], input[name="password"]')) { + if (document.getElementById('loginForm') || document.querySelector('form[action*="login"], form input[type="password"]')) { return true; } } catch (_) {} @@ -53,21 +44,20 @@ if (isAuthPage()) { postToNative({ action: 'authStateChanged', isAuthenticated: false, isAuthPage: true }); - // Watch for navigation/login completion without running any scripts or CSS let authCheckInterval = setInterval(() => { if (!isAuthPage()) { clearInterval(authCheckInterval); window.location.reload(); } }, 1000); - return; // Leave login/signup completely untouched! + return; } else { postToNative({ action: 'authStateChanged', isAuthenticated: true, isAuthPage: false }); } console.log('[FocusGram] In-page controller initialized.'); - // --- Immediate anti-flash style injected at document_start --- + // Anti-flash style injected at document_start. try { const immediateStyle = document.createElement('style'); immediateStyle.id = 'focusgram-immediate-nav-hide'; @@ -104,14 +94,17 @@ hideFollowerCounts: false, hideSuggestedPosts: false, hideShopTab: false, + noAutoplay: false, + grayscale: false, videoDownloadEnabled: false, + storySearchEnabled: true, + customBioFontsEnabled: true, ghostModeEnabled: false, ghostHideStorySeen: true, ghostHideDirectSeen: true, ghostHideTypingIndicator: true }; - // --- 1. GHOST MODE INTERCEPTION ENGINE --- function setupGhostModeInterceptors() { const GHOST_SCRIPT_ID = 'focusgram-ghost-interceptor'; if (document.getElementById(GHOST_SCRIPT_ID)) return; @@ -131,13 +124,78 @@ blockAds: true, blockSponsored: true, blockSuggested: false, - blockVideos: false + blockVideos: false, + blockAutoplay: false, + storySearchEnabled: true, + customBioFontsEnabled: true }; + // HTML5 video autoplay interception + const allowedVideos = new WeakSet(); + function isReelVideo() { + try { + const path = (window.location.pathname || '').toLowerCase(); + return path.startsWith('/reel') || path.includes('/reel'); + } catch (_) { + return false; + } + } + + function shouldBlockAutoplay() { + if (isReelVideo()) return false; + const cfg = window.__fgFilterConfig; + return cfg && cfg.blockAutoplay !== false; + } + + if (window.HTMLVideoElement && HTMLVideoElement.prototype.play) { + const origPlay = HTMLVideoElement.prototype.play; + HTMLVideoElement.prototype.play = function() { + try { + if (window.__fgMediaPaused) { + this.removeAttribute('autoplay'); + this.autoplay = false; + this.pause(); + return Promise.resolve(); + } + if (!shouldBlockAutoplay() || allowedVideos.has(this)) { + return origPlay.apply(this, arguments); + } + this.removeAttribute('autoplay'); + this.autoplay = false; + this.pause(); + return Promise.resolve(); + } catch (_) { + return origPlay.apply(this, arguments); + } + }; + + ['click', 'pointerup'].forEach(function(eventType) { + document.addEventListener(eventType, function(e) { + if (!e.isTrusted) return; + if (e.target.closest('button[aria-label="Like"], button[aria-label="Comment"], button[aria-label="Share"], a[role="link"]:not(:has(video))')) return; + const v = e.target.tagName === 'VIDEO' ? e.target : (e.target.closest('video') || (e.target.querySelector ? e.target.querySelector('video') : null)); + if (v) { + allowedVideos.add(v); + try { origPlay.apply(v); } catch (_) {} + } + }, true); + }); + } + window.addEventListener('message', function(e) { if (!e || !e.data) return; if (e.data.type === 'GHOSTIFY_SETTINGS_UPDATE' && e.data.settings) { - Object.assign(window.__focusgramGhostSettings, e.data.settings); + const s = e.data.settings; + if (s.enabled !== undefined) window.__focusgramGhostSettings.enabled = !!s.enabled; + const story = s.hideStorySeen !== undefined ? !!s.hideStorySeen : (s.igStory !== undefined ? !!s.igStory : window.__focusgramGhostSettings.hideStorySeen); + const seen = s.hideDirectSeen !== undefined ? !!s.hideDirectSeen : (s.igSeen !== undefined ? !!s.igSeen : window.__focusgramGhostSettings.hideDirectSeen); + const typing = s.hideTyping !== undefined ? !!s.hideTyping : (s.igTyping !== undefined ? !!s.igTyping : window.__focusgramGhostSettings.hideTyping); + window.__focusgramGhostSettings.hideStorySeen = story; + window.__focusgramGhostSettings.hideDirectSeen = seen; + window.__focusgramGhostSettings.hideTyping = typing; + window.__focusgramGhostSettings.igStory = story; + window.__focusgramGhostSettings.igSeen = seen; + window.__focusgramGhostSettings.igTyping = typing; } if (e.data.type === 'FOCUSGRAM_FILTER_UPDATE' && e.data.config) { Object.assign(window.__fgFilterConfig, e.data.config); @@ -376,7 +434,8 @@ 'update_seen_for_reel', 'reel_seen', 'stories_update_seen', - 'mark_story_read' + 'mark_story_read', + 'viewseenat' ]; const SEEN_PATTERNS = [ @@ -389,7 +448,12 @@ 'polarisdirectmarkasseenmutation', 'directseenmutation', 'usepolarismarkthreadseenmutation', - 'useigdmarkthreadasreadmutation' + 'useigdmarkthreadasreadmutation', + 'markdirectthreadseen', + 'mark_direct_thread_seen', + 'direct_v2/threads/seen', + 'direct_v2/threads/mark_seen', + 'direct_v2/threads/mark_read' ]; const TYPING_PATTERNS = [ @@ -404,7 +468,11 @@ 'typing_on', 'is_composing', 'update_activity_status', - 'activity_status' + 'activity_status', + 'typingindicatorstoredprocedure', + 'directsendtyping', + 'direct_send_typing', + 'igdsendtyping' ]; function isDirectSendEndpoint(url) { @@ -412,7 +480,9 @@ return u.includes('broadcast/text') || u.includes('broadcast/media') || u.includes('threads/broadcast') || - u.includes('action=send_message'); + u.includes('action=send_message') || + u.includes('send_item') || + u.includes('publish_message'); } async function decodeBody(body) { @@ -450,16 +520,20 @@ } function checkBlock(url, bodyStr) { - if (isDirectSendEndpoint(url)) return null; + if (isDirectSendEndpoint(url) || isDirectSendEndpoint(bodyStr)) return null; const path = String(window.location?.pathname || '').toLowerCase(); if (path.startsWith('/accounts') || path.startsWith('/login')) return null; const s = window.__focusgramGhostSettings; if (!s || !s.enabled) return null; + const storyActive = s.hideStorySeen !== undefined ? s.hideStorySeen : !!s.igStory; + const seenActive = s.hideDirectSeen !== undefined ? s.hideDirectSeen : !!s.igSeen; + const typingActive = s.hideTyping !== undefined ? s.hideTyping : !!s.igTyping; + const combined = (String(bodyStr || '') + ' ' + String(url || '')).toLowerCase(); - if (s.hideStorySeen && matchesAny(combined, STORY_PATTERNS)) return 'IG_STORY'; - if (s.hideDirectSeen && matchesAny(combined, SEEN_PATTERNS)) return 'IG_SEEN'; - if (s.hideTyping && matchesAny(combined, TYPING_PATTERNS)) return 'IG_TYPING'; + if (storyActive && matchesAny(combined, STORY_PATTERNS)) return 'IG_STORY'; + if (seenActive && matchesAny(combined, SEEN_PATTERNS)) return 'IG_SEEN'; + if (typingActive && matchesAny(combined, TYPING_PATTERNS)) return 'IG_TYPING'; return null; } @@ -497,13 +571,14 @@ return { data: {} }; } - // --- Focus & Visibility Spoofing --- + // Visibility and focus spoofing. function shouldSpoofVisibility() { const s = window.__focusgramGhostSettings; if (!s || !s.enabled) return null; + const storyActive = s.hideStorySeen !== undefined ? s.hideStorySeen : !!s.igStory; const path = String(window.location?.pathname || '').toLowerCase(); - if (s.hideStorySeen && path.startsWith('/stories/')) { + if (storyActive && path.startsWith('/stories/')) { return 'unfocused'; } return null; @@ -524,17 +599,72 @@ Object.defineProperty(document, 'visibilityState', { get: function() { const spoof = shouldSpoofVisibility(); - if (spoof === 'unfocused') return 'visible'; + if (spoof === 'unfocused') return 'hidden'; return originalVisibilityState && originalVisibilityState.get ? originalVisibilityState.get.call(document) : 'visible'; }, configurable: true }); + + const originalHidden = Object.getOwnPropertyDescriptor(Document.prototype, 'hidden'); + Object.defineProperty(document, 'hidden', { + get: function() { + const spoof = shouldSpoofVisibility(); + if (spoof === 'unfocused') return true; + return originalHidden && originalHidden.get ? + originalHidden.get.call(document) : false; + }, + configurable: true + }); } catch (err) { console.warn('[FocusGram Ghost] Focus hook notice:', err); } - // --- Hook window.fetch --- + // Intercept WebSocket frames (MQTT typing indicators and read receipts). + if (typeof WebSocket !== 'undefined' && WebSocket.prototype && WebSocket.prototype.send) { + if (!window.__FG_GHOST_WS_HOOKED__) { + window.__FG_GHOST_WS_HOOKED__ = true; + const originalWsSend = WebSocket.prototype.send; + const wsUrls = new WeakMap(); + + const OriginalWebSocket = window.WebSocket; + window.WebSocket = new Proxy(OriginalWebSocket, { + construct(target, args, newTarget) { + const ws = Reflect.construct(target, args, newTarget); + try { + wsUrls.set(ws, String(args[0] || '')); + } catch (_) {} + return ws; + } + }); + + function decodeWsSync(data) { + if (!data) return ''; + try { + if (typeof data === 'string') return data; + if (data instanceof ArrayBuffer) return new TextDecoder().decode(data); + if (ArrayBuffer.isView(data)) return new TextDecoder().decode(data); + } catch (_) {} + return ''; + } + + WebSocket.prototype.send = function(data) { + try { + const socketUrl = wsUrls.get(this) || this.url || ''; + const dataText = decodeWsSync(data); + const blockType = checkBlock(socketUrl, dataText); + if (blockType) { + console.log('[FocusGram Ghost] Hooked ' + blockType + ' WebSocket frame: ' + socketUrl); + return; // Drop frame + } + } catch (e) { + console.warn('[FocusGram Ghost] WebSocket check error:', e); + } + return originalWsSend.apply(this, arguments); + }; + } + } + const originalFetch = window.fetch; window.fetch = async function(input, init) { const url = typeof input === 'string' ? input : (input && input.url ? input.url : ''); @@ -608,7 +738,6 @@ return response; }; - // --- Hook navigator.sendBeacon --- const originalBeacon = navigator.sendBeacon; if (typeof originalBeacon === 'function') { navigator.sendBeacon = function(url, data) { @@ -631,7 +760,6 @@ }; } - // --- Hook XMLHttpRequest --- const originalOpen = XMLHttpRequest.prototype.open; const originalSend = XMLHttpRequest.prototype.send; @@ -755,6 +883,35 @@ } }, true); + // Intercept clicks on in-page Back chevron in DM threads/inbox so it never pops history + document.addEventListener('click', function(e) { + const path = (window.location.pathname || '').toLowerCase(); + const backBtn = e.target.closest('svg[aria-label="Back"], svg[aria-label="back"], svg[aria-label="Chevron left"], [aria-label="Back"], [aria-label="back"], a[href*="/direct/inbox"], a[href*="/direct/"]') + ?.closest('a, button, div[role="button"]'); + if (backBtn) { + if (path.startsWith('/direct/t/') || path.startsWith('/direct/threads/') || (path.startsWith('/direct/') && !path.startsWith('/direct/inbox'))) { + e.preventDefault(); + e.stopPropagation(); + if (typeof window.__focusgramNavigate === 'function') { + window.__focusgramNavigate('/direct/inbox/'); + } else { + window.location.href = '/direct/inbox/'; + } + return; + } + if (path === '/direct' || path === '/direct/' || path.startsWith('/direct/inbox')) { + e.preventDefault(); + e.stopPropagation(); + if (typeof window.__focusgramNavigate === 'function') { + window.__focusgramNavigate('/'); + } else { + window.location.href = '/'; + } + return; + } + } + }, true); + function checkSuggestedTrap() { if (window.location.pathname.includes('/explore/people')) { window.__focusgramNavigate('/'); @@ -782,6 +939,55 @@ return res; }; + // Override history.back and history.go in DMs so navigation follows: + // user's dm -> /direct/inbox -> home screen + // without popping previous browser history entries (which exposes locked chats). + const _origHistoryBack = window.history.back; + window.history.back = function() { + const path = (window.location.pathname || '').toLowerCase(); + if (path.startsWith('/direct/t/') || path.startsWith('/direct/threads/') || (path.startsWith('/direct/') && !path.startsWith('/direct/inbox'))) { + if (typeof window.__focusgramNavigate === 'function') { + window.__focusgramNavigate('/direct/inbox/'); + } else { + window.location.href = '/direct/inbox/'; + } + return; + } + if (path === '/direct' || path === '/direct/' || path.startsWith('/direct/inbox')) { + if (typeof window.__focusgramNavigate === 'function') { + window.__focusgramNavigate('/'); + } else { + window.location.href = '/'; + } + return; + } + return _origHistoryBack.apply(this, arguments); + }; + + const _origHistoryGo = window.history.go; + window.history.go = function(delta) { + if (typeof delta === 'number' && delta < 0) { + const path = (window.location.pathname || '').toLowerCase(); + if (path.startsWith('/direct/t/') || path.startsWith('/direct/threads/') || (path.startsWith('/direct/') && !path.startsWith('/direct/inbox'))) { + if (typeof window.__focusgramNavigate === 'function') { + window.__focusgramNavigate('/direct/inbox/'); + } else { + window.location.href = '/direct/inbox/'; + } + return; + } + if (path === '/direct' || path === '/direct/' || path.startsWith('/direct/inbox')) { + if (typeof window.__focusgramNavigate === 'function') { + window.__focusgramNavigate('/'); + } else { + window.location.href = '/'; + } + return; + } + } + return _origHistoryGo.apply(this, arguments); + }; + window.addEventListener('popstate', function() { try { window.dispatchEvent(new CustomEvent('focusgram:urlchange', { detail: window.location.href })); @@ -806,6 +1012,10 @@ type: "GHOSTIFY_SETTINGS_UPDATE", source: "GHOSTIFY_EXTENSION", settings: { + enabled: isGhostActive, + hideTyping: isGhostActive && !!currentSettings.ghostHideTypingIndicator, + hideDirectSeen: isGhostActive && !!currentSettings.ghostHideDirectSeen, + hideStorySeen: isGhostActive && !!currentSettings.ghostHideStorySeen, igTyping: isGhostActive && !!currentSettings.ghostHideTypingIndicator, igSeen: isGhostActive && !!currentSettings.ghostHideDirectSeen, igStory: isGhostActive && !!currentSettings.ghostHideStorySeen @@ -817,7 +1027,10 @@ config: { blockAds: currentSettings.noAds !== false, blockSponsored: currentSettings.noAds !== false, - blockSuggested: !!currentSettings.hideSuggestedPosts + blockSuggested: !!currentSettings.hideSuggestedPosts, + blockAutoplay: currentSettings.noAutoplay !== false, + storySearchEnabled: currentSettings.storySearchEnabled !== false, + customBioFontsEnabled: currentSettings.customBioFontsEnabled !== false } }, "*"); } @@ -828,7 +1041,7 @@ } }); - // --- 2. BOTTOM NAVBAR TRIMMING & DYNAMIC MARGINS --- + // Bottom navbar trimming and dynamic margins. const STYLE_ID = 'focusgram-ui-guardrails-style'; function updateGuardrailsCSS() { @@ -876,19 +1089,13 @@ padding-bottom: 0px !important; } - /* ========================================================================== - 3. INSTAGRAM REELS (100% INSPIRATION MATCH) - - Full viewport edge-to-edge container (100dvh). - - Video element covers 100% without letterboxing or black bars. - - Floating navigation bar sits below. - - FULL CAPTION, AUDIO BANNER, USER PROFILE, and ACTION BUTTONS sit ABOVE it. - ========================================================================== */ + /* Reels viewport and overlay offsets */ :root { --fg-reel-bottom-offset: 16px; --fg-reel-scrubber-offset: 2px; } - /* Reels fixed scroller: Ultra-smooth momentum scrolling & snapping */ + /* Reels fixed scroller */ [data-fg-reel-scroller], div[style*="position: fixed"][style*="overflow-y: scroll"], div[style*="position: fixed"][style*="overflow-y: auto"], @@ -940,7 +1147,7 @@ transform: translateZ(0) !important; } - /* Video scaling: seamless edge-to-edge presentation */ + /* Video scaling: edge-to-edge presentation */ [data-fg-reel-card] video, [data-fg-reel] video, html[data-fg-island="FLOATING"] video { @@ -1018,8 +1225,6 @@ text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85) !important; } - } - /* Ensure comments sheet / input composer sits flush above keyboard without artificial gap */ [role="dialog"] form, [role="dialog"] [style*="position: fixed"], @@ -1087,9 +1292,9 @@ if (currentSettings.blurExplore) { css += ` - main a[href*="/p/"]:not([data-fg-reveal]):not(.fg-unblurred) img, - main a[href*="/reel/"]:not([data-fg-reveal]):not(.fg-unblurred) img, - div[style*="grid"]:not([data-fg-reveal]):not(.fg-unblurred) img { + html[data-fg-explore] main a[href*="/p/"]:not([data-fg-reveal]):not(.fg-unblurred) img, + html[data-fg-explore] main a[href*="/reel/"]:not([data-fg-reveal]):not(.fg-unblurred) img, + main a[href*="/explore/"]:not([data-fg-reveal]):not(.fg-unblurred) img { filter: blur(24px) !important; transition: filter 0.2s ease-in-out !important; } @@ -1120,6 +1325,20 @@ .fg-unblurred div { filter: none !important; } + + /* Comment GIFs, sticker picker, and comment media must NEVER be blurred */ + img[src*="giphy"], + img[src*="tenor"], + img[alt*="GIF" i], + img[alt*="gif" i], + div[role="dialog"] img[src*="giphy"], + div[aria-modal="true"] img[src*="giphy"], + article ul img[src*="giphy"], + ul[class*="comment" i] img, + form img[src*="giphy"], + form img[src*="tenor"] { + filter: none !important; + } `; if (currentSettings.hideLikeCounts) { @@ -1128,20 +1347,9 @@ section a[href*="/liked_by/"], span:has(> a[href*="/liked_by/"]), div:has(> a[href*="/liked_by/"]), - section:has(svg[aria-label="Like"]) + div:not(:has(form)):not(:has(input)), - section:has(svg[aria-label="Unlike"]) + div:not(:has(form)):not(:has(input)), - section:has(svg[aria-label="Activity Feed"]) + div:not(:has(form)):not(:has(input)), - div[role="button"]:has(svg[aria-label="Like"]) + div span, - div[role="button"]:has(svg[aria-label="Unlike"]) + div span, - span[role="button"]:has(svg[aria-label="Like"]) + span, - span[role="button"]:has(svg[aria-label="Unlike"]) + span, - div[role="button"]:has(svg[aria-label="Like"]) span, - div[role="button"]:has(svg[aria-label="Unlike"]) span, - div:has(> span > svg[aria-label="Like"]) + div span, - div:has(> span > svg[aria-label="Unlike"]) + div span, - /* In Reels overlay: the count text right below the like heart icon */ - div:has(> svg[aria-label="Like"]) + div, - div:has(> svg[aria-label="Unlike"]) + div { + section:has(svg[aria-label="Like"]) + div:not(:has(form)):not(:has(input)):not(:has(svg)), + section:has(svg[aria-label="Unlike"]) + div:not(:has(form)):not(:has(input)):not(:has(svg)), + section:has(svg[aria-label="Activity Feed"]) + div:not(:has(form)):not(:has(input)):not(:has(svg)) { display: none !important; } `; @@ -1219,7 +1427,7 @@ styleEl.textContent = css; } - // --- 2. BOTTOM NAVBAR TRIMMING --- + // Bottom navbar trimming. function hideNav() { try { if (window.location.pathname.startsWith('/direct')) return; @@ -1291,10 +1499,11 @@ - // --- 3. REELS INTERACTION-BASED COUNTER & HISTORY TRACKER --- + // Reels interaction tracking and history recorder. let lastTrackedReelCode = null; let lastTrackedCaption = ''; let lastTrackedAuthor = ''; + let isInitialReelOnTabEntry = true; const harvestedReels = new Map(); function handleReelMetaEvent(raw) { @@ -1363,6 +1572,8 @@ const path = window.location.pathname || ''; // Only run when user is on Reels or viewing a Reel if (!path.startsWith('/reels') && !path.startsWith('/reel')) { + isInitialReelOnTabEntry = true; + lastTrackedReelCode = null; return; } @@ -1595,7 +1806,29 @@ return; } - // CODE CHANGED! Update last tracked state + // Handle initial reel vs subsequent scrolled reels + if (isInitialReelOnTabEntry) { + // First reel encountered on tab mount: capture code and send only metadata + // so session counter starts at 0 without counting the initial landing reel. + isInitialReelOnTabEntry = false; + lastTrackedReelCode = code; + lastTrackedCaption = caption; + lastTrackedAuthor = author; + + postToNative({ + action: 'reelWatched', + type: 'meta', + code: code, + url: reelUrl, + title: caption || (author ? ('Reel by @' + author) : ''), + caption: caption, + author: author || '', + thumbnail: '' + }); + return; + } + + // Subsequent reel scrolled to: fire IMMEDIATELY with zero artificial delay! lastTrackedReelCode = code; lastTrackedCaption = caption; lastTrackedAuthor = author; @@ -1611,28 +1844,22 @@ }); } - // Attach interaction listeners for Reels tracking without thrashing scroll frames - let checkReelDebounceTimer = null; - function debouncedCheckAndTrackActiveReel() { - if (checkReelDebounceTimer) clearTimeout(checkReelDebounceTimer); - checkReelDebounceTimer = setTimeout(checkAndTrackActiveReel, 120); - } - - window.addEventListener('scroll', debouncedCheckAndTrackActiveReel, { passive: true }); + // Attach interaction listeners for Reels tracking to update instantly + window.addEventListener('scroll', checkAndTrackActiveReel, { passive: true }); window.addEventListener('scrollend', checkAndTrackActiveReel, { passive: true }); - window.addEventListener('touchend', () => setTimeout(checkAndTrackActiveReel, 100), { passive: true }); - window.addEventListener('wheel', debouncedCheckAndTrackActiveReel, { passive: true }); + window.addEventListener('touchend', () => checkAndTrackActiveReel(), { passive: true }); + window.addEventListener('wheel', checkAndTrackActiveReel, { passive: true }); window.addEventListener('keydown', (e) => { if (['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp', 'Space'].includes(e.key)) { - setTimeout(checkAndTrackActiveReel, 120); + checkAndTrackActiveReel(); } }); window.addEventListener('popstate', checkAndTrackActiveReel); window.addEventListener('hashchange', checkAndTrackActiveReel); window.addEventListener('focusgram:urlchange', checkAndTrackActiveReel); - // Periodic ticker (every 400ms) to ensure reel transitions are never missed - setInterval(checkAndTrackActiveReel, 400); + // Periodic ticker (every 250ms) to ensure reel transitions are never missed + setInterval(checkAndTrackActiveReel, 250); // Ensure clicking 'Post' or 'Story' in the Create dropdown opens file/media picker document.addEventListener('click', (e) => { @@ -1654,7 +1881,7 @@ } }, true); - // --- 3b. REELS LAYOUT SCROLLER & OVERLAY POSITIONING (100% INSPIRATION MATCH) --- + // Reels layout scroller and overlay positioning. function fixReels() { const path = window.location.pathname.toLowerCase(); if (!path.startsWith('/reels') && !path.startsWith('/reel')) return; @@ -1703,21 +1930,26 @@ // Check if this reel is an ad / sponsored if (currentSettings.noAds !== false) { - let isSponsored = !!card.querySelector('[aria-label*="Sponsored" i], [aria-label="Ad" i], [aria-label*="advertisement" i], [data-ad-preview], a[href*="/ads/"], a[href*="/ad_"], a[href*="/about/ads"], a[href*="ads.instagram.com"]'); + let isSponsored = !!card.querySelector('[aria-label*="Sponsored" i], [aria-label="Ad" i], [aria-label*="advertisement" i], [data-ad-preview], [data-ad-id], a[href*="/ads/"], a[href*="/ad_"], a[href*="/about/ads"], a[href*="ads.instagram.com"], a[href*="facebook.com/ads"]'); if (!isSponsored) { - const badges = card.querySelectorAll('span, div[role="button"], button'); + const strictAdCtas = ['shop now', 'install now', 'get offer']; + const badges = card.querySelectorAll('button:not([data-fg-x-act]):not([data-fg-download-btn]), div[role="button"]:not([data-fg-x-act]):not([data-fg-download-btn])'); for (let s = 0; s < badges.length; s++) { const el = badges[s]; if (el.children.length > 2) continue; - const raw = (el.textContent || '').trim(); - if (!raw || raw.length > 15) continue; - if (/^[\u{1F300}-\u{1F9FF}\s]*ad$/iu.test(raw) || /^sponsored$/i.test(raw) || /^paid\s+partnership$/i.test(raw)) { + const raw = (el.textContent || '').trim().toLowerCase(); + if (!raw || raw.length > 25) continue; + if (/^[\u{1F300}-\u{1F9FF}\s]*ad$/iu.test(raw) || raw === 'sponsored' || raw === 'paid partnership' || raw === 'advertisement') { if (!el.closest('span[dir="auto"]')) { isSponsored = true; break; } } + if (strictAdCtas.includes(raw) && !el.closest('span[dir="auto"]')) { + isSponsored = true; + break; + } } } @@ -1882,8 +2114,7 @@ observer.observe(root, { childList: true, subtree: true }); } - // --- DEDICATED MODAL DETECTION OBSERVER (Senior Blueprint) --- - // Watch for modal-related attribute changes and structure modifications + // Observe modal attributes and tree changes. let lastModalCheckTime = 0; const modalDebounceMs = 50; @@ -1906,7 +2137,7 @@ } } - // --- 4. SPA URL & MODAL WATCHER --- + // SPA route and modal state observer. let lastReportedUrl = window.location.href; let lastModalState = false; let lastInputFocused = false; @@ -1924,8 +2155,7 @@ path.startsWith('/signup'); } - // --- SHARE SHEET DETECTION --- - // Track when share/modal API was called (scroll lock is primary indicator for comments) + // Share sheet detection. let shareSheetActive = false; let shareSheetCheckInterval = null; let lastShareSheetCheckTime = 0; @@ -1935,7 +2165,6 @@ if (now - lastShareSheetCheckTime < 150) return; lastShareSheetCheckTime = now; - // Efficient modal lookup without DOM thrashing const shareModals = Array.from(document.querySelectorAll('[role="dialog"], [aria-modal="true"], div[style*="fixed"]')).filter(el => { if (el.getAttribute('data-fg-hide')) return false; const cs = window.getComputedStyle(el); @@ -1970,7 +2199,6 @@ } } - // Intercept fetch to detect share/DM API calls and start monitoring const originalFetch = window.fetch.bind(window); window.fetch = function(...args) { const url = typeof args[0] === 'string' ? args[0] : (args[0]?.url || ''); @@ -1985,13 +2213,14 @@ return originalFetch.apply(this, args); }; - // --- 6. UNBLUR REVEAL ENGINE (Tap & 350ms Hold) --- + // Unblur reveal engine (tap and 350ms hold). const HOLD_MS = 350, MOVE_TOL = 10; const root = document.documentElement; let timer = 0, sx = 0, sy = 0, swallowUntil = 0; function updateFeedFlag() { root.toggleAttribute("data-fg-feed", location.pathname === "/" || location.pathname === ""); + root.toggleAttribute("data-fg-explore", location.pathname.startsWith("/explore")); } updateFeedFlag(); new MutationObserver(updateFeedFlag).observe(document, { childList: true, subtree: true }); @@ -2000,6 +2229,14 @@ function getBlurredTarget(el) { if (!el) return null; + // Comment GIFs, sticker picker, and comment controls must NEVER be treated as unblur targets + if (el.closest('img[src*="giphy"], img[src*="tenor"], img[alt*="GIF" i], img[alt*="gif" i], form, ul[class*="comment" i]')) { + return null; + } + if (el.tagName === 'IMG' && (el.src?.includes('giphy') || el.src?.includes('tenor') || el.alt?.toLowerCase().includes('gif'))) { + return null; + } + if (currentSettings.blurFeed) { // Never treat carousel next/prev arrows or navigation buttons as unblur triggers if (el.closest('button[aria-label="Next"], button[aria-label="Go back"], button[aria-label="Next slide"], button[aria-label="Previous slide"], button[aria-label="Chevron"]')) { @@ -2094,59 +2331,101 @@ if (getBlurredTarget(e.target)) e.preventDefault(); }, true); - // --- In-Page Reels Metadata Extractor --- - let lastExtractedReelUrl = ''; + // In-page reels metadata extractor. + let lastExtractedReelKey = ''; function checkAndExtractReelMetadata() { const url = window.location.href; if (!url.includes('/reel/') && !url.includes('/reels/')) return; - if (lastExtractedReelUrl === url) return; + + const videos = Array.from(document.querySelectorAll('video')); + const activeVideo = videos.find(v => !v.paused && v.getBoundingClientRect().height > 200) || + videos.find(v => { + const r = v.getBoundingClientRect(); + return r.top >= -50 && r.top < window.innerHeight * 0.6; + }) || videos[0]; let title = ''; let thumbnailUrl = ''; let author = ''; + let reelShortcode = ''; - const ogTitle = document.querySelector('meta[property="og:title"]'); - const ogImage = document.querySelector('meta[property="og:image"]'); - if (ogTitle && ogTitle.content) title = ogTitle.content.trim(); - if (ogImage && ogImage.content) thumbnailUrl = ogImage.content.trim(); + if (activeVideo) { + thumbnailUrl = activeVideo.getAttribute('poster') || ''; + let card = activeVideo.closest('article, section, div[role="presentation"]') || activeVideo.parentElement; + while (card && card !== document.body && card !== document.documentElement) { + if (card.tagName === 'ARTICLE' || card.tagName === 'SECTION') break; + if (card.getBoundingClientRect().height > window.innerHeight * 0.5) break; + card = card.parentElement; + } - if (!title && document.title) { - title = document.title.replace(' on Instagram', '').replace('â€Ē Instagram photos and videos', '').trim(); - } - if (title) { - const authorMatch = title.match(/^([^(@:]+)(?:\s*\((@[^)]+)\))?/); - if (authorMatch) { - author = (authorMatch[2] || authorMatch[1]).trim(); + if (card) { + const link = card.querySelector('a[href*="/reel/"], a[href*="/p/"], a[href*="/reels/"]'); + if (link && link.href) { + const lm = /\/(?:reel|reels|p)\/([A-Za-z0-9_-]{5,})/.exec(link.href); + if (lm) reelShortcode = lm[1]; + } + + const authorEl = card.querySelector('header a[role="link"], a[role="link"][href^="/"]:not([href*="/audio/"]):not([href*="/reel"]), header h2, header h3, header span[dir="auto"]'); + if (authorEl && authorEl.textContent) { + author = authorEl.textContent.trim().replace(/^@/, ''); + } else if (authorEl && authorEl.getAttribute('href')) { + const m = authorEl.getAttribute('href').match(/^\/([A-Za-z0-9._]+)\/?/); + if (m) author = m[1]; + } + + const textCandidates = Array.from(card.querySelectorAll('h1, h2, span[dir="auto"], div[dir="auto"]')); + for (const el of textCandidates) { + if (el.closest('a[href*="/audio/"]') || el.closest('header') || el.closest('button') || el.closest('a[role="link"]')) continue; + const txt = (el.textContent || '').trim(); + const lower = txt.toLowerCase(); + if (txt.length >= 2 && txt !== author && !lower.includes('follow') && !lower.startsWith('liked by') && !lower.includes('original audio') && !['ad', 'sponsored'].includes(lower)) { + title = txt.slice(0, 300); + break; + } + } } } - if (!thumbnailUrl) { - const videoEl = document.querySelector('video[poster]') || document.querySelector('video'); - if (videoEl && videoEl.getAttribute('poster')) { - thumbnailUrl = videoEl.getAttribute('poster'); - } - } - if (!author) { - const authorEl = document.querySelector('a[role="link"][tabindex="0"] span') || - document.querySelector('header a') || - document.querySelector('article header a'); - if (authorEl && authorEl.textContent) { - author = authorEl.textContent.trim(); + if (!reelShortcode) { + const urlMatch = /\/(?:reel|reels|p)\/([A-Za-z0-9_-]{5,})/.exec(window.location.pathname); + if (urlMatch && videos.length <= 1) { + reelShortcode = urlMatch[1]; } } - if (title || thumbnailUrl) { - lastExtractedReelUrl = url; - postToNative({ - action: 'reelViewed', - type: 'reelViewed', - url: url, - title: title || '', - author: author || '', - thumbnailUrl: thumbnailUrl || '' - }); - console.log('[FocusGram] In-page extracted reel metadata:', url); + if (!title && !author && videos.length <= 1) { + const ogTitle = document.querySelector('meta[property="og:title"]'); + if (ogTitle && ogTitle.content) title = ogTitle.content.trim(); + if (title) { + const authorMatch = title.match(/^([^(@:]+)(?:\s*\((@[^)]+)\))?/); + if (authorMatch) { + author = (authorMatch[2] || authorMatch[1]).trim().replace(/^@/, ''); + } + } + const ogImage = document.querySelector('meta[property="og:image"]'); + if (!thumbnailUrl && ogImage && ogImage.content) thumbnailUrl = ogImage.content.trim(); } + + const effectiveUrl = reelShortcode ? `https://www.instagram.com/reel/${reelShortcode}/` : url; + const dedupeKey = `${reelShortcode || author || ''}___${title || ''}`; + + if (!title && !author && !reelShortcode) { + return; + } + + if (lastExtractedReelKey === dedupeKey) return; + lastExtractedReelKey = dedupeKey; + + postToNative({ + action: 'reelViewed', + type: 'reelViewed', + url: effectiveUrl, + code: reelShortcode || (author ? `reel_${author.toLowerCase()}` : ''), + title: title || '', + author: author || '', + thumbnailUrl: thumbnailUrl || '' + }); + console.log('[FocusGram] In-page extracted reel metadata:', effectiveUrl); } function isBottomSheetOpen() { @@ -2304,10 +2583,7 @@ return mode; } - /* ========================================================================== - CHAT PRIVACY VEIL & INBOX PREVIEW MASKING - Zero-latency pure black cover over locked chat threads + snippet redaction. - ========================================================================== */ + // Chat veil and inbox preview masking. let chatLockMode = 'individual'; let chatLockAll = false; let lockedChatIds = new Set(); @@ -2846,7 +3122,7 @@ const badge = document.createElement('span'); badge.className = 'fg-chat-lock-badge'; badge.style.cssText = 'margin-left: 6px; font-size: 11px; opacity: 0.85; vertical-align: middle; display: inline-block;'; - badge.textContent = '🔒'; + badge.innerHTML = ''; titleTarget.appendChild(badge); } } else { @@ -2961,39 +3237,54 @@ try { // Hide Like Counts dynamic text if (currentSettings.hideLikeCounts) { + // 1. Liked by links and their parent containers document.querySelectorAll('a[href*="/liked_by/"]').forEach((el) => { el.style.setProperty('display', 'none', 'important'); const parent = el.closest('div'); - if (parent && parent.textContent && /liked/i.test(parent.textContent)) { + if (parent && !parent.querySelector('svg') && parent.textContent && /liked/i.test(parent.textContent)) { parent.style.setProperty('display', 'none', 'important'); } }); - document.querySelectorAll('article section ~ div span, article section ~ div div, div[role="presentation"] span, article span, article div').forEach((el) => { + + // 2. Feed post like counts text (e.g. "Liked by ...", "97 likes", "12 others") + document.querySelectorAll('article section ~ div span, article section ~ div div, div[role="presentation"] span').forEach((el) => { + if (el.querySelector('svg') || el.closest('svg') || el.querySelector('img') || el.closest('form')) return; if (el.children.length > 2) return; const text = (el.textContent || '').trim(); if (/^(liked\s+by|[\d,.]+[KkMm]?\s+likes?|[\d,.]+\s+others|be\s+the\s+first\s+to\s+like)/i.test(text)) { el.style.setProperty('display', 'none', 'important'); } }); + + // 3. Standalone count buttons in feed post: 97 + document.querySelectorAll('article span[role="button"], section span[role="button"]').forEach((el) => { + if (el.querySelector('svg') || el.closest('svg')) return; + const text = (el.textContent || '').trim(); + if (/^[\d,.]+[KkMm]?$/.test(text)) { + el.style.setProperty('display', 'none', 'important'); + } + }); + + // 4. Reels like count in right interaction rail: + // Sibling or child span with numbers/K/M (e.g. 192K) document.querySelectorAll('svg[aria-label="Like"], svg[aria-label="Unlike"]').forEach((svg) => { const btn = svg.closest('[role="button"]') || svg.parentElement; - if (btn) { - btn.querySelectorAll('span, div').forEach(child => { - if (!child.contains(svg) && /^\d+[\d,.]*[KkMm]?$/.test((child.textContent || '').trim())) { + if (!btn) return; + const container = btn.parentElement; + if (container) { + container.querySelectorAll('span, div').forEach((child) => { + if (child.contains(svg) || child.querySelector('svg') || child.closest('svg')) return; + const text = (child.textContent || '').trim(); + if (/^[\d,.]+[KkMm]?$/.test(text) || /^[\d,.]+[KkMm]?\s+likes?$/i.test(text)) { child.style.setProperty('display', 'none', 'important'); } }); - let sibling = btn.nextElementSibling; - if (sibling && /^\d+[\d,.]*[KkMm]?$/.test((sibling.textContent || '').trim())) { - sibling.style.setProperty('display', 'none', 'important'); - } - const parent = btn.parentElement; - if (parent && parent.children.length <= 4) { - parent.querySelectorAll('span').forEach(sp => { - if (/^\d+[\d,.]*[KkMm]?$/.test((sp.textContent || '').trim())) { - sp.style.setProperty('display', 'none', 'important'); - } - }); + let sib = btn.nextElementSibling; + if (sib && !sib.contains(svg) && !sib.querySelector('svg')) { + const text = (sib.textContent || '').trim(); + if (/^[\d,.]+[KkMm]?$/.test(text) || /^[\d,.]+[KkMm]?\s+likes?$/i.test(text)) { + sib.style.setProperty('display', 'none', 'important'); + } } } }); @@ -3356,7 +3647,7 @@ cleanUpDynamicContent(); }, 250); - // --- 4. INPUT FOCUS / TYPING BRIDGE --- + // Input focus and typing bridge. function setupInputFocusListeners() { document.addEventListener('focusin', (e) => { const target = e.target; @@ -3380,7 +3671,7 @@ }, true); } - // --- 5. SENIOR BLUEPRINT NATIVE MESSAGING & SPA NAVIGATION --- + // Native messaging and SPA route navigation. const ROUTES = { home: "/", search: "/explore/", @@ -3457,6 +3748,28 @@ if (msg.settings.unblurMode) { mode = msg.settings.unblurMode; } + if (currentSettings.storySearchEnabled === false) { + const existing = document.getElementById('fg-story-search-card'); + if (existing) { + existing.remove(); + storySearchCardEl = null; + } + const noRes = document.getElementById('fg-no-viewer-results'); + if (noRes) noRes.remove(); + document.querySelectorAll('div[role="dialog"] [data-fg-viewer-hidden]').forEach(el => { + el.style.display = ''; + delete el.dataset.fgViewerHidden; + }); + } + if (currentSettings.customBioFontsEnabled === false) { + if (bioToolbarEl) { + bioToolbarEl.remove(); + bioToolbarEl = null; + } + const inlineBar = document.getElementById('fg-bio-inline-bar'); + if (inlineBar) inlineBar.remove(); + postToNative({ action: 'bioInputFocused', focused: false }); + } updateGuardrailsCSS(); syncGhostSettings(); syncChatLockSettings(msg.settings); @@ -3495,7 +3808,7 @@ Object.assign(currentSettings, { noAds: config.noAds !== undefined ? config.noAds : currentSettings.noAds, noStories: config.hideStories !== undefined ? config.hideStories : currentSettings.noStories, - noReels: config.hideReels !== undefined ? config.hideReels : currentSettings.noReels, + noReels: config.hideReels !== undefined ? config.hideReels : currentSettings.hideReels, blurExplore: config.blurExplore !== undefined ? config.blurExplore : currentSettings.blurExplore, blurReels: config.blurReels !== undefined ? config.blurReels : currentSettings.blurReels, blurFeed: config.blurFeed !== undefined ? config.blurFeed : currentSettings.blurFeed, @@ -3508,14 +3821,76 @@ videoDownloadEnabled: config.videoDownloadEnabled !== undefined ? config.videoDownloadEnabled : currentSettings.videoDownloadEnabled, grayscale: config.grayscale !== undefined ? config.grayscale : currentSettings.grayscale, minimalMode: config.minimalMode !== undefined ? config.minimalMode : currentSettings.minimalMode, - ghostModeEnabled: ghost.enabled !== undefined ? ghost.enabled : currentSettings.ghostModeEnabled, - ghostHideStorySeen: ghost.igStory !== undefined ? ghost.igStory : currentSettings.ghostHideStorySeen, - ghostHideDirectSeen: ghost.igSeen !== undefined ? ghost.igSeen : currentSettings.ghostHideDirectSeen, - ghostHideTypingIndicator: ghost.igTyping !== undefined ? ghost.igTyping : currentSettings.ghostHideTypingIndicator + storySearchEnabled: config.storySearchEnabled !== undefined ? config.storySearchEnabled : currentSettings.storySearchEnabled, + customBioFontsEnabled: config.customBioFontsEnabled !== undefined ? config.customBioFontsEnabled : currentSettings.customBioFontsEnabled, + ghostModeEnabled: ghost.enabled !== undefined ? !!ghost.enabled : currentSettings.ghostModeEnabled, + ghostHideStorySeen: ghost.hideStorySeen !== undefined ? !!ghost.hideStorySeen : (ghost.igStory !== undefined ? !!ghost.igStory : currentSettings.ghostHideStorySeen), + ghostHideDirectSeen: ghost.hideDirectSeen !== undefined ? !!ghost.hideDirectSeen : (ghost.igSeen !== undefined ? !!ghost.igSeen : currentSettings.ghostHideDirectSeen), + ghostHideTypingIndicator: ghost.hideTyping !== undefined ? !!ghost.hideTyping : (ghost.igTyping !== undefined ? !!ghost.igTyping : currentSettings.ghostHideTypingIndicator) }); + + if (currentSettings.storySearchEnabled === false) { + const existing = document.getElementById('fg-story-search-card'); + if (existing) { + existing.remove(); + storySearchCardEl = null; + } + const noRes = document.getElementById('fg-no-viewer-results'); + if (noRes) noRes.remove(); + document.querySelectorAll('div[role="dialog"] [data-fg-viewer-hidden]').forEach(el => { + el.style.display = ''; + delete el.dataset.fgViewerHidden; + }); + } + + if (currentSettings.customBioFontsEnabled === false) { + if (bioToolbarEl) { + bioToolbarEl.remove(); + bioToolbarEl = null; + } + const inlineBar = document.getElementById('fg-bio-inline-bar'); + if (inlineBar) inlineBar.remove(); + const fontToolbar = document.getElementById('fg-bio-font-toolbar'); + if (fontToolbar) fontToolbar.remove(); + postToNative({ action: 'bioInputFocused', focused: false }); + } updateGuardrailsCSS(); syncGhostSettings(); } + + if (event.data && event.data.type === 'FOCUSGRAM_FILTER_UPDATE' && event.data.config) { + const cfg = event.data.config; + if (cfg.storySearchEnabled !== undefined) { + currentSettings.storySearchEnabled = cfg.storySearchEnabled; + } + if (cfg.customBioFontsEnabled !== undefined) { + currentSettings.customBioFontsEnabled = cfg.customBioFontsEnabled; + } + if (currentSettings.storySearchEnabled === false) { + const existing = document.getElementById('fg-story-search-card'); + if (existing) { + existing.remove(); + storySearchCardEl = null; + } + const noRes = document.getElementById('fg-no-viewer-results'); + if (noRes) noRes.remove(); + document.querySelectorAll('div[role="dialog"] [data-fg-viewer-hidden]').forEach(el => { + el.style.display = ''; + delete el.dataset.fgViewerHidden; + }); + } + if (currentSettings.customBioFontsEnabled === false) { + if (bioToolbarEl) { + bioToolbarEl.remove(); + bioToolbarEl = null; + } + const inlineBar = document.getElementById('fg-bio-inline-bar'); + if (inlineBar) inlineBar.remove(); + const fontToolbar = document.getElementById('fg-bio-font-toolbar'); + if (fontToolbar) fontToolbar.remove(); + postToNative({ action: 'bioInputFocused', focused: false }); + } + } }); // Forward Reels metadata events to native host @@ -3532,7 +3907,6 @@ } }); - // Senior Blueprint: Direct content script communication with WebExtension background script function postToNative(message) { if (!message) return; try { @@ -3570,8 +3944,7 @@ } }); - // --- INSTAGRAM THEME DETECTION --- - // Detect if Instagram is in light or dark mode and notify native + // Detect theme state and notify native host. let lastSentTheme = null; function detectAndSendTheme() { @@ -3647,7 +4020,7 @@ // Query initial settings from background if (typeof browser !== 'undefined' && browser.runtime && browser.runtime.sendMessage) { - browser.runtime.sendMessage({ action: 'getSettings' }, (response) => { + const applyLoadedSettings = (response) => { if (response && response.settings) { Object.assign(currentSettings, response.settings); if (response.settings.unblurMode) { @@ -3657,10 +4030,16 @@ syncGhostSettings(); syncChatLockSettings(response.settings); } - }); + }; + + try { + const res = browser.runtime.sendMessage({ action: 'getSettings' }, applyLoadedSettings); + if (res && typeof res.then === 'function') { + res.then(applyLoadedSettings).catch(() => {}); + } + } catch (_) {} } - // --- 6. INITIALIZE --- updateIslandModeFromUrl(); setupGhostModeInterceptors(); updateGuardrailsCSS(); @@ -3712,10 +4091,7 @@ subtree: false }); - // --- 7. INFINITE SCROLL PRESERVATION --- - // Instagram web uses IntersectionObserver and scroll sentinels to trigger next batch of feed posts. - // When ads are hidden or sentinel reaches bottom, synthetic scroll events and clicking "View older posts" - // ensures smooth, uninterrupted infinite feed scrolling past post 9. + // Trigger scroll events and click sentinels so infinite scroll continues after ad removal. let lastInfiniteCheck = 0; function checkAndTriggerInfiniteScroll() { const now = Date.now(); @@ -3757,9 +4133,7 @@ } } catch (_) {} - /* ========================================================================== - 8. LIVE BIO FONT STYLIST (KEYBOARD ACCESSORY BAR & REAL-TIME INPUT) - ========================================================================== */ + // Bio font stylist keyboard accessory and real-time input conversion. const BIO_FONT_MAPS = { normal: null, bold: { u: 0x1D400, l: 0x1D41A, d: 0x1D7CE }, // Mathematical Bold (Zero gaps) @@ -3855,6 +4229,7 @@ } function createBioFontToolbar() { + if (currentSettings.customBioFontsEnabled === false) return null; if (document.getElementById('fg-bio-font-toolbar')) { return document.getElementById('fg-bio-font-toolbar'); } @@ -3998,6 +4373,10 @@ window.__fgSetBioStyle = setBioActiveStyle; function showBioFontToolbar(target) { + if (currentSettings.customBioFontsEnabled === false) { + hideBioFontToolbar(); + return; + } activeBioTarget = target; createBioFontToolbar(); if (bioToolbarEl) { @@ -4011,10 +4390,18 @@ if (bioToolbarEl) { bioToolbarEl.style.display = 'none'; } + if (currentSettings.customBioFontsEnabled === false) { + const tb = document.getElementById('fg-bio-font-toolbar'); + if (tb) tb.remove(); + bioToolbarEl = null; + const ib = document.getElementById('fg-bio-inline-bar'); + if (ib) ib.remove(); + } } // Intercept keystrokes on document capture level for instant Unicode glyph conversion document.addEventListener('beforeinput', (e) => { + if (currentSettings.customBioFontsEnabled === false) return; if (!activeBioTarget || activeBioStyle === 'normal') return; if (e.data && e.inputType === 'insertText') { e.preventDefault(); @@ -4032,6 +4419,7 @@ // Dynamic focusin detection across document document.addEventListener('focusin', (e) => { + if (currentSettings.customBioFontsEnabled === false) return; if (isBioTarget(e.target)) { showBioFontToolbar(e.target); postToNative({ action: 'bioInputFocused', focused: true }); @@ -4062,11 +4450,7 @@ }); } - /* ========================================================================== - 9. NETWORK-TRIGGERED STORY VIEWER SEARCH - - Directly displays the search bar when the /list_reel_media_viewer/ - network request is sent or received. - ========================================================================== */ + // Story viewer search triggered by list_reel_media_viewer network activity. let storySearchCardEl = null; let storyViewersData = []; @@ -4226,6 +4610,15 @@ } function checkAndMountStorySearch() { + if (currentSettings.storySearchEnabled === false) { + const existing = document.getElementById('fg-story-search-card'); + if (existing) { + existing.remove(); + storySearchCardEl = null; + } + return; + } + const info = findViewersDialog(); if (!info || !info.header) { const existing = document.getElementById('fg-story-search-card'); @@ -4245,6 +4638,7 @@ function handleStoryViewersDetected(detail) { if (!detail) return; + if (currentSettings.storySearchEnabled === false) return; checkAndMountStorySearch(); if (detail.phase === 'ready' && detail.data) { @@ -4307,3 +4701,4 @@ })(); + diff --git a/Ported/app/src/main/assets/extensions/ublock/ghost.js b/app/src/main/assets/extensions/ublock/ghost.js similarity index 100% rename from Ported/app/src/main/assets/extensions/ublock/ghost.js rename to app/src/main/assets/extensions/ublock/ghost.js diff --git a/Ported/app/src/main/assets/extensions/ublock/manifest.json b/app/src/main/assets/extensions/ublock/manifest.json similarity index 91% rename from Ported/app/src/main/assets/extensions/ublock/manifest.json rename to app/src/main/assets/extensions/ublock/manifest.json index 1324918..1ca5a83 100644 --- a/Ported/app/src/main/assets/extensions/ublock/manifest.json +++ b/app/src/main/assets/extensions/ublock/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "FocusGram Content Engine", - "version": "2.1.0", + "version": "3.0.0", "description": "Privacy-preserving network-level content blocking, stealth protection, and cosmetic filtering for FocusGram.", "author": "FocusGram Project", "homepage_url": "https://github.com/Ujwal223/FocusGram", @@ -28,7 +28,8 @@ ], "js": [ "content.js", - "focusgram_content.js" + "focusgram_content.js", + "media_downloader.js" ], "run_at": "document_start", "all_frames": true diff --git a/Ported/app/src/main/assets/scripts/media_downloader.js b/app/src/main/assets/extensions/ublock/media_downloader.js similarity index 92% rename from Ported/app/src/main/assets/scripts/media_downloader.js rename to app/src/main/assets/extensions/ublock/media_downloader.js index a6f6942..ac34536 100644 --- a/Ported/app/src/main/assets/scripts/media_downloader.js +++ b/app/src/main/assets/extensions/ublock/media_downloader.js @@ -1,13 +1,8 @@ /** - * FocusGram — Standalone In-Page Media Downloader - * Injected directly into the page context by UserScriptManager. - * - * Provides: - * 1. Home Feed download buttons (inline next to Save/Share, or overlay fallback). - * 2. Reels download buttons (in-page vertical action column + ReelsTopBar native trigger). - * 3. Profile avatar HD downloader badge. - * 4. Multi-tier resolution: GraphQL PolarisPostRootQuery -> React Fiber -> DOM -> Performance Resource Timings -> In-Memory Harvest Cache. - * 5. Direct communication with native Android host via 'focusgram-download://' URI interception. + * In-page media downloader injected into web contexts. + * Attaches download buttons to feed posts, reels, and profiles, resolving media URLs + * via GraphQL, React Fiber, DOM inspection, and performance resource timings before + * delegating to the native host over custom URI schemes or extension messaging. */ 'use strict'; @@ -16,15 +11,14 @@ function isAuthPage() { try { var path = (window.location.pathname || '').toLowerCase(); - if (path.startsWith('/accounts') || path.startsWith('/login') || path.startsWith('/signup')) return true; - var c = document.cookie || ''; - if (!c.includes('sessionid=') && !c.includes('ds_user_id=')) return true; - if (document.getElementById('loginForm') || document.querySelector('form[action*="login"], input[name="password"]')) return true; + if (path.includes('/accounts/edit')) return false; + if (path.startsWith('/accounts') || path.startsWith('/login') || path.startsWith('/signup') || path.startsWith('/two_factor') || path.startsWith('/challenge')) return true; + if (document.getElementById('loginForm') || document.querySelector('form[action*="login"], form input[type="password"]')) return true; } catch (_) {} return false; } - if (isAuthPage()) return; // Leave login/signup completely untouched + if (isAuthPage()) return; if (window.__fgMediaDownloaderInstalled) return; window.__fgMediaDownloaderInstalled = true; @@ -32,13 +26,46 @@ const FG_DL_ATTR = 'data-fg-download-btn'; const IG_POST_ROOT_QUERY_DOC_ID = '27852811784380813'; + let downloadEnabled = false; + function isDownloadEnabled() { + if (typeof downloadEnabled === 'boolean' && downloadEnabled) return true; if (window.__FOCUSGRAM_CONFIG__ && window.__FOCUSGRAM_CONFIG__.videoDownloadEnabled !== undefined) { return !!window.__FOCUSGRAM_CONFIG__.videoDownloadEnabled; } return false; } + if (typeof browser !== 'undefined' && browser.runtime) { + if (browser.runtime.onMessage) { + browser.runtime.onMessage.addListener((msg) => { + if (msg && msg.action === 'updateSettings' && msg.settings) { + if (msg.settings.videoDownloadEnabled !== undefined) { + downloadEnabled = !!msg.settings.videoDownloadEnabled; + syncDownloadButtons(); + syncProfileDownloadButton(); + syncReelsDownloadButton(); + } + } + }); + } + if (browser.runtime.sendMessage) { + try { + const res = browser.runtime.sendMessage({ action: 'getSettings' }); + if (res && typeof res.then === 'function') { + res.then((r) => { + if (r && r.settings && r.settings.videoDownloadEnabled !== undefined) { + downloadEnabled = !!r.settings.videoDownloadEnabled; + syncDownloadButtons(); + syncProfileDownloadButton(); + syncReelsDownloadButton(); + } + }).catch(() => {}); + } + } catch (_) {} + } + } + let isGlobalScrolling = false; let globalScrollTimeout = null; let lastScrollTime = 0; @@ -58,7 +85,6 @@ } catch (_) {} } - // 1. Try WebExtension native messaging bridge first try { if (typeof browser !== 'undefined' && browser.runtime && browser.runtime.sendMessage) { browser.runtime.sendMessage({ @@ -71,7 +97,6 @@ } } catch (_) {} - // 2. Dispatch event for content script bridge try { window.dispatchEvent(new CustomEvent('focusgram:download-request', { detail: { url: url || '', filename: filename || '', error: error || '' } @@ -109,7 +134,6 @@ function parseMediaItem(item, slideIndex = 0) { if (!item) return null; - // 1. Carousel check: carousel_media or edge_sidecar_to_children if (item.carousel_media && Array.isArray(item.carousel_media) && item.carousel_media.length > 0) { const target = item.carousel_media[slideIndex] || item.carousel_media[0]; const parsed = parseMediaItem(target, 0); @@ -162,7 +186,6 @@ async function fetchMediaByShortcode(shortcode, slideIndex = 0) { if (!shortcode) return null; - // 1. Try GraphQL PolarisPostRootQuery try { const apiURL = new URL('/graphql/query', window.location.origin); const headers = { @@ -206,7 +229,6 @@ console.warn('[FocusGram Downloader] GraphQL shortcode query error:', e); } - // 2. Fallback to /p/{shortcode}/?__a=1&__d=dis try { const res = await fetch(`/p/${shortcode}/?__a=1&__d=dis`, { headers: { @@ -250,7 +272,7 @@ const entry = entries[i]; const name = entry.name || ''; if (!name.startsWith('http://') && !name.startsWith('https://')) continue; - if (isLikelyImageUrl(name)) continue; // STRICTLY IGNORE ALL IMAGES + if (isLikelyImageUrl(name)) continue; if (!name.includes('cdninstagram.com') && !name.includes('fbcdn.net')) continue; if (name.includes('.mp4') || name.includes('/v/t50.') || name.includes('/v/t16.') || @@ -341,14 +363,13 @@ return { url: ogVideo.content, isVideo: true, author: fiberData?.author, code: fiberData?.code }; } - // CRITICAL: When video is expected, NEVER return an image! + // Do not fall back to photo stills when expecting video. if (fiberData && (fiberData.code || fiberData.author)) { return { url: '', isVideo: true, code: fiberData.code, author: fiberData.author }; } return null; } - // PHOTO ONLY const fiberContainer = extractFromFiber(container); if (fiberContainer && fiberContainer.url && !isLikelyVideoUrl(fiberContainer.url)) { return fiberContainer; @@ -383,7 +404,6 @@ async function handleMediaDownload(postContainer, shortcode, activeVideo) { try { - // Determine active slide index for carousels let slideIndex = 0; if (postContainer) { const activeStep = postContainer.querySelector('button[aria-current="step"]'); @@ -400,7 +420,6 @@ let isVideo = hasVideoElement; let author = ''; - // 1. Instant check in harvested in-memory media cache (GraphQL/Feed/Clips responses) try { const harvest = (window.wrappedJSObject && window.wrappedJSObject.__fg_media_harvest) || window.__fg_media_harvest; if (harvest && shortcode && harvest[shortcode]) { @@ -441,7 +460,6 @@ } } catch (_) {} - // 2. Active stream from performance timing entries — STRICTLY FOR VIDEOS ONLY! if (!resolvedUrl && hasVideoElement) { const streamMedia = getActiveStreamFromPerformance(); if (streamMedia && streamMedia.url && isLikelyVideoUrl(streamMedia.url)) { @@ -450,7 +468,6 @@ } } - // 3. Check recent media harvest queue (especially for Reels viewer where URL lacks shortcode) if (!resolvedUrl) { try { const recents = (window.wrappedJSObject && window.wrappedJSObject.__fg_recent_media) || window.__fg_recent_media; @@ -495,7 +512,6 @@ } catch (_) {} } - // 4. Check DOM & React Fiber on activeVideo or container if (!resolvedUrl) { const domMedia = extractBestDomUrl(activeVideo || postContainer, hasVideoElement); if (domMedia && domMedia.url && domMedia.url.startsWith('http')) { @@ -511,7 +527,6 @@ if (!author && domMedia?.author) author = domMedia.author; } - // 5. Query Instagram API via shortcode if ((!resolvedUrl || (hasVideoElement && isLikelyImageUrl(resolvedUrl))) && shortcode) { const apiMedia = await fetchMediaByShortcode(shortcode, slideIndex); if (apiMedia && apiMedia.url && apiMedia.url.startsWith('http')) { @@ -532,7 +547,6 @@ } } - // 6. Last-resort fallback for video streams if still empty if (hasVideoElement && (!resolvedUrl || isLikelyImageUrl(resolvedUrl))) { const streamFallback = getActiveStreamFromPerformance(); if (streamFallback && streamFallback.url && isLikelyVideoUrl(streamFallback.url)) { @@ -541,7 +555,6 @@ } } - // Extract author from DOM if still missing if (!author && postContainer) { const authorEl = postContainer.querySelector('header a, a[role="link"]:not([href*="audio"]):not([href*="explore"]):not([href*="/p/"]):not([href*="/reel/"])'); if (authorEl && authorEl.textContent) { @@ -549,11 +562,9 @@ } } - // Guard: If video was requested, REJECT if resolvedUrl is an image! if (hasVideoElement && isLikelyImageUrl(resolvedUrl)) { resolvedUrl = ''; } - // Guard: If photo was requested, REJECT if resolvedUrl is a video! if (!hasVideoElement && isLikelyVideoUrl(resolvedUrl)) { resolvedUrl = ''; } @@ -568,7 +579,6 @@ return; } - // Failed to resolve const errorMsg = hasVideoElement ? 'Could not extract MP4 video URL for this post' : 'Could not extract photo URL for this post'; triggerNativeDownload(null, null, errorMsg); } catch (e) { @@ -579,7 +589,6 @@ async function handleProfilePictureDownload(username, avatarImg) { try { - // 1. Check harvested profile pictures from GraphQL / API responses const harvested = (window.wrappedJSObject && window.wrappedJSObject.__fg_profile_pics) || window.__fg_profile_pics; if (harvested && username && harvested[username.toLowerCase()]) { const hdUrl = harvested[username.toLowerCase()]; @@ -589,7 +598,6 @@ } } - // 2. Attempt official Instagram web_profile_info API for original HD avatar if (username) { try { const res = await fetch('/api/v1/users/web_profile_info/?username=' + encodeURIComponent(username), { @@ -612,7 +620,7 @@ } } - // 3. Fallback to DOM avatar image — NEVER strip /s150x150/ or regex-mutate signed Meta CDN URLs! + // Meta CDN URL parameters and path tokens are HMAC-signed; mutating them breaks signatures. if (avatarImg) { let url = ''; if (avatarImg.srcset) { @@ -649,7 +657,6 @@ if (RESERVED.includes(usernameCandidate.toLowerCase())) return; const username = usernameCandidate; - // Find profile avatar in header or main const avatarImgs = Array.from(document.querySelectorAll('header img, section header img, main header img, [role="main"] header img, img[alt*="profile picture" i], img[alt*="\'s profile" i]')) .filter(img => { const r = img.getBoundingClientRect(); @@ -658,11 +665,9 @@ const avatarImg = avatarImgs[0]; if (!avatarImg) return; - // The button or container enclosing the avatar const avatarBtn = avatarImg.closest('button, [role="button"]') || avatarImg.parentElement; if (!avatarBtn) return; - // Wrapper is either the parent of the avatar button, or the button itself const wrapper = avatarBtn.parentElement || avatarBtn; if (wrapper.querySelector('[data-fg-profile-dl="1"]')) return; if (document.querySelector('[data-fg-profile-dl="1"]')) return; @@ -721,7 +726,6 @@ function findActionRow(post) { if (!post) return null; - // 1. Look for Save/Bookmark button const saveBtn = post.querySelector('button[aria-label*="Save" i], [role="button"][aria-label*="Save" i], button[aria-label*="Bookmark" i], [role="button"][aria-label*="Bookmark" i], svg[aria-label*="Save" i], svg[aria-label*="Bookmark" i], svg polygon, svg path[d^="M20 22"], svg path[d^="M20 21"]'); if (saveBtn) { const btn = saveBtn.closest('button, [role="button"]') || saveBtn; @@ -730,7 +734,6 @@ } } - // 2. Look for Share button const shareBtn = post.querySelector('button[aria-label*="Share" i], [role="button"][aria-label*="Share" i], svg[aria-label*="Share" i], svg path[d^="M13.973 20.046"], svg path[d*="M22 3L2 10"]'); if (shareBtn) { const btn = shareBtn.closest('button, [role="button"]') || shareBtn; @@ -739,7 +742,6 @@ } } - // 3. Look for Comment button const commentBtn = post.querySelector('button[aria-label*="Comment" i], [role="button"][aria-label*="Comment" i], svg[aria-label*="Comment" i]'); if (commentBtn) { const btn = commentBtn.closest('button, [role="button"]') || commentBtn; @@ -748,7 +750,6 @@ } } - // 4. Look for Like button const likeBtn = post.querySelector('button[aria-label*="Like" i], [role="button"][aria-label*="Like" i], svg[aria-label*="Like" i], svg path[d*="M16.792 3.904"]'); if (likeBtn) { const btn = likeBtn.closest('button, [role="button"]') || likeBtn; @@ -757,7 +758,6 @@ } } - // 5. Look for section containing action buttons const section = post.querySelector('section'); if (section) { const buttons = section.querySelectorAll('button, [role="button"]'); @@ -779,7 +779,6 @@ const posts = []; const seen = new Set(); - // Target individual
elements document.querySelectorAll('article').forEach(el => { const r = el.getBoundingClientRect(); if (r.height > 120 && r.width > 120 && !seen.has(el)) { @@ -791,7 +790,6 @@ posts.forEach(post => { if (post.querySelector('[' + FG_DL_ATTR + '="1"]')) return; - // Extract shortcode from post const link = post.querySelector('a[href*="/p/"], a[href*="/reel/"], a[href*="/reels/"]'); const href = link ? (link.getAttribute('href') || '') : ''; const codeMatch = href.match(/\/(?:p|reel|reels)\/([A-Za-z0-9_-]+)/); @@ -799,7 +797,6 @@ const actionInfo = findActionRow(post); - // Create download button const dlBtn = document.createElement('div'); dlBtn.className = 'fg-feed-download-btn'; dlBtn.setAttribute(FG_DL_ATTR, '1'); @@ -838,7 +835,7 @@ handleMediaDownload(post, code); }; - // Touch events: strictly require stationary 30ms hold and abort immediately on any movement/scroll + // Require stationary 30ms hold to prevent accidental triggers during fling scrolls. dlBtn.addEventListener('touchstart', (e) => { if (isGlobalScrolling || (Date.now() - lastScrollTime < 250)) { isHolding = false; @@ -893,14 +890,12 @@ holdThresholdPassed = false; }, true); - // Raw clicks are always cancelled so scroll-release never clicks button dlBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); }, true); if (actionInfo && actionInfo.row && actionInfo.anchor) { - // Inline action bar button matching Instagram styling dlBtn.style.cssText = 'display:inline-flex;align-items:center;justify-content:center;cursor:pointer;padding:8px;margin-right:4px;color:currentColor;opacity:0.9;vertical-align:middle;box-sizing:border-box;pointer-events:auto;transition:transform 0.15s ease;'; dlBtn.innerHTML = ``; if (actionInfo.position === 'before') { @@ -909,7 +904,6 @@ actionInfo.row.insertBefore(dlBtn, actionInfo.anchor.nextSibling || actionInfo.anchor); } } else { - // Fallback: append to section or overlay on post media container (NOT avatar!) const section = post.querySelector('section'); if (section) { dlBtn.style.cssText = 'display:inline-flex;align-items:center;justify-content:center;cursor:pointer;padding:8px;margin-right:4px;color:currentColor;opacity:0.9;vertical-align:middle;box-sizing:border-box;pointer-events:auto;'; @@ -931,7 +925,7 @@ } function syncReelsDownloadButton() { - // User requested removing in-page Reels download button (native ReelsTopBar handles reel downloads) + // ReelsTopBar handles reel downloads natively; strip injected buttons. document.querySelectorAll('[data-fg-reel-dl="1"]').forEach(el => el.remove()); } @@ -972,7 +966,6 @@ if (m && m[1] !== 'reels' && m[1] !== 'reel') code = m[1]; } - // Also check fiber for code if (!code && (activeVideo || card)) { const fiberData = extractFromFiber(activeVideo) || extractFromFiber(card); if (fiberData && fiberData.code) { @@ -987,18 +980,23 @@ } } - // Attach to global window scope so FocusGramApp ReelsTopBar can invoke it directly window.__fgDownloadActiveReel = downloadActiveReel; window.addEventListener('focusgram:download-reel', downloadActiveReel); - // Periodic heartbeat to maintain buttons across SPA renders + window.addEventListener('message', (e) => { + if (e.data && e.data.type === 'FOCUSGRAM_UPDATE' && e.data.config) { + syncDownloadButtons(); + syncProfileDownloadButton(); + syncReelsDownloadButton(); + } + }); + setInterval(() => { syncDownloadButtons(); syncProfileDownloadButton(); syncReelsDownloadButton(); }, 300); - // Initial pass setTimeout(() => { syncDownloadButtons(); syncProfileDownloadButton(); diff --git a/Ported/app/src/main/assets/omni.ja b/app/src/main/assets/omni.ja similarity index 100% rename from Ported/app/src/main/assets/omni.ja rename to app/src/main/assets/omni.ja diff --git a/Ported/app/src/main/assets/reelhistory/bg.js b/app/src/main/assets/reelhistory/bg.js similarity index 100% rename from Ported/app/src/main/assets/reelhistory/bg.js rename to app/src/main/assets/reelhistory/bg.js diff --git a/Ported/app/src/main/assets/reelhistory/manifest.json b/app/src/main/assets/reelhistory/manifest.json similarity index 100% rename from Ported/app/src/main/assets/reelhistory/manifest.json rename to app/src/main/assets/reelhistory/manifest.json diff --git a/Ported/app/src/main/assets/reelhistory/reels.js b/app/src/main/assets/reelhistory/reels.js similarity index 81% rename from Ported/app/src/main/assets/reelhistory/reels.js rename to app/src/main/assets/reelhistory/reels.js index 4512142..e3a43da 100644 --- a/Ported/app/src/main/assets/reelhistory/reels.js +++ b/app/src/main/assets/reelhistory/reels.js @@ -1,10 +1,8 @@ -const DWELL_MS = 1000; // a reel counts after 1 second on screen +const DWELL_MS = 200; const REEL_RE = /^\/reels?\/([A-Za-z0-9_-]{8,})\/?/; const CODE_RE = /^[A-Za-z0-9_.-]{3,}$/; -// ---- In-Page API Interception via Injected Script ---- -// Content scripts run in an isolated world where exportFunction may not exist. -// Injecting a DOM script into the page context guarantees direct access to fetch and XMLHttpRequest. +// Injected into page context to intercept fetch and XMLHttpRequest responses across isolated worlds. (function injectApiHook() { const SCRIPT_ID = 'fg-reel-api-hook'; if (document.getElementById(SCRIPT_ID)) return; @@ -166,9 +164,8 @@ const CODE_RE = /^[A-Za-z0-9_.-]{3,}$/; (document.head || document.documentElement).appendChild(s); })(); -// ---- Source 2: Store harvested API metadata ---- -const apiMeta = new Map(); // code -> { author, caption } -const authorToCode = new Map(); // lowercase username -> code +const apiMeta = new Map(); +const authorToCode = new Map(); window.addEventListener('fg-api-reel', (e) => { if (e && e.detail) { @@ -179,7 +176,6 @@ window.addEventListener('fg-api-reel', (e) => { } }); -// ---- Source 1 (primary): the tags in the page head ---- function fromHead(code) { const get = (sel, attr) => { const e = document.querySelector(sel); @@ -202,17 +198,16 @@ function fromHead(code) { return author || caption ? { author, caption } : null; } -// Head first, DOM second, API third, field by field. +// Resolve metadata from active DOM card, cached API responses, and head tags. function lookup(code) { const a = apiMeta.get(code) || {}; - const h = fromHead(code) || {}; - let author = a.author || h.author || ''; - let caption = a.caption || h.caption || ''; + let author = a.author || ''; + let caption = a.caption || ''; if (!author || !caption) { const videos = Array.from(document.querySelectorAll('video')); for (const v of videos) { - let card = v.closest('article') || v.closest('section') || v.parentElement; + let card = v.closest('article, section, div[role="presentation"]') || v.parentElement; while (card && card !== document.body && card !== document.documentElement) { if (card.tagName === 'ARTICLE' || card.tagName === 'SECTION') break; const r = card.getBoundingClientRect(); @@ -221,9 +216,10 @@ function lookup(code) { } if (!card) continue; const link = card.querySelector('a[href*="/reel/"], a[href*="/p/"], a[href*="/reels/"]'); - if (link && link.href && link.href.includes(code)) { + const isMatch = (link && link.href && link.href.includes(code)) || (!v.paused && v.getBoundingClientRect().height > 200); + if (isMatch) { if (!author) { - const aEl = card.querySelector('header a[role="link"], a[role="link"][href^="/"]:not([href*="/audio/"]):not([href*="/reel"])'); + const aEl = card.querySelector('header a[role="link"], a[role="link"][href^="/"]:not([href*="/audio/"]):not([href*="/reel"]), header h2, header h3'); if (aEl) author = aEl.textContent.trim().replace(/^@/, ''); } if (!caption) { @@ -250,20 +246,26 @@ function lookup(code) { } } } - break; + if (author || caption) break; } } } + if (!author || !caption) { + const h = fromHead(code) || {}; + if (!author) author = h.author || ''; + if (!caption) caption = h.caption || ''; + } + if (!author && code.startsWith('reel_')) { author = code.replace(/^reel_/, ''); } return { author: author || '', caption: caption || '' }; } -// ---- Detect which reel is on screen and report it ---- let cur = null; let timer = null; +let isInitialReelMount = true; const unfilled = new Set(); function report(type, code) { @@ -275,60 +277,65 @@ function report(type, code) { } function getActiveReelCode() { - const m = REEL_RE.exec(location.pathname); - if (m) return m[1]; - const videos = Array.from(document.querySelectorAll('video')); const activeVideo = videos.find(v => !v.paused && v.getBoundingClientRect().height > 200) || videos.find(v => { const r = v.getBoundingClientRect(); return r.top >= -50 && r.top < window.innerHeight * 0.6; }) || videos[0]; - if (!activeVideo) return null; - let card = activeVideo.closest('article') || activeVideo.closest('section') || activeVideo.parentElement; - while (card && card !== document.body && card !== document.documentElement) { - if (card.tagName === 'ARTICLE' || card.tagName === 'SECTION') break; - const r = card.getBoundingClientRect(); - if (r.height > window.innerHeight * 0.5) break; - card = card.parentElement; - } - if (!card) return null; - - // 1. Direct link to reel - const link = card.querySelector('a[href*="/reel/"], a[href*="/p/"], a[href*="/reels/"]'); - if (link && link.href) { - const lm = /\/(?:reel|reels|p)\/([A-Za-z0-9_-]{8,})/.exec(link.href); - if (lm) return lm[1]; - } - - // 2. Identify author - const authorEl = card.querySelector('header a[role="link"], a[role="link"][href^="/"]:not([href*="/audio/"]):not([href*="/reel"]), a[href^="/"]:not([href*="/audio/"]):not([href*="/reel"])'); - if (authorEl) { - let username = authorEl.textContent.trim().replace(/^@/, '').toLowerCase(); - if (!username && authorEl.getAttribute('href')) { - const match = authorEl.getAttribute('href').match(/^\/([A-Za-z0-9._]+)\/?/); - if (match) username = match[1].toLowerCase(); + if (activeVideo) { + if (window.__fg_media_cache && (activeVideo.currentSrc || activeVideo.src)) { + const src = activeVideo.currentSrc || activeVideo.src; + const cleanSrc = src.split('?')[0]; + for (const [code, item] of window.__fg_media_cache) { + if (item.videoUrl && (cleanSrc.includes(item.videoUrl.split('?')[0]) || item.videoUrl.split('?')[0].includes(cleanSrc))) { + return code; + } + } } - if (username) { - return 'reel_' + username; + + let card = activeVideo.closest('article, section, div[role="presentation"]') || activeVideo.parentElement; + while (card && card !== document.body && card !== document.documentElement) { + if (card.tagName === 'ARTICLE' || card.tagName === 'SECTION') break; + const r = card.getBoundingClientRect(); + if (r.height > window.innerHeight * 0.5) break; + card = card.parentElement; + } + if (card) { + const link = card.querySelector('a[href*="/reel/"], a[href*="/p/"], a[href*="/reels/"]'); + if (link && link.href) { + const lm = /\/(?:reel|reels|p)\/([A-Za-z0-9_-]{5,})/.exec(link.href); + if (lm) return lm[1]; + } } } + const m = REEL_RE.exec(location.pathname); + if (m) return m[1]; + return null; } function tick() { + if (!location.pathname.startsWith('/reel')) { + isInitialReelMount = true; + cur = null; + clearTimeout(timer); + return; + } const code = getActiveReelCode(); if (code === cur) return; cur = code; clearTimeout(timer); if (!code) return; - timer = setTimeout(() => { - if (cur !== code || document.visibilityState !== 'visible') return; - const found = report('watched', code); - if (!found.author || !found.caption) unfilled.add(code); - }, DWELL_MS); + if (isInitialReelMount) { + isInitialReelMount = false; + report('meta', code); + return; + } + const found = report('watched', code); + if (!found.author || !found.caption) unfilled.add(code); } function fill() { diff --git a/Ported/app/src/main/assets/reellock/bg.js b/app/src/main/assets/reellock/bg.js similarity index 80% rename from Ported/app/src/main/assets/reellock/bg.js rename to app/src/main/assets/reellock/bg.js index c392395..071f595 100644 --- a/Ported/app/src/main/assets/reellock/bg.js +++ b/app/src/main/assets/reellock/bg.js @@ -1,10 +1,10 @@ -let lastBeat = 0; // last time the page said it is on /direct/t/ -let armedAt = 0; // last time a reel feed request was cancelled +let lastBeat = 0; +let armedAt = 0; browser.runtime.onMessage.addListener((m) => { if (!m || m.t !== 'fg-reel') return; lastBeat = (m.active && m.enabled !== false) ? Date.now() : 0; - return Promise.resolve({ armedAt }); // tell the page when a reel was last detected + return Promise.resolve({ armedAt }); }); // Match request names first, doc_ids as a backup (doc_ids change on Instagram updates). diff --git a/Ported/app/src/main/assets/reellock/manifest.json b/app/src/main/assets/reellock/manifest.json similarity index 100% rename from Ported/app/src/main/assets/reellock/manifest.json rename to app/src/main/assets/reellock/manifest.json diff --git a/Ported/app/src/main/assets/reellock/reellock.js b/app/src/main/assets/reellock/reellock.js similarity index 81% rename from Ported/app/src/main/assets/reellock/reellock.js rename to app/src/main/assets/reellock/reellock.js index 60b1591..0e172a7 100644 --- a/Ported/app/src/main/assets/reellock/reellock.js +++ b/app/src/main/assets/reellock/reellock.js @@ -1,10 +1,10 @@ const inDM = () => location.pathname.startsWith('/direct/t/'); const isToggleActive = () => document.documentElement.getAttribute('data-fg-block-dm-reels') !== 'false'; -const MIN = 0.45; // "big" = at least 45% of viewport width and height -let on = false; // currently on /direct/t/ -let reel = false; // a DM reel viewer is open right now -let armedAt = 0; // last reel detection time reported by bg.js -let consumed = 0; // detections at or before this time are already handled +const MIN = 0.45; +let on = false; +let reel = false; +let armedAt = 0; +let consumed = 0; const locked = new Set(); const css = document.createElement('style'); @@ -62,20 +62,19 @@ function scan() { const vids = [...document.querySelectorAll('video')].filter(big); - if (reel && !vids.length) { endReel(); return; } // viewer closed + if (reel && !vids.length) { endReel(); return; } if (!reel) { - // Start only if a reel feed request was cancelled in the last 5 seconds. + // Start only if a reel feed request was cancelled in the last 5 seconds (avoids trapping direct video messages). const fresh = armedAt > consumed && Date.now() - armedAt < 5000; - if (!(fresh && vids.length)) return; // e.g. a video message: leave it alone + if (!(fresh && vids.length)) return; reel = true; } vids.forEach(lockRoot); } -// Block swipe/wheel gestures while a DM reel is open, except over things -// that scroll on their own (the comments list, for example). +// Block swipe and wheel gestures while a DM reel is open, allowing independent sub-scrollers like comments. function insideScroller(el) { for (let p = el; p && p !== document.body && p !== document.documentElement; p = p.parentElement) { if (p.nodeType !== 1) continue; diff --git a/Ported/app/src/main/assets/scripts/autoplay_blocker.js b/app/src/main/assets/scripts/autoplay_blocker.js similarity index 73% rename from Ported/app/src/main/assets/scripts/autoplay_blocker.js rename to app/src/main/assets/scripts/autoplay_blocker.js index ae475ca..21a2ee8 100644 --- a/Ported/app/src/main/assets/scripts/autoplay_blocker.js +++ b/app/src/main/assets/scripts/autoplay_blocker.js @@ -1,8 +1,8 @@ /** - * FocusGram — Feed & Video Autoplay Blocker UserScript + * Feed video autoplay blocker. * - * Terminates automated HTML5 video autoplay and loops in feed and explore, - * while seamlessly preserving intentional user clicks and reels playback. + * Intercepts HTML5 play() calls to prevent automatic playback in feed and explore + * while permitting intentional user tap-to-play gestures and reels playback. */ 'use strict'; @@ -11,15 +11,14 @@ function isAuthPage() { try { var path = (window.location.pathname || '').toLowerCase(); - if (path.startsWith('/accounts') || path.startsWith('/login') || path.startsWith('/signup')) return true; - var c = document.cookie || ''; - if (!c.includes('sessionid=') && !c.includes('ds_user_id=')) return true; - if (document.getElementById('loginForm') || document.querySelector('form[action*="login"], input[name="password"]')) return true; + if (path.includes('/accounts/edit')) return false; + if (path.startsWith('/accounts') || path.startsWith('/login') || path.startsWith('/signup') || path.startsWith('/two_factor') || path.startsWith('/challenge')) return true; + if (document.getElementById('loginForm') || document.querySelector('form[action*="login"], form input[type="password"]')) return true; } catch (_) {} return false; } - if (isAuthPage()) return; // Leave login/signup completely untouched + if (isAuthPage()) return; if (window.__fgAutoplayPatched) return; window.__fgAutoplayPatched = true; @@ -47,14 +46,12 @@ const closestVid = target.closest('video'); if (closestVid) return closestVid; - // Check if inside post container or media wrapper const container = target.closest('article, [role="presentation"], div[style*="aspect-ratio"], div:has(video)'); if (container) { const v = container.querySelector('video'); if (v) return v; } - // Search upward through ancestor elements let p = target; for (let i = 0; i < 6 && p; i++) { const v = p.querySelector('video'); @@ -69,23 +66,27 @@ if (HTMLVideoElement.prototype.play) { HTMLVideoElement.prototype.play = function () { try { - // If on Reels or blockAutoplay is off, allow playback + if (window.__fgMediaPaused) { + this.removeAttribute('autoplay'); + this.autoplay = false; + this.pause(); + return Promise.resolve(); + } + if (!shouldBlock()) { return originalPlay.apply(this, arguments); } - // Strictly allow ONLY videos that the user explicitly tapped if (allowedVideos.has(this)) { return originalPlay.apply(this, arguments); } - // Strip autoplay attributes and freeze playback this.removeAttribute('autoplay'); this.autoplay = false; this.loop = false; this.pause(); - // Return resolved promise so React/Instagram engine does not crash + // Resolving promise avoids uncaught rejection in React video wrappers. return Promise.resolve(); } catch (e) { return originalPlay.apply(this, arguments); @@ -93,11 +94,10 @@ }; } - // Capture explicit user click/tap gestures directly on post videos ['click', 'pointerup'].forEach((eventType) => { document.addEventListener(eventType, function (e) { if (!e.isTrusted) return; - // Ignore if tapped on buttons/comments/links outside the video area + // Ignore interaction when tapping surrounding action buttons or comments. if (e.target.closest('button[aria-label="Like"], button[aria-label="Comment"], button[aria-label="Share"], a[role="link"]:not(:has(video))')) { return; } diff --git a/Ported/app/src/main/assets/scripts/content_hider.js b/app/src/main/assets/scripts/content_hider.js similarity index 87% rename from Ported/app/src/main/assets/scripts/content_hider.js rename to app/src/main/assets/scripts/content_hider.js index b44fe7c..16ae783 100644 --- a/Ported/app/src/main/assets/scripts/content_hider.js +++ b/app/src/main/assets/scripts/content_hider.js @@ -1,11 +1,5 @@ /** - * FocusGram — Content Hider, UI Guardrails & SPA Navigation UserScript - * - * Provides: - * 1. Hiding Instagram's native web bottom navigation bar so only FocusGram's native bar appears. - * 2. Instant SPA navigation helper without page reloads. - * 3. Profile navigation resolving the current user profile rather than edit profile. - * 4. Dynamic CSS injection reacting to FocusSettings (Stories, Reels, Ads, Suggested Posts, Vanity Metrics). + * Content hider, layout adjustments, and client-side SPA routing. */ 'use strict'; @@ -16,10 +10,9 @@ function isAuthPage() { try { var path = (window.location.pathname || '').toLowerCase(); - if (path.startsWith('/accounts') || path.startsWith('/login') || path.startsWith('/signup')) return true; - var c = document.cookie || ''; - if (!c.includes('sessionid=') && !c.includes('ds_user_id=')) return true; - if (document.getElementById('loginForm') || document.querySelector('form[action*="login"], input[name="password"]')) return true; + if (path.includes('/accounts/edit')) return false; + if (path.startsWith('/accounts') || path.startsWith('/login') || path.startsWith('/signup') || path.startsWith('/two_factor') || path.startsWith('/challenge')) return true; + if (document.getElementById('loginForm') || document.querySelector('form[action*="login"], form input[type="password"]')) return true; } catch (_) {} return false; } @@ -27,14 +20,13 @@ if (isAuthPage()) { var existing = document.getElementById(STYLE_ID); if (existing) existing.remove(); - return; // Leave login/signup completely untouched + return; // Leave login and signup untouched. } - // 1. Client-Side SPA Navigation Helper window.__focusgramNavigate = function (path) { if (!path) return; try { - // Priority 1: Match and click navigation link in the DOM + // First try matching and clicking navigation anchors already in the DOM. let target = null; if (path === '/') { target = document.querySelector('a[href="/"][role="link"]') || @@ -61,7 +53,6 @@ return true; } - // Priority 2: HTML5 History API pushState if (window.history && typeof window.history.pushState === 'function') { window.history.pushState(null, '', path); window.dispatchEvent(new PopStateEvent('popstate', { state: null })); @@ -71,14 +62,11 @@ console.warn('[FocusGram] SPA navigation error:', e); } - // Fallback: standard assignment window.location.href = path; }; - // 2. Client-Side Profile Navigation Helper window.__focusgramOpenProfile = function () { try { - // Find user profile avatar/link in navigation bar const profileLink = document.querySelector('a[href*="/profile/"]') || document.querySelector('a[role="link"]:has(img[alt*="profile" i])') || document.querySelector('a[role="link"]:has(img[alt*="\'s profile picture" i])') || @@ -93,7 +81,6 @@ } } - // Check viewer username from window configuration objects const username = window._sharedData?.config?.viewer?.username || window.__initialData?.data?.viewer?.username || window.__viewerUsername; @@ -109,11 +96,10 @@ window.__focusgramNavigate('/'); }; - // 3. Dynamic Styles Generation reacting to FocusSettings function updateStyles() { const config = window.__FOCUSGRAM_CONFIG__ || {}; let css = ` - /* ALWAYS hide Instagram web's own bottom navigation bar — strictly require Explore link so DMs are never hidden */ + /* Hide Instagram web navigation bar when it contains Explore, leaving DM headers visible. */ nav[role="navigation"]:has(a[href*="/explore/"]), div[role="navigation"]:has(a[href*="/explore/"]), footer[role="contentinfo"]:has(a[href*="/explore/"]) { @@ -169,7 +155,7 @@ if (config.blurFeed) { css += ` - /* Feed Carousel Slides & Single Post Media - ONLY blur media elements, NEVER parent wrappers! */ + /* Blur feed media elements without blurring parent containers. */ html[data-fg-feed] article img:not(header img):not([alt*="profile" i]):not([data-fg-reveal]):not(.fg-unblurred), html[data-fg-feed] article video:not([data-fg-reveal]):not(.fg-unblurred), html[data-fg-feed] article canvas:not([data-fg-reveal]):not(.fg-unblurred) { @@ -177,7 +163,7 @@ transition: filter 0.25s ease-in-out !important; } - /* Preserve feed header avatars unblurred */ + /* Keep header avatars unblurred. */ html[data-fg-feed] article header img, html[data-fg-feed] article img[alt*="profile" i] { filter: none !important; @@ -187,9 +173,9 @@ if (config.blurExplore) { css += ` - main a[href*="/p/"]:not([data-fg-reveal]):not(.fg-unblurred) img, - main a[href*="/reel/"]:not([data-fg-reveal]):not(.fg-unblurred) img, - div[style*="grid"]:not([data-fg-reveal]):not(.fg-unblurred) img { + html[data-fg-explore] main a[href*="/p/"]:not([data-fg-reveal]):not(.fg-unblurred) img, + html[data-fg-explore] main a[href*="/reel/"]:not([data-fg-reveal]):not(.fg-unblurred) img, + main a[href*="/explore/"]:not([data-fg-reveal]):not(.fg-unblurred) img { filter: blur(24px) !important; transition: filter 0.2s ease-in-out !important; } @@ -220,6 +206,20 @@ .fg-unblurred div { filter: none !important; } + + /* Comment GIFs, sticker picker, and comment media must NEVER be blurred */ + img[src*="giphy"], + img[src*="tenor"], + img[alt*="GIF" i], + img[alt*="gif" i], + div[role="dialog"] img[src*="giphy"], + div[aria-modal="true"] img[src*="giphy"], + article ul img[src*="giphy"], + ul[class*="comment" i] img, + form img[src*="giphy"], + form img[src*="tenor"] { + filter: none !important; + } `; if (config.hideLikeCounts) { @@ -228,19 +228,9 @@ section a[href*="/liked_by/"], span:has(> a[href*="/liked_by/"]), div:has(> a[href*="/liked_by/"]), - section:has(svg[aria-label="Like"]) + div:not(:has(form)):not(:has(input)), - section:has(svg[aria-label="Unlike"]) + div:not(:has(form)):not(:has(input)), - section:has(svg[aria-label="Activity Feed"]) + div:not(:has(form)):not(:has(input)), - div[role="button"]:has(svg[aria-label="Like"]) + div span, - div[role="button"]:has(svg[aria-label="Unlike"]) + div span, - span[role="button"]:has(svg[aria-label="Like"]) + span, - span[role="button"]:has(svg[aria-label="Unlike"]) + span, - div:has(> svg[aria-label="Like"]) + span, - div:has(> svg[aria-label="Unlike"]) + span, - button:has(svg[aria-label="Like"]) + span, - button:has(svg[aria-label="Unlike"]) + span, - [role="button"]:has(svg[aria-label="Like"]) span, - [role="button"]:has(svg[aria-label="Unlike"]) span { + section:has(svg[aria-label="Like"]) + div:not(:has(form)):not(:has(input)):not(:has(svg)), + section:has(svg[aria-label="Unlike"]) + div:not(:has(form)):not(:has(input)):not(:has(svg)), + section:has(svg[aria-label="Activity Feed"]) + div:not(:has(form)):not(:has(input)):not(:has(svg)) { display: none !important; visibility: hidden !important; } @@ -266,7 +256,6 @@ `; } - /* Instagram Reels Layout & Overlay Lift (Always active, regardless of ad blocker) */ css += ` :root { --fg-reel-bottom-offset: 16px; @@ -340,15 +329,13 @@ styleEl.textContent = css; } - // Dynamic scanning for elements not coverable by pure CSS function scanDynamicDOM() { const config = window.__FOCUSGRAM_CONFIG__ || {}; try { - // Filter Sponsored Ads if (config.noAds !== false) { const SPONSORED_KW = ['sponsored', 'gesponsert', 'sponsorisÃĐ', 'sponsorise', 'publicidad', 'patrocinado', 'sponsorizzato', 'sponsrad', 'paid partnership', 'werbung', 'anuncio', 'werbeanzeige']; const AD_CTA = ['shop now', 'install now', 'sign up', 'apply now', 'book now', 'order now', 'get offer']; - // Strictly target individual article elements to avoid hiding feed wrappers + // Collapse individual articles to 1px to avoid blanking feed container wrappers. document.querySelectorAll('article').forEach((article) => { if (article.dataset.fgAdFiltered) return; let isAd = false; @@ -434,9 +421,7 @@ }); } - // Filter Suggested Posts if (config.hideSuggestedPosts) { - // Strictly target individual article elements or specific suggested-users widgets document.querySelectorAll('article').forEach((article) => { if (article.dataset.fgSuggestedHidden) return; const headers = article.querySelectorAll('header span, header h2, header h3, h2, h3, h4'); @@ -462,47 +447,60 @@ }); } - // Hide Like Counts if (config.hideLikeCounts) { + // 1. Liked by links and their parent containers document.querySelectorAll('a[href*="/liked_by/"]').forEach((el) => { el.style.setProperty('display', 'none', 'important'); const parent = el.closest('div'); - if (parent && parent.textContent && /liked/i.test(parent.textContent)) { + if (parent && !parent.querySelector('svg') && parent.textContent && /liked/i.test(parent.textContent)) { parent.style.setProperty('display', 'none', 'important'); } }); - document.querySelectorAll('article section ~ div span, article section ~ div div, div[role="presentation"] span, article span, article div').forEach((el) => { + + // 2. Feed post like counts text (e.g. "Liked by ...", "97 likes", "12 others") + document.querySelectorAll('article section ~ div span, article section ~ div div, div[role="presentation"] span').forEach((el) => { + if (el.querySelector('svg') || el.closest('svg') || el.querySelector('img') || el.closest('form')) return; if (el.children.length > 2) return; const text = (el.textContent || '').trim(); if (/^(liked\s+by|[\d,.]+[KkMm]?\s+likes?|[\d,.]+\s+others|be\s+the\s+first\s+to\s+like)/i.test(text)) { el.style.setProperty('display', 'none', 'important'); } }); + + // 3. Standalone count buttons in feed post: 97 + document.querySelectorAll('article span[role="button"], section span[role="button"]').forEach((el) => { + if (el.querySelector('svg') || el.closest('svg')) return; + const text = (el.textContent || '').trim(); + if (/^[\d,.]+[KkMm]?$/.test(text)) { + el.style.setProperty('display', 'none', 'important'); + } + }); + + // 4. Reels like count in right interaction rail: + // Sibling or child span with numbers/K/M (e.g. 192K) document.querySelectorAll('svg[aria-label="Like"], svg[aria-label="Unlike"]').forEach((svg) => { const btn = svg.closest('[role="button"]') || svg.parentElement; - if (btn) { - btn.querySelectorAll('span, div').forEach(child => { - if (!child.contains(svg) && /^\d+[\d,.]*[KkMm]?$/.test((child.textContent || '').trim())) { + if (!btn) return; + const container = btn.parentElement; + if (container) { + container.querySelectorAll('span, div').forEach((child) => { + if (child.contains(svg) || child.querySelector('svg') || child.closest('svg')) return; + const text = (child.textContent || '').trim(); + if (/^[\d,.]+[KkMm]?$/.test(text) || /^[\d,.]+[KkMm]?\s+likes?$/i.test(text)) { child.style.setProperty('display', 'none', 'important'); } }); - let sibling = btn.nextElementSibling; - if (sibling && /^\d+[\d,.]*[KkMm]?$/.test((sibling.textContent || '').trim())) { - sibling.style.setProperty('display', 'none', 'important'); - } - const parent = btn.parentElement; - if (parent && parent.children.length <= 4) { - parent.querySelectorAll('span').forEach(sp => { - if (/^\d+[\d,.]*[KkMm]?$/.test((sp.textContent || '').trim())) { - sp.style.setProperty('display', 'none', 'important'); - } - }); + let sib = btn.nextElementSibling; + if (sib && !sib.contains(svg) && !sib.querySelector('svg')) { + const text = (sib.textContent || '').trim(); + if (/^[\d,.]+[KkMm]?$/.test(text) || /^[\d,.]+[KkMm]?\s+likes?$/i.test(text)) { + sib.style.setProperty('display', 'none', 'important'); + } } } }); } - // Hide Follower Counts if (config.hideFollowerCounts) { document.querySelectorAll('a[href*="/followers/"], a[href*="/following/"]').forEach((el) => { el.querySelectorAll('span').forEach(s => { @@ -511,10 +509,9 @@ }); } - // Always tag and lift Reels bottom metadata & action buttons above the floating nav bar tagReelsOverlay(); - // Minimal Mode in-page SPA redirect guard + // Minimal mode redirects root/explore browsing into direct messages. if (config.minimalMode) { const curPath = (window.location.pathname || '').toLowerCase(); if (curPath === '/' || curPath === '' || curPath.startsWith('/explore') || curPath.startsWith('/reels') || curPath.startsWith('/reel') || curPath.startsWith('/p/')) { @@ -633,6 +630,7 @@ const path = window.location.pathname || ''; const isFeed = path === '/' || path === ''; document.documentElement.toggleAttribute('data-fg-feed', isFeed); + document.documentElement.toggleAttribute('data-fg-explore', path.startsWith('/explore')); } updateFeedAttr(); window.addEventListener('popstate', updateFeedAttr); @@ -645,17 +643,24 @@ function getBlurredTarget(el) { if (!el) return null; + + // Comment GIFs, sticker picker, and comment controls must NEVER be treated as unblur targets + if (el.closest('img[src*="giphy"], img[src*="tenor"], img[alt*="GIF" i], img[alt*="gif" i], form, ul[class*="comment" i]')) { + return null; + } + if (el.tagName === 'IMG' && (el.src?.includes('giphy') || el.src?.includes('tenor') || el.alt?.toLowerCase().includes('gif'))) { + return null; + } const config = window.__FOCUSGRAM_CONFIG__ || {}; if (config.blurFeed) { - // Never treat carousel next/prev arrows or navigation buttons as unblur triggers + // Ignore slide advance controls, headers, and reactions when resolving unblur targets. if (el.closest('button[aria-label="Next"], button[aria-label="Go back"], button[aria-label="Next slide"], button[aria-label="Previous slide"], button[aria-label="Chevron"], header, section:has(svg[aria-label="Like"])')) { return null; } const article = el.closest('article'); if (article) { - // Find specific clicked media element or its immediate slide const media = el.matches('img, video, canvas') ? el : (el.closest('img, video, canvas') || el.querySelector('img, video, canvas')); const slide = el.closest('li, [role="group"], [style*="scroll-snap-align"], [data-testid*="slide"]') || (media ? media.closest('div[style*="width: 100%"], div[style*="min-width"]') : null); @@ -667,7 +672,7 @@ return media; } - // Fallback for clicking media container of single post + // Single post media container fallback. const mediaContainer = article.querySelector('div[style*="aspect-ratio"], div:has(> img), div:has(> video)'); if (mediaContainer && mediaContainer.contains(el)) { const postImg = mediaContainer.querySelector('img:not(header img), video'); @@ -758,7 +763,6 @@ } }, true); - // Expose hook to reload styles on config update window.__focusgramUpdateStyles = function () { updateStyles(); scanDynamicDOM(); @@ -774,7 +778,7 @@ }); } - // Debounced observer to hide dynamic elements smoothly + // Debounce DOM scanning to avoid thrashing on feed mutations. let timer = null; const observer = new MutationObserver(() => { if (timer) clearTimeout(timer); diff --git a/Ported/app/src/main/assets/scripts/fetch_interceptor.js b/app/src/main/assets/scripts/fetch_interceptor.js similarity index 91% rename from Ported/app/src/main/assets/scripts/fetch_interceptor.js rename to app/src/main/assets/scripts/fetch_interceptor.js index 91d1ec1..41c64a5 100644 --- a/Ported/app/src/main/assets/scripts/fetch_interceptor.js +++ b/app/src/main/assets/scripts/fetch_interceptor.js @@ -1,13 +1,8 @@ /** - * FocusGram Unified Network Ad & Content Filter via Main-World Fetch Interception - * Injected at document_start in the page execution context. + * Network ad and sponsored content filter via main-world fetch and XHR interception. * - * Intercepts Instagram GraphQL and REST API responses (Feed, Reels, Stories) - * and purges ad/sponsored objects before React receives them. - * This guarantees: - * 1. Ads are 100% blocked before rendering. - * 2. React never builds DOM elements for ads. - * 3. Scroll-snap and virtual scroller remain continuous and never collapse or go blank. + * Purges sponsored and recommended nodes from GraphQL and REST payloads + * before React hydration to preserve virtual scroll dimensions. */ 'use strict'; @@ -16,15 +11,13 @@ try { var path = (window.location.pathname || '').toLowerCase(); if (path.includes('/accounts/edit')) return false; - if (path.startsWith('/accounts') || path.startsWith('/login') || path.startsWith('/signup')) return true; - var c = document.cookie || ''; - if (!c.includes('sessionid=') && !c.includes('ds_user_id=')) return true; - if (document.getElementById('loginForm') || document.querySelector('form[action*="login"], input[name="password"]')) return true; + if (path.startsWith('/accounts') || path.startsWith('/login') || path.startsWith('/signup') || path.startsWith('/two_factor') || path.startsWith('/challenge')) return true; + if (document.getElementById('loginForm') || document.querySelector('form[action*="login"], form input[type="password"]')) return true; } catch (_) {} return false; } - if (isAuthPage()) return; // Leave login/signup completely untouched + if (isAuthPage()) return; if (window.__fgFetchInterceptorInstalled) return; window.__fgFetchInterceptorInstalled = true; @@ -35,7 +28,9 @@ blockSuggested: false, blockVideos: false, blockAutoplay: false, - blockGraphQLQueryWhenFeedPosts: false + blockGraphQLQueryWhenFeedPosts: false, + storySearchEnabled: true, + customBioFontsEnabled: true }; const AD_KEYWORDS = [ @@ -108,17 +103,14 @@ const config = window.__fgFilterConfig; if (!config || !node || typeof node !== 'object') return false; - // Filter ads if (config.blockAds !== false && isAdNode(node)) { return true; } - // Filter sponsored posts if (config.blockSponsored !== false && isSponsoredNode(node) && !isAdNode(node)) { return true; } - // Filter suggested posts if enabled if (config.blockSuggested && isSuggestedNode(node)) { return true; } @@ -497,7 +489,6 @@ window.__fgProcessDirectMessagesPayload = processDirectMessagesPayload; - // Override fetch in the main world const _fetch = window.fetch.bind(window); window.fetch = async function (input, init) { @@ -507,13 +498,13 @@ ? input.href : input?.url ?? ''; - // Story viewer request detection: immediately notify UI when request is sent and received + // Notify UI when story viewers endpoint is requested or resolved if (url.includes('/list_reel_media_viewer/')) { const mediaIdMatch = url.match(/\/api\/v1\/media\/(\d+)\/list_reel_media_viewer\//); const mediaId = mediaIdMatch ? mediaIdMatch[1] : ''; const loadingDetail = { phase: 'loading', mediaId: mediaId, url: url }; - // Dispatch to both window & document for maximum compatibility across script compartments + // Dispatch across window and document compartments window.dispatchEvent(new CustomEvent('focusgram:story-viewers-detected', { detail: loadingDetail })); document.dispatchEvent(new CustomEvent('focusgram:story-viewers-detected', { detail: loadingDetail })); window.postMessage({ type: 'FOCUSGRAM_STORY_VIEWERS', detail: loadingDetail }, '*'); @@ -533,10 +524,8 @@ return response; } - // Always execute fetch const response = await _fetch(input, init); - // Only inspect GraphQL, feed, clips, and threads endpoints if (!url.includes('/graphql') && !url.includes('/api/v1/feed') && !url.includes('/api/v1/clips/') && !url.includes('/api/v1/direct_v2/threads/')) { return response; } @@ -579,8 +568,7 @@ const originalContentType = response.headers.get('content-type') || 'application/json; charset=utf-8'; const newHeaders = new Headers(response.headers); - // CRITICAL: Must delete content-length and content-encoding so the uncompressed - // JSON body is not truncated or corrupted by the browser HTTP layer + // Delete content-length and content-encoding so the modified JSON body is not truncated. newHeaders.delete('content-length'); newHeaders.delete('content-encoding'); newHeaders.set('content-type', originalContentType); @@ -601,7 +589,7 @@ configurable: true }); - // Hook XMLHttpRequest for complete coverage of background API calls + // Hook XMLHttpRequest for background requests const origXHR = window.XMLHttpRequest; if (origXHR && origXHR.prototype) { const origOpen = origXHR.prototype.open; @@ -641,12 +629,7 @@ }; } - /* ========================================================================== - STORY VIEWER CLIENT-SIDE SEARCH ENGINE - Autonomous DOM detector + Network Interception. - Injects a native-style Instagram search bar inside the "Viewers" modal - whether opened from cached story data or network requests. - ========================================================================== */ + // Story viewer search bar injection for the viewers modal. let storyViewersData = []; let storySearchCardEl = null; @@ -656,7 +639,6 @@ const headings = Array.from(d.querySelectorAll('h2, [role="heading"], div')); const h2 = headings.find(el => el.textContent.trim().toLowerCase() === 'viewers' && el.children.length === 0); if (h2) { - // h2.parentElement is the flex row containing Close button, Viewers title, and spacer const headerRow = h2.parentElement; const scrollContainer = d.querySelector('div[style*="overflow"]') || d.querySelector('div[style*="height"]') || @@ -778,8 +760,14 @@ } } - row.style.display = matched ? '' : 'none'; - if (matched) matchedCount++; + if (matched) { + row.style.display = ''; + delete row.dataset.fgViewerHidden; + matchedCount++; + } else { + row.style.display = 'none'; + row.dataset.fgViewerHidden = 'true'; + } }); // Handle "No results found" placeholder @@ -798,7 +786,37 @@ } } + function cleanupStorySearch() { + const existing = document.getElementById('fg-story-search-card'); + if (existing) { + existing.remove(); + storySearchCardEl = null; + } + const noResults = document.getElementById('fg-no-viewer-results'); + if (noResults) noResults.remove(); + + document.querySelectorAll('[data-fg-viewer-hidden]').forEach(el => { + el.style.display = ''; + delete el.dataset.fgViewerHidden; + }); + } + + function isStorySearchEnabled() { + if (window.__fgFilterConfig && typeof window.__fgFilterConfig.storySearchEnabled === 'boolean') { + return window.__fgFilterConfig.storySearchEnabled; + } + if (window.__FOCUSGRAM_CONFIG__ && typeof window.__FOCUSGRAM_CONFIG__.storySearchEnabled === 'boolean') { + return window.__FOCUSGRAM_CONFIG__.storySearchEnabled; + } + return false; + } + function checkAndMountStorySearch() { + if (!isStorySearchEnabled()) { + cleanupStorySearch(); + return; + } + const info = findViewersDialog(); if (!info || !info.header) { const existing = document.getElementById('fg-story-search-card'); @@ -820,6 +838,10 @@ function handleStoryViewersDetected(detail) { if (!detail) return; + if (!isStorySearchEnabled()) { + cleanupStorySearch(); + return; + } checkAndMountStorySearch(); if (detail.phase === 'ready' && detail.data) { @@ -854,9 +876,33 @@ }); viewersObserver.observe(document.documentElement, { childList: true, subtree: true }); - window.__fgSetFilterConfig = function (cfg) { + function handleFilterConfigUpdate(cfg) { if (typeof cfg === 'object' && cfg) { + if (!window.__fgFilterConfig) window.__fgFilterConfig = {}; Object.assign(window.__fgFilterConfig, cfg); + if (window.__fgFilterConfig.storySearchEnabled === false) { + cleanupStorySearch(); + } else { + checkAndMountStorySearch(); + } } + } + + const prevFetchSetFilter = window.__fgSetFilterConfig; + window.__fgSetFilterConfig = function (cfg) { + if (typeof prevFetchSetFilter === 'function') prevFetchSetFilter(cfg); + handleFilterConfigUpdate(cfg); }; + + window.addEventListener('message', (e) => { + if (e.data && e.data.type === 'FOCUSGRAM_FILTER_UPDATE' && e.data.config) { + handleFilterConfigUpdate(e.data.config); + } + }); + window.addEventListener('focusgram:filter-config-update', (e) => { + handleFilterConfigUpdate(e.detail); + }); + document.addEventListener('focusgram:filter-config-update', (e) => { + handleFilterConfigUpdate(e.detail); + }); })(); diff --git a/Ported/app/src/main/assets/scripts/ghost.js b/app/src/main/assets/scripts/ghost.js similarity index 100% rename from Ported/app/src/main/assets/scripts/ghost.js rename to app/src/main/assets/scripts/ghost.js diff --git a/Ported/app/src/main/assets/scripts/grayscale.js b/app/src/main/assets/scripts/grayscale.js similarity index 81% rename from Ported/app/src/main/assets/scripts/grayscale.js rename to app/src/main/assets/scripts/grayscale.js index b1eee87..259e5bb 100644 --- a/Ported/app/src/main/assets/scripts/grayscale.js +++ b/app/src/main/assets/scripts/grayscale.js @@ -1,8 +1,7 @@ /** - * FocusGram — Hardware-Accelerated CSS Grayscale Filter UserScript + * Dynamic CSS grayscale filter. * - * Applies full grayscale filter to strip colorful dopamine triggers from Instagram, - * while allowing instant toggle and schedule support without reload. + * Applies a full grayscale filter to media and document root when enabled in settings. */ 'use strict'; @@ -44,6 +43,14 @@ window.__fgSetGrayscale = applyGrayscale; + window.addEventListener('message', (e) => { + if (e.data && e.data.type === 'FOCUSGRAM_UPDATE' && e.data.config) { + if (e.data.config.grayscale !== undefined) { + applyGrayscale(!!e.data.config.grayscale); + } + } + }); + const initialGrayscale = (window.__FOCUSGRAM_CONFIG__ && window.__FOCUSGRAM_CONFIG__.grayscale) || false; applyGrayscale(initialGrayscale); diff --git a/app/src/main/assets/scripts/media_downloader.js b/app/src/main/assets/scripts/media_downloader.js new file mode 100644 index 0000000..ac34536 --- /dev/null +++ b/app/src/main/assets/scripts/media_downloader.js @@ -0,0 +1,1006 @@ +/** + * In-page media downloader injected into web contexts. + * Attaches download buttons to feed posts, reels, and profiles, resolving media URLs + * via GraphQL, React Fiber, DOM inspection, and performance resource timings before + * delegating to the native host over custom URI schemes or extension messaging. + */ + +'use strict'; + +(function () { + function isAuthPage() { + try { + var path = (window.location.pathname || '').toLowerCase(); + if (path.includes('/accounts/edit')) return false; + if (path.startsWith('/accounts') || path.startsWith('/login') || path.startsWith('/signup') || path.startsWith('/two_factor') || path.startsWith('/challenge')) return true; + if (document.getElementById('loginForm') || document.querySelector('form[action*="login"], form input[type="password"]')) return true; + } catch (_) {} + return false; + } + + if (isAuthPage()) return; + + if (window.__fgMediaDownloaderInstalled) return; + window.__fgMediaDownloaderInstalled = true; + + const FG_DL_ATTR = 'data-fg-download-btn'; + const IG_POST_ROOT_QUERY_DOC_ID = '27852811784380813'; + + let downloadEnabled = false; + + function isDownloadEnabled() { + if (typeof downloadEnabled === 'boolean' && downloadEnabled) return true; + if (window.__FOCUSGRAM_CONFIG__ && window.__FOCUSGRAM_CONFIG__.videoDownloadEnabled !== undefined) { + return !!window.__FOCUSGRAM_CONFIG__.videoDownloadEnabled; + } + return false; + } + + if (typeof browser !== 'undefined' && browser.runtime) { + if (browser.runtime.onMessage) { + browser.runtime.onMessage.addListener((msg) => { + if (msg && msg.action === 'updateSettings' && msg.settings) { + if (msg.settings.videoDownloadEnabled !== undefined) { + downloadEnabled = !!msg.settings.videoDownloadEnabled; + syncDownloadButtons(); + syncProfileDownloadButton(); + syncReelsDownloadButton(); + } + } + }); + } + if (browser.runtime.sendMessage) { + try { + const res = browser.runtime.sendMessage({ action: 'getSettings' }); + if (res && typeof res.then === 'function') { + res.then((r) => { + if (r && r.settings && r.settings.videoDownloadEnabled !== undefined) { + downloadEnabled = !!r.settings.videoDownloadEnabled; + syncDownloadButtons(); + syncProfileDownloadButton(); + syncReelsDownloadButton(); + } + }).catch(() => {}); + } + } catch (_) {} + } + } + + let isGlobalScrolling = false; + let globalScrollTimeout = null; + let lastScrollTime = 0; + window.addEventListener('scroll', () => { + isGlobalScrolling = true; + lastScrollTime = Date.now(); + clearTimeout(globalScrollTimeout); + globalScrollTimeout = setTimeout(() => { + isGlobalScrolling = false; + }, 250); + }, { passive: true, capture: true }); + + function triggerNativeDownload(url, filename, error) { + if (url) { + try { + window.location.href = 'focusgram-download://download?url=' + encodeURIComponent(url) + '&filename=' + encodeURIComponent(filename || ''); + } catch (_) {} + } + + try { + if (typeof browser !== 'undefined' && browser.runtime && browser.runtime.sendMessage) { + browser.runtime.sendMessage({ + action: 'downloadMedia', + url: url || '', + filename: filename || '', + error: error || '' + }); + return; + } + } catch (_) {} + + try { + window.dispatchEvent(new CustomEvent('focusgram:download-request', { + detail: { url: url || '', filename: filename || '', error: error || '' } + })); + window.postMessage({ + type: 'FOCUSGRAM_DOWNLOAD_REQUEST', + url: url || '', + filename: filename || '', + error: error || '' + }, '*'); + } catch (e) { + console.error('[FocusGram Downloader] triggerNativeDownload error:', e); + } + } + + function getCookie(name) { + try { + const match = document.cookie.match(new RegExp('(?:^|; )' + name.replace(/([.$?*|{}()\[\]\\\/+^])/g, '\\$1') + '=([^;]*)')); + return match ? decodeURIComponent(match[1]) : ''; + } catch (_) {} + return ''; + } + + function getFbDtsg() { + try { + for (const script of document.scripts) { + const text = script.textContent || ''; + const match = text.match(/"DTSGInitialData",\[\],\{"token":"([^"]+)"/) || text.match(/"dtsg":\s*\{\s*"token":"([^"]+)"/); + if (match) return match[1]; + } + } catch (_) {} + return ''; + } + + function parseMediaItem(item, slideIndex = 0) { + if (!item) return null; + + if (item.carousel_media && Array.isArray(item.carousel_media) && item.carousel_media.length > 0) { + const target = item.carousel_media[slideIndex] || item.carousel_media[0]; + const parsed = parseMediaItem(target, 0); + if (parsed) { + parsed.id = item.pk || item.id || parsed.id; + parsed.code = item.code || parsed.code; + parsed.author = item.user?.username || parsed.author; + return parsed; + } + } + if (item.edge_sidecar_to_children?.edges && Array.isArray(item.edge_sidecar_to_children.edges) && item.edge_sidecar_to_children.edges.length > 0) { + const edge = item.edge_sidecar_to_children.edges[slideIndex] || item.edge_sidecar_to_children.edges[0]; + const target = edge?.node; + if (target) { + const parsed = parseMediaItem(target, 0); + if (parsed) { + parsed.id = item.id || parsed.id; + parsed.code = item.shortcode || parsed.code; + parsed.author = item.owner?.username || parsed.author; + return parsed; + } + } + } + + const isVideo = !!(item.is_video || item.media_type === 2 || (item.video_versions && item.video_versions.length > 0) || item.video_url); + let videoUrl = item.video_url || ''; + let imageUrl = item.display_url || ''; + + if (item.video_versions && Array.isArray(item.video_versions) && item.video_versions.length > 0) { + videoUrl = item.video_versions[0]?.url || videoUrl; + } + if (item.image_versions2?.candidates && Array.isArray(item.image_versions2.candidates) && item.image_versions2.candidates.length > 0) { + imageUrl = item.image_versions2.candidates[0]?.url || imageUrl; + } else if (item.display_resources && Array.isArray(item.display_resources) && item.display_resources.length > 0) { + imageUrl = item.display_resources[item.display_resources.length - 1]?.src || imageUrl; + } + + return { + isVideo: isVideo && !!videoUrl, + url: (isVideo && videoUrl) ? videoUrl : (imageUrl || videoUrl), + videoUrl: videoUrl, + imageUrl: imageUrl, + code: item.code || item.shortcode || '', + id: item.pk || item.id || '', + author: item.user?.username || item.owner?.username || '', + caption: item.caption?.text || item.edge_media_to_caption?.edges?.[0]?.node?.text || '' + }; + } + + async function fetchMediaByShortcode(shortcode, slideIndex = 0) { + if (!shortcode) return null; + + try { + const apiURL = new URL('/graphql/query', window.location.origin); + const headers = { + 'content-type': 'application/x-www-form-urlencoded', + 'x-fb-friendly-name': 'PolarisPostRootQuery', + 'x-ig-app-id': '936619743392459', + 'x-requested-with': 'XMLHttpRequest', + }; + const csrfToken = getCookie('csrftoken'); + if (csrfToken) headers['x-csrftoken'] = csrfToken; + + const fbDtsg = getFbDtsg(); + const bodyParams = { + fb_api_caller_class: 'RelayModern', + fb_api_req_friendly_name: 'PolarisPostRootQuery', + doc_id: IG_POST_ROOT_QUERY_DOC_ID, + variables: JSON.stringify({ + shortcode: shortcode, + __relay_internal__pv__PolarisShortDramaEnabledrelayprovider: false, + __relay_internal__pv__PolarisMultiCaptionCarouselEnabledrelayprovider: false, + }), + server_timestamps: 'true', + }; + if (fbDtsg) bodyParams.fb_dtsg = fbDtsg; + + const res = await fetch(apiURL.href, { + method: 'POST', + headers: headers, + body: new URLSearchParams(bodyParams).toString(), + credentials: 'include' + }); + + if (res.ok) { + const json = await res.json(); + const item = json?.data?.['xdt_api__v1__media__shortcode__web_info']?.items?.[0]; + if (item) { + return parseMediaItem(item, slideIndex); + } + } + } catch (e) { + console.warn('[FocusGram Downloader] GraphQL shortcode query error:', e); + } + + try { + const res = await fetch(`/p/${shortcode}/?__a=1&__d=dis`, { + headers: { + 'x-ig-app-id': '936619743392459', + 'x-requested-with': 'XMLHttpRequest' + }, + credentials: 'include' + }); + if (res.ok) { + const json = await res.json(); + const item = json?.items?.[0] || json?.graphql?.shortcode_media; + if (item) return parseMediaItem(item, slideIndex); + } + } catch (_) {} + + return null; + } + + function isLikelyImageUrl(url) { + if (!url || typeof url !== 'string') return false; + const lower = url.toLowerCase(); + return lower.includes('.jpg') || lower.includes('.jpeg') || lower.includes('.png') || + lower.includes('.webp') || lower.includes('dst-jpg') || lower.includes('dst-png') || + lower.includes('dst-webp') || lower.startsWith('data:image'); + } + + function isLikelyVideoUrl(url) { + if (!url || typeof url !== 'string') return false; + if (isLikelyImageUrl(url)) return false; + const lower = url.toLowerCase(); + return lower.includes('.mp4') || lower.includes('.webm') || lower.includes('/v/t50.') || + lower.includes('/v/t16.') || lower.includes('video_dashinit') || lower.startsWith('data:video'); + } + + function getActiveStreamFromPerformance() { + try { + const perf = (window.wrappedJSObject && window.wrappedJSObject.performance) || window.performance; + if (!perf || typeof perf.getEntriesByType !== 'function') return null; + const entries = perf.getEntriesByType('resource'); + for (let i = entries.length - 1; i >= 0 && i >= entries.length - 100; i--) { + const entry = entries[i]; + const name = entry.name || ''; + if (!name.startsWith('http://') && !name.startsWith('https://')) continue; + if (isLikelyImageUrl(name)) continue; + if (!name.includes('cdninstagram.com') && !name.includes('fbcdn.net')) continue; + + if (name.includes('.mp4') || name.includes('/v/t50.') || name.includes('/v/t16.') || + name.includes('video_dashinit') || (name.includes('bytestart') && !isLikelyImageUrl(name))) { + try { + const u = new URL(name); + u.searchParams.delete('bytestart'); + u.searchParams.delete('byteend'); + return { url: u.toString(), isVideo: true }; + } catch (_) { + const cleanUrl = name.replace(/[?&]bytestart=\d+/, '').replace(/[?&]byteend=\d+/, ''); + return { url: cleanUrl, isVideo: true }; + } + } + } + } catch (_) {} + return null; + } + + function extractFromFiber(node) { + if (!node) return null; + try { + const key = Object.keys(node).find(k => k.startsWith('__reactFiber') || k.startsWith('__reactProps')); + if (!key) return null; + let fiber = node[key]; + for (let i = 0; i < 40 && fiber; i++) { + const props = fiber.memoizedProps || fiber.pendingProps; + if (props) { + const author = props.user?.username || props.owner?.username || props.media?.user?.username || props.media?.owner?.username || props.item?.user?.username || props.clip?.user?.username || ''; + const code = props.code || props.shortcode || props.media?.code || props.media?.shortcode || props.item?.code || props.clip?.code || props.post?.code || ''; + + if (props.video_versions && Array.isArray(props.video_versions) && props.video_versions[0]?.url) { + return { url: props.video_versions[0].url, isVideo: true, code: code, author: author }; + } + if (props.media?.video_versions?.[0]?.url) { + return { url: props.media.video_versions[0].url, isVideo: true, code: code, author: author }; + } + if (props.item?.video_versions?.[0]?.url) { + return { url: props.item.video_versions[0].url, isVideo: true, code: code, author: author }; + } + if (props.clip?.video_versions?.[0]?.url) { + return { url: props.clip.video_versions[0].url, isVideo: true, code: code, author: author }; + } + if (props.post?.video_versions?.[0]?.url) { + return { url: props.post.video_versions[0].url, isVideo: true, code: code, author: author }; + } + if (props.videoUrl && typeof props.videoUrl === 'string' && props.videoUrl.startsWith('http') && isLikelyVideoUrl(props.videoUrl)) { + return { url: props.videoUrl, isVideo: true, code: code, author: author }; + } + if (props.display_url && typeof props.display_url === 'string' && props.display_url.startsWith('http')) { + return { url: props.display_url, isVideo: false, code: code, author: author }; + } + if (code || author) { + return { code: code, author: author }; + } + } + fiber = fiber.return; + } + } catch (_) {} + return null; + } + + function extractBestDomUrl(container, isVideoExpected = false) { + if (!container) return null; + + const video = container.querySelector('video') || (container.tagName === 'VIDEO' ? container : null); + const hasVideo = !!video || isVideoExpected; + + if (hasVideo) { + const fiberData = (video && (extractFromFiber(video) || extractFromFiber(video.parentElement))) || extractFromFiber(container); + if (fiberData && fiberData.url && isLikelyVideoUrl(fiberData.url)) { + return fiberData; + } + + if (video) { + const vSrc = video.src || video.currentSrc || ''; + if (vSrc && (vSrc.startsWith('http://') || vSrc.startsWith('https://')) && !vSrc.startsWith('blob:') && isLikelyVideoUrl(vSrc)) { + return { url: vSrc, isVideo: true, author: fiberData?.author, code: fiberData?.code }; + } + const source = video.querySelector('source'); + if (source && source.src && (source.src.startsWith('http://') || source.src.startsWith('https://')) && !source.src.startsWith('blob:') && isLikelyVideoUrl(source.src)) { + return { url: source.src, isVideo: true, author: fiberData?.author, code: fiberData?.code }; + } + } + + const ogVideo = document.querySelector('meta[property="og:video"], meta[property="og:video:secure_url"]'); + if (ogVideo && ogVideo.content && (ogVideo.content.startsWith('http://') || ogVideo.content.startsWith('https://')) && isLikelyVideoUrl(ogVideo.content)) { + return { url: ogVideo.content, isVideo: true, author: fiberData?.author, code: fiberData?.code }; + } + + // Do not fall back to photo stills when expecting video. + if (fiberData && (fiberData.code || fiberData.author)) { + return { url: '', isVideo: true, code: fiberData.code, author: fiberData.author }; + } + return null; + } + + const fiberContainer = extractFromFiber(container); + if (fiberContainer && fiberContainer.url && !isLikelyVideoUrl(fiberContainer.url)) { + return fiberContainer; + } + + const imgs = Array.from(container.querySelectorAll('img')).filter(img => { + const r = img.getBoundingClientRect(); + return r.width > 120 && r.height > 120; + }); + const mainImg = imgs[0]; + if (mainImg) { + if (mainImg.srcset) { + const parts = mainImg.srcset.split(',').map(s => s.trim().split(/\s+/)); + const best = parts[parts.length - 1]?.[0]; + if (best && (best.startsWith('http://') || best.startsWith('https://'))) { + return { url: best.replace(/&/g, '&'), isVideo: false, code: fiberContainer?.code, author: fiberContainer?.author }; + } + } + const iSrc = mainImg.currentSrc || mainImg.src; + if (iSrc && (iSrc.startsWith('http://') || iSrc.startsWith('https://'))) { + return { url: iSrc.replace(/&/g, '&'), isVideo: false, code: fiberContainer?.code, author: fiberContainer?.author }; + } + } + + const ogImage = document.querySelector('meta[property="og:image"]'); + if (ogImage && ogImage.content && (ogImage.content.startsWith('http://') || ogImage.content.startsWith('https://'))) { + return { url: ogImage.content, isVideo: false }; + } + + return null; + } + + async function handleMediaDownload(postContainer, shortcode, activeVideo) { + try { + let slideIndex = 0; + if (postContainer) { + const activeStep = postContainer.querySelector('button[aria-current="step"]'); + if (activeStep && activeStep.parentElement) { + const steps = Array.from(activeStep.parentElement.querySelectorAll('button')); + const idx = steps.indexOf(activeStep); + if (idx >= 0) slideIndex = idx; + } + } + + const hasVideoElement = !!(activeVideo || (postContainer && (postContainer.querySelector('video') || postContainer.tagName === 'VIDEO')) || window.location.pathname.includes('/reel')); + + let resolvedUrl = ''; + let isVideo = hasVideoElement; + let author = ''; + + try { + const harvest = (window.wrappedJSObject && window.wrappedJSObject.__fg_media_harvest) || window.__fg_media_harvest; + if (harvest && shortcode && harvest[shortcode]) { + const cached = harvest[shortcode]; + if (cached.author) author = cached.author; + if (cached.carousel && cached.carousel.length > 0 && cached.carousel[slideIndex]) { + const slide = cached.carousel[slideIndex]; + if (hasVideoElement) { + if (slide.isVideo && slide.videoUrl && isLikelyVideoUrl(slide.videoUrl)) { + resolvedUrl = slide.videoUrl; + isVideo = true; + } + } else { + if (!slide.isVideo && slide.imageUrl && isLikelyImageUrl(slide.imageUrl)) { + resolvedUrl = slide.imageUrl; + isVideo = false; + } else if (slide.url && !isLikelyVideoUrl(slide.url)) { + resolvedUrl = slide.url; + isVideo = false; + } + } + } else { + if (hasVideoElement) { + if (cached.videoUrl && isLikelyVideoUrl(cached.videoUrl)) { + resolvedUrl = cached.videoUrl; + isVideo = true; + } + } else { + if (cached.imageUrl && isLikelyImageUrl(cached.imageUrl)) { + resolvedUrl = cached.imageUrl; + isVideo = false; + } else if (cached.url && !isLikelyVideoUrl(cached.url)) { + resolvedUrl = cached.url; + isVideo = false; + } + } + } + } + } catch (_) {} + + if (!resolvedUrl && hasVideoElement) { + const streamMedia = getActiveStreamFromPerformance(); + if (streamMedia && streamMedia.url && isLikelyVideoUrl(streamMedia.url)) { + resolvedUrl = streamMedia.url; + isVideo = true; + } + } + + if (!resolvedUrl) { + try { + const recents = (window.wrappedJSObject && window.wrappedJSObject.__fg_recent_media) || window.__fg_recent_media; + if (recents && recents.length > 0) { + const authorEl = postContainer ? (postContainer.querySelector('header a, a[role="link"]:not([href*="audio"]):not([href*="explore"]):not([href*="/p/"]):not([href*="/reel/"])')) : null; + const cardAuthor = (authorEl?.textContent || '').trim().toLowerCase().replace('@', ''); + if (cardAuthor) { + const match = recents.find(r => (r.author || '').toLowerCase() === cardAuthor); + if (match) { + if (!author && match.author) author = match.author; + if (hasVideoElement && match.videoUrl && isLikelyVideoUrl(match.videoUrl)) { + resolvedUrl = match.videoUrl; + isVideo = true; + } else if (!hasVideoElement && (match.imageUrl || match.url)) { + const candidate = match.imageUrl || match.url; + if (!isLikelyVideoUrl(candidate)) { + resolvedUrl = candidate; + isVideo = false; + } + } + if (!shortcode && match.code) shortcode = match.code; + } + } + if (!resolvedUrl && (window.location.pathname.includes('/reels') || window.location.pathname.includes('/reel/'))) { + const top = recents[0]; + if (top) { + if (!author && top.author) author = top.author; + if (hasVideoElement && top.videoUrl && isLikelyVideoUrl(top.videoUrl)) { + resolvedUrl = top.videoUrl; + isVideo = true; + } else if (!hasVideoElement && (top.imageUrl || top.url)) { + const candidate = top.imageUrl || top.url; + if (!isLikelyVideoUrl(candidate)) { + resolvedUrl = candidate; + isVideo = false; + } + } + if (!shortcode && top.code) shortcode = top.code; + } + } + } + } catch (_) {} + } + + if (!resolvedUrl) { + const domMedia = extractBestDomUrl(activeVideo || postContainer, hasVideoElement); + if (domMedia && domMedia.url && domMedia.url.startsWith('http')) { + if (hasVideoElement && isLikelyVideoUrl(domMedia.url)) { + resolvedUrl = domMedia.url; + isVideo = true; + } else if (!hasVideoElement && !isLikelyVideoUrl(domMedia.url)) { + resolvedUrl = domMedia.url; + isVideo = false; + } + } + if (!shortcode && domMedia?.code) shortcode = domMedia.code; + if (!author && domMedia?.author) author = domMedia.author; + } + + if ((!resolvedUrl || (hasVideoElement && isLikelyImageUrl(resolvedUrl))) && shortcode) { + const apiMedia = await fetchMediaByShortcode(shortcode, slideIndex); + if (apiMedia && apiMedia.url && apiMedia.url.startsWith('http')) { + if (hasVideoElement) { + const targetVid = (apiMedia.videoUrl && isLikelyVideoUrl(apiMedia.videoUrl)) ? apiMedia.videoUrl : (isLikelyVideoUrl(apiMedia.url) ? apiMedia.url : ''); + if (targetVid) { + resolvedUrl = targetVid; + isVideo = true; + } + } else { + const targetImg = (apiMedia.imageUrl && isLikelyImageUrl(apiMedia.imageUrl)) ? apiMedia.imageUrl : (!isLikelyVideoUrl(apiMedia.url) ? apiMedia.url : ''); + if (targetImg) { + resolvedUrl = targetImg; + isVideo = false; + } + } + if (!author && apiMedia.author) author = apiMedia.author; + } + } + + if (hasVideoElement && (!resolvedUrl || isLikelyImageUrl(resolvedUrl))) { + const streamFallback = getActiveStreamFromPerformance(); + if (streamFallback && streamFallback.url && isLikelyVideoUrl(streamFallback.url)) { + resolvedUrl = streamFallback.url; + isVideo = true; + } + } + + if (!author && postContainer) { + const authorEl = postContainer.querySelector('header a, a[role="link"]:not([href*="audio"]):not([href*="explore"]):not([href*="/p/"]):not([href*="/reel/"])'); + if (authorEl && authorEl.textContent) { + author = authorEl.textContent.trim().replace(/^@/, ''); + } + } + + if (hasVideoElement && isLikelyImageUrl(resolvedUrl)) { + resolvedUrl = ''; + } + if (!hasVideoElement && isLikelyVideoUrl(resolvedUrl)) { + resolvedUrl = ''; + } + + if (resolvedUrl && (resolvedUrl.startsWith('http://') || resolvedUrl.startsWith('https://') || resolvedUrl.startsWith('data:'))) { + const cleanAuthor = author ? author.replace(/^@/, '').trim().slice(0, 13) : ''; + const authorPrefix = cleanAuthor ? ('@' + cleanAuthor) : '@Focusgram'; + const isFinalVideo = hasVideoElement && isVideo && !isLikelyImageUrl(resolvedUrl); + const ext = isFinalVideo ? '.mp4' : '.jpg'; + const filename = `${authorPrefix} - Focusgram${slideIndex > 0 ? ('_' + (slideIndex + 1)) : ''}${ext}`; + triggerNativeDownload(resolvedUrl, filename, null); + return; + } + + const errorMsg = hasVideoElement ? 'Could not extract MP4 video URL for this post' : 'Could not extract photo URL for this post'; + triggerNativeDownload(null, null, errorMsg); + } catch (e) { + console.error('[FocusGram Downloader] handleMediaDownload error:', e); + triggerNativeDownload(null, null, 'Download error: ' + (e.message || e)); + } + } + + async function handleProfilePictureDownload(username, avatarImg) { + try { + const harvested = (window.wrappedJSObject && window.wrappedJSObject.__fg_profile_pics) || window.__fg_profile_pics; + if (harvested && username && harvested[username.toLowerCase()]) { + const hdUrl = harvested[username.toLowerCase()]; + if (hdUrl && hdUrl.startsWith('http')) { + triggerNativeDownload(hdUrl, username + '_profile_pic_hd.jpg', null); + return; + } + } + + if (username) { + try { + const res = await fetch('/api/v1/users/web_profile_info/?username=' + encodeURIComponent(username), { + headers: { + 'x-ig-app-id': '936619743392459', + 'x-requested-with': 'XMLHttpRequest' + }, + credentials: 'include' + }); + if (res.ok) { + const json = await res.json(); + const hdUrl = json?.data?.user?.profile_pic_url_hd || json?.data?.user?.profile_pic_url; + if (hdUrl && hdUrl.startsWith('http')) { + triggerNativeDownload(hdUrl, username + '_profile_pic_hd.jpg', null); + return; + } + } + } catch (e) { + console.warn('[FocusGram Downloader] HD profile pic API failed:', e); + } + } + + // Meta CDN URL parameters and path tokens are HMAC-signed; mutating them breaks signatures. + if (avatarImg) { + let url = ''; + if (avatarImg.srcset) { + const parts = avatarImg.srcset.split(',').map(s => s.trim().split(/\s+/)); + url = parts[parts.length - 1]?.[0] || ''; + } + if (!url) url = avatarImg.currentSrc || avatarImg.src || ''; + if (url && url.startsWith('http')) { + const cleanUrl = url.replace(/&/g, '&'); + triggerNativeDownload(cleanUrl, (username || 'instagram') + '_profile_pic.jpg', null); + return; + } + } + + triggerNativeDownload(null, null, 'Could not extract profile picture URL'); + } catch (e) { + triggerNativeDownload(null, null, 'Profile download error: ' + (e.message || e)); + } + } + + function syncProfileDownloadButton() { + if (!isDownloadEnabled()) { + document.querySelectorAll('[data-fg-profile-dl="1"]').forEach(el => el.remove()); + return; + } + + const path = (window.location.pathname || '').replace(/^\/|\/$/g, ''); + if (!path) return; + const segments = path.split('/').filter(Boolean); + if (segments.length === 0) return; + + const RESERVED = ['explore', 'reels', 'reel', 'p', 'direct', 'stories', 'accounts', 'developer', 'about', 'legal', 'settings']; + const usernameCandidate = segments[0]; + if (RESERVED.includes(usernameCandidate.toLowerCase())) return; + const username = usernameCandidate; + + const avatarImgs = Array.from(document.querySelectorAll('header img, section header img, main header img, [role="main"] header img, img[alt*="profile picture" i], img[alt*="\'s profile" i]')) + .filter(img => { + const r = img.getBoundingClientRect(); + return r.width >= 40 && r.height >= 40; + }); + const avatarImg = avatarImgs[0]; + if (!avatarImg) return; + + const avatarBtn = avatarImg.closest('button, [role="button"]') || avatarImg.parentElement; + if (!avatarBtn) return; + + const wrapper = avatarBtn.parentElement || avatarBtn; + if (wrapper.querySelector('[data-fg-profile-dl="1"]')) return; + if (document.querySelector('[data-fg-profile-dl="1"]')) return; + + const pos = window.getComputedStyle(wrapper).position; + if (!pos || pos === 'static') wrapper.style.position = 'relative'; + + const dlBadge = document.createElement('div'); + dlBadge.setAttribute('data-fg-profile-dl', '1'); + dlBadge.setAttribute('role', 'button'); + dlBadge.setAttribute('tabindex', '0'); + dlBadge.setAttribute('aria-label', 'Download Profile Picture'); + dlBadge.title = 'Download Profile Picture'; + dlBadge.style.cssText = [ + 'position:absolute', + 'bottom:-4px', + 'right:-4px', + 'width:36px', + 'height:36px', + 'border-radius:50%', + 'background:linear-gradient(135deg, #00C6FF 0%, #0072FF 100%)', + 'color:#ffffff', + 'border:2.5px solid #18181c', + 'display:flex', + 'align-items:center', + 'justify-content:center', + 'cursor:pointer', + 'z-index:9999', + 'box-shadow:0 3px 10px rgba(0,0,0,0.5)', + 'pointer-events:auto', + 'transition:transform 0.15s ease' + ].join(';'); + + dlBadge.innerHTML = ``; + + let lastTrigger = 0; + const triggerDownload = (e) => { + e.preventDefault(); + e.stopPropagation(); + const now = Date.now(); + if (now - lastTrigger < 1000) return; + lastTrigger = now; + dlBadge.style.transform = 'scale(0.85)'; + setTimeout(() => { dlBadge.style.transform = 'scale(1)'; }, 150); + handleProfilePictureDownload(username, avatarImg); + }; + + dlBadge.addEventListener('click', triggerDownload, true); + dlBadge.addEventListener('touchend', triggerDownload, true); + dlBadge.addEventListener('touchstart', (e) => e.stopPropagation(), true); + dlBadge.addEventListener('pointerdown', (e) => e.stopPropagation(), true); + + wrapper.appendChild(dlBadge); + } + + function findActionRow(post) { + if (!post) return null; + + const saveBtn = post.querySelector('button[aria-label*="Save" i], [role="button"][aria-label*="Save" i], button[aria-label*="Bookmark" i], [role="button"][aria-label*="Bookmark" i], svg[aria-label*="Save" i], svg[aria-label*="Bookmark" i], svg polygon, svg path[d^="M20 22"], svg path[d^="M20 21"]'); + if (saveBtn) { + const btn = saveBtn.closest('button, [role="button"]') || saveBtn; + if (btn && btn.parentElement) { + return { row: btn.parentElement, anchor: btn, position: 'before' }; + } + } + + const shareBtn = post.querySelector('button[aria-label*="Share" i], [role="button"][aria-label*="Share" i], svg[aria-label*="Share" i], svg path[d^="M13.973 20.046"], svg path[d*="M22 3L2 10"]'); + if (shareBtn) { + const btn = shareBtn.closest('button, [role="button"]') || shareBtn; + if (btn && btn.parentElement) { + return { row: btn.parentElement, anchor: btn, position: 'after' }; + } + } + + const commentBtn = post.querySelector('button[aria-label*="Comment" i], [role="button"][aria-label*="Comment" i], svg[aria-label*="Comment" i]'); + if (commentBtn) { + const btn = commentBtn.closest('button, [role="button"]') || commentBtn; + if (btn && btn.parentElement) { + return { row: btn.parentElement, anchor: btn, position: 'after' }; + } + } + + const likeBtn = post.querySelector('button[aria-label*="Like" i], [role="button"][aria-label*="Like" i], svg[aria-label*="Like" i], svg path[d*="M16.792 3.904"]'); + if (likeBtn) { + const btn = likeBtn.closest('button, [role="button"]') || likeBtn; + if (btn && btn.parentElement) { + return { row: btn.parentElement, anchor: btn, position: 'after' }; + } + } + + const section = post.querySelector('section'); + if (section) { + const buttons = section.querySelectorAll('button, [role="button"]'); + if (buttons.length >= 1) { + const lastBtn = buttons[buttons.length - 1]; + return { row: lastBtn.parentElement || section, anchor: lastBtn, position: 'after' }; + } + } + + return null; + } + + function syncDownloadButtons() { + if (!isDownloadEnabled()) { + document.querySelectorAll('[' + FG_DL_ATTR + '="1"]').forEach(btn => btn.remove()); + return; + } + + const posts = []; + const seen = new Set(); + + document.querySelectorAll('article').forEach(el => { + const r = el.getBoundingClientRect(); + if (r.height > 120 && r.width > 120 && !seen.has(el)) { + seen.add(el); + posts.push(el); + } + }); + + posts.forEach(post => { + if (post.querySelector('[' + FG_DL_ATTR + '="1"]')) return; + + const link = post.querySelector('a[href*="/p/"], a[href*="/reel/"], a[href*="/reels/"]'); + const href = link ? (link.getAttribute('href') || '') : ''; + const codeMatch = href.match(/\/(?:p|reel|reels)\/([A-Za-z0-9_-]+)/); + const shortcode = codeMatch ? codeMatch[1] : ''; + + const actionInfo = findActionRow(post); + + const dlBtn = document.createElement('div'); + dlBtn.className = 'fg-feed-download-btn'; + dlBtn.setAttribute(FG_DL_ATTR, '1'); + dlBtn.setAttribute('tabindex', '0'); + dlBtn.setAttribute('role', 'button'); + dlBtn.setAttribute('aria-label', 'Download Media'); + dlBtn.title = 'Download Media'; + + let lastTrigger = 0; + let touchStartX = 0; + let touchStartY = 0; + let touchStartTime = 0; + let isHolding = false; + let hasMoved = false; + let holdThresholdPassed = false; + let holdTimer = null; + + const executeDownload = () => { + const now = Date.now(); + if (now - lastTrigger < 1000) return; + lastTrigger = now; + + dlBtn.style.transform = 'scale(0.82)'; + setTimeout(() => { dlBtn.style.transform = 'scale(1)'; }, 150); + + let code = shortcode; + if (!code) { + const l = post.querySelector('a[href*="/p/"], a[href*="/reel/"], a[href*="/reels/"]'); + const h = l ? (l.getAttribute('href') || '') : ''; + const m = h.match(/\/(?:p|reel|reels)\/([A-Za-z0-9_-]+)/); + if (m && m[1] !== 'p' && m[1] !== 'reel' && m[1] !== 'reels') { + code = m[1]; + } + } + + handleMediaDownload(post, code); + }; + + // Require stationary 30ms hold to prevent accidental triggers during fling scrolls. + dlBtn.addEventListener('touchstart', (e) => { + if (isGlobalScrolling || (Date.now() - lastScrollTime < 250)) { + isHolding = false; + hasMoved = true; + return; + } + if (!e.touches || e.touches.length === 0) return; + touchStartX = e.touches[0].clientX; + touchStartY = e.touches[0].clientY; + touchStartTime = Date.now(); + isHolding = true; + hasMoved = false; + holdThresholdPassed = false; + + clearTimeout(holdTimer); + holdTimer = setTimeout(() => { + if (isHolding && !hasMoved && !isGlobalScrolling && (Date.now() - lastScrollTime >= 250)) { + holdThresholdPassed = true; + } + }, 30); + }, { passive: true, capture: true }); + + dlBtn.addEventListener('touchmove', (e) => { + if (!isHolding || !e.touches || e.touches.length === 0) return; + const dx = Math.abs(e.touches[0].clientX - touchStartX); + const dy = Math.abs(e.touches[0].clientY - touchStartY); + if (dx > 3 || dy > 3 || isGlobalScrolling) { + hasMoved = true; + isHolding = false; + clearTimeout(holdTimer); + } + }, { passive: true, capture: true }); + + dlBtn.addEventListener('touchcancel', () => { + isHolding = false; + hasMoved = true; + clearTimeout(holdTimer); + }, { passive: true, capture: true }); + + dlBtn.addEventListener('touchend', (e) => { + e.preventDefault(); + e.stopPropagation(); + clearTimeout(holdTimer); + const holdDuration = Date.now() - touchStartTime; + + if (isHolding && !hasMoved && (holdThresholdPassed || holdDuration >= 30) && !isGlobalScrolling && (Date.now() - lastScrollTime >= 250)) { + executeDownload(); + } + + isHolding = false; + hasMoved = false; + holdThresholdPassed = false; + }, true); + + dlBtn.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + }, true); + + if (actionInfo && actionInfo.row && actionInfo.anchor) { + dlBtn.style.cssText = 'display:inline-flex;align-items:center;justify-content:center;cursor:pointer;padding:8px;margin-right:4px;color:currentColor;opacity:0.9;vertical-align:middle;box-sizing:border-box;pointer-events:auto;transition:transform 0.15s ease;'; + dlBtn.innerHTML = ``; + if (actionInfo.position === 'before') { + actionInfo.row.insertBefore(dlBtn, actionInfo.anchor); + } else { + actionInfo.row.insertBefore(dlBtn, actionInfo.anchor.nextSibling || actionInfo.anchor); + } + } else { + const section = post.querySelector('section'); + if (section) { + dlBtn.style.cssText = 'display:inline-flex;align-items:center;justify-content:center;cursor:pointer;padding:8px;margin-right:4px;color:currentColor;opacity:0.9;vertical-align:middle;box-sizing:border-box;pointer-events:auto;'; + dlBtn.innerHTML = ``; + section.appendChild(dlBtn); + } else { + const postMedia = post.querySelector('video')?.parentElement || + Array.from(post.querySelectorAll('img')).find(img => !img.closest('header') && !img.alt.includes('profile') && img.getBoundingClientRect().width > 120)?.parentElement || + post; + const pos = window.getComputedStyle(postMedia).position; + if (!pos || pos === 'static') postMedia.style.position = 'relative'; + + dlBtn.style.cssText = 'position:absolute;bottom:12px;right:12px;z-index:90;width:38px;height:38px;border-radius:50%;background:rgba(0,0,0,0.65);border:1px solid rgba(255,255,255,0.25);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;backdrop-filter:blur(8px);box-shadow:0 2px 8px rgba(0,0,0,0.4);pointer-events:auto;transition:transform 0.15s ease;'; + dlBtn.innerHTML = ``; + postMedia.appendChild(dlBtn); + } + } + }); + } + + function syncReelsDownloadButton() { + // ReelsTopBar handles reel downloads natively; strip injected buttons. + document.querySelectorAll('[data-fg-reel-dl="1"]').forEach(el => el.remove()); + } + + async function downloadActiveReel() { + try { + const vh = window.innerHeight; + const videos = Array.from(document.querySelectorAll('video')); + let activeVideo = null; + let minCenterDist = Infinity; + for (const v of videos) { + const r = v.getBoundingClientRect(); + if (r.height > 100 && r.bottom > 0 && r.top < vh) { + const dist = Math.abs((r.top + r.height / 2) - vh / 2); + if (!v.paused) { + activeVideo = v; + break; + } + if (dist < minCenterDist) { + minCenterDist = dist; + activeVideo = v; + } + } + } + if (!activeVideo && videos.length > 0) activeVideo = videos[0]; + + const card = activeVideo ? (activeVideo.closest('[data-fg-reel-card]') || activeVideo.closest('article') || activeVideo.parentElement) : document; + + let code = null; + if (card) { + const reelLink = card.querySelector('a[href*="/reel/"], a[href*="/p/"], a[href*="/reels/"]'); + if (reelLink && reelLink.href) { + const m = reelLink.href.match(/\/(?:reel|reels|p)\/([A-Za-z0-9_-]{4,})/); + if (m && m[1] !== 'reels' && m[1] !== 'reel') code = m[1]; + } + } + if (!code) { + const m = window.location.pathname.match(/\/(?:reel|reels|p)\/([A-Za-z0-9_-]{4,})/); + if (m && m[1] !== 'reels' && m[1] !== 'reel') code = m[1]; + } + + if (!code && (activeVideo || card)) { + const fiberData = extractFromFiber(activeVideo) || extractFromFiber(card); + if (fiberData && fiberData.code) { + code = fiberData.code; + } + } + + await handleMediaDownload(card, code, activeVideo); + } catch (e) { + console.error('[FocusGram Downloader] downloadActiveReel error:', e); + triggerNativeDownload(null, null, 'Download error: ' + (e.message || e)); + } + } + + window.__fgDownloadActiveReel = downloadActiveReel; + window.addEventListener('focusgram:download-reel', downloadActiveReel); + + window.addEventListener('message', (e) => { + if (e.data && e.data.type === 'FOCUSGRAM_UPDATE' && e.data.config) { + syncDownloadButtons(); + syncProfileDownloadButton(); + syncReelsDownloadButton(); + } + }); + + setInterval(() => { + syncDownloadButtons(); + syncProfileDownloadButton(); + syncReelsDownloadButton(); + }, 300); + + setTimeout(() => { + syncDownloadButtons(); + syncProfileDownloadButton(); + syncReelsDownloadButton(); + }, 200); + +})(); diff --git a/Ported/app/src/main/assets/scripts/native_feel.js b/app/src/main/assets/scripts/native_feel.js similarity index 87% rename from Ported/app/src/main/assets/scripts/native_feel.js rename to app/src/main/assets/scripts/native_feel.js index bd25114..4859f82 100644 --- a/Ported/app/src/main/assets/scripts/native_feel.js +++ b/app/src/main/assets/scripts/native_feel.js @@ -1,12 +1,6 @@ /** - * FocusGram — Native Feel, Touch Smoothing & Keyboard Fix UserScript - * - * Implements native Android application styling and touch physics: - * 1. Hides web scrollbars and eliminates blue tap highlights. - * 2. Restricts text selection to input and editable areas to prevent accidental selection during gestures. - * 3. Enforces hardware-accelerated momentum scrolling (120Hz display refresh support). - * 4. Dispatches synthetic viewport and orientation change events upon soft keyboard show/hide, - * preventing Instagram Direct Message (DM) thread freeze and layout stalls. + * Native touch physics, keyboard resize adaptation, file input enhancement, + * and live bio font styling. */ 'use strict'; @@ -16,15 +10,13 @@ try { var path = (window.location.pathname || '').toLowerCase(); if (path.includes('/accounts/edit')) return false; - if (path.startsWith('/accounts') || path.startsWith('/login') || path.startsWith('/signup')) return true; - var c = document.cookie || ''; - if (!c.includes('sessionid=') && !c.includes('ds_user_id=')) return true; - if (document.getElementById('loginForm') || document.querySelector('form[action*="login"], input[name="password"]')) return true; + if (path.startsWith('/accounts') || path.startsWith('/login') || path.startsWith('/signup') || path.startsWith('/two_factor') || path.startsWith('/challenge')) return true; + if (document.getElementById('loginForm') || document.querySelector('form[action*="login"], form input[type="password"]')) return true; } catch (_) {} return false; } - if (isAuthPage()) return; // Leave login/signup completely untouched + if (isAuthPage()) return; if (window.__fgNativeFeelInitialized) return; window.__fgNativeFeelInitialized = true; @@ -32,7 +24,6 @@ const STYLE_ID = 'focusgram-native-feel-styles'; const CSS_RULES = ` - /* 1. Hide all scrollbars across containers */ * { -ms-overflow-style: none !important; scrollbar-width: none !important; @@ -43,13 +34,12 @@ height: 0 !important; } - /* 2. Eliminate web touch callouts and tap highlights */ * { -webkit-tap-highlight-color: transparent !important; -webkit-touch-callout: none !important; } - /* 3. Disable browser text selection except on editable fields */ + /* Restrict selection to editable surfaces so gestures don't trigger drag-select. */ * { -webkit-user-select: none !important; user-select: none !important; @@ -59,17 +49,14 @@ user-select: text !important; } - /* 4. Enforce hardware-accelerated momentum touch scrolling on scrollable containers */ html, body, [data-fg-reel-scroller], div[style*="overflow"], main { -webkit-overflow-scrolling: touch !important; } - /* 5. Remove web focus rings and outlines */ *:focus, *:focus-visible { outline: none !important; } - /* 6. Subtle smooth fade-in for image loading */ img { animation: fgFadeIn 0.12s ease-in-out; } @@ -207,20 +194,15 @@ } catch (_) {} } - /* ========================================================================== - 7. LIVE BIO FONT STYLIST & REAL-TIME INPUT ENGINE - Directly mounts a dedicated styling accessory bar above textarea#pepBio, - handles bidirectional Unicode mathematical font conversion, - and transforms typing in real time. - ========================================================================== */ + // Unicode mathematical alphanumeric font mapping offsets for the bio styler. const BIO_FONT_MAPS = { normal: null, - bold: { u: 0x1D400, l: 0x1D41A, d: 0x1D7CE }, // Mathematical Bold (Zero gaps) - sans: { u: 0x1D5D4, l: 0x1D5EE, d: 0x1D7EC }, // Sans-Serif Bold (Zero gaps) - sans_italic: { u: 0x1D608, l: 0x1D622, d: null }, // Sans-Serif Italic (Zero gaps) - mono: { u: 0x1D670, l: 0x1D68A, d: 0x1D7F6 }, // Monospace (Zero gaps) - gothic: { u: 0x1D56C, l: 0x1D586, d: null }, // Bold Fraktur/Gothic (Zero gaps) - script: { u: 0x1D4D0, l: 0x1D4EA, d: null } // Bold Script/Cursive (Zero gaps) + bold: { u: 0x1D400, l: 0x1D41A, d: 0x1D7CE }, + sans: { u: 0x1D5D4, l: 0x1D5EE, d: 0x1D7EC }, + sans_italic: { u: 0x1D608, l: 0x1D622, d: null }, + mono: { u: 0x1D670, l: 0x1D68A, d: 0x1D7F6 }, + gothic: { u: 0x1D56C, l: 0x1D586, d: null }, + script: { u: 0x1D4D0, l: 0x1D4EA, d: null } }; let activeBioStyle = 'bold'; @@ -329,7 +311,7 @@ if (!target) return; - // Convert ONLY selected text if user made an explicit text selection + // Apply style to active text selection if present; otherwise subsequent typing uses this style. const val = target.value || ''; if (val && target.selectionStart !== undefined && target.selectionEnd !== undefined && target.selectionStart !== target.selectionEnd) { const start = target.selectionStart; @@ -341,8 +323,6 @@ setReactInputValue(target, before + converted + after); target.setSelectionRange(start, start + converted.length); } - // If NO text is selected, do NOT alter existing text! - // activeBioStyle is set, so only characters typed afterwards will be converted. updateBioCharCounter(target); } @@ -473,7 +453,29 @@ return bar; } + function isBioFontsEnabled() { + if (window.__fgFilterConfig && typeof window.__fgFilterConfig.customBioFontsEnabled === 'boolean') { + return window.__fgFilterConfig.customBioFontsEnabled; + } + if (window.__FOCUSGRAM_CONFIG__ && typeof window.__FOCUSGRAM_CONFIG__.customBioFontsEnabled === 'boolean') { + return window.__FOCUSGRAM_CONFIG__.customBioFontsEnabled; + } + return false; + } + + function cleanupBioStylist() { + const existing = document.getElementById('fg-bio-inline-bar'); + if (existing) existing.remove(); + bioInlineBarEl = null; + notifyBioFocus(false); + } + function checkAndMountBioStylist() { + if (!isBioFontsEnabled()) { + cleanupBioStylist(); + return; + } + const bioEl = document.getElementById('pepBio') || document.querySelector('textarea[aria-label="Bio"]') || document.querySelector('textarea[placeholder="Bio"]') || @@ -487,6 +489,7 @@ // Intercept keystrokes for real-time live typing transformation document.addEventListener('beforeinput', (e) => { + if (!isBioFontsEnabled()) return; if (!isBioTarget(e.target) || activeBioStyle === 'normal') return; if (e.data && e.inputType === 'insertText') { e.preventDefault(); @@ -502,6 +505,7 @@ }, true); document.addEventListener('focusin', (e) => { + if (!isBioFontsEnabled()) return; if (isBioTarget(e.target)) { activeBioTarget = e.target; mountBioInlineBar(e.target); @@ -521,14 +525,42 @@ } }, true); - // Watch for dynamic rendering of Bio textarea on /accounts/edit/ + // Detect bio textarea mount during SPA transitions on /accounts/edit/. const bioObserver = new MutationObserver(() => { checkAndMountBioStylist(); }); bioObserver.observe(document.documentElement, { childList: true, subtree: true }); setInterval(checkAndMountBioStylist, 500); - // Initialize immediately or upon DOM ready + function handleBioConfigUpdate(cfg) { + if (!cfg) return; + if (!window.__fgFilterConfig) window.__fgFilterConfig = {}; + Object.assign(window.__fgFilterConfig, cfg); + if (cfg.customBioFontsEnabled === false) { + cleanupBioStylist(); + } else if (cfg.customBioFontsEnabled === true) { + checkAndMountBioStylist(); + } + } + + window.addEventListener('message', (e) => { + if (e.data && e.data.type === 'FOCUSGRAM_FILTER_UPDATE' && e.data.config) { + handleBioConfigUpdate(e.data.config); + } + }); + window.addEventListener('focusgram:filter-config-update', (e) => { + handleBioConfigUpdate(e.detail); + }); + document.addEventListener('focusgram:filter-config-update', (e) => { + handleBioConfigUpdate(e.detail); + }); + + const prevNativeSetFilter = window.__fgSetFilterConfig; + window.__fgSetFilterConfig = function (cfg) { + if (typeof prevNativeSetFilter === 'function') prevNativeSetFilter(cfg); + handleBioConfigUpdate(cfg); + }; + injectNativeStyles(); applyScrollSmoothing(); setupKeyboardViewportFix(); diff --git a/app/src/main/assets/scripts/reel_metadata_extractor.js b/app/src/main/assets/scripts/reel_metadata_extractor.js new file mode 100644 index 0000000..6227bfb --- /dev/null +++ b/app/src/main/assets/scripts/reel_metadata_extractor.js @@ -0,0 +1,162 @@ +/** + * Reel metadata extractor. + * + * Extracts author, caption, and thumbnail URLs from client-side DOM + * (Open Graph tags, JSON-LD data, and document headers) for local watch history. + */ + +'use strict'; + +(function () { + if (window.__fgReelExtractorInitialized) return; + window.__fgReelExtractorInitialized = true; + + let lastExtractedReelKey = ''; + + function extractMetadata() { + const currentUrl = window.location.href; + + if (!currentUrl.includes('/reels/') && !currentUrl.includes('/reel/')) { + return null; + } + + const videos = Array.from(document.querySelectorAll('video')); + const activeVideo = videos.find(v => !v.paused && v.getBoundingClientRect().height > 200) || + videos.find(v => { + const r = v.getBoundingClientRect(); + return r.top >= -50 && r.top < window.innerHeight * 0.6; + }) || videos[0]; + + let title = ''; + let thumbnailUrl = ''; + let author = ''; + let reelShortcode = ''; + + if (activeVideo) { + thumbnailUrl = activeVideo.getAttribute('poster') || ''; + let card = activeVideo.closest('article, section, div[role="presentation"]') || activeVideo.parentElement; + while (card && card !== document.body && card !== document.documentElement) { + if (card.tagName === 'ARTICLE' || card.tagName === 'SECTION') break; + if (card.getBoundingClientRect().height > window.innerHeight * 0.5) break; + card = card.parentElement; + } + + if (card) { + const link = card.querySelector('a[href*="/reel/"], a[href*="/p/"], a[href*="/reels/"]'); + if (link && link.href) { + const lm = /\/(?:reel|reels|p)\/([A-Za-z0-9_-]{5,})/.exec(link.href); + if (lm) reelShortcode = lm[1]; + } + + const authorEl = card.querySelector('header a[role="link"], a[role="link"][href^="/"]:not([href*="/audio/"]):not([href*="/reel"]), header h2, header h3, header span[dir="auto"]'); + if (authorEl && authorEl.textContent) { + author = authorEl.textContent.trim().replace(/^@/, ''); + } else if (authorEl && authorEl.getAttribute('href')) { + const m = authorEl.getAttribute('href').match(/^\/([A-Za-z0-9._]+)\/?/); + if (m) author = m[1]; + } + + const textCandidates = Array.from(card.querySelectorAll('h1, h2, span[dir="auto"], div[dir="auto"]')); + for (const el of textCandidates) { + if (el.closest('a[href*="/audio/"]') || el.closest('header') || el.closest('button') || el.closest('a[role="link"]')) continue; + const txt = (el.textContent || '').trim(); + const lower = txt.toLowerCase(); + if (txt.length >= 2 && txt !== author && !lower.includes('follow') && !lower.startsWith('liked by') && !lower.includes('original audio') && !['ad', 'sponsored'].includes(lower)) { + title = txt.slice(0, 300); + break; + } + } + } + } + + if (!reelShortcode) { + const urlMatch = /\/(?:reel|reels|p)\/([A-Za-z0-9_-]{5,})/.exec(window.location.pathname); + if (urlMatch && videos.length <= 1) { + reelShortcode = urlMatch[1]; + } + } + + // Only fall back to static head meta when viewing a standalone single-reel URL, never during SPA feed swiping. + if (!title && !author && videos.length <= 1) { + const ogTitle = document.querySelector('meta[property="og:title"]'); + const ogImage = document.querySelector('meta[property="og:image"]'); + if (ogTitle && ogTitle.content) title = ogTitle.content.trim(); + if (ogImage && ogImage.content) thumbnailUrl = ogImage.content.trim(); + + if (!title && document.title) { + title = document.title.replace(' on Instagram', '').replace('â€Ē Instagram photos and videos', '').trim(); + } + + if (title) { + const authorMatch = title.match(/^([^(@:]+)(?:\s*\((@[^)]+)\))?/); + if (authorMatch) { + author = (authorMatch[2] || authorMatch[1]).trim().replace(/^@/, ''); + } + } + } + + if (!thumbnailUrl && !title && !author && !reelShortcode) { + return null; + } + + const effectiveUrl = reelShortcode ? `https://www.instagram.com/reel/${reelShortcode}/` : currentUrl; + const dedupeKey = `${reelShortcode || author || ''}___${title || ''}`; + + if (lastExtractedReelKey === dedupeKey) { + return null; + } + lastExtractedReelKey = dedupeKey; + + return { + url: effectiveUrl, + code: reelShortcode || author || '', + title: title || 'Instagram Reel', + author: author || 'Instagram Creator', + thumbnailUrl: thumbnailUrl || '', + timestamp: Date.now() + }; + } + + function extractAndBroadcast() { + const data = extractMetadata(); + if (!data) return; + + try { + window.dispatchEvent(new CustomEvent('focusgram:reelmetadata', { + detail: data + })); + } catch (_) {} + + try { + if (typeof browser !== 'undefined' && browser.runtime && browser.runtime.sendMessage) { + browser.runtime.sendMessage({ + type: 'ReelMetadata', + payload: data + }).catch(() => {}); + } + } catch (_) {} + + console.log('[FocusGram] Reel metadata extracted on-device:', data.url); + } + + extractAndBroadcast(); + + window.addEventListener('focusgram:routechange', () => { + setTimeout(extractAndBroadcast, 400); + }); + + let debounceTimer = null; + const observer = new MutationObserver(() => { + if (window.location.href.includes('/reel')) { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(extractAndBroadcast, 500); + } + }); + + observer.observe(document.documentElement || document.body, { + childList: true, + subtree: true + }); + + console.log('[FocusGram] Reels Metadata Extractor active.'); +})(); diff --git a/Ported/app/src/main/assets/scripts/spa_navigation_monitor.js b/app/src/main/assets/scripts/spa_navigation_monitor.js similarity index 76% rename from Ported/app/src/main/assets/scripts/spa_navigation_monitor.js rename to app/src/main/assets/scripts/spa_navigation_monitor.js index df4fc18..3a0091d 100644 --- a/Ported/app/src/main/assets/scripts/spa_navigation_monitor.js +++ b/app/src/main/assets/scripts/spa_navigation_monitor.js @@ -1,10 +1,8 @@ /** - * FocusGram — SPA Navigation Monitor & Dynamic Mutation Bridge + * Client-side SPA navigation monitor. * - * Hooks into HTML5 History API (pushState, replaceState) and popstate events - * to detect client-side route transitions in Instagram's Single Page Application (SPA). - * Instantly re-enforces content disabling guards and informs native delegates - * within 5ms without relying on full page reload cycles. + * Patches HTML5 History API and listens to navigation events to notify + * content filters and native delegates of in-page route changes. */ 'use strict'; @@ -22,7 +20,6 @@ if (targetUrl === lastUrl) return; lastUrl = targetUrl; - // 1. Immediately re-apply content hiding guards (< 5ms) if (typeof window.__fgContentHiderSweep === 'function') { try { window.__fgContentHiderSweep(); @@ -31,7 +28,7 @@ } } - // 2. Dispatch custom DOM event for other modular UserScripts (e.g. Autoplay blocker, Reels metadata) + // Notify other user scripts (autoplay blocker, reel extractor). try { window.dispatchEvent(new CustomEvent('focusgram:routechange', { detail: { url: targetUrl, timestamp: Date.now() } @@ -40,7 +37,7 @@ console.warn('[FocusGram SPA Monitor] Event dispatch error:', e); } - // 3. Inform WebExtension runtime if bridge is present + // Forward route change to WebExtension background worker when available. try { if (typeof browser !== 'undefined' && browser.runtime && browser.runtime.sendMessage) { browser.runtime.sendMessage({ diff --git a/app/src/main/assets/scripts/stealth_instagram.js b/app/src/main/assets/scripts/stealth_instagram.js new file mode 100644 index 0000000..7cb9d69 --- /dev/null +++ b/app/src/main/assets/scripts/stealth_instagram.js @@ -0,0 +1,358 @@ +/** + * Client-side privacy filter. + * + * Suppresses story viewed telemetry, direct message read receipts, and typing indicators + * via fetch, XMLHttpRequest, sendBeacon, and WebSocket interception. + */ + +'use strict'; + +(function () { + if (window.__FG_STEALTH_INITIALIZED__) return; + window.__FG_STEALTH_INITIALIZED__ = true; + + const DEFAULT_CONFIG = { + enabled: false, + igStory: true, + igSeen: true, + igTyping: true, + hideStorySeen: true, + hideDirectSeen: true, + hideTyping: true + }; + + window.__FOCUSGRAM_GHOST_CONFIG__ = Object.assign({}, DEFAULT_CONFIG, window.__FOCUSGRAM_GHOST_CONFIG__ || {}); + + const PATTERNS = { + igTyping: [ + "indicate_activity", + "typing_indicator", + "activity_indicator", + "is_typing", + "direct_v2/threads/broadcast/typing", + "direct_v2/threads/typing", + "sendtypingindicator", + "send_typing_indicator", + "typing_on", + "is_composing", + "update_activity_status", + "activity_status", + "typingindicatorstoredprocedure", + "directsendtyping", + "direct_send_typing", + "igdsendtyping" + ], + igSeen: [ + "mark_read", + "mark_seen", + "thread_seen", + "directmarkasseen", + "markasseen", + "directthreadmarkitemsseen", + "polarisdirectmarkasseenmutation", + "directseenmutation", + "usepolarismarkthreadseenmutation", + "useigdmarkthreadasreadmutation", + "markdirectthreadseen", + "mark_direct_thread_seen", + "direct_v2/threads/seen", + "direct_v2/threads/mark_seen", + "direct_v2/threads/mark_read" + ], + igStory: [ + "storiesupdateseenmutation", + "polarisstoriesseenmutation", + "usepolarisstoriesv3seenmutation", + "reelmediaseen", + "storiesupdateseen", + "seenstoriesupdatemutation", + "polarisapireelseenmutation", + "xdt_mark_story_reel_seen", + "26997980659837802", + "polarisapiforcestoryseenmutation", + "xdt_api__v1__stories__reel__seen", + "9647304595318258", + "api/v1/stories/reel/seen", + "stories/reel/seen", + "mark_story_seen", + "update_seen_for_reel", + "reel_seen", + "stories_update_seen", + "mark_story_read", + "viewseenat" + ] + }; + + function shouldBlock(data, url, method) { + const config = window.__FOCUSGRAM_GHOST_CONFIG__; + if (!config || !config.enabled) return null; + + const path = String(window.location?.pathname || '').toLowerCase(); + if (path.startsWith('/accounts') || path.startsWith('/login')) return null; + + const urlString = String(url || "").toLowerCase(); + const bodyText = (typeof data === "string" ? data : "").toLowerCase(); + const text = `${bodyText} ${urlString}`; + + // Do not suppress actual user message dispatch or media uploads. + if (urlString.includes("threads/broadcast/text") || + urlString.includes("direct_v2/threads/broadcast") || + urlString.includes("broadcast/media") || + urlString.includes("action=send_message") || + urlString.includes("send_item") || + urlString.includes("publish_message") || + urlString.includes("configure")) { + return null; + } + + const typingActive = config.hideTyping !== undefined ? config.hideTyping : !!config.igTyping; + const storyActive = config.hideStorySeen !== undefined ? config.hideStorySeen : !!config.igStory; + const seenActive = config.hideDirectSeen !== undefined ? config.hideDirectSeen : !!config.igSeen; + + if (typingActive && PATTERNS.igTyping.some(p => text.includes(p))) { + return "IG_TYPING"; + } + + if (storyActive && PATTERNS.igStory.some(p => text.includes(p))) { + return "IG_STORY"; + } + + if (seenActive && PATTERNS.igSeen.some(p => text.includes(p))) { + return "IG_SEEN"; + } + + return null; + } + + function createMockPayload(blockType, url, bodyText) { + const combined = `${bodyText} ${url}`.toLowerCase(); + const isGraphQL = combined.includes("/graphql") || combined.includes("fb_api_req_friendly_name") || combined.includes("doc_id"); + + if (!isGraphQL) { + return { status: "ok", blocked: blockType }; + } + + if (blockType === "IG_STORY") { + if (combined.includes("xdt_api__v1__stories__reel__seen") || + combined.includes("polarisapiforcestoryseenmutation") || + combined.includes("9647304595318258")) { + return { + data: { + xdt_api__v1__stories__reel__seen: { + __typename: "XDTEmptyRecord" + } + } + }; + } + return { + data: { + xdt_mark_story_reel_seen: { + __typename: "XDTMarkSeenResponse" + } + } + }; + } + + return { data: {} }; + } + + const originalFetch = window.fetch; + window.fetch = async function (input, init) { + const url = typeof input === "string" ? input : (input && input.url ? input.url : ""); + const method = (init && init.method) || (input && input.method) || "GET"; + const body = (init && init.body) || ""; + + let bodyText = ""; + try { + if (typeof body === "string") { + bodyText = body; + } else if (typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams) { + bodyText = body.toString(); + } else if (typeof FormData !== "undefined" && body instanceof FormData) { + const parts = []; + for (const [k, v] of body.entries()) { + parts.push(k + '=' + (typeof v === 'string' ? v : '')); + } + bodyText = parts.join('&'); + } else if (typeof Blob !== "undefined" && body instanceof Blob) { + bodyText = await body.text(); + } else if (body instanceof ArrayBuffer) { + bodyText = new TextDecoder().decode(body); + } else if (ArrayBuffer.isView(body)) { + bodyText = new TextDecoder().decode(body); + } else if (!body && typeof Request !== "undefined" && input instanceof Request) { + const cloned = input.clone(); + bodyText = await cloned.text(); + } + } catch (_) {} + + const blockType = shouldBlock(bodyText, url, method); + if (blockType) { + console.log(`[FocusGram Stealth] Filtered fetch: ${blockType} -> ${url}`); + const payload = createMockPayload(blockType, url, bodyText); + return new Response(JSON.stringify(payload), { + status: 200, + headers: { "Content-Type": "application/json" } + }); + } + + return originalFetch.apply(this, arguments); + }; + + const originalXhrOpen = XMLHttpRequest.prototype.open; + const originalXhrSend = XMLHttpRequest.prototype.send; + + XMLHttpRequest.prototype.open = function (method, url) { + this._fgStealthUrl = url; + this._fgStealthMethod = method; + return originalXhrOpen.apply(this, arguments); + }; + + XMLHttpRequest.prototype.send = function (body) { + let bodyText = ""; + try { + if (typeof body === "string") { + bodyText = body; + } else if (typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams) { + bodyText = body.toString(); + } else if (typeof FormData !== "undefined" && body instanceof FormData) { + const parts = []; + for (const [k, v] of body.entries()) { + parts.push(k + '=' + (typeof v === 'string' ? v : '')); + } + bodyText = parts.join('&'); + } + } catch (_) {} + + const blockType = shouldBlock(bodyText, this._fgStealthUrl, this._fgStealthMethod); + if (blockType) { + console.log(`[FocusGram Stealth] Filtered XHR: ${blockType} -> ${this._fgStealthUrl}`); + const payload = createMockPayload(blockType, this._fgStealthUrl, bodyText); + Object.defineProperty(this, 'readyState', { value: 4, writable: true }); + Object.defineProperty(this, 'status', { value: 200, writable: true }); + Object.defineProperty(this, 'responseText', { value: JSON.stringify(payload), writable: true }); + Object.defineProperty(this, 'response', { value: JSON.stringify(payload), writable: true }); + setTimeout(() => { + this.dispatchEvent(new Event('readystatechange')); + this.dispatchEvent(new Event('load')); + }, 10); + return; + } + + return originalXhrSend.apply(this, arguments); + }; + + // Intercept WebSocket frames (MQTT typing indicators and read receipts). + if (typeof WebSocket !== 'undefined' && WebSocket.prototype && WebSocket.prototype.send) { + if (!window.__FG_STEALTH_WS_HOOKED__) { + window.__FG_STEALTH_WS_HOOKED__ = true; + const originalWsSend = WebSocket.prototype.send; + const wsUrlMap = new WeakMap(); + + const OriginalWs = window.WebSocket; + window.WebSocket = new Proxy(OriginalWs, { + construct(target, args, newTarget) { + const ws = Reflect.construct(target, args, newTarget); + try { + wsUrlMap.set(ws, String(args[0] || '')); + } catch (_) {} + return ws; + } + }); + + WebSocket.prototype.send = function (data) { + try { + const socketUrl = wsUrlMap.get(this) || this.url || ''; + let text = ''; + if (typeof data === 'string') { + text = data; + } else if (data instanceof ArrayBuffer) { + text = new TextDecoder().decode(data); + } else if (ArrayBuffer.isView(data)) { + text = new TextDecoder().decode(data); + } + const blockType = shouldBlock(text, socketUrl, 'WS'); + if (blockType) { + console.log(`[FocusGram Stealth] Filtered WebSocket frame: ${blockType} -> ${socketUrl}`); + return; // Drop silently + } + } catch (e) { + console.warn('[FocusGram Stealth] WebSocket send check error:', e); + } + return originalWsSend.apply(this, arguments); + }; + } + } + + // Report false for document focus and hidden for visibility on stories to suppress auto-seen telemetry. + const originalHasFocus = document.hasFocus.bind(document); + document.hasFocus = function () { + const config = window.__FOCUSGRAM_GHOST_CONFIG__; + const storyActive = config && config.enabled && (config.hideStorySeen !== undefined ? config.hideStorySeen : config.igStory); + if (storyActive && window.location.pathname.startsWith('/stories/')) { + return false; + } + return originalHasFocus(); + }; + + try { + const originalVisibilityState = Object.getOwnPropertyDescriptor(Document.prototype, 'visibilityState'); + Object.defineProperty(document, 'visibilityState', { + get: function () { + const config = window.__FOCUSGRAM_GHOST_CONFIG__; + const storyActive = config && config.enabled && (config.hideStorySeen !== undefined ? config.hideStorySeen : config.igStory); + if (storyActive && window.location.pathname.startsWith('/stories/')) { + return 'hidden'; + } + return originalVisibilityState && originalVisibilityState.get ? + originalVisibilityState.get.call(document) : 'visible'; + }, + configurable: true + }); + + const originalHidden = Object.getOwnPropertyDescriptor(Document.prototype, 'hidden'); + Object.defineProperty(document, 'hidden', { + get: function () { + const config = window.__FOCUSGRAM_GHOST_CONFIG__; + const storyActive = config && config.enabled && (config.hideStorySeen !== undefined ? config.hideStorySeen : config.igStory); + if (storyActive && window.location.pathname.startsWith('/stories/')) { + return true; + } + return originalHidden && originalHidden.get ? + originalHidden.get.call(document) : false; + }, + configurable: true + }); + } catch (_) {} + + // Sync updated privacy toggles from host and extension messages. + function applyGhostSettings(source) { + if (!source) return; + const enabled = source.enabled !== undefined ? !!source.enabled : window.__FOCUSGRAM_GHOST_CONFIG__.enabled; + const story = source.hideStorySeen !== undefined ? !!source.hideStorySeen : (source.igStory !== undefined ? !!source.igStory : window.__FOCUSGRAM_GHOST_CONFIG__.igStory); + const seen = source.hideDirectSeen !== undefined ? !!source.hideDirectSeen : (source.igSeen !== undefined ? !!source.igSeen : window.__FOCUSGRAM_GHOST_CONFIG__.igSeen); + const typing = source.hideTyping !== undefined ? !!source.hideTyping : (source.igTyping !== undefined ? !!source.igTyping : window.__FOCUSGRAM_GHOST_CONFIG__.igTyping); + Object.assign(window.__FOCUSGRAM_GHOST_CONFIG__, { + enabled: enabled, + hideStorySeen: story, + hideDirectSeen: seen, + hideTyping: typing, + igStory: story, + igSeen: seen, + igTyping: typing + }); + } + + window.addEventListener("message", (event) => { + if (!event.data) return; + if (event.data.type === "GHOSTIFY_SETTINGS_UPDATE" && event.data.settings) { + applyGhostSettings(event.data.settings); + } + if (event.data.source === "FOCUSGRAM_HOST" && (event.data.type === "FOCUSGRAM_UPDATE" || event.data.type === "STEALTH_UPDATE")) { + const ghost = event.data.ghost || event.data.config || {}; + applyGhostSettings(ghost); + } + }); + + console.log("[FocusGram] Stealth Privacy Engine active."); +})(); diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/FocusGramApp.kt b/app/src/main/kotlin/com/ujwal/focusgram/FocusGramApp.kt similarity index 85% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/FocusGramApp.kt rename to app/src/main/kotlin/com/ujwal/focusgram/FocusGramApp.kt index f25e432..dded6b8 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/FocusGramApp.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/FocusGramApp.kt @@ -12,9 +12,10 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch /** - * FocusGramApp - * Application entry point for FocusGram Native. - * Manages global singleton lifecycles such as GeckoRuntime, Room Database, and Settings DataStore. + * Application entry point managing process isolation, storage singletons, and engine pre-warming. + * + * GeckoView spawns sandboxed child processes for tab and compositor isolation; service initialization + * is constrained exclusively to the primary application process. */ class FocusGramApp : Application() { @@ -40,21 +41,19 @@ class FocusGramApp : Application() { Log.i(TAG, "FocusGram Native Application starting up on main process...") - // Initialize local persistence & security components database = FocusGramDatabase.getInstance(this) settingsRepository = SettingsRepository(this) encryptedStorage = EncryptedStorage(this) - // Pre-warm GeckoRuntime, mount Content Blocker WebExtension, and run legacy migration + // Pre-warming GeckoRuntime and mounting uBlock on Main avoids cold-start frame drops + // when the primary WebView composable binds to the session manager. applicationScope.launch(Dispatchers.Main) { try { - // 1. Migrate legacy Flutter data if present kotlinx.coroutines.withContext(Dispatchers.IO) { val migrator = LegacyFlutterDataMigrator(this@FocusGramApp, settingsRepository, database) migrator.checkAndMigrate() } - // 2. Pre-warm GeckoRuntime singleton & mount Content Blocker on UI thread val manager = GeckoRuntimeManager.getInstance(this@FocusGramApp) manager.extensionManager.initializeUBlockOrigin() Log.i(TAG, "GeckoRuntime & Content Blocker initialized successfully.") diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/MainActivity.kt b/app/src/main/kotlin/com/ujwal/focusgram/MainActivity.kt similarity index 78% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/MainActivity.kt rename to app/src/main/kotlin/com/ujwal/focusgram/MainActivity.kt index 2d53f0f..11eec2f 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/MainActivity.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/MainActivity.kt @@ -10,9 +10,10 @@ import com.ujwal.focusgram.core.ui.theme.FocusGramTheme import com.ujwal.focusgram.ui.FocusGramApp /** - * MainActivity - * Single-Activity host for Jetpack Compose UI and GeckoView container. - * Requests native 120Hz/high display refresh rate for fluid scrolling. + * Single-Activity host mounting Compose UI and coordinating window attributes. + * + * Requests the highest available display refresh mode (e.g., 90Hz/120Hz) on supported hardware + * and keeps window-level task descriptions aligned with the active launcher icon alias. */ class MainActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -35,6 +36,17 @@ class MainActivity : FragmentActivity() { } catch (_: Exception) {} } + override fun onStop() { + super.onStop() + // Ensure Live Session Time Counter notification auto-dismisses immediately when minimized + com.ujwal.focusgram.feature.screentime.ScreenTimeService.stopSession(this) + } + + override fun onDestroy() { + super.onDestroy() + com.ujwal.focusgram.feature.screentime.ScreenTimeService.stopSession(this) + } + private fun applyHighRefreshRate() { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/database/Daos.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/database/Daos.kt similarity index 100% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/database/Daos.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/database/Daos.kt diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/database/Entities.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/database/Entities.kt similarity index 74% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/database/Entities.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/database/Entities.kt index bc6fe8d..cb8860f 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/database/Entities.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/database/Entities.kt @@ -4,13 +4,12 @@ import androidx.room.Entity import androidx.room.PrimaryKey /** - * ScreenTimeRecord - * Tracks total on-device Instagram usage seconds per calendar date. + * Daily on-device screen time total and Reel consumption count. */ @Entity(tableName = "screen_time_records") data class ScreenTimeRecord( @PrimaryKey - val date: String, // Format: YYYY-MM-DD + val date: String, // ISO date string: YYYY-MM-DD val seconds: Int, val reelsSeconds: Int = 0, val reelsWatched: Int = 0, @@ -18,9 +17,8 @@ data class ScreenTimeRecord( ) /** - * ReelsHistoryItem - * Local client-side history of reels viewed by the user. - * Zero video media downloaded or cached to disk. + * Metadata record for a viewed Reel. Stores only title and author URL; + * raw video streams are never persisted. */ @Entity(tableName = "reels_history") data class ReelsHistoryItem( @@ -35,8 +33,7 @@ data class ReelsHistoryItem( ) /** - * SessionRecord - * Detailed session records for mindful digital wellness analytics. + * Timestamped session boundary recording elapsed usage intervals. */ @Entity(tableName = "session_records") data class SessionRecord( @@ -50,8 +47,7 @@ data class SessionRecord( ) /** - * SnapshotItem - * Saved offline DOM bookmarks and page state for offline viewing. + * Serialized HTML content and metadata for offline post or thread review. */ @Entity(tableName = "saved_snapshots") data class SnapshotItem( diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/database/FocusGramDatabase.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/database/FocusGramDatabase.kt similarity index 86% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/database/FocusGramDatabase.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/database/FocusGramDatabase.kt index 559bb78..3774b22 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/database/FocusGramDatabase.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/database/FocusGramDatabase.kt @@ -6,9 +6,7 @@ import androidx.room.Room import androidx.room.RoomDatabase /** - * FocusGramDatabase - * 100% on-device SQLite database managed by Room. - * Stores screen time metrics, reels history, focus sessions, and saved offline snapshots. + * Room SQLite database for screen time intervals, reels history, and snapshots. */ @Database( entities = [ @@ -40,6 +38,7 @@ abstract class FocusGramDatabase : RoomDatabase() { FocusGramDatabase::class.java, DB_NAME ) + // Drops and recreates tables on version bump; historical tables are ephemeral local caches .fallbackToDestructiveMigration() .build() .also { instance = it } diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/datastore/LegacyFlutterDataMigrator.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/datastore/LegacyFlutterDataMigrator.kt similarity index 87% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/datastore/LegacyFlutterDataMigrator.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/datastore/LegacyFlutterDataMigrator.kt index d367eca..21879fc 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/datastore/LegacyFlutterDataMigrator.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/datastore/LegacyFlutterDataMigrator.kt @@ -11,10 +11,8 @@ import org.json.JSONArray import org.json.JSONObject /** - * LegacyFlutterDataMigrator - * Reads FlutterSharedPreferences.xml on first launch of FocusGram Native Kotlin - * and migrates all existing toggles, screen time data, reels history, and credit balances into - * Jetpack DataStore and Room Database. + * One-time migrator that reads legacy Flutter SharedPreferences from disk + * and transfers settings, screen time metrics, and reel history into DataStore and Room. */ class LegacyFlutterDataMigrator( private val context: Context, @@ -40,7 +38,6 @@ class LegacyFlutterDataMigrator( Log.i(TAG, "Found ${allEntries.size} entries in FlutterSharedPreferences. Beginning migration...") try { - // 1. Migrate Focus & Content Toggles allEntries[key("no_ads")]?.let { if (it is Boolean) settingsRepository.setNoAds(it) } allEntries[key("no_stories")]?.let { if (it is Boolean) settingsRepository.setNoStories(it) } allEntries[key("no_reels")]?.let { if (it is Boolean) settingsRepository.setNoReels(it) } @@ -54,8 +51,8 @@ class LegacyFlutterDataMigrator( allEntries[key("hide_follower_counts")]?.let { if (it is Boolean) settingsRepository.setHideFollowerCounts(it) } allEntries[key("hide_shop_tab")]?.let { if (it is Boolean) settingsRepository.setHideShopTab(it) } allEntries[key("minimal_mode_enabled")]?.let { if (it is Boolean) settingsRepository.setMinimalMode(it) } + allEntries[key("set_sanitize_links")]?.let { if (it is Boolean) settingsRepository.setSanitizeLinks(it) } - // 2. Migrate Mindful Gates & Wellness val breathGateEnabled = (allEntries[key("set_show_breath_gate")] as? Boolean) ?: true val breathGateSeconds = (allEntries[key("breath_gate_seconds")] as? Number)?.toInt() ?: 10 settingsRepository.setBreathGate(breathGateEnabled, breathGateSeconds) @@ -66,26 +63,17 @@ class LegacyFlutterDataMigrator( allEntries[key("grayscale_enabled")]?.let { if (it is Boolean) settingsRepository.setGrayscale(it) } - // 3. Migrate Credits & Ad Balances - val instaMin = (allEntries[key("insta_min")] as? Number)?.toInt() ?: 0 - if (instaMin > 0) settingsRepository.addInstaMinutes(instaMin) - - val reelsMin = (allEntries[key("reels_min")] as? Number)?.toInt() ?: 0 - if (reelsMin > 0) settingsRepository.addReelsMinutes(reelsMin) - - // 4. Migrate Screen Time Records from JSON string val rawScreenTime = allEntries[key("screen_time_data")] as? String if (!rawScreenTime.isNullOrBlank()) { migrateScreenTime(rawScreenTime) } - // 5. Migrate Reels History from JSON string val rawReelsHistory = allEntries[key("reels_history")] as? String if (!rawReelsHistory.isNullOrBlank()) { migrateReelsHistory(rawReelsHistory) } - // Mark migration complete in legacy prefs + // Flag migration so subsequent boots bypass legacy preferences inspection sharedPrefs.edit().putBoolean(KEY_MIGRATED_MARKER, true).apply() Log.i(TAG, "✓ FlutterSharedPreferences data migration completed successfully!") @@ -136,6 +124,7 @@ class LegacyFlutterDataMigrator( } } + // Flutter's shared_preferences package prepends "flutter." to all stored keys private fun key(flutterKey: String) = "flutter.$flutterKey" companion object { diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/datastore/SettingsRepository.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/datastore/SettingsRepository.kt similarity index 81% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/datastore/SettingsRepository.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/datastore/SettingsRepository.kt index 38b3f9f..c1eb9f9 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/datastore/SettingsRepository.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/datastore/SettingsRepository.kt @@ -20,9 +20,7 @@ import org.json.JSONObject private val Context.dataStore: DataStore by preferencesDataStore(name = "focusgram_settings") /** - * SettingsRepository - * Reactive Jetpack DataStore repository managing all focus toggles, - * digital wellness guardrails, mindful gate parameters, and rewarded credit balances on-device. + * Persists toggles, cooldown thresholds, and focus metrics via Preferences DataStore. */ class SettingsRepository(private val context: Context) { @@ -54,35 +52,35 @@ class SettingsRepository(private val context: Context) { requireWordChallenge = prefs[KEY_REQUIRE_WORD_CHALLENGE] ?: true, wordChallengeCount = prefs[KEY_WORD_CHALLENGE_COUNT] ?: 30, effortFrictionEnabled = prefs[KEY_EFFORT_FRICTION_ENABLED] ?: true, - cooldownMinutes = prefs[KEY_COOLDOWN_MINUTES] ?: 15, + cooldownMinutes = prefs[KEY_COOLDOWN_MINUTES] ?: 5, dailyLimitMinutes = prefs[KEY_DAILY_LIMIT_MINUTES] ?: 60, dailyLimitAdClaimedDate = prefs[KEY_DAILY_LIMIT_AD_DATE] ?: "", + dailyLimitExtensionsCount = prefs[KEY_DAILY_LIMIT_EXTENSIONS_COUNT] ?: 0, grayscaleEnabled = prefs[KEY_GRAYSCALE_ENABLED] ?: false, smoothScroll120Hz = prefs[KEY_SMOOTH_SCROLL_120HZ] ?: true, reelsHistoryEnabled = prefs[KEY_REELS_HISTORY_ENABLED] ?: true, - reelsMinutes = prefs[KEY_REELS_MINUTES] ?: 0, - instaMinutes = prefs[KEY_INSTA_MINUTES] ?: 0, - adsWatchedToday = prefs[KEY_ADS_WATCHED_TODAY] ?: 0, adsWatchedAllTime = prefs[KEY_ADS_WATCHED_ALL_TIME] ?: 0, lastAdDate = prefs[KEY_LAST_AD_DATE] ?: "", - userLevel = prefs[KEY_USER_LEVEL] ?: 1, - userXP = prefs[KEY_USER_XP] ?: 0, appLockEnabled = prefs[KEY_APP_LOCK_ENABLED] ?: false, biometricEnabled = prefs[KEY_BIOMETRIC_ENABLED] ?: false, autoLockOnBackground = prefs[KEY_AUTO_LOCK_BACKGROUND] ?: true, - isFirstRun = prefs[KEY_IS_FIRST_RUN] ?: false, + isFirstRun = prefs[KEY_IS_FIRST_RUN] ?: true, startupPage = prefs[KEY_STARTUP_PAGE] ?: "home", videoDownloadEnabled = prefs[KEY_VIDEO_DOWNLOAD_ENABLED] ?: false, sanitizeLinks = prefs[KEY_SANITIZE_LINKS] ?: true, notifySessionEnd = prefs[KEY_NOTIFY_SESSION_END] ?: false, - notifyPersistent = prefs[KEY_NOTIFY_PERSISTENT] ?: false, + notifyPersistent = prefs[KEY_NOTIFY_PERSISTENT] ?: true, + notifyEvery5Minutes = prefs[KEY_NOTIFY_EVERY_5_MINUTES] ?: true, portraitOnly = prefs[KEY_PORTRAIT_ONLY] ?: true, + showSessionTimeInBottomBar = prefs[KEY_SHOW_SESSION_TIME_IN_BOTTOM_BAR] ?: true, appIcon = prefs[KEY_APP_ICON] ?: "default", customIconsEnabled = prefs[KEY_CUSTOM_ICONS_ENABLED] ?: false, + storySearchEnabled = prefs[KEY_STORY_SEARCH_ENABLED] ?: true, + customBioFontsEnabled = prefs[KEY_CUSTOM_BIO_FONTS_ENABLED] ?: true, chatLockMode = prefs[KEY_CHAT_LOCK_MODE] ?: if (prefs[KEY_CHAT_LOCK_ALL] == true) "whole_dm" else "individual", chatLockAll = (prefs[KEY_CHAT_LOCK_MODE] ?: if (prefs[KEY_CHAT_LOCK_ALL] == true) "whole_dm" else "individual") == "whole_dm", @@ -94,7 +92,14 @@ class SettingsRepository(private val context: Context) { ghostModeEnabled = prefs[KEY_GHOST_MODE_ENABLED] ?: false, ghostHideStorySeen = prefs[KEY_GHOST_HIDE_STORY_SEEN] ?: true, ghostHideDirectSeen = prefs[KEY_GHOST_HIDE_DIRECT_SEEN] ?: true, - ghostHideTypingIndicator = prefs[KEY_GHOST_HIDE_TYPING] ?: true + ghostHideTypingIndicator = prefs[KEY_GHOST_HIDE_TYPING] ?: true, + + activeSessionDurationMinutes = prefs[KEY_ACTIVE_SESSION_DURATION_MINUTES] ?: 0, + activeSessionRemainingSeconds = prefs[KEY_ACTIVE_SESSION_REMAINING_SECONDS] ?: 0, + activeSessionElapsedSeconds = prefs[KEY_ACTIVE_SESSION_ELAPSED_SECONDS] ?: 0, + activeSessionReelsWatched = prefs[KEY_ACTIVE_SESSION_REELS_WATCHED] ?: 0, + activeSessionReelCodes = prefs[KEY_ACTIVE_SESSION_REEL_CODES] ?: emptySet(), + activeSessionIsActive = prefs[KEY_ACTIVE_SESSION_IS_ACTIVE] ?: false ) } @@ -155,6 +160,7 @@ class SettingsRepository(private val context: Context) { prefs[KEY_BREATH_GATE_SECONDS] = seconds } } + suspend fun setShowBreathGate(enabled: Boolean) = setBreathGate(enabled) suspend fun setEffortFriction(enabled: Boolean, wordCount: Int = 30) { context.dataStore.edit { prefs -> @@ -171,9 +177,10 @@ class SettingsRepository(private val context: Context) { suspend fun extendDailyLimit5Minutes(today: String) { context.dataStore.edit { prefs -> - val current = prefs[KEY_DAILY_LIMIT_MINUTES] ?: 60 - prefs[KEY_DAILY_LIMIT_MINUTES] = current + 5 + val currentDate = prefs[KEY_DAILY_LIMIT_AD_DATE] ?: "" + val currentCount = if (currentDate == today) prefs[KEY_DAILY_LIMIT_EXTENSIONS_COUNT] ?: 0 else 0 prefs[KEY_DAILY_LIMIT_AD_DATE] = today + prefs[KEY_DAILY_LIMIT_EXTENSIONS_COUNT] = (currentCount + 1).coerceAtMost(2) } } @@ -182,59 +189,35 @@ class SettingsRepository(private val context: Context) { suspend fun setSanitizeLinks(enabled: Boolean) = setBoolean(KEY_SANITIZE_LINKS, enabled) suspend fun setNotifySessionEnd(enabled: Boolean) = setBoolean(KEY_NOTIFY_SESSION_END, enabled) suspend fun setNotifyPersistent(enabled: Boolean) = setBoolean(KEY_NOTIFY_PERSISTENT, enabled) + suspend fun setNotifyEvery5Minutes(enabled: Boolean) = setBoolean(KEY_NOTIFY_EVERY_5_MINUTES, enabled) suspend fun setPortraitOnly(enabled: Boolean) = setBoolean(KEY_PORTRAIT_ONLY, enabled) + suspend fun setShowSessionTimeInBottomBar(enabled: Boolean) = setBoolean(KEY_SHOW_SESSION_TIME_IN_BOTTOM_BAR, enabled) - suspend fun addInstaMinutes(minutes: Int) { + suspend fun saveActiveSession( + durationMinutes: Int, + remainingSeconds: Int, + elapsedSeconds: Int, + reelsWatched: Int = 0, + reelCodes: Set = emptySet(), + isActive: Boolean + ) { context.dataStore.edit { prefs -> - val current = prefs[KEY_INSTA_MINUTES] ?: 0 - prefs[KEY_INSTA_MINUTES] = (current + minutes).coerceAtLeast(0) + prefs[KEY_ACTIVE_SESSION_DURATION_MINUTES] = durationMinutes + prefs[KEY_ACTIVE_SESSION_REMAINING_SECONDS] = remainingSeconds + prefs[KEY_ACTIVE_SESSION_ELAPSED_SECONDS] = elapsedSeconds + prefs[KEY_ACTIVE_SESSION_REELS_WATCHED] = reelsWatched + prefs[KEY_ACTIVE_SESSION_REEL_CODES] = reelCodes + prefs[KEY_ACTIVE_SESSION_IS_ACTIVE] = isActive } } - suspend fun deductInstaMinute() { + suspend fun clearActiveSession() { context.dataStore.edit { prefs -> - val current = prefs[KEY_INSTA_MINUTES] ?: 0 - if (current > 0) prefs[KEY_INSTA_MINUTES] = current - 1 - } - } - - suspend fun addReelsMinutes(minutes: Int) { - context.dataStore.edit { prefs -> - val current = prefs[KEY_REELS_MINUTES] ?: 0 - prefs[KEY_REELS_MINUTES] = (current + minutes).coerceAtLeast(0) - } - } - - suspend fun deductReelsMinute() { - context.dataStore.edit { prefs -> - val current = prefs[KEY_REELS_MINUTES] ?: 0 - if (current > 0) prefs[KEY_REELS_MINUTES] = current - 1 - } - } - - suspend fun recordRewardedAd(todayKey: String, minutesEarned: Int = 2) { - context.dataStore.edit { prefs -> - val lastDate = prefs[KEY_LAST_AD_DATE] ?: "" - val todayCount = if (lastDate == todayKey) (prefs[KEY_ADS_WATCHED_TODAY] ?: 0) else 0 - val allTime = (prefs[KEY_ADS_WATCHED_ALL_TIME] ?: 0) + 1 - - prefs[KEY_LAST_AD_DATE] = todayKey - prefs[KEY_ADS_WATCHED_TODAY] = todayCount + 1 - prefs[KEY_ADS_WATCHED_ALL_TIME] = allTime - - // Credit the minutes - val currentInsta = prefs[KEY_INSTA_MINUTES] ?: 0 - prefs[KEY_INSTA_MINUTES] = currentInsta + minutesEarned - } - } - - suspend fun addXP(amount: Int) { - context.dataStore.edit { prefs -> - val currentXP = prefs[KEY_USER_XP] ?: 0 - val newXP = currentXP + amount - prefs[KEY_USER_XP] = newXP - // 100 XP per level - prefs[KEY_USER_LEVEL] = (newXP / 100) + 1 + prefs[KEY_ACTIVE_SESSION_IS_ACTIVE] = false + prefs[KEY_ACTIVE_SESSION_REMAINING_SECONDS] = 0 + prefs[KEY_ACTIVE_SESSION_ELAPSED_SECONDS] = 0 + prefs[KEY_ACTIVE_SESSION_REELS_WATCHED] = 0 + prefs[KEY_ACTIVE_SESSION_REEL_CODES] = emptySet() } } @@ -363,6 +346,32 @@ class SettingsRepository(private val context: Context) { suspend fun setUseSeparateChatPin(enabled: Boolean) = setBoolean(KEY_USE_SEPARATE_CHAT_PIN, enabled) suspend fun setChatBiometricEnabled(enabled: Boolean) = setBoolean(KEY_CHAT_BIOMETRIC_ENABLED, enabled) + suspend fun setFirstRunCompleted() { + context.dataStore.edit { prefs -> + prefs[KEY_IS_FIRST_RUN] = false + } + } + + suspend fun resetFirstRun() { + context.dataStore.edit { prefs -> + prefs[KEY_IS_FIRST_RUN] = true + } + } + + suspend fun setDismissedUpdateVersion(version: String) { + context.dataStore.edit { prefs -> + prefs[KEY_LAST_DISMISSED_UPDATE] = version + } + } + + suspend fun setStorySearchEnabled(enabled: Boolean) = setBoolean(KEY_STORY_SEARCH_ENABLED, enabled) + suspend fun setCustomBioFontsEnabled(enabled: Boolean) = setBoolean(KEY_CUSTOM_BIO_FONTS_ENABLED, enabled) + + suspend fun getDismissedUpdateVersion(): String? { + val prefs = context.dataStore.data.first() + return prefs[KEY_LAST_DISMISSED_UPDATE] + } + companion object { val KEY_NO_ADS = booleanPreferencesKey("no_ads") val KEY_NO_STORIES = booleanPreferencesKey("no_stories") @@ -391,30 +400,31 @@ class SettingsRepository(private val context: Context) { val KEY_COOLDOWN_MINUTES = intPreferencesKey("cooldown_minutes") val KEY_DAILY_LIMIT_MINUTES = intPreferencesKey("daily_limit_minutes") val KEY_DAILY_LIMIT_AD_DATE = stringPreferencesKey("daily_limit_ad_date") + val KEY_DAILY_LIMIT_EXTENSIONS_COUNT = intPreferencesKey("daily_limit_extensions_count") val KEY_GRAYSCALE_ENABLED = booleanPreferencesKey("grayscale_enabled") val KEY_SMOOTH_SCROLL_120HZ = booleanPreferencesKey("smooth_scroll_120hz") val KEY_REELS_HISTORY_ENABLED = booleanPreferencesKey("reels_history_enabled") - val KEY_REELS_MINUTES = intPreferencesKey("reels_min") - val KEY_INSTA_MINUTES = intPreferencesKey("insta_min") - val KEY_ADS_WATCHED_TODAY = intPreferencesKey("ads_today_count") val KEY_ADS_WATCHED_ALL_TIME = intPreferencesKey("ads_all_time") val KEY_LAST_AD_DATE = stringPreferencesKey("ads_today_date") - val KEY_USER_LEVEL = intPreferencesKey("user_level") - val KEY_USER_XP = intPreferencesKey("user_xp") val KEY_APP_LOCK_ENABLED = booleanPreferencesKey("app_lock_enabled") val KEY_BIOMETRIC_ENABLED = booleanPreferencesKey("biometric_enabled") val KEY_AUTO_LOCK_BACKGROUND = booleanPreferencesKey("auto_lock_background") val KEY_IS_FIRST_RUN = booleanPreferencesKey("set_is_first_run") + val KEY_LAST_DISMISSED_UPDATE = stringPreferencesKey("last_dismissed_update_version") val KEY_STARTUP_PAGE = stringPreferencesKey("startup_page") val KEY_VIDEO_DOWNLOAD_ENABLED = booleanPreferencesKey("video_download_enabled") val KEY_SANITIZE_LINKS = booleanPreferencesKey("set_sanitize_links") val KEY_NOTIFY_SESSION_END = booleanPreferencesKey("set_notify_session_end") val KEY_NOTIFY_PERSISTENT = booleanPreferencesKey("set_notify_persistent") + val KEY_NOTIFY_EVERY_5_MINUTES = booleanPreferencesKey("set_notify_every_5_minutes") val KEY_PORTRAIT_ONLY = booleanPreferencesKey("portrait_only") + val KEY_SHOW_SESSION_TIME_IN_BOTTOM_BAR = booleanPreferencesKey("show_session_time_in_bottom_bar") + val KEY_STORY_SEARCH_ENABLED = booleanPreferencesKey("story_search_enabled") + val KEY_CUSTOM_BIO_FONTS_ENABLED = booleanPreferencesKey("custom_bio_fonts_enabled") val KEY_GHOST_MODE_ENABLED = booleanPreferencesKey("ghost_mode_enabled") val KEY_GHOST_HIDE_STORY_SEEN = booleanPreferencesKey("ghost_hide_story_seen") @@ -422,6 +432,13 @@ class SettingsRepository(private val context: Context) { val KEY_GHOST_HIDE_TYPING = booleanPreferencesKey("ghost_hide_typing") val KEY_LAST_SESSION_END_TIMESTAMP = longPreferencesKey("last_session_end_timestamp") + val KEY_ACTIVE_SESSION_DURATION_MINUTES = intPreferencesKey("active_session_duration_minutes") + val KEY_ACTIVE_SESSION_REMAINING_SECONDS = intPreferencesKey("active_session_remaining_seconds") + val KEY_ACTIVE_SESSION_ELAPSED_SECONDS = intPreferencesKey("active_session_elapsed_seconds") + val KEY_ACTIVE_SESSION_REELS_WATCHED = intPreferencesKey("active_session_reels_watched") + val KEY_ACTIVE_SESSION_REEL_CODES = stringSetPreferencesKey("active_session_reel_codes") + val KEY_ACTIVE_SESSION_IS_ACTIVE = booleanPreferencesKey("active_session_is_active") + val KEY_APP_ICON = stringPreferencesKey("app_icon") val KEY_CUSTOM_ICONS_ENABLED = booleanPreferencesKey("custom_icons_enabled") val KEY_CHAT_LOCK_MODE = stringPreferencesKey("chat_lock_mode") diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/ExtensionManager.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/engine/ExtensionManager.kt similarity index 94% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/ExtensionManager.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/engine/ExtensionManager.kt index d73ae30..9ae0c62 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/ExtensionManager.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/engine/ExtensionManager.kt @@ -13,10 +13,8 @@ import org.mozilla.geckoview.GeckoRuntime import org.mozilla.geckoview.WebExtension /** - * ExtensionManager - * Manages the lifecycle, installation, and two-way messaging of built-in WebExtensions in GeckoView. - * Provides real-time synchronization between Android Settings and the in-page Content Script, - * handling SPA navigation, logo actions, typing focus, ad-blocking toggling, and Ghost Mode. + * Bridges between native Android settings and the bundled uBlock Origin / FocusGram + * content script over GeckoView WebExtension messaging ports. */ class ExtensionManager(private val runtime: GeckoRuntime) { @@ -306,6 +304,8 @@ class ExtensionManager(private val runtime: GeckoRuntime) { put("minimalMode", settings.minimalModeEnabled) put("videoDownloadEnabled", settings.videoDownloadEnabled) put("portraitOnly", settings.portraitOnly) + put("storySearchEnabled", settings.storySearchEnabled) + put("customBioFontsEnabled", settings.customBioFontsEnabled) put("ghostModeEnabled", settings.ghostModeEnabled) put("ghostHideStorySeen", settings.ghostHideStorySeen) put("ghostHideDirectSeen", settings.ghostHideDirectSeen) @@ -363,7 +363,7 @@ class ExtensionManager(private val runtime: GeckoRuntime) { } /** - * Senior Blueprint: Sets feed post reveal mode ("tap" or "hold") dynamically. + * Updates feed post reveal mode ("tap" or "hold") in the running content script. */ fun setRevealMode(mode: String) { try { @@ -379,7 +379,7 @@ class ExtensionManager(private val runtime: GeckoRuntime) { } /** - * Executes instant client-side SPA navigation inside Instagram web without full reload. + * Directs the in-page content script to navigate via client-side History pushState. */ fun navigateSpa(path: String) { try { @@ -394,16 +394,16 @@ class ExtensionManager(private val runtime: GeckoRuntime) { } /** - * Opens the authenticated user's profile inside Instagram web. + * Navigates to the current user's profile without reloading the page. */ fun openProfileSpa() { go("profile") } /** - * Senior Blueprint: Triggers client-side SPA navigation in Instagram - * by clicking the real hidden navigation anchor. - * @param target "home", "search", "reels", "dm", "profile" + * Simulates a user click on Instagram's internal navigation anchor. + * + * @param target One of "home", "search", "reels", "dm", "profile". */ fun go(target: String) { try { @@ -434,8 +434,7 @@ class ExtensionManager(private val runtime: GeckoRuntime) { } /** - * Senior Blueprint: Synchronizes IslandMode (NORMAL, FLOATING, HIDDEN) - * with the WebExtension content script to dynamically update body padding. + * Signals navigation layout changes so the content script adjusts document body padding. */ fun setIslandMode(mode: IslandMode) { try { @@ -460,7 +459,7 @@ class ExtensionManager(private val runtime: GeckoRuntime) { suspend fun setUBlockEnabled(enabled: Boolean) = setContentBlockerEnabled(enabled) /** - * Sends request to in-page content script to extract and report active Reel video URL for downloading. + * Requests the content script to extract and report the active Reel video source URL. */ fun triggerReelDownload() { try { @@ -475,8 +474,7 @@ class ExtensionManager(private val runtime: GeckoRuntime) { } /** - * Sends the active Unicode font style selection to the WebExtension content script - * for real-time live bio transformation. + * Sends the selected Unicode character style to the content script for in-place text styling. */ fun setBioStyle(style: String) { try { diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramNavigationDelegate.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramNavigationDelegate.kt similarity index 86% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramNavigationDelegate.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramNavigationDelegate.kt index 958ec11..4bc4a35 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramNavigationDelegate.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramNavigationDelegate.kt @@ -7,6 +7,7 @@ import android.util.Log import org.mozilla.geckoview.AllowOrDeny import org.mozilla.geckoview.GeckoResult import org.mozilla.geckoview.GeckoSession +import org.mozilla.geckoview.WebRequestError /** * FocusGramNavigationDelegate @@ -27,18 +28,27 @@ class FocusGramNavigationDelegate( val host = uri.host?.lowercase() ?: "" val scheme = uri.scheme?.lowercase() ?: "" - // 1. Allow internal asset protocols, data URIs, and javascript evaluation + // Internal scheme evaluation if (scheme == "resource" || scheme == "about" || scheme == "data" || scheme == "blob" || scheme == "javascript") { return GeckoResult.fromValue(AllowOrDeny.ALLOW) } - // 2. Prevent deep-linking into the official native Instagram app + // Allow internal local app-private files + if (scheme == "file") { + val path = uri.path ?: "" + val appFilesPath = context.filesDir.absolutePath + if (path.startsWith(appFilesPath)) { + return GeckoResult.fromValue(AllowOrDeny.ALLOW) + } + } + + // Block attempts to hand off navigation to the official native Instagram app if (scheme == "instagram") { Log.d(TAG, "Prevented native Instagram app deep link: ${request.uri}") return GeckoResult.fromValue(AllowOrDeny.DENY) } - // 2b. Intercept native media download requests from in-page client scripts + // Custom scheme used by in-page downloader scripts to pass media URLs to native storage if (scheme == "focusgram-download") { val mediaUrl = uri.getQueryParameter("url") ?: "" val filename = uri.getQueryParameter("filename") ?: "" @@ -55,7 +65,7 @@ class FocusGramNavigationDelegate( return GeckoResult.fromValue(AllowOrDeny.DENY) } - // 3. Intercept navigation trap to suggested accounts (/explore/people) from Favorites + // Instagram traps users into /explore/people when Favorites feed is empty; force back home val path = uri.path?.lowercase() ?: "" if (path.contains("/explore/people")) { Log.i(TAG, "Intercepted suggested accounts trap (/explore/people). Redirecting to home feed.") @@ -63,7 +73,7 @@ class FocusGramNavigationDelegate( return GeckoResult.fromValue(AllowOrDeny.DENY) } - // 3b. Minimal Mode enforcement: redirect home feed, explore, reels, and individual posts to DMs inbox + // Minimal mode restrains usage exclusively to Direct Messages if (sessionManager.currentSettings.minimalModeEnabled && (host == "instagram.com" || host == "www.instagram.com")) { if (path == "/" || path == "" || path.startsWith("/explore") || path.startsWith("/reels") || path.startsWith("/reel") || path.startsWith("/p/")) { Log.i(TAG, "Minimal Mode active. Redirecting restricted path $path to /direct/inbox/") @@ -72,15 +82,12 @@ class FocusGramNavigationDelegate( } } - // 4. Allow Instagram, Meta CDN, and Authentication login domains + // Whitelisted domain tree (Instagram, CDN, Meta Account Center) if (isAllowedHost(host)) { return GeckoResult.fromValue(AllowOrDeny.ALLOW) } - // 5. For external links: - // ONLY launch external system browser if the navigation was triggered by an explicit user gesture - // (tapping an external link in bio/story) or opening a new tab (target="_blank"). - // NEVER launch external browser for background pings, telemetry, iframes, or non-user requests! + // Launch external browser only for intentional user taps, suppressing background redirects/telemetry val isExplicitUserNavigation = request.hasUserGesture || request.target == GeckoSession.NavigationDelegate.TARGET_WINDOW_NEW if (isExplicitUserNavigation && (scheme == "http" || scheme == "https")) { Log.i(TAG, "User clicked external link: ${request.uri}. Launching external browser intent.") @@ -147,8 +154,15 @@ class FocusGramNavigationDelegate( sessionManager.updateNavigationState(canBack = canGoBack, canForward = false) } - override fun onCanGoForward(session: GeckoSession, canGoForward: Boolean) { - // Will be called when forward navigation changes + override fun onCanGoForward(session: GeckoSession, canGoForward: Boolean) {} + + override fun onLoadError( + session: GeckoSession, + uri: String?, + error: WebRequestError + ): GeckoResult? { + Log.w(TAG, "Navigation error: uri=$uri, category=${error.category}, code=${error.code}") + return GeckoResult.fromValue(null) } private fun isAllowedHost(host: String): Boolean { @@ -202,7 +216,7 @@ class FocusGramNavigationDelegate( "instagr.am", "cdninstagram.com", "fbcdn.net", - // Required for Facebook Authentication & Account Center logins + // Meta Account Center & OAuth domains necessary for Instagram login "facebook.com", "m.facebook.com", "accountscenter.instagram.com", diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramPermissionDelegate.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramPermissionDelegate.kt similarity index 84% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramPermissionDelegate.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramPermissionDelegate.kt index f9eed62..dae68d6 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramPermissionDelegate.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramPermissionDelegate.kt @@ -10,9 +10,7 @@ import org.mozilla.geckoview.GeckoResult import org.mozilla.geckoview.GeckoSession /** - * FocusGramPermissionDelegate - * Handles web content and Android runtime permission requests (Camera, Microphone, Media) - * from GeckoView sessions while maintaining user privacy and strict origin isolation. + * Filters and delegates Android runtime and content origin permission requests from GeckoView. */ class FocusGramPermissionDelegate( private val context: Context, @@ -30,8 +28,8 @@ class FocusGramPermissionDelegate( ContextCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED } - // On Android 13+ (API 33+), READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE are deprecated - // and automatically denied by the OS. System file pickers do not require storage permissions. + // Android 13+ (API 33+) deprecates broad storage permissions and auto-denies them; + // modern media selection goes through SAF / Photo Picker instead. val actionableUngranted = if (android.os.Build.VERSION.SDK_INT >= 33) { ungranted.filter { it != android.Manifest.permission.READ_EXTERNAL_STORAGE && @@ -50,8 +48,7 @@ class FocusGramPermissionDelegate( Handler(Looper.getMainLooper()).post { try { onAndroidPermissionRequest.invoke(actionableUngranted.toTypedArray()) { granted -> - // Always grant to GeckoView so it proceeds to onFilePrompt() where - // native ActivityResultContracts (Photo Picker / SAF) handle file access securely. + // Grant to GeckoView so it advances to onFilePrompt where SAF handles the URI callback.grant() } } catch (e: Exception) { diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramPromptDelegate.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramPromptDelegate.kt similarity index 93% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramPromptDelegate.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramPromptDelegate.kt index 35a263d..d4fc4a7 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramPromptDelegate.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramPromptDelegate.kt @@ -11,9 +11,7 @@ import org.mozilla.geckoview.GeckoResult import org.mozilla.geckoview.GeckoSession /** - * FocusGramPromptDelegate - * Handles browser prompts (file picker, camera uploads, JavaScript alerts, password saves) - * through native Android Jetpack contracts while preventing data leakage and compositor freezing. + * Bridges GeckoView prompts (file uploads, dialogs, form submissions) to native Android handlers. */ class FocusGramPromptDelegate( private val context: Context, @@ -47,8 +45,7 @@ class FocusGramPromptDelegate( val result = GeckoResult() - // CRITICAL: GeckoView calls onFilePrompt on a background thread. - // ActivityResultLauncher.launch() MUST be called on the main Looper thread. + // GeckoView invokes onFilePrompt on a worker thread; launcher contracts require main looper Handler(Looper.getMainLooper()).post { try { handler.onPickFiles(mimeTypes, isMultiple, capture) { uris -> diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoResultExtensions.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoResultExtensions.kt similarity index 73% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoResultExtensions.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoResultExtensions.kt index cf6f8f0..28cb673 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoResultExtensions.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoResultExtensions.kt @@ -6,8 +6,10 @@ import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException /** - * Await extension for GeckoResult. - * Bridges Mozilla's reactive GeckoResult into Kotlin Coroutines seamlessly. + * Adapts GeckoView's callback-based [GeckoResult] to a cancellable coroutine. + * + * If the engine fails with a null exception, wraps it in [RuntimeException] + * because [suspendCancellableCoroutine.resumeWithException] requires a non-null Throwable. */ suspend fun GeckoResult.await(): T? = suspendCancellableCoroutine { continuation -> accept( diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoRuntimeManager.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoRuntimeManager.kt similarity index 87% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoRuntimeManager.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoRuntimeManager.kt index 2a53aff..1d961d4 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoRuntimeManager.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoRuntimeManager.kt @@ -7,14 +7,10 @@ import org.mozilla.geckoview.GeckoRuntime import org.mozilla.geckoview.GeckoRuntimeSettings /** - * GeckoRuntimeManager - * High-performance, privacy-hardened singleton managing the Mozilla GeckoView engine lifecycle. + * Initializes and owns the singleton [GeckoRuntime] lifecycle. * - * Configures: - * - ContentBlocking: ETP Strict, Social Tracking Protection, First-Party Cookie Isolation. - * - Extensions Process: Enabled for Content Blocker WebExtension execution. - * - Privacy: Query parameter stripping, DNT, Sec-GPC. - * - Hardening: about:config disabled in production, console output managed. + * Configures strict tracking protection (ETP strict, social tracking isolation, + * query parameter stripping) and enables WebExtension execution for client-side content scripts. */ class GeckoRuntimeManager private constructor(context: Context) { diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoSessionManager.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoSessionManager.kt similarity index 87% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoSessionManager.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoSessionManager.kt index d2fef14..edc2748 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoSessionManager.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/engine/GeckoSessionManager.kt @@ -12,14 +12,14 @@ import org.mozilla.geckoview.GeckoSession import org.mozilla.geckoview.GeckoSessionSettings /** - * GeckoSessionManager - * Manages the primary GeckoSession lifecycle, background suspension, memory reclamation, - * and page progress tracking for FocusGram. + * Manages the primary [GeckoSession] lifecycle, foreground/background media states, + * and DOM navigation bridge for Instagram web. */ class GeckoSessionManager(private val runtime: GeckoRuntime) { val session: GeckoSession var userScriptManager: UserScriptManager? = null + var extensionManager: ExtensionManager? = null var currentSettings: FocusSettings = FocusSettings() private val _isLoading = MutableStateFlow(false) @@ -55,6 +55,7 @@ class GeckoSessionManager(private val runtime: GeckoRuntime) { val isAuth = (url.contains("/accounts") && !url.contains("/accounts/edit")) || url.contains("/login") || url.contains("/signup") if (!isAuth) { injectBottomBarTrimmer() + extensionManager?.broadcastSettings(currentSettings) userScriptManager?.injectAll(session, currentSettings) } } @@ -67,7 +68,8 @@ class GeckoSessionManager(private val runtime: GeckoRuntime) { .allowJavascript(true) .userAgentMode(GeckoSessionSettings.USER_AGENT_MODE_MOBILE) .userAgentOverride(IOS_USER_AGENT) - .usePrivateMode(false) // Retain Instagram authentication cookies locally + // False keeps session cookies on disk; private mode clears them on process death + .usePrivateMode(false) .build() session = GeckoSession(settings) @@ -94,6 +96,7 @@ class GeckoSessionManager(private val runtime: GeckoRuntime) { val isAuth = (url.contains("/accounts") && !url.contains("/accounts/edit")) || url.contains("/login") || url.contains("/signup") if (isAuthenticated && !isAuth) { injectBottomBarTrimmer() + extensionManager?.broadcastSettings(currentSettings) userScriptManager?.injectAll(session, currentSettings) } } @@ -134,7 +137,7 @@ class GeckoSessionManager(private val runtime: GeckoRuntime) { fun onPause() { if (isOpen) { Log.d(TAG, "Suspending GeckoSession for background...") - session.setActive(false) + pauseMedia() } } @@ -167,7 +170,9 @@ class GeckoSessionManager(private val runtime: GeckoRuntime) { fun updateSettings(settings: FocusSettings) { currentSettings = settings - if (isAuthenticated) { + val url = _currentUrl.value.lowercase() + val isAuth = (url.contains("/accounts") && !url.contains("/accounts/edit")) || url.contains("/login") || url.contains("/signup") + if (!isAuth) { userScriptManager?.injectConfig(session, settings) } } @@ -297,14 +302,15 @@ class GeckoSessionManager(private val runtime: GeckoRuntime) { } /** - * Senior Blueprint: Ensures bottom navigation bar is marked and hidden - * using position: fixed and bottom bounds inspection. + * Identifies Instagram's fixed bottom tab bar in the DOM and suppresses it + * so it does not collide with the native Jetpack Compose Island navigation bar. */ fun injectBottomBarTrimmer() { val js = """javascript:(function() { try { var p = (window.location.pathname || '').toLowerCase(); - if (p.startsWith('/direct') || (p.startsWith('/accounts') && !p.startsWith('/accounts/edit')) || p.startsWith('/login') || p.startsWith('/signup')) return; + var h = (window.location.hostname || '').toLowerCase(); + if (p.startsWith('/direct') || (p.startsWith('/accounts') && !p.startsWith('/accounts/edit')) || p.startsWith('/login') || p.startsWith('/signup') || p.startsWith('/two_factor') || p.startsWith('/challenge') || p.startsWith('/onetap') || h.includes('facebook.com') || h.includes('accountscenter.')) return; var c = document.cookie || ''; if ((p === '/' || p === '') && !c.includes('sessionid=') && !c.includes('ds_user_id=')) return; if (document.getElementById('loginForm') || document.querySelector('form[action*="login"], input[name="password"]')) return; @@ -376,6 +382,25 @@ class GeckoSessionManager(private val runtime: GeckoRuntime) { } fun goBack(): Boolean { + val currentUrlStr = _currentUrl.value.lowercase() + val path = IslandMode.extractPath(currentUrlStr).substringBefore('?').substringBefore('#').trimEnd('/').ifEmpty { "/" } + + val isDmThread = path.startsWith("/direct/t/") || path.startsWith("/direct/threads/") || (path.startsWith("/direct/") && path != "/direct/inbox" && path != "/direct") + val isDmInbox = path == "/direct" || path == "/direct/inbox" + val isRootHome = path == "/" || path.isEmpty() + + if (isDmThread) { + navigateTo("https://www.instagram.com/direct/inbox/") + return true + } + if (isDmInbox) { + navigateTo("https://www.instagram.com/") + return true + } + if (isRootHome) { + return false + } + if (_canGoBack.value) { session.goBack() return true @@ -393,7 +418,10 @@ class GeckoSessionManager(private val runtime: GeckoRuntime) { return false } - fun attachNavigationDelegate(context: Context, onRouteChanged: ((String) -> Unit)? = null) { + fun attachNavigationDelegate( + context: Context, + onRouteChanged: ((String) -> Unit)? = null + ) { session.navigationDelegate = FocusGramNavigationDelegate(context, this, onRouteChanged) } @@ -408,9 +436,9 @@ class GeckoSessionManager(private val runtime: GeckoRuntime) { fun pauseMedia() { try { - session.setActive(false) - val pauseJs = "javascript:(function(){ try { document.querySelectorAll('video, audio').forEach(function(m){ m.pause(); }); } catch(_) {} })();" + val pauseJs = "javascript:(function(){ try { window.__fgMediaPaused = true; document.querySelectorAll('video, audio').forEach(function(m){ try { m.pause(); } catch(_) {} }); } catch(_) {} })();" session.loadUri(pauseJs) + session.setActive(false) } catch (e: Exception) { Log.w(TAG, "Error pausing media: ${e.message}") } @@ -419,6 +447,8 @@ class GeckoSessionManager(private val runtime: GeckoRuntime) { fun resumeMedia() { try { session.setActive(true) + val resumeJs = "javascript:(function(){ try { window.__fgMediaPaused = false; } catch(_) {} })();" + session.loadUri(resumeJs) } catch (e: Exception) { Log.w(TAG, "Error resuming media: ${e.message}") } @@ -435,6 +465,8 @@ class GeckoSessionManager(private val runtime: GeckoRuntime) { companion object { private const val TAG = "GeckoSessionManager" const val DEFAULT_URL = "https://www.instagram.com/" + + // Mobile Safari UA causes Instagram to serve touch-friendly web layouts without Google Play banners const val IOS_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1" } } diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/UserScriptManager.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/engine/UserScriptManager.kt similarity index 65% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/UserScriptManager.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/engine/UserScriptManager.kt index 0847e46..f9a5d1b 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/engine/UserScriptManager.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/engine/UserScriptManager.kt @@ -6,9 +6,7 @@ import com.ujwal.focusgram.core.model.FocusSettings import org.mozilla.geckoview.GeckoSession /** - * UserScriptManager - * Loads, configures, and evaluates FocusGram client-side scripts inside GeckoSession, - * including stealth privacy hooks, ad cosmetic filters, and UI guardrails. + * Reads asset scripts and evaluates them in the running GeckoSession document context. */ class UserScriptManager(private val context: Context) { @@ -26,8 +24,8 @@ class UserScriptManager(private val context: Context) { } /** - * Injects the dynamic FocusSettings JSON config into the web execution environment - * and broadcasts updates to active scripts. + * Serializes [FocusSettings] and chat lock state to `window.__FOCUSGRAM_CONFIG__` + * and dispatches a DOM custom event for active script listeners. */ fun injectConfig(session: GeckoSession, settings: FocusSettings) { val lockedIdsJson = org.json.JSONArray(settings.lockedChatIds.map { it.lowercase().trim().removePrefix("@") }).toString() @@ -53,10 +51,15 @@ class UserScriptManager(private val context: Context) { grayscale: ${settings.grayscaleEnabled}, minimalMode: ${settings.minimalModeEnabled}, videoDownloadEnabled: ${settings.videoDownloadEnabled}, - portraitOnly: ${settings.portraitOnly} + portraitOnly: ${settings.portraitOnly}, + storySearchEnabled: ${settings.storySearchEnabled}, + customBioFontsEnabled: ${settings.customBioFontsEnabled} }; window.__FOCUSGRAM_GHOST_CONFIG__ = { enabled: ${settings.ghostModeEnabled}, + hideStorySeen: ${settings.ghostHideStorySeen}, + hideDirectSeen: ${settings.ghostHideDirectSeen}, + hideTyping: ${settings.ghostHideTypingIndicator}, igStory: ${settings.ghostHideStorySeen}, igSeen: ${settings.ghostHideDirectSeen}, igTyping: ${settings.ghostHideTypingIndicator} @@ -87,27 +90,46 @@ class UserScriptManager(private val context: Context) { window.__focusgramGhostSettings.hideStorySeen = ${settings.ghostHideStorySeen}; window.__focusgramGhostSettings.hideDirectSeen = ${settings.ghostHideDirectSeen}; window.__focusgramGhostSettings.hideTyping = ${settings.ghostHideTypingIndicator}; + window.__focusgramGhostSettings.igStory = ${settings.ghostHideStorySeen}; + window.__focusgramGhostSettings.igSeen = ${settings.ghostHideDirectSeen}; + window.__focusgramGhostSettings.igTyping = ${settings.ghostHideTypingIndicator}; } if (window.__fgSetGrayscale) { window.__fgSetGrayscale(${settings.grayscaleEnabled}); } + const filterUpdatePayload = { + blockAds: ${settings.noAds}, + blockSponsored: ${settings.noAds}, + blockSuggested: ${settings.hideSuggestedPosts}, + storySearchEnabled: ${settings.storySearchEnabled}, + customBioFontsEnabled: ${settings.customBioFontsEnabled} + }; if (window.__fgSetFilterConfig) { - window.__fgSetFilterConfig({ - blockAds: ${settings.noAds}, - blockSponsored: ${settings.noAds}, - blockSuggested: ${settings.hideSuggestedPosts} - }); + window.__fgSetFilterConfig(filterUpdatePayload); } + try { + window.dispatchEvent(new CustomEvent('focusgram:filter-config-update', { detail: filterUpdatePayload })); + document.dispatchEvent(new CustomEvent('focusgram:filter-config-update', { detail: filterUpdatePayload })); + } catch(_) {} document.documentElement.setAttribute('data-fg-block-dm-reels', '${settings.blockDmReelsSwiping}'); window.postMessage({ source: "GHOSTIFY_EXTENSION", type: "GHOSTIFY_SETTINGS_UPDATE", settings: { - igTyping: ${settings.ghostModeEnabled && settings.ghostHideTypingIndicator}, + enabled: ${settings.ghostModeEnabled}, + hideStorySeen: ${settings.ghostModeEnabled && settings.ghostHideStorySeen}, + hideDirectSeen: ${settings.ghostModeEnabled && settings.ghostHideDirectSeen}, + hideTyping: ${settings.ghostModeEnabled && settings.ghostHideTypingIndicator}, + igStory: ${settings.ghostModeEnabled && settings.ghostHideStorySeen}, igSeen: ${settings.ghostModeEnabled && settings.ghostHideDirectSeen}, - igStory: ${settings.ghostModeEnabled && settings.ghostHideStorySeen} + igTyping: ${settings.ghostModeEnabled && settings.ghostHideTypingIndicator} } }, "*"); + window.postMessage({ + source: "FOCUSGRAM_HOST", + type: "FOCUSGRAM_FILTER_UPDATE", + config: filterUpdatePayload + }, "*"); window.postMessage({ source: "FOCUSGRAM_HOST", type: "FOCUSGRAM_UPDATE", @@ -120,9 +142,10 @@ class UserScriptManager(private val context: Context) { } /** - * Evaluates a UserScript inside the active GeckoSession document. - * Safely URL-encodes the script body to prevent Gecko's C++ parser from - * rejecting scripts containing newlines or control characters. + * Executes JavaScript inside the active session. + * + * Wraps the script in an IIFE and percent-encodes it because Gecko's parser + * rejects raw newlines or unencoded control characters in `javascript:` URIs. */ fun evaluate(session: GeckoSession, script: String) { if (script.isBlank()) return @@ -136,13 +159,24 @@ class UserScriptManager(private val context: Context) { } fun injectAll(session: GeckoSession, settings: FocusSettings) { + val scripts = listOf( + "fetch_interceptor.js", + "content_hider.js", + "autoplay_blocker.js", + "grayscale.js", + "native_feel.js", + "media_downloader.js", + "stealth_instagram.js" + ) val bundle = StringBuilder() - bundle.append(loadScript("fetch_interceptor.js")).append("\n") - bundle.append(loadScript("content_hider.js")).append("\n") - bundle.append(loadScript("autoplay_blocker.js")).append("\n") - bundle.append(loadScript("grayscale.js")).append("\n") - bundle.append(loadScript("native_feel.js")).append("\n") - bundle.append(loadScript("media_downloader.js")).append("\n") + for (name in scripts) { + val code = loadScript(name) + if (code.isNotBlank()) { + bundle.append("try {\n") + .append(code) + .append("\n} catch (e) { console.error('[FocusGram] Script error in ").append(name).append(":', e); }\n") + } + } evaluate(session, bundle.toString()) injectConfig(session, settings) } diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/model/FocusSettings.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/model/FocusSettings.kt similarity index 67% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/model/FocusSettings.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/model/FocusSettings.kt index 10df1cc..489e0a9 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/model/FocusSettings.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/model/FocusSettings.kt @@ -1,12 +1,12 @@ package com.ujwal.focusgram.core.model /** - * FocusSettings - * Immutable data model representing all user-configurable focus toggles, - * digital wellness guardrails, mindful gate parameters, and reward credits. + * User-configurable toggles and operational thresholds stored on-device. + * + * All state is persisted via DataStore Preferences. Defaults prioritize distraction + * elimination (ad blocking, autoplay suppression, explore blurring). */ data class FocusSettings( - // Focus Mode & Content Guardrails val noAds: Boolean = true, val noStories: Boolean = false, val noReels: Boolean = false, @@ -15,7 +15,7 @@ data class FocusSettings( val blurExplore: Boolean = true, val blurReels: Boolean = false, val blurFeed: Boolean = false, - val unblurMode: String = "tap", // "tap" or "hold" + val unblurMode: String = "tap", // "tap" reveals on click; "hold" requires continuous touch val tapToUnblur: Boolean = true, val requireLongPress: Boolean = false, val hideSuggestedPosts: Boolean = false, @@ -26,47 +26,41 @@ data class FocusSettings( val hideFeedPosts: Boolean = false, val blockDmReelsSwiping: Boolean = true, - // Mindful Gates & Wellness val showBreathGate: Boolean = true, val breathGateSeconds: Int = 10, val requireWordChallenge: Boolean = true, val wordChallengeCount: Int = 30, val effortFrictionEnabled: Boolean = true, - val cooldownMinutes: Int = 15, + val cooldownMinutes: Int = 5, val dailyLimitMinutes: Int = 60, val dailyLimitAdClaimedDate: String = "", + val dailyLimitExtensionsCount: Int = 0, - // Appearance & System val grayscaleEnabled: Boolean = false, val smoothScroll120Hz: Boolean = true, val reelsHistoryEnabled: Boolean = true, - // Rewarded Ads & Discipline Economy - val reelsMinutes: Int = 0, - val instaMinutes: Int = 0, - val adsWatchedToday: Int = 0, val adsWatchedAllTime: Int = 0, val lastAdDate: String = "", - val userLevel: Int = 1, - val userXP: Int = 0, - // App Lock & Privacy val appLockEnabled: Boolean = false, val biometricEnabled: Boolean = false, val autoLockOnBackground: Boolean = true, - val isFirstRun: Boolean = false, + val isFirstRun: Boolean = true, - // Extras & Notifications val startupPage: String = "home", val videoDownloadEnabled: Boolean = false, val sanitizeLinks: Boolean = true, val notifySessionEnd: Boolean = false, - val notifyPersistent: Boolean = false, + val notifyPersistent: Boolean = true, + val notifyEvery5Minutes: Boolean = true, val portraitOnly: Boolean = true, + val showSessionTimeInBottomBar: Boolean = true, val appIcon: String = "default", val customIconsEnabled: Boolean = false, + val storySearchEnabled: Boolean = true, + val customBioFontsEnabled: Boolean = true, - // Chat Lock & Direct Message Privacy val chatLockMode: String = "individual", // "off", "individual", "whole_dm" val chatLockAll: Boolean = false, val lockedChatIds: Set = emptySet(), @@ -74,9 +68,15 @@ data class FocusSettings( val useSeparateChatPin: Boolean = false, val chatBiometricEnabled: Boolean = true, - // Ghost Mode (Privacy & Stealth) val ghostModeEnabled: Boolean = false, val ghostHideStorySeen: Boolean = true, val ghostHideDirectSeen: Boolean = true, - val ghostHideTypingIndicator: Boolean = true + val ghostHideTypingIndicator: Boolean = true, + + val activeSessionDurationMinutes: Int = 0, + val activeSessionRemainingSeconds: Int = 0, + val activeSessionElapsedSeconds: Int = 0, + val activeSessionReelsWatched: Int = 0, + val activeSessionReelCodes: Set = emptySet(), + val activeSessionIsActive: Boolean = false ) diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/model/IslandMode.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/model/IslandMode.kt similarity index 52% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/model/IslandMode.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/model/IslandMode.kt index af03788..9629c40 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/model/IslandMode.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/model/IslandMode.kt @@ -1,43 +1,39 @@ package com.ujwal.focusgram.core.model /** - * IslandMode - * Defines the presentation state of FocusGram's native navigation island - * and the corresponding content-script body padding for Instagram web. + * Presentation states for the native navigation island overlay and corresponding + * page bottom padding in Instagram web. * - * Implements Senior Architecture Blueprint: - * - Edge-to-edge full-screen GeckoView. - * - Floating native OLED island pill overlay. - * - Route-driven visibility and padding states: - * - NORMAL: Feed, Explore, User Profile, Direct Inbox (island shown, 96px body padding) - * - FLOATING: Reels (full bleed, 0px body padding, translucent island) - * - HIDDEN: Direct conversation threads, Comments, Stories, Create post (0px body padding) + * Instagram web layouts rely on CSS viewport heights and sticky footers. Injecting + * 96px bottom padding prevents the floating island from obscuring buttons in feed + * and explore, while full-bleed views (Reels) need 0px padding to avoid letterboxing. */ enum class IslandMode { /** * Standard browsing view (Feed, Search/Explore, User Profile, Direct Inbox). - * Island is shown with frosted glass elevation. - * Content script applies bottom padding (96px) so content scrolls behind the island. + * The native island is displayed and web content receives bottom padding (96px). */ NORMAL, /** * Reels view (/reels, /reel/...). - * Video container is full-bleed 100vh with 0px body padding to avoid distortion or gaps. - * Island floats translucently and automatically accommodates full-screen gestures. + * Full-screen 100vh layout with 0px padding to prevent video letterboxing or scroll glitches. + * The island is rendered with reduced opacity. */ FLOATING, /** - * Hidden state (DM thread /direct/t/, Comments /comments or modal, Stories /stories/, Create /create, Soft keyboard IME). - * Island is completely animated offscreen so bottom input composers sit completely flush. + * Active interaction view (DM conversations, comment sheets, story viewer, create composer). + * The island is translated offscreen so input fields align directly with the software keyboard. */ HIDDEN; companion object { /** - * Resolves the IslandMode from a given URL path. - * Uses pure string parsing for cross-platform reliability on both Android runtime and JVM JUnit tests. + * Resolves the navigation mode for a given URL path. + * + * Uses string splitting rather than android.net.Uri to allow direct execution + * in host JVM unit tests without Android mock stubs. */ fun fromUrl(url: String?): IslandMode { if (url.isNullOrBlank()) return NORMAL @@ -55,7 +51,7 @@ enum class IslandMode { } /** - * Extracts the URL path segment cleanly from absolute or relative URLs. + * Returns the path portion of a URL, defaulting to "/" if no path exists. */ fun extractPath(url: String): String { val clean = url.trim().lowercase() diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/security/EncryptedStorage.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/security/EncryptedStorage.kt similarity index 90% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/security/EncryptedStorage.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/security/EncryptedStorage.kt index 2efafd8..4b51d7e 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/security/EncryptedStorage.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/security/EncryptedStorage.kt @@ -8,14 +8,11 @@ import androidx.security.crypto.MasterKey import java.security.MessageDigest /** - * EncryptedStorage - * Hardware-backed cryptographic vault utilizing Android KeyStore MasterKey (AES-256 GCM) - * and EncryptedSharedPreferences (AES-256 SIV + AES-256 GCM). + * Hardware-backed encrypted storage for authentication PINs and lockout timestamps. * - * Secures: - * - App Lock 4-digit PIN (hashed with SHA-256 + salt and encrypted at rest). - * - Biometric authentication state. - * - Brute-force lockout and failed attempt tracking. + * Keys and values are encrypted using Android KeyStore master keys (AES-256 GCM) + * via [EncryptedSharedPreferences]. PINs are salted with a fixed prefix before hashing + * so the raw 4-digit code is never written into preferences in plain form. */ class EncryptedStorage(context: Context) { @@ -36,7 +33,7 @@ class EncryptedStorage(context: Context) { } /** - * Hashes and securely stores user's App Lock PIN. + * Stores the salted SHA-256 digest of the app lock PIN and resets any active lockout. */ fun savePin(pin: String) { val hashedPin = hashPin(pin) @@ -49,7 +46,7 @@ class EncryptedStorage(context: Context) { } /** - * Verifies entered PIN against hardware-encrypted hash. + * Compares entered PIN digest against stored digest, enforcing lockout on repeated failures. */ fun verifyPin(enteredPin: String): Boolean { if (isLockedOut()) { @@ -82,8 +79,6 @@ class EncryptedStorage(context: Context) { Log.i(TAG, "App Lock PIN removed from vault.") } - // --- Chat Lock PIN Management --- - fun saveChatPin(pin: String) { val hashedPin = hashPin("chat_$pin") securePrefs.edit() @@ -122,8 +117,6 @@ class EncryptedStorage(context: Context) { Log.i(TAG, "Chat Lock PIN removed from vault.") } - // --- Brute Force Protection --- - fun isLockedOut(): Boolean { val lockoutUntil = securePrefs.getLong(KEY_LOCKOUT_UNTIL, 0) return System.currentTimeMillis() < lockoutUntil @@ -139,6 +132,7 @@ class EncryptedStorage(context: Context) { val currentAttempts = securePrefs.getInt(KEY_FAILED_ATTEMPTS, 0) + 1 val editor = securePrefs.edit().putInt(KEY_FAILED_ATTEMPTS, currentAttempts) + // Lock out after 5 consecutive failed attempts to slow down brute-force PIN guessing if (currentAttempts >= MAX_FAILED_ATTEMPTS) { val lockoutDurationMs = LOCKOUT_DURATION_SECONDS * 1000L val lockoutUntil = System.currentTimeMillis() + lockoutDurationMs @@ -155,8 +149,6 @@ class EncryptedStorage(context: Context) { .apply() } - // --- Generic Key/Value Secure Storage --- - fun putSecureString(key: String, value: String) { securePrefs.edit().putString(key, value).apply() } diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/CupertinoWheelPicker.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/CupertinoWheelPicker.kt similarity index 80% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/CupertinoWheelPicker.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/CupertinoWheelPicker.kt index 8e14377..75e158f 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/CupertinoWheelPicker.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/CupertinoWheelPicker.kt @@ -47,14 +47,10 @@ import com.ujwal.focusgram.core.ui.theme.PureBlack import kotlin.math.abs /** - * CupertinoWheelPicker - * Authentic Apple Clock styled rotating drum wheel picker. - * Features: - * - 3D cylindrical drum perspective: center item is perfectly flat (0°), 1 above/below are tilted, - * 2 above/below tilt progressively and vanish into top/bottom shadow gradients. - * - Center row frosted lens highlight with subtle borders. - * - Hardware CLOCK_TICK haptics with Vibrator fallback. - * - Smooth inertial snap fling behavior. + * Rotating drum wheel picker with 3D cylindrical projection and inertial snap flinging. + * + * Items above and below the center index rotate along the X-axis and scale down + * to simulate a physical tumbler drum. */ @OptIn(ExperimentalFoundationApi::class) @Composable @@ -66,7 +62,8 @@ fun CupertinoWheelPicker( visibleItemsCount: Int = 5, itemHeight: Dp = 46.dp, label: String = "min", - showGradientShadows: Boolean = true + showGradientShadows: Boolean = true, + labelProvider: ((Int) -> String)? = null ) { val view = LocalView.current val density = LocalDensity.current @@ -79,17 +76,22 @@ fun CupertinoWheelPicker( val listState = rememberLazyListState(initialFirstVisibleItemIndex = initialIndex) val snapFlingBehavior = rememberSnapFlingBehavior(lazyListState = listState) - // Calculate current centered index based on scroll offset val currentCenteredIndex by remember { derivedStateOf { - val firstVisible = listState.firstVisibleItemIndex - val offset = listState.firstVisibleItemScrollOffset - val extra = if (offset > itemHeightPx / 2f) 1 else 0 - (firstVisible + extra).coerceIn(0, (items.size - 1).coerceAtLeast(0)) + val layoutInfo = listState.layoutInfo + val visibleItems = layoutInfo.visibleItemsInfo + if (visibleItems.isEmpty()) { + initialIndex + } else { + val viewportCenter = (layoutInfo.viewportStartOffset + layoutInfo.viewportEndOffset) / 2 + val closest = visibleItems.minByOrNull { item -> + abs((item.offset + item.size / 2) - viewportCenter) + } + closest?.index ?: initialIndex + } } } - // Trigger iOS CLOCK_TICK haptic feedback and notify on index change LaunchedEffect(listState) { snapshotFlow { currentCenteredIndex } .collect { index -> @@ -106,7 +108,6 @@ fun CupertinoWheelPicker( .fillMaxWidth(), contentAlignment = Alignment.Center ) { - // iOS Clock Center Lens Highlight Bar with subtle outline Box( modifier = Modifier .fillMaxWidth() @@ -138,17 +139,17 @@ fun CupertinoWheelPicker( .fillMaxWidth() .height(itemHeight) .graphicsLayer { - // Calculate continuous distance from center for ultra-smooth 3D projection - val scrollFraction = if (itemHeightPx > 0f) listState.firstVisibleItemScrollOffset / itemHeightPx else 0f - val continuousCenter = listState.firstVisibleItemIndex + scrollFraction - val distanceFromCenter = index - continuousCenter + val layoutInfo = listState.layoutInfo + val viewportCenter = (layoutInfo.viewportStartOffset + layoutInfo.viewportEndOffset) / 2f + val itemInfo = layoutInfo.visibleItemsInfo.firstOrNull { it.index == index } + val distanceFromCenter = if (itemInfo != null && itemHeightPx > 0f) { + ((itemInfo.offset + itemInfo.size / 2f) - viewportCenter) / itemHeightPx + } else { + (index - currentCenteredIndex).toFloat() + } val absDistance = abs(distanceFromCenter) - // 3D Drum Perspective: - // Center item (0) is perfectly flat (rotationX = 0f) - // Item 1 above (-1) tilts top away (+28f) - // Item 1 below (+1) tilts bottom away (-28f) - // Outer items progressively tilt and vanish into shadow + // Cylindrical drum perspective tilt and foreshortening rotationX = (-distanceFromCenter * 28f).coerceIn(-65f, 65f) scaleX = (1f - absDistance * 0.09f).coerceIn(0.70f, 1f) scaleY = (1f - absDistance * 0.09f).coerceIn(0.70f, 1f) @@ -162,14 +163,15 @@ fun CupertinoWheelPicker( horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { + val displayText = labelProvider?.invoke(itemValue) ?: itemValue.toString() Text( - text = itemValue.toString(), + text = displayText, color = if (isSelected) Color.White else Color(0xFF8E8E93), fontSize = if (isSelected) 30.sp else 24.sp, fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, textAlign = TextAlign.Center ) - if (label.isNotBlank()) { + if (label.isNotBlank() && labelProvider == null) { Text( text = " $label", color = if (isSelected) Color.White.copy(alpha = 0.85f) else Color(0xFF636366), @@ -183,7 +185,6 @@ fun CupertinoWheelPicker( } if (showGradientShadows) { - // Top cylinder gradient shadow (fades upper tilted items into pure black) Box( modifier = Modifier .align(Alignment.TopCenter) @@ -200,7 +201,6 @@ fun CupertinoWheelPicker( ) ) - // Bottom cylinder gradient shadow (fades lower tilted items into pure black) Box( modifier = Modifier .align(Alignment.BottomCenter) @@ -221,9 +221,7 @@ fun CupertinoWheelPicker( } /** - * CupertinoOptionWheelPicker - * Reusable iOS drum wheel picker for discrete options such as "Off", "5s", "10 min", etc. - * Features 3D cylinder perspective with center item flat, progressive tilt, and gradient fades. + * Cylindrical drum wheel picker for discrete option lists with center magnification. */ @OptIn(ExperimentalFoundationApi::class) @Composable @@ -250,10 +248,17 @@ fun CupertinoOptionWheelPicker( val currentCenteredIndex by remember { derivedStateOf { - val firstVisible = listState.firstVisibleItemIndex - val offset = listState.firstVisibleItemScrollOffset - val extra = if (offset > itemHeightPx / 2f) 1 else 0 - (firstVisible + extra).coerceIn(0, (options.size - 1).coerceAtLeast(0)) + val layoutInfo = listState.layoutInfo + val visibleItems = layoutInfo.visibleItemsInfo + if (visibleItems.isEmpty()) { + initialIndex + } else { + val viewportCenter = (layoutInfo.viewportStartOffset + layoutInfo.viewportEndOffset) / 2 + val closest = visibleItems.minByOrNull { item -> + abs((item.offset + item.size / 2) - viewportCenter) + } + closest?.index ?: initialIndex + } } } @@ -273,7 +278,6 @@ fun CupertinoOptionWheelPicker( .fillMaxWidth(), contentAlignment = Alignment.Center ) { - // Center frosted highlight lens Box( modifier = Modifier .fillMaxWidth() @@ -306,9 +310,14 @@ fun CupertinoOptionWheelPicker( .fillMaxWidth() .height(itemHeight) .graphicsLayer { - val scrollFraction = if (itemHeightPx > 0f) listState.firstVisibleItemScrollOffset / itemHeightPx else 0f - val continuousCenter = listState.firstVisibleItemIndex + scrollFraction - val distanceFromCenter = index - continuousCenter + val layoutInfo = listState.layoutInfo + val viewportCenter = (layoutInfo.viewportStartOffset + layoutInfo.viewportEndOffset) / 2f + val itemInfo = layoutInfo.visibleItemsInfo.firstOrNull { it.index == index } + val distanceFromCenter = if (itemInfo != null && itemHeightPx > 0f) { + ((itemInfo.offset + itemInfo.size / 2f) - viewportCenter) / itemHeightPx + } else { + (index - currentCenteredIndex).toFloat() + } val absDistance = abs(distanceFromCenter) rotationX = (-distanceFromCenter * 26f).coerceIn(-60f, 60f) @@ -336,7 +345,6 @@ fun CupertinoOptionWheelPicker( } if (showGradientShadows) { - // Top cylinder shadow Box( modifier = Modifier .align(Alignment.TopCenter) @@ -353,7 +361,6 @@ fun CupertinoOptionWheelPicker( ) ) - // Bottom cylinder shadow Box( modifier = Modifier .align(Alignment.BottomCenter) @@ -383,9 +390,7 @@ private fun getVibrator(context: Context): Vibrator? { } /** - * Perform crisp haptic tick even when user has system "touch feedback" disabled. - * Uses direct physical vibration (35ms pulse) to ensure tactile feedback on all devices - * (both ERM and LRA motors, like MX Player), without relying on unsupported predefined effects. + * Wheel tick haptic fallback when system-level touch feedback is disabled. */ private fun performWheelHaptic(context: Context, view: android.view.View) { HapticHelper.performWheelTick(context, view) diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/FocusGramComponents.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/FocusGramComponents.kt similarity index 88% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/FocusGramComponents.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/FocusGramComponents.kt index cb19691..ce9616e 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/FocusGramComponents.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/FocusGramComponents.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.ujwal.focusgram.core.ui.theme.DarkBorder import com.ujwal.focusgram.core.ui.theme.DarkCard +import androidx.compose.ui.graphics.Brush import com.ujwal.focusgram.core.ui.theme.DarkCardElevated import com.ujwal.focusgram.core.ui.theme.InstagramBlue import com.ujwal.focusgram.core.ui.theme.PureBlack @@ -46,8 +47,7 @@ import com.ujwal.focusgram.core.ui.theme.TextSecondary import com.ujwal.focusgram.core.ui.theme.tactilePress /** - * FocusGramCard - * High-contrast OLED dark surface card container with subtle borders. + * Dark surface container card with liquid glass styling, rounded corners, and optional tactile click feedback. */ @Composable fun FocusGramCard( @@ -58,17 +58,30 @@ fun FocusGramCard( content: @Composable () -> Unit ) { val shape = RoundedCornerShape(16.dp) + val glassBorderBrush = Brush.verticalGradient( + colors = listOf( + Color(0x2DFFFFFF), + Color(0x0DFFFFFF) + ) + ) + val glassBgBrush = Brush.verticalGradient( + colors = listOf( + backgroundColor.copy(alpha = 0.90f), + backgroundColor.copy(alpha = 0.78f) + ) + ) + val cardModifier = if (onClick != null) { modifier .clip(shape) - .border(BorderStroke(1.dp, borderColor), shape) - .background(backgroundColor) + .border(BorderStroke(1.dp, glassBorderBrush), shape) + .background(glassBgBrush) .tactilePress(onClick = onClick) } else { modifier .clip(shape) - .border(BorderStroke(1.dp, borderColor), shape) - .background(backgroundColor) + .border(BorderStroke(1.dp, glassBorderBrush), shape) + .background(glassBgBrush) } Box(modifier = cardModifier) { @@ -77,8 +90,7 @@ fun FocusGramCard( } /** - * FocusGramSectionHeader - * Categorical section divider with uppercase title and secondary description. + * Categorical header displaying an uppercase section title and optional subtitle. */ @Composable fun FocusGramSectionHeader( @@ -111,8 +123,7 @@ fun FocusGramSectionHeader( } /** - * FocusGramTopBar - * Minimalist edge-to-edge top bar with back navigation. + * Top app bar with back navigation and custom trailing actions. */ @Composable fun FocusGramTopBar( @@ -159,8 +170,7 @@ fun FocusGramTopBar( } /** - * FocusGramToggleRow - * Standard settings row with title, description, and switch. + * Settings row with icon, title, description, and an on/off switch. */ @Composable fun FocusGramToggleRow( diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/IOSSettingsComponents.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/IOSSettingsComponents.kt similarity index 70% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/IOSSettingsComponents.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/IOSSettingsComponents.kt index 6414a40..0b42ede 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/IOSSettingsComponents.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/IOSSettingsComponents.kt @@ -3,6 +3,7 @@ package com.ujwal.focusgram.core.ui.components import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -32,6 +33,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight @@ -46,16 +48,13 @@ import androidx.compose.runtime.setValue import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -// iOS Palette val IOSBg = Color(0xFF000000) val IOSGroupBg = Color(0xFF1C1C1E) val IOSDivider = Color(0xFF2C2C2E) val IOSTextPrimary = Color(0xFFFFFFFF) /** - * DynamicNetworkIcon - * Asynchronously loads project logos dynamically over the network on page open. - * Does NOT bundle images in the APK to ensure zero APK bloat. + * Loads project and brand icons from remote URLs to keep base APK install size low. */ @Composable fun DynamicNetworkIcon( @@ -123,8 +122,7 @@ val IOSRed = Color(0xFFFF3B30) val IOSTeal = Color(0xFF5AC8FA) /** - * IOSSectionTitle - * Small uppercase section header standard in iOS Settings and Instagram iOS settings. + * Section label rendered in uppercase small caps above grouped settings containers. */ @Composable fun IOSSectionTitle(title: String) { @@ -139,28 +137,39 @@ fun IOSSectionTitle(title: String) { } /** - * IOSGroupedCard - * Rounded corner container grouping settings rows together. + * Grouped rounded-corner container for settings rows with subtle liquid glass styling. */ @Composable fun IOSGroupedCard( modifier: Modifier = Modifier, content: @Composable () -> Unit ) { - val shape = RoundedCornerShape(12.dp) + val shape = RoundedCornerShape(14.dp) + val glassBorderBrush = Brush.verticalGradient( + colors = listOf( + Color(0x2DFFFFFF), + Color(0x0DFFFFFF) + ) + ) + val glassBgBrush = Brush.verticalGradient( + colors = listOf( + Color(0xF01E1E22), + Color(0xE0161619) + ) + ) Column( modifier = modifier .fillMaxWidth() .clip(shape) - .background(IOSGroupBg) + .border(BorderStroke(1.dp, glassBorderBrush), shape) + .background(glassBgBrush) ) { content() } } /** - * IOSSwitch - * Apple iOS authentic toggle switch with fluid sliding animation and tactile styling. + * Sliding toggle switch styled with smooth thumb offset animation. */ @Composable fun IOSSwitch( @@ -202,8 +211,7 @@ fun IOSSwitch( } /** - * IOSSettingsRow - * Single row inside an iOS grouped card. + * Grouped item row with icon, title, optional subtitle, and switch or navigation chevron. */ @Composable fun IOSSettingsRow( @@ -217,79 +225,106 @@ fun IOSSettingsRow( onCheckedChange: ((Boolean) -> Unit)? = null, trailingText: String? = null, onClick: (() -> Unit)? = null, - showDivider: Boolean = true + showDivider: Boolean = true, + isDependent: Boolean = false ) { + val baseModifier = if (isDependent) Modifier.background(Color(0x0AFFFFFF)) else Modifier val rowModifier = if (onClick != null && checked == null) { Modifier .fillMaxWidth() + .then(baseModifier) .clickable { onClick() } } else { - Modifier.fillMaxWidth() + Modifier + .fillMaxWidth() + .then(baseModifier) } Column(modifier = rowModifier) { Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 12.dp), + .padding( + start = if (isDependent) 15.dp else 16.dp, + end = 16.dp, + top = if (isDependent) 10.dp else 12.dp, + bottom = if (isDependent) 10.dp else 12.dp + ), verticalAlignment = Alignment.CenterVertically ) { + if (isDependent) { + Box( + modifier = Modifier + .size(width = 16.dp, height = 20.dp) + .padding(end = 4.dp), + contentAlignment = Alignment.CenterStart + ) { + Text( + text = "â†ģ", + color = Color(0xFF636366), + fontSize = 15.sp, + fontWeight = FontWeight.Bold + ) + } + } + if (iconRes != null) { Box( modifier = Modifier - .size(30.dp) - .clip(RoundedCornerShape(7.dp)) - .background(iconBgColor), + .size(if (isDependent) 24.dp else 30.dp) + .clip(RoundedCornerShape(if (isDependent) 6.dp else 7.dp)) + .background(if (isDependent) iconBgColor.copy(alpha = 0.65f) else iconBgColor), contentAlignment = Alignment.Center ) { Icon( painter = androidx.compose.ui.res.painterResource(id = iconRes), contentDescription = null, tint = Color.Unspecified, - modifier = Modifier.size(20.dp) + modifier = Modifier.size(if (isDependent) 16.dp else 20.dp) ) } - Spacer(modifier = Modifier.width(14.dp)) + Spacer(modifier = Modifier.width(if (isDependent) 10.dp else 14.dp)) } else if (iconUrl != null) { DynamicNetworkIcon( imageUrl = iconUrl, fallbackIcon = icon ?: Icons.Default.Info, - iconBgColor = iconBgColor + iconBgColor = if (isDependent) iconBgColor.copy(alpha = 0.65f) else iconBgColor, + modifier = Modifier.size(if (isDependent) 24.dp else 30.dp) ) - Spacer(modifier = Modifier.width(14.dp)) + Spacer(modifier = Modifier.width(if (isDependent) 10.dp else 14.dp)) } else if (icon != null) { Box( modifier = Modifier - .size(30.dp) - .clip(RoundedCornerShape(7.dp)) - .background(iconBgColor), + .size(if (isDependent) 24.dp else 30.dp) + .clip(RoundedCornerShape(if (isDependent) 6.dp else 7.dp)) + .background(if (isDependent) iconBgColor.copy(alpha = 0.65f) else iconBgColor), contentAlignment = Alignment.Center ) { Icon( imageVector = icon, contentDescription = null, - tint = Color.White, - modifier = Modifier.size(18.dp) + tint = if (isDependent) Color.White.copy(alpha = 0.85f) else Color.White, + modifier = Modifier.size(if (isDependent) 14.dp else 18.dp) ) } - Spacer(modifier = Modifier.width(14.dp)) + Spacer(modifier = Modifier.width(if (isDependent) 10.dp else 14.dp)) } Column(modifier = Modifier.weight(1f)) { Text( text = title, - color = IOSTextPrimary, - fontSize = 16.sp, + color = if (isDependent) IOSTextPrimary.copy(alpha = 0.88f) else IOSTextPrimary, + fontSize = if (isDependent) 15.sp else 16.sp, fontWeight = FontWeight.Normal ) if (subtitle != null) { Spacer(modifier = Modifier.height(2.dp)) Text( text = subtitle, - color = IOSTextSecondary, - fontSize = 13.sp, + color = if (isDependent) IOSTextSecondary.copy(alpha = 0.85f) else IOSTextSecondary, + fontSize = if (isDependent) 12.sp else 13.sp, fontWeight = FontWeight.Normal, - lineHeight = 17.sp + lineHeight = if (isDependent) 16.sp else 17.sp ) } } @@ -307,7 +342,7 @@ fun IOSSettingsRow( Text( text = trailingText, color = IOSTextSecondary, - fontSize = 15.sp, + fontSize = if (isDependent) 14.sp else 15.sp, fontWeight = FontWeight.Normal ) Spacer(modifier = Modifier.width(6.dp)) @@ -328,7 +363,13 @@ fun IOSSettingsRow( HorizontalDivider( color = IOSDivider, thickness = 0.5.dp, - modifier = Modifier.padding(start = if (icon != null) 60.dp else 16.dp) + modifier = Modifier.padding( + start = if (isDependent) { + if (icon != null || iconRes != null || iconUrl != null) 58.dp else 28.dp + } else { + if (icon != null || iconRes != null || iconUrl != null) 60.dp else 16.dp + } + ) ) } } diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/SkeletonScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/SkeletonScreen.kt similarity index 97% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/SkeletonScreen.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/SkeletonScreen.kt index b543c70..de8db20 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/SkeletonScreen.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/ui/components/SkeletonScreen.kt @@ -72,8 +72,7 @@ fun ShimmerBrush(): Brush { } /** - * SkeletonScreen - * Native OLED shimmer skeleton displayed during Instagram page cold-start. + * Placeholder shimmer layout rendered while the web session loads the initial document. */ @Composable fun SkeletonScreen( @@ -102,7 +101,6 @@ fun SkeletonScreen( @Composable private fun FeedSkeleton(brush: Brush) { Column(modifier = Modifier.fillMaxSize()) { - // Stories Tray Shimmer Row( modifier = Modifier .fillMaxWidth() @@ -133,7 +131,6 @@ private fun FeedSkeleton(brush: Brush) { Spacer(modifier = Modifier.height(12.dp)) - // Feed Post 1 PostSkeleton(brush) } } @@ -163,7 +160,6 @@ private fun PostSkeleton(brush: Brush) { ) } - // Post Media Area Box( modifier = Modifier .fillMaxWidth() @@ -171,7 +167,6 @@ private fun PostSkeleton(brush: Brush) { .background(brush) ) - // Post Actions Row( modifier = Modifier .fillMaxWidth() diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Color.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Color.kt similarity index 77% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Color.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Color.kt index 1ce1b3c..e4fbc96 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Color.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Color.kt @@ -2,26 +2,29 @@ package com.ujwal.focusgram.core.ui.theme import androidx.compose.ui.graphics.Color -// Pure OLED Black Design System +/** + * OLED high-contrast color tokens. + * + * Uses true black (#000000) surfaces to conserve battery on OLED panels + * and match Instagram's dark mode palette. + */ val PureBlack = Color(0xFF000000) val DarkSurface = Color(0xFF0A0A0A) val DarkCard = Color(0xFF141414) val DarkCardElevated = Color(0xFF1E1E1E) val DarkBorder = Color(0xFF262626) -// Typography & Content val TextPrimary = Color(0xFFF5F5F5) val TextSecondary = Color(0xFFA3A3A3) val TextMuted = Color(0xFF737373) -// FocusGram Brand Accents val InstagramBlue = Color(0xFF3797EF) +val InstagramPink = Color(0xFFE1306C) val FocusGold = Color(0xFFFFD700) val FocusGoldDark = Color(0xFFB8860B) val FocusGreen = Color(0xFF2ECC71) val AlertRed = Color(0xFFED4956) -// Light Mode Tokens val PureWhite = Color(0xFFFFFFFF) val LightGrayBg = Color(0xFFF2F2F7) val SecondaryText = Color(0xFF8E8E93) diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Motion.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Motion.kt similarity index 87% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Motion.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Motion.kt index 14b2985..22c6f59 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Motion.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Motion.kt @@ -18,8 +18,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalView /** - * FocusGram Motion & Spring Physics - * Tactile spring dynamics tailored for responsive OLED dark mode interactions. + * Spring animation specs for touch press and release states. */ object FocusGramMotion { val BouncySpring = spring( @@ -39,8 +38,7 @@ object FocusGramMotion { } /** - * Modifier to apply a tactile spring bounce and subtle scale on press - * accompanied by haptic feedback. + * Animates a scale-down press state and triggers a keyboard tap haptic on click. */ fun Modifier.tactilePress( targetScale: Float = 0.96f, @@ -74,9 +72,8 @@ fun Modifier.tactilePress( } /** - * Modifier to detect long press (600ms) and trigger onLongPress callback. - * Provides haptic feedback on trigger. - * NOTE: Use tactilePressWithLongPress for combined short tap + long press handling. + * Detects a sustained press and triggers haptic feedback on recognition. + * Prefer [tactilePressWithLongPress] if the element also handles short taps. */ fun Modifier.longPress( durationMillis: Long = 600L, @@ -97,8 +94,7 @@ fun Modifier.longPress( } /** - * Combined modifier for buttons that need both short-tap and long-press handling. - * Properly chains gesture detection to avoid event consumption conflicts. + * Handles concurrent tap and long-press gestures without pointer event collisions. */ fun Modifier.tactilePressWithLongPress( targetScale: Float = 0.96f, diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Shape.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Shape.kt similarity index 89% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Shape.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Shape.kt index ca1557e..be49e21 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Shape.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Shape.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Shapes import androidx.compose.ui.unit.dp -// FocusGram Smooth Squircle Corner Radii val Shapes = Shapes( small = RoundedCornerShape(8.dp), medium = RoundedCornerShape(14.dp), diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Theme.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Theme.kt similarity index 100% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Theme.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Theme.kt diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Type.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Type.kt similarity index 100% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Type.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/ui/theme/Type.kt diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/utils/AppIconManager.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/utils/AppIconManager.kt similarity index 83% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/utils/AppIconManager.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/utils/AppIconManager.kt index 38d738b..702d88e 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/utils/AppIconManager.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/utils/AppIconManager.kt @@ -18,10 +18,10 @@ import com.ujwal.focusgram.MainActivity import com.ujwal.focusgram.R /** - * AppIconManager - * Controls dynamic launcher icon switching via Android PackageManager activity-alias toggles. - * LITERALLY replaces the app icon across Home Screen, App Drawer, Recents Page, and App Info page. - * Safe, purely client-side, with DONT_KILL_APP flag to avoid killing running tasks. + * Switches launcher icons dynamically via [PackageManager.setComponentEnabledSetting] + * on manifest `` entries. + * + * Uses [PackageManager.DONT_KILL_APP] to avoid killing the process during component state changes. */ object AppIconManager { private const val TAG = "AppIconManager" @@ -34,7 +34,6 @@ object AppIconManager { val category: String, @DrawableRes val iconRes: Int ) { - // --- Core Icons --- DEFAULT( id = "default", aliasSuffix = "MainActivityDefault", @@ -60,11 +59,10 @@ object AppIconManager { iconRes = R.mipmap.ic_launcher_all_white ), - // --- Custom Redesigns (alt-logos) --- REDESIGN( id = "redesign", aliasSuffix = "MainActivityRedesign", - displayName = "FocusGram Redesign", + displayName = "Modern Insta", subtitle = "Official modern brand redesign", category = "Redesigns", iconRes = R.mipmap.ic_launcher_redesign @@ -110,7 +108,6 @@ object AppIconManager { iconRes = R.mipmap.ic_launcher_scribble ), - // --- Originals (Authentic Brand History) --- POLAROID( id = "polaroid", aliasSuffix = "MainActivityPolaroid", @@ -152,9 +149,8 @@ object AppIconManager { } /** - * Atomically switches active launcher icon by enabling the chosen alias and disabling others. - * Replaces the app icon on Home Screen, App Drawer (App Screen), and App Info (System Settings). - * Also updates the running Activity's Recents overview TaskDescription immediately. + * Activates the selected launcher alias and deactivates all others. + * Also updates the running Activity's [ActivityManager.TaskDescription] for the Recents screen. */ fun setAppIcon(context: Context, iconId: String) { val targetOption = IconOption.fromId(iconId) @@ -163,34 +159,26 @@ object AppIconManager { Log.i(TAG, "Switching app icon to: ${targetOption.displayName} (${targetOption.aliasSuffix})") - // 1. Enable target alias so there's always at least one enabled launcher entry point - try { - val targetComponent = ComponentName(packageName, "$packageName.${targetOption.aliasSuffix}") - packageManager.setComponentEnabledSetting( - targetComponent, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, - PackageManager.DONT_KILL_APP + fun enableComponent(suffix: String, enable: Boolean) { + val state = if (enable) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else PackageManager.COMPONENT_ENABLED_STATE_DISABLED + val names = listOf( + ComponentName(packageName, "com.ujwal.focusgram.$suffix"), + ComponentName(packageName, "$packageName.$suffix") ) - } catch (e: Exception) { - Log.e(TAG, "Failed enabling target launcher alias ${targetOption.aliasSuffix}: ${e.message}", e) - } - - // 2. Disable all other aliases - for (option in IconOption.values()) { - if (option == targetOption) continue - try { - val comp = ComponentName(packageName, "$packageName.${option.aliasSuffix}") - packageManager.setComponentEnabledSetting( - comp, - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP - ) - } catch (e: Exception) { - Log.w(TAG, "Failed disabling launcher alias ${option.aliasSuffix}: ${e.message}") + for (comp in names) { + try { + packageManager.setComponentEnabledSetting(comp, state, PackageManager.DONT_KILL_APP) + } catch (_: Exception) {} } } - // 3. Immediately update TaskDescription for the Recents screen + enableComponent(targetOption.aliasSuffix, true) + + for (option in IconOption.values()) { + if (option == targetOption) continue + enableComponent(option.aliasSuffix, false) + } + if (context is Activity) { applyTaskDescription(context, iconId) } diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/utils/HapticHelper.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/utils/HapticHelper.kt similarity index 88% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/utils/HapticHelper.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/utils/HapticHelper.kt index 12632ba..381e386 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/utils/HapticHelper.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/utils/HapticHelper.kt @@ -11,14 +11,11 @@ import android.view.HapticFeedbackConstants import android.view.View /** - * HapticHelper - * Universal haptic feedback and vibration utility for FocusGram. + * Haptic and vibration feedback utility. * - * Guarantees physical vibration across all hardware motors (ERM & LRA), - * regardless of whether system "Touch feedback" is disabled in user Settings. - * Emulates the proven direct-hardware approach of media apps like MX Player - * by pulsing the physical vibrator directly with sufficient duration (35ms) - * to overcome motor inertia without relying on unsupported predefined effects. + * Uses direct physical motor pulses (25-60ms) to ensure consistent tactile response + * across both ERM (eccentric rotating mass) and LRA (linear resonant actuator) hardware, + * even when system-level touch feedback is toggled off in device settings. */ object HapticHelper { @@ -38,10 +35,9 @@ object HapticHelper { } /** - * Performs a crisp, responsive drum-wheel scroll tick. - * Direct hardware pulse of 35ms ensures ERM motors overcome inertia and - * rotate to produce an unmistakable tactile click, while LRA motors give a crisp bump. - * Debounced at 35ms to ensure smooth performance during rapid flings. + * Drum-wheel scroll tick pulse. + * 35ms pulse overcomes ERM motor inertia while delivering distinct tactile feedback on LRA hardware. + * Debounced at 35ms to prevent pulse queueing during rapid flings. */ fun performWheelTick(context: Context, view: View? = null) { val now = SystemClock.uptimeMillis() diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/utils/HistoricalIconManager.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/utils/HistoricalIconManager.kt similarity index 92% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/utils/HistoricalIconManager.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/utils/HistoricalIconManager.kt index 185cc4f..68ccc33 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/utils/HistoricalIconManager.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/utils/HistoricalIconManager.kt @@ -15,8 +15,7 @@ import java.net.HttpURLConnection import java.net.URL /** - * OriginalIcon (Historical Instagram Brand Icon) - * Represents an authentic Instagram brand era logo downloaded dynamically on demand. + * Metadata descriptor for a historical brand icon available for on-demand download. */ data class OriginalIcon( val id: String, @@ -29,9 +28,7 @@ data class OriginalIcon( typealias HistoricalIcon = OriginalIcon /** - * HistoricalIconManager - * Dynamically downloads authentic original Instagram brand logos on demand, - * preventing any APK size bloat. + * Downloads and caches historical brand icons into app cache storage on demand. */ object HistoricalIconManager { private const val TAG = "HistoricalIconManager" @@ -75,7 +72,7 @@ object HistoricalIconManager { val downloadProgress: StateFlow = _downloadProgress.asStateFlow() /** - * Ensures all original icons are loaded from cache or downloaded on-the-fly. + * Loads cached icons from disk or downloads missing assets sequentially. */ suspend fun loadHistoricalIcons(context: Context) { if (_isLoading.value) return @@ -85,7 +82,6 @@ object HistoricalIconManager { val iconDir = File(context.cacheDir, "original_icons").apply { mkdirs() } val loadedMap = HashMap() - // 1. First check existing disk cache for (icon in ORIGINAL_ICONS) { val file = File(iconDir, "${icon.id}.png") if (file.exists() && file.length() > 0) { @@ -94,7 +90,6 @@ object HistoricalIconManager { } _downloadedIcons.value = loadedMap.toMap() - // 2. Identify missing icons to download val missing = ORIGINAL_ICONS.filter { !loadedMap.containsKey(it.id) } if (missing.isEmpty()) { _downloadProgress.value = 1f diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/utils/MediaDownloadHelper.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/utils/MediaDownloadHelper.kt similarity index 95% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/core/utils/MediaDownloadHelper.kt rename to app/src/main/kotlin/com/ujwal/focusgram/core/utils/MediaDownloadHelper.kt index 14c813b..ba950f0 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/core/utils/MediaDownloadHelper.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/utils/MediaDownloadHelper.kt @@ -30,9 +30,9 @@ data class DownloadProgressState( ) /** - * MediaDownloadHelper - * Native client-side media downloader using Android System DownloadManager & MediaStore. - * Compliant with on-device architecture: downloads directly to user's public Downloads directory. + * Handles media downloads using Android's system [DownloadManager] and [MediaStore]. + * + * Saves directly into the public Downloads directory without intermediate server relays. */ object MediaDownloadHelper { private const val TAG = "MediaDownloadHelper" @@ -47,8 +47,9 @@ object MediaDownloadHelper { private var lastDownloadTimestamp: Long = 0L /** - * Determines whether media is a video based on explicit URL and filename markers. - * Note: NEVER treat "/v/" in CDN URLs as video because all Meta CDN photos contain "/v/". + * Identifies video URLs. + * + * Note: Avoid matching "/v/" alone, as Meta's image CDNs also use "/v/t51" in photo paths. */ fun detectIsVideo(url: String, filename: String = ""): Boolean { val lowerUrl = url.lowercase() @@ -71,22 +72,18 @@ object MediaDownloadHelper { } /** - * Formats target filename according to FocusGram specification: - * Author name (first 10-13 characters) followed by ' - Focusgram.' - * E.g. '@ujwal - Focusgram.mp4' or '@ujwal - Focusgram.jpg'. + * Normalizes output filename format: `@ - Focusgram[slide].`. */ fun formatTargetFilename(rawFilename: String, isVideo: Boolean): String { val extension = if (isVideo) "mp4" else "jpg" var clean = rawFilename.trim() - // Strip known extension if present if (clean.endsWith(".mp4", ignoreCase = true) || clean.endsWith(".jpg", ignoreCase = true) || clean.endsWith(".jpeg", ignoreCase = true) || clean.endsWith(".png", ignoreCase = true) ) { clean = clean.substringBeforeLast('.') } - // Handle slide suffix (e.g. _2 for carousel slides) var slideSuffix = "" val slideMatch = Regex("_(\\d+)$").find(clean) if (slideMatch != null) { @@ -94,7 +91,6 @@ object MediaDownloadHelper { clean = clean.removeSuffix(slideSuffix) } - // Extract author prefix val authorName = when { clean.startsWith("@") -> clean.removePrefix("@").substringBefore(" - ").substringBefore('_').trim() clean.contains(" - Focusgram", ignoreCase = true) -> { @@ -413,10 +409,10 @@ object MediaDownloadHelper { } private fun saveBytesToDownloads(context: Context, bytes: ByteArray, filename: String, isVideo: Boolean) { - // Inspect magic bytes to verify whether bytes are an image or a video + // Inspect header magic bytes (JPEG SOI 0xFFD8FF or PNG 0x89504E47) to guard against misidentified media val isActuallyImage = bytes.size >= 4 && ( - (bytes[0] == 0xFF.toByte() && bytes[1] == 0xD8.toByte() && bytes[2] == 0xFF.toByte()) || // JPEG - (bytes[0] == 0x89.toByte() && bytes[1] == 0x50.toByte() && bytes[2] == 0x4E.toByte() && bytes[3] == 0x47.toByte()) // PNG + (bytes[0] == 0xFF.toByte() && bytes[1] == 0xD8.toByte() && bytes[2] == 0xFF.toByte()) || + (bytes[0] == 0x89.toByte() && bytes[1] == 0x50.toByte() && bytes[2] == 0x4E.toByte() && bytes[3] == 0x47.toByte()) ) val resolvedIsVideo = if (isActuallyImage) false else isVideo val resolvedFilename = if (isActuallyImage && filename.endsWith(".mp4", ignoreCase = true)) { diff --git a/app/src/main/kotlin/com/ujwal/focusgram/core/utils/TimeFormatters.kt b/app/src/main/kotlin/com/ujwal/focusgram/core/utils/TimeFormatters.kt new file mode 100644 index 0000000..68e1df8 --- /dev/null +++ b/app/src/main/kotlin/com/ujwal/focusgram/core/utils/TimeFormatters.kt @@ -0,0 +1,35 @@ +package com.ujwal.focusgram.core.utils + +import java.util.Locale + +object TimeFormatters { + /** + * Formats duration in minutes to a human-readable "Xh Ym", "Xh", or "Ym". + * Never outputs raw minutes over 59 (e.g. 270 min -> "4h 30m"). + */ + fun formatMinutes(minutes: Int): String { + if (minutes <= 0) return "0m" + val hours = minutes / 60 + val mins = minutes % 60 + return when { + hours > 0 && mins > 0 -> "${hours}h ${mins}m" + hours > 0 -> "${hours}h" + else -> "${mins}m" + } + } + + /** + * Formats seconds into MM:SS or HH:MM:SS. + */ + fun formatSeconds(seconds: Int): String { + if (seconds <= 0) return "00:00" + val hours = seconds / 3600 + val mins = (seconds % 3600) / 60 + val secs = seconds % 60 + return if (hours > 0) { + String.format(Locale.US, "%d:%02d:%02d", hours, mins, secs) + } else { + String.format(Locale.US, "%02d:%02d", mins, secs) + } + } +} diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockScreen.kt similarity index 98% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockScreen.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockScreen.kt index bfcb267..fcce026 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockScreen.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockScreen.kt @@ -59,8 +59,7 @@ import kotlinx.coroutines.launch import kotlin.math.roundToInt /** - * AppLockScreen - * Hardware-backed PIN and Biometric lock gate protecting FocusGram. + * PIN keypad and Biometric authentication gate. */ @Composable fun AppLockScreen( @@ -82,6 +81,7 @@ fun AppLockScreen( fun triggerBiometricPrompt() { if (!isBiometricAllowed) return + // BiometricPrompt requires a FragmentActivity. Unwraps ContextWrapper chains (e.g. TintContextWrapper). var ctx = context while (ctx is android.content.ContextWrapper) { if (ctx is FragmentActivity) break @@ -132,7 +132,6 @@ fun AppLockScreen( } } - // Trigger BiometricPrompt only if enabled and allowed LaunchedEffect(isBiometricAllowed) { if (isBiometricAllowed) { triggerBiometricPrompt() @@ -231,7 +230,6 @@ fun AppLockScreen( Spacer(modifier = Modifier.weight(1f)) - // Lock Icon Box( modifier = Modifier .size(68.dp) @@ -265,7 +263,6 @@ fun AppLockScreen( Spacer(modifier = Modifier.height(36.dp)) - // 4 PIN Dots with Shake Offset Row( modifier = Modifier .offset { IntOffset(shakeOffset.value.roundToInt(), 0) } @@ -285,7 +282,6 @@ fun AppLockScreen( } } - // Error Text if (errorMessage != null) { Text( text = errorMessage!!, @@ -299,7 +295,6 @@ fun AppLockScreen( Spacer(modifier = Modifier.weight(1.5f)) - // 3x4 Numeric Keypad Column( modifier = Modifier .fillMaxWidth() diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockSettingsPage.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockSettingsPage.kt similarity index 98% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockSettingsPage.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockSettingsPage.kt index 8c42074..8568ace 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockSettingsPage.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockSettingsPage.kt @@ -36,6 +36,9 @@ private val IOSBlue = Color(0xFF007AFF) private val IOSGreen = Color(0xFF34C759) private val IOSOrange = Color(0xFFFF9500) +/** + * Settings page for toggling PIN and biometric authentication requirements. + */ @Composable fun AppLockSettingsPage( repository: SettingsRepository, diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockSetupScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockSetupScreen.kt similarity index 98% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockSetupScreen.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockSetupScreen.kt index 27b6434..4c171ff 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockSetupScreen.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/AppLockSetupScreen.kt @@ -52,8 +52,7 @@ import com.ujwal.focusgram.core.ui.theme.TextSecondary import kotlinx.coroutines.launch /** - * AppLockSetupScreen - * 4-digit PIN creation and confirmation with AES-256 GCM encrypted storage. + * 4-digit PIN creation and confirmation step screen. */ @Composable fun AppLockSetupScreen( @@ -175,7 +174,6 @@ fun AppLockSetupScreen( Spacer(modifier = Modifier.height(30.dp)) - // PIN Dots Indicator Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { repeat(4) { idx -> val isFilled = idx < currentInput.length @@ -199,7 +197,6 @@ fun AppLockSetupScreen( Spacer(modifier = Modifier.weight(1f)) - // Numeric Keypad NumericKeypad( onDigit = { onDigitPress(it) }, onDelete = { onBackspace() } diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/ChatLockScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/ChatLockScreen.kt similarity index 96% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/ChatLockScreen.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/applock/ChatLockScreen.kt index 60980c4..0e70c8d 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/ChatLockScreen.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/applock/ChatLockScreen.kt @@ -69,9 +69,7 @@ private val ChatLockAccentGlow = Color(0xFFC77DFF) private val KeypadCardBg = Color(0xFF16161A) /** - * ChatLockScreen - * Bespoke, distinct hardware-backed PIN and Biometric security gate specifically - * tailored for locked Direct Messages and private conversations. + * PIN and biometric authentication gate for locked Direct Messages. */ @Composable fun ChatLockScreen( @@ -93,6 +91,7 @@ fun ChatLockScreen( fun triggerBiometricPrompt() { if (!isBiometricAllowed) return + // BiometricPrompt requires a FragmentActivity. Unwraps ContextWrapper chains. var ctx = context while (ctx is android.content.ContextWrapper) { if (ctx is FragmentActivity) break @@ -143,7 +142,6 @@ fun ChatLockScreen( } } - // Trigger Biometric only if explicitly allowed and configured LaunchedEffect(isBiometricAllowed) { if (isBiometricAllowed) { triggerBiometricPrompt() @@ -236,7 +234,6 @@ fun ChatLockScreen( .padding(horizontal = 28.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - // Top Navigation Bar (Exit / Cancel) Row( modifier = Modifier .fillMaxWidth() @@ -263,7 +260,6 @@ fun ChatLockScreen( Spacer(modifier = Modifier.size(36.dp)) } - // Subtle privacy status pill Row( modifier = Modifier .clip(RoundedCornerShape(12.dp)) @@ -293,7 +289,6 @@ fun ChatLockScreen( Spacer(modifier = Modifier.weight(0.7f)) - // Chat Lock Shield Badge Box( modifier = Modifier .size(72.dp) @@ -337,7 +332,6 @@ fun ChatLockScreen( Spacer(modifier = Modifier.height(28.dp)) - // 4 PIN Dots with Smooth Glow & Shake Animation Row( modifier = Modifier .offset { IntOffset(shakeOffset.value.roundToInt(), 0) } @@ -362,7 +356,6 @@ fun ChatLockScreen( } } - // Error Message if (errorMessage != null) { Text( text = errorMessage ?: "", @@ -378,14 +371,12 @@ fun ChatLockScreen( Spacer(modifier = Modifier.weight(1.2f)) - // Modern 3x4 Keypad Column( modifier = Modifier .fillMaxWidth() .padding(bottom = 20.dp), verticalArrangement = Arrangement.spacedBy(14.dp) ) { - // Only render "BIO" key if biometric is explicitly allowed & enrolled! val bioKey = if (isBiometricAllowed) "BIO" else "" val keypadRows = listOf( listOf("1", "2", "3"), diff --git a/app/src/main/kotlin/com/ujwal/focusgram/feature/diagnostics/DebugMenuScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/diagnostics/DebugMenuScreen.kt new file mode 100644 index 0000000..ae65656 --- /dev/null +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/diagnostics/DebugMenuScreen.kt @@ -0,0 +1,334 @@ +package com.ujwal.focusgram.feature.diagnostics + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FilterChipDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.ujwal.focusgram.core.datastore.SettingsRepository +import com.ujwal.focusgram.core.model.FocusSettings +import com.ujwal.focusgram.core.ui.components.FocusGramCard +import com.ujwal.focusgram.core.ui.components.FocusGramSectionHeader +import com.ujwal.focusgram.core.ui.components.FocusGramTopBar +import com.ujwal.focusgram.core.ui.theme.AlertRed +import com.ujwal.focusgram.core.ui.theme.DarkCard +import com.ujwal.focusgram.core.ui.theme.DarkCardElevated +import com.ujwal.focusgram.core.ui.theme.FocusGold +import com.ujwal.focusgram.core.ui.theme.FocusGreen +import com.ujwal.focusgram.core.ui.theme.InstagramBlue +import com.ujwal.focusgram.core.ui.theme.PureBlack +import com.ujwal.focusgram.core.ui.theme.TextMuted +import com.ujwal.focusgram.core.ui.theme.TextPrimary +import com.ujwal.focusgram.core.ui.theme.TextSecondary + +/** + * Developer diagnostics screen for inspecting state, testing simulation, and + * dynamically adjusting active session time and guardrail bypasses. + */ +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun DebugMenuScreen( + repository: SettingsRepository, + onBack: () -> Unit, + onSimulateUpdate: () -> Unit = {}, + onSimulateNotice: () -> Unit = {}, + onResetLimits: () -> Unit = {}, + isLimitsBypassed: Boolean = false, + onToggleLimitsBypass: () -> Unit = onResetLimits, + currentSessionDurationMinutes: Int = 15, + currentSessionRemainingSeconds: Int = 15 * 60, + currentSessionElapsedSeconds: Int = 0, + onAdjustSessionTime: (newDurationMinutes: Int, newRemainingSeconds: Int, newElapsedSeconds: Int) -> Unit = { _, _, _ -> }, + modifier: Modifier = Modifier +) { + val settings by repository.settingsFlow.collectAsState(initial = FocusSettings()) + var customMinutesInput by remember { mutableStateOf(currentSessionDurationMinutes.toString()) } + + Column( + modifier = modifier + .fillMaxSize() + .background(PureBlack) + .padding(WindowInsets.statusBars.asPaddingValues()) + ) { + FocusGramTopBar( + title = "Developer Diagnostics", + onBack = onBack + ) + + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(14.dp) + ) { + item { + FocusGramCard( + modifier = Modifier.fillMaxWidth(), + backgroundColor = DarkCardElevated + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = "Current State", + style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), + color = FocusGold + ) + Spacer(modifier = Modifier.height(6.dp)) + Text( + text = "AppLock: ${settings.appLockEnabled} · Daily Limit: ${settings.dailyLimitMinutes}m\n" + + "Cooldown: ${settings.cooldownMinutes}m · FirstRun: ${settings.isFirstRun}", + style = MaterialTheme.typography.bodyMedium, + color = TextPrimary + ) + } + } + } + + item { + FocusGramSectionHeader(title = "Active Session Controller") + } + + item { + FocusGramCard( + modifier = Modifier.fillMaxWidth(), + backgroundColor = DarkCardElevated + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = "Adjust Session Time", + style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), + color = FocusGold + ) + + Text( + text = "Duration: ${currentSessionDurationMinutes}m · Remaining: ${currentSessionRemainingSeconds / 60}m ${currentSessionRemainingSeconds % 60}s · Elapsed: ${currentSessionElapsedSeconds / 60}m ${currentSessionElapsedSeconds % 60}s", + style = MaterialTheme.typography.bodyMedium, + color = TextPrimary + ) + + // Quick +/- adjustments + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + OutlinedButton( + onClick = { + val newDuration = currentSessionDurationMinutes + 5 + val newRemaining = currentSessionRemainingSeconds + 300 + onAdjustSessionTime(newDuration, newRemaining, currentSessionElapsedSeconds) + }, + modifier = Modifier.weight(1f) + ) { + Text("+5m") + } + OutlinedButton( + onClick = { + val newDuration = currentSessionDurationMinutes + 15 + val newRemaining = currentSessionRemainingSeconds + 900 + onAdjustSessionTime(newDuration, newRemaining, currentSessionElapsedSeconds) + }, + modifier = Modifier.weight(1f) + ) { + Text("+15m") + } + OutlinedButton( + onClick = { + val newRemaining = (currentSessionRemainingSeconds - 300).coerceAtLeast(0) + val newDuration = maxOf(1, currentSessionDurationMinutes - 5) + onAdjustSessionTime(newDuration, newRemaining, currentSessionElapsedSeconds) + }, + modifier = Modifier.weight(1f) + ) { + Text("-5m") + } + OutlinedButton( + onClick = { + // Set remaining to 60s for immediate testing of sunset colors & session expiry + val newRemaining = 60 + val newDuration = maxOf(1, (currentSessionElapsedSeconds + 60 + 59) / 60) + onAdjustSessionTime(newDuration, newRemaining, currentSessionElapsedSeconds) + }, + modifier = Modifier.weight(1.3f) + ) { + Text("Set 1m") + } + } + + // Preset chips + Text( + text = "Presets:", + style = MaterialTheme.typography.labelMedium, + color = TextMuted + ) + FlowRow( + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + listOf(1, 5, 9, 15, 26, 27, 32, 45, 60).forEach { mins -> + FilterChip( + selected = currentSessionDurationMinutes == mins, + onClick = { + customMinutesInput = mins.toString() + val newRemaining = if (currentSessionElapsedSeconds > 0) { + (mins * 60 - currentSessionElapsedSeconds).coerceAtLeast(0) + } else { + mins * 60 + } + val newElapsed = if (currentSessionElapsedSeconds > 0) currentSessionElapsedSeconds else 0 + onAdjustSessionTime(mins, newRemaining, newElapsed) + }, + label = { Text("${mins}m") }, + colors = FilterChipDefaults.filterChipColors( + selectedContainerColor = FocusGold, + selectedLabelColor = PureBlack + ) + ) + } + } + + // Custom minutes input field + OutlinedTextField( + value = customMinutesInput, + onValueChange = { customMinutesInput = it.filter { ch -> ch.isDigit() } }, + label = { Text("Set Custom Minutes") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = FocusGold, + focusedLabelColor = FocusGold, + focusedTextColor = TextPrimary, + unfocusedTextColor = TextPrimary + ) + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Button( + onClick = { + val mins = customMinutesInput.toIntOrNull() ?: currentSessionDurationMinutes + if (mins > 0) { + val newRemaining = if (currentSessionElapsedSeconds > 0) { + (mins * 60 - currentSessionElapsedSeconds).coerceAtLeast(0) + } else { + mins * 60 + } + val newElapsed = if (currentSessionElapsedSeconds > 0) currentSessionElapsedSeconds else 0 + onAdjustSessionTime(mins, newRemaining, newElapsed) + } + }, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors(containerColor = FocusGold, contentColor = PureBlack) + ) { + Text("Set Total Duration", fontWeight = FontWeight.Bold) + } + + Button( + onClick = { + val mins = customMinutesInput.toIntOrNull() ?: 15 + if (mins > 0) { + val newRemaining = mins * 60 + val newDuration = maxOf(mins, (currentSessionElapsedSeconds + newRemaining + 59) / 60) + onAdjustSessionTime(newDuration, newRemaining, currentSessionElapsedSeconds) + } + }, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors(containerColor = InstagramBlue) + ) { + Text("Set Remaining", fontWeight = FontWeight.Bold) + } + } + } + } + } + + item { + FocusGramSectionHeader(title = "Testing & Simulation") + } + + item { + FocusGramCard( + modifier = Modifier.fillMaxWidth(), + backgroundColor = DarkCardElevated + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + Button( + onClick = onSimulateUpdate, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors(containerColor = InstagramBlue) + ) { + Text("Simulate Update Available (v3.1.0)") + } + Button( + onClick = onSimulateNotice, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors(containerColor = FocusGreen) + ) { + Text("Simulate Remote Notice (popup.json)") + } + Button( + onClick = onToggleLimitsBypass, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + containerColor = if (isLimitsBypassed) AlertRed else FocusGold, + contentColor = if (isLimitsBypassed) Color.White else PureBlack + ) + ) { + Text( + text = if (isLimitsBypassed) "Don't Skip Cooldowns & Daily Limits" else "Skip Cooldowns & Daily Limits", + fontWeight = FontWeight.Bold + ) + } + } + } + } + + item { + Spacer(modifier = Modifier.height(30.dp)) + } + } + } +} diff --git a/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelHistory.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelHistory.kt new file mode 100644 index 0000000..35dec26 --- /dev/null +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelHistory.kt @@ -0,0 +1,360 @@ +package com.ujwal.focusgram.feature.history + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import org.json.JSONArray +import org.json.JSONObject +import java.io.File +import java.util.concurrent.Executors + +/** + * Persisted record for a watched Reel item. + * + * [author] and [caption] are populated asynchronously as DOM scrapers find them. + */ +data class ReelEntry( + val code: String, + val author: String, + val caption: String, + val lastSeen: Long, + val views: Int, +) { + val url: String get() = "https://www.instagram.com/reel/${code.removePrefix("reel_")}/" +} + +/** + * Manages locally-cached history of viewed Reels, persisted to a JSON file. + * + * Keeps up to 500 entries, newest first. Thread-safe via synchronized lock and single-thread executor. + */ +class ReelHistory(private val file: File) { + private val lock = Any() + private val io = Executors.newSingleThreadExecutor() + private val sessionCodes = HashSet() + private var entries: List = load() + + var onNewReelWatched: ((String) -> Unit)? = null + + private fun getTodayStartMillis(): Long { + val cal = java.util.Calendar.getInstance().apply { + set(java.util.Calendar.HOUR_OF_DAY, 0) + set(java.util.Calendar.MINUTE, 0) + set(java.util.Calendar.SECOND, 0) + set(java.util.Calendar.MILLISECOND, 0) + } + return cal.timeInMillis + } + + private fun calculateTodayCount(list: List): Int { + val start = getTodayStartMillis() + return list.count { it.lastSeen >= start } + } + + private val _items = MutableStateFlow(entries) + val items: StateFlow> = _items + + private val _sessionCount = MutableStateFlow(0) + val sessionCount: StateFlow = _sessionCount + + private val _todayCount = MutableStateFlow(calculateTodayCount(entries)) + val todayCount: StateFlow = _todayCount + + companion object { + private val IGNORED_CODES = setOf( + "home", "explore", "reels", "reel", "direct", "accounts", + "stories", "p", "about", "legal", "videos", "audio", "clips", + "trending", "foryou", "following", "live" + ) + private val GENERIC_AUTHORS = setOf( + "home", "explore", "reels", "reel", "direct", "instagram", + "instagram creator", "instagram user", "instagram video", "creator", "user", "" + ) + private val GENERIC_CAPTIONS = setOf( + "instagram reel", "reel", "reels", "instagram", "instagram video", "video", "" + ) + + fun normalizeCaption(caption: String): String { + return caption.lowercase() + .replace(Regex("\\s+"), " ") + .replace(Regex("[^a-z0-9 ]"), "") + .trim() + } + + fun isMatchingReel( + codeA: String, authorA: String, captionA: String, + codeB: String, authorB: String, captionB: String + ): Boolean { + val cleanCodeA = if (codeA.startsWith("reel_")) codeA.removePrefix("reel_") else codeA + val cleanCodeB = if (codeB.startsWith("reel_")) codeB.removePrefix("reel_") else codeB + val cleanAuthorA = authorA.trim().removePrefix("@").lowercase() + val cleanAuthorB = authorB.trim().removePrefix("@").lowercase() + + val validCodeA = cleanCodeA.length >= 4 && cleanCodeA !in IGNORED_CODES && cleanCodeA != cleanAuthorA + val validCodeB = cleanCodeB.length >= 4 && cleanCodeB !in IGNORED_CODES && cleanCodeB != cleanAuthorB + + // 1. Direct shortcode match + if (validCodeA && validCodeB && cleanCodeA.equals(cleanCodeB, ignoreCase = true)) { + return true + } + + // 2. Same author matching + if (cleanAuthorA.isNotBlank() && cleanAuthorA == cleanAuthorB) { + val normA = normalizeCaption(captionA) + val normB = normalizeCaption(captionB) + if (normA.isNotBlank() && normB.isNotBlank()) { + val prefixLen = minOf(normA.length, normB.length, 30) + if (prefixLen >= 8 && normA.take(prefixLen) == normB.take(prefixLen)) { + return true + } + } + // Author placeholder matching (only when captions match, never blindly merge different reels) + val isAuthorCodeA = cleanCodeA == cleanAuthorA || !validCodeA + val isAuthorCodeB = cleanCodeB == cleanAuthorB || !validCodeB + if (isAuthorCodeA && isAuthorCodeB) { + if (normA.isNotBlank() && normB.isNotBlank() && normA.take(15) == normB.take(15)) { + return true + } + } + } + + return false + } + } + + private fun sanitizeCaption(rawCaption: String, cleanAuthor: String): String { + val clean = rawCaption.trim() + val lower = clean.lowercase() + val isGeneric = lower.isBlank() || + lower in GENERIC_CAPTIONS || + lower.startsWith("instagram reel") || + lower.startsWith("reel by @") || + lower.startsWith("reel by ") || + (cleanAuthor.isNotBlank() && lower == cleanAuthor.lowercase()) || + lower.endsWith("· original audio") || + lower.endsWith("original audio") + return if (isGeneric) "" else clean + } + + private fun isCaptionInvalid(caption: String, author: String): Boolean { + return sanitizeCaption(caption, author).isBlank() + } + + fun onWatched(code: String, author: String, caption: String): Int = synchronized(lock) { + val cleanCode = code.trim().removePrefix("/").removeSuffix("/") + val lowerCode = cleanCode.lowercase() + if (cleanCode.length < 5 || lowerCode in IGNORED_CODES) { + return _sessionCount.value + } + val finalCode = if (cleanCode.startsWith("reel_")) cleanCode.removePrefix("reel_") else cleanCode + if (finalCode.length < 5 || finalCode.lowercase() in IGNORED_CODES) { + return _sessionCount.value + } + + val cleanAuthor = author.trim().removePrefix("@") + val lowerAuthor = cleanAuthor.lowercase() + val isGenericAuthor = lowerAuthor in GENERIC_AUTHORS || lowerAuthor.startsWith("instagram") + val sanitizedAuthor = if (isGenericAuthor) "" else cleanAuthor + + val sanitizedCaption = sanitizeCaption(caption, sanitizedAuthor) + + val isNew = sessionCodes.add(finalCode) + val matchingEntries = entries.filter { existing -> + isMatchingReel( + existing.code, existing.author, existing.caption, + finalCode, sanitizedAuthor, sanitizedCaption + ) || existing.code.equals(finalCode, ignoreCase = true) + } + + val resolvedAuthor = sanitizedAuthor.ifEmpty { matchingEntries.firstOrNull { it.author.isNotBlank() }?.author ?: "" } + val resolvedCaption = sanitizedCaption.ifEmpty { + val oldCaption = matchingEntries.firstOrNull { it.caption.isNotBlank() }?.caption ?: "" + if (!isCaptionInvalid(oldCaption, resolvedAuthor)) oldCaption else "" + } + + val bestCode = if (finalCode.equals(sanitizedAuthor, ignoreCase = true) && matchingEntries.any { !it.code.equals(sanitizedAuthor, ignoreCase = true) && it.code.length >= 4 }) { + matchingEntries.first { !it.code.equals(sanitizedAuthor, ignoreCase = true) && it.code.length >= 4 }.code + } else { + finalCode + } + + val maxPriorViews = matchingEntries.maxOfOrNull { it.views } ?: 0 + val newViews = maxOf(maxPriorViews, matchingEntries.size) + 1 + + val updated = ReelEntry( + code = bestCode, + author = resolvedAuthor, + caption = resolvedCaption, + lastSeen = System.currentTimeMillis(), + views = newViews, + ) + + val remaining = entries.filter { existing -> + !isMatchingReel( + existing.code, existing.author, existing.caption, + finalCode, sanitizedAuthor, sanitizedCaption + ) && !existing.code.equals(finalCode, ignoreCase = true) && + !existing.code.equals(bestCode, ignoreCase = true) + } + + commit(listOf(updated) + remaining) + _sessionCount.value = sessionCodes.size + if (isNew) { + onNewReelWatched?.invoke(finalCode) + } + sessionCodes.size + } + + fun onMeta(code: String, author: String, caption: String) { + synchronized(lock) { + val cleanCode = if (code.startsWith("reel_")) code.removePrefix("reel_") else code + val cleanAuthor = author.trim().removePrefix("@") + val lowerAuthor = cleanAuthor.lowercase() + val isGenericAuthor = lowerAuthor in GENERIC_AUTHORS || lowerAuthor.startsWith("instagram") + val sanitizedAuthor = if (isGenericAuthor) "" else cleanAuthor + val sanitizedCaption = sanitizeCaption(caption, sanitizedAuthor) + + if (entries.none { existing -> + isMatchingReel( + existing.code, existing.author, existing.caption, + cleanCode, sanitizedAuthor, sanitizedCaption + ) || existing.code.equals(cleanCode, ignoreCase = true) + }) return + + commit(entries.map { + if (isMatchingReel(it.code, it.author, it.caption, cleanCode, sanitizedAuthor, sanitizedCaption) || + it.code.equals(cleanCode, ignoreCase = true)) { + val resolvedAuthor = sanitizedAuthor.ifEmpty { it.author } + val currentCaptionInvalid = isCaptionInvalid(it.caption, resolvedAuthor) + val resolvedCaption = if (sanitizedCaption.isNotEmpty()) { + sanitizedCaption + } else if (!currentCaptionInvalid) { + it.caption + } else { + "" + } + it.copy( + code = if (cleanCode.length >= 5 && !cleanCode.equals(sanitizedAuthor, ignoreCase = true)) cleanCode else it.code, + author = resolvedAuthor, + caption = resolvedCaption + ) + } else it + }) + } + } + + fun delete(code: String) = synchronized(lock) { commit(entries.filter { it.code != code }) } + + fun deleteMatching(code: String, author: String, caption: String) = synchronized(lock) { + val cleanCode = if (code.startsWith("reel_")) code.removePrefix("reel_") else code + val cleanAuthor = author.trim().removePrefix("@") + val cleanCaption = caption.trim() + commit(entries.filter { existing -> + !isMatchingReel( + existing.code, existing.author, existing.caption, + cleanCode, cleanAuthor, cleanCaption + ) && !existing.code.equals(cleanCode, ignoreCase = true) + }) + } + + fun clear() = synchronized(lock) { commit(emptyList()) } + + fun clearToday() = synchronized(lock) { + val todayStart = getTodayStartMillis() + commit(entries.filter { it.lastSeen < todayStart }) + } + + fun clearPastDays(days: Int) = synchronized(lock) { + val cal = java.util.Calendar.getInstance().apply { + add(java.util.Calendar.DAY_OF_YEAR, -(days - 1)) + set(java.util.Calendar.HOUR_OF_DAY, 0) + set(java.util.Calendar.MINUTE, 0) + set(java.util.Calendar.SECOND, 0) + set(java.util.Calendar.MILLISECOND, 0) + } + val cutoff = cal.timeInMillis + commit(entries.filter { it.lastSeen < cutoff }) + } + + fun clearThisMonth() = synchronized(lock) { + val cal = java.util.Calendar.getInstance().apply { + set(java.util.Calendar.DAY_OF_MONTH, 1) + set(java.util.Calendar.HOUR_OF_DAY, 0) + set(java.util.Calendar.MINUTE, 0) + set(java.util.Calendar.SECOND, 0) + set(java.util.Calendar.MILLISECOND, 0) + } + val cutoff = cal.timeInMillis + commit(entries.filter { it.lastSeen < cutoff }) + } + + fun resetSession() = synchronized(lock) { + sessionCodes.clear() + _sessionCount.value = 0 + } + + /** + * Restores in-memory session reel state across app restarts for an ongoing session. + * Guarantees sessionCount will never regress below fallbackCount even if individual codes + * were partially persisted. + */ + fun restoreSession(codes: Set, fallbackCount: Int = 0) = synchronized(lock) { + sessionCodes.clear() + sessionCodes.addAll(codes) + if (sessionCodes.size < fallbackCount) { + for (i in sessionCodes.size until fallbackCount) { + sessionCodes.add("restored_reel_$i") + } + } + _sessionCount.value = sessionCodes.size + } + + fun getSessionCodes(): Set = synchronized(lock) { + sessionCodes.toSet() + } + + private fun commit(list: List) { + entries = list.take(500) + _items.value = entries + _todayCount.value = calculateTodayCount(entries) + val snapshot = entries + io.execute { save(snapshot) } + } + + private fun save(list: List) { + try { + val arr = JSONArray() + list.forEach { + arr.put( + JSONObject() + .put("code", it.code) + .put("author", it.author) + .put("caption", it.caption) + .put("lastSeen", it.lastSeen) + .put("views", it.views) + ) + } + // Writes to a temporary file before renaming to prevent corruption if killed mid-write + val tmp = File(file.parentFile, file.name + ".tmp") + tmp.writeText(arr.toString()) + tmp.renameTo(file) + } catch (_: Exception) {} + } + + private fun load(): List = try { + if (!file.exists()) emptyList() else { + val arr = JSONArray(file.readText()) + (0 until arr.length()).map { + val o = arr.getJSONObject(it) + ReelEntry( + o.getString("code"), + o.optString("author"), + o.optString("caption"), + o.optLong("lastSeen"), + o.optInt("views", 1) + ) + } + } + } catch (_: Exception) { + emptyList() + } +} diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelMessageDelegate.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelMessageDelegate.kt similarity index 90% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelMessageDelegate.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelMessageDelegate.kt index e3dcf4c..4b8a52d 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelMessageDelegate.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelMessageDelegate.kt @@ -4,7 +4,11 @@ import org.json.JSONObject import org.mozilla.geckoview.GeckoResult import org.mozilla.geckoview.WebExtension +/** + * Bridges WebExtension messages from content scripts into [ReelHistory]. + */ class ReelMessageDelegate(private val history: ReelHistory) : WebExtension.MessageDelegate { + // Instagram shortcode regex: alphanumeric with underscores, dashes, dots, at least 3 chars private val codeRe = Regex("^[A-Za-z0-9_.-]{3,}$") override fun onConnect(port: WebExtension.Port) { diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelsHistoryRepository.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelsHistoryRepository.kt similarity index 92% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelsHistoryRepository.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelsHistoryRepository.kt index bbb40ca..59b0e2c 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelsHistoryRepository.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelsHistoryRepository.kt @@ -10,13 +10,10 @@ import java.util.Locale import java.util.UUID /** - * ReelsHistoryRepository - * Manages locally stored metadata of reels viewed by the user. + * Repository for Room-backed Reels viewing history. * - * ARCHITECTURE RULES: - * - 100% on-device SQLite (Room). - * - Zero video media is downloaded or cached to disk. - * - Zero network calls or external logging. + * Keeps up to [MAX_ENTRIES] records. Only stores metadata (URLs, timestamps, titles); + * no media is cached or transmitted off-device. */ class ReelsHistoryRepository( private val reelsHistoryDao: ReelsHistoryDao diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelsHistoryScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelsHistoryScreen.kt similarity index 56% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelsHistoryScreen.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelsHistoryScreen.kt index edaca7d..299dede 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelsHistoryScreen.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/history/ReelsHistoryScreen.kt @@ -1,6 +1,8 @@ package com.ujwal.focusgram.feature.history import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -64,9 +66,16 @@ import java.util.Date import java.util.Locale /** - * ReelsHistoryScreen + * UI representation of a watched reel with aggregated rewatch counts. + */ +data class DisplayReelItem( + val entry: ReelEntry, + val rewatchCount: Int, + val latestTimestamp: Long +) + +/** * Searchable on-device viewer for watched Reels history. - * Zero video media downloaded or cached to disk. */ @Composable fun ReelsHistoryScreen( @@ -84,15 +93,69 @@ fun ReelsHistoryScreen( val allReels by reelHistory.items.collectAsState() val reels = remember(allReels, searchQuery) { - val deduplicated = allReels.distinctBy { it.code.ifBlank { it.url } } + val groups = LinkedHashMap>() + for (item in allReels) { + val cleanCode = item.code.trim() + .removePrefix("reel_") + .removePrefix("/") + .removeSuffix("/") + .substringAfterLast("/reel/") + .substringAfterLast("/reels/") + .substringBefore("?") + .substringBefore("&") + .substringBefore("/") + .trim() + .lowercase() + + val ignoredCodes = setOf("reels", "reel", "videos", "audio", "clips", "home", "explore", "direct", "stories", "") + val cleanAuthor = item.author.trim().lowercase().removePrefix("@") + val cleanCaption = item.caption.trim().lowercase() + val isUsernameCode = cleanCode == cleanAuthor + + val validCode = cleanCode.length >= 4 && cleanCode !in ignoredCodes && !isUsernameCode + // Prioritize unique shortcode first so cards don't duplicate when metadata arrives + val key = if (validCode) { + "code_$cleanCode" + } else if (cleanAuthor.isNotBlank() && cleanCaption.isNotBlank()) { + "meta_${cleanAuthor}___${cleanCaption.take(80)}" + } else { + "url_${item.url.trim().substringBefore("?").trimEnd('/')}" + } + groups.getOrPut(key) { mutableListOf() }.add(item) + } + + val deduplicated = groups.values.map { list -> + val latestSeen = list.maxOf { it.lastSeen } + val maxViews = list.maxOf { it.views } + val totalRewatchCount = maxOf(maxViews, list.size) + val bestAuthor = list.firstOrNull { it.author.isNotBlank() }?.author ?: list.first().author + val bestCaption = list.firstOrNull { it.caption.isNotBlank() }?.caption ?: list.first().caption + val bestCode = list.firstOrNull { + it.code.isNotBlank() && it.code.length >= 4 && it.code.lowercase() != it.author.trim().lowercase().removePrefix("@") + }?.code ?: list.first().code + + val representative = list.first().copy( + code = bestCode, + author = bestAuthor, + caption = bestCaption, + lastSeen = latestSeen, + views = totalRewatchCount + ) + DisplayReelItem( + entry = representative, + rewatchCount = totalRewatchCount, + latestTimestamp = latestSeen + ) + }.sortedByDescending { it.latestTimestamp } + if (searchQuery.isBlank()) { deduplicated } else { val q = searchQuery.trim().lowercase() deduplicated.filter { - it.author.lowercase().contains(q) || - it.caption.lowercase().contains(q) || - it.code.lowercase().contains(q) + it.entry.author.lowercase().contains(q) || + it.entry.caption.lowercase().contains(q) || + it.entry.code.lowercase().contains(q) } } } @@ -119,8 +182,6 @@ fun ReelsHistoryScreen( } ) - - // Local search filter field OutlinedTextField( value = searchQuery, onValueChange = { searchQuery = it }, @@ -185,11 +246,13 @@ fun ReelsHistoryScreen( .padding(horizontal = 16.dp, vertical = 8.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { - items(reels, key = { it.code }) { item -> + items(reels, key = { "${it.entry.code}_${it.entry.author}_${it.latestTimestamp}" }) { item -> ReelHistoryCard( item = item, - onClick = { onOpenReel(item.url) }, - onDelete = { reelHistory.delete(item.code) } + onClick = { onOpenReel(item.entry.url) }, + onDelete = { + reelHistory.deleteMatching(item.entry.code, item.entry.author, item.entry.caption) + } ) } } @@ -199,25 +262,76 @@ fun ReelsHistoryScreen( if (showClearDialog) { AlertDialog( onDismissRequest = { showClearDialog = false }, - title = { Text("Clear Reels History?", color = TextPrimary) }, - text = { + title = { Text( - "This removes all locally recorded reel visit history.", - color = TextSecondary + text = "Clear Reels History", + color = TextPrimary, + fontWeight = FontWeight.Bold, + fontSize = 18.sp ) }, - containerColor = DarkCard, - confirmButton = { - Button( - onClick = { - reelHistory.clear() - showClearDialog = false - }, - colors = ButtonDefaults.buttonColors(containerColor = AlertRed) + text = { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) ) { - Text("Clear All") + Text( + text = "Choose the timeframe you would like to clear:", + color = TextSecondary, + fontSize = 13.sp, + lineHeight = 18.sp + ) + Spacer(modifier = Modifier.height(2.dp)) + + ClearOptionRow( + label = "Today", + subtitle = "Clear reels watched today", + onClick = { + reelHistory.clearToday() + showClearDialog = false + } + ) + + ClearOptionRow( + label = "3 Days", + subtitle = "Clear reels from the past 3 days", + onClick = { + reelHistory.clearPastDays(3) + showClearDialog = false + } + ) + + ClearOptionRow( + label = "Last Week", + subtitle = "Clear reels from the past 7 days", + onClick = { + reelHistory.clearPastDays(7) + showClearDialog = false + } + ) + + ClearOptionRow( + label = "This Month", + subtitle = "Clear reels from this calendar month", + onClick = { + reelHistory.clearThisMonth() + showClearDialog = false + } + ) + + ClearOptionRow( + label = "All History", + subtitle = "Permanently remove all recorded reel history", + isDestructive = true, + onClick = { + reelHistory.clear() + showClearDialog = false + } + ) } }, + containerColor = DarkCardElevated, + confirmButton = {}, dismissButton = { TextButton(onClick = { showClearDialog = false }) { Text("Cancel", color = TextSecondary) @@ -227,9 +341,57 @@ fun ReelsHistoryScreen( } } +@Composable +private fun ClearOptionRow( + label: String, + subtitle: String, + isDestructive: Boolean = false, + onClick: () -> Unit +) { + Box( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(10.dp)) + .background(if (isDestructive) AlertRed.copy(alpha = 0.12f) else DarkCard) + .border( + width = 1.dp, + color = if (isDestructive) AlertRed.copy(alpha = 0.4f) else DarkBorder, + shape = RoundedCornerShape(10.dp) + ) + .clickable(onClick = onClick) + .padding(horizontal = 14.dp, vertical = 10.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = label, + color = if (isDestructive) AlertRed else TextPrimary, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold + ) + Text( + text = subtitle, + color = TextMuted, + fontSize = 11.sp + ) + } + Icon( + imageVector = Icons.Default.Delete, + contentDescription = null, + tint = if (isDestructive) AlertRed else TextMuted, + modifier = Modifier.size(16.dp) + ) + } + } +} + @Composable private fun ReelHistoryCard( - item: ReelEntry, + item: DisplayReelItem, onClick: () -> Unit, onDelete: () -> Unit ) { @@ -243,7 +405,6 @@ private fun ReelHistoryCard( .padding(12.dp), verticalAlignment = Alignment.CenterVertically ) { - // Thumbnail icon placeholder Box( modifier = Modifier .size(52.dp) @@ -262,9 +423,9 @@ private fun ReelHistoryCard( Spacer(modifier = Modifier.width(14.dp)) Column(modifier = Modifier.weight(1f)) { - if (item.author.isNotBlank()) { + if (item.entry.author.isNotBlank()) { Text( - text = "@${item.author}", + text = "@${item.entry.author}", style = MaterialTheme.typography.labelMedium.copy(fontWeight = FontWeight.Bold), color = InstagramBlue, maxLines = 1, @@ -273,7 +434,7 @@ private fun ReelHistoryCard( Spacer(modifier = Modifier.height(2.dp)) } Text( - text = if (item.caption.isNotBlank()) item.caption else "Reel /${item.code}", + text = if (item.entry.caption.isNotBlank()) item.entry.caption else "Reel /${item.entry.code}", style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Normal), color = TextPrimary, maxLines = 2, @@ -281,10 +442,11 @@ private fun ReelHistoryCard( ) Spacer(modifier = Modifier.height(4.dp)) Row(verticalAlignment = Alignment.CenterVertically) { + val timeStr = formatRelativeTime(item.latestTimestamp) Text( - text = formatRelativeTime(item.lastSeen), + text = if (item.rewatchCount > 1) "$timeStr â€Ē x${item.rewatchCount}" else timeStr, style = MaterialTheme.typography.labelSmall.copy(fontSize = 11.sp), - color = TextMuted + color = if (item.rewatchCount > 1) InstagramBlue else TextMuted ) } } diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/InstagramIcons.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/InstagramIcons.kt similarity index 88% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/InstagramIcons.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/InstagramIcons.kt index ef50666..c290444 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/InstagramIcons.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/InstagramIcons.kt @@ -9,23 +9,7 @@ import androidx.compose.ui.graphics.vector.PathParser import androidx.compose.ui.unit.dp /** - * InstagramIcons - * 100% authentic vector assets extracted directly from the user's custom SVG files: - * - Home: - * Outline: /home/ujwal/Projects/FocusGram/bottom-icons/akar-icons--home-alt1.svg (viewBox 0 0 24 24) - * Filled: /home/ujwal/Projects/FocusGram/bottom-icons/ci--paper-plane(for messages/fluent--home-16-filled.svg (viewBox 0 0 16 16) - * - Search: - * Outline: /home/ujwal/Projects/FocusGram/bottom-icons/akar-icons--search.svg (viewBox 0 0 24 24) - * Filled: /home/ujwal/Projects/FocusGram/bottom-icons/ci--paper-plane(for messages/boxicons--search-filled.svg (viewBox 0 0 24 24) - * - Reels: - * Outline: /home/ujwal/Projects/FocusGram/bottom-icons/selfhst--instagram-reels-dark.svg (viewBox 0 0 512 512) - * Filled: /home/ujwal/Projects/FocusGram/bottom-icons/selfhst--instagram-reels-light.svg (viewBox 0 0 512 512) - * - Direct (Messages): - * Outline: /home/ujwal/Projects/FocusGram/bottom-icons/ci--paper-plane(for messages/DM).svg (viewBox 0 0 24 24) - * Filled: /home/ujwal/Projects/FocusGram/bottom-icons/ci--paper-plane(for messages/DM (Filled).svg (viewBox 0 0 640 640) - * - Profile: - * Outline: /home/ujwal/Projects/FocusGram/bottom-icons/ci--paper-plane(for messages/ant-design--user-outlined.svg (viewBox 0 0 1024 1024) - * Filled: /home/ujwal/Projects/FocusGram/bottom-icons/ci--paper-plane(for messages/boxicons--user-filled.svg (viewBox 0 0 24 24) + * Vector icon assets for navigation tabs and action controls. */ object InstagramIcons { diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/NativeBottomNavBar.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/NativeBottomNavBar.kt similarity index 84% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/NativeBottomNavBar.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/NativeBottomNavBar.kt index f9dab43..96221cf 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/NativeBottomNavBar.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/NativeBottomNavBar.kt @@ -60,6 +60,7 @@ import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PathMeasure import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.lerp import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext @@ -90,14 +91,10 @@ enum class NavTab( } /** - * NativeBottomNavBar - * Authentic OLED navigation island with iOS-style fluid physics and Instagram iconography. - * Features: - * - Real-time light/dark theme adaptation from in-page GeckoView state. - * - Sliding capsule pill indicator and spring icon bounce for fluid tab transitions. - * - Double-click detection on Home & Reels to refresh in-place and scroll to top. - * - Profile long-press (1100ms) with clockwise perimeter loading bar starting above the Home icon. - * - Auto-hide in DMs, comments, search modals, and soft keyboard appearances. + * Navigation bar island rendered as a floating capsule with animated tab transitions. + * + * Supports double-tap on Home/Reels to refresh, and profile long-press to open Settings + * with a perimeter sweep progress animation starting above the Home icon. */ @Composable fun NativeBottomNavBar( @@ -111,6 +108,7 @@ fun NativeBottomNavBar( isDownloading: Boolean = false, isDownloadCompleted: Boolean = false, isDownloadFailed: Boolean = false, + sessionProgress: Float = 0f, isAuthenticated: Boolean = false, isAuthPage: Boolean = false, onNavClick: ((String) -> Unit)? = null, @@ -149,8 +147,15 @@ fun NativeBottomNavBar( } else { isInDirect } - val isExplicitAuthRoute = (lowerUrl.contains("/accounts") && !lowerUrl.contains("/accounts/edit")) || lowerUrl.contains("/login") || lowerUrl.contains("/signup") - val isAuth = isAuthPage || isExplicitAuthRoute + val isExplicitAuthRoute = (lowerUrl.contains("/accounts") && !lowerUrl.contains("/accounts/edit")) || + lowerUrl.contains("/login") || + lowerUrl.contains("/signup") || + lowerUrl.contains("/two_factor") || + lowerUrl.contains("/challenge") || + lowerUrl.contains("/onetap") || + lowerUrl.contains("accountscenter.") || + lowerUrl.contains("facebook.com") + val isAuth = isAuthPage || !isAuthenticated || isExplicitAuthRoute val isVisible = !isAuth && !isImeVisible && !shouldHideInDirect && !isWebInputFocused && !isModalActive && islandMode != IslandMode.HIDDEN // Real-time animated color palette matching Instagram's web theme @@ -229,6 +234,13 @@ fun NativeBottomNavBar( label = "animatedDownloadProgress" ) + // Animated active session perimeter progress + val animatedSessionProgress by animateFloatAsState( + targetValue = if (settings.showSessionTimeInBottomBar) sessionProgress.coerceIn(0f, 1f) else 0f, + animationSpec = tween(durationMillis = 1000, easing = LinearEasing), + label = "animatedSessionProgress" + ) + // Glow animation for download completion (glow out and glow in 2 times) val glowAnim = remember { Animatable(0f) } androidx.compose.runtime.LaunchedEffect(isDownloadCompleted) { @@ -320,7 +332,6 @@ fun NativeBottomNavBar( .border(BorderStroke(1.dp, glassBorderBrush), navShape), contentAlignment = Alignment.Center ) { - // 0. Frosted Glass Blur Background Layer (~13% more blur) Box( modifier = Modifier .fillMaxSize() @@ -329,7 +340,6 @@ fun NativeBottomNavBar( .background(islandBackground) ) - // 0.1 Specular Glass Sheen Gradient (+4% glass reflection) Box( modifier = Modifier .fillMaxSize() @@ -359,7 +369,6 @@ fun NativeBottomNavBar( val startXPx = (totalWidthPx / tabCount) / 2f val cornerRadiusPx = with(density) { min(32.dp.toPx(), totalHeightPx / 2f) } - // 1. Sliding Capsule Indicator (iOS-style physics) val animatedPillOffset by animateDpAsState( targetValue = tabWidthDp * selectedTabIndex, animationSpec = spring( @@ -380,7 +389,6 @@ fun NativeBottomNavBar( .align(Alignment.CenterStart) ) - // 2. Tab Items Row Row( modifier = Modifier .fillMaxSize() @@ -507,49 +515,40 @@ fun NativeBottomNavBar( } } - // 3. Profile Long-Press Animated Clockwise Perimeter Border Indicator + // Profile long-press clockwise perimeter border indicator starting above Home icon val currentProgress = longPressProgressAnim.value if (currentProgress > 0f) { Canvas(modifier = Modifier.fillMaxSize()) { val fullPath = Path().apply { moveTo(startXPx, 0f) - // Top edge to top-right lineTo(totalWidthPx - cornerRadiusPx, 0f) - // Top-right corner arc arcTo( rect = Rect(totalWidthPx - 2 * cornerRadiusPx, 0f, totalWidthPx, 2 * cornerRadiusPx), startAngleDegrees = 270f, sweepAngleDegrees = 90f, forceMoveTo = false ) - // Right edge going down lineTo(totalWidthPx, totalHeightPx - cornerRadiusPx) - // Bottom-right corner arc arcTo( rect = Rect(totalWidthPx - 2 * cornerRadiusPx, totalHeightPx - 2 * cornerRadiusPx, totalWidthPx, totalHeightPx), startAngleDegrees = 0f, sweepAngleDegrees = 90f, forceMoveTo = false ) - // Bottom edge to bottom-left lineTo(cornerRadiusPx, totalHeightPx) - // Bottom-left corner arc arcTo( rect = Rect(0f, totalHeightPx - 2 * cornerRadiusPx, 2 * cornerRadiusPx, totalHeightPx), startAngleDegrees = 90f, sweepAngleDegrees = 90f, forceMoveTo = false ) - // Left edge going up lineTo(0f, cornerRadiusPx) - // Top-left corner arc arcTo( rect = Rect(0f, 0f, 2 * cornerRadiusPx, 2 * cornerRadiusPx), startAngleDegrees = 180f, sweepAngleDegrees = 90f, forceMoveTo = false ) - // Top edge returning to Home start point lineTo(startXPx, 0f) close() } @@ -564,10 +563,10 @@ fun NativeBottomNavBar( path = segmentPath, brush = Brush.linearGradient( colors = listOf( - Color(0xFF833AB4), // Instagram Purple - Color(0xFFFD1D1D), // Instagram Red - Color(0xFFF77737), // Instagram Orange - Color(0xFFFFDC80) // Instagram Yellow + Color(0xFF833AB4), + Color(0xFFFD1D1D), + Color(0xFFF77737), + Color(0xFFFFDC80) ) ), style = Stroke( @@ -578,7 +577,6 @@ fun NativeBottomNavBar( } } - // 4. Media Download Clockwise Perimeter Progress Animation & 2x Completion Glow val currentDlProgress = animatedDownloadProgress val currentGlow = glowAnim.value if (currentDlProgress > 0f || currentGlow > 0f) { @@ -619,22 +617,21 @@ fun NativeBottomNavBar( val blueGradient = Brush.linearGradient( colors = listOf( - Color(0xFF00E5FF), // Bright Cyan - Color(0xFF0091FF), // Electric Sky Blue - Color(0xFF0055FF), // Deep Vivid Blue - Color(0xFF00D2FF) // Cyan-Blue + Color(0xFF00E5FF), + Color(0xFF0091FF), + Color(0xFF0055FF), + Color(0xFF00D2FF) ) ) val failureGradient = Brush.linearGradient( colors = listOf( - Color(0xFFFF3B30), // Bright Red - Color(0xFFCC0000), // Dark Red - Color(0xFF8B0000), // Very Dark Crimson - Color(0xFFFF3B30) // Bright Red + Color(0xFFFF3B30), + Color(0xFFCC0000), + Color(0xFF8B0000), + Color(0xFFFF3B30) ) ) - // 4a. If downloading or failed: Clockwise perimeter sweep starting above Home icon if (currentDlProgress > 0f && currentGlow == 0f) { val pathMeasure = PathMeasure() pathMeasure.setPath(fullPath, false) @@ -652,9 +649,7 @@ fun NativeBottomNavBar( ) } - // 4b. If download completed: 2x glowing outer halo + luminous neon perimeter rim if (currentGlow > 0f) { - // Outer diffused glow halo drawPath( path = fullPath, color = Color(0xFF00D2FF).copy(alpha = 0.6f * currentGlow), @@ -663,7 +658,6 @@ fun NativeBottomNavBar( cap = StrokeCap.Round ) ) - // Core luminous neon rim drawPath( path = fullPath, brush = Brush.linearGradient( @@ -682,6 +676,70 @@ fun NativeBottomNavBar( } } } + + // Active session perimeter progress indicator (golden sweep along the perimeter) + val currentSessionProgress = animatedSessionProgress + if (settings.showSessionTimeInBottomBar && currentSessionProgress > 0f && currentProgress == 0f && currentDlProgress == 0f && currentGlow == 0f) { + Canvas(modifier = Modifier.fillMaxSize()) { + val fullPath = Path().apply { + moveTo(startXPx, 0f) + lineTo(totalWidthPx - cornerRadiusPx, 0f) + arcTo( + rect = Rect(totalWidthPx - 2 * cornerRadiusPx, 0f, totalWidthPx, 2 * cornerRadiusPx), + startAngleDegrees = 270f, + sweepAngleDegrees = 90f, + forceMoveTo = false + ) + lineTo(totalWidthPx, totalHeightPx - cornerRadiusPx) + arcTo( + rect = Rect(totalWidthPx - 2 * cornerRadiusPx, totalHeightPx - 2 * cornerRadiusPx, totalWidthPx, totalHeightPx), + startAngleDegrees = 0f, + sweepAngleDegrees = 90f, + forceMoveTo = false + ) + lineTo(cornerRadiusPx, totalHeightPx) + arcTo( + rect = Rect(0f, totalHeightPx - 2 * cornerRadiusPx, 2 * cornerRadiusPx, totalHeightPx), + startAngleDegrees = 90f, + sweepAngleDegrees = 90f, + forceMoveTo = false + ) + lineTo(0f, cornerRadiusPx) + arcTo( + rect = Rect(0f, 0f, 2 * cornerRadiusPx, 2 * cornerRadiusPx), + startAngleDegrees = 180f, + sweepAngleDegrees = 90f, + forceMoveTo = false + ) + lineTo(startXPx, 0f) + close() + } + + val pathMeasure = PathMeasure() + pathMeasure.setPath(fullPath, false) + val totalLen = pathMeasure.length + val segmentPath = Path() + pathMeasure.getSegment(0f, totalLen * currentSessionProgress.coerceIn(0f, 1f), segmentPath, true) + + val p = currentSessionProgress.coerceIn(0f, 1f) + // Sunset effect: as session approaches completion, colors evolve from noon gold into warm sunset reddish-orange + val sunsetStart = lerp(Color(0xFFFFE57F), Color(0xFFFF8A65), p) + val sunsetMid1 = lerp(Color(0xFFFFD700), Color(0xFFFF7043), p) + val sunsetMid2 = lerp(Color(0xFFFFB300), Color(0xFFFF5722), p) + val sunsetEnd = lerp(Color(0xFFFFA000), Color(0xFFD84315), p) + + drawPath( + path = segmentPath, + brush = Brush.linearGradient( + colors = listOf(sunsetStart, sunsetMid1, sunsetMid2, sunsetEnd) + ), + style = Stroke( + width = 3.dp.toPx(), + cap = StrokeCap.Round + ) + ) + } + } } } } diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/ReelsTopBar.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/ReelsTopBar.kt similarity index 94% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/ReelsTopBar.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/ReelsTopBar.kt index 37c6c98..88a3a0f 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/ReelsTopBar.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/navigation/ReelsTopBar.kt @@ -34,12 +34,8 @@ import androidx.compose.ui.res.painterResource import com.ujwal.focusgram.R /** - * ReelsTopBar - * Minimal Instagram Reels top header: - * - "Reels" (Bold white header on left) - * - Session watched counter (pill in middle) - * - Download icon button (on right, opposite side of Reels) - * - Subtle vertical scrim gradient for legibility over video frames. + * Top header overlay for Reels sessions, displaying the current session view count + * and download trigger with a vertical scrim for legibility over video frames. */ @Composable fun ReelsTopBar( diff --git a/app/src/main/kotlin/com/ujwal/focusgram/feature/notice/RemoteNoticeService.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/notice/RemoteNoticeService.kt new file mode 100644 index 0000000..d8af94b --- /dev/null +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/notice/RemoteNoticeService.kt @@ -0,0 +1,113 @@ +package com.ujwal.focusgram.feature.notice + +import android.content.Context +import android.content.SharedPreferences +import android.util.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.json.JSONObject +import java.io.BufferedReader +import java.net.HttpURLConnection +import java.net.URL + +/** + * Data payload representing a broadcast announcement or critical notice fetched from GitHub. + */ +data class RemoteNotice( + val show: Boolean, + val id: String, + val header: String, + val body: String, + val maxShows: Int = 1, + val buttonText: String = "OK" +) + +/** + * Service that queries GitHub for remote notice/alert configurations (android/popup.json). + * + * Allows maintainers to broadcast critical notices or updates to all clients via a GitHub commit. + * All requests are read-only HTTP GET calls targeting raw GitHub contents. + */ +class RemoteNoticeService(private val context: Context) { + + companion object { + private const val TAG = "RemoteNoticeService" + const val POPUP_URL = + "https://raw.githubusercontent.com/Ujwal223/FocusGram/refs/heads/main/android/popup.json" + private const val PREFS_NAME = "focusgram_remote_notices" + private const val CONNECT_TIMEOUT_MS = 5000 + private const val READ_TIMEOUT_MS = 5000 + + fun getMockNotice(): RemoteNotice { + return RemoteNotice( + show = true, + id = "test_notice_preview", + header = "Test Broadcast Notice", + body = "This is a simulated broadcast notice to verify the dialog rendering, liquid glass styling, and dismiss actions.", + maxShows = 99, + buttonText = "Understood" + ) + } + } + + private val prefs: SharedPreferences by lazy { + context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + } + + /** + * Silently fetches the latest notice from GitHub. + */ + suspend fun fetchNotice(): RemoteNotice? = withContext(Dispatchers.IO) { + try { + val urlString = "$POPUP_URL?t=${System.currentTimeMillis()}" + val url = URL(urlString) + val connection = (url.openConnection() as HttpURLConnection).apply { + requestMethod = "GET" + connectTimeout = CONNECT_TIMEOUT_MS + readTimeout = READ_TIMEOUT_MS + setRequestProperty("Cache-Control", "no-cache") + setRequestProperty("User-Agent", "FocusGram-Android-Client") + } + + if (connection.responseCode != HttpURLConnection.HTTP_OK) { + return@withContext null + } + + val text = connection.inputStream.bufferedReader().use(BufferedReader::readText) + val json = JSONObject(text) + + val show = json.optBoolean("show", false) + val id = json.optString("id", "").trim() + val header = json.optString("header", json.optString("title", "Notice")).trim() + val body = json.optString("body", "").trim() + val maxShows = json.optInt("max_shows", 1) + val buttonText = json.optString("button_text", "OK").trim() + + RemoteNotice( + show = show, + id = id, + header = header, + body = body, + maxShows = maxShows, + buttonText = buttonText + ) + } catch (e: Exception) { + Log.d(TAG, "Notice fetch silent skip: ${e.message}") + null + } + } + + fun shouldShow(notice: RemoteNotice): Boolean { + if (!notice.show || notice.id.isBlank()) return false + val key = "notice_count_${notice.id}" + val count = prefs.getInt(key, 0) + return count < notice.maxShows + } + + fun markShown(noticeId: String) { + if (noticeId.isBlank()) return + val key = "notice_count_$noticeId" + val count = prefs.getInt(key, 0) + prefs.edit().putInt(key, count + 1).apply() + } +} diff --git a/app/src/main/kotlin/com/ujwal/focusgram/feature/onboarding/OnboardingScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/onboarding/OnboardingScreen.kt new file mode 100644 index 0000000..7b3a3ac --- /dev/null +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/onboarding/OnboardingScreen.kt @@ -0,0 +1,1000 @@ +package com.ujwal.focusgram.feature.onboarding + +import android.Manifest +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.provider.Settings +import android.view.HapticFeedbackConstants +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.filled.HourglassBottom +import androidx.compose.material.icons.filled.Link +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.NotificationsActive +import androidx.compose.material.icons.filled.Security +import androidx.compose.material.icons.filled.Shield +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Switch +import androidx.compose.material3.SwitchDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.ujwal.focusgram.R +import com.ujwal.focusgram.core.datastore.SettingsRepository +import com.ujwal.focusgram.core.model.FocusSettings +import com.ujwal.focusgram.core.ui.components.CupertinoWheelPicker +import com.ujwal.focusgram.core.utils.AppIconManager +import com.ujwal.focusgram.core.ui.theme.DarkBorder +import com.ujwal.focusgram.core.ui.theme.DarkCard +import com.ujwal.focusgram.core.ui.theme.DarkCardElevated +import com.ujwal.focusgram.core.ui.theme.FocusGold +import com.ujwal.focusgram.core.ui.theme.FocusGreen +import com.ujwal.focusgram.core.ui.theme.InstagramBlue +import com.ujwal.focusgram.core.ui.theme.InstagramPink +import com.ujwal.focusgram.core.ui.theme.PureBlack +import com.ujwal.focusgram.core.ui.theme.TextMuted +import com.ujwal.focusgram.core.ui.theme.TextPrimary +import com.ujwal.focusgram.core.ui.theme.TextSecondary +import com.ujwal.focusgram.core.utils.TimeFormatters +import kotlinx.coroutines.launch + +@Composable +fun OnboardingScreen( + settings: FocusSettings, + settingsRepository: SettingsRepository, + onComplete: () -> Unit, + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val view = LocalView.current + val scope = rememberCoroutineScope() + val totalPages = 6 + val pagerState = rememberPagerState(pageCount = { totalPages }) + + var notificationGranted by remember { mutableStateOf(false) } + val notificationPermissionLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.RequestPermission() + ) { isGranted -> + notificationGranted = isGranted + view.performHapticFeedback(HapticFeedbackConstants.CONFIRM) + } + + Box( + modifier = modifier + .fillMaxSize() + .background(PureBlack) + .padding(WindowInsets.statusBars.asPaddingValues()) + .padding(WindowInsets.navigationBars.asPaddingValues()) + ) { + Column(modifier = Modifier.fillMaxSize()) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "${pagerState.currentPage + 1} of $totalPages", + style = MaterialTheme.typography.labelSmall.copy( + color = TextMuted, + fontWeight = FontWeight.Medium, + letterSpacing = 0.5.sp + ) + ) + + if (pagerState.currentPage < totalPages - 1) { + TextButton( + onClick = { + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + scope.launch { + settingsRepository.setFirstRunCompleted() + onComplete() + } + } + ) { + Text( + text = "Skip", + style = MaterialTheme.typography.labelMedium.copy( + color = TextMuted, + fontWeight = FontWeight.SemiBold + ) + ) + } + } else { + Spacer(modifier = Modifier.width(48.dp)) + } + } + + HorizontalPager( + state = pagerState, + modifier = Modifier + .weight(1f) + .fillMaxWidth() + ) { page -> + when (page) { + 0 -> WelcomeSlide() + 1 -> TimeIntentSlide(settings = settings, repository = settingsRepository) + 2 -> CalmControlsSlide(settings = settings, repository = settingsRepository) + 3 -> LinkHandlingSlide() + 4 -> NotificationSlide( + isGranted = notificationGranted, + onRequestPermission = { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } else { + notificationGranted = true + } + } + ) + 5 -> LoginSecuritySlide() + } + } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 20.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(bottom = 20.dp) + ) { + repeat(totalPages) { index -> + val isSelected = pagerState.currentPage == index + val width by animateDpAsState( + targetValue = if (isSelected) 22.dp else 7.dp, + animationSpec = tween(durationMillis = 250), + label = "dot_width" + ) + Box( + modifier = Modifier + .padding(horizontal = 3.dp) + .height(7.dp) + .width(width) + .clip(RoundedCornerShape(4.dp)) + .background( + if (isSelected) InstagramBlue else DarkCardElevated + ) + ) + } + } + + val isLastPage = pagerState.currentPage == totalPages - 1 + Button( + onClick = { + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + scope.launch { + if (isLastPage) { + settingsRepository.setFirstRunCompleted() + onComplete() + } else { + pagerState.animateScrollToPage( + page = pagerState.currentPage + 1, + animationSpec = tween(durationMillis = 350) + ) + } + } + }, + modifier = Modifier + .fillMaxWidth() + .height(54.dp), + colors = ButtonDefaults.buttonColors(containerColor = InstagramBlue), + shape = RoundedCornerShape(16.dp) + ) { + Text( + text = if (isLastPage) "Get Started" else "Continue", + style = MaterialTheme.typography.titleMedium.copy( + fontWeight = FontWeight.Bold, + color = PureBlack + ) + ) + } + } + } + } +} + +@Composable +private fun WelcomeSlide() { + val context = LocalContext.current + val scrollState = rememberScrollState() + val appIconBitmap = remember(context) { + AppIconManager.getIconBitmap(context, R.mipmap.ic_launcher) + } + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(horizontal = 28.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Box( + modifier = Modifier + .size(92.dp) + .clip(RoundedCornerShape(22.dp)) + .background(DarkCard) + .border(1.5.dp, DarkBorder, RoundedCornerShape(22.dp)), + contentAlignment = Alignment.Center + ) { + if (appIconBitmap != null) { + Image( + bitmap = appIconBitmap.asImageBitmap(), + contentDescription = "FocusGram App Logo", + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(22.dp)) + ) + } else { + Icon( + imageVector = Icons.Default.HourglassBottom, + contentDescription = "FocusGram App Logo", + tint = FocusGold, + modifier = Modifier.size(44.dp) + ) + } + } + + Spacer(modifier = Modifier.height(28.dp)) + + Text( + text = "Welcome to FocusGram", + style = MaterialTheme.typography.headlineMedium.copy( + fontWeight = FontWeight.Bold, + color = TextPrimary + ), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(10.dp)) + + Text( + text = "Use Instagram with healthy boundaries, zero tracking algorithms, and complete digital wellness.", + style = MaterialTheme.typography.bodyMedium.copy( + color = TextSecondary, + lineHeight = 22.sp + ), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(32.dp)) + + Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { + ValuePropRow( + icon = Icons.Default.HourglassBottom, + tint = FocusGold, + title = "Timed Reel Sessions", + subtitle = "Watch Reels intentionally with timers instead of infinite feeds." + ) + ValuePropRow( + icon = Icons.Default.VisibilityOff, + tint = InstagramBlue, + title = "Calm Feed & Explore", + subtitle = "Blur or hide addictive feeds, recommendations, and explore grids." + ) + ValuePropRow( + icon = Icons.Default.Shield, + tint = FocusGreen, + title = "100% Client-Side Privacy", + subtitle = "Zero external servers, zero telemetry, zero credential interception." + ) + } + } +} + +@Composable +private fun LoginSecuritySlide() { + val scrollState = rememberScrollState() + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Box( + modifier = Modifier + .size(80.dp) + .clip(CircleShape) + .background(FocusGreen.copy(alpha = 0.14f)) + .border(1.5.dp, FocusGreen.copy(alpha = 0.5f), CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.Security, + contentDescription = null, + tint = FocusGreen, + modifier = Modifier.size(40.dp) + ) + } + + Spacer(modifier = Modifier.height(22.dp)) + + Text( + text = "Official Instagram Login", + style = MaterialTheme.typography.headlineMedium.copy( + fontWeight = FontWeight.Bold, + color = TextPrimary + ), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "Direct, secure, and 100% private", + style = MaterialTheme.typography.bodySmall.copy( + color = TextSecondary, + fontSize = 13.sp + ), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(24.dp)) + + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + SecurityCard( + icon = Icons.Default.Lock, + title = "Direct Official Login", + body = "You sign in straight on instagram.com inside isolated GeckoView. FocusGram never touches your credentials." + ) + + SecurityCard( + icon = Icons.Default.Shield, + title = "Complete Black Box", + body = "The login interface is an isolated web container. Passwords and 2FA credentials can never be intercepted or seen." + ) + + SecurityCard( + icon = Icons.Default.CheckCircle, + title = "Zero Off-Device Data", + body = "100% on-device and open source. Zero analytics, zero trackers, zero external servers." + ) + } + } +} + +@Composable +private fun TimeIntentSlide( + settings: FocusSettings, + repository: SettingsRepository +) { + val scrollState = rememberScrollState() + val scope = rememberCoroutineScope() + val view = LocalView.current + val dailyLimitOptions = remember { listOf(15, 30, 45, 60, 75, 90, 120, 150, 180, 240) } + val selectedIndex = remember(settings.dailyLimitMinutes) { + val exact = dailyLimitOptions.indexOf(settings.dailyLimitMinutes) + if (exact >= 0) exact + else { + val closest = dailyLimitOptions.indexOfFirst { it >= settings.dailyLimitMinutes } + if (closest >= 0) closest else dailyLimitOptions.lastIndex + } + } + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Box( + modifier = Modifier + .size(80.dp) + .clip(CircleShape) + .background(FocusGold.copy(alpha = 0.15f)) + .border(1.5.dp, FocusGold.copy(alpha = 0.5f), CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.HourglassBottom, + contentDescription = null, + tint = FocusGold, + modifier = Modifier.size(40.dp) + ) + } + + Spacer(modifier = Modifier.height(22.dp)) + + Text( + text = "Time With Intent", + style = MaterialTheme.typography.headlineMedium.copy( + fontWeight = FontWeight.Bold, + color = TextPrimary + ), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "Set daily limits and calm pause gates to prevent mindless scrolling.", + style = MaterialTheme.typography.bodyMedium.copy(color = TextSecondary), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(20.dp)) + + Text( + text = "Daily Screen Time Limit: ${TimeFormatters.formatMinutes(settings.dailyLimitMinutes)}", + style = MaterialTheme.typography.labelLarge.copy( + fontWeight = FontWeight.SemiBold, + color = TextPrimary + ) + ) + + Spacer(modifier = Modifier.height(8.dp)) + + CupertinoWheelPicker( + items = dailyLimitOptions, + selectedIndex = selectedIndex, + onItemSelected = { idx -> + val chosen = dailyLimitOptions.getOrElse(idx) { 60 } + scope.launch { repository.setDailyLimitMinutes(chosen) } + }, + labelProvider = { TimeFormatters.formatMinutes(it) }, + itemHeight = 44.dp, + visibleItemsCount = 5, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + ) + + Spacer(modifier = Modifier.height(16.dp)) + + ToggleRow( + title = "Mindfulness Breath Gate", + subtitle = "A brief 10-second pause before opening the app to reset intention.", + checked = settings.showBreathGate, + onCheckedChange = { checked -> + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + scope.launch { repository.setShowBreathGate(checked) } + } + ) + } +} + +@Composable +private fun CalmControlsSlide( + settings: FocusSettings, + repository: SettingsRepository +) { + val scrollState = rememberScrollState() + val scope = rememberCoroutineScope() + val view = LocalView.current + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Box( + modifier = Modifier + .size(80.dp) + .clip(CircleShape) + .background(InstagramPink.copy(alpha = 0.15f)) + .border(1.5.dp, InstagramPink.copy(alpha = 0.5f), CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.VisibilityOff, + contentDescription = null, + tint = InstagramPink, + modifier = Modifier.size(40.dp) + ) + } + + Spacer(modifier = Modifier.height(22.dp)) + + Text( + text = "Declutter Your Mind", + style = MaterialTheme.typography.headlineMedium.copy( + fontWeight = FontWeight.Bold, + color = TextPrimary + ), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "Choose which addictive parts of Instagram to quiet down by default:", + style = MaterialTheme.typography.bodyMedium.copy(color = TextSecondary), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(24.dp)) + + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + ToggleRow( + title = "Hide Feed Posts", + subtitle = "Removes main feed posts so you only open Instagram with purpose.", + checked = settings.hideFeedPosts, + onCheckedChange = { checked -> + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + scope.launch { repository.setHideFeedPosts(checked) } + } + ) + + ToggleRow( + title = "Block Video Autoplay", + subtitle = "Stops videos and reels from playing automatically.", + checked = settings.noAutoplay, + onCheckedChange = { checked -> + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + scope.launch { repository.setNoAutoplay(checked) } + } + ) + + ToggleRow( + title = "Hide Stories Tray", + subtitle = "Hides the top stories bar to prevent distraction.", + checked = settings.noStories, + onCheckedChange = { checked -> + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + scope.launch { repository.setNoStories(checked) } + } + ) + } + } +} + +@Composable +private fun LinkHandlingSlide() { + val context = LocalContext.current + val view = LocalView.current + val scrollState = rememberScrollState() + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Box( + modifier = Modifier + .size(80.dp) + .clip(CircleShape) + .background(InstagramBlue.copy(alpha = 0.15f)) + .border(1.5.dp, InstagramBlue.copy(alpha = 0.5f), CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.Link, + contentDescription = null, + tint = InstagramBlue, + modifier = Modifier.size(40.dp) + ) + } + + Spacer(modifier = Modifier.height(22.dp)) + + Text( + text = "Open Links in FocusGram", + style = MaterialTheme.typography.headlineMedium.copy( + fontWeight = FontWeight.Bold, + color = TextPrimary + ), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "Handle instagram.com links here instead of launching the tracking-heavy official app.", + style = MaterialTheme.typography.bodyMedium.copy(color = TextSecondary), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(24.dp)) + + Box( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(16.dp)) + .border( + BorderStroke( + 1.dp, + Brush.verticalGradient(listOf(Color(0x38FFFFFF), Color(0x10FFFFFF))) + ), + RoundedCornerShape(16.dp) + ) + .background( + Brush.verticalGradient(listOf(Color(0xEB1E1E22), Color(0xD9161619))) + ) + .padding(18.dp) + ) { + Column { + Text( + text = "Recommended Setup", + style = MaterialTheme.typography.titleSmall.copy( + fontWeight = FontWeight.Bold, + color = TextPrimary + ) + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "1. Tap \"Configure App Links\" below\n2. Select \"Open by default\"\n3. Tap \"Add link\" and check all instagram.com domains", + style = MaterialTheme.typography.bodySmall.copy( + color = TextSecondary, + lineHeight = 20.sp + ) + ) + Spacer(modifier = Modifier.height(16.dp)) + OutlinedButton( + onClick = { + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + openDefaultLinkSettings(context) + }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp) + ) { + Text( + text = "Configure App Links", + style = MaterialTheme.typography.labelLarge.copy( + fontWeight = FontWeight.Bold, + color = InstagramBlue + ) + ) + } + } + } + } +} + +@Composable +private fun NotificationSlide( + isGranted: Boolean, + onRequestPermission: () -> Unit +) { + val scrollState = rememberScrollState() + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Box( + modifier = Modifier + .size(80.dp) + .clip(CircleShape) + .background(FocusGreen.copy(alpha = 0.15f)) + .border(1.5.dp, FocusGreen.copy(alpha = 0.5f), CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.NotificationsActive, + contentDescription = null, + tint = FocusGreen, + modifier = Modifier.size(40.dp) + ) + } + + Spacer(modifier = Modifier.height(22.dp)) + + Text( + text = "Useful Alerts Only", + style = MaterialTheme.typography.headlineMedium.copy( + fontWeight = FontWeight.Bold, + color = TextPrimary + ), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "FocusGram only notifies you when your session timer expires or when cooldown completes. Never any engagement hooks or spam.", + style = MaterialTheme.typography.bodyMedium.copy( + color = TextSecondary, + lineHeight = 22.sp + ), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(28.dp)) + + if (isGranted) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(14.dp)) + .background(FocusGreen.copy(alpha = 0.14f)) + .border(1.dp, FocusGreen.copy(alpha = 0.4f), RoundedCornerShape(14.dp)) + .padding(14.dp) + ) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = null, + tint = FocusGreen, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = "Notifications Enabled", + style = MaterialTheme.typography.labelLarge.copy( + color = FocusGreen, + fontWeight = FontWeight.Bold + ) + ) + } + } else { + OutlinedButton( + onClick = onRequestPermission, + shape = RoundedCornerShape(14.dp), + modifier = Modifier + .fillMaxWidth() + .height(50.dp) + ) { + Text( + text = "Enable Session Notifications", + style = MaterialTheme.typography.labelLarge.copy( + fontWeight = FontWeight.Bold, + color = FocusGreen + ) + ) + } + } + } +} + +@Composable +private fun ValuePropRow( + icon: ImageVector, + tint: Color, + title: String, + subtitle: String +) { + val shape = RoundedCornerShape(14.dp) + val glassBorderBrush = Brush.verticalGradient( + colors = listOf(Color(0x38FFFFFF), Color(0x10FFFFFF)) + ) + val glassBgBrush = Brush.verticalGradient( + colors = listOf(Color(0xEB1E1E22), Color(0xD9161619)) + ) + Row( + modifier = Modifier + .fillMaxWidth() + .clip(shape) + .border(BorderStroke(1.dp, glassBorderBrush), shape) + .background(glassBgBrush) + .padding(14.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(40.dp) + .clip(CircleShape) + .background(tint.copy(alpha = 0.15f)), + contentAlignment = Alignment.Center + ) { + Icon(imageVector = icon, contentDescription = null, tint = tint, modifier = Modifier.size(20.dp)) + } + Spacer(modifier = Modifier.width(14.dp)) + Column { + Text( + text = title, + style = MaterialTheme.typography.titleSmall.copy( + fontWeight = FontWeight.SemiBold, + color = TextPrimary + ) + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = subtitle, + style = MaterialTheme.typography.bodySmall.copy( + color = TextSecondary, + fontSize = 12.sp, + lineHeight = 16.sp + ) + ) + } + } +} + +@Composable +private fun SecurityCard( + icon: ImageVector, + title: String, + body: String +) { + val shape = RoundedCornerShape(14.dp) + val glassBorderBrush = Brush.verticalGradient( + colors = listOf(Color(0x38FFFFFF), Color(0x10FFFFFF)) + ) + val glassBgBrush = Brush.verticalGradient( + colors = listOf(Color(0xEB1E1E22), Color(0xD9161619)) + ) + Row( + modifier = Modifier + .fillMaxWidth() + .clip(shape) + .border(BorderStroke(1.dp, glassBorderBrush), shape) + .background(glassBgBrush) + .padding(14.dp), + verticalAlignment = Alignment.Top + ) { + Box( + modifier = Modifier + .size(36.dp) + .clip(CircleShape) + .background(FocusGreen.copy(alpha = 0.12f)), + contentAlignment = Alignment.Center + ) { + Icon(imageVector = icon, contentDescription = null, tint = FocusGreen, modifier = Modifier.size(18.dp)) + } + Spacer(modifier = Modifier.width(12.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = title, + style = MaterialTheme.typography.titleSmall.copy( + fontWeight = FontWeight.SemiBold, + color = TextPrimary + ) + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = body, + style = MaterialTheme.typography.bodySmall.copy( + color = TextSecondary, + fontSize = 12.sp, + lineHeight = 17.sp + ) + ) + } + } +} + +@Composable +private fun ToggleRow( + title: String, + subtitle: String, + checked: Boolean, + onCheckedChange: (Boolean) -> Unit +) { + val shape = RoundedCornerShape(14.dp) + val glassBorderBrush = Brush.verticalGradient( + colors = listOf(Color(0x38FFFFFF), Color(0x10FFFFFF)) + ) + val glassBgBrush = Brush.verticalGradient( + colors = listOf(Color(0xEB1E1E22), Color(0xD9161619)) + ) + Row( + modifier = Modifier + .fillMaxWidth() + .clip(shape) + .border(BorderStroke(1.dp, glassBorderBrush), shape) + .background(glassBgBrush) + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column(modifier = Modifier.weight(1f).padding(end = 12.dp)) { + Text( + text = title, + style = MaterialTheme.typography.titleSmall.copy( + fontWeight = FontWeight.SemiBold, + color = TextPrimary + ) + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = subtitle, + style = MaterialTheme.typography.bodySmall.copy( + color = TextSecondary, + fontSize = 12.sp + ) + ) + } + Switch( + checked = checked, + onCheckedChange = onCheckedChange, + colors = SwitchDefaults.colors( + checkedThumbColor = PureBlack, + checkedTrackColor = InstagramBlue, + uncheckedThumbColor = TextMuted, + uncheckedTrackColor = DarkCardElevated + ) + ) + } +} + +private fun openDefaultLinkSettings(context: Context) { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val intent = Intent( + Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, + Uri.parse("package:${context.packageName}") + ).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + context.startActivity(intent) + } else { + val intent = Intent( + Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.parse("package:${context.packageName}") + ).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + context.startActivity(intent) + } + } catch (_: Exception) { + val intent = Intent(Settings.ACTION_SETTINGS).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + context.startActivity(intent) + } +} diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeScreen.kt similarity index 55% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeScreen.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeScreen.kt index 05767f1..a3f754c 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeScreen.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeScreen.kt @@ -42,6 +42,7 @@ import androidx.compose.runtime.setValue import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flowOf import com.ujwal.focusgram.feature.history.ReelHistory +import com.ujwal.focusgram.core.utils.TimeFormatters import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -66,13 +67,17 @@ import java.text.SimpleDateFormat import java.util.Calendar import java.util.Date import java.util.Locale +import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.material3.DatePickerDialog import androidx.compose.material3.DateRangePicker import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.TextButton import androidx.compose.material3.rememberDateRangePickerState import androidx.compose.runtime.mutableStateOf +import com.ujwal.focusgram.core.ui.components.IOSDivider +import com.ujwal.focusgram.core.ui.components.IOSSectionTitle import kotlin.math.max enum class DailyBreakdownRange(val label: String) { @@ -82,6 +87,9 @@ enum class DailyBreakdownRange(val label: String) { CUSTOM("Custom") } +/** + * Screen time analytics screen displaying daily usage charts, Reels consumption stats, and log history. + */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun ScreenTimeScreen( @@ -107,7 +115,7 @@ fun ScreenTimeScreen( val todayMinutes = todaySeconds / 60 val todayReelsMinutes = todayReelsSeconds / 60 val todayReelsWatched = if (reelHistory != null) todayWatchedFromHistory else (todayRecord?.reelsWatched ?: 0) - val limitFraction = (todayMinutes.toFloat() / max(1, dailyLimitMinutes)).coerceIn(0f, 1f) + val limitFraction = if (dailyLimitMinutes > 0) (todayMinutes.toFloat() / dailyLimitMinutes).coerceIn(0f, 1f) else 0f var selectedRange by remember { mutableStateOf(DailyBreakdownRange.PAST_7_DAYS) } var customStartDateMillis by remember { mutableStateOf(null) } @@ -127,16 +135,14 @@ fun ScreenTimeScreen( else -> "${todayReelsMinutes}m ${todayReelsSeconds % 60}s" } - // Compute past 7 days breakdown for bar chart val last7Days = remember(records) { - val cal = Calendar.getInstance() - val list = mutableListOf>() // Pair(DayLabel, Minutes) + val list = mutableListOf>() // DayLabel, Minutes, isToday for (i in 6 downTo 0) { val dCal = Calendar.getInstance().apply { add(Calendar.DAY_OF_YEAR, -i) } val dStr = dateFormat.format(dCal.time) val dLabel = dayLabelFormat.format(dCal.time) val mins = ((records.firstOrNull { it.date == dStr }?.seconds) ?: 0) / 60 - list.add(Pair(dLabel, mins)) + list.add(Triple(dLabel, mins, i == 0)) } list } @@ -145,6 +151,10 @@ fun ScreenTimeScreen( max(60, last7Days.maxOfOrNull { it.second } ?: 60) } + val maxScale = remember(maxDayMinutes, dailyLimitMinutes) { + max(maxDayMinutes, if (dailyLimitMinutes in 1..180) dailyLimitMinutes else maxDayMinutes) + } + val weeklyTotalMinutes = last7Days.sumOf { it.second } val weeklyDailyAverage = weeklyTotalMinutes / 7 @@ -258,7 +268,6 @@ fun ScreenTimeScreen( .background(IOSBg) .padding(WindowInsets.statusBars.asPaddingValues()) ) { - // iOS Navigation Bar Row( modifier = Modifier .fillMaxWidth() @@ -287,151 +296,268 @@ fun ScreenTimeScreen( .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - // Summary Card item { IOSGroupedCard(modifier = Modifier.padding(top = 8.dp)) { Column(modifier = Modifier.padding(18.dp)) { + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = "DAILY AVERAGE", + color = IOSTextSecondary, + fontSize = 11.sp, + fontWeight = FontWeight.SemiBold, + letterSpacing = 0.5.sp + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = "${weeklyDailyAverage}m", + color = IOSTextPrimary, + fontSize = 32.sp, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = "Today: $todayUsageText" + if (todayReelsSeconds > 0) " â€Ē $todayReelsText in Reels" else "", + color = IOSTextSecondary, + fontSize = 13.sp + ) + } + + Spacer(modifier = Modifier.height(18.dp)) + + // Apple-style bar chart area with fixed track heights and no clipping + Box( + modifier = Modifier + .fillMaxWidth() + .height(110.dp) + ) { + // Subtle limit guideline when applicable + if (dailyLimitMinutes in 1..maxScale) { + val limitFraction = (dailyLimitMinutes.toFloat() / maxScale).coerceIn(0f, 1f) + Box( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomStart) + .padding(bottom = (limitFraction * 80).dp) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(0.5.dp) + .background(Color(0xFF3A3A3C)) + ) + Text( + text = "Limit", + color = IOSTextSecondary.copy(alpha = 0.7f), + fontSize = 9.sp, + fontWeight = FontWeight.Medium, + modifier = Modifier + .align(Alignment.TopEnd) + .padding(bottom = 2.dp) + ) + } + } + + Row( + modifier = Modifier.fillMaxSize(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Bottom + ) { + last7Days.forEach { (_, minutes, isToday) -> + val barFraction = if (minutes > 0) (minutes.toFloat() / maxScale).coerceIn(0.06f, 1f) else 0f + val animatedHeight by animateFloatAsState( + targetValue = barFraction, + animationSpec = tween(500), + label = "barHeight" + ) + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.weight(1f) + ) { + if (minutes > 0) { + Text( + text = "${minutes}m", + color = if (isToday) IOSTextPrimary else IOSTextSecondary, + fontSize = 10.sp, + fontWeight = if (isToday) FontWeight.SemiBold else FontWeight.Normal, + maxLines = 1 + ) + } else { + Spacer(modifier = Modifier.height(14.dp)) + } + + Spacer(modifier = Modifier.height(4.dp)) + + // Bar track container with constant 80dp height + Box( + modifier = Modifier + .width(16.dp) + .height(80.dp), + contentAlignment = Alignment.BottomCenter + ) { + // Subtle background column track + Box( + modifier = Modifier + .fillMaxSize() + .clip(RoundedCornerShape(4.dp)) + .background(Color(0xFF2C2C2E)) + ) + + // Active filled bar + if (barFraction > 0f) { + Box( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(animatedHeight) + .clip(RoundedCornerShape(4.dp)) + .background( + if (dailyLimitMinutes > 0 && minutes >= dailyLimitMinutes) Color(0xFFFF453A) + else IOSBlue + ) + ) + } + } + } + } + } + } + + Spacer(modifier = Modifier.height(8.dp)) + + // Dedicated X-Axis day labels row: completely decoupled from bar height to prevent clipping Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - Row( - modifier = Modifier.weight(1f), - verticalAlignment = Alignment.CenterVertically - ) { - Box( - modifier = Modifier - .size(32.dp) - .clip(CircleShape) - .background(IOSTeal.copy(alpha = 0.15f)), - contentAlignment = Alignment.Center + last7Days.forEach { (dayLabel, _, isToday) -> + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.weight(1f) ) { - Icon( - imageVector = Icons.Default.HourglassTop, - contentDescription = null, - tint = IOSTeal, - modifier = Modifier.size(18.dp) - ) - } - Spacer(modifier = Modifier.width(6.dp)) - Column { Text( - text = "Usage", - color = IOSTextSecondary, + text = dayLabel, + color = if (isToday) IOSTextPrimary else IOSTextSecondary, fontSize = 11.sp, - fontWeight = FontWeight.Medium, - maxLines = 1 - ) - Text( - text = todayUsageText, - color = IOSTextPrimary, - fontSize = 15.sp, - fontWeight = FontWeight.Bold, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } - - Row( - modifier = Modifier.weight(1.05f), - verticalAlignment = Alignment.CenterVertically - ) { - Box( - modifier = Modifier - .size(32.dp) - .clip(CircleShape) - .background(IOSPurple.copy(alpha = 0.15f)), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = Icons.Default.PlayArrow, - contentDescription = null, - tint = IOSPurple, - modifier = Modifier.size(18.dp) - ) - } - Spacer(modifier = Modifier.width(6.dp)) - Column { - Text( - text = "Reels Time", - color = IOSTextSecondary, - fontSize = 11.sp, - fontWeight = FontWeight.Medium, - maxLines = 1 - ) - Text( - text = todayReelsText, - color = IOSTextPrimary, - fontSize = 15.sp, - fontWeight = FontWeight.Bold, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } - - Row( - modifier = Modifier.weight(0.95f), - verticalAlignment = Alignment.CenterVertically - ) { - Box( - modifier = Modifier - .size(32.dp) - .clip(CircleShape) - .background(IOSOrange.copy(alpha = 0.15f)), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = Icons.Default.Insights, - contentDescription = null, - tint = IOSOrange, - modifier = Modifier.size(18.dp) - ) - } - Spacer(modifier = Modifier.width(6.dp)) - Column { - Text( - text = "Watched", - color = IOSTextSecondary, - fontSize = 11.sp, - fontWeight = FontWeight.Medium, - maxLines = 1 - ) - Text( - text = "$todayReelsWatched", - color = IOSTextPrimary, - fontSize = 15.sp, - fontWeight = FontWeight.Bold, - maxLines = 1, - overflow = TextOverflow.Ellipsis + fontWeight = if (isToday) FontWeight.Bold else FontWeight.Medium, + textAlign = TextAlign.Center ) + if (isToday) { + Box( + modifier = Modifier + .padding(top = 2.dp) + .size(3.dp) + .clip(CircleShape) + .background(IOSBlue) + ) + } else { + Spacer(modifier = Modifier.height(5.dp)) + } } } } - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(18.dp)) + HorizontalDivider(color = IOSDivider, thickness = 0.5.dp) + Spacer(modifier = Modifier.height(14.dp)) - // Daily Limit Progress + // Glanceable metrics row following Apple HIG typography Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = "USAGE TODAY", + color = IOSTextSecondary, + fontSize = 10.sp, + fontWeight = FontWeight.SemiBold, + letterSpacing = 0.5.sp + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = todayUsageText, + color = IOSTextPrimary, + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + + Column(modifier = Modifier.weight(1f)) { + Text( + text = "REELS TIME", + color = IOSTextSecondary, + fontSize = 10.sp, + fontWeight = FontWeight.SemiBold, + letterSpacing = 0.5.sp + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = todayReelsText, + color = IOSTextPrimary, + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + + Column(modifier = Modifier.weight(1f)) { + Text( + text = "WATCHED", + color = IOSTextSecondary, + fontSize = 10.sp, + fontWeight = FontWeight.SemiBold, + letterSpacing = 0.5.sp + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = "$todayReelsWatched reels", + color = IOSTextPrimary, + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + } + } + } + + item { + IOSGroupedCard { + Column(modifier = Modifier.padding(16.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically ) { Text( - text = "Daily Limit ($dailyLimitMinutes min)", - color = IOSTextSecondary, - fontSize = 12.sp + text = if (dailyLimitMinutes > 0) "Daily Limit (${TimeFormatters.formatMinutes(dailyLimitMinutes)})" else "Daily Limit (Off)", + color = IOSTextPrimary, + fontSize = 15.sp, + fontWeight = FontWeight.SemiBold ) Text( - text = "${(limitFraction * 100).toInt()}% used", - color = if (limitFraction >= 1f) Color(0xFFFF453A) else IOSTextPrimary, - fontSize = 12.sp, - fontWeight = FontWeight.SemiBold + text = if (dailyLimitMinutes > 0) "${(limitFraction * 100).toInt()}% used" else "Off", + color = if (dailyLimitMinutes > 0 && limitFraction >= 1f) Color(0xFFFF453A) else IOSTextSecondary, + fontSize = 13.sp, + fontWeight = FontWeight.Medium ) } - Spacer(modifier = Modifier.height(6.dp)) + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = if (dailyLimitMinutes > 0) "${TimeFormatters.formatMinutes(dailyLimitMinutes)} daily allowance" else "No daily time limit active", + color = IOSTextSecondary, + fontSize = 12.sp + ) + + Spacer(modifier = Modifier.height(10.dp)) LinearProgressIndicator( progress = { limitFraction }, @@ -439,7 +565,7 @@ fun ScreenTimeScreen( .fillMaxWidth() .height(6.dp) .clip(RoundedCornerShape(3.dp)), - color = if (limitFraction >= 1f) Color(0xFFFF453A) else IOSTeal, + color = if (limitFraction >= 1f) Color(0xFFFF453A) else IOSBlue, trackColor = Color(0xFF2C2C2E), strokeCap = StrokeCap.Round ) @@ -447,171 +573,105 @@ fun ScreenTimeScreen( } } - // 7-Day Bar Chart item { IOSGroupedCard { - Column(modifier = Modifier.padding(18.dp)) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "Last 7 Days", - color = IOSTextPrimary, - fontSize = 16.sp, - fontWeight = FontWeight.SemiBold - ) - Text( - text = "Avg $weeklyDailyAverage min/day", - color = IOSTextSecondary, - fontSize = 13.sp - ) - } - - Spacer(modifier = Modifier.height(20.dp)) - - Row( + Row( + modifier = Modifier.padding(16.dp), + verticalAlignment = Alignment.Top + ) { + Box( modifier = Modifier - .fillMaxWidth() - .height(130.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.Bottom + .size(32.dp) + .clip(RoundedCornerShape(8.dp)) + .background(IOSOrange.copy(alpha = 0.15f)), + contentAlignment = Alignment.Center ) { - last7Days.forEach { (dayLabel, minutes) -> - val barFraction = (minutes.toFloat() / maxDayMinutes).coerceIn(0.06f, 1f) - val animatedHeight by animateFloatAsState( - targetValue = barFraction, - animationSpec = tween(500), - label = "barHeight" - ) - - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.weight(1f) - ) { - Text( - text = if (minutes > 0) "${minutes}m" else "-", - color = IOSTextSecondary, - fontSize = 10.sp, - modifier = Modifier.padding(bottom = 4.dp) - ) - - Box( - modifier = Modifier - .fillMaxHeight(animatedHeight) - .width(18.dp) - .clip(RoundedCornerShape(topStart = 6.dp, topEnd = 6.dp)) - .background( - if (minutes >= dailyLimitMinutes) Color(0xFFFF453A) - else IOSBlue - ) - ) - - Spacer(modifier = Modifier.height(6.dp)) - - Text( - text = dayLabel, - color = IOSTextSecondary, - fontSize = 11.sp, - fontWeight = FontWeight.Medium - ) - } - } - } - } - } - } - - // Insights & Discipline Stats - item { - IOSGroupedCard { - Column(modifier = Modifier.padding(16.dp)) { - Row(verticalAlignment = Alignment.CenterVertically) { Icon( imageVector = Icons.Default.Insights, contentDescription = null, tint = IOSOrange, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = "Mindful Insights", - color = IOSTextPrimary, - fontSize = 15.sp, - fontWeight = FontWeight.SemiBold + modifier = Modifier.size(18.dp) ) } - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.width(12.dp)) - Text( - text = if (weeklyDailyAverage <= dailyLimitMinutes) { - "Great job! You are staying within your mindful intention limits this week." - } else { - "You are averaging slightly above your planned limit. Consider adjusting your daily timer." - }, - color = IOSTextSecondary, - fontSize = 13.sp, - lineHeight = 18.sp - ) + Column(modifier = Modifier.weight(1f)) { + Text( + text = "Mindful Insight", + color = IOSTextPrimary, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = when { + dailyLimitMinutes <= 0 -> "You have no daily limit set. Monitor your daily usage breakdown below to maintain mindful habits." + weeklyDailyAverage <= dailyLimitMinutes -> "Great job! You are averaging ${TimeFormatters.formatMinutes(weeklyDailyAverage)} per day, well within your ${TimeFormatters.formatMinutes(dailyLimitMinutes)} daily limit." + else -> "You are averaging ${TimeFormatters.formatMinutes(weeklyDailyAverage)} per day, which exceeds your planned ${TimeFormatters.formatMinutes(dailyLimitMinutes)} limit. Take regular breaks to recharge." + }, + color = IOSTextSecondary, + fontSize = 13.sp, + lineHeight = 18.sp + ) + } } } } - // Recent Daily Log with customizable ranges + // iOS-style Segmented Control for time range item { - Row( + Box( modifier = Modifier .fillMaxWidth() - .padding(start = 4.dp, end = 4.dp, top = 4.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + .clip(RoundedCornerShape(9.dp)) + .background(Color(0xFF1C1C1E)) + .border(0.5.dp, Color(0xFF2C2C2E), RoundedCornerShape(9.dp)) + .padding(2.dp) ) { - Text( - text = "DAILY BREAKDOWN", - color = IOSTextSecondary, - fontSize = 12.sp, - fontWeight = FontWeight.SemiBold - ) - - Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { + Row(modifier = Modifier.fillMaxWidth()) { DailyBreakdownRange.values().forEach { range -> val isSelected = selectedRange == range Box( modifier = Modifier - .clip(RoundedCornerShape(6.dp)) - .background(if (isSelected) IOSBlue else Color(0xFF2C2C2E)) + .weight(1f) + .height(30.dp) + .clip(RoundedCornerShape(7.dp)) + .background(if (isSelected) Color(0xFF3A3A3C) else Color.Transparent) .clickable { if (range == DailyBreakdownRange.CUSTOM) { showCustomPicker = true } else { selectedRange = range } - } - .padding(horizontal = 7.dp, vertical = 3.dp) + }, + contentAlignment = Alignment.Center ) { Text( text = range.label, color = if (isSelected) Color.White else IOSTextSecondary, - fontSize = 10.sp, - fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal + fontSize = 12.sp, + fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Medium ) } } } } + } - Spacer(modifier = Modifier.height(6.dp)) + item { + IOSSectionTitle("ACTIVITY BREAKDOWN") + } + item { IOSGroupedCard { - Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { + Column { if (displayedRecords.isEmpty()) { Text( text = "No screen time recorded for this period.", color = IOSTextSecondary, fontSize = 14.sp, - modifier = Modifier.padding(vertical = 12.dp) + modifier = Modifier.padding(16.dp) ) } else { displayedRecords.forEachIndexed { idx, record -> @@ -619,18 +679,21 @@ fun ScreenTimeScreen( val reelsM = record.reelsSeconds / 60 val reelsS = record.reelsSeconds % 60 val totalDisplay = if (record.seconds < 60) "${record.seconds}s" else "$mins min" + val dayRatio = if (maxDayMinutes > 0) (mins.toFloat() / maxDayMinutes).coerceIn(0.04f, 1f) else 0f + Row( modifier = Modifier .fillMaxWidth() - .padding(vertical = 10.dp), + .padding(horizontal = 16.dp, vertical = 12.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - Column { + Column(modifier = Modifier.weight(1f)) { Text( text = formatRecordDate(record.date), color = IOSTextPrimary, - fontSize = 15.sp + fontSize = 15.sp, + fontWeight = FontWeight.Medium ) val subtitleParts = mutableListOf() if (record.reelsSeconds > 0) { @@ -642,27 +705,54 @@ fun ScreenTimeScreen( subtitleParts.add("$watchedCount watched") } if (subtitleParts.isNotEmpty()) { + Spacer(modifier = Modifier.height(2.dp)) Text( text = subtitleParts.joinToString(" â€Ē "), - color = IOSPurple, - fontSize = 12.sp, - fontWeight = FontWeight.Medium + color = IOSTextSecondary, + fontSize = 12.sp ) } } - Text( - text = totalDisplay, - color = if (mins >= dailyLimitMinutes) Color(0xFFFF453A) else IOSTextSecondary, - fontSize = 15.sp, - fontWeight = FontWeight.Medium - ) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + // Apple-style horizontal proportion bar + Box( + modifier = Modifier + .width(36.dp) + .height(4.dp) + .clip(RoundedCornerShape(2.dp)) + .background(Color(0xFF2C2C2E)), + contentAlignment = Alignment.CenterStart + ) { + Box( + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth(dayRatio) + .clip(RoundedCornerShape(2.dp)) + .background( + if (dailyLimitMinutes > 0 && mins >= dailyLimitMinutes) Color(0xFFFF453A) + else IOSBlue + ) + ) + } + + Text( + text = totalDisplay, + color = if (mins >= dailyLimitMinutes && dailyLimitMinutes > 0) Color(0xFFFF453A) else IOSTextPrimary, + fontSize = 15.sp, + fontWeight = FontWeight.SemiBold + ) + } } + if (idx < displayedRecords.lastIndex) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(0.5.dp) - .background(Color(0xFF2C2C2E)) + HorizontalDivider( + color = IOSDivider, + thickness = 0.5.dp, + modifier = Modifier.padding(start = 16.dp) ) } } diff --git a/app/src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeService.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeService.kt new file mode 100644 index 0000000..5b43b9e --- /dev/null +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeService.kt @@ -0,0 +1,437 @@ +package com.ujwal.focusgram.feature.screentime + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.IBinder +import android.util.Log +import androidx.core.app.NotificationCompat +import androidx.core.app.ServiceCompat +import android.content.pm.ServiceInfo +import com.ujwal.focusgram.FocusGramApp +import com.ujwal.focusgram.MainActivity +import com.ujwal.focusgram.R +import com.ujwal.focusgram.core.database.ScreenTimeRecord +import com.ujwal.focusgram.core.database.SessionRecord +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +/** + * Foreground service that tracks active session duration, updates the persistent notification, + * and broadcasts [ACTION_LIMIT_EXCEEDED] when daily limits are exhausted. + */ +class ScreenTimeService : Service() { + + private val serviceScope = CoroutineScope(Dispatchers.Default) + private var trackingJob: Job? = null + + private var sessionStartTime = 0L + private var sessionSeconds = 0 + private var sessionDurationMinutes = 15 + private var sessionType = "INSTAGRAM" // "INSTAGRAM" or "REELS" + private var isReelsSession = false + + private fun getSessionSubtitle(isPaused: Boolean = false): String { + val durationPart = if (sessionDurationMinutes > 0) "${sessionDurationMinutes}m" else "Stay intentional" + return if (isPaused) { + "Paused outside Instagram â€Ē $durationPart" + } else { + "Session in progress â€Ē $durationPart" + } + } + + override fun onCreate() { + super.onCreate() + createNotificationChannel() + createIntervalNotificationChannel() + val minutes = sessionSeconds / 60 + val secs = sessionSeconds % 60 + val timeStr = String.format("%02d:%02d", minutes, secs) + val initialTitle = "Session Timer: $timeStr" + val initialNotification = buildNotification(initialTitle, getSessionSubtitle(false)) + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + ServiceCompat.startForeground( + this, + NOTIFICATION_ID, + initialNotification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE + ) + } else { + startForeground(NOTIFICATION_ID, initialNotification) + } + } catch (e: Exception) { + Log.e(TAG, "Primary startForeground failed in onCreate: ${e.message}", e) + try { + startForeground(NOTIFICATION_ID, initialNotification) + } catch (fallbackEx: Exception) { + Log.e(TAG, "Fallback startForeground failed: ${fallbackEx.message}", fallbackEx) + stopSelf() + } + } + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + when (intent?.action) { + ACTION_START -> { + isReelsSession = intent.getBooleanExtra(EXTRA_IS_REELS, false) + val initSecs = intent.getIntExtra(EXTRA_INITIAL_SECONDS, 0) + if (initSecs > 0) { + sessionSeconds = initSecs + } + val durMins = intent.getIntExtra(EXTRA_SESSION_DURATION_MINUTES, 0) + if (durMins > 0) { + sessionDurationMinutes = durMins + } + sessionType = if (isReelsSession) "REELS" else "INSTAGRAM" + startTracking() + } + ACTION_UPDATE_ROUTE -> { + val wasReels = isReelsSession + isReelsSession = intent.getBooleanExtra(EXTRA_IS_REELS, false) + sessionType = if (isReelsSession) "REELS" else "INSTAGRAM" + if (wasReels != isReelsSession && trackingJob?.isActive == true) { + updateSessionNotification() + } + } + ACTION_PAUSE -> { + val initSecs = intent.getIntExtra(EXTRA_INITIAL_SECONDS, -1) + if (initSecs >= 0) { + sessionSeconds = initSecs + } + val durMins = intent.getIntExtra(EXTRA_SESSION_DURATION_MINUTES, 0) + if (durMins > 0) { + sessionDurationMinutes = durMins + } + pauseTracking() + } + ACTION_RESUME -> { + val initSecs = intent.getIntExtra(EXTRA_INITIAL_SECONDS, 0) + if (initSecs > 0) { + sessionSeconds = initSecs + } + val durMins = intent.getIntExtra(EXTRA_SESSION_DURATION_MINUTES, 0) + if (durMins > 0) { + sessionDurationMinutes = durMins + } + startTracking() + } + ACTION_STOP -> { + stopTracking() + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + stopForeground(STOP_FOREGROUND_REMOVE) + } else { + @Suppress("DEPRECATION") + stopForeground(true) + } + } catch (_: Exception) {} + try { + val manager = getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager + manager?.cancel(NOTIFICATION_ID) + } catch (_: Exception) {} + stopSelf() + } + } + return START_NOT_STICKY + } + + override fun onDestroy() { + super.onDestroy() + stopTracking() + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + stopForeground(STOP_FOREGROUND_REMOVE) + } else { + @Suppress("DEPRECATION") + stopForeground(true) + } + } catch (_: Exception) {} + try { + val manager = getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager + manager?.cancel(NOTIFICATION_ID) + } catch (_: Exception) {} + } + + private fun startTracking() { + if (trackingJob?.isActive == true) return + + sessionStartTime = System.currentTimeMillis() - (sessionSeconds * 1000L) + updateSessionNotification() + + trackingJob = serviceScope.launch { + val app = FocusGramApp.instance + val settingsRepo = app.settingsRepository + + while (isActive) { + delay(1000L) + sessionSeconds++ + updateSessionNotification() + + // Check 5-minute interval alert + if (sessionSeconds > 0 && sessionSeconds % 300 == 0) { + try { + val settings = settingsRepo.settingsFlow.first() + if (settings.notifyEvery5Minutes) { + sendIntervalAlert(sessionSeconds / 60) + } + } catch (e: Exception) { + Log.w(TAG, "Error checking interval notification: ${e.message}") + } + } + } + } + } + + private fun updateSessionNotification() { + val minutes = sessionSeconds / 60 + val secs = sessionSeconds % 60 + val timeStr = String.format("%02d:%02d", minutes, secs) + val title = "Session Timer: $timeStr" + updateNotification(title, getSessionSubtitle(false)) + } + + private fun pauseTracking() { + trackingJob?.cancel() + trackingJob = null + val minutes = sessionSeconds / 60 + val secs = sessionSeconds % 60 + val timeStr = String.format("%02d:%02d", minutes, secs) + updateNotification("Session Timer: $timeStr (Paused)", getSessionSubtitle(true)) + } + + private fun stopTracking() { + trackingJob?.cancel() + trackingJob = null + + val endTime = System.currentTimeMillis() + val duration = ((endTime - sessionStartTime) / 1000).toInt() + + if (duration > 5) { + serviceScope.launch { + try { + val app = FocusGramApp.instance + app.database.sessionRecordDao().insert( + SessionRecord( + sessionType = sessionType, + startTime = sessionStartTime, + endTime = endTime, + durationSeconds = duration, + completed = true + ) + ) + } catch (e: Exception) { + Log.e(TAG, "Error saving session record on stop: ${e.message}", e) + } + } + } + } + + private fun createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + CHANNEL_ID, + "FocusGram Session Timer", + NotificationManager.IMPORTANCE_LOW + ).apply { + description = "Shows active Instagram usage session duration and limits." + setShowBadge(false) + } + val manager = getSystemService(NotificationManager::class.java) + manager.createNotificationChannel(channel) + } + } + + private fun createIntervalNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + CHANNEL_INTERVAL_ID, + "5-Minute Session Alerts", + NotificationManager.IMPORTANCE_HIGH + ).apply { + description = "Periodic heads-up alerts every 5 minutes during an active session." + enableLights(true) + enableVibration(true) + } + val manager = getSystemService(NotificationManager::class.java) + manager.createNotificationChannel(channel) + } + } + + private fun sendIntervalAlert(minutesElapsed: Int) { + try { + val launchIntent = Intent(this, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP + } + val pendingIntent = PendingIntent.getActivity( + this, + minutesElapsed, + launchIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + val alert = NotificationCompat.Builder(this, CHANNEL_INTERVAL_ID) + .setContentTitle("Session Alert: $minutesElapsed min") + .setContentText("You've been using Instagram for $minutesElapsed minutes. Stay mindful.") + .setSmallIcon(R.drawable.ic_notification) + .setContentIntent(pendingIntent) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setDefaults(NotificationCompat.DEFAULT_ALL) + .setAutoCancel(true) + .build() + + val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + manager.notify(INTERVAL_NOTIFICATION_BASE_ID + minutesElapsed, alert) + } catch (e: Exception) { + Log.w(TAG, "Failed to send 5-minute interval notification: ${e.message}") + } + } + + private fun buildNotification(title: String, content: String): Notification { + val launchIntent = Intent(this, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP + } + val pendingIntent = PendingIntent.getActivity( + this, + 0, + launchIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + val notification = NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle(title) + .setContentText(content) + .setSmallIcon(R.drawable.ic_notification) + .setContentIntent(pendingIntent) + .setOngoing(true) + .setAutoCancel(false) + .setSilent(true) + .setCategory(NotificationCompat.CATEGORY_STATUS) + .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE) + .setPriority(NotificationCompat.PRIORITY_LOW) + .build() + notification.flags = notification.flags or Notification.FLAG_ONGOING_EVENT or Notification.FLAG_NO_CLEAR + return notification + } + + private fun updateNotification(title: String, content: String) { + try { + val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + manager.notify(NOTIFICATION_ID, buildNotification(title, content)) + } catch (e: Exception) { + Log.w(TAG, "Failed to update notification: ${e.message}") + } + } + + override fun onBind(intent: Intent?): IBinder? = null + + companion object { + private const val TAG = "ScreenTimeService" + const val CHANNEL_ID = "focusgram_session_timer" + const val CHANNEL_INTERVAL_ID = "focusgram_interval_alerts" + const val NOTIFICATION_ID = 1001 + const val INTERVAL_NOTIFICATION_BASE_ID = 2000 + + const val ACTION_START = "com.ujwal.focusgram.action.START_SESSION" + const val ACTION_PAUSE = "com.ujwal.focusgram.action.PAUSE_SESSION" + const val ACTION_RESUME = "com.ujwal.focusgram.action.RESUME_SESSION" + const val ACTION_STOP = "com.ujwal.focusgram.action.STOP_SESSION" + const val ACTION_UPDATE_ROUTE = "com.ujwal.focusgram.action.UPDATE_ROUTE" + const val ACTION_LIMIT_EXCEEDED = "com.ujwal.focusgram.action.LIMIT_EXCEEDED" + const val EXTRA_IS_REELS = "extra_is_reels" + const val EXTRA_INITIAL_SECONDS = "extra_initial_seconds" + const val EXTRA_SESSION_DURATION_MINUTES = "extra_session_duration_minutes" + + fun startSession(context: Context, isReels: Boolean = false, initialSeconds: Int = 0, sessionDurationMinutes: Int = 15) { + try { + val intent = Intent(context, ScreenTimeService::class.java).apply { + action = ACTION_START + putExtra(EXTRA_IS_REELS, isReels) + putExtra(EXTRA_INITIAL_SECONDS, initialSeconds) + putExtra(EXTRA_SESSION_DURATION_MINUTES, sessionDurationMinutes) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(intent) + } else { + context.startService(intent) + } + } catch (e: Exception) { + Log.e(TAG, "Failed to startSession: ${e.message}", e) + } + } + + fun pauseSession(context: Context, elapsedSeconds: Int = -1, sessionDurationMinutes: Int = 0) { + try { + val intent = Intent(context, ScreenTimeService::class.java).apply { + action = ACTION_PAUSE + if (elapsedSeconds >= 0) { + putExtra(EXTRA_INITIAL_SECONDS, elapsedSeconds) + } + if (sessionDurationMinutes > 0) { + putExtra(EXTRA_SESSION_DURATION_MINUTES, sessionDurationMinutes) + } + } + context.startService(intent) + } catch (e: Exception) { + Log.w(TAG, "Failed to pause ScreenTimeService: ${e.message}") + } + } + + fun resumeSession(context: Context, elapsedSeconds: Int = 0, sessionDurationMinutes: Int = 0) { + try { + val intent = Intent(context, ScreenTimeService::class.java).apply { + action = ACTION_RESUME + putExtra(EXTRA_INITIAL_SECONDS, elapsedSeconds) + if (sessionDurationMinutes > 0) { + putExtra(EXTRA_SESSION_DURATION_MINUTES, sessionDurationMinutes) + } + } + context.startService(intent) + } catch (e: Exception) { + Log.w(TAG, "Failed to resume ScreenTimeService: ${e.message}") + } + } + + fun updateRoute(context: Context, isReels: Boolean) { + val intent = Intent(context, ScreenTimeService::class.java).apply { + action = ACTION_UPDATE_ROUTE + putExtra(EXTRA_IS_REELS, isReels) + } + try { + context.startService(intent) + } catch (e: Exception) { + Log.w(TAG, "Failed to send route update to ScreenTimeService: ${e.message}") + } + } + + fun stopSession(context: Context) { + try { + val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager + manager?.cancel(NOTIFICATION_ID) + } catch (_: Exception) {} + val intent = Intent(context, ScreenTimeService::class.java).apply { + action = ACTION_STOP + } + try { + context.startService(intent) + } catch (_: Exception) { + try { + context.stopService(Intent(context, ScreenTimeService::class.java)) + } catch (_: Exception) {} + } + } + } +} diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/session/AppSessionPicker.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/session/AppSessionPicker.kt similarity index 92% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/session/AppSessionPicker.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/session/AppSessionPicker.kt index 12f4c4a..faab375 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/session/AppSessionPicker.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/session/AppSessionPicker.kt @@ -40,10 +40,7 @@ import com.ujwal.focusgram.core.ui.components.CupertinoWheelPicker import com.ujwal.focusgram.core.ui.theme.PureBlack /** - * AppSessionPicker - * Premium iOS monochrome intention timer displayed on launch or settings. - * Allows users to mindfully set their planned Instagram session duration (1 to 60 minutes) - * using an authentic Apple Clock rotating drum wheel picker or quick preset chips. + * Intention timer modal for configuring planned Instagram session duration (1 to 60 minutes). */ @Composable fun AppSessionPicker( @@ -66,7 +63,6 @@ fun AppSessionPicker( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth() ) { - // Top Dismiss Button (if modal) if (onDismiss != null) { Row( modifier = Modifier.fillMaxWidth(), @@ -94,7 +90,6 @@ fun AppSessionPicker( Spacer(modifier = Modifier.height(12.dp)) - // Monochrome iOS Timer Badge Box( modifier = Modifier .size(64.dp) @@ -134,7 +129,6 @@ fun AppSessionPicker( Spacer(modifier = Modifier.weight(1f)) - // Apple Clock Single Minutes Drum Wheel (1..60) CupertinoWheelPicker( items = minutesList, selectedIndex = selectedMinuteIndex, @@ -149,7 +143,6 @@ fun AppSessionPicker( Spacer(modifier = Modifier.weight(1f)) - // Official Apple Style Pill Button (Pure White with Bold Black Text) Button( onClick = { onSessionSelected(selectedMinutes) }, modifier = Modifier diff --git a/app/src/main/kotlin/com/ujwal/focusgram/feature/session/DailyLimitGateScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/session/DailyLimitGateScreen.kt new file mode 100644 index 0000000..d3f7fb3 --- /dev/null +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/session/DailyLimitGateScreen.kt @@ -0,0 +1,301 @@ +package com.ujwal.focusgram.feature.session + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.HourglassBottom +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.ujwal.focusgram.core.ui.theme.AlertRed +import com.ujwal.focusgram.core.ui.theme.PureBlack +import com.ujwal.focusgram.core.utils.HapticHelper +import com.ujwal.focusgram.core.utils.TimeFormatters + +/** + * Fullscreen gate shown when the user's global daily screen time limit is reached. + * Allows up to 2 extensions (5 minutes each). Once exhausted, blocks app access for today. + */ +@Composable +fun DailyLimitGateScreen( + dailyLimitMinutes: Int, + todayMinutesUsed: Int, + extensionsRemaining: Int, + onExit: () -> Unit, + onWatchAdToExtend: () -> Unit, + onDebugBypass: (() -> Unit)? = null, + modifier: Modifier = Modifier +) { + val context = LocalContext.current + + LaunchedEffect(Unit) { + HapticHelper.performSessionCompletionAlert(context) + } + + // Intercepts back navigation: closes the app so user cannot bypass + BackHandler(enabled = true) { + onExit() + } + + val isBlockedForToday = extensionsRemaining <= 0 + + Box( + modifier = modifier + .fillMaxSize() + .background(PureBlack) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) {}, + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + ) { + Spacer(modifier = Modifier.height(32.dp)) + + Box( + modifier = Modifier + .size(68.dp) + .clip(CircleShape) + .background(Color(0xFF1C1C1E)) + .border(1.dp, if (isBlockedForToday) AlertRed.copy(alpha = 0.5f) else Color(0xFF2C2C2E), CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = if (isBlockedForToday) Icons.Default.Lock else Icons.Default.HourglassBottom, + contentDescription = null, + tint = if (isBlockedForToday) AlertRed else Color.White, + modifier = Modifier.size(32.dp) + ) + } + + Spacer(modifier = Modifier.height(20.dp)) + + Text( + text = if (isBlockedForToday) "Daily Limit Reached" else "Daily Limit Reached", + color = Color.White, + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + letterSpacing = (-0.5).sp + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = if (isBlockedForToday) { + "You have reached your daily Instagram screen time limit and used all extensions. FocusGram is locked for the rest of today." + } else { + "You have reached your planned daily Instagram limit of ${TimeFormatters.formatMinutes(dailyLimitMinutes)}. Take a break or extend." + }, + color = Color(0xFF8E8E93), + fontSize = 15.sp, + textAlign = TextAlign.Center, + lineHeight = 21.sp, + modifier = Modifier.padding(horizontal = 16.dp) + ) + + if (com.ujwal.focusgram.BuildConfig.DEBUG && onDebugBypass != null) { + Spacer(modifier = Modifier.height(16.dp)) + androidx.compose.material3.TextButton( + onClick = onDebugBypass + ) { + Text( + text = "Skip Daily Limit (Debug)", + color = Color(0xFFFF9800), + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold + ) + } + } + + Spacer(modifier = Modifier.height(32.dp)) + + Box( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(16.dp)) + .background(Color(0xFF1C1C1E)) + .border(0.5.dp, Color(0xFF2C2C2E), RoundedCornerShape(16.dp)) + .padding(horizontal = 16.dp, vertical = 14.dp) + ) { + Column { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Today's Usage", + color = Color(0xFF8E8E93), + fontSize = 14.sp + ) + Text( + text = TimeFormatters.formatMinutes(todayMinutesUsed), + color = Color.White, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold + ) + } + + HorizontalDivider( + color = Color(0xFF2C2C2E), + thickness = 0.5.dp, + modifier = Modifier.padding(vertical = 12.dp) + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Daily Limit Set", + color = Color(0xFF8E8E93), + fontSize = 14.sp + ) + Text( + text = TimeFormatters.formatMinutes(dailyLimitMinutes), + color = Color.White, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold + ) + } + + HorizontalDivider( + color = Color(0xFF2C2C2E), + thickness = 0.5.dp, + modifier = Modifier.padding(vertical = 12.dp) + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Daily Extensions", + color = Color(0xFF8E8E93), + fontSize = 14.sp + ) + Text( + text = if (isBlockedForToday) "0 / 2 remaining (Locked)" else "$extensionsRemaining / 2 remaining", + color = if (extensionsRemaining > 0) Color.White else AlertRed, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold + ) + } + } + } + + Spacer(modifier = Modifier.height(36.dp)) + + Button( + onClick = onExit, + modifier = Modifier + .fillMaxWidth() + .height(52.dp), + shape = RoundedCornerShape(26.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + contentColor = Color.Black + ) + ) { + Text( + text = "Close Instagram", + color = Color.Black, + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } + + if (!isBlockedForToday) { + Spacer(modifier = Modifier.height(12.dp)) + + OutlinedButton( + onClick = onWatchAdToExtend, + modifier = Modifier + .fillMaxWidth() + .height(52.dp), + shape = RoundedCornerShape(26.dp), + border = androidx.compose.foundation.BorderStroke(1.dp, Color(0xFF2C2C2E)), + colors = ButtonDefaults.outlinedButtonColors( + containerColor = Color(0xFF1C1C1E), + contentColor = Color.White + ) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Icon( + imageVector = Icons.Default.PlayArrow, + contentDescription = null, + tint = Color.White, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(6.dp)) + Text( + text = "Watch Ad to Extend (+5m)", + color = Color.White, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold + ) + } + } + } + + if (com.ujwal.focusgram.BuildConfig.DEBUG && onDebugBypass != null) { + Spacer(modifier = Modifier.height(16.dp)) + androidx.compose.material3.TextButton( + onClick = onDebugBypass + ) { + Text( + text = "Skip Daily Limit (Debug)", + color = Color(0xFFFF9800), + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold + ) + } + } + + Spacer(modifier = Modifier.height(32.dp)) + } + } +} diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/session/SessionExhaustedScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/session/SessionExhaustedScreen.kt similarity index 84% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/session/SessionExhaustedScreen.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/session/SessionExhaustedScreen.kt index 35d03df..6b64195 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/session/SessionExhaustedScreen.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/session/SessionExhaustedScreen.kt @@ -41,13 +41,12 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.ujwal.focusgram.core.ui.theme.PureBlack import com.ujwal.focusgram.core.utils.HapticHelper +import com.ujwal.focusgram.core.utils.TimeFormatters /** - * SessionExhaustedScreen - * Un-bypassable mindful lockdown gate displayed when the session timer expires. - * Premium iOS monochrome design (black, white, gray shades). - * Enforces session limits by consuming back gestures and clicks. - * Allows watching an ad to extend 3 minutes (up to 3 times per session). + * Lockdown gate presented when a timed session expires. + * + * Consumes back gestures and clicks to prevent bypassing the limit without exiting or extending. */ @Composable fun SessionExhaustedScreen( @@ -55,16 +54,16 @@ fun SessionExhaustedScreen( extensionsRemaining: Int, onExit: () -> Unit, onWatchAdToExtend: () -> Unit, + onDebugBypass: (() -> Unit)? = null, modifier: Modifier = Modifier ) { val context = LocalContext.current - // Trigger physical session completion vibration alert (double buzz) LaunchedEffect(Unit) { HapticHelper.performSessionCompletionAlert(context) } - // Lock down back navigation: back button will exit the app, never return to webview + // Intercepts back navigation: exits the app rather than returning to the active session BackHandler(enabled = true) { onExit() } @@ -73,7 +72,7 @@ fun SessionExhaustedScreen( modifier = modifier .fillMaxSize() .background(PureBlack) - // Consume all touches so clicks never bleed through to GeckoView underneath + // Consumes all touches so clicks never bleed through to GeckoView underneath .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null @@ -88,7 +87,6 @@ fun SessionExhaustedScreen( ) { Spacer(modifier = Modifier.height(32.dp)) - // Monochrome iOS Hourglass Badge Box( modifier = Modifier .size(64.dp) @@ -126,9 +124,22 @@ fun SessionExhaustedScreen( modifier = Modifier.padding(horizontal = 16.dp) ) + if (com.ujwal.focusgram.BuildConfig.DEBUG && onDebugBypass != null) { + Spacer(modifier = Modifier.height(16.dp)) + androidx.compose.material3.TextButton( + onClick = onDebugBypass + ) { + Text( + text = "Skip Session Limit (Debug)", + color = Color(0xFFFF9800), + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold + ) + } + } + Spacer(modifier = Modifier.height(32.dp)) - // iOS Grouped Summary Card Box( modifier = Modifier .fillMaxWidth() @@ -149,7 +160,7 @@ fun SessionExhaustedScreen( fontSize = 14.sp ) Text( - text = "$elapsedMinutes min completed", + text = "${TimeFormatters.formatMinutes(elapsedMinutes)} completed", color = Color.White, fontSize = 14.sp, fontWeight = FontWeight.SemiBold @@ -184,7 +195,6 @@ fun SessionExhaustedScreen( Spacer(modifier = Modifier.height(36.dp)) - // Primary Exit Action: Pure White Apple Style Pill Button Button( onClick = onExit, modifier = Modifier @@ -204,7 +214,6 @@ fun SessionExhaustedScreen( ) } - // Secondary Action: Extend with Ad (if extensions remaining) if (extensionsRemaining > 0) { Spacer(modifier = Modifier.height(12.dp)) @@ -241,6 +250,20 @@ fun SessionExhaustedScreen( } } + if (com.ujwal.focusgram.BuildConfig.DEBUG && onDebugBypass != null) { + Spacer(modifier = Modifier.height(16.dp)) + androidx.compose.material3.TextButton( + onClick = onDebugBypass + ) { + Text( + text = "Skip Session Limit (Debug)", + color = Color(0xFFFF9800), + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold + ) + } + } + Spacer(modifier = Modifier.height(32.dp)) } } diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/session/SessionModal.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/session/SessionModal.kt similarity index 98% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/session/SessionModal.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/session/SessionModal.kt index 15aa2ce..7a75e08 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/session/SessionModal.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/session/SessionModal.kt @@ -45,8 +45,7 @@ import com.ujwal.focusgram.core.ui.theme.TextSecondary val REEL_SESSION_PRESETS = listOf(1, 3, 5, 10, 15, 20) /** - * SessionModal - * Bottom modal container to initiate a timed Reels session. + * Bottom sheet modal for selecting or customizing a timed Reels session. */ @Composable fun SessionModal( diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/settings/SettingsScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/settings/SettingsScreen.kt similarity index 81% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/settings/SettingsScreen.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/settings/SettingsScreen.kt index 14f72fc..f2792e3 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/settings/SettingsScreen.kt @@ -48,20 +48,25 @@ import android.widget.Toast import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.material3.CircularProgressIndicator import com.ujwal.focusgram.core.utils.AppIconManager +import com.ujwal.focusgram.core.utils.TimeFormatters import com.ujwal.focusgram.core.utils.HistoricalIconManager import com.ujwal.focusgram.core.security.EncryptedStorage import androidx.compose.material.icons.automirrored.filled.OpenInNew import androidx.compose.material.icons.filled.Block +import androidx.compose.material.icons.filled.BugReport import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.DarkMode +import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Fingerprint import androidx.compose.material.icons.filled.History import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.Notifications +import androidx.compose.material.icons.filled.NotificationsActive import androidx.compose.material.icons.filled.Palette import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Security import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.ShoppingBag @@ -77,6 +82,10 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.foundation.Image +import androidx.compose.ui.graphics.Brush +import com.ujwal.focusgram.core.ui.components.FocusGramCard import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -92,6 +101,11 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.material.icons.automirrored.filled.MenuBook +import androidx.compose.material.icons.filled.SystemUpdate +import com.ujwal.focusgram.feature.update.UpdateCheckResult +import com.ujwal.focusgram.feature.update.UpdateCheckerService +import com.ujwal.focusgram.feature.update.UpdateInfo import androidx.compose.material.icons.filled.Person import com.ujwal.focusgram.core.database.ScreenTimeDao import com.ujwal.focusgram.core.datastore.SettingsRepository @@ -124,7 +138,6 @@ enum class SettingsSubpage { FOCUS, GUARDRAILS, EXTRAS, - APPEARANCE, CHOOSE_APP_ICON, NOTIFICATIONS, GHOST, @@ -133,10 +146,10 @@ enum class SettingsSubpage { } /** - * SettingsScreen - * Redesigned to match the official iOS grouped Settings hierarchy with modular subpages. - * Provides authentic Apple monochrome aesthetics (black, white, gray variants), - * grouped category rows, and modular sub-screens ported from Flutter. + * Root settings coordinator displaying grouped preference sections and nested subpages. + * + * Dispatches mutations directly to [SettingsRepository] flows while delegating deep routes + * (history, screen time, biometric app lock) to top-level navigation callbacks. */ @Composable fun SettingsScreen( @@ -155,6 +168,8 @@ fun SettingsScreen( onSubpageChange: (SettingsSubpage) -> Unit = {}, encryptedStorage: EncryptedStorage? = null, onNavigateChatPinSetup: () -> Unit = {}, + onNavigateOnboarding: () -> Unit = {}, + onNavigateDebugMenu: () -> Unit = {}, modifier: Modifier = Modifier ) { val collectedSettings by repository.settingsFlow.collectAsState(initial = inputSettings ?: FocusSettings()) @@ -169,22 +184,24 @@ fun SettingsScreen( onSubpageChange(page) } var showLegalDialog by remember { mutableStateOf(false) } - var showDeveloperDialog by remember { mutableStateOf(false) } + var showAboutDialog by remember { mutableStateOf(false) } var pendingRewardAction by remember { mutableStateOf<(suspend () -> Unit)?>(null) } var adDialogReason by remember { mutableStateOf(null) } var showSupportAdDialog by remember { mutableStateOf(false) } + var isCheckingUpdate by remember { mutableStateOf(false) } + var availableUpdate by remember { mutableStateOf(null) } + val onRequestAdReward: (action: suspend () -> Unit, reason: String) -> Unit = { action, reason -> pendingRewardAction = action adDialogReason = reason showSupportAdDialog = true } - // Intercept back presses: go back to MAIN subpage first, or pop screen BackHandler { if (activeSubpage == SettingsSubpage.CHOOSE_APP_ICON) { - setSubpage(SettingsSubpage.APPEARANCE) + setSubpage(SettingsSubpage.EXTRAS) } else if (activeSubpage != SettingsSubpage.MAIN) { setSubpage(SettingsSubpage.MAIN) } else { @@ -192,7 +209,6 @@ fun SettingsScreen( } } - // Legal Disclaimer Dialog if (showLegalDialog) { AlertDialog( onDismissRequest = { showLegalDialog = false }, @@ -232,89 +248,183 @@ fun SettingsScreen( ) } - // Developer Dialog - if (showDeveloperDialog) { + if (showAboutDialog) { AlertDialog( - onDismissRequest = { showDeveloperDialog = false }, - containerColor = Color(0xFF1C1C1E), - icon = { - Box( - modifier = Modifier - .size(48.dp) - .clip(CircleShape) - .background(Color(0xFF2C2C2E)), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = Icons.Default.Person, - contentDescription = null, - tint = Color.White, - modifier = Modifier.size(28.dp) - ) - } - }, - title = { - Text( - text = "Developer Profile", - color = Color.White, - fontWeight = FontWeight.Bold, - fontSize = 18.sp - ) - }, + onDismissRequest = { showAboutDialog = false }, + containerColor = Color(0xEB1C1C1E), + modifier = Modifier + .clip(RoundedCornerShape(24.dp)) + .border( + BorderStroke( + 1.dp, + Brush.verticalGradient(listOf(Color(0x38FFFFFF), Color(0x10FFFFFF))) + ), + RoundedCornerShape(24.dp) + ), text = { - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + val appIconBitmap = remember(context) { + AppIconManager.getIconBitmap(context, R.mipmap.ic_launcher) + } + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (appIconBitmap != null) { + Image( + bitmap = appIconBitmap.asImageBitmap(), + contentDescription = "FocusGram App Logo", + modifier = Modifier + .size(72.dp) + .clip(RoundedCornerShape(18.dp)) + ) + } else { + Icon( + imageVector = Icons.Default.Info, + contentDescription = "FocusGram App Logo", + tint = Color.White, + modifier = Modifier.size(54.dp) + ) + } + Spacer(modifier = Modifier.height(12.dp)) Text( - text = "Ujwal", + text = "FocusGram", color = Color.White, - fontWeight = FontWeight.SemiBold, - fontSize = 16.sp + fontWeight = FontWeight.Bold, + fontSize = 20.sp ) Text( - text = "Creator and maintainer of FocusGram. Independent open-source developer focused on privacy, digital wellbeing, and user-centric software.", + text = "v${com.ujwal.focusgram.BuildConfig.VERSION_NAME}", color = Color(0xFF8E8E93), - fontSize = 13.sp, - lineHeight = 18.sp - ) - Text( - text = "GitHub: @Ujwal223", - color = IOSBlue, - fontSize = 13.sp, + fontSize = 12.sp, fontWeight = FontWeight.Medium ) + Spacer(modifier = Modifier.height(12.dp)) + Text( + text = "A digital wellness and intentionality client for Instagram built with native performance. FocusGram helps you stay in control of your time with custom friction gates, mindful limits, client-side content hiders, and ad blocking. 100% private and on-device.", + color = Color(0xFFC7C7CC), + fontSize = 13.sp, + lineHeight = 18.sp, + textAlign = androidx.compose.ui.text.style.TextAlign.Center + ) + Spacer(modifier = Modifier.height(18.dp)) + + FocusGramCard( + modifier = Modifier.fillMaxWidth(), + backgroundColor = Color(0xFF242428), + onClick = { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/Ujwal223")) + context.startActivity(intent) + } + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(36.dp) + .clip(CircleShape) + .background(Color(0xFF3A3A3C)), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.Person, + contentDescription = null, + tint = Color.White, + modifier = Modifier.size(20.dp) + ) + } + Spacer(modifier = Modifier.width(12.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = "Developer", + color = Color(0xFF8E8E93), + fontSize = 11.sp, + fontWeight = FontWeight.Medium + ) + Text( + text = "Ujwal (@Ujwal223)", + color = Color.White, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold + ) + } + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowForwardIos, + contentDescription = null, + tint = Color(0xFF8E8E93), + modifier = Modifier.size(14.dp) + ) + } + } + + Spacer(modifier = Modifier.height(10.dp)) + + FocusGramCard( + modifier = Modifier.fillMaxWidth(), + backgroundColor = Color(0xFF242428), + onClick = { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/Ujwal223/FocusGram")) + context.startActivity(intent) + } + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(36.dp) + .clip(CircleShape) + .background(Color(0xFF3A3A3C)), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.OpenInNew, + contentDescription = null, + tint = IOSBlue, + modifier = Modifier.size(18.dp) + ) + } + Spacer(modifier = Modifier.width(12.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = "GitHub Repository", + color = Color(0xFF8E8E93), + fontSize = 11.sp, + fontWeight = FontWeight.Medium + ) + Text( + text = "Ujwal223/FocusGram", + color = Color.White, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold + ) + } + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowForwardIos, + contentDescription = null, + tint = Color(0xFF8E8E93), + modifier = Modifier.size(14.dp) + ) + } + } } }, confirmButton = { - Button( - onClick = { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/Ujwal223")) - context.startActivity(intent) - showDeveloperDialog = false - }, - colors = ButtonDefaults.buttonColors( - containerColor = Color.White, - contentColor = Color.Black - ), - shape = RoundedCornerShape(20.dp) + TextButton( + onClick = { showAboutDialog = false } ) { - Text("GitHub Profile", fontWeight = FontWeight.Bold) - } - }, - dismissButton = { - OutlinedButton( - onClick = { showDeveloperDialog = false }, - border = BorderStroke(1.dp, Color(0xFF3A3A3C)), - shape = RoundedCornerShape(20.dp), - colors = ButtonDefaults.outlinedButtonColors( - contentColor = Color.White - ) - ) { - Text("Close") + Text("Close", color = Color.White, fontWeight = FontWeight.SemiBold) } } ) } - // Support Ad Dialog if (showSupportAdDialog && adDialogReason != null) { AlertDialog( onDismissRequest = { @@ -324,7 +434,7 @@ fun SettingsScreen( containerColor = Color(0xFF1C1C1E), title = { Text( - text = "Support FocusGram âĪïļ", + text = "Support FocusGram", color = Color.White, fontWeight = FontWeight.Bold, fontSize = 18.sp @@ -339,7 +449,7 @@ fun SettingsScreen( fontSize = 14.sp ) Text( - text = "Your 20 seconds can provide a huge contribution to the continuous development and improvement of FocusGram.\n\nThank you for supporting this project! âĪïļ", + text = "Your 20 seconds provides a meaningful contribution to the continuous open-source development of FocusGram.\n\nThank you for supporting this project.", color = Color(0xFF8E8E93), fontSize = 13.sp, lineHeight = 18.sp @@ -392,7 +502,6 @@ fun SettingsScreen( .background(IOSBg) .padding(WindowInsets.statusBars.asPaddingValues()) ) { - // Navigation Top Bar Row( modifier = Modifier .fillMaxWidth() @@ -401,7 +510,7 @@ fun SettingsScreen( ) { IconButton(onClick = { if (activeSubpage == SettingsSubpage.CHOOSE_APP_ICON) { - setSubpage(SettingsSubpage.APPEARANCE) + setSubpage(SettingsSubpage.EXTRAS) } else if (activeSubpage != SettingsSubpage.MAIN) { setSubpage(SettingsSubpage.MAIN) } else { @@ -420,12 +529,11 @@ fun SettingsScreen( SettingsSubpage.FOCUS -> "Focus Mode" SettingsSubpage.GUARDRAILS -> "Time Control & Guardrails" SettingsSubpage.EXTRAS -> "Extras" - SettingsSubpage.APPEARANCE -> "Appearance" SettingsSubpage.CHOOSE_APP_ICON -> "Choose App Icon" SettingsSubpage.NOTIFICATIONS -> "Privacy & Notifications" SettingsSubpage.GHOST -> "Ghost Mode" - SettingsSubpage.LICENSES -> "Open Source & Credits" - SettingsSubpage.CHAT_SECURITY -> "Chat Security & DM Lock" + SettingsSubpage.LICENSES -> "Open Source Licenses & Acknowledgements" + SettingsSubpage.CHAT_SECURITY -> "Chat/DM Lock" }, color = IOSTextPrimary, fontSize = 17.sp, @@ -434,7 +542,6 @@ fun SettingsScreen( ) } - // Subpage Router when (activeSubpage) { SettingsSubpage.MAIN -> { MainSettingsContent( @@ -446,7 +553,29 @@ fun SettingsScreen( onNavigateReelsHistory = onNavigateReelsHistory, onWatchAd = onWatchAd, onShowLegal = { showLegalDialog = true }, - onShowDeveloper = { showDeveloperDialog = true }, + onShowAbout = { showAboutDialog = true }, + onNavigateOnboarding = onNavigateOnboarding, + onNavigateDebugMenu = onNavigateDebugMenu, + onCheckUpdates = { + if (!isCheckingUpdate) { + isCheckingUpdate = true + scope.launch { + val service = UpdateCheckerService(context, repository) + when (val res = service.checkForUpdates(ignoreDismissed = true)) { + is UpdateCheckResult.Available -> { + availableUpdate = res.updateInfo + } + is UpdateCheckResult.UpToDate -> { + Toast.makeText(context, "FocusGram is up to date!", Toast.LENGTH_SHORT).show() + } + is UpdateCheckResult.Failed -> { + Toast.makeText(context, "Could not check updates: ${res.reason}", Toast.LENGTH_SHORT).show() + } + } + isCheckingUpdate = false + } + } + }, screenTimeDao = screenTimeDao, reelHistory = reelHistory ) @@ -474,13 +603,7 @@ fun SettingsScreen( onNavigateUrl = { url -> onNavigateUrl(url) onBack() - } - ) - } - SettingsSubpage.APPEARANCE -> { - AppearanceSubpage( - repository = repository, - settings = settings, + }, onNavigateChooseIcon = { setSubpage(SettingsSubpage.CHOOSE_APP_ICON) } ) } @@ -488,14 +611,18 @@ fun SettingsScreen( ChooseAppIconSubpage( repository = repository, settings = settings, - onBack = { setSubpage(SettingsSubpage.APPEARANCE) } + onBack = { setSubpage(SettingsSubpage.EXTRAS) } ) } SettingsSubpage.NOTIFICATIONS -> { NotificationsSubpage(repository = repository, settings = settings) } SettingsSubpage.GHOST -> { - GhostModeSubpage(repository = repository, settings = settings) + GhostModeSubpage( + repository = repository, + settings = settings, + onRequestAdReward = onRequestAdReward + ) } SettingsSubpage.LICENSES -> { LicensesSubpage() @@ -509,11 +636,59 @@ fun SettingsScreen( ) } } + + availableUpdate?.let { update -> + AlertDialog( + onDismissRequest = { availableUpdate = null }, + title = { + Text( + text = "FocusGram v${update.latestVersion} Available", + color = IOSTextPrimary, + fontWeight = FontWeight.Bold + ) + }, + text = { + Column { + Text( + text = "A new release is available on GitHub.", + color = IOSTextSecondary, + fontSize = 13.sp + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = update.whatsNew.ifBlank { "Performance improvements and bug fixes." }, + color = IOSTextPrimary, + fontSize = 12.sp, + lineHeight = 16.sp + ) + } + }, + confirmButton = { + Button( + onClick = { + val targetUrl = update.downloadUrl ?: update.releaseUrl + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(targetUrl)).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + context.startActivity(intent) + availableUpdate = null + }, + colors = ButtonDefaults.buttonColors(containerColor = IOSBlue) + ) { + Text("Download", color = Color.White, fontWeight = FontWeight.Bold) + } + }, + dismissButton = { + OutlinedButton(onClick = { availableUpdate = null }) { + Text("Later", color = IOSTextSecondary) + } + }, + containerColor = IOSGroupBg + ) + } } } -// ─── MAIN SETTINGS SCREEN ─────────────────────────────────────────────────── - @Composable private fun MainSettingsContent( settings: FocusSettings, @@ -524,7 +699,10 @@ private fun MainSettingsContent( onNavigateReelsHistory: () -> Unit, onWatchAd: () -> Unit, onShowLegal: () -> Unit, - onShowDeveloper: () -> Unit, + onShowAbout: () -> Unit, + onNavigateOnboarding: () -> Unit, + onNavigateDebugMenu: () -> Unit, + onCheckUpdates: () -> Unit, screenTimeDao: ScreenTimeDao? = null, reelHistory: ReelHistory? = null ) { @@ -534,8 +712,8 @@ private fun MainSettingsContent( val todayRecord by (screenTimeDao?.getForDate(todayStr) ?: flowOf(null)).collectAsState(initial = null) val todayWatchedFromHistory by (reelHistory?.todayCount ?: flowOf(0)).collectAsState(initial = 0) - val todaySeconds = todayRecord?.seconds ?: (settings.instaMinutes * 60) - val todayReelsSeconds = todayRecord?.reelsSeconds ?: (settings.reelsMinutes * 60) + val todaySeconds = todayRecord?.seconds ?: 0 + val todayReelsSeconds = todayRecord?.reelsSeconds ?: 0 val todayReelsWatched = if (reelHistory != null) todayWatchedFromHistory else (todayRecord?.reelsWatched ?: 0) val appTimeText = when { @@ -555,7 +733,6 @@ private fun MainSettingsContent( .fillMaxSize() .padding(horizontal = 16.dp) ) { - // iOS Monochrome Voluntary Support Banner item { IOSGroupedCard(modifier = Modifier.padding(top = 10.dp)) { Column( @@ -672,7 +849,6 @@ private fun MainSettingsContent( } } - // Quick Stats Badge Row (iOS Monochrome) item { IOSGroupedCard(modifier = Modifier.padding(top = 10.dp)) { Row( @@ -700,20 +876,19 @@ private fun MainSettingsContent( } } - // SECTION 1: FOCUS & WELLBEING item { IOSSectionTitle(title = "FOCUS & DIGITAL WELLBEING") IOSGroupedCard { IOSSettingsRow( title = "Focus Mode", - subtitle = if (settings.minimalModeEnabled) "Minimal mode enabled" else "Distraction blocking, feed blur, content hider", + subtitle = if (settings.minimalModeEnabled) "Minimal mode enabled" else "Minimal mode, feed blur, autoplay and distraction blockers", icon = Icons.Default.Block, iconBgColor = IOSRed, onClick = { onNavigateSubpage(SettingsSubpage.FOCUS) } ) IOSSettingsRow( title = "Time Control & Guardrails", - subtitle = "Daily limit timer, cooldown pause, mindful gates", + subtitle = "Daily limit timer, cooldown between sessions, mindful gates", icon = Icons.Default.Timer, iconBgColor = IOSOrange, onClick = { onNavigateSubpage(SettingsSubpage.GUARDRAILS) }, @@ -722,13 +897,12 @@ private fun MainSettingsContent( } } - // SECTION 2: EXTRAS & NAVIGATION item { IOSSectionTitle(title = "EXTRAS") IOSGroupedCard { IOSSettingsRow( title = "Extras", - subtitle = "Startup launch page, media downloader", + subtitle = "Launch page, custom bio fonts, grayscale, app icons & media downloader", icon = Icons.Default.Star, iconBgColor = IOSBlue, onClick = { onNavigateSubpage(SettingsSubpage.EXTRAS) }, @@ -737,22 +911,6 @@ private fun MainSettingsContent( } } - // SECTION 3: APPEARANCE - item { - IOSSectionTitle(title = "APPEARANCE") - IOSGroupedCard { - IOSSettingsRow( - title = "Appearance", - subtitle = if (settings.grayscaleEnabled) "Grayscale on" else "Grayscale mode", - icon = Icons.Default.Palette, - iconBgColor = Color(0xFF636366), - onClick = { onNavigateSubpage(SettingsSubpage.APPEARANCE) }, - showDivider = false - ) - } - } - - // SECTION 4: SECURITY & PRIVACY item { IOSSectionTitle(title = "SECURITY & PRIVACY") IOSGroupedCard { @@ -770,7 +928,7 @@ private fun MainSettingsContent( onClick = onNavigateAppLock ) IOSSettingsRow( - title = "Chat Security & DM Lock", + title = "Chat/DM Lock", subtitle = if (settings.chatLockAll || settings.lockedChatIds.isNotEmpty()) "Active · Locked conversations" else "Lock inbox & chats with PIN/Biometrics", icon = Icons.AutoMirrored.Filled.Chat, iconBgColor = IOSBlue, @@ -778,7 +936,7 @@ private fun MainSettingsContent( ) IOSSettingsRow( title = "Ghost Mode (Stealth)", - subtitle = if (settings.ghostModeEnabled) "Active · Hiding story & DM seen" else "Off", + subtitle = if (settings.ghostModeEnabled) "Active · Hiding story seen & read receipts" else "Off · Browse with privacy and stealth", icon = Icons.Default.VisibilityOff, iconBgColor = IOSPurple, trailingText = if (settings.ghostModeEnabled) "On" else "Off", @@ -786,7 +944,7 @@ private fun MainSettingsContent( ) IOSSettingsRow( title = "Privacy & Notifications", - subtitle = "Link sanitization & session alerts", + subtitle = "Link tracking parameter stripper & session timer alerts", icon = Icons.Default.Notifications, iconBgColor = IOSTeal, onClick = { onNavigateSubpage(SettingsSubpage.NOTIFICATIONS) }, @@ -795,20 +953,19 @@ private fun MainSettingsContent( } } - // SECTION 5: STATS & INSIGHTS item { IOSSectionTitle(title = "STATS & HISTORY") IOSGroupedCard { IOSSettingsRow( title = "Screen Time Dashboard", - subtitle = "Daily & weekly usage analysis", + subtitle = "Daily & weekly usage breakdown, reels statistics, and charts", icon = Icons.Default.Timer, iconBgColor = IOSGreen, onClick = onNavigateScreenTime ) IOSSettingsRow( title = "Reels Watch History", - subtitle = "View and export reels you previously viewed", + subtitle = "Search, review, and manage reels you previously watched", icon = Icons.Default.History, iconBgColor = IOSBlue, onClick = onNavigateReelsHistory, @@ -817,26 +974,15 @@ private fun MainSettingsContent( } } - // SECTION 6: ABOUT & SYSTEM item { IOSSectionTitle(title = "ABOUT") IOSGroupedCard { IOSSettingsRow( - title = "Developer", - subtitle = "Ujwal (@Ujwal223)", - icon = Icons.Default.Person, - iconBgColor = Color(0xFF3A3A3C), - onClick = onShowDeveloper - ) - IOSSettingsRow( - title = "GitHub Repository", - subtitle = "Source code, issues, and releases", - icon = Icons.AutoMirrored.Filled.OpenInNew, - iconBgColor = Color(0xFF3A3A3C), - onClick = { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/Ujwal223/FocusGram")) - context.startActivity(intent) - } + title = "About FocusGram", + subtitle = "Overview, developer, and source repository", + icon = Icons.Default.Info, + iconBgColor = Color(0xFF007AFF), + onClick = onShowAbout ) IOSSettingsRow( title = "Legal Disclaimer", @@ -846,25 +992,55 @@ private fun MainSettingsContent( onClick = onShowLegal ) IOSSettingsRow( - title = "Open Source License & Credits", + title = "Open Source Licenses & Acknowledgements", subtitle = "AGPL-3.0 and third-party dependencies", icon = Icons.Default.Security, iconBgColor = Color(0xFF3A3A3C), - trailingText = "v2.0.0", - onClick = { onNavigateSubpage(SettingsSubpage.LICENSES) }, + trailingText = "v${com.ujwal.focusgram.BuildConfig.VERSION_NAME}", + onClick = { onNavigateSubpage(SettingsSubpage.LICENSES) } + ) + if (com.ujwal.focusgram.BuildConfig.DEBUG) { + IOSSettingsRow( + title = "Welcome & Privacy Guide", + subtitle = "Revisit setup, login security & link preferences", + icon = Icons.AutoMirrored.Filled.MenuBook, + iconBgColor = Color(0xFF34C759), + onClick = onNavigateOnboarding + ) + } + IOSSettingsRow( + title = "Check for Updates", + subtitle = "Look for newer FocusGram releases on GitHub", + icon = Icons.Default.SystemUpdate, + iconBgColor = Color(0xFF34C759), + onClick = onCheckUpdates, showDivider = false ) } } + if (com.ujwal.focusgram.BuildConfig.DEBUG) { + item { + IOSSectionTitle(title = "DEVELOPER DIAGNOSTICS") + IOSGroupedCard { + IOSSettingsRow( + title = "Debug Diagnostics Menu", + subtitle = "Adjust session time, skip cooldowns & limits, simulate notices", + icon = Icons.Default.BugReport, + iconBgColor = IOSOrange, + onClick = onNavigateDebugMenu, + showDivider = false + ) + } + } + } + item { Spacer(modifier = Modifier.height(40.dp)) } } } -// ─── FOCUS MODE SUBPAGE ───────────────────────────────────────────────────── - @Composable private fun FocusModeSubpage( repository: SettingsRepository, @@ -878,7 +1054,6 @@ private fun FocusModeSubpage( .fillMaxSize() .padding(horizontal = 16.dp) ) { - // MINIMALIST MODE item { IOSSectionTitle(title = "MINIMALIST EXPERIENCE") IOSGroupedCard { @@ -902,7 +1077,6 @@ private fun FocusModeSubpage( } } - // CONTENT FILTERING item { IOSSectionTitle(title = "CONTENT FILTERING") IOSGroupedCard { @@ -945,13 +1119,12 @@ private fun FocusModeSubpage( } } - // DISTRACTION BLOCKERS item { IOSSectionTitle(title = "DISTRACTION BLOCKERS") IOSGroupedCard { IOSSettingsRow( title = "Block Video Autoplay", - subtitle = "Stop videos and reels from playing automatically (click audio icon to play)", + subtitle = "Stop videos and reels from playing automatically until tapped", icon = Icons.Default.PlayArrow, iconBgColor = IOSBlue, checked = settings.noAutoplay, @@ -1009,7 +1182,6 @@ private fun FocusModeSubpage( } } - // VANITY METRICS item { IOSSectionTitle(title = "VANITY METRICS") IOSGroupedCard { @@ -1033,7 +1205,6 @@ private fun FocusModeSubpage( } } - // AD BLOCKING item { IOSSectionTitle(title = "AD BLOCKING") IOSGroupedCard { @@ -1075,7 +1246,11 @@ private fun GuardrailsSubpage( val dateFormat = remember { SimpleDateFormat("yyyy-MM-dd", Locale.US) } val todayStr = remember { dateFormat.format(Date()) } val todayRecord by (screenTimeDao?.getForDate(todayStr) ?: flowOf(null)).collectAsState(initial = null) - val isDailyLimitExhausted = settings.dailyLimitMinutes > 0 && ((todayRecord?.seconds ?: 0) >= settings.dailyLimitMinutes * 60) + val todaySeconds = todayRecord?.seconds ?: 0 + val baseDailyLimitSec = settings.dailyLimitMinutes * 60 + val isDailyLimitHit = settings.dailyLimitMinutes > 0 && todaySeconds >= baseDailyLimitSec + val isNearDailyLimit = settings.dailyLimitMinutes > 15 && (baseDailyLimitSec - todaySeconds) in 0..300 + val isDailyLimitLocked = settings.dailyLimitMinutes > 0 && (isDailyLimitHit || isNearDailyLimit) val lastSessionEndTimestamp by repository.lastSessionEndTimestampFlow.collectAsState(initial = 0L) val isCooldownActive = settings.cooldownMinutes > 0 && lastSessionEndTimestamp > 0L && @@ -1093,18 +1268,17 @@ private fun GuardrailsSubpage( .fillMaxSize() .padding(horizontal = 16.dp) ) { - // SESSION LIMITS item { IOSSectionTitle(title = "SESSION LIMITS") IOSGroupedCard { IOSSettingsRow( title = "Daily Limit Timer", - subtitle = if (isDailyLimitExhausted) "Limit exhausted for today (Locked)" else "Max session duration before Instagram locks", + subtitle = if (isDailyLimitLocked) "Limit locked to preserve digital discipline" else "Max daily usage before Instagram locks", icon = Icons.Default.Timer, - iconBgColor = if (isDailyLimitExhausted) Color(0xFF636366) else IOSOrange, - trailingText = if (settings.dailyLimitMinutes > 0) "${settings.dailyLimitMinutes} min" else "Off", + iconBgColor = if (isDailyLimitLocked) Color(0xFF636366) else IOSOrange, + trailingText = if (settings.dailyLimitMinutes > 0) TimeFormatters.formatMinutes(settings.dailyLimitMinutes) else "Off", onClick = { - if (isDailyLimitExhausted) { + if (isDailyLimitLocked) { showDailyLimitExhaustedDialog = true } else { showDailyLimitModal = true @@ -1116,7 +1290,7 @@ private fun GuardrailsSubpage( subtitle = if (isCooldownActive) "Cooldown currently active (Locked)" else "Mandatory resting pause between sessions", icon = Icons.Default.Timer, iconBgColor = if (isCooldownActive) Color(0xFF636366) else IOSBlue, - trailingText = if (settings.cooldownMinutes > 0) "${settings.cooldownMinutes} min" else "Off", + trailingText = if (settings.cooldownMinutes > 0) TimeFormatters.formatMinutes(settings.cooldownMinutes) else "Off", onClick = { if (isCooldownActive) { showCooldownActiveDialog = true @@ -1129,7 +1303,6 @@ private fun GuardrailsSubpage( } } - // MINDFUL GATES item { IOSSectionTitle(title = "MINDFUL GATES") IOSGroupedCard { @@ -1171,14 +1344,7 @@ private fun GuardrailsSubpage( initialIndex = selectedDailyIndex, onDismiss = { showDailyLimitModal = false }, onConfirm = { mins -> scope.launch { repository.setDailyLimitMinutes(mins) } }, - labelProvider = { mins -> - when { - mins == 0 -> "Off" - mins < 60 -> "$mins min" - mins % 60 == 0 -> "${mins / 60} hour" + (if (mins / 60 > 1) "s" else "") - else -> "${mins / 60}h ${mins % 60}m" - } - } + labelProvider = { mins -> if (mins == 0) "Off" else TimeFormatters.formatMinutes(mins) } ) } @@ -1190,7 +1356,7 @@ private fun GuardrailsSubpage( } WheelPickerModalDialog( title = "Cooldown Between Sessions", - subtitle = "Choose mandatory resting period after reaching limits", + subtitle = "Choose mandatory resting period after completing a session", options = cooldownOptions, initialIndex = selectedCooldownIndex, onDismiss = { showCooldownModal = false }, @@ -1257,64 +1423,35 @@ private fun GuardrailsSubpage( containerColor = Color(0xFF1C1C1E), title = { Text( - text = "Daily Limit Exhausted", + text = "Daily Limit Locked", color = Color.White, fontWeight = FontWeight.Bold, fontSize = 18.sp ) }, text = { - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - text = "Your limit is already exhausted, you cannot change this setting today.", - color = Color(0xFF8E8E93), - fontSize = 14.sp, - lineHeight = 20.sp - ) - if (settings.dailyLimitAdClaimedDate == todayStr) { - Text( - text = "Daily +5m extension already claimed today.", - color = IOSOrange, - fontSize = 13.sp, - fontWeight = FontWeight.Medium - ) - } else { - Text( - text = "You may watch a voluntary sponsored ad to extend your daily limit by 5 minutes (once today).", - color = IOSBlue, - fontSize = 13.sp - ) - } + val message = if (isDailyLimitHit) { + "Your daily screen time limit (${TimeFormatters.formatMinutes(settings.dailyLimitMinutes)}) has already been reached today. To protect your digital discipline, this limit cannot be changed or increased today." + } else { + "You are within 5 minutes of reaching your daily limit (${TimeFormatters.formatMinutes(settings.dailyLimitMinutes)}). To prevent impulsive limit increases, the setting is locked while near the limit." } + Text( + text = message, + color = Color(0xFF8E8E93), + fontSize = 14.sp, + lineHeight = 20.sp + ) }, confirmButton = { - if (settings.dailyLimitAdClaimedDate != todayStr) { - Button( - onClick = { - showDailyLimitExhaustedDialog = false - scope.launch { - repository.extendDailyLimit5Minutes(todayStr) - } - onWatchAd() - }, - colors = ButtonDefaults.buttonColors( - containerColor = Color.White, - contentColor = Color.Black - ), - shape = RoundedCornerShape(20.dp) - ) { - Text("Watch Ad (+5 min)", fontWeight = FontWeight.Bold) - } - } - }, - dismissButton = { - OutlinedButton( + Button( onClick = { showDailyLimitExhaustedDialog = false }, - border = BorderStroke(1.dp, Color(0xFF3A3A3C)), - shape = RoundedCornerShape(20.dp), - colors = ButtonDefaults.outlinedButtonColors(contentColor = Color.White) + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + contentColor = Color.Black + ), + shape = RoundedCornerShape(20.dp) ) { - Text("Close") + Text("OK", fontWeight = FontWeight.Bold) } } ) @@ -1435,13 +1572,11 @@ private fun WheelPickerModalDialog( ) } - -// ─── GHOST MODE SUBPAGE ───────────────────────────────────────────────────── - @Composable private fun GhostModeSubpage( repository: SettingsRepository, - settings: FocusSettings + settings: FocusSettings, + onRequestAdReward: (action: suspend () -> Unit, reason: String) -> Unit = { _, _ -> } ) { val scope = rememberCoroutineScope() @@ -1459,7 +1594,13 @@ private fun GhostModeSubpage( icon = Icons.Default.VisibilityOff, iconBgColor = IOSPurple, checked = settings.ghostModeEnabled, - onCheckedChange = { scope.launch { repository.setGhostMode(it) } }, + onCheckedChange = { newState -> + val actionDesc = if (newState) "enable" else "disable" + onRequestAdReward( + { repository.setGhostMode(newState) }, + "Please watch an ad in order to $actionDesc Ghost Mode." + ) + }, showDivider = settings.ghostModeEnabled ) @@ -1470,7 +1611,8 @@ private fun GhostModeSubpage( icon = Icons.Default.VisibilityOff, iconBgColor = IOSBlue, checked = settings.ghostHideStorySeen, - onCheckedChange = { scope.launch { repository.setGhostHideStorySeen(it) } } + onCheckedChange = { scope.launch { repository.setGhostHideStorySeen(it) } }, + isDependent = true ) IOSSettingsRow( title = "Hide DM Read Receipts", @@ -1478,7 +1620,8 @@ private fun GhostModeSubpage( icon = Icons.Default.VisibilityOff, iconBgColor = IOSTeal, checked = settings.ghostHideDirectSeen, - onCheckedChange = { scope.launch { repository.setGhostHideDirectSeen(it) } } + onCheckedChange = { scope.launch { repository.setGhostHideDirectSeen(it) } }, + isDependent = true ) IOSSettingsRow( title = "Suppress Typing Indicator", @@ -1487,7 +1630,8 @@ private fun GhostModeSubpage( iconBgColor = IOSOrange, checked = settings.ghostHideTypingIndicator, onCheckedChange = { scope.launch { repository.setGhostHideTypingIndicator(it) } }, - showDivider = false + showDivider = false, + isDependent = true ) } } @@ -1506,14 +1650,13 @@ private fun GhostModeSubpage( } } -// ─── EXTRAS SUBPAGE ───────────────────────────────────────────────────────── - @Composable private fun ExtrasSubpage( repository: SettingsRepository, settings: FocusSettings, onRequestAdReward: (action: suspend () -> Unit, reason: String) -> Unit = { _, _ -> }, - onNavigateUrl: (String) -> Unit = {} + onNavigateUrl: (String) -> Unit = {}, + onNavigateChooseIcon: () -> Unit = {} ) { val scope = rememberCoroutineScope() var showLaunchPageModal by remember { mutableStateOf(false) } @@ -1523,9 +1666,8 @@ private fun ExtrasSubpage( .fillMaxSize() .padding(horizontal = 16.dp) ) { - // STARTUP PAGE item { - IOSSectionTitle(title = "STARTUP") + IOSSectionTitle(title = "DISPLAY") IOSGroupedCard { val isCustomLaunch = settings.startupPage != "home" IOSSettingsRow( @@ -1544,7 +1686,7 @@ private fun ExtrasSubpage( } } }, - showDivider = isCustomLaunch + showDivider = true ) if (isCustomLaunch) { IOSSettingsRow( @@ -1557,16 +1699,79 @@ private fun ExtrasSubpage( else -> "Home Feed" }, onClick = { showLaunchPageModal = true }, - showDivider = false + showDivider = true, + isDependent = true + ) + } + + IOSSettingsRow( + title = "Custom Bio Fonts", + subtitle = "Interactive font transformation toolbar on profile edit", + icon = Icons.Default.Edit, + iconBgColor = IOSTeal, + checked = settings.customBioFontsEnabled, + onCheckedChange = { isChecked -> + scope.launch { + repository.setCustomBioFontsEnabled(isChecked) + } + }, + showDivider = true + ) + + IOSSettingsRow( + title = "Grayscale Mode", + subtitle = "Strip high-dopamine saturated feed colors to curb addiction", + icon = Icons.Default.DarkMode, + iconBgColor = Color(0xFF636366), + checked = settings.grayscaleEnabled, + onCheckedChange = { scope.launch { repository.setGrayscale(it) } }, + showDivider = true + ) + + IOSSettingsRow( + title = "Custom Icons", + subtitle = "Enable custom launcher and era icons", + icon = Icons.Default.Palette, + iconBgColor = IOSPurple, + checked = settings.customIconsEnabled, + onCheckedChange = { isChecked -> + scope.launch { repository.setCustomIconsEnabled(isChecked) } + }, + showDivider = settings.customIconsEnabled + ) + if (settings.customIconsEnabled) { + val activeOption = AppIconManager.IconOption.fromId(settings.appIcon) + IOSSettingsRow( + title = "Choose App Icon", + subtitle = "Select launcher icon style", + icon = Icons.Default.Palette, + iconBgColor = IOSBlue, + trailingText = activeOption.displayName, + onClick = onNavigateChooseIcon, + showDivider = false, + isDependent = true ) } } } - // MEDIA item { - IOSSectionTitle(title = "MEDIA") + IOSSectionTitle(title = "MEDIA & TOOLS") IOSGroupedCard { + IOSSettingsRow( + title = "Search Story Viewer List", + subtitle = "Real-time search bar inside story viewers modal", + icon = Icons.Default.Search, + iconBgColor = IOSBlue, + checked = settings.storySearchEnabled, + onCheckedChange = { isChecked -> + scope.launch { + repository.setStorySearchEnabled(isChecked) + } + }, + showDivider = true + ) + IOSSettingsRow( title = "Media Downloader", subtitle = "Download media from Feed, Reels, and Profiles", @@ -1585,9 +1790,8 @@ private fun ExtrasSubpage( } } - // DISPLAY item { - IOSSectionTitle(title = "DISPLAY") + IOSSectionTitle(title = "OTHERS") IOSGroupedCard { IOSSettingsRow( title = "Portrait Only", @@ -1600,15 +1804,23 @@ private fun ExtrasSubpage( repository.setPortraitOnly(isChecked) } }, - showDivider = false + showDivider = true + ) + + IOSSettingsRow( + title = "Show session time in bottom bar", + subtitle = "Render animated golden perimeter progress with sunset color transition on bottom navigation bar", + icon = Icons.Default.Timer, + iconBgColor = IOSOrange, + checked = settings.showSessionTimeInBottomBar, + onCheckedChange = { isChecked -> + scope.launch { + repository.setShowSessionTimeInBottomBar(isChecked) + } + }, + showDivider = true ) - } - } - // INSTAGRAM ACCOUNT PREFERENCES - item { - IOSSectionTitle(title = "INSTAGRAM") - IOSGroupedCard { IOSSettingsRow( title = "Instagram Account Settings", subtitle = "Open official Instagram preferences inside webview", @@ -1653,74 +1865,6 @@ private fun ExtrasSubpage( } } -// ─── APPEARANCE SUBPAGE ───────────────────────────────────────────────────── - -@Composable -private fun AppearanceSubpage( - repository: SettingsRepository, - settings: FocusSettings, - onNavigateChooseIcon: () -> Unit -) { - val scope = rememberCoroutineScope() - - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 16.dp) - ) { - item { - IOSSectionTitle(title = "VISUAL ERGONOMICS") - IOSGroupedCard { - IOSSettingsRow( - title = "Grayscale Mode", - subtitle = "Strip high-dopamine saturated feed colors to curb addiction", - icon = Icons.Default.DarkMode, - iconBgColor = Color(0xFF636366), - checked = settings.grayscaleEnabled, - onCheckedChange = { scope.launch { repository.setGrayscale(it) } }, - showDivider = false - ) - } - } - - item { - Spacer(modifier = Modifier.height(16.dp)) - IOSSectionTitle(title = "APP ICON") - IOSGroupedCard { - IOSSettingsRow( - title = "Custom Icons", - subtitle = "Enable custom launcher and era icons", - icon = Icons.Default.Palette, - iconBgColor = IOSPurple, - checked = settings.customIconsEnabled, - onCheckedChange = { isChecked -> - scope.launch { repository.setCustomIconsEnabled(isChecked) } - }, - showDivider = settings.customIconsEnabled - ) - if (settings.customIconsEnabled) { - val activeOption = AppIconManager.IconOption.fromId(settings.appIcon) - IOSSettingsRow( - title = "Choose App Icon", - subtitle = "Select launcher icon style", - icon = Icons.Default.Palette, - iconBgColor = IOSBlue, - trailingText = activeOption.displayName, - onClick = onNavigateChooseIcon, - showDivider = false - ) - } - } - } - - item { - Spacer(modifier = Modifier.height(40.dp)) - } - } -} - -// ─── CHOOSE APP ICON SUBPAGE ─────────────────────────────────────────────── - @Composable private fun ChooseAppIconSubpage( repository: SettingsRepository, @@ -1746,7 +1890,6 @@ private fun ChooseAppIconSubpage( .fillMaxSize() .padding(horizontal = 16.dp) ) { - // Section 1: Core System Icons item { IOSSectionTitle(title = "CORE ICONS") IOSGroupedCard { @@ -1825,7 +1968,6 @@ private fun ChooseAppIconSubpage( } } - // Section 2: Originals item { Spacer(modifier = Modifier.height(16.dp)) IOSSectionTitle(title = "ORIGINALS") @@ -1906,7 +2048,6 @@ private fun ChooseAppIconSubpage( } } - // Section 3: Redesigns (alt-logos) item { Spacer(modifier = Modifier.height(16.dp)) IOSSectionTitle(title = "REDESIGNS") @@ -2003,8 +2144,6 @@ private fun ChooseAppIconSubpage( } } -// ─── NOTIFICATIONS SUBPAGE ────────────────────────────────────────────────── - @Composable private fun NotificationsSubpage( repository: SettingsRepository, @@ -2037,7 +2176,6 @@ private fun NotificationsSubpage( .fillMaxSize() .padding(horizontal = 16.dp) ) { - // LINK PRIVACY item { IOSSectionTitle(title = "LINK PRIVACY") IOSGroupedCard { @@ -2053,7 +2191,6 @@ private fun NotificationsSubpage( } } - // SESSION ALERTS item { IOSSectionTitle(title = "SESSION ALERTS") IOSGroupedCard { @@ -2075,8 +2212,8 @@ private fun NotificationsSubpage( } ) IOSSettingsRow( - title = "Persistent Status Notification", - subtitle = "Display live session timer countdown in Android notification shade", + title = "Live Session Time Counter", + subtitle = "Display live session elapsed time in Android notification shade", icon = Icons.Default.Timer, iconBgColor = IOSBlue, checked = settings.notifyPersistent, @@ -2090,8 +2227,29 @@ private fun NotificationsSubpage( scope.launch { repository.setNotifyPersistent(false) } } }, - showDivider = false + showDivider = settings.notifyPersistent ) + if (settings.notifyPersistent) { + IOSSettingsRow( + title = "Interval Alerts (Every 5 min)", + subtitle = "Play sound and show popup notification every 5 minutes", + icon = Icons.Default.NotificationsActive, + iconBgColor = IOSOrange, + checked = settings.notifyEvery5Minutes, + onCheckedChange = { willEnable -> + if (willEnable) { + checkAndRequestNotificationPermission { + scope.launch { repository.setNotifyEvery5Minutes(true) } + } + scope.launch { repository.setNotifyEvery5Minutes(true) } + } else { + scope.launch { repository.setNotifyEvery5Minutes(false) } + } + }, + showDivider = false, + isDependent = true + ) + } } } @@ -2101,8 +2259,6 @@ private fun NotificationsSubpage( } } -// ─── LICENSES SUBPAGE ──────────────────────────────────────────────────────── - @Composable private fun LicensesSubpage() { val context = LocalContext.current @@ -2156,7 +2312,7 @@ private fun LicensesSubpage() { } item { - IOSSectionTitle(title = "THIRD-PARTY OPEN SOURCE CREDITS") + IOSSectionTitle(title = "THIRD-PARTY OPEN SOURCE LIBRARIES") IOSGroupedCard { IOSSettingsRow( title = "Mozilla GeckoView", @@ -2230,8 +2386,6 @@ private fun LicensesSubpage() { } } -// ─── CHAT SECURITY & DM PRIVACY SUBPAGE ───────────────────────────────────── - @Composable private fun ChatSecuritySubpage( repository: SettingsRepository, @@ -2320,7 +2474,8 @@ private fun ChatSecuritySubpage( icon = Icons.Default.Lock, iconBgColor = Color(0xFF636366), onClick = onNavigateChatPinSetup, - showDivider = false + showDivider = false, + isDependent = true ) } } diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/snapshots/SnapshotManagerScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/snapshots/SnapshotManagerScreen.kt similarity index 97% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/snapshots/SnapshotManagerScreen.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/snapshots/SnapshotManagerScreen.kt index 69d05f9..13e026a 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/snapshots/SnapshotManagerScreen.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/snapshots/SnapshotManagerScreen.kt @@ -52,10 +52,9 @@ import java.util.Date import java.util.Locale /** - * SnapshotManagerScreen * Offline Feed Snapshot Viewer & Bookmark Manager. - * Unlocks at Level 5. - * Enables users to browse previously loaded pages offline without issuing external network calls. + * + * Browses locally cached GeckoView pages without issuing network requests. */ @Composable fun SnapshotManagerScreen( diff --git a/app/src/main/kotlin/com/ujwal/focusgram/feature/update/UpdateBanner.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/update/UpdateBanner.kt new file mode 100644 index 0000000..213fe2e --- /dev/null +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/update/UpdateBanner.kt @@ -0,0 +1,236 @@ +package com.ujwal.focusgram.feature.update + +import android.content.Intent +import android.net.Uri +import android.view.HapticFeedbackConstants +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.material.icons.filled.SystemUpdate +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.ujwal.focusgram.core.ui.theme.DarkBorder +import com.ujwal.focusgram.core.ui.theme.DarkCard +import com.ujwal.focusgram.core.ui.theme.DarkCardElevated +import com.ujwal.focusgram.core.ui.theme.FocusGreen +import com.ujwal.focusgram.core.ui.theme.InstagramBlue +import com.ujwal.focusgram.core.ui.theme.PureBlack +import com.ujwal.focusgram.core.ui.theme.TextMuted +import com.ujwal.focusgram.core.ui.theme.TextPrimary +import com.ujwal.focusgram.core.ui.theme.TextSecondary + +/** + * An Apple/Cupertino-styled in-app banner notifying the user when a new FocusGram + * GitHub release is available, with expandable markdown release notes and one-tap download. + */ +@Composable +fun UpdateBanner( + updateInfo: UpdateInfo, + onDismiss: () -> Unit, + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val view = LocalView.current + var isExpanded by remember { mutableStateOf(false) } + + Box( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(16.dp)) + .background(DarkCard) + .border(1.dp, DarkBorder, RoundedCornerShape(16.dp)) + .padding(14.dp) + ) { + Column(modifier = Modifier.fillMaxWidth()) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Box( + modifier = Modifier + .size(36.dp) + .clip(CircleShape) + .background(InstagramBlue.copy(alpha = 0.16f)), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.SystemUpdate, + contentDescription = "Update available", + tint = InstagramBlue, + modifier = Modifier.size(20.dp) + ) + } + + Spacer(modifier = Modifier.width(12.dp)) + + Column(modifier = Modifier.weight(1f)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = "Update Available", + style = MaterialTheme.typography.labelSmall.copy( + fontWeight = FontWeight.Bold, + color = InstagramBlue, + fontSize = 11.sp + ) + ) + } + Text( + text = "FocusGram v${updateInfo.latestVersion}", + style = MaterialTheme.typography.titleSmall.copy( + fontWeight = FontWeight.SemiBold, + color = TextPrimary + ) + ) + } + + IconButton( + onClick = { + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + isExpanded = !isExpanded + }, + modifier = Modifier.size(32.dp) + ) { + Icon( + imageVector = if (isExpanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown, + contentDescription = if (isExpanded) "Collapse" else "Expand", + tint = TextSecondary, + modifier = Modifier.size(20.dp) + ) + } + + Spacer(modifier = Modifier.width(4.dp)) + + IconButton( + onClick = { + view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK) + onDismiss() + }, + modifier = Modifier.size(32.dp) + ) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Dismiss update", + tint = TextMuted, + modifier = Modifier.size(18.dp) + ) + } + } + + AnimatedVisibility( + visible = isExpanded, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + Column(modifier = Modifier.padding(top = 12.dp)) { + HorizontalDivider( + color = DarkBorder, + thickness = 0.5.dp, + modifier = Modifier.padding(bottom = 10.dp) + ) + + Text( + text = "What's New in v${updateInfo.latestVersion}", + style = MaterialTheme.typography.labelMedium.copy( + fontWeight = FontWeight.Bold, + color = TextPrimary + ) + ) + + Spacer(modifier = Modifier.height(6.dp)) + + Text( + text = formatReleaseNotes(updateInfo.whatsNew), + style = MaterialTheme.typography.bodySmall.copy( + color = TextSecondary, + lineHeight = 18.sp + ), + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .background(DarkCardElevated) + .padding(10.dp) + ) + + Spacer(modifier = Modifier.height(12.dp)) + + Button( + onClick = { + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + val targetUrl = updateInfo.downloadUrl ?: updateInfo.releaseUrl + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(targetUrl)).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + context.startActivity(intent) + }, + colors = ButtonDefaults.buttonColors(containerColor = InstagramBlue), + shape = RoundedCornerShape(12.dp), + modifier = Modifier + .fillMaxWidth() + .height(44.dp) + ) { + Text( + text = "Download on GitHub", + style = MaterialTheme.typography.labelLarge.copy( + fontWeight = FontWeight.Bold, + color = PureBlack + ) + ) + } + } + } + } + } +} + +/** + * Strips raw markdown syntax markers so release notes render cleanly in plain text. + */ +private fun formatReleaseNotes(raw: String): String { + if (raw.isBlank()) return "General bug fixes and performance improvements." + return raw + .replace(Regex("#{1,6}\\s*"), "") + .replace(Regex("\\*\\*(.*?)\\*\\*"), "$1") + .replace(Regex("\\*(.*?)\\*"), "$1") + .replace(Regex("\\[(.*?)\\]\\((.*?)\\)"), "$1") + .replace(Regex("`([^`]+)`"), "$1") + .trim() +} diff --git a/app/src/main/kotlin/com/ujwal/focusgram/feature/update/UpdateCheckerService.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/update/UpdateCheckerService.kt new file mode 100644 index 0000000..33a2368 --- /dev/null +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/update/UpdateCheckerService.kt @@ -0,0 +1,159 @@ +package com.ujwal.focusgram.feature.update + +import android.content.Context +import android.util.Log +import com.ujwal.focusgram.core.datastore.SettingsRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.withContext +import org.json.JSONObject +import java.io.BufferedReader +import java.io.InputStreamReader +import java.net.HttpURLConnection +import java.net.URL + +/** + * Checks GitHub Releases for new FocusGram versions and exposes update state. + * + * All network requests are read-only HTTP GET calls targeting the public GitHub API, + * keeping FocusGram fully independent of any third-party app store or proprietary telemetry. + */ +class UpdateCheckerService( + private val context: Context, + private val settingsRepository: SettingsRepository +) { + companion object { + private const val TAG = "UpdateChecker" + private const val GITHUB_LATEST_RELEASE_URL = + "https://api.github.com/repos/Ujwal223/FocusGram/releases/latest" + private const val CONNECT_TIMEOUT_MS = 5000 + private const val READ_TIMEOUT_MS = 5000 + + /** + * Compares semver version strings (e.g., "v2.1.0" or "2.0.0"). + * Ignores leading 'v' and trailing prerelease qualifiers for numeric segments. + */ + fun isNewerVersion(remoteTag: String, localVersion: String): Boolean { + val cleanRemote = remoteTag.trim().removePrefix("v").substringBefore("-") + val cleanLocal = localVersion.trim().removePrefix("v").substringBefore("-") + + val remoteParts = cleanRemote.split(".").map { it.toIntOrNull() ?: 0 } + val localParts = cleanLocal.split(".").map { it.toIntOrNull() ?: 0 } + + val maxLen = maxOf(remoteParts.size, localParts.size) + for (i in 0 until maxLen) { + val r = remoteParts.getOrElse(i) { 0 } + val l = localParts.getOrElse(i) { 0 } + if (r > l) return true + if (r < l) return false + } + return false + } + + fun getMockUpdateInfo(): UpdateInfo { + return UpdateInfo( + latestVersion = "3.1.0", + releaseUrl = "https://github.com/Ujwal223/FocusGram/releases", + downloadUrl = "https://github.com/Ujwal223/FocusGram/releases/latest", + whatsNew = "FocusGram v3.1.0 (Simulation Test)\nâ€Ē Verified update notification & dialog rendering\nâ€Ē Liquid glass components styling\nâ€Ē Client-side reels deduplication and rewatch count", + isUpdateAvailable = true + ) + } + } + + /** + * Queries GitHub for the latest release and checks if it exceeds the running app version. + * + * @param ignoreDismissed If true, returns [UpdateCheckResult.Available] even if the user + * previously dismissed this version banner (useful for manual settings check). + */ + suspend fun checkForUpdates(ignoreDismissed: Boolean = false): UpdateCheckResult = + withContext(Dispatchers.IO) { + try { + val url = URL(GITHUB_LATEST_RELEASE_URL) + val connection = (url.openConnection() as HttpURLConnection).apply { + requestMethod = "GET" + connectTimeout = CONNECT_TIMEOUT_MS + readTimeout = READ_TIMEOUT_MS + setRequestProperty("Accept", "application/vnd.github+json") + setRequestProperty("User-Agent", "FocusGram-Android-Client") + } + + val responseCode = connection.responseCode + if (responseCode != HttpURLConnection.HTTP_OK) { + return@withContext UpdateCheckResult.Failed("Server returned HTTP $responseCode") + } + + val responseText = connection.inputStream.bufferedReader().use(BufferedReader::readText) + val json = JSONObject(responseText) + + val tagName = json.optString("tag_name", "").trim() + val htmlUrl = json.optString("html_url", "").trim() + val rawBody = json.optString("body", "").trim() + + if (tagName.isEmpty() || htmlUrl.isEmpty()) { + return@withContext UpdateCheckResult.Failed("Malformed release payload from GitHub") + } + + val currentVersion = getCurrentVersionName() + if (!isNewerVersion(tagName, currentVersion)) { + return@withContext UpdateCheckResult.UpToDate + } + + val cleanVersion = tagName.removePrefix("v") + if (!ignoreDismissed) { + val dismissedVersion = settingsRepository.getDismissedUpdateVersion() + if (dismissedVersion == cleanVersion || dismissedVersion == tagName) { + return@withContext UpdateCheckResult.UpToDate + } + } + + var downloadUrl: String? = null + val assets = json.optJSONArray("assets") + if (assets != null) { + for (i in 0 until assets.length()) { + val asset = assets.optJSONObject(i) ?: continue + val name = asset.optString("name", "") + if (name.endsWith(".apk", ignoreCase = true)) { + val assetUrl = asset.optString("browser_download_url", "") + if (assetUrl.isNotEmpty()) { + if (name.contains("universal", ignoreCase = true) || downloadUrl == null) { + downloadUrl = assetUrl + } + } + } + } + } + + val trimmedNotes = if (rawBody.length > 2000) { + rawBody.substring(0, 2000).trim() + "..." + } else rawBody + + val info = UpdateInfo( + latestVersion = cleanVersion, + releaseUrl = htmlUrl, + downloadUrl = downloadUrl ?: htmlUrl, + whatsNew = trimmedNotes, + isUpdateAvailable = true + ) + + UpdateCheckResult.Available(info) + } catch (e: Exception) { + Log.w(TAG, "Update check failed: ${e.message}") + UpdateCheckResult.Failed(e.message ?: "Network error checking updates") + } + } + + suspend fun dismissVersion(version: String) { + settingsRepository.setDismissedUpdateVersion(version) + } + + private fun getCurrentVersionName(): String { + return try { + val pInfo = context.packageManager.getPackageInfo(context.packageName, 0) + pInfo.versionName ?: "3.0.0" + } catch (_: Exception) { + "3.0.0" + } + } +} diff --git a/app/src/main/kotlin/com/ujwal/focusgram/feature/update/UpdateInfo.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/update/UpdateInfo.kt new file mode 100644 index 0000000..2f5e84d --- /dev/null +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/update/UpdateInfo.kt @@ -0,0 +1,18 @@ +package com.ujwal.focusgram.feature.update + +/** + * Encapsulates version metadata retrieved from the GitHub Releases API. + */ +data class UpdateInfo( + val latestVersion: String, + val releaseUrl: String, + val downloadUrl: String?, + val whatsNew: String, + val isUpdateAvailable: Boolean +) + +sealed interface UpdateCheckResult { + data class Available(val updateInfo: UpdateInfo) : UpdateCheckResult + data object UpToDate : UpdateCheckResult + data class Failed(val reason: String) : UpdateCheckResult +} diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/webview/GeckoViewContainer.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/webview/GeckoViewContainer.kt similarity index 91% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/webview/GeckoViewContainer.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/webview/GeckoViewContainer.kt index 0e3aaaa..0aecc70 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/webview/GeckoViewContainer.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/webview/GeckoViewContainer.kt @@ -37,14 +37,14 @@ import com.ujwal.focusgram.core.ui.theme.PureBlack import org.mozilla.geckoview.GeckoView /** - * GeckoViewContainer - * Jetpack Compose host for Mozilla GeckoView. - * Provides edge-to-edge window insets, top progress indicator, and OLED skeleton loader. + * Hosts [GeckoView] inside Compose with edge-to-edge IME insets, + * cold-start shimmer loading, and top navigation progress indicators. */ @Composable fun GeckoViewContainer( sessionManager: GeckoSessionManager, grayscale: Boolean = false, + isVisible: Boolean = true, modifier: Modifier = Modifier ) { val isLoading by sessionManager.isLoading.collectAsState() @@ -63,7 +63,6 @@ fun GeckoViewContainer( val observer = androidx.lifecycle.LifecycleEventObserver { _, event -> when (event) { androidx.lifecycle.Lifecycle.Event.ON_STOP -> sessionManager.onPause() - androidx.lifecycle.Lifecycle.Event.ON_START -> sessionManager.onResume() else -> Unit } } @@ -79,7 +78,6 @@ fun GeckoViewContainer( .background(PureBlack) .padding(WindowInsets.ime.asPaddingValues()) ) { - // Native GeckoView host AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> @@ -95,13 +93,13 @@ fun GeckoViewContainer( if (geckoView.session != sessionManager.session) { geckoView.setSession(sessionManager.session) } + geckoView.visibility = if (isVisible) android.view.View.VISIBLE else android.view.View.INVISIBLE }, onRelease = { geckoView -> geckoView.releaseSession() } ) - // Initial skeleton shimmer screen during cold start AnimatedVisibility( visible = !initialLoadCompleted && isLoading, enter = fadeIn(), @@ -110,7 +108,6 @@ fun GeckoViewContainer( SkeletonScreen(type = getSkeletonTypeFromUrl(currentUrl)) } - // Top progress indicator for incremental page navigation AnimatedVisibility( visible = isLoading, enter = fadeIn(), diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/AdsterraAdScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/AdsterraAdScreen.kt similarity index 83% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/AdsterraAdScreen.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/AdsterraAdScreen.kt index 28804c5..9cd061d 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/AdsterraAdScreen.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/AdsterraAdScreen.kt @@ -60,6 +60,26 @@ import androidx.compose.ui.viewinterop.AndroidView import com.ujwal.focusgram.core.ui.theme.PureBlack import kotlinx.coroutines.delay +private val ADSTERRA_INTERNAL_DOMAINS = listOf( + "effectivecpmnetwork.com", + "highperformanceformat.com", + "effectivegatecpm.com", + "profitableratecpm.com", + "profitablecpmratenetwork.com", + "profitableratecpmnetwork.com", + "aqml.org", + "afders.org", + "bicea.org", + "arwf.org", + "cdn.show-sb.com", + "api3.adsterratools.com", + "adsterra.com" +) + +private fun isAdsterraInternalDomain(url: String): Boolean { + return ADSTERRA_INTERNAL_DOMAINS.any { url.contains(it, ignoreCase = true) } +} + private const val AD_HTML = """ @@ -141,9 +161,10 @@ private const val AD_HTML = """ """ /** - * AdsterraAdScreen - * Premium iOS monochrome rewarded ad viewing container running in a dedicated WebView. - * Enforces a 20s countdown timer and 2 external browser ad clicks to claim focus minutes. + * Isolated WebView container for opt-in rewarded ad viewing. + * + * Runs inside standard Android system WebView completely decoupled from GeckoView + * to preserve uBlock Origin blocking in Instagram browsing sessions. */ @SuppressLint("SetJavaScriptEnabled") @Composable @@ -161,20 +182,24 @@ fun AdsterraAdScreen( var webViewRef by remember { mutableStateOf(null) } val isSettingToggle = sessionType == "setting_toggle" + val isVoluntarySupport = sessionType == "voluntary" + val isEffortFriction = sessionType == "effort_friction" + val isSessionExtend = sessionType == "session_extend" + val timerDone = elapsedSeconds >= requiredSeconds val bothClicked = adsClicked >= 2 - val isComplete = if (isSettingToggle) timerDone else (timerDone && bothClicked) + val isComplete = if (isSettingToggle || isVoluntarySupport || isEffortFriction) timerDone else (timerDone && bothClicked) - // Ensure clean countdown and state when entering a setting toggle session + // Ensure clean countdown and state when entering a setting toggle, voluntary support, or friction session LaunchedEffect(sessionType) { - if (isSettingToggle) { + if (isSettingToggle || isVoluntarySupport || isEffortFriction) { elapsedSeconds = 0 adsClicked = 0 timerCycle++ } } - // 20s Timer Loop - continues cleanly from saved elapsedSeconds without resetting + // Preserves elapsed countdown when re-entering the screen LaunchedEffect(timerCycle) { while (elapsedSeconds < requiredSeconds) { delay(1000L) @@ -183,11 +208,16 @@ fun AdsterraAdScreen( } val statusText = when { - isComplete -> if (isSettingToggle) "Ad viewed! Tap below to apply your setting." else "Reward Unlocked! Tap below to claim focus minutes." - isSettingToggle -> "Viewing ad (${requiredSeconds - elapsedSeconds}s remaining)..." + isComplete -> when { + isVoluntarySupport -> "Thank you for supporting FocusGram! Your contribution keeps this project free." + isSettingToggle -> "Ad viewed! Tap below to apply your setting." + isEffortFriction -> "Ad viewed! Tap below to proceed to Instagram." + else -> "Reward Unlocked! Tap below to extend session by 5 minutes." + } + isSettingToggle || isVoluntarySupport || isEffortFriction -> "Viewing ad (${requiredSeconds - elapsedSeconds}s remaining)..." bothClicked -> "Both ads clicked! Waiting for countdown (${requiredSeconds - elapsedSeconds}s)..." timerDone -> "Timer completed. Please tap both ads ($adsClicked/2) to continue." - else -> "Tap BOTH ads below to earn reward credits ($adsClicked/2 clicked)" + else -> "Tap BOTH ads below to unlock your reward ($adsClicked/2 clicked)" } Surface( @@ -199,7 +229,6 @@ fun AdsterraAdScreen( .fillMaxSize() .systemBarsPadding() ) { - // iOS Monochrome Header Bar Row( modifier = Modifier .fillMaxWidth() @@ -226,19 +255,28 @@ fun AdsterraAdScreen( Column(modifier = Modifier.weight(1f)) { Text( - text = "Support FocusGram", + text = when { + isVoluntarySupport -> "Support FocusGram" + isSettingToggle -> "Unlock Setting" + isEffortFriction -> "Focus Bypass" + else -> "Extend Session" + }, color = Color.White, fontSize = 16.sp, fontWeight = FontWeight.SemiBold ) Text( - text = "Sponsored Reward", + text = when { + isVoluntarySupport -> "Voluntary Contribution" + isSettingToggle -> "Sponsored Verification" + isEffortFriction -> "Sponsored Access" + else -> "Sponsored Reward" + }, color = Color(0xFF8E8E93), fontSize = 12.sp ) } - // Apple Style Timer Pill Badge Box( modifier = Modifier .clip(RoundedCornerShape(12.dp)) @@ -255,7 +293,6 @@ fun AdsterraAdScreen( } } - // Monochrome Progress Bar LinearProgressIndicator( progress = { (elapsedSeconds.toFloat() / requiredSeconds).coerceIn(0f, 1f) }, modifier = Modifier @@ -265,15 +302,14 @@ fun AdsterraAdScreen( trackColor = Color(0xFF2C2C2E) ) - // Status Card Box( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 10.dp) - .clip(RoundedCornerShape(10.dp)) - .background(Color(0xFF1C1C1E)) - .border(0.5.dp, Color(0xFF2C2C2E), RoundedCornerShape(10.dp)) - .padding(horizontal = 14.dp, vertical = 8.dp), + .clip(RoundedCornerShape(10.dp)) + .background(Color(0xFF1C1C1E)) + .border(0.5.dp, Color(0xFF2C2C2E), RoundedCornerShape(10.dp)) + .padding(horizontal = 14.dp, vertical = 8.dp), contentAlignment = Alignment.Center ) { Text( @@ -284,7 +320,6 @@ fun AdsterraAdScreen( ) } - // Dedicated ad WebView host container Box( modifier = Modifier .weight(1f) @@ -321,7 +356,7 @@ fun AdsterraAdScreen( fun handleAdClick(url: String): Boolean { val clean = url.trim() - if (clean.isNotBlank() && !clean.contains("effectivecpmnetwork.com") && !clean.contains("highperformanceformat.com") && !clean.startsWith("about:") && !clean.startsWith("data:")) { + if (clean.isNotBlank() && !isAdsterraInternalDomain(clean) && !clean.startsWith("about:") && !clean.startsWith("data:")) { val now = System.currentTimeMillis() if (clean != lastClickedUrl || now - lastClickTime > 1500L) { lastClickedUrl = clean @@ -392,7 +427,7 @@ fun AdsterraAdScreen( super.onPageStarted(view, url, favicon) val u = url ?: "" if (u.isNotBlank() && !u.startsWith("about:") && !u.startsWith("data:")) { - if (!u.contains("effectivecpmnetwork.com") && !u.contains("highperformanceformat.com")) { + if (!isAdsterraInternalDomain(u)) { handleAdClick(u) } } @@ -412,14 +447,12 @@ fun AdsterraAdScreen( ) } - // Bottom Action Area (iOS Monochrome Buttons) Column( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 14.dp), verticalArrangement = Arrangement.spacedBy(10.dp) ) { - // Primary Claim Button: Apple Style Pill Button Button( onClick = { elapsedSeconds = 0 @@ -445,9 +478,15 @@ fun AdsterraAdScreen( ) Spacer(modifier = Modifier.width(6.dp)) val buttonLabel = when { - !isComplete -> if (isSettingToggle) "Viewing Ad (${(requiredSeconds - elapsedSeconds).coerceAtLeast(0)}s)" else "Complete Ad Actions to Unlock" + !isComplete -> if (isSettingToggle || isVoluntarySupport || isEffortFriction) { + "Viewing Ad (${(requiredSeconds - elapsedSeconds).coerceAtLeast(0)}s)" + } else { + "Complete Ad Actions to Unlock" + } + isVoluntarySupport -> "Done (Return to Settings)" isSettingToggle -> "Continue & Apply Setting" - else -> "Continue & Earn Reward (+2 min)" + isEffortFriction -> "Continue to Instagram" + else -> "Continue & Extend Session (+5 min)" } Text( text = buttonLabel, @@ -456,8 +495,7 @@ fun AdsterraAdScreen( ) } - // Retry Button if ads fail to load - if (timerDone && !bothClicked) { + if (!isComplete && timerDone && !bothClicked) { Button( onClick = { timerCycle++ @@ -496,16 +534,10 @@ fun AdsterraAdScreen( } /** - * AdPreloader - * Pre-warms Android System WebView process & Chromium runtime into memory. - * Runs completely isolated from GeckoView, having zero effect on Instagram's uBlock Origin adblocker. - * - * IMPORTANT FOR MAX EARNINGS & IVT SAFETY: - * Pre-warming uses "about:blank" to warm up the Chromium rendering engine and V8 runtime in RAM - * so AdsterraAdScreen opens with 0ms startup lag. - * Actual ad tags are loaded LIVE only when the user opens AdsterraAdScreen. - * This guarantees 100% viewable impressions, fresh click tokens, high CTR, and protects your account - * from Adsterra's Invalid Traffic (IVT) / phantom impression filters. + * Pre-warms the Android system WebView and Chromium V8 runtime in the background using about:blank. + * + * Runs isolated from GeckoView to avoid cold-start latency when opening [AdsterraAdScreen], + * without touching Instagram browsing sessions or uBlock Origin state. */ object AdPreloader { @Volatile diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BreathGateScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BreathGateScreen.kt similarity index 96% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BreathGateScreen.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BreathGateScreen.kt index 9640831..5e382df 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BreathGateScreen.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/BreathGateScreen.kt @@ -47,9 +47,7 @@ import com.ujwal.focusgram.core.utils.HapticHelper import kotlinx.coroutines.delay /** - * BreathGateScreen - * Mindful breathing countdown gate designed to interrupt habitual app opening - * and restore conscious intentionality. + * Interstitial countdown screen enforcing a mandatory breathing delay before app access. */ @Composable fun BreathGateScreen( @@ -61,7 +59,6 @@ fun BreathGateScreen( var secondsRemaining by remember { mutableIntStateOf(initialSeconds) } val canContinue = secondsRemaining <= 0 - // Countdown Timer Loop LaunchedEffect(Unit) { while (secondsRemaining > 0) { delay(1000L) @@ -70,7 +67,6 @@ fun BreathGateScreen( HapticHelper.performSuccessPulse(context) } - // Mindful Breathing Expansion/Contraction Animation val infiniteTransition = rememberInfiniteTransition(label = "breath_circle_transition") val breathScale by infiniteTransition.animateFloat( initialValue = 1.0f, diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/CooldownGateScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/CooldownGateScreen.kt similarity index 83% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/CooldownGateScreen.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/CooldownGateScreen.kt index 0352453..c6e0acc 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/CooldownGateScreen.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/CooldownGateScreen.kt @@ -48,17 +48,19 @@ import com.ujwal.focusgram.core.utils.HapticHelper import kotlinx.coroutines.delay /** - * CooldownGateScreen - * Enforces mandatory resting cooldown intervals between Instagram usage sessions. + * Fullscreen lock requiring the user to wait out a cooldown period between sessions. */ @Composable fun CooldownGateScreen( - remainingSecondsInitial: Int, + remainingSeconds: Int = 0, + remainingSecondsInitial: Int = remainingSeconds, onCooldownExpired: () -> Unit, - onExit: () -> Unit + onExit: () -> Unit, + onDebugBypass: (() -> Unit)? = null ) { val context = LocalContext.current - var remainingSeconds by remember { mutableIntStateOf(remainingSecondsInitial.coerceAtLeast(0)) } + val effectiveRemaining = (if (remainingSeconds > 0) remainingSeconds else remainingSecondsInitial).coerceAtLeast(0) + val debugBypassAction = onDebugBypass ?: onCooldownExpired val quotes = remember { listOf( @@ -72,18 +74,15 @@ fun CooldownGateScreen( val quote = remember { quotes.random() } - LaunchedEffect(remainingSeconds) { - if (remainingSeconds <= 0) { + LaunchedEffect(effectiveRemaining) { + if (effectiveRemaining <= 0) { HapticHelper.performSuccessPulse(context) onCooldownExpired() - } else { - delay(1000L) - remainingSeconds-- } } - val minutes = remainingSeconds / 60 - val seconds = remainingSeconds % 60 + val minutes = effectiveRemaining / 60 + val seconds = effectiveRemaining % 60 val formattedTime = String.format("%02d:%02d", minutes, seconds) Surface( @@ -98,14 +97,19 @@ fun CooldownGateScreen( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { - // Hourglass Icon with Warm Amber Glow Box( modifier = Modifier .size(80.dp) .clip(CircleShape) .background(Color(0xFFFF9800).copy(alpha = 0.12f)) .border(1.5.dp, Color(0xFFFF9800).copy(alpha = 0.4f), CircleShape) - .clickable { onCooldownExpired() }, + .then( + if (com.ujwal.focusgram.BuildConfig.DEBUG) { + Modifier.clickable { debugBypassAction() } + } else { + Modifier + } + ), contentAlignment = Alignment.Center ) { Icon( @@ -138,7 +142,6 @@ fun CooldownGateScreen( Spacer(modifier = Modifier.height(40.dp)) - // Big Timer Display Card Box( modifier = Modifier .fillMaxWidth() @@ -168,7 +171,6 @@ fun CooldownGateScreen( Spacer(modifier = Modifier.height(36.dp)) - // Motivational Quote Text( text = quote, style = MaterialTheme.typography.bodyMedium.copy( @@ -181,7 +183,6 @@ fun CooldownGateScreen( Spacer(modifier = Modifier.height(48.dp)) - // Exit Button OutlinedButton( onClick = onExit, modifier = Modifier @@ -196,16 +197,19 @@ fun CooldownGateScreen( ) } - Spacer(modifier = Modifier.height(12.dp)) + if (com.ujwal.focusgram.BuildConfig.DEBUG) { + Spacer(modifier = Modifier.height(12.dp)) - TextButton( - onClick = onCooldownExpired - ) { - Text( - text = "Skip Cooldown", - color = TextMuted, - fontSize = 14.sp - ) + TextButton( + onClick = debugBypassAction + ) { + Text( + text = "Skip Cooldown (Debug)", + color = Color(0xFFFF9800), + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold + ) + } } } } diff --git a/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/DisciplineMantraRepository.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/DisciplineMantraRepository.kt new file mode 100644 index 0000000..5a84b8a --- /dev/null +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/DisciplineMantraRepository.kt @@ -0,0 +1,214 @@ +package com.ujwal.focusgram.feature.wellness + +/** + * Repository of inspiring discipline mantras grouped by word count (10, 15, 20, 30, 50). + * + * Each word count list contains at least 30 unique sentences crafted to match the exact word count. + * Shuffled non-repeating queues ensure sentences cycle randomly without duplicates until all + * items for that word count are exhausted. + */ +object DisciplineMantraRepository { + private val unexhaustedPools = mutableMapOf>() + + val mantras10: List = listOf( + "I control my attention and choose presence over endless scrolling.", + "My focus is valuable and I will not waste it.", + "Real life is unfolding outside this small illuminated digital screen.", + "I enter this application with clear purpose and calm awareness.", + "Discipline creates genuine freedom and shields my precious daily energy.", + "I will not let an algorithm dictate my mental state.", + "Calm awareness guides every action I take in this moment.", + "I master my digital habits instead of serving shallow impulses.", + "Every conscious breath returns my focus to what truly matters.", + "I step back from mindless consumption to protect my peace.", + "Intentional choices today build the strong discipline I need tomorrow.", + "My peace of mind is worth more than digital approval.", + "I observe the impulse to scroll and calmly let go.", + "True clarity emerges when I silence constant unnecessary digital noise.", + "I am fully present in reality rather than lost online.", + "Deep focus is a skill that I practice with intention.", + "I honor my time by choosing deliberate focus over distraction.", + "Today I cultivate mental clarity and resist shallow algorithmic dopamine.", + "My attention belongs to my goals and my real life.", + "One intentional minute is worth hours of mindless social browsing.", + "I choose to disconnect and protect my inner mental sanctuary.", + "Quiet discipline today creates meaningful accomplishments for my future self.", + "I consume information intentionally and never let feeds consume me.", + "Patience and awareness empower me to control every digital impulse.", + "I value deep concentration over brief moments of fleeting novelty.", + "Mindful living begins with guarding how I spend my time.", + "I release the urge to seek superficial digital social validation.", + "Focused effort on what matters will transform my personal journey.", + "I pause, breathe deeply, and direct my energy toward purpose.", + "My daily habits shape my destiny and I choose presence.", + ) + + val mantras15: List = listOf( + "I refuse to surrender my sacred attention to an algorithm designed for endless addictive engagement.", + "True inner power begins the moment I decide to control my own digital screen time.", + "My creative potential flourishes only when I consciously protect my mind from shallow continuous distractions.", + "I use social platforms as intentional tools rather than allowing them to dictate my life.", + "Every mindful pause between urges strengthens my willpower and deepens my everyday clear mental clarity.", + "Distraction is costly, but deliberate attention yields profound focus and peaceful personal accomplishments every day.", + "I honor the precious hours of my day by doing real work in physical reality.", + "Stepping away from superficial feeds allows my genuine creativity and deeper focus to freely flourish.", + "I am the conscious gatekeeper of my mind and refuse to indulge impulsive online consumption.", + "Living intentionally means knowing exactly when to close this screen and return to real life.", + "A disciplined mind navigates the modern world with supreme grace, calm purpose, and unwavering resolve.", + "I choose conscious self-restraint over effortless dopamine hits that leave me feeling exhausted and unfocused.", + "My highest aspirations require undivided daily focus that no superficial digital feed can ever provide.", + "Peace comes from deliberate restraint and prioritizing meaningful relationships over endless virtual feeds and notifications.", + "I master this digital technology with disciplined poise instead of letting it dictate my day.", + "Clear intention guides my browsing so I leave this app fulfilled rather than quietly depleted.", + "Every second given to shallow feeds is stolen from my greatest personal goals and dreams.", + "I choose to experience reality directly rather than watching others live through digital screens today.", + "Self-control is a daily practice that transforms restless impulses into unshakable inner focus and clarity.", + "I cultivate deep concentration today by saying a firm no to cheap, endless online distractions.", + "My presence of mind is my greatest superpower in a world full of noisy distractions.", + "Deliberate choices protect my energy, allowing me to build a truly remarkable and focused life.", + "I release the compulsive urge to refresh and reclaim full ownership of my valuable attention.", + "Digital serenity begins when I silence unnecessary notifications and focus deeply on what truly matters.", + "I am committed to living deeply in the present moment without seeking empty digital validation.", + "When I conquer the impulse to scroll, I unlock hours of deep, rewarding creative work.", + "My attention is sacred, so I guard it against addictive algorithmic loops with fierce discipline.", + "I deliberately choose silence and deep contemplation over the endless roar of modern social media.", + "Intentional habits build the quiet confidence and mental strength necessary to achieve lasting personal greatness.", + "I step forward with conscious purpose, leaving behind mindless scrolling to embrace real world living.", + ) + + val mantras20: List = listOf( + "By choosing mindful focus over endless scrolling, I actively reclaim the time, energy, and mental peace needed for my goals.", + "I refuse to let engineered digital feeds capture my thoughts when genuine life experiences are waiting for me outside today.", + "Discipline is the ultimate bridge between empty intentions and real accomplishments, so I choose deliberate focus over mindless digital consumption.", + "Technology should elevate my daily existence rather than trap my attention in an endless loop of superficial and fleeting dopamine.", + "I take complete responsibility for where I place my attention, choosing intentional presence over reactive digital browsing every single hour.", + "When I pause and breathe, I regain conscious command over my impulses and remember what truly matters in my life.", + "Every moment spent away from algorithmic feeds is an investment in my mental health, personal relationships, and deep creative clarity.", + "I honor my mind by consuming only what nourishes my intellect, refusing to waste precious hours on shallow social noise.", + "The world needs my authentic contribution, not my passive consumption of endless videos and superficial images on a small screen.", + "Mastering my digital impulses gives me the freedom to think deeply, create meaningful work, and live with genuine peace today.", + "I step into this digital space with absolute clarity of purpose, determined to leave as soon as my task finishes.", + "True satisfaction comes from real-world accomplishments and heartfelt human connections, never from the synthetic validation of social media engagement metrics.", + "I protect my mental clarity with unwavering discipline, knowing that my attention determines the entire quality of my everyday experience.", + "A calm and focused mind is capable of extraordinary achievements when freed from the constant pull of compulsive digital notifications.", + "I practice healthy boundaries with my smartphone so that my creative energy flows freely toward the projects that inspire me.", + "Mindful living requires conscious resistance against the addictive pull of endless feeds designed to keep my mind permanently scattered today.", + "I choose to be the active creator of my life rather than a passive observer of someone else's curated reality.", + "The discipline I practice right now strengthens my character and preserves the precious hours I need for personal growth today.", + "I reject the illusion of continuous connectivity in favor of deep presence, quiet contemplation, and intentional daily productive action now.", + "My daily focus is far too valuable to be traded away for fleeting entertainment and manipulative advertising on algorithmic platforms.", + "I cultivate stillness amidst modern digital chaos, ensuring that my mind remains calm, centered, and deeply focused on my priorities.", + "Every conscious decision to close an addictive feed reinforces my self-respect and empowers me to live with genuine intentionality today.", + "I direct my energy toward meaningful pursuits that endure, letting go of the constant urge to check trivial digital updates.", + "True freedom is the ability to direct my attention wherever I choose without being pulled by compulsive digital habits today.", + "I respect my future self by making disciplined choices now, refusing to sacrifice long-term fulfillment for brief moments of amusement.", + "My peace of mind flourishes when I disconnect from endless feeds and reconnect with the quiet beauty of physical reality.", + "I choose to be fully awake to my own life rather than mesmerized by the glowing pixels of my screen.", + "Clear boundaries around my digital consumption allow my thoughts to deepen and my authentic voice to emerge with great clarity.", + "I possess the strength to resist shallow distractions and channel my focused attention into work that makes a real difference.", + "With every deliberate breath, I anchor myself in the present moment and release all desire for mindless online scrolling today.", + ) + + val mantras30: List = listOf( + "I choose to control my focus and not let algorithmic feeds govern my attention. Real life happens outside this screen, and my precious time deserves deliberate purpose and disciplined presence.", + "Technology is a tool for my growth, not a trap for dopamine. I enter with conscious intent, observe with clear detachment, and leave before endless scrolling steals my peaceful day.", + "True freedom comes from voluntary discipline. I possess mastery over my impulses, refusing to mindlessly consume superficial digital noise when real-world accomplishments demand my undivided attention and creative energy today.", + "My attention is my most precious asset, shaping what I experience. I refuse to trade it away for mindless videos, choosing instead to invest in my own genuine personal growth.", + "When I silence the constant clamor of digital notifications, I discover deep calm clarity. I step away from endless scrolling to protect my mental health, focus, and inner peace today.", + "Every time I choose deliberate focus over compulsive browsing, my discipline grows stronger. I am the conscious master of my habits, and I decide where my precious attention flows today.", + "The modern world is designed to fragment attention, but I choose concentration. I will not sacrifice my greatest ambitions on the altar of cheap, fleeting digital distractions in this moment.", + "I enter this app with a clear, singular objective. Once finished, I will promptly close it, returning to the rich and tangible reality of my actual daily life and responsibilities.", + "Mindful living means recognizing when an impulse does not serve purpose. I observe the urge to scroll, breathe through restlessness, and consciously return to what truly matters most in life.", + "Superficial metrics and endless feeds cannot bring lasting fulfillment. I choose joy in authentic human connections, deep creative work, and the quiet satisfaction of meaningful personal daily accomplishments every day.", + "I guard my focus against manipulative algorithms seeking to monetize time. My life is far too precious to spend passively consuming the endless opinions and highlights of total strangers online.", + "A disciplined mind is a peaceful sanctuary amidst the digital age. I cultivate intentionality in every action, ensuring that my smartphone remains a helpful servant, never my master in life.", + "Real accomplishments require uninterrupted hours of patient effort. I consciously resist the temptation of effortless entertainment so that I can produce work of genuine quality and lasting personal value today.", + "I respect my intellect enough to feed it substance rather than trivia. By choosing presence over pixels, I honor my potential and nurture my own creative spirit with pure intention.", + "The urge to scroll is merely a craving for novelty. By sitting with boredom and embracing stillness, I unlock the deep creativity and calm clarity that truly transform my life.", + "I commit to being fully present for people around me, rather than dividing attention between conversations and glowing screens. True connection happens when I look up and listen with care.", + "Each day gives me finite time and energy to build future goals. I will not squander this gift on endless feeds that leave me feeling scattered, tired, and unfulfilled today.", + "Conscious restraint is the foundation of freedom and self-respect. I choose to say no to addictive digital rabbit holes so that I can say a wholehearted yes to living deeply.", + "I release the compulsive habit of checking my phone every spare minute. In quiet spaces between tasks, I choose to breathe, reflect, and appreciate the richness of the present moment.", + "By taking charge of my digital habits, I regain lost time. I dedicate these precious hours to learning, moving, creating, and living with authentic joyful purpose in the real world.", + "I refuse to let synthetic notifications dictate the tempo of life. I decide when to engage, when to pause, and when to completely disconnect from the online world around me.", + "True wisdom begins with understanding the difference between urgent noise and what is truly important. I filter out the trivial so that I can dedicate myself to what matters most.", + "My mind deserves rest, clarity, and spaciousness rather than an unrelenting barrage. Today, I practice intentional digital minimalism and rediscover profound peace from quiet focused attention in every single task.", + "I choose to live an active, courageous life rather than a passive bystander watching people live. I step away from the feed and step boldly into my own journey today.", + "When I resist infinite feeds, I reclaim my ability to think deeply, read patiently, and reflect meaningfully. Deliberate discipline protects the very essence of my human intellect and daily focus.", + "I am not obligated to witness every trend or opinion on the internet. I choose peace of mind over irrelevant information, protecting my emotional bandwidth for what truly counts today.", + "Mastering my attention is the most important skill of the modern era. Today I choose to sharpen that skill by rejecting mindless consumption and embracing deep, purposeful mental engagement always.", + "Every intentional pause before unlocking my phone reminds me I am in control. I choose focus over distraction, intention over impulse, and real-world living over virtual digital illusions right now.", + "The satisfaction of finishing a meaningful project exceeds the thrill of scrolling through feeds. I choose lasting pride over momentary amusement, directing energy toward purposeful creative endeavors with steadfast discipline.", + "I walk away from digital noise with a clear conscience and calm heart. My time is reclaimed, my focus is restored, and my life is lived with intentional presence today.", + ) + + val mantras50: List = listOf( + "I recognize that infinite scrolling feeds are carefully engineered to capture my attention for corporate profit, but my life belongs to me alone. I choose voluntary discipline over impulsive consumption, stepping away from this screen to pursue meaningful real-world accomplishments, cultivate deep personal relationships, and protect my sacred mental peace.", + "True mastery over technology means using it as a deliberate instrument for my education and growth, rather than allowing restless impulses to dictate how I spend my time. Today, I establish healthy digital boundaries, breathe with conscious intention, and return to physical reality where my genuine purpose and creativity reside.", + "The greatest achievements in history required unbroken hours of deep concentration and effort, qualities that mindless social media browsing relentlessly erodes. I refuse to trade my highest aspirations for superficial digital novelty, choosing instead to guard my focus fiercely and direct my undivided energy toward creating work of lasting value.", + "When I silence the endless barrage of notifications, algorithmic recommendations, and curated digital highlights, I rediscover the quiet power of my own inner voice. I choose to live deeply in the present moment, experiencing life firsthand rather than passively observing a distorted virtual world through a small glowing glass display.", + "Every minute spent lost in algorithmic feeds is a minute stolen from my health, my intellect, my passions, and the people I love. I commit to treating my attention with respect it deserves, replacing compulsive browsing with mindful presence, purposeful action, and the genuine satisfaction that comes from real achievements.", + "Distraction is the silent thief of human potential, constantly tempting me to settle for fleeting dopamine instead of enduring fulfillment. By practicing conscious restraint and saying a resolute no to endless scrolling, I reclaim my time and build the unshakable mental discipline necessary to achieve all my most ambitious goals.", + "I understand that the urge to mindlessly check social media is merely a reaction to boredom or discomfort, not an authentic need. I choose to meet this moment with calm awareness, breathing deeply through the impulse, and choosing purposeful constructive action over passive digital consumption that leaves my spirit exhausted.", + "Living an intentional life means deciding beforehand what deserves my attention and refusing to let commercial algorithms make that choice for me. I enter this digital space with clear purpose, complete what is strictly necessary, and depart promptly so that I can invest my finite life energy into what matters.", + "A calm, focused, and disciplined mind is the most valuable asset I can cultivate in an increasingly distracted world full of noisy demands. I deliberately protect my mental sanctuary by filtering out trivial digital clutter, honoring my intellect with deep thoughts, and grounding my daily awareness firmly in physical reality.", + "The modern attention economy seeks to keep me perpetually dissatisfied and reactive, but I possess the power to walk away at any moment. Today, I embrace the quiet beauty of intentional living, choosing deep focus over surface-level entertainment, and finding genuine contentment in the simple reality of my everyday life.", + "Real confidence comes from self-control and the knowledge that my actions align with my deepest personal values, not from superficial online engagement metrics. I release the need for digital validation, turning my gaze inward to cultivate genuine wisdom, resilient character, and the quiet strength that comes from disciplined daily habits.", + "I refuse to let the continuous stream of internet chatter fragment my concentration and rob my creative voice. By setting strict boundaries with social media, I grant my mind spaciousness to think deeply, solve difficult problems, and produce work that makes a positive difference in the real world around me.", + "Every conscious choice to put down my phone is a victory for my independence, my focus, and my personal freedom. I am not a passive spectator in my life; I am the author of my own days, and I choose to write them with presence, passion, and quiet dedicated effort.", + "In a culture that glorifies constant connectivity and instant responses, I choose the quiet rebellion of stillness, patience, and deliberate unplugged living. My best ideas emerge when I allow my mind to wander freely without the intrusive stimulation of curated feeds, endless videos, and repetitive digital conversations that lead nowhere.", + "I understand that life is finite and that the hours I spend staring at algorithmic screens will never be returned to me. Therefore, I choose to invest my precious time wisely, prioritizing physical health, intellectual curiosity, genuine community, and meaningful creative pursuits that will bring me lasting pride and happiness.", + "Mindfulness is the practice of returning home to this very moment, finding wonder and peace in what is already here before me. I step out of virtual comparisons and digital envy, choosing instead to appreciate my unique journey and celebrate the everyday gifts of physical existence with a joyful heart.", + "When I conquer the compulsion to constantly check feeds, I unlock an abundance of time and energy that transforms my daily productivity. I commit to showing up fully for my responsibilities, treating my work as a craft, and giving my absolute best effort without being derailed by petty digital distractions.", + "The convenience of modern technology must never become an excuse for a fragmented mind and an undisciplined life. I hold the power to dictate the role of digital devices in my routine, ensuring they serve my goals while strictly preserving my capacity for deep, quiet, uninterrupted reflection and serious study.", + "I choose to measure the quality of my day by the depth of my focus, the kindness of my interactions, and the progress of my goals. Sensational headlines and trivial updates have no power over my peace of mind when I deliberately anchor my attention in purposeful and disciplined habits.", + "Inner strength is forged in the small, unseen moments when I decide to resist instant gratification in favor of long-term integrity. Choosing to close an addictive app and return to my real-world duties is an act of self-respect that elevates my character and builds lasting confidence in my own willpower.", + "I recognize that behind every infinite feed lies an artificial system optimized to keep me watching, but my attention is not for sale. I choose to break free from passive consumerism, reclaiming my autonomy and investing my mental energy into learning, building, and contributing meaningfully to the world around me.", + "A life well-lived is characterized by depth, purpose, and deliberate focus, not by the superficial breadth of content consumed on a screen. Today, I give my undivided attention to the task at hand, knowing that true excellence is born from the patient refusal to be distracted by trivial digital novelties.", + "I let go of the fear of missing out, realizing that the only thing I truly risk missing is my own precious life passing by unlived. By stepping back from social media feeds, I create room for genuine presence, peaceful solitude, and deep restorative rest that revitalizes my mind completely.", + "My intellect is a sacred garden that deserves to be tended with great care, not overwhelmed by endless streams of algorithmic digital weeds. I choose to nourish my curiosity with books, nature, meaningful conversations, and creative challenges that expand my perspective and strengthen my capacity for independent and critical thought.", + "When I embrace the discipline of mindful browsing, I demonstrate respect for my time, my intelligence, and my future well-being. I refuse to let digital noise drain my mental vitality, choosing instead to direct my focused efforts toward building a life of purpose, honor, and true authentic real-world fulfillment today.", + "The urge to scroll will pass if I simply pause, breathe, and observe it without judgment or compliance. In that brief space between stimulus and response lies my ultimate freedom to choose wisdom over impulse, peace over agitation, and intentional living over mindless digital habits that erode my personal potential.", + "I remember that the curated online world presents an illusion of perfection that bears little resemblance to the rich complexity of reality. I choose to embrace my authentic life with all its challenges, finding deep joy in real effort, genuine connections, and the honest pursuit of personal growth and self-mastery.", + "True digital wellness is not about deprivation, but about intentionality and alignment with what truly matters to my core values. I choose to engage with technology consciously, leaving behind addictive patterns and rediscovering the profound liberty of being fully awake, attentive, and engaged with the physical world around me today.", + "I dedicate myself to doing fewer things with greater depth and excellence, refusing to let shallow online browsing dilute my creative power. By eliminating compulsive screen time, I make space for deep relationships, craft, physical vitality, and the quiet satisfaction of knowing that my attention was spent with deliberate purpose.", + "Today I reclaim my attention, my time, and my peace of mind from the grip of mindless digital habits. I step forward into the world with clear eyes, a steady heart, and an unwavering commitment to living fully, deeply, and intentionally in the reality that truly matters most to me.", + ) + + val mantrasByWordCount: Map> = mapOf( + 10 to mantras10, + 15 to mantras15, + 20 to mantras20, + 30 to mantras30, + 50 to mantras50 + ) + + /** + * Retrieves the next random mantra for the specified word count, cycling through all + * available sentences without duplicates until the full pool is exhausted. + */ + @Synchronized + fun getNextMantra(wordCount: Int): String { + val list = mantrasByWordCount[wordCount] ?: mantras30 + var pool = unexhaustedPools[wordCount] + if (pool.isNullOrEmpty()) { + pool = list.indices.toMutableList() + pool.shuffle() + unexhaustedPools[wordCount] = pool + } + val chosenIndex = pool.removeAt(0) + return list[chosenIndex] + } + + /** + * Resets the exhaustion tracking pool, forcing a reshuffle on the next request. + */ + @Synchronized + fun resetPool(wordCount: Int? = null) { + if (wordCount != null) { + unexhaustedPools.remove(wordCount) + } else { + unexhaustedPools.clear() + } + } +} diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/EffortFrictionGate.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/EffortFrictionGate.kt similarity index 71% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/EffortFrictionGate.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/EffortFrictionGate.kt index 7d7c646..8cbfdeb 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/EffortFrictionGate.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/EffortFrictionGate.kt @@ -24,7 +24,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccessTime import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.FitnessCenter -import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon @@ -58,39 +57,29 @@ import com.ujwal.focusgram.core.ui.theme.TextMuted import com.ujwal.focusgram.core.ui.theme.TextSecondary /** - * EffortFrictionGate - * Imposes intentional friction via a 30-word mindfulness discipline mantra - * or an optional rewarded ad bypass (+2 minutes) before allowing Instagram access. + * Friction gate requiring the user to type an intentional discipline mantra to unlock an intentional browsing window. */ @Composable fun EffortFrictionGate( sessionType: String = "Instagram", // "Instagram" or "Reels" - remainingCreditMinutes: Int = 0, + wordCount: Int = 30, onProceed: () -> Unit, - onWatchAd: () -> Unit, onCancel: () -> Unit ) { val focusManager = LocalFocusManager.current val scrollState = rememberScrollState() - // Curated 30-word discipline mantras - val mantras = remember { - listOf( - "I choose to control my focus and not let algorithmic feeds govern my attention. Real life happens outside this screen, and my precious time deserves deliberate purpose and unwavering disciplined presence.", - "Technology is a tool for my growth, not a trap for my restless dopamine. I enter with conscious intent, observe with clear detachment, and leave before endless scrolling steals my peaceful day.", - "True freedom comes from voluntary discipline. I possess mastery over my impulses, refusing to mindlessly consume superficial digital noise when profound real-world accomplishments demand my undivided attention and creative energy today." - ) + val targetMantra = remember(wordCount) { + DisciplineMantraRepository.getNextMantra(wordCount) } - - val targetMantra = remember { mantras.random() } var userInput by remember { mutableStateOf("") } - // Normalize text for comparison + // Normalize whitespace and case to ensure fair match against prompt val normalizedTarget = remember(targetMantra) { targetMantra.trim().lowercase().replace(Regex("\\s+"), " ") } val normalizedInput = userInput.trim().lowercase().replace(Regex("\\s+"), " ") val isCompleted = normalizedInput == normalizedTarget - val targetWords = remember(normalizedTarget) { normalizedTarget.split(" ") } + val targetWords = remember(normalizedTarget) { normalizedTarget.split(" ").filter { it.isNotBlank() } } val inputWords = normalizedInput.split(" ").filter { it.isNotBlank() } val matchingWords = inputWords.filterIndexed { index, word -> index < targetWords.size && targetWords[index] == word @@ -110,7 +99,6 @@ fun EffortFrictionGate( ) { Spacer(modifier = Modifier.height(16.dp)) - // Friction Header Icon Box( modifier = Modifier .size(72.dp) @@ -139,42 +127,13 @@ fun EffortFrictionGate( ) Text( - text = "Type the 30-word discipline mantra or watch a rewarded ad to earn $sessionType time.", + text = "Type the ${targetWords.size}-word discipline mantra to earn $sessionType time.", style = MaterialTheme.typography.bodyMedium, color = TextSecondary, textAlign = TextAlign.Center, modifier = Modifier.padding(top = 6.dp, bottom = 20.dp) ) - // Current credit banner - if (remainingCreditMinutes > 0) { - Row( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(12.dp)) - .background(FocusGreen.copy(alpha = 0.15f)) - .border(1.dp, FocusGreen.copy(alpha = 0.3f), RoundedCornerShape(12.dp)) - .padding(horizontal = 16.dp, vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center - ) { - Icon( - imageVector = Icons.Default.AccessTime, - contentDescription = null, - tint = FocusGreen, - modifier = Modifier.size(20.dp) - ) - Text( - text = " You have $remainingCreditMinutes min remaining", - color = FocusGreen, - fontWeight = FontWeight.SemiBold, - fontSize = 14.sp - ) - } - Spacer(modifier = Modifier.height(16.dp)) - } - - // Target Mantra Card Column( modifier = Modifier .fillMaxWidth() @@ -215,7 +174,6 @@ fun EffortFrictionGate( Spacer(modifier = Modifier.height(16.dp)) - // User Typing Field OutlinedTextField( value = userInput, onValueChange = { userInput = it }, @@ -239,10 +197,9 @@ fun EffortFrictionGate( Spacer(modifier = Modifier.height(24.dp)) - // Button 1: Complete Challenge Button( onClick = onProceed, - enabled = isCompleted || remainingCreditMinutes > 0, + enabled = isCompleted, modifier = Modifier .fillMaxWidth() .height(52.dp), @@ -268,33 +225,6 @@ fun EffortFrictionGate( Spacer(modifier = Modifier.height(12.dp)) - // Button 2: Rewarded Ad Bypass - Button( - onClick = onWatchAd, - modifier = Modifier - .fillMaxWidth() - .height(52.dp), - shape = RoundedCornerShape(14.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFFFF9800), - contentColor = PureBlack - ) - ) { - Icon( - imageVector = Icons.Default.PlayArrow, - contentDescription = null, - modifier = Modifier.size(20.dp) - ) - Text( - text = " Watch Rewarded Ad (+2 min)", - fontWeight = FontWeight.Bold, - fontSize = 15.sp - ) - } - - Spacer(modifier = Modifier.height(12.dp)) - - // Button 3: Cancel / Back Out OutlinedButton( onClick = onCancel, modifier = Modifier diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/GuardrailsDashboardScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/GuardrailsDashboardScreen.kt similarity index 76% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/GuardrailsDashboardScreen.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/GuardrailsDashboardScreen.kt index 4b7140c..d6c0e80 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/GuardrailsDashboardScreen.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/GuardrailsDashboardScreen.kt @@ -39,19 +39,36 @@ import com.ujwal.focusgram.core.ui.theme.TextPrimary import com.ujwal.focusgram.core.ui.theme.TextSecondary import kotlinx.coroutines.launch +import com.ujwal.focusgram.core.database.ScreenTimeDao +import com.ujwal.focusgram.core.utils.TimeFormatters +import kotlinx.coroutines.flow.flowOf +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import androidx.compose.runtime.remember + /** - * GuardrailsDashboardScreen - * Central control panel for Daily usage limits, Cooldown timers, and Scheduled Blocking. + * Settings dashboard for configuring daily usage caps, cooldown intervals, and strict Reels blocking. */ @Composable fun GuardrailsDashboardScreen( repository: SettingsRepository, onBack: () -> Unit, + screenTimeDao: ScreenTimeDao? = null, modifier: Modifier = Modifier ) { val settings by repository.settingsFlow.collectAsState(initial = FocusSettings()) val scope = rememberCoroutineScope() + val dateFormat = remember { SimpleDateFormat("yyyy-MM-dd", Locale.US) } + val todayStr = remember { dateFormat.format(Date()) } + val todayRecord by (screenTimeDao?.getForDate(todayStr) ?: flowOf(null)).collectAsState(initial = null) + val todaySeconds = todayRecord?.seconds ?: 0 + val baseDailyLimitSec = settings.dailyLimitMinutes * 60 + val isDailyLimitHit = settings.dailyLimitMinutes > 0 && todaySeconds >= baseDailyLimitSec + val isNearDailyLimit = settings.dailyLimitMinutes > 15 && (baseDailyLimitSec - todaySeconds) in 0..300 + val isDailyLimitLocked = settings.dailyLimitMinutes > 0 && (isDailyLimitHit || isNearDailyLimit) + Column( modifier = modifier .fillMaxSize() @@ -90,20 +107,29 @@ fun GuardrailsDashboardScreen( color = TextPrimary ) Text( - text = "${settings.dailyLimitMinutes} minutes", + text = if (isDailyLimitLocked) { + "${TimeFormatters.formatMinutes(settings.dailyLimitMinutes)} (Locked)" + } else { + TimeFormatters.formatMinutes(settings.dailyLimitMinutes) + }, style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), - color = InstagramBlue + color = if (isDailyLimitLocked) TextSecondary else InstagramBlue ) } Spacer(modifier = Modifier.height(8.dp)) Slider( value = settings.dailyLimitMinutes.toFloat(), - onValueChange = { scope.launch { repository.setDailyLimitMinutes(it.toInt()) } }, + onValueChange = { + if (!isDailyLimitLocked) { + scope.launch { repository.setDailyLimitMinutes(it.toInt()) } + } + }, + enabled = !isDailyLimitLocked, valueRange = 15f..180f, steps = 10, colors = SliderDefaults.colors( - thumbColor = InstagramBlue, - activeTrackColor = InstagramBlue, + thumbColor = if (isDailyLimitLocked) TextSecondary else InstagramBlue, + activeTrackColor = if (isDailyLimitLocked) TextSecondary else InstagramBlue, inactiveTrackColor = DarkCardElevated ) ) @@ -132,7 +158,7 @@ fun GuardrailsDashboardScreen( color = TextPrimary ) Text( - text = "${settings.cooldownMinutes} minutes", + text = TimeFormatters.formatMinutes(settings.cooldownMinutes), style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), color = InstagramBlue ) diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/TimerFallbackScreen.kt b/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/TimerFallbackScreen.kt similarity index 95% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/TimerFallbackScreen.kt rename to app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/TimerFallbackScreen.kt index fb721a4..7bddcb2 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/TimerFallbackScreen.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/feature/wellness/TimerFallbackScreen.kt @@ -45,9 +45,7 @@ import com.ujwal.focusgram.core.ui.theme.TextSecondary import kotlinx.coroutines.delay /** - * TimerFallbackScreen - * Graceful 15-second timer fallback that guarantees reward granting (+2 minutes) - * even when rewarded ads cannot be served. + * 15-second timer granting +2 minutes of session time when ad networks fail or are offline. */ @Composable fun TimerFallbackScreen( @@ -108,7 +106,7 @@ fun TimerFallbackScreen( Spacer(modifier = Modifier.height(28.dp)) Text( - text = "Earning $sessionType Time", + text = "Unlocking Reward", style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.Bold), color = Color.White ) @@ -116,7 +114,7 @@ fun TimerFallbackScreen( Spacer(modifier = Modifier.height(8.dp)) Text( - text = "Hold on for a few moments while your focus credit is unlocked.", + text = "Hold on for a few moments while your reward is unlocked.", style = MaterialTheme.typography.bodyMedium, color = TextSecondary, textAlign = TextAlign.Center diff --git a/Ported/app/src/main/kotlin/com/ujwal/focusgram/ui/FocusGramApp.kt b/app/src/main/kotlin/com/ujwal/focusgram/ui/FocusGramApp.kt similarity index 73% rename from Ported/app/src/main/kotlin/com/ujwal/focusgram/ui/FocusGramApp.kt rename to app/src/main/kotlin/com/ujwal/focusgram/ui/FocusGramApp.kt index 79e7c11..08ac533 100644 --- a/Ported/app/src/main/kotlin/com/ujwal/focusgram/ui/FocusGramApp.kt +++ b/app/src/main/kotlin/com/ujwal/focusgram/ui/FocusGramApp.kt @@ -58,6 +58,9 @@ import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.outlined.Lock +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text @@ -93,6 +96,7 @@ import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.LocalLifecycleOwner import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import com.ujwal.focusgram.core.database.FocusGramDatabase @@ -117,7 +121,9 @@ import com.ujwal.focusgram.feature.history.ReelsHistoryScreen import com.ujwal.focusgram.feature.navigation.NativeBottomNavBar import com.ujwal.focusgram.feature.navigation.ReelsTopBar import com.ujwal.focusgram.feature.session.AppSessionPicker +import com.ujwal.focusgram.feature.session.DailyLimitGateScreen import com.ujwal.focusgram.feature.session.SessionExhaustedScreen +import com.ujwal.focusgram.core.utils.TimeFormatters import com.ujwal.focusgram.feature.snapshots.SnapshotManagerScreen import com.ujwal.focusgram.feature.webview.GeckoViewContainer import com.ujwal.focusgram.feature.screentime.ScreenTimeScreen @@ -127,6 +133,13 @@ import com.ujwal.focusgram.feature.wellness.BreathGateScreen import com.ujwal.focusgram.feature.wellness.CooldownGateScreen import com.ujwal.focusgram.feature.wellness.EffortFrictionGate import com.ujwal.focusgram.feature.wellness.GuardrailsDashboardScreen +import com.ujwal.focusgram.feature.onboarding.OnboardingScreen +import com.ujwal.focusgram.feature.update.UpdateBanner +import com.ujwal.focusgram.feature.update.UpdateCheckResult +import com.ujwal.focusgram.feature.update.UpdateCheckerService +import com.ujwal.focusgram.feature.update.UpdateInfo +import com.ujwal.focusgram.feature.notice.RemoteNotice +import com.ujwal.focusgram.feature.notice.RemoteNoticeService import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -143,7 +156,8 @@ enum class AppDestination { APP_LOCK_SETTINGS, CHAT_LOCK_SETUP, DEBUG_MENU, - ADSTERRA_REWARDED + ADSTERRA_REWARDED, + ONBOARDING } data class PendingChatUnlock( @@ -153,10 +167,8 @@ data class PendingChatUnlock( ) /** - * FocusGramApp - * Primary Jetpack Compose application composable. - * Orchestrates Mozilla GeckoView, built-in content blocker, mindful intention timers, - * App Lock biometric security, and native bottom navigation. + * Root composable binding Mozilla GeckoView, client-side content blocker rules, + * biometric gates, usage tracking, and the floating navigation shell. */ @Composable fun FocusGramApp() { @@ -172,8 +184,9 @@ fun FocusGramApp() { val lifecycleOwner = LocalLifecycleOwner.current val scope = rememberCoroutineScope() - // Core Managers & Repositories val settingsRepository = remember { SettingsRepository(context) } + val initialSettings = remember { settingsRepository.getSettingsSync() } + val settings by settingsRepository.settingsFlow.collectAsState(initial = initialSettings) val encryptedStorage = remember { EncryptedStorage(context) } val database = remember { FocusGramDatabase.getInstance(context) } val reelsRepository = remember { ReelsHistoryRepository(database.reelsHistoryDao()) } @@ -181,17 +194,22 @@ fun FocusGramApp() { val geckoManager = remember { GeckoRuntimeManager.getInstance(context) } val runtime = geckoManager.runtime val extensionManager = geckoManager.extensionManager - val reelHistory = remember { geckoManager.reelHistory } + val reelHistory = remember { + geckoManager.reelHistory.apply { + if (initialSettings.activeSessionIsActive && initialSettings.activeSessionRemainingSeconds > 0) { + restoreSession(initialSettings.activeSessionReelCodes, initialSettings.activeSessionReelsWatched) + } + } + } val reelsWatchedSessionCount by reelHistory.sessionCount.collectAsState() val userScriptManager = remember { UserScriptManager(context) } val sessionManager = remember { GeckoSessionManager(runtime).apply { this.userScriptManager = userScriptManager + this.extensionManager = extensionManager } } - val initialSettings = remember { settingsRepository.getSettingsSync() } - val settings by settingsRepository.settingsFlow.collectAsState(initial = initialSettings) val currentUrl by sessionManager.currentUrl.collectAsState() var isAppInForeground by remember { mutableStateOf(true) } var destination by rememberSaveable { mutableStateOf(AppDestination.MAIN_WEBVIEW) } @@ -200,7 +218,24 @@ fun FocusGramApp() { var isAppUnlocked by rememberSaveable { mutableStateOf(false) } val shouldShowLock = settings.appLockEnabled && hasPin && !isAppUnlocked - // Direct Messages & Chat Lock Evaluation + val updateCheckerService = remember { UpdateCheckerService(context, settingsRepository) } + var activeUpdateInfo by remember { mutableStateOf(null) } + val remoteNoticeService = remember { RemoteNoticeService(context) } + var activeRemoteNotice by remember { mutableStateOf(null) } + + LaunchedEffect(Unit) { + when (val res = updateCheckerService.checkForUpdates(ignoreDismissed = false)) { + is UpdateCheckResult.Available -> { + activeUpdateInfo = res.updateInfo + } + else -> {} + } + val notice = remoteNoticeService.fetchNotice() + if (notice != null && remoteNoticeService.shouldShow(notice)) { + activeRemoteNotice = notice + } + } + val threadToUsernameMap = remember { androidx.compose.runtime.mutableStateMapOf>() } var unlockedChatThreadIds by remember { mutableStateOf(emptySet()) } var isDirectAllUnlocked by remember { mutableStateOf(false) } @@ -261,6 +296,20 @@ fun FocusGramApp() { activeThreadId = currentThreadId } + LaunchedEffect(isDirectRoute) { + if (!isDirectRoute) { + isDirectAllUnlocked = false + unlockedChatThreadIds = emptySet() + extensionManager.notifyChatLocked("") + sessionManager.userScriptManager?.evaluate(sessionManager.session, """ + window.__fgUnlockedThreads = []; + window.dispatchEvent(new CustomEvent('focusgram:chat-lock-sync', { + detail: { unlockedThreads: [] } + })); + """) + } + } + val isChatLockActive = remember( currentUrl, currentPath, @@ -294,7 +343,9 @@ fun FocusGramApp() { } false } - var hasPassedSessionPicker by rememberSaveable { mutableStateOf(false) } + var hasPassedSessionPicker by rememberSaveable { + mutableStateOf(initialSettings.activeSessionIsActive && initialSettings.activeSessionRemainingSeconds > 0) + } var hasPassedBreathGate by rememberSaveable { mutableStateOf(!initialSettings.showBreathGate || initialSettings.breathGateSeconds <= 0) } var hasPassedEffortFriction by rememberSaveable { mutableStateOf(!initialSettings.effortFrictionEnabled || initialSettings.wordChallengeCount <= 0) } val lastSessionEndTimestamp by settingsRepository.lastSessionEndTimestampFlow.collectAsState(initial = 0L) @@ -303,16 +354,40 @@ fun FocusGramApp() { val dateFormat = remember { SimpleDateFormat("yyyy-MM-dd", Locale.US) } val today = remember { dateFormat.format(Date()) } val todayRecord by database.screenTimeDao().getForDate(today).collectAsState(initial = null) - val isDailyLimitExhausted = settings.dailyLimitMinutes > 0 && ((todayRecord?.seconds ?: 0) >= settings.dailyLimitMinutes * 60) + val todayDailySeconds = todayRecord?.seconds ?: 0 + val dailyExtensionsUsed = if (settings.dailyLimitAdClaimedDate == today) settings.dailyLimitExtensionsCount else 0 + val effectiveDailyLimitMinutes = settings.dailyLimitMinutes + (dailyExtensionsUsed * 5) + var isDebugDailyLimitBypassed by rememberSaveable { mutableStateOf(false) } + val isDailyLimitExhausted = !isDebugDailyLimitBypassed && settings.dailyLimitMinutes > 0 && (todayDailySeconds >= effectiveDailyLimitMinutes * 60) + val dailyExtensionsRemaining = (2 - dailyExtensionsUsed).coerceAtLeast(0) - val remainingCooldownSeconds = remember(lastSessionEndTimestamp, settings.cooldownMinutes) { - if (settings.cooldownMinutes <= 0 || lastSessionEndTimestamp <= 0L) { - 0 + val targetExpiryMillis = remember(lastSessionEndTimestamp, settings.cooldownMinutes) { + if (settings.cooldownMinutes > 0 && lastSessionEndTimestamp > 0L) { + lastSessionEndTimestamp + (settings.cooldownMinutes * 60 * 1000L) } else { - val elapsedMillis = System.currentTimeMillis() - lastSessionEndTimestamp - val cooldownMillis = settings.cooldownMinutes * 60 * 1000L - val remainingMillis = cooldownMillis - elapsedMillis - if (remainingMillis > 0) (remainingMillis / 1000).toInt() else 0 + 0L + } + } + + var remainingCooldownSeconds by remember { + val initialRemaining = if (targetExpiryMillis > 0L) { + val rem = targetExpiryMillis - System.currentTimeMillis() + if (rem > 0) ((rem + 999) / 1000).toInt() else 0 + } else 0 + mutableIntStateOf(initialRemaining) + } + + LaunchedEffect(targetExpiryMillis, isAppInForeground) { + if (targetExpiryMillis <= 0L) { + remainingCooldownSeconds = 0 + return@LaunchedEffect + } + while (isActive) { + val remainingMillis = targetExpiryMillis - System.currentTimeMillis() + val remainingSec = if (remainingMillis > 0) ((remainingMillis + 999) / 1000).toInt() else 0 + remainingCooldownSeconds = remainingSec + if (remainingSec <= 0) break + delay(1000L) } } @@ -323,30 +398,34 @@ fun FocusGramApp() { var isUserAuthenticated by remember { mutableStateOf(false) } var isAuthPage by remember { mutableStateOf(false) } - // Session intention countdown timer state - var sessionDurationMinutes by remember { mutableIntStateOf(15) } - var sessionRemainingSeconds by remember { mutableIntStateOf(15 * 60) } - var totalElapsedSeconds by remember { mutableIntStateOf(0) } - var adExtensionsUsed by remember { mutableIntStateOf(0) } + var sessionDurationMinutes by rememberSaveable { + mutableIntStateOf(if (initialSettings.activeSessionIsActive && initialSettings.activeSessionDurationMinutes > 0) initialSettings.activeSessionDurationMinutes else 15) + } + var sessionRemainingSeconds by rememberSaveable { + mutableIntStateOf(if (initialSettings.activeSessionIsActive && initialSettings.activeSessionRemainingSeconds > 0) initialSettings.activeSessionRemainingSeconds else 15 * 60) + } + var totalElapsedSeconds by rememberSaveable { + mutableIntStateOf(if (initialSettings.activeSessionIsActive) initialSettings.activeSessionElapsedSeconds else 0) + } + var sessionExtensionsUsed by remember { mutableIntStateOf(0) } + val sessionExtensionsRemaining = (3 - sessionExtensionsUsed).coerceAtLeast(0) var isSessionExhausted by remember { mutableStateOf(false) } var adRewardAction by remember { mutableStateOf<(suspend () -> Unit)?>(null) } var settingsSubpage by rememberSaveable { mutableStateOf(com.ujwal.focusgram.feature.settings.SettingsSubpage.MAIN) } - // Evaluates whether any barrier, lock, breath pause, effort friction, cooldown, or intention picker is active val isGateActive = destination != AppDestination.ADSTERRA_REWARDED && ( shouldShowLock || isDailyLimitExhausted || isSessionExhausted || - (remainingCooldownSeconds > 0 && !hasPassedCooldownGate) || + (!hasPassedSessionPicker && remainingCooldownSeconds > 0 && !hasPassedCooldownGate) || (settings.showBreathGate && settings.breathGateSeconds > 0 && !hasPassedBreathGate) || (settings.effortFrictionEnabled && settings.wordChallengeCount > 0 && !hasPassedEffortFriction) || !hasPassedSessionPicker ) - // System File Chooser / Camera / Gallery Launchers for Story, Post, DMs & Profile Uploads val pendingFilePickerCallback = remember { AtomicReference<((List) -> Unit)?>(null) } val pendingCameraCaptureUri = remember { AtomicReference(null) } - // 1. Android Modern Photo & Video Pickers (Official AndroidX contracts, zero permissions needed) + // AndroidX Photo Picker delegates across Android 11+ without storage permissions. val singlePhotoPickerLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.PickVisualMedia() ) { uri -> @@ -363,7 +442,7 @@ fun FocusGramApp() { callback?.invoke(uris) } - // 2. Direct Camera Capture Launcher (Used when HTML input requests camera capture) + // FileProvider camera capture invoked when input element specifies capture attributes. val takePictureLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.TakePicture() ) { success -> @@ -377,7 +456,7 @@ fun FocusGramApp() { } } - // 3. System Document/Gallery Fallback Launcher + // DocumentsUI / SAF fallback for older Android releases or third-party file managers. val fallbackFilePickerLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.StartActivityForResult() ) { result -> @@ -399,7 +478,6 @@ fun FocusGramApp() { callback?.invoke(uris) } - // Android Runtime Permissions Launcher (Camera, Audio, Storage) val pendingPermissionCallback = remember { AtomicReference<((Boolean) -> Unit)?>(null) } val permissionLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.RequestMultiplePermissions() @@ -480,14 +558,13 @@ fun FocusGramApp() { val currentUrlLower = sessionManager.currentUrl.value.lowercase() val isProfilePictureEdit = currentUrlLower.contains("/accounts/edit") - // In Instagram, DMs (/direct/), Stories, and Feed Posts all support videos (MP4, MOV, WebM). - // Unless explicitly updating an account avatar/profile picture, always allow both Photos and Videos. + // Instagram direct messages, stories, and feed posts accept videos (MP4, MOV, WebM). + // Profile avatar uploads are the only route strictly limited to images. val allowsVideos = hasVideos || (!isProfilePictureEdit && hasImages) val allowsImages = hasImages || isAnyMedia var launched = false - // 1. Direct Camera Capture Mode (Explicitly requested by web page via capture attribute) if (capture != org.mozilla.geckoview.GeckoSession.PromptDelegate.FilePrompt.Capture.NONE) { try { val cameraDir = File(context.cacheDir, "camera").apply { mkdirs() } @@ -506,8 +583,7 @@ fun FocusGramApp() { } } - // 2. Primary Media Picker: Modern Android Photo & Video Picker (PickVisualMedia) - // Works seamlessly on Android 11+ and Samsung Galaxy OneUI without any permissions + // PickVisualMedia delegates without storage permission prompts on Android 11+ and Samsung OneUI. if (!launched) { try { val visualMediaType = when { @@ -530,7 +606,7 @@ fun FocusGramApp() { } } - // 3. Fallback 1: ACTION_GET_CONTENT (with CATEGORY_OPENABLE for Samsung & AOSP DocumentsUI) + // ACTION_GET_CONTENT with CATEGORY_OPENABLE handles AOSP DocumentsUI and custom ROM pickers. if (!launched) { try { val getContentIntent = Intent(Intent.ACTION_GET_CONTENT).apply { @@ -568,7 +644,7 @@ fun FocusGramApp() { } } - // 4. Fallback 2: ACTION_PICK from MediaStore + // Final fallback targeting platform MediaStore URIs when DocumentsUI rejects the intent. if (!launched) { try { val pickIntent = if (allowsVideos && !allowsImages) { @@ -587,14 +663,15 @@ fun FocusGramApp() { } } - // Pre-warm Rewarded Ad container and cache in background (isolated from GeckoView) + // Pre-loads isolated webview container for voluntary ad credits in background. LaunchedEffect(Unit) { AdPreloader.prewarm(context) } - // Foreground Session Limit Ticker & Real-Time Screen / Reels Time Tracker - LaunchedEffect(hasPassedSessionPicker, isSessionExhausted, isAppInForeground) { - if (hasPassedSessionPicker && !isSessionExhausted && isAppInForeground) { + // Foreground ticker decrementing session allocation and logging per-second screen time. + // Pauses immediately when user navigates away from webview (e.g. Settings, Diagnostics). + LaunchedEffect(hasPassedSessionPicker, isSessionExhausted, isDailyLimitExhausted, isAppInForeground, destination, isGateActive) { + if (hasPassedSessionPicker && !isSessionExhausted && !isDailyLimitExhausted && isAppInForeground && destination == AppDestination.MAIN_WEBVIEW && !isGateActive) { val dateFormat = java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.US) var unsavedSeconds = 0 var unsavedReelsSeconds = 0 @@ -613,7 +690,6 @@ fun FocusGramApp() { unsavedReelsSeconds += 1 } - // Persist to Room SQLite every second for real-time responsiveness if (unsavedSeconds >= 1) { val todayStr = dateFormat.format(java.util.Date()) database.screenTimeDao().addSeconds(todayStr, unsavedSeconds, unsavedReelsSeconds) @@ -621,30 +697,21 @@ fun FocusGramApp() { unsavedReelsSeconds = 0 } - // Every 60 seconds: deduct minute credits & enforce daily limits - if (totalElapsedSeconds % 60 == 0) { - if (onReels) { - settingsRepository.deductReelsMinute() - } else { - settingsRepository.deductInstaMinute() - } - - if (settings.dailyLimitMinutes > 0) { - val today = dateFormat.format(java.util.Date()) - val todayRecord = database.screenTimeDao().getForDateSync(today) - val totalTodaySeconds = (todayRecord?.seconds ?: 0) + unsavedSeconds - if (totalTodaySeconds >= settings.dailyLimitMinutes * 60) { - isSessionExhausted = true - settingsRepository.recordSessionEnd() - HapticHelper.performSessionCompletionAlert(context) - break - } - } + if (totalElapsedSeconds % 5 == 0) { + settingsRepository.saveActiveSession( + durationMinutes = sessionDurationMinutes, + remainingSeconds = sessionRemainingSeconds, + elapsedSeconds = totalElapsedSeconds, + reelsWatched = reelsWatchedSessionCount, + reelCodes = reelHistory.getSessionCodes(), + isActive = true + ) } if (sessionRemainingSeconds <= 0) { isSessionExhausted = true - settingsRepository.recordSessionEnd() + reelHistory.resetSession() + settingsRepository.clearActiveSession() HapticHelper.performSessionCompletionAlert(context) if (settings.notifySessionEnd) { postSessionExpiredNotification(context) @@ -653,39 +720,67 @@ fun FocusGramApp() { } } } finally { - // Ensure any pending seconds are atomically written to Room on cancel or pause + // Flush pending seconds on cancellation or app backgrounding. if (unsavedSeconds > 0) { withContext(NonCancellable) { - val today = dateFormat.format(java.util.Date()) - database.screenTimeDao().addSeconds(today, unsavedSeconds, unsavedReelsSeconds) + val todayStr = dateFormat.format(java.util.Date()) + database.screenTimeDao().addSeconds(todayStr, unsavedSeconds, unsavedReelsSeconds) } } + withContext(NonCancellable) { + settingsRepository.saveActiveSession( + durationMinutes = sessionDurationMinutes, + remainingSeconds = sessionRemainingSeconds, + elapsedSeconds = totalElapsedSeconds, + reelsWatched = reelsWatchedSessionCount, + reelCodes = reelHistory.getSessionCodes(), + isActive = sessionRemainingSeconds > 0 + ) + } } } } - // Update persistent notification route state if ScreenTimeService is running - LaunchedEffect(isViewingReels, settings.notifyPersistent, hasPassedSessionPicker, isSessionExhausted) { - if (settings.notifyPersistent && hasPassedSessionPicker && !isSessionExhausted) { + LaunchedEffect(reelsWatchedSessionCount) { + if (hasPassedSessionPicker && !isSessionExhausted && sessionRemainingSeconds > 0) { + settingsRepository.saveActiveSession( + durationMinutes = sessionDurationMinutes, + remainingSeconds = sessionRemainingSeconds, + elapsedSeconds = totalElapsedSeconds, + reelsWatched = reelsWatchedSessionCount, + reelCodes = reelHistory.getSessionCodes(), + isActive = true + ) + } + } + + LaunchedEffect(isViewingReels, settings.notifyPersistent, hasPassedSessionPicker, isSessionExhausted, isDailyLimitExhausted) { + if (settings.notifyPersistent && hasPassedSessionPicker && !isSessionExhausted && !isDailyLimitExhausted) { ScreenTimeService.updateRoute(context, isViewingReels) } } - // Manage persistent countdown notification via ScreenTimeService - DisposableEffect(hasPassedSessionPicker, isSessionExhausted, settings.notifyPersistent) { - if (hasPassedSessionPicker && !isSessionExhausted && settings.notifyPersistent) { - ScreenTimeService.startSession(context, isViewingReels) + LaunchedEffect(hasPassedSessionPicker, isSessionExhausted, isDailyLimitExhausted, settings.notifyPersistent, isAppInForeground, destination, isGateActive) { + if (!settings.notifyPersistent || !hasPassedSessionPicker || isSessionExhausted || isDailyLimitExhausted || !isAppInForeground) { + ScreenTimeService.stopSession(context) + } else if (destination == AppDestination.MAIN_WEBVIEW && !isGateActive) { + ScreenTimeService.startSession(context, isViewingReels, totalElapsedSeconds, sessionDurationMinutes) + } else { + ScreenTimeService.pauseSession(context, totalElapsedSeconds, sessionDurationMinutes) } + } + + DisposableEffect(Unit) { onDispose { ScreenTimeService.stopSession(context) } } - // Media pausing: pause media when leaving webview or when barriers/gates are active, resume when returning + // Halt HTML5 media players when navigating away from webview or when security gates engage. LaunchedEffect(destination, isGateActive) { if (destination != AppDestination.MAIN_WEBVIEW || isGateActive) { sessionManager.pauseMedia() - } else { + } else if (isAppInForeground) { sessionManager.resumeMedia() } } @@ -693,17 +788,30 @@ fun FocusGramApp() { val currentDestination by rememberUpdatedState(destination) val currentHasPassedSessionPicker by rememberUpdatedState(hasPassedSessionPicker) val currentTotalElapsedSeconds by rememberUpdatedState(totalElapsedSeconds) + val currentSessionRemainingSeconds by rememberUpdatedState(sessionRemainingSeconds) + val currentSessionDurationMinutes by rememberUpdatedState(sessionDurationMinutes) + val currentIsSessionExhausted by rememberUpdatedState(isSessionExhausted) + val currentIsGateActive by rememberUpdatedState(isGateActive) - // Lifecycle observer for foreground/background state, media pausing, and auto-lock DisposableEffect(lifecycleOwner, settings.appLockEnabled, settings.autoLockOnBackground) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_STOP) { isAppInForeground = false + ScreenTimeService.stopSession(context) sessionManager.pauseMedia() - if (currentHasPassedSessionPicker && currentTotalElapsedSeconds >= 15) { - scope.launch { settingsRepository.recordSessionEnd() } + if (currentHasPassedSessionPicker && !currentIsSessionExhausted) { + scope.launch(NonCancellable) { + settingsRepository.saveActiveSession( + durationMinutes = currentSessionDurationMinutes, + remainingSeconds = currentSessionRemainingSeconds, + elapsedSeconds = currentTotalElapsedSeconds, + reelsWatched = reelsWatchedSessionCount, + reelCodes = reelHistory.getSessionCodes(), + isActive = currentSessionRemainingSeconds > 0 + ) + } } - // Do not lock the app if the user is currently on the rewarded ad screen (e.g. clicking out to view an ad in browser) + // Avoid relocking if user navigated out to an external browser tab to view an ad. if (currentDestination != AppDestination.ADSTERRA_REWARDED) { if (settings.appLockEnabled && (settings.autoLockOnBackground || !currentHasPassedSessionPicker)) { isAppUnlocked = false @@ -715,8 +823,10 @@ fun FocusGramApp() { } } else if (event == Lifecycle.Event.ON_START) { isAppInForeground = true - if (currentDestination == AppDestination.MAIN_WEBVIEW && !isGateActive) { + if (currentDestination == AppDestination.MAIN_WEBVIEW && !currentIsGateActive) { sessionManager.resumeMedia() + } else { + sessionManager.pauseMedia() } } } @@ -726,7 +836,6 @@ fun FocusGramApp() { } } - // Connect WebExtension event callbacks LaunchedEffect(extensionManager) { extensionManager.onOpenSettings = { destination = AppDestination.SETTINGS @@ -739,11 +848,10 @@ fun FocusGramApp() { } extensionManager.onUrlChanged = { newUrl -> val mode = IslandMode.fromUrl(newUrl) - val lower = newUrl.lowercase() - val isAuthRoute = (lower.contains("/accounts") && !lower.contains("/accounts/edit")) || lower.contains("/login") || lower.contains("/signup") + val isAuthRoute = isAuthUrl(newUrl) isAuthPage = isAuthRoute - if (!isAuthRoute) { - isUserAuthenticated = true + if (isAuthRoute) { + isUserAuthenticated = false } sessionManager.updateCurrentUrl(newUrl) sessionManager.updateIslandMode(mode) @@ -755,7 +863,7 @@ fun FocusGramApp() { extensionManager.onModalOrChatActive = { active -> isModalActive = active val currentMode = IslandMode.fromUrl(sessionManager.currentUrl.value) - // On Reels (FLOATING), maintain FLOATING layout so the comments sheet sits above the bottom nav bar + // On Reels, keep FLOATING layout so comments sheets position above the bottom bar. val mode = if (active && currentMode != IslandMode.FLOATING) IslandMode.HIDDEN else currentMode sessionManager.updateIslandMode(mode) extensionManager.setIslandMode(mode) @@ -773,8 +881,15 @@ fun FocusGramApp() { } val cleanCode = code.trim().removePrefix("/").removeSuffix("/") val ignoredCodes = setOf("reels", "reel", "videos", "audio", "clips", "home", "explore", "direct", "stories") - if (cleanCode.length >= 5 && cleanCode.lowercase() !in ignoredCodes) { - reelHistory.onWatched(cleanCode, author, title) + val targetCode = if (cleanCode.length >= 5 && cleanCode.lowercase() !in ignoredCodes) { + cleanCode + } else if (author.isNotBlank()) { + author.trim().removePrefix("@") + } else { + "" + } + if (targetCode.length >= 3) { + reelHistory.onWatched(targetCode, author, title) } scope.launch { reelsRepository.addReel(url, title, thumb) @@ -790,10 +905,15 @@ fun FocusGramApp() { } val cleanCode = code.trim().removePrefix("/").removeSuffix("/") val ignoredCodes = setOf("reels", "reel", "videos", "audio", "clips", "home", "explore", "direct", "stories") - if (cleanCode.length >= 5 && cleanCode.lowercase() !in ignoredCodes) { - if (title.isNotBlank() || author.isNotBlank()) { - reelHistory.onMeta(cleanCode, author, title) - } + val targetCode = if (cleanCode.length >= 5 && cleanCode.lowercase() !in ignoredCodes) { + cleanCode + } else if (author.isNotBlank()) { + author.trim().removePrefix("@") + } else { + "" + } + if (targetCode.length >= 3 && (title.isNotBlank() || author.isNotBlank())) { + reelHistory.onMeta(targetCode, author, title) } } extensionManager.onDownloadStarted = { filename -> @@ -807,7 +927,7 @@ fun FocusGramApp() { } extensionManager.onAuthStateChanged = { authenticated, authPage -> isUserAuthenticated = authenticated - isAuthPage = authPage + isAuthPage = authPage || !authenticated || isAuthUrl(sessionManager.currentUrl.value) sessionManager.setAuthenticated(authenticated) } } @@ -819,7 +939,6 @@ fun FocusGramApp() { } } - // Mount Content Blocker extension and configure delegates LaunchedEffect(Unit) { extensionManager.initializeUBlockOrigin() @@ -828,11 +947,10 @@ fun FocusGramApp() { sessionManager = sessionManager, onRouteChanged = { newUrl -> val mode = IslandMode.fromUrl(newUrl) - val lower = newUrl.lowercase() - val isAuthRoute = (lower.contains("/accounts") && !lower.contains("/accounts/edit")) || lower.contains("/login") || lower.contains("/signup") + val isAuthRoute = isAuthUrl(newUrl) isAuthPage = isAuthRoute - if (!isAuthRoute) { - isUserAuthenticated = true + if (isAuthRoute) { + isUserAuthenticated = false } sessionManager.updateCurrentUrl(newUrl) sessionManager.updateIslandMode(mode) @@ -866,7 +984,6 @@ fun FocusGramApp() { sessionManager.openAndLoad(initialUrl) } - // Reactively update web environment and extension when settings change var previousReloadSettings by remember { mutableStateOf(null) } LaunchedEffect(settings) { sessionManager.updateSettings(settings) @@ -876,19 +993,7 @@ fun FocusGramApp() { val prev = previousReloadSettings if (prev != null) { - val needsReload = prev.grayscaleEnabled != settings.grayscaleEnabled || - prev.blurExplore != settings.blurExplore || - prev.blurReels != settings.blurReels || - prev.blurFeed != settings.blurFeed || - prev.unblurMode != settings.unblurMode || - prev.hideLikeCounts != settings.hideLikeCounts || - prev.hideFollowerCounts != settings.hideFollowerCounts || - prev.noAutoplay != settings.noAutoplay || - prev.blockDmReelsSwiping != settings.blockDmReelsSwiping || - prev.videoDownloadEnabled != settings.videoDownloadEnabled || - prev.minimalModeEnabled != settings.minimalModeEnabled || - prev.hideSuggestedPosts != settings.hideSuggestedPosts || - prev.noAds != settings.noAds + val needsReload = prev.noAds != settings.noAds if (needsReload) { sessionManager.session.reload() } @@ -896,7 +1001,6 @@ fun FocusGramApp() { previousReloadSettings = settings } - // Dynamic screen orientation lock: enforce portrait-only when enabled LaunchedEffect(settings.portraitOnly, activity) { activity?.requestedOrientation = if (settings.portraitOnly) { ActivityInfo.SCREEN_ORIENTATION_PORTRAIT @@ -905,19 +1009,25 @@ fun FocusGramApp() { } } - // When any barrier, gate, lock, or intention picker is active, pressing back exits the app cleanly + // Exit immediately on back press when locked behind an active mindful barrier or PIN gate. BackHandler(enabled = isGateActive && destination != AppDestination.ADSTERRA_REWARDED) { activity?.finish() } - // Predictive back gesture: when at the root destination and cannot go back in WebView, - // disabling BackHandler allows Android's predictive back-to-home animation to trigger cleanly. + val currentUrlStr = sessionManager.currentUrl.value.lowercase() + val cleanCurrentPath = remember(currentUrlStr) { + IslandMode.extractPath(currentUrlStr).substringBefore('?').substringBefore('#').trimEnd('/').ifEmpty { "/" } + } + val isDmThread = cleanCurrentPath.startsWith("/direct/t/") || cleanCurrentPath.startsWith("/direct/threads/") || (cleanCurrentPath.startsWith("/direct/") && cleanCurrentPath != "/direct/inbox" && cleanCurrentPath != "/direct") + val isDmInbox = cleanCurrentPath == "/direct" || cleanCurrentPath == "/direct/inbox" + val isRootHome = cleanCurrentPath == "/" + + // Disabling BackHandler at root URL lets the system gesture invoke predictive back-to-home. val canHandleBack = !isGateActive && ( destination != AppDestination.MAIN_WEBVIEW || - sessionManager.canGoBack.value || - sessionManager.currentUrl.value.lowercase().let { - it.contains("/direct") || (!it.endsWith("instagram.com/") && !it.endsWith("instagram.com")) - } + isDmThread || + isDmInbox || + (!isRootHome && (sessionManager.canGoBack.value || (!currentUrlStr.endsWith("instagram.com/") && !currentUrlStr.endsWith("instagram.com")))) ) BackHandler(enabled = canHandleBack) { @@ -931,7 +1041,8 @@ fun FocusGramApp() { AppDestination.REELS_HISTORY, AppDestination.SNAPSHOTS, AppDestination.GUARDRAILS, - AppDestination.DEBUG_MENU + AppDestination.DEBUG_MENU, + AppDestination.ONBOARDING ) -> { destination = AppDestination.SETTINGS } @@ -945,17 +1056,27 @@ fun FocusGramApp() { destination == AppDestination.SETTINGS -> { destination = AppDestination.MAIN_WEBVIEW } - sessionManager.canGoBack.value -> { + destination == AppDestination.MAIN_WEBVIEW && isDmThread -> { isWebInputFocused = false isModalActive = false - sessionManager.goBack() + // Override back action in user's DM to navigate strictly to /direct/inbox without history traversal + sessionManager.navigateTo("https://www.instagram.com/direct/inbox/") } - else -> { - val currentUrlStr = sessionManager.currentUrl.value.lowercase() - if (currentUrlStr.contains("/direct") || (!currentUrlStr.endsWith("instagram.com/") && !currentUrlStr.endsWith("instagram.com"))) { - isWebInputFocused = false - isModalActive = false + destination == AppDestination.MAIN_WEBVIEW && isDmInbox -> { + isWebInputFocused = false + isModalActive = false + // Override back action in DM inbox to navigate strictly to home screen without history traversal + sessionManager.navigateTo("https://www.instagram.com/") + } + destination == AppDestination.MAIN_WEBVIEW && !isRootHome -> { + isWebInputFocused = false + isModalActive = false + if (cleanCurrentPath == "/explore" || cleanCurrentPath == "/reels" || (!cleanCurrentPath.contains('/') && cleanCurrentPath.length > 1)) { + sessionManager.navigateTo("https://www.instagram.com/") + } else if (sessionManager.canGoBack.value) { sessionManager.goBack() + } else { + sessionManager.navigateTo("https://www.instagram.com/") } } } @@ -969,6 +1090,23 @@ fun FocusGramApp() { else -> !isSystemDark } + if (settings.isFirstRun) { + OnboardingScreen( + settings = settings, + settingsRepository = settingsRepository, + onComplete = { + scope.launch { + settingsRepository.setFirstRunCompleted() + hasPassedBreathGate = !settings.showBreathGate || settings.breathGateSeconds <= 0 + hasPassedEffortFriction = true + hasPassedSessionPicker = false + hasPassedCooldownGate = true + } + } + ) + return + } + Surface( modifier = Modifier.fillMaxSize(), color = if (isAppLight) Color.White else PureBlack @@ -985,12 +1123,13 @@ fun FocusGramApp() { if (!isKeyboardOrInputActive) navBarTotalHeight else 16.dp } else 0.dp - // Persistent GeckoView layer: keeps GPU compositor surface alive through all transitions. - // On FLOATING (Reels): viewport ends cleanly above the bottom nav bar with ambient light below. - // On NORMAL routes: statusBarsPadding at top. + // Keeps the GeckoView GPU compositor surface attached throughout transitions. + // Under FLOATING mode (Reels), shifts padding so controls avoid bottom bar occlusion. + val isWebviewVisible = destination == AppDestination.MAIN_WEBVIEW && !isGateActive GeckoViewContainer( sessionManager = sessionManager, grayscale = settings.grayscaleEnabled, + isVisible = isWebviewVisible, modifier = Modifier .fillMaxSize() .then( @@ -1002,7 +1141,6 @@ fun FocusGramApp() { ) ) - // Instagram Reels Native Top Header with watched counter and media download ReelsTopBar( isVisible = !isGateActive && destination == AppDestination.MAIN_WEBVIEW && @@ -1025,7 +1163,24 @@ fun FocusGramApp() { modifier = Modifier.align(Alignment.TopCenter) ) - // Native DM Chat Header Lock Overlay Button (positioned adjacent to Instagram's Info button) + if (activeUpdateInfo != null && !isGateActive && destination == AppDestination.MAIN_WEBVIEW && !isViewingReels) { + UpdateBanner( + updateInfo = activeUpdateInfo!!, + onDismiss = { + val ver = activeUpdateInfo!!.latestVersion + activeUpdateInfo = null + scope.launch { + updateCheckerService.dismissVersion(ver) + } + }, + modifier = Modifier + .align(Alignment.TopCenter) + .statusBarsPadding() + .padding(horizontal = 16.dp, vertical = 8.dp) + ) + } + + // Overlay lock trigger positioned adjacent to Instagram's native (i) button in DM headers. if (!isGateActive && destination == AppDestination.MAIN_WEBVIEW && currentThreadId != null && settings.chatLockMode == "individual") { val isChatExplicitlyLocked = remember(currentThreadId, settings.lockedChatIds, threadToUsernameMap.size, settings.lockedChatUsernames.size) { val associatedUsers = threadToUsernameMap[currentThreadId] ?: emptySet() @@ -1063,7 +1218,7 @@ fun FocusGramApp() { if (username.isNullOrBlank()) { try { sessionManager.userScriptManager?.evaluate( - sessionManager.session, + sessionManager.session, "if (typeof window.__fgGetThreadParticipant === 'function') { window.__fgGetThreadParticipant(); }" ) } catch (_: Exception) {} @@ -1072,11 +1227,9 @@ fun FocusGramApp() { } val isNowLocked = settingsRepository.toggleLockedChat(currentThreadId, username) if (isNowLocked) { - // Conversation is now locked: engage lock immediately unlockedChatThreadIds = unlockedChatThreadIds - currentThreadId extensionManager.notifyChatLocked(currentThreadId) } else { - // Conversation is now unlocked: keep unlocked in current session unlockedChatThreadIds = unlockedChatThreadIds + currentThreadId extensionManager.notifyChatUnlocked(currentThreadId, isAll = false) } @@ -1101,7 +1254,7 @@ fun FocusGramApp() { } } - // Ambient background behind the floating nav bar on Reels (seamless gradient, no harsh border line) + // Gradient fill beneath the floating navigation bar to smooth video edge transitions on Reels. if (!isGateActive && destination == AppDestination.MAIN_WEBVIEW && islandMode == IslandMode.FLOATING && !isKeyboardOrInputActive) { Box( modifier = Modifier @@ -1130,10 +1283,15 @@ fun FocusGramApp() { ) } - // Native Bottom Navigation Bar (OLED floating island) - if (!isGateActive && destination == AppDestination.MAIN_WEBVIEW) { + if (!isGateActive && destination == AppDestination.MAIN_WEBVIEW && !isAuthPage && isUserAuthenticated) { val instagramTheme by sessionManager.instagramTheme.collectAsState() val downloadState by MediaDownloadHelper.downloadProgressFlow.collectAsState() + val sessionTotalSec = sessionDurationMinutes * 60 + val sessionProgress = if (sessionTotalSec > 0 && hasPassedSessionPicker && !isSessionExhausted) { + (totalElapsedSeconds.toFloat() / sessionTotalSec.toFloat()).coerceIn(0f, 1f) + } else { + 0f + } NativeBottomNavBar( currentUrl = currentUrl, settings = settings, @@ -1145,6 +1303,7 @@ fun FocusGramApp() { isDownloading = downloadState.isDownloading, isDownloadCompleted = downloadState.isCompleted, isDownloadFailed = downloadState.isFailed, + sessionProgress = sessionProgress, isAuthenticated = isUserAuthenticated, isAuthPage = isAuthPage, onNavClick = { target -> @@ -1169,8 +1328,8 @@ fun FocusGramApp() { ) } - // Native Floating Bio Font Stylist (Anchored directly above the Soft Keyboard via imePadding) - if (isBioFocused && !isGateActive && destination == AppDestination.MAIN_WEBVIEW) { + // Font transformation bar anchored above the soft keyboard on profile editing inputs. + if (settings.customBioFontsEnabled && isBioFocused && !isGateActive && destination == AppDestination.MAIN_WEBVIEW) { Surface( modifier = Modifier .align(Alignment.BottomCenter) @@ -1227,7 +1386,6 @@ fun FocusGramApp() { } } - // Sub-screen Animated Overlay (Settings, Reels History, App Lock, etc.) AnimatedVisibility( visible = !isGateActive && destination != AppDestination.MAIN_WEBVIEW, enter = slideInHorizontally(animationSpec = tween(280)) { it } + fadeIn(animationSpec = tween(250)), @@ -1251,7 +1409,6 @@ fun FocusGramApp() { ) { currentDestination -> when (currentDestination) { AppDestination.MAIN_WEBVIEW -> { - // Rendered by underlying Box } AppDestination.SETTINGS -> { @@ -1276,7 +1433,9 @@ fun FocusGramApp() { currentSubpage = settingsSubpage, onSubpageChange = { settingsSubpage = it }, encryptedStorage = encryptedStorage, - onNavigateChatPinSetup = { destination = AppDestination.CHAT_LOCK_SETUP } + onNavigateChatPinSetup = { destination = AppDestination.CHAT_LOCK_SETUP }, + onNavigateOnboarding = { destination = AppDestination.ONBOARDING }, + onNavigateDebugMenu = { destination = AppDestination.DEBUG_MENU } ) } @@ -1314,6 +1473,7 @@ fun FocusGramApp() { AppDestination.GUARDRAILS -> { GuardrailsDashboardScreen( repository = settingsRepository, + screenTimeDao = database.screenTimeDao(), onBack = { destination = AppDestination.SETTINGS } ) } @@ -1352,13 +1512,58 @@ fun FocusGramApp() { AppDestination.DEBUG_MENU -> { DebugMenuScreen( repository = settingsRepository, - onBack = { destination = AppDestination.SETTINGS } + onBack = { destination = AppDestination.SETTINGS }, + onSimulateUpdate = { + activeUpdateInfo = UpdateCheckerService.getMockUpdateInfo() + destination = AppDestination.MAIN_WEBVIEW + }, + onSimulateNotice = { + activeRemoteNotice = RemoteNoticeService.getMockNotice() + }, + isLimitsBypassed = isDebugDailyLimitBypassed && hasPassedCooldownGate, + onToggleLimitsBypass = { + val willBypass = !(isDebugDailyLimitBypassed && hasPassedCooldownGate) + isDebugDailyLimitBypassed = willBypass + hasPassedCooldownGate = willBypass + if (willBypass) { + isSessionExhausted = false + } + }, + currentSessionDurationMinutes = sessionDurationMinutes, + currentSessionRemainingSeconds = sessionRemainingSeconds, + currentSessionElapsedSeconds = totalElapsedSeconds, + onAdjustSessionTime = { newDuration, newRemaining, newElapsed -> + sessionDurationMinutes = newDuration + sessionRemainingSeconds = newRemaining + totalElapsedSeconds = newElapsed + hasPassedSessionPicker = true + isSessionExhausted = newRemaining <= 0 + scope.launch { + settingsRepository.saveActiveSession( + durationMinutes = newDuration, + remainingSeconds = newRemaining, + elapsedSeconds = newElapsed, + reelsWatched = reelsWatchedSessionCount, + reelCodes = reelHistory.getSessionCodes(), + isActive = newRemaining > 0 + ) + } + if (destination == AppDestination.MAIN_WEBVIEW && !isGateActive) { + ScreenTimeService.startSession(context, isViewingReels, newElapsed, newDuration) + } else { + ScreenTimeService.pauseSession(context, newElapsed, newDuration) + } + } ) } AppDestination.ADSTERRA_REWARDED -> { AdsterraAdScreen( - sessionType = if (adRewardAction != null) "setting_toggle" else if (isDailyLimitExhausted || isSessionExhausted) "session_extend" else "voluntary", + sessionType = when { + adRewardAction != null -> "setting_toggle" + isDailyLimitExhausted || isSessionExhausted -> "session_extend" + else -> "voluntary" + }, onRewardClaimed = { val pendingAction = adRewardAction adRewardAction = null @@ -1379,14 +1584,11 @@ fun FocusGramApp() { isSessionExhausted = false destination = AppDestination.MAIN_WEBVIEW } else if (isSessionExhausted) { - adExtensionsUsed += 1 + sessionExtensionsUsed += 1 sessionRemainingSeconds += 3 * 60 sessionDurationMinutes += 3 isSessionExhausted = false destination = AppDestination.MAIN_WEBVIEW - } else if (!hasPassedEffortFriction) { - hasPassedEffortFriction = true - destination = AppDestination.MAIN_WEBVIEW } else { destination = AppDestination.SETTINGS } @@ -1401,12 +1603,19 @@ fun FocusGramApp() { } ) } + + AppDestination.ONBOARDING -> { + OnboardingScreen( + settings = settings, + settingsRepository = settingsRepository, + onComplete = { destination = AppDestination.SETTINGS } + ) + } } } } - // Full-screen Gate & Mindful Session Picker Overlays - // Rendered on top of GeckoView so Instagram preloads seamlessly in the background. + // Overlays rendered atop GeckoView so web assets continue loading in background. if (isGateActive) { Box( modifier = Modifier @@ -1423,28 +1632,53 @@ fun FocusGramApp() { onUnlockSuccess = { isAppUnlocked = true }, isBiometricAllowed = settings.biometricEnabled ) - } else if ((isDailyLimitExhausted || isSessionExhausted) && destination != AppDestination.ADSTERRA_REWARDED) { - val elapsedMins = max(sessionDurationMinutes, (totalElapsedSeconds + 59) / 60) - val extensionsLeft = if (isDailyLimitExhausted) { - if (settings.dailyLimitAdClaimedDate == today) 0 else 1 - } else { - (3 - adExtensionsUsed).coerceAtLeast(0) - } - SessionExhaustedScreen( - elapsedMinutes = elapsedMins, - extensionsRemaining = extensionsLeft, + } else if (isDailyLimitExhausted && destination != AppDestination.ADSTERRA_REWARDED) { + DailyLimitGateScreen( + dailyLimitMinutes = settings.dailyLimitMinutes, + todayMinutesUsed = (todayDailySeconds + 59) / 60, + extensionsRemaining = dailyExtensionsRemaining, onExit = { activity?.finish() }, onWatchAdToExtend = { - if (extensionsLeft > 0) { + if (dailyExtensionsRemaining > 0) { destination = AppDestination.ADSTERRA_REWARDED } + }, + onDebugBypass = { + isDebugDailyLimitBypassed = true + } + ) + } else if (isSessionExhausted && destination != AppDestination.ADSTERRA_REWARDED) { + val elapsedMins = max(sessionDurationMinutes, (totalElapsedSeconds + 59) / 60) + SessionExhaustedScreen( + elapsedMinutes = elapsedMins, + extensionsRemaining = sessionExtensionsRemaining, + onExit = { + isSessionExhausted = false + hasPassedSessionPicker = false + hasPassedCooldownGate = false + reelHistory.resetSession() + scope.launch { + settingsRepository.recordSessionEnd() + settingsRepository.clearActiveSession() + } + activity?.finish() + }, + onWatchAdToExtend = { + if (sessionExtensionsRemaining > 0) { + destination = AppDestination.ADSTERRA_REWARDED + } + }, + onDebugBypass = { + isSessionExhausted = false + hasPassedSessionPicker = true } ) } else if (remainingCooldownSeconds > 0 && !hasPassedCooldownGate) { CooldownGateScreen( - remainingSecondsInitial = remainingCooldownSeconds, + remainingSeconds = remainingCooldownSeconds, onCooldownExpired = { hasPassedCooldownGate = true }, - onExit = { activity?.finish() } + onExit = { activity?.finish() }, + onDebugBypass = { hasPassedCooldownGate = true } ) } else if (settings.showBreathGate && settings.breathGateSeconds > 0 && !hasPassedBreathGate) { BreathGateScreen( @@ -1454,11 +1688,8 @@ fun FocusGramApp() { } else if (settings.effortFrictionEnabled && settings.wordChallengeCount > 0 && !hasPassedEffortFriction) { EffortFrictionGate( sessionType = "Instagram", - remainingCreditMinutes = settings.instaMinutes, + wordCount = settings.wordChallengeCount, onProceed = { hasPassedEffortFriction = true }, - onWatchAd = { - destination = AppDestination.ADSTERRA_REWARDED - }, onCancel = { activity?.finish() } ) } else if (!hasPassedSessionPicker) { @@ -1467,16 +1698,27 @@ fun FocusGramApp() { sessionDurationMinutes = minutes sessionRemainingSeconds = minutes * 60 totalElapsedSeconds = 0 - adExtensionsUsed = 0 + sessionExtensionsUsed = 0 isSessionExhausted = false hasPassedSessionPicker = true + reelHistory.resetSession() + scope.launch { + settingsRepository.saveActiveSession( + durationMinutes = minutes, + remainingSeconds = minutes * 60, + elapsedSeconds = 0, + reelsWatched = 0, + reelCodes = emptySet(), + isActive = true + ) + } } ) } } } - // Chat Lock Privacy Screen (Instant Black Cover + Hardware Biometric / PIN Gate) + // Veil rendering pure black over DOM conversation content while awaiting biometric or PIN auth. if (destination == AppDestination.MAIN_WEBVIEW && !isGateActive && isChatLockActive) { Box( modifier = Modifier @@ -1544,9 +1786,7 @@ fun FocusGramApp() { val dest = pending.targetUrl ?: "https://www.instagram.com/direct/t/${pending.threadId}/" val current = sessionManager.currentUrl.value - if (current.contains(pending.threadId)) { - sessionManager.session.reload() - } else { + if (!current.contains(pending.threadId)) { sessionManager.navigateTo(dest) } } else if ((settings.chatLockMode == "whole_dm" || settings.chatLockAll) && currentThreadId == null) { @@ -1580,7 +1820,6 @@ fun FocusGramApp() { detail: { unlockedThreads: window.__fgUnlockedThreads } })); """) - sessionManager.session.reload() } } ) @@ -1588,11 +1827,62 @@ fun FocusGramApp() { } } } + + if (activeRemoteNotice != null) { + val notice = activeRemoteNotice!! + AlertDialog( + onDismissRequest = { + remoteNoticeService.markShown(notice.id) + activeRemoteNotice = null + }, + containerColor = Color(0xEB1C1C1E), + modifier = Modifier + .clip(RoundedCornerShape(24.dp)) + .border( + BorderStroke( + 1.dp, + Brush.verticalGradient(listOf(Color(0x38FFFFFF), Color(0x10FFFFFF))) + ), + RoundedCornerShape(24.dp) + ), + title = { + Text( + text = notice.header, + color = Color.White, + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + }, + text = { + Text( + text = notice.body, + color = Color(0xFFC7C7CC), + fontSize = 14.sp, + lineHeight = 20.sp + ) + }, + confirmButton = { + Button( + onClick = { + remoteNoticeService.markShown(notice.id) + activeRemoteNotice = null + }, + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + contentColor = Color.Black + ), + shape = RoundedCornerShape(20.dp) + ) { + Text(notice.buttonText.ifBlank { "OK" }, fontWeight = FontWeight.Bold) + } + } + ) + } } private fun postSessionExpiredNotification(context: Context) { try { - // Direct physical vibration ensures the user feels the alert regardless of notification channel mutes + // Trigger haptic feedback directly so alert registers even when system notification sound is muted. HapticHelper.performSessionCompletionAlert(context) val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager @@ -1635,3 +1925,15 @@ private fun postSessionExpiredNotification(context: Context) { Log.e("FocusGramApp", "Failed to post session notification: ${e.message}", e) } } + +private fun isAuthUrl(url: String): Boolean { + val lower = url.lowercase() + return (lower.contains("/accounts") && !lower.contains("/accounts/edit")) || + lower.contains("/login") || + lower.contains("/signup") || + lower.contains("/two_factor") || + lower.contains("/challenge") || + lower.contains("/onetap") || + lower.contains("accountscenter.") || + lower.contains("facebook.com") +} diff --git a/Ported/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png similarity index 100% rename from Ported/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png rename to app/src/main/res/drawable-hdpi/ic_launcher_foreground.png diff --git a/Ported/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png similarity index 100% rename from Ported/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png rename to app/src/main/res/drawable-mdpi/ic_launcher_foreground.png diff --git a/Ported/app/src/main/res/drawable-v21/launch_background.xml b/app/src/main/res/drawable-v21/launch_background.xml similarity index 100% rename from Ported/app/src/main/res/drawable-v21/launch_background.xml rename to app/src/main/res/drawable-v21/launch_background.xml diff --git a/Ported/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png similarity index 100% rename from Ported/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png rename to app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png diff --git a/Ported/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png similarity index 100% rename from Ported/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png rename to app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png diff --git a/Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_abstract.png b/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_abstract.png similarity index 100% rename from Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_abstract.png rename to app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_abstract.png diff --git a/Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_forest.png b/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_forest.png similarity index 100% rename from Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_forest.png rename to app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_forest.png diff --git a/Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_golden.png b/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_golden.png similarity index 100% rename from Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_golden.png rename to app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_golden.png diff --git a/Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_metal.png b/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_metal.png similarity index 100% rename from Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_metal.png rename to app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_metal.png diff --git a/Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_minimalist.png b/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_minimalist.png similarity index 100% rename from Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_minimalist.png rename to app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_minimalist.png diff --git a/Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_polaroid.png b/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_polaroid.png similarity index 100% rename from Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_polaroid.png rename to app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_polaroid.png diff --git a/Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_redesign.png b/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_redesign.png similarity index 100% rename from Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_redesign.png rename to app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_redesign.png diff --git a/Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_scribble.png b/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_scribble.png similarity index 100% rename from Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_scribble.png rename to app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_scribble.png diff --git a/Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_vibrant.png b/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_vibrant.png similarity index 100% rename from Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_vibrant.png rename to app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_vibrant.png diff --git a/Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_viewfinder.png b/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_viewfinder.png similarity index 100% rename from Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_viewfinder.png rename to app/src/main/res/drawable-xxxhdpi/ic_launcher_fg_viewfinder.png diff --git a/Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png similarity index 100% rename from Ported/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png rename to app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png diff --git a/Ported/app/src/main/res/drawable/ic_chat_lock_closed.xml b/app/src/main/res/drawable/ic_chat_lock_closed.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_chat_lock_closed.xml rename to app/src/main/res/drawable/ic_chat_lock_closed.xml diff --git a/Ported/app/src/main/res/drawable/ic_chat_lock_open.xml b/app/src/main/res/drawable/ic_chat_lock_open.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_chat_lock_open.xml rename to app/src/main/res/drawable/ic_chat_lock_open.xml diff --git a/Ported/app/src/main/res/drawable/ic_credit_downloader.png b/app/src/main/res/drawable/ic_credit_downloader.png similarity index 100% rename from Ported/app/src/main/res/drawable/ic_credit_downloader.png rename to app/src/main/res/drawable/ic_credit_downloader.png diff --git a/Ported/app/src/main/res/drawable/ic_credit_geckoview.xml b/app/src/main/res/drawable/ic_credit_geckoview.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_credit_geckoview.xml rename to app/src/main/res/drawable/ic_credit_geckoview.xml diff --git a/Ported/app/src/main/res/drawable/ic_credit_iconify.xml b/app/src/main/res/drawable/ic_credit_iconify.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_credit_iconify.xml rename to app/src/main/res/drawable/ic_credit_iconify.xml diff --git a/Ported/app/src/main/res/drawable/ic_credit_jetpack_compose.xml b/app/src/main/res/drawable/ic_credit_jetpack_compose.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_credit_jetpack_compose.xml rename to app/src/main/res/drawable/ic_credit_jetpack_compose.xml diff --git a/Ported/app/src/main/res/drawable/ic_credit_sqlite.xml b/app/src/main/res/drawable/ic_credit_sqlite.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_credit_sqlite.xml rename to app/src/main/res/drawable/ic_credit_sqlite.xml diff --git a/Ported/app/src/main/res/drawable/ic_credit_ublock.xml b/app/src/main/res/drawable/ic_credit_ublock.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_credit_ublock.xml rename to app/src/main/res/drawable/ic_credit_ublock.xml diff --git a/Ported/app/src/main/res/drawable/ic_download_fill.xml b/app/src/main/res/drawable/ic_download_fill.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_download_fill.xml rename to app/src/main/res/drawable/ic_download_fill.xml diff --git a/Ported/app/src/main/res/drawable/ic_launcher_bg_instagram.xml b/app/src/main/res/drawable/ic_launcher_bg_instagram.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_launcher_bg_instagram.xml rename to app/src/main/res/drawable/ic_launcher_bg_instagram.xml diff --git a/Ported/app/src/main/res/drawable/ic_launcher_fg_all_white.xml b/app/src/main/res/drawable/ic_launcher_fg_all_white.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_launcher_fg_all_white.xml rename to app/src/main/res/drawable/ic_launcher_fg_all_white.xml diff --git a/Ported/app/src/main/res/drawable/ic_launcher_fg_classic.xml b/app/src/main/res/drawable/ic_launcher_fg_classic.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_launcher_fg_classic.xml rename to app/src/main/res/drawable/ic_launcher_fg_classic.xml diff --git a/Ported/app/src/main/res/drawable/ic_launcher_fg_discreet.xml b/app/src/main/res/drawable/ic_launcher_fg_discreet.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_launcher_fg_discreet.xml rename to app/src/main/res/drawable/ic_launcher_fg_discreet.xml diff --git a/Ported/app/src/main/res/drawable/ic_launcher_fg_instagram_official.xml b/app/src/main/res/drawable/ic_launcher_fg_instagram_official.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_launcher_fg_instagram_official.xml rename to app/src/main/res/drawable/ic_launcher_fg_instagram_official.xml diff --git a/Ported/app/src/main/res/drawable/ic_launcher_fg_mono.xml b/app/src/main/res/drawable/ic_launcher_fg_mono.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_launcher_fg_mono.xml rename to app/src/main/res/drawable/ic_launcher_fg_mono.xml diff --git a/Ported/app/src/main/res/drawable/ic_launcher_fg_oled.xml b/app/src/main/res/drawable/ic_launcher_fg_oled.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_launcher_fg_oled.xml rename to app/src/main/res/drawable/ic_launcher_fg_oled.xml diff --git a/Ported/app/src/main/res/drawable/ic_launcher_fg_pitch_black.xml b/app/src/main/res/drawable/ic_launcher_fg_pitch_black.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_launcher_fg_pitch_black.xml rename to app/src/main/res/drawable/ic_launcher_fg_pitch_black.xml diff --git a/Ported/app/src/main/res/drawable/ic_notification.xml b/app/src/main/res/drawable/ic_notification.xml similarity index 100% rename from Ported/app/src/main/res/drawable/ic_notification.xml rename to app/src/main/res/drawable/ic_notification.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_abstract.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_abstract.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_abstract.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_abstract.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_all_white.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_all_white.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_all_white.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_all_white.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_classic.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_classic.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_classic.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_classic.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_discreet.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_discreet.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_discreet.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_discreet.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_forest.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_golden.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_golden.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_golden.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_golden.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_instagram_official.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_instagram_official.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_instagram_official.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_instagram_official.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_metal.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_metal.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_metal.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_metal.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_minimalist.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_minimalist.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_minimalist.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_minimalist.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_mono.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_mono.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_mono.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_mono.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_oled.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_oled.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_oled.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_oled.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pitch_black.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pitch_black.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pitch_black.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_pitch_black.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_polaroid.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_polaroid.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_polaroid.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_polaroid.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_redesign.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_redesign.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_redesign.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_redesign.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_scribble.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_scribble.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_scribble.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_scribble.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_vibrant.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_vibrant.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_vibrant.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_vibrant.xml diff --git a/Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_viewfinder.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_viewfinder.xml similarity index 100% rename from Ported/app/src/main/res/mipmap-anydpi-v26/ic_launcher_viewfinder.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_viewfinder.xml diff --git a/Ported/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from Ported/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/Ported/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from Ported/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/Ported/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from Ported/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/Ported/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from Ported/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_abstract.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_abstract.png similarity index 100% rename from Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_abstract.png rename to app/src/main/res/mipmap-xxxhdpi/ic_launcher_abstract.png diff --git a/Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_forest.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_forest.png similarity index 100% rename from Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_forest.png rename to app/src/main/res/mipmap-xxxhdpi/ic_launcher_forest.png diff --git a/Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_golden.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_golden.png similarity index 100% rename from Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_golden.png rename to app/src/main/res/mipmap-xxxhdpi/ic_launcher_golden.png diff --git a/Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_metal.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_metal.png similarity index 100% rename from Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_metal.png rename to app/src/main/res/mipmap-xxxhdpi/ic_launcher_metal.png diff --git a/Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_minimalist.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_minimalist.png similarity index 100% rename from Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_minimalist.png rename to app/src/main/res/mipmap-xxxhdpi/ic_launcher_minimalist.png diff --git a/Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_polaroid.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_polaroid.png similarity index 100% rename from Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_polaroid.png rename to app/src/main/res/mipmap-xxxhdpi/ic_launcher_polaroid.png diff --git a/Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_redesign.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_redesign.png similarity index 100% rename from Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_redesign.png rename to app/src/main/res/mipmap-xxxhdpi/ic_launcher_redesign.png diff --git a/Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_scribble.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_scribble.png similarity index 100% rename from Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_scribble.png rename to app/src/main/res/mipmap-xxxhdpi/ic_launcher_scribble.png diff --git a/Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_vibrant.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_vibrant.png similarity index 100% rename from Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_vibrant.png rename to app/src/main/res/mipmap-xxxhdpi/ic_launcher_vibrant.png diff --git a/Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_viewfinder.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_viewfinder.png similarity index 100% rename from Ported/app/src/main/res/mipmap-xxxhdpi/ic_launcher_viewfinder.png rename to app/src/main/res/mipmap-xxxhdpi/ic_launcher_viewfinder.png diff --git a/Ported/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml similarity index 100% rename from Ported/app/src/main/res/values/colors.xml rename to app/src/main/res/values/colors.xml diff --git a/Ported/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml similarity index 100% rename from Ported/app/src/main/res/values/strings.xml rename to app/src/main/res/values/strings.xml diff --git a/Ported/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml similarity index 100% rename from Ported/app/src/main/res/values/themes.xml rename to app/src/main/res/values/themes.xml diff --git a/Ported/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml similarity index 100% rename from Ported/app/src/main/res/xml/file_paths.xml rename to app/src/main/res/xml/file_paths.xml diff --git a/Ported/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml similarity index 52% rename from Ported/app/src/main/res/xml/network_security_config.xml rename to app/src/main/res/xml/network_security_config.xml index 8f88730..507d069 100644 --- a/Ported/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -11,8 +11,20 @@ instagram.com cdninstagram.com fbcdn.net + github.com + adsterra.com effectivecpmnetwork.com highperformanceformat.com + effectivegatecpm.com + profitableratecpm.com + profitablecpmratenetwork.com + profitableratecpmnetwork.com + aqml.org + afders.org + bicea.org + arwf.org + cdn.show-sb.com + api3.adsterratools.com diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/AdsterraAdFlowTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/AdsterraAdFlowTest.kt similarity index 65% rename from Ported/app/src/test/kotlin/com/ujwal/focusgram/AdsterraAdFlowTest.kt rename to app/src/test/kotlin/com/ujwal/focusgram/AdsterraAdFlowTest.kt index 31dcab5..939993f 100644 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/AdsterraAdFlowTest.kt +++ b/app/src/test/kotlin/com/ujwal/focusgram/AdsterraAdFlowTest.kt @@ -5,7 +5,6 @@ import org.junit.Assert.assertTrue import org.junit.Test /** - * AdsterraAdFlowTest * Validates dual-container rewarded ad conditions: 20-second minimum timer and 2 external ad clicks. */ class AdsterraAdFlowTest { @@ -15,31 +14,17 @@ class AdsterraAdFlowTest { val requiredSeconds = 20 val requiredClicks = 2 - // Condition 1: 0 seconds, 0 clicks -> Incomplete - val isComplete0 = (0 >= requiredSeconds) && (0 >= requiredClicks) - assertFalse(isComplete0) - - // Condition 2: 20 seconds, 1 click -> Incomplete - val isComplete1 = (20 >= requiredSeconds) && (1 >= requiredClicks) - assertFalse(isComplete1) - - // Condition 3: 15 seconds, 2 clicks -> Incomplete - val isComplete2 = (15 >= requiredSeconds) && (2 >= requiredClicks) - assertFalse(isComplete2) - - // Condition 4: 20 seconds, 2 clicks -> Complete! - val isComplete3 = (20 >= requiredSeconds) && (2 >= requiredClicks) - assertTrue(isComplete3) - - // Condition 5: 25 seconds, 3 clicks -> Complete! - val isComplete4 = (25 >= requiredSeconds) && (3 >= requiredClicks) - assertTrue(isComplete4) + assertFalse((0 >= requiredSeconds) && (0 >= requiredClicks)) + assertFalse((20 >= requiredSeconds) && (1 >= requiredClicks)) + assertFalse((15 >= requiredSeconds) && (2 >= requiredClicks)) + assertTrue((20 >= requiredSeconds) && (2 >= requiredClicks)) + assertTrue((25 >= requiredSeconds) && (3 >= requiredClicks)) } @Test fun testSettingToggleCriteria() { val requiredSeconds = 20 - // Setting toggles only require watching the 20s ad countdown without mandatory external clicks + // Setting toggles only require watching the countdown without external clicks. fun isToggleComplete(elapsed: Int) = elapsed >= requiredSeconds assertFalse(isToggleComplete(0)) @@ -60,11 +45,10 @@ class AdsterraAdFlowTest { return shouldShowLock || isExhausted || effortFrictionActive } - // When watching ad, gate must NOT be active even if lock, exhaustion, or friction are present + // The ad viewer itself cannot be locked out by friction or exhaustion gates. assertFalse(isGateActive("ADSTERRA_REWARDED", shouldShowLock = true, isExhausted = true, effortFrictionActive = true)) assertFalse(isGateActive("ADSTERRA_REWARDED", shouldShowLock = false, isExhausted = false, effortFrictionActive = false)) - // When in Main WebView or Settings, gate evaluates normally assertTrue(isGateActive("MAIN_WEBVIEW", shouldShowLock = true, isExhausted = false, effortFrictionActive = false)) assertTrue(isGateActive("MAIN_WEBVIEW", shouldShowLock = false, isExhausted = true, effortFrictionActive = false)) assertFalse(isGateActive("MAIN_WEBVIEW", shouldShowLock = false, isExhausted = false, effortFrictionActive = false)) @@ -86,16 +70,9 @@ class AdsterraAdFlowTest { val url1 = "https://example.com/ad1" val url2 = "https://example.com/ad2" - // First click count assertTrue(shouldCountClick(url1, "", 1000L, 0L)) - - // Rapid duplicate click on same url within 500ms -> debounced assertFalse(shouldCountClick(url1, url1, 1500L, 1000L)) - - // Duplicate click after 2000ms -> accepted assertTrue(shouldCountClick(url1, url1, 3100L, 1000L)) - - // Different ad url even rapidly -> accepted assertTrue(shouldCountClick(url2, url1, 1500L, 1000L)) } } diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/AppLockSecurityTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/AppLockSecurityTest.kt similarity index 88% rename from Ported/app/src/test/kotlin/com/ujwal/focusgram/AppLockSecurityTest.kt rename to app/src/test/kotlin/com/ujwal/focusgram/AppLockSecurityTest.kt index cf85126..9f6cb19 100644 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/AppLockSecurityTest.kt +++ b/app/src/test/kotlin/com/ujwal/focusgram/AppLockSecurityTest.kt @@ -28,9 +28,9 @@ class AppLockSecurityTest { assertTrue(isValidPin("0000")) assertTrue(isValidPin("9876")) - assertFalse(isValidPin("123")) // Too short - assertFalse(isValidPin("12345")) // Too long - assertFalse(isValidPin("12a4")) // Non-digit + assertFalse(isValidPin("123")) + assertFalse(isValidPin("12345")) + assertFalse(isValidPin("12a4")) assertFalse(isValidPin("")) } @@ -42,7 +42,7 @@ class AppLockSecurityTest { assertEquals(hash1, hash2) assertNotEquals(hash1, hashDifferent) - assertEquals(64, hash1.length) // SHA-256 produces 64 hex characters + assertEquals(64, hash1.length) } @Test diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/ArchitectureIntegrityTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/ArchitectureIntegrityTest.kt similarity index 64% rename from Ported/app/src/test/kotlin/com/ujwal/focusgram/ArchitectureIntegrityTest.kt rename to app/src/test/kotlin/com/ujwal/focusgram/ArchitectureIntegrityTest.kt index edb720c..c532ef0 100644 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/ArchitectureIntegrityTest.kt +++ b/app/src/test/kotlin/com/ujwal/focusgram/ArchitectureIntegrityTest.kt @@ -6,7 +6,7 @@ import org.junit.Test /** * ArchitectureIntegrityTest - * End-to-end audit verifying that all 16 original features and activities are preserved + * End-to-end audit verifying that core features and activities are preserved * in Native Kotlin with Mozilla GeckoView and uBlock Origin, zero telemetry, and AGPL-3.0 compliance. */ class ArchitectureIntegrityTest { @@ -22,17 +22,16 @@ class ArchitectureIntegrityTest { "8. Guardrails Dashboard (Daily limits & cooldowns)", "9. Screen Time Service (Foreground service tracking)", "10. Reels History (Local Room SQLite, zero video caching)", - "11. Level Panel (Discipline tier XP progression)", - "12. Bait Me Gamification (BaitMeButton & Fullscreen wheel)", - "13. Earn Ads / Rewarded Ads (Adsterra dual-container)", - "14. Offline Snapshots (Level 5 gated offline bookmarks)", - "15. Session Picker & Modal (Intention setting)", - "16. Developer Diagnostics (Debug menu state triggers)" + "11. Ghost Mode & Stealth Privacy (Read receipt suppression)", + "12. Earn Ads / Rewarded Ads (Adsterra dual-container)", + "13. Offline Snapshots (Offline bookmarks)", + "14. Session Picker & Modal (Intention setting)", + "15. Developer Diagnostics (Debug menu state triggers)" ) @Test - fun testAllSixteenFeaturesPreserved() { - assertEquals("All 16 features must be accounted for", 16, preservedFeatures.size) + fun testAllCoreFeaturesPreserved() { + assertEquals("All 15 core features must be accounted for", 15, preservedFeatures.size) preservedFeatures.forEach { feature -> assertTrue(feature.isNotEmpty()) } @@ -40,7 +39,6 @@ class ArchitectureIntegrityTest { @Test fun testZeroTelemetryRule() { - // Verification that data never leaves the device val zeroTelemetry = true val onDeviceStorageOnly = true assertTrue("No data sent off device", zeroTelemetry) @@ -49,8 +47,7 @@ class ArchitectureIntegrityTest { @Test fun testDualContainerAdCoexistence() { - // Dual-container strategy ensures uBlock Origin in GeckoView blocks Instagram ads - // while Adsterra rewarded ads load independently in standard Android WebView + // GeckoView runs uBlock Origin to strip Instagram ads; rewarded unlock ads run in an isolated standard WebView. val geckoViewUBlockActive = true val adsterraIsolatedWebView = true assertTrue(geckoViewUBlockActive && adsterraIsolatedWebView) diff --git a/app/src/test/kotlin/com/ujwal/focusgram/AuthNavBarSuppressionTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/AuthNavBarSuppressionTest.kt new file mode 100644 index 0000000..6717ebc --- /dev/null +++ b/app/src/test/kotlin/com/ujwal/focusgram/AuthNavBarSuppressionTest.kt @@ -0,0 +1,81 @@ +package com.ujwal.focusgram + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import java.io.File + +/** + * AuthNavBarSuppressionTest + * Validates that the custom navigation bar is strictly suppressed on all + * authentication, login, 2FA, signup, and OAuth challenge routes. + */ +class AuthNavBarSuppressionTest { + + private fun isAuthUrl(url: String): Boolean { + val lower = url.lowercase() + return (lower.contains("/accounts") && !lower.contains("/accounts/edit")) || + lower.contains("/login") || + lower.contains("/signup") || + lower.contains("/two_factor") || + lower.contains("/challenge") || + lower.contains("/onetap") || + lower.contains("accountscenter.") || + lower.contains("facebook.com") + } + + private fun isNavBarVisible(url: String, isAuthenticated: Boolean, isAuthPage: Boolean): Boolean { + val lowerUrl = url.lowercase() + val isExplicitAuthRoute = isAuthUrl(lowerUrl) + val isAuth = isAuthPage || !isAuthenticated || isExplicitAuthRoute + return !isAuth + } + + @Test + fun testAuthRoutesAreDetected() { + assertTrue(isAuthUrl("https://www.instagram.com/accounts/login/")) + assertTrue(isAuthUrl("https://www.instagram.com/accounts/emailsignup/")) + assertTrue(isAuthUrl("https://www.instagram.com/accounts/two_factor/")) + assertTrue(isAuthUrl("https://www.instagram.com/login/")) + assertTrue(isAuthUrl("https://www.instagram.com/signup/")) + assertTrue(isAuthUrl("https://www.instagram.com/challenge/")) + assertTrue(isAuthUrl("https://accountscenter.instagram.com/")) + assertTrue(isAuthUrl("https://m.facebook.com/login/")) + + // Edit profile under accounts is an allowed authenticated page + assertFalse(isAuthUrl("https://www.instagram.com/accounts/edit/")) + assertFalse(isAuthUrl("https://www.instagram.com/")) + assertFalse(isAuthUrl("https://www.instagram.com/direct/inbox/")) + } + + @Test + fun testNavBarSuppressionConditions() { + // Logged out on homepage + assertFalse(isNavBarVisible("https://www.instagram.com/", isAuthenticated = false, isAuthPage = true)) + assertFalse(isNavBarVisible("https://www.instagram.com/", isAuthenticated = false, isAuthPage = false)) + + // Login screen + assertFalse(isNavBarVisible("https://www.instagram.com/accounts/login/", isAuthenticated = false, isAuthPage = true)) + assertFalse(isNavBarVisible("https://www.instagram.com/accounts/login/", isAuthenticated = true, isAuthPage = false)) + + // Authenticated on homepage + assertTrue(isNavBarVisible("https://www.instagram.com/", isAuthenticated = true, isAuthPage = false)) + + // Authenticated in DMs + assertTrue(isNavBarVisible("https://www.instagram.com/direct/inbox/", isAuthenticated = true, isAuthPage = false)) + + // Authenticated on accounts/edit + assertTrue(isNavBarVisible("https://www.instagram.com/accounts/edit/", isAuthenticated = true, isAuthPage = false)) + } + + @Test + fun testFocusGramAppEnforcesAuthCheck() { + val file = File("src/main/kotlin/com/ujwal/focusgram/ui/FocusGramApp.kt") + val content = if (file.exists()) file.readText() else File("app/src/main/kotlin/com/ujwal/focusgram/ui/FocusGramApp.kt").readText() + + assertTrue( + "FocusGramApp must check !isAuthPage && isUserAuthenticated before rendering NativeBottomNavBar", + content.contains("!isAuthPage && isUserAuthenticated") + ) + } +} diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/BlockDmReelsSwipingTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/BlockDmReelsSwipingTest.kt similarity index 94% rename from Ported/app/src/test/kotlin/com/ujwal/focusgram/BlockDmReelsSwipingTest.kt rename to app/src/test/kotlin/com/ujwal/focusgram/BlockDmReelsSwipingTest.kt index b818730..51e4e63 100644 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/BlockDmReelsSwipingTest.kt +++ b/app/src/test/kotlin/com/ujwal/focusgram/BlockDmReelsSwipingTest.kt @@ -37,24 +37,21 @@ class BlockDmReelsSwipingTest { assertTrue("assets/reellock directory must exist", dir.exists()) - // 1. manifest.json val manifest = File(dir, "manifest.json").readText() assertTrue("Manifest must declare reellock@focusgram.app", manifest.contains("reellock@focusgram.app")) assertTrue("Manifest must run at document_start", manifest.contains("\"run_at\": \"document_start\"")) - // 2. bg.js val bg = File(dir, "bg.js").readText() assertTrue("bg.js must cancel feed GraphQL queries", bg.contains("cancel: true")) assertTrue("bg.js must check fb_api_req_friendly_name or doc_id", bg.contains("NAME_RE.test(name)") || bg.contains("DOC_IDS.has(doc)")) assertTrue("bg.js must target graphql/query endpoints", bg.contains("graphql/query")) - // 3. reellock.js val script = File(dir, "reellock.js").readText() assertTrue("Script must lock container", script.contains("[data-fg-lock]")) assertTrue("Script must check /direct/t/", script.contains("/direct/t/")) assertTrue("Script must listen to touchmove and wheel with passive: false", script.contains("passive: false")) assertTrue("Script must check toggle attribute data-fg-block-dm-reels", script.contains("data-fg-block-dm-reels")) - // Make sure it does not pause or hide the first video (preserving user request) + // Sent reel video in direct messages must play normally; only continuous feed scrolling is blocked. assertFalse("Script must NOT pause sent reel video", script.contains("v.pause()")) assertFalse("Script must NOT mute sent reel video", script.contains("v.muted = true")) } diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/BreathGateTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/BreathGateTest.kt similarity index 91% rename from Ported/app/src/test/kotlin/com/ujwal/focusgram/BreathGateTest.kt rename to app/src/test/kotlin/com/ujwal/focusgram/BreathGateTest.kt index 7028ab3..315b86c 100644 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/BreathGateTest.kt +++ b/app/src/test/kotlin/com/ujwal/focusgram/BreathGateTest.kt @@ -13,7 +13,6 @@ class BreathGateTest { @Test fun testDurationCoercion() { - // Enforce valid bounds (min 3s, max 60s) val tooLow = 1.coerceIn(3, 60) assertEquals(3, tooLow) @@ -30,7 +29,6 @@ class BreathGateTest { val canContinueBefore = secondsRemaining <= 0 assertFalse(canContinueBefore) - // Decrement down to 0 while (secondsRemaining > 0) { secondsRemaining-- } diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/ChatLockAndAppIconTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/ChatLockAndAppIconTest.kt similarity index 94% rename from Ported/app/src/test/kotlin/com/ujwal/focusgram/ChatLockAndAppIconTest.kt rename to app/src/test/kotlin/com/ujwal/focusgram/ChatLockAndAppIconTest.kt index 6600cd7..74da74b 100644 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/ChatLockAndAppIconTest.kt +++ b/app/src/test/kotlin/com/ujwal/focusgram/ChatLockAndAppIconTest.kt @@ -62,6 +62,7 @@ class ChatLockAndAppIconTest { assertEquals(AppIconManager.IconOption.DEFAULT, AppIconManager.IconOption.fromId("default")) assertEquals(AppIconManager.IconOption.REDESIGN, AppIconManager.IconOption.fromId("redesign")) + assertEquals("Modern Insta", AppIconManager.IconOption.REDESIGN.displayName) assertEquals(AppIconManager.IconOption.PITCH_BLACK, AppIconManager.IconOption.fromId("pitch_black")) assertEquals(AppIconManager.IconOption.ALL_WHITE, AppIconManager.IconOption.fromId("all_white")) assertEquals(AppIconManager.IconOption.POLAROID, AppIconManager.IconOption.fromId("polaroid")) @@ -106,30 +107,21 @@ class ChatLockAndAppIconTest { } val baseSettings = FocusSettings() - // Default settings: nothing locked assertFalse(isRouteLocked("https://www.instagram.com/direct/inbox/", baseSettings, emptySet(), false)) assertFalse(isRouteLocked("https://www.instagram.com/direct/t/17849999/", baseSettings, emptySet(), false)) - // ChatLockAll enabled: all direct routes locked val lockAllSettings = baseSettings.copy(chatLockAll = true) assertTrue(isRouteLocked("https://www.instagram.com/direct/inbox/", lockAllSettings, emptySet(), false)) assertTrue(isRouteLocked("https://www.instagram.com/direct/t/17849999/", lockAllSettings, emptySet(), false)) - // Non-direct routes should not be locked assertFalse(isRouteLocked("https://www.instagram.com/reels/", lockAllSettings, emptySet(), false)) - // When all unlocked assertFalse(isRouteLocked("https://www.instagram.com/direct/inbox/", lockAllSettings, emptySet(), true)) - // Specific chat locked: val specificLockSettings = baseSettings.copy(lockedChatIds = setOf("17849999", "filian_editz")) - // Inbox itself is not locked when individual chat locked assertFalse(isRouteLocked("https://www.instagram.com/direct/inbox/", specificLockSettings, emptySet(), false)) - // Target thread is locked assertTrue(isRouteLocked("https://www.instagram.com/direct/t/17849999/", specificLockSettings, emptySet(), false)) assertTrue(isRouteLocked("https://www.instagram.com/direct/t/filian_editz/", specificLockSettings, emptySet(), false)) - // Unlocked thread is accessible assertFalse(isRouteLocked("https://www.instagram.com/direct/t/17849999/", specificLockSettings, setOf("17849999"), false)) - // Other threads not locked assertFalse(isRouteLocked("https://www.instagram.com/direct/t/99999999/", specificLockSettings, emptySet(), false)) } @@ -152,7 +144,7 @@ class ChatLockAndAppIconTest { val mainPinHash = hashMainPin("1234") val chatPinHash = hashChatPin("1234") - // Chat PIN hash must be isolated and distinct from main PIN hash even with the same digits + // Chat PIN salt differs from app lock salt to prevent hash reuse. org.junit.Assert.assertNotEquals(mainPinHash, chatPinHash) assertEquals(64, chatPinHash.length) assertEquals(chatPinHash, hashChatPin("1234")) @@ -174,9 +166,7 @@ class ChatLockAndAppIconTest { val usernames = mapOf("1784140001" to "johndoe", "1784140002" to "@alice") assertEquals("@johndoe", resolveDisplayTitle("1784140001", usernames)) assertEquals("@alice", resolveDisplayTitle("1784140002", usernames)) - // Alphabetic ID fallback assertEquals("@filian_editz", resolveDisplayTitle("filian_editz", emptyMap())) - // Pure digit ID with no username yet fallback assertEquals("Conversation (123456)", resolveDisplayTitle("9876123456", emptyMap())) } diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/CooldownGateTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/CooldownGateTest.kt similarity index 89% rename from Ported/app/src/test/kotlin/com/ujwal/focusgram/CooldownGateTest.kt rename to app/src/test/kotlin/com/ujwal/focusgram/CooldownGateTest.kt index a53d83c..259a926 100644 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/CooldownGateTest.kt +++ b/app/src/test/kotlin/com/ujwal/focusgram/CooldownGateTest.kt @@ -18,7 +18,6 @@ class CooldownGateTest { val sessionEndTime = 100_000L - // After 10 minutes (insufficient cooldown) val now10Min = sessionEndTime + (10 * 60 * 1000L) val isCooldownPassed10 = (now10Min - sessionEndTime) >= cooldownMs assertFalse(isCooldownPassed10) @@ -27,7 +26,6 @@ class CooldownGateTest { val remainingMinutes = (remainingMs / (60 * 1000L)).toInt() assertEquals(5, remainingMinutes) - // After 16 minutes (cooldown passed) val now16Min = sessionEndTime + (16 * 60 * 1000L) val isCooldownPassed16 = (now16Min - sessionEndTime) >= cooldownMs assertTrue(isCooldownPassed16) @@ -35,7 +33,7 @@ class CooldownGateTest { @Test fun testCooldownTimeStringFormatting() { - val remainingSeconds = 125 // 2m 5s + val remainingSeconds = 125 val minutes = remainingSeconds / 60 val seconds = remainingSeconds % 60 val formatted = String.format("%02d:%02d", minutes, seconds) diff --git a/app/src/test/kotlin/com/ujwal/focusgram/EffortFrictionGateTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/EffortFrictionGateTest.kt new file mode 100644 index 0000000..a54241a --- /dev/null +++ b/app/src/test/kotlin/com/ujwal/focusgram/EffortFrictionGateTest.kt @@ -0,0 +1,84 @@ +package com.ujwal.focusgram + +import com.ujwal.focusgram.feature.wellness.DisciplineMantraRepository +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * EffortFrictionGateTest + * Validates typing discipline challenge mechanics, word counting, and completion criteria. + */ +class EffortFrictionGateTest { + + private val sampleMantra = + "I choose to control my focus and not let algorithmic feeds govern my attention. Real life happens outside this screen, and my precious time deserves deliberate purpose and unwavering disciplined presence." + + @Test + fun testSampleMantraWordCount() { + val words = sampleMantra.trim().split("\\s+".toRegex()) + assertTrue("Mantra should have at least 25 words", words.size >= 25) + } + + @Test + fun testWordCountProgressCalculation() { + val targetCount = 30 + + val input10Words = "one two three four five six seven eight nine ten" + val words10 = input10Words.trim().split("\\s+".toRegex()).filter { it.isNotEmpty() } + assertEquals(10, words10.size) + + val progress = (words10.size.toFloat() / targetCount).coerceIn(0f, 1f) + assertEquals(10f / 30f, progress, 0.001f) + + val isTargetReached = words10.size >= targetCount + assertFalse(isTargetReached) + } + + @Test + fun testCompletionCondition() { + val targetCount = 30 + val fullInput = (1..35).joinToString(" ") { "word$it" } + val words = fullInput.trim().split("\\s+".toRegex()).filter { it.isNotEmpty() } + + assertTrue(words.size >= targetCount) + } + + @Test + fun testMantraRepositoryWordCountsAndQuantities() { + val supportedWordCounts = listOf(10, 15, 20, 30, 50) + for (count in supportedWordCounts) { + val list = DisciplineMantraRepository.mantrasByWordCount[count] + assertTrue("Expected mantras pool for $count words", list != null) + assertTrue("Expected at least 30 mantras for $count words, found ${list?.size}", (list?.size ?: 0) >= 30) + + list?.forEachIndexed { index, sentence -> + val words = sentence.trim().split("\\s+".toRegex()).filter { it.isNotEmpty() } + assertEquals( + "Sentence at index $index for word count $count must have exactly $count words: '$sentence'", + count, + words.size + ) + } + } + } + + @Test + fun testMantraExhaustionWithoutRepetition() { + val supportedWordCounts = listOf(10, 15, 20, 30, 50) + for (count in supportedWordCounts) { + val totalAvailable = DisciplineMantraRepository.mantrasByWordCount[count]?.size ?: 0 + val retrieved = mutableSetOf() + + // Retrieve all items in the first full pass + repeat(totalAvailable) { + val mantra = DisciplineMantraRepository.getNextMantra(count) + val isNew = retrieved.add(mantra) + assertTrue("Mantra should not repeat until pool of $totalAvailable is exhausted: '$mantra'", isNew) + } + + assertEquals(totalAvailable, retrieved.size) + } + } +} diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/FocusFeaturesEnhancementsTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/FocusFeaturesEnhancementsTest.kt similarity index 61% rename from Ported/app/src/test/kotlin/com/ujwal/focusgram/FocusFeaturesEnhancementsTest.kt rename to app/src/test/kotlin/com/ujwal/focusgram/FocusFeaturesEnhancementsTest.kt index 613d55b..44b59a6 100644 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/FocusFeaturesEnhancementsTest.kt +++ b/app/src/test/kotlin/com/ujwal/focusgram/FocusFeaturesEnhancementsTest.kt @@ -24,8 +24,11 @@ class FocusFeaturesEnhancementsTest { if (f1.exists()) return f1 val f2 = File("app/$relativePath") if (f2.exists()) return f2 - val f3 = File("Ported/app/$relativePath") + val stripped = relativePath.removePrefix("../") + val f3 = File(stripped) if (f3.exists()) return f3 + val f4 = File("app/$stripped") + if (f4.exists()) return f4 throw AssertionError("File not found: $relativePath") } @@ -82,19 +85,14 @@ class FocusFeaturesEnhancementsTest { val extensionScript = getFile("src/main/assets/extensions/ublock/focusgram_content.js").readText() for (script in listOf(hiderScript, extensionScript)) { - // Must blur individual carousel slides and media assertTrue("Must blur media elements individually without blurring parent aspect-ratio containers", script.contains("article img:not(header img)") || script.contains("article ul li")) - // Must preserve post header avatars assertTrue("Must keep post header avatars unblurred", script.contains("article header img") && script.contains("filter: none !important")) - // getBlurredTarget must detect carousel slides or media assertTrue("Must detect individual carousel slide or media in getBlurredTarget", script.contains("closest('li") || script.contains("carouselSlide") || script.contains("closest('article")) - // Must ignore carousel arrow navigation buttons assertTrue("Must ignore carousel navigation buttons", script.contains("button[aria-label=\"Next\"]")) - // revealTarget must clear inline filter permanently assertTrue("revealTarget must set filter: none !important", script.contains("target.style.setProperty('filter', 'none', 'important')")) } @@ -126,43 +124,41 @@ class FocusFeaturesEnhancementsTest { @Test fun testAdsterraDirectContainerAndPreloader() { val source = getFile("src/main/kotlin/com/ujwal/focusgram/feature/wellness/AdsterraAdScreen.kt").readText() - // Container element before script for reliable script invocation val ad1DivIdx = source.indexOf("container-e8a9b107824c939fb63d96c218c1336a") val ad1ScriptIdx = source.indexOf("https://pl18364273.effectivecpmnetwork.com/e8a9b107824c939fb63d96c218c1336a/invoke.js") assertTrue("Ad 1 container div must appear before the invoke script", ad1DivIdx != -1 && ad1ScriptIdx != -1 && ad1DivIdx < ad1ScriptIdx) - // Ad 2 isolated atOptions assertTrue("Ad 2 must have atOptions", source.contains("atOptions = {")) - // WebView settings assertTrue("Must allow mixed content", source.contains("WebSettings.MIXED_CONTENT_ALWAYS_ALLOW")) assertTrue("Must enable cacheMode LOAD_DEFAULT", source.contains("WebSettings.LOAD_DEFAULT")) assertTrue("Must sanitize User-Agent to remove ; wv", source.contains("replace(\"; wv\", \"\")")) assertTrue("Must handle window creation in WebChromeClient", source.contains("onCreateWindow")) - // Timer cycle restart support assertTrue("Must restart countdown on reload", source.contains("timerCycle")) - // AdPreloader assertTrue("Must provide AdPreloader with prewarm", source.contains("object AdPreloader") && source.contains("fun prewarm")) } @Test fun testStrictGatePrioritySequenceInFocusGramApp() { val source = getFile("src/main/kotlin/com/ujwal/focusgram/ui/FocusGramApp.kt").readText() - // Priority: AppLOCK -> Time limit reached -> Cooldown -> Breathe Gate -> Effort Friction challenge -> Set your intention + // Priority: AppLOCK -> Daily limit reached -> Session Exhausted -> Cooldown -> Breathe Gate -> Effort Friction challenge -> Set your intention val lockIdx = source.indexOf("if (shouldShowLock)") - val timeLimitIdx = source.indexOf("if ((isDailyLimitExhausted || isSessionExhausted)") + val dailyLimitIdx = source.indexOf("if (isDailyLimitExhausted && destination != AppDestination.ADSTERRA_REWARDED)") + val sessionExhaustedIdx = source.indexOf("if (isSessionExhausted && destination != AppDestination.ADSTERRA_REWARDED)") val cooldownIdx = source.indexOf("if (remainingCooldownSeconds > 0 && !hasPassedCooldownGate)") val breathGateIdx = source.indexOf("if (settings.showBreathGate && settings.breathGateSeconds > 0 && !hasPassedBreathGate)") val frictionIdx = source.indexOf("if (settings.effortFrictionEnabled && settings.wordChallengeCount > 0 && !hasPassedEffortFriction)") val sessionPickerIdx = source.indexOf("if (!hasPassedSessionPicker)") assertTrue("AppLock check must exist", lockIdx != -1) - assertTrue("Time limit exhausted check must exist", timeLimitIdx != -1) + assertTrue("Daily limit check must exist", dailyLimitIdx != -1) + assertTrue("Session exhausted check must exist", sessionExhaustedIdx != -1) assertTrue("Cooldown check must exist", cooldownIdx != -1) assertTrue("Breath gate check must exist", breathGateIdx != -1) assertTrue("Effort friction check must exist", frictionIdx != -1) assertTrue("Session picker check must exist", sessionPickerIdx != -1) - assertTrue("AppLock must be evaluated before Time Limit", lockIdx < timeLimitIdx) - assertTrue("Time Limit must be evaluated before Cooldown", timeLimitIdx < cooldownIdx) + assertTrue("AppLock must be evaluated before Daily Limit", lockIdx < dailyLimitIdx) + assertTrue("Daily Limit must be evaluated before Session Exhausted", dailyLimitIdx < sessionExhaustedIdx) + assertTrue("Session Exhausted must be evaluated before Cooldown", sessionExhaustedIdx < cooldownIdx) assertTrue("Cooldown must be evaluated before Breath Gate", cooldownIdx < breathGateIdx) assertTrue("Breath Gate must be evaluated before Effort Friction", breathGateIdx < frictionIdx) assertTrue("Effort Friction must be evaluated before Session Picker", frictionIdx < sessionPickerIdx) @@ -355,7 +351,6 @@ class FocusFeaturesEnhancementsTest { @Test fun testHomeVsProfileTabResolutionWithQueryParams() { - // Simulates the exact logic in NativeBottomNavBar fun getCleanPath(url: String): String { val afterScheme = if (url.contains("://")) url.substringAfter("://") else url val pathPart = if (afterScheme.contains('/')) afterScheme.substringAfter('/') else "" @@ -391,7 +386,7 @@ class FocusFeaturesEnhancementsTest { (lowerUrl.contains("/profile") || isSingleUserSegment) } - // Home URLs with parameters must always resolve to Home, NEVER Profile + // Query parameters on the root URL should never misclassify home feed as a profile page. assertTrue(resolveIsHome("https://www.instagram.com/")) assertFalse(resolveIsProfile("https://www.instagram.com/")) @@ -401,14 +396,12 @@ class FocusFeaturesEnhancementsTest { assertTrue(resolveIsHome("https://www.instagram.com/?theme=dark")) assertFalse(resolveIsProfile("https://www.instagram.com/?theme=dark")) - // Profile URLs assertFalse(resolveIsHome("https://www.instagram.com/zuck/")) assertTrue(resolveIsProfile("https://www.instagram.com/zuck/")) assertFalse(resolveIsHome("https://www.instagram.com/profile")) assertTrue(resolveIsProfile("https://www.instagram.com/profile")) - // Excluded pages (Explore, Reels, DMs, Posts) assertFalse(resolveIsProfile("https://www.instagram.com/explore/")) assertFalse(resolveIsProfile("https://www.instagram.com/reel/C7x90/")) assertFalse(resolveIsProfile("https://www.instagram.com/p/C7x90/")) @@ -483,5 +476,282 @@ class FocusFeaturesEnhancementsTest { val longAuthorFormatted = helper.formatTargetFilename("ujwaldeveloper12345", true) assertEquals("@ujwaldevelope - Focusgram.mp4", longAuthorFormatted) } + + @Test + fun testCommentGifsExclusionFromExploreBlur() { + val ublockScript = getFile("src/main/assets/extensions/ublock/focusgram_content.js").readText() + val contentHiderScript = getFile("src/main/assets/scripts/content_hider.js").readText() + + assertFalse("Explore blur must NOT blindly target all div[style*=\"grid\"]", + ublockScript.contains("div[style*=\"grid\"]:not([data-fg-reveal]):not(.fg-unblurred) img")) + assertFalse("Explore blur in content_hider must NOT blindly target all div[style*=\"grid\"]", + contentHiderScript.contains("div[style*=\"grid\"]:not([data-fg-reveal]):not(.fg-unblurred) img")) + + assertTrue("Must include rule preserving Giphy and comment GIFs unblurred", + ublockScript.contains("img[src*=\"giphy\"]") && ublockScript.contains("filter: none !important;")) + assertTrue("content_hider must include rule preserving Giphy and comment GIFs unblurred", + contentHiderScript.contains("img[src*=\"giphy\"]") && contentHiderScript.contains("filter: none !important;")) + + assertTrue("getBlurredTarget must ignore comment GIFs and comment inputs", + ublockScript.contains("el.closest('img[src*=\"giphy\"]")) + assertTrue("getBlurredTarget in content_hider must ignore comment GIFs and comment inputs", + contentHiderScript.contains("el.closest('img[src*=\"giphy\"]")) + } + + @Test + fun testReelsCounterStartsAtZeroWithInstantScrollUpdate() { + val ublockScript = getFile("src/main/assets/extensions/ublock/focusgram_content.js").readText() + val reelsScript = getFile("src/main/assets/reelhistory/reels.js").readText() + + assertTrue("focusgram_content.js must track isInitialReelOnTabEntry", + ublockScript.contains("isInitialReelOnTabEntry")) + assertTrue("focusgram_content.js must not count initial landing reel as watched", + ublockScript.contains("action: 'reelWatched'") && ublockScript.contains("type: 'meta'")) + assertTrue("reels.js must use responsive DWELL_MS <= 200ms", + reelsScript.contains("const DWELL_MS = 200;")) + } + + @Test + fun testPersistentNotificationTitleUsesSessionTimer() { + val serviceSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeService.kt").readText() + assertTrue("ScreenTimeService must format title as 'Session Timer: ...'", + serviceSource.contains("\"Session Timer: \$timeStr\"")) + assertFalse("ScreenTimeService must NOT toggle between Reels Focus and Instagram Focus in title", + serviceSource.contains("if (isReelsSession) \"Reels Focus: \$timeStr\" else \"Instagram Focus: \$timeStr\"")) + } + + @Test + fun testWallClockCooldownCalculationAcrossTimeJumps() { + val cooldownMinutes = 15 + val now = 1_000_000_000L + val lastSessionEndTimestamp = now - (12 * 60 * 1000L) // 12 minutes elapsed + + val targetExpiryMillis = lastSessionEndTimestamp + (cooldownMinutes * 60 * 1000L) + val remainingMillis = targetExpiryMillis - now + val remainingSeconds = if (remainingMillis > 0) ((remainingMillis + 999) / 1000).toInt() else 0 + + assertEquals("After 12 minutes of a 15-minute cooldown, exactly 3 minutes (180s) must remain", + 180, remainingSeconds) + + // After full 15 minutes elapsed + val pastTargetNow = lastSessionEndTimestamp + (16 * 60 * 1000L) + val expiredMillis = targetExpiryMillis - pastTargetNow + val expiredSeconds = if (expiredMillis > 0) ((expiredMillis + 999) / 1000).toInt() else 0 + assertEquals(0, expiredSeconds) + } + + @Test + fun testFocusGramNavigationDelegateWhitelistsFileSchemeForAppFiles() { + val navDelegateSource = getFile("src/main/kotlin/com/ujwal/focusgram/core/engine/FocusGramNavigationDelegate.kt").readText() + assertTrue("FocusGramNavigationDelegate must allow scheme == 'file' for internal files", + navDelegateSource.contains("scheme == \"file\"") && navDelegateSource.contains("appFilesPath")) + } + + @Test + fun testGhostModeMainToggleRequiresAdReward() { + val settingsSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/settings/SettingsScreen.kt").readText() + + assertTrue( + "GhostModeSubpage must accept onRequestAdReward parameter", + settingsSource.contains("private fun GhostModeSubpage(") && + settingsSource.contains("onRequestAdReward: (action: suspend () -> Unit, reason: String) -> Unit") + ) + + assertTrue( + "Main Ghost Mode toggle must request ad reward to enable or disable", + settingsSource.contains("repository.setGhostMode(newState)") && + settingsSource.contains("Please watch an ad in order to \$actionDesc Ghost Mode.") + ) + + assertTrue( + "Sub-toggles must update repository directly via coroutine scope without ad prompts", + settingsSource.contains("onCheckedChange = { scope.launch { repository.setGhostHideStorySeen(it) } }") && + settingsSource.contains("onCheckedChange = { scope.launch { repository.setGhostHideDirectSeen(it) } }") && + settingsSource.contains("onCheckedChange = { scope.launch { repository.setGhostHideTypingIndicator(it) } }") + ) + } + + @Test + fun testScreenTimeServiceShowsSelectedSessionTimeInSubtitle() { + val serviceSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeService.kt").readText() + assertTrue( + "ScreenTimeService must accept EXTRA_SESSION_DURATION_MINUTES", + serviceSource.contains("EXTRA_SESSION_DURATION_MINUTES = \"extra_session_duration_minutes\"") + ) + assertTrue( + "ScreenTimeService subtitle must include selected session duration", + serviceSource.contains("\"Session in progress â€Ē \$durationPart\"") + ) + assertTrue( + "ScreenTimeService pause tracking must format elapsed seconds accurately", + serviceSource.contains("\"Session Timer: \$timeStr (Paused)\"") + ) + } + + @Test + fun testBottomNavSessionProgressAnimationWiring() { + val navSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/navigation/NativeBottomNavBar.kt").readText() + assertTrue( + "NativeBottomNavBar must support sessionProgress parameter", + navSource.contains("sessionProgress: Float = 0f") + ) + assertTrue( + "NativeBottomNavBar must respect showSessionTimeInBottomBar toggle and draw perimeter path", + navSource.contains("settings.showSessionTimeInBottomBar") && + navSource.contains("currentSessionProgress > 0f") && + navSource.contains("Color(0xFFFFD700)") + ) + + val settingsSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/settings/SettingsScreen.kt").readText() + assertTrue( + "Settings Extras subpage must have 'Show session time in bottom bar' toggle", + settingsSource.contains("Show session time in bottom bar") && + settingsSource.contains("repository.setShowSessionTimeInBottomBar") + ) + } + + @Test + fun testActiveSessionPersistenceWiring() { + val appSource = getFile("src/main/kotlin/com/ujwal/focusgram/ui/FocusGramApp.kt").readText() + assertTrue( + "FocusGramApp must restore hasPassedSessionPicker from active session state", + appSource.contains("initialSettings.activeSessionIsActive && initialSettings.activeSessionRemainingSeconds > 0") + ) + assertTrue( + "FocusGramApp must save active session state to DataStore on session selection", + appSource.contains("settingsRepository.saveActiveSession(") + ) + assertTrue( + "FocusGramApp must clear active session state on session completion", + appSource.contains("settingsRepository.clearActiveSession()") + ) + assertTrue( + "FocusGramApp must restore reel history session count on startup if active session exists", + appSource.contains("restoreSession(initialSettings.activeSessionReelCodes, initialSettings.activeSessionReelsWatched)") + ) + } + + @Test + fun testReelsHistorySessionCountPersistenceAndDeduplication() { + val tempFile = File.createTempFile("reel_history_test", ".json") + tempFile.deleteOnExit() + + val history = com.ujwal.focusgram.feature.history.ReelHistory(tempFile) + assertEquals(0, history.sessionCount.value) + + // Simulate watching 3 unique reels + history.onWatched("code_reel_1001", "creator1", "Caption 1") + history.onWatched("code_reel_1002", "creator2", "Caption 2") + history.onWatched("code_reel_1003", "creator3", "Caption 3") + assertEquals(3, history.sessionCount.value) + + val savedCodes = history.getSessionCodes() + assertEquals(3, savedCodes.size) + + // Simulate app restart / new ReelHistory instance restoring session + val restoredHistory = com.ujwal.focusgram.feature.history.ReelHistory(tempFile) + assertEquals(0, restoredHistory.sessionCount.value) + + restoredHistory.restoreSession(savedCodes, 3) + assertEquals(3, restoredHistory.sessionCount.value) + + // Watching already watched reel should NOT increment session count + restoredHistory.onWatched("code_reel_1001", "creator1", "Caption 1") + assertEquals(3, restoredHistory.sessionCount.value) + + // Watching a new reel SHOULD increment session count + restoredHistory.onWatched("code_reel_1004", "creator4", "Caption 4") + assertEquals(4, restoredHistory.sessionCount.value) + + // Resetting session resets to 0 + restoredHistory.resetSession() + assertEquals(0, restoredHistory.sessionCount.value) + } + + @Test + fun testBottomNavBarSunsetColorsAndTransition() { + val navSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/navigation/NativeBottomNavBar.kt").readText() + assertTrue( + "NativeBottomNavBar must lerp golden colors to reddish-orange sunset colors as session progress increases", + navSource.contains("sunsetStart = lerp(Color(0xFFFFE57F)") && + navSource.contains("sunsetMid1 = lerp(Color(0xFFFFD700)") && + navSource.contains("sunsetMid2 = lerp(Color(0xFFFFB300)") && + navSource.contains("sunsetEnd = lerp(Color(0xFFFFA000)") + ) + } + + @Test + fun testCooldownGateOnlyShowsSkipOnDebugBuild() { + val cooldownSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/wellness/CooldownGateScreen.kt").readText() + assertTrue( + "CooldownGateScreen must guard skip button with BuildConfig.DEBUG", + cooldownSource.contains("if (com.ujwal.focusgram.BuildConfig.DEBUG)") && + cooldownSource.contains("Skip Cooldown (Debug)") + ) + } + + @Test + fun testDebugMenuControllerAndSkipToggle() { + val debugSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/diagnostics/DebugMenuScreen.kt").readText() + assertTrue( + "DebugMenuScreen must toggle between 'Skip Cooldowns & Daily Limits' and 'Don't Skip Cooldowns & Daily Limits'", + debugSource.contains("Don't Skip Cooldowns & Daily Limits") && + debugSource.contains("Skip Cooldowns & Daily Limits") && + debugSource.contains("isLimitsBypassed") + ) + assertTrue( + "DebugMenuScreen must support onAdjustSessionTime and session time adjustment buttons", + debugSource.contains("onAdjustSessionTime") && + debugSource.contains("Set Total Duration") && + debugSource.contains("Set Remaining") + ) + } + + @Test + fun testReelsHistoryClearDialogOptions() { + val historyScreenSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/history/ReelsHistoryScreen.kt").readText() + assertTrue( + "ReelsHistoryScreen must present clear options for Today, 3 Days, Last Week, This Month, and All History", + historyScreenSource.contains("ClearOptionRow(\n label = \"Today\"") || + historyScreenSource.contains("label = \"Today\"") + ) + assertTrue( + "ReelsHistoryScreen must offer 3 Days timeframe", + historyScreenSource.contains("label = \"3 Days\"") && + historyScreenSource.contains("reelHistory.clearPastDays(3)") + ) + assertTrue( + "ReelsHistoryScreen must offer Last Week timeframe", + historyScreenSource.contains("label = \"Last Week\"") && + historyScreenSource.contains("reelHistory.clearPastDays(7)") + ) + assertTrue( + "ReelsHistoryScreen must offer This Month timeframe", + historyScreenSource.contains("label = \"This Month\"") && + historyScreenSource.contains("reelHistory.clearThisMonth()") + ) + assertTrue( + "ReelsHistoryScreen must offer All History clear", + historyScreenSource.contains("label = \"All History\"") && + historyScreenSource.contains("reelHistory.clear()") + ) + } + + @Test + fun testScreenTimeScreenZeroDailyLimitHandling() { + val screenTimeSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeScreen.kt").readText() + assertTrue( + "ScreenTimeScreen must display 'Daily Limit (Off)' when daily limit is 0", + screenTimeSource.contains("if (dailyLimitMinutes > 0) \"Daily Limit (\${TimeFormatters.formatMinutes(dailyLimitMinutes)})\" else \"Daily Limit (Off)\"") + ) + assertTrue( + "ScreenTimeScreen must compute limitFraction as 0f when dailyLimitMinutes is 0 to avoid false 100% red bar", + screenTimeSource.contains("val limitFraction = if (dailyLimitMinutes > 0)") + ) + assertTrue( + "ScreenTimeScreen Mindful Insights must provide friendly message when daily limit is off", + screenTimeSource.contains("dailyLimitMinutes <= 0 -> \"You have no daily limit set.") + ) + } } diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/FocusSettingsTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/FocusSettingsTest.kt similarity index 69% rename from Ported/app/src/test/kotlin/com/ujwal/focusgram/FocusSettingsTest.kt rename to app/src/test/kotlin/com/ujwal/focusgram/FocusSettingsTest.kt index b80fdb7..7ab0c27 100644 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/FocusSettingsTest.kt +++ b/app/src/test/kotlin/com/ujwal/focusgram/FocusSettingsTest.kt @@ -16,29 +16,29 @@ class FocusSettingsTest { fun testDefaultSettingsIntegrity() { val settings = FocusSettings() - // Ads should default to true (blocked by uBlock) assertTrue(settings.noAds) - // Video autoplay default blocked assertTrue(settings.noAutoplay) - // Explore blurred by default, Feed and Reels unblurred by default assertTrue(settings.blurExplore) assertFalse(settings.blurReels) assertFalse(settings.blurFeed) assertEquals("tap", settings.unblurMode) assertTrue(settings.tapToUnblur) assertFalse(settings.requireLongPress) - // Suggested posts false by default to prevent feed blanking after ~9 posts + // Hiding suggested posts blanks the feed after ~9 items; keep disabled by default. assertFalse(settings.hideSuggestedPosts) - // Breathing gate enabled by default assertTrue(settings.showBreathGate) assertEquals(10, settings.breathGateSeconds) - // Block Reels swiping in DMs enabled by default assertTrue(settings.blockDmReelsSwiping) - // Initial user level - assertEquals(1, settings.userLevel) - assertEquals(0, settings.userXP) - // Portrait only enabled by default + assertEquals(5, settings.cooldownMinutes) + assertTrue(settings.showSessionTimeInBottomBar) assertTrue(settings.portraitOnly) + assertTrue(settings.notifyPersistent) + assertTrue(settings.notifyEvery5Minutes) + assertTrue(settings.sanitizeLinks) + assertFalse("Ghost Mode main toggle must default to OFF", settings.ghostModeEnabled) + assertTrue(settings.ghostHideStorySeen) + assertTrue(settings.ghostHideDirectSeen) + assertTrue(settings.ghostHideTypingIndicator) } @Test diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/IslandModeTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/IslandModeTest.kt similarity index 94% rename from Ported/app/src/test/kotlin/com/ujwal/focusgram/IslandModeTest.kt rename to app/src/test/kotlin/com/ujwal/focusgram/IslandModeTest.kt index e5e97f7..b5bfdc0 100644 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/IslandModeTest.kt +++ b/app/src/test/kotlin/com/ujwal/focusgram/IslandModeTest.kt @@ -45,13 +45,10 @@ class IslandModeTest { @Test fun testStandardNavigationRoutesResolveToNormal() { - // Feed assertEquals(IslandMode.NORMAL, IslandMode.fromUrl("https://www.instagram.com/")) assertEquals(IslandMode.NORMAL, IslandMode.fromUrl("https://www.instagram.com")) - // Explore / Search assertEquals(IslandMode.NORMAL, IslandMode.fromUrl("https://www.instagram.com/explore/")) assertEquals(IslandMode.NORMAL, IslandMode.fromUrl("https://www.instagram.com/explore")) - // Profile assertEquals(IslandMode.NORMAL, IslandMode.fromUrl("https://www.instagram.com/someuser/")) } @@ -64,20 +61,15 @@ class IslandModeTest { @Test fun testHiddenRoutesResolveToHidden() { - // Login / Signup / Accounts routes assertEquals(IslandMode.HIDDEN, IslandMode.fromUrl("https://www.instagram.com/accounts/login/")) assertEquals(IslandMode.HIDDEN, IslandMode.fromUrl("https://www.instagram.com/accounts/emailsignup/")) assertEquals(IslandMode.HIDDEN, IslandMode.fromUrl("https://www.instagram.com/login/")) assertEquals(IslandMode.HIDDEN, IslandMode.fromUrl("https://www.instagram.com/signup/")) - // Direct conversation thread and Inbox assertEquals(IslandMode.HIDDEN, IslandMode.fromUrl("https://www.instagram.com/direct/inbox/")) assertEquals(IslandMode.HIDDEN, IslandMode.fromUrl("https://www.instagram.com/direct/inbox")) assertEquals(IslandMode.HIDDEN, IslandMode.fromUrl("https://www.instagram.com/direct/t/178414000000000/")) - // Comments route assertEquals(IslandMode.HIDDEN, IslandMode.fromUrl("https://www.instagram.com/p/C-xyz123/comments/")) - // Stories route assertEquals(IslandMode.HIDDEN, IslandMode.fromUrl("https://www.instagram.com/stories/username/12345/")) - // Create post route assertEquals(IslandMode.HIDDEN, IslandMode.fromUrl("https://www.instagram.com/create/style/")) assertEquals(IslandMode.HIDDEN, IslandMode.fromUrl("https://www.instagram.com/create/select/")) } diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/LegacyMigrationLogicTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/LegacyMigrationLogicTest.kt similarity index 100% rename from Ported/app/src/test/kotlin/com/ujwal/focusgram/LegacyMigrationLogicTest.kt rename to app/src/test/kotlin/com/ujwal/focusgram/LegacyMigrationLogicTest.kt diff --git a/app/src/test/kotlin/com/ujwal/focusgram/NavigationContainmentTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/NavigationContainmentTest.kt new file mode 100644 index 0000000..55ce4df --- /dev/null +++ b/app/src/test/kotlin/com/ujwal/focusgram/NavigationContainmentTest.kt @@ -0,0 +1,107 @@ +package com.ujwal.focusgram + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * NavigationContainmentTest + * Validates domain whitelisting, authentication domain support, and external escaping defense. + */ +class NavigationContainmentTest { + + private val allowedHosts = listOf( + "instagram.com", + "instagr.am", + "cdninstagram.com", + "fbcdn.net", + "facebook.com", + "m.facebook.com", + "accountscenter.instagram.com", + "accountscenter.facebook.com" + ) + + private fun isAllowedHost(host: String): Boolean { + val lower = host.lowercase() + return allowedHosts.any { allowed -> + lower == allowed || lower.endsWith(".$allowed") + } + } + + @Test + fun testAllowedInstagramHosts() { + assertTrue(isAllowedHost("instagram.com")) + assertTrue(isAllowedHost("www.instagram.com")) + assertTrue(isAllowedHost("help.instagram.com")) + assertTrue(isAllowedHost("about.instagram.com")) + assertTrue(isAllowedHost("instagr.am")) + assertTrue(isAllowedHost("scontent.cdninstagram.com")) + assertTrue(isAllowedHost("static.xx.fbcdn.net")) + } + + @Test + fun testAllowedAuthHosts() { + assertTrue(isAllowedHost("facebook.com")) + assertTrue(isAllowedHost("m.facebook.com")) + assertTrue(isAllowedHost("accountscenter.instagram.com")) + assertTrue(isAllowedHost("accountscenter.facebook.com")) + } + + @Test + fun testDisallowedExternalHosts() { + assertFalse(isAllowedHost("google.com")) + assertFalse(isAllowedHost("youtube.com")) + assertFalse(isAllowedHost("tiktok.com")) + assertFalse(isAllowedHost("malicious-fake-instagram.com")) + assertFalse(isAllowedHost("notinstagram.com")) + assertFalse(isAllowedHost("x.com")) + } + + @Test + fun testInternalSchemesAllowed() { + val internalSchemes = listOf("resource", "about", "data", "blob", "javascript") + internalSchemes.forEach { scheme -> + val isInternal = scheme == "resource" || scheme == "about" || scheme == "data" || scheme == "blob" || scheme == "javascript" + assertTrue(isInternal) + } + } + + @Test + fun testDirectMessageBackNavigationHierarchy() { + fun resolveDmBackTarget(url: String): String? { + val path = url.removePrefix("https://www.instagram.com") + .removePrefix("http://www.instagram.com") + .substringBefore('?') + .substringBefore('#') + .trimEnd('/') + .ifEmpty { "/" } + + val isDmThread = path.startsWith("/direct/t/") || path.startsWith("/direct/threads/") || (path.startsWith("/direct/") && path != "/direct/inbox" && path != "/direct") + val isDmInbox = path == "/direct" || path == "/direct/inbox" + val isRootHome = path == "/" || path.isEmpty() + + return when { + isDmThread -> "https://www.instagram.com/direct/inbox/" + isDmInbox -> "https://www.instagram.com/" + isRootHome -> null // Allow predictive back / system launcher exit + else -> "https://www.instagram.com/" + } + } + + // 1. Thread level navigates to inbox + org.junit.Assert.assertEquals("https://www.instagram.com/direct/inbox/", resolveDmBackTarget("https://www.instagram.com/direct/t/12345/")) + org.junit.Assert.assertEquals("https://www.instagram.com/direct/inbox/", resolveDmBackTarget("https://www.instagram.com/direct/t/12345")) + org.junit.Assert.assertEquals("https://www.instagram.com/direct/inbox/", resolveDmBackTarget("https://www.instagram.com/direct/threads/999/")) + org.junit.Assert.assertEquals("https://www.instagram.com/direct/inbox/", resolveDmBackTarget("https://www.instagram.com/direct/new/")) + + // 2. Inbox navigates to Home root + org.junit.Assert.assertEquals("https://www.instagram.com/", resolveDmBackTarget("https://www.instagram.com/direct/inbox/")) + org.junit.Assert.assertEquals("https://www.instagram.com/", resolveDmBackTarget("https://www.instagram.com/direct/inbox")) + org.junit.Assert.assertEquals("https://www.instagram.com/", resolveDmBackTarget("https://www.instagram.com/direct/")) + org.junit.Assert.assertEquals("https://www.instagram.com/", resolveDmBackTarget("https://www.instagram.com/direct")) + + // 3. Home root returns null to let Android system back minimize/exit + org.junit.Assert.assertNull(resolveDmBackTarget("https://www.instagram.com/")) + org.junit.Assert.assertNull(resolveDmBackTarget("https://www.instagram.com")) + } +} diff --git a/app/src/test/kotlin/com/ujwal/focusgram/OnboardingScreenTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/OnboardingScreenTest.kt new file mode 100644 index 0000000..7551dfe --- /dev/null +++ b/app/src/test/kotlin/com/ujwal/focusgram/OnboardingScreenTest.kt @@ -0,0 +1,46 @@ +package com.ujwal.focusgram + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import java.io.File + +/** + * OnboardingScreenTest + * Validates Onboarding screen implementation integrity, resource safety, + * and page layout structure. + */ +class OnboardingScreenTest { + + private fun getOnboardingSource(): String { + val file = File("src/main/kotlin/com/ujwal/focusgram/feature/onboarding/OnboardingScreen.kt") + if (file.exists()) return file.readText() + val altFile = File("app/src/main/kotlin/com/ujwal/focusgram/feature/onboarding/OnboardingScreen.kt") + if (altFile.exists()) return altFile.readText() + error("Could not find OnboardingScreen.kt at ${file.absolutePath} or ${altFile.absolutePath}") + } + + @Test + fun testNoAdaptiveIconInPainterResource() { + val source = getOnboardingSource() + // Compose painterResource throws IllegalArgumentException when passed XML adaptive icons. + // It must NOT be called on R.mipmap.ic_launcher. + assertFalse( + "OnboardingScreen must not pass R.mipmap.ic_launcher to painterResource (causes crash on adaptive icon XML)", + source.contains("painterResource(id = R.mipmap.ic_launcher)") || + source.contains("painterResource(R.mipmap.ic_launcher)") + ) + assertTrue( + "OnboardingScreen must use safe rasterization via AppIconManager.getIconBitmap", + source.contains("AppIconManager.getIconBitmap(context, R.mipmap.ic_launcher)") + ) + } + + @Test + fun testOnboardingPageStructure() { + val source = getOnboardingSource() + assertTrue("Total pages must be 6", source.contains("val totalPages = 6")) + assertTrue("WelcomeSlide must be page 0", source.contains("0 -> WelcomeSlide()")) + assertTrue("LoginSecuritySlide must be last page (5)", source.contains("5 -> LoginSecuritySlide()")) + } +} diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/ReelLockExtensionAssetsTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/ReelLockExtensionAssetsTest.kt similarity index 100% rename from Ported/app/src/test/kotlin/com/ujwal/focusgram/ReelLockExtensionAssetsTest.kt rename to app/src/test/kotlin/com/ujwal/focusgram/ReelLockExtensionAssetsTest.kt diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/ReelsHistoryLogicTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/ReelsHistoryLogicTest.kt similarity index 50% rename from Ported/app/src/test/kotlin/com/ujwal/focusgram/ReelsHistoryLogicTest.kt rename to app/src/test/kotlin/com/ujwal/focusgram/ReelsHistoryLogicTest.kt index 5f9dd61..6efeff4 100644 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/ReelsHistoryLogicTest.kt +++ b/app/src/test/kotlin/com/ujwal/focusgram/ReelsHistoryLogicTest.kt @@ -22,7 +22,7 @@ class ReelsHistoryLogicTest { id = "1", url = url, title = "Sample Reel", - visitedAt = now - 30_000L // 30 seconds ago + visitedAt = now - 30_000L ) val hasRecentDuplicate = recentItem.url == url && kotlin.math.abs(now - recentItem.visitedAt) <= 60_000L @@ -32,7 +32,7 @@ class ReelsHistoryLogicTest { id = "2", url = url, title = "Sample Reel", - visitedAt = now - 90_000L // 90 seconds ago + visitedAt = now - 90_000L ) val hasOlderDuplicate = olderItem.url == url && kotlin.math.abs(now - olderItem.visitedAt) <= 60_000L assertFalse(hasOlderDuplicate) @@ -53,7 +53,7 @@ class ReelsHistoryLogicTest { id = "$i", url = "https://www.instagram.com/reel/test$i/", title = "Reel $i", - visitedAt = now - (i * (oneDayMs / 3)) // Distributed across ~4.6 days + visitedAt = now - (i * (oneDayMs / 3)) ) } @@ -73,7 +73,6 @@ class ReelsHistoryLogicTest { assertEquals(0, history.sessionCount.value) assertEquals(0, history.items.value.size) - // Watch reel 1 history.onWatched("abc12345", "photographer", "Nice view") assertEquals(1, history.sessionCount.value) assertEquals(1, history.items.value.size) @@ -82,38 +81,110 @@ class ReelsHistoryLogicTest { assertEquals("Nice view", history.items.value[0].caption) assertEquals(1, history.items.value[0].views) - // Watch reel 1 again (views increment, session count remains 1 unique) + // Re-watching increments view count without inflating unique session item count. history.onWatched("abc12345", "", "") assertEquals(1, history.sessionCount.value) assertEquals(1, history.items.value.size) assertEquals(2, history.items.value[0].views) assertEquals("photographer", history.items.value[0].author) - // Meta update history.onMeta("abc12345", "photographer_pro", "Updated caption") assertEquals("photographer_pro", history.items.value[0].author) assertEquals("Updated caption", history.items.value[0].caption) - // Watch reel 2 history.onWatched("def67890", "artist", "Artwork") assertEquals(2, history.sessionCount.value) assertEquals(2, history.items.value.size) - // Delete reel 1 history.delete("abc12345") assertEquals(1, history.items.value.size) assertEquals("def67890", history.items.value[0].code) - // Clear history history.clear() assertEquals(0, history.items.value.size) assertEquals(2, history.sessionCount.value) - // Reset session history.resetSession() assertEquals(0, history.sessionCount.value) } finally { tempFile.delete() } } + + @Test + fun testReelDeduplicationByAuthorAndCaptionAndPlaceholder() { + val tempFile = java.io.File.createTempFile("reel_dedup_test", ".json") + try { + val history = com.ujwal.focusgram.feature.history.ReelHistory(tempFile) + + // Step 1: Initial watch recorded with username placeholder as code + history.onWatched("plainwalk_app", "plainwalk_app", "How about a little side trip?") + assertEquals(1, history.items.value.size) + assertEquals("plainwalk_app", history.items.value[0].code) + assertEquals(1, history.items.value[0].views) + + // Step 2: Later watch with real Instagram shortcode for the same reel + history.onWatched("DF5a_b8z1", "plainwalk_app", "How about a little side trip?") + // Must consolidate to 1 item, upgrade code, increment view count to 2, and remove old entry + assertEquals(1, history.items.value.size) + assertEquals("DF5a_b8z1", history.items.value[0].code) + assertEquals(2, history.items.value[0].views) + assertEquals("plainwalk_app", history.items.value[0].author) + + // Step 3: Another watch with same author and caption moves to top with views=3 + history.onWatched("DF5a_b8z1", "plainwalk_app", "How about a little side trip?") + assertEquals(1, history.items.value.size) + assertEquals(3, history.items.value[0].views) + + // Step 4: Delete matching cleans up the entry + history.deleteMatching("DF5a_b8z1", "plainwalk_app", "How about a little side trip?") + assertEquals(0, history.items.value.size) + } finally { + tempFile.delete() + } + } + + @Test + fun testReelHistoryClearTimeframes() { + val tempFile = java.io.File.createTempFile("reel_clear_timeframe_test", ".json") + try { + val history = com.ujwal.focusgram.feature.history.ReelHistory(tempFile) + val now = System.currentTimeMillis() + val dayMs = 24 * 60 * 60 * 1000L + + // Populate test reels with known lastSeen offsets + // Entry 1: today + history.onWatched("reel_today_1", "creator1", "Today Reel") + // Entry 2: 2 days ago + history.onWatched("reel_2days_ago", "creator2", "2 Days Ago Reel") + // Entry 3: 5 days ago + history.onWatched("reel_5days_ago", "creator3", "5 Days Ago Reel") + // Entry 4: 10 days ago + history.onWatched("reel_10days_ago", "creator4", "10 Days Ago Reel") + + assertEquals(4, history.items.value.size) + + // Test clearToday: removes entries from today + history.clearToday() + // Since onWatched sets System.currentTimeMillis(), all 4 above were actually just watched today + // So clearToday() removes all 4 + assertEquals(0, history.items.value.size) + + // Test clearing specific past days with simulated entries + val cal = java.util.Calendar.getInstance() + history.onWatched("reel_new_today", "creatorA", "Today") + assertEquals(1, history.items.value.size) + + history.clearPastDays(3) + assertEquals(0, history.items.value.size) + + history.onWatched("reel_new_today_2", "creatorB", "Today 2") + assertEquals(1, history.items.value.size) + + history.clearThisMonth() + assertEquals(0, history.items.value.size) + } finally { + tempFile.delete() + } + } } diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/ScreenTimeTrackerTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/ScreenTimeTrackerTest.kt similarity index 84% rename from Ported/app/src/test/kotlin/com/ujwal/focusgram/ScreenTimeTrackerTest.kt rename to app/src/test/kotlin/com/ujwal/focusgram/ScreenTimeTrackerTest.kt index 70e686b..8e345d4 100644 --- a/Ported/app/src/test/kotlin/com/ujwal/focusgram/ScreenTimeTrackerTest.kt +++ b/app/src/test/kotlin/com/ujwal/focusgram/ScreenTimeTrackerTest.kt @@ -13,7 +13,7 @@ class ScreenTimeTrackerTest { @Test fun testSecondsToMinutesFormatting() { - val seconds = 3665 // 1 hour, 1 minute, 5 seconds + val seconds = 3665 val hours = seconds / 3600 val minutes = (seconds % 3600) / 60 @@ -27,13 +27,13 @@ class ScreenTimeTrackerTest { @Test fun testDailyLimitExceededPredicate() { val dailyLimitMinutes = 60 - val limitSeconds = dailyLimitMinutes * 60 // 3600s + val limitSeconds = dailyLimitMinutes * 60 - val underLimitSeconds = 3540 // 59 mins + val underLimitSeconds = 3540 val isBreachedUnder = underLimitSeconds >= limitSeconds assertFalse(isBreachedUnder) - val exactLimitSeconds = 3600 // 60 mins + val exactLimitSeconds = 3600 val isBreachedExact = exactLimitSeconds >= limitSeconds assertTrue(isBreachedExact) @@ -46,7 +46,7 @@ class ScreenTimeTrackerTest { fun testRemainingQuotaCalculation() { val limitMinutes = 45 val limitSeconds = limitMinutes * 60 - val usedSeconds = 1800 // 30 mins + val usedSeconds = 1800 val remainingSeconds = (limitSeconds - usedSeconds).coerceAtLeast(0) assertEquals(900, remainingSeconds) diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/SnapshotsEntityTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/SnapshotsEntityTest.kt similarity index 100% rename from Ported/app/src/test/kotlin/com/ujwal/focusgram/SnapshotsEntityTest.kt rename to app/src/test/kotlin/com/ujwal/focusgram/SnapshotsEntityTest.kt diff --git a/app/src/test/kotlin/com/ujwal/focusgram/TwoTierLimitsAndEnhancementsTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/TwoTierLimitsAndEnhancementsTest.kt new file mode 100644 index 0000000..5ffd8c1 --- /dev/null +++ b/app/src/test/kotlin/com/ujwal/focusgram/TwoTierLimitsAndEnhancementsTest.kt @@ -0,0 +1,313 @@ +package com.ujwal.focusgram + +import com.ujwal.focusgram.core.model.FocusSettings +import com.ujwal.focusgram.core.utils.TimeFormatters +import com.ujwal.focusgram.feature.history.ReelEntry +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import java.io.File + +class TwoTierLimitsAndEnhancementsTest { + + private fun getFile(relPath: String): File { + val root = File(".").canonicalFile + return if (root.name == "app") File(root.parentFile, "app/$relPath") else File(root, "app/$relPath") + } + + @Test + fun testTimeFormattersNeverShowsRawMinutesOver59() { + assertEquals("4h 30m", TimeFormatters.formatMinutes(270)) + assertEquals("1h", TimeFormatters.formatMinutes(60)) + assertEquals("1h 15m", TimeFormatters.formatMinutes(75)) + assertEquals("45m", TimeFormatters.formatMinutes(45)) + assertEquals("0m", TimeFormatters.formatMinutes(0)) + assertEquals("0m", TimeFormatters.formatMinutes(-5)) + } + + @Test + fun testTimeFormattersSecondsFormatting() { + assertEquals("00:00", TimeFormatters.formatSeconds(0)) + assertEquals("00:45", TimeFormatters.formatSeconds(45)) + assertEquals("05:30", TimeFormatters.formatSeconds(330)) + assertEquals("1:05:30", TimeFormatters.formatSeconds(3930)) + } + + @Test + fun testReelsWatchedCounterStartsAtZeroAndUpdatesInstantlyOnScroll() { + val contentScript = getFile("src/main/assets/extensions/ublock/focusgram_content.js").readText() + val reelsScript = getFile("src/main/assets/reelhistory/reels.js").readText() + + assertTrue("focusgram_content.js must track isInitialReelOnTabEntry", + contentScript.contains("let isInitialReelOnTabEntry = true;")) + assertTrue("focusgram_content.js must post type: 'meta' for initial reel to avoid starting at 1", + contentScript.contains("isInitialReelOnTabEntry = false;") && + contentScript.contains("action: 'reelWatched'") && + contentScript.contains("type: 'meta'")) + assertFalse("focusgram_content.js must NOT have dwell timer delay for reel scroll updates", + contentScript.contains("reelWatchedDwellTimer")) + + assertTrue("reels.js must use responsive DWELL_MS (200ms)", + reelsScript.contains("const DWELL_MS = 200;")) + assertTrue("reels.js must skip initial mount reel", + reelsScript.contains("isInitialReelMount")) + } + + @Test + fun testReelsAdBlockingSelectorsAndRules() { + val contentJs = getFile("src/main/assets/extensions/ublock/content.js").readText() + val focusgramJs = getFile("src/main/assets/extensions/ublock/focusgram_content.js").readText() + + assertTrue("content.js must check strict ad CTAs including 'shop now', 'install now', 'learn more'", + contentJs.contains("shop now") && contentJs.contains("install now") && contentJs.contains("learn more")) + assertTrue("content.js must detect sponsored keywords and ads", + contentJs.contains("data-fg-reel-ad") && contentJs.contains("findReelCardForVideo")) + assertTrue("focusgram_content.js must check STRICT ad CTAs and mute/pause/skip ad reels", + focusgramJs.contains("strictAdCtas") && focusgramJs.contains("data-fg-reel-ad")) + } + + @Test + fun testScreenTimeServicePauseAndResumeSupport() { + val serviceSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/screentime/ScreenTimeService.kt").readText() + assertTrue("ScreenTimeService must define ACTION_PAUSE", + serviceSource.contains("ACTION_PAUSE = \"com.ujwal.focusgram.action.PAUSE_SESSION\"")) + assertTrue("ScreenTimeService must define ACTION_RESUME", + serviceSource.contains("ACTION_RESUME = \"com.ujwal.focusgram.action.RESUME_SESSION\"")) + assertTrue("ScreenTimeService must pause tracking and update notification when paused", + serviceSource.contains("pauseTracking()") && serviceSource.contains("(Paused)")) + } + + @Test + fun testFocusGramAppPausesTimerOutsideMainWebview() { + val appSource = getFile("src/main/kotlin/com/ujwal/focusgram/ui/FocusGramApp.kt").readText() + assertTrue("FocusGramApp must pause ticker when destination != MAIN_WEBVIEW", + appSource.contains("destination == AppDestination.MAIN_WEBVIEW")) + assertTrue("FocusGramApp must send pauseSession when destination != MAIN_WEBVIEW", + appSource.contains("ScreenTimeService.pauseSession(context")) + } + + @Test + fun testReelsHistoryClientSideDeduplication() { + fun extractReelCode(entry: ReelEntry): String { + val cleanCode = entry.code.trim() + .removePrefix("reel_") + .removePrefix("/") + .removeSuffix("/") + .substringAfterLast("/reel/") + .substringAfterLast("/reels/") + .substringBefore("?") + .substringBefore("/") + .trim() + .lowercase() + + val ignoredCodes = setOf("reels", "reel", "videos", "audio", "clips", "home", "explore", "direct", "stories", "") + return if (cleanCode.length >= 4 && cleanCode !in ignoredCodes) { + cleanCode + } else { + val cleanAuthor = entry.author.trim().lowercase().removePrefix("@") + val cleanCaption = entry.caption.trim().lowercase() + if (cleanAuthor.isNotBlank() || cleanCaption.isNotBlank()) { + "${cleanAuthor}___${cleanCaption.take(60)}" + } else { + entry.url.trim().substringBefore("?").trimEnd('/') + } + } + } + + val entries = listOf( + ReelEntry("C9abc123", "alice", "Cool post", 1000L, 1), + ReelEntry("reel_C9abc123", "alice", "Cool post", 1005L, 2), + ReelEntry("/C9abc123/", "alice", "Cool post", 1010L, 1), + ReelEntry("", "bob", "Morning coffee", 1020L, 1), + ReelEntry("reels", "bob", "Morning coffee", 1030L, 2), + ReelEntry("D4xyz999", "charlie", "Different post", 1040L, 1) + ) + + val deduplicated = entries.distinctBy { extractReelCode(it) } + assertEquals(3, deduplicated.size) + assertEquals("C9abc123", deduplicated[0].code) + assertEquals("bob", deduplicated[1].author) + assertEquals("D4xyz999", deduplicated[2].code) + } + + @Test + fun testDailyLimitSettingsLockoutRules() { + // Rule: Locked if limit is already hit + fun isDailyLimitLocked(dailyLimitMinutes: Int, todaySeconds: Int): Boolean { + val baseDailyLimitSec = dailyLimitMinutes * 60 + val isDailyLimitHit = dailyLimitMinutes > 0 && todaySeconds >= baseDailyLimitSec + // Rule: Locked if within 5m (300s), but ONLY if total app limit is ABOVE 15 minutes + val isNearDailyLimit = dailyLimitMinutes > 15 && (baseDailyLimitSec - todaySeconds) in 0..300 + return dailyLimitMinutes > 0 && (isDailyLimitHit || isNearDailyLimit) + } + + // Limit is 60m (3600s). Today usage is 3400s (56m40s) -> within 5m -> locked + assertTrue(isDailyLimitLocked(dailyLimitMinutes = 60, todaySeconds = 3400)) + + // Limit is 60m (3600s). Today usage is 3000s (50m) -> remaining 10m -> NOT locked + assertFalse(isDailyLimitLocked(dailyLimitMinutes = 60, todaySeconds = 3000)) + + // Limit is 60m (3600s). Today usage is 3700s (hit) -> locked + assertTrue(isDailyLimitLocked(dailyLimitMinutes = 60, todaySeconds = 3700)) + + // Limit is 15m (900s). Rule says proximity ONLY activates if total limit > 15 min! + // At 14m (840s, remaining 60s <= 300s), proximity rule must NOT activate because limit <= 15 + assertFalse(isDailyLimitLocked(dailyLimitMinutes = 15, todaySeconds = 840)) + + // At 15m (900s), hit rule DOES activate + assertTrue(isDailyLimitLocked(dailyLimitMinutes = 15, todaySeconds = 900)) + + // Off (0m) -> never locked + assertFalse(isDailyLimitLocked(dailyLimitMinutes = 0, todaySeconds = 5000)) + } + + @Test + fun testTwoTierExtensionsArchitectureSeparation() { + val appSource = getFile("src/main/kotlin/com/ujwal/focusgram/ui/FocusGramApp.kt").readText() + + // Daily limit gate must be separate from Session Exhausted gate + assertTrue("FocusGramApp must render DailyLimitGateScreen", + appSource.contains("DailyLimitGateScreen(")) + assertTrue("FocusGramApp must render SessionExhaustedScreen", + appSource.contains("SessionExhaustedScreen(")) + + // Session extensions: 3 extensions of 3 min + assertTrue("Session extensions must allow 3 extensions", + appSource.contains("sessionExtensionsRemaining = (3 - sessionExtensionsUsed).coerceAtLeast(0)")) + assertTrue("Session extension must add 3 minutes", + appSource.contains("sessionRemainingSeconds += 3 * 60")) + + // Daily limit extensions: 2 extensions of 5 min + assertTrue("Daily extensions must allow 2 extensions", + appSource.contains("dailyExtensionsRemaining = (2 - dailyExtensionsUsed).coerceAtLeast(0)")) + assertTrue("Daily limit extension must add 5 minutes", + appSource.contains("extendDailyLimit5Minutes")) + } + + @Test + fun testReelsAdBlockingDoesNotTargetFeedContainersOrSetZeroHeight() { + val contentJs = getFile("src/main/assets/extensions/ublock/content.js").readText() + val focusgramJs = getFile("src/main/assets/extensions/ublock/focusgram_content.js").readText() + + // Card should NOT target section, main, or parent divs > 0.4 + assertFalse("content.js findReelCardForVideo must NOT select section or container div", + contentJs.contains("article, section, [role=\"region\"]")) + assertFalse("content.js must NOT set height 0px on reel card", + contentJs.contains("card.style.setProperty('height', '0px'")) + + // focusgram_content.js must NOT have stray closing brace + assertFalse("focusgram_content.js must not contain stray unmatched closing brace in CSS", + focusgramJs.contains("text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85) !important;\n }\n\n }")) + } + + @Test + fun testDebugBypassOptionsForCooldownAndDailyLimit() { + val dailyGateSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/session/DailyLimitGateScreen.kt").readText() + val sessionGateSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/session/SessionExhaustedScreen.kt").readText() + val cooldownGateSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/wellness/CooldownGateScreen.kt").readText() + val appSource = getFile("src/main/kotlin/com/ujwal/focusgram/ui/FocusGramApp.kt").readText() + + assertTrue("DailyLimitGateScreen must define onDebugBypass parameter", + dailyGateSource.contains("onDebugBypass: (() -> Unit)? = null")) + assertTrue("DailyLimitGateScreen must show Skip Daily Limit (Debug) button", + dailyGateSource.contains("Skip Daily Limit (Debug)")) + + assertTrue("SessionExhaustedScreen must define onDebugBypass parameter", + sessionGateSource.contains("onDebugBypass: (() -> Unit)? = null")) + assertTrue("SessionExhaustedScreen must show Skip Session Limit (Debug) button", + sessionGateSource.contains("Skip Session Limit (Debug)")) + + assertTrue("CooldownGateScreen must support skipping cooldown", + cooldownGateSource.contains("Skip Cooldown")) + + assertTrue("FocusGramApp must track isDebugDailyLimitBypassed", + appSource.contains("isDebugDailyLimitBypassed")) + } + + @Test + fun testAboutDialogSafeIconRasterization() { + val settingsSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/settings/SettingsScreen.kt").readText() + assertFalse("SettingsScreen must not pass R.mipmap.ic_launcher to painterResource (causes crash on adaptive icon XML)", + settingsSource.contains("painterResource(R.mipmap.ic_launcher)") || + settingsSource.contains("painterResource(id = R.mipmap.ic_launcher)")) + assertTrue("SettingsScreen must use AppIconManager.getIconBitmap for the About dialog logo", + settingsSource.contains("AppIconManager.getIconBitmap(context, R.mipmap.ic_launcher)")) + } + + @Test + fun testDebugMenuInSettingsScreen() { + val settingsSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/settings/SettingsScreen.kt").readText() + val appSource = getFile("src/main/kotlin/com/ujwal/focusgram/ui/FocusGramApp.kt").readText() + assertTrue("SettingsScreen must have DEVELOPER DIAGNOSTICS section", + settingsSource.contains("DEVELOPER DIAGNOSTICS")) + assertTrue("SettingsScreen must contain Debug Diagnostics Menu row", + settingsSource.contains("Debug Diagnostics Menu")) + assertTrue("FocusGramApp must route onNavigateDebugMenu to AppDestination.DEBUG_MENU", + appSource.contains("onNavigateDebugMenu = { destination = AppDestination.DEBUG_MENU }")) + } + + @Test + fun testExtrasSubpageCategorization() { + val settingsSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/settings/SettingsScreen.kt").readText() + assertTrue("SettingsScreen ExtrasSubpage must have OTHERS section title", + settingsSource.contains("IOSSectionTitle(title = \"OTHERS\")")) + assertFalse("SettingsScreen ExtrasSubpage must no longer have INSTAGRAM section title", + settingsSource.contains("IOSSectionTitle(title = \"INSTAGRAM\")")) + + val othersIndex = settingsSource.indexOf("IOSSectionTitle(title = \"OTHERS\")") + val portraitIndex = settingsSource.indexOf("title = \"Portrait Only\"", othersIndex) + val igSettingsIndex = settingsSource.indexOf("title = \"Instagram Account Settings\"", othersIndex) + + assertTrue("Portrait Only must be in OTHERS section", portraitIndex > othersIndex) + assertTrue("Portrait Only must be placed above Instagram Account Settings", portraitIndex < igSettingsIndex) + } + + @Test + fun testCupertinoWheelPickerAlignmentAndPrecision() { + val onboardingSource = getFile("src/main/kotlin/com/ujwal/focusgram/feature/onboarding/OnboardingScreen.kt").readText() + val pickerSource = getFile("src/main/kotlin/com/ujwal/focusgram/core/ui/components/CupertinoWheelPicker.kt").readText() + + assertFalse("OnboardingScreen must not force fixed height(160.dp) on CupertinoWheelPicker", + onboardingSource.contains(".height(160.dp)")) + assertTrue("CupertinoWheelPicker must compute centered index by finding visible item closest to viewport center", + pickerSource.contains("abs((item.offset + item.size / 2) - viewportCenter)")) + } + + @Test + fun testScriptsStrictFeatureDeactivation() { + val fetchJs = getFile("src/main/assets/scripts/fetch_interceptor.js").readText() + val nativeFeelJs = getFile("src/main/assets/scripts/native_feel.js").readText() + val contentJs = getFile("src/main/assets/extensions/ublock/focusgram_content.js").readText() + + assertTrue("fetch_interceptor.js must define cleanupStorySearch", + fetchJs.contains("function cleanupStorySearch()")) + assertTrue("fetch_interceptor.js must listen for FOCUSGRAM_FILTER_UPDATE", + fetchJs.contains("FOCUSGRAM_FILTER_UPDATE")) + + assertTrue("native_feel.js must define cleanupBioStylist", + nativeFeelJs.contains("function cleanupBioStylist()")) + assertTrue("native_feel.js must listen for FOCUSGRAM_FILTER_UPDATE", + nativeFeelJs.contains("FOCUSGRAM_FILTER_UPDATE")) + + assertTrue("focusgram_content.js must clean up fg-story-search-card when storySearchEnabled is false", + contentJs.contains("currentSettings.storySearchEnabled === false") && + contentJs.contains("existing.remove()")) + assertTrue("focusgram_content.js must clean up bioToolbarEl when customBioFontsEnabled is false", + contentJs.contains("currentSettings.customBioFontsEnabled === false") && + contentJs.contains("bioToolbarEl.remove()")) + } + + @Test + fun testReducedLiquidGlassSpecularEffect() { + val iosComponents = getFile("src/main/kotlin/com/ujwal/focusgram/core/ui/components/IOSSettingsComponents.kt").readText() + val focusComponents = getFile("src/main/kotlin/com/ujwal/focusgram/core/ui/components/FocusGramComponents.kt").readText() + + assertTrue("IOSGroupedCard must use 20% reduced specular border brush Color(0x2DFFFFFF)", + iosComponents.contains("Color(0x2DFFFFFF)")) + assertTrue("FocusGramCard must use 20% reduced specular border brush Color(0x2DFFFFFF)", + focusComponents.contains("Color(0x2DFFFFFF)")) + assertTrue("IOSSettingsRow must use 15.dp start padding for isDependent", + iosComponents.contains("if (isDependent) 15.dp else 16.dp")) + } +} diff --git a/Ported/app/src/test/kotlin/com/ujwal/focusgram/UBlockExtensionAssetsTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/UBlockExtensionAssetsTest.kt similarity index 100% rename from Ported/app/src/test/kotlin/com/ujwal/focusgram/UBlockExtensionAssetsTest.kt rename to app/src/test/kotlin/com/ujwal/focusgram/UBlockExtensionAssetsTest.kt diff --git a/app/src/test/kotlin/com/ujwal/focusgram/UpdateCheckerTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/UpdateCheckerTest.kt new file mode 100644 index 0000000..d85b142 --- /dev/null +++ b/app/src/test/kotlin/com/ujwal/focusgram/UpdateCheckerTest.kt @@ -0,0 +1,32 @@ +package com.ujwal.focusgram + +import com.ujwal.focusgram.feature.update.UpdateCheckerService +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class UpdateCheckerTest { + + @Test + fun testIsNewerVersion() { + assertTrue(UpdateCheckerService.isNewerVersion("v2.1.0", "2.0.0")) + assertTrue(UpdateCheckerService.isNewerVersion("2.1.0", "2.0.0")) + + assertTrue(UpdateCheckerService.isNewerVersion("v2.0.1", "2.0.0")) + assertTrue(UpdateCheckerService.isNewerVersion("2.0.1", "2.0.0")) + + assertTrue(UpdateCheckerService.isNewerVersion("v3.0.0", "2.5.9")) + assertTrue(UpdateCheckerService.isNewerVersion("v3.0.1", "3.0.0")) + assertFalse(UpdateCheckerService.isNewerVersion("v3.0.0", "3.0.0")) + assertFalse(UpdateCheckerService.isNewerVersion("v2.1.0", "3.0.0")) + + assertFalse(UpdateCheckerService.isNewerVersion("v2.0.0", "2.0.0")) + assertFalse(UpdateCheckerService.isNewerVersion("2.0.0", "2.0.0")) + + assertFalse(UpdateCheckerService.isNewerVersion("v1.9.9", "2.0.0")) + assertFalse(UpdateCheckerService.isNewerVersion("2.0.0", "2.1.0")) + + assertTrue(UpdateCheckerService.isNewerVersion("v2.1.0-rc1", "2.0.0")) + assertFalse(UpdateCheckerService.isNewerVersion("v2.0.0-rc1", "2.0.0")) + } +} diff --git a/app/src/test/kotlin/com/ujwal/focusgram/UserScriptAssetsTest.kt b/app/src/test/kotlin/com/ujwal/focusgram/UserScriptAssetsTest.kt new file mode 100644 index 0000000..7fcaaba --- /dev/null +++ b/app/src/test/kotlin/com/ujwal/focusgram/UserScriptAssetsTest.kt @@ -0,0 +1,64 @@ +package com.ujwal.focusgram + +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Test +import java.io.File + +/** + * UserScriptAssetsTest + * Verifies that all required client-side injection scripts exist in assets/scripts/ + * and contain valid code structure. + */ +class UserScriptAssetsTest { + + private val expectedScripts = listOf( + "content_hider.js", + "autoplay_blocker.js", + "grayscale.js", + "spa_navigation_monitor.js", + "native_feel.js", + "reel_metadata_extractor.js", + "fetch_interceptor.js", + "media_downloader.js", + "stealth_instagram.js" + ) + + @Test + fun testAllUserScriptsExistAndNonEmpty() { + // Path relative to execution root or app directory + val baseAssetsDir = File("src/main/assets/scripts") + val altAssetsDir = File("app/src/main/assets/scripts") + val assetsDir = if (baseAssetsDir.exists()) baseAssetsDir else altAssetsDir + + assertTrue("assets/scripts directory must exist", assetsDir.exists()) + + expectedScripts.forEach { scriptName -> + val scriptFile = File(assetsDir, scriptName) + assertTrue("Script file $scriptName must exist", scriptFile.exists()) + assertTrue("Script file $scriptName must not be empty", scriptFile.length() > 0) + + val content = scriptFile.readText() + assertTrue("$scriptName should contain function or IIFE", content.contains("function") || content.contains("=>")) + } + } + + @Test + fun testStealthInstagramContainsGhostModeInterceptors() { + val baseAssetsDir = File("src/main/assets/scripts") + val altAssetsDir = File("app/src/main/assets/scripts") + val assetsDir = if (baseAssetsDir.exists()) baseAssetsDir else altAssetsDir + val stealthScript = File(assetsDir, "stealth_instagram.js") + + assertTrue("stealth_instagram.js must exist", stealthScript.exists()) + val content = stealthScript.readText() + + assertTrue("stealth_instagram.js must intercept fetch", content.contains("window.fetch")) + assertTrue("stealth_instagram.js must intercept XHR", content.contains("XMLHttpRequest.prototype.send")) + assertTrue("stealth_instagram.js must intercept WebSocket", content.contains("WebSocket.prototype.send")) + assertTrue("stealth_instagram.js must spoof hasFocus", content.contains("document.hasFocus")) + assertTrue("stealth_instagram.js must contain story seen mutation patterns", content.contains("xdt_mark_story_reel_seen")) + assertTrue("stealth_instagram.js must contain direct typing patterns", content.contains("direct_v2/threads/broadcast/typing")) + assertTrue("stealth_instagram.js must contain direct seen patterns", content.contains("directmarkasseen")) + } +} diff --git a/assets/images/app-demo.png b/assets/images/app-demo.png index 2a09a35181b3599560b4bfa92e7d99006f3f68d2..842bf5c7b7a4b2a5b4231f00aaff977b1e6e8638 100644 GIT binary patch literal 874151 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�_7kow|H7yQVp_95 z#1)G&+4s3JhQbO`TdzNnHFeKSze~uJhpr&|*F%wB7Mng=5k?0%+As#dsOSfcSWb=t z*9X_1D{HE&zv){}FDg=F2~cFZjrRUp!ZE)}G@J*_<}W2BQ7~|> zm=3P+qN1Xr&n{r;nthwzVxSCPJ7rnfh7CPV-%BqTR$zqr1@j*!+Rlj=30>u@8q?ou zYlnH4x))i6MMZAo*$+>T<0Lb$iww^v~69gM_} z!=T0qnce{3-2=$S4ow}Uo>|(^3c6WsW?6Id%wC}s)oehYM%?3mc3c>0YDx)`kKnE3 zQEB&j+`XIA?(JNt8|LQal~h|Ry1KfWlb1IT2IjxR!rTT*xkZ`1x{JAGH&*MJm+@&( zcH9i)IXx&eUqp+*lvE2256}dPWoZk+s0WVDckIyr%Fs$ac|ziEVh3;=ro&W)4Xla7 zaLND)H4mx3Ocd_Mu-rn|xyM&u*XPYj1SLO8fewyxOiawPCqz0rI-tieJoXY-!6x%Z zzR}x<59QZXXx6H05#e&d1aEZnc6yjjt9_Bl_#-p7(3aBjo{1O3Bi6Fm zcljbF#cjpv99+gq(y3x`HlrU7tB^>&=yXc@S#q18?YjuU-R1oNTC7H1?UWZ%Hm+m(Q{yAylPdfNvQnkgFtDR)<)o@NZ1ntiX?=Nnpm*0{D3rz{f zqK{>7kaf|_?U>%oDocLpyqI(rcs5LP$F%r!Ski%#Kg$!1SYr|Vz?Tjhve)yc>$YU* zB`MHR;NbnKP=UCk2ZME6&B#bo%K>Q$pJpE~=DU7#tp|vUSm5+F&i(x zbnX}0{-m+Zi~*K_v03ux@*Wo#mn0p2JYU1vDAp9$+C`a7BE-`K1qU;+v$vLLowxbc zc0&@)Sg8dLj;NgkB(9v2uGU+G$F1|#kcxDe1r28RZG%yv!}=ctO?&$gJ>zYk#dH0_ zg668u>{oG>Dn_vwTY?IingF?)hjgs}JMF_7n5Ykbm45uWVR=UOkkTI3WxqWw4+1Bs z%ge1Rqx{F#Q?>EHVfZ0`prOmk&Sqj|H5)BNYA#?2eU}$!irIq*(}5sg*@cB+Cb-Z!|$ zvt`PZX++>Qov&2YT*Hu`n>%|1nNSg$yK})D4h!3Ojc^C=F8f|C^Ril+jrLB~79U|< zTk4K)_C6AS*hn>ZFlr24+4N7~P}ud_Q^6|zds$i_)|$W$L@3bEXG9TIEUt@e)h+u# z*WEWW^TnsZ<7nfWe&y>-;D~?+0c+oLvI-mcj7}gU#T175F~E8OBRn-s{twq`7PupH zLoA>OOGeHq@GMllctzDHQIA*k3*E;)Ud3_2NczU*k{s6!7v`(jAYnXae7U^$?Fx%I zIo8hpG^qZI()iEk^9uaVuOHT5ahnbbY~PaN9pYG?sG&@2)V=WuCj+MglSW*6h=m~C zR9V~pY<%`1Be!>!Q7Y;S{bx#+rs!0fq$oEeA7slbOfY4v2#cftMfaW{5Q>4{&PyZW z2{xP5$B-P(Wn!|yq(J@~7Fw&JVY{S}b**`DSzn_lhtj6`eM+SgSBi{ED_2`imDkpt zS(uefN^nDCRM&q%Kk^@QP{-Dh1@X6`X~LMFfAx)r#bwbU5xpY%4fkB$#|-P;CM%~y z(hoYxwLojo%Z4qtELF99Cn3R+tEh;$n|Q* z1>^zclTEhRf&J+S@5QlKuh(m0sjg0svqzB+g05+9C}wN@;Xf76&}6dt`*>dIrSOBA)mc^6GUcvH51_H7%td+00lo`0*Ph@U<3a)~)#$qz~O3H@Ncr?@<^vHVsFBWzY!6IB>` zCsniC3Gg}xZiX10Vqqp_v3{n#8%XVS9haXU&2qMxL5o=Pxi`l1<_a6OK0^%+Nmo~a zlamv@Pnp2Ce$9anP>&Vu%d+&{v_b`GzP=pMyjQ9s4hslBb!xy@nRmh}g0CO>#ps4o zT`BaE=mE`T<>i(nhp2amU=N)L49~@347RbcA?dYzlM<|*Si4Y`pcQLk6d7M(0~h@k z!q*uVS#xW41hC9aKg#NZnekw4l!ueE5g5C?+Y>PA4gCK7WpOFG!%R-qT89I5i}b-n zzgmILkZ4iZQD>7yw9zW1JH<_%i4uyg7y8XPj(xzA0dU61%={JF3OKV4VE$nGga|3r z^w}08W_eRE!ULIG3=rd#*Pcokg-BM{-yin(`S|#%!(j9czbA{HZ(3J1r!yJb?00HJ zQR=es6wn9v%adh*;7Y`o5Rf5Fc;z*-cJr7D(b!CrpKH3y% zeH{(#KPx?gOEOx4t}LPX`1nV?@vELyZ~h_T?>33h1g@ozuyfA;&Pie~NxjbZ!1o~E zn2VqPQ-7Rzs#=j%eJ|aqHC?f5Pc9)L0O|CZztBxUzSul{xiP()Z+sCYPvz~0DC(o4 z@F&5Qa>2P>-E^dcHH`e^&2s7$eOnks^YY&8(b-P?+j!v)64_gef8A>~cEwqYZej4b zU^Y`zhFe(#S}&*cQH>qOhu{A#vH6+2R^60=gCo3VvpN{)W7wQr7WNY|ogc`&gNgYq z``fpqU%_j~+sy>le}6=llxP+jk}Wm9JyOh0_ExvG#j&kFu>#`*`^N=Of9cy^ptW&L zwQe2JL}q9z{c3rBJkDuom<(ikT-D;@p8;Azj2o(y&X67>8=;@FOqY4|SyhDlsQs1d z%U7?ODr~^f)zZq!aU+Td)|=Ahad=#fNP>xmXB;V7`EAB!Of;@f8cx<8P1WR(uf2bN zlU&^4BeKoOsz25J_Wdmd&qLoGH*#P%0RBDhQ1fwshWF`xKPehE*`nQF4AD$e-bZ4s zZO!o6i%C1v|8HuuB@)-Yap59H)nMJ`i+5a7zS8wLRn4f->9(Fh778xfj?4`c;$yGt z3Z&FFenwXln`p}~cK1FryKIw(@wT1=mJfDamAGz!u6h$n*3oO+fw*($D}F+werDU14bXO*GWn`hrV}x zj)QNuV=qiwQy6)tuLx>^`hrg*^Zx53Q#v?{>l2kYK<7XMq6f_T;^HE7{EnM{g^)2{ zl=9YZJQ0LMggysoGhFNdK;L--aH!C}0HF@1UkQ+EkZRf%w$h%w<-O$zU^QYJ|ML9- zv=Sg?5QDawhB6%f*Akmt@Jk>H2S6i;A(-o5`+(kC)wa|1Pwux}2a~Z*u*}00L|b{u z+X?zs0QPeDpA!Kuhva_1v})uwi)!=720(jwiUCE3rx@}m=fL@9;sMv1 z8lt>gY(tC&gQyu`JjfUWnj?IS#hvxiK7^Qe?7JXr=FG{-nE|6>gx6z4^6n1*%oF?P zV!5n1VKCXi?SA7g zp^_S@Mj#hqI$ikJk7Hn}`EmN{VtUlyiBDW*@EJ&hVU7z#BJR3Mj(5U+LvY`nZTY^* z%6b6lW8DE%l%sMRNVk%g$Lw=HPi1BL8?1Ex)1th@2A-oUjsvNwoMK`pBD~5=5zPW* zV`g{XsOkA-6>>Xm8^_noiDR&gM5y+)Vf#f4JWRm1Sb1skl;1z%&u0ab7pJ2>Eqzjg zO#TsJiMfk~xy5wE&(H%SEWKPvsVsLlxTFSkNyN{{G;g*s?Sv8phZ1a`DcrPdR-C2F zv>TxJ?(_kw0&r8rfd`>^iHUzZhI_cO>sKgurR49$M90$(Y44YIj)`(}OVQ!?`x)EV zumSKlAZOmp$*WOB-czeF8bquLjMGcqUPD<~S;VLgcpXq&e8hdXx%oP(gARsqB%Eku zWF+LJgI)U;Sc!vN0;bR;AxF8m?2qmot!)U4@hkWq46*T&dHzx~DnT#gTYf4pJOOzL zTy6m3;)core|A4!IVv@^hyuG7ugi!*>h2R=z*{9H-#(}ze4BNuA;jd?w}bz6N@3hO z>QBDQd}zv{@Sr)%wd)8J3F9z4b}Q@abg@eIaEAfy3}fgZ_xhAauJ3Bi?ydv;n}6%; zLk@?4qC!i4jmQ?L~@+) zJG7I4ECS#a8giIPJ|m8J5gu+ERtX@U>gm`ZSv56|Bv=YvOpP_YkKV(cf{p|yvoyB?JO;spd6TZ2e^_Lks@bel{dI_#Q`uLQA|zWk^(%5J>1uCWtDWh7B{ z1$TrQY8D`=RQ$61AcVX=`g9U2Ys8?MkSA^F3FlBT^K&D?lizsHszgiFKL26j485U@_q8H}oW(}k;Ms(~ zbZ1|M{UFUh_WVtDYYW=JU&=#AZ56wNk0Q(s&;rY>rA}xv%07pw(qi4wSCSvsoR%ib zprW8h#64a#>P8D$E5zJ)?%c^ODq4n5b^IfPpP98aCJhRB^e%NUq-MZ=3%s)8m;JPq zKF20Xj~<3)rYoK=83BlcKuQkJ%7?*m;45;aOdsgaIzJ|JbBfv@^!UQVvV_u#TrN=)JUS8H=y83CVXR}SDqtBRdo1XoAcXB?K zdO@<|))e|T%6fdC4RFPi0N>+VAm?}s_)LCbf7*vx6jCOwga?~p^3*_H4Z?3Qi2?k6agchLRYiewyM$U0= zZalnB%;pwO6hM#C;N=TU|CKi~634=Nw6eLGRatqjF_f^9SF+e{R*DVlvkf{cBjcC) zdY{KD+Fd1pzoULBFCXq{)@?5Bd;JBd?2EG_(**IQK4XN0f#T>8+NoN#<91_^_et5y z$X-+?`k2^QEv-_pChmM`+t4&L#G9)f1?{#vC{?DZs6j*o$E=MO z4UUXtQ8XhgH!xkGis_69I>c{yC>M_bTsY`;0N6(S;N0Ea$K%(57HL$@D4_jDI6FK# zDi1Uie1~OVRl>B_nm!B-53d}6HgEs%aA0Dh3&e$xr2AV)DL5$T55xxmq*)7+83DX; zqbHuRWS$TykRZds#54<6?ofY9XS+BsR`9Co>raaLuH{R(FR1|DcWWdEbSKFbCZ&F-C) ziB^qPQA0}(iZR;Aj}LI*=vrD@&H}vy?+qLeP@ImahUTeB9M9bJxjOPSel*K~e;a`h zpg-VFBC0(&hzi$Kg8K#7C3BD|7}T%hG9n_$`uZt%rNt1|dSMBkaotsZrlx+DvBZs+ zGtNreQL$99YYAK`y)pb&y_-vLtMviVmLvYMf(8yr1*@Y6GO_>V$MwB9E= zZVGyk2%Mjb-gtWr=Ck>>aL4sOT<4c>TsECI3*MTKWoRn87BKC^`&d81$Mn0lxbUhv z=snN&=9AZzn^RV9so?lW-%0K4MC!7I% ztz)t4hd1<5qo=j$T?w+!tL+tbD+Q`x7E}Z ze`I*|_lzNcyk5Z#>u2ZZbC~m2(JkbadLkcA)O(8nk^S1t=`*~=m)kg35fB^*xE)t@ zO-;N&Q52umNdJgSO6ui)Hp~t%bF>&=9T>x{Ae|&M7)1M3=VT0xw%mQX+L>j3ejYJU zgdr7pQqVGG00alu3v_rq==)Jon%4RbGmSLhy)y>0J1VM?*f8t=YXMM8D<&%FKhblk zNaz-p?TuR%0rG{wr3lFZ$)R&S0@E_+H)+T}5G)TafT?&(2^|DHUzt36c8!VN)YJsx za1}{ONugn@KJ>XK1s$XN&aZosnwmN~zW{kfwAv8;H*}zZ_BL5$>j9Bt2n~yFh28!? z`|zF1^u-B_@uh{2%GrZ3>f+E~!V|@X`5rnfBDOr_dRy$$#SdqIOYtjQOz_ys%ip@% zp}I;-$BNNtTe+1+|Ee@?^KH=fZf{+l4HO<^I1yIb<$!pim)6!!d*ed>{d%mek2vZU zm@&KCC-NA}<8y?iK=QR{`$VBa#jbAO^7xM&vP#KCDPfj`54|pQ`JR$#++-uxBW@%hLXP# zq7h{sXJ~xJHe|Om8_jGCeAtFUM{7f)siCQLUqyHL(Li9PwjnkWAf&JMY=g8-BP52S*{QJ8GdDGU6hoR0C$7IQ6m+4Z@99)fF2Z)#DQ2H$ybJ4<_?4_0Oxt66ep`{J$>2< z+7S?PMd34qm-Q{1f-(asw}3F0fTS1zc3Ap+I{Q#oHt=zXt>(bXD*Rz)yXME>9{(h%p(%V}LGcVlu0OSJio4yZlLPk=aq5S!cnB z->dGsL`193VKHGDr}f@vh+G7j+hOF9QR;v;3_?f|`xMZ$Q|J_JrjqI!m7_Zw4hu~2 zML%5*4GrCai*_5>=)G>9zTdD+3-=}9doTflq<&lE)BdHy!%4OF5VwPq^FNS$4L$Z9+Vt;ySBv!4gH`*xJ96zPhiNcF6ZiB@x7V z_aBK;_WkK{3qQE*@`+p69)5Qee?b?9*DE^njU`FQqcxZL>>_)LX_aJ0xZeAyX1YgR zF#hxB8ANHBoqbDvUXiw#gp_o^3UiRxS=?#s_?*sWU-%)De(GrPcw(4Tfh1vxj`EP} z(8x+!h&DpRcllnN#|yZxPhfvf6$D5i@5v}Gjs$456hKRm z_PAdh=sI7X{7+|4@4oFlNHi?urIPkl+i&ACgh)D0^Et-b51|oEh2e(j&tJzzzb9jG zz_OFDXeX5&C)K3=(DF%u3Kk&fJHl4h&=!7udv$a#5*0CQnePoGNTr9 z*xiSf*63vC{^7OfqFL$DdKm@*w?wMe-dDSKks^(4OzIW7^mI$kXtDZmDLM&S zI~eQ%mkNhZ^BN$=whb5jplC%#eVF^<@BtfaQ1A6*Sv^#SwsyPe4gf-vW|%td{Mxr^ ze!!uFVuo~(K;r-$AaU9s{`cswGGQb)B@o}BZ9?cg*vyro)#y!a_>A`Np9YX+vH#RmP4mK3Vv?`&^7a`$@!W(J~(xRSD0n^}fSek07 zh&&*(clcS|8|&dLWT56zFb$B2jCxg7P7CyPSVa2-90Hgkivmh2{{$8 ziS(xeSt9xY!c@R^6K3{=TV&HUpo4_oK@`#o5IwK^-cqFUgO!yPXboZiAqQ*B-vsvN zJK^?h0m*xAP7XCLx*arTpoZ&)6UJvk8vG0Nu{a=p_L#h`TDAqje9cfXL6QM^9)JT< zP*4E=2p{^v)3)V3dk_mlPXgCU|3J+rqvj{|ptP?>a z3$GS9((-q>UnUOWPUC{@2T+E?m%}}PEG~dMeIx9fGqSTkh*ur}WjDyFp>#n1ZGr0n zAT9*(guqO39WmMl+82>uyEH1$;lu3_375k^ekS=493~=yNPJ>$lug0B-%`LDt+14OKv>31Iqo zm15>k1-dz;L-PpV3aKLb5ZUd$zkZ0*RkDAk6KdMbS-er^vs7z3sX-Ea-|x4j!O%1i zFSzIt0P!U^WtMSa%9{*Uuqh8dySlt!HLCp#swplpF%{<6PW4dGK>`vDY?dIrr*OzX z-|x^TcJ$qO?eW|WBS*uz!{%7$f0{i1rntmw4#yr3yUz&*2u4k)ZRA@gIZ<7NJ7mma zI)jvMTwW{wVLDTiIZiFo%2q(M*_qc@ahPZ$Bv{jynMBi<5o$h$@{VRbBh&AcJ-{u+62&3EcIh>t_A{d^N1_!2Xi(BJYMTk_%BKkwNJ4hH#8&AQ~q%{z%o zna78f4F&gUm9@6P06htS472t&Cb+i${ms4x)C5rRa&mIq{NJJ6ffo2z=}H@)rjXHr zl%T{!1%XDCYBq3k#=G4-zU^9SV!vS5Q4J`}v!gB7`)_K4Xv22aK(Pr0r4+Q0AniQ) ziQ_XPXWb(s$~<5CZd5OgR$3cH%gD&cA5WjeB=#q8XCRjgCSr4#u8L5~#A|Yz9?UbO zollK1BcUZvm-hCq`?O6CuGo8(n+yM$xi_4I@?-SLQ1%qgq+tdQ4&oIW)=0vZC45!@ z6#*HK>^?w42C`6ur2_;ie7{~;E<74^2O*)O}k4Gp}1 zXDgaXIbKE&tzuiqtS07-A~PFTsAvR{|3FF<+KmRs9<><$j<=N;Q~&Zoqvr_KV}pDl zlvS(!)BAcT*8r}bx|*x&dIJRmxEkDJD=#<9xN|!^oh6{5`x1U6mw&Lo4|Herj*)YQ z;VlG=F{?>ns9QKwzUo`pLzDM7fcMQq{PBkNhWZANN;KR+VN?=@fj+Y09=AE^<<~Zh zbsmdSj@lfJ>@9Relb9tWTxU{-zDgoJ8o>Z^f`ga0H zO3-}-5>x-Jt`4Z;tvEf6TMKZbni3mt~cr<`0|KcN|L z7J4O4ML{t<&GNjD6B7!wm?4Mkc~3k~05m4ZZPzvg%Tosj!%x*h?-!+iVcvos16jtY zu7AZ2xo%zUsZ#(7oGDKV6n$%OWy>IE0}?HQ=48{mb;uqXn67`usKd6*Y;P*VhxZ6W z!F+7a&n0I+%=JJyEG*37-|w^#kIz}T6r#09atCWrHn`EbWRJnJ0icxP#MTV~jcSV5 zM){r+b$dYqs2{3}O2vXp4UO^QF?z+$y>@$GOtKEbC>=}j(dCaepdMKc2jw2Lp@91X zbj!uNAZSYBFKKVjh3vk-@kHDw;H3cyR5c$qjI7|xCaJ2aL`fo1HZG{n#I2Fhu=oCAcWu`!K)s*y&8VtHLvf zU^ay%DM#5v_+1pUlpF<*bdB$gf5Lque_2mdDyJ&U)z$Sr_E~UJ+!lF$HV`V<<}$Ha zF;*};%Jfbkz?QoH#r&w1sBY`3Hgm2(PXr15ET1@w+x<_38U zyEuf0hX9}I?sge>wQCFuRY2(510`$Z zu|mJmvXHdQ*Ez{*^6ld?2#L9bgpiF5P@W?zSa8W9ebd+e9R5y`uvh|5cBKmbDqe@e2y@v+w9nXqRgX11*w_I5-njcvm{@4|M?l!SP-v9;(06{@& zBeM23I6kfaNS{_|eRu&cPlxU!jo|-DW zKU{ZaZ?{wgZ&E2NW55@dze;>H2<00}BgjYI!_LvQe@m17F?gc;Ei&qA zDqLg8d-`%$k2>=$E`et(}+#Qdi22Q5CpZ=~Y z%=%Y4q85LL**RutAQV544I<~IBUCd!W`C6-(2zpG9k`9>s{9szcF9Cw$ocL`!PlV= zZ7L6i>3(cSagdO)e9vs*{P~G#E)boGC5&wl+nz0UzfMN?*tFU7#dtvN$uZ`@-w6SH zOcPBb?l>9RRY9&}OxC*gioc&c6)uvzJfRA%Q2QYpfa$Phe2ppj<2o!!L7BZgKOsSD z2Cf>j`I+0Fzy%#MGg;XWVS<%5iAYLoVSHbu{H0ezl)^=?5})sbzEB5*MQ;p9&R6Kq>*Lf@u-B zZeZ_8`bWbU>V{7EZDHK;g$5}Z=9wKpgvg_du2@WtW1(ZdHlXVG zcn(_tbC8Ku-lp+TNTQoqPkU15L<(K`$f!30-QVipXBvhk0uv**9aJyz_i&pSUkn=~ zYdF}H=T~E%dyJ35PN((=BHgzN>he8wD@oZVN2Dt0Sp7bTR{VTp6LQ{29l@K@{HEtY zk4p>bTh+l|k-(CO&P%KYfcZm{Lb<{yn$*1UMp za*h}8zBg4}S)Et?NnfX-PiWXXt~L@cQeVc)x66BlD?T}S5%P^Z)~{~bZzKDa+1lWk6VX4ZzwclZvpUgBkh}FIwhNo(RZw(q~n4>bz%b-C$Lfb;Bv&%nq`l{`0&Yx zuNd|*4A;_m(ZUt1V#>{W5+1)>aF6;+5dD(rNq<3X+>+9L+P=9bIKc-8?c5;mRYOp{ z?L6JOAh0JOvNO$GS+PWDOiq6O3TF2zk>cs6vLv`8JkA!Vl6tqJ+_yjoq{0sK4|~lz zw>SVk4h!vg)is_~t9kiIT}bh)2WJfqlY-gq_(5b5w{|Dgb^-B7LjnZBtLWw}Y-r;8 zfEz?`r9GtP#?5-|wzLW<)9D%QgJ>A}JgFceS zR1!W(Mcx}$SeBC|^C8wPkzV3`Ft{RR%6)Rps0|2)ZXI1i*<9_}AQpBA>aj@@_ZAM_ zHz`1NUXfEUTDR$pgknL;>9De@sy!eOz-0`+Bv))9J+gkqo010(VBizt1vjs1=)rZE zUtni{YujI-_HCkVeLM&x6M#1<0eKhDC?A*I5m4CGlFE$rKKi@wOnBD~{}AW2IC5w> z-nF#e)6?UBaD(GUAGZ)+>&P9K!HLOc?PAO!$1Gu9T)tR$A3bv|oSj#C=`%<0PU5(C zUCl`jw;GKK4;SgwuF<{8*s~72`AvT{W%+R$+5XDHJrW5A)U?|>-;dqhc7EMgzQ?d5 z`Z317DWKdwF}mK*g+SUf#e2Z;UmDAFb4UNY-vc{W+I-T}9Q$cn19T~d$h&dTw||ts zetQXxIlM6m$cq83Ksdah0SHa(yE^Y9@TY;P$*y{GeVsJ`n*y=eOV?xN78GnpGPrh6 zL=iya)vF-Q+!lb7!8|Vwtob~TUPJJ;pz|u>HCT~emZmzz1uB20=yJ6PocoZU1Xm11 zx<)U>IT4r(>{RN#2rdo54BsC0J#hEA5D@Q%+oX-BPn5?(qjW5h;r7_2mc)S;f=MB3 zaRrZ;(PtrW896Tbh1EgQ0oXhHgK9k7+)dEg4|11IR37?S`V<|!kBNP<6aQADtrCF{igYEa@3B zv3Y6#I`@Ru(ENTgdO^D!|M5s={V!;~Vwb+A#>XRj^XEd8Bo6>7r5+rh%L-)V`1;J`}Z|^RF z#0~+upxw1hLio5KYbykKy1*@QsIiw=I+vGht5iHL^)vB&S^(w3aFGum1ncragk!Xy z$wTH zMR{&k1;7t*q8fQxf6`;i&BN1-JWD_mzS}y3PjVa5xEw|eUBMD2M3Y}tQVX^!f;27p z`O93;Zh-5;JPeGpu!kSM{Hqrie%pp$`(^pCeii2cbRIs+bs&d8L(?pJzAbGdwu&l= z>lYgS4Gn|rqsx1eYz3BrkJf%$EH8owi3xsB4*zme%e+R8$u@wRt_RXtE{vPUoXKD{EnkU|ARI+8@hA z=!)RW;M{t{o>(zv&%BaZDi=NZJ%L5|&Tvh=4BNLayQJteMEwOl?9Ao6{>tP1c{tWj zD>IUYB%uOnU7(~*bK?s9nH-`c~cnvbLguIR=jjm2d_kQ9`0qsdK-}v?>WY)%ldrPuX zrc92&&mUz521!N_NsSeeN7aDb@-2 zCn!Nlfa9Z!jwUes6%L|S`_p|UqqFhU=PzI00g7liw(UsFtAVnRmrP20Ho>ZJ7O~L- zwVjyfp&hKx?1G0(58i{u4-f0+!$f}k`x@-FEHd3Os3xJ3t3B7|v)-oO2{RKmv8bA< zGO;HMslnyzP%e%4eKohGPl$C5?1hXk@`Ad8 zk55vsgdiw7^&wMexj(|{K`@}i;DdYSnNQhxM7{iB6b8)0e}t2Vr{|7*>iqAY(xO*Q zce;rz-kj2&046agdZ_4beB5+z;8$dQz4+AG)KQkvwJL)!Fkug-GqLiWz320m%x4$v z{7?SHhkIcTurPfC5${hO)gR%-M*yT80XsAG7aO)(>~VvCcR>bH3&Hbf)IkQv2Z-7l zfj-?*nqWf}){__VtT-4!n>RDB4^EkQ{)~`8C6R^?vllb+V>|$#mEws(f9JGwnn(v2PZT0ybhm# z8F5!_H8NE>$=L8$^PGp+-?h_osWG~B5jvp}-FNGb?ML2lFpuFE4Py)Z+*V%kD}wJ}R|m@2IKcy6nNR1LlHc(mndtfSFnf{SOkxk4>Hz zXIGHVhxR=$-x8V#Zmaq(YzEpgi^D(y28gE@X#)atEf=aJ{p9z7?(k8cTV=UiRc)}Mg4^?O+{|J*lUSPe zxZ@-F%eTt`sY^N>SC$o6Hu88l2L0@D&+$FbUm7@B?S`ox(4b;`4H2l<(2>$vYU!94 zJ^%BbTw?Ej`{>6IStguE+Quj?U1cR$6_nT=b-_R17UL5PV37b}`iLY)ftqKtdX2sQsBSA$ zYJi&KVQpZCD!P2IFT3+Z3>{K>#(k9xJ0RxM=(|gJx$-y*8|_dZ7q~F1y3?_1{;E zZ(`oTx3w{)g~b(g&=D{$HZ5UcVU>twG83aP$b`sBp`?ZY@^%{Csz%902bu!kyOvw_V=B#gZZzW zc!fDUv7c)}(8F!fW%HD-lUxRm^2dvigWSC5U5#Og8{l#@Z{xF{es5)E<5!5lI5n18`}5k!nGn!#f4aFC7hgq4=UpNE9GyjPx9~b`xHxkbBkdMak+?b`y#OX zMXi-Gu+3!mi3pw%?&DD2sOcM637Ya~3YzcHo0K9%(-ZgpGSKeq?<)&WD`NhbJnwBW znu+LJ2B#;>_MLBOo(`$1OTQH}3cei)3YuoOpbMM4dc}OVfj~OPTDy9oCZl(Ze17;9 zpSQT#kwr+FX0Cd{GEsaABC>XIr`*U!?9|yAowKco8+7VHF0eT{~#QJmHX>p(R0V`sPJPwqMm`*O|Uw>znYq;)p0GqPa&?)h zGo}mdPpp#pSXYl~W2wo+s(b5d@a~vhL6reJ)%t&QacrRf}&Vq zDLys!eZS-A3C>4r-#blDOP?+y!nJP1Fub=WbnDE(eX(n0gJcFQsIZO}e{uOHMiu|i zz<3`ZnO?v%wM5%?g5d zV@`Stze*Y&icfUrvz@$lERI=wQF_on*Pf!)GpFXF91)WGm{O=!if4m@lBn{c%BNE3 z`v@|5R$w0Pwcp3lV(+ssTTO|4RK?!b)&}aAr?2?Pjv;`jTYqZzlR1-BW_xd@{w&I3 ztLL|xO8?@5da_*J0QJ;9y~0WU0+}NZvDG<;J<$n%z1#d*%w1vn(cDo&i4(@@|Hsr@ zKt;K|@86<;luCn?fJk>Eohs7JkV8leA>AO|Aky6;(%m49)DSW>2nZw6AT99jIp6

_4BF2JRijs)Y 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

A8AV=Nsf2xj@ov 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

f>rz~V^rL2>k9AwwLj zZo8{)iT_|66_u+v`&W5mMs_nRA838pvwZGECQWCQ*?K2O)c?FZw~ca>&pV>udYH3z z7pe5C<`wvVsjBpnCT~qIKDKSCmSCDlf7~kf+-VBag0G%)$)CWQv1PfHM)LRuw|e}< zwDe(eQy@5%lawWi>e9*2d<|3R3N3tviSQdLXnnO+e&j$(axXj{SF9w)`d))>rHTR70sJ_M&s49DQbUhN(^CuxSNw%HO zx5zoe-ng#(2ofcWN${2#i_h;EnY(S#IK3C2r2JgcQixkKXL@rowrx`^Ge4j6=F>6i zfC}No_gWpjq8ZjnE9-N<15Y^SxPzqH-2IXn@Vo$cSNA@@e)M~Lbgf9yFaA%!x^ly= zkFtK822e~>qUT|lvZE#R?0kZ=;lxi_x>r9W8}tZmzD5V2hC| z!;;`JGxpWd(_*j9w9!J!ZaL!9YX9sF=&QU9m=k zlwAqs_d5dvl*rh~Wfk6FoZ{^rP+i(Wm3_U^K8@@7b2Dd}5CDZ~P5W~V!S5>Lv&z@i z1GA#R&)rb{xNi?yX0HS@t|77({+zM-+^97pDhpplLb+56G58va=jkQB0fqhO(WA7y zGGexF3S6m=f79Zm7&_~#)4yObB`9YpTYrZd7yueDXGns}aIvzSIYs$KYeVqMb@AKW z!);5t&cNWw4|ZL;8_qRd7``-8@Ul+Zuc=g{oSE1^I93>A>fyI>s0}b1zNVi!K48}6 zC&vem?Cc@}jNphWwG{wSsX>4E)!y*WvdC`<+X$@7N`bs&ic^G=1B)d#I|a{zyA}qQ z0B?qZ2%XPZK-`F2PMn(dDkT4ZU8pD@x{FDkFi0`q`Xw55*DXKcV#v!tCzj;%Nk?e{ z!?_B!uiLS1;WVjJkJ7PfodR1TnmaYC@)t@SnXc93nf*LmF=ZlUR&jf_ZED)?r0G_E zN-^P`$_1A9c$-OsJ3W!sHu88??rlzD;40xh)DVFvxi_fBH$_0P6NVywWX+LBF)SlN} z2@E{{B9jwuq`=9pe9y8?UuI5M^M;g0+8GXXcOUR^%}RdvSJgP)5(7qWf-I=Y$*1!Lo{b< zxT*C>^b=k5nc3=VJ7COZZGBD8djH^nkN-gaUeZS|@&|h3I}c^6qe3~)vRdJ4W@?6$ zt*suftU}A@19zL4THJLqR@!feSJXM~yqXxjm9?7k4(F*@{)$eZ?Ls)Kg%^L5fpDTZ zOfoDDm0V-;#Q5JKll;|?Dv>Egm?)*lNgFHht5deJeVQCQ^c)9PT0u}quf$L4nf*@f z0;?fG4e3+1_+tZwaVZjeG)fe%4$aJj*F_0bb3GH*qtxxnpW<3}Kfc0-Tm0eIp;4_j z)sEMy?u(ixMRP{HEKKm3@cTa8>^~R_xJxcc$P1~nzaJ8KnUcpC_x;_EuwRUS7UeNz zjJ4lwKYgDU8K!Kw_wU2ou-m2hjAB>lN|+aa@s{KN^>O=Y3l9dAhj zP7$i%k^{kI;q8Y*cDh=bP1BX!Figo*_04(on=1g`m(uKqERz4om5NDF@S5qq6N|L` z17)n==jEyX5i{pRAJSUibx%@BRkCjs#LB$2e8MEJBhKf=(dST4`4APUl9?IjmPB=k zz!(WNSvi*Mw0Cuib!skelJX;VkO2Znu9vXSgj)X2PxgYI2Vx(zAq!Kb7zXs zw-*zk8hc5>Bgfv1#Bz53GroRJ zf)!F(RRsW#S&=UJHzO%kRbn8NK$Q9%%BoX|i3B^z1`6 zQbB!hp*%oR~y?C@&U;q00q;YEgR?6%}Pw z?Lrd*%6MLo-2swt`t)fiynt-f6pF98Dx&qv zk<)I5B2N6hq$T)cw3;#Tlh&pJEd|Cjq!xUC4t`U8)zGq}K3(v1mMkWD>wz`q%{9wY zlm~oQUoBQf9aaCnZ5Tin@-!i9b}Zr8o_s>;&8aUP(;tOv^KA4Jh4k%DVoF0^5Pfj; zOW>rykO-acY_e0KrO7UmJzawve@6c7Ku5Ful~`3dPqRbP2qQB^Exw#l8J+)+sP~S? zx_|%w5p`0@tn9r<5kj)JvQ<`+y%MtbjBHMOudGnAcNE#1>}-;(tgPShy58TtNK;bFk$xCUo!UHal7{)>~07+r_j-t?u}=BGZU_ z1=rg}YVBS!PFt;V1o@t$&yol4ISn|61pg0G!_1aYlO>pSfQ zroN811*wLQpM`IAP;#7mo_gw;^rreJ^|xQ@eWZIW$@ajoukgtx6Sp=cOY@@5-2m6W z)|^u@bm+%-edv4_Cx)1VX`AlsjUQct(Q7ir*Gq%!UjpJzv~cZ9dzgB>7Be~^S@>bF zfkpwoAZA3(>OR_T0rS$z>B9JT%Hk&>cV?L#xUMf)VLMz8zvg**L|j6uJaYR`IV_Il zWvV;NYx**a(6YcK}dPi}hKogg-&wi40^0AtR)) z#*mISv<3}5JSdfPDypj;W*oHoY#h;ciwM#s2hT9Zc9f6b~4GXwg4-XGtQsr;| z|5xi#Z#2Q0oNrPt`Ov*T4ZezF&uTCnv-=mkt!{j2%P`CwKd#?j77d z31N8iT$}I)84bfPoaINRQp67`B~6>0bYu06%H4XJ88H?cak#L3KYpxyo5!a(`J*da z;g*G()CaxP<+EGW_6LvNCOyW#9urj{o-M%I z2&owUvdaw(?mlhVX)zViL;>uXGCoB3|-Pd2x_RG>N}EdZfewklm?Lz zTsre7bfrqT7pYW5p;cby8?VpIWX#&V@$9A|&X0&|fqsE@QBVKnZqz~hL!|#Y zE>=A~oxy|m9aBX8iNmbugSJQKy&I?|Dm{y2`N=!@EE%0^K06D1H_OL+t2Iy_vaYqJ))uVGzOH7jArEXE( zN@d3+qfd7D+8jx|uwUxjxFIxXYqce@dER@7tNb|8-CX-7wBP}18>5^*0Y2=et>KA@ zPUId8NIS?qqEfW7?v}lRDQS-0H98c#$;;>K+2Q#d5%)kf151u6^-$Pv!of7VqCX;S z>UHDshiA2^RR=fmsVN!8qX0{7aqar`y8@y_z3btgro3d(w$B(C9!AzL(#!AHnYeBUjqgMcqx}G1ex`!zSdW;8g;UUN1v*&GhYiBW4 z3(hkNB>kkO`6ZOjY~O`8pbPwJr=F{hKU5y|X_MphP15w3?DOhLy1!4XEnl)^i@&pP z1_||A-1a>%2`8r<9pj(MVBSzbFUSLj*W>v`n^8$ z?bCC2+#g+7@37T#!Rt4=V^~X~;$>N*p8d3#B)`XZUotCk z6;#(ZPY!PE?#wj?Uf2yEn^*e`y$d7BBoU5VX>O{H=kFKkxc;;;QtagKmMZ5n!Fbxu zA0*wD5?!oOsKAk%>Ko;pWsvFqZ1V+(vM+O3rFA zU~f{P&#v6Y>XQ277ZZ29cCI3NjUw9hNt;}fXzVkH0y%r%u^h zk%#Ft-TaSVTV@Lgwj0}IcpglE4i1S*fy5&)`;&yq0C(hTdw7{T=S_}xZswd1o*&jb zy?XE|T%K)xYs(DeNYH7cy*8T;RtGpZq4hO9GZVh$j#%2EX$;k=xrL44h3{F`OQWAa z`XPuOfTlDa{82D!z~%J6S#Y{5^XBHJVamo|y8b=-WjR{)oqJzCz`sHO>TrDSo$mF^ zeG!J9KHS@r;y7&m<%j=I3-FW|8kbNYdrlwHqSIBhaE`GhTKdu+Kw}N`4WHp41wp44 zlwz~d{7wr>kNzh`BTD5KEJ6KsIgq5Rj2>iO6iKltDAFJ?b(SFEI-#goEb(~iE=~A~ zpeaQ2HBLlqYZYmgk<5_Ve)d%PGv_{!syP~Jl3f^sVB87J6-(r^9DWw#__PAug86C`m7ch+mfG?$73yNa2y zQbt_Sz2e??nlt4q&5e?a=mUDp?i&gi($84*(YW>uS-PU;RDM&&EhcIA`?4c=-qam=4aKEsHB|S6^YDe z&0Y_YkAohqP%Cv9@pjm~zMo14|A_LkI}kX?@YIyMG}R1pW~T@~T-6KudiSeqqvMP% z7T1^2?-Mbter%-N z&6ms4vNw=Nq!=gugZ9Jl{y}3tLI2$=MT{dGbfrr#QG)kqO7%loHj1!Wh{HVFttR?m zvI8^{Am!hYW88)A4zyU8et+&qQYn5ss3xF70nxg- zl@-!ZhGhtYC(sd^KyO-}4Y}{dn+y&Q6Cc0CTBhU5p6gJOCc(oAAW##FFIRtZ=g$>h z&mSMAdY#i(2L}fM?3)c^QIJTh7izxg`>!?D3l4EG3yj;%U0Sr_U0tYqb-RHA8}u7I za2KWQFa%pPjLM~rdZq$9NiHF}sge>nWf~+kjCn?vPr|} zD|QTuz+%QBdxCGTW}E51U0>xNP@R-(T(t7enJzS6Y_BTr+P zP@Mi7PXV8a(6rN(%?F*zci>f_BsI70kVJuArMq@1CRE6yCgCl$TQCAaL)cLBCw;3% zH1%n4y`~LVDevZ9j!}B@2^?RD)yHAhbrnnpwx5kVCp>;$q6yX?U0WEj$v9@`E@2PI zRAt@1<-K$3bdYA|Tj-rvKVkf_=($xN)$e`&nN!>TCU`I9H2=*5&SLeySt-P^iGdyN zSoI$Y)q2%LS%|18I(ObKlao#3cL9iI<&WNb|_HH7UeUFd`U zIEcbp-g_|4z1ZG;bKrOGj=sXhPv|n-AQbGa!F(oXA?K%YiMgInw7Hs-kI?KnE?E_YCcB$kI zYg0au`Sk1^qY2N@JVBSMRk%EfcH}$35JHxY>)O%E`E59%H*J$X2n*2;urq$7e8BjI z%VIIU`;z(R;CuK=IU8v&Bu8S_#I?p0h0{s}10DBW9N42*G1PUI9f%FZeQe&^59F!- z;`?JA+@7PcL85Yx70pU3O}>6BF=hW~o0O{J^=-lcqBYcYd&&S-rA6|*o1|JIw@gAD z^}SYDjgJ^(3>$6Gvhh|R{CSQ=M-UqEAhTJ-D*a&-&^W`wlHmnnb^{v42Yckk!+NTiVBF|nXrzh9eCsPXIYBo zA3k45|2jXzP_*ZsurMvtH+?pK&1FB38 z`HwV5H7Y@s{v|}Bu}RB$)6P)$Vp zi}^_+ld8&>>uU4FlJdQBIWBieoV(bB|6k(NrrL}BF$L{H1c3Q0U1>E`CqUW2~@<~DIAif_5{LW zTCne}564|`=^%8{veRTy$-l`Kv1vFEZ6WDGBB6)$dDxSitkXSI&h;p3UdGr_MoaHYZ4d5|d*5ZhYKDMFG* z`)yXrQ)KtAa{B2gw_ZIVo3wn-MLXBy*Ssw~`3rdszedF)FJ=ig?IbrfwK$Ys|D%1C zp~$wkPNxD!UiV50biQP6Jh zr%U7b5oM?GtmB(E+ZzTK!d;3V(n2XcO&pAGW|0VMDn%1+~P`7XfLN@ zr5nr*2&edQH2Fnw!um&5Xo9oz;!sOzAGPoe{2_k4(m2e`McS5xN9+sS;wih#l9a^r zBw|}`Wi0eT5mn8CY}V(=zdNdlnWJV1RO}pm-F)9~F)L^)Wz(pn{EB|vQ5V4{%}l`7 zc3b^iQ@pXZf3Vzp11D#E(gm%Z%E_tB+EZC=L(yHH2m{{Ijq+DB!q5}S*?83+1oa9p zI3p31JZK^bXDA5NE)G0t3TW;=cfUZZLzHKhYG!qH0*Ixc7y%e%0sA*WXj8ym4+j8b z$vZ$x4&=n`gi^at*}kk>))!vCS)dgk?7em3^>>!w6l_L_w7mg5oFE#I9nS^e5QvpO zL7-{1^s5d}cj#Z}z5ap&_w2g5Iv5&1IQD3={NQ!pLw!re;Kx*m(G$x5SKcT~rCRrQ zYNU#1d=Jr$HZO~@c<1>?kPSp_Rht2QA-o3Ms5DZY(TpGI>{rV}c+-0qIynC-#4fWq8%MeAs&#s4JSYih>`|%_JAAlp^FfI zo5g#VWg4_fB#`wb7`)XT_Le!NM?NKgxRBi}TfOKV3YAg2%wz2LiYlUUexoL5x*=hR zayYc&NIY16_%rkiJWKDWn8iJw8p0ilch^SfV+;d8{vc}T7mvjwA5{7Vq!JB3O1?SdB zkUN3$K#%k{ai!cN(W#NO-CU-FELJfU0-siv4y6?Q?Y~M_jvF~{(c$C<;B^?7TuS!a z;$32Ex;VP}H}ZZ|XGM-la(0|#s1MqSkp0@7dn!I|g6SAtI@wcEQwuruBhxfxAYgb{L`)bkXx1i4v(S`%fjR%Qx@C9 z%8a!imA)i)^ z9Dw6?ujSGW>8}?%KMFtXL3MzLD|6A{8T||}4tXkel;5-0{~nBAi8&V#V(;%KZd` zsQfd%z}D5drz+Sg-$Le%2gzlHbx;zJ+e3x%WZ#xOcI)sZksj)Fx_(&f5F#c1}a z$U+{iiK4z)Wr_Nboafjz$J53yf+obdlVdvy_YaJAMeTAwH!cu8&Qs4FULVlT3~5hx zd-_MSHob$~n#B8K-Y{7}6toermZ{7#70&*R;WGQ#{W-r(e07s9P!i2#<46Fi{{cf0 z|I1h(2~jv*?|UAi2rS+e%G%#LM^A=Z7C!hfvXRpMr`6Rb%w=N^c$j9!#%S2m-huwZ9jrEUh zj;52ei?Gz3_Q6EjFKi)hUoz{?MJ5FI7Rd~PQo~{eD`P(H*k1{vOngLMRpgo-Lue*n zbCkOAJ&1ZQDI+F0)^{hY4&E%tF8#3Ua`X8xFswjd2(7NsrDfwQcTM;f2E$j3vM_1r zuySvjM>B6#Z#U932QXoIZNz-z%O|l=i7js_$5Qk){r5ZR(dIMhrW;uOvb3M##Bn8R zmZ}DI)FZf0!pqc7CoYqk#JT*G5Q*AwSw&Z3_dg)rv7NMv(AItGkxl+}3!+%Hmg^T5 zWgA~meC`bqEUmq~^JvzYW8-g{c*9>k4?X^}#ognrQLF7&ZGYQ9d`OqK1R+}=+^W9W zOgZW!+sPmq9BZOo_J3o5_7tX#+6Q*=4+_^Xb?MUu*cn| zp~-J2}(#$PfyTr#d(M#kpdKp>i@P5 zZoAgerw6N_-LlrrM$58+pTY#}WN@X8>RtwiP&BXQ|MbSMg{2fBeh}QfaQ}rJ)z=pj zpL*ImfH%sOD(;h4$~HB_K=EfWE0*w}zul&v<)ni%l!GarvXhzun~SJv>&fFPjvVI23yv4_@*X+VJNEPOFwY}2g2L&iZx~b!d%#{r1KgtmC{N0f`xn2-8^zQrK z(ZazS$&yQhVPb(V&x3<`OTq)#8dj71oH1f$HmJI{e#NMKt(oH`WJ2RiUf#^klT?yO zH(fgIwh-Ppt2wFuxV21Ul=bm_TJJQ6nEXlJ{d)~nZ`*f7EAA^N&X#`CJpRbs#%d|` z$S`jxMq$~_Dy;+3#g2N`?3tNL3g0-;ItcVd&+V?w{-!A?WZhr6yXBN zkEh=lH1zJ3t?JdN+KG*yIT7e@$aw#2irhK@0s^=XIMv2jnW(TaVc8%EF6hl7(L`3$ z1^_}pe3_u?g-uIW>O7n_FBhf-uFv(khpk>mPJPP}GO8SduLn>-ej9b*RA-Kz|8j)} z1inxbz)#(B6SNnITrAo~6SmQ`b zsMxsEDa~Gv$Q6-zW$s89^pT)Kh+@P8@Hk?~ZdvO{caE^CiQdcqx-uS^Y(Jq#&OmM$ zOe+;EyT@_=r9jBKuCIl|y*qtRNRIXi_-~V0SnZui()GL=+ZoG@X1za`=sf`_TKsTZ5BSf;AwZO-^*_ zYv&^9FD){(7x(yF$3wq?g7F(QkT>EK17WFx73CbaWku{zx zFz$kmBXS{y!$*(Xtn<}y`hCFMvXS7ywGtZm`O0bKYR8w=R_lMv@Bf*1+xVr)js#+X z*$ubTxQm?}8XCZnu$fD|k=Xg1UN)>&a{7iXwVm#_#5;-MP38yQS;C>f`9W0L(4oI8 zB=iMrRxrAJLi+uO=?c*yfv*>r_Uu|*#v^8%C9``IWhc05F+Xmv@8X!;rw>o3tUPFZ zI`yla;#itqf;kp*B_ZHvn)FDf>1@`=3wdMhA83s6s@}#T;}-*ZpIXTe=$C4F@Gc$B ztO$@#m>J(84#7sr6l#_S`J9wp^gR4C;*w|Lqe@$({!52-S8}k*jiV?v&-UV?W2lvE zS}`f46(gs^ea+Q{qRxuc(jJ4YMJ>~g@Q9Y5Km17dZ3w;w`)@0NOXXKq##PTwnlMOo zEtkhhJcPK>d=>9j@WjX<7J_N=@3**qEkkDFD!Bey_e4YwYy0gFe&@ru$yyEi4EMM zi#vpz189Q@7nL)4V)U#YLk$ZLpjxdDq&*OV3Kj=51s$myFT%3{4nx6u`yQ5{6< z?6v&VD-c9MrFymY;n62<5R(FfgxBjsOt7E4pUatEbWbqXY<5+KyPwlFZ`hCk@CcB8 z7!KLEv0tK-)+#pr=VY1vR9)Z3oR3?bRh%$bRUwz8hIOXv4@1Ha?_DgF5BgshQ^r1; z+uevImhsx&r7jO-)HaA#WZLm3(lM`yVr5kHCwMop?Pacc^`5lb;t?fNvzhQKilMq^ zoH`SWYmH_)V-#P|{US=SyNt1&ndtI$%@yKn6Ip1`FUQC;e(={Hi8idVxjy)P&c0S} z{Kl(W&M&>1Nw3t>qEr>5>pk_SO&td2t86oj;jNso2 z-U!;5widAJF07Q5_uYC5_N|K5M9*I>Ej}gn1X=b*KwyS>HCXO-A(aCVKoCg;$jrz{ z#FbU62dU&^`@Onk9X-^`uoF4j%n9C-?l?JmSmPc%nIv^(jS;d-XB;gtNF;O+6%P`rmHztWfi?i4gO7+qS)bH^tGO*2>vOqvfbv(&{69 zXMSiX=6zmX9GLCV(B=YtOpX0Ap5(}o?w9Eg{(ls(8hLcumf}Dv>SRc!Z`@bidz2=A z?~1=ByB3SSmKfX22KJN2!QPQZSt6FA=&?!>ofgk=@9dNc;lgVyjKTi21$&pv_Osk4 zFms+C)8j2-0)$)MC8UmW_b{g+nTHqSfC?|ijL1Bk|6CxFweHqQUz%J_wB=ZMoq3ek zQ4Jd778dUF4)wq0G!bHt>SAQ#No>OGmlw3%s=U%OZG>>{%e+^YE*kL3q!hn#?DJ!EG8W{&zfJNq0ezz%5cSWdM_+gX&rj(Gab;1mb3{kVdA8%lFZbz!8x3}KEG zAniECOiQC`*dOCQ2ODn;(1K#+T@I)(5X0Qhp9IMN2C;)+5;r;j3wA(qwn^5mWnM^p zpoTp7@&M05O)SYaef+hQH~*&v5J;(v<^b%HGk|ketqe}ckr)uD+r-X>FZ_|Y0kpQh zbr~Y*Ela>cINhPS;6Y+-U%vE%FpP`7xwDx$C~)Ac08h|c8fqj?I9^o@m~t1#Viy^w zCuLsYn^V<${xqkVeVC?@(CE2i+TVQZ`nyAi8QUx2o6()dN0kOA`~d`%A}&#I1|gaw zi2pz^lyfJuhw##k)igoOGdO~gC{TO*p}sBP)yY})B5pd0TBpxOPw1l&mH|i(Cr%3w zIkJh4+c6atA*zrrqu&AOnXnaM@ZaNsAQCGkzv#FYNnEOIeu3P6sIl5dqfmcjI%Z%UpiRDSNUNr z+$voa=R=ahLvmkAM{6V}1bS0&*7?!WCUg(Z%^9#rh)|KQzNdQN1YTtqAc}AidKWMC z_qL$#us_%m#77()|Ajwl+;S8#Xul`*^5OAPEVQEmkCtvlLTKtxLZfv{Rq+#4^o{25OUFw;Ye%-9Os`E!qNc{uSCJBb0JLIodH)c57NcU*E>UUZQ<((@jcu(1^WdT63g^=m{x#Da8? ze8cs5*+^5pDX&A zXSL^vZf}=E`iiiFetXgjf%K^oO7DCgish77)=4Ezya|cjt3q>gQxh&`w#&lzY7hA2 zhTm}4_nq=^y>UF-3UZoMoG`1o+j?|_RCnJ(6SfDWZa_HVOW+HU zT#Foqf}%pJQjGFA{o9;l+^yB?PmIiVry1$FSV^u9f4}lrg@hY=i3tUm3Mh$IrP0pw z`)*QmD&}E-e1G|T_!3RM&iCY9QRl?;gEKR?MZ<~<=tuHKGbhQ9$<*}b%XFqm^OZ44 zFWri3cOVYtbBtcQZZ=ozH|rlvZnHAvDy86i6Dx}Z@2y{~v3kWv`8%R0ysg%Cx(Qd_Kuh6tdZRQZ7vdoxr~yP8#H! z1BVl_x&q!Erm$8OXS0V*9ndv})JJ&xv>2`u9W_O6Fc)ZILGD}1+$r;&rXzZFMGC_l zwB);isPSN0)DeORcu#0$IvWxr4@n!BK}raBKp>}-{C3x@gN3)V(?PQb2(FVc3EGT) zT`bk$yX9wd=TqLgW~qI~yigp9&3B0X3*pC?9d&>CwdNK9_^UlL5T7#8F%T;stULnA z*YmA7+-jezC-Ssz<5t!}4WtEV%JLNpKSW+4Rp>qJoHIx~=;jw4rA%-n@#99|&-urZ+whp}j_t*W7Z)9i zE)C&k#76NmrRy~Q+EY$x@iVW(W6-vV2qy|v8yXg=e)&nBKDwa9plHj$g=YAMB+E^@ zR{pN8xMCXKW=HA zJVIMH%uyzo5f3`wDCPey+k6WvfW+Ky`>afMTrvOoLrhEjd%Z78$-*ll&iS@tTuUFX zVq7JjcP}}6Pxi^CluZ9Hxl+>TUEZ2drJcf*bAh&QABlv&lw{7stfX+$S9Ibj!fzu? zho@ZsEQ`NehZ71y`OIeO_F40e{!Y8v*AB3$zuP90J?WSsPg&V*x@y>vra*SfUbnY> zf2)13fnqs*>&%JnZ(QwH54ZHI^OINi8v`fnIDdWJtW`j(lk75a@9V$M$q7%Nr53RD zO1)<$3M%#wb=21m{Xd7`JkGq&tw&dfx)F|nd&h#IKPCFfp6BaC8$R+}@{O%J>ZA%r z16Eb;gx_A36S9lXt?P9fC393D^Deei@AId61Kc^$y67I*S#TJMUFfmHF;pRU1WUa*U!7Xumek=3eY<_PU zEjpBd^uZ0X)d^D*ut6567_17fvRbM!^+wzemW?+r0R_z1_H^Pd<78EkD$0+xr`sQ! zD{Az6!d*N<-J2S3ScJ#*Cb+`a>CQ@V50-+HbI(HDL6zVIra zu-1>*&_?m;gCL&mbG`WPWSwz_8<1*%f(paxX z&opuZFBdI-2*R)^qxeC=n!Ah__^`>%3hfwx$NIF3kxHVSO~MOHt839RoPnBoms<+6Cp#PH6K{C^F8XglJ!G z`_B`5;Y1ouTe@%KXi)=-noKMDO4;?V!he%W{(O@2yb|Bx4oj>8g^cBj4e<xg*t(CwYhX*%^brU+q0t`7}d6tG|ZLy;! z6-ddn45+i1QmhSI9NA5$dH9bIZl;o=2_1Gt0r#@&Xn1m;tgSs|C9$63kLe2HjI0W^ zYbYMo*Jc?vXDl77`z5_Lrcc3TBmEIQ@79$H$@0a;DTt>Yau6WoC0cbwN7x6I_OQrG zlp)#x7&n_)s$Eellck1_40s5eOE=n=jyW0X4x2@@PtH;p+)~cUCY-~ak~XN^bGBY- zCbT=4x|MCrI5Q`%pE{cjOcQ1u+Z~>J&GvLg5dR^5TB_Gu=ePk)OK6WwdK#e!*Kd7Z z;F$GE(tG{h(;`c>`&hI8STnx%tKOJHQ^kBq*te_uu6?(rZ=C(|YTcjGdwMuBuR~p6 z`8lbv`^OJ#jR{KN2d@IHX5r_TOOKS5pAkKb5HF+uwQg}uh4R?-e(F z@~4#FwyU8`F4}fhC)dVK$a-gUk{Mr^t3JvfA_;XpZOIs`zwol{IL6B&6&^6ZFJrp? zL|xa5BF+URjTbKGHbIY#84=H_N127HpdXwKG}jKwe@jO5Fr1n~-0}vO|6R6!`g~2d z85%Z(36mPFy45!lD$O+koPpgpjpmRIyIqY>9B4TtEEu7Yur+e%$??E_|Cbhm`{7N> z)nwm+NTkI446kGaU;qL$7=gk(3F$jPuK<8P%1{ktOrZ8t0+SZOtpmKg{|aB^o&kge zxJyp$ta&NYym2oOW;WN)sREb*8Dh34AcW5@Dd{~}2Xe8nUy+xm0mE--=zt&s?z*6j2kW>% z-9!P8d5Hy7_?4BU;|S~sI%2FhLqYicY(L1Dw-b6*AnmSR)vrD)+Ow|Jg9~dQNV)Y` zBt$dJtgU-uN&x3EF`@A3nX#p1mv9?MB8HMkYV{H?O*ra9Zw&qt2#p7q6Uxu0vr~bi z?azs5r}6F0fyqflclUb7H%W=8ocB~6g|UgRY;5vCnhLkAwKa47Usp8Z!7B+LvF(qi zp%LrU5UmdW5#&czWu?tC?u=oAC;T?ovvMnGr&yC+`z5pRd!YzqO31}U_AU=AHa6B# zDC=QA09N2bpsoMOgugcVy2Ts_2e(fCCuV;H2U9{fkFc~G?$Occ>x@p5@j(`C=deUnc%qXmH@C|9#J8>;85(9QZja$nyLgspKwF6IS^hVQjllM)s4KCrwAOZxJ?j1&(CEueP!@QZjhZVvY2* ziCq)VN?4>Jaodyo!v{B)#)8ms{^qC61U^K`;#UJUve`|DvV)Twq%D@N*|-r!;wf&t z#?NC9pE7Qou6ZA^vdS-Av91I+S`!GG1{T?gKC~PmG{FSWg;o9DfXb&7vJcSX2GpM8 zQh|2&!Qa2z4q}H6;J%%d-v(@TkY-~flT=Vf4;7{V{-bc*y;fbdnNHW;zEy_OSM4iS(Txg<-w+AweJ zhm309s9>-8z*{h2s92ta;VP^~6kRk{im)k^s~-%S(?Ox!DO=|3=(sbYq4Ej#EWZ|( z=)Z6&GJ(AlP{A-J2PL9oKTra!0^d9sm;i#ucsJyMA8%xpp4jYrwW2nhi1V#q|{^ba8-k%FTZ^HM&ianM8;~`x6SHm zzzJvd#BrQK*Ej4s1@#gDIk6?Ua7L}SQHR*Rvz+e+zq5?38r9vxt)Gw@Vfb(Ch|Qj< zX-RG4e24xw%Mf|ywS^`JPrgw3`^o?}pOuxAhB8{_wongHWAvCe2|G)Y zC0*WF zH`W9X9JWG8#54YnFM zQ6VJM6$)HhBPpr^Bi;;$3R?7F$+~?x1!LE77fv={0o)cn`AS zBM_a7^D{~@uT&3FxCvDpT32oH4fcX-a2_$!B-}ArfmsJj21hv@D+mh=c+5ghzY`yq zy%<|)uYC$o-O*id_w=^}e!xd_K*9z8{X=ZaKr=$he^^N&_7nM)d2vWt0x-Ww?^7eR zx(MtkBG7e%pH&nxyowlIZy~n|D5;^6L!v@pKyC*k3L%g=NDzFqTa*P3C>w$$fxLN( zE%VjS88cr%mW}h=Z9R`&fm*Z}nY*8#x;nc56guJ_Q2-kcLVK``Yh~A5prvH*++tdk^Kjc?Oa(19nLQI)(xK22^LtKEM z>nC>-Wa#ZRL8HG6nl1GMe4)*TX4SL z=6N|y{a$5Cn-hJT^0q0M3Pe77F=V6)1u9&pc=+eZaj$#dq@{5`q<^L58#IU~k%a~5 z@kUp)2TnwZB#sYcVb?}S+*2dBo2%u?9=xNLV-N3$&?#Eo@ffuwq`CJuaOeW}#8KpY z`nFmJ-mrH70b?xNgY&N`z4ccaOc{SQg`Df0>7qQuao@(ET9|qW*^8E3Yco1E#F*WW zWY}}$)X;@LvvaSiV-nFZDwp_qnPyVMSwk#!y&lvMK?#qSX#ZGtVQNzi&_K1aq! zHtWq1E~<#aE^rL<9Y*+#I{P%0iSs{eylh&T{I?m?P6AGk4;4tS$gklkqOj%(1nD`e z$7<~V6=)AX^_XLU4mcc>u&TcR=jRAwXH2}#SH0804zKHoL#*8hopmH925?jedPBW% z8|nGMW#%IYGkKH3Hs=N#|0eTWAuz8Q=ecG0{$UuaBwVInKPKEK3bqDxw+Rd}te9qR z04e~^G=$>{%Id;GHZ(%-u&+&JQYbrNWawj}@j9XdEG;(-W{-UPA&Ljk`lYgZSEBVM z=H1Qd$J;93SE&74NxpUG`4U7$!2+~j?jq&Zuah=2gRrX06{cXVgF)E#`N`J*uz<^O zawA7N_+lUjB8b2bCPXkO`VOW^*hnG4JEngX>e7!yyHElw!q1LW|LTQb<2Vu~>YeXE z%NjgNT~*cS7#KVN;LH(RkaDp5^;3Lw{o4CUmgfcNsF1NIf*FJFtT@r`KZ$7$OdCMv zHaIh5b0Q8c20*nTF%9tBf3_Sgs@p$;Ey(p`v%1stGYl)^AaxmrcQ!UQPh4C6DwOcU zh6%)7_{F}$!hQ1hUrC1eS0fF}Phwg9<0SD>c{978(%!3{ZbK%oHSZ)qi*r;H)k%OK4Y1#A22%XE4 zK4W-~KhTr>Z!z}V?D;pF`vXMEFVFs6S=-bO`1GQK=nvsq(RA7>^DpD-l;yGrJBPcx z4u!@+w24Kb(lz%9$!Wr#KKL44dOuY5WoMJKJkfJJvGsAXXMcK~dEYeMU2?66I5hP% zqZyBwiqYitA2T#A3t0)B+8eVg-sRUpsp1*#e#a|*OCcGs}f43Do5mL*>S> z^66X_=(;O?k48d^!--#CuHtb|!>zQfYYHnTO`HB|%M?amwb;XyZ|E*OS5pcmcl{%j z(S7@n_;2M_w%xC*em+o(0)+?)wNz10a=L(U)&%u(2#kW1T29|r^h4oo7HIJ+zjmpf zvd5)RUk!W2-x8}LieQlu869Mp7~(f<-b`W`X&+kauYYj!mnEO8pa6$>(d%$b%yjO> zrBd}e7Xlt0$#Q2dt|X^`1aaJ31~pm>jdhr1iZyR}R@*BdsZ-p^OJitzL?f%L&)`R2 z&L?5=o3b`~IQTcIjBWPL>p090ez)cPcVmxuU`vU4Os!_R66JdLUjAQrMBkfWMItFmCcgq~DIAA|2 zA6ijl6MpajW@X?`hBu7yGnxn+4tFpEH3~c4iM?9SbKk}2YkE>n@3aVLtpR0-oJ$Zm z#}sQJ^X~3b=R!?-6qZQ;=W)K(@m^eIx~oi4TlT!Eb49{ypNcx+1JtIA>9CXakSBQ`SA@A0&r*9P`2!^eyxjA|IHl z?x_r8kOg_8mB!+mSBMb`{yUO}4)TJX1sV9l1>dYiah2Iw-|g!d+&O};*$~2lk?2c! zM^Y?XzW2+W1QPgx9TdiJuxXh>+a6VDBL=ljZgFw>V8fwW}>&Qv7%N!QnU+{D%SlGNeL6hz%wkY&Szc+ebpTc%00nqQ%il=-MLAOi9kC zGre)9xSpu))dBXurPnlDxL7s%9^B6HQ{YKd&NnJt)0H+te>{HB!;&F^Gb(?FZLq7^ zVt?rv*J$J_gLcK`aZ<+E5DWX?Tx(r@Lgd%;2Ah@y@k6BFDGFgwk>_x^QY^;v5VK%+ zM{jKBFs$;gc=0P?6`>J6@qHzrH99K#8pHi0NX5JnCAMlWQ7HbnvAlYauxw@ zovg;z+r9ROmZSByqT&=KJVh=pEa9{pVrWNjSA~^vY@#+M;}quTUoZps(tFW>m{>A z6m=qF(Ix>{T1?3}_>zInc)7JO-26V?7L^qAHG-%IcF<+$A|t9wWR{KG3c(ra4jqcY zu`yZZSVK5n##(5!l!M=M1s|9!GX8^x@6;9s2c7Nr0VwF84y5{0T=`y=^_lvNV7&Ck zV9)wUPk&IVa{jP+2_*H1wn;%A-q)|#vBcUZvvJ)f0LNLjpNQ!TItuSweE&QMTVRzU z(cBSZd=F%s0(ntvpA|lV^76OaXzEiX4wlV+Rc$r}5Nm+r5D39_(4IV6u5-F?i3y)l z`Zj0dx|-v^h;*aL55U&)5H-+f8%(A};U4eoY%v>P{ht=VP8XkHkQ&>Usr70g7M!2( zzPpP}Nk@hvdTjq$Tqq?1O zyY29-uJ&7L=!?EXjk=-+qb~Au_I&-w-m$&<(G9u&`niAVNFn*P#xwEw$8Q#BPfJPp z!wDXv$u3oEjUJ`djIOT?agiWiQ;oe7JH!+%KSpMAZ6U;E#oYQ{|DL=HE{GXx2uK{F zV-Nc1EU^c);3C!_d`UVi$*8W%AytT@j?+1>&Rj*h>ZLitTJIU8vB`_!S>ZzLkUS}owN4(&!sWO9VtHdIask@_Hz~$wk z@)g`V>WimKpD%IFoyvTFyXXJ z;p4IV=u?59IdkK}u}N9N+x}%d!6|FZd1!t#WX>ARw-BhVG9zCRF&P=^#zfX4(SE~0qM>I(nyHXozji8NXMbO zQ$o5Mq`T$NNH-`bDe&%d|KI1EW2i6=T-V-f&9&xlhLu^dn@HKp6wEp_D#3*xu&WQU z%_BhLKPO|2$ldxO%9_AKy0N3R_DNY`t0NAn2+Jqy_(WE<%ZrlmS4jAX4D{&oxi<1j z(n(W9^NW{DcYH#dIwDOy*lwyy?pVTKjFpd~2+ z>v-U;&3z9gfG?H}#!$hVv2wkZc%T{rDHv=s!6Pv0wyS8!0;#LtQdvyCV3?^}WQET} zI1jM%xi*3G-i#~!><$dZghI`@9YAudLQ#Ac_qeFC?{j0yV0oy?VLey_1Egh1ID{5h zh){ikx0?TLzJ8z`Hj6i@u`23)=`n06GY18324=E<}>hiL%%Az_yiDR6-=+RkS? z7@d!1oF^+V04dZ?bX;gK(-QT1-)pEFJjQ&yZ&7#KtkTv!_b5OU3;~cG_>BWX@Q6X* z2J*WWAB-l#6=0Ti+VC6f_U{qAD+7zGJ|F@Tl+?>q1tXR^q0FzNsl+FM_jGDbSRjpS z(EA^}kzyw$1Z1m1b$RfONyy0RCUn$Z)(WGk>0hLBk+y-Mjz4#YpHk*v7%nLG79m^N zZCb4AEJ;AlDODyh|CFiQ|E1|tN1Jc5Vwq_mRX<3rk2yng!cCtC!w{LxdS9t{*26r` zTf7@*3<%Z7;xii)7M$6>bF^kW*(ua%N&|hTvj_DI99tHhy0OWu*y2L8$VS_+ z@9#-v5rv+xSg|mXCo24;ht(b5JR&Isq#*wYDahie?+|(rYE7@>!&9#0e9KRo$AA}{ zcZ8dp^o~K+K3WeQhQ)j1Rzgyo5oa8N8maJ^bgP_`QZ@;nnh&`ztOf@MDl%?S=ks{C zz6WsR<{&kN=DhU+qBn6OM zFF27cs{2w>U!0xoAMf=FU6c^Eru9;@M{r*g>hPZ_!BI&oXyKP)MO2DPRYJrB18L1+ zTNiy}cEcnq2QM4i49}9XJuB3aAL{b4cr-kfs=4oZG3Ec7p22)N$Qk&->frz=y3;aK zx6gO{6dxc9hWOz@ZkO;X8!##8@z8+IVmmzfUy>&%vhc!#Izj{;z=j>@7ZQfeK;7b; zWfB>u6v~nFcO>Z*NE`x6I(1gHp#w-db`=8f*tSZwy9gPqR8K*LAyQf%R76mbfT01{ z7zzn0`*SSTE1*LPzR%qRknV*wGg_8cmj6*CN%LQHQX#=x5zrl_g0;;&)oP?ukiRMi z%2~LPE^>%}^5tRS%OW!~vp&3k{U)5{1yk34!9e)Tm|Pa%OuZ~|D0ouT9J0ckIPr1LViAV38H@6j7(~Af3~>$f_#kzhxyiI}&(rQ}?Wb7c_QYdIJUm0qN;etHciV zNyj$V50$SFf8fOQf(rzkqm5GnT_hRyd?`hsB?qU}m;!0>vq_?Ky#R|qa??{I=jvdx z>tG6mStfidGqk0Y~-Wp4bq9v^?o@nET<)H@68{h0%u-yZALeEuGt`>;bfUozz%mBEBqZS-{ zWWEn@LeamqvwKSL3usO%fa1~(^hoeP=Gte$#w&bt@ZZV$0~sKzv>K z4mG15{uuyY!jtGfEG19A_og$1csY((PO6`nHdLp-M4T0rah*U=o%`M?q8G&3J2@qQdcaan{=No03Gh$P9!y_s;4B zAQ=K5Fb)Jhr=tXsaIHQtP*ecFL@l8D0f#6^y@7-FTPA&@f!sASJ#Ac&SD?NK)&>5@ zGJ*$xsE2qC8h+Y%28SwW<=+9hOOBb%n7zmhDA@4)FQ865c|~PUE(4|u3>JCW8vOob z5Za9KcqPVfHZKrH095Lxi?!x}1UcR~2@r7C*pD0H zzd-h`?*CevG30zKL9m|%t}Ea-$jTqlTOulWyX>|D2{|xwlo*}Dcv{}R*`ZqF8B$UJDN@hVV`uxtWDJ6jS-1uHPq z9%?o~*9X**pC}m12wV1>ANY$Ap*(`aZ=fTMxcfhbgxFk(4~kz6ougg{eyV+{%;;n3 zigtpANT8rt_?+%B=nt+JL4+obBxRw*R&G)ZL2jaqBD7>zs>vFgwj}=GyW4v<(n0qk zUdh}AKsdmc$n1~TABcJ}euS8v%u7A9tV12SsY2eAS07`leB2+z2=NlA^;2>cjtw!YIH=PaFV~3ofddwPT7Y1BxMvA2Lqqb6 zE|NiU0#Dck-K;IuT7%6Cc(^FwkfaRnIDb&M1DtU11}$IZZ=V4ixkJ}I-+zC9JCg#R znLB%eA4alt2e>0Z=f4Glj5gpN0W^&gJ@@l%d7vCVEldP1Z0DPEbFfR$1Akxs{?HuW zySVgIyK$sra6sL0*_!~!CU`9TBADW-H^UpQ_4SyoptltY%Hd0aDn2O!n!pwD=nKcxwn~A)o*QUm5QD7DCKb<6F~P z#~GK6CjWjR!soCy1_HX#5>*rxL%@NW3K!Pp=nuD7j+}EM9UqM!_zuj+w9My_;Lbhx ziwFNL*S4^hYLp;00t4SQe{?;L>pg8v&$(g3_IP&t=d;UXx|DEL73IH(cfu}MrRz5jNA zS`QR?IP(-<(C}v~FoPc)kVdtB1OZnXSl(R(sH0b!Y`}5>=d6O0v#M(HGT>7V72SX> zO`}sPuQt$BgCXDjig{h$=Bj8?1~O@jQY`u@rpg0slux^zQXby_m+e{d@89RQZo$Vjusi|q_$NBY3Oyl+ zel;cbGvsE%mtKbn5t9G(WBzn;RRPZX^A6I627t2kJ4o>hhdYvh7z;M7v)qmj3L2!vQdax~{P&Aay6Cr62F9>uhw14x z;9vsxB#?(k5@{x%9eX}@SQ9uuI}ngV^G#vr;^LB4QF$G1)}zp~cX4B##iM`%hHg=> z?o)tJE@1;IL$Llom)rpsdtQUAFraxL(Lg-iO8)vv+1pgyfYYDN_*ei7$ZH_)%JKHk zNAP?Vsiy&<4EzDm%}_^uegk9O%>e<>yYt4bG?IO2RBUW8fRHE043(nu~P)%(M*08AI2HfPJS=M){9DzfwUx1Dw1 z@2Y8vsmmemfFjXvB3oF>M)6fNU0P;3yl_>>O&K)@#4rk=bm|MGE%m~Fc(FuN+=Z!# ziz>3`ys-)1LSY9{PX8>vxS|o3(f`t+TnZ+P;@_oT*gQ+eQm=A*vwKFGp3YL zpz6btdyPO!Hb^egE{>A%mc&8&pRIAhvdWMpnV5o%A!U5ilRZE^apVm+6{J)gUPrq! zB}H0MP*b3EZ5Ft8U-uEGuwT(oAVBb$sEVG+MpF*B=!4xGNKly^m9I}?XD5cMt9!0n z>Z2nNHFt}1UiDb859}Q5X53-Uj^}u|=zH*=`SYuF8RVx=)bO!OC-EQFINFKvKZPeL z#{d2;|D=9K?qKXTyLwTlUfZ9?w8qIDJ?nAoG5rJeKgro5ncf*{0 zf<+;&#_@sw+Cws$)=r1#=`1Ou;J(yunFJ_>f&|Ng6-Ay~J%x{=?WSYk^|sC4+e9I} z>!86R8i5S5jsUrr%hO`I3umM&))=)ELStw-rX_dsI@ao0sVnKEa{i`9q01MGfpF02 z0L`b6zJPKbBkz$_nix;weO(NS4qp8J538|5C-i_1=|T8Y>Gz2%r;n0?(_e_>3(w06 z0}E-NL5Q3JpOhjAFASgdf_}*P&%WBSM>M7A2*6Lk@rl4?m=ESQpgW;0+6D?_(YJ4r@OuC( zW$5VWXn`Sg_MX#bADYdzeV;9fWR>iu{#iP7aB_QX+@81Y!lCMv|4wXuKYe^?YO(cE z=~Oh~!m{neVrkFf+leKHizMUNb~?Y%rHstqWpC`g$94;6JVR{Y)UY^no;_NYV{Rf6 z#4>s|OJi354Y#)e|A^gm-SjzyA;Us}r&6V0=WPeCUT9x^iF{K~V0pFw2F`sBzOo({ z+gK+|8Uq^>lS@EA_2o^4hH8^Ikp!Rc8$RLpuw?al=eQVXa8Q#g&0NIw#?r6s8MCNM2e8`E<`787tMlG&}LqO2v3Ceo4KpWJnh17pMq?!C8(NoHxO{1FZYbz#ZZ z^BI9?`i#-|(LLJ^{9&YE2m%PBY_w&*zt?0U!f>;*$$qotqRL|AYMb~w{I`(lQiTw= za%kEZev{XIOXncuyN_MyfZ)D;ie)9oJt?0C1ICqD+3CU^W*&Zn8QE^_gzm);3feyB-#xcGq!%@IRcxpn_aK*f%N@H(la}pwXRFyxd*;344RBe--h|z zB4uHquYl4TAjO1uiQi2In@~l;#TQ}^lxfdiiIFKjK z(TDfYtJC8wNm?O5aOHwuGgPVz2t7eiPz);njF*e37s~Ov^{LPkW>Jad5GX+mYLKzFV*hi8eCxXX_5>6j)fVHVPe36b ztL_X`mibD3MI0m$N(M>>P<+&!=ej@_)VAlH_y_l|TUM8wXD2D#Mj66;+gfg3uzGbITbA2`COg6ag!gl3^Tw->|0? z=`p`!Pf;B~B9%Kj&ebSMS0{@Mz87N|%{sNwnOUoIm z5EudIJHb^NE*@Osg~r6ofCiq>C?3TPY-7VHXa>87JcQ$TI0; zj-ETIp`K6^qEZBTJJ9@Y=yqYq$NP+~O3Cyr`bmX8IW|jYZ_PWw`sEOHgZFYxcq4tv z_?4D}SJtahUdrq#bu4@qVq|1wAln5DDB+WSz!`vto*Dsc1)LQC#wCDf{S=g7t0H5#agbJ32|bFV{;H%_{FCerFHijb_#tJ7LtUfwhiHpIz1LpVxG-VH z+vrKxabHoCbPHI#I4}B&`oSr$Zt9W8twBbWJ<3G~+phP`E;=MA zf}}|OpfSJY^cnF>TVWY=T%o8*+8)0TkwfnKDf9F5))CJ`!^7ploDedjM>Zou{uwk%Vcpm#M{3F?YJHRo_y}NwO?wUD<7#d zh*O}F!0)Wvi!(=C9krLACQjPbwaR%cv$qx|(6#!o{_o#1ou$Ls8phVYo0}D)fg6vu z<2tu-T_>b=*zPNCcfLr~kQV4v7rxEsBhjrifr4?~sJJC*P1zk?hp!_1QnBflA{1$q z9rMtkKLg%;Eyo=ZpVno<{3 zh-0M$dqpYjv(3<%MKt2BH!}m!R^Z#H661@Uha6<0ozs$qztdfK`TFjniIJ~t3^~r+ z@>H!3Q{BYB#}4qTZ#P$X<7oI>;|(K$K>#>ZIXF4LgD_-ewKu7vmHB#=3oXnVwG~a; zX(ps!gfb_e-s!sxzLb*Fd(>WTkw)hc1Mvn1%sJF0)J3|`6%QQ9JEm=5;#7KT21B<7&vIKtg}R5CiRj|*B! zIVsth?JdUMed>{Y^-G?MUE~3F%KYKJ4U@H4A_5)4j7T!l7;GSqpy3i?Egtn8_LWtr z-D3y}b58a~`l&g}2=V0CX4uA6jU(+cXM3E?@Qtz%Q4k6bjG1B)c~Yzc+eNnH+i)u4<$(J1*T1+o4=AK(0cIzHL_Bfqy zrEh&5d75BZE8AuuT_4~S-<;+ic8ZUg{)Ue~`vsIEr&82%X<}UN@4>A`Vvs$nH#fwu zm20(xjcoE``x%s+tP;dQ?(qw;t7u+YbridVHRFEue#{W{m@Nh;y#lk(dd|GODdX0N zUPt73CauZJa_Xt}I`*WP&?!kzg9Z!2mF#`7)B+=>X-W2KLlKF;KFl0!_5fxX(!U`1;C}08HKZ&h>+i zg`ad9e$|`*WYDwpcN%oWB;@3Us!Z^FB$3I2uCB+1aO-UmiEb+p?fmqebiau9+Txyz}#hrt_lC&+kk#`Q3;wq^;Od0i6QputHPbyH3si z)3Prlh>g}~Oz=$0y|`VeKfL=MN!AM37mo!m9tZXfS5-@W%Z zU|5wGq6uQa!IPktUPM#GAtuTy`Pq}3&l*8TC$RbR3;HdgTi*#kUcWiH8;+|MvROiP z6m=vkP0){_x7a+2Uz~1O)d|fDxIdXz{ClWX$ z6jU}+=w3D9#AiPk)MHKO#XO1POBIM)bJG7pZ8m<$TdupV?4W}7N{>E1JDajRI6C^b zs}k|#;lz7l3fiEEAIG|Q9%RR29C$V@0Vh_+f6YvD)dtw5LY^03xetl+krjkUP?emI z>|Gc=Lk#BqtL+aBJ`C1ZkjFt{N+MSjyBhCSv9g9tk6Sy>P_pszcfv&R%fa9x`OyBqw~=B?>Gi!b_Tdw|AFOg&pS zxc|F}L{MMP7BkF^rc4<2mjcWg`@vY-Z~Y>{mFgr$A$^~rTZy$F!> zCItedk$Z#CwyY>-l{8^XCI01PgH!x2(b!70R5QaLO}F@!U`vH;m*btUr6QCmyPlJ9 znaNRCA2iT1K86IDIU1Bis7d8Yxk<(`X;zfZ+mjp^El**){pEGCwi!Uvwkg2J)dgP0 z$oK);AmlT{xOnGMUdO;PU`fU0#X_Fm#I*_&)L3>HknR$G>+B^Hq%@WVl3R!bb+kqI*MsS``$a*DSSiRY8#;v4P?QhHiqcPiJYk17e) z7*a+NexR%d{ZZVN`jZ`~KDE4@;wOgMlYaBnwBJtDX+cgdUMbO;^tQEbt2^J0m_dU$ zFmWm-)P2z9s?H|u@%Y=AU~O>k*BIlN;e_9FA!g@4zh@mjAOsPjGDxCEA&AeFA0C@+ ztixRHeP3x+ig-ofGlwFoXa&_+k=LtAgw?h(iuySg5`9wm4c1q|$nHCdW#)IlEC@)9 zN5g0Q4=M&sGG7&3Xppk^`bqwADYJqNIk&;&(oC*nrUig1ZCnv+8-Nw4-2v1_jC!dw zdy3-w{c3ZwUPpbEroV2UE?4VTCyVoUR1=rSLxYWMS2-t^cfNyGY52VjkJDVqP`>Ei z8;WHw@rL1>0b(H5gK){)+nKAny_^k_(C-S#S~kplywfvFJz)BN@cJ)J`G6Zk^cJUi zjJ}lvl@!hgrd_=t;8jvtb1}tRmD)Mi%5!viHV{oQ*<7W0)IZ?jq!QNIQ zUKWMaj%S{@o-(LByzJzJ48S^zS>(Lb&w(k~o+*gSs3narKaivex3aCMto#%(cVkh#LO>N zJ~hT6&q>_%qDT=t`0#~?eaP8xzRXz*XcI6qNQCittLFX^sXFB@K#1O=E`ngI<&?)! z7j=`WAMMFNP69&{Y2T;44C(?D0m;vt$debb>N9g1(nlqi8u6}iOpFP(KZC*?`a;t+ z#!m#U{Y;s+Q2>Xw@+hD5m|Pi;Dc{rdaMvS06zO692uXv`kxN zad?UeoLs_O21y|if(7a)m>VyI9bcDz>|Xb3x6 z6)n4BVP*j41}d=H!~-;bfNq0(32RK===V@|19L4{BDb6)`z`1W1oS%hlO-WHFTO`E zv!QCysj#bEBoL)*K2s>~CNBqG z2WOBBaIt}9DkhSSGp6r}s~+aI-mi@JXEP_aydr!$!;W@_J>h}ZLhO*%`uc|1m8Ag? zf7!eK@W3^LI8U8ezBg_+_I^nY)q*4LZ|nTNKxHD%o_v0x`dgN3T!>Nq_rH4X>%Jb0 z%H%3%x9mY^B^tD_FVsAVuLwsvW8C#vcJTagX6Dy=PQ8zNYKvZ>_B^pc^6j|i;eGMZ zflB3PRSzjyq(h^TUCUy`nXNrPfi1cI2<0pA$v6;+d69%PUJ$&8R6A8={xDK=4dCbN zGnpa%wqzAG9YwYN9qnai_dAU2U4YxM7BIOQkDbGV`AtnlTH3IwOGiq<+{oU(x7q5? z#3`gN*%ClyMtzzSaaIR2cqe$dut*N_zW~Hy!xL+D^+*d$;lUUU>0(b@vPC^QF@7jf z;I9bw`W2b8JsOQ;VxWq7VMgt+szQJEE!}Fomvos^R@rzG9fu5^nKbEy*s0dq!zBqA zcKZSFMs@Xe1B1xbipxJR@iPWY@=x}at-fVqRA2s)8`JYSD=g&cgo2ugdOBJT_%7&gOfDn%yHHnr0-Iy&J zVNlv`8Y10z(nA}+MQZkuquH%mE+Gl-^aCRD;Tn|{XFKrJw!)yCAeyJ#zH5?t`#HRa)hYI0; zZ>4vRwNcmF%X`1trhri)unB+%5lYbfp#VB*|E_p?X(ou5rXLaw5rYp=(ldVZ|@e+i1qUwe6i=PSR^Ib4ZFW3qK&9w zdfeWk)*G)h6D7v88vH|^m+RpuwDf(A&!|M5bCx%^P8Au|Xr|0;Wrju5n~^skAqfAJ zjju_gB&w?N0MVEFI%^3(azHHr%UXqu5Ehau&@_)1grGeB20Y#9RM*`H?MM1&7U$bp zH?T>+>=Z*k&p9@cch8)rn)yaP=Af&a+iiUx_#iFxJ}uPvGnN#ia(W-g6v(Wb6oaSp zkQZBPCz5A0e7QS&GbdM5vwT;ZbD@qI~{cIaKAUw+ z4QfYj3zd?i{8Nyc$O;LqDC=5(KC@lZVsI8wrtllbHBH?erq{KNoq$DB9~Z9bMJT5Gv0t@UPKVv6Bpn@YJs8(K5h_zsgCZ{TQF)+gi*IySp0W1J%@gV_R~}}d^~6Z97do@ z&cJNMFOvHmlLjb%wS(~-FokL$=BT$&ambl3ek?|dvU89@R}?!_W8DRVVkX(RQT~nH z$n6gDO7%|$Sb2H_F;tVSZlZd>UY}W>Z|iHfY+|w`;X%rpZ{$~AI?*eU&nvN9%4eb% zY4Gq8lJMJ;x9FK2xcWDKL{lVq0dSh z<~(M$9O?zvt{46wpnZ8emF4(wBB_J8?1h(>naSO#}bFJV!(g!{6A@C3lym$yj5> z>V^w@o85m_(xo8DsZIC;0|4?C38BQ~W#-1ajYY4C|L>0Ll{2cg%(33|@jdvq?NCGj z$MNg)AHh6kOVBP0C>l%?60ukb(95+NSiD^M(t%MBEIE}V-u-rFziF4vQ|-PGNl({t zY)7>3l%Po|j=jJc`R;U4gF~;NI=Lenp{$H6NSP8)&lvGyDV4s8aVc^RI;9g?Kn{=T zvbKky24Fo6iXCb$Oqcj67mUR_d$|@W5vYpQ;)5^v!Uxumjktd##S(m0l8VY7G%%8#vW zJo3zZC^k=R$_UAe?Rj{=!U-*H*^lMs`A`Vp-E>1=AOq$$3UFSEfZzdR5J>}e(P)F9 zM}#l=)0hOiwU$-OZuzC0-z9Kr!@`h0IMZn=^f!EJhq61%U`=n*sAXfIfv5qWzuycu zz@zw9JZWy<7c9S^^~QoY!B8-ZD2Ed137?6CSNY!`ft8$#_Fw-nK$MV=xJ2ndrsnjf zh)~t5B!xhzyzfb=nGY%9aL+sDH%02k6W(j%09EyxAW~U^R}chuFtF-g3iX@d&wf?z zy=dp*{JCHnv(vVIj|Cz**+q}MXKlA+>YS>wUaFS~97sEuMLrLNgei}$G!nmzLbFC0 z{EUNa%r=A0f%So&{l#>LE_K-Bx0ye@{`meZUUZBr2*#K)pSz6`?zIA{v66At;mzn!WJYazNMWBkW&U4M2z3zsucy_0eWj9Na>JC?%HDTcSbl>6%_EOCe&J(ieAbvsNn}}CAjU?f$$u=eG0`RLvURYk0H-&SWNfPvpu24ed^BvXY?~HT zyYs+{aV3AbIw?V)`L=~TiIR7qaEC781nk&@)%FcN4Q|^o==lBgHEUA=#iZ)ObhL}< zuuyL}GUF6KsO=}ZcNZIMU(yFA%w_uR39k=>r|#R(TUmLZ^_Y`hlqMPbcDf~=uur(S zAscf1=BkA+K7TTv340O5lrSURFhU_;IyY&a;+q7bG>j|yuYAjc*B>tV|NSY}ytq9Y z1w>ZxK(lxKzQ>7;V8)rXFp@+-1^84GSh)}} zmX#&rYYqgkBypzZ=K4U)nIf<<;xHfp=E^UqgHNFFX*0;D{tFQ1z}Grpo}xcs97;iC znTkyAzF1=IWT6* zC;&J5;FbK-+-Uq~``=jdQJ)4n{a>%KAqly})DU`AG^JBKe2pV&*jA|s`g^dNJTWm5 z0=%0zH(MX|XfA~Feh0wS+tj&?}6zh-I6QuJ50 zogvm#2tmH^wAYxp@pNirsR!35h+_@}%;mQgyb7fY1BP|LpX|Jw zX8ASpT@e9H|H>OahupjqQx^g%JV0k`+8DM)0p~KO_a*1S7mN9FZKYI>fcM`%@Uwbi z4Y#Z|hhw6}_nQ>p?aL2X5=8Nl`(1B2+`3lc}m~O#>r8u~)|_VM<` zFkdAZb5*bERteMUiW38lpiOIbV8#U#K}PI|`R3b%wC35jf37a`0u?mkRfLfIh9Y)G zIh}lEzbWfPDC-7fjBf2js*Q1YCcYibl245P?ZxKol>m7&{JSyxj|v_K?fIhm?@?5= z9cOW4RY@jED~ONbm)PUBvrf>WkXCixqGzYj{kjl%8?I-7f2nC@JrP)qim;T*X7f6uvx(p(PP2$@GgLtUzQ^G0PQD5rQFk>@AFHnXsjLGE*@5-=L$qK?SyyEjXIC zbzId>-gYD5;ppU~-%Y95E6eN~PClLtgs9R~CFCp;#tk zt;=SXKKyZ7#$~}cbb4ha)8d(R#ZB+oBT5$@UI_AF+(D1o-@~`3?H6yOh3(yiu#}ab z2{F1>oAfJ?4?+pWpdQD3CViXR2#c|eSq<*WP=#Enr0v&9ieu@#7?b09Qh|FyB3C1L zl>bKQ2qMSjHYAkPys`nFU9tJ3QCQ%t-DQS#5{UYrTF8xY^4+$wU~XZ1lil6kA$hpC zKQhF=n*4lS>n=>DTNQbTf(Pk~C_8U=Pr!5+n1Y&zT_~Y;_fa+dm}Fef;xqnr|KX}e zQi_iv(606%7{(2B7~|Bb=Bu`Czf7d3y&Q#LeG9nv0c!fAwKeVYUaj}${950-NNT-( z|5p=K?+{xF>yy27(dTb)J3T%w1W22Tz+-K1;atAP^peie9WHgIo+%ai(V-EMn3T~C zPtU}o4X&?%bigSEe!h>$OId> z`Ta&xrv`PPoHP)os2Zjn#*T&`_gMnIAjtE48nE|##9nk!rW8h+*8KnQ0#@yRq;K`& zQS<+veC+i#QnTM>iI6XJox?9YJEf@UKh?o5N!4-u;{oGsy<3Sf%kwRb6iDxTQ5%o43FAVng|ZM zljwnt)939?^phBXzX04E@JG(xe&9l+XQ~ikgO%g|PDAfGJokn|Ep?YRu*V9_QeYk% zTbn(%nyP+T5nTc0W<1KJZ%kM+AI$%ziUMMk$X~$R9LZng5kw#5k%#(pex66h4-w!Z zB7UMES6(`>6s&r1M#OYSX!a)0q?rGJsW4oj@s0bIQScL*xFsVc4K8JcaOf3<73RE2 zdH(`a|61SMa^-$RMD^y%$i1Ck?c_zy;+6Q!ITh1ELK4Gr zKDz>x>Lo*I_I0gZz0J}ygQkNG7s0Rf;HzCp5mb>Gc>T?hiC^Hx`MVR3UjW)o{eJ1? zRz33(%*=q;^xYc|Vwn$nbcCY{pCUP)1Cst2=isjZ?92hbyAEj1{qf#M=oNj4!4Y4q z0G5tYqAS9W)>DpcPKHwr%ba^Bv4`I=oaQ40z_P}i1m@NvYZuv9jt@7A&4*dtXhL4<5RJlBEIq!x5Q=LuH+KGZQqLPrqJcU!wRj0u8!yXiinl~jR7e2mk z{L-*#2&*@?^3=^~*U*&G#fyD*;q-~>W9-MM@LEqY)CP=Ez~6wovf+k@)@Kgq+z{83 zI4+_1;RFx)7mw-+;8TDFgaez5$IqbFP_{Lyqcq^MRe5ev(3xafUpjHIsPUgxgWKDE zTQ+P1;6Y$p9}u2EP>66Rjh29OD0RaBpD+kMvYQ-gchG+A{0s~R_k)7?%zmAB zvZ*h{>nu+(Yi`WHBg@*YlaiTC*K7()hZ36U-O312erOgjz_oc+xfMU@$@CGoCzl+D z@9n=v#6WW;U0s4BHFUXoZR7$e*L{5Cc-2=LYczADAN8C`6EUkHs%kfK0_IPVkfn+7 zOR-W@Gv~!dnsL4~#8Zb6Z1wLZhuIPmi9c6`&;<7lNY|>fN1Lie2dd-2TsGHTv>4o&A)|U zo*IbCuJXA~ZE|IUfr?3hd`5g%AT{-KDUnIKlu;)TB3(E8R=CFKJHSvVF6WdC2S*|4 z&dWIUm6Qic%HhlmY~fd}qa?0EH=~ToH!@u&LCSO|n4UIjH<t;dJ)~w;Zs@!Ll-pzNX-Uv@W zP#4la$0NJ!yIeQ1t^X{r8P*$62E97`VWa-c*{;!u&N#PKSX(PvNf_ns?Ypb&M0A@? z(lJaKCJbB){s!Ft+XZ+@+h-R^8A2$p|8*#!J3mt(gKvWPht90f*EE32U^b|JbNI(H z$1|G4;0Y+AUFV(e@*ZvzLR47iGzucwJR<=S79(&0LX0f76ldpNFLbF z0KgJGSickB8Lpe+VG(wtazRB>6AE9`S?$uF_qM0xq|uXIi+sYgwSuiXL$~&$)fIPk zg?EB9{XXl!-U3csfZlxhyf?8v&iPlfP49q76E|}7`-*)3i@OpQyyais-Ko@!2y{+q z;1NE5BB|)|N?m5;)LL7ZSNu&q8WTKS)Zhk0QwScH*|kZ=dxUTt93-(Av0J8l-+QJV+zK2lIqOlWMd$iXR~<2Oh=q`|5>f ziiG2VfSkx_OscFBwq=%WRe^F6%0!dLl3U8KdT}F?r0~p!O%lv$T+hA1ylVa5F29;W zTx7)|{<+ZY1hsxaXkLy1UHOlL1&w86+#boJvhj|74&Pf>vA?AkK#c-bLp(eKi0NR{ z3u{vRQN$3qyFl%=D|(g&(uABDuU32=^x1B=7xekP-u@EdKaDIa8E_&8!a=-`}l`z!-JyhJ1Eza3clmc zJIa5(;+a}aRYz4_*>0<=^p{FdTPHT;ns=n(A8f-Ah*X?Ji&@l`<*I+Yl1q=GnNiXN zVDTpvTcI2_aS%?=^S3@buMCYx5?5z~_c!~ies}S&M{)9vD>zt178WAPJk42$hE(V} zI!yedvQ=t4|F<}2>{nNXYl4wJ9k%BOYSy)Q;-aher8^aZAQrXKM6Iq);rE~E8*uqw z1lrZr+^z2V`N{S-G6sbld6VHTdrd$u#YjtAxR&nh1US`Q?CWE`%i-=U7x?4xnnUts zBLmRz&8K^GhiPm31d=^~zNl6eZEq2CVQ96l`p;Xy`k9@0~DRk|J90 z;y-~Vhu8+KL8gUr>C%XGJK{}!W6(}mE_UGO?(61}P2Io-^uVY2FN`I1f_bovIA?Ga zo^7s_kIm>hE7C(973et$AO7EOf*6`xA$~!i2Z^#l@j;t1}$1NsS6fMBv+m!oLX6 zIsnpZ9FS>7M6Xs7PF0xS5Qo4UOZY#47e!#n=Bu>x`9suGOAgBka`#2^7xNWIaPsH| zz^1KBOqpyUaMgTEJZk-YtsO0VD!{0;|FdP!;OFg86(F$%LN}q|EDxYgCn?T!=8~mw z%Exj0#RMFZ3L@wo!M`~lAai&=VjPw1Z58qyGr(9(A4~!L zUCij^w`eF5q)%77tu(&<`feZ9fDzY6%5<~5B+&LwLG0fKb{!xxeqD3|&~|i~o{O@z zXyS1dZj3h%wSFOw>lLQHDN`-vNBcbm_f4q30Im(N=}ZGj5KMhWfTer(=J$-^YnPiH zL(AcK%{bb0ai|_Lb?>f;z?{33XibEq|LU*4(p5jj-6UeOy7ajov-RZf%QqHh4g?vF z$GuS%?@OX~Ph?$|o80fFS1#=u?yc$hB5N{-Lj$;Q?@+b?kj7ER$4h>va zM6ybja@IKgHNo>A8ThEE`Z5Ujss1_NM{FsUhXm4DMt58SNMYHINlh4Mh~|se#=qmu z$(?^G^-!xxkd=p_imqS5=Vz}w{@`s13+?OGA-`labu*&9k~E%v0(pmP4l(BZWW*ND zmKXHwh!#rLps58h=O89PH^dk*&tZHiYaA8YK(mjd`Nn-8EsyL2q&iuzatI(;!_P&W zvm@hKKDcU{Hu7bsG$h01{V{np`8y`l zx#{Jaw~*w|!Q>+>s)wOa!Gmr5mi_!l_2o1nj>nPRxk%1mD(P&%yC%P)c$mGsLGyp}TIXgWq ziF2(*#ylndJN)8$I_5#V^Qz;lXyiV5-d)UVKJA#ti3^3iT6~b)n@9c2e6k7W%LbzKn_d;_)S_QrL5MM16pv>>1n#hOA5QKYmhNH3Qh=6 z6Cz2Py?CTKyZ1T&e@wk)T-43}KYWRFmvncRbax{mB@&W?bV?%~!qVL!t(0_1F5L)- zlt_0s{AaIo&hNgT_~HSv?#_4SGw-}V0|?9+s9SrF-K|vh{4GE9Wt$Gy)EIvZvdD<@ zFHO0;&SWHi-go%NTZOo!90asxb%te5zn!7ZV?yuO1v!4AfoG)N+4K3_$?ooN z-rJk$w1bL|Z;0$m>y1Ug&HiTpJtrNy+SlU*Yol*SZXkNdWg~fI3uFOXb*CO?+9b9sZH<2ykoLb@5gVZNC`D{rxO2Q*A{Dtj2>*s5 z4f+2)cHfp@9FSlGWd!*Rq>$lrdV=Cd|-1sg?6u%y$4b*N!cP+ zpSw`3HlNRp|Ez<)G@G`}Ph?VpSrP?@xizNCpzHnyP4M7+`h-=yH&;qlifqmuAmt+& z?(mIE-e|Fp7gcpJHu)L@=0#nTm=F)-NqstL)VvU!cVC06Q6ppH;XZeB6PO|p5aVoj z=Q+j6o4S3{At_<<@*Ct~P~Vj_8)!zQM@ZHpW)}ORmBeBRlA*oMBcbVj``m8d%BYHz zhjHXBk!fWM;tbw~$;b(`GZCZ`B!VI^D3}~e{LH?sJse47gZ|l*0onjdqmNkN zfCRA(FjfXr=r2`{rn7P=Ib-U*Jaw+(yMp=UoWxt z_7{P^dI%<+h;I1payEK@^7AcovND<5goxnXe(ao3k$jDu84_CN&kvwRQWgTFeKhp2 zIAEZyOC9z!7C*@Cb$LWHxt9JS+(>O;3w9C5%8uI-z{dICPFFPF9m3jUF-7*dIGR>X zn!t|YdDy(wbT%dexUZe-4Ht^nSHFT;E1QB~l@TBw1+-rGv@W}|fEKgHc&$@F-iO^C z-bvK`gq@GRyRUQqfCFnaFBtO=;I(Ws;EyBKeS@(4wu9LWuX#J~Hv&$FSgW`IE&);% z4POcGP{RJ^_3~9Eyj|8mrs=}&>aY+5e}IO-Qf*$Vv-h5fj!8WEz7kyX1aMmz=t_kB zh6x~Oyx9s!0MkPltO^^vU`NI9mBwFULOm(#>|%OhH}eWF0(0`6H8R@`Y=xFfiPYd1 zf7s1-&Ftj={C5We)g}dHWyI7##qfAuA^~o;jd9}}DDKIk>THfYb`%AbD^6tcP{XL& zWV|5Xku+V&KP{TsZYx;MOQoB!^`HYR@+at>rf#52(63k*;+=vf{0FtSAR3)diEMa_ zOXjJ9g#V$s`GL#up@H`Nso}77a#?|vPfy;b6N9n#%xAc|Fn6&Y)rRQiav!-=C%0$w z(#FruW*{L@`V%%2ar~CDVvr;r`~#8gGvcz@Z}P=a9x=4^$|#phKEi(OVxV`$OqU~o z+!n(iM6kcB9+xb19Y0QM&1+Rz&RE${58GR)&Q5B8O!t!Jo0Pr=ZW6veX~EZS=NIz- zP?I&SJd%0-@b|3j&G_dF=}y0#tb;HFC}@G3F!Ptm90g0nHQ)0A!Fb6pM;HVNi&_4$ z3AnQRzlh@70cPrcQF3-ema}y_sP3PzGhG6lhahZ=2`DE^vQAAg#m+UrR_evkac%am zOYsy)P*1^hd3tCC;HH`C z02=2##y39KE#p3s6)!U29ah_VV-b7E<+vMqwCaXUabR=&kw9N{0@hjr zzy%HGEW3gbVBs3SJr?cB0`5hGK-zROIGm=_oDazGrD1aJBcGLF*eTM3B?f!GELr@C zso}9C?y*%;JNiNS-rgOb2RgoVas6U2Ro=XI-Zy)6Dv+=KD)B`(m#ReK{8DQrX=InS z?2qv*Av|ijfcy1th9EIQvTgTGDNXoT8+ST18WD4x!}b~ME}fSeqHPYp2~^Y z*}f79zh@v4R0_EZh_ZTaFP834Q#=2H3G^>00CP;?G|9;-|N3Tnkg&rE1*A(u4LA2x`w3U*eLMwhTqek!#xBAK4TXO|%=PMF>(Q&>XXlsaj+bp?$3JCL{t-eBOK&9R zgTH;}AxJEbIlEumug4#&JA=DZ->Jg907h2OU)Z5$Ypc?N?Xy#qdi{76@B<)dC%=97 z74a9vEdSp8G*5bU_P6B*{4ZQ5q>ymJG(FzGe*Lobum!Ep>=`xu`z&Wf1qz|hV8>*s zY~CdKT5kg<-&TSn2KiTO5J%m!Eq8gH@}H+ETC~=4_qebD@phBe<*_2BM*7?b8{XR1 z$wDW_h?F!{3pXT;YX;nbsFOqf38~g6jSuRU9!riUpYD!+xH6v#i3Ft$*w-)W;E@7x zK`9*MR8ki8(%B(Cl~7z&+{8@iTU%3Isu5#nOb^yFl953;7aTj=Q2eKUZd_AzX<@-; zno%{Oj3_Uc9K6{l0>mBRca|CGt9Qu}aXLKpw&A@k#wA(a-!8oVLaZCTjfk0x3iZp^ z1)D0NK+uUQ%VIa@PAt(846w~;zsvMjAPoZ6v-qZNTTT?Hc8+coz9bpC&*}i52u=O8 zp7IIoDVbIi00YC;F&IHILGk)}i-WgsB)f!Uk=7b46&!rp@|jc~pWTi{v7pYLCw+1t zJFLch(Z`vQc6k+Z^zZ?FsCcf zz?>CvK9DkI9ltsRNzozI*5tkAPu7R}6*dkWE?{gi`?jGVaf<_#w@o>T4CMD0Rk87& zw2I6qLH{u0E1?8hP{`E3Q(GoPSB6P&z>(|aq7WU24s`majlwz5nnFm9`3&SV+xoNbNVw^jH&IRgyy!=71 z>VdB#o$RHo7l71`ML8?qQixg5f1X)%Yzw=G5Ub)5F*AOGYmQm%Hi9$Lnh`J8qI2xq4h(*QgIU0Sfo0gv5Y zE+fOGNeGLYHb|6)w?L-$=ci9jpXM294AuiQx6HWRzw94Jh!kAs94RzhLDpgWT5m{^ zT*P)e*vmoYc@Nl?dO?i`1>SF=;#W(bLC?|2Z`bj}@vZ3wMv(Ef#U=mJydPb3ydslU zSxzD*a@U_x6e@*8-s=n{hr*ZhhAxr)0|K;)z5D1a%#Vi^}4pY%V&DlrNoT+|x2O^DuZpRy&4|_Si;h*~$ zT$)8ij@r=F#ha;?Dup?e5~OBZ0)~>9vpoMqk~#f&=H}-1g&qx*0Bs*}&I(Og{oOvM z*_%dx8>Cq%>G;m!l07o;@=uKSNn|ZvqJ(@MGpXE^-*fxigppyow>6+m(d7OQA#8x0 zIsnmb-CK_rX2w7dS7)qV2vJ~{&ve(P6;NjenvaG zTcUf~$5Wy9vQH0V9eOn8&l2W{F!z>DSBh0`nNPa%+*|z+^i|5z0lKwBUNS(d!@?h z=Nn3yA*PPMcbe?S|4E}~0oeMP=+K7ZZ(gerBHb!uHQ<9#Mr2Ic2Naf%4+>l^P-i89 z#5#3NS=rW~I#P<#*I)=~_Fen@BoEA!utuHR-QlF+7tz#}7*)H93xQ7wLbOF+z{jRs zBvG$iMekFT1SWRCsY0FOwble3T5_3ki2TC$yagdD&4w`1&8Ht}A2NXw=qeXoYu|`s zd@`hzKw9J9OnU^N&v6FjT0p?31z}rE^t5U=d}ZF#+?LsJN4j#22!Oiuzb+K8cL^+h zkVnF{Z|Y(zQ1iZk*^%SU_dNMj8SgI4me=7Ie;B(oVEM5j#RTtemts$mGTvnKvUeC}O;k(tSG}teI z9u34Pu518NGSqzu$Kh#HCc+wLY2b`8o0HE&D@GvPuEnXI-vA$t*m9eNo{L|$K+8o8 zogH$DU1wWtyD;zeL7dnmKf9Q@({5wwYF2 z5;d&Cj6eUit9%P2FVA9Ar{#<={G0Ta0cLYpMKSLHiArkUd{3760S1P%Hu(!5>eG{)n`{ zN$=#meNmL&knR#6R%>eqFH$2*!|GFfSE^csXf%hYb6Me5^}6Rz@W4zCXay@^z=7$r zRI;h?_`FCtXIqiw}DRT{2|G0qA!Eb3D$I+>fUV#JN>lPy}WwOjNtZ>bG@%qV$>NrW)`dD z!(}=J|COK3>j2}+AZ_R~QrQp7A*FSiYP)GUbjaw9Oa6>=#&;6?4R;PrmEEo`%sA;( zwFd+{`B;s49Feg)ozune?+fVWoc+9tvd;ZJQ6a;eP2a7^2N#zw`EvpuZ+OR2us4t> z1fN5l(JG&y615kWFJSt z$LQf|xO4p7ZosX^?^guVU=y#zwOxz{DC1iK$P+ID7-!Y`F~Lj5`0j z>7oht>;Si14l=fYD8^&mOM8O(~ zX(GfO{Twhzk_y7pK{M{Z#yGEhT=4@?#e!-ke=-OI3LeQ8A%s%bhdMeqY`)ID1&J8| zuJFIT^|7{bkP_15k*g{DR6`9J!P&(gcTeNnQ%p)|I?UXbk@H(D;4gDy!)<)Ep>5mB z7;*`S_Fkc_tWf~bbOY9I@LwOV~TPrHz;oE-)@yMet%YNhtNh|DacnFN(3^H0 zfgr@gnO;;D2w;o9|p>$sN%(TaAsEW(|*P?xq^2EITAW_kBuP z7#Y3Sq5?&xA3{0|a2Lp4sW!P?oz$11KOs)_+yYg!1qPoWN<9znM^>-bA;1}?)TC_o z^#`gPcF0%#j%c65j&MR_{g5q|uPB<*pCb3nNJ*J9zR4kfZXhXxmshao&tL0HHI^d9 zu>M*-+1bl4C@#!wK^BbXOo<-wM$Z=h#xU>)R=iNC2@1BXMZK~uL{E+{a)LRR)d@h_ zPD5oSJU*8-0E~zQHbq-l!~uY)(zE<7q0IPV{ujA(C!l9z-HQS{M#c>t@te%oS>R3s z+b=gDWzCL2&d`>(V^qA%;^uE36vScY%*}zcK#X zcbL5mlzMDzbzTz#77_!%QD$JD1#Hb2jfqq9TG-n(i2(my&y4xxIaETRtpM;Er58X> zhN;8JMu6Ve&!PPy7FLTV^cP$FvgSLAOw6Qaz%8t$Xr3IN*1EmVvP>T?3WOydUPw)gh48RUa zv`#DD$=?C=Ca1MAnAqa!@#tw2_7L)_!OXRH zD^KDcN6VxAG3vFBE8^I=b7xZU%2>Eqar!BP5b~ zMM>7)aab}q^elf|cqH4m`P8WVKrp7a2Q!$o#KL81G!6AtF-yanj_wr8mEaU4C~qI4 zlq<=+jaUe)aFl3`pl4#fD;RBeU)VP1(*=>+DACsb40)jXt_B}%fz637XN$l!3(cWv z4H2;FEytEs1YJ@IdG7+h?lCmHrZRogl=GS4+zIEUW*$LaSHC0~Z>oAxMi;g58rPVP zmot^{tI)i(OhsraN6ryd9qx^&W}Zy}(;6oc5z*iD_c>O5ZvqHJUFp8+J2Nom`qIEK zDn7n?Ii+$c3`rWBQ;@Oz6tBwg+3VxGMb_;7J)ut)e=7V2BF_F1hyEOq^^=BRgNe-i<}Z`zZULvItna%D=Io5|#sI^PbGQO7f84Vq1JWk?5?8B52f$ z;p@B32bQzV`gI@Uv{)g!5!$;au9tO)m0RmdM57yJWuw)NK9mx-lL0PI?pH78B|g0! zFim6lHeK=^e14TTr+Q`ID*8U2ewijfd?t0IA~|;=8fjd&=G6`5auYQvKzYu1RBgKc z+V{dXAb6|wS#y~!{lc!b_(5%l;O#>NoMyrxT}ve>QxA$b=HAVbN~&HLq>dC96dV8w z551ezfExlj(UY!n&322T7D!EUGlFuliE<))dI9vN z`SJGYPljzrZ|jQR3t&JNs!cWp>k~(-?l<=cRL>r#J(Co=%P;}-!UdD#}98*f9*{ZpW%%$dDzuM$G|wjdOE&p z+Y&oks~zTe4!3ju>lcZ!pSwuz)BTngz^Oh%o)Z9(>nNK+T^jg47FZrOXkae3i5rES zhD&^12iFfvAk~=pX+=a#EHxdi@hC2ggifo0tl>v!|B?4&dlFBZdV{@r1_Q8cH7XTp z-EWw5#M)LihfPjuqRTMGE5gu~xsFQ#P_RM8Hq9HnB2PO2>VaAujzK&yIXIj{!BXT0 zb+u@VUO)xn3|b*4mzT#t*WNmM`KW?6hJx*ZQ}sE{E{U?P;&dH~ zf~b5uo$ZGYO(*@iCzY&K?{;Sh+>BsEVaMIlkqbDDPC+RztPt>@R}y~AdM18+6}sP8 zHZ6Ia$zl~K{gQHWa-2lFdlQ{eRuGbCj-R*r?>uhjpFBeTO$PnnlLmX_1PPE2h+bTx zuV4*08R0eg$9Y%P*N57690zy}-wQ=U(8Hez2hxe)_+(1nif3p@5iwMv7r5X>;&(pI zI$r;5?CeqGA4WkVbQu~{8JL;l<`wMC5iOdeRaSA9I{CO7P>_CFWGQ^ObAz%U=Ig>Bf^;BKTdRnZtR(1MqGhv;lntz%E>2(f0NA?x4jhH=M#dO@YD--sq(U&QqA_ z>O3Y;EV-+u^Mr!OtC`4%4F2GLuk(?WRKNi^Q)Xf5pv!u99|}4 zxV;Sm%~OqirdlTd2{<)#4i68x#veemNFR7e8-W}PG+xRq7F8lyiUH+@q1&V!Oilo{ zFtbH|KqYd)L|pH)-3b5_w*_!73Z{lt z2K|yEx^Gp@x2bvGU|-;#ipKW$%Nu(-zii=$g!^T7=T znF4!JZBndt&Y%LEuJ9;j+)iHO}M5kA?eFky? zU_S3gC`y7=`HEi#R9bCb-IvD(zWn2xc*^FiH?@88oRgWHw~c55A3oz<{sae z3LsdTGjjq?U_`40y=P_!ruU@}CseGs8D%}c=8r7vx0)!^KPc2bJBAVvw?mG5K4{x7 z+WJw#k`@!;#9KW+c2Mhi&$TZ1!Iq1_UxWX9{{I2Z9lZSf6v?#?oev&>?Do+fiYlnA zgjfzg0*dM*I2JG`8}jVl&R!OD85>Lk95?Nii3#W3<(26+Zhmu?|1VstdbN`L{6JMd z+Cgn{1Skw^Fk47}f4ZO^fIm*rHG_d&B>9{=${@d+u_c`#ttp2t^Se^XOT(-2m9_rd zr=awg@6S^3Any%qSy6_6aPf*_fn~SQ7$5=Dsrg(TZo1|+XJ7@a`u(j9w%Gk3i5w;` zKR{@wN*j8la&Ok*k*|bXtfa;gDhbTGBP;$7dv?CJHp}jPFVHV8637qVJC)`Ju9$9SW4Xw7v}eaA{3hbf}f*KU6ObQEkH0j zTMttCR*JEMvI|lar=|TLyzP9{PjS~Bdhs8NNFWw$WXAB`7iYt< zdV1kUDu#1Xa~L7W1h|71E{4s!tMmtngEKtB!UiNCGy36Q$8pp*G9^-s+>}P&#tUk; z<+RA6oF_~}c4g}IwX|;WP(E~(5Tt;{445*#&ll|^Q+DZH=F#A^d1xDOG6$*9F1n?i;R4 z7Zn__!PH!J9V&Uch#qM$=U&6cn#8C#)ELF9*UOHLI}m0XBHI?#uje7L*dSx$c_G3q z7mtrMq)Hd$ZQISHd`#F3Dfm`nK#7_kIMdxy@riuy1g-=brPy?}nL-PDi{-$R7cV8*gjzC zKG+Z;N+a{C0sl-WCgu>a^un7LHI*(SGEP-~;|g;l$Hv|c+@DlFq{#Ye^3~( zjL1#-GbVcM?p-mphNOy;v9|6{TrCvnpOUo&HRw(5GLh=5!TWM9I&7uDj6YPU7^-2> zs_UUDwITD7ELEDCQVC~Oy)^5n=zdFliqi4Et>z-qxBRvpyG#0|Mr4|xITF)ZM@Ru} zDChOBxyLYAuRM$%7|9JF1H(vKF%E|{Ig}bie6C2qGQdVI=AIZA>TCcQRlFm&E79o70N^Rs;kgu_4RLwqAQOb{d z?_?3-%Mn|QR;-%cb@g~~_uFHyY95ROVt027HGZ5jw6y0K7L)FH%y}9EFD;dydvq07 zRD@qPf1l)y=?{}vUHZYE&}-Q+YPLJd-7aFhKZA4moo1wPvU4_1@z{cg7`H^q*j0j# z-+obkI>@E2K#X0m7_`C2G`m@jYe}(Ami~2lo7&4YGJ#0!gv#PgCYeVlgt+Jt^seoX zvl@IEW9S679I5khOad-ee_EwNL6&9Q`3x_+C}*{Z0_{rj!OGPGasbZ!;)}?y5$7lS zb|40B=NZ&IgCJ*bT_uvCu3B9?K*4p45H*yChZOm&zG6>jD7&IVPbzJa>3F-j<0yP` zGlSqfy)x2_WLztk*{AiKJ~SxC#^5?ffhWHE%+PJ=M?4?sz5)%xGqDx%dX9Y@40d({%JxP zYm{8X1D?W2nssw02cBu@sWjF4g79yA!-kWd>}-mz`7qOXz1T_Kt@B)^k?(deB_f@) z`H1xDJ2c!PyHzg=z-kn2@1pR3ouT=WN{0gpVn_^U?*~SgUGw-b{U>n zAQ3CP@#-9>wlu|%0GRNhjilkSR=4;9$_;vCQR2pEw#3%KcGeG<~C*{VUo;TSSe)b`k?Ef6p^V z68}IX1F^#`IAm);GE5|W^XJO%SJ2_A3GpM(+67HF)-F)tB(}tm!Qi}WOGH9e?6cw@ z7KT`TAh{_VuXy(R`&&=Xhjlig8#>=kLg@5DXFx>Y8e96UH~O?$9!Sek+MNm<@sUjO z25~1EZ(kF}wMXRHL?I_@4tvTjBo{VrRcXKV{U#|UcPvjoT^OjGQ5=|L9e-Y@xtI~P z2{Gm@HxSwMmPVUaxW(liWum}}aLK>@s`!OfgqvU{TQ*oL>a+G}U`SZW5?^(-7*m{2~!d$f*66A2p~?>!#asAI_47YTK2V?NkiQ zXpV&@k~Ol}d?CWx1R{~5e5&ZZU=v=eA(|55?UlmLBo>+f7D$(1U0I+ItH*<>!06$d zt^BGH^U!d1godl!ocn3$_$zk?HHt`SQuzLn=) z`oZhI`?6lD8YAd&S^V)}B|y^TiK~)TYFg?R`B|l-`3GS}4&z>MFELi_`7_pf$YAsu z#F!|ck{fjo3~&Vp`^{Kt&CGrQSXSP8QR)QxbL{J!a4GC`|3$~ccROVz>&-#=U7BHG zF7pkaEsKwzK@-ujV!_QxD|!S0T2oe5W_o+R)5o+^jn;p!v%AuBG-VzeY2zri8|VBF z=oIFDbTcKR*m0uM>>`gtbL@}q{StTa?RiUt@M$!=xtX+b;k*5(a#ALY<5)?#BAyR#lY}tJUNU%L!RmPWHGBRpaR2Y&HoJGG z{SY8x<*}q#!-C!2fHq^9kOjKMc=>;>dt$bjR=2wf#ER7fcI9hg3ujpUxuuwL<;FvV z7_Aninrwb^G*VMhVfFFxo^T_dJCju8HG0pRN-rYXn=*=ccga7wG%ch45i4EwIbVIf zA{N<7*Mu4{W;hulE%AbphE7X~cN+Axtb1wby$$E^O;AD`=E2F3wI;!cA%11-;*z%Y z)^uv|)xXDOKjjOCq>wvLCY-%MsU^P{-^N6QWQ#2eLpsl#@IAz4ZRM$-c+jte?y3>E z+Z|g7H=ZoWQ<2z_l^Wt4?|Gn_b@l4s=8S)lO&H#CJ>8{QLq9|{pKl;EN$bxYW5tu+dnIZ$nl)Ir zGfhOc{~SYf&3VX1#m4$Wf1>bBmKuvuu#L56&hWs*KMxOn=~0YrK9f)2$pgXtBMpqF_{uxOk?RiCFJm|F>2n5mnK(hwIo6fI?6nHnO!(mu;75-o*-t5kP!0uhX?*QD6R;BlF9+mGh@VxfBaTn1Y5`1@puNBxnU8r$b@9*rz8 z^&CXx77Ub<>MKs?Js~=~cM0ad=R3-Df7W=XMC;`W{Q1Rgrm9_vFZlnR3(JPuhZV*? zjyz*m1r}(DT46qvAWwnzGmAY59JN?TQd&!-WFgS_n%p9*y?aA|wn+&kl(@4A`z|C3 zhM#BB(0JVGi62+#!u9DtnJ=1NL8f1s4E?lUoKVG+X~gzU*ko1;JFYnp^)_N}I7s-3 z!IO??H;!OaxC7V2BeZ`05=YWV4R!cD{nzhL$70}8c0v}OvMFp(JQF`5#=~dhmQNiG zkE>*_h9S0e7liL19ai=`sl~<_&2WEj&b^{v^5Xl^-P+390n;I0;hl!j+-GK{ilbE4 ztI|mxKnPq}PNo=>yoBt5CYNJFETud*=8zv3qUTQNNpnimyrSbW)4V`R$2-b>=Sx&^ zDzxk`?A@^&djjWNVE&zF$+GULz>k#uwI5;qGlVh*KGAWkpbC~35}}giD};B;6BkrO zDsv}>SW6R7%s55jWREB7Mb^EoJMm(hKZ0XTAL`ecQ7bOXd32hkR^XwM_^QAls;D2$uKv~k-k)5keB4&J-YdTGky8Y_bGa&-Eq5czGi=|NPIWX;Rzf|zbbV~;5KN+?zLIfI_}fWh zy>RW9EYJk+%hFKLE zmsWpeNY1$f{I#VL3C(dXz^GO(FlQwNzb-MH*g^=cLZ|2p$n!JiK{#{2B25$Gzzg|S`Kf9vwzy)N`R7=FQ?|-L? zssmZ{JJIV$3sLtULmSzpr;SV>z9an2*z%@J5C+YNFOJt#Qj;v54ux11(@}iKdy)*Q zZ3HmnjR=vBzgW0gL_Fr>0F4W-L+2g`_B(nl;DhNiH`vpbmscS2@cl5&H$#v67Y*0# zxhO`Y%G*U%SbEC$gJigHxpwN%a8nOFQs>F zt~&^gr?2QM@#bgQr)hxIj|j0?lSOZqTREw`v#__Q)_KcC$jykeBGVN3Q$vr_l-0|A z5z*ghFKIu_IApzb;u-;1n^Dnp_3q2w6qs|{B3=jAuAx##J=3@)Qb4|V<9Bo?o?;oAm1vV;DQh$H zh+Q8bOU{aaQhBSt;Dk?1Id2HR<0jlT7dTWVD}b&#jk=pPoWB!gi(~15%}0xPbQ! zMI@oG3-^mm!=#x0&HT(iFdlSmUpYZYZ-| zGjv_D5^=`QPNjVzvcE>IM8Du$e*U#hV`$HdPX=XPw4*Z4CA4u1tP&4bV_76ZQv2NT zb{0{M{IwqeeO+OQ>tc4&`(>2V#zJ5pf}SFUb(i26W3Zfh3(#k!XS)VQ7dF-cBHUSn#~qPxm8SyU~|cxTUy>e^&P#X};#sxh>yx_x-p< z{{kzK+Rt}ri54_c0V}UIISog0O+25)ia+vJR;_)0H!iq)t3N1AVv8=iv*u0F&FgHK zX`zM@ma-buXe8N1fneySW^Q~Zn&WMSCL-L(?YfEnN_0(@jofWXp$B>;>bnE?EZ-(W zH+eHM?*8isd5NguRZtE1_AM!Z2&mKbckGOnc9}uh$M)>DvFL51ut~gkl(rF2fHv3z zL$Rj(5VP!Hv$d>U5qTa!^#KI|vJ-)q5!B;N&(6L;fkPd7#gGUzPN~3E1(Xe-V|RbK zy>(>l-J-|IV;ZoSfGQ7w!D9?wGSvtJrvcEbVoL*gW-|_?^|__qOlErZ zZLlT1SZ|NVJ`X}LZkHan|8nmd?__^Ze4gkZUO`Y12})7#HgfWi zdMp^LSL_BNo@Y~TmQ5iybitMFT_f|V7rb5qO8s=|6QiW!#1HZ8G71d&W1rziD8FeM zHBab}A`I6`cN;24?jdBB0#`hG4^`rzQ{Da787q#Krb;&&E>*E}8jq|W(+2jw1vxD# z$;j=SKzOv{29lVkG)E}E59&S1Teh)?^|P*=l#gs%ZRm?Ss!r48ju+aH0F-n53@%dI zMCnfL@jYk`R0z6`A^5lV3tZVwprJ$UqfI{96AmaD6N+@W&rInosVol=m?v>%3Pmrb zb6-|J;?(VPE%MZ(UmTi07Nwe(p!2H5l~t^rD9oMJ#6D6A8$Y6Em`<$-{BYtAugq+s zXaeVK{`!ln9dMzXnJbyF2pr}x8V1-{?;kwEI7I`n_8e~jPa4IE&Ze?uPWgaM_*F$ZJb|3g?HVevNC}Z9%REYgGBD)lrpFK^-2ru`1$1!sp=!} zm+64g+D|veIwoN@%=BFbgS)$XzUwC%m%`52U!DbXI+vL40d7X`ftnY10zt4Sgxe1z zL^P>ILQ_O#h95Wd=#EXl`qS%ILZCMZ6FgMU^g*s!u?_h9Db=rHVq#2<3c9e$?@JS9 zEV*QoR;$Q3HC>Ei)Mt-CJ-8K&o(I-s>Pfpdx=QifJZm#6fPx*a9V(b9g!h{9(AIi; z3>AE(4MBTxLmop^MK@lEi7~qwd~2`WZ@kn90{5^gQT;O!Od{IS&yflvaG^i!Nso5frn?6j$^gyG07Te7&6o z-pMsV%t~9J(CEU7$eMq)KA-1{sUkYp=RT~|u>IM;Kt{RxKoo@)heMa)^#dcL@)&!A z5fSZ*_~e~m!Q4?jm7H^p9$jtd%yH2VLK1ProEaQx=wy9C^<6%Gc|i(&gxM>{Q5|}6gF0IiR8hExAA;wBq@d1bnoBLw@9q-lVDXgAsHu{bPhbYr(M*98 zkadSWtV;GLxnIHUjLhgf)l2qH+=f+-CjOnZHF6J^Vv&}mF#xbZ$sE8(8R8W~Y-#0+ z-}4ajs+ogtplY)!BJ?vdB2TddgY%8Ae{^tQxgOxWla*mYflVb}wk++W=aS;NTduix zWWX@;=a+32#vUPUe=c14H}*f1|A)eHJ3C(oO8e_{*Il#EdbCI*qfb+%t5W$@*+qs)#Q$F!bd~H{eyPHNW$CJbEuN>i%@E zUdJmiG5>qSi9goXCvCeaCnklfv|@PGhhvKM_^IR3-TQK%CY!gcLM47U=6E{y5$n$T z@+EHqy;0_$WuL<}PL9sB{Cs28VcA;>>E~+CO>QlC+>w=WTJeKlg9xrv<(O)G8-p@} z8*iNCW?i4X4>;|b-LK*shZuB}>Ykfwe{WGYKQm38SpziOO0;I$XZfb`7VS-Q4>Cq{ z7Q=RLJ}?Zvb!<;QSZqvuRU!oB93V&FJFL_SoFF2glNs2!{!Ofa1-*f?<`1^EUW<#% z{fjG)9>O9b0B7&1Wvgu2W#LIjZ~b`0p@LB|pl>vn4ji1rshnFoCAphG1FE1r2sBA= zfcyhUAFv`#ndGPyl0{#bmJjIBACVuNS(CJs%1ECZmx;#=IaK&Z4b|$=&gqyeHmLEW zTL5p4X+T+>zh;6kc(Io$*1R9-sUH5INYXRi>Yy`0L#IPWhjssQ}s4-xg z5MXu&`c`!&8582Tq98qBopr#Cgun6aP(7p=a$Twrj}k_g4c3~u!4pHUiw-u|;u>Ek zR$@Iv?&Zl*pwz4)xmdXJvn$cKY@TAZDOi2?nPugw#-4Pz?w^YZZI?xE2*Qk~i&&%G zB<_QMElB+*Yy4DPLUREz?}qTR5WdtJ;<@W>A)KY#`s;ES{fx|hOV!2 zbbBZDZj*cEr+)jdl@SURI#M@CPO!0eIxt>gwofSh+GAW>OsoAA_8A-hdnM&U)|p?d zERPbk$tp~gha_?SjBQvNxf+c^UHk@~@@6~SnH)=&>cDL+l+cW*D`=@Up~|O)l}0po zJe)MPw4^2}LA5vqIH$7j4&Oq7cYh^Onf_+B%E1)q;G_gzBonu^UcsLBPfzV@@>)A| zp}y5?u;^Z_y~7?}jK>R+m37!pX-4r(3jJ;3UwK&Wbh&)P+H{xWbMu!`{KaNa6i}^- zA9rCLm#G_%Du|zQ^V`j9@LerT2C*!<8;uFByTM$HjU5;4F3pBth4I(bQIpM=Vs&)> zk6L`R){JtACQIr&&*;OFMrNC4*$ndF=6gPudfgIsglveSPtXxhwe<_%E!)B#(FC#8 zdzwqJ1k8wHpWo*m<=H5st=yJ4I^B_fJl#$lHzPp=_dI0LKAXs6dKHxXU)nhZk@zwPj_IKrRAc5?~d054bZa%p)3S z0~3>RP!6$BZ%YXJW?@<{z3d3ibR_+V$p)YCI9*8jZF(&DR2OTh5kWw;+8gabPv_-!M&sV%pIDuM4d)5Tmt z`cel+?85~ho)_?2F3n^j9}n5qUlhO>wP0x}gIXv#M@L6}U0xE;{%GQS-v@B64_JL) zy8t00G9Zw>qJnjQ3;ZA~#}-uUva_=TB@<9w4q7CDeE>+-F1Z8+i5k?(*6M+(?frNT z7pl=TFEGaL?Czq@p8W&+>fiv(8mT~M28NN~;9yW24g2N7+46Jlo8!6TFd|M&Og}|V zJQrv}xgD29KYaKA{AuRktzIM2d??If0!PkRmJk?qz(Dcx{g}npST@kzQTA}90UHr` zO`bW>z~@MyVF;CW6fg4sQT5hgRjtwXumTFGgdo!0AtK#K!=@w*x&@>gln@1^Ta*?g zrKANB6p-%jP5~+D`ptFjz0dc2{~RCn$X3^7L8ma6clFgV&?6EQW&0dIcjODu!>i--VbIE z7w4yqTOj>dgHIezJ^c}|DnlBS>1hKHST*b+??gN;r>aVTVTk>6eNs{qve^`9>;Njs zgN?DbaWQcJ`v(Uh)dm)HcjBvR1E8l`5X6NJ3ARh)t2PB##f$Xhx@K(DOEaZ<3j*P&eyt=VrW@fgyD)!B!mMOyRb>xF; zb6a$@L0%k}DZxJ}g!B*1%?fQN<{Ib{4NXcV@6Fn@H1iVD5MAXu9_J?gbeA6#C)mAC z#|Nkk7Zdti_t}K!Mr3!FswYh;aAaEE{?To5Y+j`M;&vg^*p#a)t8>S5wFk}~$-&Bh zl7f~b-xx^m+-t3jb{FH~!UYoB{9d zh)ey!GL-*-W(F3Rczq+m1IHZIsl>T$?&{2ezj_voGeXf4?_Z@4@*<{d4W;5O9o*d( zdomst9)Y`ugdc$B@_f?!(7qb%>RVTCA|?v#MMpPbwFbB7+dK+BMzk%T#vp|cn545q zQrh=sDRVx4R=k;kH7JO)lo-OjA7Qa~S#-{fXzxJ=Nwv=i0m8@AmWE0w=28{fw2;e{Xovq~?`~)33RX@ZJm3$bHlp61-31 zs~jY!?jQ8d{r`gtLOxfw6qkRGU(97E_Rb1Y8sNR1_z(-HWgow;sa(TM3{T8zR>d}R zv*&5qakC+vd|;kuhn{NP8Fy}>EHPP~i(*bJFW=yU`X4DP{I(_w%`NawI*xiVCEA^n z3}?aI&pg-Hzd7)3w_Zu|Ks=|(X})gtN`ztGLac zWx#YMm+cmqp!1by;5-=!u^mEly4T65KdGy<#4>vdlspecR(XazGxkfD;@mgQgW|)y zD#(f()Ij2xm+;7d&sHfoHbr%Q|JScn!Vzl2uOuyv4)qGL>oYMjxj4iQ{1Q9K%7lNa8k7HG^}mW2NwqdTDVdWp1;<$s}88E z6IUKsgm-H+QE7`n7`Gb{5i9J8&ApR2qeZm8z6Tb*cc&-dqkvtU;cbI27lW=TN}SdH zu!s=~+GElYS1W7l1^um;#PyfwTb}d5%vUVo55Rh4124>K5V;DGFh$8xvLc89XF*qa*!q&%x!19?iZ*j2g{*u3sDAg$NWJcuRoU zAcq_nt~bCZ`(L*T%PwRwM^ac+o{P7Kiw&u#ClDY7tbzW1TfhYsU^|$zw8N406RI7{ zog2Koyt6f&0yAFDtE->Zg)1s5yg-YLyN>)=L~Q~#J;X6>SCHEaNzikGh;$!JRxBC= zag0B;gWSO5Y^Mb_hfhvkLPfZd3Vw7*0)UG?b8Oo-xaGt`c+ne(0WMv2HkTHLmi60W zj1s(VI(i3Clh3N`C_%gOxv&rxH=n_ZFJdGSuWM&)X6BC=i-8Xa^q0U|+Dv|n2AK_n z3EX(zmG~RJJ8J2V5FNB}`<0nP#hd{JRxsg`_W);4scuLP1a)f%9NQpZb`cI=5S;Yd z>jKSkV8~U%MmaB=N5@{?JnQ%>%-b)ySm}CO-Zkw}+T=XcAYOe*5$;m{fN4I9g@~Ne zOC38RIOxTKWJQOon9T$y>euSds8I7Gg8Qguv)wx0HvsuxcxVuawmQaxi^5-)etM` zBAkdR`P^k&C3E?W!2n`*M<1T zBs;IwwOjR91dkG_%9T;Pg`-T0A73%Bt;?vBbyVI&q}a$lBiQDLdoA;%(Y{BeTai^w z?kXMp-JIXL_`CHD#OUAV0?9+CtFuHOfxZ3U;9yk8l0wdaImXxV4b}jPir}|KozKg( zfdEs;iUGMfoa;zXXl3PH7TI(>JUr*$L{GODW68mp!~(t*9tK>?coc#^-Rt&>(?w55 z>M!uT=5G4_1~os?%a=9PaAx2)VBuT|kV-u`sOHddLF*Ac=cv^0Y|OU$R@B%cap9;u zz(U6BqBqjW)j3P5&u_<&csF|VCZ5ubYa($`PYH~Vy;4E2O&5(Nmi@S>V0F)MWZz-I zKvT03nml+y?(7Bx6~r6HJz!UK3l9qegfq`9(WNGvBSyl9sp-g6a;QSKcJlvNlqL_& zN4B;HHA6)Cqs^&HEwo3A(2(ZD;ey-4! zgzlQ~kUP=H{(XVsgM3@z)TcB%Y-fLn1!D6Gb39S#7HRy{#=KmD9AlNX%xdBXS5n0u zo^J5py(j9Iz>{rCMpk2IBto@PEJWSIv$pCHEgKrKGWmFAR#?@j`0(6hn_R(gRtZmq zCs#42Ei&vAHe<1V>3#X7l((C;9CA6-FE5{z2|c+v{D}XDbGOht@T_%cp>zjFI7#k= z|11Dte=R#F2Xp2S89=|gZO+3g044aCLX{JlIacikxCs%ObG1~4D4Zi3qEPz~|5f;R z&o9o7Md3)!)L_*cfhH(g`TBuh$Nls0v~@{d2G8a z8rj#^_w|D9k2`EGghfPvd7ZY?x7?^7PwJogU4384=W5`xRV4VDMUhPGsZi4gvqSXN z8dd(JC0I2C8-oDoFQ#f?Jspb{F@zO@k-V`68cW37j5Q~@0sgiHDh0qIda&GDpi{vX zhWm6MUuwJl^0Xe}FL|toIXG*#rKS%mVbl6vRa{$yv?q-b*nO9BPUivI|r$FV=1+6|b)Kvdj*gRio4hP3DKI^++sa{l6#AW$VHmCmN zPyX5VI|<|?%C4-8PqMG8wy6~m5Gb>os_9jnh0Q0^z6@C?P>#xXoOydKA*Bte)zDZX zs~E7w-U_FB5BU0YE8YL$zjc}nTl~px<5sYX5j;0H*BdOA=Y?igrVZyY+{?*db>64B z`S}a5h-Laagb2WUFaT=w+B}@`^Y~Jg9k4cu~yRVo>@;^qo~4{OOsz& zF-y!x{V_y2=#h5_^!dZaGq-5k=&*MtmT2r-@%y8mqCNLC?>|~$Q6AjkQUCLJ*_&BL zU3~MA4<;7%$lgvE)AirV{Yo>}_If_g*!&`9`h@vbpPr#OAPx&V)&EiD6!Gt#>k8_Angm*P?zUa4UUur<>oaXuh{Sv?s-o;N-&O!acr_~pw3$9_d-9^sc_n%T+_XArBL?a}jU zx)Bx$p~X^xkt{OF{^DBIl$-9iU#Oe`uZz93l`tMvWF5J#&IfTqse?9zhl}ecVjydH zv1I5p9~`QXqX}Aj35egV+}$ts75j(#&gfS?+_}1=-}f`DuI3f3%SFqKgiy6HdVcf0 zSn*wkt*I4s>Z2QBYxry^5E6rSPi`I&Uz9O3t`m&^Txv!Bf?FjacuY^}5z|OiZLpI_`_~Y3l8L zg%UQYh3am9ntbga)k0g?{dj^erVX*4r6zjbpC*Fc0_gQVx{%;w#Lw@V*%|(-`T?&dIg<7_oljk-$)S zJ@pEuU!0_wdq3}k05hLcia+btAH`jT&-JH79S#fcoJpP}-=P$_!69rcM0|eCovo(- z^Q@Z3vRxTFx$PcKuv!Cms~G`2VvLKR;N|o+t5QA~z%&34d1qbsj2X6E1QYRa9~e@fs244p$Q;|_CE!GHU(8#R;XC+ zM8L*br7fr|Ki<@3y~8rL_xGp_VcLO?}=uc32gHg3OvV#wl>=OJ_hS)af z&=5!U(J~7>IK_p}hl$3dxFqH_xH>Eehc-Mn_xQnA19&!^vSe z4&*IveqrfH~58hH^?=oAhfpubaz!WXb$(7Qk}CfPu#x z=|vk^lz&Z^NU@DAjhV@;L_bS!H9MLmHr}r!HRu5@o_de^%%Rlr=3B)jwTPY1%`#lX zHWN$K*ewI?oQNZ2geeTaWy!qdxsDp;9QLzm;jAgd2Nu zZ6yQeudzo5XMckv7O~++EEf!Z+Gv8{hjvw(rZK?mO5KRsiACL;xL9_LPPP3bk?}8D==w- zuyw`L&%K_~BL(yJU!fR9_P9=+8W&c?Hv$~b9)6Xa2KEBJNpsK3irw+2u=vTLub~Hq z7xBsmlUnYLe9L;-%TT&bY205Y?4>&=^ThS7h`A;B33&`~Q<`nsU>B_5992z@*d1Nxt#<&AGLAcB)K6gPSOCIxW73f(t{< zbGmB^(((g*dTXXH&_>u_<3DR?7!l3<`<6*D=iUn*tHdOoK8qfPnQFh<4`w+j#-n>5 z#kTG|Dexj+Tco+Suc$#sG^kdynjN?v+_?IJ76TCLNpSW2nB4}} znh5R?Y&GHiHTorlVIr0a5A8= ztut=Cj@Zs{XYidAkLQa-G;WNQ!9&CcXA>_N9(m*aaNy-y%#MLPyJn-i6h7zbJ*C=Vj)msHv9pbXvr*3VQcu zSW)%_D_1B_M!>Sq9ffqG?`ww#gT?`jE=lu>D9{J1QERj*s_$@#8XFs7_ZqM$vTOx7 z)jCtfk^r$`sI`YB8bqT>D67z}c76BE;oDbfbqk?U_hQ#Vf!GiEWPU#10xx#R9LllY z?sObB1{Y+615tFNzO}e1b7;<(LZj@X+HmD&@4B3r3=?`Y^@6yc&8yj9^9lYov(ouw zl6Mh*7MO#ma5EkbxjqLJZ}9kz%Z2+s+P%m`6ZR| z@X$J=bBspN!BXO%r9b+MU{9Z{y3`29f~WDgGq%DL3u zC$d990?*_Y$$w|>pYHzH@}~ar?N{BOFZsxZJ|0-brkxDtwDZ6B>kljtR&H=MQ~%t| zF6+aoUFr`LFbeU45QhS->{A|5%?A-EE~O z3H*g1YRGOqux`eGA*Rq`tLyW+U^mZ};p0R7uhLh(ZG~1_rptuHFeVV#j>X%kqgaH} zO2-$12}LgEIS82(Vz;Ox347XFV|iB0R5zMbDMTU&i$hF0hD{U)>on&!Rb? zOTK>gx4-Ipex1aZeEXT3ujkofX1%>QUK3Aw=YPr1U-wQs^q!WF)@a_{mMguukDMOA z09#Qz+c&)A0}7^uHjX#sg`Yj2GN$r{UH*@)7lMYl=4m63q;dELA2oizrrRQX3yrr) zqXU7~0uQGf&H`?dl94gJkH)Ag!O<_=?zLonlum~g7O8hNujbZj)SVcbTRY|oEMEO6 z{1e&T)W{D{Thk@EFG!T)%js+Hx={u!c>bErmCxUGC|7gVq4hWQ+D?_T_Urd_TKMeY z6RgFiqG?LcDgfS1CANi9(oQGsUX?b-+Yr{{jRIAH9 z9`WwS)>_EsqgKzzrrkh;bqcK zwrY_dy(MSSLCF7eFJz}lUZI=~?F%^$DKC<@Dj(ZNT>Y2FGS!YxOC*U^-t)H76GJ%s5m~VP(7ag?>jZFj z`EV`x*Jo;Q-n-Xz1$zE4II)Nd`GCZR5eocc-{Df40AvooY2w~oo-z%yCja<+tE>Gz zJD7G}yYTw3HBg}$>OKHJZx6R)5EQmx=UAT7-&8hiA%TL zC&0Lxy0XJwzsr91ck6-^5vyJ9`CbsweE#&tU4FbckZjskSvbvFvo;Xhqe@|u%+uh% zk|T|?g)3vWPTG+5h$SX3smqqSE}Y!ni(12MyD>@&MQ~kG??CC4OwTVz>g*?|UK?W6 z_+uHP5^dUL*~6({lw%OFX|Z6T2e9iq5dHRwh%@~3BQLyQJ<95Z(M+_u6Jy(k%}t#Z z)&Lq=1>tpk2(`P3G6*i&d2l0D-c*`?e@wVnhM{I>Wwg{xu6^pr>}i1)UdK$?$DB0J zkjt~3w}Jj~&E4!4@)I&OEIOAA4<2X8x>lBJgqW0Aa-ZtzumzQCvHgYyO~~g=5LyOM z%L>FBU6;12tf-*P3bS_B)#yt#)W1I&V;}#if_olpft0g1KE!ME_x3(iQo1Q0K@n_w z@d9`mr_;?UXL!_M1~2*eOqt-*!omXlaZH=p{Kd%7(AB7(6=udC%=ANzTwy%P>+7&Z zk4ICqV8C(*>kSQ4_GU$xeX)Y6sVPNRRDqfuPlIjBCKMq+H2XrrY$pCPBqXcmm}(Hf z-D7?E{Kk&=SKF$uW1~a<`Z1X*1V7??sVOO!TBGTa{Mr`BQ5X?)tYMxXEw>_s3xTgI zN4HniSLZ2%fo@DCuZc>A77Fw?P z3E%1Y9vD%9ArnHe0;5iNnibO%mf{YoN5szuFaUpi4mh#_FMJ1Y*1&7$w?~1E2*;TH zxr3tAcVUNy>(3d`U%a8AN$Nrfs$Xw0vERIgvZyz*1ORJxSs71V+JQ%Ys_mA$>!Z$q zpuFK8_uc9gn>Q0?f9_g*zh{9?T$y?t$u4mUH(xVivu0xEX(-SER8UIUA z11%8>!;iMtv2UDjhD?q@m`o*z=?bg54*n4?-TP*|t1Xgwxp%92j}l&*wTS*RxQc=0 zRUQ>sUKQc3&zZudU0xopr4qB!wyI6eXG7SeoTOk5|%#pId?A3<*i$V~DPrlmv;o`c#-Ohf$#kJbMfFU^c`o{%97Ru(pTyKlF zo`L`2|2t)A=V21Zu{{XU1L*k^aR?c4*cg2P={ad~`+IC1Gg!14WUE7CXXd6l*3S05 zv}-OQB_(Kuh4e2|Jt3y z%a(J0Kc*DNk{5T7@Q*!+0fW;P0ipls3P%JhMOfH&70#J71^>Z>ZPnE7@XHY)M3^ zPTz8Cp6o?#dLaB~ZeE)2*W>fCwIhOx(xvXOF6IF1H$Ad91Q@Z9!@_)Lda5{aZa*E9BuF$@tbl(#-a#LhJ0jP404B$N38q z3DuWxKw9ja5hazbl^8kgNX-7kpl!x`$=YV(;`;u6SKwrWWB}QSV+MCHb0i_-mX^hK zL}X+O-v>kQ{m0GC&AmEhfWi8Jy-0%ZX_fD1IBkF&MvyxL-*eZWQ6(^+S-=zbSw1ob zC4yyjPC=W#Yx-(>e5Z}WDij_YXbl#G=Y}Kd;CM;^mjEPnV}9d^fproP9fbb|f*enD z`=QqlhO6tR;xIQNPE$Z;1JwsF4)%HF!$2N9H5+77H0b#Ltbfpv0!jzsXa%jl8OU*7 zgLn!d9RPVGIDKG49)OP_`Sb78_y1In%Uz}p4qSH9nwpw=3*KH{$sktx>ak;j@V0%Q zPfU+0>VeCi^?wi*``A^FLk^^gMXk`$63B0Z58{Sa(Uw|HS` z83alK1QVM6@d_R~BHJpg#wf@%afUf*r&k&e5+K7hLXfxnBDT^_1mCbRBJ@X2TbQ%R z-66K?0N`eolst%!@dcz`nm@_BCsoX{Q0V1978r_*fUkiUYpUA-ItGff5_lhrX194d z(n@2ARVNfO`*?gg&lA5$=}uz{6+0GfYQgS2AwLadG_RNBn@;$g$cuh%BI$Jd0eZGc z-YxbW4d;wh+VVVU6Ygb-I5wp~y$#je-P+#1z#=_dn&UUQLJMltXp*cklYVPF@$nB! zzn<@)81)2*>>pWokMjil+^ZFwFX4~RiEO)d;x5b?h;NxfH+VxO-sRk!g_Lv^E%x|c z*L7J|hces<_t!5&unccw(2m8)5ztgznargUe#VI3%aVr`z~GRCm7?wOg(?$cj^M_; z6P=$+6b=qnIz9&0+-Ju}3TE6~pT690vv7hhl}X7%`IcKcI*FvzJ##gxX1rD)HLJf^ zuUGZ;l~4oJx7;{Yhpn4T3%*uf3*%ipLQz zD*S)M#UQrV(UD&M^~Xu|2+h(pnBZKq%NQCt4yG#uxo`#rxeub_$ zqp$mab4jENiHfZ0qxr^ct`|O*npUGv*`3L{14Q7o#{v$LT=-Vv2%XCJ%rWqtd>DhV z*}vrOSIf$D+C98C+=NqoA5@(y_ecz!tGI6dWF1)#eMRmX5p_#%F%0c*%;An!v7_Cg z&rrllnS<;Db#aF;zV+k_Lcfk3V^)U#cQpJR_Gd9va81TqZ|xI4hkmL5L8V&+HMJzF zclb-hF^(VCKS2gUVHV5}p+^#*{F`VWeu?7heB{KdbHqy|7O}sP;jxOJ75yae%n=t4 z?COY1tw(M0O85>~`zuB)H_(3*vR{+&0~VlLx1 zJT{mZ3IgNASbiFWhnN;O^5dQ#&Z1Z$NkE9-e!3I!JAnZPv?b6!Z2!qGc7o^p4b5!D zh`|CxfSQ3aF)T8&4AQuf(FY+9=naeBKBKKtek1J2|B{fG>AYSSANG>0~bK@Gqt7wG4Xoc)u03bN_ zgOxGsBasW~Zy=E+ch{vJ@w;sa{ca@w0 zr2c>!9g6cuu~+=aDG7$s5S00G8=_?;I*ZT8iyw%Ks{(NWqsNYDl*YU#73di`bSuL^ zog53zRzl-Dyr&zIo1+NymZepo5QWMjAtyJTsq;n*fkESVL(z5OzQ|oEtOzQx_Aig~ zk5=U++M%E?ILBanA6$&@#hNajim$nhhvjP?oaQRL=&wZHhB+v{U|j?lB?tWv9h~1e zQYQ{bzaDlBzfqIIx^2h9(KvBc6C5H$ar- z&>ONXx6U=bTX-D-PZ+exF!F%fvQMU4$Pmw=uX?h%Nto&f16Q}WCVn0ElB zAa`?X>*z=jKtNawPldY^)3M}0!Hj#LFuiV8=yA%CrNIhb$&r0CGiu)6V#wt{Jq2#P zCFBmQu#te-1#|sLetkPqtIfC(1_bIq9C}Qmgv25ePI*Q!@es2c-?_wXrhI#k zj4+mUFP_T~Mk_6R`)B^2q^>VD^_cC?aPsD8FNIFjDZl+0nf4)5CR|+R@&-A{W zVQ+54d}XN$|4kCMmCj@R0X1)*)Gha3Qt$dJoTSQSO|Ry;zL0Ko@G;!j8(zT91)z7#ByFgF`E)6?ygb5hdgSKZ`;)Db*Q zHcwIDDtQM-2r+J~JzEpDrF+PlK`5)AP+;zz&mkjEV;C(_~{ca2G$wr3?F zpH&jtqOEM8)ea8$;ryq5ENF#6AX&|%xunH1A{U9{GZNnJ+{%(axdakBAhqDhl?2Tx z$KxT`%7J7K4-oUiDGZ91pFgjFs1fEIjXW(5d)M_sYA7hx5R7suVw5q|(%gKsVIk20 z8o8rof%-N?#N-3bSds~Cu8y zWvlHUJrVpusPI>}a3HgW>3Idc6*oiKtuNT?dDE;*{2$OibY3Vl+2)EC*)(cnnrrlZ z68@Gu-mI7^F=jDjtNgsV3B~e7k`$!5M=jfn}ppb!Jka(k*UVX}b_Up$e3%4?pFhvoOea~xe>=ifnnXw8u>$BL?^ zD}6+x`A>m8T-hqbAm~w{rM_Cxtfoezr2x$>DGJ){b6)O2%RH<}I?M3ErJw7Bo2(BB z{BQU-saUiw-N>%1!_{= z$(RBe{RXqoMo9}I;Xgmw*MlT$gs5(%k963c{iQ%Of`~>DtqE?%{r4!3RC%CQ0XPZq zz5=YLxr+-g(=O5$L*>@4_Yngi%iit!lmF8Kn1B|a`fL`HLezsR-g~%4@0A|bFSxPr zupgvp@A6k)?t7=fs@o3W#gT|I6;!T(r}c>L-9ji@R-(iEicAOp{=raFa&*DSvq}wz z2xwk~i-48;Sl|V`ECi13)CpB@DP?JZvNOfI);NzX94k=nVS7GA2DWPD?d|VFe&;~~ z1LN{l$o?QD6MlZi*`HqW0HRcwQ&&oS&R-(^I#BH%OYfhKel?cfNZavFOHHMMTd`0H z#~`w8R^lt+vWhQ>-{kxUo;R2o24-e@fG=))UIu`K(*=aCf}jPVvw_R}C}2-@<$mop z8bD~_D*-=7>}z+fL)eH^I)2&=1fIg<37V4dw6Vn#V|1-6|9KxnP7NX@0=5ghOA)aO zvJAk=SgjWUMwT(*pF6a>6Y@OD^?L(#pNM(Xo=p-K`_{~YBd=vYyss42?m18=-g<*;+ ze}9%=aKbf3c^~}ii+jh|sXE|RxBKEJEmJ%;t9!9s-u@96EKQb$4Hjxj9>+R8g1wbHMk#Y{l-z-@->ncW z?6*F@k`^|wKe;ftvJ&vGNKF0inAw)1Uvt(&b>Sn8xf|FzbiHzR_j)m;WYBD>Hur_Z zcP@TUIwWK(T8rLeZ4<_rT`g@ZQtlTnsx%Q!ROcPJ**156t?qKr(M6xfIQKT{b|Uuc z!I(!mx2)9Kqi*WF?F}}w`enwNShC#(PV0mY82@Yp^IB4t{?pk zkE72l^R&d5KcrZFI?+9^Anx%^vG?W{B9Mk1KwfB7%*tFHzi$|@z_UhuxpT#R=KR6M zMvu4}d`WdHZSrci%o`jD=~jX}JIF>sWsT`|GEm=va5}MS;sFZ^$Qlr$ErjRq_@JtuGJghIKInfl zpvNfoT4FytPP-IBG=@DNwoWC85_WRPx?VAzHu6mA8nmw%cG^miuc%%Be)m~RUzXi> zt=L~f)nY`ni zT>Y54#!krc=0n!-IJ<&Y<8cP2e{f8+c`vYh)?3heI&Qj5X>pphkaplqxc%ZV{lNY9 zHKvabStjhA^}iqJmQG5pqw;ikrVn1RDUl@9zdv-Ve7+LA;Z*p`b->>ke+n-gU+%pO zE9Ka8V;8miXElC62q6%y19$E;9t5euQ>B6BuYq;Vl>Rf=>CJjxqd}3-M`zx?y~^#= zGCt|N5Lve6Yt3Yhp`qlDb-O|KTLkursx?{ylgs*!%MBMY`8V_-M+R2~7;hVswYd6S zNypI0jvkj3x!UjeQ1O#q{-lrVex8+GUy}fwpzwU-F#D3bN=&JhyD=CIVMbP z@B=kMT!bkJe}Pg4Vf%olmZ^Tiyt@#OE?by+4%O<>193%dkN@&Ti9OHvM@-*nqe!dJ z+00vQ$EJXO22ozR;Ma#JaQwe;_UHQI{6tj@3k{LJBZ>;;ehvIcUAFfounh)7I@p4b zflvqz9pwTx>r*@Qe^p?b2?wL{NA!*H3ij?gH+}G&o%{Cu8xDR&J#$$-lzTWP3bD+f zsfJbM7$+K0y3J$HtM$a;25UyN6Y{qP|F8o|8!Yl zWdllC1W=`=D2)D7V72u0fucL*8=hOl=qd1AJx~=qLGr(FFuqWrV)T{}dD}k+%c`8+ zZEV8+yAz$ooC(I8wE5kjF~7eHgIn`td`hTW^1tLA~RJlvv?1%qqppa;l zmX#0ZL2_L`hqbHH##RQ!i9wbHvJ;8WmX5=7LG^*X+K^B4N>kJI&m7d1zNKLqwY^1DJ@Z(wTVC?UQ@0b|G-xTV>@fvv`bo;$OWB0u{ zjXOWkQY|tk^4EisgMNPhn66HvJcdq-frEqdQDJ8HNblv|irFK#ZYj?Tt>>O=*v`3u zn(|JcSygk{q6IOr=;go5&|-(Db6&MAZM!X8IxWpz^YcvDQf4j#D>KudwoAV=1=o+} zq{^C1CiZm#R{zgkQWgJ~xAMyKVyMhtSYMNpw~k@$jF3sEcb61pC&0aI7t&YFGAw((%z!eCm5uOS>5ttC2PVdxIBV<0yBEVG$ z5jz4hGzyZmkkv~GTH-`3l$y1PBBM0q=elZgJmaFqKkK7D$5s<939!kgDu0MYTzJhm zH;Ox8A#<#HkNBU<)^cKn_0h1v<@=o-QD_SkqNUiQN^LUVI)2N=_va|UA@VI?u4~bA07H+OsAC7J_L;UJxm|}yc8M)~KHO#oR>vqEJAuwV6creH3 z-AJX~N=XY1V&D4- z%9PN5F2Yjvr$;Q9C*!6g1=WEN0+04OMP$aLC1MliRlxWT!M#PMcQ z7a&nQSVBNzw_%ADG}{X{>98V&jP$$5UIB?3Fn5ANm%M0w7qotTBO}CBZH}EU$45t- zU`B?{4;H3ai@Gef)`AaUt>o*AAvQ>>f03A!Bm`crx|+-lrS9TtpZfGj5@-4$&5 z!RqB59yKr~v2c-u7)(6vhu(KNp8bS&=^>o;a0dXLH;_~P^B-Ho{A|*K^pDGtEVpd_F#Uqv2LQf?6vLV$v*kt6vepO-Di?;%{ zm2Rb+WjN2OB<}rs%9-@!uad^eguw*Cr?&C&wy+nc=kXiLUl_;6=i3qoDkI9zr-s?9 zBsLG3c}DYtrM}-+iK%}gE|T{xZgPUDO>yFhQug?Rl1C``hR!d5rlI&l+=42~ZDJN{ zf^%Zp?2%7}EqB6~I7%L+sENvPA7*rX8|n>@XG>B^`l+?VZ99tNc*7P8HJqpk8q%v| zTK~k{{*DR7P^gLEZ9LPt)YJ(0>662FvB?v4OIYh{7|c~Lm3h#nWID*1%kuWp`3B9`=Yx*OViY3eWnO>kwG1&x)o9BP+D^E5 zq|Owm{j)aaoeRUhYxscMRS)^q*e!Rbx8UJpvSQ}6F|9biYa(Auiq&~yO5gcHP-qm# zqU2dnR_><)J1+f|uJ3+rEk)c8#xJnq6i7>4a5UcKvLsGAjbYi+jYelumQ`apvar0n zm*VZR!hp)YiY-Mz@X1ku)sNnb;z9GfC!NN>&G$2}%AaCL5ePotqkeUj?kx?!hCl!2 zZGwB5p5ejmbx(?C_`i3%YEJrI`XZ$d`b+Avm;0!=ei#~qPXC_HAPgsft{QgNi?!xq zH3k{enjOQms^eI3sl_{CXQLAWt%4M;uDJWGxE~|h$q=Hg^hYea5IXo?`W^00o|<Z%LQwM zz?qr0b#Z!#=t_%h0BsGZ8Pki-hi#jZHF? zi*Q(`Xx_RoyPF=yeI3?wJeLzJ7B*0{%5LWP{_19BZCqyv-48h4@xL62E&xtDR#!<3 zFLp1Nsp~Iru4v@(`rTl;6XLa7f5z{-%k0|_h$9f)mbtd!6^evMY-~-C%dgiTFDF5_ zh+?G9;{9zB4Ly$Rk)6Nq43dX6&}?Y$n7g!@zTi z2-qn0jInf%1+kO|q$RK?$^p2$Tz?+NXVaFo^$A4i@Ot-%kDq_H?r^H9KdYnl`5)H1 zV$)t_v7p1`8_`2j(yZRw=LlrDd$bU_x140( ztSYmYvPGYwIxsRK4;{-S)p~LbD8;@(h6^2YR$h|jVH-1DUjRN*3BPaEi9F##)R;)D z&gEIpWhaoz^9$_}>O_&hA&?vE(cG#dNQ!!GmxllWgQVgG*u9-aHe&(i*m?O9#^X|$ zmO*;M=mUEfnk+{|GoD?1*55WOBe^WPlb`80!N8Bi!Xp}Zz`aaMyt&|0fEwNky;KfE zukC~cK$DIT6AZpJK+eMngAln0b%bnP0Ips%UibBX3au69sZWJ=^_ok3avJ*F_Nb|@ zhwHj^*pd@Xp?%lrqMU#n(~YFxnI*dv*iy;(XI0DLCqAsxXbWv~ z$6z6n`L$w>c0X3p^tW@&)fq)?H>F-3lcAW`!^M`^^N9NDuj8h5R#ZR6y&6_xBDObN zT#`3*hohrYCRW)?;>)W%}C#NbyRxM^JbB3dcO*J{vna*Lwn!5n#wvM>|2xyW+c zt(Oz>B65{o2Nteee*0+*r|(fYScC+NDE7=KCdHr8B`A$E0c}pr7lgP0T#HWlc{_}X zvu29)6A=uKNem;l%VjH`j#s3)t$MCskEgKOheDjEi#42NwkQEX0wVqXT#C%?bDGq8NqlgXoo~uDGkSx= zq!6>(_8M1dnR!LQl}pYwZDYqxb$q4Mk&nJe$b!*eZPEvYS za`TIsQ&6(MNI$er3dv%z&f}ERwxWo9tGZ{~_!YOrvj<=`z*}91@P(fSQ==&6S>qwK z>4d_K9$UFkUX;R5=Fg!l5TFw^ExCNzSN!#BAr&=NfE>L#uH3qJ|1$F%XVuU5iL`Q_b2!)aUJ}5j6d5Xt+eBz;&2@a@P zcL9WGV5~(c;ggI66NCRk3xPKfqf6_Cm%^|o&L6WWOonk)5`R0#_aw*hbQ6++{C*}F zwL%}ZBT~O7Vp#}E^hlfm$o77R>`oJ1M;HchSCJr%>@eg0u49eld-_1Y;?qG#yv8eq zvdaA|S+IQ*?~pyLd0&UC4w>uzfgS2 zv++;Zn^DgkUgW_au!_I$i}&5lm2t5@{j({u#UzU&9qB1scur{wc-{g)Fhvf%R)yep z5|+YfOrpVY_ZV47dHR$XDj`zP){DL)MJ);RIDjEy%d|fZ4hvcww^|ZG3I zPjxtatD)A}RWIEII_e1Fy6e|%3V$JcsId3~d#K1#q9+hMq}0?Fu!jf>HO zgpt2(Q^9p3iGNBFxLagaM)-v*ckk(SE`Q%zju80^oSgrNp)X~YmT2V=-(+mBj$^0hjYj~NV5!1)QbOTz8^@Pz*$?B3}41=!yF8pnpH*UxNL|NkF zYKX8fZu=`e!i?cX1!Czad{(r0r@)GLn=R8}$2&Y;Zs*qR_j-+z*qG%~WOq|CC3+aD zSd%WE2R#;1)madX+1a6Z#FS4)PQI`k)y6SWcI|r)?K>>qFvCY5B;)UE00B3SVpU zQDoNW$xQy8Ofm2$%qI1Infa!pK6-t*a^`QV+Ez-+q(8Hk9YH zdeO4lR4covbZ0y&vq2nFI5FB-s97el=Y2%`vhj-DvAc?896^Q;mUiCuv#A5K;qT{D z*Hx9}lAl;;(i5Ebx9!k%2dL}NQW`7RD$TxN`)kepAn&A(N&SaJ@yAzEpIu2_Ep1Yi zn$l8YNl8!x-Y&hq|>U$V-*YLX*a`}!q5z^#1-9Q z$g^vtj}+C&8zeaw({|$>3tZIU8{0EoX}Cs>Z!;vvd|}+**z?r)OPXY1&)-)jIs~hV z$6*Mxx5q*`yf~?L<*MWI*A-7@NhgC@yd$dyQHS^Bq{Uh?3X)7Ig(R_|Cg~!!qvidv z@8ss*i{8LqyWBm>#lHVtvoS!ln~Z!*cWUO5bje5gkn4I?@xkJJcTnsh2GJ&ctrs6w zf+=TK4Y3=x&SuTZD(|acVKdeTEqqjW!Z`c2;uE^LK-Q!3yZO5eefdM|tNy(27W5u` z4=(55AFSx5k@Al>UpWd66mz0bUQ&!)QEKq`YF7GQyKFIpZ!os6$>+|HwW5=B*teZP zZ4|xgmY_Mhm6w+&lTS>)4gbqwGL?8U2z*3JU5U-GmU^WdVQOt4DcWu7v^c)9nWQkT zqWE8>3UV*ic%O90c`uRKzqpY^_RDzELdoQvF^Lk@_adcP%$;vHY3Enh%~I}L8=Moj^iL{PBU=ovYzQcA%;zoo<5H zNb>BUqVS^)vN70HhgBQUu~LY=j0?C){MBws7-8bycN9S0vBFwZml^AOF9b^hegq7B zu*cd^D=(@g(0bVW2L{3)8|!{^F!qA=j#sTK&g!~d!mxCB4}KduJuT*S_!ba-flmHs9S%YZqghLvQx#c`G@_tTde4AX{qfZ| zukDrcCk3@%P5Dcj;TEf|pCpOp2YuA?idjPA`)>-=pJLxcq^P)wlW3?VB^L7UI>fXv zuWcvQE!If#(f;)`Nbcg+*0OXRoX89B`7JOLLvXM9Oc1sj9q&=Z-V-?wPHFq}IY?E8 zVeC$u4pFH2ZY=@f*7>o14)f{XP7T@t!Y$l$XHW4&+lNay489N*GU8s< zII?^%6~v3Nzp_*Bg`y@qfkOoo(Ho7AmM3Li9h)byaSBSqeqW-V5fI1=x8`1W zatl8}1>aDS$ySyjeM+x=^NaAC@nOMOjpnhSza3x3C)=~g^2Dk6PSFf$Z_Un!RHf}-H_XXUUDr^aPcv3_VzZm72IiqMnF7`1u6 zieODrC*4nqoA{C*=-!Y}r`g>s!g_2owy_2?Vg!;`3LfMXX?)VjawT6ZXzHVa8*|_2S^de6)+BKhLNQj$gL7dkL+8@os=N`@H%f&guCd5uYE z{G}AgDQpj}psX_4uUa%53bhM9$GEYHRnAYa9q}`2qv7hk-b{R*S(%pjiTpmP*I%1F z>99>goT7&7hEOz_v=>?Q_+)1>E!*3UT9)na6lpE(>>}QG`g0{z4or>wKf>NS9_#-7 z|3{LQtWY*(l|4d6$;w_)h(h)V*(!2 ze^wXgcs-BjF&>Zm;n_bIe{$+bix%ewZGXh=bkMgKXpnuJk!c%C3muHV z6Y^y)^SJKqmU4dFBW+pf&K@V{sqGgkW4>eJ9L3AOFJK$>o24h@>BUmmeJM>ezhQEo zE01Aiv8!Wa80@8icb??dx-57Xykkv@R*YqTN~5u*e7WQNTaw;_XAjh7XnQ|2tdp>* z$5miJ%hvj(tu*It*p`CDy8^$b_j1UzTKd~Wh<*)s-RvZ~K7uB?829KLb@Ql*|YRk_UqD068q`twi*kQWEk-g)CPFeK(ut;HI2k(d9`AN|yC zhXdl_FJ6zkDec|;t$5#3xe=FHpof9IBbLaKj{5T7P<&1Nf*aRbF7mJF4kZ*ajj+my z;|F|rPp!Fe)v>LTnNH=RV~SXjOi+CEftu}N&6NzkLT2+}0|Nb=a`_%cAIR{zQI9yvii8@*dfTU z*%CUDjmDrk5rx&R1ifT3&!PW88q5sWWA4|IjG3%C*uNYK?#&Ivg2$J%sPri8UfIC- z7L9@NWJky4kJT{ch9YApGLjB$FnvgMa&lp=@%jSu$9AHrg98vH0tYd>BIb+wFju+o zpmPoEBeX+|kPreuw=VNyqfox?>L6F_6#>>U=)dO-D?mh}q4T8O<2olw49^ao43&(0XF5}~h zaS`um%WN5;56KtG4r=MBIrW89ZTxuY{7H!fYUy*StFCEVf{y;towsLj=ge2-mz$N? za@D*HKiSMaa-CE)#i1`$=u~W9dU!f$cGrrnV>TjgfsjW$K8?a$d;S76zF5-rA_Zy+ z%UdD%b#Z@BR0ZgwQ8|12ZTM{vvPLwwp|r4&l8F!NlbFOkA0qrH2mAI zKn{wAQ?>ak>_(2AC&QbU^3LPX;x4ol3iS^&6&vXGjHb9ZT(kPf+AbG6NUd0oqfjgB z$cj%F$aFPgeox$7HFWrrb~@*QSNSb|2EvX_|HU2}9s~6c%_+9dvHMjSs_zOOvz1;9 zBj-A=-&V1c%Td)Cc>4*?#Xx16#EhS`Cdrxwwt|l)EB}^830@Zp^yiwUNVO!+E)88% z{oExhC<-Ju z(Tvje0ggQVdbwKRDs0VA%7Y_&5eAHlGa<<7ah!A+$*PE9H!d?*rBtM7^CDsHYBYuE zD?$zG5mC=GgvqT1qWUYOi~EkZIQj+g&-J_9xcu&B`7olhe6gejIV_l5Wo-~+X&Jf; zSsV>hTvFMd*$x#~lavQJbCDr12f5_Ce-Cl-alOsX%3>&rYFQ2&Dw(o6%?EXkx19$u zifx%`OQHmP_u~Gx^ zrArtPhnE8SIPA3n}XucXGPqU zlCNcoU$PEba+8+&SQp<2`k=V?`Rff%{`2+1I8izz`pQDsS*D7P3ItmIV<~|o95b0Q zOokfRTDpycd3XiaxUQ@iogX3F^vx+~42xfmrKJ@<<-^Kz1m<2%&}Kh>S??Q`%BUbx zbkC$XbCzxLtk#ug=dpLrV!;*xWs}9)^vy&rhEoLEuoFc{CWV3wUNaM(ADOirKnnQ`f) zyz!ZO>eGpb)ye}ua~lX*-N^Csg*zk;_E)W}lH$LT;xJHn+V$vd={Eqsn3gt zlVOOwmGX5n1q&*!Qm}UTE|@aT8Jul#!>DdQYI^@7=(!F+_2}h2eVdzB7e7Rc^P8g< z{>*Pt&T&+_97o<^`cT7ta5bUix!L?ms8~BN%U>fU-4Pf#3L!!u9q*QmFUD z#m5OA{OIfB;}QzDyc$I~RI}jRqr0^dv_>kKuzGuh;79(zu5-G-FZQf$)v`|eh75f| z-1lpCB}UVaN74lD`0AS!5^obVo>^4+BvAU3ED*QDb{VY>vw#kr2e@8IDj7zfQ&@hW zF)$YrR1T)!!31Gqp zlb@{8edsbGa!Ul<1*tkrxiN?;AH-(MerEP!M(A%6QI@M%3XYJ~YRl=}jFd@q;Z;|FL>sC|65 zn{~-26z1Iw?!TqMVnd6_v|$>uaLaeDYP7B@VR%><8UeIB3)_^D6$2T8Ce!x zxpE~7{8pehN0vL3x~}RY)J$YlQ)!0+4XgCR>3`3%M)TO69Vw?Tei>Hx**LF5))7c2 zCh!d!CW*q%B5<5RJgprgL41Yrig5dF@+8-sC(=P$S1%{;t;-4v`?KZ}kiNbma4(wn zt%yIx2@1C?BvS~7HP3#iFB`K`@iuG3POK_XS`UY_|T6@bUm(Sm$csW4i^zUJ< zI4owrONvWOOm|)9YK)>pjB5#VuPvkB>=EBVh5Ap|#Q8o7A9O*_TS46!a*KAVu3-3uGjw8a`{M+>DhIwq12N8|k1-fN|cCSdHiQnwnU z!)hKBbsed-C!m}bofmXUoJEQypZWDXzbKVfnF`V+v3G*4*X1(V39gVed;r;y%NU+O z(zP&`8S$YWVm5ug48fhAIC9FTG+3HC#zJbk;?HZJwWgD-JWSfu!hdSLzThM#aOGZL zvdfsQc)kKwn=H8wr`*py4k{wYB4bQf{4d&ASeJ4}lPak*Qbri?vc4m$oF%PC=Xh0? zHa_BtK1fPTbO^t9(PC??k;@|;W{+)lg{IGJ=Kx3w)wM^L*=b;kDPkY zXGFgYgc#6X{2~!qA=vZJCKmu>Tc_RD(kcqgd{W$&d zeXuexvOC!I@h!sW2c`A;ZgJ?3#XX$n}UA=P`eDi^H0rtQh!1t00`D1|JVf|cjwn5 z=MOmoL(kzr`{^Ojr9g=Taz-q$#lc1!_yw~bMut@l4{v_)JM%?kC5Yt$Fu0)tE>g43 zzz0v)%PlFn#Cm~IWq<82KjNhq_j{2jnN1@93x^pgI{49r7+!-Ulwkmcas2?6Cu zhK`@j|6G`crUIzBa)QtIrS>Gs7{LMq!%r~~dxJ8UZrvr4m#3vSVK!E9;ftZ>uqz8+ zLu%I(lorfYgFVNL&&jB4JL0GD*GE1EYmo_m;ky<M33IpRk7s4MjV7`@4 z7v2kc8k+2j%Dt~i@BKl?gATl@hy)Q>!GiF3A!=_FiZ;Vy-Ge3=H0h%IU_}oDWf2=) z`_=Xs_~K)FskwHq0ZN-9N!(=`M2rs~}2Ty3qjgtD(YG>tzb5WBxOC&5(ejqH_`N^L|}o^ir9X0qyw4b634lQ37&RGICu7~lDbMDjfOGT z!k_*e+Bn#~-$%CvS;HYtOI?pUNi$2YZ564BpcB?{`6SVPpN_Ke=zJ$a^gr(Z_~bxSr(q zS&fkPe#@Q@XnzZIxST>oDSmAJ6!^l6dm+^Y=h21eq9wFwEHY?`GOO#>;QNrO5*PE3 zS_T|S*VoRvdvYg)&p7N0>-Q{}QwC6(@ zBwMl4_a?=nLY%xyT(&cM03&D= z*UgBDiH<5@+L-&D=ES%iSfkxs_lgi*OG@#+C*Gz}H_);Vr-0J(S%B~C$kWi4yRv7P z^O1kv_;0>p7eC_sAcRelMVl!bO7Z^9c;7mHPIPu)E{7oru@!?~Ug!&Rig%Pvj#xp% zB>2twHz-3%3+T;gGOuwg&Hf4d(iZnD(_r9^(iICqwg*C7=ofjLJZ}gD6s~`v>2&)3 z)3NeO$nNM_M#C+i=(%c4y>!xpU({Fuxa;?FIr_yp`m=dDhm}|vS-t3(4O~Cz@k=d^ z?x?i1*tcNp&t9T;q)VyrW=ict(*JL=|Zeuze2Xo4-xe zcR2DR(Hs5kj9cGhF2ydJHAWeq%WMC3X6F9%U&4-xhn>4ThKYzf#;iGb z2PvTt%Y2G3181K5QeFax52*Jh{djNNkE#{vKWlEgxKw5XXpy^>`eNgGR2vJZ1*qLAwFrN8n ze8QD+&~;8Ig&6_`Xht`nsDB4xg_(Z2BD!Jy##_Sav(r6IB?HsUd7z}u0HuB*$s+wJ zaL-mI>k`1L_&)mt^rJBGLQE=F_&y=cGtWie(N20EB)RcX+_x|>34kSAUtkUf|EwMC zr5|)Z8YVTW=da!(rw$?tc1KZSaR0{ps zk6I=d*3L9HLz0gL!@3S+J4y(BDB46#(Ty1A)#AnF=b}^z4{l_l5()0p1Q#U8AYsA` z#e!Gv^ubNP#nAQ((YCoiK=on>lL~ZEPH8EJC1(Ck{G}y6Wnk@z2r;`kA5zQfmy>cjpGkXr50~tXn?hooIkO|X+{PEdg)c1 z1|2K8R8(ekEVB_u-%lbK$+`sInLQu7l$_01p7MSx>vvWPA+w9gK{07dKoC`CxdNqy zn!?`?rbU>ZCXA1Dg???Kagi>nWt1a);JN#vF{NC={)c!L{DAEP>lQVN`z*xwBzkkk zY*}PGnFtj9s-g;2l6D2NQ{>LwRw*TA>BhEQy!GA?GkCMmOpX@gR;N+yvE8o#A}@7O zFofyrl{N6Qgl30wgagoJve(vvmhoNZGsMu1V48$NiXN9R;SW^ujeIYc+Z}yxE9m!X z)Opa`LE39oAb=^e-x8n-`VNE9mDRj?D%2mln%)+aRHjewmVS|O`KZo`n_a{mnMoPU z3_h;aTQV(Tv9R?9kXD`z>A|F!u0>z1>`i&)o^;91qxG|N0PJo8M>m**P*aId(D`!FVIrbdN1w10b-$}7NHX8?{Q;r3efE9BISU@ZcoVo^al)*C=_ z7MMjBjDs6_ygp%JVFYj>(|X!8TH1d7vKFQ{U7!TsKjQeN`X>%;jnsQr!r>9Ll=DVMl|r!;!Cxzx0e%ol}ze z-JP63+9R8%b(b>=-8!9Qk*5_-!LGI;eTF`pzjU^734v~bc@S&1ei>=^!yEoWt4&7| z(p*~j`C`M#yyPw<1M!{DfUJNStaMe1+^HNsf7E=x)@y?La5qbcjr$FQoPF|I?~ zbA~965#N$-!2l{5(x9Tf82Oy<^YhpAN{m#eO;?-_8}ODE$%)!4vP?@Iee7qdMOGMQ zj!pv%isD{QECsx{A?jwJhK}y@z~<^bSMcy=n~APQsEgY9)fpvpal46$7loWA9Z${F zhxZXbcAGrwNvk%(6mOv=N4m#)ck9ycW~Bs8s~K6DYZ=^p7xy_PY%eKzbk3CTP`$G* zWT;EWMs+mugRYHGv!}j-se6Ax?a9ie2>$B5S@D~>>8mH~M&;a85xBmj5pgwDp9sF! z2wjqnQ&#@r>cCIY-yBG0v%da@J=^Wx*Y#y1ljtMlOUuvjtzB<_^)U6*FY7WX=+egf z97%P9Mp-S9gzcH5XQ zQQPSH&Ludd7I-te;*JMllnfW+^#``sv*lKzim&Tic^eN9vj(zj=(nmrF6!UMC)4Vq zCXn`}I%_>k5I!XL)wvgrf1Pqb0xdi*g^|1A8LXQ-`(y9xnGYiOMFWV*K$HBWZywFv!&OY`EvdJD~p0mBh2go}u9z1Gi8*K0g?3ZG$(jgQQ?R-#1K zs~^qbAJq~!(GB;@{CKD+B!8{w&-p9mzvHyqsq@r;pMeYhi`Uc(4@ErMBr9);x169R zA}X^Y4|+(e@;xuwLo zqB5UT^NDyZ0ZGX;&)9>;jNwlE1QlfyiSH|Rd1k|jRwE9ejs<#5o^rx?`sLlS?;_=~ zT3Q9fAR$jmO)XD9w~BUPDQ%h43ncy0eP?oNYRJRgohJ06O62CFN;`Vw;C)Fjq{)<;z!P*Mh$qcf6 zB)xy6BxtwgT!R5wwqCZ}-a;4saF2HhMG0U^V;**U#Vb9-`T8bQde`Iy+R3YvuAQ_r zluC}P$eAX%RFQd=w6CZhEm_bX`?&pdK-Fi9?K3H_V(SQVw$UCCt(Z7q9sB%Ed(^X? z*S2(QbKE!YSY~sm$o8^56=Vt5`TJlbz4nQ{X+Y2yos*}WM_COt1g>Xca+OT@8z(sF z@tbmkMJMt4wFyL=awgij+P%emw&cM@K`X-^3n6yKpInXq`U+dl8M-M7k%i)GaF7|h zaqGl-HYwNtWF}NHBwyY?^K}j1i`KDvSLuKAo}erTV^RHnQl3zU16y-!$bftowa{NO z_sq9ANtPH*!-#T>us zj`U}J`r2G?-)NEJ-c3Q5u39lkEZuIWKSa$hi zHOOJm6=Q8wfwMIaKVDqjvNOdps24EG)9M%sZFj#ju8FnP!ahf!$&)bnL3YuT_R%o@J=6E}=E|gu)3ojui=T!xHd4Tt_?>n5We$87&=DwSC((sVUgSNac2KBW~~;J(?8 zAs^uHwRNa3QK?g2%G*mxGj9rQWHSk}n>QtXx9U?qy!6nFR(&j3X3;3E;q#;?qwxB3 zfGPcC5w$Zdp?5Up=_=K4x4d%sS{le*Ex)p4^=W_BF6piIZ5xk24@Jl1>c?a}Wg8gSJTg<3a2nZP zJxceCxK|fiT*0R0_+eG_Qyu|^HpG;wJcNoW;B*i3Kvq|@*ML%Fqz&`8;K%&WZvwNA zKAoEK)Gzesv1aor{T)?w@(&nZWhmM7STr#4GgKD-KP|xR<=O8F=aldR@zmQRP^wE= zX3V}J!z{w{5igRKJY%X}cfG74i@!rU`*FV2$hw>qpL^p0S*UZe4Ys&iI7y?Ta(5+WVMQHreUGawEuRt&C!7azXO|F5JFG_e$EZP2u^_cg+BKew@dopB z8FY<#9@nI7sd_kS35KX#jJ1*bcdDNjsY0PcLDw!dT%qYE=ehlD_0Q=>DwSQf3CNLATq#v0_ZU4ZzG&}nD8sSW>9Uf zhTje{ZCDp0W77>kP=jlCTxhnuhT;7h^h->kWP_dwT+^a}dq&~JaPo$;NS*GRBD+P> zJE10luKB+=17crA&Ow3Pc;rn3_Uj#tfbz2U!BEo!X$=URv5<-f)Dv(UbYEcf{`U5U z9&}7$H$grgDI`^Zc|K>!Vqd>05Bdjxt?<{j&fUxHG`fjzjw?%5UF+UP z+Q|;_6!%0IzTJ_jjEe6X9Uw12v6_rFIZBvUw%8d&-WqN4IQH;;5&0?cQu&qdqv$wp z!PCiOdgeUEsO#NXD)g#X#{O_PR}_pHE$hy6YCF^!l~Mo3l;&hwO{Uj273VxwJNBu2 z?%*(DUyVBK5@D;=Z9op+%tJe~jEK$DCgouhbBB^_cl&s`P>EMBE_S6SyjK$|Q||TQ zecd!%|NTl6a#5LzeeS^BJ}|{~oO3L8kuAHzTdd;}_3cuu#E3k8H+6t`*u6(t!gW4V zQoAB-BbA#|0ujQiM`O=g(K8R}R+LBdX~G+vA(c*;5jsbN9?alxNm^h_Dm*Jl=2WQeddAGNdR3bEoXa7T}}iON@3NVYlDF0p~1dj0v<{Cy}+ zUQ2A@BTS55DTGlJ20My26Z{SmQlN?{Vv2s`<m-;H^T}kgbs^zUOJZs^ARH~BI-p*KhR@@o+#A3n?fo(fUR}` z$;7{tkB}E8-KM({P5-&i@KO+_6Xe+x6ch-i0|=ET2p0=M7CY5+vHESC|M%;?)+KQR zAacA>VDJb;`YI3s|FL-hNx8L)awr2#B@#Q(9>`oqzo_kJ96R!Qka;u$ckxu4J{+zR zg_r{OFS&5vF4tM6asf3>R5+%Y0^~d_mre?KC2+$Mh&m z^vFwBs0TGgW!McFoc_^Rf}X*FlQ_YEvx|CNiJ5aGYCh~VE!w69QQF=CbxHMWXr zS*!WsWno1XG_N==#>77aT_M7zFhaBbHChb|J*hSn+1aWw`(|AS|sI(qt%ik#ZFr3V1{l2M& zKE3_s=kD~bE4FXB(uQ2UCeWmrUr}R*1cinA1g=IW45Gn zj`J@?WgSu(zR9+|k!d;c-<2BNHHjPa8?cLYRBojgx!m2vcP)}dhw}PKJJnA+)`%Kv zIW!6{`qZQ?6bIIbQ;qS(;_vSQ93)24<@7mvOGg@hf|^ z#HEw2ym2fg5;t$+BJXuAFumk*9V?Hlu`Q$V&Q2UFZf)3*BaM5WBxQhTmv>Li-(5!) z{O^G3@qm4UG>nXB(D;F$^vycBKVk5Zhj#<`npn`~|5r}x7isY%9DrG-9ifj%188Lf zR$;~g?+pX%p_z8DgtDIHaLghELBv3W2!cSr8WTf?oNfd$f!v6Vuqg}q=PzNR0K@D9 z1f*{vg}DL%lma2*>Yt915cLqA(AN{xSIr__v`bW!sF=8T2jIEd5C}hvO>kNm`jH3P zOD#>j6){2`qosS12nMWh)tvq(aHNla8J}EsXgpv;5`&{wB)371E#lMhg#wo)?_U$d zpOV%mcQs&Dt$99!0~bCT=(RV2B6_?B@3=ts zBh2GLUA68AFAK<1cgu$jYhQ#RzA&AS4+2S%&ap+h2VyQ|%0m_$upm>e^{;1Aun7(@ zL;t`}pgy<&=t!vVyO7WYqHCz)v49pZ3k3lW!hX`~eK5CE5}$3zgK6dUjX^}+pzgVX z*dCA#TWJu6UTvbj+!Da zs(RSXY9}^y+ffqFXJp)OxU8W0l;|SS^y^x;vNP|_l^1Im{?9I={}P7hpNzIx%qmZN zwEJ>eK1jM|V@JJNbh8xG44s%n%Y@ph=xAr#pA40;=8Nm%Pa5jx>&*))+KDQrkM!8BLBB{2_0HyjZ8BDO*T2$t z2K-(`yDUxA^w2B&({Bl!wis$Nb(7id(a*J-pV_^>GIna(QGOJ0!h6GshBe1E_3eZc z3kmV_)#bMm1sk~`B{D4sIe(njwMO}6nx_MYrPA5);}U-F^Di-%D-yT8I96BIkg|+o zlI?usUeC|FaQ|#*@!9yB_#5|*+i*|wa-MIR;w1m9;`;f0E$>l3>Tkwee_?z!jK|?| zMefd=ibdEh0uR`8c+!#GOw55#APq+55%9dgCV;N44dg2XeCT4PNS1loUPyebbwvmA z9U_@NTst*$(YLF?4+3||@5ZAwQ)_F&JW5Ehp{tA>Kd`P5m2a&kdsS-{JO`X?K2D*J zQJU;+%uQr1Cw%e9meqX7NlHdw$e6ERe|k${9mB-#VACA?k?Qp_NmZ-K)hg+vLVD6w4+s zb~wyl(tdxkiavxQBt~lsjv)!f>?sU`k>UfY250!ZAbLc}zlvj5*W!BnVPx5`(fc13 z>PRYq*Z95eM@I-k+C{oaiHW~KtaksZ-FIK97vZr&#)~)NQ|!QqL=q9*@{_l#9rj4L@}4KFk;k{zF#Ypw~{eFo5rbDNy!&>G~qJ zQ7*i)>c5!|9u<40@dbT_p8>|oFUzUL-x3y#7%=m6Wt&a0JT*+WH^A+ke!t_@d3$Pp z6!Rm)Y4oYcA~_1>Jx4E@gPGSd+ZyY<78#=z^EPC{79~)fm`2Dn)$3l>>vC~5HGH#l zyhxkA)8F(I-Tkk@*CtGoOe}p@X=tny2Wa#Qdsx^fS?{lRdw|;9`Vkh3%nZk(R13YN^NRImz{b5O)Hk~-%vHJR-iN6Mmsn0 z3!JJv2{qtN6?NZW0`@a#Q(*qWWqwXYRh2nU<-8WAhCfr#LtoW>3(~4Lu(2|p+Soe= z1SZb=8#6D?pjrdJU~CVgMDN`30R}KoLs%U&6{+Gb;RvjEiKa>9b^@RD+zaNr*6|~j zLT|KSP5}?yU8SV(4{bT)vQ=UrM6>=t5zOp1=|T;OG_-XA*w<|y2PyZA2LoJvrod~m z@?m3VA9Tv*$Tlj;Z-?%cazl$MDoNtog3}ew>rwKom*ay-3-opXbq|k^T62^#Cp1>M zlsLKGDtXp(gu`NEyWqQv7I0h;NdhqK!N}tbep=)LxhA;_q!)LX5uwt*CV&rD&YmKs zxx5o@-?ZSjyi^fL1&f$}03QepSLB*R$cT^MwS@zdv0#SVp47GKNqbJKl{pEDqO4uh|PWUtQ9nQ1AUM%r3gO zhQVbwwxCbY2$a3Q7bC+=`k_)_3r(SKllul?>KoQL1%a4EC$a%T80a7^U}aMM(OTV* zwJ?OOf%>_0wB}7J><_=J3m)3wTA^qs@zWUnj`^YOgIw`I1B*cB&bW8)KwjnPnT(_- zcG>Qfav+d=(eorwqi7CaxUyOty;DJaRQQXlah(De`lr+BsQyH|&#IgpD82nyb7Fhau=dIm6IO83U73D1`ggGZZ8 zjtrXSa3--Gha#P7h+DFYHW#U7o*!&I%Z-okTo4NMqzPU+g;1!Qtf1nGuT@=T4y?6w@$rGq$FCQe3bkz36syc%KvLH-Un*;Ei#C zPGVxBVie0tqeYub9b+0Cpuv3goCJlcCuQb$Qw>Q>elAK6gF zYMJz{EiVW#^h9M|7WeT*-71#}!)KQ9eqd2_E9!JZ?yAEHV+Y#k*)=+LjY0fhxb$RR z3AbLFAFGFOuuzie2mZA9DV@xeT;%iOT!9U~@GBj`PhmZL-lxLJ@IQ)f6F+moCNpC0 zV@}s%Ly2cnm@4$YzgzKGkNZuN>FT%(!o!3O4Cx4@0b!OLLz#fifG7JpK;ng9ZuTLUQmrwB zZ6<;+rGgqQS^mN(KnS3?L)K+Buy-4F`o0^-BY{L}7Kq=+b$)y6qE7RQT-A)wyZ|8h zTc8r=j%@!&H@I1pZ)Fe~MSKdRnbZcaJ(fJAF$0%`_Dw2OQ;tm$7#5Ki2rAObpg*^0 z)?JT037JLR*65TtVUQvPH`AD{6Uwd%xxkP`R0TRpQ8!kG7|hkU@<0P~@m~Q447I!X z-=Qy24aE?g=n={u`iQVU8@wX20#qAo;{OI(&_?*LTG&n~mguW{{AsCm*ZFO!PK1KT z?4aSa+xVJ(SNoBL{U+PAl!nLmAL=;?gOM9&NjVQj*$q$39%g#r^CWCOS1W&(EBv~%0Q)-5H8L5S z++Z!y=HTBF)KdGG2lJ=fQ9itR+*KM3<5))6vUH2w_PnY`{JB7>~pc{xZ^nnkh47n1hmViXdCiBwj*4&Ko$6rVnz;EM%-#2J0#9d{l z6Mc@f_b~{c1-XczsUc!J`VDDn^khiCUO%#8z%bz>WfC&zn1=O$)$mfWkuI#qW-B%H zjgc}Pjt3^n!TS{)xKJp@wKymt(7>Hj_{-W_Xk&8S7r|uy%l@bL;NWt-O-xRng{IyD zqT~YMNfuO3uo-?M1q3ayEGUrFO3D+J*_;}u6%INQq=o5ssD2g(lzc&P@#MvZTk>A6 z#wqKVoRQ$tlZnQWquvCy-l*d_<2tpbNVor882@)~Gy+=L7~A?K(w3up)tS(^ecgBG zqcL|!X~%+7^57_7lr`7tof+Gur1~Ftl)hYJ)=(_ka0$edk%*U;eGp$zz@qv!U{{$* zLZ4xc?J$g>Gadggo9yj!Uu$gg+~_`W8S9Ihq4|aGL!5uFUKf6qe>GEBNr}=(t5*K+ zhn#J*hzd>0JfmMQ8L^`H*ewEH(Rqk^R)3ah?W_G^aSWfZa4VE(T#-b~$U-q_iPVEY z4+_}N&pN5#)`ri@I)bwryBQhNF#G`#LJK5Pc_IY^FR%;N|FQY6L&LM(jU+B^x`-+##tB7-idy^tx8$tr+h zAP2t*ksUf@U~faV%~V6~4859sNVaFzM*!#ok1OEWp;-F@FAmCNxe9&NR=YWFdO0$r zEd~=E%c4Qo(vlf?ga_7DA4ffxf)*EKJ4hLevH@%bc;ulGKCpX(DgtE=c&mt{2!tTWU4S&I`ujEE58n~4!M_Fq1hA04LHk>KMlzfQOoYfT90&-? zC`9Q8ZEE-{|IyE|M7RhlU~)Ol@#0EG4zU4 zeZ1=*FRMhsyzvT)+dG*senXqr4U1cd1?`a3C8blCq1^P@pZ!`R`&~KM^P%-Bd_#w# zAm>`F;~k1i*UjoLlBGxgNEy3QG6yaqk(B+$49~Nx{3ve=Tur7OLy>@@wBXjjBawxW zr-_SsBH0CLTi>V(%B=Jwv8=sFQb;$(p0DnTCHY+RXy-#eO)&jy8fb?}5_5~YG^KXw zX$bAD=@id-xhn+CeBE09kx!3ebxy_Yg))V^Rk|HP|zWZ5N`5WDK_?oNrx`qF%g0GThgoRs$co+Yz}L3_6%=${@QtNeY_vgnRrRWGIBwLTUGl8hVvg~e4+DJ#H z9(sA)?pbQdZC(ZTTC$E@-IsR=!en46)f*gcN|_rvL$cv)QshN{%eQvfC^+m+()J@e zubuZ-VmAuNX74ZbClO}2$}Gfj@u~z%E_-M>TewUvZ*12$GAUKuaK-(3)vicUOWfZ8 z?OF^c31GU+5cR{#D<@^O%fe%}a{b6IAb)K5*iItA&VO>PM4ng}g$+?Qx!YWUOw#LO zw+9XLB!1ytQgEc-B`qgt+U@7=Fz4Q!NPQhf`DA2-XGDq}(_8-aV1ni<4cW9v>gQ#XXb0b0AamMt zHG3(a0~4I39buN*C4={jiM8WC1C1h;)%MZ9EQJP6Ew?gLOjBydf|=f*_1vP5$+=a% z`@hcT!^Tm(I+4br@!9{=0*tv4xBs3iPJrXiPjM%m5J6>F^~07ug0jF%7U?WYo1|JP&hui z^};(sCbd?3pAs52lSRsZ{V{?6j3lb+py%iaJgHsKPr1&qSfxk)N_)rSs8G?l-l zMmASS{{(-^v>{sYa9P=|BHOTIo{k?`N`=EnZ_7VHbpjLiFiY37mC`Q(c1Dw{0woJ% zX(LjPu&d*$fq7FlauY@3>a*f$)+>hMdF0n%b3=bHWrzvmKvQICKv6$@6;<&Zr}kfh@G)?iVDJ-jk1&srR{PWxL)gP~ zTunViQgUu_l|(0!oSWE_bnK^NJb^^tn(_Y@iNCyqe?L&++BJOtlIBMt`X5?nwAQ{3 zrRR4ZOe7qgD$DWyT|Mj`Sp8p1vY%G`3cDNUdr`V`ioJ^y^qr3uoDYi|boD&E7W(6) z#y=iUDnIWs;?uAjRlU-gbrB;){h~*^@rPYvT!as^8h(vf-;rw7%*CUI6D__Gxn+^u zc`e&%?glQuzQ1CYDBjD(^d@*k^mwbjOKxHfTwfVcm$alC7{;4(lqtn`g%rq{vBTt9 zLf^>JD-B?w+*0`>pO=cyzovFeHn!TLfn|l^Y?BaQs{?|$!70sF` zb^3`>5z26rV=uLQFfA^wSO2&E04pr$m8=5u7b4Gr%FcaH5*6Bk$B*CLqjmjD?t$8~ zmRQNY*sWooZ*KTQ|DL*2=3cn{^1%9PLik;q5Z@|;k`BglPm=LnUM>2WQ?lC_}SnC(noyM9H7_m-^wl(9@#I-7OS(ADd*D@)NA z6mbd5n+C6PHi@Qs^;~lnLkk&o`IaRfWTMObs?d!OYd$A5SJb6DS5dzKRm5YGZJYJ4 zeV!x&W2aSGJ)Ow0h+cRpXlP)y{a4>rN8VHCbbvK5(~t-Ke&ug*rrN9)Z_{O7(^_(( z+xgm^lin4Pj~$)Km7B3$B?|1stEMF6rm`)jhz{{DI0P~kVM_| zc$@8`oZbm3*IR*B6aM?XVk6$@@wd**6Nol)=3&r_x$l-vaVa%9R7~YD{c-j49J5cB zrN#)%i?QN;&D3>Y^x%ah)GS!>#6~Wa<}zgUyW{Jy6dSL5ES9D=Yx2e~c&~N(oaIUE zI!*bBjWkdn@!ndb_`cw&^;(p!Gs2$gYMR2IiY3>X$_rEXN!kT6EyObDN6eNMxjbjY zUn_R}egmmZKMPw?jPe|L_DJ4e;SJ2T_0VsjAlKjVL?Oni*#{?;C&G zx0c|WF!*sx&l-Qv_HnHNUw;v2&<+E2@4eWK)m;lr(rSeri<4B(@w<5`>Yw!rITY&d zz2o-9_hXu8s;TUmT{dU}(F*H{c80%v;-lXVF_5(H{9f~)njIf3t^M(OYHral`d1Ll zy@{PiXz>_FLkPd4_;sBV1RxL=|Y?G4lH;T|4Y({sgyYX?udhwArEHEB_sXf zhJUTa=pv%&YbTF=d~8G}#jbw}zZ~(vDfwfteF=Ji*QXe3LbY6@i-tFE<)^2N9`i(< zZnd7ae+MykDYz+k+E+W&sNY*peUP(0*R+dwr& zka94&oniAUD~^LIfvD`lbPf0O!__2|!{JiQWGCjYf@Jro;PlB#qoCzpf9BgVorS$u zcS|ym0H{mI&2MNEMT#@M2fNWsK(&h~uYijt(M$W`2RBK($!9wB8kPsSJ3P54&yJ)qsic>d+-WXQA`b7{D+kp2gAQe(KJRn@P**K$NxOWBtYq42cI zFhe1$6k68mz2sS4EHOzRdR!#DXgL=?*)1Bkx7wiYE?;`3tG29fSXck)w-r%R+TXZU zJ&);ow$E+`+Bph-#n1}9UbKJ2v$HNIiHT(YV%pj;@h>ej6*D0g zi#lJuegw@{Sn5Uvt*r_ltySAGQtO`GO?x}g?v%0kA z5Yk-{x!GM-t-%30rBT@5jI5%D%64r+3{fJ%5HQ8PSjX@^`hNC@TM_*v?MzQ--LBTE zx{OS|pR_GG%tu?Pb}oHH2MzkviJA*?*p064=nzMTPN_B=d8mK;ZP>febxhk9x%?=9 zaXIe>_GHX;>0Fs~d|~5Ft~$FtBbqfHTDnC)?%K<@(aC+!T8BoN<_?z^X}rawuJ7*i zPba+mG$X&>>~s5H>X69WId39pNYi`slaqF7pN} zTp_buvTijRi*VSK_yVd)1EAIdT_DoAx=RYRJPqtIhGC<5=er_U_n8_N=FIWyPQfr{ zEDY^YE!OFDofdohFq(%l`>-Q6PH-Q6W6jY@||H`0Q1NjIGRe*YY291(@# ziM@JV_X6S{F>ppY@I9u@QQq|O%|HCn85}AyydQ5F@z3@VzB1gu(u;mfYZpvAf0EU| z-iqAul1|qO-1)D$3&@4&p4oS%*+8YPjJff2S^V=@RQ6xTFOKcU`v$_+_0En*dyan# zaV#m7bl#VUAit~TG*Wfq{Nj8&1y$35rlL#s-(sMYDj<5ITs7KvZv&*9#S(N0>zr!m zhw-@QU4chRk*}TW%K5bVK3dAk8JXY=Lbvp1Wog(lWO>ZwY2014ChIi=@rq%PO~`oe z@Y|_l#evG!9}Fwv&oJ{Isf3KZ*$R^=d?6m(0TzN|7T5i`8Vi54obQa}>L%EBzC9Zg zTd)r6{d!DuS@Ee4s2$eZKi3}hvqEQaiPRDZ1N}Vc04FH^w|TpTHdu@SSuT}d9L1S} zXc_b)i!zpZCB_rZr+miSNwLGgNTYDBI$wm95P9$uFP$3)VI(1}xc@s9$O9!afI89c z^p6IUpP=jj^idH(cS4Fb$Mg8V(86T)&#Q}r{PSS$Twm|puw=~f^QpDKW^wZKY22!4 z$C_jr>P92{($4WUXzCjz>L32R%vjCx8xr2!VPfDs;^7kV)bcE@EQT#^70}*{aCbSo zQh%`!HRkm^;^8HM-@I$@uPM`XNnAp%_bNI?k~_+7xVzWA<`H;b6Uus`0lsR^e&YzB zK&%RmJS!_}_e}q=h(M{Na`Kq+$Tx*r=VV@@;Sk$Z^?Mg!^uO=91~60p-mw#yYb+$o zJOO5pO*O~p{>tuf$pg=!^AQ)c9rx)tv;PYwQ3qwZkaioXntu=F&lTDkAsjjeS1Sp2 zoNV5MxfL=TfhFqV6(ili#b@9pjPNl-jp)i{X{YX*#)|w-t(a=fB~L8zy0^ZJADuMW zUviG0Bf{o^6#_ruBjNdbuQrIE|`vKi*4V-PGVU_Za>qCA9M(# zz3vrz*jC2%SHL31Dx|j+B@1z{eGj`dA>{Qsqqy|`VHZ0gBbw|$ni)n|<{f7<-QA~; zHwba^vvkz{Wy9`a%D2*d?C9nlx;d!ubu-j%+5g^P6^v1v3g%1CwXPx^{O1~ZH0TsW z!n8lf-O3?GsF-8?{9q&BKmfX`ArKLC?0*996lH>=&#yAFx9dBqzg=y{cuemJ!NN!@ zY|5C?Tr$LlTzH;UswMwLj+SNFNyM34mQ@R=U-EFre`*!ZIKa4p$;A*5e7D!M<2@r| zXh8%cnt8T-SlxZ@>nohEHbYY#UKr0}J7F8z(BmOWKi+;j5**@j5%wFlF=uc&ntZ@V zZ{yzJjJfL?Marln4uP!X%Y6sJ<@qD1F~3MWEt75_h!bV3x}0nKdrdtb03S2!D*qLQ z@6_dcomi6-^z24O_&3q!oJOO_lwg8rdL{LS?M zxC&6t92R^sgDUUMsm-_#IBs?SXQf-=6-34-NH(-eS94{oz}BG}9o8$+b{>jS)oMZ%I$U6XDL)8&-KDwLzxI6e=Ck3%JH_|qg;jYWa76tKiY(U{EnZ^lRd_D z8iz{Yza@xuk{E|2QjD>N*Lf{^u`zxAq}44%N`UrMAmd_ZfwcD`4=gKe4#1iH_^rRT z_wHzZ39~Qh#Q1%O4^O&4sZa~Mf;H|`&Y>hL{mAPW zpHxhM)N+MwM9c2Kwv(*>8)&qvoE|O?mB$7x9jAq>3R>-V~0-ixjN#fw18d!>R&nu1B#Uqc;s5$+CHKBUu z3|qF^BP!Cyk&L_pU$PMq{^Qw}`HJZ=jNjHlY<7;-I6)pc4}$r$6uie(uxf|4A;9b- z>F^UJgT$~(W9%MM0KOC;C{jf-8Otv356^HlgumtLG@a=o!5E;U--wgrOj*u=3OS8g z&v$-@f__rMANibPKW9{%!75-VdqhEq2E`=nV#t-yOswY@>4A5yY*&HQFCUBpjSI}e zm22mf_-vzk*6+qXk_B^eYL&#j!W#WAh5rF&hngMMzzU6Ct0PRwnM)Rbufcy!QFuI` zoP)Cx`ea!gD7QTXWv9*4$vM^WIQU}j#AVdZH4N`x?0tD^lKS!RH_K%*=>Kx2bCYbAS2cY}l*|>pdKuL*8N{V0C9_vj zYO=!bn{9Y<_;LVg7iA!KJzZ(miRm1+ACdq{Uf!;6(PTFR?G2~DA2gxIMO~qqh$(IA zTZ{R>h>1_^itFcEyT+=$(8)YmoHR7=6zH1=17RwN$cVHUKeY>1baQ*?j$|M4>vW8o zj>?hgs#{JSnNldg_cq5zDKNt4t1S@kxp>#`^UzCZ%bAjMd6Linw5}I(5|R|V*2j^5k*C@9&(TW z1GD3yp}znUBWQ1rKG63Sxh;t z%Bh;ipU0J10;Gnpt;@9RPiZ`#EUPDUnp9w}Xd*ZAuX{yn-(8dfe=Kmm+q@d&nj55+ zX0L^MNpit=Rp}q(pPTcu%AFs8fo9h z?k#+lLre(uWWx5fZsG$|HJ3Ub5K9LuJX#YJnelU4icq}zL0+eY@V*-(_wa+pMxnWx zE7G`t=I?>7)||A#ugGX{Wq_t;(PZ&tc?q**&GSWEDmroB>UKxmPCqSqvI?R=ak!zo zj$bK`KB=w-^Iq}}1L1E7V&Dnf%?A=At$c&H!8{sd)y+49Q%0{XqRAAO)69L0#;=7= zOzi~M9#eGE;jCAmi2KXnW~N!S7+gJd$L^VT+L>LSxAd<4i7b>1?FGj{Lz}RCK1;1? zR(sQSX$>-sT($kbpd+4z7GX3SbRa7G+~D`kA`cx_tocjoyw~?UVs%a@IV{p( z2^5FP?Zv2mXO_sLh_W{RadC_I{jXD6sCM6`?p&zht7EHVM8LW~nXbvHQa-2DUQsgW zIf7;j%`3R#3uk7z1K79Y!_5vm3r~0ZO9}@5>CtKJ+Tl}gX<4UK#LW{vxfm#Gj?A%0G!d8093cOO0Wu+k|mmZ0;={f?jgR(xGL zPlYyLi)PYtCZl2uT^yt1aEL;X?z@ow-Vn^Clbv8oaKoD?l+8opMS;9HGjEZ}>j zxVC)c`r9_Qqk|-%{jppnUrlZ1K;67@mh0rQdx)`tkA)-vr78Nq{HX{FYTPZ$!OS7S z1nk=R(v(?aF8`2Bjn8X+?iD6FSHeVRN_I;WHdM(}gXOiVdc|)}HNp!Af?(asd}V=# zklWta*_mxc;@9sj> zp-;Tx#dPd-weL&Rpp9y`baY;n#n#SPmUpos;PYdwuJY9dO{7Y#?t6ob0cMAvhdb-_EX|QX)T;OEjW>L~OcGxUBNT;{JC3dLIg4gubBAQ=)=od15n+ zQ>}}Eha7@Al_VW|z^5Y#rcu2cn09>3SXuSU%{(zWqv1tGD%1+JXiAO7(>$`?)~EKd zx6$);(Y zY1>dRwk9Dtzkbj7zHPRnw3Ww?!N4Tr8%bgp3e61(Xc$?NS1Usb;iM&;W8{9!$-B-4Zyz#Fy zhBMn-*}wP4)M)D<7hCNgkc*D7WC+d&)|=@~iAIacFACTDU%Qgq{-+P*Dl(uwp>>_g zU2nqF7%MKYX}5|z&qSMo*^9!+u$j6Q!@ADO?3uem=_dsyKq_Xf=iKQxg$P^^!)YJ6 zC(=(ik0goP_Z009L5nWd0IZLKJJJI0_F0AdJej-$A%GD z_|<1eZql@Hy}cix%|;a?9)(A0GHLHzG5!ltTJ(Cv8a4JOed#WDwyc*h=%q_7^OO7U z-*T|w?u7Ie3&up@S?O9;SIk?>$?Jo;6xmOJYhn7MKH;sU6z$U7UfO+Y1JQBHY~oA5 zlAIkd@?FBZYxhmZIp0H7cxCHq-dG)JSH}iycg~ZDXF|w6dbY};zsk4RDK$7=NTPeO z7s0{Yf?gkQ25GBJb~&a#B3q^eYAFo3;ArQYEpgFz(=XKo2pFl~bbuy57@V1{nifkq z5I!%$Xwe&pAH0maa1cab#Qq87fw`U;%V7gwhf$m0a}h(*AFpRuPj1b@jFy?!!PYA7@a;%RrK#rh-*qFItC77xz`YX1Je9eTLQiOCP6-#|>iCQ2Vy!J&7A@5g7 zxFnowusWBh(d+1&nNiD9jaX?p6Z&s3^F1D3&*NRf)cZ(m2)fUkv|%vTcf+njwxao~ zcOg3}W5f!i7Fu%5X~RG(yIO0W~=;~6wC>3w1+G9-}#~+?Ip>) zXv|tT!FYW}92Isj%?iEyUfBl+tcN>awUed3PnIN$DJPe`91xxi}K9TkrKt`1YAhaSO@c42s9x31+lsn*9TX2?dvP-1lz(_;iUs;l!1we z)#0whl#qX%=zVGLbYNR2>RD=#L3rUYKw68E2yLAx*K?|0t zL{T0*1p^r%-Cbgwz**D_4j-6J8v;AIOW()kvoie`8d8s@XskG=tN%<{mt}QqEjIHn z`|$^8xaeNqF=(cILUp3q2eBmbPi1VfaB&9qJ>93)|&0H1tF(V^>=qZnV$CadxgBGGlW8&b57WX0sJ5#EE%7ZR*8gl|JUo4Sc z@Hc!Sry@pJGTsX|)O{w>*h4yO2uiU+;T!9iR5opr=;2F&cLHeDa4|YDI&Ps=?3!SN z+62r$fbAQAW!ZRdp@Q^Zd)$f9`>{_e0k$3VLI;<6$!);V-iTlN1W+7pztJ>8%URN- z$p4KDfn+pub8$@eHJwFbB2H6kK+QITi|)igR8Wcx<&!~#>sh*4+jYU%B_5(cuq}Zl z0Iz`Qo@W@915+}sl&G@b^#Dx-QgS_}vQT;*%34ZqcU|V|r=)(NJPy1{ct!ZvXmENt z8Ku8%wIqbmb#>5EMe3GW)Bd#IrR|jsaSq+kFp&K|HQN$QgJzKvR!F;%brtBIR<5Pm z=T`IrUp5!<9q8F5BiC%Cm$)Oz1;%CjQOL{*wMMM6Y2ht%Yv7cNRYa+7(ZwEw;$q)q zl4Ye1JQbzT<Y;xBl zYpp!dkwiuBld*rYh!y|hE|$OoPKF0*Kex4fy#4cli{Nj(2D^g5SJgByH53{V3pvKWZDR$y00der%01=eOn~k+7wF`={lUZY}4|EVjgO`$pUkt_MGKcZSKm=4UO> z?A8zyhQjG}jDO%3M-4#wd4C7PR%2KOr%GqcKN}0?J#o)(+*#aiSQlBPt&m#{Q!(0(-G2F z9q&dkDVtai6?zu756z$9Ca%7-Y?$~ETFVN=U0-}(;2~rF5&!7h(0>tMAPoE0Ie{Ouv4@W6)Eht1fZ4<|GijeNZGvV-( zNZMg1jChL|&T1V!@WyFPNeNwP88))DPVDvP!x85l`xW(*xc%d$Yc?8Ex}T3*>`txI zE#H79apl-;&Kj@GdV8jxP~3r6t4={kNPHp?xCHY=V7jc)VJ#Q5D^hPxCa0+PJ2Pxl z;BeHGMBxmHaNzE!@&zEvUl}$|c}}%@Ji0RZ=AI@EZFD<@5QXDk1jlQoQY1`ZP}Ymc z&~NxIu-?=Dm+FPGD(#yMu%m~kQ0;sZW5dIqWd8!oZ2b- zCe%&D!!bBkz$i(k^e5uaEoY3PhAG9RSFiF{-UuNFFyrBcj;((@nn)OjHEEG*6r(wl z(o*Xlg+fTY})%(J7@2PMU=EwY|E)jXt=$sar}KXRf&YBj@Od$ zCZDaDF5FmmO;H@Fg;c~tvZm$AtPp>7l$eEc_0(P*f`voy*EhJUy0+lt1WHwm42UWpA>{a?`}y`I~< zj`Gfq!F-(_J_m`czF1z!5HL4^Zn6}njH1TQ}Q_1k4zrj=@&=i*nSdG3K!uvgpa4wDBMG-7w zIjX>a6l|u=VAz%@M-}#-F?eUgGA^ew$<=Z`RIEp0m&m{c*W=OpXJh=hhO&~K_6ekj zWB-BdQ${WSN+#Nma&7bn4_nX$qvy**P5rxA!bS$Tt;D!mTC$#0mjCeI< zido$UKX_!B)j3Dhqzw0gE(K_j2<2HwRvYQt=MQW;B}S+XNpOWvJW!Q=T~d4XNLfOW zQ4X>BY5o#Za5sFp3k|DgaCk%dr zUoCc6$YcT^e8LXhShp;b;kg6mN-`wG9kV-+5qr+yVD(dp4el>zBd@X%W6n3Edu@;- z<}hPICP>9t+?z=XPj+LQEf&;l<+pAJ#*loE4vsBWG#hiWsuG#0$32SO3O>vC;vr4l z$S4!E>k`Az?zA(h$S@3_ssA?DS18@^|5rejG!$C$t69^s+KDJjj_QM~EQ$`j^`e@| zk~{rU_ZeCw#+b61SNwo}k&}L!Cxb!aozOy_q@6Jfh2_W1h2u1O0!>d=O4@|dz^IQojmttB^$Euk-@C?TzW`MA`_)yaJu`A=MZR~bX* z;;~jnJwc8_yUCvMw;cRZBH}nDRY6DT8{&h?3WbF}B587xG6}>luIvV6va6HDO_0i0 zbQuusNK>MHTNLUvQTn*=J2)Y#Iz`D6I;DqUm~YismE>(=aC^{qA><)1j>vtT)ve;E zAn#B0@Pf~R8YpD4Mx3U;BFaRz)<|+Cz1O8#HxqQujl$)6D+;vAs_iBV{P@4N5iv3n zG@i|OF|QaberY6Int1YHC;X}%CMi4C2v5u_;oxOz0kvb@8eMa%S-vJn7aw27OHqUm zZf5;mSWc)5WbZ;VpVtYa@McQ;ZG^P(F&cl%tA{(3{@0(FAN-Ty_oi%cI!QygDZBjS z#mWAo=VIx7GGTjI^vDM^g?nU471#z1J?G2g&&F_u7|}iaq}8K}PIffY7Fo~Cid9sN z%CX{cSY~#Kk>dwJ z8p8x)u$6_8gUC>OMT|vxbQiteMeHVgBc9)cI@^6JMs$?q6uYT3Uz5APPZx~N!Cj`^ zV$UdzG3dhmy6^X@ionjf*VU?z!vGlDHBo)pvg(ZjfxoV7#giLc~?wX*e-G9_cm_UYrQTJ zL=O3W8YdRM_Hm6k`A35t$}2T>^@P#%kA0b$Z~5F0e-6G2_^$t@Q#aC517;lP{}u7Y z4Vpahu$abe@`yNn7mskAcdMwWDFwsS4EpWKfDydO^XI;u*N-}w%%o98NLr2PBnA5z z#}=jBbkmx6i|&H))}P~Cl7)MAuBn7KskFWTMwW)5I=)@R&rY}fSWPjj_=BAr@-XIt zjA~e)22%1=Ih_`>izRb}?rdnfqvJ;8emYfdQ{9XCbGZo7NEm(<8T8WaA3GHr>}kl1 zjO^UE&+G;;A*q;}HrSZ+oMtS&46Hio&O3#zp)w=w$%@-jJ1F6U_g!!4}4l;9e+Iw$6lmZFvuxd4uhVu>3~1xtFo zW};Bfha`R{pacHCQNG+bog3 z0{h#!P}Qgt!Rxc_ESGTjHb0W){vFWN0x~f~g2p`bnl*n<0y>8e>r0%r5ol$G?e3ki zk2qqcNFqEx2chzXCn@knkO+F;JW>NGhreL?eCO%@WCyG>ogL@|&5`I}z$@#f;#Agm zF8b4t4-`KN(JW$#`g|6=1S%tgaCo_r1?$dW*M!|+^*bh}fR^q{%*Vuf;X;SCj**;u zuFmHbo_5#0o;TdfA`IJ({WzLXs*GPu;APgg+NB2ztV-q(2}F4yYzw6$7MGT00^Da( z2D^EP%vt#(b1=Z>#>EDb6XA-TT0BbGiyuheF*9HLSscmX>(;8+e3n=)PwA>$2DNc**LW28a#UUfY^6b+2}C&z8bx zM_|X5wqf*{IH?R-2gmpCepKebA9dnqy4AM7Iba#Dslag1MU4={99N~;3h7ImGq`zl z`1M)Kl_K3$M8=AUT0y-Me%Se+UJSOo=OLc_=!Lu(uMv4L={AL~}RhRM?_+Wu5}k>gqVKDAlgPuTbxH4y=#n3#28L#Fl2Ht|8OqgZk}wtR{v^L4S9x#!o}r9hThiWV3#)LDaD-;Tfc)o=%JJ1E(+ zlbvw$NGetKa)CsGR}IIsj>i%wMNN+i47eyZuBqc|;*#H)cn#~uBgbw<7Z*sY_W)%{ zszjyFxLzJvE4t%eS>14=6KIvmQSqMreD4KLj?5iLtN*{1e0wV;XUaut(Ywvv%fd%B z|5Z!m*>%0{$8R-+LXzX>K(%k+D}!;p77y;PoP*d+(h{iLlaW<{eh=j~7YnU3aat)U0Rf-j1|sm@+zpNJfBUYj z=iv_zJb*qjR1kE-*3>(7Apx8tqCv8(Q59gszz(P6%_-j!0wu4g1F3T?o2Yw1i+BXm^ zCg%-r^}P7DyE@u@#FFB-js~`y|7h9RonWC$#BMsS2O><>=o14h6Shu6Hwleakx4y1 z1o7_rL~*`mWp}y^?IZR@JzM524qAHZea{@06U0oHe*xEU&&B&r$j z)J_u|wSVgGb)E`ggP8X%nDUZ>(ZLN%7&7-GloaWv+>+Y2sVuLOnV`aUR(XSZ%nHVHT*)wl`H1GIDYgNQcMWqaPB@STnjpn2F1$= zmhOGg3Gce=hG6lzOQ7!Y=GmY zCks$n3?>RIZ<+7bCL6XDWZB9x`w6%sn{CD^y56@;Xemmm3vSc3u;; z%O&?xBltl0p)HZgv5;~DsgWu1?La*ik8lUUX4%bJ{#DKqzly1)lG?yn>HFQKb-@u+ zwgT9qhaW4sb=8d?!*h@rqkj2`Oj<7cIV4%x;vj2yo%>wGEWHAtG@{|oL>M`k2W#?&GqW%zJtv(<8We$OX$K=;7m+|=ae zdb|ubSg-xV+l@l~%L#TJgSCwf0k=I7$h)@y>h*QblW68bmvRT|j&z$b?Yx-|<9e;E=_ignmsO9roGGe9O+9t^-+)!ubgM_=`+1oZ;ri z310Vt_V!F5BJ^~g_k3gJky5dOH+1u-9qxINj_4gsgtEVb^4H!dnywMA<@mp-u~CDY zdM=}!PWAk_9#kOA&8809LWkV_&Irx z8lN{Lh3_!l`Tm0evt!M|*R4ifYIP)@xnT#Hm-*|sa=2{0W4C`xl#Ae=RezyOw;|I$ zoTRsJz|Hrc;w*(gR;gu+_AOn|bZCjFx0hb^zs(4NkdTSvi&$vGWb7AU6MiA1(i(1g zY(j#kUnJivt^Q8mt%qt%EbOt zv-9~~^sYBkY8|@D`htQ~O?yok;}HwHYF!P@`1y%?8v6GTNUqs%8mrA514#DyUy}%g zgMHNLLnHy5()~Aghw%krb;C^G>VVuIV6jsY4VnX6rsvoVr>3b1RjjJ4tn3jCIcTS; z?!n%g^n=CU;6aQ;gnbi~-@e;HO4=6mBKD(Q69YX(e@QZ^m9Z=owuaHb-Y=CEHf;mTN}3?0MAMo+bKL);Gcv9U z9DW0_mq9xBezI35-QSLQP>0ka-G(*s@6kdiu{qUgE3r`3k-{p|`t7n6l8PlrIa)X!Goox)o+q@RAhkuQd%~EiBU%GLBSkgfHi5<90L*>y8`f|Ux7v+l<78c z`?fs1Fm|zP=$*R7D_WkH;;h&^Js;7qC}oN|`D263wEcZ#-rR2pB1y!&?SI4l5aHS$ z0`u~!1Q@&EPiWZ>z#P!Cn}3&Mz}-xg9bmJXD#qmWRzorn(nLV6c@fy4D;`y6FMu?Mu!$mK_)E%I&DexM%Kyf$esDER8{YwfvNpfHb`*%*W|Xm!2MUCY`zE6a zxnscuSjewy=uWoFxL_LK_zS!CWVztw>}{_i;+#)5(U89QHJj4ifbQrw;N5x2Yc_pO1nPMcn+CxdpP9fek0S8;X|!v_g&ZVR5ri)p9|`~D1G z+kRyg#O$j|B-qccvz#Ocv_?gs{lP2|OEd(I(9OI zq^Od>+dfxo7?+%=xB4E}Mdov3aq?ehZRDEQ>c3LW5@bq%V{5IErAk6dxm422Vq79m z8{TLKSwg7Epv>1L%j$$waiP4eZiIKHGv!GHEFTTMFLuG-eAkz5#QR$|wE)Uv4I(oH zU`xLin`}PZ2TT=9Yo+3tDPpnwcj zPJ$BemK)%F;H@%FH*W)BXJ==@HSp!2294PbL#*%=+pv+nz@vGA^%~AnfRsEwa6yAYiUavp&eft zlTgWwj;FrRPE5Ciiy=#1VqzVC53>vTbOoob^6C|hn6z^>oQ@O)>>jn@w9Y22s+v5C z^w?!rs@CbN8hD&nEVOC+`^^`tHq5xp5AkwX*aCRPVp4%hA7NN&r3Z+NQ^lhW$Zb)` zMsy0As1l@ZTy|3LuZmne)uQp~-X2dZ*+);i}1Yi_Z9Z62+F$?tX@yzZSl53F?H!_c6 zD@}pb5J!XSqvXh=CD%!9c35;Rbx~IMAVY%`hSmveObmM7)@#PL_kh6#aF2N)RGEf? zu`<}qklXZPh5YUu=d>+Y?G`x{gldl>V_6jN$9T7rReRQ9&RaHe$tx>S zW_BTnz?1+y-8++k>_HG7L6yPjY@|RBAfBcaVGZz$F(Chd93PIf-ngj{L{?`?JJ;vR z3oKR0j^}=x27=yzvePR<3bclaUC~Vjq6@&(3s6DDWzgyZh=>f~03o2`2F!4L-N=^n zYXY|5hB3Y<`<3=TJg86^b0|Rr(0hRFUIy+Rj0#kz7DRIV^M(O}KfEtDzBG)1053G} z;V<=fKm{c861JY90TxX9`gmnhr@`?Lq^9%98<*L@cCjMzAirX0-&p%IxQtZ56-kOE z_5k(#e?XtXX>jcr`QGhMCA%`m(Hh!pu??Zfr>JG%}F)AxT6s3!za^)|rW1kjT{V^-%jBEZ<$&UP6q z0F^562`IK4ihltx;h{P|(Qnq))=-X%w4EIz7M0w3_Ww!;9?N)IFbUC$PSRP zASPNe2MU;*kR2dD3YE5oZ;R*?~wTKF;_1pj8^ ze(hNZunPyzu>lhWre)jm!>x!X<8{^>3oAR*StL_-Q|G{lfV~7O3CS72FvO zOtItT?0mcecTsZKfmfrA<{-#oKsv17wpW^U3na6gj8XF|Y&3^GI(<-k!`{5lkojud zj|>&vgED47tBr>#YdYrPI^dZbT>j=sz%!`$GIiV-FE&?i?pFm2fjj$%iLiB|?{tFo z=+TOb5>sLDH;JY>#7;Nc=NKRU4M`se1O~Ab1u!ain}n}`JdPT8BI0o*f}lC+3qW2_ z^fK8Pf(}F-V}8?* zy8*TFYBFmNI0AuX2+TM=>BKrM1Fho&@8ANtn2ftkQqw?qM@-y2tTD1aPOnRnB|Quq z=&41hCk4L8S1NVQjQ@y4sF-#5CyvkJ|7QVQ_^{dV`3J(s-8@ITIz(lHk3KH3qGOhD zl2ED27pzZBWS6o|L)@}-&e~q9pkP)|^dxFQic8|bZcT!g`!gFqAX755N*W!^Vwv zATS>%Xv)**H7b3w8>8oX`jxWg0PyU|stSL=LD>PsD|3+H_HAxI=I}U?gC)srVDGk} z|9p4gz#!c~TwVz4rX`d|ogfwL6JzP>O_c=BTq39*-7QSs)djXQBn{o{@kUu!eSE{U zCymu;Gmlz={o9sHj$g+=K-9GDpdA|V81)1rfy0?RqS@5c6pio10jhbXY#5c@uqK)^ zdlr5)xl84kNB_zg7focP)B$T!K8;UZj{rcI383D z8%$YA(4`myb(?Ij?Tm%y(GWa?p8yc{0n`8PVo%`t;T)JHUb!r;7~j?M=EY_^Ss*13 zG>|<7?6di>4+_CxuhJ;GKoE;y&50Zm;`92`=jd1cu62Ch(_Hz8OO0zQSogMMr4g8I zU6?G?GQqp_M~5IAdds#qqL{ZErxiBOhMB2TWKyDYkm(kvl$CQF)bXj?wKZ`Wb--Q; z5hxS79xbMT;f0WHiNL!nP%1M11>pPSwy*a;?LU zK*$$gQ{OE`@d9^PQ_ufav2Fb`iqbEWtzWZJNFa`6ktA~B@w-3n1z~K~gm(6@8MGw3 zH*2E^%!t|U#D}4s2Ga&!(A7|)`D?Y-A^3bQ{M-vD`&L#~Ml0=2TB)P;gRvj~{!9R& z|G4LFK?Km>cpPC?LAJJDbo_0W#_IX+;~D|OZ$M1mov&r~xmZpD%tSoUij5aT-9Bcq z9QE*I)@^=Wjq@RtNbZ~aC5D+-z0hqi5>`6L9!BO{LMy|y+#oc*bW=7|DWbtb0Q`mC z1@$V85I_4RF2LlXN;7;?1+8_Uxi@gGhsyi+V=Mr@#R^_&fu!9=3lyWu3!Ck-KA;jn zfTvrqYazKf{(<)bNOBYby<8xQ;Q~w@sGz2E*}WBMWd__Y2MGYv2At@0mOsq>`T7Bj zjM&IhApERwc)HnCtXuQHcb<}_J_Vp{P9jy&dq6;S+;9p5?Z+h0^x5FyYRgu(%5$BP zE}kBRB7ThwGcKN)()65>@bq2DQq6$i$XkYCa3xp-d>807Kdzo3m+jyIgUW$cYX28) z_6z|p-j7$bz_}Rrg=4hV4EIG~dEIWM)!PvcC(g{wI1Uh$ffN*^N}O-8!dn5c(q&pc zz&y|I?%!S+z0(Tp{W1TiUR1(g009iL!2{?$&@%MhohX1-R={pq-ATtw$c{h29ldXw z9+nQAL%aw#D&u_n;Lp%e8}`nL2Piw@Lwi&Sk|an64fCKNJ*%$iv}jcXot_3rB10m=|INrdPZcg5Ohqk&xB{_I^++SRJjMBx*gRKJi5Cxr#3lx$Tj)e6b6Zy zQC-8pnt~^7$BQCK8hBV~+>}~pW}-J8XvdqMUZUoMm<0Ong@#@-=rpE7YXN}%DQu*X z+SRe*db66%CTxK&hW^Un_=krBs9HnCCR8_k7I7c<4JEU0S7x1&e%)g1^$^@5?D4$ksISl&a0vmy+ytO9g#%Wm3v9V>BD?M4DCt zCS_S_C+W26)*%-*NSG+FU!GS)IE;zjG@oWEs)yMm?UZ0naaM038 zJ`gCuIsPR3fPY9|5X>wOB<=%WqJ5m#|8{oRki-kbD|%QlW*CmvI)(A{9AN}~j$=1@ zpwAg9Ujcx6LSHxud7J>A*BKs-M9$3(2gL*U?ZGSt1ijDzP zINQ?lFO)qA5P=|C)=>~R6$AdKw>IFhb0kmr9cY(RY(x{Be?a9wWU;oc)NXUeNurWB z-sp?aO6_xtvGxTx@J-;V%s`|Ira>cd=%EGzn(RoIs0>t?ArTFd=Z&*k&kSYz-|b!O zHE^GLW{17Y{RxsDD8i9z z-d!%lSGoXM1grN&%ifBQ1As5$9=D&rgG#CT-tCnY{K!%$v(jps+rL;qf&qTj z&qGMY>ot)lDQL5?cRU_Bu=-q8fev2+nBgeK_B4PQtS1ogL{Fj?9!6o71I>a4&r|Y^ zQ+$B)?|+yN$Qb~SzRU3G3~zhXC(V?t`%l=r?vk|FEdU;O07-DkNcO1=l9RTkrXqTchfg;a4S-H(4hi<3E>_P%>czq>lRIdTa2u+-eN?7nm~lFbW+Ux$osn8XRD z)|)VQI}r`U<#k0dD^R8lPlDM4XilZTXGM`p*?RbO7&J?-rW3M!T!_Me{6P$q(XDCV zm6Do@3l)Vzj1AsrSI!f3!4z4x$paaSsK_p&8?5{4_PX z{zfb2dgm?i!zN8;=vgZT@`~gMeJ(hcmzS^K3B8_o%MiThbZ&!4^J3AM>m!B3lwbo? zCLNyP!%K}?gVE}iSZIC)eqtbpI{+f9E=B({EU0u1G{bv(JPA18uWI1kPRF=7UdaM9 z{xF*kvJ;a&`Y`=}dV{3(lM*m-7rPHXJT%cF_KWY}M(&H|S+s?g*6X~?m~ zOz1bcBs%HOo22m_0$yhdU~EZ%7y%Cuo;}id`9u%;XviJNwnqdO{L~Xs*0o~f>Xkpw z<_DcEOyIH*rX5Q2VvzO0*ZOTe&#$ys!v&i0QeYyzBBv0hM|m(8!F$Q1<8p|?V62e+ zJ*P}*f^w5ipx<2z5g!4bB8m0`4T0 z$X+}7K3)bG&y*`eZ78^}NzlM6@A1$XmDdyzxJ>wv&yq}On{~aYn;xC_jL@(S%o3G> z+ETXnKJ9D#I_4sY81fu@^U1=H!+J@@#yJKsH~{*86yMW!zZ1A^fb1d!h^IIK>^G~^ z#)p}?xx16NVXHE~u-U5lvxoC>ml4+YJtwp!3ubBht)Q3#E(j`v0_7qDR<4=?&!1Og zJe#i#d{6u9N_T;_D^9(*Wy8Gj$ytT*UmX6F}9xsu9RfhV#>8vJDHE`g3 z(s(w)--BAVgsRedwZy&khX+C?#K16mqR=HLQkHwAOYivMRL8{d^hMc!aB&} zb!KYd@5M)*Oo=2h>usk{1HDOW-040!@8gE)UBMh zZUqg6-o&?p4B`MLs`Ia`Qi`9?RzvOQ-P+n(g`^K(`N_su7)(%nFUv5JdF3Sm(TQOi z#tOhR+0f4F7Ofvv;rjy~*JfMsqE94{$;n#F34lgK4t<;%$1R_r3No5sSqj@x$$j zuH}L&Z!IdON5?#0-PB`De&0t;8!2gQu){6Pxsib zTI;i&E?hUKs;iMXQnvQMzA&s0kf{6H@9 z))M4UO!}({cWp$rE3ys8*WMOpLRa@=1iW;xu}qG|aC(^OwhZAD zOClfTGrPgGhfr}Ac=`)FhE5`V;p)dW_T%SE7_d==VfjWU_q@$i)f2Gl(zdBC5FcQ8 zc`Li6B`!cxGdsb0ZxM}9R=~FZFuUio#fmv1F*ErzhcO z;v`?u{V7Vv9D=!DBoJ{3rjAaHqknahW)}#Cu1J6E{XK9o#7*c^lsHoXaomul3y(+NgC|gY@@xr!IfVB`* zgz!eUu!Ul$8q%6UKvd`x|MP@?^@&iEyJau)?A?#DhN9CFuc7vZoS+!X&dB|Y-5Ays zRsNt0bi3jAQMN|#Ag;KLs=^`b`MtH(dE;oP!ERHANPxmCZHUFN7=@;HTDptC<-8!L zV+C>A%;3~IX}*WjtzhjR3OO-%I)i1NI)YM#)w5w{+wm_VaVv#r-0{!mTC{#o5nr9& z^Y-IhvEO2xI#TB7JgTu{y6DdJYUPzDElRaLEAp+IS8PEIBkGh+f3(B;*=uuFSB3n1 zwUdbGZn<(lr%oeY*g37L4s&I4*ElMwQqET!nw7k@M*@QZDV~4yXbfEzj zI@vVAYSvehr`TEhICPbUF9K@YLL_cm{JVMAqUrwh--?rp00VH25eMQ>GEJZN4* zp2tV(pGAkG^Fc{(Oh(jjAPK7GR>LCeG|(qb?fyI4*?JF{YOy{J^9R<3GddyG7J(a} z0o0c>m^VgPFxm`94kci;2#g2wWB6pThNZ3p5Q80*ZaPJOBHijCa0Y$tXbDE%FBM8% zatH|tL3Gc1v2blRaIngy`s9tmE|0@We+m~e|9}D_1^8~uz9beXCP82Oz6#4u=+C}i zVp7K|=oLn?@?g%3_`~{Bm;g};nol^vZ1D9ZT-Ui5M}=?pAvuSpO9&ST_?=aMG6%%6 zw;i55k;f7SsjLAajLyH?)gzMcy?+=XBUF&VRA`IPA6@_{g*3q1f#MbaKx$y4%;g@) z&#gh1@Y7b^uSl2d`)rcn2uOtPavo3^^ni@?5aQ{B4&1wZo@;}Ac+Ef3x5`^Tz}G$D z#T@{+wby!m-WoK0EUrZBO#(;1Afja=s~4Vjg3dut3cJayeShQ^l> zbTpRC5rJphhEP0E#g{nJDo}qb`#J55eN4ox4P-F)sVC*Bd;1mW6*Q%7IL7*xyK&FjyQM5fvt-~w4+osQuNGMu6i5?TH&;*N z3xz>T9Hu_WIy8P8*?<>FUhJW@u2s{1ipftF-#;b3C1JxZhc6kvUw4I}W(?W0lp@aS zs&7g7{hHdw?Da?ufg!7`jHl0rvjpXLBX8N(J==Z~@k#dHJeI!oLu5CDn3uRZU;s~r zJNagJ82OSJYH*=Mu^P}hwNKi>XXuFFocmrU-# zM~;^5$oBcf3-r6H;C}GHM^G7K)=*TYtzdiiQ6tAVL z8@AP$9Z1*glbf9S%M%0c!#>NGwEd9Z_k?598dX|}>_3kQMZ2k^tJ^u_xo}VQ-UXJ@ zm!y4i+IYCJEusE&&wiK&Q4GAjKNo3i`9i)?@GvkDFpO8o5eabtU8xYV%%JwQU2Ae1 zI7pW!IbwZoV!LX#A@lz%zEf$X5`y&(m6dEh|lu}IG6+n+OI z`wmFA5`DQ$Goxjid3YR&_DSBVla&gA7>qOUucoF3KAHv?Q|JiN7E*!mpsD)lEUfs- zg&^Cn=Cj2LEg?#u%avtNB8gtbl1C232NnW)o0h zRZCNT+=(*3Eh5Z%Q8~t5BPgRk&GtiE^cJdCkk(5Ej?dWR2fZ1iab1)8WXhdGOOToWN_Ng7|!U;wFOyM2^{R$f%^8 zmX;5}FpLlsdUqaPwvjLi1FdIl&Z$4Lc_`6sZlXAaHs9Hf>L18V;aWADT+Ir@gu@9B zhGwH9!BWC_fDimCs67Av{kucLcJK0|j--RqBbhq^b}zoLsB$gXmvx@kaC335V=nqq zkD${W$sM%}`n-}{{}j0ib#ST0Ux_x6OPS-Sdn550PXI#bPVDC*@bA`_{u?&rCpK3+fG+0k>r z%udafnHM<3yFJ~)wp(CetrDsgsXjJ+K>My#u$8ovhi@Jg%-&^G0YitG;iV#EonR_$?-HVzAuZf>G#~Dy(g3Un1f-UDl%SZ zNM!weC)~!dE;9jJCJTUU?eBHI26gL>miPk1RPwDsfdIyE?lzx%*cB(>Hmy%+z5Ig@ zRIoh^seB2ON?${g0v9}7Sc@`LR8;@>{K9l&c#EI!c(M|bb@k1{t zfy=@C?5mfM97k?x2>x;A!#_9^&sB==@iyB5PtmB#=;&x0R1T)(7xmf*u{A=ncnIwpL9{BsiL^31r8kk;3wU8~MZk1nT8+05a0W>>iu@OV~=C-OB@&z|r|q}{Fme-_Pp4v>6RJ(oVqeuZH`nA$S%$^IG&f6=^daI!(Ps&` zDqvOARLCn6c>VJ2@umAXiK_q*ua^R4r9j40qsqgBm_0x8qY&6lJ=h za`lja1rPbY?u-`>hj8kRaGr*-Ui1s6ouB9i4TiNwhlQHKZ*?DvM8F+44h|R)t2$wD z_~vM~gr?LnH_W*62#;}_i{sC9gu4-DzTs_yK7EUVFQiD<;I%R{T=wmTsW!`<#E#C+ z7qi@KI;v20jr%d$66gVhp`B@NTLCIs{+qbSydd0{{BaozpZ_>Em z(3H9qL=_$Ee!A^$(k+m)+)1QmwRFQyo z16Ka&tVa3@DiBd!R`@V!=@*Ru#!?AnKtBtZ-^K{vb2@Qt_(Q;RPwT^nA2!zPKeD~e z-rS_C&kVz-8vKt?sd}rd2`K4nQqd{|w=AC0vTrWoM64PqV?Z;<2r^X7Q_g2D zKlq9?M&swdTAcAauhHtU7;mdJCyqS$r(x_#r68bAl5rVQxZnM&fID4Ks$&E_>tdg) zM$v(q9G8PT{0++3iz;Tr`>rxf5nVd0rA?`<12p{vQ?k=p(%81DMn?E{R zt$%ilF6t=j*5W3}5_nqGKc-@{Tk0Cz;V}7{D=xR(OY>5mF>ZaWug*q;%xFZxpZ7lM z=5Avi)_=Dr$fp-_-WDxBZY2%{&J@nRPh^AZGGyHrX28j^Fy_K}Ayw?!v2gx; zX`b`lP!i}sfI}&_;4*X^M5=N;af-d2Q(2h^B$_N%JEZwCy1GnJ@~<9o0{D*{Qu2(1 zWg4N)blX3d_n#XMNYK|4W9u$Ia*F*k_3Q8~!N1SW^VhpW%Nx=O*f-m%Vt8oK>u#Pm z0_VDO3}-M%koYdw8_f^y-$xB+dvuP>_~f5N#>tNl`T@uDUm zMLtxvJt0e*W%cjDl_HZQn$=LA$ZUYOHU_nvh4jA_IqbZOOT6cFrFz(J4cp(`LXlMo z=i03FygOT?(C?Y>spQ{>``~mw`#c@4h<$scL(_;oVLGi>$4Sa8+nOYJQK^D@J@GH? zLsc$rhDvd2o1_3qncW@#!};0C1dmb6q1lnlrZ8XD%m+^3S2@%(kGv7vZ3U4lw7Wn! zHkcBI|J_XHm#4G?xf%|ZxTfU{8L%^m$D^QtRT4Cl(f^LbL$ z5ILOTvj$g>4Ln@#$6wOOA3qNTe1O?Y@up3v#~_*7q8p zOb+#}`)>11Na+Bil{*~Rv1_2qfwu#GQ8ok3Y;n;p@8(S1-SXzY#%cdt;BPID>Rn8- zU(sQmqb}YKK1BnTXK-2@+>t~N%D?c71;pn*Tz~ZGl_@Xvr^=jNUm)p`*9FQbqT0hm zLQkkCBF;K@C@v#_Js#@q7a*tx^0W5HP++HDRJOBPKRh7|cHp`%1BLatHr2_uHA0TC%!_{5g)lCOyguGVx% zKDI&6$Qc6#%BK=}!x3RWFQ0_}O_(m9YM?AHBS4+0{Ayz?yKcozW({rVkPjR(h#0tb z@7WDf1f6!?ai4Z9_;COtRo`{B&%B4ux0CDOAUnGO@A|M@T6*`F^vZ5H_aBwWSTpLA z_}{%ZHZ|~I!NE#U`}L{7lNdAY(GX119`StwyR z3$tSJ0=@G)+k-I8o@3X*_oXa@ajkRIiZTuk4u#lX{x1wF|K%MwXyt2&>uAvvEW+$- zCbswPsL(j*(W6H?dU^`h)=ZIv(vy0*tY~GA`RAwy^oSl6O^D-u1Ehb_RXhU@f;+-L zyfgm&^YfWiXD<@e$YVeMl2`QLZt>w873*SUO3&uM)yu3{Z^YM)-?_omFg)t}Nd4aU zbN?#1zE;zn$}9)dq756pD53s*MBLUtEGF~sxXgehXun)-5~H^C24=PjjiauC!Alph z@Xk+p()0v-BV2TKm-6)mj=XJUYvGV9R(WsT$Hb>;0~Wy$uonb1s!Ri{(o<)ciBf)H zA^kZ7lJEo&4EHrGFQ|(4B2;cp&d&CCVNB2{SK8gxTFQ*ur-H6C=vi;DnEJgQ%H zp~eOduET60$uG2X74wh87zE!;mH$v_XlRJ?cQexX@i4B*-s$(R!`L^?naZx#`7RHS z0s|J?P0*l{|I%Zn#{+6f2)Z<0Zhi-{jLH&W=oPsL6@O&@swDe2urnMuyz$EH;zRO(&{G z?YQI{m>6aL{$xVcl&%>LL$q5&ziwb;s-e`no3b=MwHq^a*Q?KJc-hM3zCW{gC`UeY zzI%svJ5TJBzEa0EVcgS(vG6TbdS?C69->&RPSkAGX~WdEF|XxD`W*+}B00Hw&r}}g zJf;d7y*m;-62wd-^b|N2=yHo8E=KZ9v0X#LI>PiWsEwgDJc8MT_?Xz6qK(!>-&mCK zio_`5=sU3purRT1$_b}NQAQ=a%aEb}Xsw_T&gvexA~SQlUAjL{(#Jth6L5;DA2@^}z|e6_V1y1UJ*o_y!bMCT1(e8p9kW!6+1bLX0F zb#?XXil2!othl^|ih~6_fcJZ8eP|G-9U>fg)jn8?2|88&Cyx>6<<+ZK$V>VOQv#7A zE7jIAofQ%V`>zQMh)n~=n7WTCScARxE-bSWqeIfnpnsGwghGSirg5sm!-x0?=K}07 z6Aw=;?4Ug;4LA5dyZD@DNzt-A$9HR3bc-9JPkEry0Sd7i%jpJHi)a6!^W-o7&o;bT#0=@L`|ap8+Ex91-yc?v3d(m2qJ-DtcS^l$s=N$}AAWq4f? zJ~}GJeh6;Pou9btU3RlgacfteA3JrWo?B9&JV0^y^ZJ+Qcn6cY^nv&NlWaLk&h`|X z_iqalQNIcGp3ufc2Z_-O$@+YJI21*8MB*+o$)+#)p%_l z8;iF+-~0~FQbL@F|GKThWf>4hfV9L6^?m2P-j6n;qExZ}uKy-wG=7+}kb5$uw6Awo3V*WC!BS<2;lHhJ6Cu!?=>=0nNncQ3Nj5PGqX}R?5%rMuU#f}cxH85 zVRRzkG=o52x8Lif2`k$oAtm*gG;0O(^K+iC0*+|2+X2cC=hF+`deNTZ@Qx5Y$i5oa zY5QdpvQWhhKdEkJX4b#;!EZtA8a;vAv?ma~6DuJ#b**|yVygl2HS-KJW8;39x_kyI z{n>W4X2Y+fXkVCY{&jfxZ#m0P$2PH%-%o5qsiV6uoDQZAVO?~4(C`A@grG~T8tGk7 z%#sDp9tukVaBp6nOYkwj9b6g0onIi5$DJ{6Vr`vd+GTmonA|A;`L&X;Q=A;N21;S@ zP1(Q+{}v6$6Eo$Dsj$q`9MWeli5H6QwQ{ozr7zA~OteOA36;qefaj&A>R4zt)Vk5-iuTuVJHR~&we z=pRqW;Lvh-(-6vA(icLfp#El3s@1Z9#oc zS6v0~jv*$xcx#Ht%&gg+V(G{l8>gqgsU%Eqt8}JeODrDs#v7;#6iR)d zL=m}DPJXMC-nIMEsT6i9m}7wf2!W~I{Gb;CGoA5V$uVt4em+#kwLN z-u-%oaNywyVkE6=O)?_?T2~S~d>KIPNLO}TzX3f%tsGhft99pa= zcK*3splfWp>!DY#Y+zevDxK6q%D@t-=AI;+wTYE|LyqtUHMNrAR3ln?RdrncFZPx| zd~*JLYG#oSANRPYN$Kwfey&gG&>IoM_J5LH6ttPt?$xWEUuDYJ;NB+UP4wXqj|WwYD+{nQw*EiUulyjcgg+q zA~|!=|1ULG^z0#Cdk=eqF_WcOoSlfWQq~rxovDbJ*Y6)GIo5I6S-L2WZz-p0V<`p3 zKHrGUW(dzKD~QC)&o41QClzWc#(MH1B>OcT{&<6&2^AwD=ZStcwP++J15?PTcU!0f zQye9Ol-SEeHkx<_8k$Y|{M>)7Q+2k2mbtIE#_dTXa+z zq>NzZGp7I%dECnAXD0(>0yYkgC^F3V^j8-W|4e8+fX7JQKfv`I%3~M1yquiN02$js zEc*q16%8;0rv86E!(zm1i(>omb+J9{e}p4IV89v7LYSVMoUCPP3dbL9fg{|#h3@Bn z>cDum{%fsvG0oZ!;oe7*_<8WbuWGv(KT^2!)^nL1US&S@Rp% z!~wjG4!2TM8YB09utN|f4tV!__YN$txvWONyCNby_2BzV1B)Bb4pQ55P2^f-AuBzx zxxkNJsc|u*>=5&*TD2m%8P5#BP zvPl9*wMzWW3U8H1(+++&-U-Xba~?MeO5e7gCd$Tk<`~P@Hp1f zB*zgGE1RS8V8EGUMape78Cc-m}%cL6zE1 z*@(0}y-~fQ>}4jxjEtbKsS$+6SjvHc63{?PV**e;9wQ?oB)4KW5xo^)V@5W%ncayegQy&|Ya^MOfq~7L zi+U?LtMQ4U(jkyQdn`vW0Uojl1vlJiGM=MM?1>nncoX)DFGc5y9V%U!VlJDk2E}l1 zLl{JO3zJA*u;{C;udgq1$|FH^z|a2Vvq{|)|Mj%A6QW_Ec@gGvm!!<^z@{C5ZAMpH zyWOnylx1tut<60?f6e2V8%!lJ6PkCxks)$!X3qINr~^L;T$_ga?;mF1IE?YXGi!Rr zA)9*s6*Ei*DPFE9Kiiqz98vn+tC!GpC?`&5fFqvQ+QY-ki;P&$`uOobV9P=*_@w(9 zbztmuDt{r1*gngM%o9m6VI98Us{mEiA7iB^22g#s0YnEJ=b}(E5kL*?5-)M2s?FVY zB2I`)PaB@ArXQIK(r<@SyNS*i)z z9PRaKuakW4JsKr`@89c45f%=fH)UqH&{W7`uJJg8o=WJRb-|W$?hpR%?rxQnuS~l| z>*k5Ld*`9{N=>^GDUveF(sIY;c@4pttqn@gcD)t-$RZTxup(Z7ppXQIy|#|d3rmV4 zK)it7i_6K8MQ}avT&h%6RsB`7B9Ohfk-p5tL8SVK!*q|PLI@*E4LyCuIJ+ruVvM;K zKh@RJitr7qpEQB80M=?acI{x-SFD_uUg1tA8K<0^C%aufY(Z~!uB4W7#%tn*c}HE( zGYM5LwT)WB{z!}45f=+Y?YeZ8L@`n?qc6+cqLjy)#8v2L-kTN62_u0g-Ewvwvle*j z6BF$3lyqJf_J^oXKIy=eoGQI6wCpTU={3%xtKMiRnM)%P)-y|MW<1aGhqvEqv=B4A zCoD|)8B$#+bF3l};;2?-Qn&X0F!-Tm`Qre+#t(JC&2LuOLa4}f!)&9*;jNsEj7gkS z5C!>#jo|uwA4&6vmoeRkYomrH8p2E&7hg<`f2~y(7LaWUqkF|_u@4Q+aJ?{86jp!t z!&jc__eb^_89(9h(#9c$8;oB0)HAbhjgHqq(ORAdGS)2bZv;H^n&>`1`8oMz=<@3A&%Vo!VhuYAlv@(vUZ(yf%oyEE zWPKx>OFvt?C7rk2o+yR3jfP=a($n6|_Q$z##>5-`WuH|jYMBG`_3LDu?Mf9j-1C*& zI^W|(P0vSKM!J@s#ht4?_O%KB=3$=o-`z2Xt_<>TcATHL95jNi_cQSE3mg7gFlFO- z-MCwne)^8-3rSqpxb+kzAx_*#cicPYcV;plQsQ}Nt=h5l>j|^f+^wWZdcoFuOE#1y zkCHwQV~jQ`0l&Z{=O!L^uh(}LeAFQ+rd|WW1|fHk5Hz&(Z~{a{L3lnOwUL=ay2i$@ z>@|OYY(j5D8{_1{6__=s_SH}NQ?jb|190ji%LX#j-n&8jxR5-66B5dzIgV830s9zB z?rD(L!U^D0V*L3P2;sN2-krklJa@gT!%6VHnD)c1o`O#!di*mpd47}H`uaUU?hHed zF;%Ge!OHgLS#ghN3XpCpDT(Qhe#39og#~Vg0az1>Iy#1iUqN*1hd2;IJC@DOO<4Y6 zuU_FG#LcVdXh(;Sy&g0O(*p^QxsTWHZ$+~(FAfO{q1ZMhbq$1t2LgA%LMDnYWR?H& zc^>jyB)Um}OH2I~y#^WRpX5ta_}+@UTtS@V5eWWF0b)7iE) z(zp?c>7h(`&W`o#?iPR8+Vn0qRWdzk4J%NHx7jNrCB9AWMi}x~8fi9`$W5+H*}YF7 z>v+|Rje(9j@!54Uf#THjyA03GZvoZTFB!)m-VGBN77l~h>2*rV>PwJO+4Q9c%_ zeDs7X|F-nWjskKwfm%IVj$7Kp6ZW{-4*|?tyCMzjz-funX7CRc{D&0E^}d2EQoeag zZc-Fj=!Q+t;F0+Y|NfY}@w1u@B!d0#hdQdm683^8vxi))npgiR;J!IkG0TtB)Jyj7 z3MsHIF5BuV5k_J1D4V}9)7OuKn`>t-TK6GkA24t##Gmj#PLFU31;WcCMEz9kbb(%0 z`06A`P(y?kf>=Z>hxQKHPIYDPpG`Y{xi}c8asD}8=l%zJ;)UJ3%}f?uY15aKT*85YdzgrD%lBf4a))_jkJ;%q#y9@cHbR!g+N0_q^xoyN zfaB#226VKWx2(<%NXw>8XzR+#;*yfQk9HR_)p#4idwQ^{?MGNia4cu;!+-Qt1$Fzb z$Fg#9y@Q8R4F~D9-!nb^s-R2EMxVRO0O_qEOst2q_zCTV;O6va+&CW6_BNjZ6&5w$ zyqE%!+ydW_ZwDd|aVRw#9wRw|{jowZgd6sZ;O5gW%Xg@RJ$bnFY zkrubNwvv`wP1P&=te9KQ!N%TB%X%gmQ}Fj7J?UYc^-FZcXLL_T^q-C&jxymn$XABP z*-^ZA|J_G35lkPgU%Gl_f8B8)OC^1YB)t$_7D|Kp$)+J}-m z&v_+-1<6hQSPs_*Ee?BP(Z^A-26E(@?r$*Ty&^K$W|f}ao~@)6b4^N2QJ9TcsrUC+ z6E%!ulvJ5P@uAb?9d`c+fEF3A=~sYTV5i(f+`<2j?(*FV1AjQ5-;o2{=AgB!ad=x9 zrSA5u5EADnBPJv;eXoMXZ~#?n@IG-b82wMI1JDJ+b`d1JhO4;BX_6OgS_-gbHs_ii z6zi9Q(mJNs{7ycX6&iBn!19M~61{cF#^a|7&C}|VQR#^AqgAA;xsK^>g zxVKIw;plqwBaR-~*9dE4Yio;+igMp&x=ZbD^B}m)r9cYqC8tD6Lo#8Q1W|%}ZD_Y4 zvVA0V!Ux#peBcQ!Vi*aIcQ!)sZz~8MK9-iQLCwzc>|m85QpU)L66rv+(%agJL2lZH zd-u!Y`s3!$U%@ioaQS!U(mTcAbu$tmq_4b;3)9I8UQo~MqFbX5smt3XmnFc7JVAMO zTho2Njqz+6|Aau+hqyaxIzwZ=GIJsLOT@FP4&@0`(i+0)UaJ%LSf*@E8g(oNEKJ#{ z)UZE>G{bM+ystP6x;4kL%ban#6c~@cdll%Oo%AMK>ucSxct8~HwE<(_69To>5_LXZ;#wX=i;`M{X!L=oqJU<+vRr8zw)as8fQA5qR2JK9Zr zW&*6ecMBKgtuXPbXeq_usvQ*I0eCrG&?%Ok-9Z!Pf$FxshHhJXh`k6j5I$HS`lZGS zurxQoqX=W=*a)yJjN&rsAC|qj52x`Hr!5Kyd|?LjO_Gi40rRUQc%@NblNc<}Fo*O| z#?_SV^rIY%sUJJ`Mc*36<8pKy3%ZduC^6 z&jOt%=<>eCM4F%@5mK8)gqi&Oe8lS;M+^O`@gZwtpt$vkt?Lgtphd#?@G{n+CE5zS ztwQ*XARYE(YCc+4IPAR}Z@3uvqgX)2op1qf&A++>z_(_Vl-vSl=j`mgM-L^mCR!gK9sLUKVp(`zaVr(j zm;=~I4rs|iiP$@_vcG+$GunogwhXe_Vld!sfD}zmRa)N!l`0nu`iLxbaDW4S-5h^b zu|OMw)qbwtW#Rr>)48oKILTm6{&l| zLQGc93yr`fKl88tMfF`{&LVl4OIr~RVD$-R3M?z5TB$NcMncrk8>_aqh^Y(ybvye$ z*UC(S!~f^72+ByX2ue-Jp+zM*bZfBJGBD8e@TlZOLxrYBpFVLSq!p|>?Q*eCit9jV z`CcABsK37Q_XQ;QKY2Lx%K#M?KN^*TB^C$U?evvnJk4T?l!~jIUJ`!r!;<1+YlKz& zTX(JY_%Uyyax$2V&;Be2))qw%NUA!#Srh{E8W4v#e#gII^1!|ysC%)GC( zgt6GGdU6z^Pk)Z^(o3}pL4&Cnnazdr>m;S5d$a2n0I@g%4;H98?75>&?^ZC z(Imm1h}Z>?a0?pg!%tA&_o9GagjmPNv51GDH!;rbY&Y>vD@Ri3Qw_GFYYvMqaxd{o z$jJT;GdJ21)Ny~wHu^r$jd8R;8CC02?SJC>cl)PYrere$#vxGL`U@(`vgIphV0ns4 zO4L!deEv*hNWaAY(>E~SiE`;z^8AJTS=d?&{*%Rj^TVin54)K}IJ7jB_5R6qG2~HB z-6NJ$7G$Wb5w>f~xo1P{{Cao{4Rf?C*uEp9>OXXioOG3DS~Mb81)a%+o41H?|5H1< zg%*m|p-4RL{WM_%{oC13D%s!4MT7~M>A6_4SW&rRT@hiWy_NZ4ABu8Ug*RMey};y| zql+f!TxL13ER!HSY^X-?~1*P(QhcZYo>JaFoS)j z`sM9wp$N~15+6#pRDGc(fTMv{TYaSgf%3@mq~=AaFIP(kc0@&$aaN&ys3Xw>o(fvM zyJl~&mMl(<73<902IpH{u4+yc0Vw@878JH_U@Cg+M#98BEKZsB<%i z6^7JHHpgvm(TawHz=ib0Iyg9>fF534`rP*5GeL^h2=4>C3|idFT3S8;L03UVB`Py| z@7vzunn_qY2}8mIv*-T9zmh!Rpw7+7dEqX+jrJXKFetNus)dCa23-$Ep_8w1jYA*2 z7|76C%u@%Mu$*V>qX?NE?j5oLAOy(A#3wzv*Q|kEFEDW7>}WBWg*r8Yod6h;3xp`* zxp5p?M*x3oBeHBQ_I-81XjY_rv7+=}SgrfQLeAnl%=v>qDf&!wJPB?CyXR7fe-1llk@Od9yeJwSKJi^04F&Fx{{S>2d z)I&W(VM3pHSSr$bCjfb*<^%7nBX^3@{YHA038i*p;u$pQ@A^w~Xloi8N z1rLvVNIMVnz+=+%0`8yc6E-`uzRo|Zy7f?vATNUf*2B<@4M2mVpX;EvP_VGDmCC!9pMWEDxyy>5@2Fp&rf>h*s+cTr>N1H802X%!={Okd6(|Ctzdl+z^(< z-==x+X;oG6uMNFZ^7R#|E#02?*_rKw?+Ph!goin%xd&%8pZ{M|sNV|#tp7-L&gn( z_|;LN6oBg^=WVAD1-sL{gl2UyFJ7PmUpJ`nV6Lm)V`XEiGS_Xg4&IN{Pdpq$KdAM< z|0f3)Y1wgQ)xn1cZgdb-8O(jxNS*xggWqR|rhj1I($nqxCsG7_0>HiyT4x^}#h`7? z4tcaCI(Od9XBH569euYk&4Lfv00@TyUAc2WDg3oww*-8-=Q)F6s>UR*S)cOAV&`qi zysE14n>{$V(f=u02b^YCQOnaj_bmc-NSMYBfmT49fdO8-l^FP81jhXbbH%pNUbBYjJB(dED$(z)P`YiYa;!2mPCBGUEZnHW{x9b*8WzWmNkx;P zz*o#`i!2o^jY|ZB&#-L&G8J3!CE^J{zfA0oELqo99E2;Js z6~_HSI!QqPDORdj_ix%tW6(=7mDjB2Xwy7r3lx(_NE7n?)0Ng$_%lZ{vjxFSev}^e z4G!A;tXidk-h_x#7jkOG#Q!pt|0XZcg_N#Qs|ZpdJsnx#rbQ?qSV%9LM$#rE8pLSnS&);>pvu(g{mw+^yvfY z-<7xNLI;9hY~|^izQU}m|E-_>_(`I5OO?Mna|7>1JSq4r=)q}63x!0gXfobagtdfH znfk?I;Phv=PCbLz74YNQz%dPEH)tLVK#$p;9oRw>j!Wpp&k)Wzu*g#4(Y_fz*SOvh z60CvP7!lhZt=O~0=({ZzQ0Hn%Dk(}>eD^OQ`N>eF;{+>2A}X#sD)8f=NDsBA5jOVp&*ruJ)1fd6~dCD4TO^Ptmxgau9 zQBcDk1mznXEd21S`V$z!ii)EViRRjSQ?&1HT5AG0L&BSOuSk)1i8%hne0JyoyL#Mv z1dosptgV?2OQF>BNm(3}eM~3MU3N?HB>3N3ZlH%TK))_ygmG`>`EF0#^%0_{R{&4d z56fVqh7-JiTL*He5v0evuBJo^okW}+n0z)GAA(NVH!`xm5`W#D%%KYp@o>f(`ue~T zr+16`ehhRuiat2thjKn)t=r6w)Xeo9e$2cBXP~s%>yP&)wafH$sv$PodKHyQ<$ot)RY%vSSF7R9sSm-;>$1sqRkf@D1zae7HOg~ zl#EOX3Ij2zHQ%bS`$JF(BA6L-zZv0e$32=@?D~&@B{J;R=5_%gj~u~9w(gV*Jx1@H zp}z|6*v%Vbm2KZw++uWz3RNtTe3eZd6*o05_54oeUW;g2@K>znS$H=^Q-(ra-u}{B|QVV zhg_~r*E08aKQR`|vHfo67aJ<^x()G7*%x%3l z73o@bCut_??s|Vup+F5^`yu#dUg)`I>AAU2#`+Ce(XaL0^UdFrOSo)T4YmxcEz!Pz zKAw_I;&V$_Nd9{{{D&q=9^@afa1z<}FLRapM|^{c}9C(eE6snNV- zb20<=y* zHy)@rcTZ?uCqg$HPvEYe0x72r;6k12oU$5jHI(U~d{W8HmH*{i3%*Iwug1$E@_BpW z%yu>}>`828!n-pBI2ef90|1uf^WD-Jb%98N@l2(aePLSB8l?$AaOCtCmtUdH=nt#F z*#+UfsLxJJ!6ssP0Zs1=pHJsgmA}TwNvm6n69Oi5GQ{lipVF{b?=;ahIWlXiKDjX& zsHpq2`70AGmc%&g3!8TXfo#??R(gkWrUnO`oGISB83r9Ynpuu03x2&bUROS@lELry zh;Sn<3F6;9?~A^(AEW%II?AC@Ir7m~?ltZuoA0(bQJKs==VKB@9zoh*<$ssGtp!Zk zX00OfN!6KRKh#F^)RY;qo!z-_p?up6ko0CHpZDXH7%MOB4WMj!po zKy_Yd$$-l}L0X#d5GefHKR8+IrYR5$3?$A)0`9$hki<&{HWqWMeXD$^sR<%NUjR^g z5P-n9k9>Go)=P_pehQuaRwpfdX!6qzN_4(QQbW}O`oFVfYo$G(RYl>rXktbeupdUM zBtbkTw-W#he|f3VQcwh_^x#{##s*sH;z_h1@P$iotGHfcLS4JvZ+F5w1592M<-KI_ z#l{|!rk)zFa>Pk+?m@u>OvcOR)9URs5|99?|3Ao0;5S}kw+#|b)pq~?a|$L~ZZ4j^j;KvEfW z{o-C|^*7Qis+!BVu{`nGuT|j!&biO6SCBq8$JG}3g z?!xVjZgFzt^UY)4wh{qA(GX!_`a-WOF#N*ifQ`}yLK0#n-2@XHY=yty%V0%9Y3X0o zgYptNV`HjBWuRl{R_>ghE`a6i;_Q%;-v$f8!g0yCEr8ftu;#j`U-Elf|08cFIK0cl zSx~M%nTPg>fYniu>db~NkiTJ0vrzI|>|7PbxYm}hrO1ZElc1;ZrN7%*LpBPI<_lWI zsIpjomNb1*Bv8IdgHp>7HABR})8Qy%O!H2Tk12M#T(Y37EP2qKhKufIc5ZIdq(iRC z@mU`B@r~)5c{55dk_K9FVM2bg29cCKbZdd!8VF$)G(is3K z#b3~-M+C|Y-q>n{&^cKY)No$FQ*=CwEG>*g;qz{<;)WQyB(5^Ea0IQnIVjhW3KGA~ ziNe>Jnei@c!vb{iO->3Ef#q2(k5ej*zC6Dti-i=!fgW+XKk@W?B)JC=Zy@dyK-62o z9SxDfL0#)*?8!nV45axi#fA|k1B}uD(!U>IJPl~A2^96QN_y1&%QFAyFbh)JGKR}+ zN(Mb_wov&=q?fVRYvr%ZRR4_y?P+_77Lxk~QB1g398o#XDGHOx$aN-C)#AmGWKm9~ z$JOvmM*NbNqZdTu@XlMwQW@0j2ery#^0E{O-GzHTC8bZdqWw7G5hneBYNfvzn*%~3uj-YS| zcc?ma^Gky*2_qiG8_N!%&nW`xye@ow5*qiK;x%nAgMa{W=P1w(1rP)X$8oBl$(Fha zWk8-OwEzru56Ij_ML*Sm^M^ihtH>IIHpSeA707a}uIKD~pV#Ln*UcL7 zxm%5H@m{}pf5WD8w>iFJdq|3fe%YwoQC#OoVW#x-TKN6HewmR(+0YdoEa%Yj0xv$2 zZH8r41(xLBBe%L#-0m!XD^?=>ic7!zLT3HAVECSqkPnaJ@mq&>0&1h~f4n9lXE|3k zj=~_Yp*QU$(*zJMho5`&;b>{%*3>Y3(s^?6q2xT*wq= zb6ZKjpxRk)cG7Qska7KGh2?&0M{sRY`8|=Je`4dK(%p*I7cWf$GecLT1%GF>nyhFu zKlxE8+Wj$D6EHudd%pPTVi+>$q-E~igJ9xKA+C6smZ$4CeDE(Y@0}244`zs6fKdVR`(ml{i z-TsfJvyO`L`?@%#(jXw+-7VeS-GX$Ngmg-GmvlEsD&5_vbO}gzNW**Q_pa|+_>T+& zGtWHto_o&TpB-0qwj{*OodzL10WW()rCOi?e6iF@rzu0`ce~96p^kKZN3O-i#psSr zcIOYx^+;OP|p*G5rPX4QfKvpRJ#5 zITZ_=OL9Py1(el+c_+&z-Xg8&UqCT&v1D6&4#a*CR)(vA<>^`nm}@2`C5eN&X=qr7 zb8vGYDucrt^_ai$4T5HyyXkVIzf$Ox{{rMJkRH;Dz=!SadpX-zk(FZ+QiTu_ zB#_M*TCcXgFTpD43CZ*W-kexMv>>{vpy9DqLjnJGWx< z@tcotW2uSj1`=Xx?I39fH!HqqyiE00pk<;ahmW63|6*~rw;ouB< znfq2uMSFPza|C4mVMLsP*Uj#<`OxT)tqf%F;57jsbbLm}e%vZh(HhKyR`z!42Z%KT zNbw$n1z-$yPn&?V1An!RWMXIH!!4*)z$8WcHA+Ew-xBvV8M@vVo_}CZg#=BI29wg1 z4hY#2#Cd49R3?JDFBddhyu4c1X z!mQ}%Xo=+(-*fQumnjZCM$`WP1kgn$dTu4?YhOT>;|%J~w%|}WPnCo&!TRh2SfBv{ z3NpW!hjs9jz+wfK*1`Hs_@hl1NB6< zy5IcpmTlbC(i||+0#e8pF>9$REwTG9lugdtILbb1N`V?z?=7daxYb(c4_?2jTjhVx z4?bG7uUdC7=8CsA79w5kks^qq3g2+rjal3x&|F@ZFpXz4g^Q5Nh;x4FCu3$Lq>K11 z&Ewb28j8Xn+m>@n#D9Q4e6WD0j)oDXyVt{t7aFShA6?hnX~s{;_LBz-21)iJ0sGvR*fjcG~?KDoq9Swn~bY# zV31uubbf&WcE}f5pPxF9{!FzO>(1H27Cx3}n1H!K z9G$WXy?EBl5-Rb>3-B&sY(q^8TWe2tNQgGuFH_LNNo9tR&b*|5cG6@@WGW@3^>-vi_ zUQ}P1PuV<}nIBJCY!H8jzac#6`<*4wipGRgA1)u2S3|~`nKX4gMc!%h6s>r5$`;=4 z5aRQ(J$@5Gp67DV7|sO+zL%{O>zlhe%6pdrvj|4Qv5SGf4hK-hItmB1Z}dyg*7dh; zM7((7w>W*LxP5mX{G`yXZiRp8kQGA!pu6B~x&@+Fwn{P``%XZhapsbYh*$y(Muh5+lTWwD)Jexeb3Hv+N-NCbeo+RWF^zY05+lC`i|dU6 zN-(;aWm9)EAxx;VOhPPuQei*`as~Zw@6W3u^T_C6()%6c(vsYADkf!R5x#g{RPjT* zPBd|0lNKv_wN_-;jE2#k%~W<(hhHELj}H%%Vxitnnbh+~)Eq*juB5gX?@!`>9Ye1o zDue+~C3>u(S5Na6=tBV}Cb{$v8~S~C!euGBKYU8R0yOA-ro15p> z=Pk|5ZtdZ0)`Nb*D-%=ZJ z2b$C*tp5n#Bx+iM$2wu2Ae0jA`_-0MKf7C4SeSgJ24_al69AU*{qGscqjymG2|zDs zINxbncXcTrA(Tn_r^#w&_UWTzp1UkTYHj`4$Vg9fvA^MG#Xh%Oglq2QTuZV1 z`1KmahdOC&HpL{cB+yIdYf_@*$EuiqM;VSM#R$i+MG&0CmFqbVIT{T& zw#6Vs`4I4nbF)|VeL8F?e-*#X2VFD*@&tD*gmtS=L@mMnuQ-#GBxQPMNq-rsl3wuC z9HPe_@}$RF+snGZ?5P=Vw_=?%Y?RrMwE}$s0*_4zy!C57=-C*URyUg=Cwp%W_Xjl# z$B*5Up{o+?jO0>Tp`Qq6`1-#wD4DJP(zMGlh*YnA8;QvI;cr~ga`DZdQ5t2@myp%n zu<+AFmi2W2SldaBL7x@XApT zONO|Fb#@bhsnh96v1kyNk4p3bEg>~6tmKfV4z~vxJqDfIbd+tjjtv=Q8HVA66gf!6 z3m-$2vtZO~$HL;`Pcq#VZwY@X8i`yZUu-<$?LYUEwR9F$|D@iROfpVavgweL=4T97 zvOBl(33oKKenp2v2oD7`wZB5}FyKyBS5fH(DNIV^%P;CuC2WGzSs0gE`^5WjBa-YYG64 zMjvzA2QO;|TxJ@oUii)a?A)GjcA3Xq{cmYM;(kbM4|r+L4f1^nB(B|eN>QU@%k#o6 z`$(y56lPm`P8}>Eh<4p-d4n4@z&b&jnysSlIe)8od}$iGG+xmkEjLNY<@6A$W<*rs zcX2HI@K2iM5v6JD@v^4xkRwc-eU{(wRDftm%A|DRr&OU$pv|(|K^*2#{gz+#T8Z!8 zr>K$}^1Ew)ZZ5*TbF@dEhg0Pd0+Q4BeXQt9om&%sc>a0NV+6zY_9S47x%}>Ps0S;@ zrWWCHL@6!lpR*%wC>WxmokTnsW{(`NT#R$^7{RcTb>f=yn9RF=BpJGhh}T}((;1IK zi#jl_;`w+#pYNzrGAnW292~u(kA^E^F3YOROO@x>e%U(~*&Kc(xt{)Me5UM0t~V56 z?Svj?!Qt_(u504lJ)-34k4Wsk@69vUKVi4^rFBnV!*$T#07l@y5KDSvqiZhjlZxa| zQ(yHqe-iuCi4bBV(&D0`A}~4W93tsU5{=p6aZNy$@Q@qUVcc21^(HHpv!e@82(D$xG}(sMSW?tqu6wal<( z)ojbouBHl`d7(>EB1Nm5H*jJBaL{C?rms10H-8YJ@wm;pVXX7S)jIO6Mm)F?uNSrb z{Qgt+=I5%z5L-;dxv2voCc$z*LJV841iUdc`&Vhsy!|~H%O&yOdWX*duwBzJ#hVyA z{q5E6)>})orph@zknX(x;CGr!S8N2jKEn$n!iZ=>+u5>!>O zg(5NpQky;^rid}hgng-?a{|0!Wy-9E$TDO6S3d(dH*5nru~WFoBeL;lQA_W_8daxn zQ<~zgFlDV15=R?+brD(aVqOIpJVb31mo1Qnyy`1HtNe#}B4jZi)MOnMjZJHwcqkK% z?ZMt{rBWw*6TE*;TjaW%Hgc0l3ZF1?O5bfakYAMyLoq#S^bS$rgQamGOp=eKs6$Y9 zrI%m5y$NzNlY}JpFRQZ-sCb#Tc}ZSB_!hU09rZulM`_RVCNkvxtPlrC>I%05>G0e%Lg8IrN_(c{wNfx#}BD^0X%nh&wJzwTP5@i(P6S z{RyHB^HcPHoo{-YvS9-o{8EpuSH0KA{&JWAjH6nV zSf-Cg$M~l^W#k$mf%XResL8y5c{kI9hnuah^+hFSIOT^;A% zHZRBEhH;<_P7CTn*|Av_H~BaC#$yb(DjcCD8Ke_D*jaSrY4##Ao1)_Hf$!|KFG2|X z!q6tXg)e(Y8Z!w|#1h3#^1T?Ym1YgI5UTFs*!Sl>wnt{j@zXJt{?1#jPri8eS?d0F zMmg@7;H;pQlbaz)-P@;SzdoTF|KWl_^OQuZODU3oLtLknbH0$s+o*~?&WT}c=c(4Z zz`J7m(5?w@*|!`Y;XW>9J%}4kPzZ-xh}oMfT8W$7D}hMFz^~*tr<%qaRh^PNcH-G?Uo<9ZrPt;jx zzt*0SlamA<<(=`~7Tb32kupVf>Vyg~`j8z{KFg!SL6DBF?(Q5eJFaUtoj0nC*mP-Q z$f&67fQSl=0tu7lC*0uR@%Q%J0f0GsAY$8%4I{qNo12@<9yGuB^~Z_zB7Nr4T<;tO zv(q8F@j<)sgFfaMZ7&H9qdFIO@=$dA zlE3NoKeE63u?5un_FH3d4IVw}b_fbH&wc_^|Ho^?m(Qb1VKZsJum5;e7Q)i$|1!st z^f`|*R3PF-)#zH8>r9I>CsLlg<0SkuAc|L zt*UuFFDSM3onC>_j9j74f$H3JVC7sL&$yq%Fbzz!td`MWt8$3qe4tWM^cJr24r|PD zc!WHiCy`NAdnfD$bWu+UE?d3xdkXq6PlR{}%3h5EBqxur+5Jbk^kLb!|B8LLsD1$J{J1%GX^ z>4-<{n0h=K(;pd)6SdC{F6u58amkca$OUqgAJIK{H_HNc@z99_hySF(=-oT7xDK!j zHEt_W-w`$LQ5PjdV2FwRk_$A`ZPB`*S7eG$l^~BP7WR}8fV<2rxj27!leGM+TN$+~ z?7SLl94bf@VU;=KE=Tezcdzhak!U+wM3OWJorvJ#qlgBxaeL9IEIkFq``3~ox1Zcn zk88!3oqq6ZIr?5bE`NH6etSchVoY}V(FDPNL|cjiW6^Cs6gxmsOMy>H@@tixiJi*akqBha4NhB90i1t_KphXsg97W^FzZca-G zD0eO)o=>3N^*ak9QDMYZF9>8%Z?3`#>KC>!<+3*G?!OWDX?(E1`FI0qzw4)0Y)o?^tJ9i1^6uXh9Sm&KkAf ztB#TOILA7T;e2*spD0`xX7DeZiB;hlY-Xjhts-tHr1kH!Cyw@M;GOpL+M1HkdN}$w z$9193ht(l<-6=1)iOTVXkWLb~aA)fUl%8}0T+CSFoF@f%^u!3Ym)$yIcbz zLi7FI2^b+c;g3}Tf4iLCFWnw_o-;L~wUB-zZNW;%9UU@>Jv|`{zr0<_V^%d3AerjP z@Do;Zy)PqV+g((8!)3@w%E#~CCQzXi`{Q4F1Krdy3J4a60Wob>WQ+(;_6aODA)3Q za{z$HvcZVA4osm1rsN-_1;0{wX|3g!PesDz$?UwNsOnSj@bJr_RZEr(U*|;aD#i@a zK90npyc9QrUc|DSm7(m!fZtM0e9d|*hiQd|(vRVaLB<>{ihz_D&8Ma$qmhXGozAYn zrS}~&A@Vf)x4bA;+_%faVjQl|K10jl*S={@eegR08r*YmqTLW<-SbVZ2;Bfsjtv7| zQbH`9(xCVFU{#I%4`v%4<*i)N{28^p84oLk$xn%XJ5{ZT)?>RrPjSv+M0Ha{d4Am< zO&LNjOCaXJ=3xfRT+*eIFLhuqy>VILvZT6U^kCUiB$z0HR(-vnTf6&9s@QvJ7qKfU z&d`O$n0YAajiJQ**H*ex`|JZH0Sv+(6pqOBO6`qt78D6q0$D6G;?&o(BR-U$jDG1P zdkE^mcyB_vGK`%1sB_V@P_ghe^#q@hVa37~)O98F=7p57vUYO-Y{*-TLeg0BuYwWu z*kr68VtIw^_7jSn73{tJ>1qf>3<8wJJuuk@TXa`w@i)%5A0>3dlhJUl(!U9|KcP0?olgVW^^2;7n&E5I06jS>3}luttd{W6lul*tPQ zOs(HPC#=n_ACpPE!gU|jby$r@QF1k(I-w)nDQHsq2H+CC8fpknzYU+>DJm*Lur7}S zhL4i1!Vq05&!-J2pca(Z1i{1OhTt12^eKQf!++Nx{NVnW>3`{{R&Wb2Teo9fkMj2q z4?hE_DkZQ=kN3N8k+OGvAw}4r9p&YrEiB+Mny0tpX?;6=h-lXoOnJCUebr4A@PR(< z?pLYM07$RN7$LJLUR^a~EKVDBpPTos-@%>v2$Q!w#oQF+zGp+4v8I+H&k~>{}rGGtE>u3$m{%XP^A}wUY(LMIdOd;yP z3b-3+w4-!nP9sqLD!@Q;v{cGX1oVl5jnnyJ4?f3oX{^_Bq^MJ# z<;y-(M99qtu;-bJs6GTAW2&}jkqcBd*GQU=us|gpp(w(*!^c3Iup}UVegpB$j+`U9BPXx0@I^vJqZ6$yA#gHfoU+Hvo@hmXCb=*NR zycP2bADc3uPl5lEmC{7gz*hNzLw{lS4I=)E*{C( zugy7`mmaDV;-zw6-<=V5OOPTj!Q($+GM+n@=k>hh32GbVlqBxe zVO7`>NS?8hNDS_$|I>X^fCx)|2gq`Q738kF5Dg;-;^=jAzHJ>xkFp7Z7yOf zoOHcAko%^=^ubMIK}0}DVk zDF6@K!Z^M|+Q$E&!VaPwkf{w%FsiX&z$U0>eL9QOEBV4mLKMjxYagL0%CHc-oU>fsgblAc-90k_i6= zH3Uu;kJFdHz`%-ihGJ0Oy!=~#CRl&m;pPS6%?hD?vtPph>f&bBg+&}49b2V=HL7a@ zvSR>@_D{kC2jynJCodqcO`?7Gb9p&x*E4y{{Qmy_Hc9vn6C$ev<3&lZ`+*7@f>CgF zbAwn?f%kvHRhY84sJ1qa#s6*$VnKt1+d)#G0|TKydhJs+JFLnX8X6unI9OS2C_is2 z1J-N`HjQZGUSDyKBN^Rs&FNmfBF=EsI5Qd_&DeYWwv1A z25VoaS>|l_(aXroqX7wpMOT4#Lv6=$qN=q%*J4QtJ|X${uMXtz=wASf*SSSslQSs_ zop=XFr)3(RjuI9Yc3K)3_oeHasJN_Sk#63t&(J7^xvO_Yj^9jxj8oW2}R3eP+l4{ zJ_vcD_=UkCL^*uztK;@wysNA}TCF3RDb{_?8L09xVr!xHxVuZE3x`hav?P95-}}?e z@Jeh(Dwr(Z(t&?jJB&U)YxT53^!#XN9QuduPBVvl*0Mt1B5chfiU$*{&kuG3JO0)8 z>kgj!zAi2Lh&ef^!=GJp`Q12GcPR{n`BCTA9-;(EVk90t`H!;%EwxfvKivGJ`s-yu8TK#a^e8OpAG9kZYX3M1BMZGvA*q*qzurF1{y1I z_=KHGlGvloVW<ri9emZQR5Jo6)8Psz=pgCfi zV7978@!&PVoL*UxWfX7xmncJNe7PuBCR4tgOP;8IAVxX!LfY7-VVT1DiM0WFrA zWGgFj#OPbKxs33Q^cdZ0gK2ozz_&2=(SNBu+5VPbrAg%k6Jd)2cN%J9SGpUu}X-mgIx;gaNnC?5anKc@JL zS}bic+8iv6;ks6xKL%tWq?5v)XNy9=Sf|gJ`~H%J&o>- zi_$_`gq{-Lsm_OmU_U-;mis+^jjfF|bs}8loMEh@sRL!OBK`^)L8cmoJ;DE`XnQZA z5DV#*NI^$HH?4+K_u5lg^h_wL9fe9H_5L4V%0fC9$Su<;A(T!|(Y)HXaon0PiSQBU zsIS9gnHMQ~7vijNBNCDGEV?xKK4{iz{VnPHUNt29??3TK94^!lbrHxb7iqGm-fP&S zr^Bi`=brqh1?UPvw_Er|7SsPvfW=_G&2uSmWGI$s3oMT6Y98otc)Xt{0PJ}IK*?}M)oFHd{}^yW~x!gT)8~i1RTby47-F3zJvWOM;Sf> zy`G*(w?*Fq*a0BMo{p}`d6c5#(+44-+qJp(JO)II@3F)UYy=Ih-^Ofi{t`N-k97wo z>r`*a&`EyyK=ATM`0-6^>tJcF_~)XIyn3FB-B!K~T>cTcF`p_9H}N^+!_kUgds!Jc zOZ9J~bJ_mIUfZ19 zC9$i1VBKL~FvTw@8}AK4y1RLKUgt0!ysD@J#FaU4YI_VYe0HXwQT~TM6xq4v7ktgG zkzKwvNMg!(xO7eOp)+C3pmwTzfXlSvHwkmU?{B@?JpMhuCwSqzwzQV5@7m@k@{Sjho>LK*0yhVbM5lHwF3*6kz5DW>7>{alHr*`KYMCU zWSrCqjM1A1a?DdNqSP(pw%#m;tCg+w=gP+(IJabxZEF++sarx9?RI2ouuKl>XSV#S zN=bCtAry=4j<>ILX*zlzYFgj+fc)z^%atqjcuaSC37Tyef5yLNtK5a!yl^9it=yE# z`-DCP-}BFQ-y>6jn-v#AVZW^Zk9z&i3W|BaYBqe=^pT+s zP2-xb6oyi2CPiRs3($c}D=VV_FsH$OnG0lY{B>>_b<(a)?a*wRx(Od8lePMYe`wj5 zl9BR-=Pm{VCM0dxlRDlz^=5A;h?9P`uLcPjKE!f#dy}1=xiFx9+}JE2LCe_?KeJYK zi>*RtfDzuL>XU#d1$D$}!<_;XTlWsfD+9}iA|yxCw|kcC+^Ii!SstQyom;MO`Cs3b zTF=&T5jA+#A>FMk`{wpnL}ERL)6Gwo7(39c8ndt=uHVoOng`RM7ZSTvlgqIuiIej% zCCz+*k0V93A{lqNK{++I_=%;bKwM~!_;qNhB45UImntFkfp9|SYz3FUQf7;TCabpt z$>Hxr8mh|h0IssBg2D=EhTE|nYI}RwY_pK_?9`EQ)k%~ryzQ(8^~_9JVRX#*>A*FiNMBeuKVpN}vqB8^$+u{@$z0nmWPgRkKYI1^ zk#l~T+_9jQzLI{+n@~tfF7j#n>}{;X+iWE4Jc6&`Tkvn2;*-9t={Q~WBUmUObSIC( zvvl{XY6=so@zcfkCfXM7>eeIbe}igetmCPKq7Ihnu}u3qa{R8o-%R=lwa=&Cur9Jb zXLrk|K{TEM`BE!k^hUK}Gw9PpJ>xl7BzZ4u)e=j~>53C-^wdd0{mdH0T{mIb$-(A7 z3x{06EaAeh)B5Lu5~^7N>ssth%GKn0H1l%KwD)$nrO(|ZKm-Fm3&UtihT>9Voe*#65(U9Nn zZLLROFuGMC`Yi7r)wR~TU4Pcx9JC%SGG)?c@qxPdgYrPWH8+1w8bvH;UmsiA9b6fUou^|%lPw*D1th6CIYE~|!Y9`Wc$H1i+0JAcF% zOEneDK0V0hJmwyzU)vgtxBKD8Y{6k_yE@HZEQ=xq(};X=2xd6_s$BGL+21eRX@>Gq zNPN;!`Vd~Zzcd?qs@QyWXVyO1;G}-ZX~aHyb&v@u)jY6qhThjqR(x^dM_V()7XJ*P z{HlF@L;YVB!4r|Dpv_~xqZL8d*e_KY9&>mJpY!x5Q)4zJ>u(w+)BhZa{o-dp+Pm+H z+Ho@{lzq1$mNJsP%sYY@0uv{20S{ty%|POiwd)9QUyOs)%rG9e*KgkNdR^_I!)%wU zm%{h4PK1?BEW)mV5CtG~0+zcG;KEZw7xIpo!Ej?P;Vsbm$4k85(!}OBSCn+XIcPD= zIVwh}M;08)BSkI34-*`PJq5MUyAbY5@ojB;xCRHtfSmEfQcpc1V+-HP-r9nczW#EL z4i)Vdc{cQXnU(;BT~``6E$wOinDN`&kEDrI$;(LAhvQOYs>80QPM*;WO$W5Pce9N7 zbeW?@MVG&7Xoz%u#;bnQ&+=4i>N_1D|EpW{zH+QPpD%#N#X zyB6D?vmuT?ApwGbaGbWv>#4M4g@_-kmZOm^u5W_+{p^exj;U_=lmh!_zIM0XBx6dk zMU===BD@wj(ec2XkNqH1mL$+KJ83PagPO+5b-$uy;7HTEe2>J2A55uqYl=cW)P#xc zbTj+G)8jJ9YP~N!TqezW&u;7B#&C+|<}qE0v!seXp?xqte8}G_ibsDAc2Bw-H%Jrc+KO%vMB6()4v%Z}U~+n)TxesUB^juP|=WH_tVVCX0P1 z8ZR%DRP^-TW$g%`eU-v|b_rU1_)_W1+D8hd!t2A&B%J^2mFn}eV4P6cQ%dpOv=FgW zENG;S?Fz^i)?EE%yksLBceo1ca zrK1Nxn{!IvpaRwJ2OY0th1B)7@EU+8c2;tj+u_Mm4uxp}gw8D0hnVCt4 zg;*Zc&CAW~9@>_vZIBJT%OI*6m<=sZRI!SU#ul4*@1XeWm2hcPpMmkWXx+RAVd@g{ zHvx#M>?Kr=W98>WSvqk3{R5aga$SeV#~{R)O*cySi=I$!sSaa3hz@|{@)jlUf0c*t z0YN)KYLOs}Yg;JGH^a-@8$=4)Cm!<13{j-{v&lb}X%qbJz+lH~!R%g=RKSBBl`250 zK3AblOX3>4mKW&nwGyzNW{)tBdzGWbSuWK2=-{`Nv%li$OM#h524|9&SGy5nDCzS+ z^WeE9gTRs{LDuXNbyajN+lKUe&U!dGXp>MWUPl*+Ed#i7^MQP33{* zri!n$(xnlVhQp7~s7}jZz}Mp7JCdO*!AKXIUFKIxKCTR)kWs^u93d+_M4F^K&YHtn zV*cG(;HI2KmChT`+f;tJZie8lga^+|V)%=H78!1qhW)MFVB>g>3{OXPPF`^L23{a1 zLVS#f6k=9Vrg8bvs2nJiv}9wg&kN{nwRNa*6m zk2BYKX7wCgMB&9VGI|OW^Gdp-Q|mt29RuPLyn$!=SQ4&GSrUiQPRK`lg})u^X3lDe zHO2dvp>KxlczQQ7cFYs*USxKQLi3Cml|D*dHQBxm;=<2=2ai_xjt`U0?$gusm*YE9 zxN0^hq_#06%!YbesCWAKlat>YAAR`^*&n@qer4GzR_>x+jL2vf-`Q#MLq0ExifXORUn&b)F z|DWXx>W{3#i3%{DFO6_DsZ|~ep8la6onKv@&5HbUslc(LWfRkkIXPzlc&nzgbl^`!AS78mdU2M+rTh2HjsAUvrR!Ft6MAv|NP+p zLLvY>^>BWuct7@ZlNdL`r0RZgl$ScA(PMNqEB`*k)$n)wx79S(Q1Z4{noLxt%@PNn z3M=RQtm&z$XcX|go&==sZbJwwu3QgC+JcRA(r#zclgt4=CO+S^Rxd zvl(+i{I8Z_GwBVMS~wi(TZD)KqiHK9HnoD7&t5r`qFnlLS!!PgqOc(r-`VMf0JTV- zSV(yN?eAK@6rsz%&iz9|Zo()iDD907f~!Nyc_tG;csusXT3z>Lc8pNK1D-Ba>U}X2 zZ3TsG6FUTQ2_bIw^oTaj+~z1d8nS@w38S$cCb~X7$|AVJN(z@0Ef9f85QBwbUjaRn zP~b^KHJE5izfjlF$#z2&+nL8Cw&r z=YdhIhzTOj9!KBd@2Vc!MEJ8U``$3Rc-kD}elMu_vuA4g}7hxXC1 z5;UyTONyHkM&vEO`L5fR)r+Op9T*~%Bf%AJV@EHtx;YI7AwwB`_L{XNSSblH>$3Yc z=D2I?vei*S_|Dqa{xu^!v8|*AZvUl#voS^IT$u^<+ns6>896K!+EkU!iTs+F*XyM! z%q}+HL>L9}1rhX;kOUAz*mG2a)utDh^1Gto2)&JvOuTL>MNRv%g~e_B`7oCl0-NTu@N3-zhl&Khl|3j)(-?PSwi1g6j?( zq7b@23g4963#HS~nRI-u;$Qq1sS8CdQqJ=VCzVk*aYryst=@Eqx%2*at)7}>xk|B$ z3Vqt_qf)W+Nd2L$^X}X-hdOi#J+kJ*A{Fz3xrOCt!tV!Ct1$uOZE`|&$d4x?S!ubx4#orP$#yuGMA3~(qbU?Q@%sRJ( z*asBJqn2W{-dim_RVzyJgvqOyMbVE(57!=8ZB7$Ajq7 z5eh%pJ#t`4I6?1kmtfr^I46hLSu|YBjj^f!RYoE?97Z)~j%8sB=sHWm)Ydn{yLf^_ z9Ksh;XlnM2gtw&hzTHjvu;Vdo`<_9hA_L9Oi(ai(&hsoqXW?6Ak+<7WOx%8L!{*iR z^%h%y{0(TEa(h;>UU!BQbXN`{-QX6!P=#QzM~lnIyVAD1q6)%!BGe|n?AI9b;EBI^ ze)t$L7fKs>UPOfk;-D`R;vT8}FS-4bu9$+NVEv>kou(Q=ZI~i zn=dkqPCt{&)Wk&bQKvYPOb{N#PDmvnYY)1AK`{M>sPGOZ%a@8RB51vs5o2+_z)O>% zm`Y^h4!#gUlf`A(>vpBX<%eJDRQ{Kp^Cs!LT|=Uh+G&xX-G;qL0TMmIgqEMaLjEtV zug4Mm+V@#MI`I5V74ZlpYTxgt8$GE~8@?0nT54h-VVDCGIE&&XNSrBu~mY0LL8eH*7T z;q7@f+|6sZ{R}cBhorV#nj)32dOwy%+N8fWr+K?*1bqb;Og+QLx?jg9DC+c0L|U33 z#gnA){p=-cG5lW~I@_QKl!Uqd97-wlJB)J7#Bg+(E#u%44Mw%tcQoGK)|qUu2*F}T zqvjVT+ioxe(I8jhF?YLS_^ouvw z?SDSUYU3mrFsFg^44cjPISEw>(0+!X34joX2+Xo5ki}oWK>`^ZVASmy!o9YjpsSsJ z$rU+LE3e|jQ>rhRL?A(QLqrNq2$z$bEJp=^19VJGOmqR~g(OetQB%xS#N+whR!)+ynxIT*qcXW;|4cE$0OCa=dTl6skrMc#z0;5y#lMT1HB;%~(f zM2%kX(3mI_XwIU;O3U525m+@7>XiCfrO4qGb5xY`R<_PBw%*X9hy@+>swZaA&<#G_ zdS;IH-bYc=H}jec=AQ>QZ&0!a?uOR7yG5V-#sQUP2ZSg9;`ufogo@Svi~a-h%n{I? zx3;x4U0Zn-Y&;AyW3BA%&B3V;!UO^(2e2Q=;)WuGGpm9gMTeckuAVH%n?##xx)eF< z{(T4;6+)@o+si904Fku&q^Vu-9f)ZgD4k~3*Ok4!A^J;dWbs%+UK6UnDHBQUATm}d zM_>dnoYBRmmzG9B!(10YSUa-CfYB2Mlvj|Ddr1&U4|sRQpdSpXI*8K9-Q68g37l1d zU3LHHX#4hj>u8}RTMVN*{*^op2;Kq+uZ4C`7ktGIz*GeIfh?FD2!5-nI2%M(fT$Zz zxxX+^PfrlA1SB*UV4WaPYoN40Pb}l3qmobiD5*W~5lbRG3I4Bg8|m0L;_X`}f>&Jt>rJhYE@{A z+-Kbs*kO;>ySflRo;IeS-=R?iH^+a)zpO8-4&{D3Fap{)Fe}=f3s92^xojz&okEZo*WE3WVs?Mmm@qgT^&V|* zt~_U6(s3Tc3L3-OEh%6c;V1zL6~mtHgO$R;BaRh89px)p75v~`*hCSl#~#xa{~^lM zDFj~4fR~4Sdx7uj#`rr0uCTM1c069`Y0Zycg#FHJw3zL7-A^5j4+Dvtv*@?};vaLA zCwGL#Q!q0{%XHRi43p8yAhYEqaf;A=y5M3$`r2kG#7~`r=CT{R#Uof7HTl_-v(hCX z=UIR8VsSk?&7|{ie6lU&P8(jdPNxX8d?Sb5s9lEoow7>GKJ9i*2jA);EJm64(d|HK z9&+7e!%nVV{#COT|lbP`36+Qg0gtaaIn*{E9ZOF1Hr)D zDGF9A`;^~m2chT)y0cp1G(}Kh`El3d|6g6Q@oV$1KOnz|IlxGHp8)r_`P_UdhxM)G zH?~k8p{caaD@QPA=F=kpOYh@;u3rQM?ggCpMP@x-si3#lB2#(a#`4Z(e9$W@F$``4 zXEEH5V1ma%pGJ?tXI<+3^BmuQxl?Be>z)eYtd>Nq0_K9>x@MZ<@ zpC7+~xQCa5wl)Ig`@+#U@49NOp6(3XSH&+TYeQa#9EI#j{k{w#@n6+p1#y-0W5`&E z)dEt#hA+q^9DYS{#E7^2i+)VKjvG&G^^O986cJv7!P5tZZl(@OSsk0|OthSxlxu#U7f%b$Wu*bi)0_q!h{vF?~HLV2B;v;Qb6AgK>yO*%mwmI zLfJyWXbl|>8Ysn0d%nDag6J$jN;+UeD&^~xMfSVR$T6qYp_9ibO#O8RI{gyTJho7i zy5V2t>IrJI^YhYRvbxspsqg=E4U~hR7YLytwdlvLKOe7`eE*&XZeR$bmcX&S$Gz*p zU{i<9ebPr@%5>^v2>wd|Ra9EN*R>5`D?|E!);9LrAiQK_dHtD7&-UcU7AmA~2y&-o z!A=0W|J(Ut#3n$+p;q4S_%!D@1GV`TEiV=&F3pzTo!MP(2;0xG! zcw}sC8Ni2u;jc3!s}4i*E%&M`=96y_Smbgc*s9vc+LypxPe%P8@nB-$vu+9A1hCs7 z1sNL|1>69NqzpocNg&8Rzl<_``~tzLe!jJ>9{DRa7)LS%SkqGgTg~HkgllhazXEKZ z0w7Dm4&(`u!rykk5sDw(bX;vs0e0z1gE?bS__iHiAo|A%@%soaq3&8Q}+eJjs@X{;1|;& zr&fr#e%lZc%H74xy@l&Mb$PsX61$Pk|4$15DGtD4=5CO?vj=Rh5Vx_Y5!D>1(68qW zX^_QF+Ao#B$8WI4pl$EqU=xrNc5DqF@PmSaAXnnyv?~`}K2ZEXFrS_$ExUkiZ?D;@ zTZ{#|Qh=-+2lg;P)0oj~0A%jbzwF?{0is$*)M?VeRs#HO8~p2cN-5(fca1I)SY7Dz zBp)yC*;(1X)>8-5Cxs^<(xf?1iN%~vp9o^u3k1u~h8ZQ^QAW(-z?j+vw?@d1s(Sj^#2xpTkyuj<1QxObku#aLj!EG;e$0kov-_xaFv>p-Gz1mI#|!$&3J zOQzp7`^~}}{5!FwUK9goHp@hgtIHZZJ(j%3t$t0AH?DhPY1 zZU>({2UCV^lC6fGsk6H#jqt-USlQB`&afZn;F(5jWuYABd6B>M`YTnn;p>?8i;n1AnN-{bmRyoG2BBr3i|I!~ICUgJ< z+$JVtru2{AFKiTeKp*~UP2_MQJYYqo=r*1hUA3x^I(79^x1%JkSJYVI!eBLni{>fy zu1uia($0<(jx2q0GWo@bl}BPUz1v{!yz7r2jYgvXT(?CI1$a^l?-+{8QDdElZRS{Y zQZ~#6P{=;L+15g&Qi@x9qAp|HY4URNu|d*J${?{rA)nwb=QbK_s&{O37_{t|H(Dm< zI#FUmqaJrt`~E9k{Nt#QhAXQb9oL_fUR!fTb|U-?`_JfTd{kso&l&uBY;AvLENQYD z8+JC_=kMyxAv!6rg&t*oArrl}rrTa%Uamnt+ukt;NZ9;rz34+Km2LX(}9 zdNU8JQq3RbE+dld2L`wSa95#@M80N*c%AgXzZ)Ck7{79a4;c{0RFTU;k@Ofy%!urB zU*TldYej-p00s;ajt#_|KVFPROh95Qv-3|(^JVL_Zo+T4C zxswLv*XE?puy^6Bz~{Y>N{Gp(5o8^nM`fuR%i^rk%l0hp5WzSOj(4xjk?TKSqre&V zeGudH7=H|uy}%%NCMOHbB&cb&)e1xb;Hn%sX;~Wz6PhYx>}L?({XcrtkMeSy04lxn ziP68?+fodzIU<*4mUVklNZv3c2=##v)=6#Sxgg9}A9l_!&Wf)EDrY?f5sK&d4>LT- z-z6xnEA@FC{PMg04Y2BSO+2W_jb%Uv8i)t$J3a053WM4S6B|;P{?m2#fj(dT7nI_M zi(`8<&=9N&AM+zc9O^WfKnj3;9Vb}OKL)O8mO3Mnjb38?D}$sd(&PRr_(j9qrgz$< z`8ZY%*r2wcJ4Z4okd_7J%+vbWb0N=*MG8BxiMFjKL#ChQu2}bFCcSx>=V0PM$n;3~ zXo~APgURp2R#@JF#|i0kxe{b~`dJY!>LKyzhL9NIsRy@3)={z!#vA zPfY6D;@-pwGUN`r5P!w*jOVj%+@BPr@HW8W_B?&@!<$}IQmbsLYH?xOjBL;7Uq2u- zT5aGcD1NupmB3HJdz7&oIrw@$~Oj0n3ITJA4S%ax0veL0^1BkvM^Cm)I08BGNOI{ zKsv>Gr!22JAPMg^#cZVQ0x-U=^zINsr8%0kd>?clgIztZ_0 zMam-K-JLt*M_ryGQ+wk_Wy=)H!Yc}Q_bbg3iAdZh=p1lK{c$QbOBAmZuah_ zxzSW+7i4azjQy^&r=oKSGwoHq5ewm8(f)y9sc=3St+%#f6Modh9Ip~@Pybc%{u}Gk zpn4FA!FKp1W5<1Rl16Ccibd-Q(PZBzwlyKnwlT7KGyEE%SF|}cDyvnid@CZvAu7bsY9WMv_ghO6$2to zQ7n1HJN#l}=NlPKuDXyNdB8^fkyp9hq0DMgUD3BlBe6HX3H#?Cn3+YPCiFNIY`oii znfxT0$81u4g8Fq_Sbr%jU>DBfpa*Qz`&B`L z^N-BGP~f9&ZEfXNp<)wYO7d8^CPQpWF!G;x-B-$bdR`iyzaSMm==y}>nxXUt++jp$i`KQ>7T9{)JoadZCcfW!sY|U}mtrk-BvHn!(#u%xLeeafm07@BgS_Vat+Xkt_7TRRt)n@n1P+NnHBJUu-_e=7hFHKVNrxr`fV_-y6M(FcN(?1gPmcnr zaw_;p)Hei(ePj=SR}Gx4;qr!V-e!01*N^wgLWlT>dEl-`>XX+MY-R(rFj6YB#LMq| z-MBqo>b~PPYX}=7#G)TMkb$8g32Za~A2U4|f~jX9o$;RsNDAQIBQiwtvg_;XVYAIv zcnor|f)w9P*8>4<5hV2=PxP!B}DT{o~`Z@W_#N0cHje zh=2BL8qyzrQ4#{999p1N=d34am1N`e>nVuBR7ZQg9bqKtHo+>~f!!Wt`? zmOqb4*&iuV-XfyFS)ye8`XlYF*vy#0uJ;Q1gS_)yNmLAssUV%-?}^J&YX13uXAv;# z&h$@xxS9Usr{1Kn>51w0J~@0z#1s;VszhQZr$9P_Z1M%St9-+Oy5PPaow@{FmQs7m zM9DoyH}zvINtKv~q>?JbHkP4aqmaMyeD=6=*>-KGw7o{1{FONvpdoakD3?do1rvYz zXMW{tRDv%#HvdOX=7yRkno7rC^3<-qj%J>A6|Ku1 zTqL{ge4*A=2-d<;^;^M_rJ}+XL#6To(hk*)#UDDxwPi`=uFG?qJ=@w_+CmymFN(fx z>elBAXcn@>+e{>z$zaJ~y{W(!6T_8xD`oihqf3fj-RZR3S@?Oc%dwCok>4-0gw1kF zeAeySV};|S`s3rRdQv1HPOJP~Y=hG8gm-0{#V8p5Vi1us9WF{4G3qo-&%HV*`KQ1k zt7z}k{j4@f0#&ql9xVNl7;zPqNKez@lMk;`6n}?Od%E6m=WfH?)Z|Gak$13Qj2&<- z`tRG`#O>E%-=Z~R68H@nH^7a{QP&?!58H$Aj=rt(n~8VC@b+Z(`bZQHCz8_ zGIfGdWI_@pCkK{cGhegE`i2G55Y zt!5UNs7m5#vt9hG!Vv$$c0X==oY4S}W81HaM*(clFtH5YBnm4ucj+!hC{2Q^w_xM9 zU;%N=Adi`YuJiYzsfFtg*LQTM+s#=Cqt5DTqNx<**4TCD$~+d+$I^#`#}L z;d;tU^^vDu;jNwW?fw5wJZ^w{_3k;UrGD^DeSH#Pq4qAX*KfrMdkdWuT~{-9bDXy8 zXMIV1=j&<`HJw=8XX{#`ns}5B!<>&bcB-63n6x!#X6t7)7lj8eO7%M9r5m_#{<3lU zFclL8kOrrz{>UDby;@+s`T&mKk)2ifeSMxsoJD$8&85t$<1z~u+s^m?D^;Sp-Og?) z)psSf-2EflBp720o62oY(C>p}-M@uyg-_Bi>sJWfg+;v1E4{iQNl=Uy_m>SBK=^#0Rr6 zBTGKDGN|`cT{0SJ$??QYercJRXbg{{qodDUmoWm9@CH^7%mNF~w~fAUX)g^fEzyb~ zwguD|Lx)|WGPJmL`&~?MVW2=x@o6PU5D~x_fXQ#cmP5@C4& zi8}Bm51$87VcmPnm{V4Bw7=YqY_H&*gS2nc-XsH%>H}{8LM3|s{K1Z(NXS_cI~DT`L)rWi(*rchztyXBYBOry) z6z;|344T0w|M~OhKX&mlv|J3L7|$SW%XZ*VH4xa?`T6s{*58@u~weGrh=;6&?UA2C1Su zt&cXfpcl(dowvw|iCS2AbKxlhi|gVPCMvYW#l;hMVOU(i-`+D>#gZAyW)V_VC*nF8E4PNkN`WN`-+@ad59;-u{ufnLe$cz-e z`8shNJ+riM-$UWLY_&T_l++KKC_8V|0}^?Xoo41?a|?+P$+C+u(yla|ZyUlMU}k29 zbe*T*Qp3l19ZPM;?lC@ftlTm`XRKoS%&?fKXIBM@rBo(FSs_9Icob=BzVZy*{Peg% zZ&CiRVZp~@mp9!hS^j+iM^xB$-^oc8OSj5j`$GYzyEK!-WclVp zPqS3>o+iteUqb*e=yDs=ii=rUIdrF`Lt_X28#;#^KUunHk}6H}o&w(d9sJ(N(S&a! zwIpiX+cOSvoknj#6N~InOhwL#;X_UQlh&)uJ&LJ*95pq>m+SI8OQ~*ae_b!mc+IK# zJj`vhRY6FZ{6>JW3fX^ zsad*lPAkNg@2g`}rBiFExg3=1v&EJ&9ER29`h+~6v7NN&m3<#~@K25f$0h z-`lf~c-SS(_+oB$?It;>;-F4d5K~Lvx0~F|w(g@)OZSpH9XCMLB{l6|Xoajf{YjqD zBAu^gWzo=xII(=m?gTDDf{U+QI_V9W;Krl zqC05=44&&DnFFTo?oY!pmTwK!FR?nLINz~{21fkllubK1(vc!Di-z|0_@4y?KP?{^ zOs+jut&Y+rOxJc1=>E5HO7ixdzYg z%=IXf`_XDDkfq#SBTRoyRGta}|^>7^T$^)7Wihu1A)f8+5bIE`atp`V_OHYzze z@dEmi*zn?ao@V=J%#I_Iko|QIT|u4cX46|A+ZUO&O8r2FxVI3(Ixs$-*FH1ZX2cQH zXSL?$y5+IM=j!m-iifONcgrG#goc~Pd!&5GE8Fbd^dOsULx$LNYr(Z^kavK=UmKO0 zLMP$6xk{&d|E;s=ekdd%Jrt7kRNom^7`AGNN?;mk*V~VESGD1N9Pf}SM~?)Cik$q~ ziSFck83%6JWTx==F@9Fqz2`jCaT=5;`=KbF^--l~F5%7_k4LGY&g2a5)V%gPQ};pd zr2MkVno10#NUu=|vWR7FHnNJ^bM$T$*DEo`!WlWa4I!b}7!h*DV-*fefir@iuvxyJ z#a#1B?z?!1e#t=5E>*VP51Qi?Ze1@56{WYJ=pej$@;tofsSyil+np9F10S!U$_v0b?q`!O$*|bt!GK(^I z=ZR$U2C9|Gap$=y{je3JzJ3Z1=Y&{Y7QKc|l@)S8O(nQUhSPo+zjBGR^>&nDq#1bB zt2JlPmzXWfOxR6xwFZ<_En?O#qK{Hvu^v27)6`rwXj!E$jKQn?@k2caNAuiMAeru= z)5iD8%E~feSE%OU!(uxFwz8mkK-Bk?*ngzz%o5>?4>vLlHcOHRaMT=55*19_ON9I_#-aViY?g4GqOE5G4`W5-r4T;bX zR(=}-=y5SUdr1yf?2{EPFpeHRBo31J%xM}%XZ)Dnk&TEJ;!HY?`0Q4&pd4mpX9x85 zDotBi3GF_A+yV~%UGw5Q2tth2oR*&->~gTORYb`>Ws1f#pa*o zW2qAqIxTk*D5VN3LK+@?nI1zNulw9;KaQ>n|n%bqP7 zc{Zj(M9B2Rb{lLd;S+XY;iA50#59~DJfR}IMpu1o+AzRkWEDv2! zN=nM|#2gAtc69PBT9H!*b2X?Pu*M3!y?*f*|zp}2lc8-W!-Be*4$ax`zy zm`AXKzM#b(M4dPO#M>d>jFh3Zkgwm0*Ah1ft@jj5vIJ;YcSh=fJk zAA5~5Kced55~Vq4m3!9Lgbp;S3sTM!{)^jJImQ)3qSndU=euH=Tu{nhxvkI4l&P)t z1ixJqY92n!QTU^ zU$lPmn(Xx1^la^mI=h^!?OU7m5}3o4VsHG9Nmy-E3W-@?7TOC~*;hL$-R58i(xE-=RPamDY#j8h1|8D-5nU*kFdv;Y7 z5fvKXjAIqzHa4GSXvHY8hs>eu?)M7r>_RuOKV}$iJR0!}c%T@8&5-Mxxg=lWX?y!z z!F;p5th@2AL-Fd%uBU%q^QVIU=rSWm80q*1Bi(&T$yWG~yX01t?Fj&~GH-j*U@=5mfNI9+Kk*@f}w@{kge+ z`q_uAGv;SZitDF2**P|D?=Y^2rVf&oZR3GX{kxS?t{m7ovug{xc}{!-HR1Mra*2Ea zZ0uxe(r+I3p3#qsK!x0CycR}mil7^W^(j@BQSTHw5=iAE-@&eRH+ffgcW~!8x`dNR zs+Vn_#6fq}yI^j0K;3OHgNK(FQj3sRZ6Ax*Ar&4FfXo0-|EQ>VGs$9L0;Hb0gWgGu zo6M=5oNk;DO+JcLBZ=kUn zYGI_S)medR1JdoWG0?Q7D(Ul|V?2Wkzo;k-E^QP*{$D}{3(ToMDl1{KMwrV0;=y!H z3XOx!1sZ4`vcQuXEX`NT#fkmlUjJx!_zk#JtCAw`Onp1pYc=39!GZ$|L1YNE2ug9N zu@*F_w0-00o+cc`2=^1(3m4dj!^21tbP59_>@WIkpGVt7Dbo^;p@lSr+WSE+3Fr*>SegtL1d9!zMDxD-NqV{xTJ=vraLQ>HUYl#<@^TS7nXX_TgQ%{h(o+PWx{?mfo|QwcB@ z>ryfxxg)lwVyUCuvwud7Yj%>$?fXP*Y@io)nJ$b<+g|r2W|q@nhFt2#Hx?|nB&mO& zr&@lpvGZjWaN5jL4*z%KGJzWs4xs1;49<~*@k4o?tE6)4dALf zR%P?m(fRP`$aZ(`87xk3FLg-!qyhuAkB?r)0vt zvg3JNnmmM4*<}!wrr^US1GF5TBY- zpo>P%2mEk*{n8X`9%F*0`GuJH&OjFd#3Ma7_ak(3!e@VJkabBNGa8uUBWacJ_d}~& zV(|sygZF`XHZVE4!D2-PUosDt#`6$}j*Lh^v)cm+@h*%iRZAZgR4awZ?jOJnhctva zIbL~XYfm9c0{|U%ICTn4m2tVXA#Z@(j7c~xC-Nv%i4xNcFUp-1RFvTGJu^} zBCRbntgz3Dk2iZ3>N`OEeli=L24tN5`ST~u=Pa6Dd4J@!mBA!WcifXjU`wKElOH1e ze96mCz`l85`c(^v9AGyOqGy~zd-)T0`1N3I^xPM(!om*N7^U`Ws=cWqeSkffSz4OF z@j?jW$a~gwm-SD7e|p~p@?6S`JwjoRwFhl*FZLH>*GSyVgu=Upwppg;sW(~nctd!p ze_a@5`w-svtV(TNs14*1V(^;`vz0OIJIr-4Qn+XQwwonjfX!dn*F;ZNi&CRD>+s-?5ubfeV->-s9MS$*<<1b z&35XKGb9oBL zWMsTSJhS2D4KVl)_N53pwMe(V3(gM;l9>Z+lqFDc#AfKM^4#FVaAI!6b6psZ2ylF?bd;P-&m0Ka%~s z?9_TRyXM8(Lx00`ZztBdLW$G&NI)W8Gy<|T@GIor!3N#*jT_ZoDk2Q-REI{awKka`HNPt{b?ZZ=gxD>-?W$8 z(J;(utNxR-c&j1j6`ow((p}bPDwpalH;b>OLgN!dj5rcjk5w0o>=`u#`fE*Ml(r8} zx$X?#jVh3F9yX1&Xg`10YybL;DZynwOKx5`y7gAEtRed4%PTeiipuh56g`vH`SyZ<4l!PjDPw{zS`kyVrHl$W`knf84jPv5u_b zBl1zIjZDq+dY1)WSv#+Fbbdp1+DcJ40lt z4Ot*WttKPI=VFiIn!T{F5Yop~RY|f7YC}3ZWi7b!?Ap1eNfyE7;YWG(@;fPMX@?&d zb90mfHiOAbh6cwk)z0oTQjw?d4(Lhx>oj^ML*^&Ygz|l&L4*S>)~T<^$xU!Ag?!Sm6wTB=Y4{fK zgSj7knO%Z@9R@*Fcq|}}&I;}QmHrGo3L%y{`|DS6X)V|Bzwm^CTu=slF-RKzO0%vW zfX$lFTw#$sqjlpo+}qHe0UZ}%)&GRf_c04g_K!?1Rubise_1hzbrrC^54NnlzFb{- z0(2sqGYH(acVF#nZKHq;IXE=b0)iJaC=Wm;2fgG8`hP6ie^gcg!Xtt2+#Sza29bh5 z)>DT0Sa-`+6pvv@5IO1Vy-nhLF*A;(*fwtWE%`=m zq6D2xPy+2HmW^9nY@fPX1l z(Up^Ec<;Mc%~ip%I!=-IQ~G#Rp5}Qrm=q@FhsMS_fYs8{(t=j&w9{rocCSN<8mBi! zD8ynCv36wEt$QggWWrI6EjL|4{z#E69QR0^`T8*AUFn;au zs>(qV)C^a%JP7tc?$FGoXj#2X2xf(_fa}+aB}AyeXbr7~J&{YHVw%F@(W31x0d2ziI>}tTfaLQ#h83ni%81O?tbw(n@k+68gLj9l-QaM2_&vx3-~~C_nT31W0I(>e?Lk2} zqmcdA*8(rJHB^3(N6A=Nz)0}~kr4p3MkgxUS|=}x4O&R(MD_;cSq+HLq4pa>N;>?6 zKn}bCk?Y8VMv#*R(C~*q8glgQhtA#zTN6;5r*B4~Dy?m77(yg;;$|8LrgQQsW8$md z@_c0I$jT}a4LY6#xER16_%7J&twPxEu<0x;k^q#u4@6F5XXnqbAp743*m&)k;<7@# zCBuW|3kxO`MbWVe3(|rYg$poFX;<6cYMkv0CSU?#3M?vQq?c~{K7)3#4gL|Lp#W|_ z5|KS=Q3-A1eZ+=^I#OnasK5gZZBt;E7xR+24RVqIJV~#v){0)#by+3=PZpQc<#;&S z_YsB2R^_aTk&$12zY29E2Ey}&>MMzq|MEGTc2{5BGT7j@0rwnZBS4#G*&uy7%OgLsHCp88nukmX0dA z4|%`5%zoygb?2T#`Q~h8{O!ggqB!sC4UtQFYRE{u5%dzRc|ThA3(GS}BXdpONEc={}!BK!F&e`3kw^gilMA8S+E_BUv6IBJiILv%CkVGhB9);D!T*Civ@Udp{>~^}v@Z)l^r9vmRosrJ$eyJS~`dd&`!)ySww?Mur?D0Iqew zAwYb3BQY@%^1o@LJE1KFoRhZG2zQX{PGB=k6;)dP3(*td=^s}i5*>;TxCC)oCo1Kz z-@D7u?{f!X4#PVO03={^aPtmd<`)(sgQ_|sFz+gRNnqNAEY8uIUr1;=3CWxH?>|LF z;ld0Kk_{tZdBLWXMYrx_j3q-XFjb%!!&U|B-h;Q){sQ#Fbqv@ef;lr-5-`{RAPHFY zzq0zdTI>%H*$qV!jw8VBgP?Auj{M1`2>&FvpkUA*2X5GwgC-O^RO;{wLhhApY}gGA4T1R+Etgg0yqCd3{PnCL6PRc)LS@Dz zj^!4qc}BrNSx*PLA!NCHRRXC8xIt(^5@O+(CntR5aAQ@Mty zFOkF*Z)_c0>Y@xuvqb$UyHS$0(v*5J-oO2}>Nb0>JHlpnePng@d*=Ya^jVY{$AXxL z_r+xVB2F#|VcmZ%FOd9;4bAJKU1X`+G_}1nySVtcHRQ|DQHT?oQoScW4c^pSTn1Ci zuKV3xPM>Of3G#?vu=p~En!R!{v`ny67o&@ATP^#}eADY^;4)_^a|>&Qo?1}+Z+bL7 zC3NecoBkt{tBn;gVkwQDw!JR5r6)eeQAVdUmF)V5nB(U;Y%!MH2@V$X_Ax%agC5+G zsX?M7+0!8@6c(<$u6|zwO?q2)R9P#PpCM$Uy!el1)q^j zv{BS2)*w7WnaCUSb7Nto9xRW-end(UDtvA`P#d#q-Ss^9&5J~?Kox?i;@9zvF~sn& zK8lZt0!)PyY@XwVx8fTw9s!;(@!5v#_2LuVfvEq|4pBe<$Es^&ZT);_D{K*7ngi89 z$cPI7G4;FegR2MZ(9r|Dl=pR3J=n1|;|0THir1A(Mt^P?E-5MLB4XWTZ7q|^nx`HN zx1S0uI^iXOR{_BWpr~3{iyRKAO+0>QVUbl_9N)1d@sMAhI3NB12$t-iOzC`m9a?5G zWC)NqGRPOv#6Rg?fPoh4-o3TQoRf~uAm~E$97N~=^HPC36Xi^-16|#5$LE)ICkr7Y z_A`}MsVS949C@n>g+XwB|5sA%WpW0gngV5$ibbae8VbLeb6|WRI3XZVFk>OXnOkq4 zn=il`B&(t#4&sdAg$KIs1mz2hPZHE{>>%p4yg9?V5cmVN+V!@{01hMl#`bo0LxXPR zD)fS&*jO!WM_O13>h#e;XwCQmOoQ?PPb31IEZ&)~AG*8TsEUllLF9X?8XA&-5o;D{ zr)_t3Xn~h|K0yM*xA#`1(7ZeR3-@fM1uMklw)EFiuRr30}vosuMZ(Ag{*rc z@Ppnnf~pgM6fhrO1j9i#Vfy+gP%R;g!9M?8(3g=DSaVA1PE0B@Hm)V?N_t&)GdJ>g z&*E`^xyA8MkCtVON|Ush2z;75J3)+Sj{@7@i#^5i+N z(jxQ`^?v?1(`O%A8)PP=%o-XO16%@tQglSoH)^T@WYnyzEX0rmevP*%=U_*IAPYNp zVby{lB{28`IFf#+dPWBCmZR$(7d$+?Bw>$4Skc)4XbG$6PAKC4beDfGr6<)gdKdzD zYE`!v!14vL0?^SRfAr{4-Nk-4V2v9c>xqdZ(2WBQ%?^J(eE2ZnB1Ux3V~#8p zmNZQ6uyTN06@)aC83WrSz$OcQ^ai;gt@(f4WB7;`p{a50QP)d_)3e14JgjtRFrc0x zr^zElx7yYpk(0op6nqJ8RGmptVk7t)%mmuflIQNiJnzRu;rD_c(M5$4)>s-OhtN9*zyW`3N(->{gAJJQU!ZJ|kF|aTORCc$kFl`wTj{?w#G`PC?R z0gR+)US3`^S~rWL8vxst3?a0EjRX|BucLN=O@s>_z0~E>f_4Z1vlaMUkuNslAckWI z8|q~M$PkPRz-iTE=|fqJXNHC}kSvS1g~3aW?hX6d1dPlrrYKVKZ1^?;Ht zxqb1}1<_^qh*9geZ?#7ExCddWMqG(#q!)4FpBA&Nc(HDh`9$C*VNoYjdmC*%VLEAR z)-xOoI3#ns#*pcZGVW%-^t>107Q{dEUUmGwtH8#UsnM1&fm-nyi(k#(G90R@P3r4^ z%GWI|Rpy4RZ`j?1Dd1rc1*C61Y-U5)ZiR)4lSN%|3R$Yq_Zuc!DjvWfX_!a{{J0&1 znbf&;Em-t|C!4<*FG`v2^ViAY!7uB;HyLb{nqaZa0?}_LHZaxtQQ1Z|uGzoxvI6uG z;aNjhMw#`cWNW>=pswDDbX6UpTT>3((ZL3joV2C?;#?xgC8JSC%3LHNrtuwq?#R!wsvi zX4{k?O$Z+OZwFvPcqFi72b3Jt=pAsK6A=?X!}#?rSPlU)zR5XF1&0KekbeJP-U__QB;*@)v_1S8F4k zF`5T^=Y2h)glaphZjvZ%rX8te0nEqIk_5LbkF=r=#Trf0XMjx=s#H&6XN2a{nXf z<|7Hkg}(2MTR>oaEmgct$!4weE@dve=BB9?=}=W|7**cJKB@~!e#^)~giPDt>Y-S> zk&oKb^dEpR>PljM9t$i`OGH3)AM)C`sj+|c)W9dQ)?y6CxvBR zs;RQvWo4Hsfqmb`wA;!aJ>-Lz+pPSb>{xu%9xPRv1rwa26WC0 zEG#4Ays#k;lK23m8pSbHx0T&pHn3NRs};sv7@180VS~-(OVg|}X5Pgy*wxB=)XN9# zXKP!Sn|I60=68L|{g~0Tr@0|5hcI6pTVBVtj*TJa83@f9Ze)N05I+c5r3|d5qSj~- zk7_O$F;Xk`G8s?>u4g2k+$jHdL>Q_KK0}^a5U3qq~PHh|DlMW6J zyb-7|b}wS{62PtkCMenj1rC1xP8b5XJJQPvoiP~YL||!)$j+eyLFTVj3Omf+EpN0hc!`(K4bt zu*u2jf8@sbB}nlhw-MBhhE<1{LK9*s{kru#Z zz-*uV+G9uKopK;z+Bf~)k&FsWG94SRIoiJm2ir5eS~c#EuDkUv_)S@~=ltW3tuOv* zodrtmo_biy9s7kEh|;3_ZY{_)L%3jh8h241DQ zS?_fO?~59iXT5|RexewVSzcNZos-k#(df?+AaYa1=qu=wzr7zH0(=nWGZ6aztgrwo z!N2izZ`Ja4#?*uXhasVQ$C%?!-SU-BT4GG~=lP@V^4T6ALG4DAPSBVl;1iD6cgtyk zWd6cNE4Cdnbl!b;Xvq;t%LEZ45i zdD(MpSVS(@l!IEQdOBgKAbMRfC2qb!z{~WBhJlq8+EwDjjV zrhT3*ku=gkK!o*EiN$0+kZ|@_UKi73Wx|9*pW^(1fa!(x$?klg(SuXDE8|XRSuR(e z6*ENNS+;o2?`ynrLjWb);EgF6aEs=F*nTsB-N9|z2Ae1#D5k>k{+as?-e9qnfh7jI zBL#c|45O8{o2^W0{Bpua4ZI$yYop=k+0&zBFvJyK`f2V%zusn`Yp!@NL4KG36RhJ8e?egq|>*sRL~P%mxg?~)Dc8v8ZGs->0dNz?qB#KcvjJA{@kjeYTjvPPH-(d_FR=62rUi$+?X z+H%O+XH%+Zy|r~xE*h6p$y0wHSX$OAnz4D_X?^6bOs+!jACKpSw@h7^=39||5UO~Y zRA_QX#f=!6aqpmYU^JEyXC^&MS>$R--w>|<+B0mNf0(= z!uOK`(;9x~{Eu!UehHf9Dn?^1v{-F(%^|Y)A8wg=>VO$vOqngu#o-R?ve)^wQl#7{ zuFX2Out){|_`p|OhEnXBcsx2X;XI2ovrQ_==W;oNNKe^A0+iR6ajh`OwD*6NE|2~W zXfx~^pbFaSC>|;?-cB;s0^ql4o()cjFo39RgB&{c@py&Y(LCR}6lvW%Qg-5Rh zz9`ZlPqa8PvD-V!v$i@|w^?^u?osO%-zk?f)nE`02@Xc!285nu!KJ8XdPhaH!om~- zT_;VQUo9L=8InnP_X5360--Ay1YjhVNt1}2eE5(`JRKVaKK5mRR8wXe3I1YPy5Ppr zxkL7wz;dST52p4y+opnseEXw(FNCMl8awyc@$&CIY4R#K zDyUX9%t3J=n9s^cM(hm14dkCo0d;S^UE$T%@8@&LB8MvTwR5-TZqyeInO0b=My|qj z1~?cLKS<0)Z@9kP83ZY{mzN04^TYaf>A6nN3PS-JyQj}&O=3Gf009GQb=;6YKe z;gY}i1V}RYn9Ig1Dm!5olQm17u=zo}p}Ts%>t0cWEk4)LQ3Q&5 zfABlYd(D*p<9+Am=2rPUPP@R&PlllpZhB-Y0^%pEZ*qXs(r&|Nv(OS_cx!3k!|W&b zPde&+6oZ}i8>3AhsubU|iD}djsuA&_lFg^w3K(s-wtEuz-oj+f;Qbv@%s^V6dOY#W zE(`Yo*vj;aNY}S2rW9s42zhlh$cIH_M%v#$j{b>`=Vu^_{>_h;9iNKwf^U5AgX|oN)l+ib`UnP?bBQWG`%Am|(6915f z20OoSTdh^&GcP>~XeYkCst+#h(i$H?xBMO;P>@4=OC}=hJe#=bNg$=)h8~Y!I?>QW zQra)ZVkBxOkM`ROo@bYh52fT`^a0=x3@XIsMAzd`1NsqmMK!$0I?Af`cua4#RORKq z&tc3j#*Xs-z11nxmVuZA$N3)2V|R07u8mOc5uq zLQNPcLEF~@n^PPy5VXi-j%v}M=3Lp zu#N4L1_yO#Kp~Zb|qQ5AGpdzhrhytDgu?rAD0U96b ze%J1T@*h9`z#Q&2csE+CH#)nqy=?+*Zu}X1XG4%CA~ae!Lt^RJFexMT9vqQWwY2hXN-ga1(JLkC zzj=e&IIGN&umt9YhOmL%$OmHpgrbjy0T{txLr7};RL|8vmzl+GO3sp0PG~D#i$C>Lx+BHAaWiu^-I<>I6R3D@p?t^l8!A_KE9d^t_ON75z%H&dI{B+g+EIphfiW+@HzR3 zTO*6^#FTJSG6S$MR&)4Jls_-6py10(=C~Z|-?V!qH&g{&?nfp(uEu9olZf{GwZJX$ zQ==5n8NSI%tJ~U!YkTb!%V2ZFfO~uJ$H&%;lM6ysOn8D^-GoyO4@Iu1S|uepEvFyv zZHvtO1D15WOt!OsCS2m@o2!obhx?Q6Ii~xrPq>b9=(yd5_sv$JIYnAFgpUYnoVLsV zxO2K(3Ptl_S7>UQ)HcgSwGNPym+yXSWn%$ONc<&4SZ0DP9!2%uN zDHs<)B)Jx5^Gwm`@Eq2u&?!eXKsf^nmua7~#b~&od|jmU%wuh<>YygKT#pwwVu8aT z@PBCf%CM}qu4_e5LZm~wrMpwQyIVl%?nadE?vR$2ZcwDVJETOqyS}xb^M2>&xfE~q zz4n@OjyZxM@75O3DS+-YIiGo0Z`b1@0+0|VX8GO&j^o9}1=I=yN8UuRig&s`HXBq>`p)FW8>~J`cpw0I{oUXE)9zzINpw zE++5fDwm5^%{m~(yJ<5j+Hgk=$4ceE#6}Ng-L*%P-=dpBJTVU z!!8xf$C;K}n(5k9>&bg3G*UU(x3AY=wgm?rLlw2ZBMKvx)dY)+${?kgYEvjt_!~>WO+yo{6fx(I=00!8CMqc_qzhiQ#F&Ly`_EtUDCtW{M|kbKF5O zQ);t6ZYtyf?x~`u687WtA!-uM-);&4HYk>|=dEc}Kzf6aw_rUG9DMYm-7>|8496s+ ziMGIZ$Q1+Mwl(LtJs#Pme00Nwg)q-YMBMPoGcCRMn=OXedO1iklkSHV3W4jLbDV}ij<4E`55@sN%XMev#gu-pJ)W_)F( zq9)s&ExV-V2N(;$#&Ob(&gT^H=>JFkA4jr_HVU+yqp(uT}K)%C@Nh!UxJw-=RTfDOj!mHUprlB;n()#R*-3&CSIWph^H+ zyE4^5*&Wj*)vmW&NXoo%o?QQ_#48z?Ommzo0geN92cYJS)g4UkumiyWz;gaQQj2^h zL#FNA0q_o#TVSi2N7byMrUoXhd;}IqTEcc(AhmYLAySJb^gF}BAdfRv>KHJVwic{t z|CYc^EkANc=_YRgg-MR!2k;L9w$;XFzbZvMO_+$04f}8xytBCZHN~@;;c834@QZlA zY-{<&x7<-H2@jT$9hVDMke63ZK7|~WNewXM26lD$_t(8o-Rj;BllTavV7X^!56Zd4 zXRgc9Vnj7N#_$HcXk;daL}tn_RJK_8Vnaw!VaD z!TwB!3E$mT(^(I}(Z03gF<$M(=oK7pROCpW^v4ODmAhKndDQs#j1Jbd9cJ{Yp#R98{UjUldcQq;OAkp0_A#)1dvi!I+g^ zNWMApbt7eP^0~h`8rkrB>K)VzyA9>0{D4dJrXti{@0*0eV%rxs1?~>CF_mkB#d>*g zZ-b4DDNqgq7G4VEwrEQcB(v7)c4VN|=CGya$d+{jalF9l$PCc+Az)8ZLQ_NIFAriPOU0c(($h0-UfqGFhdb&wShWJ)o;A_f zzRSSdApgw%4?3m>wH2SVftAt(*zHoq11(&Q%bo_XACR=Ot5mQ9iy+XVf-O8@$7;5l zol9j<&`Zb(5qJdOfRGhZv*n<=0K^j6@0Ax!49IC6oMU)_C_kRSNUoeya1#I`z=t}yld50yptn8r@Pf6l` z7|$GHP#uUV-lLI8$<$JZCH%vpbfk`?Q6X0>qbd_}C3iJ_a&#x>B*0&pf3V}k#9wph zS}=;933j!fj}wm;lcu1IMHi0g5=W)5{qdarbNkWkQCrEtl2CQEL3^jwH+<`7$R|E; zKe%;hCMIfrdOW>)AOr#!=u8%YrZF`?qB;ofaK)8FTN_?^TaS5G&kZ4L|Kaey$_049 z!$5SPyK@ks4g*%*e*yUwtXK;bLTwYvF9iT^65@y0bXawL82vtNiS-#McczBDO10`& zKF;6RB(Afuv4NecAaYX+n9bWy6DyAkn0!J(TpQSosU$Pa9*nN7tvSD$cc||2WlYWk z?L5Fr*-O4;CfC2ISYP$@wE^-8h_$-7I0#Iw!<(6Z$zr)U(am7CeLj;^bX43jy5}Sg zK-i+K9X4W|pw>px@d$JotLNo^CQfg1Z~XISTsC4vVQi%uhJ+H^4%hjil5-_ASc1}Q zEjATla`Y zGuVi~yu0HO;7P6U6GjCtP>lfW(`s-<2DF9z(o!TLco1SxfY-aTu&GR(N}6srcd5Og zU^V8?ktUlS6@!3a_Y@@Vv19F%OD#eIts)JdZfBvj+hojKg+pp$!9MPsKrSFlNO;yA zR=gXsI;c44IP55$uF@$lH2718`GZ68lPp1xL&UqUbXk)?JOcu4qfvWweZXYx*fhii zkgkLb224bNbj5)C15z76LkhUvfiLDgNUFd90}ilZJ9#+r%Z3EWV0r!nWWqxl1BkT> z{IXNgt1p{W2Aa)ziV3iLX-KU{2mdW(9cINJXfH@Srv)yO^77B~gKWU-uOKk3@6i=i zes|9urTY5?zHl$4i_H|atl*+ktCd#XlxLiHPqCI^8JV5UQvcC$Yc-C~VWh=TxcL?(oT z7|65QLX~D7fRESd!#MOHH>_t1fB4IDr;}bRYunJ_XKBY!?IeBAO~C7NLd;=fN^?8A zcV#^m)R6`7HWW>~dk3Y2K{yAgksWK6w)7m&yY*TU-h^n23j+geKso&jNLX;A{AcVU zQh>~J^IPa%^svc{5AyQ)9ZY-)v}wcOYlQ(P#VPdt`tvV2IYz#Zr*vH>6B%4%TA%fX znw)&j4lK{!q1`5r)Jhw{Wl@Kr4;uf1z$gHKqOU$)66be5Hnd+bczQNpAG$rIwYQ&v zg6rD;aA0@#FE1O;+4B3@>0l*=ffT8VyCz|}Ze&?*KN;U6muICFEvhY(o@~EY-uS;J zB#+)|iY?>%EuGD66+M(PvNcPwVdD**hd)t~l;tV~Dvnxph^gI&qfH^frE5ApxM!rd zLc6WD>z5MbpZ>J2h}%%Q+rV2K1W1mWOPBkq7qE7W-{X%PY|vmRXj%kf^7e<)^^=Bt zmcb>hg|#3%Hb;<=s_j!fIb6?ED4a}ODtX(ROS3!W?s=o z*|Sc)v_mRo&ald^`7~GK0PN&IZx2jWA^i@JajJak*!BRo#|vgoPSb>{_T;!+2Z9># zszFA~r{HNjp10)zKi>isC?#VFd|SmZcCM~T;EI6=-;kRQsC_Ch&_xQC9$>K`i3;n2 z_^}kTRsunT$11VMwoy)Z8~kxF1wrOdAS6WUtl$Mimx89Dj)%n#`N~mGg2n9FxX3_M zHCili{=s76gQq9n>J^}_egd=%IRypC=e|HCM~j{;Kq|4G6YeJ<=~i08+H7lNGD=R3 zzG^V2@DJcqv?gxy0+385pBupPqmRMPA^_a|`A+A6lmU4n)z{ZI^UitstSU|-Lr#WZ zs3~x&2b?BAlP(wy+em@H7Ws6}Dxie`MmTBkdz)PW{_n>efdJL}X9yeRX5M4&zo|A$QBsq%aj$ z>J@PiJyhdz`s1aknNz(k-zYQDDPrLIl9bTb_`HBN#GUkSM{q*D-KsuHCF>T#DX1kn zRskC3sqWi*xUu(KhU}wl94YNut&e>bK9_7SdVk1MWFuirEN50oIHPMOlqQHsZG6sS zm)b$2i`;(IrLzGisK1;E_SY(m%i;v$Y6Dnm8MsAK((PJZD2|xn-Olot51(Z)Neg}| zs@9t)l$5vG3JmF(e57b7cSsWyerNR0sxn`pqlQrNx8|1T2%(CMMy1a1w$PIYL}01( zy(mq9b!jsPMg*W2kngu9g#~jCh+v(XQv2Q3V{;7qEJO&%_{AE{DfLPB4#UxF@YASi{*M6!HuEd+rH z^cj?D0CAIhS-`gi@zp>^%<-5g%E`|cDE5Dc-U1wLcOkFN6x3dk9GnFf)Bn!}i1F0b z)dn=McmP=eKDBx4OrTh4j}KMA2cV6i4g+o?FaU*^zn{7t*HcUl3M~8-vH;A8bzq@r-I|4}D3fd%BKJHddINR2bxMnb* zS#OD^3;rB9uFpEms5;#Qn`dAt3UpIdeov3ig3bK#bfWXr9vX};z2$1O%8bdXZ)Lh! zWKy@mA!odpSZ#KpgVf1^s=TnX?i&w)l!0#u4JL(3K znn7p2>c>%jVx^7YE8(B}`}Dxj8w4YOxAO)hs!6Uk)$H7v^gjVFxa}5q@+w@<*ywQE zPc-#T2toFW1v9*j!S*!Cre-=R1iv+l4axd@L%S1~iOVz{2xd8-ld3J}cohFsPb2wW z6Vxuw;pl51oso*%hZ?}pt#)6UgC7#4_qk4G@`bDs(z9h*~+Kc&VzFx!|WlS{8(+2x2t{^(@ zRQNg5wtK~=ohJ}faS7gaK6JY?GN=av=0k>!WMv6hW8fRV4)3@|DoI3jQqmE@c<0aO zmoU-gFm-JI*FXs8ugc|vYwixspLLDPZplO=+W>#5Jl^4}8eQb%pH14Zx8AqED4F%X z3=kbwnhyK2?OrKYbm+WA;&|C_Ou8}K_j5x-;LdclL(P7R?_HJSbRE_yf_Bu&w>Wct zI4Zr0_jD-oB0cE%pM+o%`hTQ;9AO`w0MLpBOI6~hfiC!y=BJg(}09VS)%nyZPMf`v+;JbtO5=dd~t^bOaJ2dB-k8w|1CJNPSkC5 z3|Gw;D_?@HWnhLb#Hya4*dmCYD|nVVdVITA$p)-ffI6rLKUw1P(0@AXnnHh+l z`UnWU0B>ahR4||}0u)$?i3aKx$glu4$_Na~JM)bA63gngO&{w^>1&Lpxa?Ns>U4m} zhI=^rUwtjSS4*|#{&GQ?EB4k;CX%P=ny*4b;|;QVBkxD zY78WF2KD77D%hGc0k9tkF!B+XA==JxI@cMv$VdYIfDYEYd>&M3KG*#}Alyxwe7N!A z&F>hn4y@j5&*VP|?B(&kW(EOiTWy0t6vp;d^hHF}n>^*~_K%j9=%jyEypAZ72O;wP z(9mnJB-nx;Q2)E^P0;nKm8wJID4+*I*B>r)fB_f#n9|t9`H+KnP7szouP$_yYb4`^KZJBgT$YQ3XoFnC2nPriirKD8u zY~kOh3Wz_;nxXN)eW6;?skXVan|*YI99eN3Llz>t*2gh9>{lL}>m)d{CKc=j#+CSg1r9yUvg%pvTf zEu#N9J9;Ym3`I@#+{DZGUcpj=EEq~MH6!Mdl9+pV(&mrPQ<{QI_$dR%8Gwr+j_kYk zPrM#q%vRJ;*jZH4j`*2KW{9d{u2Zhk;Li8z#z2#yYUmsG7`}az>~~{VDDMzZBtYKa z5l20v_fM!7_~KE!EmeJS+8*7wBf{sbcqP?u;wI$w3CoRX=rFY2uG-2~qrF0-6f)a@ za%jMI#_42T93%sQ3!ndPzXpm^`e<$aIw15*sWLU;+AE?grsEeV1!MsM`d7=cch&U? zQ}o#3P(ny6;6Ol}b(d;gfI90~5qbZ6?&;-uliqQIZ#wvis)(wQ-yUH{IOa@=S7Hx$ z$A3aX2zY<+6c!eOhVm5%osgG)81*@Hg4hdgephbNdkLXabg;^La5<7xQJH(LoLUkpgrYh=rONjsXEZQ&X zF&rl)jn(MS>`GJG#U9sj4}OQRCB}4LRQa}St%`el*O~ls+1e4{9|K|v&>?+B;99CVHU5Br{?F;R&gQLb{R|R5=IGgv)H`<0q2zgffgSuKMT?Ntrq@WeSi6|S z1D@o1%lK9m(k+bx8+6%5fjEg%Y+;r;he@mfs1OrL=r->u@adTNQJ}?bHQPH6STh+c zFqVGU+vDqXYd<6OJ^o4@hvXO~zA`bWdgMtuCvU?j8XGv088}+UBd3*lTKhLF(zUpg z2rPwrt{+yPd>%La?pHm)T9PmiPJJ8MxJ;?E72u_i*18IE_AL{f-mr07XtbaK@z$g6&3<>IpLAz)ZxiEuagM#~2;TrADf%rL zY5MbWd7?ACDpM&J6~m^@OhwVWMg9r|VY6rj7}Yn0f4X`*?`^Pt$~+Tf5loy>JRUu2 zA5~iFt&Vc__4$DM#YHPl(K1jnQ43bWD3f#S{y5g<$%~(l@P6u|qoBxzIs zif{t}#}y(IAK)O9cSsZkKGUWn^wvVPcIW6w@spe^GxX9x=_Jo1 zR4%|i8?vzlWkrb^^Lj_oZvaD){})dCG2A*g$hU-~OOQt?$h82z_(MZO`8G8VmPVtW zg;9S1}n<0S8uim(+aFr7a>q0a)a(fbF06>Nbe& zwbY**2YdEH6UWm{X~3rHC6DIs)OTABR0#uHC}`;qMZf^kN9>3!{uj(fvfD5LuWR3! zfbZ*OrKIim zo}1lOgK`UHq=9HIuz}~l+H$a#rr0D>SdaV#`A|g}!HJRacC(iYGkx=GLG59M$ z+--ksNSiuhhI@J)k*tMZh2PO_)D4Me4ev2tJD@#Gzp6}g$i?P7sK?}ct0069?L0|y{ z4dhuZPT?ZOXxn~q&kKoJAScF3*-0<-cED3UNAnP6qV*o@QPtm}E>3ctdD`Tf{k=4? z--*k+^RXP88dU-Z6-m4a_w;Ym3;ya0VIt~7l6U?T4E`EqCljTC>70h#Gx|G|9|=E2 zx3w!jA;6cC3Nya%LSz`y(zs9AC_wQz>oh7kAk`83mXnFi*s<6<6^!8==phyH?3e;E zu*jI-Mh6!OCDK4E<&Fw(J#WBeJ|xSO@I(oZ|F?VkQN#o{%NVmtAT~^3nRr`&A6_S( z$&_%4WBgHMe&Q76jUJ+?JCbG2)9}S*azNzhW#F{b{1Ktbyg}^gpM}=!;#fUnd>ZEr zTv+#>emSa_&uA4k8=~vy<~Z)=&9b4?)c-Vh7L$i!ni{l;?m)nc)+y$`1WMAV?}av$ z8Uun`=fF7BaXVe9@|AA3ba8;%W$hA!wU^SU-1`N`Mrhed`f~WH&y_wa0$xMb+$wSCnGB@ zZgw&$hV9;|koFx2o=$4AALt{$Z{p!-c}VP~`Eo~yTps#-#iC0(8@BU}M_CaX0kayg z$)SAxd+Kw!PB|+OjsO>cULcz6KiTNz@jMscAC*8qmP??ep-DFL*h}?$Zjb~tiUf6@8v#0i^K5Jiw1{x}Ucn%Xn?>?I5U~5P~hG39E4V38wwWr_#LwogM!=pPQ=vrgTzIhEy zwI2n+!-kv=wB9uVw1uKq2~f^*-Is zet)itT?8;}ljE++;|9{(kCe+TON7dywi!6qYur_?p_aE z_xU+y;(&D~&YIq=Srzx$FybH0k?b@WSj0fGa)txhxNG}G>pinhia`23tsaohNDl$D zKYSl%@m|N+oAj2IXCDWpd#N24x6xTO3qpHOP@axaoP6!DSfReD z?$g`tQNxs?jPk1dujtRya8V+OWfNd2*;&MUEb>0hc8DB3W~Yql>*f4Pl-5RUWxs79 zX@8?^_)LT*%#y$`JthLH@@t=!VxM;;D%Fc}?CPY5B=~#^;*o0GQ=5^;e|~wY84~|! zeP8>>iFJ}Eajg5Tj9tE75&`odm~mtZzFg$idDT0rf|4P#w?B)s?IOaKQ2v9xbh`$g@Y;1O;uIrDj{_76Bs) z!2Vg=z~6JEuTLt|Ke60<_K`M-p(iWZF`flwv_@7G(M`IVlP_`CzYM zA*h^8Vhhk>;LHVLO~K|62ugv*6nLgtTGBGq0O(ewI3Y-}!OS9^ycXPzfQyOV%L4t) zf9|qL*x}*;k_D2k=aw%AT-(8~Sdb$G-u1Jhwl;zCQNSdCP+EY2LgRJlt^^FY8%%+# zS;F8J`?)1ZPIe$DQho`31c>RhfLHoX^UBO@U9t>RxauoL;ullPOgH9dz8nA8=IvQtw@ zvqy_&Vf4TS*x%pJYZ6s!xT^oLel9VBTFrE5Cc>Y9UWb?*~oVkhzj|(LCz@m^Vtt?fd<`TWO@-!=JP8t zvX3QN#uqr8HEV``VX-q7wGn+ZsIerc0wO2@6H>@YIST)z_oH&)y2wZmKa`!1cq*APAp5OcM9q?>H1+-EgxE+u7?E(>UHPoN9 z;Yq>Q139wjHM7V3{nc*J7U~7KQ4mf+9fnKL8X*}GLR)sYvzcXs?{Tf#GGAqbM7X>R zKl*AXCoou}Ds?*YJRJF4e3tdR+#}J@BlE6F_bA8)u=Go`$G1g z8&C+=|HPu;W~GR4>LQr*mrR!`(;ELV7MeJd>k5vBU%m1G+D28C;h@@jF#sshciary zO+4;_r2yE4K}kp|#bTfY5_=yDH$N_L>Ns8iyGUTC5DmCh1$&K)k~6sb(7FvEGatb0 zGh^1gVzoTr4Gbavx4_771^g@ENtbrm04^C*F!Mrvr&{SWd)Xy`aG;6F(=!dw6RT{O z--UE?bFiNKWP_c6!>qpBIEZEg&<13E12FuaROLV**`&IVnb84$7o#AyDJ3I=BFY3r zy9^e}r%$wQa;{#3JSgCBXkz)^eSeMx2$l9W*<>}`oSmVKCZucz<(nEpVDh|PWs@AX3$XmtTVYBmmz#3*&#Ab=Nf+Njp) z08VB!U@jN2RwQaJ5v&1B3rIWT0PUeLC>wZgK$JMpA7B(s@<@OvaHX=%GL^!Q>S~cR;fMFY@=ldmIyS)^*bK(Z-0pJ* zaljnM>se2*1<83r4^3KGfDR$|iEiB3!)6?NVWTO@oaDkhJ(${a+P*cAtY^KW-!(v{MlN z3WkqB?^tS9#J;lJPsP8`|4skM2sl%AB09v5N9{0x?y`F@l<1#3;;-qzHhVE!<%WWd z@%m+#3e}iC(`c#WlXUAP{1}?lOaU-!i-ylAXlo7B22km9u?vYlbHggesZ!3!wf(*!e#mz2es}Bw zr}&jvmR&HmYqLDF0jc?(N2joflRXn_qO@7R-2X!y&9X!z&0+lN9(7B`Z=#9U#H&H3)a!gf^tOw zasy&FH17jF#%c7^b+n6#3*UPa{$Bz=C&Nn0Se;(+UkiDMp3cA&e87ti(SwbRQS%|r zq;IiJi>~E=F1>_-iq|5xN3k11x?>y=7F#9JCFsjR!nJ?W`L1KAW8ofGpgBbGnUrxs zOgn<2u9LlDpHrrL-J)lR4&jnz!J?+jtt2%ki5wMc*CgT37v z{OaQ2Q`x}({eWQxesAgK>>K%gitiaW3Z`)%1`1E4ru@i{^Tb7PKOT@gu zw6ANO+lo9=T!odwE}rJh*aDs(MfSTBqKVUT;m{UA9p%0mnkWRin%5|As_mYHS9qKQ zz8=Kcn;$g7QIWwC6w@dl+1E)U4HYMITFTLsKHSQHwg4Kvv0|pdL&$t z-SRr-`kGwrepCnQ*T_3rk^Va1_;4z$`}Oz!qkg%Ds+}%AL+>G-M$eCG9oHk6-P2qg zM7T++0t82@@`zy+112T}$EI%;M7Ff7Trv?H+&{e9FWBo>7kPE_#2XYUygz&@bgoch zdsFLCVS`E3fUg2 z9y+Yxv<#Ne@#LLN_FQxN@fj;ehW*`eRP^*sU44C_4{;D_*7>nFQ>K@xoZ319qcW|+ zR!NYfY>=$|CkwiF%|9NjdnYf0`3w%Z7nM#mUe;76Wcq<7H8cM3&ZyF5yCw6?b!5)1 zSFl%rsj>P@WkWJe%bT9lza4f18`psYNmCg0ox!{;(ix@R{<^Hl_4@+!^t(KY`0NDg za)h|r@5nD0N zjLcLzs$SxMbePDp$PUgI#|I5~71iCy#7(syoR(UMP=bydib+mA$omfc%!H0wAKp}j z#fp&S|BNSUB2mPZaCt>a?yXfYJAp^6Io}Xq9iGHK)UIl`wFssIXhaByYZ-0y8dk4R z(6-M(t|E}dxLu+?6GkUj3mH>)@3n&M$HgK;VCBi7pB0SZXiZ zOUiR*Cg7(hUS^ZtHnL(O{h@)7@nmnNmxH_B#qn;TeweIutPW9Vu|Zp9zKsfONdToX z=R-teO_(N<=3CoE8>}vExYD*Bb>^1p%yiMK0S|SypdSx*+o7duF4E6RtzD6+myY*k zQoZvSl|S53Sz&%yw7XlqP;lG+`mjSAr~eyOw_W{v^UJrFO^iH;^~03qHLHu4a~?;Y z!;9^1kqp&7`%&RO!12(0itDgdy^6}7zw>}?`O#JM-Zb%E+juZa5m=#QUw6HOh#Ek! z3<)1C?(|VE3!mbm^wV=#f#n>yI{kdB_a&CFap6rXTHaRkh4m{A~tJs zcpoJyE>{9c_;`Cfpt{BD_qC!e*)E@Gy2{wTYi%HXVkF{tmmhEAMjINvsBZ zK}cQ-w}#s96i0Q^0>$re27gEegb6faSy`%YLvBSEM|Aq^GY6<#hljV%KG; z8k^cZm!$7-6Ky|w4VM2u7hq*MdcWBQ;r(8*jkJ{!Ulni37sN7QdEplW$Ze zb5;i{bOG<(*AYF&ZITag=-3u~Iul>*`|cO6I$WvfW>Lm7 zKps?Iu?&hgzBE%}`&M(|aAi;Dw&2=y8k1O~|3X$`4wJ)`@l!7^n31YWTg}SetsU&n;!Jk@ozpLE>A8B5e?D~Ij|KmQpdE)|V7`a6VTvJ| z-htBctf-LtzYYkrLAFd3Q%;$Z-_UzDn4-vG5u-zPhbUMxx@SKHd?yjZQtXA5*|4yQ z=vQBlVuaPP3aC#kzt zryjB3*+R5d@r84b(xx9#)5vUZ+f#RbyAifRJ-1n`w7fz6SbLO3lLfc;LZYjlxO_J0 z&YPrdbvnFpjmyTc5pk7Xcz;G9YJ-=_ov;19zF)J)l>WplrOko2bMNfMXD6qhI@K%c z-Yo&KnfUK5F=Gd}b`z#GEOz7iZT~4Cxh?5evfy0e@tGdBeCX{{&o9dIl72EJfA(=z zf0m@Po<-Jq&TS}ZOCqk?XAR_O`nZiwFg`ci&g1w3iOQg~SZE1cH)|k!wq?5Ad+^Lg z#2*)3!9qvPzu%3?@o`(yobar#*P}!QB(JE2kwhV^;~LWNm&aW!xrgYA7+BVv-$jwv zpI~#v%I9;6)yAc9E2({rA!X?iBD!WF9(luZAS#ZBt}l+K=;iLNV0)3KpoAb#f&P^x zCst>JCh-wt;eJwta@M^|2ICo)NR)i$mfh7u2q}*^wWv1}Nldx>B+tm>KJIy13Y6^Hxn+%DUd|2@1yC{j4AMLNG7n5yaQLmhm z8=d-1p`41yNV4FMv!-{cK2gi1`g!k@nd`meXPj0N3Co7oR*oK>kaw5*gyucxABT2b zYl28Co}0}mu`Ei?hcLT7)i}nt%|^cYxb;rA-KX$?{OWGsY1zya84LfS5>Qv$2kV!` zHG297GUs{B*9IQyNAU{`EGVvqx5|=y>}pQ)=ti4Zqjg<1g50)0^Z9CjS$r6p>8?Pq zg?;UnNoV!~c?-vle(AL4NLAyFb#Ca*hft4iqlE2k^?zsww&D={a;imyH{f>v{)}zk zcB+$t5d~c9-htkv4WdH3)~hD0f6LJmf0Q*1$~i_*a>8rFZI27`RkWB((r>+4pqVu+ti8)H0 zQWJMyx>)LOVv%KWychCrVKe5&s_h0pZ$uFTX3VkhE5#X(6U3}SC1$d6eyI}4>~1!0 zlB&u1zcmdW`6JFI9ikVC4igxQCcsc3n^xX`Q}-i8!(oP6tYL3C?E>8aD^c5V;X-PJ zHn9d#$;&P&q&|Dzss^!F!o}suHG9ufs0C5>O7NQkOOkqAQ@%I3ct`1XXRHE7qil88 zMYHBGam}~Zdxvq=wuX&98@_)%4O<2jG}n?3iuRq#pZnH#=rvYX?ztS%V(BY#xmn}W z=-LRW(D)V zYzjiGsIFg2D~j9;Zd>(nt2S_OAiyYH@UFh-ACYZXIZXMuU7-(9&Od;2=Y7?(3RWUu z>#n!WVk&TNhceeD`mKOWgtRW7!FFwF@MwHtUS{;S8!MTql0m?`_dP7_%p-I<;ay6y zX%u9kR}JW>bgO-uH-@0jLsmFp??sz?!oQT4hThWFf} z9Q*oswutBHGMGLbQ3;MeBw(IYfJRxTAnR}cYvVNs<+K!(itVz43gFZX_PhwcV}LwR zhHdgQ#l+aGUo7(8EE^|}yryEJhEOn$dzV83u5i=JpP6!zqRnP)a%;=9g@suC7fUYU zh(;eNb_o+yevN9kOxA}i;N>SEFxU;Mjby=p%L_RPwO~Yk!@|qb-|bIoOd2bd|IWgO zCSSs@>F2vQ8*s(1wkLr5ii(?|z2AbT>8z*Q(-+U&i3k2t6Q|kJGP%taKL!DZ`bXOv zSK`5$`9sl)*NKVU)k63JY3Vl8xAukzTtBT^ z3sj#vUpCEq8ti$@`z$@IFqklE^U@Ks$6-}Jtj*+&Z_hS!(pe2n8!}cO_3LfyqyL$| zZF!h5w{0?se`qa!VY8n1vxAUI$#qSNp|~~Uj4V~)&eeQ0E;z_anw%{5(28{L91F?s zYYTQm7E(YZQ1=-aOaU1sj)W1=D$r|eG5%Tj*nk) z7cHiPEv9q0ATZ>5(Qte7-}Zssfn2HKiBXKAnl*uwhi^RepyFlddr$O`lL9Kdju0Xt z$%?HvvG$emviG#deoC0pOy;c)SOqU{FYWZVIep$y?^r~ zcD*T9q%8JJ`S_7jY9G_eC z_&w;ipX^i~r)KF(A@JBpIlp}`SFH~3KZY18LUoH=BMQIXHAW`2Ej#>&mMoXPq}5^V zncvNx-?wOCS{tm7zeLrIlf3jZ{2pM79=8aSx|dNc@=*h{nB0_jVR9^a$~b!Hhxjdx z78@ETUetv~M@dMCnV94`U!2E{Zo$)`$#_J_581CzX*V}}np@bJ1;zlui^s%5HFx%hUMB|y@%TA>Mp4Kz^7h0IP|VM%E0?m8Mf&>U#WS(0Ah<#t z>s{kCEkyH?-#2;ln)37<*A01NCQ@az{@J|&q%fs%jhwL)UHA6o-7??m`kmsi8mt~F z*;beD9W0zlutk)ROE(<@dH6@u8?}FEr}&^5 zDA2de%RZY5pgaiA`~HjC0Hc}-;qM`hs4jetGVM#67;0~sA#eB3-d9xSd+c!6pM#XB zfz0Q_*HR1ru}2rFcrt`Zg|v8$=|-j)7OK#43T)1g{HQdimT|k?SEv|f{cQdeB9&9mkHR(VzF6YUW}KeRj=Dop2L)zVLRY8O1!!}+P`X8 zZk_DO!yCFS!!V&&{0jeK-5>F;{f_lY=t(=t=IB#1atROI&1dE?no`>K^Ed%XQqoXt zviMWXiN3epGm$U|hd3=rx7B`y2)?Ryo>|BUftj* z`;yA+67(*Sb&nqI_uXg(6R#)Y|HcwKz1`L!x{$5g*Yyu<4<=5&G#q4h5}Y+W!n<-1 zKWobr!nPt}q)O6)zq0yG1OucZH%p0Yq-mW?l3nVZ^=6C|VIsNT(*_td6*g~@TsD|z zjnTx>lnK!DNlCUvJ2|Sfn2#Lx#h4ofL`qUbB^Zcv_LRrFhQFk~Q&yvlvf_DDnIY65 zi`)5sNA3DLr$@FWD4t9j4WB&!JpzKv_+hYI=BlE@f$PFoY|~HgVu!vaZ&FCUHC}8E ze?jr+gT(u`xx%9!H(3oe#SHBQk@<06h=z6nvqxN2d`Awsrm)5!39Lw8z<1`S98!Z2 z_v)W7*~xyVZSF?~E^g1?7b7*Tq7zk^ZL0|3aIh$bz*UNc2U*cHEFak=^O*5>naE{( z_Q=i@z86y}`>P~cLvX)RId|TzzjX1BlPW+_J5XxJKl;QVn(U)UYaU0Hl!y$H(~6{( z$@|2792OSoh+b~<1m|499WUuN`GUUcbtnEN^Wn23mq6d|?8a!u1?qM305N#tPoXl; z%>&mOk+%_EU&&{mcoU7EBNU*rf!^vv3CM6#Gu_`jmL<0_aBuj z81g4S!stP}vR&*N8+)cdxX=%*e&3umkp(o5t5(Mv;EBI;5Sol}Z+thaWxSMBZVBs2 zHs=4b!1ART)xQ`PR2ofkz}mva#rwOAqHuh860cS#CXF&Ws!YF0jg1)b`pvgm7T$3R z(mbJ_vrCDJ>4x8eKTzpw1%@w2pKe#{+ndMD+Z)b$jfZ}B3*N0HkuF8P@)W}jf)!SB zJ8sp7eWDD7+q9p>C4ZUDWmW9S*N6z;U@Jqz=6A1mDqX)KPdSMkSaBuTkWW3wlR%)S zx`byE(S*H3{W|o^cg7)O3@!|+r*C_8PUQ}b(;LpE*$|fxg8XC=imTP1Nw+)Zz^Nbw zz!qTXT)R_?xr7;Ci1w^Jpp`D_njg*e1Q?9S=>=#BD|}J-vWduK1TUs?>9XHJV_`h^ z(#XRj(YuJ_UkHB~HjB7fL3U0a)30M?Z;r2+S;{2j?`3N*rGBTZd`Mp|Yjv1j8j{!J zs@|3@chS$f2<#j_cr$!*Tiz)mFoyHC!^cUQ@>ez`6`WKqm(&zoR+4k*rw9~z`}OVz zxz~|32OW#@vYK=?zKJy2{u%5B+tP3Bi#ZHD54rA?=-(rWmhA-uT-GA25xv`N3ZcxJ zxo!_ePx~&f^hV?|ifHS4h3$U#U*){hmBOWUW3+l;BJAdvl-PPNg?I$hPf64~C2AS9 zCbhSJ4<>_ehQ}MYg*;MEz6I=ZyfiJZlGLmSA6NZKa6fNk8vT0GUzee&1<@b7b;eD^4@NJ<~@ygfy#4BW$B8G!E_jeK~Dm?DA6zphd>#iuWCf?|~ePLZg~$92$= znEEU6N$X_V-fqvcAW6)8Z}O;ba_Gy_wfP6^$|C+$r}7^XMx314YLB2AL_= zF+o_?Z{&<)Du~=q3oHuvyFRUGTG%dVA)n3bBjUcyzyIcA5iuU;eV{`5b~&(&XV^S& zo-`fyHnK}3gs26@yj&jsf}7Lep98q%Pyq^LbQ6Z9_8S$eyGsXEBJB+4RnwTC|6!DC zSRg_BQT*uE$peMR*Rv((RdkK3VpT6;ntalsf-Xg=YprhTf*APxBhmoaPlC}duLu5_ zVh)Hc)6BRg^WEzFNdqyLQIrNoVN^|uuk1cMa-QOiSY1c>~olHc1b9t!X+RO z^F${V7!!;wnf7V7kA$xX+#_AWCNDc4raHqZ-48rJFy8rK8@Tq{c*-Y7A_=K@<3ge} zMUf|bx&i5z$em@*#*F!jfnrlGv@s&VEF_cBqoKaLQ9*P^?M);yfrm^xNIfm+U*EJd9cxX|~j1!t?L(iT! zV^!WYK^v6wS>n0O?8B`=(`Mh8?8r7Q-+6K#yyAP4sEG+sg@XVX_cr#JWI6%x>m z+GP{nY@gkUFz?YFsQG9?VHb(b+9dk+Qz@p@8hM?Bx&JY&n%Pc5TIgDyp6^-5M7zW1 zEjORAkS(=){+!bg1ZHh6ID_?r`~b>{VbRLR78oNHPaQk=P$_;XliH|b7dd}i0X|`V z`;>{`%G?p+Ty=#t@lW?Z$-|5Tj?r@^DLtlaR_XJdtn`Z$O8CN?B(@lDbm+0aYn1-w zD?k2tnyFzSs6s;_gD?7m23E$(dz3svBtPauUNhxrXd|xS2!b8 z@d~k>6jrKT*vz?o@7+Dicf2w;W6C{lr)OtP0Iwur@KY4AP!_-J>YT~g1p%PxWjAFx zl!}d()_IV+=#)8D==7}PE#y}wGh68%$PX`hrxPI6x?-)?2}oK6zDM7LfkjOgZT`Y2 zlRQ9hd&jl2@I+ebX7|l$1My3ihO;o*K!{X;n6!$0pvc}w#fkq%(^*DU*>!Cji;$2K zX{5W6Zjmkt0V(O0ZYk;RmXZ=F0i{Ew1SABcLqJNp8@{>k=X-xR3>XM|uWPOu=RA+c zNt1Vy2&63Wgbe~;6rE6^G4+_SeUbhvEN_Ii9%%bWb+=wG-PGaFWBa2v1&LM5hxdfD zJhITE_j#5-$v@NlrAF=aHn$L|^)WX2x3W-T%!W7bV-Oke=b-BsNu9y z_;ciKC6BGUl?CxOsu@kK3ItirdeuKRJ>pT&df>tROO~P$o2=wXZgtzVh!naUX*ULj z9*DPuw41yT0H-8oPR;VLASve9^l+v*4WLVbSd^x(6h-jGqw2)>-{dVhh-gRcKYH0ESz#z|#*X}Y^>`+t!HK%hK;E_Cq1k-J z&O-M>i~c6>oZH<-bdr$igJ!Av`r1dvc<6zpET1AW8SZa158Ct9+d*OL!h-&fg(Fj2 zxc+_>u~ji@`@e@qSoVAy?&VLwI%57Lb7N4+KZ`=1@jb)KVPqi%G75{u8A--uT#Wy< z23Y7;RPd*>WPjtv18TsIcZVaDjJ1iX#o(8hqa-oY z`}zE3rKPY^@&a_S%UY!mk2gJ$DDM_4=g>sFZ~U}5T>DH`WAWGOg=qFFtl1&93I2z? zn2L9{&n@%j?)X|rXU+D)=e0*uMf=c7zFymC*E%tWy>vBQ1o46;s`$pOQe0eBDrz~- zebkQ;!wb)2d^u$M$n0F+7z>ovl^41y(c87KYB+H+(eLhy7VLY&g7f5@h7)j)cm=roSzOX zWzbb1Hx<7q)kH>S0|8X~zbB=HupkIau(FIMpXr6UPl6{)HA(Ytu|FYKn8VQV*0OC( z@A!=QIa>bJ-<;85j_||W$_FeZr$euJ z+Luhfxt_aR;I65v&w%LZNtqf0K5k?e9RW#g<(BaM_Am|%dvDyq3NhFx)?*7L_!5_Q zWLY6XaZhI_&-#lzzGfxIKz-IKM0TGKzha$pbZ~PK!l3uzx=|VjQ-78YALNf79o&K% zFpS=^SkmebS90FdpI)qwoh>VohD#lPzbqvGeoz78+QlG&TloyN8g<&bcHk+?MW^%?@*eR6sm&12^09_~1Qg15BKK-<2=uiWExq3Cb@C1Z{~C|8w1wM1h` zkPa1gd*a3v7{6dQ#nNS*o*KBJ6}Bt zGe#|&jZ!k;k=*S!5#OIO1r+nN9;*|p)6V8)iIZtbPt|u>D6!~Fw2W`YKSV}BSqCp? zknVy91Nd^KrKfXxzcyyDC=(KXnqfn)(m>FKPIgZ+gF#3#?Jr-KT8HYAypfgrEm=D% z);KASAxT1er>Gxh&**M3*e%X7mR?jDx)@vJ5K;d5T+(`}SB)Cy z%a})YfgT%74Wqu73532zopFfxZJp+oGrjZYnCifGz^#z#qvq51Bnz#csQR|1Pt9L^ zO#aJJSS~8kQsIl$kkYI4el%A=i1l^xQPF<~6fM83yyN!5!}ymt9W^eVBE7|uqI|%g~H9_{?_DF=d)ZY((HtrLL1>fA%oXii2 zFhk#sP)-SfUSD66?;&B< z|7ijAPYz~c*`v2BJ~Ov()4Q`$P;NcwyZs4)Q3QJea8{T3%)I01AgA{wd*8dFiG0*5 zb^=+LOWSVd4*6LHhS8O2&HPjSli$CU2ZQM%eDYy`IJ|pek>s_Pb_=<_0JN<`$jfiScd5Tc7_+_aJ?L?;SW*5HD z*x24Z+=8qJiiq(UammEpFIw=rZtcy?7*3{OnRa-|nkS0@;?hj7|XaDan)i3V<; zqLHo}uWI1Zu|*v_Q*rFfQD72Td92>)XNTQ+lry(DFS@!oY_@=yDU1Gr3$GvI4}!xw zjR8R(giV8s1|&Uj5Aj^SkNsHOAL-UP0j*Llb3MT<&Y+~QxQaNHBYZR+lY~}9yH|DY z{x-7gMA)&TCo=gZL4L0{|AtSt_dCI&q%cCLh{p0ot zzck;sk6?B=qI}Y})#T)xw?QCGgMteG8{69|F7!Mi{x#HYsYeNME)JyJ2YT-*gTLYk zrVMdmE#OkVoAIb5deriE2j}%qS1Suz$>0!LSJJ#dy0}`j@5uU8$^Ue;OfubZ<#|Cr zTm=rR08}cnTJcDXr4S>Zu0=SgSEZ5gxxfY>*ix99I0#{ZO`qaBejNSrNkGzo#`#!y zG8BX8J3v%RN=x&>w69-RzAK8T9X5*24o6g+f1HRMPXvNf9_+lXl9gH!=SxQ$AFln9 zWdxQI=nU*o7gD$hBCB>MZvAPP(GJ4dUWCS4rXvGKC&K{Vo6z90YniH3wuZ%rE!`N3OE7M@Es-z-w?d#{G`XS z4t>=CJT!$L6KrBf;2*(-7y1+e1~=en?;)%Vj5hTm#XJCU{Ld|Ii-_lRdbZXM!7T*U zF;G!~B@Vnnu!QgJFddYPzsg97?j(ozd^QM4;pHD$9(|c{F4J0k)Dj#g9@6x@TTl2{ zGGDkTvMUw|33N>nC8(Zvv-T%#%2g(m=Ym)A(nWVR8 zDQ)@+NAZ%bLY>s$#8x=#RvaXG=k!D+sJsSUx`ceVRR-d<8XjKkuMv-lH@x-% zTZOcnE?L=Fh%UIGK*5Sz+cS{?{SO%6dwMoJ5@8ZQX|EnG7)GN}#`4^Lt#)YjOP(OO zYj>7x-232C(nQ+6U!k+BbYNv3Z*}MOtUG=G^#u8>qp!vArcHjL{q#bf!uXLN%7w5U!SqPWV_u+$7jal*}ky`DdZ|ldpGxtm+Z9-`nq9ftClDv zY)}QUeb<%^TJcBHH8&2uUp#wWd6Au{7h3~}Qs*C(p}TAOvDI!{FJsflU>_S;b8H~= zzXi*Zru%<;-kp7^+=acSKo!xmj)TJtUOQs?cMi9clasUY&%h+P1D(Cu-%JOCgg450 zKp?a;^3lPRO{Hh{YZOukq)>7%2Cz*nd->%)Ux-K8*}*ChMLb~F01%vnA>Tl|F0lEj zoq?J8K)8p1^<_tV)B!xK*qu)oLb(BWHvZIL z3f}bnQFWCn^M2yq-ri4knXsak3f~*I(_h)a5nosaMC{ii2q{h3Km&Q6FTIDbv~yhQ z^2|O5XNJP&<{55Yt%L(uAT|T?K@{O^|7%r-0;tQe~y#pkmv2-A&3=!L`>PP z8U9I_-GF4;={JK5_1&0gRDs1u8m&dKo;=_s53~uw!`GzLpDvsCp z{WeU^e$*)LZ`V*gDfv`BV0l7znoQkA$=!khEoQ4eZ|cjj8D>aKt3>O8tb6%yL9POf zU`b$L!5w0gR%it=i-uIX@58%#Z~BNQ^A~ zCp&KMBun3}9>T8cvk|olnJ?VaOIq-h8YpV78_Gfi_qQpb< zoO%7E2;*md+;of&AGq8Qj&W}YhO^PA)x-}wiF|j(LbL{aBen=HAyB&N46He%%xmHY987L^Xs`e2bSM?SbWxzF`$n z%7Re-KVR>pxgT=!@bahw$+pM}Jk1f$1uSEzC>1u*_P&Mg-1;={ zd*L|xTn4c~12!Yhi{+&^YVxGh$lJec>DST)=u1Fwtb%P*;YVwRBiAGUjS>LqXe)~xDF9~H8Mb@y>}^ae zU7sApW)4<$lXiDSXUcZq{lWXS5qsk1k5q^yV_Bd`lDED_iMh_d))o-JYdTSM&_{=M@%5{+jO=$^_9AUZ+}P%8r_RLJ$ACi=a$O z#pv{STSM{RK;Rn1%XXC4sdqGUi9a`ew5wD4obu~H$9;E`P;~o1`^Vx(IT}pCMONUH zjV=G)?$v0OdmctM*Oip~@VW*aK}v)QqrC4W6Zg~Us^Xjnq_#t(G~azZv)wDSl0pJg;tj_gK4HsBx|oX;g*hQH(!A_K z(U*8U%5;~9)x?&Wu_M=Ai=+ zPnU4*=Xf7ac;uRyE^MU&lp`x^Yh&Yn1`O(-0Dk6-;-C&0=Wt$O5xuY#h$OfgiM*H{ zBd}Nul0%$h*s1n3m;|5LRba1n^62NwDvhLZncWAnBf>HcJamA&0@Nh1aWB_;J}7zu zQGerXyzLrZ&$J>runNbcQxuo)fhAEZTicFWJAkr(&rUpf@3qF!3#wUIJg>1J6K8qR zh1jlwJd9H*v8#)2YRX{6#_)nzhRl^GllM0`EaTBD8iW1Z3W5rFG(Ptj|9(}2FZy)b z#5caSGo_%RPptGyzVzDXd-RtV^Ec+OPQ@Sd)aQx-UYZ54#53*KB_69Q--&Ost<<7S4zmuylBF>K>CtEVbQKMvmcZENoc z873_J3s&H>#>IY(Rgu-Yl%2%*RxEOkYksGQPS45C`Kdlh*6|}>>UpVxh*09__2FvJ~n*@sktM179XPGc8jkKb5IlK@_ zN-Zpf6?x8k3^AlSTr;AyQeEEs$pd?&L}6Pc#=Pr|F%=s`ccPH=B`TgrE*(*f@Ni2% z_tE<*5p^w#>_#9$X=m_n)xGYGpC8YsiP;19Zt?dMXIXY9*Mc&~%Rco@YiNVp1KIP* z7k2I!`hHLAyxyY+1d>GRC1xm?TM#1mkJ~Dn=T8irCfN+L#AydqQ*r$bw;$2*|KV|J zCF`>8N6PTtZ(*M-B+s2oDc%RoVnE49I^G{E_1sXO)*=AqOO{ z;w(1=zlDdFmkWXxJj;*~b#P!7UIj^ku z6(KS(DWI3g31kOYTWp`+p@B1q$i*RE;_8ocV&q$rFrpp7!*8Y|mH(W*BQqh?&f3~( zd}TOVVGbUo-Gz3M9R#wz8a#}_s&#GIB7+W0qH@958=Sc^fkV-Hx`W?_pvu)9oRSdS|-On~|%Q#ct zZ#}o%Ddh-YdsFag&r^cP42`t#aINsMvkOU~gWh}L8ktqu0J?{|+06A>J3ed$V->Bp zkRN!xK>HABws&>pm-w{A0{;=J?BW0N=Gf=^iZ%O^T7XC*@@(Kry*%n<#G?DVsLGjn z`M0R(mSlphg0T)S$0wG%2WW;4pUQZ83bwmy2yTZ5CQbQ&uvhucPL_;^g%k=T1g7M> zdJR z-t_CPwYmVr0ys1P!a(>d0OA9V-zyyg*rYyK4EcDJ+72XPI;D)ioyb~)oe?xK z()o~`L74?71U?(F#R(tn0M~!S#wlP;qe%E-zSxXcLmplYHoYu;lJasoIz%aEV1-C( zKA64b=}!`}@X;$kMqVi9DOLRLV%AhGWX%Xue0}|X-;1s8xr<8*;EEuOgaL*23Ibzx zyfr0%y5L5mG8~a8HqCi%r9@Q*DX#RNI!(+bfpD5I+Fyam!$j@Y7a)WHEte`@Twc!1 zj26=OY))}q-AtL@Qa8+CN$r$pf?M*d#3A1#P{=yqmz0i9`lF`SvN9GtUb|Pq>L{o~ zZIozoLfnmk&}*Ksny;tYB@z_)6MSbx&|^n!uR>pArG?o{c8p?X@5-HPHM}geN$A_( zhZ65S4`S{!C-mqxu6`KdzCM_CjSKb zp@+?B>_m+2O6WMe-|s~1ZYkCOt)fX*Oi-kY7mbY!m`IttPE1~e*PJ|@ZK~(Ny){%x zR8lLG40Gw0ug9!O-U`}fCvo$BmvdYaH|xYnlp)l1Y9%x!8uz=aTB6lcrFNDiv0fdH zo|1d;r5eon`1mrWf`*>ytLL&>EuwxRyz=YQQAbMc)Eq8Dg^-h;tB(5cjPIps~Wc zm&y#=vY|?OuBqAi;d#C`*d&O@)LkHoG}y9ku7-mc`_mq_?=5Xhhk<;KSi*h-79*RS zUNo?>Yz_&eVvcAQ-<(GvxYSj)(+#_3nR2arHeBTOVq=Ah80#i0b(*0xA84Fi2DMOi zC3iQRD~-DjuYWEUHJje{`ptm>@Px&V@OMtVjEIHlaRcxEgPQ;#U>p0y$ zM3T=C*BU#rPDIjsprzHtDmQ`Z-5o5McZ*%3wP{!P_I=7viiscxeq0xgayvqf7%l$E zcYqfY;g0SU;^tysBC}Xiv)UEO(cx*QB-iMq9MNp|;krM#&$^$yD#w7oKg)L~x4Pvn zBQ4?*DOx>B+$hBRY^GxD-s0Po@Y>Kj`*``xNB?-BNFYsQD6nzo{47|N>BPvsR^MV| zlhb}%E%2u*w&WjESx3qqkNZsa65WY|u4c1Of;8sVga*GxCuMe}FkP2tA7rW-rdp!1 z_kX7e^Zv74t{dba=d=8z6(UEGV*2+aMs-5bH~jW(S8g#GDK7)>Ow=mb*p!}JHhCRc zvx^G+iT~zL&)5pv;SfCf15n7*&KM}<9*-qadF|&m<2(EN`%x(&azh}2 zWe;IA=Qf!-!MR$ySkrM=GNlp3e*(D|G@)v3%U73T#X#f~GNK}u1vXF#Fi3zlqW0&k z(VA9y!!HoLdj+Jw)!Gdzw7{k}VCv&2pN2{28%#r{Ydc-^@nZxum%#J`39d9qs5o8L zQ~@{`WJEakM0HRCUQitZ=sN+@7~#SMEBnoEh~g_x^#}Fg9q7*lViWUPlgHf)F?HYm z@oXIbFX^vuijSSHm;b(W*~b3o!KX=i&~w)8a(B{W#@kPw43)(Lqv*<&&H^@}EM z7p+ONCccKJ{jB!6bJ~HFgwVsTQ26ow!q~`OGc8M>6JPnhFzx*o3XC7g&4a!#*p*Pi zqqowC*0=B_O~ajkuiW|FsDT<@Bmu9@UM=I8c_2>kza39nE8ILe8eV{2PuHrNoQrR9 z-@!aHk<_|4-+nhuW(Hkg_Mz0PPoq15t)%wyEnAHwWWU>lrg%5J>EnrtMTPLtU#zI2 zD@%)xKbL8uOm{N)XL+Nr98Gefw^&4-yh>$YkWS5=m+dz8=>FIQp~E2+P#91^vAI?#O3J^Ht9IOBlq_!+0J~9zdD(Z^*Nr*jKh%}f5M}0 zlH+r{68H209YLh=G{J%_VqW@wgBZ{$#!Fq8pFh{W3c`f@J$u4*!Oiqt~A3ANqYI;Y}?|k0|Qj^(E?+@eMwwfX$MZ7%iB-jiP3b|qA_@1Mcm$S;Qe=_g421E7q)_ERdv*&v3 zYCYh#(6fuv9ycr@kJ656^wKKG9~{Yoi)_-&GZxxIIgP%11R>|e#S}Go6Mj_bjp7Vr z8P*ILV#kf2MKKD#ARcKQ25ZuP;nWO4afQ$Itidk9e-`UGq9D zUn1RtClx45d-y|E_VGdv@mqLUwXAY9i>S{-DoA9zY0or%iZVJcUm@SQJc;sqj4N4h zmk=Lp3m!P>iiet<7o=)}@WV?{0iWvGI0k9A&yP`8pCKZH(|CJTB%MJfCHITx&d%Jm zc9^KiZ`GdqB{|H`9(eb)*U}H{*a)oWZj1Q&R<%y(+KuC#ok)4Lth3IM{KM6k@S|V3 zaveBHng4yv;FaxCJvKi~E5TiqZu-UMKo|v`PBSQ1^6@k2-lo}bDt6X}18fmv_sT4n zaK~=i7rHMq&)!@KVu_x<61_e(AryNd<7 zwNPrJBR>0dI90_A{CJ=rN2lbpRNHnmrVDNIIsx=DN_{RpFIV@1tudKC-%8O^wy?|93#{5D+PSP_Z!_qz75V(tAbtH>xT z)5r+;GF4uGk!or$*b!P6DyS*{WjAi{r{7-31gYuAw<~YV5fRHaET&@t#J~s9ML$^E z7D>7#m+NhsodjqU9#&-*f~TnEvmr4_fqbHk*63!gAJ=c&hIKEc6{S<2N3aH=u#K)n zC1Jtzi88mW?6y9)@Pn{#Qx*{tas=`;dmh5n>HPdVtY);^{BB)Hci!V*4mGzXZj!_<-wwOpGqq)<(Jk`>QyH0FIFX&-g0U5vFArDl=^DrT+Nrb=8Cb| zS!~oY?-rMzzsU1)C1`qzBd@WStg7=41iJ6JK2EAubhMVxFG{o;lOgqsusq&P^Z z-Kkm$10Z-@awy?Z-lgz8rG(}2pP78Io(C(Q@&;g> z-R7~H<=(8l6k_DDR6|17x77!uBsO3h!e=;k;K+EG>3z1uK{$^?va3HHiHrAOxZOGgkI9^n(~B&}A6vO(DIJQl-AU4|qqGrGXv?-WK2X?*tNP*)=vu(*uy zhzNMK&G;fFgnV9RD`+8szEOH+_`$C*9{kt6xP^=yRIzc#2}N3dh-Y@1G*&vl<>uV=nrJzVp=4uhB z#x5yGU6$O^!H`7#U*E7#3sLj?%S4q{e{h4ubnXd{(ufc}zO}v36q)IlUR@l@EP*;g zuY=3h)(00%JFTK^LK+;kwk10*#7%+rZoX`W_j(V^U_a;$1O9t}h$)Q#5@Qe5A~{r0x*zT1?fW9A$xA0?xt}`32>X68jDH18zrfsxh>M21xM3mfYgXOp0;DeVjPJaj@qBh!D$wg^7kQMUwlhMu{Ok8&Q--piO=K&R zioM=a#Vf7e5&iMj;*II~?eXo+r!bn;(9qz&$|3$gEx<1D98EIl)ZE&Z`XRUcP*Jg+ z@)=t9I?7^Khp*)npbY~272nZu`@L+X|348y@`?TUkqII)ScOnb>r9p8{+|S`aMQO^RM}P(I!XM5EUKBRi2gAC@Gk~-_6K1H z!u2FC6Ep;ugO!GO7CK*%{yZV{x0@~)W(*nv$mzWnMii2Lk+q~cU%{=e2n`?WI9`=D zDMRnLlhHHlc6~dii;E&EtossIMZ7&%o~Ns0*MeFM!&3cw|62S;+NkS;WK%5Ncl68K zy_gf;GdqxWQ=dPitl031S^$KO^w zvSGhgaZymZet5Xobh{0uaUWM}$>qAfXrs}B@Lq#+`Vv13A7J3-)TQWJMm6=@Tg-Et zzU68ujX3nGD-Zew0@4N62G0kR&ya1I<{AWR^YXrxs_>H{Gpt2#@v*6{Gu`1Z(^&R1 zS9eu4dvOqE&HjZP?f_U8^E}$SDVpl4X2EEG953rZ(_};l_r{L)vlx<)0ILk!`PSKs z{#vNeHDdT$sSGZ+)Ye2P7^Laj5Mytp=Dk<6wtWN5On6{^Ub-#-utTJd4?1&n?m!G^#P9#x8 z7Zbacz})sPPQK}{gN`SLK1p2N02@r&xt4oto9qgC%U^L=N1urpT_Q6<*6=vIvlp2) zz+eJ*E{}CC#4J7C4F`3>mwowqfzKLj3on?d^EX%+vFN8yD@68Nm;sw-{*?%J}4jap<#@OYY8ab4z&{C1qc_X~oaG z2gUKn$murUlX(fFP_xB*LR;r5H_fZ>zNe}aG!1xO<>;j=1tm`Ix|n3^rpUE+H(+KKh#jA+E|b-(y;xOCRud>gR)CNyRL|WW;bJ+hX?lTy{v# z8*)%$P)kR>0_#=i0D(e9L=E;fu_4c3V(N8-hrj=zWgIA$F-t@6*yfdaa(?Kn5DRI< zehXIev0#R`iY>0GsfobFt;RjYqQoDbY;+}oAqxjUc8+DEvJb^kgojOflNfTl{HXB-)fQT0^LvvpXrk&S{c-+kt|Me^h$;-q~MLgCjC_C@BuP^D^L3EHRN~;Db zY^GJZm@6&Y>b~5x`El#u{PcaVgEJp-_ZV*>ffOhfW<$-;Lk+8tg(YONH zDj(Bs;^Osr$@w|v^8SIknWiVhQ?})nZ2Po7d>Lv-npNvpArG!M*7^8%4FiogVzZ8t zuU+0~{n9!)u6Q>xKlPq{x`O`sKc7aSgKd@W3(SJscNy;CQ}v-!&U1+V7Z#G}l8g>2 zgw-E@&*dt0B2nrY|MDusYH*R12DkV$i1Ar>W$W(eWc+@<`L}E~n)Y|FVv-DO>s$n% zeUe^dA%&ZI&m(iyj55ff$I6hTwn^aP>CI#(*C}7YOxoyh;t=Fzb~4#03>Z%aXcEz2 zvcR>+w|gFo-7vRtMVz3Rv;HUU_JgsQ&%b!;vgM^U;#Y6oKBNo6p7B_UesiqD3NvBz z&WJ(n2B&*{seWAvvCdi=miU(hGO>kOHRp_e3Sj1 zOk)$tJf6$>M~x`IFJx&XfzAvFk=4KlbE_0L9@z+UbCTEfwf8+!cm+!cS$M`Rh6NKS8*UlX`Df zkuMk%BFBnd^K0oj3V@4aVM9YOV*Cw-hsWI}D%xsioz*U#CpXIjf!hs`A!xH8o1zt( z#0Z1{4|AX5#*r~x>U~y_!?i1e+w8s{^Rai{#>~!YEYclfHDsSp^nY?9M<-V4z0v!r z&rGr@tSj~vzFl-PEi-1_=vJNe-dvW&oaAHws*LGBIo;mWuH`O#?$Bxp;@+L<&@Hv@Uf4KSA@qf{T zCr%7yv%+!I|45wu?#)wBw@b=@vJ183iI9>E#hj)fT8WWWx$#~Xnc46{?Sttt9XR@= zYkNtQEeuE=QKSmTnh=K61!N1nAJS>jY{nWAF~d%uSXmF64%Ks_(ze{jILi#~F~#i; z02SDv&~T;ybKG)vj}a4h04X}lH|8<-EX(o&YSwlQ*%|^+)S#3$3HBcdm=PBrXOH1kA2~BB@sVA*Jj&p`9 zEbPCjH2af?eye4rivM5}L(F$*{15|Xomj{F1Ar${9`0VCNcbPj;WYY=?|}E&hp;jnHfb$#38YRoMHugyU5Czkdm~!2+#+~-CzaD*^OOEW@9}_;Jq!uU@C;rS9NwnnYjpn;fX>1e}y?Mh5zSaVW ze+8#%{Au1_pT94~S6HB3<;Ms{^szNXW!hiY$RK+!INpW9C`{Jm8^>#v6i4hLp&met zSe0h!19v#AM`gd}m6r$q`)3Vem4)wG9t**3+@?RUzdi|JVRw(7Kyctdgets#3T)9P z3(l9(m= zs|v7M-SOG+hOYJdUmq+;^D06cd7-riDHiFDD(zk=KGMsV88E()W#q(YSI2Q$1q>`M zG~iiYY#-&nF$qVy<%M_M(9E3uS)|2)E%{L4rXg>&Z~nJ1q&k73d>q3&<-(133WvuE z)dpJS+k_6*?AI}D!$_ZMt-(UxZE+*b?62Scn`a=?HNKY~#zB4g_#Cwna~Wou$T}&LmE+|b3fLLWL{U@**w2{_ZK2=CoBJReR4 zq-0y4x{63=x<$5}k`m^)o--!M2KCW`16Qg(kPvZV1X5i7LHU^@mID=7LeE_gCsOjwzu}IKj&4o+@)q=maR9!E zhY!MOL3rWU&3Q3Hrz4%);y-qTmZ4AbJ@jjNs6bx*8!fqn<_O5OvKO|Sc8>pT;@)f@ z)v=&=I}$<`1-RC~L3qm0dx`=e#pvpj`Bv6s3xTgtB_f8YkMs}kWcyw307r3{UNbX` znmQg8P6M1lww6O<81z2{y)1&{g!uB{kSvWV0Y5j8uyc!wkd;R2|9vclPU6VvrA%v)rormNeh#5W5XGhLCLGY9^k_a5neg-_o2a@Mrd+Gp=~p=&$65=I^Kr$Zi#^(Skmg5sBs6X?^&Or)1LqoC_6y7@CUq| z5#+7?uq&wU5p+d>fOni9k(>f?t`9~#Apc#2FSrbE+a0|-VPI3~1o#3j7+Amm#Sk5> z1O}`J!qBrcXv5;&zJ2)c(uaL5^uK%dzq5T#orLq|)#ZO3pF0oWj6mu-C}HK!@d4Gv zl3l-)uG@5&7P+stSC8mLc@250PQnMVZ#!EV%EjUIFIxr{Q_nH}*12Q)3TQfDa?g0| zuVdg4OA87qh0JY<>G$s)!G^$^Z#B3^xHf z@7y9PWIJ}<^5I0xMZ0!AIO>NF!}Eum^R7gegk>i-*UdX^6u?$|-B-iK^JQPrFcUQAWM-z8~uC?Lj`l8A%5?WxsHy0%7&_secMBO8W%kSvy1#l69 zL;yN0dztf>At0O0gb&6dFbxPr?zemMAmgrr=Y$rjU&WEXKt5^HbL?Dx1HxH^G8KYn zW+Z!S8c-vgduUTXfl2@*FJirYeGO+TZSccX*=53fF8k>Nk)V4J2=@0rzHY3*!3y;R zl1#&9bzl8r1algP(e0bFoSPmXEqKBZ*vIK;D>d92^VTQmJXf?`d%@!&TG6yEI3Tb} zio`A)l~nPjF@op zz|TqEAYwhr5W~NSN)?YHaV2BpgBNjdfaFB zw7`?!6DHyar8$C#lP>7Wg!o-#=StVUB^ff-^#3n6MszeCem#4Hm?J_gVS+sS4DPpd z5G%(leR@4B*m`O#B#8=PJB@G0(!lrZq2dS)LF9ALOZ;c8-IM6C*O{u_<|{&)9QPMyo5VzLO8Bj?F)AQFUv?c;g%Fb0xy{Y*rtRBWWVZ8=ZV5(KeC$n%ICM}cHvGrcAi~|_Vn>K8!}k{Rxt6H#JK)W0qm8P} z<61=JSA>(|J6&mK+ZR6BEWam8UvgSXWnvJ}qHUkmR~rkClDeFCPYZTvM)3#H4p;hN z=a+kY=3nIP>mmgi{((KcT$GF|OwV^8Km9)0n+3 zqJ_S%60!ls*oi&!IWq$rR5(=A?n`_8aGGQp(L&on&*gl5wt8px`eMhMrXKq(f_Xgf{C4jV=zAX^aljzO{O3dtjK#5GZ~_q)F(h}Nb>(XV zb!>~%45(|M;z6KeANX^ZtxZpoc=+6T0O_n-jmjLM_)^^|aH>opikE4NQv=@lns^*E2*n9XLaN3uC(W zN5-u-FBhshYwa-F^+iM4jST#*Tmj9Zd)lK?u?VvE)!OOTeI5o)YG>A>h$ox>-v{aj zxMmR8E6Bs)>}4T<0*DJ#`08*30RV>YGX!qsWa=3Hx1i4tlOgx)P)AkGb4%j<3RUh; z$8NIjUowMB2#8>e(6;yp4??|E?ma31k321KH&zftNVt3_YWs;S-hE@OoB4jy1{AT4 zw(CfU0#$>e7oT2zyA5$~9>(~7ki6H!v`hf%lhcu$>yC(r%!nHHCy;8=IZdRC#3rQj ze?<+)ADSNM!efgdBz(3~Z@AoN_SmfIj#G0QQQ~vlX*t<#n!idxjNN{hYI!@`QpNv) zYpk%g2tEyPgr)#-7l9MStpMCnYb?Yzg`s*dZn61vjXeqs?80-}>BRv-AXPl%mm%(gT}L*6CWxAa)F_`C zO~)wcZV8&AqGU#hxfQB@_dZcb{c7UHyo+1ec3s4m z{W7JKxw1Kd`U*Be&A|#)ZJO5;p61h|O27V}=@&IF>uMk$@YiPPEv;--WGJXIQxHKR zT@Iw7Uu?t%gHqzMwY(Vcd-W7fh=V1Kt|85IoU$@pd5hlK4gsMbGKF6}pIATu z!b80B&UwK2_s8TJ_xU?e<+$k6$II^J+<1ya60rUS^)YhW#q17J;Q%GJq|CTJmjNmn z*pgn3TeI8LIFiOXR-n%AERe%HJ;=EMkxX=Y+M*yZ8CD=4$!qmRkGTUn z{bk7Ss7bN7O=a)F%neE$1QUXv)O>9y&vv%)Nu^ToiH=Y4j}2%-C<`p(zo;~5M-=Z? zekZ5U4GM!MO$zYRszFL*U*Q8PGw4LoLV_mCsMF%bniuzND6idtG^|_ySuAvXg5(m9 z3tD~gfc3D%Y$z~Gy?tk2Sls@pNGASE=fg*lrvvZXJf~f?E{^IphDeszV{CNF3{$^L z|E@58Jno)5W|uowO4!)vQP@UAaD?1gQ|Xd=AoIUrGUSGS(&D*+^h^KdG48G7gu#rs zokO@%b^Y^6>!CN{4%>at2Iku@l1FmtikjwiL8X{=Z0xkrXjx}Po@?=k{@bjQ%ZZ=x zoHP~ANkPL?%68g((-)H8smINXf&=w~+t?dPjvKDJuP7B3r)xoNN(b$izwTEjLGv2| zYqGQV!~(W22$1@?fG~u2pGb=8IV^yN6zq{m;m?1X`@JuIfnp;jkaW_)jb6ec0`rzU%n(qvNeVwlC}T%h+9)%n$K1;5wsfw zX%}XbEZ-MCxwk50FJ)HFh~mfAIksc+IY5+mLi`Ts!sRkC$XKXlV*Zxb&Fu?MkF4`# z6g}6{>aMjj=vB+SuOMi{-z8QYF~89eMD$3zmYz-HRo;Yf;$mgpiFZl*J5?#WPa z##>*LsbgKkCw}s+tt5MvXONghc0Ep+zU3MZE0lYGhBA(7)oYa*)5YSf`SY_Et=gSS zFP7CE?KH1D!$y#1@3=-^&AsiNHZ-kBv;%QUd4U?*_XX6Dyz|&bUMx0r#;K#uS;j<) z7?XxBf((1%1l+y^qLSkvzUvS32hqOOA;Pz+MskKqymjSy-eItpQwOi8Eo5}K4L|0w zEy*XsvF%<@dQ^Xxbao5~qV48t2XwC2r`=-vFy>8^r8)if!1qt*;*0h5#_j0#n58bf zIu_>8Tvm2=i-qscer9>|L(RPcg&5tJFJGDgPYcbS%GT>DM5ShWy1Vesd%J-}zcC4s z)WHpqf#CNd4!2w)-5%8VAeD6LDuLzb!`AU5Yy^rD$_Ekz&b{H!TlD73q{Bw$tJkJo zb(@5R8;y^oWymU{h_1TKudDHGZj4n|nex#zU;pO)R|oxzY~OP$MCk(YyaQUUl91yh zD6lO+C1cMQGg%wGG$Alw^0{ViZ5Z9eD5Ih=%*K)iEfh2M=QvM)q*hMl;l)eLV#qXfjZ@Kq3tyXo@yAg9GzY!snn zceKrf%0ZaC7{0{V7=htCXR$qY_sJ!8@m1BdGB+;r8DDmTRDV#frC-dE!BUmRfKQIt z@n%Joe(Ur2gev7iH5yzy5C_1f+hV-L*%g1z&;#C41$;?k8-gO_lp)6?%L15+Jb2*^-@|8A& ztlJ2X5bpq34vV!IebpfUV0rl&)gSsf(}?9L2wN?$h%~bhTORPr<>duH2VGqqPwaXx zwu&WE_jNdHRwyYc+3TH!+_MTpT6I$zKQwWh{ocqicmB+#Cac)H%bnJBg*hJ1n!x{jP1`cI#Z=WcD*44QAx zG(eLApamUgUN8R5})k3qVfLK~{6uhfHl<}23H z+P}s&?{IyHnD$OCmJaM*SWavvvFUv<#+6?nnnHS(nsyvyC;7x(0y`$yb4GFX@@MTx zyoJS^>sOv-PI2RgsU@l@+#eLsUYp8%J+yDhyfs|keK;V+^wl0`kl%Gq!%Aso?`hYD zjD{s1PYI77+F#PMa~6rz!c(!NOV!)oWU+s&->ar;#H8d*&%`?1MdK`}+;e7LuI-z| z4c5PUYrUwIzJiOzap+6GLa`cpL#5`g$V#SbIlB}dH>=Tz(KNX-ISk|$Q%}_d%&2bM z-UuhkThpqh+T>>@#mt!fk`bNPzQktgE|s>A^x)ff+;crzK?BwU@?$nx=~c~D zLzwA|!Gpn=B)WkEA2bDebqZ_s0_8`h`!1*RgSWi#nlN{oNgg^6KN@v9-xvC7-fTTgPRoBWn`rX5$ZrpbnQr~l$w|VVEqf_a`qiyIeKyKiKf^?A@U^kgX+Sf_f zOM=U@Wr=YRwP`2;-h&vgtWun>N@GI(z2cR2x+RzGv;#Z2>snqeeBgbr;wl(x+KFs( z0Ch^j770zO?C3ttyJvB!{sx_eNz!&xCm2GL+xIR`>&XEP%gdh#v#2Vb00Eql-+umj z_t&MNZB|X`E^hmI zU+KFo8dY9wNFg;iSS>6bp^+K{e?*6q0(I2rRI?67?d?K65d&amiz#y0+>BubW=myD zv|_sI^G(0RoDKv}yf|5}I$6v20#LTkU3;V!mOM1uUPcEEDYPHCh(ulgw<@mkmYmX6 z&0w(j#x}bDQKEbn9JbNB(sA-PxB8j3?#Y7>&#UPV5}sdgda_#4(x<(@RalON>4t3| zpMQAKdoh2Wl^Mf0)WAPTS)%^Y=D8(_W(%%6mX^w(81L}IH&voFciR34Kh>ORET8&& zPMogGB$)8+QTrw5*pD^Z1;BNf?zowshC6qa@btuv`WxJOskT)60K4*Bi6JPygTT1H zLus|z7t?N{xg2!dHVIaKRlBD(YspH*A4#vyYsg=W-QeRLsG5(s#BJl-jvBZfSHer+ zq$ht>M`l!pl1uhhZlT4=yEPeJtH}=HxA^fE-UH2>o?4$S-vrJ-5-_bRN7d~&#$@MH)ggLxZq>U*J7P-TlVg-s2t(<~Zy zQ*&Kl`-ee`cm{wk{i(maVAm}PvKh?bwH>iTh&u7hGuI*maEZ-;2M9S)knIzMq3)6y z*qfE}_z@Bpo(RUWSK#Oy3VIM8`(OOf5W=2|25R4V2w_+(>EdK24q5OtI29W>SKBy- zYy@t{>3(?0yo{1<-nSytWS!uJ+J)K0VF)G!S`gPhDc3{*T44@1mrki=IC*Ft_}bK1 znk^E}BN5C98T#u3z4*qP#V-q9f@y!;d!D=GYOo?DmoxZex4Zcs?g4nr11A|ib-^$iQzP?T7VydA7JYsP^X`7~hQp95xVmoKR#C zanjk&njxSBQ_p6azI(O7mQ%mfWqiTAzrPP@5Dj3aN1!mPp7#Pqm)rGP-lwNYCKSjF zQSc#kOgc5~`v6XDHT;{wQ^S#?BCS)C^y6Z!^||}SUj7V2 z1r*imdiX=)>bPiCb!cS{Xk|t%eTMV8+?>@=({I?LfA;>5yO_zW85Kkcm{w_jPE%uE zy`IiF!SS)WM1kOX$;)d>S4VFdYjtavTFA?{k~Kg2fDxlpvl{#c=4O z`KcD6%(ehG#`o<;9J(ElqtU%{ZKbqwf)U<)5FEg-;slR<7p6};16M#8A`2iV5I6|x z1~X`d_qzF#L|m_evk@7ExCr3HZeco;9x}!odd#0tR(AotCQ7h&w*E!S77$q8I%R0I#>&GB*O3?b?5IuK<)?_1 zrlH|Ri}58?+q!UE!S{W!UVILIysu8brGcCTO&wen2&g=P+Crx1D4AZs?=B#3VuFDS z7KDK?eh@s~y1cjpjNcDu1B-WVcY9!v^CN_jfUcZNnXdSYM z&Jffv2zThZg@pw;L!pQ(syo4kf%1o0>v&DGa8L=tJZDybX>ShZBEEuQ1MiJqFSz-& zeG*VqLyKQx-qB`cTwRE z;3@@$HT_!p-%(<;bo>_X$o0oi8_j&2x{81G>D1MywRfeR{I{s8^=77Hl{p^aW(lbM zi^HhBUHMt|wr^ir{0WK&87)WbUGLw4awD^HjQcMNDYV^&wgUYGgroe=?@_%D6d!O+ z{D0)r7ngr(wPR`9ytYpB*S(-v#&4 zYRALHqlQtx?dUBLm&gKZy3ialmhlXhrJFHNu0)~nP&~Pcrrg;5XS}#3e^X}b>EG!w zG$z~^lpil`@vaWm)U3e(xcs_Ha;5C6qor=oxE3K4X<7E8EkoZdrXry=wg**z?fZw9 z5|>=3dNnJ~6Q9Ne%jt<{>qNWM)^>1Fcj|9_bfajK2{u|R+$xR5rtV_)I`+SqiS?*c ziP63yi51XCb^n@HmZ&W%?bGlFxQhA{o*@H%DynXK6ASbNfecKK@j?vpw3r;9x*FTR zCm;&Gd3gTS$Ly~LeM>_-(3hE7*bvFl)^I3ObnH^q4bTN^-F|`Nj(+{V{-Xi?)ag{6 z)1JL@`*~sf7E_{8ch-cisHhi;zfBaZ2aRrMijJ1A*2~X`W%X}M6u+Ui0i4R`( z0%s|NL;kQi?wy|}qE9;-gS5VRVRhOZyO?*=+*SzlxcytiSBAQI~g+ITU1meJpfN&*0;=E5`%FH z8c{#RHK51tBctkb&nCOO$nHGu8yf2V@neT|G!Pw&_@+W6L&Kj}Atgft1C5Bd;ZyMDa&h?wB7jeMrs+S1AR^R!XN>S(;N;g|BM4dJc+Y`v+K0#3wAr)zTOzew)a zs>`449sP>wPDXcTxzmkT!14NRx672wJ3ekJthO)gWHuzXzk zxJ2gdJx4RI(1jL?cgzLNxz%I}WjMSX-~3d{){v3EZ>rVtTe!iYec7cd%zgp}h$DG1 z7rR`S|1hbbnv3c%pEYY7he$L+boH9fMVFho9YPK#?U8Fse0%5Sso~V*i3dp@)FpQ> zlqVy9cs_Y8GS_r>{-%p>T5DVWG@w;PMCVJCe68|VH61=}XWllbrkoLGb!eUb#_Bh9 zMqAX|ex0j#h+K&7lhAi=-p6clgN^;kemoYf7CiD4j70RxHo{T=-0gp!nN(otOWD`a z|NcTZ%}tR$D+($i4XgPb%KD;5ZL7=1FCK)wW;h@zjOx#XYA)H~Vj-gq}LO)J1<@`aZQyw`D)W zHKV(vmKEK6E)aX~*O~E+IHpAf(TVgKlF6?J@;a4a8kd~xL?6|D_bj(lQt^(Kd}&A1 z!E}DxC#3Osi#1NEve29A*k-y^c-+2|?-O%B-m2YN#BzaP*7quA)$gtA&=kXNjHpq5 zmE}VW$sF~}(baJ~K^g%8_gEAeOls-zAzA`vhfN;3k)5rC$}g{u!ei!JV>mt8JYsgd0I>zz&>CIP_ zf)YDC4zWy4-FOq_H^%3TE?X5Cr~PyM_%?HlZq#gPGdUis9hldu{`-a__k4C>b{E~< zo~EEgO1ATT{`1Z!_ThT!^elzWc$#ALHJT5C)C5rFEbgyPN6%LNv$CF8GBNh$QihB6 zLHe}M=Ux(wmElnN)CH^R(I;$<`{>%&s3=v3%-GOy_H@r{u?j_pVm?}-;h;#*K3JJ{ zqOHmi+||qY%7{rQNq97?5cK#Ffh)?e^`?n~YcRvjPi1n6S8AWXDE}sJ6EK3xD4_R> zzMbJqjlt&^i@Z7LRXLC2r=L?Z`+X&WZ)G70eu+&*&yq&b34qZw%o~n6~J)sL3HDv)lfA_6!nc6+2^WzoM&H0?6eXS%LTD_sv>nkc+ zy`8Axl0#QZ!}q4}{BR2Lm$o%mDjwFbXZXvxHRa`&;j3&2d@O4u-u+VYyJveW~=CG z3Q$%wI;~&PyYVdozYjHJi11^vCUFhL>{FKXJj~!o{JU!CdZtx`M^tL*l-02cWOP+= z8w}rCD>z&RGF%s*UJVW&!nY2Wz7Zsy=I4?|yx$qFxmSN6_KASNs0cHJq}q9FUfu|0 z*_6Rn@eigm^}OvcqKs>#)*W&d<2wQ|{`_DguG%~1LCq`CTD zbC!`$z0^@>@~M1sxV*hBd%10jyPn?Ey_uT!4y1~=>E`O{x?R=FgQKph1{*6turl7> z&$M^xgNx01hkto-T!Pd4pL>^IE&unBkP3Y8+O@w$rR|yLxfko?y{ihe#H&j_g=K}$ zU%Va-#@`r|$_V=WtF-MWW6IpXyv9>>KLMTOPp4m=%aA&%W}wigKhla^#d;*rtsQhz zf@z47UrEJV{B#y)V}fs3B+lfdC34=!JSl?rXIXD=_J_g#)YZloyXC5pV`tR_a;C19 zl(c2NWv#>l-+P87DGw+$<6K3iC%T-k*QQiQHKU0k=MeGDN+su!Yh2kkmL1yMr9(Yr z-n8PcFsMlrM$ZOa^St$g=*TFfDnq$q*Jj{4T4#na>+Rt1cw*y652rGo-yHpTQ>$ks z;He(S@P3*%K5#$H%}-KuqRD0a?ICce7>HgV!x;Z1ix;j;HY7mq+`$ z@=(Eo_owUOxGr2ChPjut@-BnLG9`ozvI2joKL$&hg?)G=@HS33oG`(?2lMlNH}!&E zQr6Fh!!c`AA%xc-(YtDT4^!7tNvPiWMx2hTr%FpN{@)daeI;p;|L>KbooD=f&8)v& zQnY%R)XgpOx?k|)n)ZPwfit?rLGB-}_x_<+rZslf!NDG52=BS8jw^gCoYk8GpN>)O z>4B$(k&?1p;my{@aWZU&%ZoFSMa5Z>g!%CB<%Cjw5sNNm=kR>l&|TcE>!Tf7O+R(r-@fN=n`p7wvy+bI z5|IiVC&(ssUH*A1p-Y`Jp;zS3q_T7goeDwsFFNMl`8?kL4%n`M%HmD-v3LVne$`L` z!{48KNehKicY)%{a{3w+Y@zt)=}n;k><(AyA}7P17jzw*{Cd>YK5x}(^*-j~dN1VD zg;Bb|gjkD$a5Cg`fCpSTx}#`D1zF##$ar+r*ul zLEglT=cXOeK~4|8_x66gumij<^%LTU9q?>g}gmefST9L|_P>6<)4_x zpJ~hlrJoNTUABB$b~!W*yh9J{&LpJT{h6sxJDlmKF~nwRSGCb36+khTpW9iDag z)HkpHLO#=N1+Nfr9CCC&3VB1@Z&$MgFoUtBGd*tM7SmBxCZDZIV`oe*YA^)JE8slP z!+h`rE94dm*-674X;eccYGVv^rGOE1DM_BRTs8LDgf8oXV)i>q7DFt(r!B2 zWmeTrWuA(?uRM$*?pmFQ)(`yRGZdzu^RHfbi23)w7n`l=*p3NeE-27hvOmW!( z+ij5(bfv(0fUc{fv@AdPbwal8Co)|;GIZA$6Z#*`22C$|H;LrCqN9tj*hyeGa$?o? zi$a0+&#z@<36o#A3~xJGp_NAQUUSQQ!9Ch-GPU=erO4BOXkcZl(%naF++s~#NzPp7 zJ8rht->Rr=B9jAWrI(d14YAs3_eJRFejWOr}ufaYy`%|Ly7Nlgo$06+7N4wL9YZ_4P*bgF-vvc3PK7CxREcJ`NN^ z6=Ksle3Z(~r}cIAJA$obt~<-~yUE9()##}iRJhG*5l+l*|Ic=7NF3-Amg~Vg1>UcYJUGM28ezxi}x>3-mu6z#nJ{Jq* zrusmrfUY}w%c3SrgEGrP9g8$TLztC(qS%SgrX-1f!&NWI_8G5;V8iyd(~xRuFOOCD zkr7Kn-0RTa?Z{FJeVugiQ&nh3!nKSGi`c+jm|QN=^7FP5R-wF>VsmNh5akyVgX79i zBNL5diTVsTH{wu7b4k-Slu8LzsOZ0bjnuK`)r(P8e3kI6WN-E+vqgLgugJt3qLpy0 zq{@{JT>-|%n?q6h|E zv}Cc)7xfg-RAy`BG3MJ(qtyY{NYS<#b>5%Pn5mub6$RG-#7M9mnxbm66PtDv@7aWJ07gr zPBhGG_xHjWb>=OS#D!AHT0b`Y)ju=C;xa zGc3P^+wCv-pVi_AGCW@nJCm(#K4 z!Hou;+gvl&ZD!_t@5x9^0yhe(sY9Q)*}R>5pIs=$)F_$_*EQ#j?zq1!efDQ{#Z3K~ z%7%NpKR3s9` z!|rI}=20*gg^MoLRkpo~Eosfax2fXT8FmLlyFjm{y&dz|=Ys#?)4fa2(|MzL=Q-VV z?$1)0PC=GG6L+9{%qT652?z-I6jsUjk}?+pwqon*#36?`xLqxCda!0%vus-J2jS+D zA79$qR;c#{H5%cldp~y8H%!#vTlRp89yOBJNtUyQ+K>M|IM@YUOuEH}&zs(Np5w3B zgJ~G=Xl_^U~L|aC0E&>Nci<47YnlI7C1i1S=Z)*>byoo&05)v(vlL#7M-9`vD~#lH@h?U=Z_Z+2@jJ*{5S4n)@`j()}@71(vH zR6>Q2scx%^!kgZVVWXP!!-o&>I>H7j?6qs|_D%zv6}2-CwI$;TiQ6>MR>2N=KcOB(TU&U1Z5qg|xwlvnLvw{_$umiT7 zWV%j{&KGVG-Hgo4mj?$P2BQ|{<~M(}HRYXq5C<%GHVs>L$1*g;y-2#49$CM^#jh9P zTc(SPU`J4-YTsK^{3Oh2*K|WtM}fl$HO&$=-hGTey_nycogX_7Lk~0d3XXN@6U7n6 z4||d|zfFut=&CK)2Fr{wCgnSR2KZjJlVCew=vo?QoPfuZ{5-lG*k{CvL7}#O=;wF zDif-s3Sjdws&U@hP|9wsKcA@QxqDYHbA@Lig;8rg#A^_LzJQwfjfz-~m^0UUrFLyp zPvrJ1ZhpF@)|g+BtF7KMqU489gj`0wKPHzCdD}HFUdbrVXEsrxuPN*T75`5VICeq` z7L6G77QLD5lTINeT9T?C?UX{Y+^r~<-C-y%woX~H6Yo9aL@b#8``Z4G=DqAi_rg;^ zh-=56{_>QlJyl!(K~TU=A>q8Vegi!7Vbz41`K-6&%I$x@=Mq2EJ=$Y-njv7TGq& zHR!On4O+d)QFz;+W207M!XvwUU0GpzYp-c|WcmSCDYNJx<*5%5b1ylEUQldwKmf|y z7SkSICDv9Pi_TkkSwtQbcWI-ftrZmV7zBbGNSHPLw%e$C0fmiIprHTCWdN8 zXk4qEIMAf!h@HlQRy`#7Vg$SXk0y(@lg4{D+pBFJJ1?Eyi3tlUKpGj8?BU69c3D({KYX}3g^Q0bpH+@! zjF+xq0pdS6>7RRh>wbNL(qb9~Q))dU+{v9h{wh+IYM;1U*AGNNCe+a#A#MimK%rB+QDI|dcE*Qa*qqJ zRQR-aW%c!`AzYd^PANDTha8aKJ zqCkXiXapZ@E4tpb$ctPt2p6GzGB&1vjZZ6$*#N1 zGA_O(T(F1zlR*FOU8&=YIBeWcnIu4p@pg050fyVwcx;&3*|9$#1-lmTp0?Yf?_da6 z1f7_kjCm1Q8-ec4j5Xbwt`*qYI6L0k5#`d5z8ldbFlx{QKkZoFLncrIhrDg#S++XN1KZg*c#qGmO> z3jJf%cpOxgx{+8|{9R+YS*tR7!aKWsL&<(rE3t5&U6))Y#n!uM;i8Igh}uV$JXQmg z%GWt{UOav~&#bD(9{1T>7c(|X=P!#lLrH$pL~TTQRj7?as*K=7z%4!n8aop++mLL2 zX;$`ZMb?09_t+VOrvcegYykyLf9zhdSCEc9;xw@S`nf)Pq~A4OTy9YRQE))({Sa)j zIA(2;4ky1CW>uMgQ$?FQqDgBLxTUe-rsV-Y^;awKWs@Jl0P*T=I7DXCrUkIw{arBGn=uqq<{+qW;?>UQ7 zT~l*>T7P-^$wm&3X#~Ukvhg$Oud^mZ8Rkt0?<@9$&J&<9xoZ}hL`@P6A(YnI+ku7-ve#NPc z_SyD^qdzsP-R9}!vKN>uDej3#txtMv);r#*u&+YHtR1hgtNVZ$+`za=!hx|~SQQWU z-<0{bcYGou9pOTg&7g5PuJAtZ#a_zEqyB?u-A)Lo&E%l+i&aD>snp>yOpu{JoiD{QD7f@=6^La!4PnmzI)ZzBAW?BxrP%WH`-&DucY}`z|&j z>HH@-qZ1LLVF#lg`=r)vR)+#98zMtbYk6Lcr!!!3@jWfE7O3}b;7=4FR9n( z6~gNL%&LNvjbp?M3zi_n2uL8heI-~LrK^O3k}^cPm^#{Pbs!tD6v1|?$rgv0We~4} z*Y9BRmCa3<4HySb8&Gz@P58UN@4VL|biWk`3i|IL2|^Urf2L~EGVh;AJ=i=SvSQj= zG~jI8sF>>b`QhF%_-t^g98Q`!+@3wtKXqg%>(IN7+Q?>KLKv1r`B=<=2;?Tbnfa&K0c_ZSIO4EnsDFBg}iMX z*c`!j>9nU(@oH;njAGQdJ_I0uNSMP6N5Zq2ef=|dcke-ZpNwtKhqUi8po)6^l753Z zrsJzc*Y!Hk_`1TT`fUW1Siaybbqxy-XADAIs;01E(}?>d!c;Cqe*o8baf|6M=*o?t zg>^T4_4+j;Fhevs@c31m%B(sC;_~9gcG3`&1Dr@u%Z%0g`>SZZO2RC#syFp5?~))Z z9P;>pHE7B28Mi+bH#vQ$%pUr=9O+$$G>M$jt-{ue&>MBee9P@8Gs~Je<9BcvB{9E! zTS=J+xx;bwfH!r)8%|fv{X(wR-zPp&4g0=foz;)MOov>ll3e5SlHTu_U2XTy0MGm7at2upRC>pZ_80zv7=}334vwaF zm9ihnq;-TpzrVZA_PftYlPwEfTQItv!0O#X?FW&>!glVEYtR2hgPr2$z6iP9?T)?O zE%gYq+2)i$U9A1y$`#&$M!Z1o8Ikvq2lzPKQ3|9Vi8P29h<>%<+iAqhH5?K#-OQ%; zN^Hv>v5vhOfS+b_)9)poT_&DZinY-1Z)GDe`19KcVn>TQT%|v2p#KU5$~1&3axc3- z3fM&!4xBJMAGUm$vD+LPK@rnN;J1yIdUhft>~q9je==P3l|2+nfUMZ>;NY{HC=Z9q z8)}ao-!%WD*Zv>1*wV|EdO_Tz3r`|WtK zVaBT{ymvxK zpx))a@n!JVDZ6o93MiH`fG=c;Qche)1{6T}F&-m2@~T1R?ktQ7z=ML!4pNcrC(3s~ zWOARb6&c?MTa1#Hs=vR4#Bs;NZQnF_bAjtd_KOElc9h#At1~Xyu`WqQ>{^Fqsj{-N z6BIdq2c>B!Uy+F;;A=DT*^%?xOP={yw&a?2N`sUSu=CdgZI6hckOmVJG_eerl}qn! z$Ew}$Y}KBkNPsNc!&4)Z1ShU~^oI~7#m<>n-AnZL}U^zjSJ#BE&p>LW- zUbDpcpafJuP=jdX1z~ITq|bu&b^+o)!ojrySAmFu0?@9(EkLlH!B)>%#8kVOV7vr! zcu`Oii-M8}_WJ7TYQ)`$F}w;@Dl4-TMc03To+LR&9Hl0(90SofdILMm{z;qNQk zce!ioQj+ByGIwJvSB?l5-@b-ytRI@J@~#$o*NuBi*6UwPdEo`vwc@a=F;;D`1dy@N z;HiErMhhtT(JvT}s+|?7_o()#4Dr+m)oSpo&2n+FPD8fvOo905F>Ll*k$OSscNyXu zNABKS>&Gl068Se9D?OHuuxgZLQ_OV z$`s30!nn(6s= zplVf{FSzA!e*AUYIIBU1Rlfb?xU%X zl7qA7fveKTpu(NA5xiIojTdYfV}){^TrtBIb|`GPXOB6Q(4>wYym-wd`J@BY=Jyi} z)=0s|ax5P*@yFVaREi#V?5U;ZO^`5unRBh3*8j0!Anu-aI}Uq~K26k%xIh5F2Lp#d zEw-EJagQ-k;K(9CmZ|^c)@4s~v#d(08%e}nqlXKIYH``Qxvv4IWsA#zANZag^aGv? znVbE3nt58#tu!6_!U}+Q41P7@}{f z*3pT2az`^Kbiq`(Fhckaa^Bwe5TgLdCa(NFJiMR!^|nwsRwzd{3`v1GsF}M2G{UfC zAb5%lBKJNZrfGErk9jPI2|>smg0nWgET`g-8!k!tcGJ<3Fd`%Eo~-%+yC8cvi7{Jr zkL49u<`4(EuwWj$_1x2w0t82W>sQ*|(IZZIX;wLa0QZ)AtwswCA+?m?1=O-Zgl=x% z_=NdMy~0lejPYL95zid5kYdAPoj;|6Di4;E$eMnE0JNRzo6aN=6NKfkCm8sj@7s-i z4M6(0*&itQG{S%~!zN)c8h*l?+ItW5QeBG~yK}ydF7EUo%gblbz<$vjxAK^uvb)VEcz|=)J*8j&V3U zV)2=pno?F(mA8j>4zgK`9cSuTw5k~=_l|x3PQIr1YljU&tI z&=0@17*7#)4g?odMwz9PQ{5eoei~#I~jSciuw?3$5#?FEKeE|fTx8juK&ILt9X=>YKhDG2Jz|2~5 zpEvxxWfc|3*Z?S8L2T>@RbG}x9!uJ(viO((sjG+$^4_dVH1aPwZXB# zycb5Ugh?6QZ?VW5)jLqUS)p5b;n2=0vu;PxuVO=kk#}VPrIPLM-HJHO+TMJV365LN zpMxsYs3r(kKChV+HX5wN-|luTL<<@aR+3xP#SFq1K-XYpcRRX)8NOI1&lbx4I7NES z1SdE=OL6R|3YYqit?oAy6p71~`8p!)**0=q7X+42G-&ej_FrK<$43gJY~2yBq+?L8mt((gb2T@A zrP$TD=PnR(lBBp(8eQLTD3~JtZhf@l>&9Ej<0GkHNiT(bCH9Q3cDiMO+F+!DCka|F zFfQ@&@B{$=HeGJH@vh!cLkT8n_Z(I)dztf6(s}>kF&IU3i-0?pjN54;srd-btpP|R zHU@%xFaGi{OaNpUu9cpG;-f|$1WVp%oMsRvASwk5rU3tPzPjznSToyC(U zFMz3oHjOFss*HGYwTi?wDM?VYLxaY8E8arR#e$a-NXB-%8%@p4=n=Lzf(j!WLpJOb#TwXF?FC;`MZynW~B zS=p+UrfSNMcwW2AB8C&89$#Q0^|YPfL)DvJ7!NCa{QRPHSi8p-7npA^9T2LgZFd2)*rmVh+4PI`i?; z#PNrj;}3nIzRMnv5Mp64dV3<)Wb?afxkIhQ+O-$`F-=(m_*~zQM8O8NH2rW!1~#$t z^YgQ1{|jcgl`21a{dd@r>>*&bC7}0&ks1iHFPFM(nVK_b1E6u00q1)mX-v2hCDxL0 z_SEI!Zbyc-|N#A5lLXel^Wgydkfw8e(WtG>5BWv?YWH8Ww|x03k8fvp>zl2{q9bVhLb z&B)SYgRb949a4c$FC0e|)!X&Eb)Bj2I2;qtOW~AH$^RnH5_O7g#o}khP`y%E(%84g z)~)SeOQ>gJ6TG3%v*j9I(3r+8>#dOSK>GAqfTYs3bQ${>J4y+93_;jUJ+=X#sXpEj zr73Vm4G4ZojIYN=ILm_5r8&m?QiIZ*lFsP#O7Nk)r{k`eR%>e%)gr6>>(-S`Jy}7% z4s*X~$){P7gR4hr|KcbqM0i+#yUOya$M#jVk2P6;2XkXfg^%Qt{&1EH#NyK5I?r)0 zXiO){W3#ZIlJ|Nh^5$>ocmf~MAgh(npLyBpDoel7pVUO*rHqpE%hh@KUP_ejl*0*? zb!XZP?-_T;KRYoUy*IVhmDF00dX`VWr|Fs(wdnR;)YZ@=K2iP^{qjJ0(m|cE{rVUN zqx{<-e^nF0+qdeL`){K?uG${Aye}{|08}3nk|&vRW#|}`D9<>J+FwXCtVjj~ zx+Rf5KAP2)wIPw#vbfu{JQthbb#ApYv%j9lxUse;lx(}Roa(hi2`Jf5@Y%N_NINee zfm4o~1&5-LLp4!OBrV%qIP!kNp(Y%_#GOO`%fp2|ueU8s@V>t>=OD!1HsC>fYeB+(t9obcqSKW$HFU*1E` ze%NaxXha$h>kh@TV&S}e|E$-U2Fb*l6UIjnHw1QoAn3EHnR8G_zlB8$HiieV8U9QW zL99Awn_Cy0GpEysPg_Cn3}9*F*T2I%@HG1S0|*0s#Etoc=H$^q>SQRKFu4$3cMo<0 zq*s758}Z2txQ=E8TFa{TqVw-IJ~c;w&a++p0<%SmC z-Aal|s_Crrp{@@NMcyOf{yF8)}pj$dW1FsgtMmV%89QYE^8EiP+LY_vqh+GvG-Tb&y7R+OF@7UlDN+ep0tQtL$~^A|%@ zqa{+TLCbt?J<+U~bhXD1aP99kG<5j!>Ly6qz71w-kxsG`l;M=Xm(EnjBz|4;PZQ&0 z6X`Rba&0Z7r+p7Sl&j$LZDbHsNiY{&JZE~}# z!gK6!R<*8=Dr(1#mv#EGTFeEDKTv-t%a*MUl&fxsWm%?=u?*0PNNwSRhM9TZ)7?(kvc;EN^4+F3{H>u*CqN+TslJ?hi&ib;lGyCz{YhQ{33>@-` z{^*V;tPoREtwO?&}UDl|v2gP=Z9~)m)CjjWU&j?4lR%$pOB!5kHshIm;l&aOW& zl#;E*X*zClo_5rXr(hF?A)u7TbcgZg^&U@(cPEfkMtB(Xj-+RQ`p>=+Yc?tLx&8g> z4mBI)?JrPfA-y8}jjLunV-W$Jr~-5_Fs90k;#Vz|sy|R!xlzl`@0|E9BIdWjnCb*! zc9~_CZf>#2EGyWkqK4#cmQHh9XJk>i?fKt$AAw&FAv}E306Bt;L2xDG!%=RMc=^Lj z)#};+@linAq}K<#N(ZrP1e4DgE)>Y~nrxEbBt+_hKry`^-_> zj{?61hCMcqeJD?^xQ?n3{*q26Z@W!9)AMO;G*!;0S1g}R-aZcsq|5noe|wma?A0f3 zWyLVH=*-v!mQ)Iv4w?Zr)A3t&`O-=lj9wA%n%mS-R<8`y1rJ4E362*p|7(N&u{I@V zLjT7|S=tSK2PPH_27!PB%4DlMwAcTq1)u<|nJG&WJ0O+Jd+y(@DVqW7w;$b`I~L(W zVHV@U9(Q-md8kJ#Grkh72~|nIQOL83wPAEl$3&%{A&6UOJ#CLod*bmUtc=kqY&qTj zo=*HrQce28Ih5f3aDw~JMGcy=R*(8=gIwFX(<+%C-Jm4t7HxJNkQSbfn13>XuMzay zGdNa(Q8MT?4w_2bQNDiY+9&~>TkpP&u+T>PO3ljMG@Lx;Y53Dp^?hAK2{l`-gsQM- z;hrWH;qULnsOcCQV+(gI#D$}*d@50=g*(eSj>zKgzP}a0w0KQPAQSZm+|jjd;py0s|#M0`cWKU28>FC+uas$POno? z=0<9$<;9K{yIxx#DMAu0pSZY0!|d(M*o@j*G3?rNSm!$}Tz*kNDa)Dk`$J}h8VsW- znVJQE;UZED9WO9Tbl4WvW52N1_pH<2h+R|5DBf_QH&u{jwa^*`+)s)#!3=P|{zHXH zzF#cZWg`e>462iFBM4Lg)p=Tax)hY-kmkn(NZK{XOhV>OKnId_t3%MDgM3%rIBAE9 zg5rsfPYURZ{#jy>^#>Y~hCoP{U&Uk z?E2+l446k?>xL}`C|8H-)!TrtdahC?si;hWyN9Yqd~%2J<1t|47{*WD@hW1X5 zwPkavrZ0sT|0}yLNmRohZvxu8&42?R)y|xXA z#xP8?pYOR*HVW0!QC;uF`6&V)BYUKV5=2t4+U)~nV3qP{%(B^1LGBP!aveDQp% zWR=~&ntM>xa6P=6Q@+0EI@-@9!R5jQzk6wSe|kiuI?@gXisG$EX~)7p&EIOjTUK7b{f~!{74>uRI~{;+#zIpnT={b z8x=3;iDJ`rwLXla)B2HoCvV}Ri8|ASX_QTwGp}HwSxku?XygZ&MAtJvw>7PeE50na zxuS#q$T+?B#mCw7uD5eX6h?JH1vIieFWE3l3PT@hn44pZ20Ph_7DUFkK1RutoX2zu zl6^NZIpjqr$d`l=R5@R@9Q>bj4~Pt8^x5jw9fABV8_^YZ;m z%8wnt;N>#CYzfbscUK{6xZ&`TTIfKCdpY7gzaE}baqqPqzR1<&%2KKGEn)G^^EbIH z`R~Ta{V(Q!>C7DN`#xnVr$k$Ou6)aC^2bzni08d^3$cV^;n5w@(Ygkhied^AL9|J2itu1UAtCChN{mvKu!=2oDbcTh6NA;>meDD|VusoArFKe^`V^(Wc-nK76?;h** z2iCM|HJi#rzfRfOa>e&qq4%hIprL&`CS8k7;Cr39q7W1|0Z40eD|oak$|7LFJ9ILPvglQN!G%2g>}c&<=V?*|NhT=A4!!o@CYw=jHq(L zVdQm@O~tVtY(Bu~!yzx?JckYn9TC^1$PV+B_4QEvB3Ow4KJp3*%EPh*)gBFubl+7n z`uapFHb#W**Thgggq}ja>OWhM=sVcFWK9ZTo;QekC+_K#xd=c5H9ATjb;7Kg@}^Pb znTH3$e#UAb#RPz{4E$e{U`o@=moNL%iy_Plw#JAw;Z`V_D51Q1vfn~)#WGVh^1iT;Q#D0cs>(oKNl`G_q9G6i7%=&H2q<4&Tg!lHl82~izBN1dYiz6_I` zAuTtPa0z)ghzPH9=rzMktCfw7?4qJ~umPiS^YfFkUI(>}vIA2u&`V0J7k?jK#v;N8 zVC5l>hmwXStG-@h_vz)-{kkB+KqQ+QBuem4BcB4$JT}g#J1C0C#_i&xqKHmTPj^5G zUyhL0fPt&0I`JjoVcC7Z4U|GIn@g3!(8Ha5<~XHdr-pyRsMFPE zt&O>keZ+ZNxf#z?`~d(Dg1hwo*%Qz?piBLLegU4az~VD^_x|zmkYz7iTwHEmUSd{D zU>Fd5IiXAL_ZISCz(NlE6%gyfoe70ymhcRKzk+`-%MnCZM0h#=Hzm2Vo5b|C)Y$+^xo&r-$({m=6?j#D5|P*4C*vJ|S|r`1WT zLWT7IKWp&e4^ST27V5TbZ!3^?d`?vmC2v-&2iiVb(MrT+0rUD>jd81z#NY884+n;Q zg{miCkqB4*YLid@7FzI#B2|pA@=7(X@?2JzThX5<`zK!PhU1FMlY%82_yZVSC|#eg z`tf~EsZ`=FT+6G_CKwJ=!^+pNP|4FV&2kJXJ{O7Dx~rOQl{li9kmK_9n`-8_n~~TY zfsw)l=+Xg^vl)~QRP$}j#&b2-{AKeB!jhdMd4aTf0C0kGSVWL?`|6GU9?ur6Enyp4 z=5V~Zn?)iMiPk1N{15ko&a|=$LWCcuQ58fH{ahR~OLnybT%$0lRMVgwr$Ota%Qcvi zWH5Ip_SsUYE5}4PMpp@(lKqJ-H=0!BK7lD>b{A2PLA<}m*0kYyg!pA`Cx+KYOn9BM)eZTfr0bLW4W~<-*~t7EZTup zKqWOa$OI0R8`#~E>+B&$7boj&%J(!G zv%=GO8vB&y_pG#&_*JE4{oX3SWo5aD?r$RAb=P+TKMNGm7Sb~r&`9UI(QE6$igi!cyU@xi9*smd;gg_W#iI-SJ%h-}{l3Y(hpxDT=Z~_9k1By*Fi#>>0@( z8QCk@Gb3b2HrZR*Gb{Ud?)T^S{iDaD!pn>MzR&ZV>s(`IfvCZmLu|6Wg1WNUt3Urs zV0{h~rVoQK6DNDpa#RLpmDyQ^MVy8UY?Ev?--gzd8_hL(Dl2R)EG#4)%F1>D4wfaR zDo7KZ(D87lyfB!ATeoY^(=Rp}SdSU8Eufcx=Qhh-?s5Yjdg$ydkH|Wf8nhpSiXWag zU>A%=hIJ%e0>$szpussn`yawm#HEqK{)4Z7pU3{UG_J-|Hn!%~9}w9?8-V0@Bdy!N z`Ri1j2H@>tUXI%O7uwxTGTPgyF5^dds^y?<0Ibk2$CY zfIcCWEW3~g%(27b+z8O}svf2F!^azc{GDb*g`bk3%S3||=mOXqO|VrArhQC;4gF?N zP!Ke$A7=Ahb)90e1O!)l#x1Ddwnc=6*+F_CMf-c@c_5O(a0$6X*F}-4{UEbn-u?9S zRQTPP+;4)p<2Cq^J_vlUw%frr>d`kQ5*7Dccf0dn?}|-+9XsYhqb`bZH|xbL97SOT zQ5#_LfvWJwAmUL9fQrDleE)%qYV18dRstIz9D%Ssj9BN|A1!A_-WdhO7mcq=JIHG8 z9X?I?XPd4eEmrIL1@k{@PGw?Cud@1*C{#Q7_v(u~`0RQM~a=M6*sV);_!Z zu`K%M?+;=W*z#E_mB=>B37?{I_hB~f3Sls|_%7~Q4}ONVK$1aPX`zcXMaZZzFJb}=Ini3gg@_2 zTh>uKP;>>l6F)@K@hg&rw*Oe2v7>@51!!HqpTtae=<{Qt3LfSw+JV0cP~vI82jEAWsAa!7j z3;(P8*6VhTrOZV^2!B(oxiK#J)n8{F%uX3KhjvXYOBO1{+m_naB{Qz(gR1+&vdMBC zAwL3Sb41hm{k|th&&z*%pNn~z{Pncg(7bYKv2GzE-4acV->^C7?>|A$OKGM^_JaT* z5~-K0sO=g#m_00*K>K(ntdGL%dG<{@5p;9hlP-P>Cc4+@64s=$xGiEDq_Q#|inIZ} ztQ7qUc81gbUDyl@ti{Z+OSF}uCnnh`k~czBl<-h1WA$ZrIfK9asW4&_ z;Y7)zOBzSl5*H^Bi9C|l3ShCWw=wP8iKsQ(p02Makr4lcU*cKjzSHxGW93ugeo@*R zk*kIkgX7cyb52d+25q@ksxV2<2UInuP!Lj? zj6-+){l&x2UY1lfGJgXjjpI-DaXzSovCiepy(jIR8vPwtnw^1X1FUT0pQKMVs=zuE zar1}l@6+Sl({!plOF2JZY!Y5QwJWt!QPyk1hmR7dul*aDiZ?m2`EukxveoQEXfm&q zxY@iptH0}ODHisTPxnxUH}MyjzvL~hmm=tVJY<%y{n{0J8 z<$FMWFht?EVWgfuiPiYLk%^I;HqhQEn82O12t8)IfZf?n@A#Kj5Eh}~aeC@XY4%jA z)UFQlUe@kYjWt(SWDjwtbU$Ju+oH3{tbg2cSjdTiDsRe0xE!}A;LonO9G%|Gp#NU^`Y+KlF;V~K^EjcYz{Ike9y zj`j*;rcc19B1qA+*}FgMa&GFgbXw#QgP0~9YMdi0;!1mEqIN+e z@RsxG?7zDZ-^9Dzm$Ff@1mCAj+w0^HVgm(i@&a0pXThTfN$Fl{J=QM^t-i)@qPkst zg*OnRz!wsAHzM=-$6}yyBi4=3*t>xw3965tbd$=_hvj8c1^zwp$4%$#sUG(C!SKj3 zbp9^^o#>`F7WJxCo%O3_UFMe0l-F$jBnxf@S+6`7q%F&%`2^nI1+=tyF58muvEezt4g3rhIF4MQt^0 z${zb*6p=jf>f1h)0xRh;33ESdZvWg3-k-s3c7GMx=EoT&)Oc~?xFpPXn==C;*)3_d z!98n(4~Y++`clw9;aIz1=ivFdWDfRL6%(Zfp08(QsKyGi9?X0jRteJ^Fu3E`jX~Wm z(rM31NsD#s2LX;fmMQ*q^!*p#9{W{ojvW5__Sp@u7?*(zgF2<%snp*S2ZI@HE~MEy zXp}0l`=n4%H7L*{Mc|!d2RMZ`sj5}o&bXnymT5(yG3DT6RW}cIZx3re>R(ZH?)giO zTwq?8_H#lB<@gaw{j*zy2Io>~i3G=(@!Qi7gWr<_`_>pDnBKd$ZyYtV;;<*mH7Afz z>OhrhE_=CB;6+yG{r61Uu1M?_8UfC-`On&>dL^Svd56?@ScHoOQdT<&o@zm_ZPIWF z>BWNyY_fQE2jYk@Q;Z*mgr|!YQokG(?)RgsSS{YQjK4`KmODsIO{Yy|_3T~vT6;(g zhCyXTSrtwlYcLO?`KWC*Z?=^x=O}lK{Dkq4#iznBt7oZJlTlbq*p})3AB(oj2HoB^ z7F#6Lh2Q2FG~z3J&-3z(h!OKPW-A&dW>!hxKtZ^1;w#~$`YA>mZW(E6o$K0DE|FG* z)J)|sUfoY2GEvGi$otMn$Wy#Dp%%p()cd5V1lA}P7EIu4DF!hn*Itzy-+-*6m%ha> znUm81#gt2VORUlY;R*>{R`E2}?m&^WUkrILP@<=o`gYv{tTnZ&PHy!>q^j2hqtEM^ zI>df;vg(lu#1=tp`V`ASPeX{l{?}ODmJFMz#!FXlcw;qaS-VGMcgOQUS%^cx{K@O* zq*(VE5QA`(>lnMRG%htCr1#$RTd8)Yrb9zgTZB#rBY1vCiv2JivYyk8D7N}!+p_1e z;~ta9wZR0ghJAi^vucbNO1%~9Qd$?2>LwNN(0j0LYNgJXP&H$98H&}Kef~%U@jq+% z>u#<>ZnA`Nj$cc7@NQNlO7z(>GCq0)Ep3!S-UXRQ#yjg;6YKp-;{(hV+`g%){^19xW##g(6;`C+Bx5!0}_Wznt>FP+F z9Z_3+r+xJ8v%?IE4pzC!wJ6eI7Keo`Jfe~ojYc$I;Z6(X2wEDAdx1f=)DPL2x|0hg zs^@JChdORORv8B!lw|y9 zHk_28j<@k1l{~g-E%?~`eaEArTRa|>jv-I+YIFaZtbfzL8SmSf9nF z)`_{3SF|+A_h2;uKRiU#)^qa*t&)dc(V>GBibavh4hHp>%3Bd4)aGtQb+pO7m+uYC zeL{0@GFVYC_y~!6wq*V2zrO(`*t+w*}#R9MhMGf?ZZivEP7N^1mSn}N1KYw?2 zKxueY$&nRXN6~_zD$AKD`7!JOf5@kz)M6nE+yx)C+3(=oBDL3f|rExH01HTVAYSY=-{rJJDfL8dP z2NQ2ws!277@HSPTN?2hO!_b44-Hi@b?~MwcjiH1vQe{p>%z^KOv@^_D?*a(v48#Ws zd8)(cSkQ(Z`7@#Ec6AAGbm-bv=S{8FbpY9@3p~OV`BEM;=4UJE>h{58P(+T{bWbZ5@jJC$*aia3-p{KIv|OaM6p1Ei)*NDTrJ2)?-hEe*EQ-gHXF zD(d?t)t{XIT&zyhj+GZyPJ|tI$a9k04cU|AbAiLorBR zfQ1la7k@N9 z?Ed#_kMArW|4Qw#=m*IqFrq8A;0d>{|r--Ye9f?;~XyfEQUS z@40jA;2jw2hSJ6f0f$&o(rsCmPu|DIynY>y8Z`aw?)*lE`;FHJ?UI_llAJ`X>~~=- zjm}@5=2*DBbX~pcg647IjD)W1;pa=W`Ea87n`los`P*~a)s8g1U4qUvkNu**9V^Dl zy}qa^COUYLrbeJx<9vkSOj~&S9Y6-{e5rG_~ zF{Xq??$Nx1yct!cSifZ(onr6Q@U}rt&d!_>8HaR}3X@Cz@y58%j%+%(YqHt4ww^4u zW1ZRL?Gv5Ps6IB#_h4_+iI8I7y4d2a4lAh?Dq;#icTZ+EeL+~n_dc|ww!p^ww#UrM zPLb92!}no#+Z7H{4Bi}GY``U~zaXG%3^CvGEU({5Ab18RSM(dd1xLMYtze{BYM>q0 zSHHq=!Mv-(N;zU)9bDI13$?Q;h{i5HTDu74H?ocml$&4|A9W7dfArL?f(NQU!Ahf`0fs;LDM zl>4CIYGdHvk&d`C13_{JujV6No4a8Js-~7)v)^}hpuy1ax0G9@uBLd!TgeJ~y|)an z92hD_R#aqRi-)M5MX{PbB~0$zs`&U-{NA8BNh>S7(C*gt_jU?nPIAKsOw0SshRr^f zJDSxg3{leQ8+RuoVM&I)teQTpCDJc*M27sB2ASG^lAOExFp z4iY`l_tiE7oOL38Z68f}i$FvfhiT9JafDW!UBdMh5B8wfj7*U)v+Xcs8Y@B`4Nop*uYV**U zw|#&N`p~>#AKBFdax{pusw;+M*$aJ)5}nRtt3WJ5`iRkJ^Wt8OqAGFr z4Yf|`wcdBRw@i7_@18Y=H zgax`?W9vaOA`p7*oq zw?$F$ui6UA-dqpE5+yh18!y5L_*$)qeMOmgHGC%Fw=7#CsM&F!&gL`}hgwLl_nU@y zE;FiRh4@F^+^PZ@?T8oK&u7fa*ok}UF8ByG{bK_U#@_d7G99?(z7u#C->myga69>Z z^>z`L*+4PXv06seQD{Yu3%YxGQ~llR-igjl@5`?0Ryd&HqibYzcJjvluldu%cOEk@ zv3}N^)SvYDhw*St4c5I}w4c3(yxeu11WDp9R8QoF7#9AI3t*&8%vqMQV1$=B)qG1@ zAZ%On-cwW8KfWJ7>#4w$*6Jv4$d{l%+Uvy1uN-e@bW)P>!T3_~U=i zpG|nK#PP$p;!efw-{*st)8B?{6CE)KXf?~tJvGdElPbd~pQ=EB9n5w-pd(4-0&e1~ zLZ2?k6Y#ZL>L85h1jBq6a#nqxIU18rN*L!>J~`kadL&?rw>#-fetpvqN?$PthiCwa z(rysnx1F@e8_W>HgnPLI5Pz_mzz{wrZi@A9gw#)j9H3Sp_-8=u9X#RWphp;XYS!@fz0Zi2g7z}KXR0Cl) z)ZRU6w%`W{36ru7AJkLvYj()sYs6kF^a zb(blVMg09Rd7@TMt+tWM^?zdNmhlB|1gcs^KJs2iq4=XR)Z3+BsN3ey)I{9y_HJ=X z^Pq9KXAUsq#;m%&g`woutl~b zflo-#TFt9s|G=PS9`o>zf!orQ6Kp>X@~A$r-cz9%pnY}mmwjDuTHK`}#%zp(fqp6= zBrM}54yNOc$B!vc)uVq}ek3MnFEA$vRVw?t8>|rWlw~N1rFf(NRY8Pm-*h&! zBb~!uv^}@ZhdAH2+`PiS7Qz2FSm+(t$|EJ~!ySPn@C2uzOb(XJf`7cf`MAah$(O?y z{O1Fk-epvjZ<+^S5l)~}=lBOo-2wz#9oX!NbT;K$z|*h5vw#*rfYdrzU}kKja%h+w zf#cXU(;ip9Uj<{3VE0G0#$|QD7g0+hs9a8MZAehiTSzzQNnn#p;tWAT7eNqO6m|`D z?b%-3&L0qfBH4{um7F3@PAsC|g9+c+LoaVT=XuNlM*iRlUH;A9!yf~8(eGxT(foH` z$F-rr=xDS=x%fK*Z^0c8h&Bj^C>LrV)Kyq5fqPOIU%d07_z9(VgrQN2YHB#rA4404v>SuK>+za&1ILVZFmaopMNe$~c? zGn}<-e=F|rvh;O2SGy!J#^7hEXTQq~jQ>iJ@y@hV?W?6L5zZ7<WUpS_BfmBp3YJJ0>nEVuC);T+pL$MTy$-Lu{bZ3?mP zjzt*eWyOZmOc#DgxI>+{Mx%bdt&Y*T*i%g#W?Wh{y2=ph-|6q@+I+G-l26RQz<`L6 zVX*tkQ_Mx8m-X*h|I8*Vs(7O@N^3Cbi)NfKN}#z{5C2^wR(*vON~##@!-9W;?^lYr zcnze&cWCR?ZA*H`sCvC=!X)zx3)}3dfE%`BCR{aom4dER#o)iQ?l7(SEXZI0Tv=d9 z10l-MPDaAogYo6QW!~|;T>)bHwy}394P#AVjWsX?My~t)t znkl4n=^G>yA#9HS#m7kP!M5q&UlI01V^}#JY|kVkDX!q2qE8;wbcIop@AcJ&@@Kh+ zTXtn-EO>|~AVwMR=i^2H`$r9R7ukouXTo0#5O?L zLMPxqE~%qKP0i;xKWXLtovo9q6`<13U%nVZS~%k2@`||w_ueRsmFN7tN2jv<0_lG;4IFM0>DAwr`MvF&#h^P~=NPW0q+?A&7+%U%U*}2532o z5YIhVZatww6oN*4Uk*tGM-pE3hq7gW2a{yZIr$|*JnUzU-BJxm;mslU8$OEI%a|ug zUR_?CfDpPnmPv>{QSNW2&}ErEPIJLRBQ$0({~(@jptr~c3`0#)TcH9nFbN=9uFc{*yctkr1i2~Z;iEF^m{HAJF89cE_5$LdnUKUkQpPOFZ zA9$Z2+`q^8^OUAoubEY8!Bavf3Z_Br?5*^*crt<&!frZ;gDn+|d?DW!oK0jAh4>+H^AyrEh9RX_$+>zEos zKwck4SlPU}$v5GTMIjR>rYSPa2}3sI*&Y6urs93YnX8x#lHa z*)xluSfQnyt$V4>`Smd5FGMlVMX*;JEW+3iV=^5@lMmC*-PGs zFX}D(H2pQbqomQMs-eF??zR!tidR*ZTp#tkc)N^;go3rl#Q8gWgcD5qfjZQ)86so$6zA+Nh5HLJp0$9Rfk z&2_TG&t67fdUq|D0 zZ*b!JK>aLUx8W_D@ecxEy5W5HLAefNBH{;wnAq?-t2GZR3iWiIKSRonP)wICv{D8d_YsSoH{bR!l5)#Tdnq7Ln-_wO$XU~cU@n3Zj4{j! zA$20MU2u;>Bnus!uPnwb`brrV6qQzE;nv*GfrqCrh&!}a`9v$<>=9AOzciQ4@hIRs z;QuU7VE-ctxkEn1?#DGyq#izen80a4g!o>-DpWSqKDqgWM=NX1@95{jk}P*z=auhx z?1~t&m870RI%=fK-}=Nr2Orh%Ag92#{9dA6>jx>I2|+;^LKThbC8`=t5&j1`A!g-Mt_K!Z7g=38UGY zHY>?54^DR^iXz1EhSqZjp%exwkb|C z-zZ8+&%6S6z|pf3^C>6!2NZ)Mmf1tSJPOr;1SoTjTqE&z`1~0Up~kaI-YW2YyFF4j zA=D{%qj(72SD}8$J}D_#IxjhjJ#A|wevi8?wRM~EKor}N(@{M$3Z2fuj^@7d0)dn2 zI2YyTk||*p`jwv9ld3>Q_P(1x`0#=Y74p)X1-B6oi-+?Eg`6W zgj|@sKpW6r&Md?}$;xkH3Hg}^TT>tBE|0$6Bf4Qd1xE#MBL{zq!kKKTy(5{bqln;X z?YLWNuPLD@3D2VZ@PUvz-mYQb<3u?aN~Q0|px?F^c_77F@HkG^K!a2iOUOZR)z*b7 zFTL^O5)beNAaRK5|7WXXgX+mIEn>D$s(vTUUKttPHj|ak-Y@Y`+5ezBzdXi=DMrD_ z=&o6c6Db_K7=Qo9ohqM&SR{clhYpPF!E*O|sa?+V-c)ko_{-fNwj@y3Qg}s&7H@(= z!eh`JWB0SWdu_V*(Vpu!TJpaTakc1k9gj9jdUrq!Us+Rg9a9N|(9y8q5-p{usM$#e z%;Gw{FUCrsx;_nx8WW@HWB~sn*F7TeQxIYUdp(@i@8!&MQIt4$tR5*9x-vWGKH7PG z-*9iwayD*HU_73Wn>2K=NXy02(sIg?9W&)muElB7quUupXE%>zbj+meYoq=B>;rNw z4ps;KPfvsV8b4RZX`_%B@7y}htJ=HS?LY_Xl)b|&6ZX;Y*^+19RFnrQ(yXtUW63dv z)N$J-ib(0#a0e|fzQ-AIWZZbM&Q`v5-`JqFTrD=6BZ28%@HV=;Q9_M#d_V@>G^a|9 z^n%}ZbY=PYR@Q0*-_XhTKJjPq1W!VI1B@M_jh(J{#U-1c76yrDJ?DSK8W{S6bBX(D zW())&$w|G-_MaLM9{He@p&^*PvP{1k|84_(`R}uD9Wt;CkL8{j*b8z z!9Hd#*AT%Lm?g%3{8#}@Xzw?pPAR}r04Pkd$?IZuDCZ{df?LX&O?wiWL1MBn_FLf| z(WUNI+_vM|K!zBKdxU7IqDZ|2E7tG*{;%{5pS&+mkfsVuV}9m8tX78l2B4PtdIrJ73fj<5qG3d zA2v>%laXNAPhhe3fkr5`ey1@RgnWMP`;&WKJHvVSKorcWt`7HxSsEUVD=*N-gG&vV z7&-wUmn`CsQD(O!nRan)eppfc78+&8l3jEcq#a|lvho~`s6qx65`hcFA25P`AS-)L zDVsmTCCp^-b6@}1{#AB^6(>fV-`Ar0#knUs1*U!Y&>QaeDYk(J9&Sc8lKk<)V_Yiv z%cw?D|4<(X6jO@R4{9&h2Bq}cbfbtxY;4aNz5EnN7eGr9ma*n%Tpnf3O=)_)5?k>P z;Oo%LUDKBGTP9R>d*T$yZq74()uC`>R}@RnLb{FEoFZGLcj4{CyNvo;nj6LUyDTZV zd0X=~46!lgC$Ts=)urNa_HJpXvkHq}_myhgR;k*Pb~?Z#2Rn0VaI`{5Zwu0ZNob5YVc?JpgQ+5jgODJ<-sGo1w1Be{ zc{U;17t&Fi;d(unmk--HSGpYlFRuS1k`BThLB4`ur5-uj0%Ge_!<>**16OLW*1-%8 z#OsGmj(9@nwvdy@@%DII9tI(RIB;&WqxpbW610UKa9FY8_```l{Pm3P5A3R=6bGg1Mf-Z8pLZO(~wxnsUhrQ%=jhpbr0&8%4pM+8_uZu3&l&FB8!2UN4v zpp~4*mH@C?;|kqf)Bl1n7kI*HKB;~BQ+Pxcn0{VzN}qEO7U%Oum>Z|p#rk2^!DQ72 zBNJ1`moMEb(4$g?qd*K8)N$@P*zBTmMtRAB;*KEWZ?6 zSv~IBL@o*?6cznWU$j13JYl(Xmoh3zUr)oxzk;)aqGuX#)RuX5u< z98(Foyg3GQqQ&8onTF`-P;1=-1`&-vE)xb{g^Ck}Zk-(!tD<7_O&a%g>-`TE9f z%;^piX)>mJEd#-^QZj3J8f$*Vcl~y})~bKlJ8P5a^#3HQCn{}vv{aOC90|c%aCjJo z3mm=b_;9-whCX<1pbXsc0r}kU$jEQZM9c|q&8ZUq^61k-*DL%{WqiIIe@0_&^F-g0 zBLgSv$UO;>L)EQ8%YGInCIf(}sXPw`0UDXPYJ{iYlIz9Xw=~h(+$tX_rUrYC3e#Rg zaJ>2CG^zm^#2=7v4{Q&(TUVa#{W{!Vig{B~6E=S(>!n&AcB+MS&bMSq75mMKJm|?& zr=3}9oiR61m!gjS&~t}f7?QIGHRxTcfV+yS>M(t}Qq@$k&NJEPQ_8BUtpF2xo~VRr zczOyXKUHVb7xspSsuL0@`~9r)xzcCpKn;>(a#?P{-sE&{oZo& zoAS4Zmfo|tt=4-zaOBx^q#ix-2xXiWkSa5gz>fV&`a~#Eov*>@y@gqmh>{Mz<1_Pf zr6=|}V<8d(d8uRuFB1rNw>0kBh+&B^lRkj`U&np5H>}cj>O3#omyU3?gr14)>Nb3^ z4B6nO%NZ7>J+BrUb0P{MN)zm&B#USiC^5dJ@^Cw11FYr@HR99c|J-h(y`xB*b1y~V zLm4PsCLtC z%6X9Mjb3!|^U1WW`f!UsXmQ(Hh~ zcOiCR2n-?MtMgKvDWEJG!kmu4En@frv5-lstf!|4O6EjX13wyzqEF<_pASK!=P)Qi zRt7CE;@qZTmLdTM<-ZNRD@}drr#P+ZJ-`p*hd0;*`s&-<*5v+}_{%V{WW#%c8RU)+ zoUVeG|BR3~mZy+%&3vv=0P*UJleHT+cxMMMy$2R8F}V`lMb+S63Nm zdo^33grV84nYk*e$hUS6+RLWF=}*k8nh&oV!*~nmMtWu@>aMwdXjpEyaset;=-RZ) z?@cw0mp(fKFC_c*@0V}XO}j3I#m2^#g8NchAk*qKwYpmDHkLl)$tb~$ZLzqRI@pUL zhMmA5BF6wI`IBA8ZEc5Mm>>Q~0V)i0@a$r$_E?rQIyNeypHMx$P`=Uol;&s89+>}A zYsOvYD+=>}s(_kUpiW7ZrqAMftMuBx*wbd5`riVASea_%UXHpUvwAQwZfU@C?eR7lgKN2XDwD=l z%!EIiKe+!PweuwjUcRT?+f48{sdVsOO;UK|3?mwi! zl@oa0!&;Wip1k;RqWezgV zn{2OWoLi?gv&MA&WA=hIYWij*_B|#0XqQF>9i5akuHxUH%sJoNOpW~dh0^jhpE2=U zQLU5CN2EkC>(s>}-!nt(-4IxZKCYQZrRrP&yAdwfF6b*LmNpPoz$yGswv2D75e z=_+u!F14II^u9-6!z&bKC7wVtX~yh#C-+5NmEYNCoNUn3c3ju0M{FVEKIZ>Ywd8qa z<45tJU)VTWy0Vp1F0#b|jg)$$h~aiH{fy996p>~ItE)^pVMH8lfCBr3cxwD}rh9=^ zykbk^WSwM_`+;qPHsvi@ikLGF8PB*W@RAM~PjtrGo@2Ra)dpMdeIz;IcM|zt`sIdo zXK3}99I-sbJeH%n6jrHSWKbvz1JU)~UX$w3(c>@klgd`MF!KR+#Dolj&;(ECIrbW} zqmN{QaSAM=%AqgFj@5P?lsMR&h(Xr4P_1O2Kd%K3+O@F~8pLJuItI=>K#40*K8!a; zi-6FKtVzLu1-@S4vC_R>*cZvcbt);uyxZ%3wAlr_ofb%xfL;t>gdp(99doHJI4q%} zMv5cA2uKDI+!N3rBN%A#-SBy+mUPLz9S ze3%n*jg@9ymvE7~1KU*yYc+#)Gk7}=;PWdj-A0;?qAaKGp$nKIDox4J}hv*PN+`N0-Vwh#go_H@xdZ5AG*BJ4@I`%x;${{d!;7!e{_9JB7;og?|GiI5_orlxjzksr;_ z{aF*T(BVZs7Zf0%5c)x(td~u10_A25-(%^xJ5F6INTT;9vC2C z6#7L3@E@dv4%NH7L3&>J(SP@sY{<@K6dOO%83SgFG}GE(7AEl3frp?BGFXBSgj8Bi zuepl;)3$QEdy8KKJ*5jUiykbFTv<4rEmE&EDnDa~=xa=vg{)KngqO+EU@DtS;ii%n*d zf)O9O#@gBuQKmArSPa%$deqI}?u&hld!0rfRS$Ct%7>2Sa2?HS9~4|%oUEj+1W~@8 z2Se`P5Djd+I*@@p7KjnlUMJ1jnKfI^$;YkERM+ZQDkbJT5U1~Id+0NLuFd73@h25q&N+hDbXBpy2+@j@L&f)!b{lkb4|&&MEoN5)athtzI$<+YQ=?9X%af)zTGuilTv=asg_{M#ZZG|>U7;3sPcYf zR9P*}T-y1UBLu%0m$R*FZGz*yzgQg9EiG$B6R~_xrzg-Ya)^4umdVR|%8jO?y0Xoq z^x(5~J~K08;m2DqN=L5XiLc#pEh#Cbu^N*vzofJ@Ki=~&Z!(opv*MTb7!U7X4Y!Ba+LldYO7w*ZUy z!!^E~SR4w@V8j7JIZJY|(GwH6I7=P42p%n$b{VYru{KNYVEzJ)qv@C6oiIr->AGxb z3n625-h5SDyG-GAI-GW@pyAZ_-S>Je#9Q$!1Q4%{m)(Wa2VuSfDTo!e!f@AK*g?Um zNaQ=bFL-JWx1d}`etuW~!JVhCsTBG5gnCjhaGp-v`g@l^g#zogcqA(YL3og#88CRL z>ur!-QL~ntPvtuAi~N+1o8yFZXo$}%Ai*X_o4n{GB_g#FIzs1KHwbeVupd-Q z13ENF>@bE^C4!K}#>Wp#PlFk&18kO7NYU^=<2d}G6@sI(cKu5M&t_?BDRa#7@;#{M zMw68m2&LiD6ZjpraMsy1qm$W!N=H>wvm2=s5)!wc&KwX!o4Z|si^kE5V2QuzakMA2 zY_A`Fr*`b6%QOjIBbu=*9Uu@j+=v{kAjhZ?O}~Mn5cGLiu1(e`x;+vvK5M}omGj+) zg=lL%*<*(eL#k0q^^fMo;x(v(B{@g_Ce|9&N!DT<@fP$;q&k>1;u8zk2Or5c*uTCJ zGyWOnU`Vd|e1Vs5l0tdvk@~7MZoUi}o-=otsTp0&lQ~mKP6BS!v8Bv?_8Lt*5g+J#(>{QB0~8P!{bGRxpdwa6BZWrpGK57tyv_k#>OWc zs9#SVqtU)NqWM^^_%R_PP&5Y z3u0!ESeU?)v?om{%`7#sHRIje<{J-dg)?}@D-seVJx$3mN}4YD5PNTgwSq}ydu?Ov zjqupMNVE#TFj!E3ed)w7*j%=?nSXXOP#TkSn9BLK>Y{H_d+atNlNw?#3xW;O<{MuW z<4xeu16?LA%p;E5(}}=&6Z!#Mjqaa6*G7vdVNeFU9{Byh&NGvghCt?mM|ZK0Mc1u&0=Iq@Rx>|x ztlLDJ<=g#qZ_gDqI@-8=qG0cWMKqOO@|ektD5UK&iQrr3yntq^{;d$n(iG;d>@YIw zBrizf`P)nU8I9T5ZNz*t7sp=5Bmo1g@-si3ovGMwKcT2(!S1BShv_{ zUdYWs_Xb2Fau`g(J}j%QF6pD5TNM1Jt86ph_3Qn&JfKF?ctO$tPo-cNaG!X#Du@4F z?075c=y)~@Dg!4Hl-m}s75+wepZf#-emXA^hhi0`30QSg!of-kjTAy^A{1qH-6eoh zgb7ef7;-Bbvxh9X_I1%*tYRQh23Z>v(ByJgRXM$Q`O>~$pb3PBcU0xJz2)~E|02Cm zmXwREtn9e}9Kw>}R9z)rhmQbk{GKU6v)e)WCVc}#irR?;ls6<8xD-A3-v7j)m24Vl zBxat0+jxJascUsGi+BC=T_}6k0pRuO9R^d5K!jV>-5t)XbIJxgIEt;wIJ?!1Rv=vK zv3vZ>n0B7B0rO{SA1pLbdLF z_8>VL$Wwfx`*uwa#6ME~sRC;w`FG>usB2G_)08wdL7kLV>S`Aa%R>(1ANsFr|Bxcy zM<5PllecCFJ6e!(tI( zeyxs`JOMWh)64Ux1Dq*@cY_>$sj0JDk1HX?A4sL=Wz^8Ad^< z*B&8i-ct8S1IHxf!TR!rdw6{ORb&HA_kCp2%e`B(QsDAnKB}NYzaYgMzK&z?_Q4!Jw>?eow};fk)KKFuZO&80E1l2ST8cCpZV zlSN5d_kmsqcg$(P5i`F&RZQn?^B%h)E!T~@?`kG9Bw{Jp*8L{Q{$>`oIw7&U4Bb6D zB=74yfM$vu%dl7`j_5>8MN1lvD9T>pOjX|`3UUq*SbH}fGxo6Ww;;&75Nhx_`y+7B zUIFNOwkVpj*$XUgA3k~nkt1(_%8H0S0hvD6uY+YNRcE{2BCl59gReKzj=qf_5y-jj zTC-3f`AXmhC6~&d2oTdAJX&e%>zZ@%cLU}@U!LuKb!k1L*$Zk`1x|`TkON?PxL$=I zk?;;VtVWrIEb>5W3YP|Dm=ihlMV*|gXq3{6^O@D(hgmGN;Pb$6lfduBj#%=pIa0_E zBgrs8Ys`SnEwk292n{3FLzK$H+W+V&@HqgQA&kdHoMj#bk(9%4cm&-dK)6*Kk6@hE z%B=^n4gJfJr=&ZtM@GKlzJIMXR`sc6{TDHzp|{4}dQ{(7)G?>?p8apF^Bdb!jW?gX zdhwQ%R}^mFYtlK*cN~H}M2?EU9c$!(uDos{N%QR#!e#d$MIr0Zoa5s3+us?Ua5Pr#*GHXEk z)qot4=m?tglN0@t~jk;cK+H zU>3B5T;Dd;Hi)*gnSFc4LjN)V7WXO0h8yW^8rdoN{?la{?qsl9{g_gOwy_hg=_}a8 zBfBhMVm(w8;{Ml`xOOoXt-gq!g`lqo` zk9~`gG`kswRZ93Q05(INw`3<88}R7T!F5L4eTxgGEPtp6|GD5oB*QHzFkF@-O;DZ@ z&i1Ry)$4b>&+TA=4Kym~(20>_9FvCM=hh=Z2pNQt3Sq-s0w$xlIOYJq~! z1B(tIQuXi#-~BuB_e^Ie|AN*X-aTBR@#Pc?dPmCKeJwLKh`ninQ1scnl+pOSNw<2E9A zM)3!i`^K7~>9Dy9ZRw9}j@Sgg-n8Pp^^jUe`Dd>lPzBle8r8N*;hUbhKPODQ|D%R> zSlY3_kr_?q*#_@GVCTy`N>n;+&ZnV~cO1fYr+=sRELyr|*FGj98|y{o)p_yD?<{(E z`T6hF#u+M(%<$YBfI{@t?_4H3^zmXcF+jqpXmLm7GH%v@eR-HT7A{?-_7$f#8I{zs zAaU*0z72-PDt}P?x#o>Yf^XKV{H>9O4!++$W%cb@ljzP_Pl?>>{yD|+O&_h(?!s%L zy49h-_YYLj1%RS53k&@~li-AOeX0t)fgrP;=XnfEG{FKnRxv(ueHvp?q810A63FQT zKVxo$;3$`TbgjZvpdFq`xz2EE-i^WvKzpFGr$@-6bHf0j!oe7$K+y>aaG&$@4Q9W& zdF`4+-g=_FPSnTUZd3g`@~9ovi5T155)$GSX_mT2vI^bR=~ZP$F1=gQZx4 zpeG;F;yj%@C&uI76<+Rj_XjgS2hcgN!^}17{sskF=edO3z+y@t*#7Gkk8(b-9jUGW zU>Bdw{UaoS^ zNuXtYhNEXd-mr`(*J3%qx5QibxG&=AouCEfHyd@~8`1&QW|mW%C5)`QGQP!$;#tb{ zQPK)OdT8=?5Bm?*_qMytyuZ+*t`YI|yP$e+xq4@8V1T%6_yetpflcQ;?_72o!5L|6 z1~2)H9+eLDxn6J!*4AQD?kpEoyw^FQPMx#iZltd{AC^43>kr{@p1-W+&J?X9_EnQw zJ29uAyI4lGqo=a_ZLk^yV6S1qIf$I4i*7iOo}+_cEm%c~Hs)}b<4$0veB!RR=h5)DW6u?RIdSid zkGO9ND}sV2tfSo+|_7I4c6)Vcr{y=c=2D}0c>u4_1c9;g_;Hm}*A|6*T2?MV7;5|KPyQ3d6 zP**?meEi1Td>-(HKfvNaqQLL~HAX4KJVeg7GDCC`f_4n!MDh5^G%u&$5~o*SJqEUN zmEpUdLTjDior_R`D=mh{YaJe39lg1d)Y78tAP3JPAlR`Y9qJx`1oSO}dvY7=>x&== z&H!)*AccFW+hDko4TgGgkJN`C9u4?qfowsvSkOqL<$bcp25)g#-c#j}h!EUc0zw`B zK1?4&Gc)q}FJwiFZ_<$iUI6apOuy30bJ&9LJy*p(d7aAXy8XFzxkqyq6&@abuVe)w ztRdJR_;}Pp?g$W}4Mjzf48XH(cOMu!I>B_9VF`#R?8q1L_ZtEK7uH!VtO&ITJ~$28*zlTm zPyy^fLPi)wCqgiPCaBT^m2JsN?~D(|@GMA7-rn98cYYp&E%xU^a2jYl-!7FZkChBR zkF9Kd9`+p>zIfR>&-4Fidh4Jn*Y1B9~ayz72`?|0_>ab{!&cHHY)*C!`ls~kH_t`SJ*@L9@*rHw1-9menB zxqZa$-$b3xxP1&HXKL{ayAgtKh;o-;#J9%KWE$cf(x)6ao3$Cx*2uet;=(I$P(Ln_ zy62-$+$%$BqfABNkK;$eMj(nZdK(?r$-;R`;PlsF<(Qroi_;%u zYmJ!;NJzdv_CGllm3zq20~Irui(`+Q0>*;jE&K4dSYT?aW%d`unmoU zi5ymc5D7mZb=pEH$*@oM%soeYoL#Hi0*(l;fBR47YgT{V1IX&%Y35}ZVt#=NtiO=V zeezm4-w(+Rhn$#qs_gLx7a)Z5-Gh?Ikh3$a#)EubM-T`6&b&O;;i*|B(<}2zXgaSe zZ(BcyoF{q1&lc(ePsBCMBA)LpKJ@w%@B8raPd&+hd$cEz4BU75Z?0+aUgqij|IGb) zy%zArVaK8n_~>$rIjjKqVrVr29s)K!$eb+&a3Bl-I`mJ6OWzuxb5D>GO(l->HZTjc zArA@qPn@WxETJIXt@^j(13jAEESdd}`%CzgOI%-2(b?n&Zr<+K0oD&`VXET_uF<|l zNJv=3$N6Vwn;fgZ<=0#5UEMDdBC0?7b~N5O+ORLQ z(aFux>~UbUInzBbxZ8HB)A|$J0#1M+@QF$&@^kyVOZ*o!@MqzVpO z{fZyCGw*&h4{NhHa+~cFSt6(&L(EK|J0fFhOYLUSBCh~Z22bOOImCn~1tt;(_??}3 zAVGi?V|Yvkwazwi_gr8-LteqqPz%Gq-ywNBWj^+Hpr?dKb}yUQI~2)xK3R&s=ljpA ztagLDKs_B=A1&b~jzFwot5c8n@;+n-)6CxGywUXO>(wFBqQR?Wk3&4uAH@7gSGC(E z4T~E&_J%kC!+&Sxm+y;o3LQa}iS&d%jX+c;aJo-klj(%e<4kQLeX;>CRz98hWM(@z z+YV|HSh`gmiNt>;TNpI?Ku((kxFCu~#>eCPS5qLe2y##VUp{7>ME7V#UEKp*wiNbz zcAcXFT=R}abAVC=nkmA543>)LBOkQjbwW8+Sy_f^yGo8f{ST3XmG;W7`ZYYtT#!pA z0u6FqmIBkDmj-M@BCL%)kP*58!TRk4);HF?Jtz^xZH<9D!*Ark0ns3^QqmO$%kVMf zH#TM|9oW;X*+5)lxwOE5vDyLH^>B zzlvz>lg7*l8A#b&RFZ@wKai5iK>j`CQa>Ski%WS}4#C+)N&Bj_aA8@^@Z^E_5OJ-L zvH&+VZ$?!KNw7Tizfp*Z4G8rV&Ynnrq%#BJr1Mngj^kG;-u)-X^zZrp;;Ti?1mADt zrP=j7jr1Z@8<-0E=`UE%8uy+H^!p#Aj)^U#UCkR&q+ojVl*{r)00C(?Bbp(8hDOP@ z`mU|dYbKUJO0#Mi9n_fcf|@vKc92@69uDV)J84an3C@kIi0Ib}dS-{&+R7;U1tl4E z+CTK^wx|23_NZE#_pIzm+3r)UW4$IQ6VklIr>vozzv_y0qS5>B@o{6v5_yEbG^3JP z?NSwI93q-S8^RxOa^^!2_G2;`j`SC5FOj$Lbdj=25rUxM5CsF6+z}$(fbKG@we|IE zILHJ>;Af!doEIWcWAj3P0&@H0d}@v*Fbmp9nJZJZ!++YJOF|YY5c>D|MW%ttggO%` zILLrZ(Kr}JdSIWNv`|ideo2|H%~e!|jswrcex$?SIr!F907VxVku)Tqa)DGgLjE<_ zVbn7q>Hid5m!62W9DsBX6VQVL+YA_SsB8zvHmn^$JLh~xwBi=fw-z@3?d35vPWC{q z>;w=&EgkUnR#3oxayFC%H|8p2`DT%i$b!mhBsvy;>EO&v`qnB4OF@JaDLmT=KnYS2 z1!`4g$GF0kg6pp1png#mzO6xx>!9FSh5M!jD8T##x157fUkkX_LT$ZY!IePGBYaWcw zfGL?mcjtn;DXNw@usMQfZsRky)5Xuo=CQ@>fqyK6UloRp-HTmBN8A*WKcExtk5z)y zWXC>|pN8vp&`@@Sb1J>oj(fP5Wc2Q{8!c+I4daKH7nd^;8?IW) zVS_?x7UV1I+t`7V5B^UJu>CO9(KOF!ZuGZaHX3Q3MW9P0$ri%V1yTqg213Dcoz-=;bY+6Eip|6pC~)<`GB@J{UTPb%V@xWDNj9iEOcjYyTxO@3C5? z7ytb09H?N8;+tSi&O69yFVa}L*J&!?SLeu3+3X*_7iKPGQZTtminXc6MrQGtbbPL> zR$woU>|Cq2;bXe?#B~`zes(3|YM4MsRx*I--~M#pMxIM}i;&+GC@n-Ld|0Jmf2U zXv~XWio>w@#vn@v0%Lm3%0Gd!qJaUCd9SG_A`ic@?58`9t~}#SP*%DmVvwY>Ab^~0 zjj82OpL}w73K!tnnJ=m8Glz2zfv|vAF?CzG4)2J_3R(x+iSl!E-y-lBqR#+y>Yn|y z2qJJpCM%d$i2C5e4q$u=d=PXrKT6}zM|*A$+vHlVp6)D1H&1y%6qh!4knPNr9;s;i z=OU;doT)noj*<$XvxJBsbX*TlU4(}BhEDyegw73%r%cyi#Zo!z?v00+4%cLmy}$~6 zk4x&J@uyFc(kT!K}(bsDN zHIkFzN3#Lk8$KkDRS83I9ll^~zZ;l5|5}|P$0DbiQGCT8>jtA-U<@Pr8U=G6;bv4Q zA2TXz+cP^dNx zdAebgb7b-%*n(e~nLUD$=2z~|cY00S^&@E|X{&lX;XhfKt4VNjubQoV{OH5S&gQgr ztn%*{=goJpSl*u08M2vyd~_s;;R=~-MYaD=@Mdbq_84*f2aX*io8iBIv#_`U-I1_$ zZl5;e1W$!NlRhrf+U~9uV9yk*4qR+-ULZxRp6R`x$Kb<*%!Vz4=$ns3I-$@Gem;px ztRRyim;7JaETmzB%&>9m$o_IsH3cP=O*$*Kpo~>k4;y8lhb&i z(kUUW5XchG@C+wsR?mGF#bLfcI25EUGM0+2O+v3+SyfG7P9&~A8KES|3^F; zR5bfQ2(bneVU}XTqyS7c0)k)lJHyK)*PR2;-?h4IHyiu1^6CEcWj#Bx-Y8}f{Tlp@ zedKNiUa3;cn7b*K%u2wS`k|Y|Gzo^|q*x1Ij^>&|cO}QH=5GJNuT6-b=O&sLtaouU z4fc}Mc=BZT!SkIfp18KkhW}|AQI%5f)<04dpuX*V6VIC>(2Ni}dgEr5WsZ!>a6Q!5rvzQT;?&ECYT%&&QKs0IC5?Pc~ev{moZk&=f8bcB#L)^ zeI3at3BwrW;!3E*%GAaSVEo9)oF1V4vKJ0zvY_mr9o0f|vCUK}#*@BwjT)tDbdn>j z1@okGv4_D`%8oZ~uMo;;E`k{PRa}TCd_8`o9^=)y?lOhnXZSmhog%QZ9PUH4Ce9le z*7Lc&cE`{tgp)096-y>|FgKblv)p2HOela+*%^liZR6k|rR`x&sC-Iwqdm_~Q--dt z;VbMIkkk?z?X}E;C|?m*18l@qAdy4Pa1f`+K|!^?@$2h=<1D~PECehEc6D~|G$Ec> z=Vj-P;j*d(j`y$;L?@4jBCB zzb741>`6U)pgY-vjl078{EeU6odVsFUiY#D<$SsI&E;svz+3*quBd;8=$L;4Hkam|20PBQpNGFOZK$LxEhJlRHRyUV)4PxBAN6JBA

A2{yT4S-bWhLL&(>57 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

Y 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

FMaM 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

9gHG^}2v&@v3OY@`%zhOH2 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

    x4f8?4_h)0!TMSPbhfB!O?%b8GMk75)- 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

    H)bjM*yb3^?;MxmL_ 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

    %sUne4FL-u-CLi^$T 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 H8TkD#(A(x- diff --git a/build-release.sh b/build-release.sh new file mode 100644 index 0000000..cb5fb56 --- /dev/null +++ b/build-release.sh @@ -0,0 +1,53 @@ +#!/bin/sh +set -e + +cat << "EOF" +======================================================= + FocusGram Release Build Utility +======================================================= +EOF + +CHOICE="$1" + +if [ -z "$CHOICE" ]; then + echo "Select target package variant to build:" + echo " 1) Standard FocusGram (com.ujwal.focusgram)" + echo " 2) Uncloned Instagram (com.instagram.android - replaces official app)" + echo " 3) Both Variants (Standard and Uncloned)" + printf "Enter choice [1-3] (default 1): " + read -r USER_INPUT + CHOICE="${USER_INPUT:-1}" +fi + +case "$CHOICE" in + 1|--standard|-s) + echo "" + echo "--> Building Standard FocusGram (com.ujwal.focusgram)..." + ./gradlew :app:assembleRelease -PuseInstaPackage=false + ;; + 2|--insta|-i) + echo "" + echo "--> Building Uncloned Instagram Package Edition (com.instagram.android)..." + ./gradlew :app:assembleRelease -PuseInstaPackage=true + ;; + 3|--both|-b) + echo "" + echo "--> [1/2] Building Standard FocusGram (com.ujwal.focusgram)..." + ./gradlew :app:assembleRelease -PuseInstaPackage=false + + echo "" + echo "--> [2/2] Building Uncloned Instagram Package Edition (com.instagram.android)..." + ./gradlew :app:assembleRelease -PuseInstaPackage=true + ;; + *) + echo "Invalid selection: $CHOICE. Please choose 1, 2, or 3." + exit 1 + ;; +esac + +echo "" +echo "=======================================================" +echo "Build finished! Generated APK outputs:" +echo "=======================================================" +ls -lh app/build/outputs/apk/release/*.apk 2>/dev/null || true +echo "" diff --git a/Ported/build.gradle.kts b/build.gradle.kts similarity index 100% rename from Ported/build.gradle.kts rename to build.gradle.kts diff --git a/devtools_options.yaml b/devtools_options.yaml deleted file mode 100644 index fa0b357..0000000 --- a/devtools_options.yaml +++ /dev/null @@ -1,3 +0,0 @@ -description: This file stores settings for Dart & Flutter DevTools. -documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states -extensions: diff --git a/Ported/gradle.properties b/gradle.properties similarity index 100% rename from Ported/gradle.properties rename to gradle.properties diff --git a/Ported/gradle/libs.versions.toml b/gradle/libs.versions.toml similarity index 100% rename from Ported/gradle/libs.versions.toml rename to gradle/libs.versions.toml diff --git a/Ported/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from Ported/gradle/wrapper/gradle-wrapper.jar rename to gradle/wrapper/gradle-wrapper.jar diff --git a/Ported/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from Ported/gradle/wrapper/gradle-wrapper.properties rename to gradle/wrapper/gradle-wrapper.properties diff --git a/Ported/gradlew b/gradlew similarity index 100% rename from Ported/gradlew rename to gradlew diff --git a/Ported/gradlew.bat b/gradlew.bat similarity index 100% rename from Ported/gradlew.bat rename to gradlew.bat diff --git a/lib/features/loading/skeleton_screen.dart b/lib/features/loading/skeleton_screen.dart deleted file mode 100644 index f88b3a9..0000000 --- a/lib/features/loading/skeleton_screen.dart +++ /dev/null @@ -1,532 +0,0 @@ -import 'package:flutter/material.dart'; - -enum SkeletonType { feed, reels, explore, messages, profile, generic } - -class SkeletonScreen extends StatefulWidget { - final SkeletonType skeletonType; - - const SkeletonScreen({super.key, this.skeletonType = SkeletonType.generic}); - - @override - State createState() => _SkeletonScreenState(); -} - -class _SkeletonScreenState extends State - with SingleTickerProviderStateMixin { - late AnimationController _shimmerController; - late Animation _shimmerAnimation; - - @override - void initState() { - super.initState(); - _shimmerController = AnimationController( - duration: const Duration(milliseconds: 1200), - vsync: this, - )..repeat(); - _shimmerAnimation = Tween( - begin: -1.0, - end: 2.0, - ).animate(_shimmerController); - } - - @override - void dispose() { - _shimmerController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final baseColor = theme.colorScheme.surfaceContainerHighest.withValues( - alpha: theme.brightness == Brightness.dark ? 0.25 : 0.4, - ); - final highlightColor = theme.colorScheme.onSurface.withValues(alpha: 0.08); - - return Container( - color: theme.scaffoldBackgroundColor, - width: double.infinity, - height: double.infinity, - child: AnimatedBuilder( - animation: _shimmerAnimation, - builder: (context, child) { - return ShaderMask( - shaderCallback: (rect) { - return LinearGradient( - begin: Alignment.centerLeft, - end: Alignment.centerRight, - colors: [baseColor, highlightColor, baseColor], - stops: const [0.1, 0.3, 0.6], - transform: _SlidingGradientTransform( - slidePercent: _shimmerAnimation.value, - ), - ).createShader(rect); - }, - blendMode: BlendMode.srcATop, - child: _buildSkeletonContent(context), - ); - }, - ), - ); - } - - Widget _buildSkeletonContent(BuildContext context) { - switch (widget.skeletonType) { - case SkeletonType.feed: - return _buildFeedSkeleton(context); - case SkeletonType.reels: - return _buildReelsSkeleton(context); - case SkeletonType.explore: - return _buildExploreSkeleton(context); - case SkeletonType.messages: - return _buildMessagesSkeleton(context); - case SkeletonType.profile: - return _buildProfileSkeleton(context); - case SkeletonType.generic: - return _buildGenericSkeleton(context); - } - } - - Widget _buildFeedSkeleton(BuildContext context) { - final width = MediaQuery.of(context).size.width; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 80, - child: ListView.builder( - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - itemCount: 6, - itemBuilder: (context, index) => Padding( - padding: const EdgeInsets.only(right: 12), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 56, - height: 56, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(28), - ), - ), - const SizedBox(height: 2), - Container( - width: 32, - height: 6, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(3), - ), - ), - ], - ), - ), - ), - ), - Expanded( - child: ListView.builder( - padding: const EdgeInsets.symmetric(horizontal: 0), - itemCount: 3, - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.only(bottom: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - child: Row( - children: [ - Container( - width: 32, - height: 32, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - ), - ), - const SizedBox(width: 8), - Container( - width: 80, - height: 12, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), - ], - ), - ), - Container( - width: double.infinity, - height: width, - color: Colors.white, - ), - Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: List.generate( - 3, - (i) => Padding( - padding: const EdgeInsets.only(right: 16), - child: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - ), - ), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Container( - width: width * 0.7, - height: 10, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), - ), - ], - ), - ); - }, - ), - ), - ], - ); - } - - Widget _buildReelsSkeleton(BuildContext context) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 120, - height: 200, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - ), - ), - const SizedBox(height: 24), - Container( - width: 150, - height: 16, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), - const SizedBox(height: 12), - Container( - width: 100, - height: 12, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), - ], - ), - ); - } - - Widget _buildExploreSkeleton(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(2), - child: GridView.builder( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 2, - mainAxisSpacing: 2, - ), - itemCount: 15, - itemBuilder: (context, index) { - return Container(color: Colors.white); - }, - ), - ); - } - - Widget _buildMessagesSkeleton(BuildContext context) { - return ListView.builder( - itemCount: 8, - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - child: Row( - children: [ - Container( - width: 56, - height: 56, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(28), - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: 100, - height: 14, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), - const SizedBox(height: 6), - Container( - width: 150, - height: 12, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), - ], - ), - ), - ], - ), - ); - }, - ); - } - - Widget _buildProfileSkeleton(BuildContext context) { - return SingleChildScrollView( - child: Column( - children: [ - const SizedBox(height: 24), - Container( - width: 96, - height: 96, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(48), - ), - ), - const SizedBox(height: 16), - Container( - width: 120, - height: 16, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), - const SizedBox(height: 24), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: List.generate( - 3, - (index) => Column( - children: [ - Container( - width: 40, - height: 20, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), - const SizedBox(height: 4), - Container( - width: 50, - height: 10, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), - ], - ), - ), - ), - const SizedBox(height: 24), - GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 2, - mainAxisSpacing: 2, - ), - itemCount: 9, - itemBuilder: (context, index) { - return Container(color: Colors.white); - }, - ), - ], - ), - ); - } - - Widget _buildGenericSkeleton(BuildContext context) { - final width = MediaQuery.of(context).size.width; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 16), - SingleChildScrollView( - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Row( - children: List.generate( - 6, - (index) => Padding( - padding: const EdgeInsets.only(right: 12), - child: Column( - children: [ - Container( - width: 60, - height: 60, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(30), - ), - ), - const SizedBox(height: 8), - Container( - width: 40, - height: 8, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), - ], - ), - ), - ), - ), - ), - const SizedBox(height: 16), - Expanded( - child: ListView.builder( - padding: const EdgeInsets.symmetric(horizontal: 12), - itemCount: 3, - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.only(bottom: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(18), - ), - ), - const SizedBox(width: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: width * 0.4, - height: 10, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), - const SizedBox(height: 6), - Container( - width: width * 0.25, - height: 8, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), - ], - ), - ], - ), - const SizedBox(height: 12), - Container( - width: double.infinity, - height: width * 1.1, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(18), - ), - ), - const SizedBox(height: 12), - Container( - width: width * 0.7, - height: 10, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), - const SizedBox(height: 6), - Container( - width: width * 0.5, - height: 8, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(4), - ), - ), - ], - ), - ); - }, - ), - ), - ], - ); - } -} - -class _SlidingGradientTransform extends GradientTransform { - final double slidePercent; - - const _SlidingGradientTransform({required this.slidePercent}); - - @override - Matrix4 transform(Rect bounds, {TextDirection? textDirection}) { - return Matrix4.translationValues(bounds.width * slidePercent, 0.0, 0.0); - } -} - -SkeletonType getSkeletonTypeFromUrl(String url) { - final parsed = Uri.tryParse(url); - if (parsed == null) return SkeletonType.generic; - final path = parsed.path.toLowerCase(); - - if (path.startsWith('/reels') || path.startsWith('/reel/')) { - return SkeletonType.reels; - } else if (path.startsWith('/explore')) { - return SkeletonType.explore; - } else if (path.startsWith('/messages') || path.startsWith('/inbox')) { - return SkeletonType.messages; - } else if (path.startsWith('/') && !path.startsWith('/accounts')) { - if (path.split('/').length <= 2) { - return SkeletonType.feed; - } - return SkeletonType.profile; - } - return SkeletonType.generic; -} diff --git a/lib/features/native_nav/native_bottom_nav.dart b/lib/features/native_nav/native_bottom_nav.dart deleted file mode 100644 index 270f5fd..0000000 --- a/lib/features/native_nav/native_bottom_nav.dart +++ /dev/null @@ -1,161 +0,0 @@ -import 'package:flutter/material.dart'; - -class NativeBottomNav extends StatelessWidget { - final String currentUrl; - final bool reelsEnabled; - final bool exploreEnabled; - final bool minimalMode; - final Function(String path) onNavigate; - - const NativeBottomNav({ - super.key, - required this.currentUrl, - required this.reelsEnabled, - required this.exploreEnabled, - required this.minimalMode, - required this.onNavigate, - }); - - String get _path { - final parsed = Uri.tryParse(currentUrl); - if (parsed != null && parsed.path.isNotEmpty) return parsed.path; - return currentUrl; // may already be a path from SPA callbacks - } - - bool get _onHome => _path == '/' || _path.isEmpty; - bool get _onExplore => _path.startsWith('/explore'); - bool get _onReels => _path.startsWith('/reels') || _path.startsWith('/reel/'); - bool get _onProfile => - _path.startsWith('/accounts') || - _path.contains('/profile') || - _path.split('/').where((p) => p.isNotEmpty).length == 1; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final isDark = theme.brightness == Brightness.dark; - - final bgColor = theme.colorScheme.surface.withValues( - alpha: isDark ? 0.95 : 0.98, - ); - final iconColorInactive = isDark ? Colors.white70 : Colors.black54; - final iconColorActive = theme.colorScheme.primary; - - final tabs = <_NavItem>[ - _NavItem( - icon: Icons.home_outlined, - activeIcon: Icons.home, - label: 'Home', - path: '/', - active: _onHome, - enabled: true, - ), - if (!minimalMode) - _NavItem( - icon: Icons.search_outlined, - activeIcon: Icons.search, - label: 'Search', - path: '/explore/', - active: _onExplore, - enabled: exploreEnabled, - ), - _NavItem( - icon: Icons.add_box_outlined, - activeIcon: Icons.add_box, - label: 'New', - path: '/create/select/', - active: false, - enabled: true, - ), - if (!minimalMode) - _NavItem( - icon: Icons.play_circle_outline, - activeIcon: Icons.play_circle, - label: 'Reels', - path: '/reels/', - active: _onReels, - enabled: reelsEnabled, - ), - _NavItem( - icon: Icons.person_outline, - activeIcon: Icons.person, - label: 'Profile', - path: '/accounts/edit/', - active: _onProfile, - enabled: true, - ), - ]; - - return SafeArea( - top: false, - child: Container( - decoration: BoxDecoration( - color: bgColor, - border: Border( - top: BorderSide( - color: isDark ? Colors.white10 : Colors.black12, - width: 0.5, - ), - ), - ), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: tabs.map((item) { - final color = item.active ? iconColorActive : iconColorInactive; - final opacity = item.enabled ? 1.0 : 0.35; - - return Expanded( - child: Opacity( - opacity: opacity, - child: InkWell( - onTap: item.enabled ? () => onNavigate(item.path) : null, - borderRadius: BorderRadius.circular(24), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 4, - vertical: 6, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - item.active ? item.activeIcon : item.icon, - size: 24, - color: color, - ), - const SizedBox(height: 2), - Text( - item.label, - style: TextStyle(fontSize: 10, color: color), - ), - ], - ), - ), - ), - ), - ); - }).toList(), - ), - ), - ); - } -} - -class _NavItem { - final IconData icon; - final IconData activeIcon; - final String label; - final String path; - final bool active; - final bool enabled; - - _NavItem({ - required this.icon, - required this.activeIcon, - required this.label, - required this.path, - required this.active, - required this.enabled, - }); -} diff --git a/lib/features/preloader/instagram_preloader.dart b/lib/features/preloader/instagram_preloader.dart deleted file mode 100644 index 48d9ce0..0000000 --- a/lib/features/preloader/instagram_preloader.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'dart:collection'; - -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; - -import '../../scripts/spa_navigation_monitor.dart'; -import '../../scripts/native_feel.dart'; -import '../../scripts/focus_scripts.dart'; - -class InstagramPreloader { - static HeadlessInAppWebView? _headlessWebView; - static InAppWebViewController? controller; - static final InAppWebViewKeepAlive keepAlive = InAppWebViewKeepAlive(); - static bool isReady = false; - - static Future start(String userAgent) async { - if (_headlessWebView != null) return; - - _headlessWebView = HeadlessInAppWebView( - keepAlive: keepAlive, - initialUrlRequest: URLRequest(url: WebUri('https://www.instagram.com/')), - initialSettings: InAppWebViewSettings( - userAgent: userAgent, - mediaPlaybackRequiresUserGesture: true, - useHybridComposition: true, - cacheEnabled: true, - cacheMode: CacheMode.LOAD_CACHE_ELSE_NETWORK, - domStorageEnabled: true, - databaseEnabled: true, - hardwareAcceleration: true, - transparentBackground: true, - safeBrowsingEnabled: false, - ), - initialUserScripts: UnmodifiableListView([ - // DM Ghost — comprehensive blocking, gated by window.__fgFullDmGhost flag. - // it should have worked, but sadly it didnt - UserScript( - source: kFullDmGhostJS, - injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START, - ), - UserScript( - source: kSpaNavigationMonitorScript, - injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START, - ), - UserScript( - source: kNativeFeelingScript, - injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START, - ), - // ReelMetadataExtractor removed — reel history feature deleted - ]), - onWebViewCreated: (c) { - controller = c; - }, - onLoadStop: (c, url) async { - isReady = true; - await c.evaluateJavascript(source: kNativeFeelingPostLoadScript); - }, - ); - - await _headlessWebView!.run(); - } - - static void dispose() { - _headlessWebView?.dispose(); - _headlessWebView = null; - controller = null; - isReady = false; - } -} diff --git a/lib/features/reels_history/reels_history_screen.dart b/lib/features/reels_history/reels_history_screen.dart deleted file mode 100644 index 3e5f70c..0000000 --- a/lib/features/reels_history/reels_history_screen.dart +++ /dev/null @@ -1,252 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; - -import 'reels_history_service.dart'; - -class ReelsHistoryScreen extends StatefulWidget { - const ReelsHistoryScreen({super.key}); - - @override - State createState() => _ReelsHistoryScreenState(); -} - -class _ReelsHistoryScreenState extends State { - final _service = ReelsHistoryService(); - late Future> _future; - - @override - void initState() { - super.initState(); - _future = _service.getEntries(); - } - - Future _refresh() async { - setState(() => _future = _service.getEntries()); - } - - String _formatTimestamp(DateTime dt) => - DateFormat('EEE, MMM d â€Ē h:mm a').format(dt.toLocal()); - - String _relativeTime(DateTime dt) { - final diff = DateTime.now().difference(dt.toLocal()); - if (diff.inMinutes < 60) return '${diff.inMinutes}m ago'; - if (diff.inHours < 24) return '${diff.inHours}h ago'; - if (diff.inDays < 7) return '${diff.inDays}d ago'; - return _formatTimestamp(dt); - } - - Future _confirmClearAll() async { - final ok = await showDialog( - context: context, - builder: (ctx) => AlertDialog( - title: const Text('Clear Reels History?'), - content: const Text( - 'This removes all history entries stored locally on this device.', - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - child: const Text('Cancel'), - ), - ElevatedButton( - style: ElevatedButton.styleFrom(backgroundColor: Colors.redAccent), - onPressed: () => Navigator.pop(ctx, true), - child: const Text('Clear All'), - ), - ], - ), - ); - if (ok != true || !mounted) return; - await _service.clearAll(); - await _refresh(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text( - 'Reels History', - style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600), - ), - centerTitle: true, - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_new, size: 18), - onPressed: () => Navigator.pop(context), - ), - actions: [ - IconButton( - tooltip: 'Clear All', - onPressed: _confirmClearAll, - icon: const Icon(Icons.delete_outline), - ), - ], - ), - body: RefreshIndicator( - onRefresh: _refresh, - child: FutureBuilder>( - future: _future, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } - - final entries = snapshot.data ?? const []; - - return ListView( - physics: const AlwaysScrollableScrollPhysics(), - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), - child: Row( - children: [ - const Icon( - Icons.lock_outline, - size: 12, - color: Colors.grey, - ), - const SizedBox(width: 6), - Text( - '${entries.length} reels stored locally on device only', - style: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), - ), - ], - ), - ), - const SizedBox(height: 4), - if (entries.isEmpty) - const Padding( - padding: EdgeInsets.all(48), - child: Center( - child: Column( - children: [ - Icon( - Icons.play_circle_outline, - size: 48, - color: Colors.grey, - ), - SizedBox(height: 12), - Text( - 'No Reels history yet.\nWatch a Reel and it will appear here.', - textAlign: TextAlign.center, - style: TextStyle(color: Colors.grey), - ), - ], - ), - ), - ) - else - ...entries.map((entry) { - return Dismissible( - key: ValueKey(entry.id), - direction: DismissDirection.endToStart, - background: Container( - color: Colors.redAccent.withValues(alpha: 0.15), - alignment: Alignment.centerRight, - padding: const EdgeInsets.only(right: 20), - child: const Icon( - Icons.delete_outline, - color: Colors.redAccent, - ), - ), - onDismissed: (_) async { - await _service.deleteEntry(entry.id); - // Don't call _refresh() on dismiss — removes the entry from - // the live list already via Dismissible, avoids double setState - }, - child: ListTile( - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 4, - ), - leading: _ReelThumbnail(url: entry.thumbnailUrl), - title: Text( - entry.title, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 14), - ), - subtitle: Text( - _relativeTime(entry.visitedAt), - style: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), - ), - trailing: const Icon( - Icons.play_circle_outline, - color: Colors.blue, - size: 20, - ), - onTap: () => Navigator.pop(context, entry.url), - ), - ); - }), - const SizedBox(height: 40), - ], - ); - }, - ), - ), - ); - } -} - -/// Thumbnail widget that correctly sends Referer + User-Agent headers -/// required by Instagram's CDN. Without these the CDN returns 403. -class _ReelThumbnail extends StatelessWidget { - final String url; - const _ReelThumbnail({required this.url}); - - @override - Widget build(BuildContext context) { - return ClipRRect( - borderRadius: BorderRadius.circular(8), - child: SizedBox( - width: 60, - height: 60, - child: url.isEmpty - ? _placeholder() - : Image.network( - url, - width: 60, - height: 60, - fit: BoxFit.cover, - headers: const { - // Instagram CDN requires a valid Referer header - 'Referer': 'https://www.instagram.com/', - 'User-Agent': - 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) ' - 'AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/22G86', - }, - errorBuilder: (_, _, _) => _placeholder(), - loadingBuilder: (_, child, progress) { - if (progress == null) return child; - return Container( - color: Colors.white10, - child: const Center( - child: SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(strokeWidth: 1.5), - ), - ), - ); - }, - ), - ), - ); - } - - Widget _placeholder() => Container( - color: Colors.white10, - child: const Icon( - Icons.play_circle_outline, - color: Colors.white30, - size: 28, - ), - ); -} diff --git a/lib/features/reels_history/reels_history_service.dart b/lib/features/reels_history/reels_history_service.dart deleted file mode 100644 index 5d76b04..0000000 --- a/lib/features/reels_history/reels_history_service.dart +++ /dev/null @@ -1,170 +0,0 @@ -import 'dart:convert'; - -import 'package:shared_preferences/shared_preferences.dart'; - -class ReelsHistoryEntry { - final String id; - final String url; - final String title; - final String thumbnailUrl; - final DateTime visitedAt; - final int durationSeconds; // How long the session lasted - final int adsWatchedInSession; // How many ads watched during this session - - const ReelsHistoryEntry({ - required this.id, - required this.url, - required this.title, - required this.thumbnailUrl, - required this.visitedAt, - this.durationSeconds = 0, - this.adsWatchedInSession = 0, - }); - - Map toJson() => { - 'id': id, - 'url': url, - 'title': title, - 'thumbnailUrl': thumbnailUrl, - 'visitedAt': visitedAt.toUtc().toIso8601String(), - 'durationSeconds': durationSeconds, - 'adsWatchedInSession': adsWatchedInSession, - }; - - static ReelsHistoryEntry fromJson(Map json) { - return ReelsHistoryEntry( - id: (json['id'] as String?) ?? '', - url: (json['url'] as String?) ?? '', - title: (json['title'] as String?) ?? 'Instagram Reel', - thumbnailUrl: (json['thumbnailUrl'] as String?) ?? '', - visitedAt: - DateTime.tryParse((json['visitedAt'] as String?) ?? '') ?? - DateTime.now().toUtc(), - durationSeconds: (json['durationSeconds'] as num?)?.toInt() ?? 0, - adsWatchedInSession: (json['adsWatchedInSession'] as num?)?.toInt() ?? 0, - ); - } -} - -class ReelsHistoryService { - static const String _prefsKey = 'reels_history'; - static const int _maxEntries = 200; - - SharedPreferences? _prefs; - - Future _getPrefs() async { - _prefs ??= await SharedPreferences.getInstance(); - return _prefs!; - } - - Future> getEntries() async { - final prefs = await _getPrefs(); - final raw = prefs.getString(_prefsKey); - if (raw == null || raw.isEmpty) return []; - try { - final decoded = jsonDecode(raw) as List; - final entries = decoded - .whereType() - .map((e) => ReelsHistoryEntry.fromJson(e.cast())) - .where((e) => e.url.isNotEmpty) - .toList(); - entries.sort((a, b) => b.visitedAt.compareTo(a.visitedAt)); - return entries; - } catch (_) { - return []; - } - } - - Future addEntry({ - required String url, - required String title, - required String thumbnailUrl, - int durationSeconds = 0, - int adsWatchedInSession = 0, - }) async { - if (url.isEmpty) return; - final now = DateTime.now().toUtc(); - - final entries = await getEntries(); - final recentDuplicate = entries.any((e) { - if (e.url != url) return false; - final diff = now.difference(e.visitedAt).inSeconds.abs(); - return diff <= 60; - }); - if (recentDuplicate) return; - - final entry = ReelsHistoryEntry( - id: DateTime.now().microsecondsSinceEpoch.toString(), - url: url, - title: title.isEmpty ? 'Instagram Reel' : title, - thumbnailUrl: thumbnailUrl, - visitedAt: now, - durationSeconds: durationSeconds, - adsWatchedInSession: adsWatchedInSession, - ); - - final updated = [entry, ...entries]; - if (updated.length > _maxEntries) { - updated.removeRange(_maxEntries, updated.length); - } - await _save(updated); - } - - Future deleteEntry(String id) async { - final entries = await getEntries(); - entries.removeWhere((e) => e.id == id); - await _save(entries); - } - - /// Get average reels watched per day in the last 7 days. - Future getWeeklyAverageReels() async { - final entries = await getEntries(); - if (entries.isEmpty) return 0; - - final now = DateTime.now(); - final sevenDaysAgo = now.subtract(const Duration(days: 7)); - final recent = entries - .where((e) => e.visitedAt.isAfter(sevenDaysAgo)) - .toList(); - - if (recent.isEmpty) return 0; - return recent.length / 7.0; - } - - /// Get reel counts grouped by day (for the level system). - Future> getDailyReelCounts({int days = 30}) async { - final entries = await getEntries(); - final now = DateTime.now(); - final cutoff = now.subtract(Duration(days: days)); - final recent = entries.where((e) => e.visitedAt.isAfter(cutoff)).toList(); - - final Map counts = {}; - for (final entry in recent) { - final dayKey = - '${entry.visitedAt.year}-' - '${entry.visitedAt.month.toString().padLeft(2, '0')}-' - '${entry.visitedAt.day.toString().padLeft(2, '0')}'; - counts[dayKey] = (counts[dayKey] ?? 0) + 1; - } - return counts; - } - - /// Get total reels watched in the last [days] days. - Future getRecentReelCount({int days = 7}) async { - final entries = await getEntries(); - final now = DateTime.now(); - final cutoff = now.subtract(Duration(days: days)); - return entries.where((e) => e.visitedAt.isAfter(cutoff)).length; - } - - Future clearAll() async { - final prefs = await _getPrefs(); - await prefs.remove(_prefsKey); - } - - Future _save(List entries) async { - final prefs = await _getPrefs(); - final jsonList = entries.map((e) => e.toJson()).toList(); - await prefs.setString(_prefsKey, jsonEncode(jsonList)); - } -} diff --git a/lib/features/screen_time/screen_time_screen.dart b/lib/features/screen_time/screen_time_screen.dart deleted file mode 100644 index d0da8fe..0000000 --- a/lib/features/screen_time/screen_time_screen.dart +++ /dev/null @@ -1,307 +0,0 @@ -import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import '../../services/screen_time_service.dart'; - -class ScreenTimeScreen extends StatelessWidget { - const ScreenTimeScreen({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text( - 'Screen Time', - style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600), - ), - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_new, size: 18), - onPressed: () => Navigator.pop(context), - ), - ), - body: Consumer( - builder: (context, service, _) { - final data = service.secondsByDate; - final todayKey = _todayKey(); - final todaySeconds = data[todayKey] ?? 0; - - final last7 = _lastNDays(7); - final barSpots = []; - int totalSeconds = 0; - for (var i = 0; i < last7.length; i++) { - final key = last7[i]; - final sec = data[key] ?? 0; - totalSeconds += sec; - barSpots.add( - BarChartGroupData( - x: i, - barRods: [ - BarChartRodData( - toY: sec / 60.0, - width: 10, - borderRadius: BorderRadius.circular(4), - color: Colors.blueAccent, - ), - ], - ), - ); - } - - final daysWithData = data.values.isEmpty ? 0 : data.length; - final weeklyAvgMinutes = last7.isEmpty - ? 0.0 - : totalSeconds / 60.0 / last7.length; - final allTimeMinutes = totalSeconds / 60.0; - - return ListView( - padding: const EdgeInsets.all(16), - children: [ - _buildStatCard( - title: 'Today', - value: _formatDuration(todaySeconds), - ), - const SizedBox(height: 16), - _buildChartCard(barSpots, last7), - const SizedBox(height: 16), - _buildInlineStats( - weeklyAvgMinutes: weeklyAvgMinutes, - allTimeMinutes: allTimeMinutes, - daysWithData: daysWithData, - ), - const SizedBox(height: 24), - const Text( - 'All data stored locally on your device only', - textAlign: TextAlign.center, - style: TextStyle(fontSize: 12, color: Colors.grey), - ), - const SizedBox(height: 24), - Center( - child: OutlinedButton.icon( - onPressed: () => _confirmReset(context, service), - icon: const Icon(Icons.delete_outline, size: 18), - label: const Text('Reset all data'), - style: OutlinedButton.styleFrom( - foregroundColor: Colors.redAccent, - side: const BorderSide(color: Colors.redAccent), - ), - ), - ), - ], - ); - }, - ), - ); - } - - static String _todayKey() { - final now = DateTime.now(); - return '${now.year.toString().padLeft(4, '0')}-' - '${now.month.toString().padLeft(2, '0')}-' - '${now.day.toString().padLeft(2, '0')}'; - } - - static List _lastNDays(int n) { - final now = DateTime.now(); - return List.generate(n, (i) { - final d = now.subtract(Duration(days: n - 1 - i)); - return '${d.year.toString().padLeft(4, '0')}-' - '${d.month.toString().padLeft(2, '0')}-' - '${d.day.toString().padLeft(2, '0')}'; - }); - } - - Widget _buildStatCard({required String title, required String value}) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.blue.withValues(alpha: 0.08), - borderRadius: BorderRadius.circular(14), - border: Border.all(color: Colors.blue.withValues(alpha: 0.2)), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(title, style: const TextStyle(fontSize: 14, color: Colors.grey)), - Text( - value, - style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold), - ), - ], - ), - ); - } - - Widget _buildChartCard(List bars, List last7Keys) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.15), - borderRadius: BorderRadius.circular(14), - border: Border.all(color: Colors.white12), - ), - height: 220, - child: BarChart( - BarChartData( - barGroups: bars, - gridData: FlGridData(show: false), - borderData: FlBorderData(show: false), - titlesData: FlTitlesData( - leftTitles: const AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - topTitles: const AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - rightTitles: const AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - getTitlesWidget: (value, meta) { - final index = value.toInt(); - if (index < 0 || index >= last7Keys.length) { - return const SizedBox.shrink(); - } - final label = last7Keys[index].substring( - last7Keys[index].length - 2, - ); - return Padding( - padding: const EdgeInsets.only(top: 4), - child: Text( - label, - style: const TextStyle(fontSize: 10, color: Colors.grey), - ), - ); - }, - ), - ), - ), - ), - ), - ); - } - - Widget _buildInlineStats({ - required double weeklyAvgMinutes, - required double allTimeMinutes, - required int daysWithData, - }) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.02), - borderRadius: BorderRadius.circular(14), - border: Border.all(color: Colors.white10), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _inlineStat( - label: '7-day avg', - value: '${weeklyAvgMinutes.toStringAsFixed(1)} min', - ), - _inlineDivider(), - _inlineStat( - label: 'All-time total', - value: '${allTimeMinutes.toStringAsFixed(0)} min', - ), - _inlineDivider(), - _inlineStat(label: 'Tracked days', value: '$daysWithData'), - ], - ), - ); - } - - Widget _inlineStat({required String label, required String value}) { - return Column( - children: [ - Text( - value, - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 4), - Text(label, style: const TextStyle(fontSize: 11, color: Colors.grey)), - ], - ); - } - - Widget _inlineDivider() { - return Container( - width: 1, - height: 40, - color: Colors.white.withValues(alpha: 0.08), - ); - } - - static String _formatDuration(int seconds) { - if (seconds < 60) { - return '0:${seconds.toString().padLeft(2, '0')}'; - } - final h = seconds ~/ 3600; - final m = (seconds % 3600) ~/ 60; - if (h > 0) { - return '${h}h ${m.toString().padLeft(2, '0')}m'; - } - final s = seconds % 60; - return '${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}'; - } - - Future _confirmReset( - BuildContext context, - ScreenTimeService service, - ) async { - final first = await showDialog( - context: context, - builder: (ctx) => AlertDialog( - title: const Text('Reset screen time?'), - content: const Text( - 'This will clear all locally stored screen time data for the last 30 days.', - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => Navigator.pop(ctx, true), - child: const Text( - 'Continue', - style: TextStyle(color: Colors.redAccent), - ), - ), - ], - ), - ); - if (first != true) return; - if (!context.mounted) return; - - final second = await showDialog( - context: context, - builder: (ctx) => AlertDialog( - title: const Text('Confirm reset'), - content: const Text( - 'Are you sure you want to permanently delete all screen time data?', - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - child: const Text('No'), - ), - TextButton( - onPressed: () => Navigator.pop(ctx, true), - child: const Text( - 'Yes, delete', - style: TextStyle(color: Colors.redAccent), - ), - ), - ], - ), - ); - if (!context.mounted) return; - if (second == true) { - await service.resetAll(); - } - } -} diff --git a/lib/features/update_checker/update_banner.dart b/lib/features/update_checker/update_banner.dart deleted file mode 100644 index 69051f8..0000000 --- a/lib/features/update_checker/update_banner.dart +++ /dev/null @@ -1,128 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:url_launcher/url_launcher.dart'; - -import 'update_checker_service.dart'; - -class UpdateBanner extends StatefulWidget { - final UpdateInfo updateInfo; - final VoidCallback onDismiss; - - const UpdateBanner({ - super.key, - required this.updateInfo, - required this.onDismiss, - }); - - @override - State createState() => _UpdateBannerState(); -} - -class _UpdateBannerState extends State { - bool _isExpanded = false; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final colorScheme = theme.colorScheme; - - return AnimatedContainer( - duration: const Duration(milliseconds: 200), - width: double.infinity, - decoration: BoxDecoration( - color: colorScheme.secondaryContainer, - border: Border( - bottom: BorderSide(color: colorScheme.outlineVariant, width: 0.5), - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - child: Row( - children: [ - const Text('🎉', style: TextStyle(fontSize: 16)), - const SizedBox(width: 8), - Expanded( - child: Text( - 'FocusGram ${widget.updateInfo.latestVersion} available', - style: theme.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w600, - ), - ), - ), - IconButton( - icon: Icon( - _isExpanded ? Icons.expand_less : Icons.expand_more, - size: 18, - ), - onPressed: () { - HapticFeedback.lightImpact(); - setState(() => _isExpanded = !_isExpanded); - }, - ), - IconButton( - icon: const Icon(Icons.close, size: 18), - onPressed: () { - HapticFeedback.lightImpact(); - widget.onDismiss(); - }, - ), - ], - ), - ), - if (_isExpanded) ...[ - const Divider(height: 1), - Padding( - padding: const EdgeInsets.fromLTRB(12, 8, 12, 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "What's new", - style: theme.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - Text( - _formatReleaseNotes(widget.updateInfo.whatsNew), - style: theme.textTheme.bodySmall, - ), - const SizedBox(height: 12), - SizedBox( - width: double.infinity, - child: OutlinedButton( - onPressed: () async { - final uri = Uri.parse(widget.updateInfo.releaseUrl); - await launchUrl( - uri, - mode: LaunchMode.externalApplication, - ); - }, - child: const Text('Download on GitHub'), - ), - ), - ], - ), - ), - ], - ], - ), - ); - } - - String _formatReleaseNotes(String raw) { - var text = raw; - text = text.replaceAll(RegExp(r'#{1,6}\s'), ''); - text = text.replaceAll(RegExp(r'\*\*(.*?)\*\*'), r'\1'); - text = text.replaceAll(RegExp(r'\*(.*?)\*'), r'\1'); - text = text.replaceAll( - RegExp(r'\[([^\]]+)\]\([^)]+\)'), - r'\1', - ); // links -> text - text = text.replaceAll(RegExp(r'`([^`]+)`'), r'\1'); - return text.trim(); - } -} diff --git a/lib/features/update_checker/update_checker_service.dart b/lib/features/update_checker/update_checker_service.dart deleted file mode 100644 index 2ffd2ff..0000000 --- a/lib/features/update_checker/update_checker_service.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'dart:convert'; -import 'package:flutter/foundation.dart'; -import 'package:http/http.dart' as http; -import 'package:package_info_plus/package_info_plus.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -class UpdateInfo { - final String latestVersion; // e.g. "1.0.0" - final String releaseUrl; // html_url - final String whatsNew; // trimmed body - final bool isUpdateAvailable; - - const UpdateInfo({ - required this.latestVersion, - required this.releaseUrl, - required this.whatsNew, - required this.isUpdateAvailable, - }); -} - -class UpdateCheckerService extends ChangeNotifier { - static const String _lastDismissedKey = 'last_dismissed_update_version'; - static const String _githubUrl = - 'https://api.github.com/repos/Ujwal223/FocusGram/releases/latest'; - - UpdateInfo? _updateInfo; - bool _isDismissed = false; - - bool get hasUpdate => _updateInfo != null && !_isDismissed; - UpdateInfo? get updateInfo => hasUpdate ? _updateInfo : null; - - Future checkForUpdates() async { - try { - final response = await http - .get(Uri.parse(_githubUrl)) - .timeout(const Duration(seconds: 5)); - if (response.statusCode != 200) return; - - final data = json.decode(response.body); - final String gitVersionTag = - data['tag_name'] ?? ''; // e.g. "v0.9.8-beta.2" - final String htmlUrl = data['html_url'] ?? ''; - final String body = (data['body'] as String?) ?? ''; - - if (gitVersionTag.isEmpty || htmlUrl.isEmpty) return; - - final packageInfo = await PackageInfo.fromPlatform(); - final currentVersion = packageInfo.version; // e.g. "0.9.8-beta.2" - - if (!_isNewerVersion(gitVersionTag, currentVersion)) return; - - final prefs = await SharedPreferences.getInstance(); - final dismissedVersion = prefs.getString(_lastDismissedKey); - if (dismissedVersion == gitVersionTag) { - _isDismissed = true; - return; - } - - final cleanVersion = gitVersionTag.startsWith('v') - ? gitVersionTag.substring(1) - : gitVersionTag; - - var trimmed = body.trim(); - if (trimmed.length > 1500) { - trimmed = trimmed.substring(0, 1500).trim(); - } - - _updateInfo = UpdateInfo( - latestVersion: cleanVersion, - releaseUrl: htmlUrl, - whatsNew: trimmed, - isUpdateAvailable: true, - ); - _isDismissed = false; - notifyListeners(); - } catch (e) { - // debugPrint('Update check failed: $e'); - } - } - - Future dismissUpdate() async { - if (_updateInfo == null) return; - _isDismissed = true; - final prefs = await SharedPreferences.getInstance(); - await prefs.setString(_lastDismissedKey, _updateInfo!.latestVersion); - notifyListeners(); - } - - bool _isNewerVersion(String gitTag, String current) { - // Clean versions: strip 'v' and everything after '-' (beta/rc) - String cleanGit = gitTag.startsWith('v') ? gitTag.substring(1) : gitTag; - String cleanCurrent = current; - - List gitParts = cleanGit.split('-')[0].split('.'); - List currentParts = cleanCurrent.split('-')[0].split('.'); - - for (int i = 0; i < gitParts.length && i < currentParts.length; i++) { - int gitNum = int.tryParse(gitParts[i]) ?? 0; - int curNum = int.tryParse(currentParts[i]) ?? 0; - if (gitNum > curNum) return true; - if (gitNum < curNum) return false; - } - - return false; - } -} diff --git a/lib/focus_settings.dart b/lib/focus_settings.dart deleted file mode 100644 index 7a4b454..0000000 --- a/lib/focus_settings.dart +++ /dev/null @@ -1,17 +0,0 @@ -class FocusSettings { - final bool ghostMode; // DM ghost — blocks seen/DM signals comprehensively - final bool noAds; // strip ads and sponsored posts - final bool noStories; // hide story tray - final bool noReels; // hide reels tab - final bool noAutoplay; // stop videos autoplaying - final bool noDMs; // block direct messages - - const FocusSettings({ - this.ghostMode = false, - this.noAds = true, - this.noStories = false, - this.noReels = false, - this.noAutoplay = false, - this.noDMs = false, - }); -} diff --git a/lib/main.dart b/lib/main.dart deleted file mode 100644 index 2511a94..0000000 --- a/lib/main.dart +++ /dev/null @@ -1,242 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; -import 'package:app_links/app_links.dart'; -import 'package:hive_flutter/hive_flutter.dart'; -// google_mobile_ads removed — switched to Adsterra only -import 'services/session_manager.dart'; -import 'services/settings_service.dart'; -import 'services/screen_time_service.dart'; -import 'services/focusgram_router.dart'; -import 'services/injection_controller.dart'; -import 'services/credit_store.dart'; -import 'services/bait_engine.dart'; -import 'services/app_lock_service.dart'; -import 'services/level_service.dart'; -import 'services/snapshot_service.dart'; -import 'screens/app_lock_screen.dart'; -import 'screens/onboarding_page.dart'; -import 'screens/main_webview_page.dart'; -import 'screens/breath_gate_screen.dart'; -import 'screens/app_session_picker.dart'; -import 'screens/cooldown_gate_screen.dart'; -import 'services/notification_service.dart'; -import 'features/update_checker/update_checker_service.dart'; -import 'features/preloader/instagram_preloader.dart'; -import 'widgets/remote_popup_handler.dart'; - -void main() async { - WidgetsFlutterBinding.ensureInitialized(); - - // Lock to portrait - await SystemChrome.setPreferredOrientations([ - DeviceOrientation.portraitUp, - DeviceOrientation.portraitDown, - ]); - - // ── Initialise storage & SDKs ────────────────────────────── - await Hive.initFlutter(); - final creditStore = CreditStore(); - final baitEngine = BaitEngine(); - final levelService = LevelService(); - final appLockService = AppLockService(); - final snapshotService = SnapshotService(); - - final sessionManager = SessionManager(); - final settingsService = SettingsService(); - final screenTimeService = ScreenTimeService(); - - final updateChecker = UpdateCheckerService(); - - await creditStore.init(); - await baitEngine.init(); - await appLockService.init(); - await levelService.init(); - await snapshotService.init(); - await sessionManager.init(); - await settingsService.init(); - await screenTimeService.init(); - await NotificationService().init(requestPermissions: true); - runApp( - MultiProvider( - providers: [ - ChangeNotifierProvider.value(value: sessionManager), - ChangeNotifierProvider.value(value: settingsService), - ChangeNotifierProvider.value(value: screenTimeService), - ChangeNotifierProvider.value(value: creditStore), - ChangeNotifierProvider.value(value: baitEngine), - ChangeNotifierProvider.value(value: levelService), - ChangeNotifierProvider.value(value: appLockService), - ChangeNotifierProvider.value(value: snapshotService), - ChangeNotifierProvider.value(value: updateChecker), - ], - child: const FocusGramApp(), - ), - ); - - // Fire and forget — preloads Instagram while app UI initialises. - unawaited(InstagramPreloader.start(InjectionController.iOSUserAgent)); -} - -class FocusGramApp extends StatelessWidget { - const FocusGramApp({super.key}); - - @override - Widget build(BuildContext context) { - final settings = context.watch(); - final isDark = settings.isDarkMode; - - return MaterialApp( - title: 'FocusGram', - debugShowCheckedModeBanner: false, - theme: ThemeData( - brightness: isDark ? Brightness.dark : Brightness.light, - colorScheme: isDark - ? ColorScheme.dark( - primary: Colors.blue.shade400, - surface: Colors.black, - ) - : ColorScheme.light(primary: Colors.blue), - scaffoldBackgroundColor: isDark ? Colors.black : Colors.white, - useMaterial3: true, - splashColor: Colors.transparent, - highlightColor: Colors.transparent, - ), - home: const InitialRouteHandler(), - ); - } -} - -/// Flow on every cold open: -/// 1. Onboarding (if first run) -/// 2. Cooldown Gate (if app-open cooldown active) -/// 3. Breath Gate (if enabled in settings) -/// 4. If an app session is already active, resume it -/// otherwise show App Session Picker -/// 5. Main WebView -class InitialRouteHandler extends StatefulWidget { - const InitialRouteHandler({super.key}); - - @override - State createState() => _InitialRouteHandlerState(); -} - -class _InitialRouteHandlerState extends State - with WidgetsBindingObserver { - bool _breathCompleted = false; - bool _appSessionStarted = false; - bool _onboardingCompleted = false; - bool _lockScreenDismissed = false; - late AppLinks _appLinks; - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addObserver(this); - _appLinks = AppLinks(); - _initDeepLinks(); - - WidgetsBinding.instance.addPostFrameCallback((_) { - RemotePopupHandler.checkAndShow(context); - }); - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - final appLock = context.read(); - if (state == AppLifecycleState.paused || - state == AppLifecycleState.inactive) { - appLock.onBackgrounded(); - } else if (state == AppLifecycleState.resumed) { - if (appLock.shouldLockOnResume) { - appLock.onLockScreenShown(); - _showLockScreen(); - } - } - } - - Future _showLockScreen() async { - final result = await Navigator.push( - context, - MaterialPageRoute(builder: (_) => const AppLockScreen(forAppWide: true)), - ); - if (result == true && mounted) { - setState(() => _lockScreenDismissed = true); - } - } - - Future _initDeepLinks() async { - // 1. Handle background links while app is running - _appLinks.uriLinkStream.listen((uri) { - // debugPrint('Incoming Deep Link: $uri'); - FocusGramRouter.pendingUrl.value = uri.toString(); - }); - - // 2. Handle the initial link that opened the app - final initialUri = await _appLinks.getInitialLink(); - if (initialUri != null) { - // debugPrint('Initial Deep Link: $initialUri'); - FocusGramRouter.pendingUrl.value = initialUri.toString(); - } - } - - @override - Widget build(BuildContext context) { - final sm = context.watch(); - final settings = context.watch(); - final appLock = context.watch(); - - // Step 0: App-wide lock (shows before everything, once per cold start) - if (appLock.needsUnlockOnStart && !_lockScreenDismissed) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (!appLock.isShowingLock) { - appLock.onLockScreenShown(); - _showLockScreen(); - } - }); - } - - // Step 1: Onboarding - if (settings.isFirstRun && !_onboardingCompleted) { - return OnboardingPage( - onFinish: () => setState(() => _onboardingCompleted = true), - ); - } - - // Step 2: Cooldown gate — if too soon since last session - if (sm.isAppOpenCooldownActive) { - return const CooldownGateScreen(); - } - - // Step 3: Breath gate - if (settings.showBreathGate && !_breathCompleted) { - return BreathGateScreen( - durationSeconds: settings.breathGateSeconds, - onFinish: () => setState(() => _breathCompleted = true), - ); - } - - // Step 4: App session picker / resume existing session - if (!_appSessionStarted) { - if (sm.isAppSessionActive) { - // User already has an active app session — don't ask intention again. - _appSessionStarted = true; - } else { - return AppSessionPickerScreen( - onSessionStarted: () => setState(() => _appSessionStarted = true), - ); - } - } - - // Step 5: Main app - return const MainWebViewPage(); - } -} diff --git a/lib/screens/adsterra_ad_screen.dart b/lib/screens/adsterra_ad_screen.dart deleted file mode 100644 index 37caf0a..0000000 --- a/lib/screens/adsterra_ad_screen.dart +++ /dev/null @@ -1,320 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:url_launcher/url_launcher.dart'; - -/// Full-screen ad page. User MUST click the ad to earn the reward. -/// -/// Flow: -/// 1. Ad loads in WebView for 20s -/// 2. User taps the ad → opens in external browser via url_launcher -/// 3. Timer continues counting to 20s regardless -/// 4. After 20s, "Continue & Earn Reward" button unlocks if BOTH ads clicked -/// 5. If ads not clicked within time, a Retry button appears to reload - -const String _kAdHtml = ''' - - - - - - - -

    -
    -
    Ad 2
    - - -
    - - -'''; - -class AdsterraAdScreen extends StatefulWidget { - final String sessionType; - final int requiredSeconds; - - const AdsterraAdScreen({ - super.key, - required this.sessionType, - this.requiredSeconds = 20, - }); - - @override - State createState() => _AdsterraAdScreenState(); -} - -class _AdsterraAdScreenState extends State { - int _elapsed = 0; - Timer? _timer; - int _adsClicked = 0; // count of ad clicks (need 2 for reward) - bool _retrying = false; - InAppWebViewController? _webController; - - @override - void initState() { - super.initState(); - _startTimer(); - } - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } - - void _startTimer() { - _timer?.cancel(); - _timer = Timer.periodic(const Duration(seconds: 1), (_) { - if (mounted) setState(() => _elapsed++); - }); - } - - Future _retry() async { - setState(() { - _retrying = true; - _elapsed = 0; - _adsClicked = 0; - }); - _startTimer(); - try { - await _webController?.loadData( - data: _kAdHtml, - mimeType: 'text/html', - encoding: 'utf-8', - baseUrl: WebUri('https://adsterra.com'), - ); - } catch (_) {} - if (mounted) setState(() => _retrying = false); - } - - @override - Widget build(BuildContext context) { - final timerDone = _elapsed >= widget.requiredSeconds; - final bothClicked = _adsClicked >= 2; - final done = timerDone && bothClicked; - - // When timer expired but ads not clicked, wait a bit then allow skip - final canSkip = timerDone && !bothClicked; - - String statusText; - Color statusColor; - if (bothClicked && timerDone) { - statusText = 'Ready!'; - statusColor = Colors.greenAccent; - } else if (bothClicked) { - statusText = 'Both ads clicked! Waiting for timerâ€Ķ'; - statusColor = Colors.greenAccent; - } else { - statusText = 'Tap BOTH ads below to earn XP ($_adsClicked/2)'; - statusColor = Colors.white.withValues(alpha: 0.4); - } - - String buttonText; - bool buttonEnabled; - VoidCallback? buttonAction; - - if (done) { - buttonText = 'Continue & Earn Reward'; - buttonEnabled = true; - buttonAction = () => Navigator.pop(context, true); - } else if (timerDone && !bothClicked) { - buttonText = 'Tap both ads to continue'; - buttonEnabled = false; - buttonAction = null; - } else { - final remaining = widget.requiredSeconds - _elapsed; - buttonText = 'Wait ${remaining > 0 ? remaining : 0}s'; - buttonEnabled = false; - buttonAction = null; - } - - return Scaffold( - backgroundColor: Colors.black, - body: SafeArea( - child: Column( - children: [ - // Top bar - Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Row( - children: [ - const Icon(Icons.videocam, color: Colors.white54, size: 18), - const SizedBox(width: 8), - const Text( - 'Sponsored', - style: TextStyle(color: Colors.white54, fontSize: 13), - ), - const Spacer(), - Text( - '${_elapsed.clamp(0, widget.requiredSeconds)}s / ${widget.requiredSeconds}s', - style: TextStyle( - color: done ? Colors.greenAccent : Colors.white54, - fontSize: 13, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - // Progress bar - ClipRRect( - borderRadius: BorderRadius.circular(999), - child: LinearProgressIndicator( - value: (_elapsed / widget.requiredSeconds).clamp(0.0, 1.0), - minHeight: 3, - backgroundColor: Colors.white12, - valueColor: AlwaysStoppedAnimation( - done ? Colors.greenAccent : Colors.blueAccent, - ), - ), - ), - // Hint text - Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - child: Text( - statusText, - style: TextStyle(color: statusColor, fontSize: 11), - ), - ), - // Ad WebView - Expanded( - child: InAppWebView( - initialSettings: InAppWebViewSettings( - javaScriptEnabled: true, - domStorageEnabled: true, - useHybridComposition: true, - transparentBackground: true, - cacheEnabled: false, - safeBrowsingEnabled: false, - ), - onWebViewCreated: (c) async { - _webController = c; - await c.loadData( - data: _kAdHtml, - mimeType: 'text/html', - encoding: 'utf-8', - baseUrl: WebUri('https://adsterra.com'), - ); - }, - onLoadStop: (_, url) { - // ad loaded - }, - shouldOverrideUrlLoading: (controller, nav) async { - final url = nav.request.url?.toString() ?? ''; - if (url.isNotEmpty && - !url.contains('adsterra.com') && - !url.startsWith('about:')) { - if (_adsClicked < 2) _adsClicked++; - if (mounted) setState(() {}); - await launchUrl( - Uri.parse(url), - mode: LaunchMode.externalApplication, - ); - return NavigationActionPolicy.CANCEL; - } - return NavigationActionPolicy.ALLOW; - }, - ), - ), - // Button area - Padding( - padding: const EdgeInsets.fromLTRB(24, 8, 24, 24), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - width: double.infinity, - height: 52, - child: ElevatedButton.icon( - onPressed: buttonEnabled ? buttonAction : null, - style: ElevatedButton.styleFrom( - backgroundColor: done - ? Colors.greenAccent - : Colors.grey, - foregroundColor: done ? Colors.black : Colors.white38, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - ), - icon: Icon( - done ? Icons.check_circle : Icons.timer_outlined, - size: 22, - ), - label: Text( - buttonText, - style: const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - ), - ), - ), - ), - // Retry / Skip buttons when timer done but ads not clicked - if (canSkip && !_retrying) ...[ - const SizedBox(height: 8), - SizedBox( - width: double.infinity, - height: 40, - child: OutlinedButton.icon( - onPressed: _retry, - style: OutlinedButton.styleFrom( - foregroundColor: Colors.orangeAccent, - side: BorderSide( - color: Colors.orangeAccent.withValues(alpha: 0.4), - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - icon: const Icon(Icons.refresh, size: 18), - label: const Text( - 'Retry — Reload Ads', - style: TextStyle(fontWeight: FontWeight.w600), - ), - ), - ), - const SizedBox(height: 4), - TextButton( - onPressed: () => Navigator.pop(context), - child: Text( - 'Skip (no reward)', - style: TextStyle( - color: Colors.white.withValues(alpha: 0.3), - fontSize: 13, - ), - ), - ), - ], - if (_retrying) - const Padding( - padding: EdgeInsets.only(top: 12), - child: SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Colors.orangeAccent, - ), - ), - ), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/screens/app_lock_screen.dart b/lib/screens/app_lock_screen.dart deleted file mode 100644 index 1652120..0000000 --- a/lib/screens/app_lock_screen.dart +++ /dev/null @@ -1,311 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; -import '../services/app_lock_service.dart'; - -/// The lock screen shown when FocusGram is locked. -/// -/// Supports PIN entry with optional scrambled keypad. -/// [forAppWide] controls which PIN to verify: true = app-wide, false = messages. -/// [title] lets the screen show context (e.g. "Messages Locked"). -class AppLockScreen extends StatefulWidget { - final bool forAppWide; - final String? title; - final String? subtitle; - - const AppLockScreen({ - super.key, - this.forAppWide = true, - this.title, - this.subtitle, - }); - - @override - State createState() => _AppLockScreenState(); -} - -class _AppLockScreenState extends State { - String _enteredPin = ''; - bool _showError = false; - String _errorMsg = ''; - bool _isVerifying = false; - List _scrambledDigits = []; - - @override - void initState() { - super.initState(); - _refreshScrambled(); - } - - void _refreshScrambled() { - setState(() { - _scrambledDigits = context.read().getScrambledDigits(); - }); - } - - @override - Widget build(BuildContext context) { - final appLock = context.watch(); - final isDark = Theme.of(context).brightness == Brightness.dark; - - return Scaffold( - backgroundColor: isDark ? Colors.black : Colors.white, - body: SafeArea( - child: Column( - children: [ - const Spacer(flex: 2), - // Icon - Container( - width: 64, - height: 64, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.blue.withValues(alpha: 0.1), - ), - child: const Icon( - Icons.lock_outline, - color: Colors.blueAccent, - size: 32, - ), - ), - const SizedBox(height: 20), - - // Title - Text( - widget.title ?? 'FocusGram is Locked', - style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 8), - Text( - widget.subtitle ?? 'Enter your PIN to unlock', - style: TextStyle( - fontSize: 14, - color: isDark ? Colors.white54 : Colors.black54, - ), - ), - - const SizedBox(height: 32), - - // PIN dots - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate(4, (i) { - final filled = i < _enteredPin.length; - return Container( - width: 16, - height: 16, - margin: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: filled - ? Colors.blueAccent - : (isDark ? Colors.white24 : Colors.black12), - ), - ); - }), - ), - - // Error text - if (_showError) - Padding( - padding: const EdgeInsets.only(top: 16), - child: Text( - _errorMsg, - style: const TextStyle(color: Colors.redAccent, fontSize: 13), - ), - ), - - if (_isVerifying) - const Padding( - padding: EdgeInsets.only(top: 16), - child: CircularProgressIndicator(strokeWidth: 2), - ), - - const Spacer(), - - // Keypad - _buildKeypad(appLock), - ], - ), - ), - ); - } - - Widget _buildKeypad(AppLockService appLock) { - final useScrambled = appLock.scrambleKeypad; - - // Build digit labels - final digitLabels = useScrambled - ? _scrambledDigits.map((d) => d.toString()).toList() - : List.generate(10, (i) => i.toString()); - - return Container( - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), - child: Column( - children: [ - // Row 1: 1 2 3 - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - _KeypadButton( - label: digitLabels[1], - onTap: () => _onDigit(digitLabels[1]), - ), - _KeypadButton( - label: digitLabels[2], - onTap: () => _onDigit(digitLabels[2]), - ), - _KeypadButton( - label: digitLabels[3], - onTap: () => _onDigit(digitLabels[3]), - ), - ], - ), - // Row 2: 4 5 6 - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - _KeypadButton( - label: digitLabels[4], - onTap: () => _onDigit(digitLabels[4]), - ), - _KeypadButton( - label: digitLabels[5], - onTap: () => _onDigit(digitLabels[5]), - ), - _KeypadButton( - label: digitLabels[6], - onTap: () => _onDigit(digitLabels[6]), - ), - ], - ), - // Row 3: 7 8 9 - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - _KeypadButton( - label: digitLabels[7], - onTap: () => _onDigit(digitLabels[7]), - ), - _KeypadButton( - label: digitLabels[8], - onTap: () => _onDigit(digitLabels[8]), - ), - _KeypadButton( - label: digitLabels[9], - onTap: () => _onDigit(digitLabels[9]), - ), - ], - ), - // Row 4: delete 0 scramble-refresh - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - _KeypadButton(label: 'âŒŦ', onTap: _onDelete, isFunction: true), - _KeypadButton( - label: digitLabels[0], - onTap: () => _onDigit(digitLabels[0]), - ), - if (useScrambled) - _KeypadButton( - label: 'âŸģ', - onTap: _refreshScrambled, - isFunction: true, - ) - else - const SizedBox(width: 72), // Placeholder - ], - ), - ], - ), - ); - } - - void _onDigit(String digit) { - if (_enteredPin.length >= 4) return; - setState(() { - _enteredPin += digit; - _showError = false; - }); - - if (_enteredPin.length == 4) { - _verifyPin(); - } - } - - void _onDelete() { - if (_enteredPin.isEmpty) return; - setState( - () => _enteredPin = _enteredPin.substring(0, _enteredPin.length - 1), - ); - } - - Future _verifyPin() async { - setState(() => _isVerifying = true); - - final appLock = context.read(); - final valid = await appLock.verifyPin( - _enteredPin, - forAppWide: widget.forAppWide, - ); - - if (!mounted) return; - - if (valid) { - HapticFeedback.heavyImpact(); - appLock.onUnlocked(); - Navigator.of(context).pop(true); - } else { - setState(() { - _showError = true; - _errorMsg = 'Wrong PIN. Try again.'; - _enteredPin = ''; - _isVerifying = false; - }); - HapticFeedback.heavyImpact(); - } - } - - -} - -class _KeypadButton extends StatelessWidget { - final String label; - final VoidCallback onTap; - final bool isFunction; - - const _KeypadButton({ - required this.label, - required this.onTap, - this.isFunction = false, - }); - - @override - Widget build(BuildContext context) { - final isDark = Theme.of(context).brightness == Brightness.dark; - - return SizedBox( - width: 72, - height: 72, - child: Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(36), - onTap: onTap, - child: Center( - child: Text( - label, - style: TextStyle( - fontSize: isFunction ? 28 : 24, - fontWeight: FontWeight.w500, - color: isFunction - ? Colors.blueAccent - : (isDark ? Colors.white : Colors.black87), - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/screens/app_lock_settings_page.dart b/lib/screens/app_lock_settings_page.dart deleted file mode 100644 index 30167b7..0000000 --- a/lib/screens/app_lock_settings_page.dart +++ /dev/null @@ -1,225 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; -import '../services/app_lock_service.dart'; -import 'app_lock_setup_screen.dart'; - -/// App Lock settings — two independent lock modes (app-wide + messages tab), -/// each with their own toggle, all backed by a single PIN. -class AppLockSettingsPage extends StatefulWidget { - const AppLockSettingsPage({super.key}); - - @override - State createState() => _AppLockSettingsPageState(); -} - -class _AppLockSettingsPageState extends State { - Future _ensurePin() async { - final appLock = context.read(); - if (appLock.hasPin) return true; - final ok = await Navigator.push( - context, - MaterialPageRoute(builder: (_) => const AppLockSetupScreen()), - ); - return ok == true; - } - - @override - Widget build(BuildContext context) { - final a = context.watch(); - final isDark = Theme.of(context).brightness == Brightness.dark; - final anythingOn = a.lockAppWide || a.lockMessages; - - return Scaffold( - appBar: AppBar( - title: const Text( - 'App Lock', - style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600), - ), - centerTitle: true, - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_new, size: 18), - onPressed: () => Navigator.pop(context), - ), - ), - body: ListView( - children: [ - // ── Status card ────────────────────────────────────── - Container( - margin: const EdgeInsets.all(16), - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: anythingOn - ? [ - Colors.blueAccent.withValues(alpha: 0.15), - Colors.blue.withValues(alpha: 0.05), - ] - : [ - Colors.grey.withValues(alpha: 0.1), - Colors.grey.withValues(alpha: 0.05), - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: anythingOn - ? Colors.blueAccent.withValues(alpha: 0.3) - : Colors.grey.withValues(alpha: 0.2), - ), - ), - child: Column( - children: [ - Icon( - anythingOn ? Icons.lock_rounded : Icons.lock_open_rounded, - color: anythingOn ? Colors.blueAccent : Colors.grey, - size: 48, - ), - const SizedBox(height: 12), - Text( - anythingOn ? 'Lock Active' : 'No Lock', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: anythingOn ? Colors.blueAccent : Colors.grey, - ), - ), - const SizedBox(height: 6), - Text( - _statusText(a), - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 13, - color: isDark ? Colors.white54 : Colors.black54, - ), - ), - ], - ), - ), - - const _SectionHeader(title: 'LOCK MODES'), - // ── App-wide lock ──────────────────────────────────── - SwitchListTile( - title: const Text('Lock Entire App'), - subtitle: const Text('Require PIN when opening FocusGram.'), - value: a.lockAppWide, - onChanged: (v) async { - if (v && !a.hasPin) { - if (!await _ensurePin()) return; - } - await a.setLockAppWide(v); - HapticFeedback.selectionClick(); - }, - ), - // ── Messages tab lock ──────────────────────────────── - SwitchListTile( - title: const Text('Lock Messages Tab'), - subtitle: const Text( - 'Require PIN to open Instagram Direct Messages', - ), - value: a.lockMessages, - onChanged: (v) async { - if (v && !a.hasPin) { - if (!await _ensurePin()) return; - } - await a.setLockMessages(v); - HapticFeedback.selectionClick(); - }, - ), - - // ─── PIN & extras ──────────────────────────────────── - if (a.hasPin) ...[ - const _SectionHeader(title: 'PIN & SECURITY'), - ListTile( - title: const Text('Change PIN'), - subtitle: const Text('Set a new 4-digit code'), - trailing: const Icon(Icons.arrow_forward_ios, size: 14), - onTap: () async { - final ok = await Navigator.push( - context, - MaterialPageRoute(builder: (_) => const AppLockSetupScreen()), - ); - if (ok == true && mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('PIN updated'))); - } - }, - ), - SwitchListTile( - title: const Text('Scrambled Keypad'), - subtitle: const Text('Shuffle digits on the lock screen'), - value: a.scrambleKeypad, - onChanged: (v) async { - await a.setScrambleKeypad(v); - HapticFeedback.selectionClick(); - }, - ), - // Biometrics option removed - ], - - // ── Hint if no PIN ─────────────────────────────────── - if (!a.hasPin) - Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), - child: Container( - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: Colors.blue.withValues(alpha: 0.07), - borderRadius: BorderRadius.circular(10), - ), - child: const Row( - children: [ - Icon( - Icons.info_outline, - size: 16, - color: Colors.blueAccent, - ), - SizedBox(width: 8), - Expanded( - child: Text( - 'Enable any lock mode above to set up your PIN.', - style: TextStyle(fontSize: 13), - ), - ), - ], - ), - ), - ), - - const SizedBox(height: 40), - ], - ), - ); - } - - String _statusText(AppLockService a) { - if (!a.hasPin) return 'Set a PIN to enable any lock mode.'; - final parts = []; - if (a.lockAppWide) parts.add('App-wide'); - if (a.lockMessages) parts.add('Messages tab'); - if (parts.isEmpty) return 'Both modes are off — enable one above.'; - return '${parts.join(' + ')} lock is active.'; - } -} - -class _SectionHeader extends StatelessWidget { - final String title; - const _SectionHeader({required this.title}); - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.fromLTRB(16, 24, 16, 8), - child: Text( - title, - style: const TextStyle( - color: Colors.grey, - fontSize: 11, - fontWeight: FontWeight.bold, - letterSpacing: 1.2, - ), - ), - ); - } -} diff --git a/lib/screens/app_lock_setup_screen.dart b/lib/screens/app_lock_setup_screen.dart deleted file mode 100644 index b9ebfb0..0000000 --- a/lib/screens/app_lock_setup_screen.dart +++ /dev/null @@ -1,151 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; -import '../services/app_lock_service.dart'; - -/// First-time setup screen for App Lock. -/// User enters PIN twice, then optionally enables biometrics. -class AppLockSetupScreen extends StatefulWidget { - const AppLockSetupScreen({super.key}); - - @override - State createState() => _AppLockSetupScreenState(); -} - -class _AppLockSetupScreenState extends State { - final _pinController = TextEditingController(); - final _confirmController = TextEditingController(); - bool _obscurePin = true; - bool _obscureConfirm = true; - String? _error; - - @override - void dispose() { - _pinController.dispose(); - _confirmController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Set App Lock PIN'), centerTitle: true), - body: Padding( - padding: const EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 16), - const Text( - 'Choose a 4-digit PIN to lock FocusGram.', - style: TextStyle(fontSize: 15, height: 1.5), - ), - const SizedBox(height: 32), - - // PIN field - TextField( - controller: _pinController, - obscureText: _obscurePin, - maxLength: 4, - keyboardType: TextInputType.number, - decoration: InputDecoration( - labelText: 'Enter PIN', - counterText: '', - suffixIcon: IconButton( - icon: Icon( - _obscurePin ? Icons.visibility_off : Icons.visibility, - ), - onPressed: () => setState(() => _obscurePin = !_obscurePin), - ), - border: const OutlineInputBorder(), - ), - onChanged: (_) => setState(() => _error = null), - ), - const SizedBox(height: 16), - - // Confirm PIN field - TextField( - controller: _confirmController, - obscureText: _obscureConfirm, - maxLength: 4, - keyboardType: TextInputType.number, - decoration: InputDecoration( - labelText: 'Confirm PIN', - counterText: '', - suffixIcon: IconButton( - icon: Icon( - _obscureConfirm ? Icons.visibility_off : Icons.visibility, - ), - onPressed: () => - setState(() => _obscureConfirm = !_obscureConfirm), - ), - border: const OutlineInputBorder(), - ), - onChanged: (_) => setState(() => _error = null), - ), - - // Error - if (_error != null) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Text( - _error!, - style: const TextStyle(color: Colors.redAccent), - ), - ), - - const Spacer(), - - // Save button - SizedBox( - width: double.infinity, - height: 52, - child: ElevatedButton( - onPressed: _savePin, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blueAccent, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - ), - child: const Text( - 'Enable App Lock', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), - ), - ), - ), - const SizedBox(height: 24), - ], - ), - ), - ); - } - - Future _savePin() async { - final pin = _pinController.text.trim(); - final confirm = _confirmController.text.trim(); - - if (pin.length != 4) { - setState(() => _error = 'PIN must be exactly 4 digits.'); - return; - } - if (pin != confirm) { - setState(() => _error = 'PINs do not match.'); - return; - } - if (pin == pin.split('').toSet().join('') && pin.length == 4) { - // Allow any 4-digit PIN - } - - final appLock = context.read(); - // Set both PINs to the same value for simplicity - await appLock.setPin(pin, forAppWide: true); - await appLock.setPin(pin, forAppWide: false); - - HapticFeedback.heavyImpact(); - if (mounted) { - Navigator.pop(context, true); - } - } -} diff --git a/lib/screens/app_session_picker.dart b/lib/screens/app_session_picker.dart deleted file mode 100644 index 34493a6..0000000 --- a/lib/screens/app_session_picker.dart +++ /dev/null @@ -1,226 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../services/session_manager.dart'; - -/// Shown on every cold app open. Asks the user how long they plan to use -/// Instagram today. Uses an iOS-style scroll picker (ListWheelScrollView). -class AppSessionPickerScreen extends StatefulWidget { - final VoidCallback onSessionStarted; - const AppSessionPickerScreen({super.key, required this.onSessionStarted}); - - @override - State createState() => _AppSessionPickerScreenState(); -} - -class _AppSessionPickerScreenState extends State { - static final List _minuteOptions = [ - 5, - 10, - 15, - 20, - 25, - 30, - 35, - 40, - 45, - 50, - 55, - 60, - ]; - int _selectedIndex = 0; // default: 5 min unless a previous choice exists - late final FixedExtentScrollController _scrollController; - - @override - void initState() { - super.initState(); - final lastMinutes = context.read().lastAppSessionMinutes; - final lastIndex = _minuteOptions.indexOf(lastMinutes); - _selectedIndex = lastIndex >= 0 ? lastIndex : 0; - _scrollController = FixedExtentScrollController( - initialItem: _selectedIndex, - ); - } - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final selectedMinutes = _minuteOptions[_selectedIndex]; - - return Scaffold( - backgroundColor: Colors.black, - body: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 32), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Spacer(flex: 2), - - // Icon - Container( - width: 72, - height: 72, - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: LinearGradient( - colors: [Colors.blue.shade700, Colors.blue.shade400], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - boxShadow: [ - BoxShadow( - color: Colors.blue.withValues(alpha: 0.4), - blurRadius: 24, - spreadRadius: 4, - ), - ], - ), - child: const Icon( - Icons.timer_outlined, - color: Colors.white, - size: 36, - ), - ), - - const SizedBox(height: 28), - - const Text( - 'Set Your Intention', - style: TextStyle( - color: Colors.white, - fontSize: 26, - fontWeight: FontWeight.bold, - letterSpacing: -0.5, - ), - ), - const SizedBox(height: 10), - const Text( - 'How long do you plan to use\nInstagram right now?', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white54, - fontSize: 15, - height: 1.5, - ), - ), - - const Spacer(flex: 1), - - // iOS-style scroll picker - SizedBox( - height: 220, - child: Stack( - alignment: Alignment.center, - children: [ - // Selection highlight - Container( - height: 50, - margin: const EdgeInsets.symmetric(horizontal: 0), - decoration: BoxDecoration( - color: Colors.blue.withValues(alpha: 0.12), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: Colors.blue.withValues(alpha: 0.3), - width: 1, - ), - ), - ), - ListWheelScrollView.useDelegate( - itemExtent: 50, - physics: const FixedExtentScrollPhysics(), - perspective: 0.003, - squeeze: 1.1, - diameterRatio: 2.5, - controller: _scrollController, - onSelectedItemChanged: (i) { - setState(() => _selectedIndex = i); - }, - childDelegate: ListWheelChildListDelegate( - children: _minuteOptions.asMap().entries.map((entry) { - final isSelected = entry.key == _selectedIndex; - return Center( - child: RichText( - text: TextSpan( - children: [ - TextSpan( - text: '${entry.value}', - style: TextStyle( - fontSize: isSelected ? 28 : 22, - fontWeight: isSelected - ? FontWeight.bold - : FontWeight.w300, - color: isSelected - ? Colors.white - : Colors.white38, - ), - ), - TextSpan( - text: ' min', - style: TextStyle( - fontSize: isSelected ? 16 : 14, - color: isSelected - ? Colors.white70 - : Colors.white24, - ), - ), - ], - ), - ), - ); - }).toList(), - ), - ), - ], - ), - ), - - const Spacer(flex: 1), - - // Confirm button - SizedBox( - width: double.infinity, - height: 54, - child: ElevatedButton( - onPressed: () => _confirm(context, selectedMinutes), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - elevation: 0, - ), - child: Text( - 'Start $selectedMinutes-Minute Session', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - ), - ), - - const SizedBox(height: 16), - const Text( - 'You\'ll be prompted to close the app when your time is up.', - textAlign: TextAlign.center, - style: TextStyle(color: Colors.white24, fontSize: 12), - ), - const Spacer(flex: 1), - ], - ), - ), - ), - ); - } - - void _confirm(BuildContext context, int minutes) { - context.read().startAppSession(minutes); - widget.onSessionStarted(); - } -} diff --git a/lib/screens/bait_me_button.dart b/lib/screens/bait_me_button.dart deleted file mode 100644 index f7d510d..0000000 --- a/lib/screens/bait_me_button.dart +++ /dev/null @@ -1,266 +0,0 @@ -import 'dart:async'; -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; -import 'package:url_launcher/url_launcher.dart'; -import '../services/bait_engine.dart'; -import '../services/credit_store.dart'; -import '../services/level_service.dart'; -import '../services/session_manager.dart'; - -/// The Bait Me button widget. -/// -/// Shows a gamble-themed button that triggers random outcomes. -/// Gated behind Level 3. Cooldown prevents spam. -class BaitMeButton extends StatefulWidget { - const BaitMeButton({super.key}); - - @override - State createState() => _BaitMeButtonState(); -} - -class _BaitMeButtonState extends State - with SingleTickerProviderStateMixin { - bool _isSpinning = false; - late AnimationController _spinController; - late Animation _spinAnimation; - - @override - void initState() { - super.initState(); - _spinController = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 1200), - ); - _spinAnimation = Tween(begin: 0, end: 1).animate( - CurvedAnimation(parent: _spinController, curve: Curves.easeOutBack), - ); - } - - @override - void dispose() { - _spinController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final levelService = context.watch(); - final baitEngine = context.read(); - final isUnlocked = levelService.isFeatureUnlocked(AppFeature.baitMe); - - if (!isUnlocked) { - return const SizedBox.shrink(); - } - - final isDark = Theme.of(context).brightness == Brightness.dark; - - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // The button - SizedBox( - width: 48, - height: 48, - child: Stack( - children: [ - AnimatedBuilder( - animation: _spinAnimation, - builder: (context, child) { - return Transform.rotate( - angle: _isSpinning - ? _spinAnimation.value * 2 * pi * 3 - : 0, - child: child, - ); - }, - child: Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: baitEngine.isOnCooldown - ? Colors.grey.withValues(alpha: 0.3) - : Colors.purpleAccent.withValues(alpha: 0.2), - border: Border.all( - color: baitEngine.isOnCooldown - ? Colors.grey - : Colors.purpleAccent, - width: 2, - ), - ), - child: Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(24), - onTap: baitEngine.isOnCooldown ? null : _onBaitMe, - child: Center( - child: Icon( - Icons.casino_rounded, - color: baitEngine.isOnCooldown - ? Colors.grey - : Colors.purpleAccent, - size: 22, - ), - ), - ), - ), - ), - ), - // Cooldown badge - if (baitEngine.isOnCooldown) - Positioned( - right: 0, - bottom: 0, - child: Container( - padding: const EdgeInsets.all(2), - decoration: BoxDecoration( - color: Colors.black87, - borderRadius: BorderRadius.circular(8), - ), - child: Text( - '${baitEngine.cooldownRemainingMinutes}m', - style: const TextStyle( - color: Colors.white, - fontSize: 8, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ], - ), - ), - const SizedBox(height: 2), - Text( - 'Bait Me', - style: TextStyle( - fontSize: 9, - color: isDark ? Colors.white60 : Colors.black54, - ), - ), - ], - ), - ); - } - - Future _onBaitMe() async { - HapticFeedback.mediumImpact(); - - setState(() { - _isSpinning = true; - }); - - _spinController.forward(from: 0); - - // Wait for spin animation - await Future.delayed(const Duration(milliseconds: 1200)); - - if (!mounted) return; - - final baitEngine = context.read(); - final creditStore = context.read(); - final sessionManager = context.read(); - - // Wire callbacks - baitEngine.onAddMinutes = (minutes) { - creditStore.addBonusMinutes(minutes); - HapticFeedback.heavyImpact(); - }; - - baitEngine.onResetSession = () { - creditStore.resetBalances(); - sessionManager.endSession(); - HapticFeedback.heavyImpact(); - }; - - baitEngine.onReduceSessionTime = (minutes) { - // Deduct from reel credits - for (var i = 0; i < minutes; i++) { - creditStore.drainReelsMinute(); - } - HapticFeedback.heavyImpact(); - }; - - baitEngine.onIncreaseCooldown = (minutes) { - // Increase cooldown by adding to the last session end time - // Session manager handles cooldown via _lastSessionEnd - HapticFeedback.heavyImpact(); - }; - - baitEngine.onEndReelSession = () { - sessionManager.endSession(); - HapticFeedback.heavyImpact(); - }; - - baitEngine.onEndAppSession = () { - sessionManager.endAppSession(); - HapticFeedback.heavyImpact(); - }; - - baitEngine.onOpenUrl = (url) async { - final uri = Uri.tryParse(url); - if (uri != null) { - await launchUrl(uri, mode: LaunchMode.externalApplication); - } - }; - - // Activate - final outcome = await baitEngine.activate(); - - if (!mounted) return; - - setState(() { - _isSpinning = false; - }); - - // Show result dialog - _showOutcomeDialog(context, outcome); - } - - void _showOutcomeDialog(BuildContext context, BaitOutcome outcome) { - final isDark = Theme.of(context).brightness == Brightness.dark; - - showDialog( - context: context, - barrierDismissible: false, - builder: (ctx) => AlertDialog( - backgroundColor: isDark ? const Color(0xFF1C1C1E) : Colors.white, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - BaitEngine.outcomeLabel(outcome), - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: outcome == BaitOutcome.addTenMinutes - ? Colors.greenAccent - : Colors.redAccent, - ), - ), - const SizedBox(height: 12), - Text( - BaitEngine.outcomeSubtext(outcome), - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - color: isDark ? Colors.white70 : Colors.black87, - height: 1.4, - ), - ), - ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx), - child: const Text('OK'), - ), - ], - ), - ); - } -} diff --git a/lib/screens/bait_me_full_screen.dart b/lib/screens/bait_me_full_screen.dart deleted file mode 100644 index 62a3797..0000000 --- a/lib/screens/bait_me_full_screen.dart +++ /dev/null @@ -1,254 +0,0 @@ -import 'dart:async'; -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; -import 'package:url_launcher/url_launcher.dart'; -import '../services/bait_engine.dart'; -import '../services/credit_store.dart'; -// import '../services/level_service.dart'; // unused -import '../services/session_manager.dart'; - -/// Full-screen Bait Me page with big spin animation. -class BaitMeFullScreen extends StatefulWidget { - const BaitMeFullScreen({super.key}); - - @override - State createState() => _BaitMeFullScreenState(); -} - -class _BaitMeFullScreenState extends State - with SingleTickerProviderStateMixin { - bool _isSpinning = false; - bool _done = false; - BaitOutcome? _lastOutcome; - late AnimationController _spinController; - late Animation _spinAnimation; - - @override - void initState() { - super.initState(); - _spinController = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 1800), - ); - _spinAnimation = Tween(begin: 0, end: 1).animate( - CurvedAnimation(parent: _spinController, curve: Curves.easeOutBack), - ); - } - - @override - void dispose() { - _spinController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - body: SafeArea( - child: Center( - child: Padding( - padding: const EdgeInsets.all(32), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Spacer(), - // Title - Text( - _done ? 'ðŸŽē Result!' : 'ðŸŽē Bait Me', - style: const TextStyle( - color: Colors.white, - fontSize: 28, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - Text( - _done - ? BaitEngine.outcomeSubtext( - _lastOutcome ?? BaitOutcome.addTenMinutes, - ) - : 'Tap the button to test your luck!', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white.withValues(alpha: 0.5), - fontSize: 15, - ), - ), - const Spacer(), - - // Spinning icon - AnimatedBuilder( - animation: _spinAnimation, - builder: (context, child) { - return Transform.rotate( - angle: _isSpinning - ? _spinAnimation.value * 2 * pi * 5 - : 0, - child: child, - ); - }, - child: Container( - width: 120, - height: 120, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: _done - ? Colors.green.withValues(alpha: 0.15) - : Colors.purpleAccent.withValues(alpha: 0.15), - border: Border.all( - color: _done ? Colors.greenAccent : Colors.purpleAccent, - width: 3, - ), - ), - child: Center( - child: Icon( - _done ? Icons.check_circle : Icons.casino_rounded, - color: _done ? Colors.greenAccent : Colors.purpleAccent, - size: 56, - ), - ), - ), - ), - - const Spacer(), - - // Outcome description - if (_done && _lastOutcome != null) - Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.05), - borderRadius: BorderRadius.circular(16), - ), - child: Column( - children: [ - Text( - BaitEngine.outcomeLabel(_lastOutcome!), - style: TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - color: _lastOutcome == BaitOutcome.addTenMinutes - ? Colors.greenAccent - : Colors.redAccent, - ), - ), - const SizedBox(height: 8), - Text( - BaitEngine.outcomeSubtext(_lastOutcome!), - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white.withValues(alpha: 0.6), - fontSize: 14, - height: 1.4, - ), - ), - ], - ), - ), - - const Spacer(flex: 2), - - // Big button - SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: _isSpinning ? null : _onBaitMe, - style: ElevatedButton.styleFrom( - backgroundColor: _done - ? Colors.greenAccent - : Colors.purpleAccent, - foregroundColor: Colors.black, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - elevation: 4, - ), - icon: Icon( - _isSpinning - ? Icons.hourglass_top - : _done - ? Icons.check_circle - : Icons.casino_rounded, - size: 24, - ), - label: Text( - _isSpinning - ? 'Rollingâ€Ķ' - : _done - ? 'Done — Close' - : 'ðŸŽē Spin the Wheel!', - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ), - ), - ), - - if (!_done) - Padding( - padding: const EdgeInsets.only(top: 12), - child: TextButton( - onPressed: () => Navigator.pop(context), - child: Text( - 'Not now', - style: TextStyle( - color: Colors.white.withValues(alpha: 0.3), - ), - ), - ), - ), - - const Spacer(), - ], - ), - ), - ), - ), - ); - } - - Future _onBaitMe() async { - HapticFeedback.mediumImpact(); - setState(() => _isSpinning = true); - - _spinController.forward(from: 0); - await Future.delayed(const Duration(milliseconds: 1800)); - if (!mounted) return; - - final baitEngine = context.read(); - final creditStore = context.read(); - final sessionManager = context.read(); - - baitEngine.onAddMinutes = (m) => creditStore.addBonusMinutes(m); - baitEngine.onResetSession = () => creditStore.resetBalances(); - baitEngine.onReduceSessionTime = (m) { - for (var i = 0; i < m; i++) { - creditStore.drainReelsMinute(); - } - }; - baitEngine.onEndReelSession = () => sessionManager.endSession(); - baitEngine.onEndAppSession = () => sessionManager.endAppSession(); - baitEngine.onOpenUrl = (url) async { - final uri = Uri.tryParse(url); - if (uri != null) { - await launchUrl(uri, mode: LaunchMode.externalApplication); - } - }; - - final outcome = await baitEngine.activate(); - if (!mounted) return; - - setState(() { - _isSpinning = false; - _done = true; - _lastOutcome = outcome; - }); - HapticFeedback.heavyImpact(); - } -} diff --git a/lib/screens/breath_gate_screen.dart b/lib/screens/breath_gate_screen.dart deleted file mode 100644 index 83b1bb3..0000000 --- a/lib/screens/breath_gate_screen.dart +++ /dev/null @@ -1,147 +0,0 @@ -import 'package:flutter/material.dart'; -import 'dart:async'; - -/// A mindfulness screen shown before Instagram opens. -class BreathGateScreen extends StatefulWidget { - final VoidCallback onFinish; - final int durationSeconds; - - const BreathGateScreen({ - super.key, - required this.onFinish, - this.durationSeconds = 10, - }); - - @override - State createState() => _BreathGateScreenState(); -} - -class _BreathGateScreenState extends State - with TickerProviderStateMixin { - late AnimationController _controller; - late Animation _scaleAnimation; - late int _secondsRemaining; - Timer? _timer; - bool _canContinue = false; - - @override - void initState() { - super.initState(); - _secondsRemaining = widget.durationSeconds.clamp(3, 60).toInt(); - - _controller = AnimationController( - vsync: this, - duration: const Duration(seconds: 5), - ); - - _scaleAnimation = Tween( - begin: 1.0, - end: 1.5, - ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut)); - - _controller.repeat(reverse: true); - - _startCountdown(); - } - - void _startCountdown() { - _timer = Timer.periodic(const Duration(seconds: 1), (timer) { - if (_secondsRemaining > 0) { - setState(() => _secondsRemaining--); - } else { - setState(() { - _canContinue = true; - _timer?.cancel(); - }); - } - }); - } - - @override - void dispose() { - _controller.dispose(); - _timer?.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - body: Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 40.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'Are you sure you want to open Instagram?', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.w300, - ), - ), - const SizedBox(height: 80), - - // Animated Breath Circle - ScaleTransition( - scale: _scaleAnimation, - child: Container( - width: 100, - height: 100, - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: Colors.blue.withValues(alpha: 0.3), - blurRadius: 30, - spreadRadius: 10, - ), - ], - gradient: const RadialGradient( - colors: [Colors.blue, Colors.black], - ), - ), - ), - ), - - const SizedBox(height: 80), - - Text( - _canContinue - ? 'Breathed.' - : 'Take a deep breath for $_secondsRemaining seconds...', - style: const TextStyle( - color: Colors.white70, - fontSize: 16, - fontStyle: FontStyle.italic, - ), - ), - - const SizedBox(height: 40), - - SizedBox( - width: double.infinity, - height: 50, - child: ElevatedButton( - onPressed: _canContinue ? widget.onFinish : null, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: Colors.black, - disabledBackgroundColor: Colors.white10, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(25), - ), - ), - child: const Text('Continue to Instagram'), - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/screens/cooldown_gate_screen.dart b/lib/screens/cooldown_gate_screen.dart deleted file mode 100644 index 0904292..0000000 --- a/lib/screens/cooldown_gate_screen.dart +++ /dev/null @@ -1,170 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../services/session_manager.dart'; - -/// Blocking screen shown when the user tries to reopen the app too soon -/// after their last session ended. Shows a countdown and a motivational quote. -class CooldownGateScreen extends StatefulWidget { - const CooldownGateScreen({super.key}); - - @override - State createState() => _CooldownGateScreenState(); -} - -class _CooldownGateScreenState extends State { - Timer? _timer; - static const List _quotes = [ - '"The discipline you show offline\nshapes the clarity you experience online."', - '"Every moment away from the screen\nis a moment given back to yourself."', - '"Boredom is the birthplace of creativity.\nLet it breathe."', - '"Your attention is your most valuable asset.\nSpend it wisely."', - '"Presence is a gift you give yourself first."', - '"Rest is not wasted time.\nIt is the foundation of focused action."', - ]; - late final String _quote; - - @override - void initState() { - super.initState(); - _quote = _quotes[DateTime.now().second % _quotes.length]; - _timer = Timer.periodic(const Duration(seconds: 1), (_) { - if (mounted) setState(() {}); - }); - } - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final sm = context.watch(); - final remaining = sm.appOpenCooldownRemainingSeconds; - final minutes = remaining ~/ 60; - final seconds = remaining % 60; - - // If cooldown expired, pop this gate - if (!sm.isAppOpenCooldownActive) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) Navigator.of(context).maybePop(); - }); - } - - return Scaffold( - backgroundColor: Colors.black, - body: SafeArea( - child: Center( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 40), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // Icon - Container( - width: 80, - height: 80, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.orange.withValues(alpha: 0.12), - border: Border.all( - color: Colors.orangeAccent.withValues(alpha: 0.4), - width: 1.5, - ), - ), - child: const Icon( - Icons.hourglass_top_rounded, - color: Colors.orangeAccent, - size: 38, - ), - ), - - const SizedBox(height: 32), - const Text( - 'Take a Break', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontSize: 28, - fontWeight: FontWeight.bold, - letterSpacing: -0.5, - ), - ), - - const SizedBox(height: 12), - const Text( - 'Your session has ended.\nCome back when the timer expires.', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white54, - fontSize: 15, - height: 1.5, - ), - ), - - const SizedBox(height: 48), - - // Countdown - Container( - padding: const EdgeInsets.symmetric( - horizontal: 32, - vertical: 20, - ), - decoration: BoxDecoration( - color: Colors.orange.withValues(alpha: 0.08), - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: Colors.orangeAccent.withValues(alpha: 0.25), - width: 1, - ), - ), - child: Column( - children: [ - const Text( - 'Return in', - style: TextStyle( - color: Colors.white38, - fontSize: 13, - letterSpacing: 1.2, - ), - ), - const SizedBox(height: 8), - Text( - '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}', - style: const TextStyle( - color: Colors.orangeAccent, - fontSize: 52, - fontWeight: FontWeight.w200, - letterSpacing: 4, - ), - ), - ], - ), - ), - - const SizedBox(height: 60), - - // Quote - Text( - _quote, - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.white30, - fontSize: 13, - height: 1.7, - fontStyle: FontStyle.italic, - ), - ), - ], - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/screens/debug_menu_screen.dart b/lib/screens/debug_menu_screen.dart deleted file mode 100644 index 78ac4e8..0000000 --- a/lib/screens/debug_menu_screen.dart +++ /dev/null @@ -1,342 +0,0 @@ -/*import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; -import '../services/level_service.dart'; - -/// A hidden debug menu for development & testing. -/// -/// Access: tap the app version in settings 7 times. -/// Allows manually setting XP/level to test feature gating. -class DebugMenuScreen extends StatefulWidget { - const DebugMenuScreen({super.key}); - - @override - State createState() => _DebugMenuScreenState(); -} - -class _DebugMenuScreenState extends State { - int _customLevel = 1; - int _customXp = 0; - - @override - void initState() { - super.initState(); - final levelService = context.read(); - _customLevel = levelService.level; - _customXp = levelService.xp; - } - - @override - Widget build(BuildContext context) { - final levelService = context.watch(); - final isDark = Theme.of(context).brightness == Brightness.dark; - - return Scaffold( - appBar: AppBar( - title: const Text( - 'Debug Menu', - style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600), - ), - centerTitle: true, - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_new, size: 18), - onPressed: () => Navigator.pop(context), - ), - ), - body: ListView( - padding: const EdgeInsets.all(16), - children: [ - // Current state - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.amber.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: Colors.amber.withValues(alpha: 0.3), - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - const Icon(Icons.bug_report, color: Colors.amber, size: 20), - const SizedBox(width: 8), - const Text( - 'Developer Tools', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), - ], - ), - const SizedBox(height: 12), - Text( - 'Current: Level ${levelService.level} · ${levelService.xp} XP', - style: const TextStyle(fontSize: 14), - ), - const SizedBox(height: 4), - Text( - 'Progress: ${(levelService.levelProgress * 100).toStringAsFixed(0)}% to next level', - style: TextStyle( - fontSize: 12, - color: isDark ? Colors.white54 : Colors.black54, - ), - ), - ], - ), - ), - - const SizedBox(height: 24), - - // Manual level setter - const Text( - 'SET LEVEL', - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.bold, - letterSpacing: 1.2, - color: Colors.grey, - ), - ), - const SizedBox(height: 12), - - // Quick level buttons - Wrap( - spacing: 8, - runSpacing: 8, - children: List.generate(5, (i) { - final lvl = i + 1; - final selected = _customLevel == lvl; - return ElevatedButton( - onPressed: () => setState(() => _customLevel = lvl), - style: ElevatedButton.styleFrom( - backgroundColor: selected ? Colors.blueAccent : null, - foregroundColor: selected ? Colors.white : null, - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 12, - ), - ), - child: Text('Level $lvl'), - ); - }), - ), - - const SizedBox(height: 16), - - // Set XP field - const Text( - 'SET XP', - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.bold, - letterSpacing: 1.2, - color: Colors.grey, - ), - ), - const SizedBox(height: 8), - TextField( - decoration: const InputDecoration( - labelText: 'XP Amount', - border: OutlineInputBorder(), - isDense: true, - ), - keyboardType: TextInputType.number, - controller: TextEditingController(text: '$_customXp'), - onChanged: (v) { - _customXp = int.tryParse(v) ?? 0; - }, - ), - - const SizedBox(height: 20), - - // Apply button - SizedBox( - width: double.infinity, - height: 48, - child: ElevatedButton.icon( - onPressed: () => _applyDebugSettings(levelService), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.amber, - foregroundColor: Colors.black, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - icon: const Icon(Icons.warning_amber_rounded, size: 20), - label: const Text( - 'Apply Debug Settings', - style: TextStyle(fontWeight: FontWeight.bold), - ), - ), - ), - - const SizedBox(height: 32), - - // Feature unlock preview - const Text( - 'FEATURE UNLOCK STATUS', - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.bold, - letterSpacing: 1.2, - color: Colors.grey, - ), - ), - const SizedBox(height: 8), - ...AppFeature.all.map((feature) { - final unlocked = _customLevel >= feature.requiredLevel; - return Container( - margin: const EdgeInsets.only(bottom: 4), - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - decoration: BoxDecoration( - color: (isDark ? Colors.white : Colors.black) - .withValues(alpha: 0.04), - borderRadius: BorderRadius.circular(8), - ), - child: Row( - children: [ - Icon( - unlocked ? Icons.check_circle : Icons.lock_outline, - color: unlocked ? Colors.greenAccent : Colors.grey, - size: 16, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - feature.name, - style: TextStyle( - fontSize: 13, - color: unlocked ? null : Colors.grey, - ), - ), - ), - Text( - 'Lv ${feature.requiredLevel}', - style: TextStyle( - fontSize: 12, - color: Colors.grey, - ), - ), - ], - ), - ); - }), - - const SizedBox(height: 32), - - const SizedBox(height: 40), - - // Danger zone - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.red.withValues(alpha: 0.05), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.red.withValues(alpha: 0.2)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Row( - children: [ - Icon(Icons.dangerous_outlined, color: Colors.redAccent, size: 18), - SizedBox(width: 8), - Text( - 'Danger Zone', - style: TextStyle( - color: Colors.redAccent, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const SizedBox(height: 12), - SizedBox( - width: double.infinity, - child: OutlinedButton.icon( - onPressed: () => _resetAllData(levelService), - style: OutlinedButton.styleFrom( - foregroundColor: Colors.redAccent, - side: const BorderSide(color: Colors.redAccent), - ), - icon: const Icon(Icons.delete_forever, size: 18), - label: const Text('Reset All Level Data'), - ), - ), - ], - ), - ), - ], - ), - ); - } - - Future _applyDebugSettings(LevelService levelService) async { - HapticFeedback.heavyImpact(); - // Use reflection-like approach: set the private fields via a method - // Since LevelService doesn't expose a raw setter, we provide one here. - await _forceSetLevel(levelService, _customLevel, _customXp); - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Set to Level $_customLevel with $_customXp XP'), - backgroundColor: Colors.amber.shade800, - ), - ); - } - } - - Future _forceSetLevel(LevelService levelService, int level, int xp) async { - // The LevelService stores data in Hive (local only). - // We bypass the normal XP system by writing directly to cache. - await levelService.debugSetLevel(level, xp); - await Future.delayed(const Duration(milliseconds: 100)); - if (mounted) setState(() {}); - } - - Future _resetAllData(LevelService levelService) async { - final confirmed = await showDialog( - context: context, - builder: (ctx) => AlertDialog( - title: const Text('Reset All Level Data?'), - content: const Text( - 'This will reset your level, XP, and all history to defaults. ' - 'This cannot be undone.', - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => Navigator.pop(ctx, true), - style: TextButton.styleFrom(foregroundColor: Colors.redAccent), - child: const Text('Reset'), - ), - ], - ), - ); - if (confirmed == true && mounted) { - await levelService.debugReset(); - if (mounted) { - setState(() { - _customLevel = 1; - _customXp = 0; - }); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Level data reset')), - ); - } - } - } -} -*/ diff --git a/lib/screens/effort_friction_gate.dart b/lib/screens/effort_friction_gate.dart deleted file mode 100644 index 748e08c..0000000 --- a/lib/screens/effort_friction_gate.dart +++ /dev/null @@ -1,325 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; -import '../services/credit_store.dart'; -import '../services/level_service.dart'; -import 'adsterra_ad_screen.dart'; -import 'timer_fallback_screen.dart'; -import '../widgets/native_ad_banner.dart'; - -/// Shown before a reel or Instagram session when credits are zero -/// and Effort Friction Mode is enabled. -/// -/// Fallback chain: Adsterra Social Bar (WebView) → Timer fallback. -class EffortFrictionGate extends StatefulWidget { - final String sessionType; // 'reels' or 'insta' - final VoidCallback onProceed; - final VoidCallback? onCancel; - - const EffortFrictionGate({ - super.key, - required this.sessionType, - required this.onProceed, - this.onCancel, - }); - - @override - State createState() => _EffortFrictionGateState(); -} - -class _EffortFrictionGateState extends State { - bool _isWorking = false; - String _status = ''; - - @override - Widget build(BuildContext context) { - final creditStore = context.watch(); - final isReels = widget.sessionType == 'reels'; - final credits = isReels - ? creditStore.reelsMinutes - : creditStore.instaMinutes; - - return Scaffold( - backgroundColor: Colors.black, - body: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 28), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Spacer(flex: 2), - - // Icon - Container( - width: 80, - height: 80, - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: LinearGradient( - colors: [Colors.orange.shade800, Colors.orange.shade500], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - boxShadow: [ - BoxShadow( - color: Colors.orange.withValues(alpha: 0.3), - blurRadius: 24, - spreadRadius: 4, - ), - ], - ), - child: const Icon( - Icons.play_circle_fill_rounded, - color: Colors.white, - size: 40, - ), - ), - - const SizedBox(height: 28), - - Text( - isReels ? 'Earn Reels Time' : 'Earn Instagram Time', - style: const TextStyle( - color: Colors.white, - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 12), - Text( - 'Watch a short ad to earn ${CreditStore.minutesPerAd} minutes ' - 'of ${isReels ? 'reel' : 'Instagram'} time.', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white.withValues(alpha: 0.6), - fontSize: 15, - height: 1.5, - ), - ), - - const SizedBox(height: 32), - - // Credit balance display - if (credits > 0) - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.green.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(14), - border: Border.all( - color: Colors.green.withValues(alpha: 0.2), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.access_time, - color: Colors.greenAccent, - size: 20, - ), - const SizedBox(width: 8), - Text( - 'You have $credits min remaining', - style: const TextStyle( - color: Colors.greenAccent, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - - const SizedBox(height: 20), - - // Status message - if (_status.isNotEmpty) - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.blue.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Row( - children: [ - const Icon( - Icons.info_outline, - color: Colors.blueAccent, - size: 18, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - _status, - style: const TextStyle( - color: Colors.blueAccent, - fontSize: 13, - ), - ), - ), - ], - ), - ), - - const SizedBox(height: 12), - - // Watch ad button - SizedBox( - width: double.infinity, - height: 54, - child: ElevatedButton.icon( - onPressed: _isWorking ? null : _startFallbackChain, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.orange, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - elevation: 0, - ), - icon: _isWorking - ? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Colors.white, - ), - ) - : const Icon(Icons.play_arrow_rounded, size: 22), - label: Text( - _isWorking - ? 'Workingâ€Ķ' - : 'Watch Ad (+${CreditStore.minutesPerAd} min)', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - ), - ), - - const SizedBox(height: 12), - - // Proceed button - if (credits > 0) - SizedBox( - width: double.infinity, - height: 48, - child: OutlinedButton( - onPressed: widget.onProceed, - style: OutlinedButton.styleFrom( - foregroundColor: Colors.white70, - side: BorderSide( - color: Colors.white.withValues(alpha: 0.2), - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - ), - child: const Text('Proceed with earned time'), - ), - ), - - const SizedBox(height: 16), - - // Cancel - TextButton( - onPressed: widget.onCancel ?? () => Navigator.pop(context), - child: Text( - credits > 0 ? 'Skip for now' : 'Not now', - style: TextStyle(color: Colors.white.withValues(alpha: 0.4)), - ), - ), - - const Spacer(flex: 1), - Text( - 'Ads by Adsterra', - style: TextStyle( - color: Colors.white.withValues(alpha: 0.15), - fontSize: 10, - ), - ), - const SizedBox(height: 4), - // Native banner ad at bottom - const NativeAdBanner(height: 50), - const SizedBox(height: 8), - ], - ), - ), - ), - ); - } - - // ── Fallback Chain ───────────────────────────────────────── - - Future _startFallbackChain() async { - setState(() => _isWorking = true); - - // Tier 1: Adsterra ad (full-screen WebView) - setState(() => _status = ''); - - if (mounted) { - final adsterraResult = await Navigator.push( - context, - MaterialPageRoute( - builder: (_) => AdsterraAdScreen( - sessionType: widget.sessionType, - requiredSeconds: 15, - ), - ), - ); - - if (adsterraResult == true && mounted) { - _grantReward(); - setState(() { - _isWorking = false; - _status = ''; - }); - return; - } - - if (!mounted) return; - } - - // Tier 2: Timer fallback (always works) - setState(() => _status = 'Using timer fallbackâ€Ķ'); - - if (mounted) { - final timerResult = await Navigator.push( - context, - MaterialPageRoute( - builder: (_) => TimerFallbackScreen( - sessionType: widget.sessionType, - requiredSeconds: 15, - ), - ), - ); - - if (timerResult == true && mounted) { - _grantReward(); - } - } - - if (mounted) { - setState(() { - _isWorking = false; - _status = ''; - }); - } - } - - void _grantReward() { - final creditStore = context.read(); - final levelService = context.read(); - - if (widget.sessionType == 'reels') { - creditStore.addReelsMinutes(); - } else { - creditStore.addInstaMinutes(); - } - levelService.addXpForAd(); - HapticFeedback.heavyImpact(); - } -} diff --git a/lib/screens/extras_settings_page.dart b/lib/screens/extras_settings_page.dart deleted file mode 100644 index b134167..0000000 --- a/lib/screens/extras_settings_page.dart +++ /dev/null @@ -1,188 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; - -import '../services/settings_service.dart'; -import 'ghost_mode_submenu_page.dart'; - -class ExtrasSettingsPage extends StatelessWidget { - const ExtrasSettingsPage({super.key}); - - @override - Widget build(BuildContext context) { - final settings = context.watch(); - return Scaffold( - appBar: AppBar( - title: const Text( - 'Extras', - style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600), - ), - centerTitle: true, - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_new, size: 18), - onPressed: () => Navigator.pop(context), - ), - ), - body: ListView( - children: [ - const _SectionHeader(title: 'STARTUP'), - _LaunchPagePicker(settings: settings), - const SizedBox(height: 8), - - const _SectionHeader(title: 'MEDIA'), - _SwitchTile( - title: 'Download Media (Feed + Reels)', - subtitle: 'Adds a download icon on posts and reels', - value: settings.videoDownloadEnabled, - onChanged: (v) async { - await settings.setVideoDownloadEnabled(v); - HapticFeedback.selectionClick(); - }, - ), - - const _SectionHeader(title: 'FOCUS'), - ListTile( - leading: Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: settings.ghostMode - ? Colors.purple.withValues(alpha: 0.15) - : Colors.grey.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Icon( - Icons.visibility_off_rounded, - color: settings.ghostMode ? Colors.purpleAccent : Colors.grey, - size: 20, - ), - ), - title: const Text('Ghost Mode', style: TextStyle(fontSize: 15)), - subtitle: Text( - _ghostSubtitle(settings), - style: const TextStyle(fontSize: 12), - ), - trailing: const Icon(Icons.chevron_right, size: 20), - onTap: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const GhostModeSubmenuPage()), - ), - ), - - const SizedBox(height: 40), - ], - ), - ); - } -} - -String _ghostSubtitle(SettingsService s) { - if (s.ghostMode) return 'DM Ghost active — works inside chat only'; - return 'Tap to configure ghost modes'; -} - -class _LaunchPagePicker extends StatelessWidget { - final SettingsService settings; - const _LaunchPagePicker({required this.settings}); - - @override - Widget build(BuildContext context) { - final isDark = Theme.of(context).brightness == Brightness.dark; - final options = ['home', 'following', 'favorites', 'direct']; - final labels = { - 'home': 'Home Feed', - 'following': 'Following', - 'favorites': 'Favorites', - 'direct': 'Direct Messages', - }; - - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - DropdownButtonFormField( - initialValue: settings.startupPage, - decoration: const InputDecoration( - labelText: 'Launch Page', - border: OutlineInputBorder(), - isDense: true, - contentPadding: EdgeInsets.symmetric( - horizontal: 12, - vertical: 10, - ), - ), - items: options - .map( - (p) => DropdownMenuItem( - value: p, - child: Text( - labels[p] ?? p, - style: const TextStyle(fontSize: 14), - ), - ), - ) - .toList(), - onChanged: (v) { - if (v != null) settings.setStartupPage(v); - HapticFeedback.selectionClick(); - }, - ), - const SizedBox(height: 6), - Text( - 'Choose which page opens when you launch Focusgram.', - style: TextStyle( - fontSize: 12, - color: isDark ? Colors.white38 : Colors.black38, - ), - ), - ], - ), - ); - } -} - -class _SwitchTile extends StatelessWidget { - final String title; - final String? subtitle; - final bool value; - final ValueChanged onChanged; - const _SwitchTile({ - required this.title, - this.subtitle, - required this.value, - required this.onChanged, - }); - - @override - Widget build(BuildContext context) { - return SwitchListTile( - title: Text(title, style: const TextStyle(fontSize: 15)), - subtitle: subtitle != null - ? Text(subtitle!, style: const TextStyle(fontSize: 12)) - : null, - value: value, - onChanged: onChanged, - ); - } -} - -class _SectionHeader extends StatelessWidget { - final String title; - const _SectionHeader({required this.title}); - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.fromLTRB(16, 24, 16, 8), - child: Text( - title, - style: const TextStyle( - color: Colors.grey, - fontSize: 11, - fontWeight: FontWeight.bold, - letterSpacing: 1.2, - ), - ), - ); - } -} diff --git a/lib/screens/ghost_mode_submenu_page.dart b/lib/screens/ghost_mode_submenu_page.dart deleted file mode 100644 index 98c83ec..0000000 --- a/lib/screens/ghost_mode_submenu_page.dart +++ /dev/null @@ -1,163 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../services/settings_service.dart'; - -/// Ghost Mode submenu — tap "Ghost Mode" in Extras to open this. -/// Single mode: DM Ghost (comprehensive seen-signal blocking). -class GhostModeSubmenuPage extends StatelessWidget { - const GhostModeSubmenuPage({super.key}); - - @override - Widget build(BuildContext context) { - final s = context.watch(); - final isDark = Theme.of(context).brightness == Brightness.dark; - - return Scaffold( - appBar: AppBar( - title: const Text( - 'Ghost Mode', - style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600), - ), - centerTitle: true, - ), - body: ListView( - padding: const EdgeInsets.all(16), - children: [ - // ── DM Ghost ────────────────────────────────────── - _GhostCard( - icon: Icons.visibility_off_rounded, - title: 'DM Ghost', - subtitle: 'Read messages without the person knowing (works inside chat interface — first entry only)', - value: s.ghostMode, - warning: - 'When DM Ghost is enabled, you can\'t send messages or react to any, you can just receive messages. You can turn ghost mode off anytime from topbar button.', - onChanged: (v) => s.setGhostMode(v), - isDark: isDark, - danger: true, - ), - const SizedBox(height: 24), - const SizedBox(height: 40), - ], - ), - ); - } -} - -class _GhostCard extends StatelessWidget { - final IconData icon; - final String title; - final String subtitle; - final bool value; - final String warning; - final ValueChanged onChanged; - final bool isDark; - final bool danger; - - const _GhostCard({ - required this.icon, - required this.title, - required this.subtitle, - required this.value, - required this.warning, - required this.onChanged, - required this.isDark, - this.danger = false, - }); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: (value ? (danger ? Colors.red : Colors.blue) : Colors.grey) - .withValues(alpha: value ? 0.08 : 0.03), - borderRadius: BorderRadius.circular(14), - border: Border.all( - color: (value ? (danger ? Colors.red : Colors.blue) : Colors.grey) - .withValues(alpha: value ? 0.25 : 0.1), - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon( - icon, - color: value - ? (danger ? Colors.redAccent : Colors.blueAccent) - : Colors.grey, - size: 22, - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: TextStyle( - fontWeight: FontWeight.w600, - color: value - ? (danger ? Colors.redAccent : null) - : Colors.grey, - ), - ), - const SizedBox(height: 2), - Text( - subtitle, - style: TextStyle( - fontSize: 12, - color: isDark ? Colors.white54 : Colors.black54, - ), - ), - ], - ), - ), - Switch( - value: value, - activeThumbColor: danger ? Colors.redAccent : null, - onChanged: onChanged, - ), - ], - ), - if (value) - Padding( - padding: const EdgeInsets.only(top: 10), - child: Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: (danger ? Colors.red : Colors.amber).withValues( - alpha: 0.1, - ), - borderRadius: BorderRadius.circular(8), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Icon( - danger ? Icons.warning_amber_rounded : Icons.info_outline, - size: 14, - color: danger ? Colors.redAccent : Colors.amber, - ), - const SizedBox(width: 6), - Expanded( - child: Text( - warning, - style: TextStyle( - fontSize: 11, - color: danger - ? Colors.redAccent - : Colors.amber.shade800, - ), - ), - ), - ], - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/screens/guardrails_page.dart b/lib/screens/guardrails_page.dart deleted file mode 100644 index de92f02..0000000 --- a/lib/screens/guardrails_page.dart +++ /dev/null @@ -1,458 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../services/session_manager.dart'; -import '../services/settings_service.dart'; -import '../services/level_service.dart'; -import 'adsterra_ad_screen.dart'; -import '../utils/discipline_challenge.dart'; - -class GuardrailsPage extends StatefulWidget { - const GuardrailsPage({super.key}); - - @override - State createState() => _GuardrailsPageState(); -} - -class _GuardrailsPageState extends State { - Future _handleScheduleAction( - BuildContext context, - SessionManager sm, - Future Function() action, - ) async { - if (sm.isScheduledBlockActive) { - final settings = context.read(); - final ok = await DisciplineChallenge.show( - context, - count: settings.resolvedWordChallengeCount(), - ); - if (!context.mounted || !ok) return; - } - await action(); - } - - Future _pickNewSchedule(BuildContext context, SessionManager sm) async { - final start = await showTimePicker( - context: context, - initialTime: const TimeOfDay(hour: 22, minute: 0), - helpText: 'Select Start Time', - ); - if (!context.mounted || start == null) return; - - final end = await showTimePicker( - context: context, - initialTime: const TimeOfDay(hour: 7, minute: 0), - helpText: 'Select End Time', - ); - if (!context.mounted || end == null) return; - - await sm.addSchedule( - FocusSchedule( - startHour: start.hour, - startMinute: start.minute, - endHour: end.hour, - endMinute: end.minute, - ), - ); - } - - Future _editExistingSchedule( - BuildContext context, - SessionManager sm, - int index, - FocusSchedule s, - ) async { - final start = await showTimePicker( - context: context, - initialTime: TimeOfDay(hour: s.startHour, minute: s.startMinute), - helpText: 'Edit Start Time', - ); - if (!context.mounted || start == null) return; - - final end = await showTimePicker( - context: context, - initialTime: TimeOfDay(hour: s.endHour, minute: s.endMinute), - helpText: 'Edit End Time', - ); - if (!context.mounted || end == null) return; - - await sm.updateScheduleAt( - index, - FocusSchedule( - startHour: start.hour, - startMinute: start.minute, - endHour: end.hour, - endMinute: end.minute, - ), - ); - } - - @override - Widget build(BuildContext context) { - final sm = context.watch(); - final settings = context.watch(); - final isDark = settings.isDarkMode; - - return Scaffold( - appBar: AppBar( - title: const Text( - 'Guardrails', - style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), - ), - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_new, size: 18), - onPressed: () => Navigator.pop(context), - ), - ), - body: ListView( - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: Text( - 'Set your limits to stay focused. Changes to these settings require a challenge.', - style: TextStyle( - color: isDark ? Colors.white54 : Colors.black54, - fontSize: 13, - ), - ), - ), - // If quota used up, show earn page instead of slider - if (sm.dailyRemainingSeconds <= 0) - _buildQuotaExhaustedTile(context, sm) - else - _buildFrictionSliderTile( - context: context, - sm: sm, - title: 'Daily Reel Limit', - subtitle: '${sm.dailyLimitSeconds ~/ 60} min / day', - value: (sm.dailyLimitSeconds ~/ 60).toDouble(), - min: 5, - max: 120, - divisor: 5, - isMorePermissive: (v) => v > (sm.dailyLimitSeconds ~/ 60), - warningText: - 'Increasing your limit makes it easier to scroll. Are you sure?', - onConfirmed: (v) async { - // XP penalty for increasing limit - final increase = (v.toInt() - (sm.dailyLimitSeconds ~/ 60)); - if (increase > 0) { - // context.read().grantDebugXp( - // -increase * 5, 'Penalty: increased reel limit', - // ); - } - await sm.setDailyLimitMinutes(v.toInt()); - }, - ), - _buildFrictionSliderTile( - context: context, - sm: sm, - title: 'Session Cooldown', - subtitle: '${sm.cooldownSeconds ~/ 60} min between sessions', - value: (sm.cooldownSeconds ~/ 60).toDouble(), - min: 5, - max: 180, - divisor: 5, - isMorePermissive: (v) => v < (sm.cooldownSeconds ~/ 60), - warningText: - 'Reducing cooldown makes it easier to start new sessions. Are you sure?', - onConfirmed: (v) => sm.setCooldownMinutes(v.toInt()), - ), - Divider(color: isDark ? Colors.white10 : Colors.black12, height: 32), - SwitchListTile( - title: const Text('Scheduled Blocking'), - subtitle: Text( - 'Block Instagram during specific hours', - style: TextStyle( - color: isDark ? Colors.white54 : Colors.black54, - fontSize: 13, - ), - ), - value: sm.scheduleEnabled, - onChanged: (v) => sm.setScheduleEnabled(v), - ), - if (sm.scheduleEnabled) ...[ - ...sm.schedules.asMap().entries.map((entry) { - final idx = entry.key; - final s = entry.value; - return ListTile( - title: Text( - 'Schedule ${idx + 1}', - style: const TextStyle(fontSize: 14), - ), - subtitle: Text( - '${sm.formatTime12h(s.startHour, s.startMinute)} - ${sm.formatTime12h(s.endHour, s.endMinute)}', - style: TextStyle( - color: isDark ? Colors.white54 : Colors.black54, - fontSize: 13, - ), - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon( - Icons.edit, - color: Colors.blue, - size: 20, - ), - onPressed: () => _handleScheduleAction( - context, - sm, - () => _editExistingSchedule(context, sm, idx, s), - ), - ), - IconButton( - icon: const Icon( - Icons.delete_outline, - color: Colors.redAccent, - size: 20, - ), - onPressed: () => _handleScheduleAction( - context, - sm, - () => sm.removeScheduleAt(idx), - ), - ), - ], - ), - ); - }), - ListTile( - leading: const Icon( - Icons.add_circle_outline, - color: Colors.blueAccent, - ), - title: const Text( - 'Add Focus Hours', - style: TextStyle( - color: Colors.blueAccent, - fontWeight: FontWeight.w600, - ), - ), - onTap: () => _handleScheduleAction( - context, - sm, - () => _pickNewSchedule(context, sm), - ), - ), - ], - ], - ), - ); - } - - Widget _buildQuotaExhaustedTile(BuildContext context, SessionManager sm) { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.orange.withValues(alpha: 0.08), - borderRadius: BorderRadius.circular(14), - border: Border.all(color: Colors.orange.withValues(alpha: 0.2)), - ), - child: Column( - children: [ - const Icon( - Icons.hourglass_empty, - color: Colors.orangeAccent, - size: 36, - ), - const SizedBox(height: 8), - const Text( - 'Daily Reel Quota Used Up', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), - ), - const SizedBox(height: 4), - const Text( - 'Watch an ad to earn 3 more minutes of reel time.', - textAlign: TextAlign.center, - style: TextStyle(fontSize: 13, color: Colors.grey), - ), - const SizedBox(height: 16), - SizedBox( - width: double.infinity, - child: ElevatedButton.icon( - onPressed: () => _earnQuota(context, sm), - icon: const Icon(Icons.play_circle_fill_rounded, size: 20), - label: const Text('Watch Ad (+3 min reels)'), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.orange, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - ), - ), - ], - ), - ); - } - - Future _earnQuota(BuildContext context, SessionManager sm) async { - final result = await Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const AdsterraAdScreen(sessionType: 'reels'), - ), - ); - if (result == true && context.mounted) { - sm.increaseDailyLimit(3); - context.read().addXpForAd(); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('+3 min reel quota earned!')), - ); - } - } - - Widget _buildFrictionSliderTile({ - required BuildContext context, - required SessionManager sm, - required String title, - required String subtitle, - required double value, - required double min, - required double max, - required int divisor, - required bool Function(double) isMorePermissive, - required String warningText, - required Future Function(double) onConfirmed, - }) { - return _FrictionSliderTile( - title: title, - subtitle: subtitle, - value: value, - min: min, - max: max, - divisor: divisor, - isMorePermissive: isMorePermissive, - warningText: warningText, - onConfirmed: onConfirmed, - ); - } -} - -class _FrictionSliderTile extends StatefulWidget { - final String title; - final String subtitle; - final double value; - final double min; - final double max; - final int divisor; - final bool Function(double) isMorePermissive; - final String warningText; - final Future Function(double) onConfirmed; - - const _FrictionSliderTile({ - required this.title, - required this.subtitle, - required this.value, - required this.min, - required this.max, - required this.divisor, - required this.isMorePermissive, - required this.warningText, - required this.onConfirmed, - }); - - @override - State<_FrictionSliderTile> createState() => _FrictionSliderTileState(); -} - -class _FrictionSliderTileState extends State<_FrictionSliderTile> { - late double _draftValue; - bool _pendingConfirm = false; - - @override - void initState() { - super.initState(); - _draftValue = widget.value; - } - - @override - void didUpdateWidget(_FrictionSliderTile old) { - super.didUpdateWidget(old); - if (!_pendingConfirm) _draftValue = widget.value; - } - - @override - Widget build(BuildContext context) { - final settings = context.watch(); - final isDark = settings.isDarkMode; - final divisions = ((widget.max - widget.min) / widget.divisor).round(); - - return Column( - children: [ - ListTile( - title: Text(widget.title), - subtitle: Text( - '${_draftValue.toInt()} min', - style: TextStyle(color: isDark ? Colors.white70 : Colors.black54), - ), - trailing: _pendingConfirm - ? Row( - mainAxisSize: MainAxisSize.min, - children: [ - TextButton( - onPressed: () { - setState(() { - _draftValue = widget.value; - _pendingConfirm = false; - }); - }, - child: const Text('Cancel'), - ), - ElevatedButton( - onPressed: () async { - final sm = context.read(); - final settings = context.read(); - int wordCount = settings.resolvedWordChallengeCount(); - // If we are at 0 quota, increase difficulty to 35 words - if (widget.title.contains('Daily Reel Limit') && - sm.dailyRemainingSeconds <= 0) { - wordCount = 35; - } - final success = await DisciplineChallenge.show( - context, - count: wordCount, - ); - if (!context.mounted || !success) return; - await widget.onConfirmed(_draftValue); - setState(() => _pendingConfirm = false); - }, - child: const Text('Apply'), - ), - ], - ) - : null, - ), - if (_pendingConfirm) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Text( - widget.warningText, - style: const TextStyle(color: Colors.orangeAccent, fontSize: 12), - ), - ), - Slider( - value: _draftValue, - min: widget.min, - max: widget.max, - divisions: divisions, - onChanged: (v) { - setState(() { - _draftValue = v; - _pendingConfirm = widget.isMorePermissive(v); - }); - }, - onChangeEnd: (v) { - if (!widget.isMorePermissive(v)) { - widget.onConfirmed(v); - setState(() => _pendingConfirm = false); - } - }, - ), - ], - ); - } -} diff --git a/lib/screens/level_panel_screen.dart b/lib/screens/level_panel_screen.dart deleted file mode 100644 index fb1337b..0000000 --- a/lib/screens/level_panel_screen.dart +++ /dev/null @@ -1,537 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import '../services/level_service.dart'; -import '../services/settings_service.dart'; -import '../services/credit_store.dart'; -import 'adsterra_ad_screen.dart'; - -/// Displays current level, XP progress, and locked/preview features. -class LevelPanelScreen extends StatelessWidget { - const LevelPanelScreen({super.key}); - - @override - Widget build(BuildContext context) { - final levelService = context.watch(); - final settings = context.watch(); - final isDark = settings.isDarkMode; - - return Scaffold( - appBar: AppBar( - title: const Text( - 'Your Journey', - style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600), - ), - centerTitle: true, - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_new, size: 18), - onPressed: () => Navigator.pop(context), - ), - ), - body: ListView( - padding: const EdgeInsets.all(16), - children: [ - // ── Level Header Card ────────────────────────────── - Container( - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: _levelColors(levelService.level, isDark), - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: _levelColors( - levelService.level, - isDark, - )[0].withValues(alpha: 0.3), - blurRadius: 20, - offset: const Offset(0, 8), - ), - ], - ), - child: Column( - children: [ - // Level badge - Container( - width: 72, - height: 72, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white.withValues(alpha: 0.2), - border: Border.all( - color: Colors.white.withValues(alpha: 0.4), - width: 3, - ), - ), - child: Center( - child: Text( - '${levelService.level}', - style: const TextStyle( - color: Colors.white, - fontSize: 32, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - const SizedBox(height: 16), - Text( - _levelTitle(levelService.level), - style: const TextStyle( - color: Colors.white, - fontSize: 22, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - // XP progress bar - ClipRRect( - borderRadius: BorderRadius.circular(999), - child: LinearProgressIndicator( - value: levelService.levelProgress, - minHeight: 8, - backgroundColor: Colors.white.withValues(alpha: 0.2), - valueColor: const AlwaysStoppedAnimation( - Colors.white, - ), - ), - ), - const SizedBox(height: 8), - Text( - '${levelService.xp} / ${levelService.xpForNextLevel} XP', - style: TextStyle( - color: Colors.white.withValues(alpha: 0.8), - fontSize: 14, - ), - ), - ], - ), - ), - - const SizedBox(height: 24), - - // ── Next Unlock ──────────────────────────────────── - if (levelService.nextLockedFeature != null) ...[ - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: (isDark ? Colors.white : Colors.black).withValues( - alpha: 0.05, - ), - borderRadius: BorderRadius.circular(14), - border: Border.all( - color: (isDark ? Colors.white : Colors.black).withValues( - alpha: 0.1, - ), - ), - ), - child: Row( - children: [ - Container( - width: 44, - height: 44, - decoration: BoxDecoration( - color: Colors.amber.withValues(alpha: 0.15), - borderRadius: BorderRadius.circular(12), - ), - child: const Icon( - Icons.lock_outline, - color: Colors.amber, - size: 22, - ), - ), - const SizedBox(width: 14), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Next at Level ${levelService.nextLockedFeature!.requiredLevel}', - style: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), - ), - const SizedBox(height: 2), - Text( - 'Unlock ${levelService.nextLockedFeature!.name}', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - ], - ), - ), - const SizedBox(height: 24), - ], - - // ── Feature Unlock Table ─────────────────────────── - const Text( - 'FEATURES', - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.bold, - letterSpacing: 1.2, - color: Colors.grey, - ), - ), - const SizedBox(height: 8), - ...AppFeature.all.map((feature) { - final unlocked = levelService.isFeatureUnlocked(feature); - return Container( - margin: const EdgeInsets.only(bottom: 6), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), - decoration: BoxDecoration( - color: (isDark ? Colors.white : Colors.black).withValues( - alpha: unlocked ? 0.04 : 0.02, - ), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: unlocked - ? Colors.greenAccent.withValues(alpha: 0.2) - : (isDark ? Colors.white : Colors.black).withValues( - alpha: 0.08, - ), - ), - ), - child: Row( - children: [ - Icon( - unlocked ? Icons.check_circle : Icons.lock_outline, - color: unlocked ? Colors.greenAccent : Colors.grey, - size: 20, - ), - const SizedBox(width: 12), - Expanded( - child: Text( - feature.name, - style: TextStyle( - fontSize: 14, - fontWeight: unlocked - ? FontWeight.w600 - : FontWeight.normal, - color: unlocked ? null : Colors.grey, - ), - ), - ), - Text( - unlocked ? 'Unlocked' : 'Level ${feature.requiredLevel}', - style: TextStyle( - fontSize: 12, - color: unlocked ? Colors.greenAccent : Colors.grey, - ), - ), - ], - ), - ); - }), - - const SizedBox(height: 24), - - // ── XP Rules ──────────────────────────────────────── - const Text( - 'HOW TO EARN XP', - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.bold, - letterSpacing: 1.2, - color: Colors.grey, - ), - ), - const SizedBox(height: 8), - _XpRuleTile( - icon: Icons.play_circle_outline, - label: 'Watch a rewarded ad', - value: '+2 XP (up to 20/day)', - isDark: isDark, - ), - _XpRuleTile( - icon: Icons.trending_down, - label: 'Watch fewer reels than your weekly average', - value: '+10 XP per reel saved', - isDark: isDark, - ), - _XpRuleTile( - icon: Icons.check_circle_outline, - label: 'Stay under your daily reel limit', - value: '+15 XP per day', - isDark: isDark, - ), - _XpRuleTile( - icon: Icons.login, - label: 'Open the app and check in', - value: '+1 XP per day', - isDark: isDark, - ), - const SizedBox(height: 16), - - // ── Watch Ad to earn XP ───────────────────────────── - SizedBox( - width: double.infinity, - height: 48, - child: ElevatedButton.icon( - onPressed: () => _watchAdForXp(context), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.orange, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - icon: const Icon(Icons.play_circle_fill_rounded, size: 20), - label: const Text( - 'Watch Ad to Earn +2 XP', - style: TextStyle(fontWeight: FontWeight.w600), - ), - ), - ), - const SizedBox(height: 16), - - // ── XP History ────────────────────────────────────── - const Text( - 'RECENT XP', - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.bold, - letterSpacing: 1.2, - color: Colors.grey, - ), - ), - const SizedBox(height: 8), - ...levelService.recentXpLog.take(10).map((entry) { - final dt = DateTime.tryParse(entry['time'] as String? ?? ''); - final timeStr = dt != null - ? '${dt.hour}:${dt.minute.toString().padLeft(2, '0')}' - : ''; - final amount = entry['amount'] as int; - return Container( - margin: const EdgeInsets.only(bottom: 4), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: (isDark ? Colors.white : Colors.black).withValues( - alpha: 0.04, - ), - borderRadius: BorderRadius.circular(8), - ), - child: Row( - children: [ - Icon( - amount > 0 ? Icons.add_circle : Icons.remove_circle, - color: amount > 0 ? Colors.greenAccent : Colors.redAccent, - size: 16, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - entry['reason'] as String? ?? '', - style: const TextStyle(fontSize: 13), - ), - ), - Text( - amount > 0 ? '+$amount XP' : '$amount XP', - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: amount > 0 ? Colors.greenAccent : Colors.redAccent, - ), - ), - const SizedBox(width: 8), - Text( - timeStr, - style: const TextStyle(fontSize: 10, color: Colors.grey), - ), - ], - ), - ); - }), - if (levelService.recentXpLog.isEmpty) - Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: Text( - 'No XP earned yet — watch an ad above or reduce reel time!', - style: TextStyle( - fontSize: 13, - color: isDark ? Colors.white38 : Colors.black38, - ), - ), - ), - const SizedBox(height: 20), - - const Text( - 'DEGRADATION', - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.bold, - letterSpacing: 1.2, - color: Colors.grey, - ), - ), - const SizedBox(height: 8), - Container( - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: Colors.redAccent.withValues(alpha: 0.06), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: Colors.redAccent.withValues(alpha: 0.15), - ), - ), - child: const Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon( - Icons.warning_amber_rounded, - color: Colors.redAccent, - size: 18, - ), - SizedBox(width: 8), - Text( - 'XP decays if you backslide', - style: TextStyle( - color: Colors.redAccent, - fontWeight: FontWeight.w600, - fontSize: 13, - ), - ), - ], - ), - SizedBox(height: 6), - Text( - 'â€Ē Watching more reels than your weekly average deducts XP\n' - 'â€Ē Exceeding limits for 3 consecutive days drops a level\n' - 'â€Ē Levels are preserved on monthly reset, but XP resets', - style: TextStyle( - fontSize: 12, - color: Colors.grey, - height: 1.5, - ), - ), - ], - ), - ), - const SizedBox(height: 40), - ], - ), - ); - } - - Color _levelColor(int level) { - switch (level) { - case 1: - return Colors.grey; - case 2: - return Colors.blue; - case 3: - return Colors.purple; - case 4: - return Colors.orange; - case 5: - return Colors.amber; - default: - return Colors.grey; - } - } - - List _levelColors(int level, bool isDark) { - final base = _levelColor(level); - // MaterialColor supports .shadeXXX; plain Color doesn't. - if (base is MaterialColor) { - return isDark - ? [base.shade800, base.shade900] - : [base.shade400, base.shade700]; - } - return [base, base]; - } - - /// Navigate to Adsterra ad -> grant XP on completion. - Future _watchAdForXp(BuildContext context) async { - // Try Adsterra Social Bar first - final adResult = await Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const AdsterraAdScreen(sessionType: 'reels'), - ), - ); - - if (adResult == true && context.mounted) { - context.read().addXpForAd(); - context.read().addReelsMinutes(); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('+10 XP earned!'), - duration: Duration(seconds: 2), - ), - ); - } - } - - String _levelTitle(int level) { - switch (level) { - case 1: - return 'Beginner'; - case 2: - return 'Mindful Scroller'; - case 3: - return 'Disciplined'; - case 4: - return 'Focus Master'; - case 5: - return 'Digital Monk'; - default: - return 'Level $level'; - } - } -} - -class _XpRuleTile extends StatelessWidget { - final IconData icon; - final String label; - final String value; - final bool isDark; - - const _XpRuleTile({ - required this.icon, - required this.label, - required this.value, - required this.isDark, - }); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - child: Row( - children: [ - Icon(icon, size: 18, color: Colors.greenAccent), - const SizedBox(width: 10), - Expanded( - child: Text( - label, - style: TextStyle( - fontSize: 13, - color: isDark ? Colors.white70 : Colors.black87, - ), - ), - ), - Text( - value, - style: const TextStyle( - fontSize: 12, - color: Colors.greenAccent, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ); - } -} diff --git a/lib/screens/main_webview_page.dart b/lib/screens/main_webview_page.dart deleted file mode 100644 index 5dab374..0000000 --- a/lib/screens/main_webview_page.dart +++ /dev/null @@ -1,3144 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; -import 'dart:convert'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import '../services/session_manager.dart'; -import '../services/settings_service.dart'; -import '../services/injection_controller.dart'; -import '../services/injection_manager.dart'; -import '../scripts/native_feel.dart'; -import '../scripts/grayscale.dart' as grayscale; -import '../services/screen_time_service.dart'; -import '../services/navigation_guard.dart'; -import '../services/focusgram_router.dart'; -import 'package:google_fonts/google_fonts.dart'; -import '../services/notification_service.dart'; -import '../services/bait_engine.dart'; -import '../services/credit_store.dart'; -import '../services/level_service.dart'; -import 'bait_me_full_screen.dart'; -import '../services/app_lock_service.dart'; -// snapshot_service import removed — offline feature deleted -// reels_history_service import removed — feature deleted -import 'app_lock_screen.dart'; -import '../features/update_checker/update_checker_service.dart'; -import '../utils/discipline_challenge.dart'; -import 'settings_page.dart'; -import '../features/loading/skeleton_screen.dart'; -import '../features/preloader/instagram_preloader.dart'; -import '../v2_integration/script_engine_v2_overlay.dart'; -import '../v2_integration/script_registry_v2_overlay.dart'; -import '../scripts/focus_scripts.dart'; -import 'adsterra_ad_screen.dart'; -import '../focus_settings.dart'; - -import '../services/adblock/adblock_content_blocker_loader.dart'; - -/// Core validator/dispatcher for the JS → Flutter bridge: -/// `window.flutter_inappwebview.callHandler('FocusGramMediaDownload', JSON)` -Future handleFocusGramMediaDownload({ - required String raw, - required Future Function(Uri uri) launch, -}) async { - try { - final payload = jsonDecode(raw) as Map; - - final url = payload['url'] as String?; - if (url == null || url.isEmpty) return false; - - final uri = Uri.tryParse(url); - if (uri == null || !(uri.isScheme('http') || uri.isScheme('https'))) { - return false; - } - - // Best-effort origin allow-list (Instagram/CDN). Kept permissive to avoid - // breaking legitimate downloads while still blocking obvious abuse. - final host = uri.host.toLowerCase(); - final looksInstagramCdn = - host.contains('cdninstagram.com') || - host.contains('fbcdn.net') || - host.contains('instagram.com'); - - if (!looksInstagramCdn) return false; - - await launch(uri); - return true; - } catch (_) { - // Best-effort only; never crash UI. - return false; - } -} - -class MainWebViewPage extends StatefulWidget { - const MainWebViewPage({super.key}); - - @override - State createState() => _MainWebViewPageState(); -} - -class _MainWebViewPageState extends State - with WidgetsBindingObserver { - static const String _donationPopupShownKey = 'donation_popup_shown_once'; - static final Uri _donateUri = Uri.parse('https://buymemomo.com/ujwal'); - - InAppWebViewController? _controller; - - AdblockContentBlockerData? _adblockData; - late final PullToRefreshController _pullToRefreshController; - InjectionManager? _injectionManager; - ScriptEngineV2Overlay? _v2Engine; - final GlobalKey<_EdgePanelState> _edgePanelKey = GlobalKey<_EdgePanelState>(); - bool _showSkeleton = - true; // true from the start so skeleton covers black Scaffold before WebView first paints - bool _isLoading = true; - Timer? _watchdog; - // FIX 4: Safety timer to clear stuck loading state - Timer? _loadingTimeout; - bool _extensionDialogShown = false; - bool _lastSessionActive = false; - String _currentUrl = 'https://www.instagram.com/'; - bool _hasError = false; - bool _reelsBlockedOverlay = false; - bool _exploreBlockedOverlay = false; - bool _isPreloaded = false; - bool _minimalModeBannerDismissed = false; - bool _isInDirectThread = false; - DateTime _lastMainFrameLoadStartedAt = DateTime.fromMillisecondsSinceEpoch(0); - SkeletonType _skeletonType = SkeletonType.generic; - - /// True when on the homepage and should block api/graphql + gateway. - /// Updated in onLoadStart / UrlChange before shouldInterceptRequest fires. - bool _blockHomepageGraphql = false; - - /// Helper to determine if we are on a login/onboarding page. - bool get _isOnOnboardingPage { - final path = Uri.tryParse(_currentUrl)?.path ?? ''; - final lowerPath = path.toLowerCase(); - return lowerPath.contains('/accounts/login') || - lowerPath.contains('/accounts/emailsignup') || - lowerPath.contains('/accounts/signup') || - lowerPath.contains('/legal/') || - lowerPath.contains('/help/') || - _currentUrl.contains('instagram.com/accounts/login'); - } - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addObserver(this); - _initPullToRefresh(); - _initWebView(); - _startWatchdog(); - - // Check for updates on launch - context.read().checkForUpdates(); - - // Load adblock data early. If adblock is enabled, we wait for initial data - // to be loaded so the WebView can apply contentBlockers on first render. - // This prevents ads from loading before filters are applied. - unawaited(_loadAdblockerDataEarly()); - - WidgetsBinding.instance.addPostFrameCallback((_) { - context.read().addListener(_onSessionChanged); - context.read().addListener(_onSettingsChanged); - context.read().addListener(_onScreenTimeChanged); - context.read().startTracking(); - _lastSessionActive = context.read().isSessionActive; - // Initialise structural snapshots so first change is detected correctly - final settings = context.read(); - _lastMinimalMode = settings.minimalModeEnabled; - _lastDisableReels = settings.disableReelsEntirely; - _lastDisableExplore = settings.disableExploreEntirely; - _lastBlockHomeFeedScroll = settings.blockHomeFeedScroll; - _lastBlockAutoplay = settings.blockAutoplay; - _lastGhostMode = settings.ghostMode; - _lastNoAds = settings.noAds; - _lastNoStories = settings.noStories; - _lastNoReels = settings.noReels; - _lastNoAutoplay = settings.noAutoplay; - _lastNoDMs = settings.noDMs; - _lastV2GhostModeEnabled = settings.ghostMode; - _lastV2AdBlockerDomEnabled = settings.v2AdBlockerDomEnabled; - _lastV2ContentHiderEnabled = settings.v2ContentHiderEnabled; - _lastV2FetchInterceptorEnabled = _shouldEnableFetchInterceptor(settings); - _lastV2AutoplayBlockerEnabled = settings.blockAutoplay; - _lastAdblockToggleValue = settings.v2AdBlockerDomEnabled; - _onScreenTimeChanged(); - - // Load full adblock data with longer timeout after UI is initialized - unawaited(_loadAdblockerData()); - }); - - FocusGramRouter.pendingUrl.addListener(_onPendingUrlChanged); - } - - void _onPendingUrlChanged() { - final url = FocusGramRouter.pendingUrl.value; - if (url != null && url.isNotEmpty) { - FocusGramRouter.pendingUrl.value = null; - _controller?.loadUrl(urlRequest: URLRequest(url: WebUri(url))); - } - } - - bool _shouldEnableFetchInterceptor(SettingsService settings) { - return settings.ghostMode || - settings.noAds || - settings.v2AdBlockerDomEnabled || - settings.noReels || - settings.hideSuggestedPosts || - (settings.v2ContentHiderEnabled && - (settings.contentPosts || - settings.contentReels || - settings.contentSuggested)); - } - - Future _onScreenTimeChanged() async { - if (!mounted) return; - if (context.read().totalSeconds < 300) return; - - final prefs = await SharedPreferences.getInstance(); - if (prefs.getBool(_donationPopupShownKey) ?? false) return; - await prefs.setBool(_donationPopupShownKey, true); - if (!mounted) return; - - await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Support FocusGram'), - content: const Text( - 'Please donate to support the development of this project.', - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Not now'), - ), - FilledButton( - onPressed: () { - Navigator.pop(context); - launchUrl(_donateUri, mode: LaunchMode.externalApplication); - }, - child: const Text('Donate'), - ), - ], - ), - ); - } - - /// Sets the isolated reel player flag in the WebView so the scroll-lock - /// knows it should block swipe-to-next-reel. - Future _setIsolatedPlayer(bool active) async { - await _controller?.evaluateJavascript( - source: 'window.__focusgramIsolatedPlayer = $active;', - ); - } - - /// Show a full-screen lock gate when navigating to Instagram DMs. - void _showDmLockGate() { - Navigator.push( - context, - MaterialPageRoute( - builder: (ctx) => Scaffold( - backgroundColor: Colors.black, - body: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Center( - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - Icons.lock_outline, - color: Colors.white54, - size: 64, - ), - const SizedBox(height: 24), - const Text( - 'Messages Locked', - style: TextStyle( - color: Colors.white, - fontSize: 22, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - const Text( - 'Enter your PIN to access Direct Messages', - style: TextStyle(color: Colors.white54, fontSize: 14), - ), - const SizedBox(height: 40), - ElevatedButton.icon( - onPressed: () async { - final result = await Navigator.push( - ctx, - MaterialPageRoute( - builder: (_) => const AppLockScreen( - forAppWide: false, - title: 'Messages Locked', - subtitle: - 'Enter your PIN to access Direct Messages', - ), - ), - ); - if (!ctx.mounted) return; - if (result == true) { - _dmLockOverride = true; - Navigator.pop(ctx); - } else { - _controller?.evaluateJavascript( - source: 'window.location.href = "/";', - ); - Navigator.pop(ctx); - } - }, - icon: const Icon(Icons.lock_open_rounded), - label: const Text('Unlock'), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blueAccent, - padding: const EdgeInsets.symmetric( - horizontal: 32, - vertical: 14, - ), - ), - ), - const SizedBox(height: 16), - TextButton( - onPressed: () { - _controller?.evaluateJavascript( - source: 'window.location.href = "/";', - ); - Navigator.pop(ctx); - }, - child: const Text( - 'Cancel — Go to Home', - style: TextStyle(color: Colors.white38), - ), - ), - ], - ), - ), - ), - ), - ), - ), - ), - ); - } - - /// Set ghost mode flags in the WebView so the pre-injected scripts activate. - void _setGhostModeFlags(InAppWebViewController c, SettingsService s) { - c.evaluateJavascript( - source: - ''' -window.__fgFullDmGhost = ${s.ghostMode}; -''', - ); - } - - /// Re-inject grayscale on app resume (fixes cold-start persistence bug - /// where the preloader cache can bypass onLoadStop). - void _syncGrayscaleOnResume(SettingsService settings) { - if (_injectionManager == null || _controller == null) return; - if (settings.isGrayscaleActiveNow) { - _injectionManager!.runAllPostLoadInjections(_currentUrl); - } else { - // Explicitly remove grayscale - _controller?.evaluateJavascript(source: grayscale.kGrayscaleOffJS); - } - } - - void _onSessionChanged() { - if (!mounted) return; - final sm = context.read(); - if (_lastSessionActive != sm.isSessionActive) { - _lastSessionActive = sm.isSessionActive; - - if (_lastSessionActive) { - HapticFeedback.mediumImpact(); - } else { - HapticFeedback.heavyImpact(); - } - - if (_controller != null && _injectionManager != null) { - _injectionManager!.runAllPostLoadInjections(_currentUrl); - } - - // If session became active and we were showing overlay, hide it - if (_lastSessionActive && _reelsBlockedOverlay) { - setState(() => _reelsBlockedOverlay = false); - } - } - setState(() {}); - } - - // Debounce timer so rapid toggles don't spam reloads - Timer? _reloadDebounce; - - // Snapshot of structural settings — used to detect when a reload is needed - bool _lastMinimalMode = false; - bool _lastDisableReels = false; - bool _lastDisableExplore = false; - bool _lastBlockHomeFeedScroll = false; - bool _lastBlockAutoplay = false; - bool _lastGhostMode = false; - bool _lastNoAds = false; - bool _lastNoStories = false; - bool _lastNoReels = false; - bool _lastNoAutoplay = false; - bool _lastNoDMs = false; - bool _lastV2GhostModeEnabled = false; - bool _lastV2AdBlockerDomEnabled = false; - bool _lastV2ContentHiderEnabled = false; - bool _lastV2FetchInterceptorEnabled = false; - bool _lastV2AutoplayBlockerEnabled = false; - - // Tracks v2 adblock toggle to know when to reload WebView for ContentBlocker changes - bool _lastAdblockToggleValue = false; - - void _onSettingsChanged() { - if (!mounted) return; - final settings = context.read(); - - // If adblock toggle flipped, rebuild WebView so `contentBlockers` applies. - // IMPORTANT: do NOT early-return, otherwise we skip the v2 overlay prefs sync - // (which is what enables the DOM fallback script + other v2 toggles). - if (_lastAdblockToggleValue != settings.v2AdBlockerDomEnabled) { - _lastAdblockToggleValue = settings.v2AdBlockerDomEnabled; - _adblockData = null; - _loadAdblockerData(); - _controller?.reload(); - } - - // 0. V2 overlay sync (prefs must be updated before toggling) - unawaited(() async { - final prefs = await SharedPreferences.getInstance(); - if (_v2Engine != null) { - await prefs.setBool( - 'fg_v2_${V2OverlayScriptId.ghostMode.name}_enabled', - settings.ghostMode, - ); - await prefs.setBool( - 'fg_v2_${V2OverlayScriptId.adBlockerDom.name}_enabled', - settings.v2AdBlockerDomEnabled, - ); - await prefs.setBool( - 'fg_v2_${V2OverlayScriptId.contentHider.name}_enabled', - settings.v2ContentHiderEnabled, - ); - - final bool fetchInterceptorEnabled = _shouldEnableFetchInterceptor( - settings, - ); - final bool autoplayBlockerEnabled = settings.blockAutoplay; - - await prefs.setBool( - 'fg_v2_${V2OverlayScriptId.fetchInterceptor.name}_enabled', - fetchInterceptorEnabled, - ); - await prefs.setBool( - 'fg_v2_${V2OverlayScriptId.autoplayBlocker.name}_enabled', - autoplayBlockerEnabled, - ); - - await prefs.setBool('content_stories', settings.contentStories); - await prefs.setBool('content_posts', settings.contentPosts); - await prefs.setBool('content_reels', settings.contentReels); - await prefs.setBool('content_suggested', settings.contentSuggested); - - final shouldReloadV2 = - _lastV2GhostModeEnabled != settings.ghostMode || - _lastV2AdBlockerDomEnabled != settings.v2AdBlockerDomEnabled || - _lastV2ContentHiderEnabled != settings.v2ContentHiderEnabled || - _lastV2FetchInterceptorEnabled != fetchInterceptorEnabled || - _lastV2AutoplayBlockerEnabled != autoplayBlockerEnabled; - - _lastV2GhostModeEnabled = settings.ghostMode; - _lastV2AdBlockerDomEnabled = settings.v2AdBlockerDomEnabled; - _lastV2ContentHiderEnabled = settings.v2ContentHiderEnabled; - _lastV2FetchInterceptorEnabled = fetchInterceptorEnabled; - _lastV2AutoplayBlockerEnabled = autoplayBlockerEnabled; - - if (shouldReloadV2) { - _reloadDebounce?.cancel(); - _reloadDebounce = Timer(const Duration(milliseconds: 600), () { - if (mounted) _controller?.reload(); - }); - } else { - await _v2Engine?.injectDocumentEndScripts(); - } - } - }()); - - // 1. Apply all cosmetic changes immediately via injection - if (_controller != null) { - _controller!.evaluateJavascript( - source: - 'window.__fgSetBlockAutoplay?.(${settings.blockAutoplay}); window.__fgBlockAutoplay = ${settings.blockAutoplay}; window.__fgTapToUnblur = ${settings.blurExplore && settings.tapToUnblur};', - ); - } - if (_controller != null && _injectionManager != null) { - _injectionManager!.runAllPostLoadInjections(_currentUrl); - } - - // Ghost mode flags update + reload (scripts already injected by preloader, - // but need to reload so the fetch/XHR interceptors see the new flags from - // the start of page load). - if (_lastGhostMode != settings.ghostMode) { - _lastGhostMode = settings.ghostMode; - if (_controller != null) { - _setGhostModeFlags(_controller!, settings); - // Schedule a reload so the flags take effect on fresh page load - _reloadDebounce?.cancel(); - _reloadDebounce = Timer(const Duration(milliseconds: 300), () { - if (mounted) _controller?.reload(); - }); - } - } - - // 2. Rebuild Flutter widget tree (e.g. overlay conditions, banner state) - setState(() {}); - - // 3. Detect structural changes that need a full reload. - // CSS injection alone can't undo Instagram's already-rendered React DOM. - final structuralChange = - settings.minimalModeEnabled != _lastMinimalMode || - settings.disableReelsEntirely != _lastDisableReels || - settings.disableExploreEntirely != _lastDisableExplore || - settings.blockHomeFeedScroll != _lastBlockHomeFeedScroll || - settings.blockAutoplay != _lastBlockAutoplay || - settings.ghostMode != _lastGhostMode || - settings.noAds != _lastNoAds || - settings.noStories != _lastNoStories || - settings.noReels != _lastNoReels || - settings.noAutoplay != _lastNoAutoplay || - settings.noDMs != _lastNoDMs; - - _lastMinimalMode = settings.minimalModeEnabled; - _lastDisableReels = settings.disableReelsEntirely; - _lastDisableExplore = settings.disableExploreEntirely; - _lastBlockHomeFeedScroll = settings.blockHomeFeedScroll; - _lastBlockAutoplay = settings.blockAutoplay; - _lastGhostMode = settings.ghostMode; - _lastNoAds = settings.noAds; - _lastNoStories = settings.noStories; - _lastNoReels = settings.noReels; - _lastNoAutoplay = settings.noAutoplay; - _lastNoDMs = settings.noDMs; - - if (structuralChange && _controller != null) { - // Debounce: if user toggles rapidly, only reload once they stop - _reloadDebounce?.cancel(); - _reloadDebounce = Timer(const Duration(milliseconds: 600), () { - if (mounted) _controller?.reload(); - }); - } - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - _watchdog?.cancel(); - _loadingTimeout?.cancel(); - _reloadDebounce?.cancel(); - FocusGramRouter.pendingUrl.removeListener(_onPendingUrlChanged); - context.read().removeListener(_onSessionChanged); - context.read().removeListener(_onSettingsChanged); - context.read().removeListener(_onScreenTimeChanged); - context.read().stopTracking(); - super.dispose(); - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - if (!mounted) return; - final sm = context.read(); - final screenTime = context.read(); - final settings = context.read(); - - if (state == AppLifecycleState.resumed) { - sm.setAppForeground(true); - screenTime.startTracking(); - // Cancel persistent notification when app comes to foreground - NotificationService().cancelPersistentNotification(id: 5001); - - // Re-inject grayscale on resume — schedules may have changed - // while the app was backgrounded, and injection can be lost on cold - // start due to the preloader cache bypassing onLoadStop. - _syncGrayscaleOnResume(settings); - } else if (state == AppLifecycleState.paused || - state == AppLifecycleState.inactive || - state == AppLifecycleState.detached) { - sm.setAppForeground(false); - screenTime.stopTracking(); - - // Show persistent notification when schedules are active (if enabled) - if (settings.notifyPersistent) { - final isScheduleActive = sm.isScheduledBlockActive; - final isGrayscaleActive = settings.isGrayscaleActiveNow; - - if (isScheduleActive) { - NotificationService().showPersistentNotification( - id: 5001, - title: 'FocusGram - Schedule Active', - body: 'Instagram is blocked during your focus hours', - ); - } else if (isGrayscaleActive) { - NotificationService().showPersistentNotification( - id: 5001, - title: 'FocusGram - Grayscale Active', - body: 'Instagram is in grayscale mode', - ); - } - } - } - } - - void _startWatchdog() { - _watchdog = Timer.periodic(const Duration(seconds: 15), (_) { - if (!mounted) return; - final sm = context.read(); - if (sm.isAppSessionExpired && !_extensionDialogShown) { - _extensionDialogShown = true; - _showSessionExpiredDialog(sm); - } - }); - } - - // FIX 4: Cancel any existing loading timeout and start a fresh one. - // If onLoadStop or onReceivedError haven't fired after 12 seconds, - // force-clear the loading/skeleton state so the app never appears stuck. - void _resetLoadingTimeout() { - _loadingTimeout?.cancel(); - _loadingTimeout = Timer(const Duration(seconds: 12), () { - if (!mounted) return; - if (_isLoading || _showSkeleton) { - setState(() { - _isLoading = false; - _showSkeleton = false; - }); - } - }); - } - - void _showSessionExpiredDialog(SessionManager sm) { - // Helper function to handle "Close App" action - void closeApp() { - Navigator.of(context, rootNavigator: true).pop(); - sm.endAppSession(); - SystemNavigator.pop(); - } - - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => PopScope( - canPop: false, - onPopInvokedWithResult: (didPop, result) { - // Intercept back button - treat it as "Close App" action - if (!didPop) { - closeApp(); - } - }, - child: AlertDialog( - backgroundColor: const Color(0xFF1A1A1A), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - title: const Text( - 'Session Complete ✓', - style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Your planned Instagram time is up.', - style: TextStyle(color: Colors.white70), - ), - if (sm.canExtendAppSession) ...[ - const SizedBox(height: 8), - const Text( - 'You can extend once by 10 minutes.', - style: TextStyle(color: Colors.white54, fontSize: 13), - ), - ], - ], - ), - actions: [ - TextButton( - onPressed: closeApp, - child: const Text( - 'Close App', - style: TextStyle(color: Colors.redAccent), - ), - ), - if (sm.canExtendAppSession) - ElevatedButton( - onPressed: () async { - Navigator.of(context, rootNavigator: true).pop(); - // Keep _extensionDialogShown = true while ad runs so the - // watchdog timer doesn't re-show the dialog over the ad screen. - if (!mounted) return; - final adResult = await Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const AdsterraAdScreen( - sessionType: 'reels', - requiredSeconds: 20, - ), - ), - ); - _extensionDialogShown = false; - if (adResult == true && mounted) { - sm.extendAppSession(); - } - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - child: const Text('Watch Ad (+10 min)'), - ), - ], - ), - ), - ); - } - - void _initPullToRefresh() { - _pullToRefreshController = PullToRefreshController( - settings: PullToRefreshSettings(color: Colors.blue), - onRefresh: () async { - await _controller?.reload(); - }, - ); - } - - Future _loadAdblockerData() async { - final settings = context.read(); - final prefs = await SharedPreferences.getInstance(); - final previousHosts = _adblockData?.blockedHosts; - - final loader = AdblockContentBlockerLoader(); - final data = await loader.loadOrUpdateIfNeeded( - enabled: settings.v2AdBlockerDomEnabled, - prefs: prefs, - ); - - if (mounted) { - setState(() => _adblockData = data); - if (settings.v2AdBlockerDomEnabled && - data.blockedHosts.isNotEmpty && - _controller != null && - (previousHosts == null || - !setEquals(previousHosts, data.blockedHosts))) { - unawaited(_controller?.reload()); - } - } - return data; - } - - Future _loadAdblockerDataEarly() async { - final settings = context.read(); - if (!settings.v2AdBlockerDomEnabled) return; - - try { - final prefs = await SharedPreferences.getInstance(); - final loader = AdblockContentBlockerLoader(); - final data = await loader.loadOrUpdateIfNeeded( - enabled: true, - prefs: prefs, - timeoutMs: 5000, // Short timeout for early load - ); - - if (mounted) { - setState(() => _adblockData = data); - } - } catch (_) { - // If loading fails, continue without blocking app startup - // AdblockData will be retried in _loadAdblockerData() - } - } - - bool _isBlockedByAdblockHostList(WebUri uri, Set? blockedHosts) { - if (blockedHosts == null || blockedHosts.isEmpty) return false; - - var host = uri.host.toLowerCase(); - if (blockedHosts.contains(host)) return true; - - while (true) { - final dot = host.indexOf('.'); - if (dot < 0 || dot == host.length - 1) return false; - host = host.substring(dot + 1); - if (blockedHosts.contains(host)) return true; - } - } - - void _initWebView() { - // Preloader disabled — keepAlive WebView silently fails when app cold-starts, - // leaving _isPreloaded = true with no content, causing permanent black screen. - // The fresh load path is reliable; the ~300ms preload gain is not worth it. - _isPreloaded = false; - - setState(() { - _currentUrl = 'https://www.instagram.com/accounts/login/'; - }); - - // If not preloaded, controller will be created in onWebViewCreated - _injectionManager = null; - - // Nothing else to do here – configuration is on the InAppWebView widget - } - - Future _signOut() async { - final cookieManager = CookieManager.instance(); - await cookieManager.deleteAllCookies(); - await InAppWebViewController.clearAllCache(); - if (mounted) { - setState(() { - _showSkeleton = true; - _isLoading = true; - _reelsBlockedOverlay = false; - }); - await _controller?.loadUrl( - urlRequest: URLRequest( - url: WebUri('https://www.instagram.com/accounts/login/'), - ), - ); - } - } - - /// Formats [seconds] as `MM:SS` for the cooldown countdown display. - static String _fmtSeconds(int seconds) { - final m = (seconds ~/ 60).toString().padLeft(2, '0'); - final s = (seconds % 60).toString().padLeft(2, '0'); - return '$m:$s'; - } - - static bool _isHomeFeedUrl(String url) { - final uri = Uri.tryParse(url); - if (uri == null) return url == '/' || url.isEmpty; - final path = uri.path.isEmpty ? '/' : uri.path; - return uri.host.contains('instagram.com') && path == '/'; - } - - static bool _isDirectThreadUrl(String url) { - final path = Uri.tryParse(url)?.path ?? url; - // Match both /direct/inbox/ and /direct/t/{thread_id} - return RegExp(r'^/direct/').hasMatch(path); - } - - /* unused after CDN block was removed - static bool _isFktmInstagramCdn(String url) { - final host = Uri.tryParse(url)?.host.toLowerCase() ?? ''; - return RegExp(r'^instagram\.fktm\d+-\d+\.fna\.fbcdn\.net$').hasMatch(host); - } - */ - - void _syncDirectThreadState(String url) { - final active = _isDirectThreadUrl(url); - if (_isInDirectThread == active) return; - _isInDirectThread = active; - - // Reset override when leaving DMs - if (!active) _dmLockOverride = false; - - // If Messages Tab Lock is enabled and user navigated to DMs, - // show a lock overlay. - if (active && mounted) { - final appLock = context.read(); - if (appLock.messagesLockReady && !_dmLockOverride) { - _showDmLockGate(); - } - } - } - - bool _dmLockOverride = false; - - Future _showReelSessionPicker() async { - final settings = context.read(); - if (settings.requireWordChallenge) { - final passed = await DisciplineChallenge.show( - context, - count: settings.resolvedWordChallengeCount(), - ); - if (!passed || !mounted) return; - } - _showReelSessionPickerBottomSheet(); - } - - void _showReelSessionPickerBottomSheet() { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (context) => Container( - height: MediaQuery.of(context).size.height * 0.7, - decoration: BoxDecoration( - color: const Color(0xFF121212), - borderRadius: BorderRadius.circular(25), - ), - child: Column( - children: [ - const SizedBox(height: 16), - const Text( - 'Start Reel Session', - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - const Padding( - padding: EdgeInsets.all(12), - child: Text( - 'Reels will be unblocked for the duration you choose.', - textAlign: TextAlign.center, - style: TextStyle(color: Colors.white54, fontSize: 13), - ), - ), - Expanded( - child: ListView( - padding: const EdgeInsets.symmetric(horizontal: 20), - children: [ - _buildReelSessionTile(1), - _buildReelSessionTile(3), - _buildReelSessionTile(5), - _buildReelSessionTile(10), - _buildReelSessionTile(15), - _buildReelSessionTile(20), - _buildReelSessionTile(30), - const SizedBox(height: 40), - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text( - 'Cancel', - style: TextStyle(color: Colors.white38), - ), - ), - ], - ), - ), - ], - ), - ), - ); - } - - Widget _buildReelSessionTile(int mins) { - final sm = context.read(); - return ListTile( - title: Text('$mins Minutes', style: const TextStyle(color: Colors.white)), - trailing: const Icon( - Icons.arrow_forward_ios, - color: Colors.white24, - size: 14, - ), - onTap: () async { - Navigator.pop(context); - if (sm.startSession(mins)) { - HapticFeedback.mediumImpact(); - setState(() => _reelsBlockedOverlay = false); - await _controller?.loadUrl( - urlRequest: URLRequest( - url: WebUri('https://www.instagram.com/reels/'), - ), - ); - } - }, - ); - } - - @override - Widget build(BuildContext context) { - return PopScope( - canPop: false, - onPopInvokedWithResult: (didPop, result) async { - if (didPop) return; - if (_reelsBlockedOverlay) { - setState(() => _reelsBlockedOverlay = false); - await _controller?.goBack(); - return; - } - if (_isHomeFeedUrl(_currentUrl)) { - SystemNavigator.pop(); - return; - } - final didNavigate = - await (_controller - ?.evaluateJavascript( - source: - '(function(){' - ' var before = window.location.href;' - ' history.back();' - ' return before;' - '})()', - ) - .then((_) => true) - .catchError((_) => false)) ?? - false; - if (didNavigate == true) { - await Future.delayed(const Duration(milliseconds: 120)); - return; - } - if (await (_controller?.canGoBack() ?? Future.value(false))) { - await _controller?.goBack(); - } else { - SystemNavigator.pop(); - } - }, - child: Scaffold( - // FIX 1: Use a solid color that matches the WebView background. - // When transparentBackground is false (see WebView settings), the - // WebView renders its own white/black background. Using black here - // matches the dark-mode WebView bg and prevents "flash of white". - backgroundColor: Colors.black, - body: Stack( - children: [ - SafeArea( - child: Column( - children: [ - const _UpdateBanner(), - if (!_isOnOnboardingPage) - _BrandedTopBar( - onFocusControlTap: () => - _edgePanelKey.currentState?._toggleExpansion(), - onDmGhostToggle: () { - context.read().setGhostMode(false); - _controller?.reload(); - }, - onReload: () => _controller?.reload(), - currentUrl: _currentUrl, - dmGhostActive: context.read().ghostMode, - ), - Expanded( - child: Consumer( - builder: (ctx, sm, _) { - if (sm.isScheduledBlockActive) { - return Container( - color: Colors.black, - padding: const EdgeInsets.all(32), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.bedtime_rounded, - color: Colors.blueAccent, - size: 80, - ), - const SizedBox(height: 24), - Text( - 'Focus Hours Active', - style: GoogleFonts.grandHotel( - color: Colors.white, - fontSize: 42, - ), - ), - const SizedBox(height: 12), - Text( - 'Instagram is blocked according to your schedule (${sm.activeScheduleText ?? 'Focus Hours'}).', - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.white70, - fontSize: 15, - height: 1.5, - ), - ), - const SizedBox(height: 48), - const Text( - 'Your future self will thank you for the extra sleep and focus.', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white38, - fontSize: 12, - fontStyle: FontStyle.italic, - ), - ), - ], - ), - ); - } - - final settings = context.read(); - - return Stack( - children: [ - InAppWebView( - keepAlive: InstagramPreloader.keepAlive, - initialUrlRequest: _isPreloaded - ? null - : URLRequest( - url: WebUri( - 'https://www.instagram.com/accounts/login/', - ), - ), - initialSettings: InAppWebViewSettings( - userAgent: InjectionController.iOSUserAgent, - mediaPlaybackRequiresUserGesture: - settings.blockAutoplay, - useHybridComposition: true, - cacheEnabled: true, - cacheMode: CacheMode.LOAD_CACHE_ELSE_NETWORK, - domStorageEnabled: true, - databaseEnabled: true, - thirdPartyCookiesEnabled: false, - hardwareAcceleration: true, - // FIX 2: Set to false so the WebView renders - // its own opaque background. When true + black - // Scaffold, you see black until Instagram - // finishes painting — looks like a freeze/hang. - transparentBackground: false, - safeBrowsingEnabled: false, - disableContextMenu: false, - supportZoom: false, - allowsInlineMediaPlayback: true, - verticalScrollBarEnabled: false, - horizontalScrollBarEnabled: false, - contentBlockers: - _adblockData?.contentBlockers ?? const [], - ), - initialUserScripts: UnmodifiableListView([ - ...const [], - ...buildUserScripts( - FocusSettings( - ghostMode: settings.ghostMode, - noAds: settings.noAds, - noStories: settings.noStories, - noReels: settings.noReels, - noAutoplay: settings.noAutoplay, - noDMs: settings.noDMs, - ), - ), - ]), - pullToRefreshController: _pullToRefreshController, - shouldInterceptRequest: (controller, request) async { - final url = request.url.toString(); - - const adDomains = [ - 'an.facebook.com', - 'connect.facebook.net', - 'pixel.facebook.com', - 'graph.facebook.com/logging', - 'www.instagram.com/ajax/bz', - 'www.instagram.com/api/v1/web/comet/logcalls', - 'doubleclick.net', - 'googletagmanager.com', - 'scorecardresearch.com', - ]; - if (adDomains.any(url.contains)) { - return WebResourceResponse( - data: Uint8List(0), - ); - } - - final referrer = - request.headers?['Referer'] ?? - request.headers?['referer']; - if (referrer != null && - _isDirectThreadUrl(referrer)) { - _syncDirectThreadState(referrer); - } - - /*if (_isInDirectThread && - _isFktmInstagramCdn(url)) { - if (_dmThreadCdnBlockArmed) { - return WebResourceResponse( - data: Uint8List(0), - ); - } - _dmThreadCdnBlockArmed = true; - } -*/ - // Strict/high-priority domain blocking from uBlock-style lists. - final adblockHosts = _adblockData?.blockedHosts; - if (_isBlockedByAdblockHostList( - request.url, - adblockHosts, - )) { - return WebResourceResponse( - data: Uint8List(0), - ); - } - - // Block trackers + paid pixel iframes (hardcoded safety) - const blockedDomains = [ - 'fbsbx.com/paid_ads_pixel', - 'fbsbx.com/paid_ads', - 'facebook.com/tr', - 'instagram.com/paid_ads', - 'analytics.facebook.com', - 'facebook.com/tracking', - ]; - if (blockedDomains.any( - (d) => url.contains(d), - )) { - return WebResourceResponse( - data: Uint8List(0), - ); - } - - // Also block any IG paid-pixel iframe HTML documents - if (url.contains('/paid_ads_pixel/iframe/') || - url.contains('/generete_pixels/')) { - return WebResourceResponse( - data: Uint8List(0), - ); - } - - // Block Reels API - if (settings.noReels && - (url.contains('/api/v1/clips/') || - url.contains('/api/v1/discover/'))) { - return WebResourceResponse( - data: Uint8List(0), - ); - } - - // Block DMs API - if (settings.noDMs && - (url.contains('edge-chat.instagram.com') || - url.contains('/api/v1/direct_v2/'))) { - return WebResourceResponse( - data: Uint8List(0), - ); - } - - // ── DM Ghost: block ALL seen signals ──────────────── - // Like Chrome DevTools "Block request URL" — catches all - // sources at the native WebView level. - // - // Rules: - // 1. Block specific seen endpoint patterns everywhere - // 2. Block /api/graphql on homepage (/) and DM threads - // (/direct/t/*). Allow on /direct/inbox/ so inbox loads. - if (settings.ghostMode) { - // — Seen endpoint patterns (always block) — - final seenBlocked = RegExp( - r'/api/v1/media/[\w-]+/seen/|' - r'/api/v1/stories/reel/seen/|' - r'/api/v1/direct_v2/threads/[\w-]+/seen/|' - r'/api/v1/direct_v2/visual_message/[\w-]+/seen/|' - r'/api/v1/live/[\w-]+/comment/seen/|' - r'/api/v1/direct_v2/threads/[^/]+/mark_item_seen/|' - r'/api/v1/direct_v2/mark_item_seen/|' - r'/api/v1/direct_v2/threads/[^/]+/items/[^/]+/mark_visual_item_seen/|' - r'/api/v1/direct_v2/visual_thread/[^/]+/seen/|' - r'/api/v1/direct_v2/threads/[^/]+/items/[^/]+/mark_audio_seen/|' - r'/api/v1/live/[^/]+/join/|' - r'/api/v1/live/[^/]+/get_join_requests/|' - r'/api/v1/media/seen/|' - r'/api/v1/feed/viewed_story/|' - r'/api/v1/feed/reels_tray/seen/|' - r'/api/v1/qe/|' - r'/api/v1/launcher/sync/|' - r'/api/v1/logging/|' - r'/api/v1/fb_onetap_logging/|' - r'/ajax/bz|' - r'/ajax/logging/|' - r'/api/v1/stats/|' - r'/api/v1/fbanalytics/', - ).hasMatch(url); - if (seenBlocked) { - return WebResourceResponse( - data: Uint8List.fromList( - utf8.encode('{"status":"ok"}'), - ), - statusCode: 200, - contentType: 'application/json', - ); - } - - // — Block /api/graphql + gateway on homepage & - // ANY /direct/* page (not just /direct/t/). - // Allow on /direct/inbox/ so inbox loads. - // Broader scope catches seen indicators sent - // during SPA transitions on re-entry. - final currentPath = - Uri.tryParse(_currentUrl)?.path ?? - _currentUrl; - final isHomepage = - currentPath == '/' || currentPath == ''; - final isOnDirect = currentPath.startsWith( - '/direct/', - ); - if (!currentPath.startsWith( - '/direct/inbox/', - ) && - (isHomepage || isOnDirect) && - (url.contains('/api/graphql') || - url.contains( - 'gateway.instagram.com', - ))) { - return WebResourceResponse( - data: Uint8List.fromList( - utf8.encode('{"status":"ok"}'), - ), - statusCode: 200, - contentType: 'application/json', - ); - } - } - - // Legacy homepage graphql + gateway block - // (kept for safety — the ghost mode block above now covers it) - if (_blockHomepageGraphql && - (url.contains('/api/graphql') || - url.contains( - 'gateway.instagram.com', - ))) { - return WebResourceResponse( - data: Uint8List.fromList( - utf8.encode('{"status":"ok"}'), - ), - statusCode: 200, - contentType: 'application/json', - ); - } - - /* Strip ads from feed (JS handles it) - if (settings.noAds && - url.contains( - 'instagram.com/graphql/query', - )) {/ - try { - final res = await http.post( - Uri.parse(url), - headers: Map.from( - request.headers ?? {}, - ), - ); - - final json = jsonDecode(res.body); - final connection = - json['data']?['xdt_api__v1__feed__timeline__connection']; - - if (connection != null && - connection['edges'] is List) { - final edges = connection['edges'] as List; - edges.removeWhere((e) { - final node = e['node']; - if (node == null) return false; - // Strip ads from feed - if (settings.noAds && - url.contains( - 'instagram.com/graphql', - )) { - try { - final res = await http.post( - Uri.parse(url), - headers: Map.from( - request.headers ?? {}, - ), - ); - - final json = jsonDecode(res.body); - - void filterEdges(dynamic obj) { - if (obj == null) return; - if (obj is Map) { - if (obj['edges'] is List) { - (obj['edges'] as List).removeWhere(( - e, - ) { - final node = e is Map - ? e['node'] - : null; - if (node == null || - node is! Map) - return false; - return node['is_ad'] == - true || - node['ad_id'] != null || - node['ad_action_links'] != - null || - node['is_paid_partnership'] == - true || - node['sponsor_tags'] != - null || - node['commerciality_status'] == - 'ad' || - node['commerciality_status'] == - 'shoppable_feed_ad' || - (node['__typename'] - ?.toString() - .toLowerCase() - .contains( - 'ad', - ) ?? - false); - }); - } - obj.values.forEach(filterEdges); - } else if (obj is List) { - obj.forEach(filterEdges); - } - } - - filterEdges(json); - - return WebResourceResponse( - data: Uint8List.fromList( - utf8.encode(jsonEncode(json)), - ), - headers: res.headers, - statusCode: 200, - contentType: 'application/json', - ); - } catch (_) { - return null; - } - } - }); - } - - return WebResourceResponse( - data: Uint8List.fromList( - utf8.encode(jsonEncode(json)), - ), - headers: res.headers, - statusCode: 200, - contentType: 'application/json', - ); - } catch (e) { - // if anything fails, pass through original request unmodified - return null; - } - }*/ - - return null; - }, - onWebViewCreated: (controller) async { - _controller = controller; - - // Capture settingsService before async gap to avoid BuildContext warning - final settingsService = context - .read(); - final prefs = - await SharedPreferences.getInstance(); - _injectionManager = InjectionManager( - controller: controller, - prefs: prefs, - sessionManager: sm, - ); - _injectionManager!.setSettingsService( - settingsService, - ); - - // Set ghost mode flags (scripts already injected by preloader) - _setGhostModeFlags(controller, settingsService); - - // Navigate to startup page if not Home - if (settingsService.startupPage != 'home') { - await controller.loadUrl( - urlRequest: URLRequest( - url: WebUri(settingsService.startupUrl), - ), - ); - } - - // Force-inject grayscale on initial WebView creation, - // because the preloader's keepAlive causes the main - // WebView to skip onLoadStop on cold start. - if (settingsService.isGrayscaleActiveNow) { - try { - await controller.evaluateJavascript( - source: grayscale.kGrayscaleJS, - ); - } catch (_) {} - } - - _registerJavaScriptHandlers(controller); - - // ── FocusGram v2 overlay initial sync ─────────────── - // ScriptEngineV2Overlay reads enabled state from prefs keys: - // fg_v2_{scriptName}_enabled - // Set them BEFORE DOCUMENT_START scripts are injected. - // V2 overlay toggles: - // - ghost_mode: user FocusGram "ghostMode" controls it - // - others: keep using existing v2 toggles - await prefs.setBool( - 'fg_v2_${V2OverlayScriptId.ghostMode.name}_enabled', - settingsService.ghostMode, - ); - await prefs.setBool( - 'fg_v2_${V2OverlayScriptId.adBlockerDom.name}_enabled', - settingsService.v2AdBlockerDomEnabled, - ); - await prefs.setBool( - 'fg_v2_${V2OverlayScriptId.contentHider.name}_enabled', - settingsService.v2ContentHiderEnabled, - ); - await prefs.setBool( - 'fg_v2_${V2OverlayScriptId.fetchInterceptor.name}_enabled', - _shouldEnableFetchInterceptor( - settingsService, - ), - ); - await prefs.setBool( - 'fg_v2_${V2OverlayScriptId.autoplayBlocker.name}_enabled', - settingsService.blockAutoplay, - ); - - // Content hider flags consumed by v2/content_hider.js - await prefs.setBool( - 'content_stories', - settingsService.contentStories, - ); - await prefs.setBool( - 'content_posts', - settingsService.contentPosts, - ); - await prefs.setBool( - 'content_reels', - settingsService.contentReels, - ); - await prefs.setBool( - 'content_suggested', - settingsService.contentSuggested, - ); - - // Phase 1 V2 overlay engine (theme + best-effort ad DOM cleanup) - _v2Engine = ScriptEngineV2Overlay( - controller: controller, - prefs: prefs, - ); - await _v2Engine!.initDocumentStartScripts(); - - // Start safety timeout — clears loading state - // if onLoadStop never fires (e.g. network stall). - _resetLoadingTimeout(); - }, - onLoadStart: (controller, url) { - if (!mounted) return; - final u = url?.toString() ?? ''; - _syncDirectThreadState(u); - // Update homepage graphql block flag SYNCHRONOUSLY - // (before setState, so shouldInterceptRequest sees it) - final path = Uri.tryParse(u)?.path ?? u; - _blockHomepageGraphql = - settings.ghostMode && - (path == '/' || - path == '' || - path == '/explore/'); - final lower = u.toLowerCase(); - final isOnboardingUrl = - lower.contains('/accounts/login') || - lower.contains('/accounts/emailsignup') || - lower.contains('/accounts/signup') || - lower.contains('/legal/') || - lower.contains('/help/'); - setState(() { - _isLoading = true; - _lastMainFrameLoadStartedAt = DateTime.now(); - _currentUrl = u; - _hasError = false; - _showSkeleton = !isOnboardingUrl; - // Update skeleton type based on the URL being loaded - _skeletonType = getSkeletonTypeFromUrl(u); - }); - // FIX 4: Reset the safety timeout on each new load - _resetLoadingTimeout(); - }, - onLoadStop: (controller, url) async { - _pullToRefreshController.endRefreshing(); - if (!mounted) return; - - // FIX 4: Cancel the safety timeout — load completed normally - _loadingTimeout?.cancel(); - - final current = url?.toString() ?? ''; - _syncDirectThreadState(current); - - // Re-set ghost mode flags on every page load. - // evaluateJavascript-set flags are destroyed when - // the JS context resets on navigation. The flags - // are also prepended to initialUserScripts, but - // this covers the toggle-off → reload case. - final s = context.read(); - _setGhostModeFlags(controller, s); - - setState(() { - _isLoading = false; - _currentUrl = current; - _hasError = false; - }); - - await _injectionManager - ?.runAllPostLoadInjections(current); - - // Phase 1 V2 overlay DOM scripts - await _v2Engine?.injectDocumentEndScripts(); - - // Re-inject grayscale on every page load - if (s.isGrayscaleActiveNow) { - await controller.evaluateJavascript( - source: grayscale.kGrayscaleJS, - ); - } else { - await controller.evaluateJavascript( - source: grayscale.kGrayscaleOffJS, - ); - } - - await controller.evaluateJavascript( - source: - InjectionController.notificationBridgeJS, - ); - - final isIsolatedReel = - current.contains('/reel/') && - !current.contains('/reels/'); - await _setIsolatedPlayer(isIsolatedReel); - - await controller.evaluateJavascript( - source: kNativeFeelingPostLoadScript, - ); - - await Future.delayed( - const Duration(milliseconds: 100), - ); - if (mounted) { - setState(() => _showSkeleton = false); - } - }, - shouldOverrideUrlLoading: - (controller, navigationAction) async { - final url = - navigationAction.request.url - ?.toString() ?? - ''; - final uri = navigationAction.request.url; - final appSettings = context - .read(); - _syncDirectThreadState(url); - - final disableReels = - appSettings.disableReelsEntirely; - final disableExplore = - appSettings.disableExploreEntirely; - - bool isReelsUrl(String u) => - u.contains('/reel/') || - u.contains('/reels/'); - bool isExploreUrl(String u) => - u.contains('/explore/'); - - void showBlocked(String msg) { - if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar( - SnackBar( - content: Text(msg), - behavior: SnackBarBehavior.floating, - margin: const EdgeInsets.fromLTRB( - 16, - 0, - 16, - 20, - ), - ), - ); - } - - if (disableReels && isReelsUrl(url)) { - showBlocked('Reels are disabled'); - return NavigationActionPolicy.CANCEL; - } - - if (disableExplore && isExploreUrl(url)) { - // Show overlay immediately without navigating away - setState( - () => _exploreBlockedOverlay = true, - ); - // Don't go back - just block the navigation - return NavigationActionPolicy.CANCEL; - } - - if (uri != null && - uri.host.contains('instagram.com') && - (url.contains('accounts/settings') || - url.contains('accounts/edit'))) { - return NavigationActionPolicy.ALLOW; - } - - if (url.contains('/reels/') && - !context - .read() - .isSessionActive) { - setState( - () => _reelsBlockedOverlay = true, - ); - return NavigationActionPolicy.CANCEL; - } - - if (uri != null && - !uri.host.contains('instagram.com') && - !uri.host.contains('facebook.com') && - !uri.host.contains( - 'cdninstagram.com', - ) && - !uri.host.contains('fbcdn.net')) { - await launchUrl( - uri, - mode: LaunchMode.externalApplication, - ); - return NavigationActionPolicy.CANCEL; - } - - final decision = NavigationGuard.evaluate( - url: url, - ); - if (decision.blocked) { - if (url.contains('/reels/')) { - setState( - () => _reelsBlockedOverlay = true, - ); - return NavigationActionPolicy.CANCEL; - } - return NavigationActionPolicy.CANCEL; - } - - return NavigationActionPolicy.ALLOW; - }, - onReceivedError: (controller, request, error) { - // FIX 5: Clear loading state on ANY main-frame - // error, not just HOST_LOOKUP and TIMEOUT. - // Previously, errors like CONNECTION_REFUSED or - // FAILED_URL_BLOCKED left _isLoading = true - // forever, causing the apparent "hang". - if (request.isForMainFrame == true) { - _loadingTimeout?.cancel(); - if (mounted) { - setState(() { - _isLoading = false; - _showSkeleton = false; - // Only show the full error screen for - // network-level failures, not blocked URLs - if (error.type == - WebResourceErrorType - .HOST_LOOKUP || - error.type == - WebResourceErrorType.TIMEOUT) { - _hasError = true; - } - }); - } - } - }, - ), - - if (_showSkeleton) - SkeletonScreen(skeletonType: _skeletonType), - - if (!_isOnOnboardingPage && - settings.minimalModeEnabled && - !_minimalModeBannerDismissed) - Positioned( - left: 12, - right: 12, - top: 12, - child: _MinimalModeBanner( - onDismiss: () { - HapticFeedback.lightImpact(); - setState( - () => _minimalModeBannerDismissed = true, - ); - }, - ), - ), - - // Instagram's native bottom nav is used directly. - // NativeBottomNav overlay removed — faster, looks native, - // and reels tap naturally hits shouldOverrideUrlLoading. - ], - ); - }, - ), - ), - ], - ), - ), - if (_hasError) - _NoInternetScreen( - onRetry: () { - setState(() => _hasError = false); - _controller?.reload(); - }, - ), - if (_isLoading) - Positioned( - top: - (_isOnOnboardingPage ? 0 : 60) + - MediaQuery.of(context).padding.top, - left: 0, - right: 0, - child: const _InstagramGradientProgressBar(), - ), - _EdgePanel(key: _edgePanelKey, currentUrl: _currentUrl), - - if (_exploreBlockedOverlay) - Positioned.fill( - child: Consumer( - builder: (ctx, settings, _) { - final isDark = settings.isDarkMode; - final bg = isDark ? Colors.black : Colors.white; - final textMain = isDark ? Colors.white : Colors.black; - final textDim = isDark ? Colors.white70 : Colors.black87; - final textSub = isDark ? Colors.white38 : Colors.black45; - - return Container( - color: bg.withValues(alpha: 0.95), - padding: const EdgeInsets.all(32), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.explore_off_rounded, - color: Colors.orangeAccent, - size: 80, - ), - const SizedBox(height: 24), - Text( - 'Explore is Disabled', - style: GoogleFonts.grandHotel( - color: textMain, - fontSize: 42, - ), - ), - const SizedBox(height: 12), - Text( - 'Explore is disabled in your FocusGram settings.', - textAlign: TextAlign.center, - style: TextStyle( - color: textDim, - fontSize: 15, - height: 1.5, - ), - ), - const SizedBox(height: 48), - Text( - 'You can re-enable Explore in Settings > Focus.', - textAlign: TextAlign.center, - style: TextStyle(color: textSub, fontSize: 12), - ), - const SizedBox(height: 20), - TextButton( - onPressed: () { - setState(() => _exploreBlockedOverlay = false); - _controller?.goBack(); - }, - child: Text( - 'Go Back', - style: TextStyle(color: textSub), - ), - ), - ], - ), - ); - }, - ), - ), - - if (_reelsBlockedOverlay) - Positioned.fill( - child: Consumer( - builder: (ctx, settings, _) { - final isDark = settings.isDarkMode; - final bg = isDark ? Colors.black : Colors.white; - final textMain = isDark ? Colors.white : Colors.black; - final textDim = isDark ? Colors.white70 : Colors.black87; - final textSub = isDark ? Colors.white38 : Colors.black45; - - return Container( - color: bg.withValues(alpha: 0.95), - padding: const EdgeInsets.all(32), - child: Consumer( - builder: (ctx, sm, _) { - final onCooldown = sm.isCooldownActive; - final quotaFinished = sm.dailyRemainingSeconds <= 0; - final reelsHardDisabled = - settings.disableReelsEntirely; - - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - quotaFinished - ? Icons.timer_off_rounded - : Icons.lock_clock_rounded, - color: quotaFinished - ? Colors.redAccent - : Colors.blueAccent, - size: 80, - ), - const SizedBox(height: 24), - Text( - quotaFinished - ? 'Daily Quota Finished' - : (reelsHardDisabled - ? 'Reels are Disabled' - : 'Reels are Blocked'), - style: GoogleFonts.grandHotel( - color: textMain, - fontSize: 42, - ), - ), - const SizedBox(height: 12), - Text( - quotaFinished - ? 'You have reached your planned limit for today. Step away and focus on what matters most.' - : (reelsHardDisabled - ? 'Reels are disabled in your settings.' - : 'Start a planned reel session to access the feed. Use Instagram for connection, not distraction.'), - textAlign: TextAlign.center, - style: TextStyle( - color: textDim, - fontSize: 15, - height: 1.5, - ), - ), - const SizedBox(height: 48), - if (quotaFinished) ...[ - Text( - 'Your discipline is your strength.', - style: TextStyle( - color: Colors.greenAccent.withValues( - alpha: 0.8, - ), - fontStyle: FontStyle.italic, - ), - ), - const SizedBox(height: 24), - Text( - 'To adjust your daily limit, go to Settings > Guardrails.', - textAlign: TextAlign.center, - style: TextStyle( - color: textSub, - fontSize: 12, - ), - ), - ] else if (reelsHardDisabled) ...[ - Text( - 'You can re-enable Reels in Settings > Focus.', - textAlign: TextAlign.center, - style: TextStyle( - color: textSub, - fontSize: 12, - ), - ), - ] else if (onCooldown) ...[ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 28, - vertical: 14, - ), - decoration: BoxDecoration( - color: Colors.orange.withValues( - alpha: 0.12, - ), - borderRadius: BorderRadius.circular(30), - border: Border.all( - color: Colors.orange.withValues( - alpha: 0.4, - ), - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - Icons.hourglass_bottom_rounded, - color: Colors.orangeAccent, - size: 18, - ), - const SizedBox(width: 8), - Text( - 'Cooldown: ${_fmtSeconds(sm.cooldownRemainingSeconds)}', - style: const TextStyle( - color: Colors.orangeAccent, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - const SizedBox(height: 8), - Text( - 'Wait for the cooldown to expire before starting a new session.', - textAlign: TextAlign.center, - style: TextStyle( - color: textSub, - fontSize: 12, - ), - ), - ] else - ElevatedButton( - onPressed: _showReelSessionPicker, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blueAccent, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric( - horizontal: 40, - vertical: 16, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30), - ), - ), - child: const Text( - 'Start Session', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ), - const SizedBox(height: 20), - TextButton( - onPressed: () { - setState(() => _reelsBlockedOverlay = false); - _controller?.goBack(); - }, - child: Text( - 'Go Back', - style: TextStyle(color: textSub), - ), - ), - ], - ); - }, - ), - ); - }, - ), - ), - ], - ), - ), - ); - } - - void _registerJavaScriptHandlers(InAppWebViewController controller) { - controller.addJavaScriptHandler( - handlerName: 'FocusGramNotificationChannel', - callback: (args) { - if (!mounted) return null; - final settings = context.read(); - final msg = (args.isNotEmpty ? args[0] : '') as String; - - if (DateTime.now().difference(_lastMainFrameLoadStartedAt).inSeconds < - 6) { - return null; - } - - String title = ''; - String body = ''; - bool isDM = false; - - if (msg.contains(': ')) { - final parts = msg.split(': '); - title = parts[0]; - body = parts.sublist(1).join(': '); - isDM = - title.toLowerCase().contains('message') || - title.toLowerCase().contains('direct'); - } else { - isDM = msg == 'DM'; - title = isDM ? 'Instagram Message' : 'Instagram Notification'; - body = isDM - ? 'Someone messaged you' - : 'New activity in notifications'; - } - - if (isDM && !settings.notifyDMs) return null; - if (!isDM && !settings.notifyActivity) return null; - - try { - NotificationService().showNotification( - // Use hash of message for unique ID - prevents random duplicate notifications - id: (msg.hashCode.abs() % 100000) + 2000, - title: title, - body: body, - ); - } catch (_) {} - return null; - }, - ); - - controller.addJavaScriptHandler( - handlerName: 'FocusGramBlocked', - callback: (args) { - if (!mounted) return null; - final what = (args.isNotEmpty ? args[0] : '') as String? ?? ''; - final text = what == 'reels' - ? 'Reels are disabled' - : (what == 'explore' ? 'Explore is disabled' : 'Content disabled'); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(text), - behavior: SnackBarBehavior.floating, - margin: const EdgeInsets.fromLTRB(16, 0, 16, 20), - ), - ); - return null; - }, - ); - - controller.addJavaScriptHandler( - handlerName: 'FocusGramShareChannel', - callback: (args) { - if (!mounted) return; - try { - final data = (args.isNotEmpty ? args[0] : '') as String; - String url = data; - try { - final match = RegExp(r'"url":"([^"]+)"').firstMatch(data); - if (match != null) url = match.group(1) ?? data; - } catch (_) {} - Clipboard.setData(ClipboardData(text: url)); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Link copied (tracking removed)'), - backgroundColor: Color(0xFF1A1A2E), - behavior: SnackBarBehavior.floating, - margin: EdgeInsets.fromLTRB(16, 0, 16, 20), - ), - ); - } catch (_) {} - }, - ); - - controller.addJavaScriptHandler( - handlerName: 'FocusGramMediaDownload', - callback: (args) async { - if (!mounted) return null; - - final raw = (args.isNotEmpty ? args[0] : '') as String; - - // We still want to show a tailored snackbar message, but the heavy - // JSON + security validation is delegated to the pure helper. - String type = 'video'; - try { - final payload = jsonDecode(raw) as Map; - type = (payload['type'] as String? ?? 'video').toString(); - } catch (_) { - // If payload isn't parseable, helper will reject anyway. - } - - final ok = await handleFocusGramMediaDownload( - raw: raw, - launch: (uri) => launchUrl(uri, mode: LaunchMode.externalApplication), - ); - - if (!mounted) return null; - - if (!ok) return null; - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - type == 'photo' - ? 'Opening photo downloadâ€Ķ' - : 'Opening video downloadâ€Ķ', - ), - behavior: SnackBarBehavior.floating, - margin: const EdgeInsets.fromLTRB(16, 0, 16, 20), - ), - ); - return null; - }, // closes callback - ); // closes addJavaScriptHandler - - controller.addJavaScriptHandler( - handlerName: 'FocusGramThemeChannel', - callback: (args) { - final value = (args.isNotEmpty ? args[0] : '') as String; - context.read().setDarkMode(value == 'dark'); - }, - ); - - controller.addJavaScriptHandler( - handlerName: 'Haptic', - callback: (args) { - HapticFeedback.lightImpact(); - return null; - }, - ); - - // ReelMetadata handler removed — reel history feature deleted - - controller.addJavaScriptHandler( - handlerName: 'UrlChange', - callback: (args) async { - final url = (args.isNotEmpty ? args[0] : '') as String? ?? ''; - - // Update _currentUrl SYNCHRONOUSLY before any async operations, - // so shouldInterceptRequest sees the correct path immediately. - _currentUrl = url; - - _syncDirectThreadState(url); - - final s = context.read(); - - // Update homepage graphql block for SPA navigation - final path = Uri.tryParse(url)?.path ?? url; - _blockHomepageGraphql = - s.ghostMode && (path == '/' || path == '' || path == '/explore/'); - - // Re-set ghost mode flags on SPA navigation (no page reload). - _setGhostModeFlags(controller, s); - - await _injectionManager?.runAllPostLoadInjections(url); - - // Re-inject grayscale on SPA nav (no page reload) - if (s.isGrayscaleActiveNow) { - await controller.evaluateJavascript(source: grayscale.kGrayscaleJS); - } else { - await controller.evaluateJavascript( - source: grayscale.kGrayscaleOffJS, - ); - } - - // Phase 1 V2 overlay re-inject on SPA route changes - await _v2Engine?.injectDocumentEndScripts(); - - if (!mounted) return; - setState(() { - _currentUrl = url; - // SPA navigations never fire onLoadStop — clear skeleton here - // so it doesn't stay visible forever (e.g. when navigating to DMs) - _showSkeleton = false; - _isLoading = false; - }); - - final disableReels = context - .read() - .disableReelsEntirely; - final disableExplore = context - .read() - .disableExploreEntirely; - - final isReels = path.startsWith('/reels') || path.startsWith('/reel/'); - final isExplore = path.startsWith('/explore'); - - // Block reel navigation that slipped through (e.g. DM-embedded reels) - if (disableReels && isReels) { - setState(() => _reelsBlockedOverlay = true); - await _controller?.goBack(); - return; - } - - if (_controller != null) { - if (disableExplore && isExplore) { - await _controller!.loadUrl( - urlRequest: URLRequest(url: WebUri('https://www.instagram.com/')), - ); - } - } - - // Update isolated player flag for DM-embedded reels - final isIsolatedReel = - path.contains('/reel/') && !path.startsWith('/reels'); - await _setIsolatedPlayer(isIsolatedReel); - - return null; - }, - ); - } -} - -// ─── Supporting widgets (unchanged) ────────────────────────────────────────── - -class _MinimalModeBanner extends StatelessWidget { - final VoidCallback onDismiss; - - const _MinimalModeBanner({required this.onDismiss}); - - @override - Widget build(BuildContext context) { - final isDark = Theme.of(context).brightness == Brightness.dark; - return Material( - color: Colors.transparent, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), - decoration: BoxDecoration( - color: (isDark ? Colors.black : Colors.white).withValues(alpha: 0.92), - borderRadius: BorderRadius.circular(14), - border: Border.all( - color: isDark ? Colors.white12 : Colors.black12, - width: 0.5, - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: isDark ? 0.35 : 0.12), - blurRadius: 18, - spreadRadius: 2, - ), - ], - ), - child: Row( - children: [ - Expanded( - child: Text( - 'Minimal mode — Feed & DMs only ðŸŽŊ', - style: TextStyle( - color: isDark ? Colors.white : Colors.black, - fontWeight: FontWeight.w600, - fontSize: 13, - ), - ), - ), - InkWell( - onTap: onDismiss, - borderRadius: BorderRadius.circular(20), - child: Padding( - padding: const EdgeInsets.all(4), - child: Icon( - Icons.close, - size: 18, - color: isDark ? Colors.white70 : Colors.black54, - ), - ), - ), - ], - ), - ), - ); - } -} - -class _EdgePanel extends StatefulWidget { - final String currentUrl; - const _EdgePanel({super.key, this.currentUrl = ''}); - @override - State<_EdgePanel> createState() => _EdgePanelState(); -} - -class _EdgePanelState extends State<_EdgePanel> { - bool _isExpanded = false; - void _toggleExpansion() => setState(() => _isExpanded = !_isExpanded); - - @override - Widget build(BuildContext context) { - final sm = context.watch(); - final settings = context.watch(); - final isDark = settings.isDarkMode; - final reelsHardDisabled = settings.disableReelsEntirely; - final panelBg = isDark ? const Color(0xFF111214) : Colors.white; - final textMain = isDark ? Colors.white : Colors.black87; - final textSub = isDark ? Colors.white60 : Colors.black54; - final border = isDark ? Colors.white12 : Colors.black12; - final canStart = - !reelsHardDisabled && - !sm.isSessionActive && - !sm.isCooldownActive && - sm.dailyRemainingSeconds > 0; - final statusColor = reelsHardDisabled - ? Colors.redAccent - : sm.isSessionActive - ? Colors.greenAccent - : sm.isCooldownActive - ? Colors.orangeAccent - : Colors.blueAccent; - final statusText = reelsHardDisabled - ? 'Reels blocked' - : sm.isSessionActive - ? 'Session active' - : sm.isCooldownActive - ? 'Cooldown' - : sm.dailyRemainingSeconds <= 0 - ? 'Daily limit reached' - : 'Ready'; - final sessionProgress = sm.isSessionActive && sm.perSessionSeconds > 0 - ? (sm.remainingSessionSeconds / sm.perSessionSeconds).clamp(0.0, 1.0) - : 0.0; - - return Stack( - children: [ - if (_isExpanded) - Positioned.fill( - child: GestureDetector( - onTap: _toggleExpansion, - behavior: HitTestBehavior.opaque, - child: Container( - color: Colors.black.withValues(alpha: isDark ? 0.15 : 0.05), - ), - ), - ), - AnimatedPositioned( - duration: const Duration(milliseconds: 260), - curve: Curves.easeOutCubic, - right: _isExpanded ? 12 : -328, - top: MediaQuery.of(context).padding.top + 72, - child: Container( - width: 316, - constraints: BoxConstraints( - maxHeight: - MediaQuery.of(context).size.height - - MediaQuery.of(context).padding.top - - 96, - ), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: panelBg.withValues(alpha: 0.98), - borderRadius: BorderRadius.circular(20), - border: Border.all(color: border, width: 0.5), - boxShadow: [ - BoxShadow( - color: (isDark ? Colors.black : Colors.black12).withValues( - alpha: 0.3, - ), - blurRadius: 24, - offset: const Offset(0, 12), - ), - ], - ), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - width: 38, - height: 38, - decoration: BoxDecoration( - color: statusColor.withValues(alpha: 0.14), - borderRadius: BorderRadius.circular(12), - ), - child: Icon(Icons.timer_outlined, color: statusColor), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Focus Control', - style: TextStyle( - color: textMain, - fontSize: 17, - fontWeight: FontWeight.w700, - ), - ), - const SizedBox(height: 2), - Text( - statusText, - style: TextStyle( - color: statusColor, - fontSize: 12, - ), - ), - ], - ), - ), - // Level badge - Consumer( - builder: (context, lv, _) => Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 3, - ), - decoration: BoxDecoration( - color: lv.level >= 3 - ? Colors.purple.withValues(alpha: 0.2) - : Colors.grey.withValues(alpha: 0.15), - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: lv.level >= 3 - ? Colors.purpleAccent.withValues(alpha: 0.4) - : Colors.grey.withValues(alpha: 0.2), - ), - ), - child: Text( - 'Lv ${lv.level}', - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.bold, - color: lv.level >= 3 - ? Colors.purpleAccent - : Colors.grey, - ), - ), - ), - ), - // Save current page — REMOVED - const SizedBox(width: 4), - IconButton( - tooltip: 'Close', - icon: Icon( - Icons.close_rounded, - color: textSub, - size: 22, - ), - onPressed: _toggleExpansion, - ), - ], - ), - // Bait Me button row - _BaitMeButtonRow(), - const SizedBox(height: 18), - Container( - width: double.infinity, - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: (isDark ? Colors.white : Colors.black).withValues( - alpha: 0.05, - ), - borderRadius: BorderRadius.circular(14), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Reel session', - style: TextStyle(color: textSub, fontSize: 12), - ), - const SizedBox(height: 6), - Text( - sm.isSessionActive - ? _formatTime(sm.remainingSessionSeconds) - : 'Not running', - style: TextStyle( - color: sm.isSessionActive ? statusColor : textMain, - fontSize: sm.isSessionActive ? 38 : 24, - fontWeight: FontWeight.w700, - fontFeatures: const [FontFeature.tabularFigures()], - ), - ), - const SizedBox(height: 12), - ClipRRect( - borderRadius: BorderRadius.circular(999), - child: LinearProgressIndicator( - value: sm.isSessionActive ? sessionProgress : 0, - minHeight: 6, - backgroundColor: isDark - ? Colors.white10 - : Colors.black12, - valueColor: AlwaysStoppedAnimation( - statusColor, - ), - ), - ), - ], - ), - ), - const SizedBox(height: 14), - Row( - children: [ - Expanded( - child: _buildStatCard( - 'Quota', - '${sm.dailyRemainingSeconds ~/ 60}m', - Icons.hourglass_bottom_rounded, - isDark: isDark, - ), - ), - const SizedBox(width: 10), - Expanded( - child: _buildStatCard( - 'Auto-close app', - sm.appSessionRemainingSeconds > 0 - ? _formatTime(sm.appSessionRemainingSeconds) - : 'Off', - Icons.lock_clock_rounded, - isDark: isDark, - ), - ), - ], - ), - const SizedBox(height: 10), - _buildStatusRow( - icon: Icons.local_cafe_outlined, - label: 'Cooldown', - value: sm.isCooldownActive - ? _formatTime(sm.cooldownRemainingSeconds) - : 'Inactive', - color: sm.isCooldownActive ? Colors.orangeAccent : textSub, - isDark: isDark, - ), - _buildStatusRow( - icon: Icons.block_rounded, - label: 'Hard block', - value: reelsHardDisabled ? 'On' : 'Off', - color: reelsHardDisabled ? Colors.redAccent : textSub, - isDark: isDark, - ), - - const SizedBox(height: 16), - if (sm.isSessionActive) - SizedBox( - width: double.infinity, - child: FilledButton.icon( - onPressed: () { - context.read().endSession(); - HapticFeedback.mediumImpact(); - }, - icon: const Icon(Icons.stop_circle_outlined, size: 18), - label: const Text('End Session'), - style: FilledButton.styleFrom( - backgroundColor: Colors.redAccent, - foregroundColor: Colors.white, - ), - ), - ) - else - SizedBox( - width: double.infinity, - child: FilledButton.icon( - onPressed: canStart - ? () { - _toggleExpansion(); - context - .findAncestorStateOfType< - _MainWebViewPageState - >() - ?._showReelSessionPicker(); - } - : null, - icon: const Icon(Icons.play_arrow_rounded, size: 20), - label: const Text('Start Session'), - ), - ), - const SizedBox(height: 8), - if (!canStart && !sm.isSessionActive) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - reelsHardDisabled - ? 'Turn off hard Reels blocking in Focus Mode to use timed sessions.' - : sm.isCooldownActive - ? 'A cooldown is active before the next Reel session.' - : 'Your daily Reel quota is used up.', - style: TextStyle( - color: textSub, - fontSize: 12, - height: 1.35, - ), - ), - if (sm.dailyRemainingSeconds <= 0 && - !reelsHardDisabled && - !sm.isCooldownActive) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Consumer( - builder: (ctx, credits, _) { - if (!credits.canWatchAdToday) { - return Text( - 'Ad limit reached (3/day)', - style: TextStyle( - color: textSub, - fontSize: 11, - ), - ); - } - return SizedBox( - width: double.infinity, - height: 40, - child: OutlinedButton.icon( - onPressed: () async { - final adResult = - await Navigator.push( - context, - MaterialPageRoute( - builder: (_) => - const AdsterraAdScreen( - sessionType: 'reels', - requiredSeconds: 20, - ), - ), - ); - if (adResult == true && context.mounted) { - context - .read() - .addReelsMinutes(amount: 2); - context - .read() - .addBonusDailyMinutes(2); - HapticFeedback.heavyImpact(); - } - }, - icon: const Icon(Icons.videocam, size: 16), - label: Text( - 'Watch Ad (+2 min) ' - '(${CreditStore.maxDailyAds - credits.adsWatchedToday}/3 today)', - style: const TextStyle(fontSize: 12), - ), - style: OutlinedButton.styleFrom( - foregroundColor: Colors.orangeAccent, - side: BorderSide( - color: Colors.orangeAccent.withValues( - alpha: 0.4, - ), - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - ), - ); - }, - ), - ), - ], - ), - const SizedBox(height: 10), - Divider(color: border), - ListTile( - onTap: () { - _toggleExpansion(); - context - .findAncestorStateOfType<_MainWebViewPageState>() - ?._signOut(); - }, - leading: const Icon( - Icons.logout_rounded, - color: Colors.redAccent, - size: 20, - ), - title: const Text( - 'Switch Account', - style: TextStyle( - color: Colors.redAccent, - fontSize: 13, - fontWeight: FontWeight.bold, - ), - ), - dense: true, - contentPadding: EdgeInsets.zero, - ), - ], - ), - ), - ), - ), - ], - ); - } - - Widget _buildStatCard( - String label, - String value, - IconData icon, { - bool isDark = true, - }) { - final textMain = isDark ? Colors.white : Colors.black; - final textSub = isDark ? Colors.white54 : Colors.black54; - return Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: (isDark ? Colors.white : Colors.black).withValues(alpha: 0.05), - borderRadius: BorderRadius.circular(12), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Icon(icon, color: textSub, size: 18), - const SizedBox(height: 8), - Text(label, style: TextStyle(color: textSub, fontSize: 11)), - const SizedBox(height: 2), - Text( - value, - style: TextStyle( - color: textMain, - fontSize: 18, - fontWeight: FontWeight.w700, - fontFeatures: const [FontFeature.tabularFigures()], - ), - ), - ], - ), - ); - } - - Widget _buildStatusRow({ - required IconData icon, - required String label, - required String value, - required Color color, - bool isDark = true, - }) { - final textMain = isDark ? Colors.white : Colors.black87; - final textSub = isDark ? Colors.white54 : Colors.black54; - return Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - child: Row( - children: [ - Icon(icon, color: color, size: 18), - const SizedBox(width: 10), - Expanded( - child: Text(label, style: TextStyle(color: textSub, fontSize: 13)), - ), - Text( - value, - style: TextStyle( - color: color == textSub ? textMain : color, - fontWeight: FontWeight.w600, - fontFeatures: const [FontFeature.tabularFigures()], - ), - ), - ], - ), - ); - } - - String _formatTime(int seconds) { - final m = seconds ~/ 60; - final s = seconds % 60; - return '${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}'; - } -} - -/// Small row showing the Bait Me button and daily XP for the edge panel. -class _BaitMeButtonRow extends StatelessWidget { - const _BaitMeButtonRow(); - - @override - Widget build(BuildContext context) { - final levelService = context.watch(); - final baitEngine = context.watch(); - final isUnlocked = levelService.isFeatureUnlocked(AppFeature.baitMe); - - if (!isUnlocked) return const SizedBox.shrink(); - - return Padding( - padding: const EdgeInsets.only(top: 8), - child: SizedBox( - width: double.infinity, - height: 48, - child: ElevatedButton.icon( - onPressed: baitEngine.isOnCooldown - ? null - : () => _openBaitMe(context), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.purpleAccent.withValues(alpha: 0.2), - foregroundColor: Colors.purpleAccent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - icon: const Icon(Icons.casino_rounded, size: 20), - label: Text( - baitEngine.isOnCooldown - ? 'Bait Me (${baitEngine.cooldownRemainingMinutes}m cooldown)' - : 'ðŸŽē Bait Me — Feel Lucky?', - style: const TextStyle(fontWeight: FontWeight.w600), - ), - ), - ), - ); - } - - void _openBaitMe(BuildContext context) { - Navigator.push( - context, - MaterialPageRoute(builder: (_) => const BaitMeFullScreen()), - ); - } -} - -class _BrandedTopBar extends StatelessWidget { - final VoidCallback? onFocusControlTap; - final VoidCallback? onDmGhostToggle; - final VoidCallback? onReload; - final String currentUrl; - final bool dmGhostActive; - const _BrandedTopBar({ - this.onFocusControlTap, - this.onDmGhostToggle, - this.onReload, - this.currentUrl = '', - this.dmGhostActive = false, - }); - - static bool _isDirectInbox(String url) { - final path = Uri.tryParse(url)?.path ?? url; - return path == '/direct/inbox/' || path == '/direct/inbox'; - } - - static bool _isDirectThread(String url) { - final path = Uri.tryParse(url)?.path ?? url; - return RegExp(r'^/direct/t/').hasMatch(path); - } - - @override - Widget build(BuildContext context) { - final settings = context.watch(); - final isDark = settings.isDarkMode; - final barBg = isDark ? Colors.black : Colors.white; - final textMain = isDark ? Colors.white : Colors.black; - final iconColor = isDark ? Colors.white70 : Colors.black54; - final border = isDark ? Colors.white12 : Colors.black12; - final showDmGhostBtn = _isDirectThread(currentUrl) && dmGhostActive; - final showReloadBtn = _isDirectInbox(currentUrl); - - return Container( - height: 60, - decoration: BoxDecoration( - color: barBg, - border: Border(bottom: BorderSide(color: border, width: 0.5)), - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // Left: settings icon - IconButton( - icon: Icon(Icons.settings_outlined, color: iconColor, size: 22), - onPressed: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const SettingsPage()), - ), - ), - - // Center: FocusGram logo (or DM ghost badge) - if (showDmGhostBtn) - GestureDetector( - onTap: onDmGhostToggle, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 4, - ), - decoration: BoxDecoration( - color: Colors.redAccent.withValues(alpha: 0.15), - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: Colors.redAccent.withValues(alpha: 0.4), - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - Icons.visibility_off, - color: Colors.redAccent, - size: 16, - ), - const SizedBox(width: 4), - Text( - 'DM Ghost ON', - style: TextStyle( - color: Colors.redAccent, - fontSize: 11, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(width: 4), - Icon( - Icons.close, - color: Colors.redAccent.withValues(alpha: 0.6), - size: 14, - ), - ], - ), - ), - ) - else - Text( - 'FocusGram', - style: GoogleFonts.grandHotel( - color: textMain, - fontSize: 32, - letterSpacing: 0.5, - ), - ), - - // Right: reload button + timer icon - Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (showReloadBtn) - IconButton( - icon: Icon( - Icons.refresh_rounded, - color: iconColor, - size: 22, - ), - onPressed: onReload, - tooltip: 'Reload page', - ), - IconButton( - icon: const Icon( - Icons.timer_outlined, - color: Colors.blueAccent, - size: 22, - ), - onPressed: onFocusControlTap, - ), - ], - ), - ], - ), - ), - ); - } -} - -class _InstagramGradientProgressBar extends StatelessWidget { - const _InstagramGradientProgressBar(); - @override - Widget build(BuildContext context) { - return SizedBox( - height: 2.5, - child: Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - colors: [ - Color(0xFFFEDA75), - Color(0xFFFA7E1E), - Color(0xFFD62976), - Color(0xFF962FBF), - Color(0xFF4F5BD5), - ], - begin: Alignment.centerLeft, - end: Alignment.centerRight, - ), - ), - ), - ); - } -} - -class _UpdateBanner extends StatelessWidget { - const _UpdateBanner(); - - @override - Widget build(BuildContext context) { - return Consumer( - builder: (context, update, _) { - if (!update.hasUpdate) return const SizedBox.shrink(); - - return Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [Colors.blue.shade700, Colors.blue.shade900], - ), - ), - child: Row( - children: [ - const Icon( - Icons.system_update_alt, - color: Colors.white, - size: 18, - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Update Available: ${update.updateInfo?.latestVersion ?? ''}', - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 13, - ), - ), - const SizedBox(height: 2), - InkWell( - onTap: () { - final url = update.updateInfo?.releaseUrl; - if (url != null && url.isNotEmpty) { - launchUrl( - Uri.parse(url), - mode: LaunchMode.externalApplication, - ); - } - }, - child: const Text( - 'Download on GitHub →', - style: TextStyle( - color: Colors.white70, - fontSize: 12, - decoration: TextDecoration.underline, - ), - ), - ), - ], - ), - ), - IconButton( - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - onPressed: () => update.dismissUpdate(), - icon: const Icon(Icons.close, color: Colors.white70, size: 18), - ), - ], - ), - ); - }, - ); - } -} - -class _NoInternetScreen extends StatelessWidget { - final VoidCallback onRetry; - const _NoInternetScreen({required this.onRetry}); - @override - Widget build(BuildContext context) { - final isDark = context.watch().isDarkMode; - return Container( - color: isDark ? Colors.black : Colors.white, - width: double.infinity, - height: double.infinity, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.wifi_off_rounded, - color: isDark ? Colors.white24 : Colors.black12, - size: 80, - ), - const SizedBox(height: 24), - Text( - 'No Connection', - style: TextStyle( - color: isDark ? Colors.white : Colors.black, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - Text( - 'Please check your internet settings.', - style: TextStyle( - color: isDark ? Colors.white38 : Colors.black38, - fontSize: 14, - ), - ), - const SizedBox(height: 40), - ElevatedButton( - onPressed: onRetry, - style: ElevatedButton.styleFrom( - backgroundColor: isDark ? Colors.white : Colors.black, - foregroundColor: isDark ? Colors.black : Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - ), - child: const Text('Retry'), - ), - ], - ), - ); - } -} diff --git a/lib/screens/offline_feed_viewer.dart b/lib/screens/offline_feed_viewer.dart deleted file mode 100644 index 0e7c92f..0000000 --- a/lib/screens/offline_feed_viewer.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:provider/provider.dart'; -import '../services/snapshot_service.dart'; - -/// Opens a saved page offline. Uses saved HTML content when available, -/// falls back to WebView cache. -class OfflineFeedViewer extends StatelessWidget { - final String url; - final String? pageId; - - const OfflineFeedViewer({super.key, required this.url, this.pageId}); - - @override - Widget build(BuildContext context) { - // Find the saved page with HTML content - SavedPage? page; - if (pageId != null) { - final ss = context.read(); - final matches = ss.savedPages.where((p) => p.id == pageId); - if (matches.isNotEmpty) page = matches.first; - } - - return Scaffold( - appBar: AppBar( - title: const Text( - 'Offline View', - style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600), - ), - centerTitle: true, - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_new, size: 18), - onPressed: () => Navigator.pop(context), - ), - ), - body: Column( - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - color: Colors.blue.withValues(alpha: 0.1), - child: const Row( - children: [ - Icon( - Icons.wifi_off_rounded, - size: 14, - color: Colors.blueAccent, - ), - SizedBox(width: 6), - Text( - 'Offline — saved content shown', - style: TextStyle(fontSize: 11, color: Colors.blueAccent), - ), - ], - ), - ), - Expanded( - child: page?.htmlContent != null - ? InAppWebView( - initialSettings: InAppWebViewSettings( - javaScriptEnabled: true, - domStorageEnabled: true, - transparentBackground: false, - useHybridComposition: true, - ), - onWebViewCreated: (c) async { - await c.loadData( - data: page!.htmlContent!, - mimeType: 'text/html', - encoding: 'utf-8', - baseUrl: WebUri(url), - ); - }, - ) - : InAppWebView( - initialUrlRequest: URLRequest(url: WebUri(url)), - initialSettings: InAppWebViewSettings( - cacheEnabled: true, - cacheMode: CacheMode.LOAD_CACHE_ELSE_NETWORK, - domStorageEnabled: true, - javaScriptEnabled: true, - transparentBackground: false, - useHybridComposition: true, - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/screens/onboarding_page.dart b/lib/screens/onboarding_page.dart deleted file mode 100644 index 1896724..0000000 --- a/lib/screens/onboarding_page.dart +++ /dev/null @@ -1,436 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:permission_handler/permission_handler.dart'; -import 'package:app_settings/app_settings.dart'; -import '../services/settings_service.dart'; -import '../services/notification_service.dart'; - -class OnboardingPage extends StatefulWidget { - final VoidCallback onFinish; - - const OnboardingPage({super.key, required this.onFinish}); - - @override - State createState() => _OnboardingPageState(); -} - -class _OnboardingPageState extends State { - final PageController _pageController = PageController(); - int _currentPage = 0; - - // Pages: Welcome, Focus controls, Link Handling, Blur Settings, Notifications - static const int _kTotalPages = 5; - - static const int _kBlurPage = 3; - static const int _kLinkPage = 2; - static const int _kNotifPage = 4; - - @override - Widget build(BuildContext context) { - final settings = context.watch(); - - final List slides = [ - // ── Page 0: Welcome ───────────────────────────────────────────────── - _StaticSlide( - icon: Icons.auto_awesome_rounded, - color: const Color(0xFF4F8DFF), - title: 'Welcome to FocusGram', - description: - 'Use Instagram with guardrails: timed Reel sessions, calmer feeds, optional media tools, and privacy-first controls that stay on your device.', - ), - - // ── Page 1: Focus controls ─────────────────────────────────────────── - _StaticSlide( - icon: Icons.timer_outlined, - color: const Color(0xFFFFB74D), - title: 'Time With Intent', - description: - 'Set daily limits, cooldowns, scheduled focus hours, and short Reel sessions when you choose to watch.', - ), - - // ── Page 2: Open links ─────────────────────────────────────────────── - _StaticSlide( - icon: Icons.link_rounded, - color: const Color(0xFF35C2D6), - title: 'Open Links in FocusGram', - description: - 'To open Instagram links directly here: Tap "Configure", then "Open by default" → "Add link" and select all.', - isAppSettingsPage: true, - ), - - // ── Page 3: Blur Settings ──────────────────────────────────────────── - _BlurSettingsSlide(settings: settings), - - // ── Page 4: Notifications ──────────────────────────────────────────── - _StaticSlide( - icon: Icons.notifications_active_outlined, - color: const Color(0xFF5DD18A), - title: 'Useful Alerts Only', - description: - 'Enable notifications only if you want session-end or persistent focus reminders. FocusGram will ask here, not before onboarding.', - isPermissionPage: true, - permission: Permission.notification, - ), - ]; - - return Scaffold( - backgroundColor: Colors.black, - body: Stack( - children: [ - PageView.builder( - controller: _pageController, - onPageChanged: (index) => setState(() => _currentPage = index), - itemCount: _kTotalPages, - itemBuilder: (context, index) => slides[index], - ), - Positioned( - bottom: 50, - left: 0, - right: 0, - child: Column( - children: [ - // Dot indicators - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate( - _kTotalPages, - (index) => AnimatedContainer( - duration: const Duration(milliseconds: 250), - margin: const EdgeInsets.symmetric(horizontal: 4), - width: _currentPage == index ? 12 : 8, - height: 8, - decoration: BoxDecoration( - color: _currentPage == index - ? Colors.blue - : Colors.white24, - borderRadius: BorderRadius.circular(4), - ), - ), - ), - ), - const SizedBox(height: 28), - // CTA button - Padding( - padding: const EdgeInsets.symmetric(horizontal: 32), - child: SizedBox( - width: double.infinity, - height: 56, - child: Builder( - builder: (context) { - final isLast = _currentPage == _kTotalPages - 1; - final isLink = _currentPage == _kLinkPage; - final isNotif = _currentPage == _kNotifPage; - final isBlur = _currentPage == _kBlurPage; - - String label; - if (isNotif) { - label = 'Allow & Start'; - } else if (isLink) { - label = 'Configure'; - } else if (isBlur) { - label = 'Save & Continue'; - } else if (isLast) { - label = 'Get Started'; - } else { - label = 'Next'; - } - - return ElevatedButton( - onPressed: () async { - if (isLink) { - await AppSettings.openAppSettings( - type: AppSettingsType.settings, - ); - } else if (isNotif) { - await Permission.notification.request(); - await NotificationService() - .requestPermissionsNow(); - } - - if (!context.mounted) return; - if (isLast) { - _finish(context); - } else { - _pageController.nextPage( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ); - } - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - ), - child: Text( - label, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - ); - }, - ), - ), - ), - // Skip button (available on all pages except last) - if (_currentPage < _kTotalPages - 1) - TextButton( - onPressed: () { - if (_currentPage == _kNotifPage) { - _finish(context); - } else { - _pageController.animateToPage( - _kTotalPages - 1, - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ); - } - }, - child: const Text( - 'Skip setup', - style: TextStyle(color: Colors.white38, fontSize: 14), - ), - ), - ], - ), - ), - ], - ), - ); - } - - void _finish(BuildContext context) { - context.read().setFirstRunCompleted(); - widget.onFinish(); - } -} - -// ── Static info slide ────────────────────────────────────────────────────────── - -class _StaticSlide extends StatelessWidget { - final IconData icon; - final Color color; - final String title; - final String description; - final bool isPermissionPage; - final bool isAppSettingsPage; - final Permission? permission; - - const _StaticSlide({ - required this.icon, - required this.color, - required this.title, - required this.description, - this.isPermissionPage = false, - this.isAppSettingsPage = false, - this.permission, - }); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.fromLTRB(28, 40, 28, 160), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 112, - height: 112, - decoration: BoxDecoration( - color: color.withValues(alpha: 0.14), - borderRadius: BorderRadius.circular(28), - border: Border.all(color: color.withValues(alpha: 0.28)), - ), - child: Icon(icon, size: 54, color: color), - ), - const SizedBox(height: 36), - Text( - title, - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.white, - fontSize: 30, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 16), - Text( - description, - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.white70, - fontSize: 16, - height: 1.5, - ), - ), - if (isPermissionPage || isAppSettingsPage) ...[ - const SizedBox(height: 24), - Container( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.06), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.white10), - ), - child: Text( - isPermissionPage - ? 'Permission is optional and can be changed later.' - : 'This opens Android settings; return here when done.', - textAlign: TextAlign.center, - style: const TextStyle(color: Colors.white54, fontSize: 12), - ), - ), - ], - ], - ), - ); - } -} - -// ── Blur settings slide ──────────────────────────────────────────────────────── - -class _BlurSettingsSlide extends StatelessWidget { - final SettingsService settings; - - const _BlurSettingsSlide({required this.settings}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.fromLTRB(32, 40, 32, 160), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Header - Center( - child: Icon( - Icons.blur_on_rounded, - size: 90, - color: Colors.purpleAccent, - ), - ), - const SizedBox(height: 36), - const Center( - child: Text( - 'Distraction Shield', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontSize: 30, - fontWeight: FontWeight.bold, - ), - ), - ), - const SizedBox(height: 12), - const Center( - child: Text( - 'Blur feeds you don\'t want to be tempted by. You can change these anytime in Settings.', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white60, - fontSize: 16, - height: 1.5, - ), - ), - ), - const SizedBox(height: 40), - - // Blur Home Feed toggle - _BlurToggleTile( - icon: Icons.home_rounded, - label: 'Blur Home Feed', - subtitle: 'Posts in your feed will be blurred until tapped', - value: settings.blurReels, - onChanged: (v) => settings.setBlurReels(v), - ), - const SizedBox(height: 16), - - // Blur Explore toggle - _BlurToggleTile( - icon: Icons.explore_rounded, - label: 'Blur Explore Feed', - subtitle: 'Explore thumbnails stay blurred until you tap', - value: settings.blurExplore, - onChanged: (v) => settings.setBlurExplore(v), - ), - ], - ), - ); - } -} - -class _BlurToggleTile extends StatelessWidget { - final IconData icon; - final String label; - final String subtitle; - final bool value; - final ValueChanged onChanged; - - const _BlurToggleTile({ - required this.icon, - required this.label, - required this.subtitle, - required this.value, - required this.onChanged, - }); - - @override - Widget build(BuildContext context) { - return AnimatedContainer( - duration: const Duration(milliseconds: 200), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), - decoration: BoxDecoration( - color: value - ? Colors.purpleAccent.withValues(alpha: 0.12) - : Colors.white.withValues(alpha: 0.06), - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: value - ? Colors.purpleAccent.withValues(alpha: 0.5) - : Colors.white.withValues(alpha: 0.1), - width: 1, - ), - ), - child: Row( - children: [ - Icon( - icon, - color: value ? Colors.purpleAccent : Colors.white38, - size: 28, - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - label, - style: TextStyle( - color: value ? Colors.white : Colors.white70, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 2), - Text( - subtitle, - style: const TextStyle(color: Colors.white38, fontSize: 12), - ), - ], - ), - ), - Switch( - value: value, - onChanged: onChanged, - activeThumbColor: Colors.purpleAccent, - ), - ], - ), - ); - } -} diff --git a/lib/screens/reel_player_overlay.dart b/lib/screens/reel_player_overlay.dart deleted file mode 100644 index a95c8c9..0000000 --- a/lib/screens/reel_player_overlay.dart +++ /dev/null @@ -1,134 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import '../services/injection_controller.dart'; -import '../services/session_manager.dart'; -import 'package:provider/provider.dart'; - -/// An isolated player for a single Reel opened from a DM. -/// Uses JS history interception to lock the user to the initial reel URL. -class ReelPlayerOverlay extends StatefulWidget { - final String url; - const ReelPlayerOverlay({super.key, required this.url}); - - @override - State createState() => _ReelPlayerOverlayState(); -} - -class _ReelPlayerOverlayState extends State { - DateTime? _startTime; - - @override - void initState() { - super.initState(); - _startTime = DateTime.now(); - } - - @override - void dispose() { - // Record viewing time toward daily count - if (_startTime != null) { - final durationSeconds = DateTime.now().difference(_startTime!).inSeconds; - if (mounted) { - context.read().accrueSeconds(durationSeconds); - } - } - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - appBar: AppBar( - backgroundColor: Colors.black, - elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white), - onPressed: () => Navigator.pop(context), - ), - title: const Text( - 'Reel', - style: TextStyle(color: Colors.white, fontSize: 16), - ), - actions: [ - Padding( - padding: const EdgeInsets.only(right: 12), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), - decoration: BoxDecoration( - color: Colors.orange.withValues(alpha: 0.2), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.orangeAccent, width: 0.5), - ), - child: const Text( - 'Locked', - style: TextStyle(color: Colors.orangeAccent, fontSize: 11), - ), - ), - ), - ], - ), - body: InAppWebView( - initialUrlRequest: URLRequest(url: WebUri(widget.url)), - initialSettings: InAppWebViewSettings( - userAgent: InjectionController.iOSUserAgent, - mediaPlaybackRequiresUserGesture: true, - useHybridComposition: true, - cacheEnabled: true, - cacheMode: CacheMode.LOAD_CACHE_ELSE_NETWORK, - domStorageEnabled: true, - databaseEnabled: true, - hardwareAcceleration: true, - transparentBackground: true, - safeBrowsingEnabled: false, - supportZoom: false, - allowsInlineMediaPlayback: true, - verticalScrollBarEnabled: false, - horizontalScrollBarEnabled: false, - ), - onWebViewCreated: (controller) { - // Controller is not stored; this overlay is self-contained. - }, - onLoadStop: (controller, url) async { - // Set isolated player flag to ensure scroll-lock applies even if a session is active globally - await controller.evaluateJavascript( - source: 'window.__focusgramIsolatedPlayer = true;', - ); - // Apply scroll-lock via MutationObserver: prevents swiping to next reel - await controller.evaluateJavascript( - source: InjectionController.reelsMutationObserverJS, - ); - // Also apply FocusGram baseline CSS (hides bottom nav etc.) - await controller.evaluateJavascript( - source: InjectionController.buildInjectionJS( - sessionActive: true, - blurExplore: false, - blurReels: false, - tapToUnblur: false, - enableTextSelection: true, - hideSuggestedPosts: false, - hideSponsoredPosts: false, - hideLikeCounts: false, - hideFollowerCounts: false, - // hideStoriesBar removed per user request - hideExploreTab: false, - hideReelsTab: false, - hideShopTab: false, - disableReelsEntirely: false, - blockHomeFeedScroll: false, - ), - ); - }, - shouldOverrideUrlLoading: (controller, action) async { - // Keep this overlay locked to instagram.com pages only - final uri = action.request.url; - if (uri == null) return NavigationActionPolicy.CANCEL; - if (!uri.host.contains('instagram.com')) { - return NavigationActionPolicy.CANCEL; - } - return NavigationActionPolicy.ALLOW; - }, - ), - ); - } -} diff --git a/lib/screens/session_modal.dart b/lib/screens/session_modal.dart deleted file mode 100644 index b7382d1..0000000 --- a/lib/screens/session_modal.dart +++ /dev/null @@ -1,143 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../services/session_manager.dart'; -import '../services/settings_service.dart'; -import '../utils/discipline_challenge.dart'; - -class SessionModal extends StatefulWidget { - const SessionModal({super.key}); - - @override - State createState() => _SessionModalState(); -} - -class _SessionModalState extends State { - double _customMinutes = 5.0; - - @override - Widget build(BuildContext context) { - final sm = context.watch(); - - return Container( - padding: const EdgeInsets.all(24), - decoration: const BoxDecoration( - color: Color(0xFF121212), - borderRadius: BorderRadius.vertical(top: Radius.circular(24)), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Start Reel Session', - style: TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - IconButton( - onPressed: () => Navigator.pop(context), - icon: const Icon(Icons.close, color: Colors.white54), - ), - ], - ), - const SizedBox(height: 8), - Text( - 'Remaining Daily: ${sm.dailyRemainingSeconds ~/ 60}m', - style: const TextStyle(color: Colors.white70), - ), - if (sm.isCooldownActive) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - 'Cooldown active: ${sm.cooldownRemainingSeconds ~/ 60}m ${sm.cooldownRemainingSeconds % 60}s left', - style: const TextStyle(color: Colors.orangeAccent), - ), - ), - const SizedBox(height: 24), - const Text( - 'Presets', - style: TextStyle(color: Colors.white, fontWeight: FontWeight.w600), - ), - const SizedBox(height: 12), - Wrap( - spacing: 8, - runSpacing: 8, - children: [1, 3, 5, 10, 15, 20, 30].map((m) { - return SizedBox( - width: 72, - child: ElevatedButton( - onPressed: (sm.isCooldownActive || sm.isDailyLimitExhausted) - ? null - : () => _start(m), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white12, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 12), - ), - child: Text('${m}m'), - ), - ); - }).toList(), - ), - const SizedBox(height: 32), - const Text( - 'Custom Duration', - style: TextStyle(color: Colors.white, fontWeight: FontWeight.w600), - ), - Slider( - value: _customMinutes, - min: 1, - max: 60, - divisions: 59, - label: '${_customMinutes.toInt()}m', - onChanged: (v) => setState(() => _customMinutes = v), - ), - const SizedBox(height: 24), - SizedBox( - width: double.infinity, - height: 50, - child: ElevatedButton( - onPressed: (sm.isCooldownActive || sm.isDailyLimitExhausted) - ? null - : () => _start(_customMinutes.toInt()), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: const Text( - 'Start Session', - style: TextStyle(fontSize: 16), - ), - ), - ), - const SizedBox(height: 16), - ], - ), - ); - } - - void _start(int minutes) async { - final sm = context.read(); - final settings = context.read(); - - if (settings.requireWordChallenge) { - final success = await DisciplineChallenge.show( - context, - count: settings.resolvedWordChallengeCount(), - ); - if (!success) return; - } - - if (sm.startSession(minutes)) { - if (mounted) Navigator.pop(context); - } - } -} diff --git a/lib/screens/settings_page.dart b/lib/screens/settings_page.dart deleted file mode 100644 index 2cb2412..0000000 --- a/lib/screens/settings_page.dart +++ /dev/null @@ -1,1424 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; -import 'package:package_info_plus/package_info_plus.dart'; -import 'package:url_launcher/url_launcher.dart'; -import '../services/session_manager.dart'; -import '../services/settings_service.dart'; -import '../services/level_service.dart'; -import '../services/credit_store.dart'; -import '../services/app_lock_service.dart'; -// snapshot_service import removed — offline feature deleted -import '../services/focusgram_router.dart'; -import 'app_lock_settings_page.dart'; -// snapshot_manager_screen import removed — offline feature deleted -import 'level_panel_screen.dart'; -//import 'debug_menu_screen.dart'; -import '../widgets/native_ad_banner.dart'; -import '../features/screen_time/screen_time_screen.dart'; -// reels_history_screen import removed — feature deleted -import 'guardrails_page.dart'; -import 'extras_settings_page.dart'; - -// ─── Main Settings Page ─────────────────────────────────────────────────────── - -class SettingsPage extends StatelessWidget { - const SettingsPage({super.key}); - - @override - Widget build(BuildContext context) { - final sm = context.watch(); - final settings = context.watch(); - final isDark = settings.isDarkMode; - - return Scaffold( - appBar: AppBar( - title: const Text( - 'Settings', - style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600), - ), - centerTitle: true, - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_new, size: 18), - onPressed: () => Navigator.pop(context), - ), - ), - body: ListView( - children: [ - const _DonateTile(), - _buildStatsRow(sm, context), - - const _SectionHeader(title: 'FOCUS & BLOCKING'), - _SubmoduleTile( - icon: Icons.block_rounded, - iconColor: Colors.redAccent, - title: 'Focus Mode', - subtitle: settings.minimalModeEnabled - ? 'Minimal mode on' - : 'Blocking, Content Hider, Feed Blur and more', - onTap: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const FocusSettingsPage()), - ), - ), - _SubmoduleTile( - icon: Icons.timer_outlined, - iconColor: Colors.blueAccent, - title: 'Time Control & Guardrails', - subtitle: 'Daily limit, cooldown, scheduled blocking', - enabled: - !(settings.disableReelsEntirely || settings.minimalModeEnabled), - disabledSubtitle: 'Reels are fully disabled', - onTap: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const GuardrailsPage()), - ), - ), - - const _SectionHeader(title: 'EXTRAS'), - _SubmoduleTile( - icon: Icons.download_rounded, - iconColor: Colors.orangeAccent, - title: 'Extras', - subtitle: 'Startup Page, Download media, Ghost Mode', - enabled: true, - onTap: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const ExtrasSettingsPage()), - ), - ), - - const _SectionHeader(title: 'APPEARANCE'), - _SubmoduleTile( - icon: Icons.palette_outlined, - iconColor: Colors.purpleAccent, - title: 'Appearance', - subtitle: settings.grayscaleEnabled - ? 'Grayscale on' - : settings.grayscaleSchedules.isNotEmpty - ? 'Grayscale scheduled (${settings.grayscaleSchedules.length} schedules)' - : 'Grayscale and schedules', - onTap: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const AppearancePage()), - ), - ), - - const _SectionHeader(title: 'SECURITY'), - _SubmoduleTile( - icon: Icons.lock_rounded, - iconColor: Colors.blueAccent, - title: 'App Lock', - subtitle: _appLockSubtitle(context.watch()), - onTap: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const AppLockSettingsPage()), - ), - ), - - const _SectionHeader(title: 'PRIVACY & NOTIFICATIONS'), - _SubmoduleTile( - icon: Icons.lock_outline, - iconColor: Colors.tealAccent, - title: 'Privacy & Notifications', - subtitle: 'Manage Your Notifications', - onTap: () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const PrivacyNotificationsPage(), - ), - ), - ), - - const _SectionHeader(title: 'STATS & HISTORY'), - _SubmoduleTile( - icon: Icons.bar_chart_rounded, - iconColor: Colors.greenAccent, - title: 'Screen Time Dashboard', - subtitle: 'Daily & weekly usage', - onTap: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const ScreenTimeScreen()), - ), - ), - _SubmoduleTile( - icon: Icons.trending_up_rounded, - iconColor: Colors.amber, - title: 'Your Journey', - subtitle: - 'Level ${context.watch().level} · ${context.watch().xp} XP', - onTap: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const LevelPanelScreen()), - ), - ), - - // Quick XP debug grant (visible in settings for testing) - // _XpDebugTile(), - // Reels History removed - const _SectionHeader(title: 'ABOUT'), - _VersionTile(), - ListTile( - title: const Text('GitHub'), - trailing: const Icon(Icons.open_in_new, size: 14), - onTap: () => launchUrl( - Uri.parse('https://github.com/Ujwal223/FocusGram'), - mode: LaunchMode.externalApplication, - ), - ), - ListTile( - title: const Text('Legal Disclaimer'), - trailing: const Icon(Icons.info_outline, size: 14), - onTap: () => _showLegalDisclaimer(context), - ), - ListTile( - title: const Text('Open Source Licenses'), - trailing: const Icon(Icons.arrow_forward_ios, size: 14), - onTap: () => showLicensePage(context: context), - ), - - const Divider(height: 40, indent: 16, endIndent: 16), - ListTile( - leading: const Icon( - Icons.settings_outlined, - color: Colors.purpleAccent, - ), - title: const Text('Instagram Settings'), - subtitle: const Text( - 'Open native Instagram account settings', - style: TextStyle(fontSize: 12), - ), - trailing: const Icon( - Icons.open_in_new, - color: Colors.white24, - size: 14, - ), - onTap: () { - Navigator.pop(context); - FocusGramRouter.pendingUrl.value = - 'https://www.instagram.com/accounts/settings/?entrypoint=profile'; - }, - ), - const SizedBox(height: 20), - const NativeAdBanner(height: 60), - const SizedBox(height: 40), - Center( - child: Text( - 'FocusGram · Built with 💖 by Ujwal Chapagain', - style: TextStyle( - color: isDark ? Colors.white12 : Colors.black12, - fontSize: 12, - ), - ), - ), - const SizedBox(height: 24), - ], - ), - ); - } - - Widget _buildStatsRow(SessionManager sm, BuildContext context) { - final creditStore = context.watch(); - final cells = [ - _statCell('Opens Today', '${sm.dailyOpenCount}×', Colors.blue), - _dividerCell(), - _statCell( - 'Reels Used', - '${sm.dailyUsedSeconds ~/ 60}m', - Colors.orangeAccent, - ), - _dividerCell(), - _statCell( - 'Remaining', - '${sm.dailyRemainingSeconds ~/ 60}m', - Colors.greenAccent, - ), - ]; - - if (true) { - // ad counter always shown - cells.addAll([ - _dividerCell(), - _statCell( - 'XP Ads Watched', - '${creditStore.adsWatchedToday}', - Colors.purpleAccent, - ), - ]); - } - - return Container( - margin: const EdgeInsets.fromLTRB(16, 20, 16, 4), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.blue.withValues(alpha: 0.05), - borderRadius: BorderRadius.circular(14), - border: Border.all(color: Colors.blue.withValues(alpha: 0.1)), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: cells, - ), - ); - } - - Widget _statCell(String label, String value, Color color) => Column( - children: [ - Text( - value, - style: TextStyle( - color: color, - fontSize: 22, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 4), - Text(label, style: const TextStyle(color: Colors.grey, fontSize: 11)), - ], - ); - - Widget _dividerCell() => Container( - width: 1, - height: 36, - color: Colors.blue.withValues(alpha: 0.1), - ); - - String _appLockSubtitle(AppLockService a) { - if (!a.anyLockEnabled) return 'Protect FocusGram with a PIN'; - final parts = []; - if (a.lockAppWide) parts.add('App-wide'); - if (a.lockMessages) parts.add('Messages'); - return '${parts.join(' + ')} lock active'; - } - - void _showLegalDisclaimer(BuildContext context) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Legal Disclaimer'), - content: const SingleChildScrollView( - child: Text( - 'FocusGram is an independent, free, and open-source productivity tool ' - 'licensed under AGPL-3.0. Not affiliated with Meta or Instagram.\n\n' - 'How it works: FocusGram embeds a standard Android System WebView that loads instagram.com. \n' - 'All user-facing features are implemented exclusively via client-side modifications and are never transmitted to or processed by Meta servers.\n\n' - 'All features are client-side only. We do not use private APIs, ' - 'intercept credentials, scrape, harvest or collect any user data.', - style: TextStyle(fontSize: 13, height: 1.4), - ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Close'), - ), - ], - ), - ); - } -} - -// ─── Focus Settings ─────────────────────────────────────────────────────────── - -class FocusSettingsPage extends StatelessWidget { - const FocusSettingsPage({super.key}); - - @override - Widget build(BuildContext context) { - final settings = context.watch(); - return Scaffold( - appBar: _subAppBar(context, 'Focus Mode'), - body: ListView( - children: [ - const _SectionHeader(title: 'BLOCKING'), - - Container( - margin: const EdgeInsets.fromLTRB(16, 0, 16, 8), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: Colors.blue.withValues(alpha: 0.07), - borderRadius: BorderRadius.circular(10), - border: Border.all(color: Colors.blue.withValues(alpha: 0.12)), - ), - child: const Row( - children: [ - Icon(Icons.info_outline, size: 14, color: Colors.blueAccent), - SizedBox(width: 8), - Expanded( - child: Text( - 'Blocking changes apply immediately. The page reloads automatically in the background.', - style: TextStyle(fontSize: 11, color: Colors.blueAccent), - ), - ), - ], - ), - ), - ListTile( - leading: Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: Colors.redAccent.withValues(alpha: 0.12), - borderRadius: BorderRadius.circular(10), - ), - child: const Icon( - Icons.shield_rounded, - color: Colors.redAccent, - size: 20, - ), - ), - title: const Text('Minimal Mode', style: TextStyle(fontSize: 15)), - subtitle: Text( - settings.minimalModeEnabled - ? 'Enabled - tap to customize' - : 'Disabled - tap to configure', - style: TextStyle( - fontSize: 12, - color: settings.minimalModeEnabled - ? Colors.greenAccent - : Colors.grey, - ), - ), - trailing: Switch( - value: settings.minimalModeEnabled, - onChanged: (v) async { - await settings.setMinimalModeEnabled(v); - HapticFeedback.selectionClick(); - }, - ), - onTap: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const MinimalModeSubmenuPage()), - ), - ), - - const SizedBox(height: 8), - - const _SectionHeader(title: 'FRICTION'), - _SwitchTile( - title: 'Mindfulness Gate', - subtitle: '${settings.breathGateSeconds}s before opening Instagram', - value: settings.showBreathGate, - onChanged: (v) => settings.setShowBreathGate(v), - ), - if (settings.showBreathGate) - _NumberEditTile( - title: 'Gate Duration', - label: '${settings.breathGateSeconds} seconds', - initialValue: settings.breathGateSeconds, - min: 3, - max: 60, - suffix: 'seconds', - onSubmitted: (v) => settings.setBreathGateSeconds(v), - ), - _SwitchTile( - title: 'Typing Challenge', - subtitle: settings.wordChallengeCount == 0 - ? 'Random: 10-35 words' - : '${settings.wordChallengeCount} words', - value: settings.requireWordChallenge, - onChanged: (v) => settings.setRequireWordChallenge(v), - ), - if (settings.requireWordChallenge) - _ChoiceTile( - title: 'Typing Words', - value: settings.wordChallengeCount, - label: settings.wordChallengeCount == 0 - ? 'Random (10-35)' - : '${settings.wordChallengeCount} words', - options: const [20, 25, 30, 35, 0], - optionLabel: (v) => v == 0 ? 'Random (10-35)' : '$v words', - onSelected: (v) => settings.setWordChallengeCount(v), - ), - - _SwitchTile( - title: 'Effort Friction Mode', - subtitle: 'Earn credits by watching ads — enabled by default', - value: settings.effortFrictionEnabled, - onChanged: (v) async { - if (v && - !context.read().isFeatureUnlocked( - AppFeature.effortFriction, - )) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Unlocks at Level 3')), - ); - return; - } - await settings.setEffortFrictionEnabled(v); - HapticFeedback.selectionClick(); - }, - ), - - const _SectionHeader(title: 'MEDIA'), - // Block Autoplay removed — was unreliable - _SwitchTile( - title: 'Blur Feed & Explore', - subtitle: 'Blurs post thumbnails until tapped', - value: settings.blurExplore, - onChanged: (v) => settings.setBlurExplore(v), - ), - // Tap to Unblur as child toggle (shown directly under Blur Feed when enabled) - if (settings.blurExplore) - Padding( - padding: const EdgeInsets.only(left: 32), - child: _SwitchTile( - title: 'Tap to Unblur', - subtitle: 'First tap reveals the post (doesn\'t open it)', - value: settings.tapToUnblur, - onChanged: (v) => settings.setTapToUnblur(v), - ), - ), - - const _SectionHeader(title: 'CONTENT HIDER'), - - _SwitchTile( - title: 'Hide Feed Posts', - subtitle: 'Hides home feed posts', - value: settings.contentPosts, - onChanged: (v) => settings.setContentPostsEnabled(v), - ), - - const SizedBox(height: 40), - ], - ), - ); - } -} - -class _DonateTile extends StatelessWidget { - const _DonateTile(); - - static final Uri _donateUri = Uri.parse('https://buymemomo.com/ujwal'); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.fromLTRB(16, 16, 16, 4), - decoration: BoxDecoration( - color: Colors.pinkAccent.withValues(alpha: 0.10), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.pinkAccent.withValues(alpha: 0.22)), - ), - child: ListTile( - leading: Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: Colors.pinkAccent.withValues(alpha: 0.14), - borderRadius: BorderRadius.circular(10), - ), - child: const Icon( - Icons.favorite_rounded, - color: Colors.pinkAccent, - size: 20, - ), - ), - title: const Text( - 'Please donate to support the development of this project.', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600), - ), - subtitle: const Text( - 'Your support keeps FocusGram free and maintained.', - style: TextStyle(fontSize: 12), - ), - trailing: const Icon(Icons.open_in_new, size: 14), - onTap: () => - launchUrl(_donateUri, mode: LaunchMode.externalApplication), - ), - ); - } -} - -// ─── Minimal Mode Submenu ───────────────────────────────────────────────────── - -class MinimalModeSubmenuPage extends StatefulWidget { - const MinimalModeSubmenuPage({super.key}); - - @override - State createState() => _MinimalModeSubmenuPageState(); -} - -class _MinimalModeSubmenuPageState extends State { - late bool _blurExplore; - late bool _disableReelsEntirely; - late bool _disableExploreEntirely; - late bool _blockHomeFeedScroll; - - @override - void initState() { - super.initState(); - final settings = context.read(); - _blurExplore = settings.blurExplore; - _disableReelsEntirely = settings.disableReelsEntirely; - _disableExploreEntirely = settings.disableExploreEntirely; - _blockHomeFeedScroll = settings.blockHomeFeedScroll; - } - - Future _updateSetting(String key, bool value) async { - final settings = context.read(); - setState(() { - switch (key) { - case 'blurExplore': - _blurExplore = value; - break; - case 'disableReelsEntirely': - _disableReelsEntirely = value; - break; - case 'disableExploreEntirely': - _disableExploreEntirely = value; - break; - case 'blockHomeFeedScroll': - _blockHomeFeedScroll = value; - break; - } - }); - - switch (key) { - case 'blurExplore': - await settings.setBlurExplore(value); - break; - case 'disableReelsEntirely': - await settings.setDisableReelsEntirelyInternal(value); - break; - case 'disableExploreEntirely': - await settings.setDisableExploreEntirelyInternal(value); - break; - case 'blockHomeFeedScroll': - await settings.setBlockHomeFeedScrollInternal(value); - break; - } - - if (!mounted) return; - final latest = context.read(); - setState(() { - _blurExplore = latest.blurExplore; - _disableReelsEntirely = latest.disableReelsEntirely; - _disableExploreEntirely = latest.disableExploreEntirely; - _blockHomeFeedScroll = latest.blockHomeFeedScroll; - }); - HapticFeedback.selectionClick(); - } - - Future _turnOnMinimalMode() async { - final settings = context.read(); - await settings.setMinimalModeEnabled(true); - setState(() { - _blurExplore = true; - _disableReelsEntirely = true; - _disableExploreEntirely = true; - _blockHomeFeedScroll = true; - }); - HapticFeedback.mediumImpact(); - } - - Future _turnOffMinimalMode() async { - final settings = context.read(); - await settings.setMinimalModeEnabled(false); - // Refresh local state after turning off - setState(() { - _blurExplore = settings.blurExplore; - _disableReelsEntirely = settings.disableReelsEntirely; - _disableExploreEntirely = settings.disableExploreEntirely; - _blockHomeFeedScroll = settings.blockHomeFeedScroll; - }); - HapticFeedback.mediumImpact(); - } - - @override - Widget build(BuildContext context) { - final settings = context.watch(); - final isDark = settings.isDarkMode; - final isMinimalModeEnabled = settings.minimalModeEnabled; - - return Scaffold( - appBar: _subAppBar(context, 'Minimal Mode'), - body: ListView( - children: [ - Container( - margin: const EdgeInsets.all(16), - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: isMinimalModeEnabled - ? [ - Colors.redAccent.withValues(alpha: 0.2), - Colors.red.withValues(alpha: 0.1), - ] - : [ - Colors.grey.withValues(alpha: 0.1), - Colors.grey.withValues(alpha: 0.05), - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: isMinimalModeEnabled - ? Colors.redAccent.withValues(alpha: 0.3) - : Colors.grey.withValues(alpha: 0.2), - ), - ), - child: Column( - children: [ - Icon( - isMinimalModeEnabled - ? Icons.shield_rounded - : Icons.shield_outlined, - color: isMinimalModeEnabled ? Colors.redAccent : Colors.grey, - size: 48, - ), - const SizedBox(height: 12), - Text( - isMinimalModeEnabled - ? 'Minimal Mode Active' - : 'Minimal Mode Disabled', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: isMinimalModeEnabled - ? Colors.redAccent - : Colors.grey, - ), - ), - const SizedBox(height: 8), - Text( - isMinimalModeEnabled - ? 'Distractions are blocked. Customize which features stay enabled below.' - : 'Turn on to block all distractions at once, or customize individual settings below.', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 13, - color: isDark ? Colors.white54 : Colors.black54, - ), - ), - const SizedBox(height: 16), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: isMinimalModeEnabled - ? _turnOffMinimalMode - : _turnOnMinimalMode, - style: ElevatedButton.styleFrom( - backgroundColor: isMinimalModeEnabled - ? Colors.grey - : Colors.redAccent, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: Text( - isMinimalModeEnabled - ? 'Turn Off Minimal Mode' - : 'Turn On Minimal Mode', - ), - ), - ), - ], - ), - ), - - const _SectionHeader(title: 'CUSTOMIZE SETTINGS'), - - Container( - margin: const EdgeInsets.fromLTRB(16, 0, 16, 8), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: Colors.blue.withValues(alpha: 0.07), - borderRadius: BorderRadius.circular(10), - border: Border.all(color: Colors.blue.withValues(alpha: 0.12)), - ), - child: const Row( - children: [ - Icon( - Icons.touch_app_rounded, - size: 14, - color: Colors.blueAccent, - ), - SizedBox(width: 8), - Expanded( - child: Text( - 'Toggle settings below to customize what gets enabled when Minimal Mode is turned ON.', - style: TextStyle(fontSize: 11, color: Colors.blueAccent), - ), - ), - ], - ), - ), - - _SwitchTile( - title: 'Blur Feed & Explore', - subtitle: 'Blurs post thumbnails until tapped', - value: _blurExplore, - onChanged: (v) => _updateSetting('blurExplore', v), - ), - _SwitchTile( - title: 'Block Home Feed Scroll', - subtitle: 'Freeze vertical scrolling on the home feed only', - value: _blockHomeFeedScroll, - onChanged: (v) => _updateSetting('blockHomeFeedScroll', v), - ), - _SwitchTile( - title: 'Disable Reels Entirely', - subtitle: 'Block all Reels with no session option', - value: _disableReelsEntirely, - onChanged: (v) => _updateSetting('disableReelsEntirely', v), - ), - _SwitchTile( - title: 'Disable Explore Entirely', - subtitle: 'Block Explore completely (not just blur)', - value: _disableExploreEntirely, - onChanged: (v) => _updateSetting('disableExploreEntirely', v), - ), - - const SizedBox(height: 40), - ], - ), - ); - } -} - -// ─── Appearance ─────────────────────────────────────────────────────────────── - -class AppearancePage extends StatefulWidget { - const AppearancePage({super.key}); - - @override - State createState() => _AppearancePageState(); -} - -class _AppearancePageState extends State { - Future _addSchedule( - BuildContext context, - SettingsService settings, - ) async { - TimeOfDay? startTime = await showTimePicker( - context: context, - initialTime: const TimeOfDay(hour: 21, minute: 0), - helpText: 'Select start time', - ); - - if (startTime == null || !context.mounted) return; - - TimeOfDay? endTime = await showTimePicker( - context: context, - initialTime: const TimeOfDay(hour: 6, minute: 0), - helpText: 'Select end time', - ); - - if (endTime == null || !context.mounted) return; - - final newSchedule = { - 'enabled': true, - 'startTime': - '${startTime.hour.toString().padLeft(2, '0')}:${startTime.minute.toString().padLeft(2, '0')}', - 'endTime': - '${endTime.hour.toString().padLeft(2, '0')}:${endTime.minute.toString().padLeft(2, '0')}', - }; - - await settings.addGrayscaleSchedule(newSchedule); - } - - Future _editSchedule( - BuildContext context, - SettingsService settings, - int index, - ) async { - final schedules = settings.grayscaleSchedules; - if (index >= schedules.length) return; - - final current = schedules[index]; - final startParts = (current['startTime'] as String).split(':'); - final endParts = (current['endTime'] as String).split(':'); - - // Capture context before async gap - final capturedContext = context; - - TimeOfDay? startTime = await showTimePicker( - context: capturedContext, - initialTime: TimeOfDay( - hour: int.parse(startParts[0]), - minute: int.parse(startParts[1]), - ), - helpText: 'Select start time', - ); - - if (startTime == null || !capturedContext.mounted) return; - - TimeOfDay? endTime = await showTimePicker( - context: capturedContext, - initialTime: TimeOfDay( - hour: int.parse(endParts[0]), - minute: int.parse(endParts[1]), - ), - helpText: 'Select end time', - ); - - if (endTime == null || !capturedContext.mounted) return; - - final updatedSchedule = { - ...current, - 'startTime': - '${startTime.hour.toString().padLeft(2, '0')}:${startTime.minute.toString().padLeft(2, '0')}', - 'endTime': - '${endTime.hour.toString().padLeft(2, '0')}:${endTime.minute.toString().padLeft(2, '0')}', - }; - - await settings.updateGrayscaleSchedule(index, updatedSchedule); - } - - Future _toggleSchedule(SettingsService settings, int index) async { - final schedules = List>.from( - settings.grayscaleSchedules, - ); - if (index >= schedules.length) return; - - schedules[index] = { - ...schedules[index], - 'enabled': !(schedules[index]['enabled'] as bool), - }; - - await settings.setGrayscaleSchedules(schedules); - } - - Future _deleteSchedule(SettingsService settings, int index) async { - final confirmed = await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Delete Schedule'), - content: const Text('Are you sure you want to delete this schedule?'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: const Text('Delete'), - ), - ], - ), - ); - - if (confirmed == true) { - await settings.removeGrayscaleSchedule(index); - } - } - - String _formatTimeRange(Map schedule) { - return '${schedule['startTime']} - ${schedule['endTime']}'; - } - - @override - Widget build(BuildContext context) { - final settings = context.watch(); - final isDark = settings.isDarkMode; - return Scaffold( - appBar: _subAppBar(context, 'Appearance'), - body: ListView( - children: [ - const _SectionHeader(title: 'DISPLAY'), - _SwitchTile( - title: 'Grayscale Mode', - subtitle: - 'Makes Instagram black & white — reduces dopamine response', - value: settings.grayscaleEnabled, - onChanged: (v) => settings.setGrayscaleEnabled(v), - ), - const _SectionHeader(title: 'GRAYSCALE SCHEDULES'), - Container( - margin: const EdgeInsets.fromLTRB(16, 0, 16, 12), - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), - decoration: BoxDecoration( - color: Colors.blue.withValues(alpha: 0.07), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.blue.withValues(alpha: 0.15)), - ), - child: const Text( - 'Auto-enable grayscale during specific hours. Similar to Scheduled Blocking in Guardrails. You can add multiple schedules.', - style: TextStyle(fontSize: 12, height: 1.5), - ), - ), - - // Status indicator - if (settings.grayscaleSchedules.isNotEmpty) - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), - child: Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: settings.isGrayscaleActiveNow - ? Colors.green.withValues(alpha: 0.1) - : Colors.orange.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: settings.isGrayscaleActiveNow - ? Colors.green.withValues(alpha: 0.3) - : Colors.orange.withValues(alpha: 0.3), - ), - ), - child: Row( - children: [ - Icon( - settings.isGrayscaleActiveNow - ? Icons.check_circle - : Icons.schedule, - color: settings.isGrayscaleActiveNow - ? Colors.greenAccent - : Colors.orangeAccent, - size: 20, - ), - const SizedBox(width: 12), - Expanded( - child: Text( - settings.isGrayscaleActiveNow - ? 'Grayscale is active now' - : 'Grayscale is currently inactive', - style: TextStyle( - fontSize: 13, - color: settings.isGrayscaleActiveNow - ? Colors.greenAccent - : Colors.orangeAccent, - ), - ), - ), - ], - ), - ), - ), - - // Schedule list - ...List.generate(settings.grayscaleSchedules.length, (index) { - final schedule = settings.grayscaleSchedules[index]; - final isEnabled = schedule['enabled'] as bool; - return ListTile( - leading: Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: (isEnabled ? Colors.purpleAccent : Colors.grey) - .withValues(alpha: 0.12), - borderRadius: BorderRadius.circular(10), - ), - child: Icon( - isEnabled - ? Icons.play_circle_outline - : Icons.pause_circle_outline, - color: isEnabled ? Colors.purpleAccent : Colors.grey, - size: 20, - ), - ), - title: Text( - _formatTimeRange(schedule), - style: TextStyle( - color: isEnabled ? Colors.purpleAccent : Colors.grey, - fontWeight: FontWeight.w500, - ), - ), - subtitle: Text( - isEnabled ? 'Active' : 'Disabled', - style: TextStyle( - fontSize: 12, - color: isDark ? Colors.white54 : Colors.black45, - ), - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Switch( - value: isEnabled, - onChanged: (v) => _toggleSchedule(settings, index), - ), - PopupMenuButton( - onSelected: (value) { - if (value == 'edit') { - _editSchedule(context, settings, index); - } else if (value == 'delete') { - _deleteSchedule(settings, index); - } - }, - itemBuilder: (context) => [ - const PopupMenuItem( - value: 'edit', - child: Row( - children: [ - Icon(Icons.edit, size: 18), - SizedBox(width: 8), - Text('Edit'), - ], - ), - ), - const PopupMenuItem( - value: 'delete', - child: Row( - children: [ - Icon(Icons.delete, size: 18, color: Colors.red), - SizedBox(width: 8), - Text('Delete', style: TextStyle(color: Colors.red)), - ], - ), - ), - ], - ), - ], - ), - onTap: () => _editSchedule(context, settings, index), - ); - }), - - // Add schedule button - ListTile( - leading: Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: Colors.green.withValues(alpha: 0.12), - borderRadius: BorderRadius.circular(10), - ), - child: const Icon( - Icons.add_circle_outline, - color: Colors.green, - size: 20, - ), - ), - title: const Text( - 'Add Schedule', - style: TextStyle(color: Colors.green), - ), - subtitle: Text( - 'Add a new grayscale schedule', - style: TextStyle( - fontSize: 12, - color: isDark ? Colors.white54 : Colors.black45, - ), - ), - onTap: () => _addSchedule(context, settings), - ), - - const SizedBox(height: 40), - ], - ), - ); - } -} - -// ─── Privacy & Notifications ────────────────────────────────────────────────── - -class PrivacyNotificationsPage extends StatelessWidget { - const PrivacyNotificationsPage({super.key}); - - @override - Widget build(BuildContext context) { - final settings = context.watch(); - return Scaffold( - appBar: _subAppBar(context, 'Privacy & Notifications'), - body: ListView( - children: [ - const _SectionHeader(title: 'NOTIFICATIONS'), - Container( - margin: const EdgeInsets.fromLTRB(16, 0, 16, 12), - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), - decoration: BoxDecoration( - color: Colors.blue.withValues(alpha: 0.07), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.blue.withValues(alpha: 0.15)), - ), - child: const Text( - 'FocusGram can show notifications when your Focus session ends. ' - 'Instagram\'s own notification system handles background alerts.', - style: TextStyle(fontSize: 12, height: 1.5), - ), - ), - _SwitchTile( - title: 'Session End Notification', - subtitle: 'Notify when Focus session time is up', - value: settings.notifySessionEnd, - onChanged: (v) => settings.setNotifySessionEnd(v), - ), - _SwitchTile( - title: 'Persistent Notification', - subtitle: 'Show ongoing notification while using Instagram', - value: settings.notifyPersistent, - onChanged: (v) => settings.setNotifyPersistent(v), - ), - - const _SectionHeader(title: 'INSTA NOTIFICATIONS'), - _SwitchTile( - title: 'DM Notifications', - subtitle: 'Show notification when someone messages you', - value: settings.notifyDMs, - onChanged: (v) => settings.setNotifyDMs(v), - ), - _SwitchTile( - title: 'Activity Notifications', - subtitle: 'Likes, comments, follows and other activity', - value: settings.notifyActivity, - onChanged: (v) => settings.setNotifyActivity(v), - ), - - const SizedBox(height: 40), - ], - ), - ); - } -} - -// ─── Shared widgets ─────────────────────────────────────────────────────────── - -PreferredSizeWidget _subAppBar(BuildContext context, String title) => AppBar( - title: Text( - title, - style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600), - ), - centerTitle: true, - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_new, size: 18), - onPressed: () => Navigator.pop(context), - ), -); - -class _SubmoduleTile extends StatelessWidget { - final IconData icon; - final Color iconColor; - final String title; - final String subtitle; - final String? disabledSubtitle; - final bool enabled; - final VoidCallback onTap; - - const _SubmoduleTile({ - required this.icon, - required this.iconColor, - required this.title, - required this.subtitle, - this.disabledSubtitle, - this.enabled = true, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - return ListTile( - leading: Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: (enabled ? iconColor : Colors.grey).withValues(alpha: 0.12), - borderRadius: BorderRadius.circular(10), - ), - child: Icon(icon, color: enabled ? iconColor : Colors.grey, size: 20), - ), - title: Text( - title, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w500, - color: enabled ? null : Colors.grey, - ), - ), - subtitle: Text( - enabled ? subtitle : (disabledSubtitle ?? subtitle), - style: const TextStyle(fontSize: 12), - ), - trailing: const Icon( - Icons.arrow_forward_ios, - size: 14, - color: Colors.grey, - ), - onTap: enabled ? onTap : null, - ); - } -} - -class _SwitchTile extends StatelessWidget { - final String title; - final String? subtitle; - final bool value; - final ValueChanged onChanged; - - const _SwitchTile({ - required this.title, - this.subtitle, - required this.value, - required this.onChanged, - }); - - @override - Widget build(BuildContext context) { - return SwitchListTile( - title: Text(title, style: const TextStyle(fontSize: 15)), - subtitle: subtitle != null - ? Text(subtitle ?? '', style: const TextStyle(fontSize: 12)) - : null, - value: value, - onChanged: onChanged, - ); - } -} - -class _ChoiceTile extends StatelessWidget { - final String title; - final T value; - final String label; - final List options; - final String Function(T value) optionLabel; - final ValueChanged onSelected; - - const _ChoiceTile({ - required this.title, - required this.value, - required this.label, - required this.options, - required this.optionLabel, - required this.onSelected, - }); - - @override - Widget build(BuildContext context) { - return ListTile( - title: Text(title, style: const TextStyle(fontSize: 15)), - subtitle: Text(label, style: const TextStyle(fontSize: 12)), - trailing: PopupMenuButton( - initialValue: value, - onSelected: onSelected, - itemBuilder: (context) => options - .map( - (option) => PopupMenuItem( - value: option, - child: Text(optionLabel(option)), - ), - ) - .toList(), - child: const Icon(Icons.expand_more_rounded, size: 22), - ), - onTap: () async { - final selected = await showModalBottomSheet( - context: context, - builder: (context) => SafeArea( - child: ListView( - shrinkWrap: true, - children: options - .map( - (option) => ListTile( - title: Text(optionLabel(option)), - trailing: option == value - ? const Icon(Icons.check_rounded) - : null, - onTap: () => Navigator.pop(context, option), - ), - ) - .toList(), - ), - ), - ); - if (selected != null) onSelected(selected); - }, - ); - } -} - -class _NumberEditTile extends StatelessWidget { - final String title; - final String label; - final int initialValue; - final int min; - final int max; - final String suffix; - final ValueChanged onSubmitted; - - const _NumberEditTile({ - required this.title, - required this.label, - required this.initialValue, - required this.min, - required this.max, - required this.suffix, - required this.onSubmitted, - }); - - @override - Widget build(BuildContext context) { - return ListTile( - title: Text(title, style: const TextStyle(fontSize: 15)), - subtitle: Text(label, style: const TextStyle(fontSize: 12)), - trailing: const Icon(Icons.edit_outlined, size: 20), - onTap: () async { - final controller = TextEditingController(text: '$initialValue'); - final result = await showDialog( - context: context, - builder: (dialogContext) => AlertDialog( - title: Text(title), - content: TextField( - controller: controller, - autofocus: true, - keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - decoration: InputDecoration( - suffixText: suffix, - helperText: '$min-$max $suffix', - ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(dialogContext), - child: const Text('Cancel'), - ), - ElevatedButton( - onPressed: () { - final parsed = int.tryParse(controller.text.trim()); - if (parsed == null) return; - Navigator.pop(dialogContext, parsed.clamp(min, max).toInt()); - }, - child: const Text('Save'), - ), - ], - ), - ); - controller.dispose(); - if (result != null) onSubmitted(result); - }, - ); - } -} - -class _VersionTile extends StatelessWidget { - const _VersionTile(); - - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: PackageInfo.fromPlatform(), - builder: (context, snapshot) => ListTile( - title: const Text('Version'), - trailing: Text( - snapshot.data?.version ?? 'â€Ķ', - style: const TextStyle(color: Colors.grey), - ), - ), - ); - } -} - -class _SectionHeader extends StatelessWidget { - final String title; - const _SectionHeader({required this.title}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.fromLTRB(16, 24, 16, 8), - child: Text( - title, - style: const TextStyle( - color: Colors.grey, - fontSize: 11, - fontWeight: FontWeight.bold, - letterSpacing: 1.2, - ), - ), - ); - } -} diff --git a/lib/screens/snapshot_manager_screen.dart b/lib/screens/snapshot_manager_screen.dart deleted file mode 100644 index 4169fea..0000000 --- a/lib/screens/snapshot_manager_screen.dart +++ /dev/null @@ -1,329 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../services/snapshot_service.dart'; -import '../services/level_service.dart'; -import 'offline_feed_viewer.dart'; - -/// Manages saved pages for offline viewing via WebView cache. -/// Gated behind Level 5. -class SnapshotManagerScreen extends StatelessWidget { - const SnapshotManagerScreen({super.key}); - - @override - Widget build(BuildContext context) { - final levelService = context.watch(); - final isUnlocked = levelService.level >= 5; // offline pages at L5 - final isDark = Theme.of(context).brightness == Brightness.dark; - - return Scaffold( - appBar: AppBar( - title: const Text( - 'Offline Pages', - style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600), - ), - centerTitle: true, - leading: IconButton( - icon: const Icon(Icons.arrow_back_ios_new, size: 18), - onPressed: () => Navigator.pop(context), - ), - ), - body: isUnlocked - ? const _SavedPageList() - : Center( - child: Padding( - padding: const EdgeInsets.all(32), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.lock_outline, - size: 64, - color: Colors.grey.withValues(alpha: 0.4), - ), - const SizedBox(height: 16), - const Text( - 'Unlocks at Level 5', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - Text( - 'Earn XP to unlock offline browsing.\n' - 'Watch ads and reduce reel time to level up.', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - color: isDark ? Colors.white54 : Colors.black54, - height: 1.5, - ), - ), - ], - ), - ), - ), - ); - } -} - -class _SavedPageList extends StatelessWidget { - const _SavedPageList(); - - @override - Widget build(BuildContext context) { - final snapshotService = context.watch(); - final isDark = Theme.of(context).brightness == Brightness.dark; - - return Column( - children: [ - // Info card - Container( - margin: const EdgeInsets.all(16), - padding: const EdgeInsets.all(14), - decoration: BoxDecoration( - color: Colors.blue.withValues(alpha: 0.07), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.blue.withValues(alpha: 0.12)), - ), - child: Row( - children: [ - const Icon( - Icons.info_outline, - size: 16, - color: Colors.blueAccent, - ), - const SizedBox(width: 10), - Expanded( - child: Text( - 'The WebView already caches pages you visit. ' - 'Save bookmarks here to easily reopen them when offline.\n' - 'No API needed — the cache handles everything.', - style: TextStyle( - fontSize: 12, - color: isDark ? Colors.white60 : Colors.black54, - height: 1.4, - ), - ), - ), - ], - ), - ), - - // Header - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), - child: Row( - children: [ - Text( - '${snapshotService.totalSaved} saved page${snapshotService.totalSaved == 1 ? '' : 's'}', - style: TextStyle( - fontSize: 12, - color: isDark ? Colors.white54 : Colors.black54, - ), - ), - const Spacer(), - if (snapshotService.totalSaved > 0) - GestureDetector( - onTap: () => _confirmClearAll(context, snapshotService), - child: Text( - 'Clear all', - style: TextStyle( - fontSize: 12, - color: Colors.redAccent.withValues(alpha: 0.7), - ), - ), - ), - ], - ), - ), - - // Page list - Expanded( - child: snapshotService.savedPages.isEmpty - ? Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.bookmark_border_rounded, - size: 48, - color: Colors.grey.withValues(alpha: 0.3), - ), - const SizedBox(height: 12), - Text( - 'No saved pages yet', - style: TextStyle( - color: isDark ? Colors.white38 : Colors.black38, - ), - ), - const SizedBox(height: 4), - Text( - 'Visit Instagram pages online, then save them here\nto browse offline later.', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - color: isDark ? Colors.white24 : Colors.black26, - height: 1.4, - ), - ), - ], - ), - ) - : ListView.builder( - itemCount: snapshotService.savedPages.length, - itemBuilder: (context, index) { - final page = snapshotService.savedPages[index]; - return ListTile( - leading: Container( - width: 44, - height: 44, - decoration: BoxDecoration( - color: Colors.blue.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(10), - ), - child: const Icon( - Icons.web_rounded, - color: Colors.blueAccent, - size: 22, - ), - ), - title: Text( - page.title, - style: const TextStyle(fontSize: 14), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - subtitle: Text( - _formatDate(page.savedAt), - style: const TextStyle(fontSize: 12), - ), - trailing: PopupMenuButton( - onSelected: (value) { - if (value == 'delete') { - _confirmDelete(context, snapshotService, page.id); - } else if (value == 'open') { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => OfflineFeedViewer( - url: page.url, - pageId: page.id, - ), - ), - ); - } - }, - itemBuilder: (context) => [ - const PopupMenuItem( - value: 'open', - child: Row( - children: [ - Icon(Icons.open_in_browser, size: 18), - SizedBox(width: 8), - Text('Open Offline'), - ], - ), - ), - const PopupMenuItem( - value: 'delete', - child: Row( - children: [ - Icon( - Icons.delete_outline, - color: Colors.redAccent, - size: 18, - ), - SizedBox(width: 8), - Text( - 'Remove', - style: TextStyle(color: Colors.redAccent), - ), - ], - ), - ), - ], - ), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => OfflineFeedViewer(url: page.url), - ), - ); - }, - ); - }, - ), - ), - ], - ); - } - - void _confirmDelete( - BuildContext context, - SnapshotService service, - String id, - ) { - showDialog( - context: context, - builder: (ctx) => AlertDialog( - title: const Text('Remove page?'), - content: const Text( - 'Removes the bookmark. Cache is preserved automatically.', - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () { - service.deletePage(id); - Navigator.pop(ctx); - }, - child: const Text( - 'Remove', - style: TextStyle(color: Colors.redAccent), - ), - ), - ], - ), - ); - } - - void _confirmClearAll(BuildContext context, SnapshotService service) { - showDialog( - context: context, - builder: (ctx) => AlertDialog( - title: const Text('Clear all saved pages?'), - content: const Text('This removes all bookmarks.'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () { - service.deleteAll(); - Navigator.pop(ctx); - }, - child: const Text( - 'Clear', - style: TextStyle(color: Colors.redAccent), - ), - ), - ], - ), - ); - } - - String _formatDate(DateTime dt) { - final now = DateTime.now(); - final diff = now.difference(dt); - if (diff.inMinutes < 60) return '${diff.inMinutes}m ago'; - if (diff.inHours < 24) return '${diff.inHours}h ago'; - if (diff.inDays < 7) return '${diff.inDays}d ago'; - return '${dt.month}/${dt.day} ${dt.hour}:${dt.minute.toString().padLeft(2, '0')}'; - } -} diff --git a/lib/screens/timer_fallback_screen.dart b/lib/screens/timer_fallback_screen.dart deleted file mode 100644 index 550148c..0000000 --- a/lib/screens/timer_fallback_screen.dart +++ /dev/null @@ -1,198 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -/// A 15-second timer that acts as the last-resort fallback -/// when both AdMob and Adsterra fail to serve an ad. -/// -/// Shows a digital wellness quote while the user waits. -/// After the timer, they earn the same reward. -class TimerFallbackScreen extends StatefulWidget { - final String sessionType; - final int requiredSeconds; - - const TimerFallbackScreen({ - super.key, - required this.sessionType, - this.requiredSeconds = 15, - }); - - @override - State createState() => _TimerFallbackScreenState(); -} - -class _TimerFallbackScreenState extends State { - int _remaining = 0; - Timer? _timer; - int _quoteIndex = 0; - - static const _quotes = [ - '"The secret of getting ahead is getting started." — Mark Twain', - '"Focus on being productive instead of busy." — Tim Ferriss', - '"Almost everything will work if you unplug it for a few minutes." — Ann Lamott', - '"The key is not to prioritize what\'s on your schedule, but to schedule your priorities." — Stephen Covey', - '"Your mind is for having ideas, not holding them." — David Allen', - '"Simplicity is the ultimate sophistication." — Leonardo da Vinci', - '"The ability to simplify means to eliminate the unnecessary." — Hans Hofmann', - '"In the midst of chaos, there is also opportunity." — Sun Tzu', - ]; - - @override - void initState() { - super.initState(); - _remaining = widget.requiredSeconds; - _quoteIndex = DateTime.now().millisecondsSinceEpoch % _quotes.length; - _startTimer(); - } - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } - - void _startTimer() { - _timer = Timer.periodic(const Duration(seconds: 1), (_) { - if (!mounted) return; - setState(() { - if (_remaining > 0) { - _remaining--; - } else { - _timer?.cancel(); - HapticFeedback.heavyImpact(); - } - }); - }); - } - - @override - Widget build(BuildContext context) { - final done = _remaining <= 0; - - return Scaffold( - backgroundColor: Colors.black, - body: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 32), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Spacer(flex: 2), - - // Icon - Container( - width: 72, - height: 72, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.green.withValues(alpha: 0.1), - border: Border.all( - color: Colors.green.withValues(alpha: 0.3), - width: 2, - ), - ), - child: Icon( - done ? Icons.check_circle : Icons.timer_outlined, - color: done ? Colors.greenAccent : Colors.green, - size: 36, - ), - ), - - const SizedBox(height: 28), - - // Timer - Text( - done ? 'Done!' : '$_remaining', - style: TextStyle( - color: done ? Colors.greenAccent : Colors.white, - fontSize: 56, - fontWeight: FontWeight.bold, - fontFeatures: const [FontFeature.tabularFigures()], - ), - ), - - const SizedBox(height: 8), - Text( - done - ? 'You earned ${widget.sessionType == 'reels' ? 'reel' : 'Instagram'} time' - : 'Please wait while we prepare your reward', - style: TextStyle( - color: Colors.white.withValues(alpha: 0.5), - fontSize: 14, - ), - ), - - const SizedBox(height: 40), - - // Quote - Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.05), - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: Colors.white.withValues(alpha: 0.08), - ), - ), - child: Text( - _quotes[_quoteIndex], - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white.withValues(alpha: 0.6), - fontSize: 15, - height: 1.5, - fontStyle: FontStyle.italic, - ), - ), - ), - - const Spacer(flex: 1), - - // Continue button - SizedBox( - width: double.infinity, - height: 54, - child: ElevatedButton.icon( - onPressed: done ? () => Navigator.pop(context, true) : null, - style: ElevatedButton.styleFrom( - backgroundColor: done ? Colors.greenAccent : Colors.grey, - foregroundColor: done ? Colors.black : Colors.white38, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - elevation: 0, - ), - icon: Icon( - done ? Icons.check_circle : Icons.hourglass_empty, - size: 22, - ), - label: Text( - done - ? 'Continue & Earn Reward' - : 'Wait $_remaining seconds', - style: const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - ), - ), - ), - ), - - const SizedBox(height: 16), - Text( - 'No ad available — timer reward instead', - style: TextStyle( - color: Colors.white.withValues(alpha: 0.2), - fontSize: 11, - ), - ), - - const Spacer(flex: 1), - ], - ), - ), - ), - ); - } -} diff --git a/lib/scripts/autoplay_blocker.dart b/lib/scripts/autoplay_blocker.dart deleted file mode 100644 index 0a6c133..0000000 --- a/lib/scripts/autoplay_blocker.dart +++ /dev/null @@ -1,258 +0,0 @@ -/// JavaScript to block autoplaying videos on Instagram feed/explore while: -/// - Allowing videos to play normally when "Block Autoplay Videos" is OFF -/// - Allowing user-initiated playback on click when blocking is ON -/// - NEVER blocking reels (they should always play normally per user request) -/// -/// This script: -/// - Overrides HTMLVideoElement.prototype.play before Instagram initialises. -/// - PAUSES any playing videos immediately when autoplay is blocked (only for feed/explore). -/// - Returns Promise.resolve() for blocked autoplay calls (never throws). -/// - Uses a per-element flag set by user clicks to permanently allow that video to play. -/// - Strips the autoplay attribute from dynamically added
    navigation, so the CSS -/// display:none in kDisableReelsEntirelyCssScript doesn't remove the preview -/// card from the thread. This script finds them structurally and covers them -/// with a blocking overlay that also swallows all touch/click events. -/// -/// Inject when disableReelsEntirely OR minimalMode is on. -const String kDmReelBlockerJS = r''' -(function() { - if (window.__fgDmReelBlockerRunning) return; - window.__fgDmReelBlockerRunning = true; - - const BLOCKED_ATTR = 'data-fg-blocked'; - - function buildOverlay() { - const div = document.createElement('div'); - div.setAttribute(BLOCKED_ATTR, '1'); - div.style.cssText = [ - 'position:absolute', - 'inset:0', - 'z-index:99999', - 'display:flex', - 'flex-direction:column', - 'align-items:center', - 'justify-content:center', - 'background:rgba(0,0,0,0.85)', - 'border-radius:inherit', - 'pointer-events:all', - 'gap:8px', - 'cursor:default', - ].join(';'); - - const icon = document.createElement('span'); - icon.textContent = 'ðŸšŦ'; - icon.style.cssText = 'font-size:28px;line-height:1'; - - const label = document.createElement('span'); - label.textContent = 'Reels are disabled'; - label.style.cssText = [ - 'color:#fff', - 'font-size:13px', - 'font-weight:600', - 'font-family:-apple-system,sans-serif', - 'text-align:center', - 'padding:0 12px', - ].join(';'); - - const sub = document.createElement('span'); - sub.textContent = 'Disable "Block Reels" in FocusGram settings'; - sub.style.cssText = [ - 'color:rgba(255,255,255,0.5)', - 'font-size:11px', - 'font-family:-apple-system,sans-serif', - 'text-align:center', - 'padding:0 16px', - ].join(';'); - - div.appendChild(icon); - div.appendChild(label); - div.appendChild(sub); - - // Swallow all interaction so the reel beneath cannot be triggered - ['click','touchstart','touchend','touchmove','pointerdown'].forEach(function(evt) { - div.addEventListener(evt, function(e) { - e.preventDefault(); - e.stopImmediatePropagation(); - }, { capture: true }); - }); - - return div; - } - - function overlayContainer(container) { - if (!container) return; - if (container.querySelector('[' + BLOCKED_ATTR + ']')) return; // already overlaid - container.style.position = 'relative'; - container.style.overflow = 'hidden'; - container.appendChild(buildOverlay()); - } - - function blockDmReels() { - try { - // Strategy 1: links inside the DM thread - document.querySelectorAll('a[href*="/reel/"]').forEach(function(link) { - try { - link.style.setProperty('pointer-events', 'none', 'important'); - overlayContainer(link.closest('div') || link.parentElement); - } catch(_) {} - }); - - // Strategy 2: wrapper) - // Only targets videos inside the Direct thread or on /direct/ path - document.querySelectorAll('video').forEach(function(video) { - try { - const inDm = !!video.closest('[aria-label="Direct"], [aria-label*="Direct"]'); - const isDmPath = window.location.pathname.includes('/direct/'); - if (!inDm && !isDmPath) return; - - const container = video.closest('div[class]') || video.parentElement; - if (!container) return; - video.style.setProperty('pointer-events', 'none', 'important'); - overlayContainer(container); - } catch(_) {} - }); - } catch(_) {} - } - - blockDmReels(); - - let _t = null; - new MutationObserver(function() { - clearTimeout(_t); - _t = setTimeout(blockDmReels, 200); - }).observe(document.documentElement, { childList: true, subtree: true }); -})(); -'''; diff --git a/lib/scripts/core_injection.dart b/lib/scripts/core_injection.dart deleted file mode 100644 index d7e8ce2..0000000 --- a/lib/scripts/core_injection.dart +++ /dev/null @@ -1,587 +0,0 @@ -// Core JS and CSS payloads injected into the Instagram WebView. -// -// WARNING: Do not add any network interception logic ("ghost mode") here. -// All scripts in this file must be limited to UI behaviour, navigation helpers, -// and local-only features that do not modify data sent to Meta's servers. - -// ── CSS payloads ────────────────────────────────────────────────────────────── - -/// Base UI polish — hides scrollbars and Instagram's nav tab-bar. -/// Important: we must NOT hide [role="tablist"] inside dialogs/modals, -/// because Instagram's comment input sheet also uses that role and the -/// CSS would paint a grey overlay on top of the typing area. -const String kGlobalUIFixesCSS = ''' - ::-webkit-scrollbar { display: none !important; width: 0 !important; height: 0 !important; } - * { - -ms-overflow-style: none !important; - scrollbar-width: none !important; - -webkit-tap-highlight-color: transparent !important; - } - /* Only hide the PRIMARY nav tablist (bottom bar), not tablist inside dialogs */ - body > div > div > [role="tablist"]:not([role="dialog"] [role="tablist"]), - [aria-label="Direct"] header { - display: none !important; - visibility: hidden !important; - height: 0 !important; - pointer-events: none !important; - } -'''; - -/// Blurs images/videos in the home feed AND on Explore. -/// Activated via the body[path] attribute written by the path tracker script. -const String kBlurHomeFeedAndExploreCSS = ''' - body[path="/"] article img, - body[path="/"] article video, - body[path^="/explore"] img, - body[path^="/explore"] video, - body[path="/explore/"] img, - body[path="/explore/"] video { - filter: blur(20px) !important; - transition: filter 0.15s ease !important; - } - /* Per-post unblur override (set by kTapToUnblurJS) */ - /* Must match the blur selector's specificity (body[path="/"] article img = 0,0,1,3) */ - body[path="/"] [data-fg-unblurred="1"] img, - body[path="/"] [data-fg-unblurred="1"] video, - body[path^="/explore"] [data-fg-unblurred="1"] img, - body[path^="/explore"] [data-fg-unblurred="1"] video { - filter: none !important; - -webkit-filter: none !important; - } - body[path="/"] article img:hover, - body[path="/"] article video:hover, - body[path^="/explore"] img:hover, - body[path^="/explore"] video:hover { - filter: blur(20px) !important; - } -'''; - -/// Prevents text selection to keep the app feeling native. -const String kDisableSelectionCSS = ''' - * { -webkit-user-select: none !important; user-select: none !important; } -'''; - -/// Hides reel posts in the home feed when no Reel Session is active. -/// The Reels nav tab is NOT hidden — Flutter intercepts that navigation. -const String kHideReelsFeedContentCSS = ''' - a[href*="/reel/"], - div[data-media-type="2"] { - display: none !important; - visibility: hidden !important; - } -'''; - -/// Blurs reel thumbnails in the feed AND reel preview cards sent in DMs. -/// -/// Feed reels are wrapped in a[href*="/reel/"] — straightforward. -/// DM reel previews are inline media cards NOT wrapped in a[href*="/reel/"], -/// so they need separate selectors targeting img/video inside [aria-label="Direct"]. -/// Profile photos are excluded via :not([alt*="rofile"]) — covers both -/// "profile" and "Profile" without case-sensitivity workarounds. -const String kBlurReelsCSS = ''' - a[href*="/reel/"] img { - filter: blur(12px) !important; - } - - [aria-label="Direct"] img:not([alt*="rofile"]):not([alt=""]), - [aria-label="Direct"] video { - filter: blur(12px) !important; - } -'''; - -/// Allows users to unblur blurred media by tapping it. -/// -/// Behaviour: -/// - Only active when `window.__fgTapToUnblur === true`. -/// - Only applies on Home feed (`/`) and Explore (`/explore*`) where FocusGram blurs. -/// - First tap unblurs the post media and swallows the click (prevents opening). -/// - Subsequent taps behave normally (Instagram opens the post as usual). -const String kTapToUnblurJS = r''' - (function fgTapToUnblur() { - if (window.__fgTapToUnblurPatched) return; - window.__fgTapToUnblurPatched = true; - - function isBlurContext() { - try { - const p = (document.body && document.body.getAttribute('path')) || window.location.pathname || ''; - return p === '/' || p.indexOf('/explore') === 0; - } catch (_) { - return false; - } - } - - function findMediaFromTarget(t) { - try { - if (!t) return null; - if (t.closest) { - const direct = t.closest('img,video'); - if (direct) return direct; - } - // Walk up a few levels and look for a media element inside. - let n = t; - for (let i = 0; i < 6 && n; i++) { - if (n.querySelector) { - const m = n.querySelector('img,video'); - if (m) return m; - } - n = n.parentElement; - } - } catch (_) {} - return null; - } - - function getHost(media) { - try { - return media.closest('article') || media.closest('a') || media.parentElement; - } catch (_) { - return null; - } - } - - function markUnblurred(host) { - try { - host.setAttribute('data-fg-unblurred', '1'); - } catch (_) {} - } - - function isUnblurred(host) { - try { - return host && host.getAttribute && host.getAttribute('data-fg-unblurred') === '1'; - } catch (_) { - return false; - } - } - - function unblurAllMediaInHost(host) { - try { - host.querySelectorAll('img,video').forEach(function(el) { - el.style.setProperty('filter', 'none', 'important'); - el.style.setProperty('-webkit-filter', 'none', 'important'); - }); - } catch (_) {} - } - - function unblurMedia(media) { - try { - media.style.setProperty('filter', 'none', 'important'); - media.style.setProperty('-webkit-filter', 'none', 'important'); - } catch (_) {} - } - - document.addEventListener('click', function(e) { - try { - if (window.__fgTapToUnblur !== true) return; - if (!isBlurContext()) return; - const media = findMediaFromTarget(e.target); - if (!media) return; - const host = getHost(media); - if (!host) return; - - // ALWAYS re-unblur media — Instagram swaps DOM elements in carousels, - // so the inline style applied on first tap is lost on subsequent pages. - unblurMedia(media); - - if (isUnblurred(host)) return; // allow normal Instagram click-through - - // First tap: unblur and swallow click so it doesn't open the post. - markUnblurred(host); - if (e.cancelable) e.preventDefault(); - e.stopPropagation(); - } catch (_) {} - }, true); - })(); -'''; - -// ── JavaScript helpers ──────────────────────────────────────────────────────── - -/// Removes the "Open in App" nag banner. -const String kDismissAppBannerJS = ''' - (function fgDismissBanner() { - ['[id*="app-banner"]','[class*="app-banner"]', - 'div[role="dialog"][aria-label*="app"]','[id*="openInApp"]'] - .forEach(s => document.querySelectorAll(s).forEach(el => el.remove())); - })(); -'''; - -/// Intercepts clicks on /reels/ links when no session is active and redirects -/// to a recognisable URL so Flutter's NavigationDelegate can catch and block it. -/// -/// Without this, fast SPA clicks bypass the NavigationDelegate entirely. -const String kStrictReelsBlockJS = r''' - (function fgReelsBlock() { - if (window.__fgReelsBlockPatched) return; - window.__fgReelsBlockPatched = true; - document.addEventListener('click', e => { - if (window.__focusgramSessionActive) return; - const a = e.target && e.target.closest('a[href*="/reels/"]'); - if (!a) return; - e.preventDefault(); - e.stopPropagation(); - window.location.href = '/reels/?fg=blocked'; - }, true); - })(); -'''; - -/// SPA route tracker: writes `body[path]` and notifies Flutter of path changes -/// via the `UrlChange` handler so reels can be blocked on SPA navigation. -const String kTrackPathJS = ''' - (function fgTrackPath() { - if (window.__fgPathTrackerRunning) return; - window.__fgPathTrackerRunning = true; - let last = window.location.pathname; - function check() { - const p = window.location.pathname; - if (p !== last) { - last = p; - if (document.body) document.body.setAttribute('path', p); - if (window.flutter_inappwebview) { - window.flutter_inappwebview.callHandler('UrlChange', p); - } - } - } - if (document.body) document.body.setAttribute('path', last); - setInterval(check, 500); - })(); -'''; - -/// Detects Instagram's current theme (dark/light) and notifies Flutter. -const String kThemeDetectorJS = r''' - (function fgThemeSync() { - if (window.__fgThemeSyncRunning) return; - window.__fgThemeSyncRunning = true; - - function getTheme() { - try { - // 1. Check Instagram's specific classes - const h = document.documentElement; - if (h.classList.contains('style-dark')) return 'dark'; - if (h.classList.contains('style-light')) return 'light'; - - // 2. Check body background color - const bg = window.getComputedStyle(document.body).backgroundColor; - const rgb = bg.match(/\d+/g); - if (rgb && rgb.length >= 3) { - const luminance = (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) / 255; - return luminance < 0.5 ? 'dark' : 'light'; - } - } catch(_) {} - return 'dark'; // Fallback - } - - let last = ''; - function check() { - const current = getTheme(); - if (current !== last) { - last = current; - if (window.flutter_inappwebview) { - window.flutter_inappwebview.callHandler('FocusGramThemeChannel', current); - } - } - } - setInterval(check, 1500); - check(); - })(); -'''; - -/// Prevents swipe-to-next-reel in the isolated DM reel player and when Reels -/// are blocked by FocusGram's session controls. -const String kReelsMutationObserverJS = r''' - (function fgReelLock() { - if (window.__fgReelLockRunning) return; - window.__fgReelLockRunning = true; - - const MODAL_SEL = '[role="dialog"],[role="menu"],[role="listbox"],[class*="Modal"],[class*="Sheet"],[class*="Drawer"],._aano,[class*="caption"]'; - - function lockMode() { - // Lock DM reels to prevent swipe-to-next, and optionally lock the home - // feed as a separate Minimal Mode control. - const isDmReel = window.location.pathname.includes('/direct/') && - !!document.querySelector('[class*="ReelsVideoPlayer"]'); - if (isDmReel) return 'dm_reel'; - if (window.__fgBlockHomeFeedScroll === true && - (window.location.pathname === '/' || window.location.pathname === '')) { - return 'home_feed'; - } - return null; - } - - function isLocked() { - return lockMode() !== null; - } - - function allowInteractionTarget(t) { - if (!t || !t.closest) return false; - if (t.closest('input,textarea,[contenteditable="true"]')) return true; - if (t.closest(MODAL_SEL)) return true; - return false; - } - - let sy = 0; - document.addEventListener('touchstart', e => { - sy = e.touches && e.touches[0] ? e.touches[0].clientY : 0; - }, { capture: true, passive: true }); - - document.addEventListener('touchmove', e => { - if (!isLocked()) return; - const dy = e.touches && e.touches[0] ? e.touches[0].clientY - sy : 0; - if (Math.abs(dy) > 2) { - // Mark the first DM reel as loaded on first swipe attempt - if (window.location.pathname.includes('/direct/')) { - window.__fgDmReelAlreadyLoaded = true; - } - if (allowInteractionTarget(e.target)) return; - if (e.cancelable) e.preventDefault(); - e.stopPropagation(); - } - }, { capture: true, passive: false }); - - function block(e) { - if (!isLocked()) return; - if (allowInteractionTarget(e.target)) return; - if (e.cancelable) e.preventDefault(); - e.stopPropagation(); - } - document.addEventListener('wheel', block, { capture: true, passive: false }); - document.addEventListener('keydown', e => { - if (!['ArrowDown','ArrowUp',' ','PageUp','PageDown'].includes(e.key)) return; - if (e.target && e.target.closest && e.target.closest('input,textarea,[contenteditable="true"]')) return; - block(e); - }, { capture: true, passive: false }); - - const REEL_SEL = '[class*="ReelsVideoPlayer"], video'; - let __fgOrigHtmlOverflow = null; - let __fgOrigBodyOverflow = null; - - function applyOverflowLock() { - try { - const mode = lockMode(); - const hasReel = !!document.querySelector(REEL_SEL); - if ((mode === 'dm_reel' && hasReel) || mode === 'home_feed') { - if (__fgOrigHtmlOverflow === null) { - __fgOrigHtmlOverflow = document.documentElement.style.overflow || ''; - __fgOrigBodyOverflow = document.body ? (document.body.style.overflow || '') : ''; - } - document.documentElement.style.overflow = 'hidden'; - if (document.body) document.body.style.overflow = 'hidden'; - } else if (__fgOrigHtmlOverflow !== null) { - document.documentElement.style.overflow = __fgOrigHtmlOverflow; - if (document.body) document.body.style.overflow = __fgOrigBodyOverflow || ''; - __fgOrigHtmlOverflow = null; - __fgOrigBodyOverflow = null; - } - } catch (_) {} - } - - function sync() { - const reels = document.querySelectorAll(REEL_SEL); - applyOverflowLock(); - - if (window.location.pathname.includes('/direct/') && reels.length > 0) { - // Give the first reel 3.5 s to buffer before activating the DM lock - if (!window.__fgDmReelTimer) { - window.__fgDmReelTimer = setTimeout(() => { - if (document.querySelector(REEL_SEL)) { - window.__fgDmReelAlreadyLoaded = true; - } - window.__fgDmReelTimer = null; - }, 3500); - } - } - - if (reels.length === 0) { - if (window.__fgDmReelTimer) { - clearTimeout(window.__fgDmReelTimer); - window.__fgDmReelTimer = null; - } - window.__fgDmReelAlreadyLoaded = false; - } - } - - sync(); - new MutationObserver(ms => { - if (ms.some(m => m.addedNodes.length || m.removedNodes.length)) sync(); - }).observe(document.body, { childList: true, subtree: true }); - - // Keep __focusgramIsolatedPlayer in sync with SPA navigations - if (!window.__fgIsolatedPlayerSync) { - window.__fgIsolatedPlayerSync = true; - let _lastPath = window.location.pathname; - setInterval(() => { - const p = window.location.pathname; - if (p === _lastPath) return; - _lastPath = p; - window.__focusgramIsolatedPlayer = - p.includes('/reel/') && !p.startsWith('/reels'); - if (!p.includes('/direct/')) window.__fgDmReelAlreadyLoaded = false; - applyOverflowLock(); - }, 400); - } - })(); -'''; - -/// Periodically checks Instagram's UI for unread counts (badges) on the Direct -/// and Notifications icons, as well as the page title. Sends an event to -/// Flutter whenever a new notification is detected. -const String kBadgeMonitorJS = r''' - (function fgBadgeMonitor() { - if (window.__fgBadgeMonitorRunning) return; - window.__fgBadgeMonitorRunning = true; - - const startedAt = Date.now(); - let initialised = false; - let lastDmCount = 0; - let lastNotifCount = 0; - let lastTitleUnread = 0; - - function parseBadgeCount(el) { - if (!el) return 0; - try { - const raw = (el.innerText || el.textContent || '').trim(); - const n = parseInt(raw, 10); - return isNaN(n) ? 1 : n; - } catch (_) { - return 1; - } - } - - function check() { - try { - // 1. Check Title for (N) indicator - const titleMatch = document.title.match(/\((\d+)\)/); - const currentTitleUnread = titleMatch ? parseInt(titleMatch[1]) : 0; - - // 2. Scan for DM unread badge - const dmBadge = document.querySelector([ - 'a[href*="/direct/inbox/"] [style*="rgb(255, 48, 64)"]', - 'a[href*="/direct/inbox/"] [style*="255, 48, 64"]', - 'a[href*="/direct/inbox/"] [aria-label*="unread"]', - 'div[role="button"][aria-label*="Direct"] [style*="255, 48, 64"]', - 'a[href*="/direct/inbox/"] svg[aria-label*="Direct"] + div', - 'a[href*="/direct/inbox/"] ._a9-v', - ].join(',')); - const currentDmCount = parseBadgeCount(dmBadge); - - // 3. Scan for Notifications unread badge - const notifBadge = document.querySelector([ - 'a[href*="/notifications"] [style*="rgb(255, 48, 64)"]', - 'a[href*="/notifications"] [style*="255, 48, 64"]', - 'a[href*="/notifications"] [aria-label*="unread"]' - ].join(',')); - const currentNotifCount = parseBadgeCount(notifBadge); - - // Establish baseline on first run and suppress false positives right after reload. - if (!initialised) { - lastDmCount = currentDmCount; - lastNotifCount = currentNotifCount; - lastTitleUnread = currentTitleUnread; - initialised = true; - return; - } - if (Date.now() - startedAt < 6000) { - lastDmCount = currentDmCount; - lastNotifCount = currentNotifCount; - lastTitleUnread = currentTitleUnread; - return; - } - - if (currentDmCount > lastDmCount) { - if (window.flutter_inappwebview) { - window.flutter_inappwebview.callHandler('FocusGramNotificationChannel', 'DM'); - } - } else if (currentNotifCount > lastNotifCount) { - if (window.flutter_inappwebview) { - window.flutter_inappwebview.callHandler('FocusGramNotificationChannel', 'Activity'); - } - } else if (currentTitleUnread > lastTitleUnread && currentTitleUnread > (currentDmCount + currentNotifCount)) { - if (window.flutter_inappwebview) { - window.flutter_inappwebview.callHandler('FocusGramNotificationChannel', 'Activity'); - } - } - - lastDmCount = currentDmCount; - lastNotifCount = currentNotifCount; - lastTitleUnread = currentTitleUnread; - } catch(_) {} - } - - // Initial check after some delay to let page settle - setTimeout(check, 2000); - setInterval(check, 1000); - })(); -'''; - -/// Forwards Web Notification events to the native Flutter channel. -const String kNotificationBridgeJS = ''' - (function fgNotifBridge() { - if (!window.Notification || window.__fgNotifBridged) return; - window.__fgNotifBridged = true; - const startedAt = Date.now(); - const _N = window.Notification; - window.Notification = function(title, opts) { - try { - // Avoid false positives on reload / initial bootstrap. - if (Date.now() - startedAt < 6000) { - return new _N(title, opts); - } - if (window.flutter_inappwebview) { - window.flutter_inappwebview.callHandler( - 'FocusGramNotificationChannel', - title + (opts && opts.body ? ': ' + opts.body : ''), - ); - } - } catch(_) {} - return new _N(title, opts); - }; - window.Notification.permission = 'granted'; - window.Notification.requestPermission = () => Promise.resolve('granted'); - })(); -'''; - -/// Strips tracking query params (igsh, utm_*, fbclidâ€Ķ) from all links and the -/// native Web Share API. Sanitised share URLs are routed to Flutter's share -/// channel instead. -const String kLinkSanitizationJS = r''' - (function fgSanitize() { - if (window.__fgSanitizePatched) return; - window.__fgSanitizePatched = true; - const STRIP = [ - 'igsh','igshid','fbclid', - 'utm_source','utm_medium','utm_campaign','utm_term','utm_content', - 'ref','s','_branch_match_id','_branch_referrer', - ]; - function clean(raw) { - try { - const u = new URL(raw, location.origin); - STRIP.forEach(p => u.searchParams.delete(p)); - return u.toString(); - } catch(_) { return raw; } - } - if (navigator.share) { - const _s = navigator.share.bind(navigator); - navigator.share = function(d) { - const u = d && d.url ? clean(d.url) : null; - if (window.flutter_inappwebview && u) { - window.flutter_inappwebview.callHandler( - 'FocusGramShareChannel', - JSON.stringify({ url: u, title: (d && d.title) || '' }), - ); - return Promise.resolve(); - } - return _s({ ...d, url: u || (d && d.url) }); - }; - } - document.addEventListener('click', e => { - const a = e.target && e.target.closest('a[href]'); - if (!a) return; - const href = a.getAttribute('href'); - if (!href || href.startsWith('#') || href.startsWith('javascript')) return; - try { - const u = new URL(href, location.origin); - if (STRIP.some(p => u.searchParams.has(p))) { - STRIP.forEach(p => u.searchParams.delete(p)); - a.href = u.toString(); - } - } catch(_) {} - }, true); - })(); -'''; diff --git a/lib/scripts/dm_keyboard_fix.dart b/lib/scripts/dm_keyboard_fix.dart deleted file mode 100644 index c5f4f05..0000000 --- a/lib/scripts/dm_keyboard_fix.dart +++ /dev/null @@ -1,15 +0,0 @@ -/// JS to help Instagram's layout detect viewport changes when the Android -/// soft keyboard appears in a WebView container. -/// -/// It listens for resize events and re-dispatches an `orientationchange` -/// event, which nudges Instagram's layout system out of the DM loading -/// spinner state. -const String kDmKeyboardFixJS = r''' - // Fix: tell Instagram's layout system the viewport has changed after keyboard events - // This resolves the loading state that appears on DM screens in WebView - window.addEventListener('resize', function() { - try { - window.dispatchEvent(new Event('orientationchange')); - } catch (_) {} - }); -'''; diff --git a/lib/scripts/focus_scripts.dart b/lib/scripts/focus_scripts.dart deleted file mode 100644 index 436551c..0000000 --- a/lib/scripts/focus_scripts.dart +++ /dev/null @@ -1,463 +0,0 @@ -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import '../focus_settings.dart'; - -/// Flutter sets these flags after settings load to enable ghost modes. -/// Must be called from onWebViewCreated or on settings change. -const String kSetGhostFlagsJS = ''' -(function(){ - // Placeholder — Flutter replaces these with actual setting values: - // window.__fgPartialGhost = true/false; - // window.__fgFullDmGhost = true/false; - // window.__fgStoryGhost = true/false; - // window.__fgGhostReady = true; // signals scripts can proceed -})(); -'''; - -// ═══════════════════════════════════════════════════════════════ -// PARTIAL GHOST MODE — existing behavior -// Blocks seen API patterns, WebSocket chat gateways, and uses -// first-click gate for api/graphql on /direct/* (inbox loads, then block). -// ═══════════════════════════════════════════════════════════════ -const String kPartialGhostJS = r''' -(function() { - if (window.__fgPartialGhostPatched) return; - window.__fgPartialGhostPatched = true; - - // ── Seen API patterns ────────────────────────────────────── - var SEEN = [/\/api\/v1\/media\/[\w-]+\/seen\//, - /\/api\/v1\/stories\/reel\/seen\//, - /\/api\/v1\/direct_v2\/threads\/[\w-]+\/seen\//, - /\/api\/v1\/direct_v2\/visual_message\/[\w-]+\/seen\//, - /\/api\/v1\/live\/[\w-]+\/comment\/seen\//]; - function isSeen(u) { for(var i=0;i buildUserScripts(FocusSettings settings) { - final startScripts = []; - final endScripts = []; - - // Prepend flag values directly into the script so they survive page navigation. - // (evaluateJavascript-set flags are destroyed when the JS context resets on load.) - // DM Ghost uses the comprehensive Full DM approach (URL blocklist, GraphQL ops, SW killer, beacon, WS). - // it should have worked, but sadly it didnt - if (settings.ghostMode) { - startScripts.add('window.__fgFullDmGhost=true;$kFullDmGhostJS'); - } - if (settings.noAutoplay) startScripts.add(noAutoplayJS); - - // AT_DOCUMENT_END - if (settings.noStories) endScripts.add(hideStoryTrayJS); - if (settings.noReels) endScripts.add(hideReelsJS); - if (settings.noDMs) endScripts.add(hideDMsJS); - - final scripts = []; - if (startScripts.isNotEmpty) { - scripts.add( - UserScript( - source: startScripts.join('\n'), - injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START, - forMainFrameOnly: false, - ), - ); - } - if (endScripts.isNotEmpty) { - scripts.add( - UserScript( - source: endScripts.join('\n'), - injectionTime: UserScriptInjectionTime.AT_DOCUMENT_END, - forMainFrameOnly: true, - ), - ); - } - return scripts; -} - -// ── Existing non-ghost helpers (unchanged) ─────────────────── - -const String noAutoplayJS = ''' -document.addEventListener('play', function(e) { - if (e.target.tagName === 'VIDEO') e.target.pause(); -}, true); -'''; - -const String hideStoryTrayJS = ''' -(function(){var s=document.createElement('style');s.textContent='[data-pagelet="story_tray"]{display:none!important}';document.head.appendChild(s);})(); -'''; - -const String hideReelsJS = ''' -(function(){new MutationObserver(function(){document.querySelectorAll('a[href="/reels/"]').forEach(function(e){var p=e.closest('div');if(p)p.style.setProperty('display','none','important')});document.querySelectorAll('a[href="/explore/"]').forEach(function(e){var p=e.closest('div');if(p)p.style.setProperty('display','none','important')})}).observe(document.body,{childList:true,subtree:true});})(); -'''; - -const String hideDMsJS = ''' -(function(){var s=document.createElement('style');s.textContent='a[href="/direct/inbox/"]{display:none!important}';document.head.appendChild(s);})(); -'''; diff --git a/lib/scripts/grayscale.dart b/lib/scripts/grayscale.dart deleted file mode 100644 index a54833e..0000000 --- a/lib/scripts/grayscale.dart +++ /dev/null @@ -1,48 +0,0 @@ -/// Grayscale style injector. -/// Uses a - -$_kMediumRectCode - -'''; - - @override - Widget build(BuildContext context) { - return Container( - width: double.infinity, - constraints: const BoxConstraints(maxHeight: 270), - decoration: BoxDecoration( - color: Colors.transparent, - border: Border( - top: BorderSide(color: Colors.grey.withValues(alpha: 0.08)), - bottom: BorderSide(color: Colors.grey.withValues(alpha: 0.08)), - ), - ), - child: SizedBox( - height: 250, - child: InAppWebView( - initialSettings: InAppWebViewSettings( - javaScriptEnabled: true, - domStorageEnabled: true, - transparentBackground: true, - cacheEnabled: false, - safeBrowsingEnabled: false, - useHybridComposition: true, - ), - onWebViewCreated: (c) async { - await c.loadData( - data: _html, - mimeType: 'text/html', - encoding: 'utf-8', - baseUrl: WebUri('https://adsterra.com'), - ); - }, - ), - ), - ); - } -} diff --git a/lib/widgets/native_ad_banner.dart b/lib/widgets/native_ad_banner.dart deleted file mode 100644 index 6f5446a..0000000 --- a/lib/widgets/native_ad_banner.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; - -// ── Adsterra banner codes ──────────────────────────────────────────── -// 320×50 — standard mobile banner, used at bottom of screens -const String _kBanner320x50 = ''' - - -'''; - -/// A small 320×50 banner that loads natively inside the app. -/// Place at the bottom of screens. -class NativeAdBanner extends StatelessWidget { - final double height; - final String? customCode; - - const NativeAdBanner({super.key, this.height = 60, this.customCode}); - - String get _html { - final code = customCode ?? _kBanner320x50; - return ''' - - - - - - -$code - -'''; - } - - @override - Widget build(BuildContext context) { - return Container( - // Subtle native look — barely visible border, no "AD" label - decoration: BoxDecoration( - color: Colors.transparent, - border: Border( - top: BorderSide(color: Colors.grey.withValues(alpha: 0.08)), - ), - ), - child: SizedBox( - height: height, - child: InAppWebView( - initialSettings: InAppWebViewSettings( - javaScriptEnabled: true, - domStorageEnabled: true, - transparentBackground: true, - cacheEnabled: false, - safeBrowsingEnabled: false, - useHybridComposition: true, - ), - onWebViewCreated: (c) async { - await c.loadData( - data: _html, - mimeType: 'text/html', - encoding: 'utf-8', - baseUrl: WebUri('https://adsterra.com'), - ); - }, - ), - ), - ); - } -} diff --git a/lib/widgets/remote_popup_handler.dart b/lib/widgets/remote_popup_handler.dart deleted file mode 100644 index 1240fcc..0000000 --- a/lib/widgets/remote_popup_handler.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../services/remote_popup_service.dart'; - -class RemotePopupHandler { - static Future checkAndShow(BuildContext context) async { - final popup = await RemotePopupService.fetchPopup(); - if (popup == null) return; - - final shouldShow = await RemotePopupService.shouldShow(popup); - if (!shouldShow) return; - - await RemotePopupService.markShown(popup); - - if (!context.mounted) return; - - showDialog( - context: context, - barrierDismissible: true, - builder: (_) { - return AlertDialog( - title: Text(popup.title), - content: Text(popup.body), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: Text(popup.buttonText), - ), - ], - ); - }, - ); - } -} diff --git a/pubspec.lock b/pubspec.lock deleted file mode 100644 index f1bb14c..0000000 --- a/pubspec.lock +++ /dev/null @@ -1,1122 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - app_links: - dependency: "direct main" - description: - name: app_links - sha256: "5f88447519add627fe1cbcab4fd1da3d4fed15b9baf29f28b22535c95ecee3e8" - url: "https://pub.dev" - source: hosted - version: "6.4.1" - app_links_linux: - dependency: transitive - description: - name: app_links_linux - sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 - url: "https://pub.dev" - source: hosted - version: "1.0.3" - app_links_platform_interface: - dependency: transitive - description: - name: app_links_platform_interface - sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" - url: "https://pub.dev" - source: hosted - version: "2.0.2" - app_links_web: - dependency: transitive - description: - name: app_links_web - sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 - url: "https://pub.dev" - source: hosted - version: "1.0.4" - app_settings: - dependency: "direct main" - description: - name: app_settings - sha256: "64d50e666fd96ae90301bf71205f05019286f940ad6f5fed3d1be19c6af7546a" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - archive: - dependency: transitive - description: - name: archive - sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff - url: "https://pub.dev" - source: hosted - version: "4.0.9" - args: - dependency: transitive - description: - name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" - source: hosted - version: "2.7.0" - async: - dependency: transitive - description: - name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.dev" - source: hosted - version: "2.13.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - characters: - dependency: transitive - description: - name: characters - sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" - source: hosted - version: "1.4.1" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" - url: "https://pub.dev" - source: hosted - version: "2.0.4" - cli_util: - dependency: transitive - description: - name: cli_util - sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c - url: "https://pub.dev" - source: hosted - version: "0.4.2" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" - source: hosted - version: "1.1.2" - code_assets: - dependency: transitive - description: - name: code_assets - sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - cross_file: - dependency: transitive - description: - name: cross_file - sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" - url: "https://pub.dev" - source: hosted - version: "0.3.5+2" - crypto: - dependency: transitive - description: - name: crypto - sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.dev" - source: hosted - version: "3.0.7" - dbus: - dependency: transitive - description: - name: dbus - sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 - url: "https://pub.dev" - source: hosted - version: "0.7.12" - equatable: - dependency: transitive - description: - name: equatable - sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" - url: "https://pub.dev" - source: hosted - version: "2.0.8" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" - source: hosted - version: "1.3.3" - ffi: - dependency: transitive - description: - name: ffi - sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - file_selector_linux: - dependency: transitive - description: - name: file_selector_linux - sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" - url: "https://pub.dev" - source: hosted - version: "0.9.4" - file_selector_macos: - dependency: transitive - description: - name: file_selector_macos - sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" - url: "https://pub.dev" - source: hosted - version: "0.9.5" - file_selector_platform_interface: - dependency: transitive - description: - name: file_selector_platform_interface - sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" - url: "https://pub.dev" - source: hosted - version: "2.7.0" - file_selector_windows: - dependency: transitive - description: - name: file_selector_windows - sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" - url: "https://pub.dev" - source: hosted - version: "0.9.3+5" - fl_chart: - dependency: "direct main" - description: - name: fl_chart - sha256: f2e9137f261d0f53a820f6b829c80ba570ac915284c8e32789d973834796eca0 - url: "https://pub.dev" - source: hosted - version: "0.71.0" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_inappwebview: - dependency: "direct main" - description: - name: flutter_inappwebview - sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" - url: "https://pub.dev" - source: hosted - version: "6.1.5" - flutter_inappwebview_android: - dependency: transitive - description: - name: flutter_inappwebview_android - sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" - url: "https://pub.dev" - source: hosted - version: "1.1.3" - flutter_inappwebview_internal_annotations: - dependency: transitive - description: - name: flutter_inappwebview_internal_annotations - sha256: e30fba942e3debea7b7e6cdd4f0f59ce89dd403a9865193e3221293b6d1544c6 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - flutter_inappwebview_ios: - dependency: transitive - description: - name: flutter_inappwebview_ios - sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" - url: "https://pub.dev" - source: hosted - version: "1.1.2" - flutter_inappwebview_macos: - dependency: transitive - description: - name: flutter_inappwebview_macos - sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 - url: "https://pub.dev" - source: hosted - version: "1.1.2" - flutter_inappwebview_platform_interface: - dependency: transitive - description: - name: flutter_inappwebview_platform_interface - sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 - url: "https://pub.dev" - source: hosted - version: "1.3.0+1" - flutter_inappwebview_web: - dependency: transitive - description: - name: flutter_inappwebview_web - sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" - url: "https://pub.dev" - source: hosted - version: "1.1.2" - flutter_inappwebview_windows: - dependency: transitive - description: - name: flutter_inappwebview_windows - sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" - url: "https://pub.dev" - source: hosted - version: "0.6.0" - flutter_launcher_icons: - dependency: "direct dev" - description: - name: flutter_launcher_icons - sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" - url: "https://pub.dev" - source: hosted - version: "0.14.4" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" - url: "https://pub.dev" - source: hosted - version: "6.0.0" - flutter_local_notifications: - dependency: "direct main" - description: - name: flutter_local_notifications - sha256: "2b50e938a275e1ad77352d6a25e25770f4130baa61eaf02de7a9a884680954ad" - url: "https://pub.dev" - source: hosted - version: "20.1.0" - flutter_local_notifications_linux: - dependency: transitive - description: - name: flutter_local_notifications_linux - sha256: dce0116868cedd2cdf768af0365fc37ff1cbef7c02c4f51d0587482e625868d0 - url: "https://pub.dev" - source: hosted - version: "7.0.0" - flutter_local_notifications_platform_interface: - dependency: transitive - description: - name: flutter_local_notifications_platform_interface - sha256: "23de31678a48c084169d7ae95866df9de5c9d2a44be3e5915a2ff067aeeba899" - url: "https://pub.dev" - source: hosted - version: "10.0.0" - flutter_local_notifications_windows: - dependency: transitive - description: - name: flutter_local_notifications_windows - sha256: e97a1a3016512437d9c0b12fae7d1491c3c7b9aa7f03a69b974308840656b02a - url: "https://pub.dev" - source: hosted - version: "2.0.1" - flutter_plugin_android_lifecycle: - dependency: transitive - description: - name: flutter_plugin_android_lifecycle - sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 - url: "https://pub.dev" - source: hosted - version: "2.0.33" - flutter_secure_storage: - dependency: "direct main" - description: - name: flutter_secure_storage - sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" - url: "https://pub.dev" - source: hosted - version: "9.2.4" - flutter_secure_storage_linux: - dependency: transitive - description: - name: flutter_secure_storage_linux - sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 - url: "https://pub.dev" - source: hosted - version: "1.2.3" - flutter_secure_storage_macos: - dependency: transitive - description: - name: flutter_secure_storage_macos - sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" - url: "https://pub.dev" - source: hosted - version: "3.1.3" - flutter_secure_storage_platform_interface: - dependency: transitive - description: - name: flutter_secure_storage_platform_interface - sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 - url: "https://pub.dev" - source: hosted - version: "1.1.2" - flutter_secure_storage_web: - dependency: transitive - description: - name: flutter_secure_storage_web - sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - flutter_secure_storage_windows: - dependency: transitive - description: - name: flutter_secure_storage_windows - sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 - url: "https://pub.dev" - source: hosted - version: "3.1.2" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - flutter_windowmanager_plus: - dependency: "direct main" - description: - name: flutter_windowmanager_plus - sha256: "4e2bf7c7f374699fd74d59785f1d74efd40052c24a5edde5a4d825cc72608d40" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - glob: - dependency: transitive - description: - name: glob - sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" - source: hosted - version: "2.1.3" - google_fonts: - dependency: "direct main" - description: - name: google_fonts - sha256: "4e9391085e524954a51e3625b7c9c7e9851dc3f376603208bb45c24b9a66255d" - url: "https://pub.dev" - source: hosted - version: "8.1.0" - gtk: - dependency: transitive - description: - name: gtk - sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c - url: "https://pub.dev" - source: hosted - version: "2.1.0" - hive: - dependency: "direct main" - description: - name: hive - sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" - url: "https://pub.dev" - source: hosted - version: "2.2.3" - hive_flutter: - dependency: "direct main" - description: - name: hive_flutter - sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc - url: "https://pub.dev" - source: hosted - version: "1.1.0" - hooks: - dependency: transitive - description: - name: hooks - sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - http: - dependency: "direct main" - description: - name: http - sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.dev" - source: hosted - version: "1.6.0" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" - source: hosted - version: "4.1.2" - image: - dependency: transitive - description: - name: image - sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce - url: "https://pub.dev" - source: hosted - version: "4.8.0" - image_picker: - dependency: "direct main" - description: - name: image_picker - sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" - url: "https://pub.dev" - source: hosted - version: "1.2.1" - image_picker_android: - dependency: transitive - description: - name: image_picker_android - sha256: eda9b91b7e266d9041084a42d605a74937d996b87083395c5e47835916a86156 - url: "https://pub.dev" - source: hosted - version: "0.8.13+14" - image_picker_for_web: - dependency: transitive - description: - name: image_picker_for_web - sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" - url: "https://pub.dev" - source: hosted - version: "3.1.1" - image_picker_ios: - dependency: transitive - description: - name: image_picker_ios - sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588 - url: "https://pub.dev" - source: hosted - version: "0.8.13+6" - image_picker_linux: - dependency: transitive - description: - name: image_picker_linux - sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" - url: "https://pub.dev" - source: hosted - version: "0.2.2" - image_picker_macos: - dependency: transitive - description: - name: image_picker_macos - sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" - url: "https://pub.dev" - source: hosted - version: "0.2.2+1" - image_picker_platform_interface: - dependency: transitive - description: - name: image_picker_platform_interface - sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" - url: "https://pub.dev" - source: hosted - version: "2.11.1" - image_picker_windows: - dependency: transitive - description: - name: image_picker_windows - sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae - url: "https://pub.dev" - source: hosted - version: "0.2.2" - intl: - dependency: "direct main" - description: - name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" - url: "https://pub.dev" - source: hosted - version: "0.20.2" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 - url: "https://pub.dev" - source: hosted - version: "4.11.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" - source: hosted - version: "11.0.2" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" - source: hosted - version: "3.0.10" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - lints: - dependency: transitive - description: - name: lints - sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" - url: "https://pub.dev" - source: hosted - version: "6.1.0" - local_auth: - dependency: "direct main" - description: - name: local_auth - sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b" - url: "https://pub.dev" - source: hosted - version: "2.3.0" - local_auth_android: - dependency: transitive - description: - name: local_auth_android - sha256: a0bdfcc0607050a26ef5b31d6b4b254581c3d3ce3c1816ab4d4f4a9173e84467 - url: "https://pub.dev" - source: hosted - version: "1.0.56" - local_auth_darwin: - dependency: transitive - description: - name: local_auth_darwin - sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49" - url: "https://pub.dev" - source: hosted - version: "1.6.1" - local_auth_platform_interface: - dependency: transitive - description: - name: local_auth_platform_interface - sha256: f98b8e388588583d3f781f6806e4f4c9f9e189d898d27f0c249b93a1973dd122 - url: "https://pub.dev" - source: hosted - version: "1.1.0" - local_auth_windows: - dependency: transitive - description: - name: local_auth_windows - sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5 - url: "https://pub.dev" - source: hosted - version: "1.0.11" - logging: - dependency: transitive - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" - source: hosted - version: "0.12.19" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" - source: hosted - version: "0.13.0" - meta: - dependency: transitive - description: - name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" - url: "https://pub.dev" - source: hosted - version: "1.18.0" - mime: - dependency: transitive - description: - name: mime - sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - native_toolchain_c: - dependency: transitive - description: - name: native_toolchain_c - sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac" - url: "https://pub.dev" - source: hosted - version: "0.17.4" - nested: - dependency: transitive - description: - name: nested - sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - objective_c: - dependency: transitive - description: - name: objective_c - sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" - url: "https://pub.dev" - source: hosted - version: "9.3.0" - package_info_plus: - dependency: "direct main" - description: - name: package_info_plus - sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20" - url: "https://pub.dev" - source: hosted - version: "9.0.1" - package_info_plus_platform_interface: - dependency: transitive - description: - name: package_info_plus_platform_interface - sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" - url: "https://pub.dev" - source: hosted - version: "3.2.1" - path: - dependency: transitive - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - path_provider: - dependency: transitive - description: - name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" - source: hosted - version: "2.1.5" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e - url: "https://pub.dev" - source: hosted - version: "2.2.22" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" - url: "https://pub.dev" - source: hosted - version: "2.6.0" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" - source: hosted - version: "2.3.0" - permission_handler: - dependency: "direct main" - description: - name: permission_handler - sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" - url: "https://pub.dev" - source: hosted - version: "11.4.0" - permission_handler_android: - dependency: transitive - description: - name: permission_handler_android - sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc - url: "https://pub.dev" - source: hosted - version: "12.1.0" - permission_handler_apple: - dependency: transitive - description: - name: permission_handler_apple - sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 - url: "https://pub.dev" - source: hosted - version: "9.4.7" - permission_handler_html: - dependency: transitive - description: - name: permission_handler_html - sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" - url: "https://pub.dev" - source: hosted - version: "0.1.3+5" - permission_handler_platform_interface: - dependency: transitive - description: - name: permission_handler_platform_interface - sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 - url: "https://pub.dev" - source: hosted - version: "4.3.0" - permission_handler_windows: - dependency: transitive - description: - name: permission_handler_windows - sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" - url: "https://pub.dev" - source: hosted - version: "0.2.1" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" - url: "https://pub.dev" - source: hosted - version: "7.0.2" - platform: - dependency: transitive - description: - name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" - source: hosted - version: "3.1.6" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - posix: - dependency: transitive - description: - name: posix - sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" - url: "https://pub.dev" - source: hosted - version: "6.5.0" - provider: - dependency: "direct main" - description: - name: provider - sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" - url: "https://pub.dev" - source: hosted - version: "6.1.5+1" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf - url: "https://pub.dev" - source: hosted - version: "2.5.5" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: cbc40be9be1c5af4dab4d6e0de4d5d3729e6f3d65b89d21e1815d57705644a6f - url: "https://pub.dev" - source: hosted - version: "2.4.20" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" - url: "https://pub.dev" - source: hosted - version: "2.5.6" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.dev" - source: hosted - version: "2.4.3" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - source_span: - dependency: transitive - description: - name: source_span - sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" - source: hosted - version: "1.10.2" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" - source: hosted - version: "1.12.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" - source: hosted - version: "1.4.1" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" - source: hosted - version: "1.2.2" - test_api: - dependency: transitive - description: - name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" - url: "https://pub.dev" - source: hosted - version: "0.7.11" - timezone: - dependency: transitive - description: - name: timezone - sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 - url: "https://pub.dev" - source: hosted - version: "0.10.1" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - url_launcher: - dependency: "direct main" - description: - name: url_launcher - sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 - url: "https://pub.dev" - source: hosted - version: "6.3.2" - url_launcher_android: - dependency: transitive - description: - name: url_launcher_android - sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" - url: "https://pub.dev" - source: hosted - version: "6.3.28" - url_launcher_ios: - dependency: transitive - description: - name: url_launcher_ios - sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" - url: "https://pub.dev" - source: hosted - version: "6.4.1" - url_launcher_linux: - dependency: transitive - description: - name: url_launcher_linux - sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a - url: "https://pub.dev" - source: hosted - version: "3.2.2" - url_launcher_macos: - dependency: transitive - description: - name: url_launcher_macos - sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" - url: "https://pub.dev" - source: hosted - version: "3.2.5" - url_launcher_platform_interface: - dependency: transitive - description: - name: url_launcher_platform_interface - sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - url_launcher_web: - dependency: transitive - description: - name: url_launcher_web - sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f - url: "https://pub.dev" - source: hosted - version: "2.4.2" - url_launcher_windows: - dependency: transitive - description: - name: url_launcher_windows - sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" - url: "https://pub.dev" - source: hosted - version: "3.1.5" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" - source: hosted - version: "2.2.0" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" - url: "https://pub.dev" - source: hosted - version: "15.0.2" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - win32: - dependency: transitive - description: - name: win32 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.dev" - source: hosted - version: "5.15.0" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - xml: - dependency: transitive - description: - name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" - url: "https://pub.dev" - source: hosted - version: "6.6.1" - yaml: - dependency: transitive - description: - name: yaml - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" - source: hosted - version: "3.1.3" -sdks: - dart: ">=3.10.7 <4.0.0" - flutter: ">=3.38.4" diff --git a/pubspec.yaml b/pubspec.yaml deleted file mode 100644 index bb6c5fc..0000000 --- a/pubspec.yaml +++ /dev/null @@ -1,83 +0,0 @@ -name: focusgram -description: "FocusGram is a free, open-source digital wellness tool for Android. Blocks distracting content, sets time limits, and helps you use social media intentionally." -publish_to: 'none' - -version: 2.1.0 - -environment: - sdk: ^3.10.7 - -dependencies: - flutter: - sdk: flutter - - # WebView engine - flutter_inappwebview: ^6.1.5 - - # Local key-value persistence — latest stable - shared_preferences: ^2.5.5 - - # Date/time formatting for daily resets — latest stable - intl: ^0.20.2 - - # Reactive state management — latest stable - provider: ^6.1.5 - - # Local notifications for session reminders — latest stable - flutter_local_notifications: ^20.1.0 - - # URL launcher for About page links — latest stable - url_launcher: ^6.3.2 - package_info_plus: ^9.0.0 - # Handling Instagram deep links — latest stable - app_links: ^6.4.1 - # Open system settings — latest stable - app_settings: ^7.0.0 - google_fonts: ^8.1.0 - http: ^1.6.0 - permission_handler: ^11.4.0 - # Image/file picker for story uploads on Android - image_picker: ^1.2.0 - flutter_windowmanager_plus: ^1.0.1 - - # Charts for on-device screen time dashboard (MIT) - fl_chart: ^0.71.0 - - # ── Local storage ────────────────────────────────────────── - # google_mobile_ads removed — switched to Adsterra only - # firebase removed — level data stored in Hive locally - hive: ^2.2.3 - hive_flutter: ^1.1.0 - flutter_secure_storage: ^9.2.4 - local_auth: ^2.3.0 - # connectivity_plus, workmanager, flutter_background_service removed — - # unused after offline snapshots pivoted to WebView cache. - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^6.0.0 - flutter_launcher_icons: ^0.14.4 - -flutter: - uses-material-design: true - - assets: - - assets/images/focusgram.png - - assets/images/focusgram.ico - - assets/scripts/ghost_mode.js - - assets/scripts/ad_blocker_dom.js - - assets/scripts/content_hider.js - - assets/scripts/theme_detector.js - - assets/scripts/fetch_interceptor.js - - assets/scripts/autoplay_blocker.js - -flutter_launcher_icons: - android: true - ios: false - image_path: "assets/images/focusgram.png" - adaptive_icon_background: "#000000" - adaptive_icon_foreground: "assets/images/focusgram.png" - min_sdk_android: 24 - - diff --git a/Ported/settings.gradle.kts b/settings.gradle.kts similarity index 100% rename from Ported/settings.gradle.kts rename to settings.gradle.kts diff --git a/test/screens/main_webview_page_media_download_test.dart b/test/screens/main_webview_page_media_download_test.dart deleted file mode 100644 index 92d2a27..0000000 --- a/test/screens/main_webview_page_media_download_test.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:focusgram/screens/main_webview_page.dart'; - -void main() { - group('handleFocusGramMediaDownload', () { - test('rejects non-http(s) schemes', () async { - final launched = []; - final ok = await handleFocusGramMediaDownload( - raw: '{"type":"video","url":"file:///etc/passwd","filename":"x"}', - launch: (uri) async => launched.add(uri), - ); - - expect(ok, isFalse); - expect(launched, isEmpty); - }); - - test('accepts http(s) instagram-like hosts and calls launcher', () async { - final launched = []; - final ok = await handleFocusGramMediaDownload( - raw: - '{"type":"video","url":"https://cdninstagram.com/v/1.mp4","filename":"x"}', - launch: (uri) async => launched.add(uri), - ); - - expect(ok, isTrue); - expect(launched, hasLength(1)); - expect(launched.first.scheme, 'https'); - expect(launched.first.host.toLowerCase(), contains('cdninstagram.com')); - }); - - test('rejects non-instagram hosts even if http(s)', () async { - final launched = []; - final ok = await handleFocusGramMediaDownload( - raw: - '{"type":"video","url":"https://example.com/video.mp4","filename":"x"}', - launch: (uri) async => launched.add(uri), - ); - - expect(ok, isFalse); - expect(launched, isEmpty); - }); - - test('rejects malformed JSON safely', () async { - final launched = []; - final ok = await handleFocusGramMediaDownload( - raw: '{not json', - launch: (uri) async => launched.add(uri), - ); - - expect(ok, isFalse); - expect(launched, isEmpty); - }); - - test('rejects missing url field', () async { - final launched = []; - final ok = await handleFocusGramMediaDownload( - raw: '{"type":"video","filename":"x"}', - launch: (uri) async => launched.add(uri), - ); - - expect(ok, isFalse); - expect(launched, isEmpty); - }); - }); -} diff --git a/test/services/adblocker_test.dart b/test/services/adblocker_test.dart deleted file mode 100644 index 6c53456..0000000 --- a/test/services/adblocker_test.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -import 'package:focusgram/services/injection_manager.dart'; -import 'package:focusgram/services/adblock/adblock_content_blocker_loader.dart'; -import 'package:focusgram/services/session_manager.dart'; -import 'package:focusgram/services/settings_service.dart'; - -class _FakeJsEvaluator implements JsEvaluator { - final List sources = []; - - @override - Future evaluateJavascript({required String source}) async { - sources.add(source); - } -} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() { - SharedPreferences.setMockInitialValues({}); - }); - - test( - 'v2AdBlockerDomEnabled(true) does NOT trigger sponsored-post JS injection (handled by V2 engine)', - () async { - final prefs = await SharedPreferences.getInstance(); - final sm = SessionManager(); - final fakeEval = _FakeJsEvaluator(); - - final mgr = InjectionManager.forTest( - jsEvaluator: fakeEval, - prefs: prefs, - sessionManager: sm, - ); - - final settings = SettingsService(); - await settings.init(); - await settings.setV2AdBlockerDomEnabled(true); - - expect(settings.v2AdBlockerDomEnabled, isTrue); - - mgr.setSettingsService(settings); - await mgr.runAllPostLoadInjections('https://www.instagram.com/'); - - // Verify that sponsored posts JS injection is NOT triggered by InjectionManager - // (it's handled by the V2 DOM Ad Blocker engine instead) - final sponsoredPostsInjected = fakeEval.sources.any( - (s) => s.contains('hideSponsoredPosts') || s.contains('Sponsored'), - ); - - expect( - sponsoredPostsInjected, - isFalse, - reason: - 'Sponsored posts blocking is now handled by V2 DOM Ad Blocker, not JS injection', - ); - }, - ); - - test( - 'adblock parser extracts strict host rules and ignores allow/cosmetic rules', - () { - final hosts = AdblockContentBlockerLoader.parseHostsFromFilterText(''' -! comment -[Adblock Plus 2.0] -||ads.example.com^ -||tracker.example.net/path.js\$third-party -@@||allowed.example.com^ -example.com##.sponsored -||wild*.example.com^ -||bad,domain.example^ -||sub.adguard.example.org^\$script,third-party -'''); - - expect( - hosts, - containsAll({ - 'ads.example.com', - 'tracker.example.net', - 'sub.adguard.example.org', - }), - ); - expect(hosts, isNot(contains('allowed.example.com'))); - expect(hosts, isNot(contains('wild*.example.com'))); - expect(hosts, isNot(contains('bad,domain.example'))); - }, - ); -} diff --git a/test/services/app_lock_service_test.dart b/test/services/app_lock_service_test.dart deleted file mode 100644 index 4a568f9..0000000 --- a/test/services/app_lock_service_test.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:focusgram/services/app_lock_service.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() { - SharedPreferences.setMockInitialValues({}); - }); - - group('AppLockService — PIN verification', () { - test('verifyPin returns true for correct PIN', () async { - final service = AppLockService(); - await service.init(); - - await service.setPin('1234', forAppWide: true); - - final valid = await service.verifyPin('1234', forAppWide: true); - expect(valid, isTrue); - }); - - test('verifyPin returns false for wrong PIN', () async { - final service = AppLockService(); - await service.init(); - - await service.setPin('1234', forAppWide: true); - - final valid = await service.verifyPin('0000', forAppWide: true); - expect(valid, isFalse); - }); - - test('verifyPin with forAppWide:false checks messages PIN', () async { - final service = AppLockService(); - await service.init(); - - await service.setPin('5678', forAppWide: false); - - final valid = await service.verifyPin('5678', forAppWide: false); - expect(valid, isTrue); - }); - - test('onUnlocked resets lock state', () async { - final service = AppLockService(); - await service.init(); - - await service.setPin('1234', forAppWide: true); - service.onBackgrounded(); - expect(service.shouldLockOnResume, isTrue); - - service.onUnlocked(); - expect(service.shouldLockOnResume, isFalse); - expect(service.isShowingLock, isFalse); - }); - }); - - group('AppLockService — PIN management', () { - test('hasPin returns true after PIN is set', () async { - final service = AppLockService(); - await service.init(); - - expect(service.hasPin, isFalse); - - await service.setPin('1234', forAppWide: true); - expect(service.hasPin, isTrue); - }); - - test('verifyPin returns false when no PIN is set', () async { - final service = AppLockService(); - await service.init(); - - final valid = await service.verifyPin('1234', forAppWide: true); - expect(valid, isFalse); - }); - }); -} diff --git a/test/services/dm_ghost_injection_test.dart b/test/services/dm_ghost_injection_test.dart deleted file mode 100644 index af03912..0000000 --- a/test/services/dm_ghost_injection_test.dart +++ /dev/null @@ -1,93 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:focusgram/focus_settings.dart'; -import 'package:focusgram/scripts/focus_scripts.dart'; - -void main() { - group('FocusSettings — Field cleanup', () { - test( - 'only ghostMode remains (fullDmGhost and storyGhost removed)', - () async { - const settings = FocusSettings(ghostMode: true); - - expect(settings.ghostMode, isTrue); - expect(settings.noAds, isTrue); - expect(settings.noStories, isFalse); - expect(settings.noReels, isFalse); - expect(settings.noAutoplay, isFalse); - expect(settings.noDMs, isFalse); - - // Verify fullDmGhost and storyGhost are NOT fields anymore - // (these would be compile errors if they existed) - }, - ); - - test('default ghostMode is false', () async { - const settings = FocusSettings(); - expect(settings.ghostMode, isFalse); - }); - }); - - group('buildUserScripts — DM Ghost injection', () { - test('injects kFullDmGhostJS when ghostMode is true', () async { - final scripts = buildUserScripts(const FocusSettings(ghostMode: true)); - - expect(scripts.length, equals(1)); - expect( - scripts[0].injectionTime, - equals(UserScriptInjectionTime.AT_DOCUMENT_START), - ); - - // Verify the comprehensive Full DM ghost JS is injected - final src = scripts[0].source; - expect(src, contains('__fgFullDmGhost=true')); - expect(src, contains('__fgFullDmGhostPatched')); - expect(src, contains('shouldBlockDmPath')); - expect(src, contains('DM_URLS')); - expect(src, contains('DM_OPS')); - expect(src, contains('serviceWorker')); - expect(src, contains('sendBeacon')); - }); - - test('does NOT inject ghost scripts when ghostMode is false', () async { - final scripts = buildUserScripts(const FocusSettings(ghostMode: false)); - - // Should have no DOCUMENT_START scripts - final startScripts = scripts.where( - (s) => s.injectionTime == UserScriptInjectionTime.AT_DOCUMENT_START, - ); - for (final s in startScripts) { - expect(s.source.contains('__fgFullDmGhost'), isFalse); - } - }); - - test('injects noAutoplay alongside DM Ghost', () async { - final scripts = buildUserScripts( - const FocusSettings(ghostMode: true, noAutoplay: true), - ); - - final startScripts = scripts.where( - (s) => s.injectionTime == UserScriptInjectionTime.AT_DOCUMENT_START, - ); - expect(startScripts.length, equals(1)); - expect(startScripts.first.source, contains('__fgFullDmGhost=true')); - expect(startScripts.first.source, contains('document.addEventListener')); - }); - - test( - 'injects hideStoryTray at DOCUMENT_END when noStories is true', - () async { - final scripts = buildUserScripts(const FocusSettings(noStories: true)); - - final endScripts = scripts.where( - (s) => s.injectionTime == UserScriptInjectionTime.AT_DOCUMENT_END, - ); - expect(endScripts.length, equals(1)); - expect( - endScripts.first.source, - contains('[data-pagelet="story_tray"]'), - ); - }, - ); - }); -} diff --git a/test/services/focusgram_router_test.dart b/test/services/focusgram_router_test.dart deleted file mode 100644 index b064783..0000000 --- a/test/services/focusgram_router_test.dart +++ /dev/null @@ -1,66 +0,0 @@ -// test/services/focusgram_router_test.dart -// -// Tests for FocusGramRouter — the lightweight cross-widget URL notifier. -// -// Run with: flutter test test/services/focusgram_router_test.dart - -import 'package:flutter_test/flutter_test.dart'; -import 'package:focusgram/services/focusgram_router.dart'; - -void main() { - // Reset between tests so state does not bleed across - tearDown(() { - FocusGramRouter.pendingUrl.value = null; - }); - - group('FocusGramRouter.pendingUrl', () { - test('initial value is null', () { - expect(FocusGramRouter.pendingUrl.value, isNull); - }); - - test('can be set to a URL string', () { - FocusGramRouter.pendingUrl.value = - 'https://www.instagram.com/accounts/settings/'; - expect( - FocusGramRouter.pendingUrl.value, - 'https://www.instagram.com/accounts/settings/', - ); - }); - - test('can be cleared back to null', () { - FocusGramRouter.pendingUrl.value = 'https://www.instagram.com/'; - FocusGramRouter.pendingUrl.value = null; - expect(FocusGramRouter.pendingUrl.value, isNull); - }); - - test('notifies listeners when value changes', () { - bool notified = false; - void listener() => notified = true; - FocusGramRouter.pendingUrl.addListener(listener); - - FocusGramRouter.pendingUrl.value = 'https://www.instagram.com/'; - - expect(notified, isTrue); - FocusGramRouter.pendingUrl.removeListener(listener); - }); - - test('does NOT notify when value is set to same value', () { - FocusGramRouter.pendingUrl.value = 'https://x.com/'; - int callCount = 0; - void listener() => callCount++; - FocusGramRouter.pendingUrl.addListener(listener); - - // Setting to the exact same value should not notify - FocusGramRouter.pendingUrl.value = 'https://x.com/'; - expect(callCount, 0); - - FocusGramRouter.pendingUrl.removeListener(listener); - }); - - test('is a singleton — same instance across multiple accesses', () { - final a = FocusGramRouter.pendingUrl; - final b = FocusGramRouter.pendingUrl; - expect(identical(a, b), isTrue); - }); - }); -} diff --git a/test/services/ghost_mode_focus_scripts_test.dart b/test/services/ghost_mode_focus_scripts_test.dart deleted file mode 100644 index 8795483..0000000 --- a/test/services/ghost_mode_focus_scripts_test.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:focusgram/focus_settings.dart'; -import 'package:focusgram/scripts/focus_scripts.dart'; - -void main() { - group('FocusSettings — Field cleanup', () { - test( - 'only ghostMode remains (fullDmGhost and storyGhost removed)', - () async { - const settings = FocusSettings(ghostMode: true); - - expect(settings.ghostMode, isTrue); - expect(settings.noAds, isTrue); - expect(settings.noStories, isFalse); - expect(settings.noReels, isFalse); - expect(settings.noAutoplay, isFalse); - expect(settings.noDMs, isFalse); - - // Verify fullDmGhost and storyGhost are NOT fields anymore - // (these would be compile errors if they existed) - }, - ); - - test('default ghostMode is false', () async { - const settings = FocusSettings(); - expect(settings.ghostMode, isFalse); - }); - }); - - group('buildUserScripts — Ghost mode injection', () { - test('injects kFullDmGhostJS when ghostMode is true', () async { - final scripts = buildUserScripts(const FocusSettings(ghostMode: true)); - - // Should have exactly 1 DOCUMENT_START script - expect(scripts.length, equals(1)); - expect( - scripts[0].injectionTime, - equals(UserScriptInjectionTime.AT_DOCUMENT_START), - ); - - // The script source should contain the Full DM ghost code - expect(scripts[0].source, contains('__fgFullDmGhost=true')); - expect(scripts[0].source, contains('__fgFullDmGhostPatched')); - }); - - test('does NOT inject ghost scripts when ghostMode is false', () async { - final scripts = buildUserScripts(const FocusSettings(ghostMode: false)); - - // Should have no start scripts (ghostMode is the only start-level script) - // unless other features like noAutoplay are also false - if (scripts.isEmpty) return; - - // If scripts exist (e.g. noAutoplay), verify ghost mode NOT in them - for (final s in scripts) { - expect(s.source.contains('__fgFullDmGhost'), isFalse); - } - }); - - test('injects noAutoplay when set', () async { - final scripts = buildUserScripts( - const FocusSettings(ghostMode: true, noAutoplay: true), - ); - - // Should have 1 DOCUMENT_START script combining ghost + autoplay - final startScripts = scripts.where( - (s) => s.injectionTime == UserScriptInjectionTime.AT_DOCUMENT_START, - ); - expect(startScripts.length, equals(1)); - expect(startScripts.first.source, contains('__fgFullDmGhost=true')); - expect(startScripts.first.source, contains('document.addEventListener')); - }); - }); -} diff --git a/test/services/ghost_mode_settings_test.dart b/test/services/ghost_mode_settings_test.dart deleted file mode 100644 index 67ef08a..0000000 --- a/test/services/ghost_mode_settings_test.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:focusgram/services/settings_service.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() { - SharedPreferences.setMockInitialValues({}); - }); - - group('SettingsService — Ghost mode toggle', () { - test('ghostMode defaults to false', () async { - final s = SettingsService(); - await s.init(); - expect(s.ghostMode, isFalse); - }); - - test('ghostMode toggle persists and loads on restart', () async { - final s = SettingsService(); - await s.init(); - await s.setGhostMode(true); - - expect(s.ghostMode, isTrue); - - // Simulate restart by creating a new instance with saved prefs - final s2 = SettingsService(); - await s2.init(); - expect(s2.ghostMode, isTrue); - }); - - test('ghostMode toggles off correctly', () async { - final s = SettingsService(); - await s.init(); - await s.setGhostMode(true); - expect(s.ghostMode, isTrue); - - await s.setGhostMode(false); - expect(s.ghostMode, isFalse); - }); - }); - - group('SettingsService — Grayscale persistence', () { - test('grayscaleEnabled defaults to false', () async { - final s = SettingsService(); - await s.init(); - expect(s.grayscaleEnabled, isFalse); - }); - - test('setGrayscaleEnabled persists and isActiveNow returns true', () async { - final s = SettingsService(); - await s.init(); - await s.setGrayscaleEnabled(true); - - expect(s.grayscaleEnabled, isTrue); - expect(s.isGrayscaleActiveNow, isTrue); - - // Simulate restart - final s2 = SettingsService(); - await s2.init(); - expect(s2.grayscaleEnabled, isTrue); - }); - - test('isGrayscaleActiveNow returns true when toggle is on', () async { - final s = SettingsService(); - await s.init(); - await s.setGrayscaleEnabled(true); - expect(s.isGrayscaleActiveNow, isTrue); - }); - - test( - 'isGrayscaleActiveNow returns false when toggle off and no schedules', - () async { - final s = SettingsService(); - await s.init(); - expect(s.isGrayscaleActiveNow, isFalse); - }, - ); - }); -} diff --git a/test/services/ghost_mode_url_patterns_test.dart b/test/services/ghost_mode_url_patterns_test.dart deleted file mode 100644 index d72c2a4..0000000 --- a/test/services/ghost_mode_url_patterns_test.dart +++ /dev/null @@ -1,249 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; - -void main() { - // The regex patterns used in shouldInterceptRequest for DM Ghost blocking. - // These are the same patterns embedded in main_webview_page.dart. - final seenPattern = RegExp( - r'/api/v1/media/[\w-]+/seen/|' - r'/api/v1/stories/reel/seen/|' - r'/api/v1/direct_v2/threads/[\w-]+/seen/|' - r'/api/v1/direct_v2/visual_message/[\w-]+/seen/|' - r'/api/v1/live/[\w-]+/comment/seen/|' - r'/api/v1/direct_v2/threads/[^/]+/mark_item_seen/|' - r'/api/v1/direct_v2/mark_item_seen/|' - r'/api/v1/direct_v2/threads/[^/]+/items/[^/]+/mark_visual_item_seen/|' - r'/api/v1/direct_v2/visual_thread/[^/]+/seen/|' - r'/api/v1/direct_v2/threads/[^/]+/items/[^/]+/mark_audio_seen/|' - r'/api/v1/live/[^/]+/join/|' - r'/api/v1/live/[^/]+/get_join_requests/|' - r'/api/v1/media/seen/|' - r'/api/v1/feed/viewed_story/|' - r'/api/v1/feed/reels_tray/seen/|' - r'/api/v1/qe/|' - r'/api/v1/launcher/sync/|' - r'/api/v1/logging/|' - r'/api/v1/fb_onetap_logging/|' - r'/ajax/bz|' - r'/ajax/logging/|' - r'/api/v1/stats/|' - r'/api/v1/fbanalytics/', - ); - - group('DM Ghost — Seen endpoint pattern matching', () { - // ── Story seen endpoints ─────────────────────────────────── - test('blocks /api/v1/media/{id}/seen/', () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/media/12345/seen/', - ), - isTrue, - ); - }); - - test('blocks /api/v1/stories/reel/seen/', () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/stories/reel/seen/', - ), - isTrue, - ); - }); - - test('blocks /api/v1/feed/viewed_story/', () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/feed/viewed_story/', - ), - isTrue, - ); - }); - - test('blocks /api/v1/feed/reels_tray/seen/', () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/feed/reels_tray/seen/', - ), - isTrue, - ); - }); - - // ── DM read receipts ────────────────────────────────────── - test('blocks /api/v1/direct_v2/threads/{id}/mark_item_seen/', () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/direct_v2/threads/abc123/mark_item_seen/', - ), - isTrue, - ); - }); - - test('blocks /api/v1/direct_v2/mark_item_seen/', () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/direct_v2/mark_item_seen/', - ), - isTrue, - ); - }); - - test('blocks /api/v1/direct_v2/threads/{id}/seen/', () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/direct_v2/threads/abc123/seen/', - ), - isTrue, - ); - }); - - test('blocks /api/v1/direct_v2/visual_message/{id}/seen/', () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/direct_v2/visual_message/xyz/seen/', - ), - isTrue, - ); - }); - - // ── Ephemeral / visual seen ─────────────────────────────── - test( - 'blocks /api/v1/direct_v2/threads/{id}/items/{id}/mark_visual_item_seen/', - () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/direct_v2/threads/abc/items/def/mark_visual_item_seen/', - ), - isTrue, - ); - }, - ); - - test('blocks /api/v1/direct_v2/visual_thread/{id}/seen/', () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/direct_v2/visual_thread/abc/seen/', - ), - isTrue, - ); - }); - - // ── Audio seen ──────────────────────────────────────────── - test('blocks /api/v1/direct_v2/threads/{id}/items/{id}/mark_audio_seen/', () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/direct_v2/threads/abc/items/def/mark_audio_seen/', - ), - isTrue, - ); - }); - - // ── Live ────────────────────────────────────────────────── - test('blocks /api/v1/live/{id}/join/', () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/live/abc123/join/', - ), - isTrue, - ); - }); - - test('blocks /api/v1/live/{id}/get_join_requests/', () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/live/abc123/get_join_requests/', - ), - isTrue, - ); - }); - - test('blocks /api/v1/live/{id}/comment/seen/', () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/live/abc123/comment/seen/', - ), - isTrue, - ); - }); - - // ── Analytics / tracking ────────────────────────────────── - test('blocks /api/v1/qe/', () { - expect( - seenPattern.hasMatch('https://www.instagram.com/api/v1/qe/some_param'), - isTrue, - ); - }); - - test('blocks /api/v1/launcher/sync/', () { - expect( - seenPattern.hasMatch('https://www.instagram.com/api/v1/launcher/sync/'), - isTrue, - ); - }); - - test('blocks /api/v1/logging/', () { - expect( - seenPattern.hasMatch('https://www.instagram.com/api/v1/logging/event'), - isTrue, - ); - }); - - test('blocks /api/v1/stats/', () { - expect( - seenPattern.hasMatch('https://www.instagram.com/api/v1/stats/'), - isTrue, - ); - }); - - test('blocks /api/v1/fbanalytics/', () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/fbanalytics/event', - ), - isTrue, - ); - }); - - test('blocks /ajax/bz', () { - expect(seenPattern.hasMatch('https://www.instagram.com/ajax/bz'), isTrue); - }); - - test('blocks /ajax/logging/', () { - expect( - seenPattern.hasMatch('https://www.instagram.com/ajax/logging/'), - isTrue, - ); - }); - - // ── Should NOT block legitimate endpoints ───────────────── - test('does NOT block normal feed timeline request', () { - expect( - seenPattern.hasMatch('https://www.instagram.com/api/v1/feed/timeline/'), - isFalse, - ); - }); - - test('does NOT block graphql queries', () { - expect( - seenPattern.hasMatch('https://www.instagram.com/api/graphql'), - isFalse, - ); - }); - - test('does NOT block direct_v2 inbox', () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/direct_v2/inbox/', - ), - isFalse, - ); - }); - - test('does NOT block user posts', () { - expect( - seenPattern.hasMatch( - 'https://www.instagram.com/api/v1/users/12345/posts/', - ), - isFalse, - ); - }); - }); -} diff --git a/test/services/injection_manager_test.dart b/test/services/injection_manager_test.dart deleted file mode 100644 index 5c4fccc..0000000 --- a/test/services/injection_manager_test.dart +++ /dev/null @@ -1,155 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:focusgram/services/injection_manager.dart'; -import 'package:focusgram/services/session_manager.dart'; -import 'package:focusgram/services/settings_service.dart'; - -class _FakeJsEvaluator implements JsEvaluator { - final List sources = []; - - @override - Future evaluateJavascript({required String source}) async { - sources.add(source); - } -} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() { - SharedPreferences.setMockInitialValues({}); - }); - - test( - 'does NOT inject hideSuggestedPosts JS even when legacy setting is true', - () async { - final prefs = await SharedPreferences.getInstance(); - final sm = SessionManager(); - final fakeEval = _FakeJsEvaluator(); - - final mgr = InjectionManager.forTest( - jsEvaluator: fakeEval, - prefs: prefs, - sessionManager: sm, - ); - - final settings = SettingsService(); - await settings.init(); - await settings.setHideSuggestedPosts(true); - - mgr.setSettingsService(settings); - - await mgr.runAllPostLoadInjections('https://www.instagram.com/'); - - final any = fakeEval.sources.any((s) => s.contains('hideSuggestedPosts')); - expect(any, isFalse); - }, - ); - - test( - 'does NOT inject hideSuggestedPosts JS when settings.hideSuggestedPosts=false', - () async { - final prefs = await SharedPreferences.getInstance(); - final sm = SessionManager(); - final fakeEval = _FakeJsEvaluator(); - - final mgr = InjectionManager.forTest( - jsEvaluator: fakeEval, - prefs: prefs, - sessionManager: sm, - ); - - final settings = SettingsService(); - await settings.init(); - await settings.setHideSuggestedPosts(false); - - mgr.setSettingsService(settings); - - await mgr.runAllPostLoadInjections('https://www.instagram.com/'); - - final any = fakeEval.sources.any((s) => s.contains('hideSuggestedPosts')); - expect(any, isFalse); - }, - ); - - test( - 'injects video downloader JS only when settings.videoDownloadEnabled=true', - () async { - final prefs = await SharedPreferences.getInstance(); - final sm = SessionManager(); - final fakeEval = _FakeJsEvaluator(); - - final mgr = InjectionManager.forTest( - jsEvaluator: fakeEval, - prefs: prefs, - sessionManager: sm, - ); - - final settings = SettingsService(); - await settings.init(); - await settings.setVideoDownloadEnabled(true); - - mgr.setSettingsService(settings); - - await mgr.runAllPostLoadInjections('https://www.instagram.com/'); - - final any = fakeEval.sources.any( - (s) => s.contains('__fgMediaDownloadRunning'), - ); - expect(any, isTrue); - }, - ); - - test( - 'does NOT inject video downloader JS when settings.videoDownloadEnabled=false', - () async { - final prefs = await SharedPreferences.getInstance(); - final sm = SessionManager(); - final fakeEval = _FakeJsEvaluator(); - - final mgr = InjectionManager.forTest( - jsEvaluator: fakeEval, - prefs: prefs, - sessionManager: sm, - ); - - final settings = SettingsService(); - await settings.init(); - await settings.setVideoDownloadEnabled(false); - - mgr.setSettingsService(settings); - - await mgr.runAllPostLoadInjections('https://www.instagram.com/'); - - final any = fakeEval.sources.any( - (s) => s.contains('__fgMediaDownloadRunning'), - ); - expect(any, isFalse); - }, - ); - - test('injects home feed scroll lock flag when enabled', () async { - final prefs = await SharedPreferences.getInstance(); - final sm = SessionManager(); - final fakeEval = _FakeJsEvaluator(); - - final mgr = InjectionManager.forTest( - jsEvaluator: fakeEval, - prefs: prefs, - sessionManager: sm, - ); - - final settings = SettingsService(); - await settings.init(); - await settings.setBlockHomeFeedScrollInternal(true); - - mgr.setSettingsService(settings); - - await mgr.runAllPostLoadInjections('https://www.instagram.com/'); - - final any = fakeEval.sources.any( - (s) => s.contains('window.__fgBlockHomeFeedScroll = true;'), - ); - expect(any, isTrue); - }); -} diff --git a/test/services/level_service_test.dart b/test/services/level_service_test.dart deleted file mode 100644 index 4536e54..0000000 --- a/test/services/level_service_test.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:hive_flutter/hive_flutter.dart'; -import 'package:focusgram/services/level_service.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() async { - SharedPreferences.setMockInitialValues({}); - if (!Hive.isAdapterRegistered(0)) { - await Hive.initFlutter(); - } - }); - - group('AppFeature — Your Journey unlock table', () { - test('fullDmGhost is NOT in the all list', () async { - expect(AppFeature.fullDmGhost, isNotNull); - - final contains = AppFeature.all.any((f) => f.id == 'full_dm_ghost'); - expect(contains, isFalse); - }); - - test('storyGhost and reelsHistory are NOT in the all list', () async { - final hasStory = AppFeature.all.any((f) => f.id == 'custom_friction'); - final hasReels = AppFeature.all.any((f) => f.id == 'reels_history'); - expect(hasStory, isFalse); - expect(hasReels, isFalse); - }); - - test('all list contains only active features', () async { - final ids = AppFeature.all.map((f) => f.id).toSet(); - expect(ids, contains('ghost_mode')); - expect(ids, contains('effort_friction')); - expect(ids, contains('download_media')); - expect(ids, contains('bait_me')); - expect(ids, contains('app_lock')); - expect(ids.length, equals(5)); - }); - }); - - group('LevelService — No Firestore dependency', () { - test('init succeeds without Firestore (uses Hive only)', () async { - final levelService = LevelService(); - - await expectLater(() => levelService.init(), returnsNormally); - - expect(levelService.level, equals(1)); - expect(levelService.xp, equals(0)); - }); - - test('addXpForAd awards XP without Firestore', () async { - final levelService = LevelService(); - await levelService.init(); - - await levelService.addXpForAd(); - - expect(levelService.xp, greaterThan(0)); - expect(levelService.adsWatchedTotal, equals(1)); - }); - - test('level progresses from XP', () async { - final levelService = LevelService(); - await levelService.init(); - - expect(levelService.level, equals(1)); - expect(levelService.xp, equals(0)); - expect(levelService.levelProgress, equals(0.0)); - }); - }); -} diff --git a/test/services/navigation_guard_test.dart b/test/services/navigation_guard_test.dart deleted file mode 100644 index 0a18007..0000000 --- a/test/services/navigation_guard_test.dart +++ /dev/null @@ -1,193 +0,0 @@ -// test/services/navigation_guard_test.dart -// -// Tests for NavigationGuard — the URL allow/block logic. -// -// Run with: flutter test test/services/navigation_guard_test.dart - -import 'package:flutter_test/flutter_test.dart'; -import 'package:focusgram/services/navigation_guard.dart'; - -void main() { - group('NavigationGuard.evaluate', () { - // ── Instagram domain — allowed ────────────────────────────────────────── - - test('allows root instagram.com', () { - final d = NavigationGuard.evaluate(url: 'https://www.instagram.com/'); - expect(d.blocked, isFalse); - }); - - test('allows profile pages', () { - final d = NavigationGuard.evaluate( - url: 'https://www.instagram.com/someuser/', - ); - expect(d.blocked, isFalse); - }); - - test('allows DM inbox', () { - final d = NavigationGuard.evaluate( - url: 'https://www.instagram.com/direct/inbox/', - ); - expect(d.blocked, isFalse); - }); - - test('allows Explore page', () { - final d = NavigationGuard.evaluate( - url: 'https://www.instagram.com/explore/', - ); - expect(d.blocked, isFalse); - }); - - test('allows instagram.com without www', () { - final d = NavigationGuard.evaluate( - url: 'https://instagram.com/accounts/login/', - ); - expect(d.blocked, isFalse); - }); - - test('allows a specific reel URL from a DM share', () { - final d = NavigationGuard.evaluate( - url: 'https://www.instagram.com/reel/ABC123xyz/', - ); - expect(d.blocked, isFalse); - }); - - test('allows login page', () { - final d = NavigationGuard.evaluate( - url: 'https://www.instagram.com/accounts/login/', - ); - expect(d.blocked, isFalse); - }); - - // ── Reels FEED tab — blocked ──────────────────────────────────────────── - - test('blocks the reels feed tab /reels/', () { - final d = NavigationGuard.evaluate( - url: 'https://www.instagram.com/reels/', - ); - expect(d.blocked, isTrue); - expect(d.reason, isNotNull); - }); - - test('blocks the reels feed tab /reels (no trailing slash)', () { - final d = NavigationGuard.evaluate( - url: 'https://www.instagram.com/reels', - ); - expect(d.blocked, isTrue); - }); - - test('blocks the reels feed with our FocusGram query param', () { - // NavigationDelegate fires this URL from _strictReelsBlockJS - final d = NavigationGuard.evaluate( - url: 'https://www.instagram.com/reels/?fg=blocked', - ); - // NOTE: The guard blocks /reels/ root; query params don't change outcome - // This is expected to be blocked since it matches _reelsFeedRegex - expect(d.blocked, isTrue); - }); - - // ── External domains — blocked ────────────────────────────────────────── - - test('blocks external HTTP domains', () { - final d = NavigationGuard.evaluate(url: 'https://evil.com/phish'); - expect(d.blocked, isTrue); - expect(d.reason, contains('External domain')); - }); - - test('blocks Facebook redirects', () { - final d = NavigationGuard.evaluate( - url: 'https://facebook.com/redirect?url=...', - ); - expect(d.blocked, isTrue); - }); - - test('blocks l.instagram.com tracking redirect', () { - final d = NavigationGuard.evaluate( - url: 'https://l.instagram.com/?u=https%3A%2F%2Fexample.com', - ); - // l.instagram.com is not in _allowedHosts → blocked - expect(d.blocked, isTrue); - }); - - // ── Non-HTTP schemes — allowed ────────────────────────────────────────── - - test('allows about:blank', () { - final d = NavigationGuard.evaluate(url: 'about:blank'); - expect(d.blocked, isFalse); - }); - - test('allows data: URIs', () { - final d = NavigationGuard.evaluate(url: 'data:text/html,hello'); - expect(d.blocked, isFalse); - }); - - // ── Invalid URL — safe fallback ───────────────────────────────────────── - - test('does not throw on empty string — returns not blocked', () { - final d = NavigationGuard.evaluate(url: ''); - expect(d.blocked, isFalse); - }); - - test('does not throw on malformed URL', () { - final d = NavigationGuard.evaluate(url: ':::bad:::'); - expect(d.blocked, isFalse); - }); - }); - - group('NavigationGuard.isSpecificReel', () { - test('returns true for a real reel URL', () { - expect( - NavigationGuard.isSpecificReel( - 'https://www.instagram.com/reel/ABC123/', - ), - isTrue, - ); - }); - - test('returns true for reel URL without trailing slash', () { - expect( - NavigationGuard.isSpecificReel('https://www.instagram.com/reel/XYZ'), - isTrue, - ); - }); - - test('returns false for the reels FEED root', () { - expect( - NavigationGuard.isSpecificReel('https://www.instagram.com/reels/'), - isFalse, - ); - }); - - test('returns false for profile URL', () { - expect( - NavigationGuard.isSpecificReel('https://www.instagram.com/someuser/'), - isFalse, - ); - }); - - test('returns false for DM inbox', () { - expect( - NavigationGuard.isSpecificReel( - 'https://www.instagram.com/direct/inbox/', - ), - isFalse, - ); - }); - - test('returns false for empty string', () { - expect(NavigationGuard.isSpecificReel(''), isFalse); - }); - }); - - group('BlockDecision', () { - test('const constructor fields are accessible', () { - const d = BlockDecision(blocked: true, reason: 'test'); - expect(d.blocked, isTrue); - expect(d.reason, 'test'); - }); - - test('reason can be null', () { - const d = BlockDecision(blocked: false, reason: null); - expect(d.reason, isNull); - }); - }); -} diff --git a/test/services/reels_blocker_test.dart b/test/services/reels_blocker_test.dart deleted file mode 100644 index 4500a11..0000000 --- a/test/services/reels_blocker_test.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:focusgram/services/injection_controller.dart'; - -void main() { - group('InjectionController reels blocker', () { - test('includes strict reels blocker JS when sessionActive=false', () { - final js = InjectionController.buildInjectionJS( - sessionActive: false, - blurExplore: false, - blurReels: false, - tapToUnblur: false, - enableTextSelection: false, - hideSuggestedPosts: false, - hideSponsoredPosts: false, - hideLikeCounts: false, - hideFollowerCounts: false, - hideExploreTab: false, - hideReelsTab: false, - hideShopTab: false, - disableReelsEntirely: false, - blockHomeFeedScroll: false, - ); - - expect(js, contains('window.__fgReelsBlockPatched')); - expect(js, contains("window.location.href = '/reels/?fg=blocked';")); - }); - - test( - 'does NOT include strict reels blocker JS when sessionActive=true', - () { - final js = InjectionController.buildInjectionJS( - sessionActive: true, - blurExplore: false, - blurReels: false, - tapToUnblur: false, - enableTextSelection: false, - hideSuggestedPosts: false, - hideSponsoredPosts: false, - hideLikeCounts: false, - hideFollowerCounts: false, - hideExploreTab: false, - hideReelsTab: false, - hideShopTab: false, - disableReelsEntirely: false, - blockHomeFeedScroll: false, - ); - - expect(js, isNot(contains('window.__fgReelsBlockPatched'))); - expect( - js, - isNot(contains("window.location.href = '/reels/?fg=blocked';")), - ); - }, - ); - }); -} diff --git a/test/services/screen_time_service_test.dart b/test/services/screen_time_service_test.dart deleted file mode 100644 index aa46dda..0000000 --- a/test/services/screen_time_service_test.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:focusgram/services/screen_time_service.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() { - SharedPreferences.setMockInitialValues({}); - }); - - test('init loads persisted secondsByDate', () async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setString( - ScreenTimeService.prefKey, - '{"2026-01-01": 42, "2026-01-02": 7}', - ); - - final s = ScreenTimeService(); - await s.init(); - - expect(s.secondsByDate['2026-01-01'], 42); - expect(s.secondsByDate['2026-01-02'], 7); - }); - - test('resetAll clears stored data and in-memory map', () async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setString(ScreenTimeService.prefKey, '{"2026-01-01": 42}'); - - final s = ScreenTimeService(); - await s.init(); - expect(s.secondsByDate.isNotEmpty, isTrue); - - await s.resetAll(); - expect(s.secondsByDate, isEmpty); - - final raw = prefs.getString(ScreenTimeService.prefKey); - expect(raw, isNull); - }); - - test( - 'startTracking increments today seconds and stopTracking persists', - () async { - final s = ScreenTimeService(); - await s.init(); - - final beforeTodayKey = DateTime.now(); - final todayKey = - '${beforeTodayKey.year.toString().padLeft(4, '0')}-' - '${beforeTodayKey.month.toString().padLeft(2, '0')}-' - '${beforeTodayKey.day.toString().padLeft(2, '0')}'; - - s.startTracking(); - - // Wait ~2 seconds (test is unit-ish; still acceptable). - await Future.delayed(const Duration(seconds: 2)); - - s.stopTracking(); - - expect(s.secondsByDate[todayKey], isNotNull); - expect(s.secondsByDate[todayKey]!, greaterThanOrEqualTo(2)); - - final prefs = await SharedPreferences.getInstance(); - final stored = prefs.getString(ScreenTimeService.prefKey); - expect(stored, isNotNull); - expect(stored, contains(todayKey)); - }, - ); -} diff --git a/test/services/session_extension_test.dart b/test/services/session_extension_test.dart deleted file mode 100644 index 68207f0..0000000 --- a/test/services/session_extension_test.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:focusgram/services/session_manager.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() { - SharedPreferences.setMockInitialValues({}); - }); - - group('SessionManager — Extension flow', () { - test('canExtendAppSession is true when session just ended', () async { - final sm = SessionManager(); - await sm.init(); - - // Start an app session - sm.startAppSession(60); - expect(sm.isSessionActive, isTrue); - - // End it - sm.endAppSession(); - expect(sm.isAppSessionExpired, isTrue); - expect(sm.canExtendAppSession, isTrue); - }); - - test('extendAppSession sets canExtendAppSession to false', () async { - final sm = SessionManager(); - await sm.init(); - - sm.startAppSession(60); - sm.endAppSession(); - expect(sm.canExtendAppSession, isTrue); - - sm.extendAppSession(); - expect(sm.canExtendAppSession, isFalse); - expect(sm.isSessionActive, isTrue); - }); - - test( - 'canExtendAppSession is false after re-ending an extended session', - () async { - final sm = SessionManager(); - await sm.init(); - - sm.startAppSession(60); - sm.endAppSession(); - sm.extendAppSession(); - sm.endAppSession(); - - expect(sm.canExtendAppSession, isFalse); - }, - ); - }); - - group('SessionManager — App session lifecycle', () { - test('startAppSession sets isSessionActive', () async { - final sm = SessionManager(); - await sm.init(); - - sm.startAppSession(30); - expect(sm.isSessionActive, isTrue); - }); - - test('endAppSession clears session and sets expired', () async { - final sm = SessionManager(); - await sm.init(); - - sm.startAppSession(30); - sm.endAppSession(); - - expect(sm.isSessionActive, isFalse); - expect(sm.isAppSessionExpired, isTrue); - }); - }); -} diff --git a/test/services/session_manager_test.dart b/test/services/session_manager_test.dart deleted file mode 100644 index 0031f1a..0000000 --- a/test/services/session_manager_test.dart +++ /dev/null @@ -1,293 +0,0 @@ -// test/services/session_manager_test.dart -// -// Tests for SessionManager — reel session logic, daily quotas, cooldowns, -// app session tracking, and scheduled blocking. -// -// SessionManager uses SharedPreferences and Timer internally. -// We use SharedPreferences.setMockInitialValues({}) for isolation. -// Timer-based tests use FakeAsync where needed. -// -// Run with: flutter test test/services/session_manager_test.dart - -import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:focusgram/services/session_manager.dart'; - -// Helper: initialise a SessionManager with a clean SharedPreferences slate. -Future _freshManager() async { - SharedPreferences.setMockInitialValues({}); - final sm = SessionManager(); - await sm.init(); - return sm; -} - -void main() { - // ── Initial state ──────────────────────────────────────────────────────── - - group('SessionManager — initial state', () { - test('no reel session active on first init', () async { - final sm = await _freshManager(); - expect(sm.isSessionActive, isFalse); - }); - - test('remainingSessionSeconds is 0 when no session', () async { - final sm = await _freshManager(); - expect(sm.remainingSessionSeconds, 0); - }); - - test('dailyUsedSeconds starts at 0', () async { - final sm = await _freshManager(); - expect(sm.dailyUsedSeconds, 0); - }); - - test('dailyRemainingSeconds matches default limit', () async { - final sm = await _freshManager(); - expect(sm.dailyRemainingSeconds, sm.dailyLimitSeconds); - }); - - test('isDailyLimitExhausted is false initially', () async { - final sm = await _freshManager(); - expect(sm.isDailyLimitExhausted, isFalse); - }); - - test('isCooldownActive is false initially', () async { - final sm = await _freshManager(); - expect(sm.isCooldownActive, isFalse); - }); - - test('cooldownRemainingSeconds is 0 when no cooldown', () async { - final sm = await _freshManager(); - expect(sm.cooldownRemainingSeconds, 0); - }); - - test('daily open count is incremented on init', () async { - final sm = await _freshManager(); - // init() calls _incrementOpenCount once - expect(sm.dailyOpenCount, 1); - }); - }); - - // ── Reel session — startSession ────────────────────────────────────────── - - group('SessionManager.startSession', () { - test('returns true and activates session within daily quota', () async { - final sm = await _freshManager(); - final ok = sm.startSession(5); - expect(ok, isTrue); - expect(sm.isSessionActive, isTrue); - }); - - test('session expires after requested minutes (approx)', () async { - final sm = await _freshManager(); - sm.startSession(5); - // Remaining should be <= 5 min = 300 s - expect(sm.remainingSessionSeconds, lessThanOrEqualTo(300)); - expect(sm.remainingSessionSeconds, greaterThan(290)); - }); - - test('returns false when daily limit is exhausted', () async { - SharedPreferences.setMockInitialValues({ - 'sessn_daily_limit_sec': 300, // 5 min daily limit - 'sessn_daily_used_sec': 300, // already used all of it - 'sessn_daily_date': _today(), - }); - final sm = SessionManager(); - await sm.init(); - final ok = sm.startSession(5); - expect(ok, isFalse); - expect(sm.isSessionActive, isFalse); - }); - - test('returns false during cooldown', () async { - // Last session ended 5 minutes ago; cooldown is 15 min - final lastEnd = DateTime.now().subtract(const Duration(minutes: 5)); - SharedPreferences.setMockInitialValues({ - 'sessn_last_end_ts': lastEnd.millisecondsSinceEpoch, - 'sessn_cooldown_sec': 900, // 15 min cooldown - 'sessn_daily_date': _today(), - }); - final sm = SessionManager(); - await sm.init(); - expect(sm.isCooldownActive, isTrue); - final ok = sm.startSession(5); - expect(ok, isFalse); - }); - - test('notifies listeners when session starts', () async { - final sm = await _freshManager(); - bool notified = false; - sm.addListener(() => notified = true); - sm.startSession(5); - expect(notified, isTrue); - }); - }); - - // ── Reel session — endSession ──────────────────────────────────────────── - - group('SessionManager.endSession', () { - test('deactivates an active session', () async { - final sm = await _freshManager(); - sm.startSession(5); - expect(sm.isSessionActive, isTrue); - sm.endSession(); - expect(sm.isSessionActive, isFalse); - }); - - test('remainingSessionSeconds becomes 0 after end', () async { - final sm = await _freshManager(); - sm.startSession(5); - sm.endSession(); - expect(sm.remainingSessionSeconds, 0); - }); - - test('cooldown becomes active after ending a session', () async { - final sm = await _freshManager(); - sm.startSession(5); - sm.endSession(); - // Only active if cooldownSeconds > 0 - if (sm.cooldownSeconds > 0) { - expect(sm.isCooldownActive, isTrue); - } - }); - - test('notifies listeners on end', () async { - final sm = await _freshManager(); - sm.startSession(5); - bool notified = false; - sm.addListener(() => notified = true); - sm.endSession(); - expect(notified, isTrue); - }); - }); - - // ── Daily quota ────────────────────────────────────────────────────────── - - group('SessionManager — daily quota', () { - test('dailyRemainingSeconds is capped at 0 when exhausted', () async { - SharedPreferences.setMockInitialValues({ - 'sessn_daily_limit_sec': 300, - 'sessn_daily_used_sec': 400, // over limit - 'sessn_daily_date': _today(), - }); - final sm = SessionManager(); - await sm.init(); - expect(sm.dailyRemainingSeconds, 0); - expect(sm.isDailyLimitExhausted, isTrue); - }); - - test('dailyRemainingSeconds decrements correctly', () async { - SharedPreferences.setMockInitialValues({ - 'sessn_daily_limit_sec': 600, - 'sessn_daily_used_sec': 100, - 'sessn_daily_date': _today(), - }); - final sm = SessionManager(); - await sm.init(); - expect(sm.dailyRemainingSeconds, 500); - }); - }); - - // ── App session ────────────────────────────────────────────────────────── - - group('SessionManager — app session', () { - test('app session is active when end is in the future', () async { - final future = DateTime.now().add(const Duration(minutes: 30)); - SharedPreferences.setMockInitialValues({ - 'app_sess_end_ts': future.millisecondsSinceEpoch, - 'sessn_daily_date': _today(), - }); - final sm = SessionManager(); - await sm.init(); - expect(sm.isAppSessionActive, isTrue); - }); - - test('app session is NOT active when end is in the past', () async { - final past = DateTime.now().subtract(const Duration(minutes: 10)); - SharedPreferences.setMockInitialValues({ - 'app_sess_end_ts': past.millisecondsSinceEpoch, - 'sessn_daily_date': _today(), - }); - final sm = SessionManager(); - await sm.init(); - expect(sm.isAppSessionActive, isFalse); - }); - - test('appSessionRemainingSeconds is > 0 for a future session', () async { - final future = DateTime.now().add(const Duration(minutes: 30)); - SharedPreferences.setMockInitialValues({ - 'app_sess_end_ts': future.millisecondsSinceEpoch, - 'sessn_daily_date': _today(), - }); - final sm = SessionManager(); - await sm.init(); - expect(sm.appSessionRemainingSeconds, greaterThan(0)); - }); - - test('canExtendAppSession is true by default', () async { - final sm = await _freshManager(); - expect(sm.canExtendAppSession, isTrue); - }); - - test('canExtendAppSession is false when extension used', () async { - SharedPreferences.setMockInitialValues({ - 'app_sess_ext_used': true, - 'sessn_daily_date': _today(), - }); - final sm = SessionManager(); - await sm.init(); - expect(sm.canExtendAppSession, isFalse); - }); - }); - - // ── Scheduled blocking ─────────────────────────────────────────────────── - - group('SessionManager — scheduled blocking', () { - test('isScheduledBlockActive is false when schedule disabled', () async { - final sm = await _freshManager(); - expect(sm.scheduleEnabled, isFalse); - expect(sm.isScheduledBlockActive, isFalse); - }); - - test('simple daytime range (9:00–17:00) blocks at noon', () async { - final sm = await _freshManager(); - // We can't control DateTime.now() but we CAN test the logic - // by verifying the method doesn't throw and returns a bool. - expect(sm.isScheduledBlockActive, isA()); - }); - }); - - // ── setAppForeground ───────────────────────────────────────────────────── - - group('SessionManager.setAppForeground', () { - test('does nothing when value is unchanged', () async { - final sm = await _freshManager(); - bool notified = false; - sm.addListener(() => notified = true); - sm.setAppForeground(true); // already true by default (in foreground) - expect(notified, isFalse); - }); - - test('notifies when transitioning to background', () async { - final sm = await _freshManager(); - bool notified = false; - sm.addListener(() => notified = true); - sm.setAppForeground(false); - expect(notified, isTrue); - }); - - test('notifies when returning to foreground', () async { - final sm = await _freshManager(); - sm.setAppForeground(false); // go to background first - bool notified = false; - sm.addListener(() => notified = true); - sm.setAppForeground(true); - expect(notified, isTrue); - }); - }); -} - -/// Returns today's date formatted as 'yyyy-MM-dd' (same format as SessionManager). -String _today() { - final now = DateTime.now(); - return '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}'; -} diff --git a/test/services/settings_service_test.dart b/test/services/settings_service_test.dart deleted file mode 100644 index b091887..0000000 --- a/test/services/settings_service_test.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:focusgram/services/settings_service.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() { - SharedPreferences.setMockInitialValues({}); - }); - - group('SettingsService — Phase 2 Extras', () { - test('defaults are OFF for video download/hide suggested', () async { - final s = SettingsService(); - await s.init(); - - expect(s.videoDownloadEnabled, isFalse); - expect(s.hideSuggestedPosts, isFalse); - }); - - test('setVideoDownloadEnabled persists', () async { - final s = SettingsService(); - await s.init(); - - await s.setVideoDownloadEnabled(true); - - final prefs = await SharedPreferences.getInstance(); - expect(s.videoDownloadEnabled, isTrue); - expect(prefs.getBool('video_download_enabled'), isTrue); - }); - - test('setHideSuggestedPosts persists', () async { - final s = SettingsService(); - await s.init(); - - await s.setHideSuggestedPosts(true); - - final prefs = await SharedPreferences.getInstance(); - expect(s.hideSuggestedPosts, isTrue); - expect(prefs.getBool('hide_suggested_posts'), isTrue); - }); - }); - - group('SettingsService — minimal mode', () { - test( - 'home feed scroll can be disabled while minimal mode stays on', - () async { - final s = SettingsService(); - await s.init(); - - await s.setMinimalModeEnabled(true); - await s.setBlockHomeFeedScrollInternal(false); - - expect(s.minimalModeEnabled, isTrue); - expect(s.blockHomeFeedScroll, isFalse); - - final prefs = await SharedPreferences.getInstance(); - expect(prefs.getBool('internal_block_home_feed_scroll'), isFalse); - expect(prefs.getBool('minimal_mode_enabled'), isTrue); - }, - ); - - test( - 'minimal mode turns off when all child features are disabled', - () async { - final s = SettingsService(); - await s.init(); - - await s.setMinimalModeEnabled(true); - await s.setBlurExplore(false); - await s.setBlockHomeFeedScrollInternal(false); - await s.setDisableReelsEntirelyInternal(false); - await s.setDisableExploreEntirelyInternal(false); - - expect(s.minimalModeEnabled, isFalse); - expect(s.blurExplore, isFalse); - expect(s.blockHomeFeedScroll, isFalse); - expect(s.disableReelsEntirely, isFalse); - expect(s.disableExploreEntirely, isFalse); - }, - ); - }); - - group('SettingsService — v2 filtering split', () { - test( - 'ad blocker and suggested posts toggles persist independently', - () async { - final s = SettingsService(); - await s.init(); - - await s.setV2AdBlockerDomEnabled(true); - await s.setContentSuggestedEnabled(true); - await s.setV2AdBlockerDomEnabled(false); - - expect(s.v2AdBlockerDomEnabled, isFalse); - expect(s.contentSuggested, isTrue); - expect(s.v2ContentHiderEnabled, isTrue); - - final prefs = await SharedPreferences.getInstance(); - expect(prefs.getBool('v2_adblock_dom_enabled'), isFalse); - expect(prefs.getBool('content_suggested'), isTrue); - }, - ); - }); -} diff --git a/v2/MainActivity.kt b/v2/MainActivity.kt deleted file mode 100644 index d7cdc8d..0000000 --- a/v2/MainActivity.kt +++ /dev/null @@ -1,84 +0,0 @@ -// android/app/src/main/kotlin/com/focusgram/focusgram/MainActivity.kt -// -// Ghost mode WebView integration notes - -package com.focusgram.focusgram - -import android.os.Bundle -import io.flutter.embedding.android.FlutterActivity -import io.flutter.embedding.engine.FlutterEngine - -class MainActivity : FlutterActivity() { - override fun configureFlutterEngine(flutterEngine: FlutterEngine) { - super.configureFlutterEngine(flutterEngine) - } -} - - -// ───────────────────────────────────────────────────────────────────────────── -// WEBVIEW WIDGET INTEGRATION -// ───────────────────────────────────────────────────────────────────────────── -// -// In your WebView widget (wherever InAppWebView is constructed): -// -// class InstagramWebView extends StatefulWidget { ... } -// -// class _InstagramWebViewState extends State { -// late GhostModeService _ghost; -// -// @override -// void initState() { -// super.initState(); -// _ghost = GhostModeService(); -// _ghost.load().then((_) { -// setState(() {}); -// }); -// } -// -// @override -// Widget build(BuildContext context) { -// return InAppWebView( -// initialUrlRequest: URLRequest( -// url: WebUri('https://www.instagram.com'), -// ), -// initialSettings: _ghost.buildWebViewSettings(), -// initialUserScripts: UnmodifiableListView(_ghost.buildUserScripts()), -// onWebViewCreated: (controller) { -// _ghost.onWebViewCreated(controller); -// }, -// onLoadStop: (controller, url) async { -// await _ghost.onPageLoaded(url?.uriValue); -// }, -// shouldInterceptRequest: (controller, request) { -// return _ghost.shouldInterceptRequest(controller, request); -// }, -// ); -// } -// } -// -// ───────────────────────────────────────────────────────────────────────────── -// PUBSPEC ADDITIONS -// ───────────────────────────────────────────────────────────────────────────── -// -// dependencies: -// flutter_inappwebview: ^6.1.5 # already present -// shared_preferences: ^2.3.0 -// -// ───────────────────────────────────────────────────────────────────────────── -// DEBUGGING: HOW TO VERIFY GHOST MODE WORKING -// ───────────────────────────────────────────────────────────────────────────── -// -// 1. Enable WebView remote debugging: -// In main.dart: if (kDebugMode) { InAppWebViewController.setWebContentsDebuggingEnabled(true); } -// -// 2. Open chrome://inspect in desktop Chrome while app runs on USB device. -// -// 3. In DevTools console, run: -// window.fetch('/api/v1/media/seen/test/', {method:'POST'}) -// .then(r => r.text()).then(console.log) -// → Should print: {"status":"ok"} (blocked, not sent) -// -// 4. Check Network tab — blocked requests should NOT appear (they resolve locally). -// -// 5. For story view test: open a Story, check Network tab for any request to -// /media/seen/ or /viewed_story/ — should be absent. diff --git a/v2/PHASE1_DEPS.yaml b/v2/PHASE1_DEPS.yaml deleted file mode 100644 index 20edbef..0000000 --- a/v2/PHASE1_DEPS.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# ── pubspec.yaml additions for FocusGram Phase 1 ────────────────────────── -# -# Merge these into your existing pubspec.yaml -# - -dependencies: - flutter: - sdk: flutter - - # WebView — already in project - flutter_inappwebview: ^6.1.5 - - # Persistence - shared_preferences: ^2.3.2 - sqflite: ^2.3.3+1 # Phase 2 history DB — add now, use later - path_provider: ^2.1.4 - - # Network (Phase 2 download manager — add now) - dio: ^5.7.0 - - # Gallery save (Phase 2) - gal: ^2.3.0 - - # Permissions (Phase 2) - permission_handler: ^11.3.1 - - -flutter: - assets: - - assets/scripts/ghost_mode.js - - assets/scripts/theme_detector.js - - assets/scripts/ad_blocker_dom.js - - assets/scripts/content_hider.js - - assets/scripts/media_detector.js # empty for now - - assets/scripts/history_tracker.js # empty for now - - assets/blocklists/easylist_mini.txt # Phase 1.5 — download and bundle - - -# ── AndroidManifest.xml additions ───────────────────────────────────────── -# -# In android/app/src/main/AndroidManifest.xml, inside : -# -# -# -# Also add permissions: -# -# -# -# - - -# ── android/app/src/main/res/values/styles.xml ──────────────────────────── -# -# Add to your launch theme for true edge-to-edge: -# -# shortEdges -# @android:color/transparent -# @android:color/transparent -# false -# false -# false ← Android 10+ diff --git a/v2/ad_blocker_dom.js b/v2/ad_blocker_dom.js deleted file mode 100644 index c56705b..0000000 --- a/v2/ad_blocker_dom.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * FocusGram DOM Ad Blocker - * SHould have Removed sponsored posts, "Suggested for you" injections, and ad elements. - * Uses structure-based selectors — NOT class names (those change weekly). - * Injected at DOCUMENT_END. - */ -(function () { - 'use strict'; - - // ─── Sponsored text signals (Instagram localizes these) ─────────────────── - // We match the STRUCTURE not just English text. - // In IG mobile web, sponsored label appears as a or
    - // that is a direct sibling/child of the article header area. - const SPONSORED_TEXTS = new Set([ - 'sponsored', // en - 'gesponsert', // de - 'patrocinado', // es/pt - 'sponsorisÃĐ', // fr - 'sponsorizzato', // it - 'sponsrad', // sv - 'sponsoreret', // da - 'gesponsord', // nl - 'Ņ€ÐĩКÐŧаОa', // ru - 'āĪĩāĪŋāΜāĨāĪžāĪūāĪŠāĪĻ', // hi - 'åđŋ告', // zh - 'ad', // en short - ]); - - const isSponsoredText = (text) => - SPONSORED_TEXTS.has(text.trim().toLowerCase()); - - // ─── Remove a single article element ────────────────────────────────────── - const removeArticle = (el) => { - // Walk up to find the article or main feed item container - const target = el.closest('article') ?? el.closest('div[data-media-id]') ?? el; - target.remove(); - }; - - // ─── Core ad scanner ────────────────────────────────────────────────────── - const scanAndRemove = () => { - // Strategy 1: inside feed - document.querySelectorAll('a[href*="/ads/"]').forEach((a) => { - a.closest('article')?.remove(); - }); - - // Strategy 2: Sponsored text in article spans - document.querySelectorAll('article').forEach((article) => { - const spans = article.querySelectorAll('span, div'); - for (const span of spans) { - if ( - span.children.length === 0 && // leaf node - isSponsoredText(span.textContent) - ) { - article.remove(); - return; - } - } - }); - - // Strategy 3: "Suggested for you" feed injections - document.querySelectorAll('article, section').forEach((el) => { - const firstText = el.querySelector('span, div, h4')?.textContent?.trim(); - if ( - firstText && - (firstText.toLowerCase().startsWith('suggested') || - firstText.toLowerCase().startsWith('you might') || - firstText.toLowerCase() === 'posts you might like') - ) { - el.remove(); - } - }); - - // Strategy 4: Instagram marks some ad containers with aria-label - document - .querySelectorAll('[aria-label*="Sponsored"], [aria-label*="Ad"]') - .forEach((el) => { - el.closest('article')?.remove(); - }); - - // Strategy 5: Tracking pixel iframes / hidden images - document.querySelectorAll('iframe[width="0"], iframe[height="0"]').forEach((el) => el.remove()); - document - .querySelectorAll('img[width="1"][height="1"], img[width="0"][height="0"]') - .forEach((el) => el.remove()); - }; - - // ─── Run on load + watch for new content ────────────────────────────────── - scanAndRemove(); - - const observer = new MutationObserver((mutations) => { - // Only scan if nodes were added (skip attribute/text changes) - const hasAdditions = mutations.some((m) => m.addedNodes.length > 0); - if (hasAdditions) scanAndRemove(); - }); - - const feed = document.querySelector('main') ?? document.body; -observer.observe(feed, { childList: true, subtree: true }); -})(); diff --git a/v2/assets/scripts/ad_blocker_dom.js b/v2/assets/scripts/ad_blocker_dom.js deleted file mode 100644 index 611518c..0000000 --- a/v2/assets/scripts/ad_blocker_dom.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * FocusGram DOM Ad Blocker - * Removes sponsored posts, "Suggested for you" injections, and ad elements. - * Uses structure-based selectors — NOT class names (those change weekly). - * Injected at DOCUMENT_END. - */ -(function () { - 'use strict'; - - // ─── Sponsored text signals (Instagram localizes these) ─────────────────── - // We match the STRUCTURE not just English text. - // In IG mobile web, sponsored label appears as a or
    - // that is a direct sibling/child of the article header area. - const SPONSORED_TEXTS = new Set([ - 'sponsored', // en - 'gesponsert', // de - 'patrocinado', // es/pt - 'sponsorisÃĐ', // fr - 'sponsorizzato', // it - 'sponsrad', // sv - 'sponsoreret', // da - 'gesponsord', // nl - 'Ņ€ÐĩКÐŧаОa', // ru - 'āĪĩāĪŋāΜāĨāĪžāĪūāĪŠāĪĻ', // hi - 'åđŋ告', // zh - 'ad', // en short - ]); - - const isSponsoredText = (text) => - SPONSORED_TEXTS.has(text.trim().toLowerCase()); - - // ─── Remove a single article element ────────────────────────────────────── - const removeArticle = (el) => { - // Walk up to find the article or main feed item container - const target = el.closest('article') ?? el.closest('div[data-media-id]') ?? el; - target.remove(); - }; - - // ─── Core ad scanner ────────────────────────────────────────────────────── - const scanAndRemove = () => { - // Strategy 1: inside feed - document.querySelectorAll('a[href*="/ads/"]').forEach((a) => { - a.closest('article')?.remove(); - }); - - // Strategy 2: Sponsored text in article spans - document.querySelectorAll('article').forEach((article) => { - const spans = article.querySelectorAll('span, div'); - for (const span of spans) { - if ( - span.children.length === 0 && // leaf node - isSponsoredText(span.textContent) - ) { - article.remove(); - return; - } - } - }); - - // Strategy 3: "Suggested for you" feed injections - document.querySelectorAll('article, section').forEach((el) => { - const firstText = el.querySelector('span, div, h4')?.textContent?.trim(); - if ( - firstText && - (firstText.toLowerCase().startsWith('suggested') || - firstText.toLowerCase().startsWith('you might') || - firstText.toLowerCase() === 'posts you might like') - ) { - el.remove(); - } - }); - - // Strategy 4: Instagram marks some ad containers with aria-label - document - .querySelectorAll('[aria-label*="Sponsored"], [aria-label*="Ad"]') - .forEach((el) => { - el.closest('article')?.remove(); - }); - - // Strategy 5: Tracking pixel iframes / hidden images - document.querySelectorAll('iframe[width="0"], iframe[height="0"]').forEach((el) => el.remove()); - document - .querySelectorAll('img[width="1"][height="1"], img[width="0"][height="0"]') - .forEach((el) => el.remove()); - }; - - // ─── Run on load + watch for new content ────────────────────────────────── - scanAndRemove(); - - const observer = new MutationObserver((mutations) => { - // Only scan if nodes were added (skip attribute/text changes) - const hasAdditions = mutations.some((m) => m.addedNodes.length > 0); - if (hasAdditions) scanAndRemove(); - }); - - observer.observe(document.body, { - childList: true, - subtree: true, - }); -})(); diff --git a/v2/assets/scripts/autoplay_blocker.js b/v2/assets/scripts/autoplay_blocker.js deleted file mode 100644 index 6911383..0000000 --- a/v2/assets/scripts/autoplay_blocker.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * FocusGram Autoplay Blocker - * Injected at DOCUMENT_START — before Instagram's JS loads. - * Prevents video autoplay by: - * 1. Blocking play() calls on video elements - * 2. Disabling autoplay attribute - * 3. Removing preload attributes - */ -(function () { - 'use strict'; - - window.__fgBlockAutoplay = false; - - // Override HTMLMediaElement.play() to check our flag - const _play = HTMLMediaElement.prototype.play; - HTMLMediaElement.prototype.play = function () { - if (window.__fgBlockAutoplay) { - // Return a resolved promise to avoid breaking Instagram's code - return Promise.resolve(); - } - return _play.call(this); - }; - - // Override autoplay property setter - const _videoDescriptor = Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'autoplay') || {}; - const _originalAutoplaySetter = _videoDescriptor.set; - - Object.defineProperty(HTMLVideoElement.prototype, 'autoplay', { - set: function (value) { - if (window.__fgBlockAutoplay && value) { - // Silently ignore autoplay attempts when blocking is enabled - return; - } - if (_originalAutoplaySetter) { - _originalAutoplaySetter.call(this, value); - } - }, - get: function () { - if (_videoDescriptor.get) { - return _videoDescriptor.get.call(this); - } - return this.getAttribute('autoplay') !== null; - }, - enumerable: _videoDescriptor.enumerable, - configurable: true, - }); - - // On page load and SPA navigation, scan for video elements and remove autoplay - const removeAutoplayFromVideos = () => { - document.querySelectorAll('video, [role="video"]').forEach(el => { - if (window.__fgBlockAutoplay) { - el.autoplay = false; - el.removeAttribute('autoplay'); - if (el.paused === false) { - el.pause(); - } - } - }); - }; - - // Run on load and when document changes - removeAutoplayFromVideos(); - - if (!window.__fgAutoplayObserver) { - let _timer = null; - window.__fgAutoplayObserver = new MutationObserver(() => { - clearTimeout(_timer); - _timer = setTimeout(removeAutoplayFromVideos, 500); - }); - window.__fgAutoplayObserver.observe(document.documentElement, { - childList: true, - subtree: true, - }); - } - - // Allow Flutter to toggle - window.__fgSetBlockAutoplay = function (enabled) { - window.__fgBlockAutoplay = !!enabled; - if (enabled) { - removeAutoplayFromVideos(); - } - }; -})(); diff --git a/v2/assets/scripts/content_hider.js b/v2/assets/scripts/content_hider.js deleted file mode 100644 index 0e8f718..0000000 --- a/v2/assets/scripts/content_hider.js +++ /dev/null @@ -1,304 +0,0 @@ -/** - * FocusGram Content Hider - * Toggleable visibility for: stories tray, feed posts, reels, suggested content. - * Flutter controls via window.__fgContent.* - * Injected at DOCUMENT_END. - * - * Key fixes applied: - * - Blank-feed fix: hideReels uses DOM removal (not display:none) so layout doesn't collapse - * - MutationObserver callback now re-applies CSS AND re-runs all hide functions each cycle - * - SPA-heartbeat via window event listener re-applies CSS on pushState/replaceState - * - Stories tray detection strengthened for fresh SPA navigations - * - Suggested posts detection uses multiple text-node matching strategies - */ -(function () { - 'use strict'; - - if (window.__fgContent && window.__fgContent.__focusgramReady) { - return; - } - - const STYLE_ID = 'fg-content-hider'; - let hideStories = false; - let hidePosts = false; - let hideSuggested = false; - let hideReels = false; - - // ─── CSS rules ───────────────────────────────────────────────────────────── - - function buildCSS() { - const selectors = []; - - if (hideStories) { - selectors.push( - '[role="list"]:has([aria-label*="tory"])', - '[role="listbox"]:has([aria-label*="tory"])', - '[role="menu"] > ul', - 'section > div > div:first-child [style*="overflow"]', - '[role="list"] [style*="overflow"]', - ); - } - - if (hidePosts) { - selectors.push( - 'body:not([path*="direct"]):not([data-fg-path*="direct"]) article:not([aria-label])', - 'body:not([path*="direct"]):not([data-fg-path*="direct"]) [data-pressable-container] > article', - ); - } - - // hideReels CSS is intentionally NOT added here. - // We use DOM removal instead (see removeReels()) so that room is never left - // blank in the feed, and Instagram's infinite-scroll can prove scroll height. - - return selectors.length - ? selectors.join(',\n') + ' { display: none !important; visibility: hidden !important; }' - : ''; - } - - function applyCSS() { - if (document.body) { - document.body.setAttribute('data-fg-path', window.location.pathname || '/'); - } - let style = document.getElementById(STYLE_ID); - if (!style) { - style = document.createElement('style'); - style.id = STYLE_ID; - document.head.appendChild(style); - } - style.textContent = buildCSS(); - } - - // ─── Story tray JS ───────────────────────────────────────────────────────── - - function hideStoryTray() { - if (!hideStories) return; - - // Strategy 1:
      children of a named list or menu - document.querySelectorAll('[role="list"] ul, [role="menu"] ul').forEach(function (ul) { - try { - const items = ul.querySelectorAll('li, button, a'); - if (items.length < 2) return; - ul.style.setProperty('display', 'none', 'important'); - } catch (_) {} - }); - - // Strategy 2: horizontally scrolling container with circle items - document.querySelectorAll('[style*="overflow"], [style*="overflow-x"]').forEach(function (c) { - try { - if ('' === (window.getComputedStyle(c).overflow + '').replace(/none/g, '')) return; - const cands = c.querySelectorAll('li, div[class*="story"], [class*="story"]'); - if (cands.length < 2) return; - const s0 = window.getComputedStyle(cands[0]); - if (s0.width && parseFloat(s0.width) <= 90) { - c.parentElement && (c.parentElement.style.setProperty('display', 'none', 'important')); - } - } catch (_) {} - }); - } - - // ─── Suggested posts ─────────────────────────────────────────────────────── - - function removeSuggested() { - if (!hideSuggested) return; - - var SIGNALS = [ - 'suggested for you', - 'suggested posts', - 'suggested reels', - 'suggested', - 'because you watched', - 'because you follow', - 'you might like', - 'posts you might like', - 'accounts you might like', - 'recommendations', - ]; - - function norm(s) { - return (s || '').replace(/\s+/g, ' ').trim().toLowerCase(); - } - - function hasSignal(s) { - var t = norm(s); - if (!t) return false; - return SIGNALS.some(function (signal) { - if (signal === 'suggested') return t === signal; - return t.indexOf(signal) >= 0; - }); - } - - function hideContainer(from) { - var parent = from; - for (var depth = 0; depth < 10 && parent && parent !== document.body; depth++) { - var role = parent.getAttribute && parent.getAttribute('role'); - var tag = parent.tagName; - var hasMedia = parent.querySelector && parent.querySelector('img,video,a[href*="/p/"],a[href*="/reel/"]'); - if ( - tag === 'ARTICLE' || - tag === 'SECTION' || - role === 'listitem' || - (hasMedia && parent.getBoundingClientRect && parent.getBoundingClientRect().height > 120) - ) { - parent.style.setProperty('display', 'none', 'important'); - parent.setAttribute('data-fg-hidden-suggested', '1'); - return true; - } - parent = parent.parentElement; - } - return false; - } - - document.querySelectorAll('article, section, [role="listitem"]').forEach(function (node) { - try { - if (node.getAttribute('data-fg-hidden-suggested') === '1') return; - var ownLabel = node.getAttribute('aria-label'); - if (hasSignal(ownLabel)) { hideContainer(node); return; } - var text = norm(node.innerText || node.textContent || ''); - if ( - text.indexOf('suggested for you') >= 0 || - text.indexOf('suggested posts') >= 0 || - text.indexOf('suggested reels') >= 0 || - text.indexOf('because you watched') >= 0 || - text.indexOf('because you follow') >= 0 - ) { - hideContainer(node); - } - } catch (_) {} - }); - - document.querySelectorAll('span, h1, h2, h3, h4, div[aria-label], a[aria-label]').forEach(function (el) { - try { - if (hasSignal(el.textContent) || hasSignal(el.getAttribute('aria-label'))) { - hideContainer(el); - } - } catch (_) {} - }); - } - - // ─── Reels – DOM REMOVE (not display:none) ───────────────────────────────── - // display:none keeps the element in the DOM, so Instagram's virtual-scroll still - // reserves the slot → blank gaps. Removing the article from the DOM collapses the - // gap cleanly and lets the feed flow naturally. - function removeReels() { - if (!hideReels) return; - - var toRemove = []; - document.querySelectorAll('article').forEach(function (el) { - try { - // Fast path: check for a reel-signal attribute first - var mt = (el.getAttribute('data-media-type') || el.dataset && el.dataset.mediaType || '').trim(); - if (mt === '2') { toRemove.push(el); return; } - - // Fallback: text-node scan for /reels/ markers - var walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT); - var n; - while ((n = walker.nextNode())) { - if (n.nodeValue.indexOf('/reels/') >= 0 || n.nodeValue.indexOf('/reel/') >= 0) { - toRemove.push(el); break; - } - } - } catch (_) {} - }); - - toRemove.forEach(function (el) { try { el.remove(); } catch (_) {} }); - } - - // ─── Public API ──────────────────────────────────────────────────────────── - - window.__fgContent = { - __focusgramReady: true, - setHideStories: function (val) { hideStories = !!val; applyCSS(); hideStoryTray(); }, - setHidePosts: function (val) { hidePosts = !!val; applyCSS(); }, - setHideSuggested: function (val) { - hideSuggested = !!val; - applyCSS(); - if (val) removeSuggested(); - }, - setHideReels: function (val) { - hideReels = !!val; - applyCSS(); - if (val) removeReels(); - }, - applyAll: function (flags) { - hideStories = !!flags.stories; - hidePosts = !!flags.posts; - hideReels = !!flags.reels; - hideSuggested = !!flags.suggested; - applyCSS(); - if (hideSuggested) removeSuggested(); - if (hideStories) hideStoryTray(); - if (hideReels) removeReels(); - }, - }; - - // ─── SPA heartbeat ───────────────────────────────────────────────────────── - // pushState/replaceState don't fire any DOM event we can listen for. - // Hook the methods themselves so we know a navigation happened, then debounce - // re-apply. This also catches the case where the MutationObserver was on `body` - // and that node got replaced by Instagram's SPA re-render. - - function scheduleReapply() { - clearTimeout(window.__fg_applyTimer); - window.__fg_applyTimer = setTimeout(function () { - applyCSS(); - if (hideStories) hideStoryTray(); - if (hideSuggested) removeSuggested(); - if (hideReels) removeReels(); - }, 250); - } - - var _origPush = history.pushState; - var _origReplace = history.replaceState; - - history.pushState = function () { - _origPush.apply(this, arguments); - scheduleReapply(); - }; - - history.replaceState = function () { - _origReplace.apply(this, arguments); - scheduleReapply(); - }; - - // Reinforce on popstate too (user hits back/forward) - window.addEventListener('popstate', scheduleReapply, { passive: true }); - // For pushState on the same URL (rare but possible) – poll path briefly - window.addEventListener('pageshow', scheduleReapply, { passive: true }); - window.addEventListener('focus', scheduleReapply, { passive: true }); - - // ─── MutationObserver ─────────────────────────────────────────────────────── - // Monitors for dynamic DOM changes (new rows, lazy-loaded articles) and - // re-applies everything on each cycle. Does NOT guard on a per-element timer - // that would never re-fire after the body is replaced by SPA re-render. - - if (!window.__fgContentObserver) { - window.__fgContentObserver = new MutationObserver(function () { - clearTimeout(window.__fg_moTimer); - window.__fg_moTimer = setTimeout(function () { - applyCSS(); - if (hideStories) hideStoryTray(); - if (hideSuggested) removeSuggested(); - if (hideReels) removeReels(); - }, 300); - }); - - // `document.documentElement` survives SPA navigations (body gets replaced - // but stays). Observing it catches both subtree mutations and, via - // the SPA heartbeat above, re-applies after pushState. - window.__fgContentObserver.observe(document.documentElement, { - childList: true, - subtree: true, - }); - } - - // ─── Initial run ──────────────────────────────────────────────────────────── - applyCSS(); - if (hideStories) hideStoryTray(); - if (hideSuggested) removeSuggested(); - if (hideReels) removeReels(); - - // Signal ready — Flutter will call applyAll() with stored prefs - if (window.ContentChannel) { - window.ContentChannel.postMessage(JSON.stringify({ type: 'ready' })); - } -})(); diff --git a/v2/assets/scripts/fetch_interceptor.js b/v2/assets/scripts/fetch_interceptor.js deleted file mode 100644 index c414187..0000000 --- a/v2/assets/scripts/fetch_interceptor.js +++ /dev/null @@ -1,281 +0,0 @@ -/** - * FocusGram Unified Feed Filter via Fetch Interception - * Injected at DOCUMENT_START — before Instagram's JS loads. - * - * This script intercepts GraphQL fetch calls and filters feed content based on: - * - Ads (is_ad, ad_action_link, product_type, ad_id, ad_header_style) - * - Sponsored posts (ad_action_link, ad_header_style) - * - Suggested posts (is_suggested, is_suggested_for_you, __typename) - * - Videos/Reels (is_video, media_type, clips_metadata) - * - Autoplay blocking (video autoplay prevention) - */ -(function () { - 'use strict'; - - // Configuration flags (set by Flutter via prefs) - window.__fgFilterConfig = { - blockAds: false, - blockSponsored: false, - blockSuggested: false, - blockVideos: false, - blockAutoplay: false, - blockGraphQLQueryWhenFeedPosts: false, - }; - - // Helper: Check if a node is an ad - const isAdNode = (node) => { - if (!node || typeof node !== 'object') return false; - - return !!( - node.is_ad || - node.ad_action_link || - node.ad_id || - (node.product_type && node.product_type === 'ad') || - (node.ad_header_style && node.ad_header_style !== 'none') || - (node.__typename && node.__typename === 'GraphAdStory') - ); - }; - - // Helper: Check if a node is sponsored - const isSponsoredNode = (node) => { - if (!node || typeof node !== 'object') return false; - - return !!( - (node.ad_action_link && node.ad_action_link.href) || - (node.ad_header_style && node.ad_header_style !== 'none') - ); - }; - - // Helper: Check if a node is suggested content - const isSuggestedNode = (node) => { - if (!node || typeof node !== 'object') return false; - const typename = String(node.__typename || ''); - const reason = JSON.stringify({ - reason: node.suggested_reason, - social_context: node.social_context, - title: node.title, - header: node.header, - label: node.label, - }).toLowerCase(); - - return !!( - node.is_suggested || - node.is_suggested_for_you || - node.is_recommendation || - node.suggested_users || - node.suggested_media || - node.suggested_content || - node.recommendation_source || - typename.includes('Suggested') || - typename.includes('Recommendation') || - reason.includes('suggested') || - reason.includes('recommend') - ); - }; - - // Helper: Check if a node is a video/reel - const isVideoNode = (node) => { - if (!node || typeof node !== 'object') return false; - - return !!( - node.is_video || - (node.media_type === 2) || - node.clips_metadata || - (node.__typename && ( - node.__typename.includes('Clips') || - node.__typename.includes('Video') - )) - ); - }; - - const isFeedMediaNode = (node) => { - if (!node || typeof node !== 'object') return false; - return !!( - node.pk || - node.id || - node.code || - node.media_type || - node.image_versions2 || - node.video_versions || - node.carousel_media || - node.__typename?.includes('Media') || - node.__typename?.includes('Timeline') - ); - }; - - // Helper: Check for media in carousel - const hasVideoInCarousel = (node) => { - if (!node || typeof node !== 'object') return false; - - if (node.media_type === 8) { - const edges = node.edge_sidecar_to_children?.edges || []; - return edges.some(edge => isVideoNode(edge.node)); - } - return false; - }; - - // Main filter function for feed nodes - const shouldFilterNode = (node) => { - const config = window.__fgFilterConfig; - - if (!node || typeof node !== 'object') return false; - - if (config.blockGraphQLQueryWhenFeedPosts && isFeedMediaNode(node)) { - return true; - } - - // Check ads - if (config.blockAds && isAdNode(node)) { - return true; - } - - // Check sponsored (separate from ads) - if (config.blockSponsored && isSponsoredNode(node) && !isAdNode(node)) { - return true; - } - - // Check suggested content - if (config.blockSuggested && isSuggestedNode(node)) { - return true; - } - - // Check videos/reels - if (config.blockVideos && (isVideoNode(node) || hasVideoInCarousel(node))) { - return true; - } - - return false; - }; - - // Recursively filter GraphQL response edges - const filterEdges = (edges, path = []) => { - if (!Array.isArray(edges)) return edges; - - return edges.filter(edge => { - if (!edge || !edge.node) return true; - const node = edge.node; - - // Keep the edge if it doesn't match any filter - if (!shouldFilterNode(node)) return true; - - // Log filtered content for debugging - if (window.__fgDebugFilter) { - const type = node.__typename || 'Unknown'; - console.debug('[FocusGram Filter]', `Filtered ${type} at ${path.join('/')}`); - } - - return false; - }); - }; - - // Recursively walk GraphQL response and filter edges - const walkAndFilter = (obj, visited = new Set()) => { - if (!obj || typeof obj !== 'object' || visited.has(obj)) return; - visited.add(obj); - - // Handle arrays - if (Array.isArray(obj)) { - obj.forEach(item => walkAndFilter(item, visited)); - return; - } - - // Check for edges array (common GraphQL pattern) - if (obj.edges && Array.isArray(obj.edges)) { - obj.edges = filterEdges(obj.edges); - } - - // Recurse into children - for (const key in obj) { - if (obj.hasOwnProperty(key) && key !== '__typename') { - const val = obj[key]; - if (val && typeof val === 'object') { - walkAndFilter(val, visited); - } - } - } - }; - - // Override fetch - const _fetch = window.fetch.bind(window); - - window.fetch = async function (input, init) { - const url = typeof input === 'string' - ? input - : input instanceof URL - ? input.href - : input?.url ?? ''; - - // Call original fetch - let response = await _fetch(input, init); - - // Only intercept GraphQL feed queries - if (!url.includes('/graphql') && !url.includes('/api/v1/feed')) { - return response; - } - - // Clone response to read body - const cloned = response.clone(); - - try { - const contentType = response.headers.get('content-type') || ''; - if (!contentType.includes('application/json')) { - return response; - } - - const data = await cloned.json(); - - // Filter the response data - walkAndFilter(data); - - // Return modified response - return new Response(JSON.stringify(data), { - status: response.status, - statusText: response.statusText, - headers: response.headers, - }); - } catch (e) { - // On error, return original response - return response; - } - }; - - // Preserve native function appearance - Object.defineProperty(window, 'fetch', { - value: window.fetch, - writable: true, - configurable: true, - }); - window.fetch.toString = () => 'function fetch() { [native code] }'; - - const _xhrOpen = XMLHttpRequest.prototype.open; - const _xhrSend = XMLHttpRequest.prototype.send; - XMLHttpRequest.prototype.open = function (method, url) { - this.__fgUrl = typeof url === 'string' ? url : String(url || ''); - return _xhrOpen.apply(this, arguments); - }; - XMLHttpRequest.prototype.send = function () { - if ( - window.__fgFilterConfig.blockVideos && - this.__fgUrl && - (this.__fgUrl.includes('/api/v1/clips/') || - this.__fgUrl.includes('/api/v1/discover/')) - ) { - try { this.abort(); } catch (_) {} - return; - } - return _xhrSend.apply(this, arguments); - }; - - // Allow Flutter to update config flags - window.__fgSetFilterConfig = function (config) { - if (typeof config === 'object') { - Object.assign(window.__fgFilterConfig, config); - if (window.__fgDebugFilter) { - console.debug('[FocusGram Filter] Config updated:', window.__fgFilterConfig); - } - } - }; - - // Enable debug logging - window.__fgDebugFilter = false; -})(); diff --git a/v2/assets/scripts/ghost_mode.js b/v2/assets/scripts/ghost_mode.js deleted file mode 100644 index 6b199df..0000000 --- a/v2/assets/scripts/ghost_mode.js +++ /dev/null @@ -1,256 +0,0 @@ -/** - * FocusGram Ghost Mode - * Injected at DOCUMENT_START — before Instagram's JS loads. - * Blocks story-seen, message-seen, and online-presence signals. - */ -(function () { - 'use strict'; - - // ─── Direct Message API block ──────────────────────────────────────────── - // ── First-interaction gate: allow inbox to load, then block ─ - window.__fgDirectApiBlocked = false; - document.addEventListener('click', function() { - if (window.location.pathname.indexOf('/direct/') === 0) window.__fgDirectApiBlocked = true; - }, true); - document.addEventListener('touchstart', function() { - if (window.location.pathname.indexOf('/direct/') === 0) window.__fgDirectApiBlocked = true; - }, true); - var _prevD = window.location.pathname.indexOf('/direct/') === 0; - setInterval(function() { - var now = window.location.pathname.indexOf('/direct/') === 0; - if (now !== _prevD) { _prevD = now; window.__fgDirectApiBlocked = false; } - }, 300); - - function _blockIfNeeded(url) { - return window.__fgDirectApiBlocked && - window.location.pathname.indexOf('/direct/') === 0 && - url.indexOf('/api/graphql') !== -1; - } - - const _f = window.fetch.bind(window); - window.fetch = function(input, init) { - const url = (typeof input === 'string') ? input : (input && input.url) ? input.url : ''; - if (_blockIfNeeded(url)) { - return Promise.resolve(new Response(JSON.stringify({status:'ok'}), { - status: 200, headers: {'Content-Type': 'application/json'} - })); - } - return _f(input, init); - }; - - const _o = XMLHttpRequest.prototype.open; - const _s = XMLHttpRequest.prototype.send; - XMLHttpRequest.prototype.open = function(m, u) { - this.__fgUrl = u || ''; return _o.apply(this, arguments); - }; - XMLHttpRequest.prototype.send = function(body) { - if (_blockIfNeeded(this.__fgUrl || '')) { - const self = this; setTimeout(function() { - Object.defineProperty(self,'readyState',{get:function(){return 4}}); - Object.defineProperty(self,'status',{get:function(){return 200}}); - Object.defineProperty(self,'responseText',{get:function(){return '{"status":"ok"}'}}); - self.dispatchEvent(new Event('readystatechange')); self.dispatchEvent(new Event('load')); - }, 5); return; - } - return _s.apply(this, arguments); - }; - - // ─── Seen API patterns ──────────────────────────────────────────────────── - const SEEN_PATTERNS = [ - /\/api\/v1\/media\/[\w-]+\/seen\//, - /\/api\/v1\/stories\/reel\/seen\//, - /\/api\/v1\/direct_v2\/threads\/[\w-]+\/seen\//, - /\/api\/v1\/direct_v2\/visual_message\/[\w-]+\/seen\//, - /\/api\/v1\/live\/[\w-]+\/comment\/seen\//, - ]; - - // ─── Activity patterns (like, comment) — intercepted for local history ──── - const ACTIVITY_PATTERNS = [ - /\/api\/v1\/web\/likes\/[\w-]+\/like\//, - /\/api\/v1\/web\/comments\/add\//, - /\/api\/v1\/friendships\/[\w-]+\/follow\//, - ]; - - const isSeen = (url) => SEEN_PATTERNS.some((p) => p.test(url)); - const isActivity = (url) => ACTIVITY_PATTERNS.some((p) => p.test(url)); - - const fakeOkResponse = () => - new Response(JSON.stringify({ status: 'ok' }), { - status: 200, - headers: { 'Content-Type': 'application/json' }, - }); - - // ─── Fetch override ─────────────────────────────────────────────────────── - const _fetch = window.fetch.bind(window); - - const patchedFetch = async function (input, init) { - const url = - typeof input === 'string' - ? input - : input instanceof URL - ? input.href - : input?.url ?? ''; - - // Block seen - if (isSeen(url)) { - if (window.GhostChannel) { - window.GhostChannel.postMessage( - JSON.stringify({ type: 'seen_blocked', url }) - ); - } - return fakeOkResponse(); - } - - // Intercept activity for local history - if (isActivity(url) && window.ActivityChannel) { - const body = init?.body; - const bodyText = - body instanceof URLSearchParams - ? body.toString() - : typeof body === 'string' - ? body - : ''; - window.ActivityChannel.postMessage( - JSON.stringify({ url, body: bodyText, timestamp: Date.now() }) - ); - } - - return _fetch(input, init); - }; - - // Disguise as native - Object.defineProperty(window, 'fetch', { - value: patchedFetch, - writable: true, - configurable: true, - enumerable: true, - }); - window.fetch.toString = () => 'function fetch() { [native code] }'; - window.fetch[Symbol.toStringTag] = 'fetch'; - - // ─── XMLHttpRequest override ────────────────────────────────────────────── - const _XHROpen = XMLHttpRequest.prototype.open; - const _XHRSend = XMLHttpRequest.prototype.send; - - XMLHttpRequest.prototype.open = function (method, url, ...args) { - this._fg_url = url ?? ''; - this._fg_method = (method ?? '').toUpperCase(); - return _XHROpen.call(this, method, url, ...args); - }; - - XMLHttpRequest.prototype.send = function (body) { - if (this._fg_url && isSeen(this._fg_url)) { - // Fire readyState 4 with fake success without actually sending - const self = this; - setTimeout(() => { - Object.defineProperty(self, 'readyState', { get: () => 4 }); - Object.defineProperty(self, 'status', { get: () => 200 }); - Object.defineProperty(self, 'responseText', { - get: () => '{"status":"ok"}', - }); - Object.defineProperty(self, 'response', { - get: () => '{"status":"ok"}', - }); - self.dispatchEvent(new Event('readystatechange')); - self.dispatchEvent(new Event('load')); - }, 10); - return; - } - return _XHRSend.call(this, body); - }; - - // ─── WebSocket intercept (message-seen via WS) ──────────────────────────── - // Strict WS URL blocking (ghost mode requirement) - // sid/cid vary per user/chat; block by endpoint prefix, not exact query. - const isBlockedWssUrl = (u) => { - if (!u) return false; - const urlStr = String(u); - - return ( - urlStr.startsWith('wss://gateway.instagram.com/ws/streamcontroller') || - urlStr.startsWith('wss://edge-chat.instagram.com/chat?sid=') - ); - }; - - // Signal to other injected scripts that ghost-mode is active - window.__fgGhostModeActive = true; - - const _WS = window.WebSocket; - - function PatchedWebSocket(url, protocols) { - const urlStr = typeof url === 'string' ? url : url?.toString?.() ?? ''; - - // If the WebSocket URL is one of the blocked endpoints, return an inert WS-like object - if (isBlockedWssUrl(urlStr)) { - return { - send: () => {}, - close: () => {}, - readyState: 1, - addEventListener: () => {}, - removeEventListener: () => {}, - }; - } - - const ws = protocols ? new _WS(url, protocols) : new _WS(url); - const _send = ws.send.bind(ws); - - ws.send = function (data) { - if (typeof data === 'string') { - // IG sends seen ops as JSON with "op":"4" or "op":"seen" depending on version - try { - const parsed = JSON.parse(data); - if ( - parsed?.op === '4' || - parsed?.op === 'seen' || - (parsed?.payload && JSON.parse(parsed.payload)?.op === 'seen') - ) { - return; // drop - } - } catch (_) {} - // Text-based seen signal check - if (data.includes('"seen"') && data.includes('"thread_id"')) { - return; - } - } - return _send(data); - }; - - return ws; - } - - // Preserve WebSocket prototype chain so IG's ws checks pass - PatchedWebSocket.prototype = _WS.prototype; - PatchedWebSocket.CONNECTING = _WS.CONNECTING; - PatchedWebSocket.OPEN = _WS.OPEN; - PatchedWebSocket.CLOSING = _WS.CLOSING; - PatchedWebSocket.CLOSED = _WS.CLOSED; - window.WebSocket = PatchedWebSocket; - - // ─── Visibility trick — hide "Active Now" ──────────────────────────────── - // Only applied if user enables online-status hiding - // Wrapped in a named fn so Flutter can call it: - // controller.evaluateJavascript(source: 'window.__fgEnableOnlineHide()') - window.__fgEnableOnlineHide = function () { - Object.defineProperty(document, 'visibilityState', { - get: () => 'hidden', - configurable: true, - }); - Object.defineProperty(document, 'hidden', { - get: () => true, - configurable: true, - }); - document.dispatchEvent(new Event('visibilitychange')); - }; - - window.__fgDisableOnlineHide = function () { - // Restore by deleting the overrides (falls back to native getter) - delete document.visibilityState; - delete document.hidden; - document.dispatchEvent(new Event('visibilitychange')); - }; - - // Signal to Flutter that ghost mode JS is active - if (window.GhostChannel) { - window.GhostChannel.postMessage(JSON.stringify({ type: 'ready' })); - } -})(); diff --git a/v2/assets/scripts/theme_detector.js b/v2/assets/scripts/theme_detector.js deleted file mode 100644 index 54749c3..0000000 --- a/v2/assets/scripts/theme_detector.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * FocusGram Theme Detector - * Reads Instagram's background + bottom nav color and reports to Flutter. - * Injected at DOCUMENT_END so DOM is ready. - */ -(function () { - 'use strict'; - - const parseRgb = (str) => { - // Parses "rgb(255, 255, 255)" or "rgba(0, 0, 0, 1)" → { r, g, b, a } - const m = str.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); - if (!m) return null; - return { - r: parseInt(m[1]), - g: parseInt(m[2]), - b: parseInt(m[3]), - a: m[4] !== undefined ? parseFloat(m[4]) : 1, - }; - }; - - const toHex = ({ r, g, b }) => - '#' + - [r, g, b].map((v) => v.toString(16).padStart(2, '0')).join(''); - - const detectColors = () => { - // Background — Instagram sets it on or a root div - const bodyBg = getComputedStyle(document.body).backgroundColor; - - // Bottom nav — IG mobile web renders a fixed bottom bar - // Target by role="navigation" or position:fixed at bottom - let navBg = bodyBg; - const navCandidates = document.querySelectorAll( - 'nav, [role="navigation"], div[style*="bottom"]' - ); - for (const el of navCandidates) { - const style = getComputedStyle(el); - if ( - style.position === 'fixed' && - parseInt(style.bottom) <= 10 && - style.backgroundColor !== 'rgba(0, 0, 0, 0)' - ) { - navBg = style.backgroundColor; - break; - } - } - - const bodyColor = parseRgb(bodyBg); - const navColor = parseRgb(navBg); - - if (!bodyColor) return; - - // Determine dark/light - const luminance = (0.299 * bodyColor.r + 0.587 * bodyColor.g + 0.114 * bodyColor.b) / 255; - const isDark = luminance < 0.5; - - const payload = { - bodyHex: toHex(bodyColor), - navHex: navColor ? toHex(navColor) : toHex(bodyColor), - isDark, - }; - - if (window.ThemeChannel) { - window.ThemeChannel.postMessage(JSON.stringify(payload)); - } - }; - - // Run on load - detectColors(); - - // Watch for Instagram's dark mode toggle (adds/removes class on ) - const observer = new MutationObserver(detectColors); - observer.observe(document.documentElement, { - attributes: true, - attributeFilter: ['class', 'style', 'color-scheme'], - }); - observer.observe(document.body, { - attributes: true, - attributeFilter: ['class', 'style'], - }); - - // Also run after navigation (Instagram is SPA, URL changes without reload) - let lastUrl = location.href; - new MutationObserver(() => { - if (location.href !== lastUrl) { - lastUrl = location.href; - setTimeout(detectColors, 300); // small delay for IG to render new page - } - }).observe(document.body, { childList: true, subtree: true }); -})(); diff --git a/v2/channel_registry.dart b/v2/channel_registry.dart deleted file mode 100644 index fc2c6ea..0000000 --- a/v2/channel_registry.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'dart:convert'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import '../theme/system_ui_manager.dart'; - -typedef ActivityCallback = void Function(Map event); - -class ChannelRegistry { - final ActivityCallback? onActivityEvent; - - const ChannelRegistry({this.onActivityEvent}); - - // ── Build all JavaScript channels ───────────────────────────────────────── - Set build() { - return { - _ghostChannel(), - _themeChannel(), - _contentChannel(), - _activityChannel(), - }; - } - - // ───────────────────────────────────────────────────────────────────────── - - JavaScriptChannel _ghostChannel() => JavaScriptChannel( - name: 'GhostChannel', - onMessageReceived: (msg) { - try { - final data = jsonDecode(msg.message) as Map; - if (kDebugMode) { - debugPrint('[Ghost] ${data['type']} — ${data['url'] ?? ''}'); - } - // In release: silent. Could surface to a debug overlay in dev builds. - } catch (_) {} - }, - ); - - JavaScriptChannel _themeChannel() => JavaScriptChannel( - name: 'ThemeChannel', - onMessageReceived: (msg) { - SystemUiManager.applyFromThemePayload(msg.message); - }, - ); - - JavaScriptChannel _contentChannel() => JavaScriptChannel( - name: 'ContentChannel', - onMessageReceived: (msg) { - // 'ready' signal — engine pushes flags back via evaluateJavascript - // handled in ScriptEngine.injectDocumentEndScripts() - if (kDebugMode) debugPrint('[Content] ${msg.message}'); - }, - ); - - JavaScriptChannel _activityChannel() => JavaScriptChannel( - name: 'ActivityChannel', - onMessageReceived: (msg) { - try { - final data = jsonDecode(msg.message) as Map; - onActivityEvent?.call(data); - } catch (_) {} - }, - ); -} diff --git a/v2/content_hider.js b/v2/content_hider.js deleted file mode 100644 index 6a698be..0000000 --- a/v2/content_hider.js +++ /dev/null @@ -1,166 +0,0 @@ -/** - * FocusGram Content Hider - * Toggleable visibility for: stories tray, feed posts, suggested content. - * Flutter controls via window.__fgContent.* - * Injected at DOCUMENT_END. - * - * Improvements: - * - Better story tray detection using multiple strategies - * - Overlay for hidden feed content with loading indicator - * - Improved suggested posts detection - * - Fixed reels hiding to avoid blank feed issues - */ -(function () { - 'use strict'; - - const STYLE_ID = 'fg-content-hider'; - const OVERLAY_ID = 'fg-content-overlay'; - let hideStories = false; - let hidePosts = false; - let hideSuggested = false; - let hideReels = false; - - // ─── CSS rules ──────────────────────────────────────────────────────────── - - const buildCSS = () => { - let css = ''; - - if (hideStories) { - // Story tray: IG mobile web renders as a scrollable
        of circles - // near the top of the main feed. We target the outermost container - // by its scroll behaviour and presence of story-like items. - css += ` - /* Story tray */ - div[style*="overflow-x"] > ul, - div[role="menu"] > ul, - section > div > div:first-child ul[style*="scroll"] { - display: none !important; - } - `; - } - - if (hidePosts) { - // Feed articles — but NOT DM threads or profile pages - // Only apply on /, /reels/ — not /direct/ or /p/ or /@username/ - css += ` - /* Feed posts */ - main article { - display: none !important; - } - `; - } - - if (hideReels) { - css += ` - /* Reels in feed */ - article:has(video) { - display: none !important; - } - `; - } - - return css; - }; - - const applyCSS = () => { - let style = document.getElementById(STYLE_ID); - if (!style) { - style = document.createElement('style'); - style.id = STYLE_ID; - document.head.appendChild(style); - } - style.textContent = buildCSS(); - }; - - // ─── JS-based removal for suggested (CSS can't catch dynamic text) ──────── - - const removeSuggested = () => { - if (!hideSuggested) return; - document.querySelectorAll('article, section, div').forEach((el) => { - const firstLeaf = el.querySelector('span:not(:has(*)), h4'); - if (!firstLeaf) return; - const t = firstLeaf.textContent.trim().toLowerCase(); - if ( - t === 'suggested for you' || - t === 'you might like' || - t === 'suggested posts' || - t === 'posts you might like' - ) { - (el.closest('article') ?? el).remove(); - } - }); - }; - - // ─── Story tray JS fallback (for when CSS selector misses) ─────────────── - - const hideStoryTrayJS = () => { - if (!hideStories) return; - document.querySelectorAll('ul').forEach((ul) => { - const items = ul.querySelectorAll('li'); - if (items.length < 2) return; - // Story bubbles: li contains a button with a circular image - const first = items[0]; - const hasCircleImg = - first.querySelector('canvas') || - first.querySelector('img') || - first.querySelector('button'); - const isHorizontal = ul.scrollWidth > ul.clientWidth; - if (hasCircleImg && isHorizontal) { - ul.style.setProperty('display', 'none', 'important'); - } - }); - }; - - // ─── Public API — Flutter calls these via evaluateJavascript ───────────── - - window.__fgContent = { - setHideStories: (val) => { - hideStories = !!val; - applyCSS(); - hideStoryTrayJS(); - }, - setHidePosts: (val) => { - hidePosts = !!val; - applyCSS(); - }, - setHideReels: (val) => { - hideReels = !!val; - applyCSS(); - }, - setHideSuggested: (val) => { - hideSuggested = !!val; - if (val) removeSuggested(); - }, - applyAll: (flags) => { - hideStories = !!flags.stories; - hidePosts = !!flags.posts; - hideReels = !!flags.reels; - hideSuggested = !!flags.suggested; - applyCSS(); - if (hideSuggested) removeSuggested(); - if (hideStories) hideStoryTrayJS(); - }, - }; - - // ─── MutationObserver to re-apply on SPA navigation ────────────────────── - - let lastUrl = location.href; - const mo = new MutationObserver(() => { - if (location.href !== lastUrl) { - lastUrl = location.href; - setTimeout(() => { - applyCSS(); - if (hideSuggested) removeSuggested(); - if (hideStories) hideStoryTrayJS(); - }, 400); - } - if (hideSuggested) removeSuggested(); - }); - - mo.observe(document.body, { childList: true, subtree: true }); - - // Signal ready — Flutter will call applyAll() with stored prefs - if (window.ContentChannel) { - window.ContentChannel.postMessage(JSON.stringify({ type: 'ready' })); - } -})(); diff --git a/v2/fetch_interceptor.js b/v2/fetch_interceptor.js deleted file mode 100644 index 7a90bf4..0000000 --- a/v2/fetch_interceptor.js +++ /dev/null @@ -1,233 +0,0 @@ -/** - * FocusGram Unified Feed Filter via Fetch Interception - * Injected at DOCUMENT_START — before Instagram's JS loads. - * - * This script intercepts GraphQL fetch calls and filters feed content based on: - * - Ads (is_ad, ad_action_link, product_type, ad_id, ad_header_style) - * - Sponsored posts (ad_action_link, ad_header_style) - * - Suggested posts (is_suggested, is_suggested_for_you, __typename) - * - Videos/Reels (is_video, media_type, clips_metadata) - * - Autoplay blocking (video autoplay prevention) - */ -(function () { - 'use strict'; - - // Configuration flags (set by Flutter via prefs) - window.__fgFilterConfig = { - blockAds: false, - blockSponsored: false, - blockSuggested: false, - blockVideos: false, - blockAutoplay: false, - }; - - // Helper: Check if a node is an ad - const isAdNode = (node) => { - if (!node || typeof node !== 'object') return false; - return !!( - node.is_ad || - node.ad_id || - node.ad_action_link || - node.ad_action_links?.length > 0 || - node.is_paid_partnership || - node.sponsor_tags?.length > 0 || - (node.commerciality_status === 'ad') || - (node.commerciality_status === 'shoppable_feed_ad') || - (node.product_type === 'ad') || - (node.ad_header_style && node.ad_header_style !== 'none') || - node.__typename === 'GraphAdStory' || - node.__typename === 'XDTAdFeedUnit' || - (node.__typename?.toLowerCase().includes('ad')) - ); - }; - - // Helper: Check if a node is sponsored - const isSponsoredNode = (node) => { - if (!node || typeof node !== 'object') return false; - - return !!( - (node.ad_action_link && node.ad_action_link.href) || - (node.ad_header_style && node.ad_header_style !== 'none') - ); - }; - - // Helper: Check if a node is suggested content - const isSuggestedNode = (node) => { - if (!node || typeof node !== 'object') return false; - - return !!( - node.is_suggested || - node.is_suggested_for_you || - (node.__typename && node.__typename.includes('Suggested')) - ); - }; - - // Helper: Check if a node is a video/reel - const isVideoNode = (node) => { - if (!node || typeof node !== 'object') return false; - - return !!( - node.is_video || - (node.media_type === 2) || - node.clips_metadata || - (node.__typename && ( - node.__typename.includes('Clips') || - node.__typename.includes('Video') - )) - ); - }; - - // Helper: Check for media in carousel - const hasVideoInCarousel = (node) => { - if (!node || typeof node !== 'object') return false; - - if (node.media_type === 8) { - const edges = node.edge_sidecar_to_children?.edges || []; - return edges.some(edge => isVideoNode(edge.node)); - } - return false; - }; - - // Main filter function for feed nodes - const shouldFilterNode = (node) => { - const config = window.__fgFilterConfig; - - if (!node || typeof node !== 'object') return false; - - // Check ads - if (config.blockAds && isAdNode(node)) { - return true; - } - - // Check sponsored (separate from ads) - if (config.blockSponsored && isSponsoredNode(node) && !isAdNode(node)) { - return true; - } - - // Check suggested content - if (config.blockSuggested && isSuggestedNode(node)) { - return true; - } - - // Check videos/reels - if (config.blockVideos && (isVideoNode(node) || hasVideoInCarousel(node))) { - return true; - } - - return false; - }; - - // Recursively filter GraphQL response edges - const filterEdges = (edges, path = []) => { - if (!Array.isArray(edges)) return edges; - - return edges.filter(edge => { - if (!edge || !edge.node) return true; - const node = edge.node; - - // Keep the edge if it doesn't match any filter - if (!shouldFilterNode(node)) return true; - - // Log filtered content for debugging - if (window.__fgDebugFilter) { - const type = node.__typename || 'Unknown'; - console.debug('[FocusGram Filter]', `Filtered ${type} at ${path.join('/')}`); - } - - return false; - }); - }; - - // Recursively walk GraphQL response and filter edges - const walkAndFilter = (obj, visited = new Set()) => { - if (!obj || typeof obj !== 'object' || visited.has(obj)) return; - visited.add(obj); - - // Handle arrays - if (Array.isArray(obj)) { - obj.forEach(item => walkAndFilter(item, visited)); - return; - } - - // Check for edges array (common GraphQL pattern) - if (obj.edges && Array.isArray(obj.edges)) { - obj.edges = filterEdges(obj.edges); - } - - // Recurse into children - for (const key in obj) { - if (obj.hasOwnProperty(key) && key !== '__typename') { - const val = obj[key]; - if (val && typeof val === 'object') { - walkAndFilter(val, visited); - } - } - } - }; - - - // Override fetch - const _fetch = window.fetch.bind(window); - - window.fetch = async function (input, init) { - const url = typeof input === 'string' - ? input - : input instanceof URL - ? input.href - : input?.url ?? ''; - - // Call original fetch - let response = await _fetch(input, init); - - // Only intercept GraphQL feed queries - if (!url.includes('/graphql') && !url.includes('/api/v1/feed')) { - return response; - } - - // Clone response to read body - const cloned = response.clone(); - - try { - const contentType = response.headers.get('content-type') || ''; - if (!contentType.includes('application/json')) { - return response; - } - - const data = await cloned.json(); - - // Filter the response data - walkAndFilter(data); - - // Return modified response - return new Response(JSON.stringify(data), { - status: response.status, - statusText: response.statusText, - headers: response.headers, - }); - } catch (e) { - // On error, return original response - return response; - } - }; - - // Preserve native function appearance - Object.defineProperty(window, 'fetch', { - value: window.fetch, - writable: true, - configurable: true, - }); - window.fetch.toString = () => 'function fetch() { [native code] }'; - - // Allow Flutter to update config flags - window.__fgSetFilterConfig = function (config) { - if (typeof config === 'object') { - Object.assign(window.__fgFilterConfig, config); - if (window.__fgDebugFilter) { - console.debug('[FocusGram Filter] Config updated:', window.__fgFilterConfig); - } - } - }; - - // Enable debug logging - window.__fgDebugFilter = false; -})(); diff --git a/v2/ghost_mode.js b/v2/ghost_mode.js deleted file mode 100644 index 2b78da6..0000000 --- a/v2/ghost_mode.js +++ /dev/null @@ -1,179 +0,0 @@ -/** - * FocusGram Ghost Mode - * Injected at DOCUMENT_START — before Instagram's JS loads. - * Blocks story-seen, message-seen, and online-presence signals. - */ -(function () { - 'use strict'; - - // ─── Seen API patterns ──────────────────────────────────────────────────── - const SEEN_PATTERNS = [ - /\/api\/v1\/media\/[\w-]+\/seen\//, - /\/api\/v1\/stories\/reel\/seen\//, - /\/api\/v1\/direct_v2\/threads\/[\w-]+\/seen\//, - /\/api\/v1\/direct_v2\/visual_message\/[\w-]+\/seen\//, - /\/api\/v1\/live\/[\w-]+\/comment\/seen\//, - ]; - - // ─── Activity patterns (like, comment) — intercepted for local history ──── - const ACTIVITY_PATTERNS = [ - /\/api\/v1\/web\/likes\/[\w-]+\/like\//, - /\/api\/v1\/web\/comments\/add\//, - /\/api\/v1\/friendships\/[\w-]+\/follow\//, - ]; - - const isSeen = (url) => SEEN_PATTERNS.some((p) => p.test(url)); - const isActivity = (url) => ACTIVITY_PATTERNS.some((p) => p.test(url)); - - const fakeOkResponse = () => - new Response(JSON.stringify({ status: 'ok' }), { - status: 200, - headers: { 'Content-Type': 'application/json' }, - }); - - // ─── Fetch override ─────────────────────────────────────────────────────── - const _fetch = window.fetch.bind(window); - - const patchedFetch = async function (input, init) { - const url = - typeof input === 'string' - ? input - : input instanceof URL - ? input.href - : input?.url ?? ''; - - // Block seen - if (isSeen(url)) { - if (window.GhostChannel) { - window.GhostChannel.postMessage( - JSON.stringify({ type: 'seen_blocked', url }) - ); - } - return fakeOkResponse(); - } - - // Intercept activity for local history - if (isActivity(url) && window.ActivityChannel) { - const body = init?.body; - const bodyText = - body instanceof URLSearchParams - ? body.toString() - : typeof body === 'string' - ? body - : ''; - window.ActivityChannel.postMessage( - JSON.stringify({ url, body: bodyText, timestamp: Date.now() }) - ); - } - - return _fetch(input, init); - }; - - // Disguise as native - Object.defineProperty(window, 'fetch', { - value: patchedFetch, - writable: true, - configurable: true, - enumerable: true, - }); - window.fetch.toString = () => 'function fetch() { [native code] }'; - window.fetch[Symbol.toStringTag] = 'fetch'; - - // ─── XMLHttpRequest override ────────────────────────────────────────────── - const _XHROpen = XMLHttpRequest.prototype.open; - const _XHRSend = XMLHttpRequest.prototype.send; - - XMLHttpRequest.prototype.open = function (method, url, ...args) { - this._fg_url = url ?? ''; - this._fg_method = (method ?? '').toUpperCase(); - return _XHROpen.call(this, method, url, ...args); - }; - - XMLHttpRequest.prototype.send = function (body) { - if (this._fg_url && isSeen(this._fg_url)) { - // Fire readyState 4 with fake success without actually sending - const self = this; - setTimeout(() => { - Object.defineProperty(self, 'readyState', { get: () => 4 }); - Object.defineProperty(self, 'status', { get: () => 200 }); - Object.defineProperty(self, 'responseText', { - get: () => '{"status":"ok"}', - }); - Object.defineProperty(self, 'response', { - get: () => '{"status":"ok"}', - }); - self.dispatchEvent(new Event('readystatechange')); - self.dispatchEvent(new Event('load')); - }, 10); - return; - } - return _XHRSend.call(this, body); - }; - - // ─── WebSocket intercept (message-seen via WS) ──────────────────────────── - const _WS = window.WebSocket; - - function PatchedWebSocket(url, protocols) { - const ws = protocols ? new _WS(url, protocols) : new _WS(url); - const _send = ws.send.bind(ws); - - ws.send = function (data) { - if (typeof data === 'string') { - // IG sends seen ops as JSON with "op":"4" or "op":"seen" depending on version - try { - const parsed = JSON.parse(data); - if ( - parsed?.op === '4' || - parsed?.op === 'seen' || - (parsed?.payload && JSON.parse(parsed.payload)?.op === 'seen') - ) { - return; // drop - } - } catch (_) {} - // Text-based seen signal check - if (data.includes('"seen"') && data.includes('"thread_id"')) { - return; - } - } - return _send(data); - }; - - return ws; - } - - // Preserve WebSocket prototype chain so IG's ws checks pass - PatchedWebSocket.prototype = _WS.prototype; - PatchedWebSocket.CONNECTING = _WS.CONNECTING; - PatchedWebSocket.OPEN = _WS.OPEN; - PatchedWebSocket.CLOSING = _WS.CLOSING; - PatchedWebSocket.CLOSED = _WS.CLOSED; - window.WebSocket = PatchedWebSocket; - - // ─── Visibility trick — hide "Active Now" ──────────────────────────────── - // Only applied if user enables online-status hiding - // Wrapped in a named fn so Flutter can call it: - // controller.evaluateJavascript(source: 'window.__fgEnableOnlineHide()') - window.__fgEnableOnlineHide = function () { - Object.defineProperty(document, 'visibilityState', { - get: () => 'hidden', - configurable: true, - }); - Object.defineProperty(document, 'hidden', { - get: () => true, - configurable: true, - }); - document.dispatchEvent(new Event('visibilitychange')); - }; - - window.__fgDisableOnlineHide = function () { - // Restore by deleting the overrides (falls back to native getter) - delete document.visibilityState; - delete document.hidden; - document.dispatchEvent(new Event('visibilitychange')); - }; - - // Signal to Flutter that ghost mode JS is active - if (window.GhostChannel) { - window.GhostChannel.postMessage(JSON.stringify({ type: 'ready' })); - } -})(); diff --git a/v2/ghost_mode_script.dart b/v2/ghost_mode_script.dart deleted file mode 100644 index 897bcb0..0000000 --- a/v2/ghost_mode_script.dart +++ /dev/null @@ -1,286 +0,0 @@ -// lib/services/ghost_mode_script.dart -// Injected at AT_DOCUMENT_START — before Instagram's JS caches fetch/XHR refs - -const String kGhostModeJS = r""" -(function () { - 'use strict'; - - // ─── BLOCKED REST ENDPOINTS ─────────────────────────────────────────────── - // Patterns matched against full request URL - const URL_BLOCKLIST = [ - // Story viewed receipts - /\/api\/v1\/media\/seen\//, - /\/api\/v1\/feed\/viewed_story\//, - /\/api\/v1\/feed\/reels_tray\/seen\//, - - // DM read receipts (REST fallback path) - /\/api\/v1\/direct_v2\/threads\/[^/]+\/mark_item_seen\//, - /\/api\/v1\/direct_v2\/mark_item_seen\//, - - // Ephemeral photo/video reply viewed (Anti-Reply Image) - /\/api\/v1\/direct_v2\/threads\/[^/]+\/items\/[^/]+\/mark_visual_item_seen\//, - /\/api\/v1\/direct_v2\/visual_thread\/[^/]+\/seen\//, - - // Voice message listened receipt - /\/api\/v1\/direct_v2\/threads\/[^/]+\/items\/[^/]+\/mark_audio_seen\//, - - // Live join broadcast notification - /\/api\/v1\/live\/[^/]+\/join\//, - /\/api\/v1\/live\/[^/]+\/get_join_requests\//, - /\/api\/v1\/live\/[^/]+\/start_broadcast\//, - - // Analytics / tracking - /\/api\/v1\/qe\//, - /\/api\/v1\/launcher\/sync\//, - /\/api\/v1\/logging\//, - /\/api\/v1\/fb_onetap_logging\//, - /\/ajax\/bz/, - /\/ajax\/logging\//, - /\/api\/v1\/stats\//, - /\/api\/v1\/fbanalytics\//, - /\/api\/v1\/growth\/account_linked_now\//, - ]; - - // ─── BLOCKED GRAPHQL OPERATIONS ─────────────────────────────────────────── - // Instagram web uses GraphQL for many actions — match by operation name in body - const GRAPHQL_OP_BLOCKLIST = [ - // Story seen - 'MarkStorySeen', - 'markStorySeen', - 'ReelSeenMutation', - 'reel_seen', - 'IgFeedSeen', - - // DM read receipts - 'MarkDirectThreadItemSeen', - 'markDirectThreadItemSeen', - 'DirectMarkItemSeen', - 'DirectThreadMarkSeen', - - // Ephemeral media seen - 'MarkVisualMessageSeen', - 'DirectMarkVisualItemSeen', - - // Voice message listened - 'MarkAudioMessageSeen', - 'AudioSeenMutation', - - // Live join - 'LiveJoinBroadcast', - 'JoinLiveBroadcast', - 'MarkLiveViewer', - - // Analytics mutations - 'LogImpression', - 'LogClick', - 'FeedbackSeenMutation', - ]; - - // ─── HELPERS ────────────────────────────────────────────────────────────── - function shouldBlockUrl(url) { - if (!url) return false; - try { - const path = new URL(url, location.origin).pathname + new URL(url, location.origin).search; - return URL_BLOCKLIST.some(p => p.test(path)); - } catch { - return URL_BLOCKLIST.some(p => p.test(url)); - } - } - - function shouldBlockGraphQL(body) { - if (!body) return false; - let str = ''; - if (typeof body === 'string') { - str = body; - } else if (body instanceof URLSearchParams) { - str = body.toString(); - } - return GRAPHQL_OP_BLOCKLIST.some(op => str.includes(op)); - } - - function isGraphQLEndpoint(url) { - return url.includes('/graphql') || url.includes('/api/graphql'); - } - - function fakeOk(body) { - return new Response( - JSON.stringify(body || { status: 'ok', result: 'success' }), - { - status: 200, - headers: { 'Content-Type': 'application/json' }, - } - ); - } - - // ─── FETCH INTERCEPT ────────────────────────────────────────────────────── - const _fetch = window.fetch; - window.fetch = async function (input, init) { - const url = - typeof input === 'string' - ? input - : input instanceof Request - ? input.url - : String(input); - - if (shouldBlockUrl(url)) { - return fakeOk(); - } - - // Clone body for GraphQL inspection without consuming it - if (isGraphQLEndpoint(url) && init) { - let bodyStr = ''; - if (typeof init.body === 'string') { - bodyStr = init.body; - } else if (init.body instanceof URLSearchParams) { - bodyStr = init.body.toString(); - } else if (init.body instanceof FormData) { - // FormData: iterate entries to build string - try { - init.body.forEach((v, k) => { bodyStr += k + '=' + v + '&'; }); - } catch {} - } - if (shouldBlockGraphQL(bodyStr)) { - return fakeOk(); - } - } - - return _fetch.apply(this, arguments); - }; - - // ─── XHR INTERCEPT ─────────────────────────────────────────────────────── - const _xhrOpen = XMLHttpRequest.prototype.open; - const _xhrSend = XMLHttpRequest.prototype.send; - - XMLHttpRequest.prototype.open = function (method, url) { - this.__ghostUrl = url; - return _xhrOpen.apply(this, arguments); - }; - - XMLHttpRequest.prototype.send = function (body) { - const url = this.__ghostUrl || ''; - - const blockByUrl = shouldBlockUrl(url); - const blockByOp = isGraphQLEndpoint(url) && shouldBlockGraphQL( - typeof body === 'string' ? body : '' - ); - - if (blockByUrl || blockByOp) { - const self = this; - // Must use defineProperty because readyState etc are read-only - Object.defineProperty(self, 'readyState', { get: () => 4, configurable: true }); - Object.defineProperty(self, 'status', { get: () => 200, configurable: true }); - Object.defineProperty(self, 'responseText', { - get: () => '{"status":"ok"}', - configurable: true, - }); - Object.defineProperty(self, 'response', { - get: () => '{"status":"ok"}', - configurable: true, - }); - setTimeout(() => { - try { self.onreadystatechange && self.onreadystatechange(); } catch {} - try { self.onload && self.onload(); } catch {} - // Fire events - ['readystatechange', 'load'].forEach(t => { - try { self.dispatchEvent(new Event(t)); } catch {} - }); - }, 10); - return; - } - - return _xhrSend.apply(this, arguments); - }; - - // ─── WEBSOCKET INTERCEPT (typing + live join) ───────────────────────────── - // Instagram uses MQTT over WebSocket for real-time events. - // Typing indicator = MQTT PUBLISH to topic containing typing/activity tokens. - // Live join viewer notification = MQTT PUBLISH with live topic. - const _OrigWS = window.WebSocket; - - function GhostWebSocket(url, protocols) { - const ws = protocols ? new _OrigWS(url, protocols) : new _OrigWS(url); - const _wsSend = ws.send.bind(ws); - - ws.send = function (data) { - if (data instanceof ArrayBuffer || data instanceof Uint8Array) { - const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data; - - // MQTT packet type in top 4 bits of byte 0 - // PUBLISH = 0x3x (0x30 QoS0, 0x32 QoS1, 0x34 QoS2) - const packetType = bytes[0] & 0xF0; - if (packetType === 0x30) { - // Read remaining length (byte 1, simplified for short packets) - // MQTT topic starts at byte 4 (2 byte remaining-len + 2 byte topic-len) - try { - const decoded = new TextDecoder('utf-8', { fatal: false }).decode(bytes); - // Block typing / activity indicator / seen-receipt publishes - if ( - decoded.includes('/t_fs') || // foreground state (typing) - decoded.includes('/t_mt') || // mark thread seen - decoded.includes('/t_s') || // seen receipt - decoded.includes('/t_se') || // seen receipt (alt) - decoded.includes('activity_indicator') || - decoded.includes('is_typing') || - decoded.includes('direct_typing') || - decoded.includes('/live/viewer') || // live join notification - decoded.includes('live_viewer_list') - ) { - return; // Drop packet silently - } - } catch {} - } - } else if (typeof data === 'string') { - // Some WS implementations send JSON - if ( - data.includes('typing') || - data.includes('live_viewer') || - data.includes('is_typing') - ) { - return; - } - } - - return _wsSend(data); - }; - - return ws; - } - - // Preserve static properties - GhostWebSocket.prototype = _OrigWS.prototype; - Object.assign(GhostWebSocket, { - CONNECTING: _OrigWS.CONNECTING, - OPEN: _OrigWS.OPEN, - CLOSING: _OrigWS.CLOSING, - CLOSED: _OrigWS.CLOSED, - }); - window.WebSocket = GhostWebSocket; - - // ─── KILL SERVICE WORKER ────────────────────────────────────────────────── - // SW runs in separate context — bypasses all JS intercepts above. - // Kill registration so our fetch/XHR overrides are the only intercept layer. - if ('serviceWorker' in navigator) { - // Block new registrations - navigator.serviceWorker.register = function () { - return Promise.reject(new Error('[GhostMode] SW blocked')); - }; - // Unregister any already registered - navigator.serviceWorker.getRegistrations().then(regs => { - regs.forEach(r => r.unregister()); - }).catch(() => {}); - } - - // ─── BEACON API BLOCK ──────────────────────────────────────────────────── - // Instagram uses sendBeacon for analytics on page unload - if (navigator.sendBeacon) { - navigator.sendBeacon = function (url) { - if (shouldBlockUrl(url)) return true; // Lie — say it succeeded - // Block all beacon calls to ig domains — analytics only - if (url.includes('instagram.com') || url.includes('facebook.com')) return true; - return false; - }; - } - - console.log('[FocusGram] GhostMode active'); -})(); -"""; diff --git a/v2/ghost_mode_service.dart b/v2/ghost_mode_service.dart deleted file mode 100644 index 303b11a..0000000 --- a/v2/ghost_mode_service.dart +++ /dev/null @@ -1,233 +0,0 @@ -// lib/services/ghost_mode_service.dart -// -// Three-layer ghost mode: -// 1. AT_DOCUMENT_START JS injection — overrides fetch/XHR/WS before IG code runs -// 2. shouldInterceptRequest — native Android intercept (catches SW requests too) -// 3. FLAG_SECURE — anti-screenshot at OS level (disabled per user request) -// -// Usage: -// final service = GhostModeService(); -// await service.load(); // reads saved prefs -// -// InAppWebView( -// initialUserScripts: service.buildUserScripts(), -// onWebViewCreated: (c) => service.onWebViewCreated(c), -// shouldInterceptRequest: service.shouldInterceptRequest, -// ) -// -// // Anti-screenshot: disabled per user request -// // service.applyWindowFlags(context); - -import 'dart:typed_data'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -import 'ghost_mode_script.dart'; - -// ─── Feature flags ──────────────────────────────────────────────────────────── -class GhostFeatures { - bool hideStoryViews; - bool hideReadReceipts; - bool hideLiveJoin; - bool hideTypingIndicator; - bool hideVoiceListened; - bool hideReplyImageViewed; - bool disableAnalytics; - - GhostFeatures({ - this.hideStoryViews = true, - this.hideReadReceipts = true, - this.hideLiveJoin = true, - this.hideTypingIndicator = true, - this.hideVoiceListened = true, - this.hideReplyImageViewed = true, - this.disableAnalytics = true, - }); - - static const _keys = { - 'hideStoryViews': 'gm_story', - 'hideReadReceipts': 'gm_read', - 'hideLiveJoin': 'gm_live', - 'hideTypingIndicator': 'gm_typing', - 'hideVoiceListened': 'gm_voice', - 'hideReplyImageViewed': 'gm_reply', - 'disableAnalytics': 'gm_analytics', - }; - - Future save() async { - final p = await SharedPreferences.getInstance(); - await Future.wait([ - p.setBool(_keys['hideStoryViews']!, hideStoryViews), - p.setBool(_keys['hideReadReceipts']!, hideReadReceipts), - p.setBool(_keys['hideLiveJoin']!, hideLiveJoin), - p.setBool(_keys['hideTypingIndicator']!, hideTypingIndicator), - p.setBool(_keys['hideVoiceListened']!, hideVoiceListened), - p.setBool(_keys['hideReplyImageViewed']!, hideReplyImageViewed), - p.setBool(_keys['disableAnalytics']!, disableAnalytics), - ]); - } - - static Future load() async { - final p = await SharedPreferences.getInstance(); - return GhostFeatures( - hideStoryViews: p.getBool(_keys['hideStoryViews']!) ?? true, - hideReadReceipts: p.getBool(_keys['hideReadReceipts']!) ?? true, - hideLiveJoin: p.getBool(_keys['hideLiveJoin']!) ?? true, - hideTypingIndicator: p.getBool(_keys['hideTypingIndicator']!) ?? true, - hideVoiceListened: p.getBool(_keys['hideVoiceListened']!) ?? true, - hideReplyImageViewed: p.getBool(_keys['hideReplyImageViewed']!) ?? true, - disableAnalytics: p.getBool(_keys['disableAnalytics']!) ?? true, - ); - } -} - -// ─── Native URL blocklist (mirrors JS side — belt & suspenders) ─────────────── -final _nativeBlocklist = [ - RegExp(r'/api/v1/media/seen/'), - RegExp(r'/api/v1/feed/viewed_story/'), - RegExp(r'/api/v1/feed/reels_tray/seen/'), - RegExp(r'/api/v1/direct_v2/threads/[^/]+/mark_item_seen/'), - RegExp(r'/api/v1/direct_v2/mark_item_seen/'), - RegExp(r'/api/v1/direct_v2/threads/[^/]+/items/[^/]+/mark_visual_item_seen/'), - RegExp(r'/api/v1/direct_v2/visual_thread/[^/]+/seen/'), - RegExp(r'/api/v1/direct_v2/threads/[^/]+/items/[^/]+/mark_audio_seen/'), - RegExp(r'/api/v1/live/[^/]+/join/'), - RegExp(r'/api/v1/live/[^/]+/get_join_requests/'), - RegExp(r'/api/v1/qe/'), - RegExp(r'/api/v1/launcher/sync/'), - RegExp(r'/api/v1/logging/'), - RegExp(r'/api/v1/stats/'), - RegExp(r'/api/v1/fb_onetap_logging/'), - RegExp(r'/ajax/bz'), - RegExp(r'/ajax/logging/'), -]; - -final Uint8List _fakeOkBody = Uint8List.fromList('{"status":"ok"}'.codeUnits); - -// ─── Main service ───────────────────────────────────────────────────────────── -class GhostModeService { - GhostFeatures features = GhostFeatures(); - InAppWebViewController? _controller; - - Future load() async { - features = await GhostFeatures.load(); - } - - // ─── WebView setup ──────────────────────────────────────────────────────── - - /// Call from InAppWebView.onWebViewCreated - void onWebViewCreated(InAppWebViewController controller) { - _controller = controller; - } - - /// Pass to InAppWebView.initialUserScripts - /// AT_DOCUMENT_START = injected before ANY page script — critical for - /// overriding fetch/XHR before Instagram caches original refs. - List buildUserScripts() { - if (!_anyGhostEnabled()) return []; - return [ - UserScript( - source: _buildConfiguredScript(), - injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START, - forMainFrameOnly: false, // Apply to iframes too - ), - ]; - } - - /// Pass to InAppWebView.shouldInterceptRequest - /// Works at native Android level — catches requests from service workers too. - Future shouldInterceptRequest( - InAppWebViewController controller, - WebResourceRequest request, - ) async { - if (!_anyGhostEnabled()) return null; - final path = request.url.path; - if (_nativeBlocklist.any((re) => re.hasMatch(path))) { - return WebResourceResponse( - statusCode: 200, - reasonPhrase: 'OK', - contentType: 'application/json', - headers: {'Content-Type': 'application/json'}, - data: _fakeOkBody, - ); - } - return null; // Let through - } - - /// InAppWebViewSettings required for shouldInterceptRequest to fire - InAppWebViewSettings buildWebViewSettings() { - return InAppWebViewSettings( - useShouldInterceptRequest: true, // Enable native intercept callback - useShouldOverrideUrlLoading: true, - javaScriptEnabled: true, - disableDefaultErrorPage: true, - useHybridComposition: - true, // Needed for FLAG_SECURE to work (though disabled) - // Disable service worker cache that can replay seen-events offline - cacheEnabled: false, // Start clean — optional, tradeoff vs perf - ); - } - - // ─── Anti-screenshot ──────────────────────────────────────────────────────── - // Anti-screenshot disabled per user request - - Future applyWindowFlags(BuildContext context) async { - // Anti-screenshot disabled per user request - return; - } - - Future clearWindowFlags() async { - // Anti-screenshot disabled per user request - return; - } - - // ─── Re-inject after page nav (SPA navigation doesn't re-run userScripts) ── - - /// Call from InAppWebView.onLoadStop - Future onPageLoaded(Uri? url) async { - if (_controller == null || !_anyGhostEnabled()) return; - // Re-inject on each navigation — SPA route changes don't re-fire userScripts - await _controller!.evaluateJavascript(source: _buildConfiguredScript()); - } - - // ─── Private helpers ──────────────────────────────────────────────────────── - - bool _anyGhostEnabled() => - features.hideStoryViews || - features.hideReadReceipts || - features.hideLiveJoin || - features.hideTypingIndicator || - features.hideVoiceListened || - features.hideReplyImageViewed || - features.disableAnalytics; - - /// Build JS with feature flags baked in — disabled features skip their blocks - String _buildConfiguredScript() { - // Prepend a config object that the script reads - // The kGhostModeJS already handles all features unconditionally. - // If you need per-feature toggles, swap the const for a builder function. - // - // For now: only inject if ghost mode is on at all. - // Per-feature granularity can be added by replacing URL_BLOCKLIST - // sections conditionally — left as extension point. - return ''' - window.__GHOST_CONFIG__ = ${_configJson()}; - $kGhostModeJS - '''; - } - - String _configJson() { - return '''{ - "hideStoryViews": ${features.hideStoryViews}, - "hideReadReceipts": ${features.hideReadReceipts}, - "hideLiveJoin": ${features.hideLiveJoin}, - "hideTypingIndicator": ${features.hideTypingIndicator}, - "hideVoiceListened": ${features.hideVoiceListened}, - "hideReplyImageViewed": ${features.hideReplyImageViewed}, - "disableAnalytics": ${features.disableAnalytics} - }'''; - } -} diff --git a/v2/instagram_webview.dart b/v2/instagram_webview.dart deleted file mode 100644 index 724bb0c..0000000 --- a/v2/instagram_webview.dart +++ /dev/null @@ -1,201 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -import '../injection/script_engine.dart'; -import '../injection/script_registry.dart'; -import '../channels/channel_registry.dart'; -import '../webview/webview_config.dart'; -import '../services/ghost_mode_service.dart'; - -class InstagramWebView extends StatefulWidget { - const InstagramWebView({super.key}); - - @override - State createState() => InstagramWebViewState(); -} - -class InstagramWebViewState extends State { - InAppWebViewController? _controller; - ScriptEngine? _engine; - GhostModeService? _ghostMode; - bool _loading = true; - - // ── Public API — call from Settings screen ───────────────────────────── - Future toggleScript(ScriptId id, bool enabled) async { - await _engine?.toggle(id, enabled); - } - - Future setContentFlag(String flag, bool value) async { - await _engine?.setContentFlag(flag, value); - } - - Future setOnlineHide(bool enabled) async { - await _engine?.setOnlineHide(enabled); - } - - // Ghost mode controls - Future setGhostModeEnabled(bool enabled) async { - if (_ghostMode != null) { - _ghostMode!.features.disableAnalytics = enabled; - _ghostMode!.features.hideStoryViews = enabled; - _ghostMode!.features.hideReadReceipts = enabled; - _ghostMode!.features.hideLiveJoin = enabled; - _ghostMode!.features.hideTypingIndicator = enabled; - _ghostMode!.features.hideVoiceListened = enabled; - _ghostMode!.features.hideReplyImageViewed = enabled; - await _ghostMode!.features.save(); - // Reapply settings if webview exists - if (_controller != null) { - // Force reload to apply new settings - await _controller!.reload(); - } - } - } - - Future setAntiScreenshot(bool enabled) async { - if (_ghostMode != null) { - _ghostMode!.features.antiScreenshot = enabled; - await _ghostMode!.features.save(); - if (_ghostMode!.features.antiScreenshot) { - await _ghostMode!.applyWindowFlags(context); - } else { - await _ghostMode!.clearWindowFlags(); - } - } - } - - // ───────────────────────────────────────────────────────────────────────── - - @override - Widget build(BuildContext context) { - return Stack( - children: [ - InAppWebView( - initialUrlRequest: WebViewConfig.initialRequest, - initialSettings: - _ghostMode?.buildWebViewSettings() ?? WebViewConfig.settings, - - // ── ContentBlockers — merged base + EasyList rules ────────────── - contentBlockers: WebViewConfig.baseContentBlockers, - - // ── User Scripts — AT_DOCUMENT_START critical for ghost mode ───── - initialUserScripts: UnmodifiableListView( - _ghostMode?.buildUserScripts() ?? [], - ), - - // ── JavaScript channels ───────────────────────────────────────── - javascriptChannels: ChannelRegistry( - onActivityEvent: (event) { - // Forward to history DB in Phase 2 - debugPrint('[Activity] $event'); - }, - ).build(), - - onWebViewCreated: (controller) async { - _controller = controller; - - //Interceptor for adblock - shouldInterceptRequest: - (controller, request) async { - final url = request.url.toString(); - - const adDomains = [ - 'an.facebook.com', - 'connect.facebook.net', - 'pixel.facebook.com', - 'graph.facebook.com/logging', - 'www.instagram.com/ajax/bz', - 'www.instagram.com/api/v1/web/comet/logcalls', - 'doubleclick.net', - 'googletagmanager.com', - 'scorecardresearch.com', - ]; - - if (adDomains.any(url.contains)) { - return WebResourceResponse( - contentType: 'application/json', - httpStatus: WebResourceResponseHTTPStatus(statusCode: 200), - data: Uint8List.fromList(utf8.encode('{}')), - ); - } - return null; - }; - - // Initialize GhostModeService - _ghostMode = GhostModeService(); - await _ghostMode!.load(); - - // Initialize existing script engine for other scripts - final prefs = await SharedPreferences.getInstance(); - _engine = ScriptEngine(controller: controller, prefs: prefs); - - // Inject DOCUMENT_START scripts (ghost mode, etc.) - await _engine!.initDocumentStartScripts(); - }, - - onLoadStop: (controller, url) async { - // Inject DOCUMENT_END scripts - await _engine?.injectDocumentEndScripts(); - - // Re-inject ghost mode scripts on SPA navigation - await _ghostMode?.onPageLoaded(url?.uriValue); - - setState(() => _loading = false); - }, - - onLoadStart: (controller, url) { - setState(() => _loading = true); - }, - - onProgressChanged: (controller, progress) { - if (progress >= 80 && _loading) { - setState(() => _loading = false); - } - }, - - // ── Navigation policy ─────────────────────────────────────────── - shouldOverrideUrlLoading: (controller, navigationAction) async { - final url = navigationAction.request.url?.toString() ?? ''; - - // Block external redirects — keep user inside instagram.com - if (!url.contains('instagram.com') && - !url.contains('cdninstagram.com') && - !url.contains('fbcdn.net') && - url.startsWith('http')) { - // TODO: open in external browser via url_launcher - return NavigationActionPolicy.CANCEL; - } - - return NavigationActionPolicy.ALLOW; - }, - - // ── Re-inject on SPA navigation ───────────────────────────────── - // Instagram is a SPA — URL changes via pushState don't trigger - // onLoadStop. Re-inject DOM scripts on URL change. - onUpdateVisitedHistory: (controller, url, isReload) async { - if (!isReload!) { - await _engine?.injectDocumentEndScripts(); - } - }, - - // ── Native intercept for service worker requests ──────────────── - shouldInterceptRequest: (controller, request) async { - return await _ghostMode?.shouldInterceptRequest( - controller, - request, - ) ?? - null; - }, - ), - - // ── Subtle loading indicator ────────────────────────────────────── - if (_loading) - const LinearProgressIndicator( - minHeight: 2, - backgroundColor: Colors.transparent, - ), - ], - ); - } -} diff --git a/v2/main.dart b/v2/main.dart deleted file mode 100644 index 7e16e04..0000000 --- a/v2/main.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'core/theme/system_ui_manager.dart'; -import 'core/webview/instagram_webview.dart'; - -void main() async { - WidgetsFlutterBinding.ensureInitialized(); - - // Enable web contents debugging for ghost mode verification - if (kDebugMode) { - InAppWebViewController.setWebContentsDebuggingEnabled(true); - } - - await SystemUiManager.enableEdgeToEdge(); - runApp(const FocusGramApp()); -} - -class FocusGramApp extends StatelessWidget { - const FocusGramApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'FocusGram', - debugShowCheckedModeBanner: false, - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF0095F6)), - useMaterial3: true, - ), - home: const MainScreen(), - ); - } -} - -class MainScreen extends StatefulWidget { - const MainScreen({super.key}); - - @override - State createState() => _MainScreenState(); -} - -class _MainScreenState extends State { - final _webViewKey = GlobalKey(); - - @override - Widget build(BuildContext context) { - return Scaffold( - // backgroundColor transparent — lets WebView color bleed to system bars - backgroundColor: Colors.black, - body: SafeArea( - // bottom: false — let WebView extend behind nav bar for true edge-to-edge - bottom: false, - child: InstagramWebView(key: _webViewKey), - ), - ); - } -} diff --git a/v2/script_engine.dart b/v2/script_engine.dart deleted file mode 100644 index ed90f73..0000000 --- a/v2/script_engine.dart +++ /dev/null @@ -1,230 +0,0 @@ -import 'dart:convert'; -import 'package:flutter/services.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -import 'script_registry.dart'; - -class ScriptEngine { - final InAppWebViewController controller; - final SharedPreferences prefs; - - // Cache raw JS per asset path to avoid repeated rootBundle reads - final Map _cache = {}; - - ScriptEngine({required this.controller, required this.prefs}); - - // ── Init: restore enabled state from prefs, inject DOCUMENT_START scripts ─ - // Call this from onWebViewCreated (for DOCUMENT_START scripts via addUserScript) - Future initDocumentStartScripts() async { - for (final script in ScriptRegistry.all) { - // Restore enabled state - final saved = prefs.getBool('script_${script.id.name}'); - if (saved != null) script.enabled = saved; - - if (script.injectionTime == UserScriptInjectionTime.AT_DOCUMENT_START && - script.enabled) { - final code = await _load(script.assetPath); - if (code == null) continue; - await controller.addUserScript( - UserScript( - source: code, - injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START, - allowedOriginRules: {'https://www.instagram.com'}, - ), - ); - } - } - - // Initialize script configurations after scripts are loaded - await _initializeScriptConfigs(); - } - - // ── Initialize script configurations from saved preferences ──────────────── - Future _initializeScriptConfigs() async { - // Fetch interceptor config - final fetchInterceptor = ScriptRegistry.byId(ScriptId.fetchInterceptor); - if (fetchInterceptor.enabled) { - await _updateFetchInterceptorConfig(); - } - - // Autoplay blocker config - final autoplayBlocker = ScriptRegistry.byId(ScriptId.autoplayBlocker); - if (autoplayBlocker.enabled) { - await _updateAutoplayBlockerConfig(); - } - - // Content hider flags - await _pushContentFlags(); - } - - // ── Called from onLoadStop: inject all DOCUMENT_END enabled scripts ──────── - Future injectDocumentEndScripts() async { - for (final script in ScriptRegistry.all.where( - (s) => - s.injectionTime == UserScriptInjectionTime.AT_DOCUMENT_END && - s.enabled, - )) { - await _inject(script); - } - // After content_hider is injected, push saved content flags - await _pushContentFlags(); - } - - // ── Toggle a script on/off ───────────────────────────────────────────────── - Future toggle(ScriptId id, bool enabled) async { - final script = ScriptRegistry.byId(id); - script.enabled = enabled; - await prefs.setBool('script_${id.name}', enabled); - - if (!enabled) { - if (script.injectionTime == UserScriptInjectionTime.AT_DOCUMENT_START) { - await controller.removeUserScriptsByGroupName(id.name); - } - // For DOM scripts: reload so mutations stop - await controller.reload(); - return; - } - - if (script.injectionTime == UserScriptInjectionTime.AT_DOCUMENT_START) { - final code = await _load(script.assetPath); - if (code == null) return; - await controller.addUserScript( - UserScript( - source: code, - injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START, - groupName: id.name, - allowedOriginRules: {'https://www.instagram.com'}, - ), - ); - await controller.reload(); - } else { - await _inject(script); - } - - // Re-initialize configurations after toggle - await _initializeScriptConfigs(); - } - - // ── Content hider flags ──────────────────────────────────────────────────── - Future setContentFlag(String flag, bool value) async { - await prefs.setBool('content_$flag', value); - await _pushContentFlags(); - } - - Future _pushContentFlags() async { - final contentHider = ScriptRegistry.byId(ScriptId.contentHider); - if (!contentHider.enabled) return; - - final flags = { - 'stories': prefs.getBool('content_stories') ?? false, - 'posts': prefs.getBool('content_posts') ?? false, - 'reels': prefs.getBool('content_reels') ?? false, - 'suggested': prefs.getBool('content_suggested') ?? false, - }; - await controller.evaluateJavascript( - source: 'window.__fgContent?.applyAll(${jsonEncode(flags)})', - ); - } - - // ── Fetch interceptor configuration ──────────────────────────────────────── - Future setFetchInterceptorConfig({ - bool? blockAds, - bool? blockSponsored, - bool? blockSuggested, - bool? blockVideos, - bool? blockAutoplay, - }) async { - final prefs = await SharedPreferences.getInstance(); - final config = { - 'blockAds': blockAds ?? prefs.getBool('fetch_block_ads') ?? false, - 'blockSponsored': - blockSponsored ?? prefs.getBool('fetch_block_sponsored') ?? false, - 'blockSuggested': - blockSuggested ?? prefs.getBool('fetch_block_suggested') ?? false, - 'blockVideos': - blockVideos ?? prefs.getBool('fetch_block_videos') ?? false, - 'blockAutoplay': - blockAutoplay ?? prefs.getBool('fetch_block_autoplay') ?? false, - }; - - // Save individual prefs - await prefs.setBool('fetch_block_ads', config['blockAds']!); - await prefs.setBool('fetch_block_sponsored', config['blockSponsored']!); - await prefs.setBool('fetch_block_suggested', config['blockSuggested']!); - await prefs.setBool('fetch_block_videos', config['blockVideos']!); - await prefs.setBool('fetch_block_autoplay', config['blockAutoplay']!); - - // Apply to webview - await controller.evaluateJavascript( - source: 'window.__fgSetFilterConfig?.(${jsonEncode(config)})', - ); - } - - Future _updateFetchInterceptorConfig() async { - final prefs = await SharedPreferences.getInstance(); - await setFetchInterceptorConfig( - blockAds: prefs.getBool('fetch_block_ads'), - blockSponsored: prefs.getBool('fetch_block_sponsored'), - blockSuggested: prefs.getBool('fetch_block_suggested'), - blockVideos: prefs.getBool('fetch_block_videos'), - blockAutoplay: prefs.getBool('fetch_block_autoplay'), - ); - } - - // ── Autoplay blocker configuration ───────────────────────────────────────── - Future setAutoplayBlockerEnabled(bool enabled) async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setBool('autoplay_blocker_enabled', enabled); - - // Apply to webview - await controller.evaluateJavascript( - source: 'window.__fgSetBlockAutoplay?.(${jsonEncode(enabled)})', - ); - } - - Future _updateAutoplayBlockerConfig() async { - final prefs = await SharedPreferences.getInstance(); - await setAutoplayBlockerEnabled( - prefs.getBool('autoplay_blocker_enabled') ?? false, - ); - } - - // ── Online status hide ───────────────────────────────────────────────────── - Future setOnlineHide(bool enabled) async { - await prefs.setBool('ghost_online_hide', enabled); - if (enabled) { - await controller.evaluateJavascript( - source: 'window.__fgEnableOnlineHide?.()', - ); - } else { - await controller.evaluateJavascript( - source: 'window.__fgDisableOnlineHide?.()', - ); - } - } - - // ── Private helpers ──────────────────────────────────────────────────────── - Future _inject(InstaScript script) async { - final code = await _load(script.assetPath); - if (code == null) return; - try { - await controller.evaluateJavascript(source: code); - } catch (e) { - // Script failed — log but don't crash - debugPrint('[ScriptEngine] Failed to inject ${script.id.name}: $e'); - } - } - - Future _load(String assetPath) async { - if (_cache.containsKey(assetPath)) return _cache[assetPath]; - try { - final code = await rootBundle.loadString(assetPath); - _cache[assetPath] = code; - return code; - } catch (e) { - debugPrint('[ScriptEngine] Asset not found: $assetPath'); - return null; - } - } -} diff --git a/v2/script_registry.dart b/v2/script_registry.dart deleted file mode 100644 index 866ef3b..0000000 --- a/v2/script_registry.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; - -enum ScriptId { - ghostMode, - themeDetector, - contentHider, - fetchInterceptor, - autoplayBlocker, - mediaDetector, - historyTracker, -} - -class InstaScript { - final ScriptId id; - final String name; - final String description; - final String assetPath; - final UserScriptInjectionTime injectionTime; - bool enabled; - - InstaScript({ - required this.id, - required this.name, - required this.description, - required this.assetPath, - this.injectionTime = UserScriptInjectionTime.AT_DOCUMENT_END, - this.enabled = false, - }); -} - -class ScriptRegistry { - static final List all = [ - // ── DOCUMENT_START — must be before IG's JS loads ── - InstaScript( - id: ScriptId.fetchInterceptor, - name: 'Ad & Content Blocker', - description: - 'Blocks ads, sponsored, suggested content, videos, and prevents autoplay via GraphQL interception.', - assetPath: 'assets/scripts/fetch_interceptor.js', - injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START, - enabled: false, - ), - InstaScript( - id: ScriptId.autoplayBlocker, - name: 'Autoplay Blocker', - description: 'Prevents video autoplay.', - assetPath: 'assets/scripts/autoplay_blocker.js', - injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START, - enabled: false, - ), - - // ── DOCUMENT_END — DOM must be ready ── - InstaScript( - id: ScriptId.themeDetector, - name: 'Theme Detector', - description: 'Reads page colors and syncs system UI bars.', - assetPath: 'assets/scripts/theme_detector.js', - injectionTime: UserScriptInjectionTime.AT_DOCUMENT_END, - enabled: true, // always on — needed for native feel - ), - InstaScript( - id: ScriptId.contentHider, - name: 'Content Hider', - description: 'Toggleable hide for stories, posts, reels, suggested.', - assetPath: 'assets/scripts/content_hider.js', - injectionTime: UserScriptInjectionTime.AT_DOCUMENT_END, - enabled: false, - ), - // Phase 2 scripts — registered but empty asset paths for now - InstaScript( - id: ScriptId.mediaDetector, - name: 'Media Downloader', - description: 'Injects download buttons on photos and reels.', - assetPath: 'assets/scripts/media_detector.js', - injectionTime: UserScriptInjectionTime.AT_DOCUMENT_END, - enabled: false, - ), - InstaScript( - id: ScriptId.historyTracker, - name: 'History Tracker', - description: 'Locally tracks reels watched and actions taken.', - assetPath: 'assets/scripts/history_tracker.js', - injectionTime: UserScriptInjectionTime.AT_DOCUMENT_END, - enabled: false, - ), - ]; -} diff --git a/v2/system_ui_manager.dart b/v2/system_ui_manager.dart deleted file mode 100644 index 63d6b70..0000000 --- a/v2/system_ui_manager.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:convert'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -class SystemUiManager { - // ── Apply colors read from JS ThemeDetector ───────────────────────────── - static void applyFromThemePayload(String jsonPayload) { - try { - final data = jsonDecode(jsonPayload) as Map; - final isDark = data['isDark'] as bool? ?? false; - final bodyHex = - data['bodyHex'] as String? ?? (isDark ? '#000000' : '#ffffff'); - final navHex = data['navHex'] as String? ?? bodyHex; - - final bodyColor = _parseHex(bodyHex); - final navColor = _parseHex(navHex); - - SystemChrome.setSystemUIOverlayStyle( - SystemUiOverlayStyle( - statusBarColor: bodyColor, - statusBarIconBrightness: isDark ? Brightness.light : Brightness.dark, - statusBarBrightness: isDark ? Brightness.dark : Brightness.light, - systemNavigationBarColor: navColor, - systemNavigationBarIconBrightness: isDark - ? Brightness.light - : Brightness.dark, - systemNavigationBarDividerColor: Colors.transparent, - ), - ); - } catch (_) { - // Fallback to safe defaults - applyLight(); - } - } - - // ── Fallback presets ───────────────────────────────────────────────────── - static void applyLight() { - SystemChrome.setSystemUIOverlayStyle( - const SystemUiOverlayStyle( - statusBarColor: Color(0xFFFFFFFF), - statusBarIconBrightness: Brightness.dark, - statusBarBrightness: Brightness.light, - systemNavigationBarColor: Color(0xFFFFFFFF), - systemNavigationBarIconBrightness: Brightness.dark, - systemNavigationBarDividerColor: Colors.transparent, - ), - ); - } - - static void applyDark() { - SystemChrome.setSystemUIOverlayStyle( - const SystemUiOverlayStyle( - statusBarColor: Color(0xFF000000), - statusBarIconBrightness: Brightness.light, - statusBarBrightness: Brightness.dark, - systemNavigationBarColor: Color(0xFF000000), - systemNavigationBarIconBrightness: Brightness.light, - systemNavigationBarDividerColor: Colors.transparent, - ), - ); - } - - // ── Edge-to-edge setup — call once in main() ───────────────────────────── - static Future enableEdgeToEdge() async { - await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); - SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); - applyLight(); // default until theme detector fires - } - - // ───────────────────────────────────────────────────────────────────────── - static Color _parseHex(String hex) { - final clean = hex.replaceAll('#', ''); - if (clean.length == 6) { - return Color(int.parse('FF$clean', radix: 16)); - } else if (clean.length == 8) { - return Color(int.parse(clean, radix: 16)); - } - return Colors.white; - } -} diff --git a/v2/theme_detector.js b/v2/theme_detector.js deleted file mode 100644 index 54749c3..0000000 --- a/v2/theme_detector.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * FocusGram Theme Detector - * Reads Instagram's background + bottom nav color and reports to Flutter. - * Injected at DOCUMENT_END so DOM is ready. - */ -(function () { - 'use strict'; - - const parseRgb = (str) => { - // Parses "rgb(255, 255, 255)" or "rgba(0, 0, 0, 1)" → { r, g, b, a } - const m = str.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); - if (!m) return null; - return { - r: parseInt(m[1]), - g: parseInt(m[2]), - b: parseInt(m[3]), - a: m[4] !== undefined ? parseFloat(m[4]) : 1, - }; - }; - - const toHex = ({ r, g, b }) => - '#' + - [r, g, b].map((v) => v.toString(16).padStart(2, '0')).join(''); - - const detectColors = () => { - // Background — Instagram sets it on or a root div - const bodyBg = getComputedStyle(document.body).backgroundColor; - - // Bottom nav — IG mobile web renders a fixed bottom bar - // Target by role="navigation" or position:fixed at bottom - let navBg = bodyBg; - const navCandidates = document.querySelectorAll( - 'nav, [role="navigation"], div[style*="bottom"]' - ); - for (const el of navCandidates) { - const style = getComputedStyle(el); - if ( - style.position === 'fixed' && - parseInt(style.bottom) <= 10 && - style.backgroundColor !== 'rgba(0, 0, 0, 0)' - ) { - navBg = style.backgroundColor; - break; - } - } - - const bodyColor = parseRgb(bodyBg); - const navColor = parseRgb(navBg); - - if (!bodyColor) return; - - // Determine dark/light - const luminance = (0.299 * bodyColor.r + 0.587 * bodyColor.g + 0.114 * bodyColor.b) / 255; - const isDark = luminance < 0.5; - - const payload = { - bodyHex: toHex(bodyColor), - navHex: navColor ? toHex(navColor) : toHex(bodyColor), - isDark, - }; - - if (window.ThemeChannel) { - window.ThemeChannel.postMessage(JSON.stringify(payload)); - } - }; - - // Run on load - detectColors(); - - // Watch for Instagram's dark mode toggle (adds/removes class on ) - const observer = new MutationObserver(detectColors); - observer.observe(document.documentElement, { - attributes: true, - attributeFilter: ['class', 'style', 'color-scheme'], - }); - observer.observe(document.body, { - attributes: true, - attributeFilter: ['class', 'style'], - }); - - // Also run after navigation (Instagram is SPA, URL changes without reload) - let lastUrl = location.href; - new MutationObserver(() => { - if (location.href !== lastUrl) { - lastUrl = location.href; - setTimeout(detectColors, 300); // small delay for IG to render new page - } - }).observe(document.body, { childList: true, subtree: true }); -})(); diff --git a/v2/webview_config.dart b/v2/webview_config.dart deleted file mode 100644 index 21ec16b..0000000 --- a/v2/webview_config.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; - -class WebViewConfig { - // ── User agent — exactly as user specified ──────────────────────────────── - static const String userAgent = - 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) ' - 'AppleWebKit/605.1.15 (KHTML, like Gecko) ' - 'Version/26.0 Mobile/15E148 Safari/604.1'; - - static const String instagramUrl = 'https://www.instagram.com/'; - - // ── Base InAppWebView settings ──────────────────────────────────────────── - static InAppWebViewSettings get settings => InAppWebViewSettings( - // Identity - userAgent: userAgent, - - // Performance - hardwareAcceleration: true, - // useHybridComposition: false breaks some Android 12+ devices — keep true - useHybridComposition: true, - cacheEnabled: true, - cacheMode: CacheMode.LOAD_DEFAULT, - - // Media - mediaPlaybackRequiresUserGesture: false, - allowsInlineMediaPlayback: true, - allowsPictureInPictureMediaPlayback: true, - - // UX — feel like native, not browser - overScrollMode: OverScrollMode.NEVER, - verticalScrollBarEnabled: false, - horizontalScrollBarEnabled: false, - supportZoom: false, - builtInZoomControls: false, - displayZoomControls: false, - scrollsToTop: true, - - // JS & storage — IG needs all of these - javaScriptEnabled: true, - javaScriptCanOpenWindowsAutomatically: false, - domStorageEnabled: true, - databaseEnabled: true, - allowFileAccessFromFileURLs: false, - allowUniversalAccessFromFileURLs: false, - - // Compat - mixedContentMode: MixedContentMode.COMPATIBILITY_MODE, - safeBrowsingEnabled: - false, // IG known-safe domain, no need for extra latency - // Disable Chrome custom tabs popup (links open in WebView) - suppressesIncrementalRendering: false, - - // iOS specific - allowsBackForwardNavigationGestures: true, - allowsLinkPreview: false, - isFraudulentWebsiteWarningEnabled: false, - - // Android specific - forceDark: ForceDark.AUTO, // respect system dark mode - algorithmicDarkeningAllowed: true, - ); - - // ── ContentBlocker rules — ad network blocking ───────────────────────── - // These are baked-in rules targeting known ad/tracking domains. - // Full EasyList parsing is handled separately and merged at runtime. - // This set is always-on regardless of user toggle. - static List get baseContentBlockers => [ - // Meta ad infrastructure - _block('.*connect\\.facebook\\.net.*'), - _block('.*graph\\.facebook\\.com.*ads.*'), - _block('.*an\\.facebook\\.com.*'), - - // Google ad networks - _block('.*doubleclick\\.net.*'), - _block('.*googleadservices\\.com.*'), - _block('.*googlesyndication\\.com.*'), - _block('.*adservice\\.google\\..*'), - - // Common trackers - _block('.*scorecardresearch\\.com.*'), - _block('.*quantserve\\.com.*'), - _block('.*chartbeat\\.com.*'), - _block('.*newrelic\\.com.*'), - - // Ad servers - _block('.*ads\\.yahoo\\.com.*'), - _block('.*advertising\\.com.*'), - _block('.*adnxs\\.com.*'), - _block('.*adsrvr\\.org.*'), - _block('.*taboola\\.com.*'), - _block('.*outbrain\\.com.*'), - _block('.*pubmatic\\.com.*'), - _block('.*rubiconproject\\.com.*'), - _block('.*openx\\.net.*'), - _block('.*casalemedia\\.com.*'), - _block('.*criteo\\.com.*'), - _block('.*criteo\\.net.*'), - - // Pixel trackers - _block('.*pixel\\.quantserve\\.com.*'), - _block('.*pixel\\.facebook\\.com.*'), - - // IG-specific ad endpoints (safe to block — don't affect core IG) - _block('.*\\.instagram\\.com.*\\/ads\\/.*'), - ]; - - static ContentBlocker _block(String pattern) => ContentBlocker( - trigger: ContentBlockerTrigger(urlFilter: pattern), - action: ContentBlockerAction(type: ContentBlockerActionType.BLOCK), - ); - - // ── URLRequest for initial load ─────────────────────────────────────────── - static URLRequest get initialRequest => URLRequest( - url: WebUri(instagramUrl), - headers: {'Accept-Language': 'en-US,en;q=0.9', 'DNT': '1'}, - ); -}

    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�_(f~+aBGrl)qX91vhfw)(Lf`TS|FarI-^MqvX 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 Xa<|S`zvWh?1COD;iC)E3`=|d0V)5`S literal 410926 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%0ARs6!DK6auT3aIMk3I>;BaZ~loDWxks}ebv^U=Q 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

FrkH=ynKhmDGjyvTgzQ-1| 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