Added Notification Bridge.

Added an onboarding screen.
Fixed problem wherebottom bar blocked sending message.
added a option to go to instagram's settings page in our settings page.
added a notifications icon in bottombar.
Other few Improvements and Bug fixes.
This commit is contained in:
Ujwal
2026-02-23 11:37:15 +05:45
parent e23731d9e8
commit 878e625f0e
22 changed files with 726 additions and 78 deletions
+43 -2
View File
@@ -17,6 +17,16 @@ class InjectionController {
-webkit-tap-highlight-color: transparent !important;
outline: none !important;
}
/* Hide all scrollbars */
::-webkit-scrollbar {
display: none !important;
width: 0 !important;
height: 0 !important;
}
* {
-ms-overflow-style: none !important;
scrollbar-width: none !important;
}
''';
/// CSS to disable text selection globally.
@@ -93,14 +103,19 @@ class InjectionController {
/// Robust CSS that hides Instagram's native bottom nav bar.
static const String _hideInstagramNavCSS = '''
div[role="tablist"], nav[role="navigation"],
._acbl, ._aa4b, ._aahi, ._ab8s, section nav, footer nav {
/* Hide bottom nav but keep search header */
div[role="tablist"], footer nav, ._acbl, ._aa4b {
display: none !important;
visibility: hidden !important;
height: 0 !important;
overflow: hidden !important;
pointer-events: none !important;
}
/* Only hide top nav if not on search page */
body:not([path*="/explore/search/"]) nav[role="navigation"],
body:not([path*="/explore/search/"]) section nav {
display: none !important;
}
body, #react-root, main {
padding-bottom: 0 !important;
margin-bottom: 0 !important;
@@ -214,6 +229,30 @@ class InjectionController {
})();
''';
/// Hijacks the Web Notification API to bridge Instagram notifications to native.
static String get notificationBridgeJS => """
(function() {
const NativeNotification = window.Notification;
if (!NativeNotification) return;
window.Notification = function(title, options) {
const body = (options && options.body) ? options.body : "";
// Pass to Flutter
if (window.FocusGramNotificationChannel) {
window.FocusGramNotificationChannel.postMessage(title + ": " + body);
}
return new NativeNotification(title, options);
};
window.Notification.permission = "granted";
window.Notification.requestPermission = function() {
return Promise.resolve("granted");
};
})();
""";
/// MutationObserver for Reel scroll locking.
static const String reelsMutationObserverJS = '''
(function() {
@@ -267,6 +306,8 @@ class InjectionController {
return '''
${buildSessionStateJS(sessionActive)}
/* Set path attribute on body for CSS targeting */
document.body.setAttribute('path', window.location.pathname);
${_buildMutationObserver(css.toString())}
$_periodicNavRemoverJS
$_dismissAppBannerJS
+69
View File
@@ -0,0 +1,69 @@
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class NotificationService {
static final NotificationService _instance = NotificationService._internal();
factory NotificationService() => _instance;
NotificationService._internal();
final FlutterLocalNotificationsPlugin _notificationsPlugin =
FlutterLocalNotificationsPlugin();
Future<void> init() async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings initializationSettingsIOS =
DarwinInitializationSettings(
requestAlertPermission: false,
requestBadgePermission: false,
requestSoundPermission: false,
);
const InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
await _notificationsPlugin.initialize(
settings: initializationSettings,
onDidReceiveNotificationResponse: (details) {
// Handle notification tap
},
);
}
Future<void> showNotification({
required int id,
required String title,
required String body,
}) async {
const AndroidNotificationDetails androidDetails =
AndroidNotificationDetails(
'focusgram_channel',
'FocusGram Notifications',
channelDescription: 'Notifications for FocusGram sessions and alerts',
importance: Importance.max,
priority: Priority.high,
showWhen: true,
);
const DarwinNotificationDetails iosDetails = DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
);
const NotificationDetails platformDetails = NotificationDetails(
android: androidDetails,
iOS: iosDetails,
);
await _notificationsPlugin.show(
id: id,
title: title,
body: body,
notificationDetails: platformDetails,
);
}
}
+8
View File
@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'notification_service.dart';
/// Manages all session logic for FocusGram:
///
@@ -308,6 +309,13 @@ class SessionManager extends ChangeNotifier {
_lastSessionEnd = DateTime.now();
_prefs?.setInt(_keySessionExpiry, 0);
_prefs?.setInt(_keyLastSessionEnd, _lastSessionEnd!.millisecondsSinceEpoch);
// Alert User
NotificationService().showNotification(
id: 999,
title: 'Session Ended',
body: 'Your Reel session has expired. Time to focus!',
);
}
// ── Reel session API ───────────────────────────────────────
+45 -6
View File
@@ -10,16 +10,23 @@ class SettingsService extends ChangeNotifier {
static const _keyRequireWordChallenge = 'set_require_word_challenge';
static const _keyGhostMode = 'set_ghost_mode';
static const _keyEnableTextSelection = 'set_enable_text_selection';
static const _keyEnabledTabs = 'set_enabled_tabs';
static const _keyShowInstaSettings = 'set_show_insta_settings';
static const _keyIsFirstRun = 'set_is_first_run';
SharedPreferences? _prefs;
bool _blurExplore = true; // Default: blur explore feed posts/reels
bool _blurReels = false; // If false: hide reels in feed (after session ends)
bool _requireLongPress = true; // Long-press FAB to start session
bool _showBreathGate = true; // Show breathing gate on every open
bool _blurExplore = true;
bool _blurReels = false;
bool _requireLongPress = true;
bool _showBreathGate = true;
bool _requireWordChallenge = true;
bool _ghostMode = true; // Default: hide seen/typing
bool _enableTextSelection = false; // Default: disabled
bool _ghostMode = true;
bool _enableTextSelection = false;
bool _showInstaSettings = true;
List<String> _enabledTabs = ['Home', 'Search', 'Create', 'Reels', 'Profile'];
bool _isFirstRun = true;
bool get blurExplore => _blurExplore;
bool get blurReels => _blurReels;
@@ -28,6 +35,9 @@ class SettingsService extends ChangeNotifier {
bool get requireWordChallenge => _requireWordChallenge;
bool get ghostMode => _ghostMode;
bool get enableTextSelection => _enableTextSelection;
bool get showInstaSettings => _showInstaSettings;
List<String> get enabledTabs => _enabledTabs;
bool get isFirstRun => _isFirstRun;
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
@@ -38,6 +48,17 @@ class SettingsService extends ChangeNotifier {
_requireWordChallenge = _prefs!.getBool(_keyRequireWordChallenge) ?? true;
_ghostMode = _prefs!.getBool(_keyGhostMode) ?? true;
_enableTextSelection = _prefs!.getBool(_keyEnableTextSelection) ?? false;
_showInstaSettings = _prefs!.getBool(_keyShowInstaSettings) ?? true;
_enabledTabs =
_prefs!.getStringList(_keyEnabledTabs) ??
['Home', 'Search', 'Create', 'Reels', 'Profile'];
_isFirstRun = _prefs!.getBool(_keyIsFirstRun) ?? true;
notifyListeners();
}
Future<void> setFirstRunCompleted() async {
_isFirstRun = false;
await _prefs?.setBool(_keyIsFirstRun, false);
notifyListeners();
}
@@ -82,4 +103,22 @@ class SettingsService extends ChangeNotifier {
await _prefs?.setBool(_keyEnableTextSelection, v);
notifyListeners();
}
Future<void> setShowInstaSettings(bool v) async {
_showInstaSettings = v;
await _prefs?.setBool(_keyShowInstaSettings, v);
notifyListeners();
}
Future<void> toggleTab(String tab) async {
if (_enabledTabs.contains(tab)) {
if (_enabledTabs.length > 1) {
_enabledTabs.remove(tab);
}
} else {
_enabledTabs.add(tab);
}
await _prefs?.setStringList(_keyEnabledTabs, _enabledTabs);
notifyListeners();
}
}