From b5c705a1fef746fb1961c15e7419dc9f876a55a6 Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Fri, 18 Aug 2023 20:01:18 +0800 Subject: [PATCH] feat: no titlebar on macOS Fixes #136 --- lib/app.dart | 49 ++---- lib/locator.dart | 12 +- lib/main.dart | 153 ++++++++++-------- lib/view/page/backup.dart | 3 +- lib/view/page/convert.dart | 3 +- lib/view/page/debug.dart | 4 +- lib/view/page/docker.dart | 3 +- lib/view/page/editor.dart | 101 ++++++------ lib/view/page/home.dart | 61 ++++--- lib/view/page/pkg.dart | 3 +- lib/view/page/private_key/edit.dart | 3 +- lib/view/page/private_key/list.dart | 3 +- lib/view/page/process.dart | 3 +- lib/view/page/server/detail.dart | 3 +- lib/view/page/server/edit.dart | 3 +- lib/view/page/setting.dart | 3 +- lib/view/page/snippet/edit.dart | 3 +- lib/view/page/ssh/virt_key_setting.dart | 4 +- lib/view/page/storage/local.dart | 3 +- lib/view/page/storage/sftp.dart | 3 +- lib/view/page/storage/sftp_mission.dart | 8 +- lib/view/widget/custom_appbar.dart | 22 +++ macos/Flutter/GeneratedPluginRegistrant.swift | 2 + macos/Podfile | 2 +- macos/Podfile.lock | 12 +- macos/Runner.xcodeproj/project.pbxproj | 16 +- pubspec.lock | 8 + pubspec.yaml | 1 + 28 files changed, 290 insertions(+), 204 deletions(-) create mode 100644 lib/view/widget/custom_appbar.dart diff --git a/lib/app.dart b/lib/app.dart index 8f3c6545..69ac2cc6 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -28,8 +28,7 @@ class MyApp extends StatelessWidget { // Issue #57 // if not [ok] -> [AMOLED] mode, use [ThemeMode.dark] final themeMode = isAMOLED ? ThemeMode.values[tMode] : ThemeMode.dark; - final localeStr = _setting.locale.fetch(); - final locale = localeStr?.toLocale; + final locale = _setting.locale.fetch()?.toLocale; final darkTheme = ThemeData( useMaterial3: true, brightness: Brightness.dark, @@ -47,39 +46,25 @@ class MyApp extends StatelessWidget { useMaterial3: true, colorSchemeSeed: primaryColor, ), - darkTheme: isAMOLED - ? darkTheme - : darkTheme.copyWith( - scaffoldBackgroundColor: Colors.black, - dialogBackgroundColor: Colors.black, - drawerTheme: const DrawerThemeData( - backgroundColor: Colors.black, - ), - appBarTheme: const AppBarTheme( - backgroundColor: Colors.black, - ), - dialogTheme: const DialogTheme( - backgroundColor: Colors.black, - ), - bottomSheetTheme: const BottomSheetThemeData( - backgroundColor: Colors.black, - ), - listTileTheme: const ListTileThemeData( - tileColor: Colors.black12, - ), - cardTheme: const CardTheme( - color: Colors.black12, - ), - navigationBarTheme: const NavigationBarThemeData( - backgroundColor: Colors.black, - ), - popupMenuTheme: const PopupMenuThemeData( - color: Colors.black, - ), - ), + darkTheme: isAMOLED ? darkTheme : _getAmoledTheme(darkTheme), home: fullScreen ? const FullScreenPage() : const HomePage(), ); }, ); } } + +ThemeData _getAmoledTheme(ThemeData darkTheme) => darkTheme.copyWith( + scaffoldBackgroundColor: Colors.black, + dialogBackgroundColor: Colors.black, + drawerTheme: const DrawerThemeData(backgroundColor: Colors.black), + appBarTheme: const AppBarTheme(backgroundColor: Colors.black), + dialogTheme: const DialogTheme(backgroundColor: Colors.black), + bottomSheetTheme: + const BottomSheetThemeData(backgroundColor: Colors.black), + listTileTheme: const ListTileThemeData(tileColor: Colors.black12), + cardTheme: const CardTheme(color: Colors.black12), + navigationBarTheme: + const NavigationBarThemeData(backgroundColor: Colors.black), + popupMenuTheme: const PopupMenuThemeData(color: Colors.black), + ); diff --git a/lib/locator.dart b/lib/locator.dart index 278f5784..a6867b50 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -18,11 +18,11 @@ import 'data/store/snippet.dart'; GetIt locator = GetIt.instance; -void setupLocatorForServices() { +void _setupLocatorForServices() { locator.registerLazySingleton(() => AppService()); } -void setupLocatorForProviders() { +void _setupLocatorForProviders() { locator.registerSingleton(AppProvider()); locator.registerSingleton(PkgProvider()); locator.registerSingleton(DebugProvider()); @@ -34,7 +34,7 @@ void setupLocatorForProviders() { locator.registerSingleton(SftpProvider()); } -Future setupLocatorForStores() async { +Future _setupLocatorForStores() async { final setting = SettingStore(); await setting.init(boxName: 'setting'); locator.registerSingleton(setting); @@ -57,7 +57,7 @@ Future setupLocatorForStores() async { } Future setupLocator() async { - await setupLocatorForStores(); - setupLocatorForProviders(); - setupLocatorForServices(); + await _setupLocatorForStores(); + _setupLocatorForProviders(); + _setupLocatorForServices(); } diff --git a/lib/main.dart b/lib/main.dart index 587057a5..8b7afaa5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,17 +3,20 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:logging/logging.dart'; +import 'package:macos_window_utils/window_manipulator.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:toolbox/data/model/app/net_view.dart'; -import 'package:toolbox/data/model/ssh/virtual_key.dart'; +import 'package:toolbox/view/widget/custom_appbar.dart'; import 'app.dart'; import 'core/analysis.dart'; +import 'core/utils/platform.dart'; import 'core/utils/ui.dart'; +import 'data/model/app/net_view.dart'; import 'data/model/server/private_key_info.dart'; import 'data/model/server/server_private_info.dart'; import 'data/model/server/snippet.dart'; +import 'data/model/ssh/virtual_key.dart'; import 'data/provider/app.dart'; import 'data/provider/debug.dart'; import 'data/provider/docker.dart'; @@ -27,69 +30,10 @@ import 'data/store/setting.dart'; import 'locator.dart'; import 'view/widget/rebuild.dart'; -late final DebugProvider _debug; - -Future initApp() async { - await initHive(); - await setupLocator(); - - _debug = locator(); - locator().loadData(); - locator().loadData(); - - final settings = locator(); - await loadFontFile(settings.fontPath.fetch()); - - SharedPreferences.setPrefix(''); - - Logger.root.level = Level.ALL; - Logger.root.onRecord.listen((record) { - var str = '[${record.loggerName}][${record.level.name}]: ${record.message}'; - if (record.error != null) { - str += '\n${record.error}'; - _debug.addMultiline(record.error.toString(), Colors.red); - } - if (record.stackTrace != null) { - str += '\n${record.stackTrace}'; - _debug.addMultiline(record.stackTrace.toString(), Colors.white); - } - // ignore: avoid_print - print(str); - }); -} - -Future initHive() async { - await Hive.initFlutter(); - // 以 typeId 为顺序 - Hive.registerAdapter(PrivateKeyInfoAdapter()); - Hive.registerAdapter(SnippetAdapter()); - Hive.registerAdapter(ServerPrivateInfoAdapter()); - Hive.registerAdapter(VirtKeyAdapter()); - Hive.registerAdapter(NetViewTypeAdapter()); -} - -void runInZone(void Function() body) { - final zoneSpec = ZoneSpecification( - print: (Zone self, ZoneDelegate parent, Zone zone, String line) { - parent.print(zone, line); - // This is a hack to avoid - // `setState() or markNeedsBuild() called during build` - // error. - Future.delayed(const Duration(milliseconds: 1), () { - _debug.addText(line); - }); - }, - ); - - runZonedGuarded( - body, - (obj, trace) => Analysis.recordException(trace), - zoneSpecification: zoneSpec, - ); -} +DebugProvider? _debug; Future main() async { - runInZone(() async { + _runInZone(() async { await initApp(); runApp( MultiProvider( @@ -111,3 +55,86 @@ Future main() async { ); }); } + +void _runInZone(void Function() body) { + final zoneSpec = ZoneSpecification( + print: (Zone self, ZoneDelegate parent, Zone zone, String line) { + parent.print(zone, line); + // This is a hack to avoid + // `setState() or markNeedsBuild() called during build` + // error. + Future.delayed(const Duration(milliseconds: 1), () { + _debug?.addText(line); + }); + }, + ); + + runZonedGuarded( + body, + (obj, trace) => Analysis.recordException(trace), + zoneSpecification: zoneSpec, + ); +} + +Future initApp() async { + await _initMacOSWindow(); + + // Base of all data. + await _initHive(); + await setupLocator(); + + // Setup [DebugProvider] first to catch all logs. + _debug = locator(); + _setupLogger(); + _setupProviders(); + + // Load font + final settings = locator(); + loadFontFile(settings.fontPath.fetch()); + + // SharedPreferences is only used on Android for saving home widgets settings. + if (!isAndroid) return; + SharedPreferences.setPrefix(''); +} + +void _setupProviders() { + locator().loadData(); + locator().loadData(); +} + +Future _initHive() async { + await Hive.initFlutter(); + // 以 typeId 为顺序 + Hive.registerAdapter(PrivateKeyInfoAdapter()); + Hive.registerAdapter(SnippetAdapter()); + Hive.registerAdapter(ServerPrivateInfoAdapter()); + Hive.registerAdapter(VirtKeyAdapter()); + Hive.registerAdapter(NetViewTypeAdapter()); +} + +void _setupLogger() { + Logger.root.level = Level.ALL; + Logger.root.onRecord.listen((record) { + var str = '[${record.loggerName}][${record.level.name}]: ${record.message}'; + if (record.error != null) { + str += '\n${record.error}'; + _debug?.addMultiline(record.error.toString(), Colors.red); + } + if (record.stackTrace != null) { + str += '\n${record.stackTrace}'; + _debug?.addMultiline(record.stackTrace.toString(), Colors.white); + } + // ignore: avoid_print + print(str); + }); +} + +Future _initMacOSWindow() async { + if (!isMacOS) return; + WidgetsFlutterBinding.ensureInitialized(); + await WindowManipulator.initialize(); + WindowManipulator.makeTitlebarTransparent(); + WindowManipulator.enableFullSizeContentView(); + WindowManipulator.hideTitle(); + await CustomAppBar.updateTitlebarHeight(); +} diff --git a/lib/view/page/backup.dart b/lib/view/page/backup.dart index fda18366..7ce97dc1 100644 --- a/lib/view/page/backup.dart +++ b/lib/view/page/backup.dart @@ -19,6 +19,7 @@ import '../../data/store/private_key.dart'; import '../../data/store/server.dart'; import '../../data/store/snippet.dart'; import '../../locator.dart'; +import '../widget/custom_appbar.dart'; const backupFormatVersion = 1; @@ -34,7 +35,7 @@ class BackupPage extends StatelessWidget { Widget build(BuildContext context) { final s = S.of(context)!; return Scaffold( - appBar: AppBar( + appBar: CustomAppBar( title: Text(s.backupAndRestore, style: textSize18), ), body: _buildBody(context, s), diff --git a/lib/view/page/convert.dart b/lib/view/page/convert.dart index ca246ea1..592ac4e1 100644 --- a/lib/view/page/convert.dart +++ b/lib/view/page/convert.dart @@ -7,6 +7,7 @@ import 'package:toolbox/data/res/ui.dart'; import 'package:toolbox/view/widget/value_notifier.dart'; import '../../core/utils/ui.dart'; +import '../widget/custom_appbar.dart'; import '../widget/input_field.dart'; import '../widget/popup_menu.dart'; import '../widget/round_rect_card.dart'; @@ -52,7 +53,7 @@ class _ConvertPageState extends State Widget build(BuildContext context) { super.build(context); return Scaffold( - appBar: AppBar( + appBar: CustomAppBar( title: Text(_s.convert), ), body: SingleChildScrollView( diff --git a/lib/view/page/debug.dart b/lib/view/page/debug.dart index 635b8f70..506eff41 100644 --- a/lib/view/page/debug.dart +++ b/lib/view/page/debug.dart @@ -3,6 +3,8 @@ import 'package:provider/provider.dart'; import 'package:toolbox/core/extension/navigator.dart'; import 'package:toolbox/data/provider/debug.dart'; +import '../widget/custom_appbar.dart'; + class DebugPage extends StatefulWidget { const DebugPage({Key? key}) : super(key: key); @@ -14,7 +16,7 @@ class _DebugPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( + appBar: CustomAppBar( leading: IconButton( onPressed: () => context.pop(), icon: const Icon(Icons.arrow_back, color: Colors.white), diff --git a/lib/view/page/docker.dart b/lib/view/page/docker.dart index 857067b4..c5d8b9af 100644 --- a/lib/view/page/docker.dart +++ b/lib/view/page/docker.dart @@ -18,6 +18,7 @@ import '../../data/res/ui.dart'; import '../../data/res/url.dart'; import '../../data/store/docker.dart'; import '../../locator.dart'; +import '../widget/custom_appbar.dart'; import '../widget/popup_menu.dart'; import '../widget/round_rect_card.dart'; import '../widget/two_line_text.dart'; @@ -64,7 +65,7 @@ class _DockerManagePageState extends State { Widget build(BuildContext context) { return Consumer(builder: (_, ___, __) { return Scaffold( - appBar: AppBar( + appBar: CustomAppBar( centerTitle: true, title: TwoLineText(up: 'Docker', down: widget.spi.name), actions: [ diff --git a/lib/view/page/editor.dart b/lib/view/page/editor.dart index 552ae660..48e26104 100644 --- a/lib/view/page/editor.dart +++ b/lib/view/page/editor.dart @@ -15,6 +15,7 @@ import 'package:toolbox/data/res/highlight.dart'; import 'package:toolbox/data/store/setting.dart'; import 'package:toolbox/locator.dart'; +import '../widget/custom_appbar.dart'; import '../widget/two_line_text.dart'; class EditorPage extends StatefulWidget { @@ -68,55 +69,9 @@ class _EditorPageState extends State with AfterLayoutMixin { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: () { - if (_codeTheme != null) { - return _codeTheme!['root']!.backgroundColor; - } - return null; - }(), - appBar: AppBar( - centerTitle: true, - title: TwoLineText(up: getFileName(widget.path) ?? '', down: _s.editor), - actions: [ - PopupMenuButton( - icon: const Icon(Icons.language), - onSelected: (value) { - _controller.language = suffix2HighlightMap[value]; - _langCode = value; - }, - initialValue: _langCode, - itemBuilder: (BuildContext context) { - return suffix2HighlightMap.keys.map((e) { - return PopupMenuItem( - value: e, - child: Text(e), - ); - }).toList(); - }, - ) - ], - ), - body: Visibility( - visible: (_codeTheme != null), - replacement: const Center( - child: CircularProgressIndicator(), - ), - child: SingleChildScrollView( - child: CodeTheme( - data: CodeThemeData( - styles: _codeTheme ?? - (isDarkMode(context) ? monokaiTheme : a11yLightTheme)), - child: CodeField( - focusNode: _focusNode, - controller: _controller, - lineNumberStyle: const LineNumberStyle( - width: 47, - margin: 7, - ), - ), - ), - ), - ), + backgroundColor: _codeTheme?['root']?.backgroundColor, + appBar: _buildAppBar(), + body: _buildBody(), floatingActionButton: FloatingActionButton( child: const Icon(Icons.done), onPressed: () { @@ -126,6 +81,54 @@ class _EditorPageState extends State with AfterLayoutMixin { ); } + PreferredSizeWidget _buildAppBar() { + return CustomAppBar( + title: TwoLineText(up: getFileName(widget.path) ?? '', down: _s.editor), + actions: [ + PopupMenuButton( + icon: const Icon(Icons.language), + onSelected: (value) { + _controller.language = suffix2HighlightMap[value]; + _langCode = value; + }, + initialValue: _langCode, + itemBuilder: (BuildContext context) { + return suffix2HighlightMap.keys.map((e) { + return PopupMenuItem( + value: e, + child: Text(e), + ); + }).toList(); + }, + ) + ], + ); + } + + Widget _buildBody() { + return Visibility( + visible: _codeTheme != null, + replacement: const Center( + child: CircularProgressIndicator(), + ), + child: SingleChildScrollView( + child: CodeTheme( + data: CodeThemeData( + styles: _codeTheme ?? + (isDarkMode(context) ? monokaiTheme : a11yLightTheme)), + child: CodeField( + focusNode: _focusNode, + controller: _controller, + lineNumberStyle: const LineNumberStyle( + width: 47, + margin: 7, + ), + ), + ), + ), + ); + } + @override FutureOr afterFirstLayout(BuildContext context) async { if (widget.path != null) { diff --git a/lib/view/page/home.dart b/lib/view/page/home.dart index 7e9cfc69..0909b520 100644 --- a/lib/view/page/home.dart +++ b/lib/view/page/home.dart @@ -20,6 +20,7 @@ import '../../data/res/ui.dart'; import '../../data/res/url.dart'; import '../../data/store/setting.dart'; import '../../locator.dart'; +import '../widget/custom_appbar.dart'; import '../widget/url_text.dart'; import 'backup.dart'; import 'convert.dart'; @@ -109,31 +110,10 @@ class _HomePageState extends State @override Widget build(BuildContext context) { super.build(context); - final actions = [ - IconButton( - icon: const Icon(Icons.developer_mode, size: 23), - tooltip: _s.debug, - onPressed: () => AppRoute( - const DebugPage(), - 'Debug Page', - ).go(context), - ), - ]; - if (isDesktop && _selectIndex.value == AppTab.server.index) { - actions.add( - IconButton( - icon: const Icon(Icons.refresh, size: 23), - tooltip: 'Refresh', - onPressed: () => _serverProvider.refreshData(), - ), - ); - } + return Scaffold( drawer: _buildDrawer(), - appBar: AppBar( - title: const Text(BuildData.name), - actions: actions, - ), + appBar: _buildAppBar(), body: PageView.builder( controller: _pageController, itemCount: AppTab.values.length, @@ -151,9 +131,44 @@ class _HomePageState extends State ); } + PreferredSizeWidget _buildAppBar() { + final actions = [ + IconButton( + icon: const Icon(Icons.developer_mode, size: 23), + tooltip: _s.debug, + onPressed: () => AppRoute( + const DebugPage(), + 'Debug Page', + ).go(context), + ), + ]; + if (isDesktop && _selectIndex.value == AppTab.server.index) { + actions.add( + ValueBuilder( + listenable: _selectIndex, + build: () { + if (_selectIndex.value != AppTab.server.index) { + return const SizedBox(); + } + return IconButton( + icon: const Icon(Icons.refresh, size: 23), + tooltip: 'Refresh', + onPressed: () => _serverProvider.refreshData(), + ); + }, + ), + ); + } + return CustomAppBar( + title: const Text(BuildData.name), + actions: actions, + ); + } + Widget _buildBottomBar() { return NavigationBar( selectedIndex: _selectIndex.value, + height: kBottomNavigationBarHeight * 1.2, animationDuration: const Duration(milliseconds: 250), onDestinationSelected: (int index) { if (_selectIndex.value == index) return; diff --git a/lib/view/page/pkg.dart b/lib/view/page/pkg.dart index 5c67c798..2e79b75d 100644 --- a/lib/view/page/pkg.dart +++ b/lib/view/page/pkg.dart @@ -12,6 +12,7 @@ import '../../data/provider/pkg.dart'; import '../../data/provider/server.dart'; import '../../data/res/ui.dart'; import '../../locator.dart'; +import '../widget/custom_appbar.dart'; import '../widget/round_rect_card.dart'; import '../widget/two_line_text.dart'; @@ -72,7 +73,7 @@ class _PkgManagePageState extends State Widget build(BuildContext context) { return Consumer(builder: (_, pkg, __) { return Scaffold( - appBar: AppBar( + appBar: CustomAppBar( centerTitle: true, title: TwoLineText(up: _s.pkg, down: widget.spi.name), ), diff --git a/lib/view/page/private_key/edit.dart b/lib/view/page/private_key/edit.dart index ad048e96..09d83d12 100644 --- a/lib/view/page/private_key/edit.dart +++ b/lib/view/page/private_key/edit.dart @@ -17,6 +17,7 @@ import '../../../data/model/server/private_key_info.dart'; import '../../../data/provider/private_key.dart'; import '../../../data/res/ui.dart'; import '../../../locator.dart'; +import '../../widget/custom_appbar.dart'; const _format = 'text/plain'; @@ -89,7 +90,7 @@ class _PrivateKeyEditPageState extends State }, icon: const Icon(Icons.delete)) ]; - return AppBar( + return CustomAppBar( title: Text(_s.edit, style: textSize18), actions: actions, ); diff --git a/lib/view/page/private_key/list.dart b/lib/view/page/private_key/list.dart index 91c8e37e..669c7c1d 100644 --- a/lib/view/page/private_key/list.dart +++ b/lib/view/page/private_key/list.dart @@ -14,6 +14,7 @@ import '../../../core/utils/platform.dart'; import '../../../data/model/server/private_key_info.dart'; import '../../../data/provider/private_key.dart'; import '../../../data/res/ui.dart'; +import '../../widget/custom_appbar.dart'; import 'edit.dart'; import '../../../view/widget/round_rect_card.dart'; @@ -37,7 +38,7 @@ class _PrivateKeyListState extends State @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( + appBar: CustomAppBar( title: Text(_s.privateKey, style: textSize18), ), body: _buildBody(), diff --git a/lib/view/page/process.dart b/lib/view/page/process.dart index 009249b7..bb64c9bf 100644 --- a/lib/view/page/process.dart +++ b/lib/view/page/process.dart @@ -15,6 +15,7 @@ import 'package:toolbox/view/widget/two_line_text.dart'; import '../../data/provider/server.dart'; import '../../locator.dart'; +import '../widget/custom_appbar.dart'; class ProcessPage extends StatefulWidget { final ServerPrivateInfo spi; @@ -124,7 +125,7 @@ class _ProcessPageState extends State { ); } return Scaffold( - appBar: AppBar( + appBar: CustomAppBar( centerTitle: true, title: TwoLineText(up: widget.spi.name, down: _s.process), actions: actions, diff --git a/lib/view/page/server/detail.dart b/lib/view/page/server/detail.dart index bcf85411..75023589 100644 --- a/lib/view/page/server/detail.dart +++ b/lib/view/page/server/detail.dart @@ -14,6 +14,7 @@ import '../../../data/res/default.dart'; import '../../../data/res/ui.dart'; import '../../../data/store/setting.dart'; import '../../../locator.dart'; +import '../../widget/custom_appbar.dart'; import '../../widget/round_rect_card.dart'; class ServerDetailPage extends StatefulWidget { @@ -75,7 +76,7 @@ class _ServerDetailPageState extends State Widget _buildMainPage(Server si) { return Scaffold( - appBar: AppBar( + appBar: CustomAppBar( title: Text(si.spi.name, style: textSize18), ), body: ReorderableListView.builder( diff --git a/lib/view/page/server/edit.dart b/lib/view/page/server/edit.dart index b76f7456..cff2a43a 100644 --- a/lib/view/page/server/edit.dart +++ b/lib/view/page/server/edit.dart @@ -15,6 +15,7 @@ import '../../../data/provider/server.dart'; import '../../../data/res/ui.dart'; import '../../../data/store/private_key.dart'; import '../../../locator.dart'; +import '../../widget/custom_appbar.dart'; import '../../widget/tag/editor.dart'; import '../private_key/edit.dart'; @@ -116,7 +117,7 @@ class _ServerEditPageState extends State with AfterLayoutMixin { icon: const Icon(Icons.delete), ); final actions = widget.spi != null ? [delBtn] : null; - return AppBar( + return CustomAppBar( title: Text(_s.edit, style: textSize18), actions: actions, ); diff --git a/lib/view/page/setting.dart b/lib/view/page/setting.dart index 58aa5120..8e9ce968 100644 --- a/lib/view/page/setting.dart +++ b/lib/view/page/setting.dart @@ -29,6 +29,7 @@ import '../../data/res/ui.dart'; import '../../data/store/server.dart'; import '../../data/store/setting.dart'; import '../../locator.dart'; +import '../widget/custom_appbar.dart'; import '../widget/future_widget.dart'; import '../widget/round_rect_card.dart'; @@ -100,7 +101,7 @@ class _SettingPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( + appBar: CustomAppBar( title: Text(_s.setting), ), body: ListView( diff --git a/lib/view/page/snippet/edit.dart b/lib/view/page/snippet/edit.dart index c318cae9..757558bd 100644 --- a/lib/view/page/snippet/edit.dart +++ b/lib/view/page/snippet/edit.dart @@ -9,6 +9,7 @@ import '../../../data/model/server/snippet.dart'; import '../../../data/provider/snippet.dart'; import '../../../data/res/ui.dart'; import '../../../locator.dart'; +import '../../widget/custom_appbar.dart'; import '../../widget/tag/editor.dart'; class SnippetEditPage extends StatefulWidget { @@ -54,7 +55,7 @@ class _SnippetEditPageState extends State @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( + appBar: CustomAppBar( title: Text(_s.edit, style: textSize18), actions: _buildAppBarActions(), ), diff --git a/lib/view/page/ssh/virt_key_setting.dart b/lib/view/page/ssh/virt_key_setting.dart index 9eecde8c..f90f133e 100644 --- a/lib/view/page/ssh/virt_key_setting.dart +++ b/lib/view/page/ssh/virt_key_setting.dart @@ -9,6 +9,8 @@ import 'package:toolbox/data/store/setting.dart'; import 'package:toolbox/locator.dart'; import 'package:toolbox/view/widget/round_rect_card.dart'; +import '../../widget/custom_appbar.dart'; + class SSHVirtKeySettingPage extends StatefulWidget { const SSHVirtKeySettingPage({Key? key}) : super(key: key); @@ -29,7 +31,7 @@ class _SSHVirtKeySettingPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( + appBar: CustomAppBar( title: Text(_s.editVirtKeys), ), body: _buildBody(), diff --git a/lib/view/page/storage/local.dart b/lib/view/page/storage/local.dart index 799ffbd9..bb72c9aa 100644 --- a/lib/view/page/storage/local.dart +++ b/lib/view/page/storage/local.dart @@ -22,6 +22,7 @@ import '../../../core/utils/ui.dart'; import '../../../data/model/app/path_with_prefix.dart'; import '../../../data/res/path.dart'; import '../../../data/res/ui.dart'; +import '../../widget/custom_appbar.dart'; import '../../widget/fade_in.dart'; import 'sftp_mission.dart'; @@ -64,7 +65,7 @@ class _LocalStoragePageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( + appBar: CustomAppBar( leading: IconButton( icon: const BackButtonIcon(), onPressed: () { diff --git a/lib/view/page/storage/sftp.dart b/lib/view/page/storage/sftp.dart index c485c6be..560682bb 100644 --- a/lib/view/page/storage/sftp.dart +++ b/lib/view/page/storage/sftp.dart @@ -26,6 +26,7 @@ import '../../../data/provider/sftp.dart'; import '../../../data/res/path.dart'; import '../../../data/res/ui.dart'; import '../../../locator.dart'; +import '../../widget/custom_appbar.dart'; import '../../widget/fade_in.dart'; import '../../widget/input_field.dart'; import '../../widget/two_line_text.dart'; @@ -74,7 +75,7 @@ class _SftpPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( + appBar: CustomAppBar( leading: IconButton( icon: const BackButtonIcon(), onPressed: () { diff --git a/lib/view/page/storage/sftp_mission.dart b/lib/view/page/storage/sftp_mission.dart index b33c531d..3fa39b91 100644 --- a/lib/view/page/storage/sftp_mission.dart +++ b/lib/view/page/storage/sftp_mission.dart @@ -13,6 +13,7 @@ import '../../../core/utils/ui.dart'; import '../../../data/model/sftp/req.dart'; import '../../../data/provider/sftp.dart'; import '../../../data/res/ui.dart'; +import '../../widget/custom_appbar.dart'; import '../../widget/round_rect_card.dart'; class SftpMissionPage extends StatefulWidget { @@ -34,11 +35,8 @@ class _SftpMissionPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text( - _s.mission, - style: textSize18, - ), + appBar: CustomAppBar( + title: Text(_s.mission, style: textSize18), ), body: _buildBody(), ); diff --git a/lib/view/widget/custom_appbar.dart b/lib/view/widget/custom_appbar.dart new file mode 100644 index 00000000..ebd5ae79 --- /dev/null +++ b/lib/view/widget/custom_appbar.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:macos_window_utils/window_manipulator.dart'; + +double? _titlebarHeight; + +class CustomAppBar extends AppBar implements PreferredSizeWidget { + CustomAppBar({ + super.key, + super.title, + super.actions, + super.centerTitle, + super.leading, + super.backgroundColor, + }) : super(toolbarHeight: (_titlebarHeight ?? 0) + kToolbarHeight); + + static Future updateTitlebarHeight() async { + final newTitlebarHeight = await WindowManipulator.getTitlebarHeight(); + if (_titlebarHeight != newTitlebarHeight) { + _titlebarHeight = newTitlebarHeight; + } + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 5f077980..09f69ef3 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,14 @@ import FlutterMacOS import Foundation +import macos_window_utils import path_provider_foundation import share_plus import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/macos/Podfile b/macos/Podfile index c795730d..b52666a1 100644 --- a/macos/Podfile +++ b/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.14' +platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 97e82977..eb11fba7 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1,5 +1,7 @@ PODS: - FlutterMacOS (1.0.0) + - macos_window_utils (1.0.0): + - FlutterMacOS - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS @@ -13,6 +15,7 @@ PODS: DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) + - macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) @@ -21,6 +24,8 @@ DEPENDENCIES: EXTERNAL SOURCES: FlutterMacOS: :path: Flutter/ephemeral + macos_window_utils: + :path: Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin share_plus: @@ -32,11 +37,12 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 + macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 + path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 - url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451 + url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 -PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 +PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 COCOAPODS: 1.12.1 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index c4de14a8..3976fd12 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -195,7 +195,6 @@ 15AF97DF993E8968098D6EBE /* Pods-RunnerTests.release.xcconfig */, 7CFA7DE7FABA75685DFB6948 /* Pods-RunnerTests.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -475,9 +474,9 @@ baseConfigurationReference = C1C758C41C4E208965A68933 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 476; + CURRENT_PROJECT_VERSION = 477; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.476; + MARKETING_VERSION = 1.0.477; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -490,9 +489,9 @@ baseConfigurationReference = 15AF97DF993E8968098D6EBE /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 476; + CURRENT_PROJECT_VERSION = 477; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.476; + MARKETING_VERSION = 1.0.477; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -505,9 +504,9 @@ baseConfigurationReference = 7CFA7DE7FABA75685DFB6948 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 476; + CURRENT_PROJECT_VERSION = 477; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.476; + MARKETING_VERSION = 1.0.477; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -575,6 +574,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.15; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; @@ -701,6 +701,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.15; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -721,6 +722,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.15; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; diff --git a/pubspec.lock b/pubspec.lock index 3994bb9c..e5128f83 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -510,6 +510,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + macos_window_utils: + dependency: "direct main" + description: + name: macos_window_utils + sha256: "43a90473f8786f00f07203e6819dab67e032f8896dafa4a6f85fbc71fba32c0b" + url: "https://pub.dev" + source: hosted + version: "1.2.0" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2581b688..0a5d62ad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,6 +45,7 @@ dependencies: code_text_field: ^1.1.0 shared_preferences: ^2.1.1 crypto: ^3.0.3 + macos_window_utils: ^1.2.0 dev_dependencies: flutter_native_splash: ^2.1.6