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