mirror of
https://github.com/Ujwal223/FocusGram.git
synced 2026-05-24 16:14:01 +02:00
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:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 ───────────────────────────────────────
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user