Files
localsend/app/lib/config/init.dart
T

278 lines
9.4 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:common/api_route_builder.dart';
import 'package:common/constants.dart';
import 'package:common/isolate.dart';
import 'package:common/model/dto/file_dto.dart';
import 'package:common/model/dto/multicast_dto.dart';
import 'package:common/util/dio.dart';
import 'package:common/util/logger.dart';
import 'package:dart_mappable/dart_mappable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:localsend_app/config/theme.dart';
import 'package:localsend_app/pages/home_page.dart';
import 'package:localsend_app/pages/home_page_controller.dart';
import 'package:localsend_app/provider/animation_provider.dart';
import 'package:localsend_app/provider/app_arguments_provider.dart';
import 'package:localsend_app/provider/device_info_provider.dart';
import 'package:localsend_app/provider/network/nearby_devices_provider.dart';
import 'package:localsend_app/provider/network/server/server_provider.dart';
import 'package:localsend_app/provider/persistence_provider.dart';
// [FOSS_REMOVE_START]
import 'package:localsend_app/provider/purchase_provider.dart';
// [FOSS_REMOVE_END]
import 'package:localsend_app/provider/selection/selected_sending_files_provider.dart';
import 'package:localsend_app/provider/settings_provider.dart';
import 'package:localsend_app/provider/tv_provider.dart';
import 'package:localsend_app/provider/window_dimensions_provider.dart';
import 'package:localsend_app/config/refena.dart';
import 'package:localsend_app/util/i18n.dart';
import 'package:localsend_app/util/native/autostart_helper.dart';
import 'package:localsend_app/util/native/cache_helper.dart';
import 'package:localsend_app/util/native/context_menu_helper.dart';
import 'package:localsend_app/util/native/cross_file_converters.dart';
import 'package:localsend_app/util/native/device_info_helper.dart';
import 'package:localsend_app/util/native/open_file_receiver.dart';
import 'package:localsend_app/util/native/platform_check.dart';
import 'package:localsend_app/util/native/tray_helper.dart';
import 'package:localsend_app/util/ui/dynamic_colors.dart';
import 'package:localsend_app/util/ui/snackbar.dart';
import 'package:logging/logging.dart';
import 'package:refena_flutter/refena_flutter.dart';
import 'package:share_handler/share_handler.dart';
import 'package:window_manager/window_manager.dart';
final _logger = Logger('Init');
/// Will be called before the MaterialApp started
Future<RefenaContainer> preInit(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized();
initLogger(args.contains('-v') || args.contains('--verbose') ? Level.ALL : Level.INFO);
MapperContainer.globals.use(const FileDtoMapper());
final dynamicColors = await getDynamicColors();
final persistenceService = await PersistenceService.initialize(
supportsDynamicColors: dynamicColors != null,
);
if (persistenceService.isFirstAppStart && !persistenceService.isPortableMode()) {
await enableContextMenu();
}
initI18n();
bool startHidden = false;
if (checkPlatformIsDesktop()) {
// Check if this app is already open and let it "show up".
// If this is the case, then exit the current instance.
final dio = createDio(const Duration(milliseconds: 100), persistenceService.getSecurityContext());
try {
await dio.post(
ApiRoute.show.targetRaw(
'127.0.0.1',
persistenceService.getPort(),
persistenceService.isHttps(),
peerProtocolVersion,
),
queryParameters: {
'token': persistenceService.getShowToken(),
},
data: jsonEncode({
'args': args,
}),
);
exit(0); // Another instance does exist because no error is thrown
} catch (_) {}
// initialize tray AFTER i18n has been initialized
try {
await initTray();
} catch (e) {
_logger.warning('Initializing tray failed: $e');
}
// initialize size and position
await WindowManager.instance.ensureInitialized();
await WindowDimensionsController(persistenceService).initDimensionsConfiguration();
if (args.contains(startHiddenFlag)) {
// keep this app hidden
startHidden = true;
} else {
await WindowManager.instance.show();
}
}
setDefaultRouteTransition();
final container = RefenaContainer(
observers: kDebugMode ? [CustomRefenaObserver()] : [],
overrides: [
persistenceProvider.overrideWithValue(persistenceService),
deviceRawInfoProvider.overrideWithValue(await getDeviceInfo()),
appArgumentsProvider.overrideWithValue(args),
tvProvider.overrideWithValue(await checkIfTv()),
dynamicColorsProvider.overrideWithValue(dynamicColors),
sleepProvider.overrideWithInitialState((ref) => startHidden),
],
);
// initialize multi-threading
container.set(parentIsolateProvider.overrideWithNotifier((ref) {
final settings = ref.read(settingsProvider);
return IsolateController(
initialState: ParentIsolateState.initial(
SyncState(
securityContext: persistenceService.getSecurityContext(),
deviceInfo: ref.read(deviceInfoProvider),
alias: settings.alias,
port: settings.port,
protocol: settings.https ? ProtocolType.https : ProtocolType.http,
multicastGroup: settings.multicastGroup,
discoveryTimeout: settings.discoveryTimeout,
serverRunning: true,
download: false,
),
),
);
}));
await container.redux(parentIsolateProvider).dispatchAsync(IsolateSetupAction());
return container;
}
StreamSubscription? _sharedMediaSubscription;
/// Will be called when home page has been initialized
Future<void> postInit(BuildContext context, Ref ref, bool appStart) async {
await updateSystemOverlayStyle(context);
if (checkPlatform([TargetPlatform.android])) {
try {
await FlutterDisplayMode.setHighRefreshRate();
} catch (e) {
_logger.warning('Setting high refresh rate failed', e);
}
}
try {
await ref.notifier(serverProvider).startServerFromSettings();
} catch (e) {
if (context.mounted) {
context.showSnackBar(e.toString());
}
}
try {
ref.redux(nearbyDevicesProvider).dispatchAsync(StartMulticastListener()); // ignore: unawaited_futures
} catch (e) {
_logger.warning('Starting multicast listener failed', e);
}
if (appStart) {
if (defaultTargetPlatform == TargetPlatform.macOS) {
final files = await getOpenedFiles();
if (files.isNotEmpty) {
await ref.global.dispatchAsync(_HandleAppStartArgumentsAction(
args: files,
));
}
// handle future dropped files
getOpenedFilesStream().listen((files) {
ref.global.dispatchAsync(_HandleAppStartArgumentsAction(
args: files,
));
});
} else {
final args = ref.read(appArgumentsProvider);
await ref.global.dispatchAsync(_HandleAppStartArgumentsAction(
args: args,
));
}
}
bool hasInitialShare = false;
if (checkPlatformCanReceiveShareIntent()) {
final shareHandler = ShareHandlerPlatform.instance;
if (appStart) {
final initialSharedPayload = await shareHandler.getInitialSharedMedia();
if (initialSharedPayload != null) {
hasInitialShare = true;
// ignore: unawaited_futures
ref.global.dispatchAsync(_HandleShareIntentAction(
payload: initialSharedPayload,
));
}
}
_sharedMediaSubscription?.cancel(); // ignore: unawaited_futures
_sharedMediaSubscription = shareHandler.sharedMediaStream.listen((SharedMedia payload) {
ref.global.dispatchAsync(_HandleShareIntentAction(
payload: payload,
));
});
}
if (appStart && !hasInitialShare && (checkPlatformWithGallery() || checkPlatformCanReceiveShareIntent())) {
// Clear cache on every app start.
// If we received a share intent, then don't clear it, otherwise the shared file will be lost.
ref.global.dispatchAsync(ClearCacheAction()); // ignore: unawaited_futures
}
// [FOSS_REMOVE_START]
if (checkPlatformSupportPayment()) {
// ignore: unawaited_futures
ref.redux(purchaseProvider).dispatchAsync(InitPurchaseStream());
}
// [FOSS_REMOVE_END]
}
class _HandleShareIntentAction extends AsyncGlobalAction {
final SharedMedia payload;
_HandleShareIntentAction({
required this.payload,
});
@override
Future<void> reduce() async {
final message = payload.content;
if (message != null && message.trim().isNotEmpty) {
ref.redux(selectedSendingFilesProvider).dispatch(AddMessageAction(message: message));
}
await ref.redux(selectedSendingFilesProvider).dispatchAsync(AddFilesAction(
files: payload.attachments?.where((a) => a != null).cast<SharedAttachment>() ?? <SharedAttachment>[],
converter: CrossFileConverters.convertSharedAttachment,
));
ref.redux(homePageControllerProvider).dispatch(ChangeTabAction(HomeTab.send));
}
}
class _HandleAppStartArgumentsAction extends AsyncGlobalAction {
final List<String> args;
_HandleAppStartArgumentsAction({
required this.args,
});
@override
Future<void> reduce() async {
final filesAdded = await ref.redux(selectedSendingFilesProvider).dispatchAsyncTakeResult(LoadSelectionFromArgsAction(args));
if (filesAdded) {
ref.redux(homePageControllerProvider).dispatch(ChangeTabAction(HomeTab.send));
}
}
}