import 'dart:io'; import 'package:appflowy_backend/appflowy_backend.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import '../workspace/application/settings/settings_location_cubit.dart'; import 'deps_resolver.dart'; import 'launch_configuration.dart'; import 'plugin/plugin.dart'; import 'tasks/prelude.dart'; // [[diagram: flowy startup flow]] // ┌──────────┐ // │ FlowyApp │ // └──────────┘ // │ impl // ▼ // ┌────────┐ 1.run ┌──────────┐ // │ System │───┬───▶│EntryPoint│ // └────────┘ │ └──────────┘ ┌─────────────────┐ // │ ┌──▶ │ RustSDKInitTask │ // │ ┌───────────┐ │ └─────────────────┘ // └──▶ │AppLauncher│───┤ // 2.launch └───────────┘ │ ┌─────────────┐ ┌──────────────────┐ ┌───────────────┐ // └───▶│AppWidgetTask│────────▶│ApplicationWidget │─────▶│ SplashScreen │ // └─────────────┘ └──────────────────┘ └───────────────┘ // // 3.build MaterialApp final getIt = GetIt.instance; abstract class EntryPoint { Widget create(LaunchConfiguration config); } class FlowyRunner { static Future run( EntryPoint f, { LaunchConfiguration config = const LaunchConfiguration( autoRegistrationSupported: false, ), }) async { // Clear all the states in case of rebuilding. await getIt.reset(); // Specify the env final env = integrationEnv(); initGetIt(getIt, env, f, config); final directory = getIt() .fetchLocation() .then((value) => Directory(value)); // add task final launcher = getIt(); launcher.addTasks( [ // handle platform errors. const PlatformErrorCatcherTask(), // localization const InitLocalizationTask(), // init the app window const InitAppWindowTask(), // Init Rust SDK InitRustSDKTask(directory: directory), // Load Plugins, like document, grid ... const PluginLoadTask(), // init the app widget // ignore in test mode if (!env.isTest()) ...[ const HotKeyTask(), const InitAppWidgetTask(), const InitPlatformServiceTask() ], ], ); await launcher.launch(); // execute the tasks } } Future initGetIt( GetIt getIt, IntegrationMode env, EntryPoint f, LaunchConfiguration config, ) async { getIt.registerFactory(() => f); getIt.registerLazySingleton(() { return FlowySDK(); }); getIt.registerLazySingleton( () => AppLauncher( context: LaunchContext( getIt, env, config, ), ), ); getIt.registerSingleton(PluginSandbox()); await DependencyResolver.resolve(getIt); } class LaunchContext { GetIt getIt; IntegrationMode env; LaunchConfiguration config; LaunchContext(this.getIt, this.env, this.config); } enum LaunchTaskType { dataProcessing, appLauncher, } /// The interface of an app launch task, which will trigger /// some nonresident indispensable task in app launching task. abstract class LaunchTask { const LaunchTask(); LaunchTaskType get type => LaunchTaskType.dataProcessing; Future initialize(LaunchContext context); } class AppLauncher { AppLauncher({ required this.context, }); final LaunchContext context; final List tasks = []; void addTask(LaunchTask task) { tasks.add(task); } void addTasks(Iterable tasks) { this.tasks.addAll(tasks); } Future launch() async { for (final task in tasks) { await task.initialize(context); } } } enum IntegrationMode { develop, release, test, } extension IntegrationEnvExt on IntegrationMode { bool isTest() { return this == IntegrationMode.test; } } IntegrationMode integrationEnv() { if (Platform.environment.containsKey('FLUTTER_TEST')) { return IntegrationMode.test; } if (kReleaseMode) { return IntegrationMode.release; } return IntegrationMode.develop; }