mirror of
https://github.com/localsend/localsend.git
synced 2026-04-25 17:43:41 -04:00
feat: remove msix, improve auto start (#1467)
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user