Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 50 additions & 31 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'dart:io';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:window_manager/window_manager.dart';

import 'database/database.dart';
import 'models/app_settings.dart';
Expand Down Expand Up @@ -46,6 +47,7 @@ import 'utils/notification_payload.dart';
import 'package:permission_handler/permission_handler.dart';
import 'widgets/deep_link_listener.dart';
import 'services/debug_log_service.dart';
import 'services/window_state_service.dart';

// Global navigator key for deep linking
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Expand Down Expand Up @@ -90,6 +92,11 @@ Future<void> _runAppStartup() async {
print('🚀 TEAM Flutter starting...');
print('✅ Flutter binding initialized');

final isDesktop = !Platform.isAndroid && !Platform.isIOS;
if (isDesktop) {
await windowManager.ensureInitialized();
}

try {
// Initialize the database
print('📦 Initializing database...');
Expand All @@ -108,6 +115,12 @@ Future<void> _runAppStartup() async {
final settingsService = SettingsService(prefs);
print('✅ Settings loaded');

if (isDesktop) {
final windowStateService = WindowStateService(prefs);
await windowStateService.restoreWindowState();
windowManager.addListener(windowStateService);
}

// Initialize notification plugin
print('🔔 Initializing notifications...');
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
Expand Down Expand Up @@ -329,7 +342,8 @@ Future<void> _runAppStartup() async {
/// Handle notification tap to navigate to specific chat
void _handleNotificationTap(
NotificationResponse details, AppDatabase database) async {
print('📬 Notification tapped: ${details.payload} action=${details.actionId}');
print(
'📬 Notification tapped: ${details.payload} action=${details.actionId}');

// Mesh-connection "Stop" action button, or a swipe-dismiss of the persistent
// mesh notification → fully stop the service (kills a stuck reconnect).
Expand Down Expand Up @@ -376,7 +390,8 @@ void _handleNotificationTap(
if (contact.isRepeater) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.directMessagesDisabledForRepeaters),
content: Text(AppLocalizations.of(context)!
.directMessagesDisabledForRepeaters),
),
);
return;
Expand Down Expand Up @@ -505,7 +520,9 @@ class TeamFlutterApp extends StatelessWidget {
final appTheme = settings.settings.appTheme;
final isNighttime = appTheme == AppThemeMode.nighttime;
SystemChrome.setEnabledSystemUIMode(
isNighttime ? SystemUiMode.immersiveSticky : SystemUiMode.edgeToEdge,
isNighttime
? SystemUiMode.immersiveSticky
: SystemUiMode.edgeToEdge,
);
return MaterialApp(
navigatorKey: navigatorKey,
Expand All @@ -530,21 +547,24 @@ class TeamFlutterApp extends StatelessWidget {
),
useMaterial3: true,
),
darkTheme: isNighttime ? _nighttimeTheme() : ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.dark,
),
appBarTheme: const AppBarTheme(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.grey,
),
useMaterial3: true,
),
darkTheme: isNighttime
? _nighttimeTheme()
: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.dark,
),
appBarTheme: const AppBarTheme(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
),
bottomNavigationBarTheme:
const BottomNavigationBarThemeData(
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.grey,
),
useMaterial3: true,
),
themeMode: switch (appTheme) {
AppThemeMode.light => ThemeMode.light,
AppThemeMode.dark => ThemeMode.dark,
Expand All @@ -569,7 +589,6 @@ class TeamFlutterApp extends StatelessWidget {
}
}


ThemeData _nighttimeTheme() {
final base = ColorScheme.fromSeed(
seedColor: NightColors.primary,
Expand Down Expand Up @@ -604,18 +623,18 @@ ThemeData _nighttimeTheme() {
iconTheme: const IconThemeData(color: NightColors.onSurface),
hintColor: NightColors.onSurfaceVariant,
switchTheme: SwitchThemeData(
trackColor: WidgetStateProperty.resolveWith((states) => states
.contains(WidgetState.selected)
? NightColors.primary
: NightColors.dimmest),
thumbColor: WidgetStateProperty.resolveWith((states) => states
.contains(WidgetState.selected)
? NightColors.onSurface
: NightColors.dim),
trackOutlineColor: WidgetStateProperty.resolveWith((states) => states
.contains(WidgetState.selected)
? Colors.transparent
: NightColors.dim),
trackColor: WidgetStateProperty.resolveWith((states) =>
states.contains(WidgetState.selected)
? NightColors.primary
: NightColors.dimmest),
thumbColor: WidgetStateProperty.resolveWith((states) =>
states.contains(WidgetState.selected)
? NightColors.onSurface
: NightColors.dim),
trackOutlineColor: WidgetStateProperty.resolveWith((states) =>
states.contains(WidgetState.selected)
? Colors.transparent
: NightColors.dim),
),
sliderTheme: const SliderThemeData(
activeTrackColor: NightColors.primary,
Expand Down
66 changes: 66 additions & 0 deletions lib/services/window_state_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) 2026 tmacinc
// Licensed under CC BY-NC-SA 4.0

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:window_manager/window_manager.dart';

/// Remembers the desktop window's size and position across launches.
///
/// Desktop-only, ephemeral UI state that nothing else needs to react to --
/// deliberately kept out of the reactive AppSettings/SettingsService model.
///
/// Note: window_manager's "finished" resize/move events
/// (onWindowResized/onWindowMoved) are macOS/Windows only, not available on
/// Linux. This uses the continuous onWindowResize/onWindowMove events with
/// its own debounce instead, so it works on Linux too.
class WindowStateService with WindowListener {
static const _keyX = 'window_x';
static const _keyY = 'window_y';
static const _keyWidth = 'window_width';
static const _keyHeight = 'window_height';
static const _debounce = Duration(milliseconds: 500);

final SharedPreferences _prefs;
Timer? _debounceTimer;

WindowStateService(this._prefs);

/// Applies the saved window bounds, if any were previously recorded.
/// A first launch (nothing saved yet) leaves the platform default alone.
Future<void> restoreWindowState() async {
final x = _prefs.getDouble(_keyX);
final y = _prefs.getDouble(_keyY);
final width = _prefs.getDouble(_keyWidth);
final height = _prefs.getDouble(_keyHeight);
if (x == null || y == null || width == null || height == null) return;

await windowManager.setBounds(Rect.fromLTWH(x, y, width, height));
}

@override
void onWindowResize() => _scheduleSave();

@override
void onWindowMove() => _scheduleSave();

void _scheduleSave() {
_debounceTimer?.cancel();
_debounceTimer = Timer(_debounce, _saveBounds);
}

Future<void> _saveBounds() async {
final bounds = await windowManager.getBounds();
await _prefs.setDouble(_keyX, bounds.left);
await _prefs.setDouble(_keyY, bounds.top);
await _prefs.setDouble(_keyWidth, bounds.width);
await _prefs.setDouble(_keyHeight, bounds.height);
}

void dispose() {
_debounceTimer?.cancel();
windowManager.removeListener(this);
}
}
8 changes: 8 additions & 0 deletions linux/flutter/generated_plugin_registrant.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,25 @@
#include "generated_plugin_registrant.h"

#include <gtk/gtk_plugin.h>
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
#include <window_manager/window_manager_plugin.h>

void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) gtk_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
gtk_plugin_register_with_registrar(gtk_registrar);
g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin");
screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar);
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
g_autoptr(FlPluginRegistrar) window_manager_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin");
window_manager_plugin_register_with_registrar(window_manager_registrar);
}
2 changes: 2 additions & 0 deletions linux/flutter/generated_plugins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

list(APPEND FLUTTER_PLUGIN_LIST
gtk
screen_retriever_linux
sqlite3_flutter_libs
url_launcher_linux
window_manager
)

list(APPEND FLUTTER_FFI_PLUGIN_LIST
Expand Down
4 changes: 4 additions & 0 deletions macos/Flutter/GeneratedPluginRegistrant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import geolocator_apple
import mobile_scanner
import package_info_plus
import path_provider_foundation
import screen_retriever_macos
import share_plus
import shared_preferences_foundation
import sqflite_darwin
import sqlite3_flutter_libs
import wakelock_plus
import window_manager

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
Expand All @@ -30,9 +32,11 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
}
62 changes: 55 additions & 7 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -737,10 +737,10 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev"
source: hosted
version: "0.12.18"
version: "0.12.19"
material_color_utilities:
dependency: transitive
description:
Expand All @@ -753,10 +753,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
url: "https://pub.dev"
source: hosted
version: "1.17.0"
version: "1.18.0"
mgrs_dart:
dependency: transitive
description:
Expand Down Expand Up @@ -1045,6 +1045,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.28.0"
screen_retriever:
dependency: transitive
description:
name: screen_retriever
sha256: ace919117a7520c13a50a6259e60c4a0d4cbe98809468792a91b5c5adada2aa6
url: "https://pub.dev"
source: hosted
version: "0.2.2"
screen_retriever_linux:
dependency: transitive
description:
name: screen_retriever_linux
sha256: "7b52006a5ceae1f3d5af7f77188c3290d6e7d8ded16d99809bea84967c65c257"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
screen_retriever_macos:
dependency: transitive
description:
name: screen_retriever_macos
sha256: a1489b99cce597c45a54b9aae1cd94c8d4705353b7e0bb2457a6e4de44e0ad8a
url: "https://pub.dev"
source: hosted
version: "0.2.2"
screen_retriever_platform_interface:
dependency: transitive
description:
name: screen_retriever_platform_interface
sha256: "94a5535277510a63184ca178ce12a1449bc0b38618879aa1c18bf57369c5064a"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
screen_retriever_windows:
dependency: transitive
description:
name: screen_retriever_windows
sha256: dafc6922b0bfbf1d48cf3ccbf519b4fff47bdcb820da1728ea6db675fecc9324
url: "https://pub.dev"
source: hosted
version: "0.2.2"
share_plus:
dependency: "direct main"
description:
Expand Down Expand Up @@ -1270,10 +1310,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
url: "https://pub.dev"
source: hosted
version: "0.7.9"
version: "0.7.11"
timezone:
dependency: transitive
description:
Expand Down Expand Up @@ -1426,6 +1466,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.10.1"
window_manager:
dependency: "direct main"
description:
name: window_manager
sha256: "05c231fd7b23d2380f14c5cc10b7b93d60d4fa4a2fb4e0f032de27e44b5560e9"
url: "https://pub.dev"
source: hosted
version: "0.5.2"
wkt_parser:
dependency: transitive
description:
Expand Down Expand Up @@ -1459,5 +1507,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.9.0-0 <4.0.0"
dart: ">=3.10.0-0 <4.0.0"
flutter: ">=3.27.0"
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ dependencies:

# Utilities
crypto: ^3.0.3
window_manager: ^0.5.2
http: ^1.2.0
package_info_plus: ^8.0.0
battery_plus: ^6.2.0
Expand Down
Loading