Initial commit
Some checks failed
Main Deploy Workflow / deploy_web (push) Has been cancelled
Main Deploy Workflow / deploy_playstore_internal (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
2026-09-11 15:59:56 -03:00
commit 0037b0b9cf
651 changed files with 199705 additions and 0 deletions

180
lib/main.dart Normal file
View File

@@ -0,0 +1,180 @@
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'dart:async';
import 'dart:isolate';
import 'dart:ui';
import 'package:collection/collection.dart';
import 'package:tatchat/config/app_config.dart';
import 'package:tatchat/utils/client_manager.dart';
import 'package:tatchat/utils/error_reporter.dart';
import 'package:tatchat/utils/notification_background_handler.dart';
import 'package:tatchat/utils/platform_infos.dart';
import 'package:tatchat/utils/start_push_foreground_service.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_vodozemac/flutter_vodozemac.dart' as vod;
import 'package:just_audio_media_kit/just_audio_media_kit.dart';
import 'package:material_ui/material_ui.dart';
import 'package:matrix/matrix.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:universal_html/universal_html.dart' as web;
import 'config/setting_keys.dart';
import 'utils/background_push.dart';
import 'widgets/tatu_chat_app.dart';
ReceivePort? mainIsolateReceivePort;
bool _vodozemacInitialized = false;
bool isIntegrationTest = false;
void main(List<String> args) => runZonedGuarded(() async {
// Forward Flutter errors to global error reporter
FlutterError.onError = (details) => Zone.current.handleUncaughtError(
details.exception,
details.stack ?? StackTrace.current,
);
isIntegrationTest = args.singleOrNull == 'integration_test';
if (PlatformInfos.isAndroid) {
final port = mainIsolateReceivePort = ReceivePort();
IsolateNameServer.removePortNameMapping(AppConfig.mainIsolatePortName);
IsolateNameServer.registerPortWithName(
port.sendPort,
AppConfig.mainIsolatePortName,
);
await waitForPushIsolateDone();
}
// Sanitize hash for OIDC:
if (kIsWeb) {
final hash = web.window.location.hash;
if (hash.isNotEmpty && !hash.startsWith('/')) {
web.window.location.hash = hash.replaceFirst('#', '#?');
}
}
// Our background push shared isolate accesses flutter-internal things very early in the startup proccess
// To make sure that the parts of flutter needed are started up already, we need to ensure that the
// widget bindings are initialized already.
WidgetsFlutterBinding.ensureInitialized();
final store = await AppSettings.init();
Logs().i('Welcome to ${AppSettings.applicationName.value} <3');
kEnableMatrixSdkBenchmarks = AppSettings.benchmarksInLogs.value;
if (!_vodozemacInitialized) {
await vod.init(
wasmPath: './assets/assets/vodozemac/${AppConfig.vodozemacVersion}/',
);
_vodozemacInitialized = true;
}
Logs().nativeColors = !PlatformInfos.isIOS;
// If the app starts in detached mode, we assume that it is in
// background fetch mode for processing push notifications. This is
// currently only supported on Android.
if (PlatformInfos.isAndroid &&
AppLifecycleState.detached == WidgetsBinding.instance.lifecycleState) {
await ForegroundServices.startService('background_push');
final clients = await ClientManager.getClients(store: store);
// Do not send online presences when app is in background fetch mode.
for (final client in clients) {
client.backgroundSync = false;
client.syncPresence = PresenceType.offline;
}
// In the background fetch mode we do not want to waste ressources with
// starting the Flutter engine but process incoming push notifications.
BackgroundPush.clientOnly(clients);
// To start the flutter engine afterwards we add an custom observer.
WidgetsBinding.instance.addObserver(AppStarter(clients, store));
Logs().i(
'${AppSettings.applicationName.value} started in background-fetch mode. No GUI will be created unless the app is no longer detached.',
);
return;
}
final clients = await ClientManager.getClients(store: store);
// Started in foreground mode.
Logs().i(
'${AppSettings.applicationName.value} started in foreground mode. Rendering GUI...',
);
await startGui(clients, store);
}, ErrorReporter.onFlutterError);
/// Fetch the pincode for the applock and start the flutter engine.
Future<void> startGui(List<Client> clients, SharedPreferences store) async {
// Fetch the pin for the applock if existing for mobile applications.
String? pin;
var useBiometrics = false;
if (PlatformInfos.supportsAppLock) {
try {
pin = await const FlutterSecureStorage().read(
key: 'chat.fluffy.app_lock',
);
useBiometrics =
(await const FlutterSecureStorage().read(
key: 'chat.fluffy.use_biometrics',
)) ==
'true';
} catch (e, s) {
Logs().d('Unable to read PIN from Secure storage', e, s);
}
}
if (PlatformInfos.isLinux || PlatformInfos.isWindows) {
JustAudioMediaKit.ensureInitialized();
}
// Preload first client
final firstClient = clients.firstOrNull;
await firstClient?.roomsLoading;
await firstClient?.accountDataLoading;
runApp(
TatuChatApp(
clients: clients,
appLockSettings: (pincode: pin, useBiometrics: useBiometrics),
store: store,
),
);
}
/// Watches the lifecycle changes to start the application when it
/// is no longer detached.
class AppStarter with WidgetsBindingObserver {
final List<Client> clients;
final SharedPreferences store;
bool guiStarted = false;
AppStarter(this.clients, this.store);
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (guiStarted) return;
if (state == AppLifecycleState.detached) return;
Logs().i(
'${AppSettings.applicationName.value} switches from the detached background-fetch mode to ${state.name} mode. Rendering GUI...',
);
// Switching to foreground mode needs to reenable send online sync presence.
for (final client in clients) {
client.backgroundSync = true;
client.syncPresence = PresenceType.online;
}
startGui(clients, store);
// We must make sure that the GUI is only started once.
guiStarted = true;
}
}