feat: remove msix, improve auto start (#1467)

This commit is contained in:
Tien Do Nam
2024-07-09 18:43:59 +02:00
committed by GitHub
parent 0ced63b881
commit b8068e6f5d
15 changed files with 214 additions and 236 deletions

View File

@@ -90,6 +90,14 @@ Create a file named `settings.json` located in the same directory as the executa
This file can be empty.
The app will use this file to store settings instead of the default location.
**Start hidden**
(Updated in v1.15.0)
To start the app hidden (only in tray), use the `--hidden` flag (example: `localsend_app.exe --hidden`).
On v1.14.0 and earlier, the app starts hidden if `autostart` flag is set, and the hidden setting is enabled.
## How It Works
LocalSend uses a secure communication protocol that allows devices to communicate with each other using a REST API. All data is sent securely over HTTPS, and the TLS/SSL certificate is generated on the fly on each device, ensuring maximum security.

View File

@@ -2,6 +2,8 @@
- feat: add clear button in the send tab (@Caesarovich)
- feat: add URL view when sharing via link that shows the URL in bigger font (@harriseldon)
- feat(windows): toggle "start hidden" in-app instead of referring to the system settings (@Tienisto)
- feat(desktop): make auto start + start hidden more stable, now listens to `--hidden` parameter instead of `autostart` (@Tienisto)
- feat(desktop): show progress in the taskbar (@NightFeather0615)
- feat: add discovery timeout setting for advanced users (@o2e)
- fix: sanitize file names with invalid characters (@Caesarovich)

View File

@@ -25,6 +25,7 @@ import 'package:localsend_app/theme.dart';
import 'package:localsend_app/util/api_route_builder.dart';
import 'package:localsend_app/util/i18n.dart';
import 'package:localsend_app/util/logger.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/cross_file_converters.dart';
import 'package:localsend_app/util/native/device_info_helper.dart';
@@ -37,8 +38,6 @@ import 'package:refena_flutter/refena_flutter.dart';
import 'package:share_handler/share_handler.dart';
import 'package:window_manager/window_manager.dart';
const launchAtStartupArg = 'autostart';
final _logger = Logger('Init');
/// Will be called before the MaterialApp started
@@ -86,13 +85,11 @@ Future<RefenaContainer> preInit(List<String> args) async {
// initialize size and position
await WindowManager.instance.ensureInitialized();
await WindowDimensionsController(persistenceService).initDimensionsConfiguration();
if (!args.contains(launchAtStartupArg) || !persistenceService.isAutoStartLaunchMinimized()) {
// We show this app, when (1) app started manually, (2) app should not start minimized
// In other words: only start minimized when launched on startup and "launchMinimized" is configured
await WindowManager.instance.show();
} else {
if (args.contains(startHiddenFlag)) {
// keep this app hidden
startHidden = true;
} else {
await WindowManager.instance.show();
}
}

View File

@@ -22,8 +22,6 @@ class SettingsState with SettingsStateMappable {
final bool quickSave; // automatically accept file requests
final bool autoFinish; // automatically finish sessions
final bool minimizeToTray; // minimize to tray instead of exiting the app
final bool launchAtStartup; // Tracks if the option is enabled on Linux
final bool autoStartLaunchMinimized; // start hidden in tray (only available when launchAtStartup is true)
final bool https;
final SendMode sendMode;
final bool saveWindowPlacement;
@@ -47,8 +45,6 @@ class SettingsState with SettingsStateMappable {
required this.quickSave,
required this.autoFinish,
required this.minimizeToTray,
required this.launchAtStartup,
required this.autoStartLaunchMinimized,
required this.https,
required this.sendMode,
required this.saveWindowPlacement,

View File

@@ -47,10 +47,6 @@ class SettingsStateMapper extends ClassMapperBase<SettingsState> {
static const Field<SettingsState, bool> _f$autoFinish = Field('autoFinish', _$autoFinish);
static bool _$minimizeToTray(SettingsState v) => v.minimizeToTray;
static const Field<SettingsState, bool> _f$minimizeToTray = Field('minimizeToTray', _$minimizeToTray);
static bool _$launchAtStartup(SettingsState v) => v.launchAtStartup;
static const Field<SettingsState, bool> _f$launchAtStartup = Field('launchAtStartup', _$launchAtStartup);
static bool _$autoStartLaunchMinimized(SettingsState v) => v.autoStartLaunchMinimized;
static const Field<SettingsState, bool> _f$autoStartLaunchMinimized = Field('autoStartLaunchMinimized', _$autoStartLaunchMinimized);
static bool _$https(SettingsState v) => v.https;
static const Field<SettingsState, bool> _f$https = Field('https', _$https);
static SendMode _$sendMode(SettingsState v) => v.sendMode;
@@ -83,8 +79,6 @@ class SettingsStateMapper extends ClassMapperBase<SettingsState> {
#quickSave: _f$quickSave,
#autoFinish: _f$autoFinish,
#minimizeToTray: _f$minimizeToTray,
#launchAtStartup: _f$launchAtStartup,
#autoStartLaunchMinimized: _f$autoStartLaunchMinimized,
#https: _f$https,
#sendMode: _f$sendMode,
#saveWindowPlacement: _f$saveWindowPlacement,
@@ -110,8 +104,6 @@ class SettingsStateMapper extends ClassMapperBase<SettingsState> {
quickSave: data.dec(_f$quickSave),
autoFinish: data.dec(_f$autoFinish),
minimizeToTray: data.dec(_f$minimizeToTray),
launchAtStartup: data.dec(_f$launchAtStartup),
autoStartLaunchMinimized: data.dec(_f$autoStartLaunchMinimized),
https: data.dec(_f$https),
sendMode: data.dec(_f$sendMode),
saveWindowPlacement: data.dec(_f$saveWindowPlacement),
@@ -180,8 +172,6 @@ abstract class SettingsStateCopyWith<$R, $In extends SettingsState, $Out> implem
bool? quickSave,
bool? autoFinish,
bool? minimizeToTray,
bool? launchAtStartup,
bool? autoStartLaunchMinimized,
bool? https,
SendMode? sendMode,
bool? saveWindowPlacement,
@@ -214,8 +204,6 @@ class _SettingsStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Setting
bool? quickSave,
bool? autoFinish,
bool? minimizeToTray,
bool? launchAtStartup,
bool? autoStartLaunchMinimized,
bool? https,
SendMode? sendMode,
bool? saveWindowPlacement,
@@ -238,8 +226,6 @@ class _SettingsStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Setting
if (quickSave != null) #quickSave: quickSave,
if (autoFinish != null) #autoFinish: autoFinish,
if (minimizeToTray != null) #minimizeToTray: minimizeToTray,
if (launchAtStartup != null) #launchAtStartup: launchAtStartup,
if (autoStartLaunchMinimized != null) #autoStartLaunchMinimized: autoStartLaunchMinimized,
if (https != null) #https: https,
if (sendMode != null) #sendMode: sendMode,
if (saveWindowPlacement != null) #saveWindowPlacement: saveWindowPlacement,
@@ -264,8 +250,6 @@ class _SettingsStateCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Setting
quickSave: data.get(#quickSave, or: $value.quickSave),
autoFinish: data.get(#autoFinish, or: $value.autoFinish),
minimizeToTray: data.get(#minimizeToTray, or: $value.minimizeToTray),
launchAtStartup: data.get(#launchAtStartup, or: $value.launchAtStartup),
autoStartLaunchMinimized: data.get(#autoStartLaunchMinimized, or: $value.autoStartLaunchMinimized),
https: data.get(#https, or: $value.https),
sendMode: data.get(#sendMode, or: $value.sendMode),
saveWindowPlacement: data.get(#saveWindowPlacement, or: $value.saveWindowPlacement),

View File

@@ -11,7 +11,6 @@ import 'package:localsend_app/provider/settings_provider.dart';
import 'package:localsend_app/provider/version_provider.dart';
import 'package:localsend_app/theme.dart';
import 'package:localsend_app/util/device_type_ext.dart';
import 'package:localsend_app/util/native/autostart_helper.dart';
import 'package:localsend_app/util/native/pick_directory_path.dart';
import 'package:localsend_app/util/native/platform_check.dart';
import 'package:localsend_app/widget/custom_dropdown_button.dart';
@@ -25,9 +24,6 @@ import 'package:refena_flutter/refena_flutter.dart';
import 'package:routerino/routerino.dart';
import 'package:url_launcher/url_launcher.dart';
final _isLinux = checkPlatform([TargetPlatform.linux]);
final _isWindows = checkPlatform([TargetPlatform.windows]);
class SettingsTab extends StatelessWidget {
const SettingsTab();
@@ -100,62 +96,27 @@ class SettingsTab extends StatelessWidget {
},
),
],
// Linux autostart is simpler, so a boolean entry is used
if (_isLinux)
if (checkPlatformIsDesktop()) ...[
_BooleanEntry(
label: t.settingsTab.general.launchAtStartup,
value: vm.settings.launchAtStartup,
onChanged: (b) async {
late bool result;
if (await isLinuxLaunchAtStartEnabled()) {
result = await initDisableAutoStart(vm.settings);
} else {
result = await initEnableAutoStartAndOpenSettings(vm.settings);
}
if (result) {
await ref.notifier(settingsProvider).setLaunchAtStartup(b);
}
},
value: vm.autoStart,
onChanged: (_) => vm.onToggleAutoStart(context),
),
// Windows requires a manual action, so this settings entry is required
if (_isWindows)
_SettingsEntry(
label: t.settingsTab.general.launchAtStartup,
child: TextButton(
style: TextButton.styleFrom(
backgroundColor: Theme.of(context).inputDecorationTheme.fillColor,
shape: RoundedRectangleBorder(borderRadius: Theme.of(context).inputDecorationTheme.borderRadius),
foregroundColor: Theme.of(context).colorScheme.onSurface,
),
onPressed: () async {
await initDisableAutoStart(vm.settings);
await initEnableAutoStartAndOpenSettings(vm.settings, _isWindows);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Text(t.general.settings, style: Theme.of(context).textTheme.titleMedium),
),
),
),
if (_isWindows || _isLinux)
Visibility(
visible: vm.settings.launchAtStartup || _isWindows,
visible: vm.autoStart,
maintainAnimation: true,
maintainState: true,
child: AnimatedOpacity(
opacity: vm.settings.launchAtStartup || _isWindows ? 1.0 : 0.0,
opacity: vm.autoStart ? 1.0 : 0.0,
duration: const Duration(milliseconds: 500),
child: _BooleanEntry(
label: t.settingsTab.general.launchMinimized,
value: vm.settings.autoStartLaunchMinimized,
onChanged: (b) async {
await initDisableAutoStart(vm.settings);
await ref.notifier(settingsProvider).setAutoStartLaunchMinimized(b);
await initEnableAutoStartAndOpenSettings(vm.settings, _isWindows);
},
value: vm.autoStartLaunchHidden,
onChanged: (_) => vm.onToggleAutoStartLaunchHidden(context),
),
),
),
],
],
_BooleanEntry(
label: t.settingsTab.general.animations,

View File

@@ -6,6 +6,7 @@ import 'package:localsend_app/provider/device_info_provider.dart';
import 'package:localsend_app/provider/network/server/server_provider.dart';
import 'package:localsend_app/provider/settings_provider.dart';
import 'package:localsend_app/theme.dart';
import 'package:localsend_app/util/native/autostart_helper.dart';
import 'package:localsend_app/util/native/device_info_helper.dart';
import 'package:localsend_app/util/sleep.dart';
import 'package:localsend_app/util/ui/dynamic_colors.dart';
@@ -56,6 +57,8 @@ class SettingsTabController extends ReduxNotifier<SettingsTabVm> {
serverState: _serverService.state,
deviceInfo: _initialDeviceInfo,
colorModes: _supportsDynamicColors ? ColorMode.values : ColorMode.values.where((e) => e != ColorMode.system).toList(),
autoStart: false,
autoStartLaunchHidden: false,
onChangeTheme: (context, theme) async {
await _settingsService.setTheme(theme);
await sleepAsync(500); // workaround: brightness takes some time to be updated
@@ -73,6 +76,26 @@ class SettingsTabController extends ReduxNotifier<SettingsTabVm> {
onTapLanguage: (context) async {
await context.push(() => const LanguagePage());
},
onToggleAutoStart: (context) async {
final bool success;
if (state.autoStart) {
success = await disableAutoStart();
} else {
success = await enableAutoStart(startHidden: state.autoStartLaunchHidden);
}
if (success) {
redux.dispatch(_SetAutoStartAction(!state.autoStart));
}
},
onToggleAutoStartLaunchHidden: (context) async {
if (state.autoStart) {
final success = await enableAutoStart(startHidden: !state.autoStartLaunchHidden);
if (success) {
redux.dispatch(_SetAutoStartLaunchHiddenAction(!state.autoStartLaunchHidden));
}
}
},
onTapRestartServer: (context) async {
try {
final newServerState = await _serverService.restartServer(
@@ -107,7 +130,7 @@ class SettingsTabController extends ReduxNotifier<SettingsTabVm> {
}
@override
get initialAction => _SettingsTabWatchAction();
get initialAction => _SettingsTabInitAction();
@override
void dispose() {
@@ -120,6 +143,19 @@ class SettingsTabController extends ReduxNotifier<SettingsTabVm> {
}
}
class _SettingsTabInitAction extends AsyncReduxAction<SettingsTabController, SettingsTabVm> {
@override
Future<SettingsTabVm> reduce() async {
dispatch(_SettingsTabWatchAction());
final autoStartEnabled = await isAutoStartEnabled();
final autoStartHidden = await isAutoStartHidden();
return state.copyWith(
autoStart: autoStartEnabled,
autoStartLaunchHidden: autoStartHidden,
);
}
}
class _SettingsTabWatchAction extends WatchAction<SettingsTabController, SettingsTabVm> {
@override
SettingsTabVm reduce() {
@@ -141,3 +177,25 @@ class SetAdvancedAction extends ReduxAction<SettingsTabController, SettingsTabVm
return state.copyWith(advanced: advanced);
}
}
class _SetAutoStartAction extends ReduxAction<SettingsTabController, SettingsTabVm> {
final bool enabled;
_SetAutoStartAction(this.enabled);
@override
SettingsTabVm reduce() {
return state.copyWith(autoStart: enabled);
}
}
class _SetAutoStartLaunchHiddenAction extends ReduxAction<SettingsTabController, SettingsTabVm> {
final bool enabled;
_SetAutoStartLaunchHiddenAction(this.enabled);
@override
SettingsTabVm reduce() {
return state.copyWith(autoStartLaunchHidden: enabled);
}
}

View File

@@ -21,9 +21,13 @@ class SettingsTabVm with SettingsTabVmMappable {
final DeviceInfoResult deviceInfo;
final List<ThemeMode> themeModes = ThemeMode.values;
final List<ColorMode> colorModes;
final bool autoStart;
final bool autoStartLaunchHidden;
final void Function(BuildContext context, ThemeMode mode) onChangeTheme;
final void Function(ColorMode mode) onChangeColorMode;
final void Function(BuildContext context) onTapLanguage;
final void Function(BuildContext context) onToggleAutoStart;
final void Function(BuildContext context) onToggleAutoStartLaunchHidden;
final void Function(BuildContext context) onTapRestartServer;
final void Function(BuildContext context) onTapStartServer;
final void Function() onTapStopServer;
@@ -40,9 +44,13 @@ class SettingsTabVm with SettingsTabVmMappable {
required this.serverState,
required this.deviceInfo,
required this.colorModes,
required this.autoStart,
required this.autoStartLaunchHidden,
required this.onChangeTheme,
required this.onChangeColorMode,
required this.onTapLanguage,
required this.onToggleAutoStart,
required this.onToggleAutoStartLaunchHidden,
required this.onTapRestartServer,
required this.onTapStartServer,
required this.onTapStopServer,

View File

@@ -42,12 +42,21 @@ class SettingsTabVmMapper extends ClassMapperBase<SettingsTabVm> {
static const Field<SettingsTabVm, DeviceInfoResult> _f$deviceInfo = Field('deviceInfo', _$deviceInfo);
static List<ColorMode> _$colorModes(SettingsTabVm v) => v.colorModes;
static const Field<SettingsTabVm, List<ColorMode>> _f$colorModes = Field('colorModes', _$colorModes);
static bool _$autoStart(SettingsTabVm v) => v.autoStart;
static const Field<SettingsTabVm, bool> _f$autoStart = Field('autoStart', _$autoStart);
static bool _$autoStartLaunchHidden(SettingsTabVm v) => v.autoStartLaunchHidden;
static const Field<SettingsTabVm, bool> _f$autoStartLaunchHidden = Field('autoStartLaunchHidden', _$autoStartLaunchHidden);
static void Function(BuildContext, ThemeMode) _$onChangeTheme(SettingsTabVm v) => v.onChangeTheme;
static const Field<SettingsTabVm, void Function(BuildContext, ThemeMode)> _f$onChangeTheme = Field('onChangeTheme', _$onChangeTheme);
static void Function(ColorMode) _$onChangeColorMode(SettingsTabVm v) => v.onChangeColorMode;
static const Field<SettingsTabVm, void Function(ColorMode)> _f$onChangeColorMode = Field('onChangeColorMode', _$onChangeColorMode);
static void Function(BuildContext) _$onTapLanguage(SettingsTabVm v) => v.onTapLanguage;
static const Field<SettingsTabVm, void Function(BuildContext)> _f$onTapLanguage = Field('onTapLanguage', _$onTapLanguage);
static void Function(BuildContext) _$onToggleAutoStart(SettingsTabVm v) => v.onToggleAutoStart;
static const Field<SettingsTabVm, void Function(BuildContext)> _f$onToggleAutoStart = Field('onToggleAutoStart', _$onToggleAutoStart);
static void Function(BuildContext) _$onToggleAutoStartLaunchHidden(SettingsTabVm v) => v.onToggleAutoStartLaunchHidden;
static const Field<SettingsTabVm, void Function(BuildContext)> _f$onToggleAutoStartLaunchHidden =
Field('onToggleAutoStartLaunchHidden', _$onToggleAutoStartLaunchHidden);
static void Function(BuildContext) _$onTapRestartServer(SettingsTabVm v) => v.onTapRestartServer;
static const Field<SettingsTabVm, void Function(BuildContext)> _f$onTapRestartServer = Field('onTapRestartServer', _$onTapRestartServer);
static void Function(BuildContext) _$onTapStartServer(SettingsTabVm v) => v.onTapStartServer;
@@ -71,9 +80,13 @@ class SettingsTabVmMapper extends ClassMapperBase<SettingsTabVm> {
#serverState: _f$serverState,
#deviceInfo: _f$deviceInfo,
#colorModes: _f$colorModes,
#autoStart: _f$autoStart,
#autoStartLaunchHidden: _f$autoStartLaunchHidden,
#onChangeTheme: _f$onChangeTheme,
#onChangeColorMode: _f$onChangeColorMode,
#onTapLanguage: _f$onTapLanguage,
#onToggleAutoStart: _f$onToggleAutoStart,
#onToggleAutoStartLaunchHidden: _f$onToggleAutoStartLaunchHidden,
#onTapRestartServer: _f$onTapRestartServer,
#onTapStartServer: _f$onTapStartServer,
#onTapStopServer: _f$onTapStopServer,
@@ -93,9 +106,13 @@ class SettingsTabVmMapper extends ClassMapperBase<SettingsTabVm> {
serverState: data.dec(_f$serverState),
deviceInfo: data.dec(_f$deviceInfo),
colorModes: data.dec(_f$colorModes),
autoStart: data.dec(_f$autoStart),
autoStartLaunchHidden: data.dec(_f$autoStartLaunchHidden),
onChangeTheme: data.dec(_f$onChangeTheme),
onChangeColorMode: data.dec(_f$onChangeColorMode),
onTapLanguage: data.dec(_f$onTapLanguage),
onToggleAutoStart: data.dec(_f$onToggleAutoStart),
onToggleAutoStartLaunchHidden: data.dec(_f$onToggleAutoStartLaunchHidden),
onTapRestartServer: data.dec(_f$onTapRestartServer),
onTapStartServer: data.dec(_f$onTapStartServer),
onTapStopServer: data.dec(_f$onTapStopServer),
@@ -160,9 +177,13 @@ abstract class SettingsTabVmCopyWith<$R, $In extends SettingsTabVm, $Out> implem
ServerState? serverState,
DeviceInfoResult? deviceInfo,
List<ColorMode>? colorModes,
bool? autoStart,
bool? autoStartLaunchHidden,
void Function(BuildContext, ThemeMode)? onChangeTheme,
void Function(ColorMode)? onChangeColorMode,
void Function(BuildContext)? onTapLanguage,
void Function(BuildContext)? onToggleAutoStart,
void Function(BuildContext)? onToggleAutoStartLaunchHidden,
void Function(BuildContext)? onTapRestartServer,
void Function(BuildContext)? onTapStartServer,
void Function()? onTapStopServer,
@@ -195,9 +216,13 @@ class _SettingsTabVmCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Setting
Object? serverState = $none,
DeviceInfoResult? deviceInfo,
List<ColorMode>? colorModes,
bool? autoStart,
bool? autoStartLaunchHidden,
void Function(BuildContext, ThemeMode)? onChangeTheme,
void Function(ColorMode)? onChangeColorMode,
void Function(BuildContext)? onTapLanguage,
void Function(BuildContext)? onToggleAutoStart,
void Function(BuildContext)? onToggleAutoStartLaunchHidden,
void Function(BuildContext)? onTapRestartServer,
void Function(BuildContext)? onTapStartServer,
void Function()? onTapStopServer,
@@ -213,9 +238,13 @@ class _SettingsTabVmCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Setting
if (serverState != $none) #serverState: serverState,
if (deviceInfo != null) #deviceInfo: deviceInfo,
if (colorModes != null) #colorModes: colorModes,
if (autoStart != null) #autoStart: autoStart,
if (autoStartLaunchHidden != null) #autoStartLaunchHidden: autoStartLaunchHidden,
if (onChangeTheme != null) #onChangeTheme: onChangeTheme,
if (onChangeColorMode != null) #onChangeColorMode: onChangeColorMode,
if (onTapLanguage != null) #onTapLanguage: onTapLanguage,
if (onToggleAutoStart != null) #onToggleAutoStart: onToggleAutoStart,
if (onToggleAutoStartLaunchHidden != null) #onToggleAutoStartLaunchHidden: onToggleAutoStartLaunchHidden,
if (onTapRestartServer != null) #onTapRestartServer: onTapRestartServer,
if (onTapStartServer != null) #onTapStartServer: onTapStartServer,
if (onTapStopServer != null) #onTapStopServer: onTapStopServer,
@@ -233,9 +262,13 @@ class _SettingsTabVmCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Setting
serverState: data.get(#serverState, or: $value.serverState),
deviceInfo: data.get(#deviceInfo, or: $value.deviceInfo),
colorModes: data.get(#colorModes, or: $value.colorModes),
autoStart: data.get(#autoStart, or: $value.autoStart),
autoStartLaunchHidden: data.get(#autoStartLaunchHidden, or: $value.autoStartLaunchHidden),
onChangeTheme: data.get(#onChangeTheme, or: $value.onChangeTheme),
onChangeColorMode: data.get(#onChangeColorMode, or: $value.onChangeColorMode),
onTapLanguage: data.get(#onTapLanguage, or: $value.onTapLanguage),
onToggleAutoStart: data.get(#onToggleAutoStart, or: $value.onToggleAutoStart),
onToggleAutoStartLaunchHidden: data.get(#onToggleAutoStartLaunchHidden, or: $value.onToggleAutoStartLaunchHidden),
onTapRestartServer: data.get(#onTapRestartServer, or: $value.onTapRestartServer),
onTapStartServer: data.get(#onTapStartServer, or: $value.onTapStartServer),
onTapStopServer: data.get(#onTapStopServer, or: $value.onTapStopServer),

View File

@@ -60,7 +60,6 @@ const _quickSave = 'ls_quick_save';
const _autoFinish = 'ls_auto_finish';
const _minimizeToTray = 'ls_minimize_to_tray';
const _launchAtStartup = 'ls_launch_at_startup';
const _autoStartLaunchMinimized = 'ls_auto_start_launch_minimized';
const _https = 'ls_https';
const _sendMode = 'ls_send_mode';
const _enableAnimations = 'ls_enable_animations';
@@ -317,14 +316,6 @@ class PersistenceService {
await _prefs.setBool(_launchAtStartup, launchAtStartup);
}
bool isAutoStartLaunchMinimized() {
return _prefs.getBool(_autoStartLaunchMinimized) ?? true;
}
Future<void> setAutoStartLaunchMinimized(bool launchMinimized) async {
await _prefs.setBool(_autoStartLaunchMinimized, launchMinimized);
}
bool isHttps() {
return _prefs.getBool(_https) ?? true;
}

View File

@@ -31,8 +31,6 @@ class SettingsService extends PureNotifier<SettingsState> {
quickSave: _persistence.isQuickSave(),
autoFinish: _persistence.isAutoFinish(),
minimizeToTray: _persistence.isMinimizeToTray(),
launchAtStartup: _persistence.isLaunchAtStartup(),
autoStartLaunchMinimized: _persistence.isAutoStartLaunchMinimized(),
https: _persistence.isHttps(),
sendMode: _persistence.getSendMode(),
saveWindowPlacement: _persistence.getSaveWindowPlacement(),
@@ -134,20 +132,6 @@ class SettingsService extends PureNotifier<SettingsState> {
);
}
Future<void> setLaunchAtStartup(bool launchAtStartup) async {
await _persistence.setLaunchAtStartup(launchAtStartup);
state = state.copyWith(
launchAtStartup: launchAtStartup,
);
}
Future<void> setAutoStartLaunchMinimized(bool launchMinimized) async {
await _persistence.setAutoStartLaunchMinimized(launchMinimized);
state = state.copyWith(
autoStartLaunchMinimized: launchMinimized,
);
}
Future<void> setHttps(bool https) async {
await _persistence.setHttps(https);
state = state.copyWith(

View File

@@ -1,73 +1,115 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:launch_at_startup/launch_at_startup.dart';
import 'package:localsend_app/init.dart';
import 'package:localsend_app/model/state/settings_state.dart';
import 'package:localsend_app/util/native/platform_check.dart';
import 'package:logging/logging.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:win32_registry/win32_registry.dart';
const startHiddenFlag = '--hidden';
final _logger = Logger('AutoStartHelper');
/// Currently, only works for windows
Future<bool> initEnableAutoStartAndOpenSettings(SettingsState settings, [bool? isWindows]) async {
Future<bool> enableAutoStart({required bool startHidden}) async {
try {
// In case somebody don't use msix
final packageInfo = await PackageInfo.fromPlatform();
launchAtStartup.setup(
appName: packageInfo.appName,
appPath: Platform.resolvedExecutable,
args: [if (!settings.autoStartLaunchMinimized) launchAtStartupArg],
);
// We just add this entry so we have the same behaviour like in msix
if (!(await launchAtStartup.isEnabled())) {
final result = await launchAtStartup.enable();
return result;
if (checkPlatform(const [TargetPlatform.linux, TargetPlatform.macOS])) {
final packageInfo = await PackageInfo.fromPlatform();
launchAtStartup.setup(
appName: packageInfo.appName,
appPath: Platform.resolvedExecutable,
args: [
if (startHidden) startHiddenFlag,
],
);
await launchAtStartup.enable();
} else {
// launch_at_startup does not add quotes around the executable path
final packageInfo = await PackageInfo.fromPlatform();
_getWindowsRegistryKey().createValue(RegistryValue(
packageInfo.appName,
RegistryValueType.string,
'"${Platform.resolvedExecutable}"${startHidden ? ' $startHiddenFlag' : ''}',
));
}
return true;
} catch (e) {
_logger.warning('Could not init auto start', e);
_logger.warning('Could enable auto start', e);
return false;
}
// Can be linux on startup flag change
if (isWindows ?? false) {
try {
// Ideally, we should configure it programmatically
// The launch_at_startup package does not support this currently
// See: https://learn.microsoft.com/en-us/uwp/api/Windows.ApplicationModel.StartupTask?view=winrt-22621
await launchUrl(Uri.parse('ms-settings:startupapps'));
} catch (e) {
_logger.warning('Could not open startup settings', e);
}
}
return false;
}
Future<bool> initDisableAutoStart(SettingsState settings) async {
Future<bool> disableAutoStart() async {
try {
// In case somebody don't use msix
final packageInfo = await PackageInfo.fromPlatform();
launchAtStartup.setup(
appName: packageInfo.appName,
appPath: Platform.resolvedExecutable,
args: [if (settings.autoStartLaunchMinimized) launchAtStartupArg],
);
// We just add this entry so we have the same behaviour like in msix
if (await launchAtStartup.isEnabled()) {
final result = await launchAtStartup.disable();
return result;
switch (defaultTargetPlatform) {
case TargetPlatform.linux:
File(_getLinuxFilePath(packageInfo.appName)).deleteSync();
break;
case TargetPlatform.macOS:
File(_getMacOSFilePath(packageInfo.appName)).deleteSync();
break;
case TargetPlatform.windows:
_getWindowsRegistryKey().deleteValue(packageInfo.appName);
break;
default:
break;
}
return true;
} catch (e) {
_logger.warning('Could not init auto start', e);
_logger.warning('Could disable auto start', e);
return false;
}
return false;
}
Future<bool> isLinuxLaunchAtStartEnabled() async {
Future<bool> isAutoStartEnabled() async {
final packageInfo = await PackageInfo.fromPlatform();
File desktopFile = File('${Platform.environment['HOME']}/.config/autostart/${packageInfo.appName}.desktop');
return desktopFile.existsSync();
switch (defaultTargetPlatform) {
case TargetPlatform.linux:
return File(_getLinuxFilePath(packageInfo.appName)).existsSync();
case TargetPlatform.macOS:
return File(_getMacOSFilePath(packageInfo.appName)).existsSync();
case TargetPlatform.windows:
return _getWindowsRegistryKey().getValueAsString(packageInfo.appName)?.contains(Platform.resolvedExecutable) ?? false;
default:
return false;
}
}
Future<bool> isAutoStartHidden() async {
final packageInfo = await PackageInfo.fromPlatform();
switch (defaultTargetPlatform) {
case TargetPlatform.linux:
final file = File(_getLinuxFilePath(packageInfo.appName));
if (!file.existsSync()) {
return false;
}
return file.readAsStringSync().contains(startHiddenFlag);
case TargetPlatform.macOS:
final file = File(_getMacOSFilePath(packageInfo.appName));
if (!file.existsSync()) {
return false;
}
return file.readAsStringSync().contains(startHiddenFlag);
case TargetPlatform.windows:
return _getWindowsRegistryKey().getValueAsString(packageInfo.appName)?.contains(startHiddenFlag) ?? false;
default:
return false;
}
}
RegistryKey _getWindowsRegistryKey() {
return Registry.openPath(
RegistryHive.currentUser,
path: r'Software\Microsoft\Windows\CurrentVersion\Run',
desiredAccessRights: AccessRights.allAccess,
);
}
String _getLinuxFilePath(String appName) {
return '${Platform.environment['HOME']}/.config/autostart/$appName.desktop';
}
String _getMacOSFilePath(String appName) {
return '${Platform.environment['HOME']}/Library/LaunchAgents/$appName.plist';
}

View File

@@ -33,14 +33,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
archive:
dependency: transitive
description:
name: archive
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
url: "https://pub.dev"
source: hosted
version: "3.6.1"
args:
dependency: transitive
description:
@@ -153,14 +145,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.3"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
url: "https://pub.dev"
source: hosted
version: "0.4.1"
clock:
dependency: transitive
description:
@@ -216,14 +200,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.4"
console:
dependency: transitive
description:
name: console
sha256: e04e7824384c5b39389acdd6dc7d33f3efe6b232f6f16d7626f194f6a01ad69a
url: "https://pub.dev"
source: hosted
version: "4.1.0"
convert:
dependency: transitive
description:
@@ -572,14 +548,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
get_it:
dependency: transitive
description:
name: get_it
sha256: e6017ce7fdeaf218dc51a100344d8cb70134b80e28b760f8bb23c242437bafd7
url: "https://pub.dev"
source: hosted
version: "7.6.7"
glob:
dependency: transitive
description:
@@ -660,14 +628,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.2"
image:
dependency: transitive
description:
name: image
sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
url: "https://pub.dev"
source: hosted
version: "4.2.0"
image_picker:
dependency: "direct main"
description:
@@ -900,14 +860,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.4.4"
msix:
dependency: "direct dev"
description:
name: msix
sha256: "519b183d15dc9f9c594f247e2d2339d855cf0eaacc30e19b128e14f3ecc62047"
url: "https://pub.dev"
source: hosted
version: "3.16.7"
nested:
dependency: transitive
description:
@@ -1771,7 +1723,7 @@ packages:
source: hosted
version: "5.1.1"
win32_registry:
dependency: transitive
dependency: "direct main"
description:
name: win32_registry
sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a"

View File

@@ -68,6 +68,7 @@ dependencies:
uuid: 3.0.7
wakelock_plus: 1.1.4
wechat_assets_picker: 8.8.1+1
win32_registry: 1.1.2
window_manager: 0.3.9
windows_taskbar: 1.1.2
yaru: 1.2.2
@@ -78,7 +79,6 @@ dev_dependencies:
flutter_gen_runner: 5.6.0
flutter_lints: 3.0.1
mockito: 5.4.4
msix: 3.16.7
refena_inspector: 2.0.0
slang_build_runner: 3.31.0
slang_gpt: 0.10.2
@@ -102,21 +102,3 @@ flutter:
flutter_gen:
line_length: 150
msix_config:
display_name: LocalSend
publisher_display_name: Tien Do Nam
# Using third-party CA for now (see: https://github.com/localsend/localsend/issues/220)
# publisher: CN=0A8E9755-183F-4F0B-823F-1B8C991D7B97
identity_name: 11157TienDoNam.LocalSend
logo_path: assets\img\logo-512.png
architecture: x64
languages: en, ar, bn, cs, da, de, el, es-ES, eu, fa, fr, he, hu, in, it, ja, ko, ne, nl, pl, pt-BR, ru, sv, th, tr, uk, vi, zh-CN, zh-HK, zh-TW
# https://github.com/localsend/localsend/issues/398
os_min_version: 10.0.19041.0
startup_task:
task_id: localsend
enabled: false
parameters: autostart

View File

@@ -443,26 +443,6 @@ class MockPersistenceService extends _i1.Mock implements _i3.PersistenceService
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
@override
bool isAutoStartLaunchMinimized() => (super.noSuchMethod(
Invocation.method(
#isAutoStartLaunchMinimized,
[],
),
returnValue: false,
returnValueForMissingStub: false,
) as bool);
@override
_i4.Future<void> setAutoStartLaunchMinimized(bool? launchMinimized) => (super.noSuchMethod(
Invocation.method(
#setAutoStartLaunchMinimized,
[launchMinimized],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
@override
bool isHttps() => (super.noSuchMethod(
Invocation.method(