diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml
index 55530182..4a2ac6cc 100644
--- a/app/android/app/src/main/AndroidManifest.xml
+++ b/app/android/app/src/main/AndroidManifest.xml
@@ -46,9 +46,9 @@
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
+ android:name="io.flutter.embedding.android.NormalTheme"
+ android:resource="@style/NormalTheme"
+ />
diff --git a/app/android/app/src/main/res/values-night/styles.xml b/app/android/app/src/main/res/values-night/styles.xml
index 3c389d5f..06952be7 100644
--- a/app/android/app/src/main/res/values-night/styles.xml
+++ b/app/android/app/src/main/res/values-night/styles.xml
@@ -5,7 +5,6 @@
- @drawable/launch_background
- - shortEdges
diff --git a/app/android/app/src/main/res/values/styles.xml b/app/android/app/src/main/res/values/styles.xml
index b69f5353..cb1ef880 100644
--- a/app/android/app/src/main/res/values/styles.xml
+++ b/app/android/app/src/main/res/values/styles.xml
@@ -5,7 +5,6 @@
- @drawable/launch_background
- - shortEdges
diff --git a/app/lib/config/init.dart b/app/lib/config/init.dart
index 8ce4c060..ae371e2f 100644
--- a/app/lib/config/init.dart
+++ b/app/lib/config/init.dart
@@ -1,7 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
-import 'package:bitsdojo_window/bitsdojo_window.dart';
+
import 'package:dart_mappable/dart_mappable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
@@ -137,13 +137,11 @@ Future preInit(List args) async {
startHidden = await isLaunchedAsLoginItem() && await getLaunchAtLoginMinimized();
}
- doWhenWindowReady(() {
- if (startHidden) {
- unawaited(hideToTray());
- } else {
- unawaited(showFromTray());
- }
- });
+ if (startHidden) {
+ unawaited(hideToTray());
+ } else {
+ unawaited(showFromTray());
+ }
if (defaultTargetPlatform == TargetPlatform.macOS) {
await setupStatusBar();
diff --git a/app/lib/pages/about/about_page.dart b/app/lib/pages/about/about_page.dart
index b7554a99..61eb58a9 100644
--- a/app/lib/pages/about/about_page.dart
+++ b/app/lib/pages/about/about_page.dart
@@ -3,7 +3,6 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:localsend_app/gen/strings.g.dart';
import 'package:localsend_app/pages/debug/debug_page.dart';
-import 'package:localsend_app/widget/custom_basic_appbar.dart';
import 'package:localsend_app/widget/local_send_logo.dart';
import 'package:localsend_app/widget/responsive_list_view.dart';
import 'package:routerino/routerino.dart';
@@ -24,7 +23,9 @@ class AboutPage extends StatelessWidget {
Widget build(BuildContext context) {
final primaryColor = Theme.of(context).colorScheme.primary;
return Scaffold(
- appBar: basicLocalSendAppbar(t.aboutPage.title),
+ appBar: AppBar(
+ title: Text(t.aboutPage.title),
+ ),
body: ResponsiveListView(
padding: const EdgeInsets.symmetric(horizontal: 15),
children: [
diff --git a/app/lib/pages/changelog_page.dart b/app/lib/pages/changelog_page.dart
index 7e1f64f2..e6d4acd3 100644
--- a/app/lib/pages/changelog_page.dart
+++ b/app/lib/pages/changelog_page.dart
@@ -4,7 +4,6 @@ import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:localsend_app/gen/assets.gen.dart';
import 'package:localsend_app/gen/strings.g.dart';
import 'package:localsend_app/util/ui/nav_bar_padding.dart';
-import 'package:localsend_app/widget/custom_basic_appbar.dart';
class ChangelogPage extends StatelessWidget {
const ChangelogPage();
@@ -12,7 +11,9 @@ class ChangelogPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
- appBar: basicLocalSendAppbar(t.changelogPage.title),
+ appBar: AppBar(
+ title: Text(t.changelogPage.title),
+ ),
body: FutureBuilder(
future: rootBundle.loadString(Assets.changelog), // ignore: discarded_futures
builder: (context, data) {
@@ -21,8 +22,8 @@ class ChangelogPage extends StatelessWidget {
}
return Markdown(
padding: EdgeInsets.only(
- left: 15 + MediaQuery.of(context).padding.left,
- right: 15 + MediaQuery.of(context).padding.right,
+ left: 15,
+ right: 15,
top: 15,
bottom: 15 + getNavBarPadding(context),
),
diff --git a/app/lib/pages/debug/debug_page.dart b/app/lib/pages/debug/debug_page.dart
index 37c0900c..5d33bdee 100644
--- a/app/lib/pages/debug/debug_page.dart
+++ b/app/lib/pages/debug/debug_page.dart
@@ -8,7 +8,6 @@ import 'package:localsend_app/pages/debug/security_debug_page.dart';
import 'package:localsend_app/provider/app_arguments_provider.dart';
import 'package:localsend_app/provider/persistence_provider.dart';
import 'package:localsend_app/util/shared_preferences/shared_preferences_file.dart';
-import 'package:localsend_app/widget/custom_basic_appbar.dart';
import 'package:localsend_app/widget/debug_entry.dart';
import 'package:refena_flutter/refena_flutter.dart';
import 'package:routerino/routerino.dart';
@@ -24,7 +23,9 @@ class DebugPage extends StatelessWidget {
final store = SharedPreferencesStorePlatform.instance;
return Scaffold(
- appBar: basicLocalSendAppbar('Debugging'),
+ appBar: AppBar(
+ title: const Text('Debugging'),
+ ),
body: ListView(
padding: const EdgeInsets.only(left: 20, right: 20, top: 10, bottom: 30),
children: [
diff --git a/app/lib/pages/debug/discovery_debug_page.dart b/app/lib/pages/debug/discovery_debug_page.dart
index aacf393d..43258083 100644
--- a/app/lib/pages/debug/discovery_debug_page.dart
+++ b/app/lib/pages/debug/discovery_debug_page.dart
@@ -3,7 +3,6 @@ import 'package:intl/intl.dart';
import 'package:localsend_app/provider/logging/discovery_logs_provider.dart';
import 'package:localsend_app/provider/network/nearby_devices_provider.dart';
import 'package:localsend_app/widget/copyable_text.dart';
-import 'package:localsend_app/widget/custom_basic_appbar.dart';
import 'package:localsend_app/widget/responsive_list_view.dart';
import 'package:refena_flutter/refena_flutter.dart';
@@ -17,7 +16,9 @@ class DiscoveryDebugPage extends StatelessWidget {
final ref = context.ref;
final logs = ref.watch(discoveryLoggerProvider);
return Scaffold(
- appBar: basicLocalSendAppbar('Discovery Debugging'),
+ appBar: AppBar(
+ title: const Text('Discovery Debugging'),
+ ),
body: ResponsiveListView(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
children: [
diff --git a/app/lib/pages/debug/http_logs_page.dart b/app/lib/pages/debug/http_logs_page.dart
index 3296cf7e..d28af532 100644
--- a/app/lib/pages/debug/http_logs_page.dart
+++ b/app/lib/pages/debug/http_logs_page.dart
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:localsend_app/provider/logging/http_logs_provider.dart';
import 'package:localsend_app/widget/copyable_text.dart';
-import 'package:localsend_app/widget/custom_basic_appbar.dart';
import 'package:localsend_app/widget/responsive_list_view.dart';
import 'package:refena_flutter/refena_flutter.dart';
@@ -15,7 +14,9 @@ class HttpLogsPage extends StatelessWidget {
Widget build(BuildContext context) {
final logs = context.ref.watch(httpLogsProvider);
return Scaffold(
- appBar: basicLocalSendAppbar('HTTP Logs'),
+ appBar: AppBar(
+ title: const Text('HTTP Logs'),
+ ),
body: ResponsiveListView(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
children: [
diff --git a/app/lib/pages/debug/security_debug_page.dart b/app/lib/pages/debug/security_debug_page.dart
index 6df0c679..1ccf9a71 100644
--- a/app/lib/pages/debug/security_debug_page.dart
+++ b/app/lib/pages/debug/security_debug_page.dart
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:localsend_app/provider/security_provider.dart';
-import 'package:localsend_app/widget/custom_basic_appbar.dart';
import 'package:localsend_app/widget/debug_entry.dart';
import 'package:localsend_app/widget/responsive_list_view.dart';
import 'package:refena_flutter/refena_flutter.dart';
@@ -12,7 +11,9 @@ class SecurityDebugPage extends StatelessWidget {
Widget build(BuildContext context) {
final securityContext = context.ref.watch(securityProvider);
return Scaffold(
- appBar: basicLocalSendAppbar('Security Debugging'),
+ appBar: AppBar(
+ title: const Text('Security Debugging'),
+ ),
body: ResponsiveListView(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
maxWidth: 700,
diff --git a/app/lib/pages/donation/donation_page.dart b/app/lib/pages/donation/donation_page.dart
index 6eeada23..88787691 100644
--- a/app/lib/pages/donation/donation_page.dart
+++ b/app/lib/pages/donation/donation_page.dart
@@ -5,7 +5,6 @@ import 'package:localsend_app/pages/donation/donation_page_vm.dart';
// [FOSS_REMOVE_START]
import 'package:localsend_app/provider/purchase_provider.dart';
// [FOSS_REMOVE_END]
-import 'package:localsend_app/widget/custom_basic_appbar.dart';
import 'package:localsend_app/widget/responsive_list_view.dart';
import 'package:refena_flutter/refena_flutter.dart';
import 'package:url_launcher/url_launcher.dart';
@@ -22,7 +21,9 @@ class DonationPage extends StatelessWidget {
// [FOSS_REMOVE_END]
builder: (context, vm) {
return Scaffold(
- appBar: basicLocalSendAppbar(t.donationPage.title),
+ appBar: AppBar(
+ title: Text(t.donationPage.title),
+ ),
body: Stack(
children: [
ResponsiveListView(
diff --git a/app/lib/pages/home_page.dart b/app/lib/pages/home_page.dart
index 5a98e086..92db41c6 100644
--- a/app/lib/pages/home_page.dart
+++ b/app/lib/pages/home_page.dart
@@ -1,6 +1,5 @@
import 'dart:io';
-import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter/material.dart';
import 'package:localsend_app/config/init.dart';
@@ -12,15 +11,13 @@ import 'package:localsend_app/pages/tabs/send_tab.dart';
import 'package:localsend_app/pages/tabs/settings_tab.dart';
import 'package:localsend_app/provider/selection/selected_sending_files_provider.dart';
import 'package:localsend_app/util/native/cross_file_converters.dart';
-import 'package:localsend_app/util/native/platform_check.dart';
import 'package:localsend_app/widget/responsive_builder.dart';
import 'package:refena_flutter/refena_flutter.dart';
enum HomeTab {
receive(Icons.wifi),
send(Icons.send),
- settings(Icons.settings)
- ;
+ settings(Icons.settings);
const HomeTab(this.icon);
@@ -107,78 +104,62 @@ class _HomePageState extends State with Refena {
body: Row(
children: [
if (!sizingInformation.isMobile)
- Stack(
- children: [
- NavigationRail(
- selectedIndex: vm.currentTab.index,
- onDestinationSelected: (index) => vm.changeTab(HomeTab.values[index]),
- extended: sizingInformation.isDesktop,
- backgroundColor: Theme.of(context).cardColorWithElevation,
- leading: sizingInformation.isDesktop
- ? Column(
- children: [
- checkPlatform([TargetPlatform.macOS])
- ? // considered adding some extra space so it looks more natural
- SizedBox(height: 40)
- : SizedBox(height: 20),
- const Text(
- 'LocalSend',
- style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
- textAlign: TextAlign.center,
- ),
- SizedBox(height: 20),
- ],
- )
- : checkPlatform([TargetPlatform.macOS])
- ? SizedBox(
- height: 20,
- )
- : null,
- destinations: HomeTab.values.map((tab) {
- return NavigationRailDestination(
- icon: Icon(tab.icon),
- label: Text(tab.label),
- );
- }).toList(),
- ),
- // makes the top draggable
- Positioned(
- top: 0,
- left: 0,
- right: 0,
- height: 40,
- child: MoveWindow(),
- ),
- ],
+ NavigationRail(
+ selectedIndex: vm.currentTab.index,
+ onDestinationSelected: (index) => vm.changeTab(HomeTab.values[index]),
+ extended: sizingInformation.isDesktop,
+ backgroundColor: Theme.of(context).cardColorWithElevation,
+ leading: sizingInformation.isDesktop
+ ? const Column(
+ children: [
+ SizedBox(height: 20),
+ Text(
+ 'LocalSend',
+ style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
+ textAlign: TextAlign.center,
+ ),
+ SizedBox(height: 20),
+ ],
+ )
+ : null,
+ destinations: HomeTab.values.map((tab) {
+ return NavigationRailDestination(
+ icon: Icon(tab.icon),
+ label: Text(tab.label),
+ );
+ }).toList(),
),
Expanded(
- child: Stack(
- children: [
- PageView(
- controller: vm.controller,
- physics: const NeverScrollableScrollPhysics(),
- children: const [
- SafeArea(child: ReceiveTab()),
- SafeArea(child: SendTab()),
- SettingsTab(),
- ],
- ),
- if (_dragAndDropIndicator)
- Container(
- width: double.infinity,
- decoration: BoxDecoration(
- color: Theme.of(context).scaffoldBackgroundColor,
- ),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const Icon(Icons.file_download, size: 128),
- const SizedBox(height: 30),
- Text(t.sendTab.placeItems, style: Theme.of(context).textTheme.titleLarge),
- ],
- ),
+ child: SafeArea(
+ left: sizingInformation.isMobile,
+ child: Stack(
+ children: [
+ PageView(
+ controller: vm.controller,
+ physics: const NeverScrollableScrollPhysics(),
+ children: const [
+ ReceiveTab(),
+ SendTab(),
+ SettingsTab(),
+ ],
),
- ],
+ if (_dragAndDropIndicator)
+ Container(
+ width: double.infinity,
+ decoration: BoxDecoration(
+ color: Theme.of(context).scaffoldBackgroundColor,
+ ),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ const Icon(Icons.file_download, size: 128),
+ const SizedBox(height: 30),
+ Text(t.sendTab.placeItems, style: Theme.of(context).textTheme.titleLarge),
+ ],
+ ),
+ ),
+ ],
+ ),
),
),
],
diff --git a/app/lib/pages/language_page.dart b/app/lib/pages/language_page.dart
index 470d4db4..7038cfbd 100644
--- a/app/lib/pages/language_page.dart
+++ b/app/lib/pages/language_page.dart
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:localsend_app/gen/strings.g.dart';
import 'package:localsend_app/provider/settings_provider.dart';
-import 'package:localsend_app/widget/custom_basic_appbar.dart';
import 'package:localsend_app/widget/responsive_list_view.dart';
import 'package:refena_flutter/refena_flutter.dart';
@@ -28,7 +27,9 @@ class _LanguagePageState extends State {
final t = Translations.of(context);
final activeLocale = context.ref.watch(settingsProvider.select((s) => s.locale));
return Scaffold(
- appBar: basicLocalSendAppbar(t.sendTab.selection.title),
+ appBar: AppBar(
+ title: Text(t.settingsTab.general.language),
+ ),
body: ResponsiveListView(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20),
children: [
diff --git a/app/lib/pages/progress_page.dart b/app/lib/pages/progress_page.dart
index 11066f0f..130470a4 100644
--- a/app/lib/pages/progress_page.dart
+++ b/app/lib/pages/progress_page.dart
@@ -1,7 +1,6 @@
import 'dart:async';
import 'dart:typed_data';
-import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:localsend_app/config/theme.dart';
@@ -17,7 +16,6 @@ import 'package:localsend_app/util/native/platform_check.dart';
import 'package:localsend_app/util/native/taskbar_helper.dart';
import 'package:localsend_app/util/notification_strings.dart';
import 'package:localsend_app/util/ui/nav_bar_padding.dart';
-import 'package:localsend_app/widget/custom_basic_appbar.dart';
import 'package:localsend_app/widget/custom_progress_bar.dart';
import 'package:localsend_app/widget/dialogs/cancel_session_dialog.dart';
import 'package:localsend_app/widget/dialogs/error_dialog.dart';
@@ -246,7 +244,11 @@ class _ProgressPageState extends State with Refena {
},
canPop: false,
child: Scaffold(
- appBar: widget.showAppBar ? basicLocalSendAppbar(title) : null,
+ appBar: widget.showAppBar
+ ? AppBar(
+ title: Text(title),
+ )
+ : null,
body: Stack(
children: [
ListView.builder(
@@ -532,15 +534,6 @@ class _ProgressPageState extends State with Refena {
),
),
),
- checkPlatform([TargetPlatform.macOS])
- ? Positioned(
- top: 0,
- left: 0,
- right: 0,
- height: 40,
- child: MoveWindow(),
- )
- : SizedBox(),
],
),
),
diff --git a/app/lib/pages/receive_history_page.dart b/app/lib/pages/receive_history_page.dart
index 606424af..530eda87 100644
--- a/app/lib/pages/receive_history_page.dart
+++ b/app/lib/pages/receive_history_page.dart
@@ -11,7 +11,6 @@ import 'package:localsend_app/util/native/directories.dart';
import 'package:localsend_app/util/native/open_file.dart';
import 'package:localsend_app/util/native/open_folder.dart';
import 'package:localsend_app/util/native/platform_check.dart';
-import 'package:localsend_app/widget/custom_basic_appbar.dart';
import 'package:localsend_app/widget/dialogs/file_info_dialog.dart';
import 'package:localsend_app/widget/dialogs/history_clear_dialog.dart';
import 'package:localsend_app/widget/file_thumbnail.dart';
@@ -64,8 +63,11 @@ class ReceiveHistoryPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final entries = context.watch(receiveHistoryProvider);
+
return Scaffold(
- appBar: basicLocalSendAppbar(t.receiveHistoryPage.title),
+ appBar: AppBar(
+ title: Text(t.receiveHistoryPage.title),
+ ),
body: ResponsiveListView(
padding: const EdgeInsets.symmetric(vertical: 20),
children: [
diff --git a/app/lib/pages/selected_files_page.dart b/app/lib/pages/selected_files_page.dart
index 8b2d993b..9642d4be 100644
--- a/app/lib/pages/selected_files_page.dart
+++ b/app/lib/pages/selected_files_page.dart
@@ -5,7 +5,6 @@ import 'package:localsend_app/gen/strings.g.dart';
import 'package:localsend_app/provider/selection/selected_sending_files_provider.dart';
import 'package:localsend_app/util/native/open_file.dart';
import 'package:localsend_app/util/ui/nav_bar_padding.dart';
-import 'package:localsend_app/widget/custom_basic_appbar.dart';
import 'package:localsend_app/widget/dialogs/message_input_dialog.dart';
import 'package:localsend_app/widget/file_thumbnail.dart';
import 'package:localsend_app/widget/responsive_list_view.dart';
@@ -23,7 +22,9 @@ class SelectedFilesPage extends StatelessWidget {
final selectedFiles = ref.watch(selectedSendingFilesProvider);
return Scaffold(
- appBar: basicLocalSendAppbar(t.sendTab.selection.title),
+ appBar: AppBar(
+ title: Text(t.sendTab.selection.title),
+ ),
body: ResponsiveListView.single(
padding: const EdgeInsets.symmetric(horizontal: 15),
tabletPadding: const EdgeInsets.symmetric(horizontal: 15),
diff --git a/app/lib/pages/send_page.dart b/app/lib/pages/send_page.dart
index 55f4437c..35ff3dd2 100644
--- a/app/lib/pages/send_page.dart
+++ b/app/lib/pages/send_page.dart
@@ -10,7 +10,6 @@ import 'package:localsend_app/util/favorites.dart';
import 'package:localsend_app/util/native/taskbar_helper.dart';
import 'package:localsend_app/widget/animations/initial_fade_transition.dart';
import 'package:localsend_app/widget/animations/initial_slide_transition.dart';
-import 'package:localsend_app/widget/custom_basic_appbar.dart';
import 'package:localsend_app/widget/dialogs/error_dialog.dart';
import 'package:localsend_app/widget/list_tile/device_list_tile.dart';
import 'package:localsend_app/widget/responsive_list_view.dart';
@@ -90,7 +89,7 @@ class _SendPageState extends State with Refena {
},
canPop: true,
child: Scaffold(
- appBar: widget.showAppBar ? basicLocalSendAppbar('') : null,
+ appBar: widget.showAppBar ? AppBar() : null,
body: SafeArea(
child: Center(
child: ConstrainedBox(
diff --git a/app/lib/pages/settings/network_interfaces_page.dart b/app/lib/pages/settings/network_interfaces_page.dart
index eede28df..3c5df687 100644
--- a/app/lib/pages/settings/network_interfaces_page.dart
+++ b/app/lib/pages/settings/network_interfaces_page.dart
@@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
import 'package:local_hero/local_hero.dart';
import 'package:localsend_app/gen/strings.g.dart';
import 'package:localsend_app/provider/settings_provider.dart';
-import 'package:localsend_app/widget/custom_basic_appbar.dart';
import 'package:localsend_app/widget/dialogs/text_field_tv.dart';
import 'package:localsend_app/widget/labeled_checkbox.dart';
import 'package:localsend_app/widget/responsive_list_view.dart';
@@ -44,7 +43,9 @@ class _NetworkInterfacesPageState extends State {
? context.notifier(settingsProvider).setNetworkWhitelist
: context.notifier(settingsProvider).setNetworkBlacklist;
return Scaffold(
- appBar: basicLocalSendAppbar(t.networkInterfacesPage.title),
+ appBar: AppBar(
+ title: Text(t.networkInterfacesPage.title),
+ ),
body: LocalHeroScope(
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut,
diff --git a/app/lib/pages/tabs/receive_tab.dart b/app/lib/pages/tabs/receive_tab.dart
index 379edcd1..83c65cef 100644
--- a/app/lib/pages/tabs/receive_tab.dart
+++ b/app/lib/pages/tabs/receive_tab.dart
@@ -1,4 +1,3 @@
-import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:flutter/material.dart';
import 'package:localsend_app/gen/strings.g.dart';
import 'package:localsend_app/pages/home_page.dart';
@@ -7,7 +6,6 @@ import 'package:localsend_app/pages/receive_history_page.dart';
import 'package:localsend_app/pages/tabs/receive_tab_vm.dart';
import 'package:localsend_app/provider/animation_provider.dart';
import 'package:localsend_app/util/ip_helper.dart';
-import 'package:localsend_app/util/native/platform_check.dart';
import 'package:localsend_app/widget/animations/initial_fade_transition.dart';
import 'package:localsend_app/widget/column_list_view.dart';
import 'package:localsend_app/widget/custom_icon_button.dart';
@@ -32,9 +30,6 @@ class ReceiveTab extends StatelessWidget {
return Stack(
children: [
- checkPlatform([TargetPlatform.macOS])
- ? SizedBox(height: 50, child: MoveWindow())
- : SizedBox(height: 0, width: 0), // makes the top part that's not occupied by another widget draggable
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: ResponsiveListView.defaultMaxWidth),
diff --git a/app/lib/pages/tabs/send_tab.dart b/app/lib/pages/tabs/send_tab.dart
index 3705c25d..083027c7 100644
--- a/app/lib/pages/tabs/send_tab.dart
+++ b/app/lib/pages/tabs/send_tab.dart
@@ -1,4 +1,3 @@
-import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:localsend_app/config/theme.dart';
@@ -50,233 +49,222 @@ class SendTab extends StatelessWidget {
final sizingInformation = SizingInformation(MediaQuery.sizeOf(context).width);
final buttonWidth = sizingInformation.isDesktop ? BigButton.desktopWidth : BigButton.mobileWidth;
final ref = context.ref;
- return Stack(
+ return ResponsiveListView(
+ padding: EdgeInsets.zero,
children: [
- ResponsiveListView(
- padding: EdgeInsets.zero,
- children: [
- const SizedBox(height: 20),
- if (vm.selectedFiles.isEmpty) ...[
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: _horizontalPadding),
- child: Text(
- t.sendTab.selection.title,
- style: Theme.of(context).textTheme.titleMedium,
- ),
- ),
- ResponsiveWrapView(
- outerHorizontalPadding: 15,
- outerVerticalPadding: 10,
- childPadding: 10,
- minChildWidth: buttonWidth,
- children: _options.map((option) {
- return BigButton(
- icon: option.icon,
- label: option.label,
- filled: false,
- onTap: () async => ref.global.dispatchAsync(
- PickFileAction(
- option: option,
- context: context,
- ),
- ),
- );
- }).toList(),
- ),
- ] else ...[
- Card(
- margin: const EdgeInsets.only(bottom: 10, left: _horizontalPadding, right: _horizontalPadding),
- child: Padding(
- padding: const EdgeInsetsDirectional.only(start: 15, top: 5, bottom: 15),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- Text(
- t.sendTab.selection.title,
- style: Theme.of(context).textTheme.titleMedium,
- ),
- const Spacer(),
- CustomIconButton(
- onPressed: () => ref.redux(selectedSendingFilesProvider).dispatch(ClearSelectionAction()),
- child: Icon(Icons.close, color: Theme.of(context).colorScheme.secondary),
- ),
- const SizedBox(width: 5),
- ],
- ),
- const SizedBox(height: 5),
- Text(t.sendTab.selection.files(files: vm.selectedFiles.length)),
- Text(t.sendTab.selection.size(size: vm.selectedFiles.fold(0, (prev, curr) => prev + curr.size).asReadableFileSize)),
- const SizedBox(height: 10),
- SizedBox(
- height: defaultThumbnailSize,
- child: ListView.builder(
- scrollDirection: Axis.horizontal,
- itemCount: vm.selectedFiles.length,
- itemBuilder: (context, index) {
- final file = vm.selectedFiles[index];
- return Padding(
- padding: const EdgeInsets.only(right: 10),
- child: SmartFileThumbnail.fromCrossFile(file),
- );
- },
- ),
- ),
- const SizedBox(height: 10),
- Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- TextButton(
- style: TextButton.styleFrom(
- foregroundColor: Theme.of(context).colorScheme.onSurface,
- ),
- onPressed: () async {
- await context.push(() => const SelectedFilesPage());
- },
- child: Text(t.general.edit),
- ),
- const SizedBox(width: 15),
- ElevatedButton.icon(
- style: ElevatedButton.styleFrom(
- backgroundColor: Theme.of(context).colorScheme.primary,
- foregroundColor: Theme.of(context).colorScheme.onPrimary,
- ),
- onPressed: () async {
- if (_options.length == 1) {
- // open directly
- await ref.global.dispatchAsync(
- PickFileAction(
- option: _options.first,
- context: context,
- ),
- );
- return;
- }
- await AddFileDialog.open(
- context: context,
- options: _options,
- );
- },
- icon: const Icon(Icons.add),
- label: Text(t.general.add),
- ),
- const SizedBox(width: 15),
- ],
- ),
- ],
- ),
- ),
- ),
- ],
- Row(
- children: [
- const SizedBox(width: _horizontalPadding),
- Flexible(
- child: Padding(
- padding: const EdgeInsets.symmetric(vertical: 10),
- child: Text(t.sendTab.nearbyDevices, style: Theme.of(context).textTheme.titleMedium),
- ),
- ),
- const SizedBox(width: 10),
- _ScanButton(
- ips: vm.localIps,
- ),
- Tooltip(
- message: t.sendTab.manualSending,
- child: CustomIconButton(
- onPressed: () async => vm.onTapAddress(context),
- child: const Icon(Icons.ads_click),
- ),
- ),
- Tooltip(
- message: t.dialogs.favoriteDialog.title,
- child: CustomIconButton(
- onPressed: () async => await vm.onTapFavorite(context),
- child: const Icon(Icons.favorite),
- ),
- ),
- _SendModeButton(
- onSelect: (mode) async => vm.onTapSendMode(context, mode),
- ),
- ],
+ const SizedBox(height: 20),
+ if (vm.selectedFiles.isEmpty) ...[
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: _horizontalPadding),
+ child: Text(
+ t.sendTab.selection.title,
+ style: Theme.of(context).textTheme.titleMedium,
),
- if (vm.nearbyDevices.isEmpty)
- const Padding(
- padding: EdgeInsets.only(bottom: 10, left: _horizontalPadding, right: _horizontalPadding),
- child: Opacity(
- opacity: 0.3,
- child: DevicePlaceholderListTile(),
- ),
- ),
- ...vm.nearbyDevices.map((device) {
- final favoriteEntry = vm.favoriteDevices.findDevice(device);
- return Padding(
- padding: const EdgeInsets.only(bottom: 10, left: _horizontalPadding, right: _horizontalPadding),
- child: Hero(
- tag: 'device-${device.ip}',
- child: vm.sendMode == SendMode.multiple
- ? _MultiSendDeviceListTile(
- device: device,
- isFavorite: favoriteEntry != null,
- nameOverride: favoriteEntry?.alias,
- vm: vm,
- )
- : DeviceListTile(
- device: device,
- isFavorite: favoriteEntry != null,
- nameOverride: favoriteEntry?.alias,
- onFavoriteTap: () async => await vm.onToggleFavorite(context, device),
- onTap: () async => await vm.onTapDevice(context, device),
- ),
+ ),
+ ResponsiveWrapView(
+ outerHorizontalPadding: 15,
+ outerVerticalPadding: 10,
+ childPadding: 10,
+ minChildWidth: buttonWidth,
+ children: _options.map((option) {
+ return BigButton(
+ icon: option.icon,
+ label: option.label,
+ filled: false,
+ onTap: () async => ref.global.dispatchAsync(
+ PickFileAction(
+ option: option,
+ context: context,
+ ),
),
);
- }),
- const SizedBox(height: 10),
- Center(
- child: TextButton(
- onPressed: () async {
- await context.push(() => const TroubleshootPage());
- },
- child: Text(t.troubleshootPage.title),
- ),
- ),
- const SizedBox(height: 20),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: _horizontalPadding),
- child: Consumer(
- builder: (context, ref) {
- final animations = ref.watch(animationProvider);
- return OpacitySlideshow(
- durationMillis: 6000,
- running: animations,
+ }).toList(),
+ ),
+ ] else ...[
+ Card(
+ margin: const EdgeInsets.only(bottom: 10, left: _horizontalPadding, right: _horizontalPadding),
+ child: Padding(
+ padding: const EdgeInsetsDirectional.only(start: 15, top: 5, bottom: 15),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
children: [
Text(
- t.sendTab.help,
- style: const TextStyle(color: Colors.grey),
- textAlign: TextAlign.center,
+ t.sendTab.selection.title,
+ style: Theme.of(context).textTheme.titleMedium,
),
- if (checkPlatformCanReceiveShareIntent())
- Text(
- t.sendTab.shareIntentInfo,
- style: const TextStyle(color: Colors.grey),
- textAlign: TextAlign.center,
- ),
+ const Spacer(),
+ CustomIconButton(
+ onPressed: () => ref.redux(selectedSendingFilesProvider).dispatch(ClearSelectionAction()),
+ child: Icon(Icons.close, color: Theme.of(context).colorScheme.secondary),
+ ),
+ const SizedBox(width: 5),
],
- );
- },
+ ),
+ const SizedBox(height: 5),
+ Text(t.sendTab.selection.files(files: vm.selectedFiles.length)),
+ Text(t.sendTab.selection.size(size: vm.selectedFiles.fold(0, (prev, curr) => prev + curr.size).asReadableFileSize)),
+ const SizedBox(height: 10),
+ SizedBox(
+ height: defaultThumbnailSize,
+ child: ListView.builder(
+ scrollDirection: Axis.horizontal,
+ itemCount: vm.selectedFiles.length,
+ itemBuilder: (context, index) {
+ final file = vm.selectedFiles[index];
+ return Padding(
+ padding: const EdgeInsets.only(right: 10),
+ child: SmartFileThumbnail.fromCrossFile(file),
+ );
+ },
+ ),
+ ),
+ const SizedBox(height: 10),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ TextButton(
+ style: TextButton.styleFrom(
+ foregroundColor: Theme.of(context).colorScheme.onSurface,
+ ),
+ onPressed: () async {
+ await context.push(() => const SelectedFilesPage());
+ },
+ child: Text(t.general.edit),
+ ),
+ const SizedBox(width: 15),
+ ElevatedButton.icon(
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Theme.of(context).colorScheme.primary,
+ foregroundColor: Theme.of(context).colorScheme.onPrimary,
+ ),
+ onPressed: () async {
+ if (_options.length == 1) {
+ // open directly
+ await ref.global.dispatchAsync(
+ PickFileAction(
+ option: _options.first,
+ context: context,
+ ),
+ );
+ return;
+ }
+ await AddFileDialog.open(
+ context: context,
+ options: _options,
+ );
+ },
+ icon: const Icon(Icons.add),
+ label: Text(t.general.add),
+ ),
+ const SizedBox(width: 15),
+ ],
+ ),
+ ],
),
),
- const SizedBox(height: 50),
+ ),
+ ],
+ Row(
+ children: [
+ const SizedBox(width: _horizontalPadding),
+ Flexible(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 10),
+ child: Text(t.sendTab.nearbyDevices, style: Theme.of(context).textTheme.titleMedium),
+ ),
+ ),
+ const SizedBox(width: 10),
+ _ScanButton(
+ ips: vm.localIps,
+ ),
+ Tooltip(
+ message: t.sendTab.manualSending,
+ child: CustomIconButton(
+ onPressed: () async => vm.onTapAddress(context),
+ child: const Icon(Icons.ads_click),
+ ),
+ ),
+ Tooltip(
+ message: t.dialogs.favoriteDialog.title,
+ child: CustomIconButton(
+ onPressed: () async => await vm.onTapFavorite(context),
+ child: const Icon(Icons.favorite),
+ ),
+ ),
+ _SendModeButton(
+ onSelect: (mode) async => vm.onTapSendMode(context, mode),
+ ),
],
),
- // make the top draggable on Desktop
- checkPlatform([TargetPlatform.macOS])
- ? SizedBox(height: 50, child: MoveWindow())
- : SizedBox(
- height: 0,
- width: 0,
- ),
+ if (vm.nearbyDevices.isEmpty)
+ const Padding(
+ padding: EdgeInsets.only(bottom: 10, left: _horizontalPadding, right: _horizontalPadding),
+ child: Opacity(
+ opacity: 0.3,
+ child: DevicePlaceholderListTile(),
+ ),
+ ),
+ ...vm.nearbyDevices.map((device) {
+ final favoriteEntry = vm.favoriteDevices.findDevice(device);
+ return Padding(
+ padding: const EdgeInsets.only(bottom: 10, left: _horizontalPadding, right: _horizontalPadding),
+ child: Hero(
+ tag: 'device-${device.ip}',
+ child: vm.sendMode == SendMode.multiple
+ ? _MultiSendDeviceListTile(
+ device: device,
+ isFavorite: favoriteEntry != null,
+ nameOverride: favoriteEntry?.alias,
+ vm: vm,
+ )
+ : DeviceListTile(
+ device: device,
+ isFavorite: favoriteEntry != null,
+ nameOverride: favoriteEntry?.alias,
+ onFavoriteTap: () async => await vm.onToggleFavorite(context, device),
+ onTap: () async => await vm.onTapDevice(context, device),
+ ),
+ ),
+ );
+ }),
+ const SizedBox(height: 10),
+ Center(
+ child: TextButton(
+ onPressed: () async {
+ await context.push(() => const TroubleshootPage());
+ },
+ child: Text(t.troubleshootPage.title),
+ ),
+ ),
+ const SizedBox(height: 20),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: _horizontalPadding),
+ child: Consumer(
+ builder: (context, ref) {
+ final animations = ref.watch(animationProvider);
+ return OpacitySlideshow(
+ durationMillis: 6000,
+ running: animations,
+ children: [
+ Text(
+ t.sendTab.help,
+ style: const TextStyle(color: Colors.grey),
+ textAlign: TextAlign.center,
+ ),
+ if (checkPlatformCanReceiveShareIntent())
+ Text(
+ t.sendTab.shareIntentInfo,
+ style: const TextStyle(color: Colors.grey),
+ textAlign: TextAlign.center,
+ ),
+ ],
+ );
+ },
+ ),
+ ),
+ const SizedBox(height: 50),
],
);
},
diff --git a/app/lib/pages/tabs/settings_tab.dart b/app/lib/pages/tabs/settings_tab.dart
index 8db398d7..920d27b3 100644
--- a/app/lib/pages/tabs/settings_tab.dart
+++ b/app/lib/pages/tabs/settings_tab.dart
@@ -1,7 +1,5 @@
import 'dart:io';
-import 'dart:ui';
-import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:localsend_app/config/theme.dart';
@@ -46,566 +44,538 @@ class SettingsTab extends StatelessWidget {
provider: (ref) => settingsTabControllerProvider,
builder: (context, vm) {
final ref = context.ref;
- return Stack(
+ return ResponsiveListView(
+ padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 40),
children: [
Padding(
- padding: EdgeInsets.only(
- right: MediaQuery.of(context).padding.right,
- ), // So camera or 3-button navigation doesn't interfere on the right, rest is handled
- child: ResponsiveListView(
- padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 40),
- children: [
- SizedBox(height: 30 + MediaQuery.of(context).padding.top),
- _SettingsSection(
- title: t.settingsTab.general.title,
- children: [
- _SettingsEntry(
- label: t.settingsTab.general.brightness,
- child: CustomDropdownButton(
- value: vm.settings.theme,
- items: vm.themeModes.map((theme) {
- return DropdownMenuItem(
- value: theme,
- alignment: Alignment.center,
- child: Text(theme.humanName),
- );
- }).toList(),
- onChanged: (theme) => vm.onChangeTheme(context, theme),
- ),
- ),
- _SettingsEntry(
- label: t.settingsTab.general.color,
- child: CustomDropdownButton(
- value: vm.settings.colorMode,
- items: vm.colorModes.map((colorMode) {
- return DropdownMenuItem(
- value: colorMode,
- alignment: Alignment.center,
- child: Text(colorMode.humanName),
- );
- }).toList(),
- onChanged: vm.onChangeColorMode,
- ),
- ),
- _ButtonEntry(
- label: t.settingsTab.general.language,
- buttonLabel: vm.settings.locale?.humanName ?? t.settingsTab.general.languageOptions.system,
- onTap: () => vm.onTapLanguage(context),
- ),
- if (checkPlatformIsDesktop()) ...[
- /// Wayland does window position handling, so there's no need for it. See [https://github.com/localsend/localsend/issues/544]
- if (vm.advanced && checkPlatformIsNotWaylandDesktop())
- _BooleanEntry(
- label: defaultTargetPlatform == TargetPlatform.windows
- ? t.settingsTab.general.saveWindowPlacementWindows
- : t.settingsTab.general.saveWindowPlacement,
- value: vm.settings.saveWindowPlacement,
- onChanged: (b) async {
- await ref.notifier(settingsProvider).setSaveWindowPlacement(b);
- },
- ),
- if (checkPlatformHasTray()) ...[
- _BooleanEntry(
- label: t.settingsTab.general.minimizeToTray,
- value: vm.settings.minimizeToTray,
- onChanged: (b) async {
- await ref.notifier(settingsProvider).setMinimizeToTray(b);
- },
- ),
- ],
- if (checkPlatformIsDesktop()) ...[
- _BooleanEntry(
- label: t.settingsTab.general.launchAtStartup,
- value: vm.autoStart,
- onChanged: (_) => vm.onToggleAutoStart(context),
- ),
- Visibility(
- visible: vm.autoStart,
- maintainAnimation: true,
- maintainState: true,
- child: AnimatedOpacity(
- opacity: vm.autoStart ? 1.0 : 0.0,
- duration: const Duration(milliseconds: 500),
- child: _BooleanEntry(
- label: t.settingsTab.general.launchMinimized,
- value: vm.autoStartLaunchHidden,
- onChanged: (_) => vm.onToggleAutoStartLaunchHidden(context),
- ),
- ),
- ),
- ],
- if (vm.advanced && checkPlatform([TargetPlatform.windows])) ...[
- _BooleanEntry(
- label: t.settingsTab.general.showInContextMenu,
- value: vm.showInContextMenu,
- onChanged: (_) => vm.onToggleShowInContextMenu(context),
- ),
- ],
- ],
- _BooleanEntry(
- label: t.settingsTab.general.animations,
- value: vm.settings.enableAnimations,
- onChanged: (b) async {
- await ref.notifier(settingsProvider).setEnableAnimations(b);
- },
- ),
- ],
+ padding: const EdgeInsets.only(left: 8),
+ child: Text(t.settingsTab.title, style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center),
+ ),
+ const SizedBox(height: 30),
+ _SettingsSection(
+ title: t.settingsTab.general.title,
+ children: [
+ _SettingsEntry(
+ label: t.settingsTab.general.brightness,
+ child: CustomDropdownButton(
+ value: vm.settings.theme,
+ items: vm.themeModes.map((theme) {
+ return DropdownMenuItem(
+ value: theme,
+ alignment: Alignment.center,
+ child: Text(theme.humanName),
+ );
+ }).toList(),
+ onChanged: (theme) => vm.onChangeTheme(context, theme),
),
- _SettingsSection(
- title: t.settingsTab.receive.title,
- children: [
- _BooleanEntry(
- label: t.settingsTab.receive.quickSave,
- value: vm.settings.quickSave,
- onChanged: (b) async {
- final old = vm.settings.quickSave;
- await ref.notifier(settingsProvider).setQuickSave(b);
- if (!old && b && context.mounted) {
- await QuickSaveNotice.open(context);
- }
- },
- ),
- _BooleanEntry(
- label: t.settingsTab.receive.quickSaveFromFavorites,
- value: vm.settings.quickSaveFromFavorites,
- onChanged: (b) async {
- final old = vm.settings.quickSaveFromFavorites;
- await ref.notifier(settingsProvider).setQuickSaveFromFavorites(b);
- if (!old && b && context.mounted) {
- await QuickSaveFromFavoritesNotice.open(context);
- }
- },
- ),
- _BooleanEntry(
- label: t.settingsTab.receive.requirePin,
- value: vm.settings.receivePin != null,
- onChanged: (b) async {
- final currentPIN = vm.settings.receivePin;
- if (currentPIN != null) {
- await ref.notifier(settingsProvider).setReceivePin(null);
- } else {
- final String? newPin = await showDialog(
- context: context,
- builder: (_) => const PinDialog(
- obscureText: false,
- generateRandom: false,
- ),
- );
-
- if (newPin != null && newPin.isNotEmpty) {
- await ref.notifier(settingsProvider).setReceivePin(newPin);
- }
- }
-
- // The pin is enforced by the Rust server, so it needs a restart.
- if (ref.read(serverProvider) != null) {
- await ref.notifier(serverProvider).restartServerFromSettings();
- }
- },
- ),
- if (checkPlatformWithFileSystem())
- _SettingsEntry(
- label: t.settingsTab.receive.destination,
- 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 {
- if (vm.settings.destination != null) {
- await ref.notifier(settingsProvider).setDestination(null);
- if (defaultTargetPlatform == TargetPlatform.macOS) {
- await removeExistingDestinationAccess();
- }
- return;
- }
-
- final directory = await pickDirectoryPath();
- if (directory != null) {
- if (defaultTargetPlatform == TargetPlatform.macOS) {
- await persistDestinationFolderAccess(directory);
- }
- await ref.notifier(settingsProvider).setDestination(directory);
- }
- },
- child: Padding(
- padding: const EdgeInsets.symmetric(vertical: 5),
- child: Text(vm.settings.destination ?? t.settingsTab.receive.downloads, style: Theme.of(context).textTheme.titleMedium),
- ),
- ),
- ),
- if (checkPlatformWithGallery())
- _BooleanEntry(
- label: t.settingsTab.receive.saveToGallery,
- value: vm.settings.saveToGallery,
- onChanged: (b) async {
- await ref.notifier(settingsProvider).setSaveToGallery(b);
- },
- ),
- _BooleanEntry(
- label: t.settingsTab.receive.autoFinish,
- value: vm.settings.autoFinish,
- onChanged: (b) async {
- await ref.notifier(settingsProvider).setAutoFinish(b);
- },
- ),
- _BooleanEntry(
- label: t.settingsTab.receive.saveToHistory,
- value: vm.settings.saveToHistory,
- onChanged: (b) async {
- await ref.notifier(settingsProvider).setSaveToHistory(b);
- },
- ),
- ],
+ ),
+ _SettingsEntry(
+ label: t.settingsTab.general.color,
+ child: CustomDropdownButton(
+ value: vm.settings.colorMode,
+ items: vm.colorModes.map((colorMode) {
+ return DropdownMenuItem(
+ value: colorMode,
+ alignment: Alignment.center,
+ child: Text(colorMode.humanName),
+ );
+ }).toList(),
+ onChanged: vm.onChangeColorMode,
),
- if (vm.advanced)
- _SettingsSection(
- title: t.settingsTab.send.title,
- children: [
- _BooleanEntry(
- label: t.settingsTab.send.shareViaLinkAutoAccept,
- value: vm.settings.shareViaLinkAutoAccept,
- onChanged: (b) async {
- await ref.notifier(settingsProvider).setShareViaLinkAutoAccept(b);
- },
- ),
- ],
+ ),
+ _ButtonEntry(
+ label: t.settingsTab.general.language,
+ buttonLabel: vm.settings.locale?.humanName ?? t.settingsTab.general.languageOptions.system,
+ onTap: () => vm.onTapLanguage(context),
+ ),
+ if (checkPlatformIsDesktop()) ...[
+ /// Wayland does window position handling, so there's no need for it. See [https://github.com/localsend/localsend/issues/544]
+ if (vm.advanced && checkPlatformIsNotWaylandDesktop())
+ _BooleanEntry(
+ label: defaultTargetPlatform == TargetPlatform.windows
+ ? t.settingsTab.general.saveWindowPlacementWindows
+ : t.settingsTab.general.saveWindowPlacement,
+ value: vm.settings.saveWindowPlacement,
+ onChanged: (b) async {
+ await ref.notifier(settingsProvider).setSaveWindowPlacement(b);
+ },
),
- _SettingsSection(
- title: t.settingsTab.network.title,
- children: [
- AnimatedCrossFade(
- crossFadeState:
- vm.serverState != null &&
- (vm.serverState!.alias != vm.settings.alias ||
- vm.serverState!.port != vm.settings.port ||
- vm.serverState!.https != vm.settings.https)
- ? CrossFadeState.showSecond
- : CrossFadeState.showFirst,
- duration: const Duration(milliseconds: 200),
- alignment: Alignment.topLeft,
- firstChild: Container(),
- secondChild: Padding(
- padding: const EdgeInsets.only(bottom: 15),
- child: Text(t.settingsTab.network.needRestart, style: TextStyle(color: Theme.of(context).colorScheme.warning)),
+ if (checkPlatformHasTray()) ...[
+ _BooleanEntry(
+ label: t.settingsTab.general.minimizeToTray,
+ value: vm.settings.minimizeToTray,
+ onChanged: (b) async {
+ await ref.notifier(settingsProvider).setMinimizeToTray(b);
+ },
+ ),
+ ],
+ if (checkPlatformIsDesktop()) ...[
+ _BooleanEntry(
+ label: t.settingsTab.general.launchAtStartup,
+ value: vm.autoStart,
+ onChanged: (_) => vm.onToggleAutoStart(context),
+ ),
+ Visibility(
+ visible: vm.autoStart,
+ maintainAnimation: true,
+ maintainState: true,
+ child: AnimatedOpacity(
+ opacity: vm.autoStart ? 1.0 : 0.0,
+ duration: const Duration(milliseconds: 500),
+ child: _BooleanEntry(
+ label: t.settingsTab.general.launchMinimized,
+ value: vm.autoStartLaunchHidden,
+ onChanged: (_) => vm.onToggleAutoStartLaunchHidden(context),
),
),
- _SettingsEntry(
- label: '${t.settingsTab.network.server}${vm.serverState == null ? ' (${t.general.offline})' : ''}',
- child: DecoratedBox(
- decoration: BoxDecoration(
- color: Theme.of(context).inputDecorationTheme.fillColor,
- borderRadius: Theme.of(context).inputDecorationTheme.borderRadius,
- ),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: [
- if (vm.serverState == null)
- Tooltip(
- message: t.general.start,
- child: TextButton(
- style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.onSurface),
- onPressed: () => vm.onTapStartServer(context),
- child: const Icon(Icons.play_arrow),
- ),
- )
- else
- Tooltip(
- message: t.general.restart,
- child: TextButton(
- style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.onSurface),
- onPressed: () => vm.onTapRestartServer(context),
- child: const Icon(Icons.refresh),
- ),
- ),
- Tooltip(
- message: t.general.stop,
- child: TextButton(
- style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.onSurface),
- onPressed: vm.serverState == null ? null : vm.onTapStopServer,
- child: const Icon(Icons.stop),
- ),
- ),
- ],
- ),
+ ),
+ ],
+ if (vm.advanced && checkPlatform([TargetPlatform.windows])) ...[
+ _BooleanEntry(
+ label: t.settingsTab.general.showInContextMenu,
+ value: vm.showInContextMenu,
+ onChanged: (_) => vm.onToggleShowInContextMenu(context),
+ ),
+ ],
+ ],
+ _BooleanEntry(
+ label: t.settingsTab.general.animations,
+ value: vm.settings.enableAnimations,
+ onChanged: (b) async {
+ await ref.notifier(settingsProvider).setEnableAnimations(b);
+ },
+ ),
+ ],
+ ),
+ _SettingsSection(
+ title: t.settingsTab.receive.title,
+ children: [
+ _BooleanEntry(
+ label: t.settingsTab.receive.quickSave,
+ value: vm.settings.quickSave,
+ onChanged: (b) async {
+ final old = vm.settings.quickSave;
+ await ref.notifier(settingsProvider).setQuickSave(b);
+ if (!old && b && context.mounted) {
+ await QuickSaveNotice.open(context);
+ }
+ },
+ ),
+ _BooleanEntry(
+ label: t.settingsTab.receive.quickSaveFromFavorites,
+ value: vm.settings.quickSaveFromFavorites,
+ onChanged: (b) async {
+ final old = vm.settings.quickSaveFromFavorites;
+ await ref.notifier(settingsProvider).setQuickSaveFromFavorites(b);
+ if (!old && b && context.mounted) {
+ await QuickSaveFromFavoritesNotice.open(context);
+ }
+ },
+ ),
+ _BooleanEntry(
+ label: t.settingsTab.receive.requirePin,
+ value: vm.settings.receivePin != null,
+ onChanged: (b) async {
+ final currentPIN = vm.settings.receivePin;
+ if (currentPIN != null) {
+ await ref.notifier(settingsProvider).setReceivePin(null);
+ } else {
+ final String? newPin = await showDialog(
+ context: context,
+ builder: (_) => const PinDialog(
+ obscureText: false,
+ generateRandom: false,
),
- ),
- _SettingsEntry(
- label: t.settingsTab.network.alias,
- child: TextFieldWithActions(
- name: t.settingsTab.network.alias,
- controller: vm.aliasController,
- onChanged: (s) async {
- await ref.notifier(settingsProvider).setAlias(s);
- },
- actions: [
- Tooltip(
- message: t.settingsTab.network.generateRandomAlias,
- child: IconButton(
- onPressed: () async {
- // Generates random alias
- final newAlias = generateRandomAlias();
+ );
- // Update the TextField with the new alias
- vm.aliasController.text = newAlias;
+ if (newPin != null && newPin.isNotEmpty) {
+ await ref.notifier(settingsProvider).setReceivePin(newPin);
+ }
+ }
- // Persist the new alias using the settingsProvider
- await ref.notifier(settingsProvider).setAlias(newAlias);
- },
- icon: const Icon(Icons.casino),
- ),
- ),
- Tooltip(
- message: t.settingsTab.network.useSystemName,
- child: IconButton(
- onPressed: () async {
- final String newAlias;
- if (Platform.isMacOS) {
- final result = await Process.run('scutil', ['--get', 'ComputerName']);
- newAlias = result.stdout.toString().trim();
- } else {
- newAlias = Platform.localHostname;
- }
-
- vm.aliasController.text = newAlias;
- await ref.notifier(settingsProvider).setAlias(newAlias);
- },
- icon: const Icon(Icons.desktop_windows_rounded),
- ),
- ),
- ],
- ),
- ),
- if (vm.advanced)
- _SettingsEntry(
- label: t.settingsTab.network.deviceType,
- child: CustomDropdownButton(
- value: vm.deviceInfo.deviceType,
- items: DeviceType.values.map((type) {
- return DropdownMenuItem(
- value: type,
- alignment: Alignment.center,
- child: Icon(type.icon),
- );
- }).toList(),
- onChanged: (type) async {
- await ref.notifier(settingsProvider).setDeviceType(type);
- },
- ),
- ),
- if (vm.advanced)
- _SettingsEntry(
- label: t.settingsTab.network.deviceModel,
- child: TextFieldTv(
- name: t.settingsTab.network.deviceModel,
- controller: vm.deviceModelController,
- onChanged: (s) async {
- await ref.notifier(settingsProvider).setDeviceModel(s);
- },
- ),
- ),
- if (vm.advanced)
- _SettingsEntry(
- label: t.settingsTab.network.port,
- child: TextFieldTv(
- name: t.settingsTab.network.port,
- controller: vm.portController,
- onChanged: (s) async {
- final port = int.tryParse(s);
- if (port != null) {
- await ref.notifier(settingsProvider).setPort(port);
- }
- },
- ),
- ),
- if (vm.advanced)
- _ButtonEntry(
- label: t.settingsTab.network.network,
- buttonLabel: switch (vm.settings.networkWhitelist != null || vm.settings.networkBlacklist != null) {
- true => t.settingsTab.network.networkOptions.filtered,
- false => t.settingsTab.network.networkOptions.all,
- },
- onTap: () async {
- await context.push(() => const NetworkInterfacesPage());
- },
- ),
- if (vm.advanced)
- _SettingsEntry(
- label: t.settingsTab.network.discoveryTimeout,
- child: TextFieldTv(
- name: t.settingsTab.network.discoveryTimeout,
- controller: vm.timeoutController,
- onChanged: (s) async {
- final timeout = int.tryParse(s);
- if (timeout != null) {
- await ref.notifier(settingsProvider).setDiscoveryTimeout(timeout);
- }
- },
- ),
- ),
- if (vm.advanced)
- _BooleanEntry(
- label: t.settingsTab.network.encryption,
- value: vm.settings.https,
- onChanged: (b) async {
- final old = vm.settings.https;
- await ref.notifier(settingsProvider).setHttps(b);
- if (old && !b && context.mounted) {
- await EncryptionDisabledNotice.open(context);
- }
- },
- ),
- if (vm.advanced)
- _SettingsEntry(
- label: t.settingsTab.network.multicastGroup,
- child: TextFieldTv(
- name: t.settingsTab.network.multicastGroup,
- controller: vm.multicastController,
- onChanged: (s) async {
- await ref.notifier(settingsProvider).setMulticastGroup(s);
- },
- ),
- ),
- AnimatedCrossFade(
- crossFadeState: vm.settings.port != defaultPort ? CrossFadeState.showSecond : CrossFadeState.showFirst,
- duration: const Duration(milliseconds: 200),
- alignment: Alignment.topLeft,
- firstChild: Container(),
- secondChild: Padding(
- padding: const EdgeInsets.only(bottom: 15),
- child: Text(
- t.settingsTab.network.portWarning(defaultPort: defaultPort),
- style: const TextStyle(color: Colors.grey),
- ),
- ),
- ),
- AnimatedCrossFade(
- crossFadeState: vm.settings.multicastGroup != defaultMulticastGroup ? CrossFadeState.showSecond : CrossFadeState.showFirst,
- duration: const Duration(milliseconds: 200),
- alignment: Alignment.topLeft,
- firstChild: Container(),
- secondChild: Padding(
- padding: const EdgeInsets.only(bottom: 15),
- child: Text(
- t.settingsTab.network.multicastGroupWarning(defaultMulticast: defaultMulticastGroup),
- style: const TextStyle(color: Colors.grey),
- ),
- ),
- ),
- ],
- ),
- _SettingsSection(
- title: t.settingsTab.other.title,
- padding: const EdgeInsets.only(bottom: 0),
- children: [
- _ButtonEntry(
- label: t.aboutPage.title,
- buttonLabel: t.general.open,
- onTap: () async {
- await context.push(() => const AboutPage());
- },
- ),
- _ButtonEntry(
- label: t.settingsTab.other.support,
- buttonLabel: t.settingsTab.other.donate,
- onTap: () async {
- await context.push(() => const DonationPage());
- },
- ),
- _ButtonEntry(
- label: t.settingsTab.other.privacyPolicy,
- buttonLabel: t.general.open,
- onTap: () async {
- await launchUrl(
- Uri.parse('https://localsend.org/privacy'),
- mode: LaunchMode.externalApplication,
- );
- },
- ),
- if (checkPlatform([TargetPlatform.iOS, TargetPlatform.macOS]))
- _ButtonEntry(
- label: t.settingsTab.other.termsOfUse,
- buttonLabel: t.general.open,
- onTap: () async {
- await launchUrl(
- Uri.parse('https://www.apple.com/legal/internet-services/itunes/dev/stdeula/'),
- mode: LaunchMode.externalApplication,
- );
- },
- ),
- ],
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- LabeledCheckbox(
- label: t.settingsTab.advancedSettings,
- value: vm.advanced,
- labelFirst: true,
- onChanged: (b) async {
- vm.onTapAdvanced(b == true);
- await ref.notifier(settingsProvider).setAdvancedSettingsEnabled(b == true);
- },
- ),
- const SizedBox(width: 10),
- ],
- ),
- const SizedBox(height: 20),
- const LocalSendLogo(withText: true),
- const SizedBox(height: 5),
- ref
- .watch(versionProvider)
- .maybeWhen(
- data: (version) => Text(
- 'Version: $version',
- textAlign: TextAlign.center,
- ),
- orElse: () => Container(),
- ),
- Text(
- '© ${DateTime.now().year} Tien Do Nam',
- textAlign: TextAlign.center,
- ),
- Center(
- child: TextButton.icon(
+ // The pin is enforced by the Rust server, so it needs a restart.
+ if (ref.read(serverProvider) != null) {
+ await ref.notifier(serverProvider).restartServerFromSettings();
+ }
+ },
+ ),
+ if (checkPlatformWithFileSystem())
+ _SettingsEntry(
+ label: t.settingsTab.receive.destination,
+ 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 context.push(() => const ChangelogPage());
+ if (vm.settings.destination != null) {
+ await ref.notifier(settingsProvider).setDestination(null);
+ if (defaultTargetPlatform == TargetPlatform.macOS) {
+ await removeExistingDestinationAccess();
+ }
+ return;
+ }
+
+ final directory = await pickDirectoryPath();
+ if (directory != null) {
+ if (defaultTargetPlatform == TargetPlatform.macOS) {
+ await persistDestinationFolderAccess(directory);
+ }
+ await ref.notifier(settingsProvider).setDestination(directory);
+ }
},
- icon: const Icon(Icons.history),
- label: Text(t.changelogPage.title),
- ),
- ),
- const SizedBox(height: 80),
- ],
- ),
- ),
- // a pseudo appbar that is draggable for the settings page
- SizedBox(
- height: 50 + MediaQuery.of(context).padding.top,
- child: ClipRRect(
- child: BackdropFilter(
- filter: ImageFilter.blur(
- sigmaX: 20.0,
- sigmaY: 20.0,
- ),
- child: MoveWindow(
- child: SafeArea(
- child: Container(
- alignment: Alignment.center,
- child: Padding(
- padding: const EdgeInsets.only(left: 8),
- child: Text(t.settingsTab.title, style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center),
- ),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 5),
+ child: Text(vm.settings.destination ?? t.settingsTab.receive.downloads, style: Theme.of(context).textTheme.titleMedium),
),
),
),
+ if (checkPlatformWithGallery())
+ _BooleanEntry(
+ label: t.settingsTab.receive.saveToGallery,
+ value: vm.settings.saveToGallery,
+ onChanged: (b) async {
+ await ref.notifier(settingsProvider).setSaveToGallery(b);
+ },
+ ),
+ _BooleanEntry(
+ label: t.settingsTab.receive.autoFinish,
+ value: vm.settings.autoFinish,
+ onChanged: (b) async {
+ await ref.notifier(settingsProvider).setAutoFinish(b);
+ },
),
+ _BooleanEntry(
+ label: t.settingsTab.receive.saveToHistory,
+ value: vm.settings.saveToHistory,
+ onChanged: (b) async {
+ await ref.notifier(settingsProvider).setSaveToHistory(b);
+ },
+ ),
+ ],
+ ),
+ if (vm.advanced)
+ _SettingsSection(
+ title: t.settingsTab.send.title,
+ children: [
+ _BooleanEntry(
+ label: t.settingsTab.send.shareViaLinkAutoAccept,
+ value: vm.settings.shareViaLinkAutoAccept,
+ onChanged: (b) async {
+ await ref.notifier(settingsProvider).setShareViaLinkAutoAccept(b);
+ },
+ ),
+ ],
+ ),
+ _SettingsSection(
+ title: t.settingsTab.network.title,
+ children: [
+ AnimatedCrossFade(
+ crossFadeState:
+ vm.serverState != null &&
+ (vm.serverState!.alias != vm.settings.alias ||
+ vm.serverState!.port != vm.settings.port ||
+ vm.serverState!.https != vm.settings.https)
+ ? CrossFadeState.showSecond
+ : CrossFadeState.showFirst,
+ duration: const Duration(milliseconds: 200),
+ alignment: Alignment.topLeft,
+ firstChild: Container(),
+ secondChild: Padding(
+ padding: const EdgeInsets.only(bottom: 15),
+ child: Text(t.settingsTab.network.needRestart, style: TextStyle(color: Theme.of(context).colorScheme.warning)),
+ ),
+ ),
+ _SettingsEntry(
+ label: '${t.settingsTab.network.server}${vm.serverState == null ? ' (${t.general.offline})' : ''}',
+ child: DecoratedBox(
+ decoration: BoxDecoration(
+ color: Theme.of(context).inputDecorationTheme.fillColor,
+ borderRadius: Theme.of(context).inputDecorationTheme.borderRadius,
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ if (vm.serverState == null)
+ Tooltip(
+ message: t.general.start,
+ child: TextButton(
+ style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.onSurface),
+ onPressed: () => vm.onTapStartServer(context),
+ child: const Icon(Icons.play_arrow),
+ ),
+ )
+ else
+ Tooltip(
+ message: t.general.restart,
+ child: TextButton(
+ style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.onSurface),
+ onPressed: () => vm.onTapRestartServer(context),
+ child: const Icon(Icons.refresh),
+ ),
+ ),
+ Tooltip(
+ message: t.general.stop,
+ child: TextButton(
+ style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.onSurface),
+ onPressed: vm.serverState == null ? null : vm.onTapStopServer,
+ child: const Icon(Icons.stop),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ _SettingsEntry(
+ label: t.settingsTab.network.alias,
+ child: TextFieldWithActions(
+ name: t.settingsTab.network.alias,
+ controller: vm.aliasController,
+ onChanged: (s) async {
+ await ref.notifier(settingsProvider).setAlias(s);
+ },
+ actions: [
+ Tooltip(
+ message: t.settingsTab.network.generateRandomAlias,
+ child: IconButton(
+ onPressed: () async {
+ // Generates random alias
+ final newAlias = generateRandomAlias();
+
+ // Update the TextField with the new alias
+ vm.aliasController.text = newAlias;
+
+ // Persist the new alias using the settingsProvider
+ await ref.notifier(settingsProvider).setAlias(newAlias);
+ },
+ icon: const Icon(Icons.casino),
+ ),
+ ),
+ Tooltip(
+ message: t.settingsTab.network.useSystemName,
+ child: IconButton(
+ onPressed: () async {
+ final String newAlias;
+ if (Platform.isMacOS) {
+ final result = await Process.run('scutil', ['--get', 'ComputerName']);
+ newAlias = result.stdout.toString().trim();
+ } else {
+ newAlias = Platform.localHostname;
+ }
+
+ vm.aliasController.text = newAlias;
+ await ref.notifier(settingsProvider).setAlias(newAlias);
+ },
+ icon: const Icon(Icons.desktop_windows_rounded),
+ ),
+ ),
+ ],
+ ),
+ ),
+ if (vm.advanced)
+ _SettingsEntry(
+ label: t.settingsTab.network.deviceType,
+ child: CustomDropdownButton(
+ value: vm.deviceInfo.deviceType,
+ items: DeviceType.values.map((type) {
+ return DropdownMenuItem(
+ value: type,
+ alignment: Alignment.center,
+ child: Icon(type.icon),
+ );
+ }).toList(),
+ onChanged: (type) async {
+ await ref.notifier(settingsProvider).setDeviceType(type);
+ },
+ ),
+ ),
+ if (vm.advanced)
+ _SettingsEntry(
+ label: t.settingsTab.network.deviceModel,
+ child: TextFieldTv(
+ name: t.settingsTab.network.deviceModel,
+ controller: vm.deviceModelController,
+ onChanged: (s) async {
+ await ref.notifier(settingsProvider).setDeviceModel(s);
+ },
+ ),
+ ),
+ if (vm.advanced)
+ _SettingsEntry(
+ label: t.settingsTab.network.port,
+ child: TextFieldTv(
+ name: t.settingsTab.network.port,
+ controller: vm.portController,
+ onChanged: (s) async {
+ final port = int.tryParse(s);
+ if (port != null) {
+ await ref.notifier(settingsProvider).setPort(port);
+ }
+ },
+ ),
+ ),
+ if (vm.advanced)
+ _ButtonEntry(
+ label: t.settingsTab.network.network,
+ buttonLabel: switch (vm.settings.networkWhitelist != null || vm.settings.networkBlacklist != null) {
+ true => t.settingsTab.network.networkOptions.filtered,
+ false => t.settingsTab.network.networkOptions.all,
+ },
+ onTap: () async {
+ await context.push(() => const NetworkInterfacesPage());
+ },
+ ),
+ if (vm.advanced)
+ _SettingsEntry(
+ label: t.settingsTab.network.discoveryTimeout,
+ child: TextFieldTv(
+ name: t.settingsTab.network.discoveryTimeout,
+ controller: vm.timeoutController,
+ onChanged: (s) async {
+ final timeout = int.tryParse(s);
+ if (timeout != null) {
+ await ref.notifier(settingsProvider).setDiscoveryTimeout(timeout);
+ }
+ },
+ ),
+ ),
+ if (vm.advanced)
+ _BooleanEntry(
+ label: t.settingsTab.network.encryption,
+ value: vm.settings.https,
+ onChanged: (b) async {
+ final old = vm.settings.https;
+ await ref.notifier(settingsProvider).setHttps(b);
+ if (old && !b && context.mounted) {
+ await EncryptionDisabledNotice.open(context);
+ }
+ },
+ ),
+ if (vm.advanced)
+ _SettingsEntry(
+ label: t.settingsTab.network.multicastGroup,
+ child: TextFieldTv(
+ name: t.settingsTab.network.multicastGroup,
+ controller: vm.multicastController,
+ onChanged: (s) async {
+ await ref.notifier(settingsProvider).setMulticastGroup(s);
+ },
+ ),
+ ),
+ AnimatedCrossFade(
+ crossFadeState: vm.settings.port != defaultPort ? CrossFadeState.showSecond : CrossFadeState.showFirst,
+ duration: const Duration(milliseconds: 200),
+ alignment: Alignment.topLeft,
+ firstChild: Container(),
+ secondChild: Padding(
+ padding: const EdgeInsets.only(bottom: 15),
+ child: Text(
+ t.settingsTab.network.portWarning(defaultPort: defaultPort),
+ style: const TextStyle(color: Colors.grey),
+ ),
+ ),
+ ),
+ AnimatedCrossFade(
+ crossFadeState: vm.settings.multicastGroup != defaultMulticastGroup ? CrossFadeState.showSecond : CrossFadeState.showFirst,
+ duration: const Duration(milliseconds: 200),
+ alignment: Alignment.topLeft,
+ firstChild: Container(),
+ secondChild: Padding(
+ padding: const EdgeInsets.only(bottom: 15),
+ child: Text(
+ t.settingsTab.network.multicastGroupWarning(defaultMulticast: defaultMulticastGroup),
+ style: const TextStyle(color: Colors.grey),
+ ),
+ ),
+ ),
+ ],
+ ),
+ _SettingsSection(
+ title: t.settingsTab.other.title,
+ padding: const EdgeInsets.only(bottom: 0),
+ children: [
+ _ButtonEntry(
+ label: t.aboutPage.title,
+ buttonLabel: t.general.open,
+ onTap: () async {
+ await context.push(() => const AboutPage());
+ },
+ ),
+ _ButtonEntry(
+ label: t.settingsTab.other.support,
+ buttonLabel: t.settingsTab.other.donate,
+ onTap: () async {
+ await context.push(() => const DonationPage());
+ },
+ ),
+ _ButtonEntry(
+ label: t.settingsTab.other.privacyPolicy,
+ buttonLabel: t.general.open,
+ onTap: () async {
+ await launchUrl(
+ Uri.parse('https://localsend.org/privacy'),
+ mode: LaunchMode.externalApplication,
+ );
+ },
+ ),
+ if (checkPlatform([TargetPlatform.iOS, TargetPlatform.macOS]))
+ _ButtonEntry(
+ label: t.settingsTab.other.termsOfUse,
+ buttonLabel: t.general.open,
+ onTap: () async {
+ await launchUrl(
+ Uri.parse('https://www.apple.com/legal/internet-services/itunes/dev/stdeula/'),
+ mode: LaunchMode.externalApplication,
+ );
+ },
+ ),
+ ],
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ LabeledCheckbox(
+ label: t.settingsTab.advancedSettings,
+ value: vm.advanced,
+ labelFirst: true,
+ onChanged: (b) async {
+ vm.onTapAdvanced(b == true);
+ await ref.notifier(settingsProvider).setAdvancedSettingsEnabled(b == true);
+ },
+ ),
+ const SizedBox(width: 10),
+ ],
+ ),
+ const SizedBox(height: 20),
+ const LocalSendLogo(withText: true),
+ const SizedBox(height: 5),
+ ref
+ .watch(versionProvider)
+ .maybeWhen(
+ data: (version) => Text(
+ 'Version: $version',
+ textAlign: TextAlign.center,
+ ),
+ orElse: () => Container(),
+ ),
+ Text(
+ '© ${DateTime.now().year} Tien Do Nam',
+ textAlign: TextAlign.center,
+ ),
+ Center(
+ child: TextButton.icon(
+ style: TextButton.styleFrom(
+ foregroundColor: Theme.of(context).colorScheme.onSurface,
+ ),
+ onPressed: () async {
+ await context.push(() => const ChangelogPage());
+ },
+ icon: const Icon(Icons.history),
+ label: Text(t.changelogPage.title),
),
),
+ const SizedBox(height: 80),
],
);
},
diff --git a/app/lib/pages/troubleshoot_page.dart b/app/lib/pages/troubleshoot_page.dart
index 293dcbc6..0a49cd09 100644
--- a/app/lib/pages/troubleshoot_page.dart
+++ b/app/lib/pages/troubleshoot_page.dart
@@ -7,7 +7,6 @@ import 'package:localsend_app/provider/settings_provider.dart';
import 'package:localsend_app/util/native/cmd_helper.dart';
import 'package:localsend_app/util/native/macos_channel.dart' as macos_channel;
import 'package:localsend_app/util/native/platform_check.dart';
-import 'package:localsend_app/widget/custom_basic_appbar.dart';
import 'package:localsend_app/widget/custom_icon_button.dart';
import 'package:localsend_app/widget/dialogs/not_available_on_platform_dialog.dart';
import 'package:localsend_app/widget/responsive_list_view.dart';
@@ -20,7 +19,9 @@ class TroubleshootPage extends StatelessWidget {
Widget build(BuildContext context) {
final settings = context.ref.watch(settingsProvider);
return Scaffold(
- appBar: basicLocalSendAppbar(t.troubleshootPage.title),
+ appBar: AppBar(
+ title: Text(t.troubleshootPage.title),
+ ),
body: ResponsiveListView(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 30),
children: [
diff --git a/app/lib/pages/web_send_page.dart b/app/lib/pages/web_send_page.dart
index 2aac2fa8..f44da98d 100644
--- a/app/lib/pages/web_send_page.dart
+++ b/app/lib/pages/web_send_page.dart
@@ -8,7 +8,6 @@ import 'package:localsend_app/provider/network/server/server_provider.dart';
import 'package:localsend_app/provider/settings_provider.dart';
import 'package:localsend_app/util/native/platform_check.dart';
import 'package:localsend_app/util/ui/snackbar.dart';
-import 'package:localsend_app/widget/custom_basic_appbar.dart';
import 'package:localsend_app/widget/dialogs/pin_dialog.dart';
import 'package:localsend_app/widget/dialogs/qr_dialog.dart';
import 'package:localsend_app/widget/dialogs/zoom_dialog.dart';
@@ -98,7 +97,9 @@ class _WebSendPageState extends State with Refena {
},
canPop: false,
child: Scaffold(
- appBar: basicLocalSendAppbar(t.webSharePage.title),
+ appBar: AppBar(
+ title: Text(t.webSharePage.title),
+ ),
body: Builder(
builder: (context) {
if (_stateEnum != _ServerState.running) {
diff --git a/app/lib/util/native/tray_helper.dart b/app/lib/util/native/tray_helper.dart
index 43b8860c..0424907c 100644
--- a/app/lib/util/native/tray_helper.dart
+++ b/app/lib/util/native/tray_helper.dart
@@ -1,6 +1,5 @@
import 'dart:io';
-import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:flutter/foundation.dart';
import 'package:localsend_app/gen/assets.gen.dart';
import 'package:localsend_app/gen/strings.g.dart';
@@ -86,7 +85,6 @@ Future showFromTray() async {
// This will crash on Windows
// https://github.com/localsend/localsend/issues/32
await windowManager.setSkipTaskbar(false);
- appWindow.show();
}
// Enable animations
diff --git a/app/lib/widget/custom_basic_appbar.dart b/app/lib/widget/custom_basic_appbar.dart
deleted file mode 100644
index 2d1c7ec9..00000000
--- a/app/lib/widget/custom_basic_appbar.dart
+++ /dev/null
@@ -1,75 +0,0 @@
-import 'dart:io';
-import 'dart:ui';
-
-import 'package:bitsdojo_window/bitsdojo_window.dart';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-import 'package:localsend_app/util/native/platform_check.dart';
-
-class CustomBackButton extends StatelessWidget {
- final Color? color;
-
- const CustomBackButton({super.key, this.color});
-
- @override
- Widget build(BuildContext context) {
- final isRtl = Directionality.of(context) == TextDirection.rtl;
- return IconButton(
- icon: Icon(
- isRtl ? Icons.arrow_forward_ios_rounded : Icons.arrow_back_ios_new_rounded,
- color: color ?? IconTheme.of(context).color,
- ),
- tooltip: MaterialLocalizations.of(context).backButtonTooltip,
- onPressed: () async {
- await Navigator.maybePop(context);
- },
- );
- }
-}
-
-PreferredSizeWidget basicLocalSendAppbar(String title) {
- // Creates a very simple new appBar to support bitsdojo_windows on mac and make them draggable
- // if you want have more items on here for a specific page, make sure to add it here as an option
- // so that mac users can still appreciate this near native new design
- return checkPlatform([TargetPlatform.macOS])
- ? PreferredSize(
- preferredSize: const Size.fromHeight(kToolbarHeight),
- child: ClipRRect(
- child: BackdropFilter(
- filter: ImageFilter.blur(
- sigmaX: 20.0,
- sigmaY: 20.0,
- ),
- child: MoveWindow(
- child: Container(
- color: Colors.transparent,
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- // Padding space for macOS traffic lights
- if (!kIsWeb && Platform.isMacOS) const SizedBox(width: 60),
- // Originally leading Icon
- CustomBackButton(),
- // Center Title
- Expanded(
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: Center(
- child: FittedBox(
- fit: BoxFit.scaleDown,
- child: Text(title, style: TextStyle(fontSize: 100, fontWeight: FontWeight.normal)),
- ),
- ),
- ),
- ),
- // For true centering of the icon since it shifted
- const SizedBox(width: 60),
- ],
- ),
- ),
- ),
- ),
- ),
- )
- : AppBar(title: Text(title));
-}
diff --git a/app/linux/flutter/generated_plugin_registrant.cc b/app/linux/flutter/generated_plugin_registrant.cc
index 7ec0cb2b..d4c9863b 100644
--- a/app/linux/flutter/generated_plugin_registrant.cc
+++ b/app/linux/flutter/generated_plugin_registrant.cc
@@ -6,7 +6,6 @@
#include "generated_plugin_registrant.h"
-#include
#include
#include
#include
@@ -21,9 +20,6 @@
#include
void fl_register_plugins(FlPluginRegistry* registry) {
- g_autoptr(FlPluginRegistrar) bitsdojo_window_linux_registrar =
- fl_plugin_registry_get_registrar_for_plugin(registry, "BitsdojoWindowPlugin");
- bitsdojo_window_plugin_register_with_registrar(bitsdojo_window_linux_registrar);
g_autoptr(FlPluginRegistrar) desktop_drop_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin");
desktop_drop_plugin_register_with_registrar(desktop_drop_registrar);
diff --git a/app/linux/flutter/generated_plugins.cmake b/app/linux/flutter/generated_plugins.cmake
index e35f864a..cb61d44c 100644
--- a/app/linux/flutter/generated_plugins.cmake
+++ b/app/linux/flutter/generated_plugins.cmake
@@ -3,7 +3,6 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
- bitsdojo_window_linux
desktop_drop
dynamic_color
file_selector_linux
diff --git a/app/macos/Flutter/GeneratedPluginRegistrant.swift b/app/macos/Flutter/GeneratedPluginRegistrant.swift
index 697ca79f..e2c19611 100644
--- a/app/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/app/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,7 +5,6 @@
import FlutterMacOS
import Foundation
-import bitsdojo_window_macos
import connectivity_plus
import desktop_drop
import device_info_plus
@@ -29,7 +28,6 @@ import wakelock_plus
import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
- BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin"))
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
diff --git a/app/macos/Podfile.lock b/app/macos/Podfile.lock
index fe17c41a..e6a62d6d 100644
--- a/app/macos/Podfile.lock
+++ b/app/macos/Podfile.lock
@@ -1,6 +1,4 @@
PODS:
- - bitsdojo_window_macos (0.0.1):
- - FlutterMacOS
- connectivity_plus (0.0.1):
- FlutterMacOS
- Defaults (4.2.2)
@@ -54,7 +52,6 @@ PODS:
- FlutterMacOS
DEPENDENCIES:
- - bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`)
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`)
- Defaults (~> 4.2)
- desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`)
@@ -85,8 +82,6 @@ SPEC REPOS:
- Defaults
EXTERNAL SOURCES:
- bitsdojo_window_macos:
- :path: Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos
connectivity_plus:
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos
desktop_drop:
@@ -135,7 +130,6 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
SPEC CHECKSUMS:
- bitsdojo_window_macos: 7959fb0ca65a3ccda30095c181ecb856fae48ea9
connectivity_plus: 4adf20a405e25b42b9c9f87feff8f4b6fde18a4e
Defaults: d785e56c0fb8890dc40351603f05c8e1df1bdd45
desktop_drop: 10a3e6a7fa9dbe350541f2574092fecfa345a07b
diff --git a/app/macos/Runner/AppDelegate.swift b/app/macos/Runner/AppDelegate.swift
index 35591ac9..49f2d859 100644
--- a/app/macos/Runner/AppDelegate.swift
+++ b/app/macos/Runner/AppDelegate.swift
@@ -3,7 +3,6 @@ import FlutterMacOS
import Defaults
import DockProgress
import LaunchAtLogin
-import bitsdojo_window_macos
enum DockIcon: CaseIterable {
case regular
diff --git a/app/macos/Runner/MainFlutterWindow.swift b/app/macos/Runner/MainFlutterWindow.swift
index b7174531..c9700270 100644
--- a/app/macos/Runner/MainFlutterWindow.swift
+++ b/app/macos/Runner/MainFlutterWindow.swift
@@ -1,13 +1,8 @@
import Cocoa
import FlutterMacOS
import window_manager
-import bitsdojo_window_macos // used to make custom window bars on macOS (or any desktop operating system for that matter)
-class MainFlutterWindow: BitsdojoWindow {
- // just following intructions from https://pub.dev/packages/bitsdojo_window
- override func bitsdojo_window_configure() -> UInt {
- return BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP
- }
+class MainFlutterWindow: NSWindow {
override func awakeFromNib() {
let flutterViewController = FlutterViewController.init()
let windowFrame = self.frame
@@ -15,9 +10,13 @@ class MainFlutterWindow: BitsdojoWindow {
self.setFrame(windowFrame, display: true)
RegisterGeneratedPlugins(registry: flutterViewController)
- // window_manager: start window hidden
- hiddenWindowAtLaunch()
super.awakeFromNib()
}
+
+ // window_manager: start hidden
+ override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) {
+ super.order(place, relativeTo: otherWin)
+ hiddenWindowAtLaunch()
+ }
}
diff --git a/app/pubspec.lock b/app/pubspec.lock
index 2efe5dc7..c9a002a7 100644
--- a/app/pubspec.lock
+++ b/app/pubspec.lock
@@ -81,46 +81,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.8.2"
- bitsdojo_window:
- dependency: "direct main"
- description:
- name: bitsdojo_window
- sha256: "88ef7765dafe52d97d7a3684960fb5d003e3151e662c18645c1641c22b873195"
- url: "https://pub.dev"
- source: hosted
- version: "0.1.6"
- bitsdojo_window_linux:
- dependency: transitive
- description:
- name: bitsdojo_window_linux
- sha256: "9519c0614f98be733e0b1b7cb15b827007886f6fe36a4fb62cf3d35b9dd578ab"
- url: "https://pub.dev"
- source: hosted
- version: "0.1.4"
- bitsdojo_window_macos:
- dependency: transitive
- description:
- name: bitsdojo_window_macos
- sha256: f7c5be82e74568c68c5b8449e2c5d8fd12ec195ecd70745a7b9c0f802bb0268f
- url: "https://pub.dev"
- source: hosted
- version: "0.1.4"
- bitsdojo_window_platform_interface:
- dependency: transitive
- description:
- name: bitsdojo_window_platform_interface
- sha256: "65daa015a0c6dba749bdd35a0f092e7a8ba8b0766aa0480eb3ef808086f6e27c"
- url: "https://pub.dev"
- source: hosted
- version: "0.1.2"
- bitsdojo_window_windows:
- dependency: transitive
- description:
- name: bitsdojo_window_windows
- sha256: fa982cf61ede53f483e50b257344a1c250af231a3cdc93a7064dd6dc0d720b68
- url: "https://pub.dev"
- source: hosted
- version: "0.1.6"
boolean_selector:
dependency: transitive
description:
diff --git a/app/pubspec.yaml b/app/pubspec.yaml
index bd1546a2..36a2b098 100644
--- a/app/pubspec.yaml
+++ b/app/pubspec.yaml
@@ -11,7 +11,6 @@ environment:
dependencies:
basic_utils: 5.8.2
- bitsdojo_window: ^0.1.6
collection: ^1.17.2 # allow newer versions, so it can compile with newer Flutter versions
connectivity_plus: 7.2.0
convert: 3.1.2
diff --git a/app/windows/flutter/generated_plugin_registrant.cc b/app/windows/flutter/generated_plugin_registrant.cc
index 842cfae6..e1b56e62 100644
--- a/app/windows/flutter/generated_plugin_registrant.cc
+++ b/app/windows/flutter/generated_plugin_registrant.cc
@@ -6,7 +6,6 @@
#include "generated_plugin_registrant.h"
-#include
#include
#include
#include
@@ -23,8 +22,6 @@
#include
void RegisterPlugins(flutter::PluginRegistry* registry) {
- BitsdojoWindowPluginRegisterWithRegistrar(
- registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
DesktopDropPluginRegisterWithRegistrar(
diff --git a/app/windows/flutter/generated_plugins.cmake b/app/windows/flutter/generated_plugins.cmake
index d319a2e4..d9e680f0 100644
--- a/app/windows/flutter/generated_plugins.cmake
+++ b/app/windows/flutter/generated_plugins.cmake
@@ -3,7 +3,6 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
- bitsdojo_window_windows
connectivity_plus
desktop_drop
dynamic_color