From 4a47f617cb686ad16932548ebcbf116263b32eb5 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 6 Feb 2023 14:48:27 -0500 Subject: [PATCH 01/45] chore: account for latest Flutter:master --- example/lib/pages/fields_page.dart | 12 +++++----- example/lib/pages/toolbar_page.dart | 6 ++--- example/pubspec.lock | 26 +++++++++++----------- lib/src/fields/text_field.dart | 4 ++-- lib/src/layout/scaffold.dart | 2 +- pubspec.lock | 34 ++++++++++++++--------------- 6 files changed, 42 insertions(+), 42 deletions(-) diff --git a/example/lib/pages/fields_page.dart b/example/lib/pages/fields_page.dart index e7865f98..291437cf 100644 --- a/example/lib/pages/fields_page.dart +++ b/example/lib/pages/fields_page.dart @@ -352,8 +352,8 @@ const countries = [ var actionResults = [ SearchResultItem( "Build project", - child: Row( - children: const [ + child: const Row( + children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 8.0), child: MacosIcon(CupertinoIcons.hammer), @@ -365,8 +365,8 @@ var actionResults = [ ), SearchResultItem( "Debug project", - child: Row( - children: const [ + child: const Row( + children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 8.0), child: MacosIcon(CupertinoIcons.tickets), @@ -378,8 +378,8 @@ var actionResults = [ ), SearchResultItem( "Open containing folder", - child: Row( - children: const [ + child: const Row( + children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 8.0), child: MacosIcon(CupertinoIcons.folder), diff --git a/example/lib/pages/toolbar_page.dart b/example/lib/pages/toolbar_page.dart index 4e77fc86..f6188ca3 100644 --- a/example/lib/pages/toolbar_page.dart +++ b/example/lib/pages/toolbar_page.dart @@ -157,11 +157,11 @@ class _ToolbarPageState extends State { ), children: [ ContentArea(builder: (context) { - return SingleChildScrollView( - padding: const EdgeInsets.all(30), + return const SingleChildScrollView( + padding: EdgeInsets.all(30), child: Center( child: Column( - children: const [ + children: [ Text( "The toolbar appears below the title bar of the macOS app or integrates with it.", textAlign: TextAlign.center, diff --git a/example/pubspec.lock b/example/pubspec.lock index 45cafbfa..f6155c18 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.1" cupertino_icons: dependency: "direct main" description: @@ -79,10 +79,10 @@ packages: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" lints: dependency: transitive description: @@ -102,10 +102,10 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: c94db23593b89766cda57aab9ac311e3616cf87c6fa4e9749df032f66f30dcb8 url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.14" material_color_utilities: dependency: transitive description: @@ -118,10 +118,10 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "12307e7f0605ce3da64cf0db90e5fcab0869f3ca03f76be6bb2991ce0a55e82b" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.0" nested: dependency: transitive description: @@ -134,10 +134,10 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" provider: dependency: "direct main" description: @@ -195,10 +195,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: "6182294da5abf431177fccc1ee02401f6df30f766bc6130a0852c6b6d7ee6b2d" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.4.18" vector_math: dependency: transitive description: @@ -208,5 +208,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.18.0 <3.0.0" + dart: ">=2.19.0 <4.0.0" flutter: ">=1.20.0" diff --git a/lib/src/fields/text_field.dart b/lib/src/fields/text_field.dart index 99f390cc..720786dc 100644 --- a/lib/src/fields/text_field.dart +++ b/lib/src/fields/text_field.dart @@ -105,7 +105,7 @@ class _TextFieldSelectionGestureDetectorBuilder final _MacosTextFieldState _state; @override - void onSingleTapUp(TapUpDetails details) { + void onSingleTapUp(TapDragUpDetails details) { // Because TextSelectionGestureDetector listens to taps that happen on // widgets in front of it, tapping the clear button will also trigger // this handler. If the clear button widget recognizes the up event, @@ -127,7 +127,7 @@ class _TextFieldSelectionGestureDetectorBuilder } @override - void onDragSelectionEnd(DragEndDetails details) { + void onDragSelectionEnd(TapDragEndDetails details) { _state._requestKeyboard(); } } diff --git a/lib/src/layout/scaffold.dart b/lib/src/layout/scaffold.dart index 712ab1a7..16aa71e1 100644 --- a/lib/src/layout/scaffold.dart +++ b/lib/src/layout/scaffold.dart @@ -115,7 +115,7 @@ class _MacosScaffoldState extends State { } class _ScaffoldBody extends MultiChildRenderObjectWidget { - _ScaffoldBody({ + const _ScaffoldBody({ super.children, }); diff --git a/pubspec.lock b/pubspec.lock index eb16ba9a..9a886f15 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.1" convert: dependency: transitive description: @@ -223,10 +223,10 @@ packages: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" json_annotation: dependency: transitive description: @@ -255,10 +255,10 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: c94db23593b89766cda57aab9ac311e3616cf87c6fa4e9749df032f66f30dcb8 url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.14" material_color_utilities: dependency: transitive description: @@ -271,10 +271,10 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "12307e7f0605ce3da64cf0db90e5fcab0869f3ca03f76be6bb2991ce0a55e82b" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.0" mime: dependency: transitive description: @@ -311,10 +311,10 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" petitparser: dependency: transitive description: @@ -460,26 +460,26 @@ packages: dependency: transitive description: name: test - sha256: a5fcd2d25eeadbb6589e80198a47d6a464ba3e2049da473943b8af9797900c2d + sha256: b54d427664c00f2013ffb87797a698883c46aee9288e027a50b46eaee7486fa2 url: "https://pub.dev" source: hosted - version: "1.22.0" + version: "1.22.2" test_api: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: "6182294da5abf431177fccc1ee02401f6df30f766bc6130a0852c6b6d7ee6b2d" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.4.18" test_core: dependency: transitive description: name: test_core - sha256: "0ef9755ec6d746951ba0aabe62f874b707690b5ede0fecc818b138fcc9b14888" + sha256: "95ecc12692d0dd59080ab2d38d9cf32c7e9844caba23ff6cd285690398ee8ef4" url: "https://pub.dev" source: hosted - version: "0.4.20" + version: "0.4.22" typed_data: dependency: transitive description: @@ -545,5 +545,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.18.0 <3.0.0" + dart: ">=2.19.0 <4.0.0" flutter: ">=1.20.0" From 411d00d56f17f68beaefb0fc0e8c817b343c4a03 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 6 Feb 2023 14:48:50 -0500 Subject: [PATCH 02/45] fix(example): incorrect variable usage --- example/lib/pages/indicators_page.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example/lib/pages/indicators_page.dart b/example/lib/pages/indicators_page.dart index 58a6d391..7c118b96 100644 --- a/example/lib/pages/indicators_page.dart +++ b/example/lib/pages/indicators_page.dart @@ -39,14 +39,14 @@ class _IndicatorsPageState extends State { child: Column( children: [ CapacityIndicator( - value: sliderValue, - onChanged: (v) => setState(() => sliderValue = v), + value: capacitorValue, + onChanged: (v) => setState(() => capacitorValue = v), discrete: true, ), const SizedBox(height: 20), CapacityIndicator( - value: sliderValue, - onChanged: (v) => setState(() => sliderValue = v), + value: capacitorValue, + onChanged: (v) => setState(() => capacitorValue = v), ), const SizedBox(height: 20), MacosSlider( From 2d5a7e09b9d77ecaf74786564259fc4f7a1a95fa Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Tue, 21 Feb 2023 10:26:34 -0500 Subject: [PATCH 03/45] Version 1.11.1 (#366) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Version 1.7.1 (#287) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner * Version 1.4.1 (#255) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner Co-authored-by: Minas Giannekas * chore: repository, homepage fields * chore: update readme * feat(starter_app): Version 1.1.0 * feat(starter_app): multi-window support * feat: starter_app 1.2.1 * chore: move brick to its own repo & go back to old pana action * Expand remaining part of row in MacosListTile (#265) * Expand remaining part of row https://github.com/GroovinChip/macos_ui/issues/264 * Increment to version 1.4.2 * End sidebar (#267) * chore: add missing trailing comma * chore: improve MacosIconButton animation curve * chore: remove false_secrets from pubspec.yaml * feat: end sidebar Also fixes the tests portion of the pr_prelaunch_tasks script * feat: add "isEndSidebarShown" to MacosWindowScope * fix: Correct the placement of the leading widget in disclosure sidebar items (#272) * chore: Update changelog * test: fix issues with date_picker_test * Account for cases where the month and the day are the same * Fix offstage warnings by removing tester taps that disabled the caret controls * Update Actions (#279) * Update flutter_analysis.yml * Update dart_code_metrics.yaml * Update gh_pages.yml * Update pana_analysis.yml * Update codecov.yaml * fix syntax issue * Version 1.6.0 - `MacosTabView` & `MacosSegmentedControl` (#273) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: update README example for MacosTabView * chore: fix typo in MacosSegmentedControl * chore: fix typo in MacosSegmentedControl docstring * test: remove explicitly setting active property of MacosTabs Co-authored-by: Minas Giannekas * Version 1.7.0: `MacosImageIcon` & sidebar updates (#274) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * feat: MacosImageIcon & sidebar updates * test: fix issues with date_picker_test * Account for cases where the month and the day are the same * Fix offstage warnings by removing tester taps that disabled the caret controls * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: fix README * test: fix date picker test Co-authored-by: Minas Giannekas * chore: fix typo in pr template * feat: gh action to auto-generate releases on push to stable * chore: update release action with latest from stable * feat: add action to publish to pub * docs: update readme * Addresses #237 * Adds MacosColorWell to selectors section * Adds code snippets for date & time pickers * fix: 1.7.1 Fixes issue where end sidebar window breakpoint wasn’t being respected Co-authored-by: Minas Giannekas Co-authored-by: Jon Saw Co-authored-by: Minas Giannekas * Version 1.7.3 (#293) * Version 1.7.4 (#295) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner * Version 1.4.1 (#255) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner Co-authored-by: Minas Giannekas * chore: repository, homepage fields * chore: update readme * feat(starter_app): Version 1.1.0 * feat(starter_app): multi-window support * feat: starter_app 1.2.1 * chore: move brick to its own repo & go back to old pana action * Expand remaining part of row in MacosListTile (#265) * Expand remaining part of row https://github.com/GroovinChip/macos_ui/issues/264 * Increment to version 1.4.2 * End sidebar (#267) * chore: add missing trailing comma * chore: improve MacosIconButton animation curve * chore: remove false_secrets from pubspec.yaml * feat: end sidebar Also fixes the tests portion of the pr_prelaunch_tasks script * feat: add "isEndSidebarShown" to MacosWindowScope * fix: Correct the placement of the leading widget in disclosure sidebar items (#272) * chore: Update changelog * test: fix issues with date_picker_test * Account for cases where the month and the day are the same * Fix offstage warnings by removing tester taps that disabled the caret controls * Update Actions (#279) * Update flutter_analysis.yml * Update dart_code_metrics.yaml * Update gh_pages.yml * Update pana_analysis.yml * Update codecov.yaml * fix syntax issue * Version 1.6.0 - `MacosTabView` & `MacosSegmentedControl` (#273) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: update README example for MacosTabView * chore: fix typo in MacosSegmentedControl * chore: fix typo in MacosSegmentedControl docstring * test: remove explicitly setting active property of MacosTabs Co-authored-by: Minas Giannekas * Version 1.7.0: `MacosImageIcon` & sidebar updates (#274) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * feat: MacosImageIcon & sidebar updates * test: fix issues with date_picker_test * Account for cases where the month and the day are the same * Fix offstage warnings by removing tester taps that disabled the caret controls * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: fix README * test: fix date picker test Co-authored-by: Minas Giannekas * chore: fix typo in pr template * feat: gh action to auto-generate releases on push to stable * chore: update release action with latest from stable * feat: add action to publish to pub * docs: update readme * Addresses #237 * Adds MacosColorWell to selectors section * Adds code snippets for date & time pickers * fix: 1.7.1 Fixes issue where end sidebar window breakpoint wasn’t being respected * Tab view padding (#285) * fix: use prepared title wrapped with a DefaultTextStyle instead of the raw title (#289) * chore: fix typo Closes #290 * feat: add `backgroundColor` to `MacosSheet` (#291) Co-authored-by: Minas Giannekas Co-authored-by: Jon Saw Co-authored-by: Minas Giannekas Co-authored-by: st merlhin <77164238+stMerlHin@users.noreply.github.com> Co-authored-by: jtdLab <72231111+jtdLab@users.noreply.github.com> * Version 1.7.5 (#299) * chore: fix Flutter 3.3 warnings * Version 1.7.6 (#327) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner * Version 1.4.1 (#255) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner Co-authored-by: Minas Giannekas * chore: repository, homepage fields * chore: update readme * feat(starter_app): Version 1.1.0 * feat(starter_app): multi-window support * feat: starter_app 1.2.1 * chore: move brick to its own repo & go back to old pana action * Expand remaining part of row in MacosListTile (#265) * Expand remaining part of row https://github.com/GroovinChip/macos_ui/issues/264 * Increment to version 1.4.2 * End sidebar (#267) * chore: add missing trailing comma * chore: improve MacosIconButton animation curve * chore: remove false_secrets from pubspec.yaml * feat: end sidebar Also fixes the tests portion of the pr_prelaunch_tasks script * feat: add "isEndSidebarShown" to MacosWindowScope * fix: Correct the placement of the leading widget in disclosure sidebar items (#272) * chore: Update changelog * test: fix issues with date_picker_test * Account for cases where the month and the day are the same * Fix offstage warnings by removing tester taps that disabled the caret controls * Update Actions (#279) * Update flutter_analysis.yml * Update dart_code_metrics.yaml * Update gh_pages.yml * Update pana_analysis.yml * Update codecov.yaml * fix syntax issue * Version 1.6.0 - `MacosTabView` & `MacosSegmentedControl` (#273) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: update README example for MacosTabView * chore: fix typo in MacosSegmentedControl * chore: fix typo in MacosSegmentedControl docstring * test: remove explicitly setting active property of MacosTabs Co-authored-by: Minas Giannekas * Version 1.7.0: `MacosImageIcon` & sidebar updates (#274) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * feat: MacosImageIcon & sidebar updates * test: fix issues with date_picker_test * Account for cases where the month and the day are the same * Fix offstage warnings by removing tester taps that disabled the caret controls * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: fix README * test: fix date picker test Co-authored-by: Minas Giannekas * chore: fix typo in pr template * feat: gh action to auto-generate releases on push to stable * chore: update release action with latest from stable * feat: add action to publish to pub * docs: update readme * Addresses #237 * Adds MacosColorWell to selectors section * Adds code snippets for date & time pickers * fix: 1.7.1 Fixes issue where end sidebar window breakpoint wasn’t being respected * Tab view padding (#285) * fix: use prepared title wrapped with a DefaultTextStyle instead of the raw title (#289) * chore: fix typo Closes #290 * feat: add `backgroundColor` to `MacosSheet` (#291) * chore: fix Flutter 3.3 warnings * fix: address ScrollController bug in MacosPopupButton (#300) * fix(tests): account for Jan -> Dec & Dec -> Jan date_picker_test.dart was failing due to not accounting for going from January to December and vice-versa. Co-authored-by: Minas Giannekas Co-authored-by: Jon Saw Co-authored-by: Minas Giannekas Co-authored-by: st merlhin <77164238+stMerlHin@users.noreply.github.com> Co-authored-by: jtdLab <72231111+jtdLab@users.noreply.github.com> * Version 1.9.0 (#339) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner * Version 1.4.1 (#255) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner Co-authored-by: Minas Giannekas * chore: repository, homepage fields * chore: update readme * feat(starter_app): Version 1.1.0 * feat(starter_app): multi-window support * feat: starter_app 1.2.1 * chore: move brick to its own repo & go back to old pana action * Expand remaining part of row in MacosListTile (#265) * Expand remaining part of row https://github.com/GroovinChip/macos_ui/issues/264 * Increment to version 1.4.2 * End sidebar (#267) * chore: add missing trailing comma * chore: improve MacosIconButton animation curve * chore: remove false_secrets from pubspec.yaml * feat: end sidebar Also fixes the tests portion of the pr_prelaunch_tasks script * feat: add "isEndSidebarShown" to MacosWindowScope * fix: Correct the placement of the leading widget in disclosure sidebar items (#272) * chore: Update changelog * test: fix issues with date_picker_test * Account for cases where the month and the day are the same * Fix offstage warnings by removing tester taps that disabled the caret controls * Update Actions (#279) * Update flutter_analysis.yml * Update dart_code_metrics.yaml * Update gh_pages.yml * Update pana_analysis.yml * Update codecov.yaml * fix syntax issue * Version 1.6.0 - `MacosTabView` & `MacosSegmentedControl` (#273) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: update README example for MacosTabView * chore: fix typo in MacosSegmentedControl * chore: fix typo in MacosSegmentedControl docstring * test: remove explicitly setting active property of MacosTabs Co-authored-by: Minas Giannekas * Version 1.7.0: `MacosImageIcon` & sidebar updates (#274) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * feat: MacosImageIcon & sidebar updates * test: fix issues with date_picker_test * Account for cases where the month and the day are the same * Fix offstage warnings by removing tester taps that disabled the caret controls * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: fix README * test: fix date picker test Co-authored-by: Minas Giannekas * chore: fix typo in pr template * feat: gh action to auto-generate releases on push to stable * chore: update release action with latest from stable * feat: add action to publish to pub * docs: update readme * Addresses #237 * Adds MacosColorWell to selectors section * Adds code snippets for date & time pickers * fix: 1.7.1 Fixes issue where end sidebar window breakpoint wasn’t being respected * Tab view padding (#285) * fix: use prepared title wrapped with a DefaultTextStyle instead of the raw title (#289) * chore: fix typo Closes #290 * feat: add `backgroundColor` to `MacosSheet` (#291) * chore: fix Flutter 3.3 warnings * fix: address ScrollController bug in MacosPopupButton (#300) * fix(tests): account for Jan -> Dec & Dec -> Jan date_picker_test.dart was failing due to not accounting for going from January to December and vice-versa. * fix(plugin): Ensure the native color panel releases when closed * Update flutter_analysis.yml Closes #334 * Various bug fixes & minor updates (#338) * Macos slider (#337) * chore: run flutter format . * chore: fix analysis * chore: Bump version and update CHANGELOG.md * chore: Update images to self taken ones as MacOS images are outdated * fix: fix position offset by a small value * fix: PR review feedback * Update lib/src/indicators/slider.dart --------- Co-authored-by: Reuben Turner --------- Co-authored-by: Minas Giannekas Co-authored-by: Jon Saw Co-authored-by: Minas Giannekas Co-authored-by: st merlhin <77164238+stMerlHin@users.noreply.github.com> Co-authored-by: jtdLab <72231111+jtdLab@users.noreply.github.com> Co-authored-by: Norbert Kozsir * Version 1.9.1 (#341) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner * Version 1.4.1 (#255) * Sidebar top (#244) * chore: refactor dir structure * feat: Sidebar top & updated default control color * feat(example): search results in top * chore: bump version, changelog * chore: run flutter pub upgrade * Update CHANGELOG.md * Update lib/src/layout/sidebar/sidebar.dart Co-authored-by: Minas Giannekas * chore: update issue templates * chore: update pr_prelaunch script * Flutter 3 upgrade & MacosColor update (#248) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * chore: remove unused code * Starter app (#251) * chore: Update pubspec.yaml files to support Flutter 3 * chore: run dart fix --apply * chore: migrate Scrollbar to Flutter 3 * chore: update flutter_lints & subsequent fixes * feat: add missing functions to MacosColor the Color class has a number of functions that MacosColor had not implemented * chore: use super parameters * chore: update changelog * chore: tweak example app Uses the new PlatformMenuBar. Also update product name. * chore: small changelog tweak * chore: run flutter format . * chore: run dart fix --apply * chore: run flutter format . * chore: remove unused code in example * chore: remove unused import * feat: first pass at starter app brick * chore: improve starter app brick * chore: fix widget test in starter app * feat: conditional prompts & running pub get * chore: finalize brick * chore: run flutter format * chore: exclude starter app from analyzer * Full screen opaque toolbar issue (closes #249) (#252) * fix: don't show app window toolbar when in full screen * chore: update README * chore: update brick app window code * chore: update pubspec and changelog * chore: update actions Co-authored-by: Reuben Turner Co-authored-by: Minas Giannekas * chore: repository, homepage fields * chore: update readme * feat(starter_app): Version 1.1.0 * feat(starter_app): multi-window support * feat: starter_app 1.2.1 * chore: move brick to its own repo & go back to old pana action * Expand remaining part of row in MacosListTile (#265) * Expand remaining part of row https://github.com/GroovinChip/macos_ui/issues/264 * Increment to version 1.4.2 * End sidebar (#267) * chore: add missing trailing comma * chore: improve MacosIconButton animation curve * chore: remove false_secrets from pubspec.yaml * feat: end sidebar Also fixes the tests portion of the pr_prelaunch_tasks script * feat: add "isEndSidebarShown" to MacosWindowScope * fix: Correct the placement of the leading widget in disclosure sidebar items (#272) * chore: Update changelog * test: fix issues with date_picker_test * Account for cases where the month and the day are the same * Fix offstage warnings by removing tester taps that disabled the caret controls * Update Actions (#279) * Update flutter_analysis.yml * Update dart_code_metrics.yaml * Update gh_pages.yml * Update pana_analysis.yml * Update codecov.yaml * fix syntax issue * Version 1.6.0 - `MacosTabView` & `MacosSegmentedControl` (#273) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: update README example for MacosTabView * chore: fix typo in MacosSegmentedControl * chore: fix typo in MacosSegmentedControl docstring * test: remove explicitly setting active property of MacosTabs Co-authored-by: Minas Giannekas * Version 1.7.0: `MacosImageIcon` & sidebar updates (#274) * feat: MacosTabView & MacosSegmentedControl * chore: fixup scripts * test: tests for segmented control & tab view * chore: remove unused code * chore: run flutter format . * chore: bump code metrics * docs: dartdoc updates * docs: fix a documentation error * chore: run flutter format . * feat: MacosImageIcon & sidebar updates * test: fix issues with date_picker_test * Account for cases where the month and the day are the same * Fix offstage warnings by removing tester taps that disabled the caret controls * refactor: make active property of MacosTab optional, since it is handled via MacosSegmentedControl * chore: fix import change * refactor: change colors to match default native app design * docs: update tab view screenshot in readme * chore: fix README * test: fix date picker test Co-authored-by: Minas Giannekas * chore: fix typo in pr template * feat: gh action to auto-generate releases on push to stable * chore: update release action with latest from stable * feat: add action to publish to pub * docs: update readme * Addresses #237 * Adds MacosColorWell to selectors section * Adds code snippets for date & time pickers * fix: 1.7.1 Fixes issue where end sidebar window breakpoint wasn’t being respected * Tab view padding (#285) * fix: use prepared title wrapped with a DefaultTextStyle instead of the raw title (#289) * chore: fix typo Closes #290 * feat: add `backgroundColor` to `MacosSheet` (#291) * chore: fix Flutter 3.3 warnings * fix: address ScrollController bug in MacosPopupButton (#300) * fix(tests): account for Jan -> Dec & Dec -> Jan date_picker_test.dart was failing due to not accounting for going from January to December and vice-versa. * fix(plugin): Ensure the native color panel releases when closed * Update flutter_analysis.yml Closes #334 * Various bug fixes & minor updates (#338) * Macos slider (#337) * chore: run flutter format . * chore: fix analysis * chore: Bump version and update CHANGELOG.md * chore: Update images to self taken ones as MacOS images are outdated * fix: fix position offset by a small value * fix: PR review feedback * Update lib/src/indicators/slider.dart --------- Co-authored-by: Reuben Turner * Adds `intialDate` to `MacosDatePicker` (#329) * Adds `intialDate` to `MacosDatePicker` * Bumps `macos_ui` version to `1.7.7` * Apply suggestions from code review * spelling correction --------- Co-authored-by: Reuben Turner --------- Co-authored-by: Minas Giannekas Co-authored-by: Jon Saw Co-authored-by: Minas Giannekas Co-authored-by: st merlhin <77164238+stMerlHin@users.noreply.github.com> Co-authored-by: jtdLab <72231111+jtdLab@users.noreply.github.com> Co-authored-by: Norbert Kozsir Co-authored-by: Elijah Luckey * chore: update gitignore * chore: update pr template * chore: update contributing.md & remove scripts * feat: update `flutter_analysis` workflow (#356) * attempt to automate dartfmt on pr's * finish the if condition * add some output messages * docs: update README Closes #352 * docs(ToolBar): update dartdocs Closes #351 * Bottom resizable pane (#349) * add bottom_resizable_pane.dart file * add test for BottomResizablePane * incremented the package version as appropriate and updated CHANGELOG.md * include the bottomResizable pane widget on the example app (ButtonsPage) and exports the file * makes ResizablePane horizontally draggable * update doc comments * include breaking change detail in CHANGELOG.md file * update ResizablePane test * remove bottom_resizable_pane.dart file and his test file * update buttons_page.dart file in example app * fix lints * run dart format * set the correct parameter name of resizable pane instance in example's ButtonsPage * Apply suggestions from code review * tweak changelog * dartfmt --------- Co-authored-by: Reuben Turner * docs: add usage note regarding Flutter channel Closes #362 * chore(actions): update `flutter_analysis.yaml` Closes #365 * fix: SearchField overlay actions are not performed (#357) * fix: SearchField overlay actions are not performed See #348 for more details * fix: run flutter pub get * fix: add missing MacosSearchField test * formatting tweak --------- Co-authored-by: Reuben Turner * address lints * chore(actions): use master channel * disable pana check on customer_testing branch * chore(actions): job-level "if" * chore(actions): do not run pana action for customer_testing branch --------- Co-authored-by: Minas Giannekas Co-authored-by: Jon Saw Co-authored-by: Minas Giannekas Co-authored-by: st merlhin <77164238+stMerlHin@users.noreply.github.com> Co-authored-by: jtdLab <72231111+jtdLab@users.noreply.github.com> Co-authored-by: Norbert Kozsir Co-authored-by: Elijah Luckey Co-authored-by: FelixMethe <42588649+FelixMethe@users.noreply.github.com> --- .github/PULL_REQUEST_TEMPLATE.md | 4 +- .github/workflows/flutter_analysis.yml | 18 +- .github/workflows/pana_analysis.yml | 1 + .gitignore | 2 +- CHANGELOG.md | 10 + CONTRIBUTING.md | 8 +- README.md | 66 +- example/.gitignore | 1 + example/lib/pages/buttons_page.dart | 726 +++++++++++---------- example/lib/pages/fields_page.dart | 4 +- example/lib/pages/indicators_page.dart | 2 +- example/lib/pages/selectors_page.dart | 1 + example/lib/pages/tabview_page.dart | 2 +- example/lib/pages/toolbar_page.dart | 6 +- example/pubspec.lock | 2 +- lib/src/fields/search_field.dart | 66 +- lib/src/layout/resizable_pane.dart | 232 ++++--- lib/src/layout/toolbar/toolbar.dart | 20 +- pr_prelaunch_tasks.sh | 39 -- publish_tasks.sh | 16 - pubspec.yaml | 2 +- test/buttons/checkbox_test.dart | 2 +- test/buttons/help_button_test.dart | 2 +- test/buttons/icon_button_test.dart | 2 +- test/buttons/popup_button_test.dart | 2 +- test/buttons/pulldown_button_test.dart | 2 +- test/buttons/push_button_test.dart | 2 +- test/buttons/radio_button_test.dart | 2 +- test/buttons/segmented_control_test.dart | 2 +- test/buttons/switch_test.dart | 2 +- test/fields/search_field_test.dart | 12 +- test/fields/text_field_test.dart | 2 +- test/layout/macos_list_tile_test.dart | 2 +- test/layout/resizeable_pane_test.dart | 314 +++++---- test/layout/tab_view_test.dart | 2 +- test/selectors/date_picker_test.dart | 4 +- test/theme/help_button_theme_test.dart | 2 +- test/theme/icon_button_theme_test.dart | 2 +- test/theme/icon_theme_test.dart | 2 +- test/theme/popup_button_theme_test.dart | 2 +- test/theme/pulldown_button_theme_test.dart | 2 +- test/theme/push_button_theme_test.dart | 2 +- test/theme/search_field_theme_test.dart | 2 +- 43 files changed, 907 insertions(+), 689 deletions(-) delete mode 100644 pr_prelaunch_tasks.sh delete mode 100644 publish_tasks.sh diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index af69bfd4..bb71af66 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,9 +7,7 @@ ## Pre-launch Checklist -- [ ] I have run `dartfmt` on all changed files - [ ] I have incremented the package version as appropriate and updated `CHANGELOG.md` with my changes - [ ] I have added/updated relevant documentation - [ ] I have run "optimize/organize imports" on all changed files -- [ ] I have addressed all analyzer warnings as best I could - \ No newline at end of file +- [ ] I have addressed all analyzer warnings as best I could \ No newline at end of file diff --git a/.github/workflows/flutter_analysis.yml b/.github/workflows/flutter_analysis.yml index 29e896de..0187a10d 100644 --- a/.github/workflows/flutter_analysis.yml +++ b/.github/workflows/flutter_analysis.yml @@ -10,13 +10,27 @@ jobs: - name: Install Flutter uses: subosito/flutter-action@v2 with: - channel: stable + channel: master - name: Install dependencies run: flutter pub get + - uses: actions/checkout@v3 + - name: Set up git + run: | + git config user.name "GitHub Actions Bot" + git config user.email "<>" + - name: Format code - run: dart format --set-exit-if-changed . + run: | + dart format . + if [ $? -eq 1 ]; then + git add . + git commit -m "chore: formatting corrections" + git push + echo "Code has been formatted and changes have been committed and pushed." + fi + echo "All code is properly formatted!" - name: Analyze code run: flutter analyze --fatal-infos . diff --git a/.github/workflows/pana_analysis.yml b/.github/workflows/pana_analysis.yml index a32d6cb3..9f90d17e 100644 --- a/.github/workflows/pana_analysis.yml +++ b/.github/workflows/pana_analysis.yml @@ -4,6 +4,7 @@ on: [pull_request, workflow_dispatch] jobs: package-analysis: runs-on: ubuntu-latest + if: github.base_ref != 'customer_testing' steps: - uses: actions/checkout@v3 diff --git a/.gitignore b/.gitignore index 70efb322..fcfa202c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ *.iws .idea/ -.vscode/launch.json +.vscode # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line diff --git a/CHANGELOG.md b/CHANGELOG.md index 994154c1..15c38001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [1.11.1] +* Fixed an issue where the `MacosSearchField` would not perform an action when an item was selected. + +## [1.11.0] +* 🚨 Breaking Changes 🚨 +* `ResizablePane` can now be vertically resized + * `ResizablePane.startWidth` has been changed to `ResizablePane.startSize` + * `ResizablePane.minWidth` has been changed to `ResizablePane.minSize` + * `ResizablePane.maxWidth` has been changed to `ResizablePane.maxSize` + ## [1.10.0] 🚨 Breaking Changes 🚨 * `MacosScrollbar` has been completely overhauled and now resembles the native macOS scrollbar in appearance and diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b6b77d0..bed82a0e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,16 +18,12 @@ This repository uses [conventional commits](https://www.conventionalcommits.org/ As mentioned above, all pull requests should target `dev`. #### Pre-launch script -Before opening your pull request, run the `pr_prelaunch_tasks.sh` script to ensure that your changes meet the -following requirements: +Before opening your pull request, please ensure that the following +following requirements are met: * All code is properly formatted * There are no Dart analysis warnings * All tests pass -If the format step of the script results in changes, the script will make those change, commit them, and prompt you to push the commit. - -If the `dart fix` step results in changes, the script will make those changes, commit them, and prompt you to push the commit. - Pull requests should **always** be merged via GitHub and not via command-line. ### Versioning diff --git a/README.md b/README.md index 663d9d65..1a1a1451 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,10 @@ Guides, codelabs, and other documentation can be found at https://macosui.dev -## 🚨 Usage notes +## 🚨 Usage notes +### Flutter channel +`macos_ui` is developed against Flutter's `stable` channel. To ensure a smooth development experience with `macos_ui`, you should build your application on Flutter's `stable` channel. + ### Platform Compatibility pub.dev shows that `macos_ui` only supports macOS. This is because `macos_ui` calls some native code, and therefore @@ -50,6 +53,7 @@ should avoid allowing your application window to be resized below the height of - [Layout](#layout) - [MacosWindow](#macoswindow) + - [Sidebar](#sidebar) - [MacosScaffold](#macosscaffold) - [Modern Window Look](#modern-window-look) - [ToolBar](#toolbar) @@ -148,6 +152,56 @@ your `MacosScaffold` in a `Builder` widget in order for this to work properly. +## Sidebar +A sidebar enables app navigation and provides quick access to top-level collections of content in your app. + +Sidebars may be placed at the left or right of your app. To place a sidebar on the left, use the `MacosWindow.sidebar` property. To place a sidebar on the right, use the `MacosWindow.endSidebar` property. + + + +Example usage: + +```dart +int pageIndex = 0; + +... + +MacosWindow( + sidebar: Sidebar( + minWidth: 200, + builder: (context, scrollController) { + return SidebarItems( + currentIndex: pageIndex, + scrollController: scrollController, + itemSize: SidebarItemSize.large, + onChanged: (i) { + setState(() => pageIndex = i); + }, + items: const [ + SidebarItem( + label: Text('Page One'), + ), + SidebarItem( + label: Text('Page Two'), + ), + ], + ); + }, + ), + endSidebar: Sidebar( + startWidth: 200, + minWidth: 200, + maxWidth: 300, + shownByDefault: false, + builder: (context, _) { + return const Center( + child: Text('End Sidebar'), + ); + }, + ), +), +``` + ## MacosScaffold The `MacosScaffold` is what you might call a "page". @@ -355,7 +409,7 @@ CustomToolbarItem( ## MacosListTile -A widget that aims to approximate the [ListTile] widget found in +A widget that aims to approximate the [`ListTile`](https://api.flutter.dev/flutter/material/ListTile-class.html) widget found in Flutter's material library. ![MacosListTile](https://imgur.com/pQB99M2.png) @@ -862,10 +916,10 @@ You can set `discrete` to `true` to make it a discrete capacity indicator. A slider is a control that lets people select a value from a continuous or discrete range of values by moving the slider thumb. - Continuous | Discrete | -|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ![Continuous Slider Example](https://i.imgur.com/dc4YjoX.png) | ![Discrete Slider Example](https://i.imgur.com/KckOTUf.png) | -| A horizontal slider where any value continuous value between a min and max can be selected | A horizontal slider where only discrete values between a min and max can be selected. Tick marks are often displayed to provide context. | + | Continuous | Discrete | + | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | + | ![Continuous Slider Example](https://i.imgur.com/dc4YjoX.png) | ![Discrete Slider Example](https://i.imgur.com/KckOTUf.png) | + | A horizontal slider where any value continuous value between a min and max can be selected | A horizontal slider where only discrete values between a min and max can be selected. Tick marks are often displayed to provide context. | Here's an example of how to create an interactive continuous slider: diff --git a/example/.gitignore b/example/.gitignore index 8b52fde8..4e8c3b2b 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -47,3 +47,4 @@ app.*.map.json /android/app/release /windows/ +linux/ diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index dafc5ea2..6533484b 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -60,8 +60,8 @@ class _ButtonsPageState extends State { ), children: [ ResizablePane( - minWidth: 180, - startWidth: 200, + minSize: 180, + startSize: 200, windowBreakpoint: 700, resizableSide: ResizableSide.right, builder: (_, __) { @@ -72,366 +72,394 @@ class _ButtonsPageState extends State { ), ContentArea( builder: (context, scrollController) { - return SingleChildScrollView( - controller: scrollController, - padding: const EdgeInsets.all(20), - child: Column( - children: [ - const Text('MacosBackButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosBackButton( - onPressed: () => debugPrint('click'), - fillColor: Colors.transparent, - ), - const SizedBox(width: 16.0), - MacosBackButton( - onPressed: () => debugPrint('click'), - ), - ], - ), - const SizedBox(height: 20), - const Text('MacosDisclosureButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosDisclosureButton( - isPressed: isDisclosureButtonPressed, - onPressed: () { - debugPrint('click'); - setState(() { - isDisclosureButtonPressed = - !isDisclosureButtonPressed; - }); - }), - ], - ), - const SizedBox(height: 20), - const Text('MacosIconButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosIconButton( - icon: const MacosIcon( - CupertinoIcons.star_fill, + return Column( + children: [ + Flexible( + fit: FlexFit.loose, + child: SingleChildScrollView( + controller: scrollController, + padding: const EdgeInsets.all(20), + child: Column( + children: [ + const Text('MacosBackButton'), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosBackButton( + onPressed: () => debugPrint('click'), + fillColor: Colors.transparent, + ), + const SizedBox(width: 16.0), + MacosBackButton( + onPressed: () => debugPrint('click'), + ), + ], ), - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(7), - onPressed: () {}, - ), - const SizedBox(width: 8), - const MacosIconButton( - icon: MacosIcon( - CupertinoIcons.plus_app, + const SizedBox(height: 20), + const Text('MacosDisclosureButton'), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosDisclosureButton( + isPressed: isDisclosureButtonPressed, + onPressed: () { + debugPrint('click'); + setState(() { + isDisclosureButtonPressed = + !isDisclosureButtonPressed; + }); + }), + ], ), - shape: BoxShape.circle, - //onPressed: () {}, - ), - const SizedBox(width: 8), - MacosIconButton( - icon: const MacosIcon( - CupertinoIcons.minus_square, + const SizedBox(height: 20), + const Text('MacosIconButton'), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosIconButton( + icon: const MacosIcon( + CupertinoIcons.star_fill, + ), + shape: BoxShape.rectangle, + borderRadius: BorderRadius.circular(7), + onPressed: () {}, + ), + const SizedBox(width: 8), + const MacosIconButton( + icon: MacosIcon( + CupertinoIcons.plus_app, + ), + shape: BoxShape.circle, + //onPressed: () {}, + ), + const SizedBox(width: 8), + MacosIconButton( + icon: const MacosIcon( + CupertinoIcons.minus_square, + ), + backgroundColor: Colors.transparent, + onPressed: () {}, + ), + ], ), - backgroundColor: Colors.transparent, - onPressed: () {}, - ), - ], - ), - const SizedBox(height: 20), - const Text('PushButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - PushButton( - buttonSize: ButtonSize.large, - child: const Text('Large'), - onPressed: () { - MacosWindowScope.of(context).toggleSidebar(); - }, - ), - const SizedBox(width: 20), - PushButton( - buttonSize: ButtonSize.small, - child: const Text('Small'), - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) { - return MacosScaffold( - toolBar: const ToolBar( - title: Text('New page'), - ), - children: [ - ContentArea( - builder: (context, _) { - return Center( - child: PushButton( - buttonSize: ButtonSize.large, - child: const Text('Go Back'), - onPressed: () { - Navigator.of(context).maybePop(); + const SizedBox(height: 20), + const Text('PushButton'), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + PushButton( + buttonSize: ButtonSize.large, + child: const Text('Large'), + onPressed: () { + MacosWindowScope.of(context).toggleSidebar(); + }, + ), + const SizedBox(width: 20), + PushButton( + buttonSize: ButtonSize.small, + child: const Text('Small'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + buttonSize: ButtonSize.large, + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context) + .maybePop(); + }, + ), + ); }, ), - ); - }, - ), - ResizablePane( - minWidth: 180, - startWidth: 200, - windowBreakpoint: 700, - resizableSide: ResizableSide.left, - builder: (_, __) { - return const Center( - child: Text('Resizable Pane'), - ); - }, - ), - ], + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: ResizableSide.left, + builder: (_, __) { + return const Center( + child: Text('Resizable Pane'), + ); + }, + ), + ], + ); + }, + ), ); }, ), - ); - }, - ), - const SizedBox(width: 20), - PushButton( - buttonSize: ButtonSize.large, - isSecondary: true, - child: const Text('Secondary'), - onPressed: () { - MacosWindowScope.of(context).toggleSidebar(); - }, - ), - ], - ), - const SizedBox(height: 20), - const Text('MacosSwitch'), - const SizedBox(height: 8), - MacosSwitch( - value: switchValue, - onChanged: (value) { - setState(() => switchValue = value); - }, - ), - const SizedBox(height: 20), - const Text('MacosPulldownButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosPulldownButton( - title: "PDF", - items: [ - MacosPulldownMenuItem( - title: const Text('Open in Preview'), - onTap: () => debugPrint("Opening in preview..."), - ), - MacosPulldownMenuItem( - title: const Text('Save as PDF...'), - onTap: () => debugPrint("Saving as PDF..."), - ), - MacosPulldownMenuItem( - enabled: false, - title: const Text('Save as Postscript'), - onTap: () => debugPrint("Saving as Postscript..."), - ), - const MacosPulldownMenuDivider(), - MacosPulldownMenuItem( - enabled: false, - title: const Text('Save to iCloud Drive'), - onTap: () => debugPrint("Saving to iCloud..."), - ), - MacosPulldownMenuItem( - enabled: false, - title: const Text('Save to Web Receipts'), - onTap: () => - debugPrint("Saving to Web Receipts..."), - ), - MacosPulldownMenuItem( - title: const Text('Send in Mail...'), - onTap: () => debugPrint("Sending via Mail..."), - ), - const MacosPulldownMenuDivider(), - MacosPulldownMenuItem( - title: const Text('Edit Menu...'), - onTap: () => debugPrint("Editing menu..."), - ), - ], - ), - const SizedBox(width: 20), - const MacosPulldownButton( - title: "PDF", - disabledTitle: "Disabled", - items: [], - ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosPulldownButton( - icon: CupertinoIcons.ellipsis_circle, - items: [ - MacosPulldownMenuItem( - title: const Text('New Folder'), - onTap: () => debugPrint("Creating new folder..."), - ), - MacosPulldownMenuItem( - title: const Text('Open'), - onTap: () => debugPrint("Opening..."), - ), - MacosPulldownMenuItem( - title: const Text('Open with...'), - onTap: () => debugPrint("Opening with..."), - ), - MacosPulldownMenuItem( - title: const Text('Import from iPhone...'), - onTap: () => debugPrint("Importing..."), - ), - const MacosPulldownMenuDivider(), - MacosPulldownMenuItem( - enabled: false, - title: const Text('Remove'), - onTap: () => debugPrint("Deleting..."), - ), - MacosPulldownMenuItem( - title: const Text('Move to Bin'), - onTap: () => debugPrint("Moving to Bin..."), - ), - const MacosPulldownMenuDivider(), - MacosPulldownMenuItem( - title: const Text('Tags...'), - onTap: () => debugPrint("Tags..."), - ), - ], - ), - const SizedBox(width: 20), - const MacosPulldownButton( - icon: CupertinoIcons.square_grid_3x2, - items: [], - ), - ], - ), - const SizedBox(height: 20), - const Text('MacosPopupButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosPopupButton( - value: popupValue, - onChanged: (String? newValue) { - setState(() => popupValue = newValue!); - }, - items: ['One', 'Two', 'Three', 'Four'] - .map>((String value) { - return MacosPopupMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - ), - const SizedBox(width: 20), - MacosPopupButton( - disabledHint: const Text("Disabled"), - onChanged: null, - items: null, - ), - ], - ), - const SizedBox(height: 20), - MacosPopupButton( - value: languagePopupValue, - onChanged: (String? newValue) { - setState(() => languagePopupValue = newValue!); - }, - items: languages - .map>((String value) { - return MacosPopupMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - ), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('System Theme'), - const SizedBox(width: 8), - MacosRadioButton( - groupValue: context.watch().mode, - value: ThemeMode.system, - onChanged: (value) { - context.read().mode = value!; - }, - ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Light Theme'), - const SizedBox(width: 24), - MacosRadioButton( - groupValue: context.watch().mode, - value: ThemeMode.light, - onChanged: (value) { - context.read().mode = value!; - }, - ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Dark Theme'), - const SizedBox(width: 26), - MacosRadioButton( - groupValue: context.watch().mode, - value: ThemeMode.dark, - onChanged: (value) { - context.read().mode = value!; - }, - ), - ], - ), - const SizedBox(height: 20), - const Text('MacosSegmentedControl'), - const SizedBox(height: 8), - MacosSegmentedControl( - controller: _tabController, - tabs: [ - MacosTab( - label: 'Tab 1', - active: _tabController.index == 0, - ), - MacosTab( - label: 'Tab 2', - active: _tabController.index == 1, - ), - MacosTab( - label: 'Tab 3', - active: _tabController.index == 2, - ), - ], + const SizedBox(width: 20), + PushButton( + buttonSize: ButtonSize.large, + isSecondary: true, + child: const Text('Secondary'), + onPressed: () { + MacosWindowScope.of(context).toggleSidebar(); + }, + ), + ], + ), + const SizedBox(height: 20), + const Text('MacosSwitch'), + const SizedBox(height: 8), + MacosSwitch( + value: switchValue, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + const SizedBox(height: 20), + const Text('MacosPulldownButton'), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosPulldownButton( + title: "PDF", + items: [ + MacosPulldownMenuItem( + title: const Text('Open in Preview'), + onTap: () => + debugPrint("Opening in preview..."), + ), + MacosPulldownMenuItem( + title: const Text('Save as PDF...'), + onTap: () => debugPrint("Saving as PDF..."), + ), + MacosPulldownMenuItem( + enabled: false, + title: const Text('Save as Postscript'), + onTap: () => + debugPrint("Saving as Postscript..."), + ), + const MacosPulldownMenuDivider(), + MacosPulldownMenuItem( + enabled: false, + title: const Text('Save to iCloud Drive'), + onTap: () => + debugPrint("Saving to iCloud..."), + ), + MacosPulldownMenuItem( + enabled: false, + title: const Text('Save to Web Receipts'), + onTap: () => + debugPrint("Saving to Web Receipts..."), + ), + MacosPulldownMenuItem( + title: const Text('Send in Mail...'), + onTap: () => + debugPrint("Sending via Mail..."), + ), + const MacosPulldownMenuDivider(), + MacosPulldownMenuItem( + title: const Text('Edit Menu...'), + onTap: () => debugPrint("Editing menu..."), + ), + ], + ), + const SizedBox(width: 20), + const MacosPulldownButton( + title: "PDF", + disabledTitle: "Disabled", + items: [], + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosPulldownButton( + icon: CupertinoIcons.ellipsis_circle, + items: [ + MacosPulldownMenuItem( + title: const Text('New Folder'), + onTap: () => + debugPrint("Creating new folder..."), + ), + MacosPulldownMenuItem( + title: const Text('Open'), + onTap: () => debugPrint("Opening..."), + ), + MacosPulldownMenuItem( + title: const Text('Open with...'), + onTap: () => debugPrint("Opening with..."), + ), + MacosPulldownMenuItem( + title: const Text('Import from iPhone...'), + onTap: () => debugPrint("Importing..."), + ), + const MacosPulldownMenuDivider(), + MacosPulldownMenuItem( + enabled: false, + title: const Text('Remove'), + onTap: () => debugPrint("Deleting..."), + ), + MacosPulldownMenuItem( + title: const Text('Move to Bin'), + onTap: () => debugPrint("Moving to Bin..."), + ), + const MacosPulldownMenuDivider(), + MacosPulldownMenuItem( + title: const Text('Tags...'), + onTap: () => debugPrint("Tags..."), + ), + ], + ), + const SizedBox(width: 20), + const MacosPulldownButton( + icon: CupertinoIcons.square_grid_3x2, + items: [], + ), + ], + ), + const SizedBox(height: 20), + const Text('MacosPopupButton'), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosPopupButton( + value: popupValue, + onChanged: (String? newValue) { + setState(() => popupValue = newValue!); + }, + items: [ + 'One', + 'Two', + 'Three', + 'Four' + ].map>((String value) { + return MacosPopupMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + const SizedBox(width: 20), + MacosPopupButton( + disabledHint: const Text("Disabled"), + onChanged: null, + items: null, + ), + ], + ), + const SizedBox(height: 20), + MacosPopupButton( + value: languagePopupValue, + onChanged: (String? newValue) { + setState(() => languagePopupValue = newValue!); + }, + items: languages + .map>((String value) { + return MacosPopupMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('System Theme'), + const SizedBox(width: 8), + MacosRadioButton( + groupValue: context.watch().mode, + value: ThemeMode.system, + onChanged: (value) { + context.read().mode = value!; + }, + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Light Theme'), + const SizedBox(width: 24), + MacosRadioButton( + groupValue: context.watch().mode, + value: ThemeMode.light, + onChanged: (value) { + context.read().mode = value!; + }, + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Dark Theme'), + const SizedBox(width: 26), + MacosRadioButton( + groupValue: context.watch().mode, + value: ThemeMode.dark, + onChanged: (value) { + context.read().mode = value!; + }, + ), + ], + ), + const SizedBox(height: 20), + const Text('MacosSegmentedControl'), + const SizedBox(height: 8), + MacosSegmentedControl( + controller: _tabController, + tabs: [ + MacosTab( + label: 'Tab 1', + active: _tabController.index == 0, + ), + MacosTab( + label: 'Tab 2', + active: _tabController.index == 1, + ), + MacosTab( + label: 'Tab 3', + active: _tabController.index == 2, + ), + ], + ), + ], + ), ), - ], - ), + ), + ResizablePane( + minSize: 50, + startSize: 200, + //windowBreakpoint: 600, + builder: (_, __) { + return const Center( + child: Text('Resizable Pane'), + ); + }, + resizableSide: ResizableSide.top, + ) + ], ); }, ), ResizablePane( - minWidth: 180, - startWidth: 200, + minSize: 180, + startSize: 200, windowBreakpoint: 800, resizableSide: ResizableSide.left, builder: (_, __) { diff --git a/example/lib/pages/fields_page.dart b/example/lib/pages/fields_page.dart index c80ad815..c63e9f09 100644 --- a/example/lib/pages/fields_page.dart +++ b/example/lib/pages/fields_page.dart @@ -126,8 +126,8 @@ class _FieldsPageState extends State { }, ), ResizablePane( - minWidth: 180, - startWidth: 200, + minSize: 180, + startSize: 200, windowBreakpoint: 800, resizableSide: ResizableSide.left, builder: (_, __) { diff --git a/example/lib/pages/indicators_page.dart b/example/lib/pages/indicators_page.dart index 5daeaaf9..1dd4caf5 100644 --- a/example/lib/pages/indicators_page.dart +++ b/example/lib/pages/indicators_page.dart @@ -40,7 +40,7 @@ class _IndicatorsPageState extends State { children: [ CapacityIndicator( value: capacitorValue, - onChanged: (v) => setState(() => sliderValue = v), + onChanged: (v) => setState(() => capacitorValue = v), splits: 20, discrete: true, ), diff --git a/example/lib/pages/selectors_page.dart b/example/lib/pages/selectors_page.dart index e44a1936..8256a33e 100644 --- a/example/lib/pages/selectors_page.dart +++ b/example/lib/pages/selectors_page.dart @@ -30,6 +30,7 @@ class _SelectorsPageState extends State { ContentArea( builder: (context, scrollController) { return SingleChildScrollView( + controller: scrollController, padding: const EdgeInsets.all(20), child: Column( children: [ diff --git a/example/lib/pages/tabview_page.dart b/example/lib/pages/tabview_page.dart index 786b11b0..64d4d8ff 100644 --- a/example/lib/pages/tabview_page.dart +++ b/example/lib/pages/tabview_page.dart @@ -22,7 +22,7 @@ class _TabViewPageState extends State { ), children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Padding( padding: const EdgeInsets.all(24.0), child: MacosTabView( diff --git a/example/lib/pages/toolbar_page.dart b/example/lib/pages/toolbar_page.dart index af01f229..7492869b 100644 --- a/example/lib/pages/toolbar_page.dart +++ b/example/lib/pages/toolbar_page.dart @@ -158,11 +158,11 @@ class _ToolbarPageState extends State { children: [ ContentArea( builder: (context, scrollController) { - return SingleChildScrollView( - padding: const EdgeInsets.all(30), + return const SingleChildScrollView( + padding: EdgeInsets.all(30), child: Center( child: Column( - children: const [ + children: [ Text( "The toolbar appears below the title bar of the macOS app or integrates with it.", textAlign: TextAlign.center, diff --git a/example/pubspec.lock b/example/pubspec.lock index f6155c18..9d37f274 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "1.10.0" + version: "1.11.1" matcher: dependency: transitive description: diff --git a/lib/src/fields/search_field.dart b/lib/src/fields/search_field.dart index d7eddb63..2064ef9a 100644 --- a/lib/src/fields/search_field.dart +++ b/lib/src/fields/search_field.dart @@ -326,39 +326,39 @@ class _MacosSearchFieldState extends State> { } height += _kResultsOverlayMargin; - return MacosOverlayFilter( - borderRadius: _kBorderRadius, - color: MacosSearchFieldTheme.of(context).resultsBackgroundColor, - child: SizedBox( - height: height, - child: ListView.builder( - reverse: showOverlayAbove, - padding: const EdgeInsets.all(6.0), - itemCount: snapshot.data!.length, - itemBuilder: (context, index) { - var selectedItem = snapshot.data![index]!; - return _SearchResultItemButton( - resultHeight: widget.resultHeight, - onPressed: () { - searchController!.text = selectedItem.searchKey; - searchController!.selection = TextSelection.fromPosition( - TextPosition( - offset: searchController!.text.length, - ), - ); - selectedItem.onSelected?.call(); - // Hide the results - suggestionStream.sink.add(null); - if (widget.onResultSelected != null) { - widget.onResultSelected!(selectedItem); - } - }, - child: selectedItem.child ?? - Text( - selectedItem.searchKey, - ), - ); - }, + return TextFieldTapRegion( + child: MacosOverlayFilter( + borderRadius: _kBorderRadius, + color: MacosSearchFieldTheme.of(context).resultsBackgroundColor, + child: SizedBox( + height: height, + child: ListView.builder( + reverse: showOverlayAbove, + padding: const EdgeInsets.all(6.0), + itemCount: snapshot.data!.length, + itemBuilder: (context, index) { + var selectedItem = snapshot.data![index]!; + return _SearchResultItemButton( + resultHeight: widget.resultHeight, + onPressed: () { + searchController!.text = selectedItem.searchKey; + searchController!.selection = + TextSelection.fromPosition( + TextPosition( + offset: searchController!.text.length, + ), + ); + selectedItem.onSelected?.call(); + // Hide the results + suggestionStream.sink.add(null); + if (widget.onResultSelected != null) { + widget.onResultSelected!(selectedItem); + } + }, + child: selectedItem.child ?? Text(selectedItem.searchKey), + ); + }, + ), ), ), ); diff --git a/lib/src/layout/resizable_pane.dart b/lib/src/layout/resizable_pane.dart index c71ff8c2..68ccfbed 100644 --- a/lib/src/layout/resizable_pane.dart +++ b/lib/src/layout/resizable_pane.dart @@ -15,16 +15,19 @@ enum ResizableSide { /// The right side of the [ResizablePane]. right, + + /// The top side of the [ResizablePane]. + top, } /// {@template resizablePane} -/// A widget that can be resized horizontally. +/// A widget that can be resized horizontally or vertically. /// -/// The [builder], [minWidth] and [resizableSide] can not be null. -/// The [maxWidth] and the [windowBreakpoint] default to `500.00`. +/// The [builder], [minSize] and [resizableSide] can not be null. +/// The [maxSize] and the [windowBreakpoint] default to `500.00`. /// [isResizable] defaults to `true`. /// -/// The [startWidth] is the initial width. +/// The [startSize] is the initial width or height depending on the orientation of the pane. /// {@endtemplate} class ResizablePane extends StatefulWidget { /// {@macro resizablePane} @@ -32,19 +35,19 @@ class ResizablePane extends StatefulWidget { super.key, required this.builder, this.decoration, - this.maxWidth = 500.0, - required this.minWidth, + this.maxSize = 500.0, + required this.minSize, this.isResizable = true, required this.resizableSide, this.windowBreakpoint, - required this.startWidth, + required this.startSize, }) : assert( - maxWidth >= minWidth, - 'minWidth should not be more than maxWidth.', + maxSize >= minSize, + 'minSize should not be more than maxSize.', ), assert( - (startWidth >= minWidth) && (startWidth <= maxWidth), - 'startWidth must not be less than minWidth or more than maxWidth', + (startSize >= minSize) && (startSize <= maxSize), + 'startSize must not be less than minSize or more than maxWidth', ); /// The builder that creates a child to display in this widget, which will @@ -61,19 +64,34 @@ class ResizablePane extends StatefulWidget { /// resizable side of this widget. final bool isResizable; - /// Specifies the maximum width that this [ResizablePane] can have. + /// Specifies the maximum width or height that this [ResizablePane] can have + /// according to its orientation. + /// + /// The orientation is horizontal if the [resizableSide] is + /// [ResizableSide.left] or [ResizableSide.right] and vertical if the + /// [resizableSide] is [ResizableSide.top]). /// - /// The value can be null and defaults to `500.0`. - final double maxWidth; + /// If this value is null, it defaults to `500.0`. + final double maxSize; - /// Specifies the minimum width that this [ResizablePane] can have. - final double minWidth; + /// Specifies the minimum width of height that this [ResizablePane] can have + /// according to its orientation. + /// + /// The orientation is horizontal if the [resizableSide] is + /// [ResizableSide.left] or [ResizableSide.right] and vertical if the + /// [resizableSide] is [ResizableSide.top]. + final double minSize; - /// Specifies the width that this [ResizablePane] first starts width. + /// Specifies the width or height that this [ResizablePane] first starts with + /// according to its orientation. + /// + /// The orientation is horizontal if the [resizableSide] is + /// [ResizableSide.left] or [ResizableSide.right] and vertical if the + /// [resizableSide] is [ResizableSide.top]). /// - /// The [startWidth] should not be more than the [maxWidth] or - /// less than the [minWidth]. - final double startWidth; + /// The [startSize] should not be more than the [maxSize] or + /// less than the [minSize]. + final double startSize; /// Indicates the draggable side of the [ResizablePane] for resizing final ResizableSide resizableSide; @@ -86,21 +104,26 @@ class ResizablePane extends StatefulWidget { } class _ResizablePaneState extends State { - SystemMouseCursor _cursor = SystemMouseCursors.resizeColumn; + late SystemMouseCursor _cursor; final _scrollController = ScrollController(); - late double _width; - late double _dragStartWidth; + late double _size; + late double _dragStartSize; late double _dragStartPosition; Color get _dividerColor => MacosTheme.of(context).dividerColor; bool get _resizeOnRight => widget.resizableSide == ResizableSide.right; + bool get _resizeOnTop => widget.resizableSide == ResizableSide.top; + BoxDecoration get _decoration { final borderSide = BorderSide(color: _dividerColor); final right = Border(right: borderSide); final left = Border(left: borderSide); - return BoxDecoration(border: _resizeOnRight ? right : left).copyWith( + final top = Border(top: borderSide); + return BoxDecoration( + border: _resizeOnTop ? top : (_resizeOnRight ? right : left), + ).copyWith( color: widget.decoration?.color, border: widget.decoration?.border, borderRadius: widget.decoration?.borderRadius, @@ -112,51 +135,99 @@ class _ResizablePaneState extends State { ); } + BoxConstraints get _boxConstraint { + if (_resizeOnTop) { + return BoxConstraints( + maxHeight: widget.maxSize, + minHeight: widget.minSize, + ).normalize(); + } + return BoxConstraints( + maxWidth: widget.maxSize, + minWidth: widget.minSize, + ).normalize(); + } + Widget get _resizeArea { - return GestureDetector( - behavior: HitTestBehavior.opaque, - child: MouseRegion( - cursor: _cursor, - child: const SizedBox(width: 5), - ), - onHorizontalDragStart: (details) { - _dragStartWidth = _width; - _dragStartPosition = details.globalPosition.dx; - }, - onHorizontalDragUpdate: (details) { - setState(() { - final newWidth = _resizeOnRight - ? _dragStartWidth - - (_dragStartPosition - details.globalPosition.dx) - : _dragStartWidth + - (_dragStartPosition - details.globalPosition.dx); - _width = math.max( - widget.minWidth, - math.min( - widget.maxWidth, - newWidth, + return _resizeOnTop + ? GestureDetector( + behavior: HitTestBehavior.opaque, + child: MouseRegion( + cursor: _cursor, + child: const SizedBox(width: 5), ), + onVerticalDragStart: (details) { + _dragStartSize = _size; + _dragStartPosition = details.globalPosition.dy; + }, + onVerticalDragUpdate: (details) { + setState(() { + final newHeight = _dragStartSize + + (_dragStartPosition - details.globalPosition.dy); + _size = math.max( + widget.minSize, + math.min( + widget.maxSize, + newHeight, + ), + ); + if (_size == widget.minSize) { + _cursor = SystemMouseCursors.resizeUp; + } else if (_size == widget.maxSize) { + _cursor = SystemMouseCursors.resizeDown; + } else { + _cursor = SystemMouseCursors.resizeRow; + } + }); + }, + ) + : GestureDetector( + behavior: HitTestBehavior.opaque, + child: MouseRegion( + cursor: _cursor, + child: const SizedBox(width: 5), + ), + onHorizontalDragStart: (details) { + _dragStartSize = _size; + _dragStartPosition = details.globalPosition.dx; + }, + onHorizontalDragUpdate: (details) { + setState(() { + final newWidth = _resizeOnRight + ? _dragStartSize - + (_dragStartPosition - details.globalPosition.dx) + : _dragStartSize + + (_dragStartPosition - details.globalPosition.dx); + _size = math.max( + widget.minSize, + math.min( + widget.maxSize, + newWidth, + ), + ); + if (_size == widget.minSize) { + _cursor = _resizeOnRight + ? SystemMouseCursors.resizeRight + : SystemMouseCursors.resizeLeft; + } else if (_size == widget.maxSize) { + _cursor = _resizeOnRight + ? SystemMouseCursors.resizeLeft + : SystemMouseCursors.resizeRight; + } else { + _cursor = SystemMouseCursors.resizeColumn; + } + }); + }, ); - if (_width == widget.minWidth) { - _cursor = _resizeOnRight - ? SystemMouseCursors.resizeRight - : SystemMouseCursors.resizeLeft; - } else if (_width == widget.maxWidth) { - _cursor = _resizeOnRight - ? SystemMouseCursors.resizeLeft - : SystemMouseCursors.resizeRight; - } else { - _cursor = SystemMouseCursors.resizeColumn; - } - }); - }, - ); } @override void initState() { super.initState(); - _width = widget.startWidth; + _cursor = _resizeOnTop + ? SystemMouseCursors.resizeRow + : SystemMouseCursors.resizeColumn; + _size = widget.startSize; _scrollController.addListener(() => setState(() {})); } @@ -164,12 +235,12 @@ class _ResizablePaneState extends State { void didUpdateWidget(covariant ResizablePane oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.windowBreakpoint != widget.windowBreakpoint || - oldWidget.minWidth != widget.minWidth || - oldWidget.maxWidth != widget.maxWidth || + oldWidget.minSize != widget.minSize || + oldWidget.maxSize != widget.maxSize || oldWidget.resizableSide != widget.resizableSide) { setState(() { - if (widget.minWidth > _width) _width = widget.minWidth; - if (widget.maxWidth < _width) _width = widget.maxWidth; + if (widget.minSize > _size) _size = widget.minSize; + if (widget.maxSize < _size) _size = widget.maxSize; }); } } @@ -186,19 +257,23 @@ class _ResizablePaneState extends State { final maxHeight = media.size.height; final maxWidth = media.size.width; - if (widget.windowBreakpoint != null && - maxWidth <= widget.windowBreakpoint!) { - return const SizedBox.shrink(); + if (_resizeOnTop) { + if (widget.windowBreakpoint != null && + maxHeight <= widget.windowBreakpoint!) { + return const SizedBox.shrink(); + } + } else { + if (widget.windowBreakpoint != null && + maxWidth <= widget.windowBreakpoint!) { + return const SizedBox.shrink(); + } } return Container( - width: _width, - height: maxHeight, + width: _resizeOnTop ? maxWidth : _size, + height: _resizeOnTop ? _size : maxHeight, decoration: _decoration, - constraints: BoxConstraints( - maxWidth: widget.maxWidth, - minWidth: widget.minWidth, - ).normalize(), + constraints: _boxConstraint, child: Stack( children: [ SafeArea( @@ -209,7 +284,7 @@ class _ResizablePaneState extends State { child: widget.builder(context, _scrollController), ), ), - if (widget.isResizable && !_resizeOnRight) + if (widget.isResizable && !_resizeOnRight && !_resizeOnTop) Positioned( left: 0, width: 5, @@ -223,6 +298,13 @@ class _ResizablePaneState extends State { height: maxHeight, child: _resizeArea, ), + if (widget.isResizable && _resizeOnTop) + Positioned( + top: 0, + width: maxWidth, + height: 5, + child: _resizeArea, + ), ], ), ); diff --git a/lib/src/layout/toolbar/toolbar.dart b/lib/src/layout/toolbar/toolbar.dart index 01a3c12d..294a24a6 100644 --- a/lib/src/layout/toolbar/toolbar.dart +++ b/lib/src/layout/toolbar/toolbar.dart @@ -89,15 +89,17 @@ class ToolBar extends StatefulWidget { /// Typically the [leading] widget is a [MacosIcon] or a [MacosIconButton]. final Widget? leading; - /// Controls whether we should try to imply the leading widget if null. + /// Controls whether the toolbar should try to imply if the [leading] widget + /// is null. /// - /// If `true` and [leading] is null, automatically try to deduce what the leading - /// widget should be. If `false` and [leading] is null, leading space is given to [title]. - /// If leading widget is not null, this parameter has no effect. + /// If `true` and [leading] are null, the toolbar will automatically try to + /// deduce what the leading widget should be. If `false` and [leading] is + /// null, leading space is given to [title]. If the [leading] widget is not + /// null, this parameter has no effect. final bool automaticallyImplyLeading; - /// A list of [ToolbarItem] widgets to display in a row after the [title] widget, - /// as the toolbar actions. + /// A list of [ToolbarItem] widgets to display in a row after the [title] + /// widget, as the toolbar actions. /// /// Toolbar items include [ToolBarIconButton], [ToolBarPulldownButton], /// [ToolBarSpacer], and [CustomToolbarItem] widgets. @@ -108,14 +110,14 @@ class ToolBar extends StatefulWidget { /// at the right edge of the toolbar. final List? actions; - /// Whether the title should be centered. + /// Whether the [title] should be centered. final bool centerTitle; /// The color of the divider below the toolbar. /// - /// Defaults to MacosTheme.of(context).dividerColor. + /// Defaults to `MacosTheme.of(context).dividerColor`. /// - /// Set it to MacosColors.transparent to remove. + /// Set this to `MacosColors.transparent` to remove. final Color? dividerColor; @override diff --git a/pr_prelaunch_tasks.sh b/pr_prelaunch_tasks.sh deleted file mode 100644 index b7d2ff83..00000000 --- a/pr_prelaunch_tasks.sh +++ /dev/null @@ -1,39 +0,0 @@ -dart format --set-exit-if-changed . -if [ $? -eq 1 ]; then - dart format lib - git add . - git commit -m "chore: run flutter format ." - echo "push changes? [y/n]" - read -r pushResponse - if [ "$pushResponse" = "y" ]; then - git push origin - fi -fi -echo "Run dart fix --dry-run? [y/n]" -read -r dryRunResponse -if [ "$dryRunResponse" = "y" ]; then - dart fix --dry-run -fi -echo "Run dart fix --apply? [y/n]" -read -r applyResponse -if [ "$applyResponse" = "y" ]; then - dart fix --apply - if [ -z "$(git status --porcelain)" ]; then - echo "No changes to commit" - else - git add . - git commit -m "chore: run dart fix --apply" - echo "push changes? [y/n]" - read -r pushResponse - if [ "$pushResponse" = "y" ]; then - git push origin - fi - fi -fi -echo "Run tests? [y/n]" -read -r testResponse -if [ "$testResponse" = "y" ]; then - flutter test -else - exit 0 -fi \ No newline at end of file diff --git a/publish_tasks.sh b/publish_tasks.sh deleted file mode 100644 index a6aa33e1..00000000 --- a/publish_tasks.sh +++ /dev/null @@ -1,16 +0,0 @@ -# MAINTAINER ONLY SCRIPT. DO NOT RUN THIS SCRIPT UNLESS YOU ARE THE MAINTAINER. -pana --no-warning -echo "Are you ready to dry-run publish macos_ui? [y/n]" -read -r dryRunResponse -if [ "$dryRunResponse" = "y" ]; then - flutter pub publish --dry-run -else - exit 0 -fi -echo "Are you ready to publish macos_ui to pub.dev? [y/n]" -read -r publishResponse -if [ "$publishResponse" = "y" ]; then - flutter pub publish -else - exit 0 -fi \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 7b6b263b..d1760775 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 1.10.0 +version: 1.11.1 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" diff --git a/test/buttons/checkbox_test.dart b/test/buttons/checkbox_test.dart index 9ba9c1a5..09881007 100644 --- a/test/buttons/checkbox_test.dart +++ b/test/buttons/checkbox_test.dart @@ -12,7 +12,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return StatefulBuilder( builder: (context, setState) { return MacosCheckbox( diff --git a/test/buttons/help_button_test.dart b/test/buttons/help_button_test.dart index 6124e5c0..2f11f430 100644 --- a/test/buttons/help_button_test.dart +++ b/test/buttons/help_button_test.dart @@ -24,7 +24,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return HelpButton( onPressed: mockOnPressedFunction.handler, ); diff --git a/test/buttons/icon_button_test.dart b/test/buttons/icon_button_test.dart index a4c1e543..057f6a3b 100644 --- a/test/buttons/icon_button_test.dart +++ b/test/buttons/icon_button_test.dart @@ -19,7 +19,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return MacosIconButton( icon: const Icon(CupertinoIcons.add), onPressed: mockOnPressedFunction.handler, diff --git a/test/buttons/popup_button_test.dart b/test/buttons/popup_button_test.dart index d685d3da..704501ef 100644 --- a/test/buttons/popup_button_test.dart +++ b/test/buttons/popup_button_test.dart @@ -17,7 +17,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return StatefulBuilder( builder: (context, setState) { return MacosPopupButton( diff --git a/test/buttons/pulldown_button_test.dart b/test/buttons/pulldown_button_test.dart index aa42cc83..e22a355e 100644 --- a/test/buttons/pulldown_button_test.dart +++ b/test/buttons/pulldown_button_test.dart @@ -22,7 +22,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Center( child: MacosPulldownButton( title: "test", diff --git a/test/buttons/push_button_test.dart b/test/buttons/push_button_test.dart index 99fe95ae..96dc571e 100644 --- a/test/buttons/push_button_test.dart +++ b/test/buttons/push_button_test.dart @@ -25,7 +25,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return PushButton( buttonSize: ButtonSize.small, onPressed: mockOnPressedFunction.handler, diff --git a/test/buttons/radio_button_test.dart b/test/buttons/radio_button_test.dart index cb51f3b3..d561b643 100644 --- a/test/buttons/radio_button_test.dart +++ b/test/buttons/radio_button_test.dart @@ -20,7 +20,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Center( child: MacosRadioButton( value: TestOptions.first, diff --git a/test/buttons/segmented_control_test.dart b/test/buttons/segmented_control_test.dart index 1ccf4a74..62d19796 100644 --- a/test/buttons/segmented_control_test.dart +++ b/test/buttons/segmented_control_test.dart @@ -14,7 +14,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Center( child: MacosSegmentedControl( controller: controller, diff --git a/test/buttons/switch_test.dart b/test/buttons/switch_test.dart index 75c3e863..642f0f11 100644 --- a/test/buttons/switch_test.dart +++ b/test/buttons/switch_test.dart @@ -12,7 +12,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Center( child: MacosSwitch( value: selected, diff --git a/test/fields/search_field_test.dart b/test/fields/search_field_test.dart index a6f1a750..4fdbc863 100644 --- a/test/fields/search_field_test.dart +++ b/test/fields/search_field_test.dart @@ -29,10 +29,11 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Center( child: SizedBox( width: 300.0, + height: 500.0, child: MacosSearchField( results: kOptions.map((e) => SearchResultItem(e)).toList(), @@ -64,6 +65,15 @@ void main() { ListView list = find.byType(ListView).evaluate().first.widget as ListView; // 'chameleon' and 'elephant' are displayed. expect(list.semanticChildCount, 2); + + await tester.ensureVisible(find.text('elephant')); + await tester.pump(); + + await tester.tap(find.text('elephant')); + await tester.pump(); + + expect(controller.text, 'elephant'); + expect(find.byType(ListView), findsNothing); }, ); } diff --git a/test/fields/text_field_test.dart b/test/fields/text_field_test.dart index d83ce27b..71b12568 100644 --- a/test/fields/text_field_test.dart +++ b/test/fields/text_field_test.dart @@ -12,7 +12,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Center( child: MacosTextField( controller: controller, diff --git a/test/layout/macos_list_tile_test.dart b/test/layout/macos_list_tile_test.dart index 6f86b952..e019a007 100644 --- a/test/layout/macos_list_tile_test.dart +++ b/test/layout/macos_list_tile_test.dart @@ -23,7 +23,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return MacosListTile( title: const Text('List Tile'), onClick: mockOnPressedFunction.handler, diff --git a/test/layout/resizeable_pane_test.dart b/test/layout/resizeable_pane_test.dart index bae5ac4d..86638a6a 100644 --- a/test/layout/resizeable_pane_test.dart +++ b/test/layout/resizeable_pane_test.dart @@ -7,150 +7,226 @@ void main() { group('ResizablePane', () { for (var side in matrix) { - group(side == ResizableSide.left ? "left" : "right", () { - const double maxWidth = 300; - const double minWidth = 100; - const double startWidth = 200; - - final resizablePane = ResizablePane( - builder: (context, scrollController) => const Text('Hello there'), - minWidth: minWidth, - startWidth: startWidth, - maxWidth: maxWidth, - resizableSide: side, - ); - - final view = MacosApp( - home: MacosWindow( - child: MacosScaffold( - children: [ - resizablePane, - ContentArea( - builder: (context, scrollController) { - return const Text('Hello there'); - }, - ), - ], - ), - ), - ); - - final resizablePaneFinder = find.byWidget(resizablePane); - final dragFinder = find.descendant( - of: resizablePaneFinder, - matching: find.byType(GestureDetector), - ); - - final directionModifier = side == ResizableSide.right ? 1 : -1; - final double safeDelta = 50.0 * directionModifier; - final double overflowDelta = 500.0 * directionModifier; - - testWidgets('initial width equals startWidth', (tester) async { - await tester.pumpWidget(view); - - var resizablePaneRenderObject = - tester.renderObject(resizablePaneFinder); - expect(resizablePaneRenderObject.size.width, startWidth); - }); - - testWidgets('dragging wider works', (tester) async { - await tester.pumpWidget(view); - - await tester.drag( - dragFinder, - Offset(safeDelta, 0), + bool verticallyResizable = side == ResizableSide.top; + + group( + side == ResizableSide.top + ? 'top' + : (side == ResizableSide.left ? 'left' : 'right'), + () { + const double maxSize = 300; + const double minSize = 100; + const double startSize = 200; + + final resizablePane = ResizablePane( + builder: (context, scrollController) => const Text('Hello there'), + minSize: minSize, + startSize: startSize, + maxSize: maxSize, + resizableSide: side, ); - await tester.pump(); - var resizablePaneRenderObject = - tester.renderObject(resizablePaneFinder); - expect( - resizablePaneRenderObject.size.width, - startWidth + safeDelta * directionModifier, + final view = side == ResizableSide.top + ? MacosApp( + home: MacosWindow( + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, scrollController) { + return Column( + children: [ + const Flexible( + fit: FlexFit.loose, + child: Center( + child: Text('Hello there'), + ), + ), + resizablePane, + ], + ); + }, + ), + ], + ), + ), + ) + : MacosApp( + home: MacosWindow( + child: MacosScaffold( + children: [ + resizablePane, + ContentArea( + builder: (context, scrollController) { + return const Text('Hello there'); + }, + ), + ], + ), + ), + ); + + final resizablePaneFinder = find.byWidget(resizablePane); + final dragFinder = find.descendant( + of: resizablePaneFinder, + matching: find.byType(GestureDetector), ); - }); - testWidgets('dragging wider respects maxWidth', (tester) async { - await tester.pumpWidget(view); + // No need to check if the resizable side is top because directionModifier + // would take -1 if it is the case + final directionModifier = side == ResizableSide.right ? 1 : -1; + final double safeDelta = 50.0 * directionModifier; + final double overflowDelta = 500.0 * directionModifier; - await tester.drag( - dragFinder, - Offset(overflowDelta, 0), - ); - await tester.pump(); + testWidgets('initial size equals startSize', (tester) async { + await tester.pumpWidget(view); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var initialSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; - var resizablePaneRenderObject = - tester.renderObject(resizablePaneFinder); - expect(resizablePaneRenderObject.size.width, maxWidth); - }); + expect(initialSize, startSize); + }); - testWidgets( - 'drag events past maxWidth have no effect', - (tester) async { + testWidgets('dragging wider works $side', (tester) async { await tester.pumpWidget(view); - final dragStartLocation = tester.getCenter(dragFinder); - final drag = await tester.startGesture(dragStartLocation); - await drag.moveBy(Offset(overflowDelta, 0)); - await drag.moveBy(Offset(-10.0 * directionModifier, 0)); - await drag.up(); + await tester.drag( + dragFinder, + verticallyResizable ? Offset(0, safeDelta) : Offset(safeDelta, 0), + ); await tester.pump(); var resizablePaneRenderObject = tester.renderObject(resizablePaneFinder); - expect(resizablePaneRenderObject.size.width, maxWidth); - }, - ); - - testWidgets('dragging narrower works', (tester) async { - await tester.pumpWidget(view); + expect( + verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width, + startSize + safeDelta * directionModifier, + ); + }); + + testWidgets('dragging wider respects maxSize', (tester) async { + await tester.pumpWidget(view); - await tester.drag( - dragFinder, - Offset(-safeDelta, 0), - ); - await tester.pump(); + await tester.drag( + dragFinder, + verticallyResizable + ? Offset(0, overflowDelta) + : Offset(overflowDelta, 0), + ); + await tester.pump(); - var resizablePaneRenderObject = - tester.renderObject(resizablePaneFinder); - expect( - resizablePaneRenderObject.size.width, - startWidth - safeDelta * directionModifier, + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var currentSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + expect(currentSize, maxSize); + }); + + testWidgets( + 'drag events past maxSize have no effect $side', + (tester) async { + await tester.pumpWidget(view); + + final dragStartLocation = tester.getCenter(dragFinder); + final drag = await tester.startGesture(dragStartLocation); + await drag.moveBy( + verticallyResizable + ? Offset(0, overflowDelta) + : Offset(overflowDelta, 0), + ); + await drag.moveBy( + verticallyResizable + ? Offset(0, -10.0 * directionModifier) + : Offset(-10.0 * directionModifier, 0), + ); + await drag.up(); + await tester.pump(); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var currentSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + expect(currentSize, maxSize); + }, ); - }); - - testWidgets('dragging narrower respects minWidth', (tester) async { - await tester.pumpWidget(view); - await tester.drag( - dragFinder, - Offset(-overflowDelta, 0), - ); - await tester.pump(); + testWidgets('dragging narrower works', (tester) async { + await tester.pumpWidget(view); - var resizablePaneRenderObject = - tester.renderObject(resizablePaneFinder); - expect(resizablePaneRenderObject.size.width, minWidth); - }); + await tester.drag( + dragFinder, + verticallyResizable + ? Offset(0, -safeDelta) + : Offset(-safeDelta, 0), + ); + await tester.pump(); - testWidgets( - 'drag events past minWidth have no effect', - (tester) async { + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var currentSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + expect( + currentSize, + startSize - safeDelta * directionModifier, + ); + }); + + testWidgets('dragging narrower respects minSize', (tester) async { await tester.pumpWidget(view); - final dragStartLocation = tester.getCenter(dragFinder); - final drag = await tester.startGesture(dragStartLocation); - await drag.moveBy(Offset(-overflowDelta, 0)); - await drag.moveBy(Offset(10.0 * directionModifier, 0)); - await drag.up(); + await tester.drag( + dragFinder, + verticallyResizable + ? Offset(0, -overflowDelta) + : Offset(-overflowDelta, 0), + ); await tester.pump(); var resizablePaneRenderObject = tester.renderObject(resizablePaneFinder); - expect(resizablePaneRenderObject.size.width, minWidth); - }, - ); - }); + var currentSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + expect(currentSize, minSize); + }); + + testWidgets( + 'drag events past minSize have no effect', + (tester) async { + await tester.pumpWidget(view); + + final dragStartLocation = tester.getCenter(dragFinder); + final drag = await tester.startGesture(dragStartLocation); + await drag.moveBy( + verticallyResizable + ? Offset(0, -overflowDelta) + : Offset(-overflowDelta, 0), + ); + await drag.moveBy( + verticallyResizable + ? Offset(0, 10.0 * directionModifier) + : Offset(10.0 * directionModifier, 0), + ); + await drag.up(); + await tester.pump(); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var currentSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + expect(currentSize, minSize); + }, + ); + }, + ); } }); } diff --git a/test/layout/tab_view_test.dart b/test/layout/tab_view_test.dart index 3673f4d1..2fb09c07 100644 --- a/test/layout/tab_view_test.dart +++ b/test/layout/tab_view_test.dart @@ -14,7 +14,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Padding( padding: const EdgeInsets.all(24.0), child: MacosTabView( diff --git a/test/selectors/date_picker_test.dart b/test/selectors/date_picker_test.dart index e6d36763..f0c5afbc 100644 --- a/test/selectors/date_picker_test.dart +++ b/test/selectors/date_picker_test.dart @@ -15,7 +15,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Center( child: MacosDatePicker( onDateChanged: (date) {}, @@ -262,7 +262,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { return Center( child: MacosDatePicker( onDateChanged: (date) { diff --git a/test/theme/help_button_theme_test.dart b/test/theme/help_button_theme_test.dart index cd93ac74..702a85ed 100644 --- a/test/theme/help_button_theme_test.dart +++ b/test/theme/help_button_theme_test.dart @@ -59,7 +59,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { capturedContext = context; return const HelpButton(); }, diff --git a/test/theme/icon_button_theme_test.dart b/test/theme/icon_button_theme_test.dart index 92832505..af7a40e6 100644 --- a/test/theme/icon_button_theme_test.dart +++ b/test/theme/icon_button_theme_test.dart @@ -66,7 +66,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { capturedContext = context; return MacosIconButton( icon: const Icon(CupertinoIcons.add), diff --git a/test/theme/icon_theme_test.dart b/test/theme/icon_theme_test.dart index 23182817..8e98f80a 100644 --- a/test/theme/icon_theme_test.dart +++ b/test/theme/icon_theme_test.dart @@ -66,7 +66,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { capturedContext = context; return const MacosIcon( CupertinoIcons.add, diff --git a/test/theme/popup_button_theme_test.dart b/test/theme/popup_button_theme_test.dart index 710f2f49..daa039de 100644 --- a/test/theme/popup_button_theme_test.dart +++ b/test/theme/popup_button_theme_test.dart @@ -70,7 +70,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { capturedContext = context; return MacosPopupButton( value: popupValue, diff --git a/test/theme/pulldown_button_theme_test.dart b/test/theme/pulldown_button_theme_test.dart index 44264874..373ac0c4 100644 --- a/test/theme/pulldown_button_theme_test.dart +++ b/test/theme/pulldown_button_theme_test.dart @@ -69,7 +69,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { capturedContext = context; return const Center( child: MacosPulldownButton( diff --git a/test/theme/push_button_theme_test.dart b/test/theme/push_button_theme_test.dart index 8ced6d85..5bcc4ef1 100644 --- a/test/theme/push_button_theme_test.dart +++ b/test/theme/push_button_theme_test.dart @@ -61,7 +61,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { capturedContext = context; return const PushButton( buttonSize: ButtonSize.small, diff --git a/test/theme/search_field_theme_test.dart b/test/theme/search_field_theme_test.dart index 866f83cc..da838f37 100644 --- a/test/theme/search_field_theme_test.dart +++ b/test/theme/search_field_theme_test.dart @@ -65,7 +65,7 @@ void main() { child: MacosScaffold( children: [ ContentArea( - builder: (context, scrollController) { + builder: (context, _) { capturedContext = context; return const Center( child: MacosSearchField(), From 079b51fa313c520c0325e2f486b48f2df335c92f Mon Sep 17 00:00:00 2001 From: Minas Giannekas Date: Fri, 3 Mar 2023 16:54:03 +0100 Subject: [PATCH 04/45] When toolbar item is clicked, first pop the route and then call its callback - Fixes #346 (#381) * fix: when a toolbar item is clicked, remove route first and then call its callback - fixes #346 * chore: update version and changelog --- CHANGELOG.md | 3 +++ example/pubspec.lock | 2 +- lib/src/layout/toolbar/toolbar_overflow_menu_item.dart | 2 +- pubspec.yaml | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a944e6cd..50742fa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [1.12.2] +* Fixed a bug where clicking on a overflowed toolbar item with a navigation callback wouldn't work ([#346](https://github.com/GroovinChip/macos_ui/issues/346)). + ## [1.12.1+1] * Fixed a typo in the December abbreviation displayed in the `MacosDatePicker`. diff --git a/example/pubspec.lock b/example/pubspec.lock index 06d01493..cf8e59b8 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "1.12.1+1" + version: "1.12.2" matcher: dependency: transitive description: diff --git a/lib/src/layout/toolbar/toolbar_overflow_menu_item.dart b/lib/src/layout/toolbar/toolbar_overflow_menu_item.dart index ed4ff69f..e92c57c2 100644 --- a/lib/src/layout/toolbar/toolbar_overflow_menu_item.dart +++ b/lib/src/layout/toolbar/toolbar_overflow_menu_item.dart @@ -54,8 +54,8 @@ class _ToolbarOverflowMenuItemState extends State { } void _handleOnTap() { - widget.onPressed?.call(); Navigator.pop(context); + widget.onPressed?.call(); } bool get _isHighlighted => _isHovered || widget.isSelected == true; diff --git a/pubspec.yaml b/pubspec.yaml index 2971ef05..4d660503 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 1.12.1+1 +version: 1.12.2 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From fc6a26bb2c1ecfee71dc17a2dff7d3512e431412 Mon Sep 17 00:00:00 2001 From: Jonas Schlauch <72231111+jtdLab@users.noreply.github.com> Date: Mon, 20 Mar 2023 16:10:40 +0100 Subject: [PATCH 05/45] feat: add support for `routerConfig` to `MacosApp.router` (#390) * feat: add support for routerConfig to MacosApp.router * remove required keywords in constructor * rm ! in _buildMacosApp * Update CHANGELOG.md Co-authored-by: Reuben Turner --------- Co-authored-by: Reuben Turner --- CHANGELOG.md | 3 +++ example/pubspec.lock | 2 +- lib/src/macos_app.dart | 22 +++++++++++++++------- pubspec.yaml | 2 +- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50742fa2..c79798a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [1.12.3] +* Added support for `routerConfig` to `MacosApp.router`. ([#388](https://github.com/macosui/macos_ui/issues/388)) + ## [1.12.2] * Fixed a bug where clicking on a overflowed toolbar item with a navigation callback wouldn't work ([#346](https://github.com/GroovinChip/macos_ui/issues/346)). diff --git a/example/pubspec.lock b/example/pubspec.lock index cf8e59b8..546efaa5 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "1.12.2" + version: "1.12.3" matcher: dependency: transitive description: diff --git a/lib/src/macos_app.dart b/lib/src/macos_app.dart index c4068f30..a430ce1b 100644 --- a/lib/src/macos_app.dart +++ b/lib/src/macos_app.dart @@ -74,15 +74,17 @@ class MacosApp extends StatefulWidget { }) : routeInformationProvider = null, routeInformationParser = null, routerDelegate = null, - backButtonDispatcher = null; + backButtonDispatcher = null, + routerConfig = null; /// Creates a [MacosApp] that uses the [Router] instead of a [Navigator]. MacosApp.router({ super.key, this.routeInformationProvider, - required RouteInformationParser this.routeInformationParser, - required RouterDelegate this.routerDelegate, + this.routeInformationParser, + this.routerDelegate, this.backButtonDispatcher, + this.routerConfig, this.builder, this.title = '', this.onGenerateTitle, @@ -104,7 +106,8 @@ class MacosApp extends StatefulWidget { this.themeMode, this.theme, this.darkTheme, - }) : assert(supportedLocales.isNotEmpty), + }) : assert(routerDelegate != null || routerConfig != null), + assert(supportedLocales.isNotEmpty), navigatorObservers = null, navigatorKey = null, onGenerateRoute = null, @@ -157,6 +160,9 @@ class MacosApp extends StatefulWidget { /// {@macro flutter.widgets.widgetsApp.backButtonDispatcher} final BackButtonDispatcher? backButtonDispatcher; + /// {@macro flutter.widgets.widgetsApp.routerConfig} + final RouterConfig? routerConfig; + /// {@macro flutter.widgets.widgetsApp.builder} final TransitionBuilder? builder; @@ -300,7 +306,8 @@ class MacosApp extends StatefulWidget { } class _MacosAppState extends State { - bool get _usesRouter => widget.routerDelegate != null; + bool get _usesRouter => + widget.routerDelegate != null || widget.routerConfig != null; Widget _macosBuilder(BuildContext context, Widget? child) { final mode = widget.themeMode ?? ThemeMode.system; @@ -346,9 +353,10 @@ class _MacosAppState extends State { return c.CupertinoApp.router( key: GlobalObjectKey(this), routeInformationProvider: widget.routeInformationProvider, - routeInformationParser: widget.routeInformationParser!, - routerDelegate: widget.routerDelegate!, + routeInformationParser: widget.routeInformationParser, + routerDelegate: widget.routerDelegate, backButtonDispatcher: widget.backButtonDispatcher, + routerConfig: widget.routerConfig, builder: _macosBuilder, title: widget.title, onGenerateTitle: widget.onGenerateTitle, diff --git a/pubspec.yaml b/pubspec.yaml index 4d660503..7e1d73a1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 1.12.2 +version: 1.12.3 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From fcf6ea1e72fa090cfc3a0bcfd836d81751c7ccec Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Tue, 21 Mar 2023 10:41:57 -0400 Subject: [PATCH 06/45] DCM lint updates and related fixes (#393) * lints: add `double-literal-format` lint and fixes * lints: add `prefer-first`, `prefer-last`, and `prefer-immediate-return` Includes fixes * chore: remove a redundant `async` * fix: avoid non-const or final global state * lints: add always-remove-listener and fix * lints: add `avoid-unnecessary-setstate` and fixes * lints: add `avoid-wrapping-in-padding` and fixes * lints: add `prefer-const-border-radius` and fixes * lints: add `prefer-correct-edge-insets-constructor` and fixes * lints: add `use-setstate-synchronously` and fixes * lints: remove `number-of-parameters` * fix(MacosTextField): remove FocusNode listener instead of disposing FocusNode * chore(actions): split out tests into their own workflow --- .github/workflows/flutter_analysis.yml | 4 -- .github/workflows/test.yaml | 19 +++++++ analysis_options.yaml | 12 ++++- lib/src/buttons/back_button.dart | 2 +- lib/src/buttons/checkbox.dart | 4 +- lib/src/buttons/disclosure_button.dart | 2 +- lib/src/buttons/icon_button.dart | 2 +- lib/src/buttons/popup_button.dart | 7 ++- lib/src/buttons/pulldown_button.dart | 6 +-- lib/src/buttons/segmented_control.dart | 4 +- .../buttons/toolbar/toolbar_icon_button.dart | 2 +- lib/src/fields/text_field.dart | 3 +- lib/src/indicators/progress_indicators.dart | 4 +- lib/src/indicators/slider.dart | 17 ++++--- lib/src/layout/resizable_pane.dart | 6 +-- lib/src/layout/scrollbar.dart | 2 +- lib/src/layout/sidebar/sidebar_items.dart | 9 ++-- lib/src/layout/toolbar/sliver_toolbar.dart | 3 +- lib/src/layout/toolbar/toolbar_divider.dart | 10 +--- lib/src/layout/toolbar/toolbar_popup.dart | 5 +- lib/src/layout/window.dart | 50 ++++++++++--------- lib/src/macos_app.dart | 3 +- lib/src/selectors/color_well.dart | 2 +- lib/src/selectors/date_picker.dart | 33 ++++++------ lib/src/theme/macos_theme.dart | 4 +- lib/src/theme/tooltip_theme.dart | 2 +- lib/src/theme/typography.dart | 6 ++- test/buttons/pulldown_button_test.dart | 2 +- 28 files changed, 122 insertions(+), 103 deletions(-) create mode 100644 .github/workflows/test.yaml diff --git a/.github/workflows/flutter_analysis.yml b/.github/workflows/flutter_analysis.yml index 4a4b58ff..fba04aa4 100644 --- a/.github/workflows/flutter_analysis.yml +++ b/.github/workflows/flutter_analysis.yml @@ -34,7 +34,3 @@ jobs: - name: Analyze code run: flutter analyze --fatal-infos . - - - name: Test code - run: flutter test - diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 00000000..8c64e72e --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,19 @@ +name: Flutter Analysis +on: [pull_request, workflow_dispatch] + +jobs: + package-analysis: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Install Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + + - name: Install dependencies + run: flutter pub get + + - name: Test code + run: flutter test diff --git a/analysis_options.yaml b/analysis_options.yaml index d4a906dd..74448811 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -14,13 +14,23 @@ analyzer: dart_code_metrics: metrics: cyclomatic-complexity: 20 - number-of-parameters: 4 maximum-nesting-level: 5 metrics-exclude: - test/** - example/test/** rules: - prefer-trailing-comma + - double-literal-format + - prefer-first + - prefer-last + - prefer-immediate-return + - avoid-global-state + - always-remove-listener + - avoid-unnecessary-setstate + - avoid-wrapping-in-padding + - prefer-const-border-radius + - prefer-correct-edge-insets-constructor + - use-setstate-synchronously - member-ordering: alphabetize: false order: diff --git a/lib/src/buttons/back_button.dart b/lib/src/buttons/back_button.dart index 5bce067b..605356a4 100644 --- a/lib/src/buttons/back_button.dart +++ b/lib/src/buttons/back_button.dart @@ -196,7 +196,7 @@ class MacosBackButtonState extends State : _isHovered ? hoverColor : fillColor, - borderRadius: BorderRadius.circular(7), + borderRadius: const BorderRadius.all(Radius.circular(7)), ), child: Icon( CupertinoIcons.back, diff --git a/lib/src/buttons/checkbox.dart b/lib/src/buttons/checkbox.dart index 93e0b1c0..1ad46d62 100644 --- a/lib/src/buttons/checkbox.dart +++ b/lib/src/buttons/checkbox.dart @@ -106,7 +106,7 @@ class MacosCheckbox extends StatelessWidget { : activeColor ?? theme.primaryColor, context, ), - borderRadius: BorderRadius.circular(4.0), + borderRadius: const BorderRadius.all(Radius.circular(4.0)), ) : BoxDecoration( color: isLight ? null : CupertinoColors.tertiaryLabel, @@ -118,7 +118,7 @@ class MacosCheckbox extends StatelessWidget { context, ), ), - borderRadius: BorderRadius.circular(4.0), + borderRadius: const BorderRadius.all(Radius.circular(4.0)), ), child: Icon( isDisabled || value == false diff --git a/lib/src/buttons/disclosure_button.dart b/lib/src/buttons/disclosure_button.dart index 3a8a3d6b..5cef6e0e 100644 --- a/lib/src/buttons/disclosure_button.dart +++ b/lib/src/buttons/disclosure_button.dart @@ -177,7 +177,7 @@ class MacosDisclosureButtonState extends State ? const MacosColor(0xff3C383C) : const MacosColor(0xffE5E5E5) : fillColor, - borderRadius: BorderRadius.circular(7), + borderRadius: const BorderRadius.all(Radius.circular(7)), ), child: RotatedBox( quarterTurns: widget.isPressed ? 1 : 3, diff --git a/lib/src/buttons/icon_button.dart b/lib/src/buttons/icon_button.dart index 9b339aa7..88e8ef20 100644 --- a/lib/src/buttons/icon_button.dart +++ b/lib/src/buttons/icon_button.dart @@ -253,7 +253,7 @@ class MacosIconButtonState extends State borderRadius: widget.borderRadius != null ? widget.borderRadius : widget.shape == BoxShape.rectangle - ? BorderRadius.circular(7.0) + ? const BorderRadius.all(Radius.circular(7)) : null, color: !enabled ? disabledColor diff --git a/lib/src/buttons/popup_button.dart b/lib/src/buttons/popup_button.dart index a08cb113..8158e63c 100644 --- a/lib/src/buttons/popup_button.dart +++ b/lib/src/buttons/popup_button.dart @@ -1091,8 +1091,7 @@ class _MacosPopupButtonState extends State> void _handleTap() { final TextDirection? textDirection = Directionality.maybeOf(context); - const EdgeInsetsGeometry menuMargin = - EdgeInsetsDirectional.only(start: 4.0, end: 4.0); + const EdgeInsetsGeometry menuMargin = EdgeInsets.symmetric(horizontal: 4.0); final List<_MenuItem> menuItems = <_MenuItem>[ for (int index = 0; index < widget.items!.length; index += 1) @@ -1239,7 +1238,7 @@ class _MacosPopupButtonState extends State> boxShadow: [ BoxShadow( color: buttonStyles.borderColor, - offset: const Offset(0, .5), + offset: const Offset(0, 0.5), blurRadius: 0.2, spreadRadius: 0, ), @@ -1251,7 +1250,7 @@ class _MacosPopupButtonState extends State> ), borderRadius: _kBorderRadius, ), - padding: const EdgeInsets.fromLTRB(8.0, 0.0, 2.0, 0.0), + padding: const EdgeInsets.only(left: 8.0, right: 2.0), height: _kPopupButtonHeight, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/src/buttons/pulldown_button.dart b/lib/src/buttons/pulldown_button.dart index dbe7e0d1..51b783f4 100644 --- a/lib/src/buttons/pulldown_button.dart +++ b/lib/src/buttons/pulldown_button.dart @@ -808,7 +808,7 @@ class _MacosPulldownButtonState extends State void _handleTap() { final TextDirection? textDirection = Directionality.maybeOf(context); const EdgeInsetsGeometry menuMargin = - EdgeInsetsDirectional.only(start: 4.0, end: 4.0); + EdgeInsets.symmetric(horizontal: 4.0); final List<_MenuItem> menuItems = <_MenuItem>[ for (int index = 0; index < widget.items!.length; index += 1) @@ -904,7 +904,7 @@ class _MacosPulldownButtonState extends State boxShadow: [ BoxShadow( color: buttonStyles.borderColor, - offset: const Offset(0, .5), + offset: const Offset(0, 0.5), blurRadius: 0.2, spreadRadius: 0, ), @@ -913,7 +913,7 @@ class _MacosPulldownButtonState extends State color: buttonStyles.bgColor, borderRadius: borderRadius, ), - padding: const EdgeInsets.fromLTRB(8.0, 0.0, 2.0, 0.0), + padding: const EdgeInsets.only(left: 8.0, right: 2.0), height: buttonHeight, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/src/buttons/segmented_control.dart b/lib/src/buttons/segmented_control.dart index 411fd8d1..45f36539 100644 --- a/lib/src/buttons/segmented_control.dart +++ b/lib/src/buttons/segmented_control.dart @@ -54,8 +54,8 @@ class _MacosSegmentedControlState extends State { const Color(0xFFDBDCDE), const Color(0xFF4F5155), ), - offset: const Offset(0, .5), - spreadRadius: .5, + offset: const Offset(0, 0.5), + spreadRadius: 0.5, ), ], borderRadius: const BorderRadius.all( diff --git a/lib/src/buttons/toolbar/toolbar_icon_button.dart b/lib/src/buttons/toolbar/toolbar_icon_button.dart index 3d94a1f3..4b165a3d 100644 --- a/lib/src/buttons/toolbar/toolbar_icon_button.dart +++ b/lib/src/buttons/toolbar/toolbar_icon_button.dart @@ -77,7 +77,7 @@ class ToolBarIconButton extends ToolbarItem { if (showLabel) { iconButton = Padding( - padding: const EdgeInsets.fromLTRB(6.0, 6.0, 6.0, 0.0), + padding: const EdgeInsets.only(left: 6.0, top: 6.0, right: 6.0), child: Column( children: [ iconButton, diff --git a/lib/src/fields/text_field.dart b/lib/src/fields/text_field.dart index 9387b029..f3cf0888 100644 --- a/lib/src/fields/text_field.dart +++ b/lib/src/fields/text_field.dart @@ -358,7 +358,7 @@ class MacosTextField extends StatefulWidget { this.focusNode, this.decoration, this.focusedDecoration, - this.padding = const EdgeInsets.fromLTRB(2.0, 4.0, 2.0, 4.0), + this.padding = const EdgeInsets.symmetric(horizontal: 2.0, vertical: 4.0), this.placeholder, this.placeholderStyle = _kDefaultPlaceholderStyle, this.prefix, @@ -1157,6 +1157,7 @@ class _MacosTextFieldState extends State void dispose() { _focusNode?.dispose(); _controller?.dispose(); + _effectiveFocusNode.removeListener(_handleFocusChanged); super.dispose(); } diff --git a/lib/src/indicators/progress_indicators.dart b/lib/src/indicators/progress_indicators.dart index 9bc9196e..6fe47cd9 100644 --- a/lib/src/indicators/progress_indicators.dart +++ b/lib/src/indicators/progress_indicators.dart @@ -107,7 +107,7 @@ class _DeterminateCirclePainter extends CustomPainter { final Color? borderColor; static const double _twoPi = math.pi * 2.0; - static const double _epsilon = .001; + static const double _epsilon = 0.001; static const double _sweep = _twoPi - _epsilon; static const double _startAngle = -math.pi / 2.0; @@ -240,7 +240,7 @@ class _DeterminateBarPainter extends CustomPainter { void paint(Canvas canvas, Size size) { // Draw the background line canvas.drawRRect( - BorderRadius.circular(100).toRRect( + const BorderRadius.all(Radius.circular(100)).toRRect( Offset.zero & size, ), Paint() diff --git a/lib/src/indicators/slider.dart b/lib/src/indicators/slider.dart index 8a172c2e..9e5e7514 100644 --- a/lib/src/indicators/slider.dart +++ b/lib/src/indicators/slider.dart @@ -178,8 +178,9 @@ class MacosSlider extends StatelessWidget { backgroundColor, context, ), - borderRadius: - BorderRadius.circular(_kSliderBorderRadius), + borderRadius: const BorderRadius.all( + Radius.circular(_kSliderBorderRadius), + ), ), ), ), @@ -192,8 +193,9 @@ class MacosSlider extends StatelessWidget { width: width * _percentage, decoration: BoxDecoration( color: MacosDynamicColor.resolve(color, context), - borderRadius: - BorderRadius.circular(_kSliderBorderRadius), + borderRadius: const BorderRadius.all( + Radius.circular(_kSliderBorderRadius), + ), ), ), ), @@ -273,7 +275,8 @@ class _ContinuousThumb extends StatelessWidget { width: _kContinuousThumbSize, decoration: BoxDecoration( color: color, - borderRadius: BorderRadius.circular(_kContinuousThumbSize), + borderRadius: + const BorderRadius.all(Radius.circular(_kContinuousThumbSize)), boxShadow: const [ BoxShadow( color: Color.fromRGBO(0, 0, 0, 0.1), @@ -300,7 +303,9 @@ class _DiscreteThumb extends StatelessWidget { width: _kDiscreteThumbWidth, decoration: BoxDecoration( color: color, - borderRadius: BorderRadius.circular(_kDiscreteThumbBorderRadius), + borderRadius: const BorderRadius.all( + Radius.circular(_kDiscreteThumbBorderRadius), + ), boxShadow: const [ BoxShadow( color: Color.fromRGBO(0, 0, 0, 0.1), diff --git a/lib/src/layout/resizable_pane.dart b/lib/src/layout/resizable_pane.dart index 68ccfbed..b3f6b338 100644 --- a/lib/src/layout/resizable_pane.dart +++ b/lib/src/layout/resizable_pane.dart @@ -238,10 +238,8 @@ class _ResizablePaneState extends State { oldWidget.minSize != widget.minSize || oldWidget.maxSize != widget.maxSize || oldWidget.resizableSide != widget.resizableSide) { - setState(() { - if (widget.minSize > _size) _size = widget.minSize; - if (widget.maxSize < _size) _size = widget.maxSize; - }); + if (widget.minSize > _size) _size = widget.minSize; + if (widget.maxSize < _size) _size = widget.maxSize; } } diff --git a/lib/src/layout/scrollbar.dart b/lib/src/layout/scrollbar.dart index 334aced5..1cfc819b 100644 --- a/lib/src/layout/scrollbar.dart +++ b/lib/src/layout/scrollbar.dart @@ -156,7 +156,7 @@ class _RawMacosScrollBarState extends RawScrollbarState<_RawMacosScrollBar> { ); _trackColorTween = ColorTween( begin: MacosColors.transparent, - end: widget.effectiveThumbColor.withOpacity(.15), + end: widget.effectiveThumbColor.withOpacity(0.15), ).animate(_trackColorAnimationController); _thumbThicknessAnimationController.addListener(() { updateScrollbarPainter(); diff --git a/lib/src/layout/sidebar/sidebar_items.dart b/lib/src/layout/sidebar/sidebar_items.dart index f5954951..65b3dadb 100644 --- a/lib/src/layout/sidebar/sidebar_items.dart +++ b/lib/src/layout/sidebar/sidebar_items.dart @@ -206,9 +206,7 @@ class _SidebarItem extends StatelessWidget { /// Typically a [Navigator] call final VoidCallback? onClick; - void _handleActionTap() async { - onClick?.call(); - } + void _handleActionTap() => onClick?.call(); Map> get _actionMap => >{ ActivateIntent: CallbackAction( @@ -286,9 +284,8 @@ class _SidebarItem extends StatelessWidget { padding: EdgeInsets.only(right: spacing), child: MacosIconTheme.merge( data: MacosIconThemeData( - color: selected - ? MacosColors.white - : theme.primaryColor, + color: + selected ? MacosColors.white : theme.primaryColor, size: itemSize.iconSize, ), child: item.leading!, diff --git a/lib/src/layout/toolbar/sliver_toolbar.dart b/lib/src/layout/toolbar/sliver_toolbar.dart index 8ec1b087..a5a00dbf 100644 --- a/lib/src/layout/toolbar/sliver_toolbar.dart +++ b/lib/src/layout/toolbar/sliver_toolbar.dart @@ -275,7 +275,7 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate { ); } - final Widget toolBar = FlexibleSpaceBar.createSettings( + return FlexibleSpaceBar.createSettings( minExtent: minExtent, maxExtent: maxExtent, currentExtent: math.max(minExtent, maxExtent - shrinkOffset), @@ -295,7 +295,6 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate { height: height, ), ); - return toolBar; } @override diff --git a/lib/src/layout/toolbar/toolbar_divider.dart b/lib/src/layout/toolbar/toolbar_divider.dart index d20a3990..346a999c 100644 --- a/lib/src/layout/toolbar/toolbar_divider.dart +++ b/lib/src/layout/toolbar/toolbar_divider.dart @@ -23,15 +23,9 @@ class ToolBarDivider extends ToolbarItem { const Color.fromRGBO(255, 255, 255, 0.25), ); if (displayMode == ToolbarItemDisplayMode.inToolbar) { - return Padding( - padding: padding!, - child: Container(color: color, width: 1, height: 28), - ); + return Container(color: color, width: 1, height: 28, padding: padding!); } else { - return Padding( - padding: padding!, - child: Container(color: color, height: 1), - ); + return Container(color: color, height: 1, padding: padding!); } } } diff --git a/lib/src/layout/toolbar/toolbar_popup.dart b/lib/src/layout/toolbar/toolbar_popup.dart index 9d618128..87f1158b 100644 --- a/lib/src/layout/toolbar/toolbar_popup.dart +++ b/lib/src/layout/toolbar/toolbar_popup.dart @@ -203,7 +203,7 @@ class _ToolbarPopupMenuState extends State<_ToolbarPopupMenu> { super.initState(); _fadeOpacity = CurvedAnimation( parent: widget.route.animation!, - curve: const Interval(0.0, 0.50), + curve: const Interval(0.0, 0.5), reverseCurve: const Interval(0.75, 1.0), ); } @@ -335,7 +335,7 @@ class _ToolbarPopupRoute extends PopupRoute { @override Widget buildPage(context, animation, secondaryAnimation) { return LayoutBuilder(builder: (context, constraints) { - final page = _ToolbarPopupRoutePage( + return _ToolbarPopupRoutePage( target: target, placementOffset: placementOffset, placement: placement, @@ -349,7 +349,6 @@ class _ToolbarPopupRoute extends PopupRoute { horizontalOffset: horizontalOffset, position: position, ); - return page; }); } diff --git a/lib/src/layout/window.dart b/lib/src/layout/window.dart index 9ce38eb0..f20979b9 100644 --- a/lib/src/layout/window.dart +++ b/lib/src/layout/window.dart @@ -85,30 +85,28 @@ class _MacosWindowState extends State { @override void didUpdateWidget(covariant MacosWindow old) { super.didUpdateWidget(old); - setState(() { - if (widget.sidebar == null) { - _sidebarWidth = 0.0; - } else if (widget.sidebar!.minWidth != old.sidebar!.minWidth || - widget.sidebar!.maxWidth != old.sidebar!.maxWidth) { - if (widget.sidebar!.minWidth > _sidebarWidth) { - _sidebarWidth = widget.sidebar!.minWidth; - } - if (widget.sidebar!.maxWidth! < _sidebarWidth) { - _sidebarWidth = widget.sidebar!.maxWidth!; - } + if (widget.sidebar == null) { + _sidebarWidth = 0.0; + } else if (widget.sidebar!.minWidth != old.sidebar!.minWidth || + widget.sidebar!.maxWidth != old.sidebar!.maxWidth) { + if (widget.sidebar!.minWidth > _sidebarWidth) { + _sidebarWidth = widget.sidebar!.minWidth; } - if (widget.endSidebar == null) { - _endSidebarWidth = 0.0; - } else if (widget.endSidebar!.minWidth != old.endSidebar!.minWidth || - widget.endSidebar!.maxWidth != old.endSidebar!.maxWidth) { - if (widget.endSidebar!.minWidth > _endSidebarWidth) { - _endSidebarWidth = widget.endSidebar!.minWidth; - } - if (widget.endSidebar!.maxWidth! < _endSidebarWidth) { - _endSidebarWidth = widget.endSidebar!.maxWidth!; - } + if (widget.sidebar!.maxWidth! < _sidebarWidth) { + _sidebarWidth = widget.sidebar!.maxWidth!; } - }); + } + if (widget.endSidebar == null) { + _endSidebarWidth = 0.0; + } else if (widget.endSidebar!.minWidth != old.endSidebar!.minWidth || + widget.endSidebar!.maxWidth != old.endSidebar!.maxWidth) { + if (widget.endSidebar!.minWidth > _endSidebarWidth) { + _endSidebarWidth = widget.endSidebar!.minWidth; + } + if (widget.endSidebar!.maxWidth! < _endSidebarWidth) { + _endSidebarWidth = widget.endSidebar!.maxWidth!; + } + } } @override @@ -465,13 +463,17 @@ class _MacosWindowState extends State { setState(() => _sidebarSlideDuration = 300); setState(() => _showSidebar = !_showSidebar); await Future.delayed(Duration(milliseconds: _sidebarSlideDuration)); - setState(() => _sidebarSlideDuration = 0); + if (mounted) { + setState(() => _sidebarSlideDuration = 0); + } }, endSidebarToggler: () async { setState(() => _sidebarSlideDuration = 300); setState(() => _showEndSidebar = !_showEndSidebar); await Future.delayed(Duration(milliseconds: _sidebarSlideDuration)); - setState(() => _sidebarSlideDuration = 0); + if (mounted) { + setState(() => _sidebarSlideDuration = 0); + } }, child: layout, ); diff --git a/lib/src/macos_app.dart b/lib/src/macos_app.dart index a430ce1b..459918fb 100644 --- a/lib/src/macos_app.dart +++ b/lib/src/macos_app.dart @@ -409,8 +409,7 @@ class _MacosAppState extends State { @override Widget build(BuildContext context) { // leaves room for assertions, etc - Widget result = _buildMacosApp(context); - return result; + return _buildMacosApp(context); } Iterable> get _localizationsDelegates sync* { diff --git a/lib/src/selectors/color_well.dart b/lib/src/selectors/color_well.dart index cff9cd9f..9f1c5685 100644 --- a/lib/src/selectors/color_well.dart +++ b/lib/src/selectors/color_well.dart @@ -106,7 +106,7 @@ class _MacosColorWellState extends State { Widget build(BuildContext context) { final theme = MacosTheme.of(context); final outerColor = theme.brightness.isDark - ? MacosColors.systemGrayColor.withOpacity(0.50) + ? MacosColors.systemGrayColor.withOpacity(0.5) : MacosColors.white; return GestureDetector( onTap: () async { diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index 4fec86a3..f764394f 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -319,7 +319,8 @@ class _MacosDatePickerState extends State { child: Column( children: [ Padding( - padding: const EdgeInsets.fromLTRB(2.0, 2.0, 0.0, 4.0), + padding: + const EdgeInsets.only(left: 2.0, top: 2.0, bottom: 4.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -410,7 +411,7 @@ class _MacosDatePickerState extends State { ), ), Padding( - padding: const EdgeInsets.fromLTRB(6.0, 0.0, 5.0, 0.0), + padding: const EdgeInsets.only(left: 6.0, right: 5.0), child: Column( children: [ GridView.custom( @@ -496,7 +497,7 @@ class _MacosDatePickerState extends State { ); decoration = BoxDecoration( color: datePickerTheme.monthViewCurrentDateColor, - borderRadius: BorderRadius.circular(3.0), + borderRadius: const BorderRadius.all(Radius.circular(3.0)), ); } else if (isToday) { dayText = Text( @@ -512,7 +513,7 @@ class _MacosDatePickerState extends State { ); decoration = BoxDecoration( color: datePickerTheme.monthViewSelectedDateColor, - borderRadius: BorderRadius.circular(3.0), + borderRadius: const BorderRadius.all(Radius.circular(3.0)), ); } @@ -524,20 +525,18 @@ class _MacosDatePickerState extends State { }); widget.onDateChanged.call(_formatAsDateTime()); }, - child: Padding( + child: Container( + decoration: decoration, padding: const EdgeInsets.symmetric(vertical: 2.0), - child: Container( - decoration: decoration, - child: Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.only(right: 2.0), - child: dayText ?? - Text( - localizations.formatDecimal(day), - style: dayStyle, - ), - ), + child: Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.only(right: 2.0), + child: dayText ?? + Text( + localizations.formatDecimal(day), + style: dayStyle, + ), ), ), ), diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index ac03ea2d..c8ea1f55 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -3,8 +3,8 @@ import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; CupertinoDynamicColor _kScrollbarColor = CupertinoDynamicColor.withBrightness( - color: MacosColors.systemGrayColor.color.withOpacity(.8), - darkColor: MacosColors.systemGrayColor.darkColor.withOpacity(.8), + color: MacosColors.systemGrayColor.color.withOpacity(0.8), + darkColor: MacosColors.systemGrayColor.darkColor.withOpacity(0.8), ); /// Applies a macOS-style theme to descendant macOS widgets. diff --git a/lib/src/theme/tooltip_theme.dart b/lib/src/theme/tooltip_theme.dart index b0958ea7..1551c8bd 100644 --- a/lib/src/theme/tooltip_theme.dart +++ b/lib/src/theme/tooltip_theme.dart @@ -89,7 +89,7 @@ class MacosTooltipThemeData with Diagnosticable { brightness.isDark ? CupertinoColors.white : CupertinoColors.black, ), decoration: () { - final radius = BorderRadius.circular(2.0); + const radius = BorderRadius.all(Radius.circular(2.0)); final shadow = [ BoxShadow( color: brightness.isDark diff --git a/lib/src/theme/typography.dart b/lib/src/theme/typography.dart index 670d694c..d3efbeaf 100644 --- a/lib/src/theme/typography.dart +++ b/lib/src/theme/typography.dart @@ -140,8 +140,10 @@ class MacosTypography with Diagnosticable { required this.caption2, }); - static MacosTypography black = MacosTypography(color: CupertinoColors.black); - static MacosTypography white = MacosTypography(color: CupertinoColors.white); + static final MacosTypography black = + MacosTypography(color: CupertinoColors.black); + static final MacosTypography white = + MacosTypography(color: CupertinoColors.white); /// Style used for body text. final TextStyle body; diff --git a/test/buttons/pulldown_button_test.dart b/test/buttons/pulldown_button_test.dart index e22a355e..668914d0 100644 --- a/test/buttons/pulldown_button_test.dart +++ b/test/buttons/pulldown_button_test.dart @@ -75,7 +75,7 @@ void main() { MacosPulldownMenuItem( title: const Text('one'), onTap: () { - menuItemTapCounters[0] += 1; + menuItemTapCounters.first += 1; }, ), MacosPulldownMenuItem( From aa82a08d1c573c439a8a1c8266df55ca7ee6d656 Mon Sep 17 00:00:00 2001 From: Erick Ghaumez Date: Sun, 26 Mar 2023 11:24:52 +0200 Subject: [PATCH 07/45] Fix the online gallery link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b0925894..a88690b9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Flutter widgets and themes implementing the current macOS design language. -Check out our **interactive widget gallery** online at https://groovinchip.github.io/macos_ui/#/ +Check out our **interactive widget gallery** online at https://macosui.github.io/macos_ui/#/ Guides, codelabs, and other documentation can be found at https://macosui.dev From a52b616f14b502de236c3ed0fb622e6abfb52b1c Mon Sep 17 00:00:00 2001 From: Elias Yishak <42216813+eliasyishak@users.noreply.github.com> Date: Fri, 31 Mar 2023 11:38:03 -0400 Subject: [PATCH 08/45] Fix for invalid dates (#402) --- CHANGELOG.md | 3 +++ lib/src/selectors/date_picker.dart | 12 ++++++++++-- pubspec.yaml | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c79798a3..55ffc59e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [1.12.4] +* Default the `_selectedDay` state variable to be 1 when selecting the previous/next month from widget to ensure new date is valid for `_formatAsDateTime()` method (https://github.com/flutter/flutter/issues/123669 & https://github.com/macosui/macos_ui/pull/402) + ## [1.12.3] * Added support for `routerConfig` to `MacosApp.router`. ([#388](https://github.com/macosui/macos_ui/issues/388)) diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index f764394f..348fec85 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -354,9 +354,13 @@ class _MacosDatePickerState extends State { setState(() { _selectedYear--; _selectedMonth = 12; + _selectedDay = 1; }); } else { - setState(() => _selectedMonth--); + setState(() { + _selectedMonth--; + _selectedDay = 1; + }); } widget.onDateChanged.call(_formatAsDateTime()); }, @@ -397,9 +401,13 @@ class _MacosDatePickerState extends State { setState(() { _selectedYear++; _selectedMonth = 1; + _selectedDay = 1; }); } else { - setState(() => _selectedMonth++); + setState(() { + _selectedMonth++; + _selectedDay = 1; + }); } widget.onDateChanged.call(_formatAsDateTime()); diff --git a/pubspec.yaml b/pubspec.yaml index 7e1d73a1..40ee143e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 1.12.3 +version: 1.12.4 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From 7de5419bfca8e91886fec1f12e66f05cd0e52098 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Thu, 6 Apr 2023 11:02:44 -0400 Subject: [PATCH 09/45] Revert "Merge branch 'customer_testing' into dev" This reverts commit ebf1d069238a468ba1f22d053ee0781239cf8db2, reversing changes made to a52b616f14b502de236c3ed0fb622e6abfb52b1c. --- .github/workflows/flutter_analysis.yml | 2 +- example/lib/pages/toolbar_page.dart | 2 +- example/pubspec.lock | 26 ++++++++++---------- lib/src/fields/text_field.dart | 4 +-- lib/src/layout/scaffold.dart | 2 +- pubspec.lock | 34 +++++++++++++------------- 6 files changed, 35 insertions(+), 35 deletions(-) diff --git a/.github/workflows/flutter_analysis.yml b/.github/workflows/flutter_analysis.yml index 4557a34d..fba04aa4 100644 --- a/.github/workflows/flutter_analysis.yml +++ b/.github/workflows/flutter_analysis.yml @@ -10,7 +10,7 @@ jobs: - name: Install Flutter uses: subosito/flutter-action@v2 with: - channel: master + channel: stable - name: Install dependencies run: flutter pub get diff --git a/example/lib/pages/toolbar_page.dart b/example/lib/pages/toolbar_page.dart index b026a364..6e1754ce 100644 --- a/example/lib/pages/toolbar_page.dart +++ b/example/lib/pages/toolbar_page.dart @@ -162,7 +162,7 @@ class _ToolbarPageState extends State { padding: const EdgeInsets.all(30), child: Center( child: Column( - children: [ + children: const [ Text( 'A toolbar provides convenient access to frequently used commands and controls that perform actions relevant to the current view.', textAlign: TextAlign.center, diff --git a/example/pubspec.lock b/example/pubspec.lock index eaf08376..546efaa5 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.0" cupertino_icons: dependency: "direct main" description: @@ -79,10 +79,10 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.6.5" lints: dependency: transitive description: @@ -102,10 +102,10 @@ packages: dependency: transitive description: name: matcher - sha256: c94db23593b89766cda57aab9ac311e3616cf87c6fa4e9749df032f66f30dcb8 + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" url: "https://pub.dev" source: hosted - version: "0.12.14" + version: "0.12.13" material_color_utilities: dependency: transitive description: @@ -118,10 +118,10 @@ packages: dependency: transitive description: name: meta - sha256: "12307e7f0605ce3da64cf0db90e5fcab0869f3ca03f76be6bb2991ce0a55e82b" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.8.0" nested: dependency: transitive description: @@ -134,10 +134,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.8.2" provider: dependency: "direct main" description: @@ -195,10 +195,10 @@ packages: dependency: transitive description: name: test_api - sha256: "6182294da5abf431177fccc1ee02401f6df30f766bc6130a0852c6b6d7ee6b2d" + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 url: "https://pub.dev" source: hosted - version: "0.4.18" + version: "0.4.16" vector_math: dependency: transitive description: @@ -208,5 +208,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.19.0 <4.0.0" + dart: ">=2.18.0 <3.0.0" flutter: ">=1.20.0" diff --git a/lib/src/fields/text_field.dart b/lib/src/fields/text_field.dart index b1f1e1bb..f3cf0888 100644 --- a/lib/src/fields/text_field.dart +++ b/lib/src/fields/text_field.dart @@ -105,7 +105,7 @@ class _TextFieldSelectionGestureDetectorBuilder final _MacosTextFieldState _state; @override - void onSingleTapUp(TapDragUpDetails details) { + void onSingleTapUp(TapUpDetails details) { // Because TextSelectionGestureDetector listens to taps that happen on // widgets in front of it, tapping the clear button will also trigger // this handler. If the clear button widget recognizes the up event, @@ -127,7 +127,7 @@ class _TextFieldSelectionGestureDetectorBuilder } @override - void onDragSelectionEnd(TapDragEndDetails details) { + void onDragSelectionEnd(DragEndDetails details) { _state._requestKeyboard(); } } diff --git a/lib/src/layout/scaffold.dart b/lib/src/layout/scaffold.dart index 16aa71e1..712ab1a7 100644 --- a/lib/src/layout/scaffold.dart +++ b/lib/src/layout/scaffold.dart @@ -115,7 +115,7 @@ class _MacosScaffoldState extends State { } class _ScaffoldBody extends MultiChildRenderObjectWidget { - const _ScaffoldBody({ + _ScaffoldBody({ super.children, }); diff --git a/pubspec.lock b/pubspec.lock index 7a731dae..6cfa9f14 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.0" convert: dependency: transitive description: @@ -231,10 +231,10 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.6.5" json_annotation: dependency: transitive description: @@ -263,10 +263,10 @@ packages: dependency: transitive description: name: matcher - sha256: c94db23593b89766cda57aab9ac311e3616cf87c6fa4e9749df032f66f30dcb8 + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" url: "https://pub.dev" source: hosted - version: "0.12.14" + version: "0.12.13" material_color_utilities: dependency: transitive description: @@ -279,10 +279,10 @@ packages: dependency: transitive description: name: meta - sha256: "12307e7f0605ce3da64cf0db90e5fcab0869f3ca03f76be6bb2991ce0a55e82b" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.8.0" mime: dependency: transitive description: @@ -319,10 +319,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.8.2" petitparser: dependency: transitive description: @@ -468,26 +468,26 @@ packages: dependency: transitive description: name: test - sha256: b54d427664c00f2013ffb87797a698883c46aee9288e027a50b46eaee7486fa2 + sha256: a5fcd2d25eeadbb6589e80198a47d6a464ba3e2049da473943b8af9797900c2d url: "https://pub.dev" source: hosted - version: "1.22.2" + version: "1.22.0" test_api: dependency: transitive description: name: test_api - sha256: "6182294da5abf431177fccc1ee02401f6df30f766bc6130a0852c6b6d7ee6b2d" + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 url: "https://pub.dev" source: hosted - version: "0.4.18" + version: "0.4.16" test_core: dependency: transitive description: name: test_core - sha256: "95ecc12692d0dd59080ab2d38d9cf32c7e9844caba23ff6cd285690398ee8ef4" + sha256: "0ef9755ec6d746951ba0aabe62f874b707690b5ede0fecc818b138fcc9b14888" url: "https://pub.dev" source: hosted - version: "0.4.22" + version: "0.4.20" typed_data: dependency: transitive description: @@ -553,5 +553,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <4.0.0" + dart: ">=2.18.0 <3.0.0" flutter: ">=1.20.0" From 5fc566cb5b2e199e1b08103a7a26f29649f8b4d9 Mon Sep 17 00:00:00 2001 From: Zemlaynikin Max Date: Thu, 6 Apr 2023 22:30:06 +0400 Subject: [PATCH 10/45] Fix: use the `sidebar` and `endSidebar` key parameter (#400) * Fix: use the `sidebard` and `endSidebar` key parameter - pass sidebar key to the sidebar root widget (`AnimatedPositioned`) - create a new sidebar `ScrollController` when the key is change * Increment version and update changelog * Apply suggestions from code review * Update pubspec.lock file in example --------- Co-authored-by: Reuben Turner --- CHANGELOG.md | 3 + example/pubspec.lock | 2 +- lib/src/layout/window.dart | 158 +++++++++++++++++++++---------------- pubspec.yaml | 2 +- 4 files changed, 95 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55ffc59e..741b038b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [1.12.5] +* Fixed a bug where the `Sidebar.key` parameter wasn't used, which caused certain layouts to be unachievable. + ## [1.12.4] * Default the `_selectedDay` state variable to be 1 when selecting the previous/next month from widget to ensure new date is valid for `_formatAsDateTime()` method (https://github.com/flutter/flutter/issues/123669 & https://github.com/macosui/macos_ui/pull/402) diff --git a/example/pubspec.lock b/example/pubspec.lock index 546efaa5..0d45eb9e 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "1.12.3" + version: "1.12.5" matcher: dependency: transitive description: diff --git a/lib/src/layout/window.dart b/lib/src/layout/window.dart index f20979b9..21e82e07 100644 --- a/lib/src/layout/window.dart +++ b/lib/src/layout/window.dart @@ -52,8 +52,8 @@ class MacosWindow extends StatefulWidget { } class _MacosWindowState extends State { - final _sidebarScrollController = ScrollController(); - final _endSidebarScrollController = ScrollController(); + var _sidebarScrollController = ScrollController(); + var _endSidebarScrollController = ScrollController(); double _sidebarWidth = 0.0; double _sidebarDragStartWidth = 0.0; double _sidebarDragStartPosition = 0.0; @@ -74,39 +74,59 @@ class _MacosWindowState extends State { _endSidebarWidth = (widget.endSidebar?.startWidth ?? widget.endSidebar?.minWidth) ?? _endSidebarWidth; - if (widget.sidebar?.builder != null) { - _sidebarScrollController.addListener(() => setState(() {})); - } - if (widget.endSidebar?.builder != null) { - _endSidebarScrollController.addListener(() => setState(() {})); - } + _addSidebarScrollControllerListenerIfNeeded(); + _addEndSidebarScrollControllerListenerIfNeeded(); } @override void didUpdateWidget(covariant MacosWindow old) { super.didUpdateWidget(old); - if (widget.sidebar == null) { + final sidebar = widget.sidebar; + if (sidebar == null) { _sidebarWidth = 0.0; - } else if (widget.sidebar!.minWidth != old.sidebar!.minWidth || - widget.sidebar!.maxWidth != old.sidebar!.maxWidth) { - if (widget.sidebar!.minWidth > _sidebarWidth) { - _sidebarWidth = widget.sidebar!.minWidth; + } else if (sidebar.minWidth != old.sidebar!.minWidth || + sidebar.maxWidth != old.sidebar!.maxWidth) { + if (sidebar.minWidth > _sidebarWidth) { + _sidebarWidth = sidebar.minWidth; } - if (widget.sidebar!.maxWidth! < _sidebarWidth) { - _sidebarWidth = widget.sidebar!.maxWidth!; + if (sidebar.maxWidth! < _sidebarWidth) { + _sidebarWidth = sidebar.maxWidth!; } } - if (widget.endSidebar == null) { + if (sidebar?.key != old.sidebar?.key) { + _sidebarScrollController.dispose(); + _sidebarScrollController = ScrollController(); + _addSidebarScrollControllerListenerIfNeeded(); + } + final endSidebar = widget.endSidebar; + if (endSidebar == null) { _endSidebarWidth = 0.0; - } else if (widget.endSidebar!.minWidth != old.endSidebar!.minWidth || - widget.endSidebar!.maxWidth != old.endSidebar!.maxWidth) { - if (widget.endSidebar!.minWidth > _endSidebarWidth) { - _endSidebarWidth = widget.endSidebar!.minWidth; + } else if (endSidebar.minWidth != old.endSidebar!.minWidth || + endSidebar.maxWidth != old.endSidebar!.maxWidth) { + if (endSidebar.minWidth > _endSidebarWidth) { + _endSidebarWidth = endSidebar.minWidth; } - if (widget.endSidebar!.maxWidth! < _endSidebarWidth) { - _endSidebarWidth = widget.endSidebar!.maxWidth!; + if (endSidebar.maxWidth! < _endSidebarWidth) { + _endSidebarWidth = endSidebar.maxWidth!; } } + if (endSidebar?.key != old.endSidebar?.key) { + _endSidebarScrollController.dispose(); + _endSidebarScrollController = ScrollController(); + _addEndSidebarScrollControllerListenerIfNeeded(); + } + } + + void _addSidebarScrollControllerListenerIfNeeded() { + if (widget.sidebar?.builder != null) { + _sidebarScrollController.addListener(() => setState(() {})); + } + } + + void _addEndSidebarScrollControllerListenerIfNeeded() { + if (widget.endSidebar?.builder != null) { + _endSidebarScrollController.addListener(() => setState(() {})); + } } @override @@ -120,13 +140,15 @@ class _MacosWindowState extends State { // ignore: code-metrics Widget build(BuildContext context) { assert(debugCheckHasMacosTheme(context)); - if (widget.sidebar?.startWidth != null) { - assert((widget.sidebar!.startWidth! >= widget.sidebar!.minWidth) && - (widget.sidebar!.startWidth! <= widget.sidebar!.maxWidth!)); + final sidebar = widget.sidebar; + final endSidebar = widget.endSidebar; + if (sidebar?.startWidth != null) { + assert((sidebar!.startWidth! >= sidebar.minWidth) && + (sidebar.startWidth! <= sidebar.maxWidth!)); } - if (widget.endSidebar?.startWidth != null) { - assert((widget.endSidebar!.startWidth! >= widget.endSidebar!.minWidth) && - (widget.endSidebar!.startWidth! <= widget.endSidebar!.maxWidth!)); + if (endSidebar?.startWidth != null) { + assert((endSidebar!.startWidth! >= endSidebar.minWidth) && + (endSidebar.startWidth! <= endSidebar.maxWidth!)); } final MacosThemeData theme = MacosTheme.of(context); late Color backgroundColor = widget.backgroundColor ?? theme.canvasColor; @@ -137,8 +159,8 @@ class _MacosWindowState extends State { final isMac = !kIsWeb && defaultTargetPlatform == TargetPlatform.macOS; // Respect the sidebar color override from parent if one is given - if (widget.sidebar?.decoration?.color != null) { - sidebarBackgroundColor = widget.sidebar!.decoration!.color!; + if (sidebar?.decoration?.color != null) { + sidebarBackgroundColor = sidebar!.decoration!.color!; } else if (isMac && MediaQuery.of(context).platformBrightness.isDark == theme.brightness.isDark) { @@ -154,8 +176,8 @@ class _MacosWindowState extends State { } // Respect the end sidebar color override from parent if one is given - if (widget.endSidebar?.decoration?.color != null) { - endSidebarBackgroundColor = widget.endSidebar!.decoration!.color!; + if (endSidebar?.decoration?.color != null) { + endSidebarBackgroundColor = endSidebar!.decoration!.color!; } else if (isMac && MediaQuery.of(context).platformBrightness.isDark == theme.brightness.isDark) { @@ -173,9 +195,8 @@ class _MacosWindowState extends State { builder: (context, constraints) { final width = constraints.maxWidth; final height = constraints.maxHeight; - final isAtBreakpoint = width <= (widget.sidebar?.windowBreakpoint ?? 0); - final isAtEndBreakpoint = - width <= (widget.endSidebar?.windowBreakpoint ?? 0); + final isAtBreakpoint = width <= (sidebar?.windowBreakpoint ?? 0); + final isAtEndBreakpoint = width <= (endSidebar?.windowBreakpoint ?? 0); final canShowSidebar = _showSidebar && !isAtBreakpoint; final canShowEndSidebar = _showEndSidebar && !isAtEndBreakpoint; final visibleSidebarWidth = canShowSidebar ? _sidebarWidth : 0.0; @@ -195,8 +216,9 @@ class _MacosWindowState extends State { ), // Sidebar - if (widget.sidebar != null) + if (sidebar != null) AnimatedPositioned( + key: sidebar.key, curve: curve, duration: duration, height: height, @@ -206,39 +228,39 @@ class _MacosWindowState extends State { curve: Curves.easeInOut, color: sidebarBackgroundColor, constraints: BoxConstraints( - minWidth: widget.sidebar!.minWidth, - maxWidth: widget.sidebar!.maxWidth!, + minWidth: sidebar.minWidth, + maxWidth: sidebar.maxWidth!, minHeight: height, maxHeight: height, ).normalize(), child: Column( children: [ - if ((widget.sidebar?.topOffset ?? 0) > 0) - SizedBox(height: widget.sidebar?.topOffset), + if (sidebar.topOffset > 0) + SizedBox(height: sidebar.topOffset), if (_sidebarScrollController.hasClients && _sidebarScrollController.offset > 0.0) Divider(thickness: 1, height: 1, color: dividerColor), - if (widget.sidebar!.top != null && - constraints.maxHeight > 81) + if (sidebar.top != null && constraints.maxHeight > 81) Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: widget.sidebar!.top!, + child: sidebar.top!, ), Expanded( child: MacosScrollbar( controller: _sidebarScrollController, child: Padding( - padding: widget.sidebar?.padding ?? EdgeInsets.zero, - child: widget.sidebar! - .builder(context, _sidebarScrollController), + padding: sidebar.padding, + child: sidebar.builder( + context, + _sidebarScrollController, + ), ), ), ), - if (widget.sidebar?.bottom != null && - constraints.maxHeight > 141) + if (sidebar.bottom != null && constraints.maxHeight > 141) Padding( padding: const EdgeInsets.all(16.0), - child: widget.sidebar!.bottom!, + child: sidebar.bottom!, ), ], ), @@ -273,7 +295,7 @@ class _MacosWindowState extends State { ), // Sidebar resizer - if (widget.sidebar?.isResizable ?? false) + if (sidebar?.isResizable ?? false) AnimatedPositioned( curve: curve, duration: duration, @@ -287,13 +309,12 @@ class _MacosWindowState extends State { _sidebarDragStartPosition = details.globalPosition.dx; }, onHorizontalDragUpdate: (details) { - final sidebar = widget.sidebar!; setState(() { var newWidth = _sidebarDragStartWidth + details.globalPosition.dx - _sidebarDragStartPosition; - if (sidebar.startWidth != null && + if (sidebar!.startWidth != null && sidebar.snapToStartBuffer != null && (newWidth - sidebar.startWidth!).abs() <= sidebar.snapToStartBuffer!) { @@ -338,8 +359,9 @@ class _MacosWindowState extends State { ), // End sidebar - if (widget.endSidebar != null) + if (endSidebar != null) AnimatedPositioned( + key: endSidebar.key, left: width - visibleEndSidebarWidth, curve: curve, duration: duration, @@ -350,38 +372,39 @@ class _MacosWindowState extends State { curve: Curves.easeInOut, color: endSidebarBackgroundColor, constraints: BoxConstraints( - minWidth: widget.endSidebar!.minWidth, - maxWidth: widget.endSidebar!.maxWidth!, + minWidth: endSidebar.minWidth, + maxWidth: endSidebar.maxWidth!, minHeight: height, maxHeight: height, ).normalize(), child: Column( children: [ - if ((widget.endSidebar?.topOffset ?? 0) > 0) - SizedBox(height: widget.endSidebar?.topOffset), + if (endSidebar.topOffset > 0) + SizedBox(height: endSidebar.topOffset), if (_endSidebarScrollController.hasClients && _endSidebarScrollController.offset > 0.0) Divider(thickness: 1, height: 1, color: dividerColor), - if (widget.endSidebar!.top != null) + if (endSidebar.top != null) Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: widget.endSidebar!.top!, + child: endSidebar.top!, ), Expanded( child: MacosScrollbar( controller: _endSidebarScrollController, child: Padding( - padding: - widget.endSidebar?.padding ?? EdgeInsets.zero, - child: widget.endSidebar! - .builder(context, _endSidebarScrollController), + padding: endSidebar.padding, + child: endSidebar.builder( + context, + _endSidebarScrollController, + ), ), ), ), - if (widget.endSidebar?.bottom != null) + if (endSidebar.bottom != null) Padding( padding: const EdgeInsets.all(16.0), - child: widget.endSidebar!.bottom!, + child: endSidebar.bottom!, ), ], ), @@ -389,7 +412,7 @@ class _MacosWindowState extends State { ), // End sidebar resizer - if (widget.endSidebar?.isResizable ?? false) + if (endSidebar?.isResizable ?? false) AnimatedPositioned( curve: curve, duration: duration, @@ -403,13 +426,12 @@ class _MacosWindowState extends State { _endSidebarDragStartPosition = details.globalPosition.dx; }, onHorizontalDragUpdate: (details) { - final endSidebar = widget.endSidebar!; setState(() { var newWidth = _endSidebarDragStartWidth - details.globalPosition.dx + _endSidebarDragStartPosition; - if (endSidebar.startWidth != null && + if (endSidebar!.startWidth != null && endSidebar.snapToStartBuffer != null && (newWidth + endSidebar.startWidth!).abs() <= endSidebar.snapToStartBuffer!) { diff --git a/pubspec.yaml b/pubspec.yaml index 40ee143e..bf720d2b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 1.12.4 +version: 1.12.5 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From a77d86f45d0f9df9cfdf755422027f00f540bf61 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Tue, 11 Apr 2023 19:24:01 +0200 Subject: [PATCH 11/45] Migrate to macos_window_utils (#377) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: bump Dart SDK version * add `.vscode/settings.json` to .gitignore * feat: add macos_window_utils * feat: wrap side in example with `TransparentMacOSSidebar` * feat: enable wallpaper tinting on content area * feat: adjust macOS window brightness depending on theme * feat: add a way to disable wallpaper tinting Wallpaper tinting is now also disabled whenever an overlay filter is used. * refactor: refactor wallpaper tinted area * fix: fix wallpaper tinting override being applied on overlay filter rebuild * fix: do not override wallpaper tinting if no `WallpaperTintingSettingsCubit` is found * refactor: stop relying on exceptions to check if `WallpaperTintingSettingsCubit` exists in widget tree * change: switch from BLoC to global wallpaper tinting settings * chore: remove unused imports * change: make canvas color mimic `NSWindow.windowBackgroundColor` * change: make wallpaper tinted area rebuild on layout change * feat: make toolbar wallpaper-tinted * doc: document `disableWallpaperTinting` * doc: improve documentation of `disableWallpaperTinting` Add link to #16296 and clarify that disabling wallpaper tinting is meant to be a temporary solution. * doc: document wallpaper tinted area * doc: document wallpaper tinting override * doc: document wallpaper tinting settings builder * doc: document wallpaper tinting settings data * change: make `numberOfWallpaperTintingOverrides` private * doc: document global wallpaper tinting settings * doc: fix typo * refactor: refactor wallpaper tinted area * feat: add `insertRepaintBoundary` property * change: enable `insertRepaintBoundary` in toolbar * change: enable `insertRepaintBoundary` in scaffold * change: export wallpaper tinted area * feat: enable wallpaper tinting on end sidebar * feat: add `sidebarState` property to window * fix: fix background color of end sidebar not matching canvas color when theme brightness does not match platform brightness * fix: fix wallpaper tinting override using `deactivate` instead of `dispose` * merge * change: comment out swift code that hides the toolbar in fullscreen mode * change: upgrade to macos_window_utils ^1.1.1 * feat: migrate window delegate to macos_window_utils * change: remove commented-out and unused Swift code * fix: fix sliver toolbar not having a backdrop filter * docs: document toolbar's `isVisible` property * docs: document sliver toolbar's `isVisible` property * docs: document sliver toolbar page's `isVisible` property * docs: document `_WallpaperTintedAreaOrBlurFilter` * refactor: remove unused `key` parameter * refactor: remove unused import * readme: update “Modern window look” * change: bump version * changelog: add entry for version 2.0.0 * changelog: add migration hint * merge: toolbar * fix: remove duplicate `debugFillProperties` * refactor: rename `pages` to `pageBuilders` * fix: remove unused import * fix: remove unused imports * change: remove unnecessary `NSWindowDelegate` from `MainFlutterWindow` * change: rename `isVisible` property to `allowWallpaperTintingOverrides` for toolbar * change: rename `isVisible` property to `allowWallpaperTintingOverrides` for sliver toolbar * fix: fix typo in comment * fix: fix `disableWallpaperTinting` having no effect * change: disable `VisualEffectSubviewContainer` when disabling wallpaper tinting in window * fix: fix some unit tests not passing Some unit tests were failing with the following message: “A Timer is still pending even after the widget tree was disposed.” The cause of that was that `VisualEffectSubviewContainer`, or more precisely `VisualEffectSubviewContainerWithGlobalKey` is using a timer to update its visual effect subview outside of the widget's `build` method. The issue is fixed by either disabling wallpaper tinting in the window (and therefore eliminating the use of `VisualEffectSubviewContainer`) or, in cases where that was impossible (such as the sidebar) running `await tester.pump(Duration.zero);` to allow the timer to complete. * remove unused import * change default canvas color `NSColor.windowBackgroundColor` was found to be inaccurate. The color has instead been changed to a color that was captured using the Digital Color Meter. * upgrade to macos_window_utils 1.1.2 * update “Modern window look” in readme for use with macos_window_utils 1.1.2 * change version to 2.0.0-beta.1 Co-authored-by: Reuben Turner * add code formatting to documentation Highlighted `macos_ui` as a code element. Co-authored-by: Reuben Turner * add missing comma to documentation Co-authored-by: Reuben Turner * change version in changelog entry to 2.0.0-beta.1 Co-authored-by: Reuben Turner * change version in pubspec.lock * format `window.dart` * replace `Colors.transparent` with `MacosColors.transparent` * document `WallpaperTintingSettingsBuilder` * document `WallpaperTintedArea` * implement `MacosWindowUtilsConfig` * export `src/macos_window_utils_config.dart` * use `MacosWindowUtilsConfig` in example * document `MacosWindowUtilsConfig` constructor * rename `_initMacosWindowUtils` to `_configureMacosWindowUtils` in example * document `MacosWindowUtilsConfig` usage in readme * replace opacity widget with repaint boundary * improve documentation for `sidebarState` * improve `MacosWindowUtilsConfig` documentation * fix inconsistencies introduced by merging * Update example/lib/main.dart * remove ”do” prefix from field names in `MacosWindowUtilsConfig` --------- Co-authored-by: Reuben Turner --- CHANGELOG.md | 10 + README.md | 104 +++------- example/lib/main.dart | 55 +++-- example/lib/pages/sliver_toolbar_page.dart | 23 ++- .../Flutter/GeneratedPluginRegistrant.swift | 2 + example/macos/Podfile | 2 +- example/macos/Podfile.lock | 8 +- .../macos/Runner.xcodeproj/project.pbxproj | 9 +- example/macos/Runner/MainFlutterWindow.swift | 75 +------ example/pubspec.lock | 14 +- lib/macos_ui.dart | 5 + lib/src/layout/scaffold.dart | 20 +- lib/src/layout/toolbar/sliver_toolbar.dart | 34 ++- lib/src/layout/toolbar/toolbar.dart | 176 +++++++++++----- lib/src/layout/wallpaper_tinted_area.dart | 156 ++++++++++++++ .../global_wallpaper_tinting_settings.dart | 46 +++++ .../wallpaper_tinting_override.dart | 41 ++++ .../wallpaper_tinting_settings_builder.dart | 46 +++++ .../wallpaper_tinting_settings_data.dart | 38 ++++ lib/src/layout/window.dart | 194 +++++++++++------- lib/src/macos_window_utils_config.dart | 120 +++++++++++ lib/src/theme/macos_theme.dart | 5 +- lib/src/theme/overlay_filter.dart | 67 +++--- lib/src/utils.dart | 27 +++ pubspec.lock | 12 +- pubspec.yaml | 3 +- test/buttons/back_button_test.dart | 1 + test/buttons/help_button_test.dart | 1 + test/buttons/icon_button_test.dart | 1 + test/buttons/push_button_test.dart | 1 + test/buttons/radio_button_test.dart | 1 + test/layout/macos_list_tile_test.dart | 1 + test/layout/resizeable_pane_test.dart | 2 + test/layout/sliver_toolbar_test.dart | 10 + test/layout/window_test.dart | 27 +++ test/selectors/date_picker_test.dart | 2 + test/theme/help_button_theme_test.dart | 1 + test/theme/icon_button_theme_test.dart | 1 + test/theme/icon_theme_test.dart | 1 + test/theme/popup_button_theme_test.dart | 1 + test/theme/pulldown_button_theme_test.dart | 1 + test/theme/push_button_theme_test.dart | 1 + test/theme/search_field_theme_test.dart | 1 + 43 files changed, 993 insertions(+), 353 deletions(-) create mode 100644 lib/src/layout/wallpaper_tinted_area.dart create mode 100644 lib/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart create mode 100644 lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_override.dart create mode 100644 lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_builder.dart create mode 100644 lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_data.dart create mode 100644 lib/src/macos_window_utils_config.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 741b038b..07499bb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [2.0.0-beta.1] +🚨 Breaking Changes 🚨 +* Migrate macos_ui to [macos_window_utils](https://pub.dev/packages/macos_window_utils), which provides the following benefits: + * Window animation smoothness is drastically improved, particularly when miniaturizing and deminiaturizing the application window. + * Some visual artifacts that occurred while the window was being (de)miniaturized (such as the application's shadow going missing) no longer occur. + * The sidebar remains transparent when the app's brightness setting mismatches the OS setting. + * Wallpaper tinting is now supported. + +To migrate an existing application, please refer to the “Modern window look” section in the README. + ## [1.12.5] * Fixed a bug where the `Sidebar.key` parameter wasn't used, which caused certain layouts to be unachievable. diff --git a/README.md b/README.md index a88690b9..4c8f9447 100644 --- a/README.md +++ b/README.md @@ -226,96 +226,42 @@ See the documentation for customizations and `ToolBar` examples. ## Modern window look A new look for macOS apps was introduced in Big Sur (macOS 11). To match that look -in your Flutter app, like our screenshots, your `macos/Runner/MainFlutterWindow.swift` -file should look like this: +in your Flutter app, macos_ui relies on [macos_window_utils](https://pub.dev/packages/macos_window_utils), which requires a minimum macOS deployment target of 10.14.6. Therefore, make sure to open the `macos/Runner.xcworkspace` folder of your project using Xcode and search for `Runner.xcodeproj`. Go to `Info` > `Deployment Target` and set the `macOS Deployment Target` to `10.14.6` or above. Then, open your project's `Podfile` (if it doesn't show up in Xcode, you can find it in your project's `macos` directory via VS Code) and set the minimum deployment version in the first line to `10.14.6` or above: -```swift -import Cocoa -import FlutterMacOS - -class BlurryContainerViewController: NSViewController { - let flutterViewController = FlutterViewController() - - init() { - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError() - } +```podspec +platform :osx, '10.14.6' +``` - override func loadView() { - let blurView = NSVisualEffectView() - blurView.autoresizingMask = [.width, .height] - blurView.blendingMode = .behindWindow - blurView.state = .active - if #available(macOS 10.14, *) { - blurView.material = .sidebar - } - self.view = blurView - } +Now, configure your window inside your `main()` as follows: - override func viewDidLoad() { - super.viewDidLoad() +```dart +/// This method initializes macos_window_utils and styles the window. +Future _configureMacosWindowUtils() async { + const config = MacosWindowUtilsConfig( + toolbarStyle: NSWindowToolbarStyle.unified, + ); + await config.apply(); +} - self.addChild(flutterViewController) +void main() async { + await _configureMacosWindowUtils(); - flutterViewController.view.frame = self.view.bounds - flutterViewController.backgroundColor = .clear // **Required post-Flutter 3.7.0** - flutterViewController.view.autoresizingMask = [.width, .height] - self.view.addSubview(flutterViewController.view) - } + runApp(const MacosUIGalleryApp()); } +``` -class MainFlutterWindow: NSWindow, NSWindowDelegate { - override func awakeFromNib() { - delegate = self - let blurryContainerViewController = BlurryContainerViewController() - let windowFrame = self.frame - self.contentViewController = blurryContainerViewController - self.setFrame(windowFrame, display: true) - - if #available(macOS 10.13, *) { - let customToolbar = NSToolbar() - customToolbar.showsBaselineSeparator = false - self.toolbar = customToolbar - } - self.titleVisibility = .hidden - self.titlebarAppearsTransparent = true - if #available(macOS 11.0, *) { - // Use .expanded if the app will have a title bar, else use .unified - self.toolbarStyle = .unified - } - - self.isMovableByWindowBackground = true - self.styleMask.insert(NSWindow.StyleMask.fullSizeContentView) - - self.isOpaque = false - self.backgroundColor = .clear - - RegisterGeneratedPlugins(registry: blurryContainerViewController.flutterViewController) - - super.awakeFromNib() - } +Please note that if you are using a title bar (`TitleBar`) in your `MacosWindow`, you should set the `toolbarStyle` of your window to `NSWindowToolbarStyle.expanded`, in order to properly align the close, minimize, zoom window buttons: - func window(_ window: NSWindow, willUseFullScreenPresentationOptions proposedOptions: NSApplication.PresentationOptions = []) -> NSApplication.PresentationOptions { - return [.autoHideToolbar, .autoHideMenuBar, .fullScreen] - } - - func windowWillEnterFullScreen(_ notification: Notification) { - self.toolbar?.isVisible = false - } - - func windowDidExitFullScreen(_ notification: Notification) { - self.toolbar?.isVisible = true - } +```dart +Future _configureMacosWindowUtils() async { + const config = MacosWindowUtilsConfig( + toolbarStyle: NSWindowToolbarStyle.expanded, + ); + await config.apply(); } - ``` -See [this issue comment](https://github.com/flutter/flutter/issues/59969#issuecomment-916682559) for more details on the new look and explanations for how it works. - -Please note that if you are using a title bar (`TitleBar`) in your `MacosWindow`, you should set the `toolbarStyle` of NSWindow to `.expanded`, in order to properly align the close, minimize, zoom window buttons. In any other case, you should keep it as `.unified`. This must be set beforehand, i.e. it cannot be switched in runtime. +In any other case, you should keep it as `NSWindowToolbarStyle.unified`. ## ToolBar diff --git a/example/lib/main.dart b/example/lib/main.dart index 3d1a24e7..cba22164 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -13,7 +13,15 @@ import 'package:provider/provider.dart'; import 'theme.dart'; -void main() { +/// This method initializes macos_window_utils and styles the window. +Future _configureMacosWindowUtils() async { + const config = MacosWindowUtilsConfig(); + await config.apply(); +} + +Future main() async { + await _configureMacosWindowUtils(); + runApp(const MacosUIGalleryApp()); } @@ -55,23 +63,25 @@ class _WidgetGalleryState extends State { late final searchFieldController = TextEditingController(); - final List pages = [ - CupertinoTabView( - builder: (_) => const ButtonsPage(), - ), - const IndicatorsPage(), - const FieldsPage(), - const ColorsPage(), - const Center( - child: MacosIcon( - CupertinoIcons.add, - ), - ), - const DialogsPage(), - const ToolbarPage(), - const SliverToolbarPage(), - const TabViewPage(), - const SelectorsPage(), + final List pageBuilders = [ + (bool isVisible) => CupertinoTabView( + builder: (_) => const ButtonsPage(), + ), + (bool isVisible) => const IndicatorsPage(), + (bool isVisible) => const FieldsPage(), + (bool isVisible) => const ColorsPage(), + (bool isVisible) => const Center( + child: MacosIcon( + CupertinoIcons.add, + ), + ), + (bool isVisible) => const DialogsPage(), + (bool isVisible) => const ToolbarPage(), + (bool isVisible) => SliverToolbarPage( + isVisible: isVisible, + ), + (bool isVisible) => const TabViewPage(), + (bool isVisible) => const SelectorsPage(), ]; @override @@ -291,7 +301,14 @@ class _WidgetGalleryState extends State { ), child: IndexedStack( index: pageIndex, - children: pages, + children: pageBuilders + .asMap() + .map((index, builder) { + final widget = builder(index == pageIndex); + return MapEntry(index, widget); + }) + .values + .toList(), ), ), ); diff --git a/example/lib/pages/sliver_toolbar_page.dart b/example/lib/pages/sliver_toolbar_page.dart index 8c2039a5..be2e680c 100644 --- a/example/lib/pages/sliver_toolbar_page.dart +++ b/example/lib/pages/sliver_toolbar_page.dart @@ -3,7 +3,27 @@ import 'package:flutter/material.dart'; import 'package:macos_ui/macos_ui.dart'; class SliverToolbarPage extends StatefulWidget { - const SliverToolbarPage({super.key}); + const SliverToolbarPage({super.key, required this.isVisible}); + + /// Whether this [SliverToolbarPage] is currently visible on the screen + /// (that is, not e.g. hidden by an [IndexedStack]). + /// + /// By default, macos_ui applies wallpaper tinting to the application's + /// window to match macOS' native appearance: + /// + /// + /// + /// However, this effect is realized by inserting `NSVisualEffectView`s behind + /// Flutter's canvas and turning the background of areas that are meant to be + /// affected by wallpaper tinting transparent. Since Flutter's + /// [`ImageFilter.blur`](https://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.blur.html) + /// does not support transparency, wallpaper tinting is disabled automatically + /// when this widget's [isVisible] is true. + /// + /// This is meant to be a temporary solution until + /// [#16296](https://github.com/flutter/flutter/issues/16296) is resolved in + /// the Flutter project. + final bool isVisible; @override State createState() => _SliverToolbarPageState(); @@ -27,6 +47,7 @@ class _SliverToolbarPageState extends State { floating: floating, pinned: pinned, toolbarOpacity: opacity, + allowWallpaperTintingOverrides: widget.isVisible, actions: [ ToolBarIconButton( label: 'Pinned', diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 722b1dd3..b06f1179 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,7 +6,9 @@ import FlutterMacOS import Foundation import macos_ui +import macos_window_utils func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin")) + MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) } diff --git a/example/macos/Podfile b/example/macos/Podfile index 049abe29..7ed4260c 100644 --- a/example/macos/Podfile +++ b/example/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.14' +platform :osx, '10.14.6' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 67acca3c..42b5eb53 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -2,21 +2,27 @@ PODS: - FlutterMacOS (1.0.0) - macos_ui (0.1.0): - FlutterMacOS + - macos_window_utils (1.0.0): + - FlutterMacOS DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - macos_ui (from `Flutter/ephemeral/.symlinks/plugins/macos_ui/macos`) + - macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`) EXTERNAL SOURCES: FlutterMacOS: :path: Flutter/ephemeral macos_ui: :path: Flutter/ephemeral/.symlinks/plugins/macos_ui/macos + macos_window_utils: + :path: Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 macos_ui: 6229a8922cd97bafb7d9636c8eb8dfb0744183ca + macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 -PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 +PODFILE CHECKSUM: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9 COCOAPODS: 1.11.3 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index e7c1634c..2c562a4e 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -61,7 +61,7 @@ 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; tabWidth = 2; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; @@ -94,7 +94,6 @@ 94ECD9F878BC8EB5F0E7094E /* Pods-Runner.release.xcconfig */, AFB798A3289226D0E5AB9985 /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -405,7 +404,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.14.6; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -484,7 +483,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.14.6; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -531,7 +530,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.14.6; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/example/macos/Runner/MainFlutterWindow.swift b/example/macos/Runner/MainFlutterWindow.swift index 22fb7aee..2722837e 100644 --- a/example/macos/Runner/MainFlutterWindow.swift +++ b/example/macos/Runner/MainFlutterWindow.swift @@ -1,82 +1,15 @@ import Cocoa import FlutterMacOS -class BlurryContainerViewController: NSViewController { - let flutterViewController = FlutterViewController() - - init() { - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError() - } - - override func loadView() { - let blurView = NSVisualEffectView() - blurView.autoresizingMask = [.width, .height] - blurView.blendingMode = .behindWindow - blurView.state = .active - if #available(macOS 10.14, *) { - blurView.material = .sidebar - } - self.view = blurView - } - - override func viewDidLoad() { - super.viewDidLoad() - - self.addChild(flutterViewController) - - flutterViewController.view.frame = self.view.bounds - flutterViewController.backgroundColor = .clear - flutterViewController.view.autoresizingMask = [.width, .height] - self.view.addSubview(flutterViewController.view) - } -} - -class MainFlutterWindow: NSWindow, NSWindowDelegate { +class MainFlutterWindow: NSWindow { override func awakeFromNib() { - delegate = self - let blurryContainerViewController = BlurryContainerViewController() + let flutterViewController = FlutterViewController.init() let windowFrame = self.frame - self.contentViewController = blurryContainerViewController + self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) - if #available(macOS 10.13, *) { - let customToolbar = NSToolbar() - customToolbar.showsBaselineSeparator = false - self.toolbar = customToolbar - } - self.titleVisibility = .hidden - self.titlebarAppearsTransparent = true - if #available(macOS 11.0, *) { - // Use .expanded if the app will have a title bar, else use .unified - self.toolbarStyle = .unified - } - - self.isMovableByWindowBackground = true - self.styleMask.insert(NSWindow.StyleMask.fullSizeContentView) - - self.isOpaque = false - self.backgroundColor = .clear - - RegisterGeneratedPlugins(registry: blurryContainerViewController.flutterViewController) + RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } - - // Hides the toolbar when in fullscreen mode - func window(_ window: NSWindow, willUseFullScreenPresentationOptions proposedOptions: NSApplication.PresentationOptions = []) -> NSApplication.PresentationOptions { - - return [.autoHideToolbar, .autoHideMenuBar, .fullScreen] - } - - func windowWillEnterFullScreen(_ notification: Notification) { - self.toolbar?.isVisible = false - } - - func windowDidExitFullScreen(_ notification: Notification) { - self.toolbar?.isVisible = true - } } diff --git a/example/pubspec.lock b/example/pubspec.lock index 0d45eb9e..a440e555 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,15 @@ packages: path: ".." relative: true source: path - version: "1.12.5" + version: "2.0.0-beta.1" + macos_window_utils: + dependency: transitive + description: + name: macos_window_utils + sha256: "510de576b5432dd9ef9e4c258abcc021c6dfbb17a78a344688848a6784b352b8" + url: "https://pub.dev" + source: hosted + version: "1.1.2" matcher: dependency: transitive description: @@ -208,5 +216,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.18.0 <3.0.0" - flutter: ">=1.20.0" + dart: ">=2.18.5 <3.0.0" + flutter: ">=3.7.0" diff --git a/lib/macos_ui.dart b/lib/macos_ui.dart index 6a71ceec..4d3ac251 100644 --- a/lib/macos_ui.dart +++ b/lib/macos_ui.dart @@ -82,3 +82,8 @@ export 'src/theme/search_field_theme.dart'; export 'src/theme/time_picker_theme.dart'; export 'src/theme/tooltip_theme.dart'; export 'src/theme/typography.dart'; +export 'src/layout/wallpaper_tinted_area.dart'; +export 'src/macos_window_utils_config.dart'; + +export 'package:macos_window_utils/macos_window_utils.dart'; +export 'package:macos_window_utils/macos/ns_window_delegate.dart'; diff --git a/lib/src/layout/scaffold.dart b/lib/src/layout/scaffold.dart index 712ab1a7..871dba0d 100644 --- a/lib/src/layout/scaffold.dart +++ b/lib/src/layout/scaffold.dart @@ -1,14 +1,8 @@ import 'dart:math' as math; import 'package:flutter/rendering.dart'; -import 'package:macos_ui/src/layout/content_area.dart'; -import 'package:macos_ui/src/layout/resizable_pane.dart'; -import 'package:macos_ui/src/layout/sidebar/sidebar.dart'; -import 'package:macos_ui/src/layout/title_bar.dart'; -import 'package:macos_ui/src/layout/toolbar/toolbar.dart'; -import 'package:macos_ui/src/layout/window.dart'; +import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; -import 'package:macos_ui/src/theme/macos_theme.dart'; /// A macOS page widget. /// @@ -92,11 +86,15 @@ class _MacosScaffoldState extends State { top: 0, width: width, height: height, - child: MediaQuery( - data: mediaQuery.copyWith( - padding: EdgeInsets.only(top: topPadding), + child: WallpaperTintedArea( + backgroundColor: backgroundColor, + insertRepaintBoundary: true, + child: MediaQuery( + data: mediaQuery.copyWith( + padding: EdgeInsets.only(top: topPadding), + ), + child: _ScaffoldBody(children: children), ), - child: _ScaffoldBody(children: children), ), ), diff --git a/lib/src/layout/toolbar/sliver_toolbar.dart b/lib/src/layout/toolbar/sliver_toolbar.dart index a5a00dbf..a96d571b 100644 --- a/lib/src/layout/toolbar/sliver_toolbar.dart +++ b/lib/src/layout/toolbar/sliver_toolbar.dart @@ -40,6 +40,7 @@ class SliverToolBar extends StatefulWidget with Diagnosticable { this.pinned = true, this.floating = false, this.toolbarOpacity = 0.9, + this.allowWallpaperTintingOverrides = true, }); /// Specifies the height of this [ToolBar]. @@ -138,6 +139,30 @@ class SliverToolBar extends StatefulWidget with Diagnosticable { /// Defaults to `0.9`. final double toolbarOpacity; + /// Whether this [SliverToolBar] is allowed to perform wallpaper tinting + /// overrides. + /// + /// This property is supposed to be set to true when this [SliverToolBar] is + /// currently visible on the screen (that is, not e.g. hidden by an + /// [IndexedStack]). + /// + /// By default, macos_ui applies wallpaper tinting to the application's + /// window to match macOS' native appearance: + /// + /// + /// + /// However, this effect is realized by inserting `NSVisualEffectView`s behind + /// Flutter's canvas and turning the background of areas that are meant to be + /// affected by wallpaper tinting transparent. Since Flutter's + /// [`ImageFilter.blur`](https://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.blur.html) + /// does not support transparency, wallpaper tinting is disabled automatically + /// when this widget's [allowWallpaperTintingOverrides] is true. + /// + /// This is meant to be a temporary solution until + /// [#16296](https://github.com/flutter/flutter/issues/16296) is resolved in + /// the Flutter project. + final bool allowWallpaperTintingOverrides; + @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); @@ -204,6 +229,7 @@ class _SliverToolBarState extends State floating: widget.floating, pinned: widget.pinned, toolbarOpacity: widget.toolbarOpacity, + allowWallpaperTintingOverrides: widget.allowWallpaperTintingOverrides, vsync: this, ), ), @@ -228,6 +254,7 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate { required this.floating, required this.pinned, required this.toolbarOpacity, + required this.allowWallpaperTintingOverrides, }); final double height; @@ -244,6 +271,7 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate { final bool floating; final bool pinned; final double toolbarOpacity; + final bool allowWallpaperTintingOverrides; @override double get minExtent => _kToolbarHeight; @@ -293,6 +321,8 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate { dividerColor: dividerColor, alignment: alignment, height: height, + enableBlur: true, + allowWallpaperTintingOverrides: allowWallpaperTintingOverrides, ), ); } @@ -311,6 +341,8 @@ class _SliverToolBarDelegate extends SliverPersistentHeaderDelegate { centerTitle != oldDelegate.centerTitle || dividerColor != oldDelegate.dividerColor || floating != oldDelegate.floating || - pinned != oldDelegate.pinned; + pinned != oldDelegate.pinned || + allowWallpaperTintingOverrides != + oldDelegate.allowWallpaperTintingOverrides; } } diff --git a/lib/src/layout/toolbar/toolbar.dart b/lib/src/layout/toolbar/toolbar.dart index a9685f6a..bad588c8 100644 --- a/lib/src/layout/toolbar/toolbar.dart +++ b/lib/src/layout/toolbar/toolbar.dart @@ -3,6 +3,7 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/layout/toolbar/overflow_handler.dart'; +import 'package:macos_ui/src/layout/wallpaper_tinting_settings/wallpaper_tinting_override.dart'; import 'package:macos_ui/src/library.dart'; /// Defines the height of a regular-sized [ToolBar] @@ -43,6 +44,8 @@ class ToolBar extends StatefulWidget with Diagnosticable { this.actions, this.centerTitle = false, this.dividerColor, + this.allowWallpaperTintingOverrides = true, + this.enableBlur = false, }); /// Specifies the height of this [ToolBar]. @@ -120,6 +123,35 @@ class ToolBar extends StatefulWidget with Diagnosticable { /// Set this to `MacosColors.transparent` to remove. final Color? dividerColor; + /// Whether this [ToolBar] is allowed to perform wallpaper tinting overrides. + /// + /// This property is supposed to be set to true when this [ToolBar] is + /// currently visible on the screen (that is, not e.g. hidden by an + /// [IndexedStack]). + /// + /// This parameter only needs to be supplied when [enableBlur] is true. + /// + /// By default, macos_ui applies wallpaper tinting to the application's + /// window to match macOS' native appearance: + /// + /// + /// + /// However, this effect is realized by inserting `NSVisualEffectView`s behind + /// Flutter's canvas and turning the background of areas that are meant to be + /// affected by wallpaper tinting transparent. Since Flutter's + /// [`ImageFilter.blur`](https://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.blur.html) + /// does not support transparency, wallpaper tinting is disabled automatically + /// when this widget's [enableBlur] and [allowWallpaperTintingOverrides] is + /// true. + /// + /// This is meant to be a temporary solution until + /// [#16296](https://github.com/flutter/flutter/issues/16296) is resolved in + /// the Flutter project. + final bool allowWallpaperTintingOverrides; + + /// Whether this [ToolBar] should have a blur backdrop filter applied to it. + final bool enableBlur; + @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); @@ -233,57 +265,56 @@ class _ToolBarState extends State { left: !kIsWeb && isMacOS ? 70 : 0, ), ), - child: ClipRect( - child: BackdropFilter( - filter: widget.decoration?.color?.opacity == 1 - ? ImageFilter.blur() - : ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0), - child: Container( - alignment: widget.alignment, - padding: widget.padding, - decoration: BoxDecoration( - color: theme.canvasColor, - border: Border(bottom: BorderSide(color: dividerColor)), - ).copyWith( - color: widget.decoration?.color, - image: widget.decoration?.image, - border: widget.decoration?.border, - borderRadius: widget.decoration?.borderRadius, - boxShadow: widget.decoration?.boxShadow, - gradient: widget.decoration?.gradient, - ), - child: NavigationToolbar( - middle: title, - centerMiddle: widget.centerTitle, - trailing: OverflowHandler( - overflowBreakpoint: overflowBreakpoint, - overflowWidget: ToolbarOverflowButton( - isDense: doAllItemsShowLabel, - overflowContentBuilder: (context) => ToolbarOverflowMenu( - children: overflowedActions - .map((action) => action.build( - context, - ToolbarItemDisplayMode.overflowed, - )) - .toList(), - ), + child: _WallpaperTintedAreaOrBlurFilter( + enableWallpaperTintedArea: !widget.enableBlur, + isWidgetVisible: widget.allowWallpaperTintingOverrides, + backgroundColor: theme.canvasColor, + widgetOpacity: widget.decoration?.color?.opacity, + child: Container( + alignment: widget.alignment, + padding: widget.padding, + decoration: BoxDecoration( + border: Border(bottom: BorderSide(color: dividerColor)), + ).copyWith( + color: widget.decoration?.color, + image: widget.decoration?.image, + border: widget.decoration?.border, + borderRadius: widget.decoration?.borderRadius, + boxShadow: widget.decoration?.boxShadow, + gradient: widget.decoration?.gradient, + ), + child: NavigationToolbar( + middle: title, + centerMiddle: widget.centerTitle, + trailing: OverflowHandler( + overflowBreakpoint: overflowBreakpoint, + overflowWidget: ToolbarOverflowButton( + isDense: doAllItemsShowLabel, + overflowContentBuilder: (context) => ToolbarOverflowMenu( + children: overflowedActions + .map((action) => action.build( + context, + ToolbarItemDisplayMode.overflowed, + )) + .toList(), ), - children: inToolbarActions - .map((e) => - e.build(context, ToolbarItemDisplayMode.inToolbar)) - .toList(), - overflowChangedCallback: (hiddenItems) { - setState(() => overflowedActionsCount = hiddenItems.length); - }, - ), - middleSpacing: 8, - leading: SafeArea( - top: false, - right: false, - bottom: false, - left: !(scope?.isSidebarShown ?? false), - child: leading ?? const SizedBox.shrink(), ), + children: inToolbarActions + .map( + (e) => e.build(context, ToolbarItemDisplayMode.inToolbar), + ) + .toList(), + overflowChangedCallback: (hiddenItems) { + setState(() => overflowedActionsCount = hiddenItems.length); + }, + ), + middleSpacing: 8, + leading: SafeArea( + top: false, + right: false, + bottom: false, + left: !(scope?.isSidebarShown ?? false), + child: leading ?? const SizedBox.shrink(), ), ), ), @@ -319,3 +350,50 @@ abstract class ToolbarItem with Diagnosticable { /// for the given display mode (in toolbar or overflowed). Widget build(BuildContext context, ToolbarItemDisplayMode displayMode); } + +/// Wraps the widget in either a [WallpaperTintingOverride] or a blurry backdrop +/// filter. +class _WallpaperTintedAreaOrBlurFilter extends StatelessWidget { + const _WallpaperTintedAreaOrBlurFilter({ + required this.child, + required this.enableWallpaperTintedArea, + required this.backgroundColor, + required this.widgetOpacity, + required this.isWidgetVisible, + }); + + final Widget child; + final bool enableWallpaperTintedArea; + final Color backgroundColor; + final double? widgetOpacity; + final bool isWidgetVisible; + + @override + Widget build(BuildContext context) { + if (enableWallpaperTintedArea) { + return WallpaperTintedArea( + backgroundColor: backgroundColor, + insertRepaintBoundary: true, + child: child, + ); + } + + if (!isWidgetVisible) { + return child; + } + + return WallpaperTintingOverride( + child: ClipRect( + child: BackdropFilter( + filter: widgetOpacity == 1.0 + ? ImageFilter.blur() + : ImageFilter.blur( + sigmaX: 5.0, + sigmaY: 5.0, + ), + child: child, + ), + ), + ); + } +} diff --git a/lib/src/layout/wallpaper_tinted_area.dart b/lib/src/layout/wallpaper_tinted_area.dart new file mode 100644 index 00000000..607ae808 --- /dev/null +++ b/lib/src/layout/wallpaper_tinted_area.dart @@ -0,0 +1,156 @@ +import 'package:flutter/cupertino.dart'; +import 'package:macos_ui/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart'; +import 'package:macos_ui/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_builder.dart'; +import 'package:macos_window_utils/macos/ns_visual_effect_view_material.dart'; +import 'package:macos_window_utils/widgets/visual_effect_subview_container/visual_effect_subview_container.dart'; + +/// A widget that applies a wallpaper tint to its child widget. +/// +/// This widget only works on macOS. +/// +/// The [backgroundColor] is the color to apply to the background when wallpaper +/// tinting is disabled. If [insertRepaintBoundary] is true, a [RepaintBoundary] +/// is inserted above this widget in the widget tree. In some instances, it may +/// be necessary to insert a [RepaintBoundary] to ensure proper rendering. +/// The [child] is the widget below this widget in the tree. +/// +/// Example: +/// +/// ```dart +/// WallpaperTintedArea( +/// backgroundColor: MacosColors.white, +/// child: Text('Hello World'), +/// ) +/// ``` +class WallpaperTintedArea extends StatelessWidget { + /// Creates a [WallpaperTintedArea]. + /// + /// Widgets wrapped in this widget will have a wallpaper tint applied to them. + /// + /// **Note:** This widget only works on macOS. + const WallpaperTintedArea({ + super.key, + required this.backgroundColor, + this.insertRepaintBoundary = false, + this.child, + }); + + /// The color to apply to the background when wallpaper tinting is disabled. + final Color backgroundColor; + + /// Whether to insert a [RepaintBoundary] above this widget in the widget + /// tree. + /// + /// In some instances, it may be necessary to insert a [RepaintBoundary] above + /// this widget into the widget tree to ensure that this widget is rendered + /// properly. + final bool insertRepaintBoundary; + + /// The widget below this widget in the tree. + final Widget? child; + + @override + Widget build(BuildContext context) { + if (insertRepaintBoundary) { + return RepaintBoundary( + child: _WallpaperTintedAreaLayoutBuilder( + backgroundColor: backgroundColor, + child: child, + ), + ); + } + + return _WallpaperTintedAreaLayoutBuilder( + backgroundColor: backgroundColor, + child: child, + ); + } +} + +class _WallpaperTintedAreaLayoutBuilder extends StatelessWidget { + const _WallpaperTintedAreaLayoutBuilder({ + required this.backgroundColor, + required this.child, + }); + + /// The color to apply to the background when wallpaper tinting is disabled. + final Color backgroundColor; + + /// The widget below this widget in the tree. + final Widget? child; + + @override + Widget build(BuildContext context) { + if (GlobalWallpaperTintingSettings + .data.isWallpaperTintingDisabledByWindow) { + return Container( + decoration: BoxDecoration( + color: backgroundColor, + ), + child: child, + ); + } + + // This LayoutBuilder forces the widget to be rebuilt when a layout change + // is detected. This is necessary for the VisualEffectSubviewContainer to + // be updated. + return LayoutBuilder( + builder: (context, _) { + return VisualEffectSubviewContainer( + material: NSVisualEffectViewMaterial.windowBackground, + child: WallpaperTintingSettingsBuilder( + builder: (context, data) { + final isWallpaperTintingEnabled = data.isWallpaperTintingEnabled; + + return _WallpaperTintedAreaTweenAnimationBuilder( + isWallpaperTintingEnabled: isWallpaperTintingEnabled, + backgroundColor: backgroundColor, + child: child, + ); + }, + ), + ); + }, + ); + } +} + +class _WallpaperTintedAreaTweenAnimationBuilder extends StatelessWidget { + const _WallpaperTintedAreaTweenAnimationBuilder({ + required this.isWallpaperTintingEnabled, + required this.backgroundColor, + required this.child, + }); + + /// Whether wallpaper tinting is enabled. + final bool isWallpaperTintingEnabled; + + /// The color to apply to the background when wallpaper tinting is disabled. + final Color backgroundColor; + + /// The widget below this widget in the tree. + final Widget? child; + + @override + Widget build(BuildContext context) { + return TweenAnimationBuilder( + duration: const Duration(milliseconds: 100), + tween: Tween( + begin: isWallpaperTintingEnabled ? 0.0 : 1.0, + end: isWallpaperTintingEnabled ? 0.0 : 1.0, + ), + builder: (context, value, child) { + return Container( + decoration: BoxDecoration( + color: backgroundColor.withOpacity(value), + backgroundBlendMode: BlendMode.src, + ), + child: child, + ); + }, + child: RepaintBoundary( + child: child, + ), + ); + } +} diff --git a/lib/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart b/lib/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart new file mode 100644 index 00000000..78ec46f1 --- /dev/null +++ b/lib/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart @@ -0,0 +1,46 @@ +import 'dart:async'; + +import 'package:macos_ui/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_data.dart'; + +/// A class that provides a global instance of [WallpaperTintingSettingsData]. +class GlobalWallpaperTintingSettings { + /// The [WallpaperTintingSettingsData] instance. + static final WallpaperTintingSettingsData data = + WallpaperTintingSettingsData(); + + /// The [StreamController] for an event stream that is triggered when [data] + /// changes. + static final _onDataChangedStreamController = + StreamController.broadcast(); + + /// A stream that can be used to listen to [data] changes. + static Stream get onDataChangedStream => + _onDataChangedStreamController.stream; + + /// Gets whether wallpaper tinting should be enabled. + static bool get isWallpaperTintingEnabled => data.isWallpaperTintingEnabled; + + /// Increments the number of active overrides. + static void addWallpaperTintingOverride() { + data.addOverride(); + _onDataChangedStreamController.add(data); + } + + /// Decrements the number of active overrides. + static void removeWallpaperTintingOverride() { + data.removeOverride(); + _onDataChangedStreamController.add(data); + } + + /// Disables wallpaper tinting altogether. + static void disableWallpaperTinting() { + data.disableWallpaperTinting(); + _onDataChangedStreamController.add(data); + } + + /// Allows wallpaper tinting, unless overridden. + static void allowWallpaperTinting() { + data.allowWallpaperTinting(); + _onDataChangedStreamController.add(data); + } +} diff --git a/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_override.dart b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_override.dart new file mode 100644 index 00000000..7cd05629 --- /dev/null +++ b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_override.dart @@ -0,0 +1,41 @@ +import 'package:flutter/widgets.dart'; +import 'package:macos_ui/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart'; + +class WallpaperTintingOverride extends StatefulWidget { + /// Creates a [WallpaperTintingOverride]. + /// + /// Including this widget in the widget tree will disable wallpaper tinting + /// globally. It is intended to be used by [MacosOverlayFilter] to disable + /// wallpaper tinting when an overlay filter is active, since + /// [`ImageFilter.blur`](https://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.blur.html) + /// does not support transparency. + const WallpaperTintingOverride({super.key, this.child}); + + /// The widget below this widget in the tree. + final Widget? child; + + @override + State createState() => + _WallpaperTintingOverrideState(); +} + +class _WallpaperTintingOverrideState extends State { + @override + void initState() { + super.initState(); + + GlobalWallpaperTintingSettings.addWallpaperTintingOverride(); + } + + @override + void dispose() { + GlobalWallpaperTintingSettings.removeWallpaperTintingOverride(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return widget.child ?? const SizedBox(); + } +} diff --git a/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_builder.dart b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_builder.dart new file mode 100644 index 00000000..19fe2873 --- /dev/null +++ b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_builder.dart @@ -0,0 +1,46 @@ +import 'package:flutter/widgets.dart'; +import 'package:macos_ui/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart'; + +import 'wallpaper_tinting_settings_data.dart'; + +/// A widget that listens for changes to [WallpaperTintingSettingsData] and +/// rebuilds with the latest data when a change is detected. +/// +/// The [builder] callback is called whenever [WallpaperTintingSettingsData] +/// changes. It should build a widget using the latest +/// [WallpaperTintingSettingsData]. +/// +/// Example: +/// +/// ```dart +/// WallpaperTintingSettingsBuilder( +/// builder: (context, data) { +/// return Text( +/// 'isWallpaperTintingEnabled: ${data.isWallpaperTintingEnabled}', +/// ); +/// }, +/// ) +/// ``` +class WallpaperTintingSettingsBuilder extends StatelessWidget { + /// Creates a [WallpaperTintingSettingsBuilder]. + /// + /// This widget can be used to listen to [WallpaperTintingSettingsData] + /// changes and rebuild if a change has been detected. + const WallpaperTintingSettingsBuilder({super.key, required this.builder}); + + /// Called when [WallpaperTintingSettingsData] changes. + final Widget Function(BuildContext, WallpaperTintingSettingsData) builder; + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: GlobalWallpaperTintingSettings.onDataChangedStream, + initialData: GlobalWallpaperTintingSettings.data, + builder: (context, snapshot) { + final data = snapshot.data ?? GlobalWallpaperTintingSettings.data; + + return builder(context, data); + }, + ); + } +} diff --git a/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_data.dart b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_data.dart new file mode 100644 index 00000000..07c8aea4 --- /dev/null +++ b/lib/src/layout/wallpaper_tinting_settings/wallpaper_tinting_settings_data.dart @@ -0,0 +1,38 @@ +/// Holds data related to wallpaper tinting. +class WallpaperTintingSettingsData { + /// The number of wallpaper tinting overrides that are currently active. + /// + /// A wallpaper tinting override causes wallpaper tinting to be disabled. + int _numberOfWallpaperTintingOverrides = 0; + + /// Whether wallpaper tinting is disabled by the application's window. + bool _isWallpaperTintingDisabledByWindow = false; + + /// Gets whether wallpaper tinting should be enabled. + bool get isWallpaperTintingEnabled => + !_isWallpaperTintingDisabledByWindow && + _numberOfWallpaperTintingOverrides == 0; + + /// Gets whether wallpaper tinting is disabled by the application's window. + bool get isWallpaperTintingDisabledByWindow => + _isWallpaperTintingDisabledByWindow; + + /// Increments the number of active overrides. + void addOverride() => _numberOfWallpaperTintingOverrides += 1; + + /// Decrements the number of active overrides. + void removeOverride() { + _numberOfWallpaperTintingOverrides -= 1; + assert(_numberOfWallpaperTintingOverrides >= 0); + } + + /// Disables wallpaper tinting altogether. + void disableWallpaperTinting() { + _isWallpaperTintingDisabledByWindow = true; + } + + /// Allows wallpaper tinting, unless overridden. + void allowWallpaperTinting() { + _isWallpaperTintingDisabledByWindow = false; + } +} diff --git a/lib/src/layout/window.dart b/lib/src/layout/window.dart index 21e82e07..3171decc 100644 --- a/lib/src/layout/window.dart +++ b/lib/src/layout/window.dart @@ -2,14 +2,10 @@ import 'dart:math' as math; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:macos_ui/src/layout/scrollbar.dart'; -import 'package:macos_ui/src/layout/content_area.dart'; -import 'package:macos_ui/src/layout/resizable_pane.dart'; -import 'package:macos_ui/src/layout/scaffold.dart'; -import 'package:macos_ui/src/layout/sidebar/sidebar.dart'; -import 'package:macos_ui/src/layout/title_bar.dart'; +import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/layout/wallpaper_tinting_settings/global_wallpaper_tinting_settings.dart'; import 'package:macos_ui/src/library.dart'; -import 'package:macos_ui/src/theme/macos_theme.dart'; +import 'package:macos_window_utils/widgets/transparent_macos_sidebar.dart'; /// A basic frame layout. /// @@ -28,6 +24,8 @@ class MacosWindow extends StatefulWidget { this.sidebar, this.backgroundColor, this.endSidebar, + this.disableWallpaperTinting = false, + this.sidebarState = NSVisualEffectViewState.followsWindowActiveState, }); /// Specifies the background color for the Window. @@ -47,6 +45,40 @@ class MacosWindow extends StatefulWidget { /// A sidebar to display at the right of the window. final Sidebar? endSidebar; + /// Whether wallpaper tinting should be disabled. + /// + /// By default, `macos_ui` applies wallpaper tinting to the application's + /// window to match macOS' native appearance: + /// + /// + /// + /// However, this effect is realized by inserting `NSVisualEffectView`s behind + /// Flutter's canvas and turning the background of areas that are meant to be + /// affected by wallpaper tinting transparent. Since Flutter's + /// [`ImageFilter.blur`](https://api.flutter.dev/flutter/dart-ui/ImageFilter/ImageFilter.blur.html) + /// does not support transparency, wallpaper tinting is disabled automatically + /// when a [MacosOverlayFilter] is present in the widget tree. + /// + /// This is meant to be a temporary solution until + /// [#16296](https://github.com/flutter/flutter/issues/16296) is resolved in + /// the Flutter project. + /// + /// Since the disabling of wallpaper tinting may be found to be too noticeable, + /// this property may be used to disable wallpaper tinting outright. + final bool disableWallpaperTinting; + + /// The state of the sidebar's [NSVisualEffectView]. + /// + /// Possible values are: + /// + /// - [NSVisualEffectViewState.active]: The sidebar is always active. + /// - [NSVisualEffectViewState.inactive]: The sidebar is always inactive. + /// - [NSVisualEffectViewState.followsWindowActiveState]: The sidebar's state + /// follows the window's active state. + /// + /// Defaults to [NSVisualEffectViewState.followsWindowActiveState]. + final NSVisualEffectViewState sidebarState; + @override State createState() => _MacosWindowState(); } @@ -74,6 +106,11 @@ class _MacosWindowState extends State { _endSidebarWidth = (widget.endSidebar?.startWidth ?? widget.endSidebar?.minWidth) ?? _endSidebarWidth; + + widget.disableWallpaperTinting + ? GlobalWallpaperTintingSettings.disableWallpaperTinting() + : GlobalWallpaperTintingSettings.allowWallpaperTinting(); + _addSidebarScrollControllerListenerIfNeeded(); _addEndSidebarScrollControllerListenerIfNeeded(); } @@ -133,6 +170,7 @@ class _MacosWindowState extends State { void dispose() { _sidebarScrollController.dispose(); _endSidebarScrollController.dispose(); + super.dispose(); } @@ -161,26 +199,17 @@ class _MacosWindowState extends State { // Respect the sidebar color override from parent if one is given if (sidebar?.decoration?.color != null) { sidebarBackgroundColor = sidebar!.decoration!.color!; - } else if (isMac && - MediaQuery.of(context).platformBrightness.isDark == - theme.brightness.isDark) { - // Only show blurry, transparent sidebar when platform brightness and app - // brightness are the same, otherwise it looks awful. Also only make the - // sidebar transparent on native Mac, or it will just be flat black or - // white. - sidebarBackgroundColor = Colors.transparent; } else { - sidebarBackgroundColor = theme.brightness.isDark - ? CupertinoColors.tertiarySystemBackground.darkColor - : CupertinoColors.systemGrey6.color; + sidebarBackgroundColor = MacosColors.transparent; } + // Set the application window's brightness on macOS + MacOSBrightnessOverrideHandler.ensureMatchingBrightness(theme.brightness); + // Respect the end sidebar color override from parent if one is given if (endSidebar?.decoration?.color != null) { endSidebarBackgroundColor = endSidebar!.decoration!.color!; - } else if (isMac && - MediaQuery.of(context).platformBrightness.isDark == - theme.brightness.isDark) { + } else if (isMac) { endSidebarBackgroundColor = theme.canvasColor; } else { endSidebarBackgroundColor = theme.brightness.isDark @@ -202,6 +231,7 @@ class _MacosWindowState extends State { final visibleSidebarWidth = canShowSidebar ? _sidebarWidth : 0.0; final visibleEndSidebarWidth = canShowEndSidebar ? _endSidebarWidth : 0.0; + final sidebarState = widget.sidebarState; final layout = Stack( children: [ @@ -233,36 +263,41 @@ class _MacosWindowState extends State { minHeight: height, maxHeight: height, ).normalize(), - child: Column( - children: [ - if (sidebar.topOffset > 0) - SizedBox(height: sidebar.topOffset), - if (_sidebarScrollController.hasClients && - _sidebarScrollController.offset > 0.0) - Divider(thickness: 1, height: 1, color: dividerColor), - if (sidebar.top != null && constraints.maxHeight > 81) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: sidebar.top!, - ), - Expanded( - child: MacosScrollbar( - controller: _sidebarScrollController, - child: Padding( - padding: sidebar.padding, - child: sidebar.builder( - context, - _sidebarScrollController, + child: TransparentMacOSSidebar( + state: sidebarState, + child: Column( + children: [ + if ((sidebar.topOffset) > 0) + SizedBox(height: sidebar.topOffset), + if (_sidebarScrollController.hasClients && + _sidebarScrollController.offset > 0.0) + Divider(thickness: 1, height: 1, color: dividerColor), + if (sidebar.top != null && constraints.maxHeight > 81) + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8.0), + child: sidebar.top!, + ), + Expanded( + child: MacosScrollbar( + controller: _sidebarScrollController, + child: Padding( + padding: sidebar.padding, + child: sidebar.builder( + context, + _sidebarScrollController, + ), ), ), ), - ), - if (sidebar.bottom != null && constraints.maxHeight > 141) - Padding( - padding: const EdgeInsets.all(16.0), - child: sidebar.bottom!, - ), - ], + if (sidebar.bottom != null && + constraints.maxHeight > 141) + Padding( + padding: const EdgeInsets.all(16.0), + child: sidebar.bottom!, + ), + ], + ), ), ), ), @@ -377,36 +412,45 @@ class _MacosWindowState extends State { minHeight: height, maxHeight: height, ).normalize(), - child: Column( - children: [ - if (endSidebar.topOffset > 0) - SizedBox(height: endSidebar.topOffset), - if (_endSidebarScrollController.hasClients && - _endSidebarScrollController.offset > 0.0) - Divider(thickness: 1, height: 1, color: dividerColor), - if (endSidebar.top != null) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: endSidebar.top!, - ), - Expanded( - child: MacosScrollbar( - controller: _endSidebarScrollController, - child: Padding( - padding: endSidebar.padding, - child: endSidebar.builder( - context, - _endSidebarScrollController, + child: WallpaperTintedArea( + backgroundColor: endSidebarBackgroundColor, + insertRepaintBoundary: true, + child: Column( + children: [ + if (endSidebar.topOffset > 0) + SizedBox(height: endSidebar.topOffset), + if (_endSidebarScrollController.hasClients && + _endSidebarScrollController.offset > 0.0) + Divider( + thickness: 1, + height: 1, + color: dividerColor, + ), + if (endSidebar.top != null) + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8.0), + child: endSidebar.top!, + ), + Expanded( + child: MacosScrollbar( + controller: _endSidebarScrollController, + child: Padding( + padding: endSidebar.padding, + child: endSidebar.builder( + context, + _endSidebarScrollController, + ), ), ), ), - ), - if (endSidebar.bottom != null) - Padding( - padding: const EdgeInsets.all(16.0), - child: endSidebar.bottom!, - ), - ], + if (endSidebar.bottom != null) + Padding( + padding: const EdgeInsets.all(16.0), + child: endSidebar.bottom!, + ), + ], + ), ), ), ), diff --git a/lib/src/macos_window_utils_config.dart b/lib/src/macos_window_utils_config.dart new file mode 100644 index 00000000..32e52568 --- /dev/null +++ b/lib/src/macos_window_utils_config.dart @@ -0,0 +1,120 @@ +import 'package:flutter/widgets.dart'; +import 'package:macos_ui/macos_ui.dart'; + +/// A class for configuring macOS window properties. +/// +/// [toolbarStyle] is the style of the window toolbar. It should be +/// [NSWindowToolbarStyle.expanded] if the app will have a title bar and +/// [NSWindowToolbarStyle.unified] otherwise. +/// +/// Example: +/// ```dart +/// final config = MacosWindowUtilsConfig( +/// toolbarStyle: NSWindowToolbarStyle.expanded, +/// ); +/// await config.apply(); +/// ``` +class MacosWindowUtilsConfig { + /// Creates a [MacosWindowUtilsConfig]. + /// + /// The [toolbarStyle] is [NSWindowToolbarStyle.unified] by default. If the + /// app will have a title bar, use [NSWindowToolbarStyle.expanded] instead. + const MacosWindowUtilsConfig({ + this.toolbarStyle = NSWindowToolbarStyle.unified, + this.enableFullSizeContentView = true, + this.makeTitlebarTransparent = true, + this.hideTitle = true, + this.removeMenubarInFullScreenMode = true, + this.autoHideToolbarAndMenuBarInFullScreenMode = true, + }); + + /// The style of the window toolbar. + /// + /// Defaults to [NSWindowToolbarStyle.unified]. Use + /// [NSWindowToolbarStyle.expanded] instead if the app will have a title bar. + final NSWindowToolbarStyle toolbarStyle; + + /// Whether to enable the full-size content view. + final bool enableFullSizeContentView; + + /// Whether to make the title bar transparent. + final bool makeTitlebarTransparent; + + /// Whether to hide the title. + final bool hideTitle; + + /// Whether to remove the menubar in full-screen mode. + final bool removeMenubarInFullScreenMode; + + /// Whether to automatically hide the toolbar and menubar in full-screen mode. + final bool autoHideToolbarAndMenuBarInFullScreenMode; + + /// Applies the configuration to the macOS window. + /// + /// This method: + /// + /// - Initializes Flutter bindings + /// - Sets the window material to + /// [NSVisualEffectViewMaterial.windowBackground] + /// - Enables the full size content view if [enableFullSizeContentView] is + /// `true` + /// - Makes the title bar transparent if [makeTitlebarTransparent] is `true` + /// - Hides the title if [hideTitle] is `true` + /// - Adds a toolbar + /// - Sets the toolbar style to [toolbarStyle] + /// - Removes the menubar in full-screen mode if + /// [removeMenubarInFullScreenMode] is `true` + /// - Auto-hides the toolbar and menubar in full-screen mode if + /// [autoHideToolbarAndMenuBarInFullScreenMode] is `true` + Future apply() async { + WidgetsFlutterBinding.ensureInitialized(); + await WindowManipulator.initialize(enableWindowDelegate: true); + await WindowManipulator.setMaterial( + NSVisualEffectViewMaterial.windowBackground, + ); + if (enableFullSizeContentView) { + await WindowManipulator.enableFullSizeContentView(); + } + if (makeTitlebarTransparent) { + await WindowManipulator.makeTitlebarTransparent(); + } + if (hideTitle) { + await WindowManipulator.hideTitle(); + } + await WindowManipulator.addToolbar(); + + await WindowManipulator.setToolbarStyle( + toolbarStyle: toolbarStyle, + ); + + if (removeMenubarInFullScreenMode) { + final delegate = _FlutterWindowDelegate(); + WindowManipulator.addNSWindowDelegate(delegate); + } + + if (autoHideToolbarAndMenuBarInFullScreenMode) { + final options = NSAppPresentationOptions.from({ + NSAppPresentationOption.fullScreen, + NSAppPresentationOption.autoHideToolbar, + NSAppPresentationOption.autoHideMenuBar, + NSAppPresentationOption.autoHideDock, + }); + options.applyAsFullScreenPresentationOptions(); + } + } +} + +/// This delegate removes the toolbar in full-screen mode. +class _FlutterWindowDelegate extends NSWindowDelegate { + @override + void windowWillEnterFullScreen() { + WindowManipulator.removeToolbar(); + super.windowWillEnterFullScreen(); + } + + @override + void windowDidExitFullScreen() { + WindowManipulator.addToolbar(); + super.windowDidExitFullScreen(); + } +} diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index c8ea1f55..78508f00 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -213,9 +213,10 @@ class MacosThemeData with Diagnosticable { final Brightness _brightness = brightness ?? Brightness.light; final bool isDark = _brightness == Brightness.dark; primaryColor ??= MacosColors.controlAccentColor; + canvasColor ??= isDark - ? CupertinoColors.systemBackground.darkElevatedColor - : CupertinoColors.systemBackground; + ? const Color.fromRGBO(40, 40, 40, 1.0) + : const Color.fromRGBO(246, 246, 246, 1.0); typography ??= MacosTypography( color: _brightness == Brightness.light ? CupertinoColors.black diff --git a/lib/src/theme/overlay_filter.dart b/lib/src/theme/overlay_filter.dart index 2dd9b78e..13ad0ccb 100644 --- a/lib/src/theme/overlay_filter.dart +++ b/lib/src/theme/overlay_filter.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/layout/wallpaper_tinting_settings/wallpaper_tinting_override.dart'; import 'package:macos_ui/src/library.dart'; /// {@template macosOverlayFilter} @@ -36,41 +37,43 @@ class MacosOverlayFilter extends StatelessWidget { Widget build(BuildContext context) { final brightness = MacosTheme.brightnessOf(context); - return Container( - decoration: BoxDecoration( - color: color ?? - (brightness.isDark - ? const Color.fromRGBO(30, 30, 30, 1) - : const Color.fromRGBO(242, 242, 247, 1)), - boxShadow: [ - BoxShadow( - color: brightness - .resolve( - CupertinoColors.systemGrey.color, - CupertinoColors.black, - ) - .withOpacity(0.25), - offset: const Offset(0, 4), - spreadRadius: 4.0, - blurRadius: 8.0, - ), - ], - border: Border.all( - color: brightness.resolve( - CupertinoColors.systemGrey3.color, - CupertinoColors.systemGrey3.darkColor, + return WallpaperTintingOverride( + child: Container( + decoration: BoxDecoration( + color: color ?? + (brightness.isDark + ? const Color.fromRGBO(30, 30, 30, 1) + : const Color.fromRGBO(242, 242, 247, 1)), + boxShadow: [ + BoxShadow( + color: brightness + .resolve( + CupertinoColors.systemGrey.color, + CupertinoColors.black, + ) + .withOpacity(0.25), + offset: const Offset(0, 4), + spreadRadius: 4.0, + blurRadius: 8.0, + ), + ], + border: Border.all( + color: brightness.resolve( + CupertinoColors.systemGrey3.color, + CupertinoColors.systemGrey3.darkColor, + ), ), + borderRadius: borderRadius, ), - borderRadius: borderRadius, - ), - child: ClipRRect( - borderRadius: borderRadius, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: 20.0, - sigmaY: 20.0, + child: ClipRRect( + borderRadius: borderRadius, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: 20.0, + sigmaY: 20.0, + ), + child: child, ), - child: child, ), ), ); diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 08f2debc..a986d412 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; @@ -93,3 +95,28 @@ class Unsupported { final String message; } + +/// A class that ensures that the application's macOS window's brightness +/// matches the given brightness. +class MacOSBrightnessOverrideHandler { + static Brightness? _lastBrightness; + + /// Ensures that the application's macOS window's brightness matches + /// [currentBrightness]. + /// + /// For performance reasons, the brightness setting will only be overridden if + /// [currentBrightness] differs from the value it had when this method was + /// previously called. Therefore, it is safe to call this method frequently. + static void ensureMatchingBrightness(Brightness currentBrightness) { + if (!Platform.isMacOS) { + return; + } + + if (currentBrightness == _lastBrightness) { + return; + } + + WindowManipulator.overrideMacOSBrightness(dark: currentBrightness.isDark); + _lastBrightness = currentBrightness; + } +} diff --git a/pubspec.lock b/pubspec.lock index 6cfa9f14..3e85534f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -259,6 +259,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + macos_window_utils: + dependency: "direct main" + description: + name: macos_window_utils + sha256: "510de576b5432dd9ef9e4c258abcc021c6dfbb17a78a344688848a6784b352b8" + url: "https://pub.dev" + source: hosted + version: "1.1.2" matcher: dependency: transitive description: @@ -553,5 +561,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.18.0 <3.0.0" - flutter: ">=1.20.0" + dart: ">=2.18.5 <3.0.0" + flutter: ">=3.7.0" diff --git a/pubspec.yaml b/pubspec.yaml index bf720d2b..afacfecd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 1.12.5 +version: 2.0.0-beta.1 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" @@ -11,6 +11,7 @@ environment: dependencies: flutter: sdk: flutter + macos_window_utils: ^1.1.2 dev_dependencies: flutter_test: diff --git a/test/buttons/back_button_test.dart b/test/buttons/back_button_test.dart index 90e85f65..cd709ee1 100644 --- a/test/buttons/back_button_test.dart +++ b/test/buttons/back_button_test.dart @@ -44,6 +44,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( toolBar: ToolBar( leading: MacosBackButton( diff --git a/test/buttons/help_button_test.dart b/test/buttons/help_button_test.dart index 2f11f430..a18b7cf9 100644 --- a/test/buttons/help_button_test.dart +++ b/test/buttons/help_button_test.dart @@ -52,6 +52,7 @@ void main() { helpButtonTheme: darkHelpButtonThemeData, ), home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/buttons/icon_button_test.dart b/test/buttons/icon_button_test.dart index 057f6a3b..8e52e106 100644 --- a/test/buttons/icon_button_test.dart +++ b/test/buttons/icon_button_test.dart @@ -45,6 +45,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/buttons/push_button_test.dart b/test/buttons/push_button_test.dart index 96dc571e..3843c6a2 100644 --- a/test/buttons/push_button_test.dart +++ b/test/buttons/push_button_test.dart @@ -55,6 +55,7 @@ void main() { pushButtonTheme: darkPushButtonThemeData, ), home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/buttons/radio_button_test.dart b/test/buttons/radio_button_test.dart index d561b643..7d878d1c 100644 --- a/test/buttons/radio_button_test.dart +++ b/test/buttons/radio_button_test.dart @@ -17,6 +17,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/layout/macos_list_tile_test.dart b/test/layout/macos_list_tile_test.dart index e019a007..b8b65f7f 100644 --- a/test/layout/macos_list_tile_test.dart +++ b/test/layout/macos_list_tile_test.dart @@ -20,6 +20,7 @@ void main() { pushButtonTheme: const PushButtonThemeData(), ), home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/layout/resizeable_pane_test.dart b/test/layout/resizeable_pane_test.dart index 86638a6a..56164763 100644 --- a/test/layout/resizeable_pane_test.dart +++ b/test/layout/resizeable_pane_test.dart @@ -29,6 +29,7 @@ void main() { final view = side == ResizableSide.top ? MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( @@ -52,6 +53,7 @@ void main() { ) : MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ resizablePane, diff --git a/test/layout/sliver_toolbar_test.dart b/test/layout/sliver_toolbar_test.dart index bcfeb9d4..0d693771 100644 --- a/test/layout/sliver_toolbar_test.dart +++ b/test/layout/sliver_toolbar_test.dart @@ -81,6 +81,8 @@ void main() { tester.getBottomLeft(find.byKey(leadingKey)), const Offset(8.0, 47.0), ); + + await tester.pump(Duration.zero); }, ); @@ -97,6 +99,8 @@ void main() { expect(tester.getTopLeft(navToolbar).dy, 4.0); expect(tester.getSize(toolbar).height, 52.0); expect(tester.getSize(navToolbar).height, 43.0); + + await tester.pump(Duration.zero); }, ); @@ -116,6 +120,8 @@ void main() { expect(tester.getTopLeft(toolbar).dy, 0.0); expect(tester.getTopLeft(navToolbar).dy, 4.0); + + await tester.pump(Duration.zero); }, ); @@ -137,6 +143,8 @@ void main() { expect(toolbar, findsNothing); expect(navToolbar, findsNothing); + + await tester.pump(Duration.zero); }, ); @@ -177,6 +185,8 @@ void main() { expect(tester.getTopLeft(toolbar).dy, 0.0); expect(navToolbar, findsOneWidget); expect(tester.getTopLeft(navToolbar).dy, 4.0); + + await tester.pump(Duration.zero); }, ); } diff --git a/test/layout/window_test.dart b/test/layout/window_test.dart index 262b5ded..92f1a80a 100644 --- a/test/layout/window_test.dart +++ b/test/layout/window_test.dart @@ -30,6 +30,7 @@ void main() { viewBuilder(Sidebar sidebar) { return MacosApp( home: MacosWindow( + disableWallpaperTinting: true, sidebar: sidebar, child: const MacosScaffold( children: [], @@ -70,6 +71,8 @@ void main() { await tester.pumpWidget(view); expectSidebarOpen(tester, width: startWidth); + + await tester.pump(Duration.zero); }); test('dragClosedBuffer defaults to half minWidth', () { @@ -84,6 +87,8 @@ void main() { await tester.pump(); expectSidebarOpen(tester, width: startWidth + safeDelta); + + await tester.pump(Duration.zero); }); testWidgets('dragging wider respects maxWidth', (tester) async { @@ -93,6 +98,8 @@ void main() { await tester.pump(); expectSidebarOpen(tester, width: maxWidth); + + await tester.pump(Duration.zero); }); testWidgets('drag events past maxWidth have no effect', (tester) async { @@ -106,6 +113,8 @@ void main() { await tester.pump(); expectSidebarOpen(tester, width: maxWidth); + + await tester.pump(Duration.zero); }); testWidgets('dragging narrower works', (tester) async { @@ -115,6 +124,8 @@ void main() { await tester.pump(); expectSidebarOpen(tester, width: startWidth - safeDelta); + + await tester.pump(Duration.zero); }); group('when dragClosed is true', () { @@ -135,6 +146,8 @@ void main() { await tester.pump(); expectSidebarOpen(tester, width: minWidth); + + await tester.pump(Duration.zero); }, ); testWidgets( @@ -146,6 +159,8 @@ void main() { await tester.pump(); expectSidebarClosed(tester); + + await tester.pump(Duration.zero); }, ); @@ -172,6 +187,8 @@ void main() { await tester.pump(); expectSidebarOpen(tester, width: startWidth - safeDelta); + + await tester.pump(Duration.zero); }, ); @@ -186,6 +203,8 @@ void main() { await tester.pump(); expectSidebarClosed(tester); + + await tester.pump(Duration.zero); }); }); @@ -199,6 +218,8 @@ void main() { await tester.pump(); expectSidebarOpen(tester, width: minWidth); + + await tester.pump(Duration.zero); }, ); @@ -213,6 +234,8 @@ void main() { await tester.pump(); expectSidebarOpen(tester, width: minWidth); + + await tester.pump(Duration.zero); }); }); @@ -243,6 +266,8 @@ void main() { tester, width: startWidth + snapToStartBuffer * 2, ); + + await tester.pump(Duration.zero); }, ); @@ -273,6 +298,8 @@ void main() { ); await tester.pump(); expectSidebarOpen(tester, width: startWidth); + + await tester.pump(Duration.zero); }, ); }); diff --git a/test/selectors/date_picker_test.dart b/test/selectors/date_picker_test.dart index f0c5afbc..b0e50b5d 100644 --- a/test/selectors/date_picker_test.dart +++ b/test/selectors/date_picker_test.dart @@ -12,6 +12,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( @@ -50,6 +51,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/theme/help_button_theme_test.dart b/test/theme/help_button_theme_test.dart index 702a85ed..98556548 100644 --- a/test/theme/help_button_theme_test.dart +++ b/test/theme/help_button_theme_test.dart @@ -56,6 +56,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/theme/icon_button_theme_test.dart b/test/theme/icon_button_theme_test.dart index af7a40e6..7d42593e 100644 --- a/test/theme/icon_button_theme_test.dart +++ b/test/theme/icon_button_theme_test.dart @@ -63,6 +63,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/theme/icon_theme_test.dart b/test/theme/icon_theme_test.dart index 8e98f80a..b45e4de6 100644 --- a/test/theme/icon_theme_test.dart +++ b/test/theme/icon_theme_test.dart @@ -63,6 +63,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/theme/popup_button_theme_test.dart b/test/theme/popup_button_theme_test.dart index daa039de..730d86a6 100644 --- a/test/theme/popup_button_theme_test.dart +++ b/test/theme/popup_button_theme_test.dart @@ -67,6 +67,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/theme/pulldown_button_theme_test.dart b/test/theme/pulldown_button_theme_test.dart index 373ac0c4..0ef34561 100644 --- a/test/theme/pulldown_button_theme_test.dart +++ b/test/theme/pulldown_button_theme_test.dart @@ -66,6 +66,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/theme/push_button_theme_test.dart b/test/theme/push_button_theme_test.dart index 5bcc4ef1..a910d981 100644 --- a/test/theme/push_button_theme_test.dart +++ b/test/theme/push_button_theme_test.dart @@ -58,6 +58,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( diff --git a/test/theme/search_field_theme_test.dart b/test/theme/search_field_theme_test.dart index da838f37..6f0851b9 100644 --- a/test/theme/search_field_theme_test.dart +++ b/test/theme/search_field_theme_test.dart @@ -62,6 +62,7 @@ void main() { await tester.pumpWidget( MacosApp( home: MacosWindow( + disableWallpaperTinting: true, child: MacosScaffold( children: [ ContentArea( From 5269fcdb5c0901c05af63e1285d2365b90dc64a2 Mon Sep 17 00:00:00 2001 From: Michelle Raouf <72160249+the-best-is-best@users.noreply.github.com> Date: Wed, 17 May 2023 01:41:26 +0300 Subject: [PATCH 12/45] support flutter 3.10 minimum dart3 (#426) --- example/lib/pages/fields_page.dart | 12 +-- example/lib/pages/toolbar_page.dart | 4 +- example/pubspec.lock | 40 ++++---- lib/src/fields/text_field.dart | 7 +- lib/src/layout/scaffold.dart | 2 +- pubspec.lock | 152 +++++++++++++++------------- pubspec.yaml | 4 +- test/indicators/scrollbar_test.dart | 4 +- test/indicators/slider_test.dart | 14 ++- 9 files changed, 124 insertions(+), 115 deletions(-) diff --git a/example/lib/pages/fields_page.dart b/example/lib/pages/fields_page.dart index 94785708..00b2aa81 100644 --- a/example/lib/pages/fields_page.dart +++ b/example/lib/pages/fields_page.dart @@ -352,8 +352,8 @@ const countries = [ var actionResults = [ SearchResultItem( 'Build project', - child: Row( - children: const [ + child: const Row( + children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 8.0), child: MacosIcon(CupertinoIcons.hammer), @@ -365,8 +365,8 @@ var actionResults = [ ), SearchResultItem( 'Debug project', - child: Row( - children: const [ + child: const Row( + children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 8.0), child: MacosIcon(CupertinoIcons.tickets), @@ -378,8 +378,8 @@ var actionResults = [ ), SearchResultItem( 'Open containing folder', - child: Row( - children: const [ + child: const Row( + children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 8.0), child: MacosIcon(CupertinoIcons.folder), diff --git a/example/lib/pages/toolbar_page.dart b/example/lib/pages/toolbar_page.dart index 6e1754ce..104bd7c1 100644 --- a/example/lib/pages/toolbar_page.dart +++ b/example/lib/pages/toolbar_page.dart @@ -160,9 +160,9 @@ class _ToolbarPageState extends State { return SingleChildScrollView( controller: scrollController, padding: const EdgeInsets.all(30), - child: Center( + child: const Center( child: Column( - children: const [ + children: [ Text( 'A toolbar provides convenient access to frequently used commands and controls that perform actions relevant to the current view.', textAlign: TextAlign.center, diff --git a/example/pubspec.lock b/example/pubspec.lock index a440e555..c9114539 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.1" cupertino_icons: dependency: "direct main" description: @@ -79,18 +79,18 @@ packages: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" lints: dependency: transitive description: name: lints - sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + sha256: "6b0206b0bf4f04961fc5438198ccb3a885685cd67d4d4a32cc20ad7f8adbe015" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0" macos_ui: dependency: "direct main" description: @@ -110,10 +110,10 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.15" material_color_utilities: dependency: transitive description: @@ -126,10 +126,10 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" nested: dependency: transitive description: @@ -142,10 +142,10 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" provider: dependency: "direct main" description: @@ -203,10 +203,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.5.1" vector_math: dependency: transitive description: @@ -216,5 +216,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.18.5 <3.0.0" - flutter: ">=3.7.0" + dart: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" diff --git a/lib/src/fields/text_field.dart b/lib/src/fields/text_field.dart index f3cf0888..08a37f6f 100644 --- a/lib/src/fields/text_field.dart +++ b/lib/src/fields/text_field.dart @@ -105,7 +105,7 @@ class _TextFieldSelectionGestureDetectorBuilder final _MacosTextFieldState _state; @override - void onSingleTapUp(TapUpDetails details) { + void onSingleTapUp(TapDragUpDetails details) { // Because TextSelectionGestureDetector listens to taps that happen on // widgets in front of it, tapping the clear button will also trigger // this handler. If the clear button widget recognizes the up event, @@ -124,11 +124,14 @@ class _TextFieldSelectionGestureDetectorBuilder } _state._requestKeyboard(); if (_state.widget.onTap != null) _state.widget.onTap!(); + + super.onSingleTapUp(details); } @override - void onDragSelectionEnd(DragEndDetails details) { + void onDragSelectionEnd(TapDragEndDetails details) { _state._requestKeyboard(); + super.onDragSelectionEnd(details); } } diff --git a/lib/src/layout/scaffold.dart b/lib/src/layout/scaffold.dart index 871dba0d..2df5a94d 100644 --- a/lib/src/layout/scaffold.dart +++ b/lib/src/layout/scaffold.dart @@ -113,7 +113,7 @@ class _MacosScaffoldState extends State { } class _ScaffoldBody extends MultiChildRenderObjectWidget { - _ScaffoldBody({ + const _ScaffoldBody({ super.children, }); diff --git a/pubspec.lock b/pubspec.lock index 3e85534f..d14c154e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0c80aeab9bc807ab10022cd3b2f4cf2ecdf231949dc1ddd9442406a003f19201" + sha256: "8880b4cfe7b5b17d57c052a5a3a8cc1d4f546261c7cc8fbd717bd53f48db0568" url: "https://pub.dev" source: hosted - version: "52.0.0" + version: "59.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: cd8ee83568a77f3ae6b913a36093a1c9b1264e7cb7f834d9ddd2311dade9c1f4 + sha256: a89627f49b0e70e068130a36571409726b04dab12da7e5625941d2c8ec278b96 url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.11.1" analyzer_plugin: dependency: transitive description: @@ -37,18 +37,18 @@ packages: dependency: transitive description: name: args - sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" + sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" async: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.1" convert: dependency: transitive description: @@ -101,10 +101,10 @@ packages: dependency: transitive description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" csslib: dependency: transitive description: @@ -117,26 +117,26 @@ packages: dependency: "direct dev" description: name: dart_code_metrics - sha256: "026e28da197a03caeccccc0b174ec98ef03da3c81c4543314d7add121aab4375" + sha256: "162c81dbd0a2ba182f38ca615335f3e8878f212ec7beea83d6bfad4e99eb541a" url: "https://pub.dev" source: hosted - version: "5.6.0" + version: "5.7.3" dart_code_metrics_presets: dependency: transitive description: name: dart_code_metrics_presets - sha256: "9c51724f836aebc4465228954cb5757e5a99737af26a452b5dec0a2d5d0b4d66" + sha256: "22e27f98e8c7d8b11cca43d2656a822935280747050ae65e8cd03c52d09c0d1c" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.7.0" dart_style: dependency: transitive description: name: dart_style - sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" + sha256: f4f1f73ab3fd2afcbcca165ee601fe980d966af6a21b5970c6c9376955c528ad url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.3.1" fake_async: dependency: transitive description: @@ -191,18 +191,18 @@ packages: dependency: transitive description: name: html - sha256: d9793e10dbe0e6c364f4c59bf3e01fb33a9b2a674bc7a1081693dba0614b6269 + sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8" url: "https://pub.dev" source: hosted - version: "0.15.1" + version: "0.15.3" http: dependency: transitive description: name: http - sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" url: "https://pub.dev" source: hosted - version: "0.13.5" + version: "0.13.6" http_multi_server: dependency: transitive description: @@ -231,26 +231,26 @@ packages: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" json_annotation: dependency: transitive description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.8.1" lints: dependency: transitive description: name: lints - sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + sha256: "6b0206b0bf4f04961fc5438198ccb3a885685cd67d4d4a32cc20ad7f8adbe015" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0" logging: dependency: transitive description: @@ -271,10 +271,10 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.15" material_color_utilities: dependency: transitive description: @@ -287,10 +287,10 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" mime: dependency: transitive description: @@ -311,10 +311,10 @@ packages: dependency: transitive description: name: node_preamble - sha256: "8ebdbaa3b96d5285d068f80772390d27c21e1fa10fb2df6627b1b9415043608d" + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" package_config: dependency: transitive description: @@ -327,18 +327,18 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" petitparser: dependency: transitive description: name: petitparser - sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "5.4.0" platform: dependency: transitive description: @@ -367,50 +367,50 @@ packages: dependency: transitive description: name: pub_semver - sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" pub_updater: dependency: transitive description: name: pub_updater - sha256: "42890302ab2672adf567dc2b20e55b4ecc29d7e19c63b6b98143ab68dd717d3a" + sha256: "05ae70703e06f7fdeb05f7f02dd680b8aad810e87c756a618f33e1794635115c" url: "https://pub.dev" source: hosted - version: "0.2.4" + version: "0.3.0" shelf: dependency: transitive description: name: shelf - sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" shelf_packages_handler: dependency: transitive description: name: shelf_packages_handler - sha256: aef74dc9195746a384843102142ab65b6a4735bb3beea791e63527b88cc83306 + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" shelf_static: dependency: transitive description: name: shelf_static - sha256: e792b76b96a36d4a41b819da593aff4bdd413576b3ba6150df5d8d9996d2e74c + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" sky_engine: dependency: transitive description: flutter @@ -476,34 +476,42 @@ packages: dependency: transitive description: name: test - sha256: a5fcd2d25eeadbb6589e80198a47d6a464ba3e2049da473943b8af9797900c2d + sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" url: "https://pub.dev" source: hosted - version: "1.22.0" + version: "1.24.1" test_api: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.5.1" test_core: dependency: transitive description: name: test_core - sha256: "0ef9755ec6d746951ba0aabe62f874b707690b5ede0fecc818b138fcc9b14888" + sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" url: "https://pub.dev" source: hosted - version: "0.4.20" + version: "0.5.1" typed_data: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" + source: hosted + version: "3.0.7" vector_math: dependency: transitive description: @@ -516,26 +524,26 @@ packages: dependency: transitive description: name: vm_service - sha256: e7fb6c2282f7631712b69c19d1bff82f3767eea33a2321c14fa59ad67ea391c7 + sha256: f3743ca475e0c9ef71df4ba15eb2d7684eecd5c8ba20a462462e4e8b561b2e11 url: "https://pub.dev" source: hosted - version: "9.4.0" + version: "11.6.0" watcher: dependency: transitive description: name: watcher - sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.0" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.0" webkit_inspection_protocol: dependency: transitive description: @@ -548,18 +556,18 @@ packages: dependency: transitive description: name: xml - sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.3.0" yaml: dependency: transitive description: name: yaml - sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" sdks: - dart: ">=2.18.5 <3.0.0" - flutter: ">=3.7.0" + dart: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index afacfecd..76275906 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,8 +5,8 @@ homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" environment: - sdk: ">=2.17.0 <3.0.0" - flutter: ">=1.20.0" + sdk: ">=3.0.0 <=4.0.0" + flutter: ">=3.10.0" dependencies: flutter: diff --git a/test/indicators/scrollbar_test.dart b/test/indicators/scrollbar_test.dart index f5ffe2c2..1173e096 100644 --- a/test/indicators/scrollbar_test.dart +++ b/test/indicators/scrollbar_test.dart @@ -16,8 +16,8 @@ void main() { testWidgets( 'Scrollbar changes position when scrolled with the mouse wheel', (tester) async { - final Size screenSize = tester.binding.window.physicalSize / - tester.binding.window.devicePixelRatio; + final Size screenSize = + tester.view.physicalSize / tester.view.devicePixelRatio; await tester.pumpWidget( Directionality( diff --git a/test/indicators/slider_test.dart b/test/indicators/slider_test.dart index 23bb0b35..99f963ff 100644 --- a/test/indicators/slider_test.dart +++ b/test/indicators/slider_test.dart @@ -4,8 +4,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:macos_ui/macos_ui.dart'; void main() { - final TestWidgetsFlutterBinding binding = - TestWidgetsFlutterBinding.ensureInitialized(); testWidgets('debugFillProperties', (tester) async { final builder = DiagnosticPropertiesBuilder(); MacosSlider( @@ -36,8 +34,8 @@ void main() { }); testWidgets('Continuous slider can move when tapped', (tester) async { - tester.binding.window.physicalSizeTestValue = const Size(100, 50); - binding.window.devicePixelRatioTestValue = 1.0; + tester.view.physicalSize = const Size(100, 50); + tester.view.devicePixelRatio = 1.0; final value = ValueNotifier(0.25); await tester.pumpWidget( @@ -65,12 +63,12 @@ void main() { await tester.pumpAndSettle(); expect(value.value, 0.0); - addTearDown(tester.binding.window.clearPhysicalSizeTestValue); + addTearDown(tester.view.resetPhysicalSize); }); testWidgets('Discrete slider snaps to correct values', (widgetTester) async { - widgetTester.binding.window.physicalSizeTestValue = const Size(100, 50); - binding.window.devicePixelRatioTestValue = 1.0; + widgetTester.view.physicalSize = const Size(100, 50); + widgetTester.view.devicePixelRatio = 1.0; final value = ValueNotifier(0.25); await widgetTester.pumpWidget( @@ -109,6 +107,6 @@ void main() { expect(value.value, 0.5); - addTearDown(widgetTester.binding.window.clearPhysicalSizeTestValue); + addTearDown(widgetTester.view.resetPhysicalSize); }); } From e99a19b7833c687e9d55b4691646d697c56fa77d Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Tue, 16 May 2023 18:43:30 -0400 Subject: [PATCH 13/45] Update dart_code_metrics.yaml --- .github/workflows/dart_code_metrics.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index a22ecac7..29c79dd3 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run Dart Code Metrics uses: dart-code-checker/dart-code-metrics-action@v3 From f7e03338dc5a52f9dd1849f354763b730fcb4ec3 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 17 May 2023 11:20:11 -0400 Subject: [PATCH 14/45] Update dart_code_metrics.yaml --- .github/workflows/dart_code_metrics.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index 29c79dd3..7d8f1ef1 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -9,7 +9,7 @@ jobs: - uses: actions/checkout@v4 - name: Run Dart Code Metrics - uses: dart-code-checker/dart-code-metrics-action@v3 + uses: dart-code-checker/dart-code-metrics-action@v4.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} pull_request_comment: true From b64e2e46a9667eb0d74d8d123530147b4078612e Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 17 May 2023 11:23:29 -0400 Subject: [PATCH 15/45] Update dart_code_metrics.yaml --- .github/workflows/dart_code_metrics.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index 7d8f1ef1..16194aed 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Run Dart Code Metrics uses: dart-code-checker/dart-code-metrics-action@v4.0.0 From f1053fc31958dbaa362a0a4c8288e8cf0919ab8c Mon Sep 17 00:00:00 2001 From: Bernardo Ferrari Date: Wed, 17 May 2023 12:25:47 -0300 Subject: [PATCH 16/45] Fix EnumProperty test. (#419) Fix test. Co-authored-by: Reuben Turner --- lib/src/fields/text_field.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/fields/text_field.dart b/lib/src/fields/text_field.dart index 08a37f6f..8564301d 100644 --- a/lib/src/fields/text_field.dart +++ b/lib/src/fields/text_field.dart @@ -759,7 +759,7 @@ class MacosTextField extends StatefulWidget { 'clearButtonMode', clearButtonMode, )); - properties.add(EnumProperty( + properties.add(DiagnosticsProperty( 'keyboardType', keyboardType, defaultValue: TextInputType.text, From 15fb991dbf6885a41c2a91a0c607b9b426306a40 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 3 Jul 2023 13:16:35 -0400 Subject: [PATCH 17/45] update dependencies --- example/pubspec.lock | 4 ++-- pubspec.lock | 12 ++++++------ pubspec.yaml | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index c9114539..bf64e601 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -102,10 +102,10 @@ packages: dependency: transitive description: name: macos_window_utils - sha256: "510de576b5432dd9ef9e4c258abcc021c6dfbb17a78a344688848a6784b352b8" + sha256: b78a210aa70ca7ccad6e7b7b810fb4689c507f4a46e299214900b2a1eb70ea23 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" matcher: dependency: transitive description: diff --git a/pubspec.lock b/pubspec.lock index d14c154e..4408d237 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -117,10 +117,10 @@ packages: dependency: "direct dev" description: name: dart_code_metrics - sha256: "162c81dbd0a2ba182f38ca615335f3e8878f212ec7beea83d6bfad4e99eb541a" + sha256: "1dc1fa763b73ed52147bd91b015d81903edc3f227b77b1672fcddba43390ed18" url: "https://pub.dev" source: hosted - version: "5.7.3" + version: "5.7.5" dart_code_metrics_presets: dependency: transitive description: @@ -162,10 +162,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" flutter_test: dependency: "direct dev" description: flutter @@ -263,10 +263,10 @@ packages: dependency: "direct main" description: name: macos_window_utils - sha256: "510de576b5432dd9ef9e4c258abcc021c6dfbb17a78a344688848a6784b352b8" + sha256: b78a210aa70ca7ccad6e7b7b810fb4689c507f4a46e299214900b2a1eb70ea23 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 76275906..e79f0cbc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,13 +11,13 @@ environment: dependencies: flutter: sdk: flutter - macos_window_utils: ^1.1.2 + macos_window_utils: ^1.1.3 dev_dependencies: flutter_test: sdk: flutter - dart_code_metrics: ^5.6.0 - flutter_lints: ^2.0.1 + dart_code_metrics: ^5.7.5 + flutter_lints: ^2.0.2 mocktail: ^0.3.0 flutter: From 3af34b1a743e6a7f3520160af74172a3ba6017bc Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 3 Jul 2023 13:18:16 -0400 Subject: [PATCH 18/45] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07499bb1..2c6bd4af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Some visual artifacts that occurred while the window was being (de)miniaturized (such as the application's shadow going missing) no longer occur. * The sidebar remains transparent when the app's brightness setting mismatches the OS setting. * Wallpaper tinting is now supported. +* Support Flutter 3.10 and Dart 3 To migrate an existing application, please refer to the “Modern window look” section in the README. From a56544aad7b67ac2b6999a78c5eb3783a12ecb40 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 3 Jul 2023 13:20:55 -0400 Subject: [PATCH 19/45] fix dart version constraint --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index e79f0cbc..02ae5581 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" environment: - sdk: ">=3.0.0 <=4.0.0" + sdk: ">=3.0.0 <4.0.0" flutter: ">=3.10.0" dependencies: From 2988b94e705ca8ba4dd23859f1c31555bc119136 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Mon, 3 Jul 2023 14:27:30 -0400 Subject: [PATCH 20/45] Rewritten `MacosSwitch` (#409) * feat(controls): rewrite `MacosSwitch` * feat(gallery): demonstrate new `MacosSwitch` * chore: update version & changelog * chore: update DCM * chore: include `MacosColor` updates in changelog * docs(MacosSwitch): update docs * chore: some cleanup * test: fix tests * docs: update readme --- CHANGELOG.md | 10 +- README.md | 24 +- example/lib/pages/buttons_page.dart | 31 +- example/pubspec.lock | 2 +- lib/macos_ui.dart | 13 +- lib/src/buttons/switch.dart | 663 ++++++++++++++++++++- lib/src/enums/control_size.dart | 25 + lib/src/theme/icon_theme.dart | 2 +- lib/src/theme/macos_colors.dart | 71 +++ pubspec.yaml | 2 +- test/buttons/switch_test.dart | 2 + test/theme/help_button_theme_test.dart | 4 +- test/theme/icon_theme_test.dart | 2 +- test/theme/popup_button_theme_test.dart | 4 +- test/theme/pulldown_button_theme_test.dart | 6 +- test/theme/push_button_theme_test.dart | 4 +- 16 files changed, 810 insertions(+), 55 deletions(-) create mode 100644 lib/src/enums/control_size.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c6bd4af..4a426fce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ +## [2.0.0-beta.2] +✨New ✨ +* `MacosSwitch` has been completely rewritten and now matches the native macOS switch in appearance and behavior. +* A `ControlSize` enum has been introduced, which will allow widgets to more closely match their native counterparts. + +🔄 Updated 🔄 +* Some previously missing elements of the `MacosColor` class have been added. + ## [2.0.0-beta.1] 🚨 Breaking Changes 🚨 -* Migrate macos_ui to [macos_window_utils](https://pub.dev/packages/macos_window_utils), which provides the following benefits: +* Migrate `macos_ui` to [macos_window_utils](https://pub.dev/packages/macos_window_utils), which provides the following benefits: * Window animation smoothness is drastically improved, particularly when miniaturizing and deminiaturizing the application window. * Some visual artifacts that occurred while the window was being (de)miniaturized (such as the application's shadow going missing) no longer occur. * The sidebar remains transparent when the app's brightness setting mismatches the OS setting. diff --git a/README.md b/README.md index 4c8f9447..8164314d 100644 --- a/README.md +++ b/README.md @@ -657,26 +657,34 @@ PushButton( ## MacosSwitch -A switch is a visual toggle between two mutually exclusive states — on and off. A switch shows that it's on when the -accent color is visible and off when the switch appears colorless. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/buttons/switches/) +A switch (also known as a toggle) is a control that offers a binary choice between two mutually exclusive states — on and off. A switch shows that it's on when the +accent color is visible and off when the switch appears colorless. -| On | Off | -| ------------------------------------------ | ------------------------------------------ | -| | | +The `ContolSize` enum can be passed to the `size` property to control the size of the switch. `MacosSwitch` supports the following +control sizes: +* `mini` +* `small` +* `regular` + +| Off | On | +|--------------------------------------------|--------------------------------------------| +| | | Here's an example of how to create a basic toggle switch: ```dart -bool selected = false; +bool switchValue = false; MacosSwitch( - value: selected, + value: switchValue, onChanged: (value) { - setState(() => selected = value); + setState(() => switchValue = value); }, ), ``` +Learn more about switches [here](https://developer.apple.com/design/human-interface-guidelines/toggles). + ## MacosSegmentedControl Displays one or more navigational tabs in a single horizontal group. Used by `MacosTabView` to navigate between the diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index 6c373f5f..befbaef1 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -217,11 +217,32 @@ class _ButtonsPageState extends State { const SizedBox(height: 20), const Text('MacosSwitch'), const SizedBox(height: 8), - MacosSwitch( - value: switchValue, - onChanged: (value) { - setState(() => switchValue = value); - }, + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MacosSwitch( + value: switchValue, + size: ControlSize.mini, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + const SizedBox(width: 16.0), + MacosSwitch( + value: switchValue, + size: ControlSize.small, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + const SizedBox(width: 16.0), + MacosSwitch( + value: switchValue, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + ], ), const SizedBox(height: 20), const Text('MacosPulldownButton'), diff --git a/example/pubspec.lock b/example/pubspec.lock index bf64e601..931e90c9 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.1" + version: "2.0.0-beta.2" macos_window_utils: dependency: transitive description: diff --git a/lib/macos_ui.dart b/lib/macos_ui.dart index 4d3ac251..62dcc602 100644 --- a/lib/macos_ui.dart +++ b/lib/macos_ui.dart @@ -14,6 +14,9 @@ library macos_ui; +export 'package:macos_window_utils/macos/ns_window_delegate.dart'; +export 'package:macos_window_utils/macos_window_utils.dart'; + export 'src/buttons/back_button.dart'; export 'src/buttons/checkbox.dart'; export 'src/buttons/disclosure_button.dart'; @@ -29,6 +32,7 @@ export 'src/buttons/toolbar/toolbar_icon_button.dart'; export 'src/buttons/toolbar/toolbar_overflow_button.dart'; export 'src/buttons/toolbar/toolbar_pulldown_button.dart'; export 'src/dialogs/macos_alert_dialog.dart'; +export 'src/enums/control_size.dart'; export 'src/fields/search_field.dart'; export 'src/fields/text_field.dart'; export 'src/icon/image_icon.dart'; @@ -37,7 +41,6 @@ export 'src/indicators/capacity_indicators.dart'; export 'src/indicators/progress_indicators.dart'; export 'src/indicators/rating_indicator.dart'; export 'src/indicators/relevance_indicator.dart'; -export 'src/layout/scrollbar.dart'; export 'src/indicators/slider.dart'; export 'src/labels/label.dart'; export 'src/labels/tooltip.dart'; @@ -45,6 +48,7 @@ export 'src/layout/content_area.dart'; export 'src/layout/macos_list_tile.dart'; export 'src/layout/resizable_pane.dart'; export 'src/layout/scaffold.dart'; +export 'src/layout/scrollbar.dart'; export 'src/layout/sidebar/sidebar.dart'; export 'src/layout/sidebar/sidebar_item.dart'; export 'src/layout/sidebar/sidebar_items.dart'; @@ -60,8 +64,10 @@ export 'src/layout/toolbar/toolbar_overflow_menu.dart'; export 'src/layout/toolbar/toolbar_overflow_menu_item.dart'; export 'src/layout/toolbar/toolbar_popup.dart'; export 'src/layout/toolbar/toolbar_spacer.dart'; +export 'src/layout/wallpaper_tinted_area.dart'; export 'src/layout/window.dart'; export 'src/macos_app.dart'; +export 'src/macos_window_utils_config.dart'; export 'src/selectors/color_well.dart'; export 'src/selectors/date_picker.dart'; export 'src/selectors/time_picker.dart'; @@ -82,8 +88,3 @@ export 'src/theme/search_field_theme.dart'; export 'src/theme/time_picker_theme.dart'; export 'src/theme/tooltip_theme.dart'; export 'src/theme/typography.dart'; -export 'src/layout/wallpaper_tinted_area.dart'; -export 'src/macos_window_utils_config.dart'; - -export 'package:macos_window_utils/macos_window_utils.dart'; -export 'package:macos_window_utils/macos/ns_window_delegate.dart'; diff --git a/lib/src/buttons/switch.dart b/lib/src/buttons/switch.dart index 6dd4375e..25d7f637 100644 --- a/lib/src/buttons/switch.dart +++ b/lib/src/buttons/switch.dart @@ -1,23 +1,48 @@ -import 'package:flutter/cupertino.dart' as c; -import 'package:flutter/foundation.dart'; +import 'dart:ui'; + import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; +const _kDefaultBorderColor = CupertinoDynamicColor.withBrightness( + color: MacosColor.fromRGBO(215, 215, 215, 1.0), + darkColor: MacosColor.fromRGBO(101, 101, 101, 1.0), +); + +const _kDefaultTrackColor = CupertinoDynamicColor.withBrightness( + color: MacosColor.fromRGBO(228, 226, 228, 1.0), + darkColor: MacosColor.fromRGBO(66, 66, 66, 1.0), +); + +// Dark color might be Color.fromRGBO(255, 255, 255, 0.721)?? +const _kDefaultKnobColor = CupertinoDynamicColor.withBrightness( + color: MacosColors.white, + darkColor: MacosColor.fromRGBO(207, 207, 207, 1.0), +); + /// {@template macosSwitch} -/// A switch is a visual toggle between two mutually exclusive -/// states — on and off. A switch shows that it's on when the -/// accent color is visible and off when the switch appears colorless. +/// A switch is a control that offers a binary choice between two mutually +/// exclusive states — on and off. +/// +/// A switch shows that it's on when the [activeColor] is visible and off when +/// the [trackColor] is visible. +/// +/// Additional Reference: +/// * [Toggles (Human Interface Guidelines)](https://developer.apple.com/design/human-interface-guidelines/components/selection-and-input/toggles) +/// * [Toggles (Apple Developer)](https://developer.apple.com/documentation/swiftui/toggle) /// {@endtemplate} -class MacosSwitch extends StatelessWidget { +class MacosSwitch extends StatefulWidget { /// {@macro macosSwitch} const MacosSwitch({ super.key, required this.value, + this.size = ControlSize.regular, required this.onChanged, this.dragStartBehavior = DragStartBehavior.start, this.activeColor, this.trackColor, + this.knobColor, this.semanticLabel, }); @@ -26,6 +51,13 @@ class MacosSwitch extends StatelessWidget { /// Must not be null. final bool value; + /// The size of the switch, which is [ControlSize.regular] by default. + /// + /// Allowable sizes are [ControlSize.mini], [ControlSize.small], and + /// [ControlSize.regular]. If [ControlSize.large] is used, the switch will + /// size itself as a [ControlSize.regular] switch. + final ControlSize size; + /// Called when the user toggles with switch on or off. /// /// The switch passes the new value to the callback but does not actually @@ -39,7 +71,7 @@ class MacosSwitch extends StatelessWidget { /// gets rebuilt; for example: /// /// ```dart - /// Switch( + /// MacosSwitch( /// value: _giveVerse, /// onChanged: (bool newValue) { /// setState(() { @@ -53,19 +85,25 @@ class MacosSwitch extends StatelessWidget { /// {@macro flutter.cupertino.CupertinoSwitch.dragStartBehavior} final DragStartBehavior dragStartBehavior; - /// The color to use when this switch is on. + /// The color to use for the track when this switch is on. /// /// Defaults to [MacosThemeData.primaryColor] when null. - final Color? activeColor; + final MacosColor? activeColor; - /// The color to use for the background when the switch is off. + /// The color to use for track when this switch is off. /// - /// Defaults to [CupertinoColors.secondarySystemFill] when null. - final Color? trackColor; + /// Defaults to [MacosTheme.primaryColor] when null. + final MacosColor? trackColor; + + /// The color to use for the switch's knob. + final MacosColor? knobColor; /// The semantic label used by screen readers. final String? semanticLabel; + @override + State createState() => _MacosSwitchState(); + @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); @@ -74,6 +112,7 @@ class MacosSwitch extends StatelessWidget { value: value, ifFalse: 'unchecked', )); + properties.add(EnumProperty('size', size)); properties.add(EnumProperty('dragStartBehavior', dragStartBehavior)); properties.add(FlagProperty( 'enabled', @@ -82,26 +121,606 @@ class MacosSwitch extends StatelessWidget { )); properties.add(ColorProperty('activeColor', activeColor)); properties.add(ColorProperty('trackColor', trackColor)); + properties.add(ColorProperty('knobColor', knobColor)); properties.add(StringProperty('semanticLabel', semanticLabel)); } +} + +class _MacosSwitchState extends State + with TickerProviderStateMixin { + late TapGestureRecognizer _tap; + late HorizontalDragGestureRecognizer _drag; + + late AnimationController _positionController; + late CurvedAnimation position; + + late AnimationController _reactionController; + late Animation _reaction; + + bool get isInteractive => widget.onChanged != null; + + // A non-null boolean value that changes to true at the end of a drag if the + // switch must be animated to the position indicated by the widget's value. + bool needsPositionAnimation = false; + + @override + void initState() { + super.initState(); + + _tap = TapGestureRecognizer() + ..onTapDown = _handleTapDown + ..onTapUp = _handleTapUp + ..onTap = _handleTap + ..onTapCancel = _handleTapCancel; + _drag = HorizontalDragGestureRecognizer() + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd + ..dragStartBehavior = widget.dragStartBehavior; + + _positionController = AnimationController( + duration: _kToggleDuration, + value: widget.value ? 1.0 : 0.0, + vsync: this, + ); + position = CurvedAnimation( + parent: _positionController, + curve: Curves.linear, + ); + _reactionController = AnimationController( + duration: _kReactionDuration, + vsync: this, + ); + _reaction = CurvedAnimation( + parent: _reactionController, + curve: Curves.ease, + ); + } + + @override + void didUpdateWidget(MacosSwitch oldWidget) { + super.didUpdateWidget(oldWidget); + _drag.dragStartBehavior = widget.dragStartBehavior; + + if (needsPositionAnimation || oldWidget.value != widget.value) { + _resumePositionAnimation(isLinear: needsPositionAnimation); + } + } + + // `isLinear` must be true if the position animation is trying to move the + // knob to the closest end after the most recent drag animation, so the curve + // does not change when the controller's value is not 0 or 1. + // + // It can be set to false when it's an implicit animation triggered by + // widget.value changes. + void _resumePositionAnimation({bool isLinear = true}) { + needsPositionAnimation = false; + position + ..curve = isLinear ? Curves.linear : Curves.ease + ..reverseCurve = isLinear ? Curves.linear : Curves.ease.flipped; + if (widget.value) { + _positionController.forward(); + } else { + _positionController.reverse(); + } + } + + void _handleTapDown(TapDownDetails details) { + if (isInteractive) { + needsPositionAnimation = false; + } + _reactionController.forward(); + } + + void _handleTap() { + if (isInteractive) { + widget.onChanged!(!widget.value); + } + } + + void _handleTapUp(TapUpDetails details) { + if (isInteractive) { + needsPositionAnimation = false; + _reactionController.reverse(); + } + } + + void _handleTapCancel() { + if (isInteractive) { + _reactionController.reverse(); + } + } + + void _handleDragStart(DragStartDetails details) { + if (isInteractive) { + needsPositionAnimation = false; + _reactionController.forward(); + } + } + + void _handleDragUpdate(DragUpdateDetails details) { + if (isInteractive) { + position + ..curve = Curves.linear + ..reverseCurve = Curves.linear; + final double delta = details.primaryDelta! / widget.size.trackInnerLength; + switch (Directionality.of(context)) { + case TextDirection.rtl: + _positionController.value -= delta; + break; + case TextDirection.ltr: + _positionController.value += delta; + break; + } + } + } + + void _handleDragEnd(DragEndDetails details) { + // Deferring the animation to the next build phase. + setState(() => needsPositionAnimation = true); + // Call onChanged when the user's intent to change value is clear. + if (position.value >= 0.5 != widget.value) { + widget.onChanged!(!widget.value); + } + _reactionController.reverse(); + } + + @override + void dispose() { + _tap.dispose(); + _drag.dispose(); + + _positionController.dispose(); + _reactionController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { assert(debugCheckHasMacosTheme(context)); final MacosThemeData theme = MacosTheme.of(context); + MacosColor borderColor = + MacosDynamicColor.resolve(_kDefaultBorderColor, context).toMacosColor(); + MacosColor activeColor = MacosColor(MacosDynamicColor.resolve( + widget.activeColor ?? theme.primaryColor, + context, + ).value); + MacosColor trackColor = widget.trackColor ?? + MacosDynamicColor.resolve(_kDefaultTrackColor, context).toMacosColor(); + MacosColor knobColor = widget.knobColor ?? + MacosDynamicColor.resolve(_kDefaultKnobColor, context).toMacosColor(); + + // Shot in the dark to try and get the border color correct for each + // possible color + if (widget.value) { + if (theme.brightness.isDark) { + borderColor.computeLuminance() > 0.5 + ? borderColor = MacosColor.darken(activeColor, 20) + : borderColor = MacosColor.lighten(activeColor, 20); + } else { + borderColor.computeLuminance() > 0.5 + ? borderColor = MacosColor.darken(activeColor, 20) + : borderColor = MacosColor.lighten(activeColor, 20); + } + } + return Semantics( - label: semanticLabel, - checked: value, - child: c.CupertinoSwitch( - value: value, - onChanged: onChanged, - dragStartBehavior: dragStartBehavior, - activeColor: MacosDynamicColor.resolve( - activeColor ?? theme.primaryColor, - context, - ), + label: widget.semanticLabel, + checked: widget.value, + child: _MacosSwitchRenderObjectWidget( + value: widget.value, + size: widget.size, + activeColor: activeColor, trackColor: trackColor, + knobColor: knobColor, + borderColor: borderColor, + onChanged: widget.onChanged, + textDirection: Directionality.of(context), + state: this, ), ); } } + +class _MacosSwitchRenderObjectWidget extends LeafRenderObjectWidget { + const _MacosSwitchRenderObjectWidget({ + required this.value, + required this.size, + required this.activeColor, + required this.trackColor, + required this.knobColor, + required this.borderColor, + required this.onChanged, + required this.textDirection, + required this.state, + }); + final bool value; + final ControlSize size; + final MacosColor activeColor; + final MacosColor trackColor; + final MacosColor knobColor; + final MacosColor borderColor; + final ValueChanged? onChanged; + final TextDirection textDirection; + final _MacosSwitchState state; + + @override + _RenderMacosSwitch createRenderObject(BuildContext context) { + return _RenderMacosSwitch( + value: value, + size: size, + activeColor: activeColor, + trackColor: trackColor, + knobColor: knobColor, + borderColor: borderColor, + onChanged: onChanged, + textDirection: textDirection, + state: state, + ); + } + + @override + void updateRenderObject( + BuildContext context, + _RenderMacosSwitch renderObject, + ) { + assert(renderObject._state == state); + renderObject + ..value = value + ..controlSize = size + ..activeColor = activeColor + ..trackColor = trackColor + ..knobColor = knobColor + ..borderColor = borderColor + ..onChanged = onChanged + ..textDirection = textDirection; + } +} + +const Size _kMiniTrackSize = Size(26.0, 15.0); +const Size _kSmallTrackSize = Size(32.0, 18.0); +const Size _kRegularTrackSize = Size(38.0, 22.0); + +const double _kMiniKnobSize = 13.0; +const double _kSmallKnobSize = 16.0; +const double _kRegularKnobSize = 20.0; + +// Shortcuts for details about how to create the switch, based on the control +// size. +extension _ControlSizeX on ControlSize { + Size get trackSize { + switch (this) { + case ControlSize.mini: + return _kMiniTrackSize; + case ControlSize.small: + return _kSmallTrackSize; + default: + return _kRegularTrackSize; + } + } + + double get knobSize { + switch (this) { + case ControlSize.mini: + return _kMiniKnobSize; + case ControlSize.small: + return _kSmallKnobSize; + default: + return _kRegularKnobSize; + } + } + + double get knobRadius => knobSize / 2.0; + double get trackInnerStart => trackSize.height / 2.0; + double get trackInnerEnd => trackSize.width - trackInnerStart; + double get trackInnerLength => trackInnerEnd - trackInnerStart; +} + +const Duration _kReactionDuration = Duration(milliseconds: 400); +const Duration _kToggleDuration = Duration(milliseconds: 300); + +class _RenderMacosSwitch extends RenderConstrainedBox { + _RenderMacosSwitch({ + required bool value, + required ControlSize size, + required MacosColor activeColor, + required MacosColor trackColor, + required MacosColor knobColor, + required MacosColor borderColor, + required ValueChanged? onChanged, + required TextDirection textDirection, + required _MacosSwitchState state, + }) : _value = value, + _size = size, + _activeColor = activeColor, + _trackColor = trackColor, + _knobPainter = MacosSwitchKnobPainter(color: knobColor), + _borderColor = borderColor, + _onChanged = onChanged, + _textDirection = textDirection, + _state = state, + super( + additionalConstraints: BoxConstraints.tightFor( + width: size.trackSize.width, + height: size.trackSize.height, + ), + ) { + state.position.addListener(markNeedsPaint); + state._reaction.addListener(markNeedsPaint); + } + + final _MacosSwitchState _state; + + bool get value => _value; + bool _value; + set value(bool newValue) { + if (newValue == _value) { + return; + } + _value = newValue; + markNeedsSemanticsUpdate(); + } + + ControlSize get controlSize => _size; + ControlSize _size; + set controlSize(ControlSize value) { + if (value == _size) { + return; + } + _size = value; + markNeedsPaint(); + } + + MacosColor get activeColor => _activeColor; + MacosColor _activeColor; + set activeColor(MacosColor value) { + if (value == _activeColor) { + return; + } + _activeColor = value; + markNeedsPaint(); + } + + MacosColor get trackColor => _trackColor; + MacosColor _trackColor; + set trackColor(MacosColor value) { + if (value == _trackColor) { + return; + } + _trackColor = value; + markNeedsPaint(); + } + + MacosColor get knobColor => _knobPainter.color; + MacosSwitchKnobPainter _knobPainter; + set knobColor(MacosColor value) { + if (value == knobColor) { + return; + } + _knobPainter = MacosSwitchKnobPainter(color: value); + markNeedsPaint(); + } + + MacosColor get borderColor => _borderColor; + MacosColor _borderColor; + set borderColor(MacosColor value) { + if (value == borderColor) { + return; + } + _borderColor = value; + markNeedsPaint(); + } + + ValueChanged? get onChanged => _onChanged; + ValueChanged? _onChanged; + set onChanged(ValueChanged? value) { + if (value == _onChanged) { + return; + } + final bool wasInteractive = isInteractive; + _onChanged = value; + if (wasInteractive != isInteractive) { + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + + TextDirection get textDirection => _textDirection; + TextDirection _textDirection; + set textDirection(TextDirection value) { + if (value == _textDirection) { + return; + } + _textDirection = value; + markNeedsPaint(); + } + + bool get isInteractive => onChanged != null; + + @override + bool hitTestSelf(Offset position) => true; + + @override + void handleEvent(PointerEvent event, BoxHitTestEntry entry) { + assert(debugHandleEvent(event, entry)); + if (event is PointerDownEvent && isInteractive) { + _state._drag.addPointer(event); + _state._tap.addPointer(event); + } + } + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + + if (isInteractive) { + config.onTap = _state._handleTap; + } + + config.isEnabled = isInteractive; + config.isToggled = _value; + } + + @override + void paint(PaintingContext context, Offset offset) { + final Canvas canvas = context.canvas; + final double currentValue = _state.position.value; + final trackSize = controlSize.trackSize; + final innerStart = controlSize.trackInnerStart; + final innerEnd = controlSize.trackInnerEnd; + + final double visualPosition; + switch (textDirection) { + case TextDirection.rtl: + visualPosition = 1.0 - currentValue; + break; + case TextDirection.ltr: + visualPosition = currentValue; + break; + } + + final Paint paint = Paint() + ..color = MacosColor.lerp(trackColor, activeColor, currentValue); + + final Rect trackRect = Rect.fromLTWH( + offset.dx + (size.width - trackSize.width) / 2.0, + offset.dy + (size.height - trackSize.height) / 2.0, + trackSize.width, + trackSize.height, + ); + final RRect trackRRect = RRect.fromRectAndRadius( + trackRect, + Radius.circular(trackSize.height / 2.0), + ); + canvas.drawRRect(trackRRect, paint); + canvas.drawRRect( + trackRRect, + Paint() + ..color = borderColor + ..style = PaintingStyle.stroke, + ); + + final double knobLeft = lerpDouble( + trackRect.left + innerStart - controlSize.knobRadius, + trackRect.left + innerEnd - controlSize.knobRadius, + visualPosition, + )!; + final double knobRight = lerpDouble( + trackRect.left + innerStart + controlSize.knobRadius, + trackRect.left + innerEnd + controlSize.knobRadius, + visualPosition, + )!; + final double knobCenterY = offset.dy + size.height / 2.0; + final Rect knobBounds = Rect.fromLTRB( + knobLeft, + knobCenterY - controlSize.knobRadius, + knobRight, + knobCenterY + controlSize.knobRadius, + ); + + _clipRRectLayer.layer = context.pushClipRRect( + needsCompositing, + Offset.zero, + knobBounds, + trackRRect, + (PaintingContext innerContext, Offset offset) { + _knobPainter.paint( + innerContext.canvas, + knobBounds, + visualPosition == 1.0, + ); + }, + oldLayer: _clipRRectLayer.layer, + ); + } + + final LayerHandle _clipRRectLayer = + LayerHandle(); + + @override + void dispose() { + _clipRRectLayer.layer = null; + super.dispose(); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder description) { + super.debugFillProperties(description); + description.add(FlagProperty( + 'value', + value: value, + ifTrue: 'checked', + ifFalse: 'unchecked', + showName: true, + )); + description.add(FlagProperty( + 'isInteractive', + value: isInteractive, + ifTrue: 'enabled', + ifFalse: 'disabled', + showName: true, + defaultValue: true, + )); + } +} + +const List _kSwitchOffBoxShadows = [ + BoxShadow( + color: Color(0x26000000), + // offset: Offset(1, 1), + blurRadius: 8.0, + blurStyle: BlurStyle.inner, + ), + BoxShadow( + color: Color(0x0F000000), + // offset: Offset(1, 1), + blurRadius: 1.0, + blurStyle: BlurStyle.inner, + ), +]; + +const List _kSwitchOnBoxShadows = [ + BoxShadow( + color: Color(0x26000000), + // offset: Offset(-3, 1), + blurRadius: 8.0, + blurStyle: BlurStyle.inner, + ), + BoxShadow( + color: Color(0x0F000000), + // offset: Offset(-1, 1), + blurRadius: 1.0, + blurStyle: BlurStyle.inner, + ), +]; + +/// Paints a macOS-style switch knob. +/// +/// Used by [MacosSwitch]. +class MacosSwitchKnobPainter { + /// Creates an object that paints a macOS-style switch knob. + const MacosSwitchKnobPainter({required this.color}); + + /// The color of the interior of the knob. + final MacosColor color; + + /// Paints the knob onto the given canvas in the given rectangle. + void paint(Canvas canvas, Rect rect, bool isOn) { + final RRect rrect = RRect.fromRectAndRadius( + rect, + Radius.circular(rect.shortestSide / 2.0), + ); + + if (isOn) { + for (final BoxShadow shadow in _kSwitchOnBoxShadows) { + canvas.drawRRect(rrect.shift(shadow.offset), shadow.toPaint()); + } + } else { + for (final BoxShadow shadow in _kSwitchOffBoxShadows) { + canvas.drawRRect(rrect.shift(shadow.offset), shadow.toPaint()); + } + } + + canvas.drawRRect(rrect, Paint()..color = color); + } +} diff --git a/lib/src/enums/control_size.dart b/lib/src/enums/control_size.dart new file mode 100644 index 00000000..835812bf --- /dev/null +++ b/lib/src/enums/control_size.dart @@ -0,0 +1,25 @@ +/// The out-of-the-box sizes that certain "control" widgets can be. +/// +/// +/// +/// Not all controls support all sizes. For example, a [PushButton] can be any +/// size, but a [MacosSwitch] can be all but large. In cases where a control +/// doesn't support a certain size, the control will automatically fall back to +/// the nearest supported size. +/// +/// Reference: +/// * https://developer.apple.com/documentation/swiftui/controlsize +/// * https://developer.apple.com/documentation/swiftui/view/controlsize(_:) +enum ControlSize { + /// A control that is minimally sized. + mini, + + /// A control that is proportionally smaller size for space-constrained views. + small, + + /// A control that is the default size. + regular, + + /// A control that is prominently sized. + large, +} diff --git a/lib/src/theme/icon_theme.dart b/lib/src/theme/icon_theme.dart index 9e7377c8..f3b801f7 100644 --- a/lib/src/theme/icon_theme.dart +++ b/lib/src/theme/icon_theme.dart @@ -210,7 +210,7 @@ class MacosIconThemeData with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(ColorProperty('color', color, defaultValue: null)); + properties.add(ColorProperty('MacosColor', color, defaultValue: null)); properties.add(DoubleProperty('opacity', opacity, defaultValue: null)); properties.add(DoubleProperty('size', size, defaultValue: null)); } diff --git a/lib/src/theme/macos_colors.dart b/lib/src/theme/macos_colors.dart index f5d92d80..73985d8f 100644 --- a/lib/src/theme/macos_colors.dart +++ b/lib/src/theme/macos_colors.dart @@ -85,6 +85,77 @@ class MacosColor extends Color { static int getAlphaFromOpacity(double opacity) { return (opacity.clamp(0.0, 1.0) * 255).round(); } + + /// Returns a new color that matches this color with the alpha channel + /// replaced with the given `opacity` (which ranges from 0.0 to 1.0). + /// + /// Out of range values will have unexpected effects. + @override + MacosColor withOpacity(double opacity) { + assert(opacity >= 0.0 && opacity <= 1.0); + return withAlpha((255.0 * opacity).round()); + } + + /// Returns a new color that matches this color with the alpha channel + /// replaced with `a` (which ranges from 0 to 255). + /// + /// Out of range values will have unexpected effects. + @override + MacosColor withAlpha(int a) { + return MacosColor.fromARGB(a, red, green, blue); + } + + /// Darkens a [MacosColor] by a [percent] amount (100 = black) without + /// changing the tint of the color. + static MacosColor darken(MacosColor c, [int percent = 10]) { + assert(1 <= percent && percent <= 100); + var f = 1 - percent / 100; + return MacosColor.fromARGB( + c.alpha, + (c.red * f).round(), + (c.green * f).round(), + (c.blue * f).round(), + ); + } + + /// Lightens a [MacosColor] by a [percent] amount (100 = white) without + /// changing the tint of the color + static MacosColor lighten(MacosColor c, [int percent = 10]) { + assert(1 <= percent && percent <= 100); + var p = percent / 100; + return MacosColor.fromARGB( + c.alpha, + c.red + ((255 - c.red) * p).round(), + c.green + ((255 - c.green) * p).round(), + c.blue + ((255 - c.blue) * p).round(), + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is MacosColor && other.value == value; + } + + @override + int get hashCode => value.hashCode; + + @override + String toString() { + return 'MacosColor(0x${value.toRadixString(16).padLeft(8, '0')})'; + } +} + +extension ColorX on Color { + /// Returns a [MacosColor] with the same color values as this [Color]. + MacosColor toMacosColor() { + return MacosColor(value); + } } /// A collection of color values lifted from the macOS system color picker. diff --git a/pubspec.yaml b/pubspec.yaml index 02ae5581..c52118c0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 2.0.0-beta.1 +version: 2.0.0-beta.2 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" diff --git a/test/buttons/switch_test.dart b/test/buttons/switch_test.dart index 642f0f11..a02bc40b 100644 --- a/test/buttons/switch_test.dart +++ b/test/buttons/switch_test.dart @@ -52,10 +52,12 @@ void main() { description, [ 'unchecked', + 'size: regular', 'dragStartBehavior: start', 'disabled', 'activeColor: null', 'trackColor: null', + 'knobColor: null', 'semanticLabel: null', ], ); diff --git a/test/theme/help_button_theme_test.dart b/test/theme/help_button_theme_test.dart index 98556548..1e127f80 100644 --- a/test/theme/help_button_theme_test.dart +++ b/test/theme/help_button_theme_test.dart @@ -45,8 +45,8 @@ void main() { expect( description, [ - 'color: Color(0xff0433ff)', - 'disabledColor: Color(0xff8e8e93)', + 'color: MacosColor(0xff0433ff)', + 'disabledColor: MacosColor(0xff8e8e93)', ], ); }); diff --git a/test/theme/icon_theme_test.dart b/test/theme/icon_theme_test.dart index b45e4de6..79076b18 100644 --- a/test/theme/icon_theme_test.dart +++ b/test/theme/icon_theme_test.dart @@ -51,7 +51,7 @@ void main() { expect( description, [ - 'color: Color(0xffffffff)', + 'MacosColor: MacosColor(0xffffffff)', 'opacity: 0.0', 'size: 20.0', ], diff --git a/test/theme/popup_button_theme_test.dart b/test/theme/popup_button_theme_test.dart index 730d86a6..7cbe6b7a 100644 --- a/test/theme/popup_button_theme_test.dart +++ b/test/theme/popup_button_theme_test.dart @@ -54,8 +54,8 @@ void main() { expect( description, [ - 'highlightColor: Color(0xff8e8e93)', - 'backgroundColor: Color(0xff0433ff)', + 'highlightColor: MacosColor(0xff8e8e93)', + 'backgroundColor: MacosColor(0xff0433ff)', 'popupColor: Color(0x19000000)', ], ); diff --git a/test/theme/pulldown_button_theme_test.dart b/test/theme/pulldown_button_theme_test.dart index 0ef34561..87fc9666 100644 --- a/test/theme/pulldown_button_theme_test.dart +++ b/test/theme/pulldown_button_theme_test.dart @@ -53,10 +53,10 @@ void main() { expect( description, [ - 'highlightColor: Color(0xff8e8e93)', - 'backgroundColor: Color(0xff0433ff)', + 'highlightColor: MacosColor(0xff8e8e93)', + 'backgroundColor: MacosColor(0xff0433ff)', 'pulldownColor: Color(0x19000000)', - 'iconColor: Color(0xff00f900)', + 'iconColor: MacosColor(0xff00f900)', ], ); }); diff --git a/test/theme/push_button_theme_test.dart b/test/theme/push_button_theme_test.dart index a910d981..a26619f8 100644 --- a/test/theme/push_button_theme_test.dart +++ b/test/theme/push_button_theme_test.dart @@ -46,8 +46,8 @@ void main() { expect( description, [ - 'color: Color(0xff0433ff)', - 'disabledColor: Color(0xff8e8e93)', + 'color: MacosColor(0xff0433ff)', + 'disabledColor: MacosColor(0xff8e8e93)', 'secondaryColor: Color(0x19000000)', ], ); From 46eb9caca2e8f5f5ae0456a4bac921ba4e9c79c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20K=C5=82os?= Date: Mon, 3 Jul 2023 21:22:04 +0200 Subject: [PATCH 21/45] fix: UX of the click on the calendar elements in `MacosDatePicker` (#417) fix: UX of the click on the calendar elements --- CHANGELOG.md | 4 ++++ example/pubspec.lock | 2 +- lib/src/selectors/date_picker.dart | 1 + pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a426fce..57d8d057 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.0.0-beta.3] +🛠️ Fixed 🛠️ +* Better UX of the click on the calendar elements in `MacosDatePicker` + ## [2.0.0-beta.2] ✨New ✨ * `MacosSwitch` has been completely rewritten and now matches the native macOS switch in appearance and behavior. diff --git a/example/pubspec.lock b/example/pubspec.lock index 931e90c9..ce6e9a1a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.2" + version: "2.0.0-beta.3" macos_window_utils: dependency: transitive description: diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index 348fec85..d5145dc4 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -526,6 +526,7 @@ class _MacosDatePickerState extends State { } Widget dayWidget = GestureDetector( + behavior: HitTestBehavior.opaque, onTap: () { setState(() { _isDaySelected = true; diff --git a/pubspec.yaml b/pubspec.yaml index c52118c0..ceb9b29b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 2.0.0-beta.2 +version: 2.0.0-beta.3 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From 32fa64cb0cc75b07b6fccfb60446e35bc5b37721 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 3 Jul 2023 15:24:03 -0400 Subject: [PATCH 22/45] fix minor formatting fix --- lib/src/buttons/pulldown_button.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/buttons/pulldown_button.dart b/lib/src/buttons/pulldown_button.dart index 51b783f4..91de97a7 100644 --- a/lib/src/buttons/pulldown_button.dart +++ b/lib/src/buttons/pulldown_button.dart @@ -807,8 +807,7 @@ class _MacosPulldownButtonState extends State void _handleTap() { final TextDirection? textDirection = Directionality.maybeOf(context); - const EdgeInsetsGeometry menuMargin = - EdgeInsets.symmetric(horizontal: 4.0); + const EdgeInsetsGeometry menuMargin = EdgeInsets.symmetric(horizontal: 4.0); final List<_MenuItem> menuItems = <_MenuItem>[ for (int index = 0; index < widget.items!.length; index += 1) From 1df03cbc11b72524bb6a084fecd49c7bc4ff3425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20K=C5=82os?= Date: Tue, 4 Jul 2023 15:48:48 +0200 Subject: [PATCH 23/45] feat: Added support for `startWeekOnMonday` to `MacosDatePicker` (#414) * feat: Added support for `startWeekOnMonday` to `MacosDatePicker` * Update lib/src/selectors/date_picker.dart --------- Co-authored-by: Reuben Turner --- CHANGELOG.md | 3 + lib/src/selectors/date_picker.dart | 35 ++++++++++- test/selectors/date_picker_test.dart | 92 ++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57d8d057..3ad855e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ ## [2.0.0-beta.3] +✨ New ✨ +* Added support for `startWeekOnMonday` to `MacosDatePicker`. + 🛠️ Fixed 🛠️ * Better UX of the click on the calendar elements in `MacosDatePicker` diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index d5145dc4..c945bf0b 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -44,6 +44,7 @@ class MacosDatePicker extends StatefulWidget { this.style = DatePickerStyle.combined, required this.onDateChanged, this.initialDate, + this.startWeekOnMonday, }); /// The [DatePickerStyle] to use. @@ -59,6 +60,14 @@ class MacosDatePicker extends StatefulWidget { /// Defaults to `DateTime.now()`. final DateTime? initialDate; + /// Allows for changing the order of day headers in the graphical Date Picker + /// to Mo, Tu, We, Th, Fr, Sa, Su. + /// + /// This is useful for internationalization purposes, as many countries begin their weeks on Mondays. + /// + /// Defaults to `false`. + final bool? startWeekOnMonday; + @override State createState() => _MacosDatePickerState(); } @@ -153,12 +162,23 @@ class _MacosDatePickerState extends State { } // Creates the day headers - Su, Mo, Tu, We, Th, Fr, Sa + // or Mo, Tu, We, Th, Fr, Sa, Su depending on the value of [startWeekOnMonday] List _dayHeaders( TextStyle? headerStyle, MaterialLocalizations localizations, ) { final result = []; - for (int i = localizations.firstDayOfWeekIndex; true; i = (i + 1) % 7) { + + // Hack due to invalid "firstDayOfWeekIndex" implementation in MaterialLocalizations + // issue: https://github.com/flutter/flutter/issues/122274 + // TODO: remove this workaround once the issue is fixed. + // Then, "firstDayOfWeekIndex" can be controlled by passing "localizationsDelegates" and "supportedLocales" to MacosApp + int firstDayOfWeekIndex = localizations.firstDayOfWeekIndex; + if (widget.startWeekOnMonday == true) { + firstDayOfWeekIndex = 1; + } + + for (int i = firstDayOfWeekIndex; result.length < 7; i = (i + 1) % 7) { final weekday = _narrowWeekdays[i]; result.add( ExcludeSemantics( @@ -170,7 +190,6 @@ class _MacosDatePickerState extends State { ), ), ); - if (i == (localizations.firstDayOfWeekIndex - 1) % 7) break; } return result; } @@ -474,9 +493,19 @@ class _MacosDatePickerState extends State { ), localizations, ); + + // Hack due to invalid "firstDayOfWeekIndex" implementation in MaterialLocalizations + // issue: https://github.com/flutter/flutter/issues/122274 + // TODO: remove this workaround once the issue is fixed. + // Then, DateUtils.getDaysInMonth will work as expected when proper "localizationsDelegates" and "supportedLocales" are provided to MacosApp + int fixedDayOffset = dayOffset; + if (widget.startWeekOnMonday == true) { + fixedDayOffset = dayOffset - 1; + } + // 1-based day of month, e.g. 1-31 for January, and 1-29 for February on // a leap year. - int day = -dayOffset; + int day = -fixedDayOffset; final dayItems = []; diff --git a/test/selectors/date_picker_test.dart b/test/selectors/date_picker_test.dart index b0e50b5d..4eb263b2 100644 --- a/test/selectors/date_picker_test.dart +++ b/test/selectors/date_picker_test.dart @@ -308,4 +308,96 @@ void main() { }, ); }); + + testWidgets( + 'Graphical MacosDatePicker with "startWeekOnMonday" set to true shows Monday as the first day of the week', + (tester) async { + await tester.pumpWidget( + MacosApp( + home: MacosWindow( + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: MacosDatePicker( + startWeekOnMonday: true, + initialDate: DateTime.parse('2023-04-01'), + onDateChanged: (date) {}, + ), + ); + }, + ), + ], + ), + ), + ), + ); + + final dayHeadersRow = find.byType(GridView).first; + final dayHeaders = find.descendant( + of: dayHeadersRow, + matching: find.byType(Text), + ); + final firstWeekday = dayHeaders.first; + final firstWeekdayText = (firstWeekday.evaluate().first.widget as Text).data; + await tester.pumpAndSettle(); + + expect(firstWeekdayText, 'Mo'); + + final calendarGrid = find.byType(GridView).last; + final dayOffsetWidgets = find.descendant( + of: calendarGrid, + matching: find.byType(SizedBox), + ); + final dayOffset = dayOffsetWidgets.evaluate().length; + + expect(dayOffset, 5); + }, + ); + + // Regression test due to invalid "firstDayOfWeekIndex" implementation in MaterialLocalizations + // issue: https://github.com/flutter/flutter/issues/122274 + // TODO: remove this once the issue is fixed and test starts failing + testWidgets( + 'Graphical MacosDatePicker still needs "startWeekOnMonday" to show Monday as the first day of the week, even when the locale is set to something other than "en_US"', + (tester) async { + await tester.pumpWidget( + MacosApp( + supportedLocales: const [ + Locale('en', 'PL'), + ], + home: MacosWindow( + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: MacosDatePicker( + startWeekOnMonday: true, + initialDate: DateTime.parse('2023-04-01'), + onDateChanged: (date) {}, + ), + ); + }, + ), + ], + ), + ), + ), + ); + + final dayHeadersRow = find.byType(GridView).first; + final dayHeaders = find.descendant( + of: dayHeadersRow, + matching: find.byType(Text), + ); + final firstWeekday = dayHeaders.first; + final firstWeekdayText = (firstWeekday.evaluate().first.widget as Text).data; + await tester.pumpAndSettle(); + + // The result will be 'Tu' if the fix is no longer needed and can be removed + expect(firstWeekdayText, 'Mo'); + }, + ); } From 37db0c3db35e6dc0bbe990c3f5c47405c6094ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20K=C5=82os?= Date: Tue, 4 Jul 2023 15:51:42 +0200 Subject: [PATCH 24/45] feat: Added support for `dateFormat` to `MacosDatePicker` (#415) Co-authored-by: Reuben Turner --- CHANGELOG.md | 1 + README.md | 12 +++ lib/src/selectors/date_picker.dart | 133 +++++++++++++++++++-------- test/selectors/date_picker_test.dart | 53 +++++++++++ 4 files changed, 159 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad855e5..4986bc13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## [2.0.0-beta.3] ✨ New ✨ +* Added support for `dateFormat` to `MacosDatePicker`. * Added support for `startWeekOnMonday` to `MacosDatePicker`. 🛠️ Fixed 🛠️ diff --git a/README.md b/README.md index 8164314d..97f5475b 100644 --- a/README.md +++ b/README.md @@ -974,6 +974,18 @@ There are three styles of `MacosDatePickers`: calendar-like interface to select a date. * `combined`: provides both `textual` and `graphical` interfaces. +You can also define the `dateFormat` to change the way dates are displayed in the textual interface. +It takes a string of tokens (case-insensitive) and replaces them with their corresponding values. +The following tokens are supported: +* `D`: day of the month (1-31) +* `DD`: day of the month (01-31) +* `M`: month of the year (1-12) +* `MM`: month of the year (01-12) +* `YYYY`: year (0000-9999) +* Any separator between tokens is preserved (e.g. `/`, `-`, `.`) + +The default format is `M/D/YYYY`. + Example usage: ```dart MacosDatePicker( diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index c945bf0b..149cb72c 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -44,6 +44,7 @@ class MacosDatePicker extends StatefulWidget { this.style = DatePickerStyle.combined, required this.onDateChanged, this.initialDate, + this.dateFormat, this.startWeekOnMonday, }); @@ -60,6 +61,19 @@ class MacosDatePicker extends StatefulWidget { /// Defaults to `DateTime.now()`. final DateTime? initialDate; + /// Changes the way dates are displayed in the textual interface. + /// + /// The following tokens are supported (case-insensitive): + /// * `D`: day of the month (1-31) + /// * `DD`: day of the month (01-31) + /// * `M`: month of the year (1-12) + /// * `MM`: month of the year (01-12) + /// * `YYYY`: year (0000-9999) + /// * Any separator between tokens is preserved (e.g. `/`, `-`, `.`) + /// + /// Defaults to `M/D/YYYY`. + final String? dateFormat; + /// Allows for changing the order of day headers in the graphical Date Picker /// to Mo, Tu, We, Th, Fr, Sa, Su. /// @@ -194,6 +208,84 @@ class _MacosDatePickerState extends State { return result; } + // Creates textual date presentation based on "dateFormat" property + List _textualDateElements() { + final separator = widget.dateFormat != null + ? widget.dateFormat!.toLowerCase().replaceAll(RegExp(r'[dmy]'), '')[0] + : '/'; + + final List dateElements = widget.dateFormat != null + ? widget.dateFormat!.toLowerCase().split(RegExp(r'[^dmy]')) + : ['m', 'd', 'y']; + + final List dateFields = []; + for (var dateElement in dateElements) { + if (dateElement.startsWith('d')) { + String value = dateElement == 'dd' && _selectedDay < 10 + // Add a leading zero + ? '0$_selectedDay' + : '$_selectedDay'; + + dateFields.add( + DatePickerFieldElement( + isSelected: _isDaySelected, + element: value, + onSelected: () { + setState(() { + _focusNode.requestFocus(); + _isDaySelected = !_isDaySelected; + _isMonthSelected = false; + _isYearSelected = false; + }); + }, + ), + ); + } else if (dateElement.startsWith('m')) { + String value = dateElement == 'mm' && _selectedMonth < 10 + // Add a leading zero + ? '0$_selectedMonth' + : '$_selectedMonth'; + + dateFields.add( + DatePickerFieldElement( + isSelected: _isMonthSelected, + element: value, + onSelected: () { + setState(() { + _focusNode.requestFocus(); + _isMonthSelected = !_isMonthSelected; + _isDaySelected = false; + _isYearSelected = false; + }); + }, + ), + ); + } else if (dateElement.startsWith('y')) { + dateFields.add( + DatePickerFieldElement( + isSelected: _isYearSelected, + element: '$_selectedYear', + onSelected: () { + setState(() { + _focusNode.requestFocus(); + _isYearSelected = !_isYearSelected; + _isDaySelected = false; + _isMonthSelected = false; + }); + }, + ), + ); + } + dateFields.add( + Text(separator), + ); + } + + dateFields.removeLast(); + + return dateFields; + } + Widget _buildTextualPicker(MacosDatePickerThemeData datePickerTheme) { return KeyboardShortcutRunner( onUpArrowKeypress: _incrementElement, @@ -214,46 +306,7 @@ class _MacosDatePickerState extends State { ), child: Row( mainAxisSize: MainAxisSize.min, - children: [ - DatePickerFieldElement( - isSelected: _isMonthSelected, - element: '$_selectedMonth', - onSelected: () { - setState(() { - _focusNode.requestFocus(); - _isMonthSelected = !_isMonthSelected; - _isDaySelected = false; - _isYearSelected = false; - }); - }, - ), - const Text('/'), - DatePickerFieldElement( - isSelected: _isDaySelected, - element: '$_selectedDay', - onSelected: () { - setState(() { - _focusNode.requestFocus(); - _isDaySelected = !_isDaySelected; - _isMonthSelected = false; - _isYearSelected = false; - }); - }, - ), - const Text('/'), - DatePickerFieldElement( - isSelected: _isYearSelected, - element: '$_selectedYear', - onSelected: () { - setState(() { - _focusNode.requestFocus(); - _isYearSelected = !_isYearSelected; - _isDaySelected = false; - _isMonthSelected = false; - }); - }, - ), - ], + children: _textualDateElements(), ), ), ), diff --git a/test/selectors/date_picker_test.dart b/test/selectors/date_picker_test.dart index 4eb263b2..0d006b89 100644 --- a/test/selectors/date_picker_test.dart +++ b/test/selectors/date_picker_test.dart @@ -82,6 +82,59 @@ void main() { }, ); + testWidgets( + 'Textual MacosDatePicker renders the date with respect to "dateFormat" property', + (tester) async { + renderWidget(String dateFormat) => MacosApp( + home: MacosWindow( + disableWallpaperTinting: true, + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: MacosDatePicker( + initialDate: DateTime.parse('2023-04-01'), + onDateChanged: (date) {}, + dateFormat: dateFormat, + style: DatePickerStyle.textual, + ), + ); + }, + ), + ], + ), + ), + ); + + getNthTextFromWidget(int index) => (find.byType(Text).at(index).evaluate().first.widget as Text).data as String; + + await tester.pumpWidget(renderWidget('dd.mm.yyyy')); + String firstDateElement = getNthTextFromWidget(0); + expect(firstDateElement, '01'); + String secondDateElement = getNthTextFromWidget(1); + expect(secondDateElement, '.'); + String thirdDateElement = getNthTextFromWidget(2); + expect(thirdDateElement, '04'); + String fourthDateElement = getNthTextFromWidget(3); + expect(fourthDateElement, '.'); + String fifthDateElement = getNthTextFromWidget(4); + expect(fifthDateElement, '2023'); + + await tester.pumpWidget(renderWidget('yyyy-m-d')); + firstDateElement = getNthTextFromWidget(0); + expect(firstDateElement, '2023'); + secondDateElement = getNthTextFromWidget(1); + expect(secondDateElement, '-'); + thirdDateElement = getNthTextFromWidget(2); + expect(thirdDateElement, '4'); + fourthDateElement = getNthTextFromWidget(3); + expect(fourthDateElement, '-'); + fifthDateElement = getNthTextFromWidget(4); + expect(fifthDateElement, '1'); + }, + ); + testWidgets( 'Can select the date field element and change the value', (tester) async { From 044af12cd130b3f9f86ea805cd9dcc5c42797055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20K=C5=82os?= Date: Tue, 4 Jul 2023 19:38:15 +0200 Subject: [PATCH 25/45] feat: Added support for `weekdayAbbreviations` and `monthAbbreviations` to `MacosDatePicker` (#416) * fix mangled conflict resolution part 1 * fix mangled PR conflict resolution part 2 * fix: remove missed state property --------- Co-authored-by: GroovinChip --- CHANGELOG.md | 1 + README.md | 4 ++ lib/src/selectors/date_picker.dart | 47 +++++++++++++++------- lib/src/utils.dart | 31 -------------- test/selectors/date_picker_test.dart | 60 ++++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4986bc13..9fca81d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## [2.0.0-beta.3] ✨ New ✨ +* Added support for `weekdayAbbreviations` and `monthAbbreviations` to `MacosDatePicker`. * Added support for `dateFormat` to `MacosDatePicker`. * Added support for `startWeekOnMonday` to `MacosDatePicker`. diff --git a/README.md b/README.md index 97f5475b..19a5ea75 100644 --- a/README.md +++ b/README.md @@ -974,6 +974,10 @@ There are three styles of `MacosDatePickers`: calendar-like interface to select a date. * `combined`: provides both `textual` and `graphical` interfaces. +Localization of the time picker is supported by the `weekdayAbbreviations` and `monthAbbreviations` parameters (instead of e.g. standard `localizations.narrowWeekdays()` in order to match Apple's spec). +* `weekdayAbbreviations` should be a list of 7 strings, one for each day of the week, starting with Sunday +* `monthAbbreviations` should be a list of 12 strings, one for each month of the year, starting with January + You can also define the `dateFormat` to change the way dates are displayed in the textual interface. It takes a string of tokens (case-insensitive) and replaces them with their corresponding values. The following tokens are supported: diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index 149cb72c..a3e20fc0 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -44,6 +44,31 @@ class MacosDatePicker extends StatefulWidget { this.style = DatePickerStyle.combined, required this.onDateChanged, this.initialDate, + // Use this to get the weekday abbreviations instead of + // localizations.narrowWeekdays() in order to match Apple's spec + this.weekdayAbbreviations = const [ + 'Su', + 'Mo', + 'Tu', + 'We', + 'Th', + 'Fr', + 'Sa', + ], + this.monthAbbreviations = const [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ], this.dateFormat, this.startWeekOnMonday, }); @@ -61,6 +86,12 @@ class MacosDatePicker extends StatefulWidget { /// Defaults to `DateTime.now()`. final DateTime? initialDate; + /// A list of 7 strings, one for each day of the week, starting with Sunday. + final List weekdayAbbreviations; + + /// A list of 12 strings, one for each month of the year, starting with January. + final List monthAbbreviations; + /// Changes the way dates are displayed in the textual interface. /// /// The following tokens are supported (case-insensitive): @@ -87,18 +118,6 @@ class MacosDatePicker extends StatefulWidget { } class _MacosDatePickerState extends State { - // Use this to get the weekday abbreviations instead of - // localizations.narrowWeekdays() in order to match Apple's spec - static const List _narrowWeekdays = [ - 'Su', - 'Mo', - 'Tu', - 'We', - 'Th', - 'Fr', - 'Sa', - ]; - final _today = DateTime.now(); late final _initialDate = widget.initialDate ?? _today; @@ -193,7 +212,7 @@ class _MacosDatePickerState extends State { } for (int i = firstDayOfWeekIndex; result.length < 7; i = (i + 1) % 7) { - final weekday = _narrowWeekdays[i]; + final weekday = widget.weekdayAbbreviations[i]; result.add( ExcludeSemantics( child: Center( @@ -398,7 +417,7 @@ class _MacosDatePickerState extends State { children: [ Expanded( child: Text( - '${intToMonthAbbr(_selectedMonth)} $_selectedYear', + '${widget.monthAbbreviations[_selectedMonth - 1]} $_selectedYear', style: const TextStyle( fontSize: 13.0, fontWeight: FontWeight.w700, diff --git a/lib/src/utils.dart b/lib/src/utils.dart index a986d412..b99c1796 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -59,37 +59,6 @@ Color iconLuminance(Color backgroundColor, bool isDark) { } } -String intToMonthAbbr(int month) { - switch (month) { - case 1: - return 'Jan'; - case 2: - return 'Feb'; - case 3: - return 'Mar'; - case 4: - return 'Apr'; - case 5: - return 'May'; - case 6: - return 'Jun'; - case 7: - return 'Jul'; - case 8: - return 'Aug'; - case 9: - return 'Sep'; - case 10: - return 'Oct'; - case 11: - return 'Nov'; - case 12: - return 'Dec'; - default: - throw Exception('Unsupported value'); - } -} - class Unsupported { const Unsupported(this.message); diff --git a/test/selectors/date_picker_test.dart b/test/selectors/date_picker_test.dart index 0d006b89..e5f4f495 100644 --- a/test/selectors/date_picker_test.dart +++ b/test/selectors/date_picker_test.dart @@ -360,6 +360,66 @@ void main() { } }, ); + + testWidgets( + 'Graphical MacosDatePicker renders abbreviations based on "weekdayAbbreviations" and "monthAbbreviations" properties', + (tester) async { + await tester.pumpWidget( + MacosApp( + home: MacosWindow( + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: MacosDatePicker( + initialDate: DateTime.parse('2023-04-01'), + onDateChanged: (date) {}, + weekdayAbbreviations: const [ + 'Nd', + 'Po', + 'Wt', + 'Śr', + 'Cz', + 'Pt', + 'So', + ], + monthAbbreviations: const [ + 'Sty', + 'Lut', + 'Mar', + 'Kwi', + 'Maj', + 'Cze', + 'Lip', + 'Sie', + 'Wrz', + 'Paź', + 'Lis', + 'Gru', + ], + ), + ); + }, + ), + ], + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.text('Kwi 2023'), findsOneWidget); + expect(find.text('Nd'), findsOneWidget); + expect(find.text('Po'), findsOneWidget); + expect(find.text('Wt'), findsOneWidget); + expect(find.text('Śr'), findsOneWidget); + expect(find.text('Cz'), findsOneWidget); + expect(find.text('Pt'), findsOneWidget); + expect(find.text('So'), findsOneWidget); + }, + ); }); testWidgets( From 057b1b8230da609e91474d53ada939832b0b459a Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 5 Jul 2023 12:43:12 -0400 Subject: [PATCH 26/45] fix: `ToolBar` title not avoiding traffic lights when no sidebar is present (#441) * fix: `ToolBar` not avoiding traffic lights when no sidebar is present * tweak changelog --- CHANGELOG.md | 4 ++++ example/pubspec.lock | 2 +- lib/src/layout/window.dart | 6 ++++-- pubspec.yaml | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fca81d1..627308cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.0.0-beta.4] +🛠️ Fixed 🛠️ +* `ToolBar`s in use where a `SideBar` is not present will now have their title's avoid the traffic lights (native window controls). + ## [2.0.0-beta.3] ✨ New ✨ * Added support for `weekdayAbbreviations` and `monthAbbreviations` to `MacosDatePicker`. diff --git a/example/pubspec.lock b/example/pubspec.lock index ce6e9a1a..727c8aac 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.3" + version: "2.0.0-beta.4" macos_window_utils: dependency: transitive description: diff --git a/lib/src/layout/window.dart b/lib/src/layout/window.dart index 3171decc..4d8b4cf5 100644 --- a/lib/src/layout/window.dart +++ b/lib/src/layout/window.dart @@ -226,8 +226,10 @@ class _MacosWindowState extends State { final height = constraints.maxHeight; final isAtBreakpoint = width <= (sidebar?.windowBreakpoint ?? 0); final isAtEndBreakpoint = width <= (endSidebar?.windowBreakpoint ?? 0); - final canShowSidebar = _showSidebar && !isAtBreakpoint; - final canShowEndSidebar = _showEndSidebar && !isAtEndBreakpoint; + final canShowSidebar = + _showSidebar && !isAtBreakpoint && sidebar != null; + final canShowEndSidebar = + _showEndSidebar && !isAtEndBreakpoint && endSidebar != null; final visibleSidebarWidth = canShowSidebar ? _sidebarWidth : 0.0; final visibleEndSidebarWidth = canShowEndSidebar ? _endSidebarWidth : 0.0; diff --git a/pubspec.yaml b/pubspec.yaml index ceb9b29b..740361b5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 2.0.0-beta.3 +version: 2.0.0-beta.4 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From 2b9c084363d83d352f3e2289015a785b298af622 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 5 Jul 2023 16:50:23 -0400 Subject: [PATCH 27/45] feat: implement `ControlSize` for `PushButton` (#447) * feat: support `ControlSize` enum in `PushButton` * chore: clean up code * chore: update version & changelog * chore: adjust border radius for regular control size * fix: border radius no longer looks choppy * improve: disabled background color * refactor: `isSecondary` -> `secondary` * chore: update changelog * docs: update top-level `PushButton` dartdoc * docs: update readme * chore: migrate to DCM Teams * fix: dcm action * fix: push button tests * docs: update readme --- .github/workflows/dart_code_metrics.yaml | 14 +- CHANGELOG.md | 10 + README.md | 15 +- analysis_options.yaml | 2 - example/lib/pages/buttons_page.dart | 461 ++++++++++++++++++++--- example/lib/pages/dialogs_page.dart | 32 +- example/pubspec.lock | 2 +- lib/src/buttons/push_button.dart | 179 ++++++--- lib/src/theme/macos_theme.dart | 4 +- pubspec.lock | 120 ------ pubspec.yaml | 3 +- test/buttons/push_button_test.dart | 10 +- test/theme/push_button_theme_test.dart | 8 +- 13 files changed, 578 insertions(+), 282 deletions(-) diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index 16194aed..1dd9587a 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -9,10 +9,14 @@ jobs: - uses: actions/checkout@v3 - name: Run Dart Code Metrics - uses: dart-code-checker/dart-code-metrics-action@v4.0.0 + uses: subosito/flutter-action@v2 + with: + channel: stable + + - name: Install dependencies + run: flutter pub get + - uses: CQLabs/setup-dcm@v1.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - pull_request_comment: true - fatal_warnings: true - fatal_performance: true - fatal_style: true + + - run: dcm analyze --ci-key="${{ secrets.DCM_CI_KEY }}" --email="${{ secrets.DCM_EMAIL }}" lib diff --git a/CHANGELOG.md b/CHANGELOG.md index 627308cb..919e1927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [2.0.0-beta.5] +🚨 Breaking Changes 🚨 +* `PushButton` has been updated to support the `ControlSize` enum. + * The `buttonSize` property has been changed to `controlSize`. + * Buttons can now be any of the following sizes: mini, small, regular, or large. +* `PushButton.isSecondary` is now `PushButton.secondary`. + +🔄 Updated 🔄 +* `PushButton`'s secondary and disabled colors more closely match their native counterparts. + ## [2.0.0-beta.4] 🛠️ Fixed 🛠️ * `ToolBar`s in use where a `SideBar` is not present will now have their title's avoid the traffic lights (native window controls). diff --git a/README.md b/README.md index 19a5ea75..cf586f34 100644 --- a/README.md +++ b/README.md @@ -632,23 +632,22 @@ MacosPopupButton( ## PushButton -A push button appears within a view and initiates an instantaneous app-specific action, such as printing a document or -deleting a file. Push buttons contain text—not icons—and often open a separate window, dialog, or app so the user can +Push buttons are the standard button type in macOS. Push buttons contain text—not icons—and often open a separate window, dialog, or app so the user can complete a task. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/buttons/push-buttons/) | Dark Theme | Light Theme | -| ------------------------------------------ | ------------------------------------------ | -| | | -| | | -| | | -| | | +|--------------------------------------------|--------------------------------------------| +| | | + +ℹ️ **Note** ℹ️ +Native push buttons can be styled as text-only, text with an icon, or icon-only. Currently, text-only push buttons are supported. To create an icon-only button, use the `MacosIconButton` widget. Here's an example of how to create a basic push button: ```dart PushButton( child: Text('button'), - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, onPressed: () { print('button pressed'); }, diff --git a/analysis_options.yaml b/analysis_options.yaml index 74448811..7ed9076e 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -5,8 +5,6 @@ linter: - use_super_parameters analyzer: - plugins: - - dart_code_metrics exclude: - test/mock_canvas.dart - test/recording_canvas.dart diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index befbaef1..3bf5fe5d 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -146,71 +146,410 @@ class _ButtonsPageState extends State { ], ), const SizedBox(height: 20), - const Text('PushButton'), + const Text('Primary PushButton'), const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, + Column( + mainAxisSize: MainAxisSize.min, children: [ - PushButton( - buttonSize: ButtonSize.large, - child: const Text('Large'), - onPressed: () { - MacosWindowScope.of(context).toggleSidebar(); - }, + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + PushButton( + controlSize: ControlSize.mini, + child: const Text('Mini'), + onPressed: () { + MacosWindowScope.of(context) + .toggleSidebar(); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.small, + child: const Text('Small'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: + ControlSize.regular, + child: + const Text('Go Back'), + onPressed: () { + Navigator.of(context) + .maybePop(); + }, + ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: + ResizableSide.left, + builder: (_, __) { + return const Center( + child: + Text('Resizable Pane'), + ); + }, + ), + ], + ); + }, + ), + ); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.regular, + child: const Text('Regular'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: + ControlSize.regular, + child: + const Text('Go Back'), + onPressed: () { + Navigator.of(context) + .maybePop(); + }, + ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: + ResizableSide.left, + builder: (_, __) { + return const Center( + child: + Text('Resizable Pane'), + ); + }, + ), + ], + ); + }, + ), + ); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.large, + child: const Text('Large'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: + ControlSize.regular, + child: + const Text('Go Back'), + onPressed: () { + Navigator.of(context) + .maybePop(); + }, + ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: + ResizableSide.left, + builder: (_, __) { + return const Center( + child: + Text('Resizable Pane'), + ); + }, + ), + ], + ); + }, + ), + ); + }, + ), + ], ), - const SizedBox(width: 20), - PushButton( - buttonSize: ButtonSize.small, - child: const Text('Small'), - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) { - return MacosScaffold( - toolBar: const ToolBar( - title: Text('New page'), - ), - children: [ - ContentArea( - builder: (context, _) { - return Center( - child: PushButton( - buttonSize: ButtonSize.large, - child: const Text('Go Back'), - onPressed: () { - Navigator.of(context) - .maybePop(); - }, - ), - ); - }, - ), - ResizablePane( - minSize: 180, - startSize: 200, - windowBreakpoint: 700, - resizableSide: ResizableSide.left, - builder: (_, __) { - return const Center( - child: Text('Resizable Pane'), - ); - }, - ), - ], - ); - }, - ), - ); - }, + ], + ), + const SizedBox(height: 8), + const Text('Secondary PushButton'), + const SizedBox(height: 8), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + PushButton( + controlSize: ControlSize.mini, + secondary: true, + child: const Text('Mini'), + onPressed: () { + MacosWindowScope.of(context) + .toggleSidebar(); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.small, + secondary: true, + child: const Text('Small'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: + ControlSize.regular, + child: + const Text('Go Back'), + onPressed: () { + Navigator.of(context) + .maybePop(); + }, + ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: + ResizableSide.left, + builder: (_, __) { + return const Center( + child: + Text('Resizable Pane'), + ); + }, + ), + ], + ); + }, + ), + ); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.regular, + secondary: true, + child: const Text('Regular'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: + ControlSize.regular, + child: + const Text('Go Back'), + onPressed: () { + Navigator.of(context) + .maybePop(); + }, + ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: + ResizableSide.left, + builder: (_, __) { + return const Center( + child: + Text('Resizable Pane'), + ); + }, + ), + ], + ); + }, + ), + ); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.large, + secondary: true, + child: const Text('Large'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: + ControlSize.regular, + child: + const Text('Go Back'), + onPressed: () { + Navigator.of(context) + .maybePop(); + }, + ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: + ResizableSide.left, + builder: (_, __) { + return const Center( + child: + Text('Resizable Pane'), + ); + }, + ), + ], + ); + }, + ), + ); + }, + ), + ], ), - const SizedBox(width: 20), - PushButton( - buttonSize: ButtonSize.large, - isSecondary: true, - child: const Text('Secondary'), - onPressed: () { - MacosWindowScope.of(context).toggleSidebar(); - }, + ], + ), + const SizedBox(height: 8), + const Text('Disabled Primary PushButton'), + const SizedBox(height: 8), + const Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + PushButton( + controlSize: ControlSize.mini, + child: Text('Mini'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.small, + child: Text('Small'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.regular, + child: Text('Regular'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.large, + child: Text('Large'), + ), + ], + ), + ], + ), + const SizedBox(height: 8), + const Text('Disabled Secondary PushButton'), + const SizedBox(height: 8), + const Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + PushButton( + controlSize: ControlSize.mini, + secondary: true, + child: Text('Mini'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.small, + secondary: true, + child: Text('Small'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.regular, + secondary: true, + child: Text('Regular'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.large, + secondary: true, + child: Text('Large'), + ), + ], ), ], ), diff --git a/example/lib/pages/dialogs_page.dart b/example/lib/pages/dialogs_page.dart index b7645978..9046b61c 100644 --- a/example/lib/pages/dialogs_page.dart +++ b/example/lib/pages/dialogs_page.dart @@ -36,7 +36,7 @@ class _DialogsPageState extends State { child: Column( children: [ PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, child: const Text('Show Alert Dialog 1'), onPressed: () => showMacosAlertDialog( context: context, @@ -52,7 +52,7 @@ class _DialogsPageState extends State { ), //horizontalActions: false, primaryButton: PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, onPressed: Navigator.of(context).pop, child: const Text('Primary'), ), @@ -61,7 +61,7 @@ class _DialogsPageState extends State { ), const SizedBox(height: 16), PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, child: const Text('Show Alert Dialog 2'), onPressed: () => showMacosAlertDialog( context: context, @@ -78,13 +78,13 @@ class _DialogsPageState extends State { ), //horizontalActions: false, primaryButton: PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, onPressed: Navigator.of(context).pop, child: const Text('Primary'), ), secondaryButton: PushButton( - buttonSize: ButtonSize.large, - isSecondary: true, + controlSize: ControlSize.regular, + secondary: true, onPressed: Navigator.of(context).pop, child: const Text('Secondary'), ), @@ -93,7 +93,7 @@ class _DialogsPageState extends State { ), const SizedBox(height: 16), PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, child: const Text('Show Alert Dialog 3'), onPressed: () => showMacosAlertDialog( context: context, @@ -110,13 +110,13 @@ class _DialogsPageState extends State { ), horizontalActions: false, primaryButton: PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, onPressed: Navigator.of(context).pop, child: const Text('Primary'), ), secondaryButton: PushButton( - buttonSize: ButtonSize.large, - isSecondary: true, + controlSize: ControlSize.regular, + secondary: true, onPressed: Navigator.of(context).pop, child: const Text('Secondary'), ), @@ -125,7 +125,7 @@ class _DialogsPageState extends State { ), const SizedBox(height: 16), PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, child: const Text('Show Alert Dialog 4'), onPressed: () => showMacosAlertDialog( context: context, @@ -143,13 +143,13 @@ class _DialogsPageState extends State { ), horizontalActions: false, primaryButton: PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, onPressed: Navigator.of(context).pop, child: const Text('Primary'), ), secondaryButton: PushButton( - buttonSize: ButtonSize.large, - isSecondary: true, + controlSize: ControlSize.regular, + secondary: true, onPressed: Navigator.of(context).pop, child: const Text('Secondary'), ), @@ -159,7 +159,7 @@ class _DialogsPageState extends State { ), const SizedBox(height: 16), PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, child: const Text('Show sheet'), onPressed: () { showMacosSheet( @@ -249,7 +249,7 @@ class DemoSheet extends StatelessWidget { ), const Spacer(), PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.regular, child: const Text('Get started'), onPressed: () => Navigator.of(context).pop(), ), diff --git a/example/pubspec.lock b/example/pubspec.lock index 727c8aac..44b2f9a3 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.4" + version: "2.0.0-beta.5" macos_window_utils: dependency: transitive description: diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 6f759c1d..4fa2258a 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -5,36 +5,120 @@ import 'package:flutter/rendering.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; -/// The sizes a [PushButton] can be. -enum ButtonSize { - /// A large [PushButton]. - large, - - /// A small [PushButton]. - small, -} - -const EdgeInsetsGeometry _kSmallButtonPadding = EdgeInsets.symmetric( - vertical: 3.0, - horizontal: 8.0, +const _kMiniButtonSize = Size(26.0, 11.0); +const _kSmallButtonSize = Size(39.0, 14.0); +const _kRegularButtonSize = Size(60.0, 18.0); +const _kLargeButtonSize = Size(48.0, 26.0); + +const _kMiniButtonPadding = EdgeInsets.only(left: 6.0, right: 6.0, bottom: 1.0); +const _kSmallButtonPadding = EdgeInsets.symmetric( + vertical: 1.0, + horizontal: 7.0, ); -const EdgeInsetsGeometry _kLargeButtonPadding = EdgeInsets.symmetric( - vertical: 6.0, - horizontal: 8.0, +const _kRegularButtonPadding = EdgeInsets.only( + left: 8.0, + right: 8.0, + top: 1.0, + bottom: 4.0, ); +const _kLargeButtonPadding = EdgeInsets.only( + right: 8.0, + left: 8.0, + bottom: 1.0, +); + +const _kMiniButtonRadius = BorderRadius.all(Radius.circular(2.0)); +const _kSmallButtonRadius = BorderRadius.all(Radius.circular(2.0)); +const _kRegularButtonRadius = BorderRadius.all(Radius.circular(5.0)); +const _kLargeButtonRadius = BorderRadius.all(Radius.circular(7.0)); + +/// Shortcuts for various [PushButton] properties based on the [ControlSize]. +extension PushButtonControlSizeX on ControlSize { + /// Determines the padding of the button's text. + EdgeInsetsGeometry get padding { + switch (this) { + case ControlSize.mini: + return _kMiniButtonPadding; + case ControlSize.small: + return _kSmallButtonPadding; + case ControlSize.regular: + return _kRegularButtonPadding; + case ControlSize.large: + return _kLargeButtonPadding; + } + } + + /// Determines the button's border radius. + BorderRadiusGeometry get borderRadius { + switch (this) { + case ControlSize.mini: + return _kMiniButtonRadius; + case ControlSize.small: + return _kSmallButtonRadius; + case ControlSize.regular: + return _kRegularButtonRadius; + case ControlSize.large: + return _kLargeButtonRadius; + } + } + + /// Determines the styling of the button's text. + TextStyle textStyle(TextStyle baseStyle) { + switch (this) { + case ControlSize.mini: + return baseStyle.copyWith(fontSize: 9.0); + case ControlSize.small: + return baseStyle.copyWith(fontSize: 11.0); + case ControlSize.regular: + return baseStyle.copyWith(fontSize: 13.0); + case ControlSize.large: + return baseStyle; + } + } -const BorderRadius _kSmallButtonRadius = BorderRadius.all(Radius.circular(5.0)); -const BorderRadius _kLargeButtonRadius = BorderRadius.all(Radius.circular(7.0)); + /// Determines the button's minimum size. + BoxConstraints get constraints { + switch (this) { + case ControlSize.mini: + return BoxConstraints( + minHeight: _kMiniButtonSize.height, + minWidth: _kMiniButtonSize.width, + ); + case ControlSize.small: + return BoxConstraints( + minHeight: _kSmallButtonSize.height, + minWidth: _kSmallButtonSize.width, + ); + case ControlSize.regular: + return BoxConstraints( + minHeight: _kRegularButtonSize.height, + minWidth: _kRegularButtonSize.width, + ); + case ControlSize.large: + return BoxConstraints( + minHeight: _kLargeButtonSize.height, + minWidth: _kLargeButtonSize.width, + ); + } + } +} /// {@template pushButton} -/// A macOS-style button. +/// A control that initiates an action. +/// +/// Push Buttons are the standard button type in macOS. +/// +/// Reference: +/// * [Button (SwiftUI)](https://developer.apple.com/documentation/SwiftUI/Button) +/// * [NSButton (AppKit)](https://developer.apple.com/documentation/appkit/nsbutton) +/// * [Buttons (Human Interface Guidelines)](https://developer.apple.com/design/human-interface-guidelines/buttons) /// {@endtemplate} class PushButton extends StatefulWidget { /// {@macro pushButton} const PushButton({ super.key, required this.child, - required this.buttonSize, + required this.controlSize, this.padding, this.color, this.disabledColor, @@ -44,7 +128,7 @@ class PushButton extends StatefulWidget { this.alignment = Alignment.center, this.semanticLabel, this.mouseCursor = SystemMouseCursors.basic, - this.isSecondary, + this.secondary, }) : assert(pressedOpacity == null || (pressedOpacity >= 0.0 && pressedOpacity <= 1.0)); @@ -55,12 +139,8 @@ class PushButton extends StatefulWidget { /// The size of the button. /// - /// Must be either [ButtonSize.small] or [ButtonSize.large]. /// - /// Small buttons have a `padding` of [_kSmallButtonPadding] and a - /// `borderRadius` of [_kSmallButtonRadius]. Large buttons have a `padding` - /// of [_kLargeButtonPadding] and a `borderRadius` of [_kLargeButtonRadius]. - final ButtonSize buttonSize; + final ControlSize controlSize; /// The amount of space to surround the child inside the bounds of the button. /// @@ -116,8 +196,8 @@ class PushButton extends StatefulWidget { /// Whether the button is used as a secondary action button (e.g. Cancel buttons in dialogs) /// /// Sets its background color to [PushButtonThemeData]'s [secondaryColor] attributes (defaults - /// are gray colors). Can still be overriden if the [color] attribute is non-null. - final bool? isSecondary; + /// are gray colors). Can still be overridden if the [color] attribute is non-null. + final bool? secondary; /// Whether the button is enabled or disabled. Buttons are disabled by default. To /// enable a button, set its [onPressed] property to a non-null value. @@ -126,7 +206,7 @@ class PushButton extends StatefulWidget { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(EnumProperty('buttonSize', buttonSize)); + properties.add(EnumProperty('controlSize', controlSize)); properties.add(ColorProperty('color', color)); properties.add(ColorProperty('disabledColor', disabledColor)); properties.add(DoubleProperty('pressedOpacity', pressedOpacity)); @@ -138,7 +218,7 @@ class PushButton extends StatefulWidget { value: enabled, ifFalse: 'disabled', )); - properties.add(DiagnosticsProperty('isSecondary', isSecondary)); + properties.add(DiagnosticsProperty('secondary', secondary)); } @override @@ -224,7 +304,7 @@ class PushButtonState extends State Widget build(BuildContext context) { assert(debugCheckHasMacosTheme(context)); final bool enabled = widget.enabled; - final bool isSecondary = widget.isSecondary != null && widget.isSecondary!; + final bool isSecondary = widget.secondary != null && widget.secondary!; final MacosThemeData theme = MacosTheme.of(context); final Color backgroundColor = MacosDynamicColor.resolve( widget.color ?? @@ -234,22 +314,9 @@ class PushButtonState extends State context, ); - final Color disabledColor = MacosDynamicColor.resolve( - widget.disabledColor ?? theme.pushButtonTheme.disabledColor!, - context, - ); - - final EdgeInsetsGeometry? buttonPadding = widget.padding == null - ? widget.buttonSize == ButtonSize.small - ? _kSmallButtonPadding - : _kLargeButtonPadding - : widget.padding; - - final BorderRadiusGeometry? borderRadius = widget.borderRadius == null - ? widget.buttonSize == ButtonSize.small - ? _kSmallButtonRadius - : _kLargeButtonRadius - : widget.borderRadius; + final disabledColor = !isSecondary + ? backgroundColor.withOpacity(0.5) + : backgroundColor.withOpacity(0.25); final Color foregroundColor = widget.enabled ? textLuminance(backgroundColor) @@ -257,7 +324,7 @@ class PushButtonState extends State ? const Color.fromRGBO(255, 255, 255, 0.25) : const Color.fromRGBO(0, 0, 0, 0.25); - final TextStyle textStyle = + final baseStyle = theme.typography.headline.copyWith(color: foregroundColor); return MouseRegion( @@ -272,25 +339,25 @@ class PushButtonState extends State button: true, label: widget.semanticLabel, child: ConstrainedBox( - constraints: const BoxConstraints( - minWidth: 49, - minHeight: 20, - ), + constraints: widget.controlSize.constraints, child: FadeTransition( opacity: _opacityAnimation, child: DecoratedBox( - decoration: BoxDecoration( - borderRadius: borderRadius, - color: !enabled ? disabledColor : backgroundColor, + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + borderRadius: widget.controlSize.borderRadius, + ), + // color: !enabled ? disabledColor : backgroundColor, + color: enabled ? backgroundColor : disabledColor, ), child: Padding( - padding: buttonPadding!, + padding: widget.controlSize.padding, child: Align( alignment: widget.alignment, widthFactor: 1.0, heightFactor: 1.0, child: DefaultTextStyle( - style: textStyle, + style: widget.controlSize.textStyle(baseStyle), child: widget.child, ), ), diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index 78508f00..6de99369 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -225,8 +225,8 @@ class MacosThemeData with Diagnosticable { pushButtonTheme ??= PushButtonThemeData( color: primaryColor, secondaryColor: isDark - ? const Color.fromRGBO(56, 56, 56, 1.0) - : const Color.fromRGBO(218, 218, 223, 1.0), + ? const Color.fromRGBO(110, 109, 112, 1.0) + : MacosColors.white, disabledColor: isDark ? const Color.fromRGBO(255, 255, 255, 0.1) : const Color.fromRGBO(244, 245, 245, 1.0), diff --git a/pubspec.lock b/pubspec.lock index 4408d237..f80cd1d5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,22 +17,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.11.1" - analyzer_plugin: - dependency: transitive - description: - name: analyzer_plugin - sha256: c1d5f167683de03d5ab6c3b53fc9aeefc5d59476e7810ba7bbddff50c6f4392d - url: "https://pub.dev" - source: hosted - version: "0.11.2" - ansicolor: - dependency: transitive - description: - name: ansicolor - sha256: "607f8fa9786f392043f169898923e6c59b4518242b68b8862eb8a8b7d9c30b4a" - url: "https://pub.dev" - source: hosted - version: "2.0.1" args: dependency: transitive description: @@ -105,38 +89,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" - csslib: - dependency: transitive - description: - name: csslib - sha256: b36c7f7e24c0bdf1bf9a3da461c837d1de64b9f8beb190c9011d8c72a3dfd745 - url: "https://pub.dev" - source: hosted - version: "0.17.2" - dart_code_metrics: - dependency: "direct dev" - description: - name: dart_code_metrics - sha256: "1dc1fa763b73ed52147bd91b015d81903edc3f227b77b1672fcddba43390ed18" - url: "https://pub.dev" - source: hosted - version: "5.7.5" - dart_code_metrics_presets: - dependency: transitive - description: - name: dart_code_metrics_presets - sha256: "22e27f98e8c7d8b11cca43d2656a822935280747050ae65e8cd03c52d09c0d1c" - url: "https://pub.dev" - source: hosted - version: "1.7.0" - dart_style: - dependency: transitive - description: - name: dart_style - sha256: f4f1f73ab3fd2afcbcca165ee601fe980d966af6a21b5970c6c9376955c528ad - url: "https://pub.dev" - source: hosted - version: "2.3.1" fake_async: dependency: transitive description: @@ -187,22 +139,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" - html: - dependency: transitive - description: - name: html - sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8" - url: "https://pub.dev" - source: hosted - version: "0.15.3" - http: - dependency: transitive - description: - name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" - url: "https://pub.dev" - source: hosted - version: "0.13.6" http_multi_server: dependency: transitive description: @@ -235,14 +171,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 - url: "https://pub.dev" - source: hosted - version: "4.8.1" lints: dependency: transitive description: @@ -331,22 +259,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 - url: "https://pub.dev" - source: hosted - version: "5.4.0" - platform: - dependency: transitive - description: - name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" - url: "https://pub.dev" - source: hosted - version: "3.1.0" pool: dependency: transitive description: @@ -355,14 +267,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - process: - dependency: transitive - description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" - url: "https://pub.dev" - source: hosted - version: "4.2.4" pub_semver: dependency: transitive description: @@ -371,14 +275,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - pub_updater: - dependency: transitive - description: - name: pub_updater - sha256: "05ae70703e06f7fdeb05f7f02dd680b8aad810e87c756a618f33e1794635115c" - url: "https://pub.dev" - source: hosted - version: "0.3.0" shelf: dependency: transitive description: @@ -504,14 +400,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" - uuid: - dependency: transitive - description: - name: uuid - sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" - url: "https://pub.dev" - source: hosted - version: "3.0.7" vector_math: dependency: transitive description: @@ -552,14 +440,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" - xml: - dependency: transitive - description: - name: xml - sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" - url: "https://pub.dev" - source: hosted - version: "6.3.0" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 740361b5..bed7d3d6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 2.0.0-beta.4 +version: 2.0.0-beta.5 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" @@ -16,7 +16,6 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - dart_code_metrics: ^5.7.5 flutter_lints: ^2.0.2 mocktail: ^0.3.0 diff --git a/test/buttons/push_button_test.dart b/test/buttons/push_button_test.dart index 3843c6a2..8bb294a0 100644 --- a/test/buttons/push_button_test.dart +++ b/test/buttons/push_button_test.dart @@ -27,7 +27,7 @@ void main() { ContentArea( builder: (context, _) { return PushButton( - buttonSize: ButtonSize.small, + controlSize: ControlSize.regular, onPressed: mockOnPressedFunction.handler, child: const Text('Push me'), ); @@ -61,7 +61,7 @@ void main() { ContentArea( builder: (context, _) { return PushButton( - buttonSize: ButtonSize.small, + controlSize: ControlSize.regular, key: pushButtonKey, onPressed: mockOnTapCancelFunction.handler, child: const Text('Push me'), @@ -84,7 +84,7 @@ void main() { testWidgets('debugFillProperties', (tester) async { final builder = DiagnosticPropertiesBuilder(); const PushButton( - buttonSize: ButtonSize.small, + controlSize: ControlSize.regular, child: Text('Test'), ).debugFillProperties(builder); @@ -96,7 +96,7 @@ void main() { expect( description, [ - 'buttonSize: small', + 'controlSize: regular', 'color: null', 'disabledColor: null', 'pressedOpacity: 0.4', @@ -104,7 +104,7 @@ void main() { 'semanticLabel: null', 'borderRadius: BorderRadius.circular(4.0)', 'disabled', - 'isSecondary: null', + 'secondary: null', ], ); }); diff --git a/test/theme/push_button_theme_test.dart b/test/theme/push_button_theme_test.dart index a26619f8..2ff90243 100644 --- a/test/theme/push_button_theme_test.dart +++ b/test/theme/push_button_theme_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/library.dart'; void main() { group('PushButton theme tests', () { @@ -65,7 +65,7 @@ void main() { builder: (context, _) { capturedContext = context; return const PushButton( - buttonSize: ButtonSize.small, + controlSize: ControlSize.regular, child: Text('Push me'), ); }, @@ -78,8 +78,8 @@ void main() { final theme = PushButtonTheme.of(capturedContext); expect(theme.color, const Color(0xff007aff)); - expect(theme.disabledColor, const Color(0xfff4f5f5)); - expect(theme.secondaryColor, const Color(0xffdadadf)); + expect(theme.disabledColor, const Color.fromRGBO(244, 245, 245, 1.0)); + expect(theme.secondaryColor, MacosColors.white); }); }); } From d85ab84446e579c7122d570c5b6de69e3f228084 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 5 Jul 2023 17:41:06 -0400 Subject: [PATCH 28/45] fix: checkbox appearance (#448) * fix: checkbox appearance * fix: checkbox tests --- CHANGELOG.md | 4 ++ example/lib/pages/buttons_page.dart | 7 ++++ example/pubspec.lock | 2 +- lib/src/buttons/checkbox.dart | 61 +++++++++++++++++++++++------ pubspec.yaml | 2 +- test/buttons/checkbox_test.dart | 2 +- 6 files changed, 63 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 919e1927..6ab311b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.0.0-beta.6] +🔄 Updated 🔄 +* `MacosCheckbox` appearance more closely matches its native counterpart. + ## [2.0.0-beta.5] 🚨 Breaking Changes 🚨 * `PushButton` has been updated to support the `ControlSize` enum. diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index 3bf5fe5d..7c480ce9 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -734,6 +734,13 @@ class _ButtonsPageState extends State { }).toList(), ), const SizedBox(height: 20), + MacosCheckbox( + value: switchValue, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/example/pubspec.lock b/example/pubspec.lock index 44b2f9a3..2b2ea121 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,7 +97,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.5" + version: "2.0.0-beta.6" macos_window_utils: dependency: transitive description: diff --git a/lib/src/buttons/checkbox.dart b/lib/src/buttons/checkbox.dart index 1ad46d62..d7f8bcc3 100644 --- a/lib/src/buttons/checkbox.dart +++ b/lib/src/buttons/checkbox.dart @@ -15,7 +15,7 @@ class MacosCheckbox extends StatelessWidget { super.key, required this.value, required this.onChanged, - this.size = 16.0, + this.size = 14.0, this.activeColor, this.disabledColor = CupertinoColors.quaternaryLabel, this.offBorderColor = CupertinoColors.tertiaryLabel, @@ -108,18 +108,55 @@ class MacosCheckbox extends StatelessWidget { ), borderRadius: const BorderRadius.all(Radius.circular(4.0)), ) - : BoxDecoration( - color: isLight ? null : CupertinoColors.tertiaryLabel, - border: Border.all( - style: isLight ? BorderStyle.solid : BorderStyle.none, - width: 0.5, - color: MacosDynamicColor.resolve( - offBorderColor, - context, + : isLight + ? ShapeDecoration( + gradient: LinearGradient( + begin: const Alignment(0.0, -1.0), + end: const Alignment(0, 0), + colors: [ + Colors.white.withOpacity(0.85), + Colors.white.withOpacity(1.0), + ], + ), + shadows: const [ + BoxShadow( + color: Color(0x3F000000), + blurRadius: 1, + blurStyle: BlurStyle.inner, + offset: Offset(0, 0), + spreadRadius: 0.0, + ), + ], + shape: RoundedRectangleBorder( + side: BorderSide( + width: 0.25, + color: Colors.black.withOpacity(0.35000000596046448), + ), + borderRadius: + const BorderRadius.all(Radius.circular(3.5)), + ), + ) + : ShapeDecoration( + gradient: LinearGradient( + begin: const Alignment(0.0, -1.0), + end: const Alignment(0, 1), + colors: [ + Colors.white.withOpacity(0.14000000059604645), + Colors.white.withOpacity(0.2800000011920929), + ], + ), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(3)), + ), + shadows: const [ + BoxShadow( + color: Color(0x3F000000), + blurRadius: 1, + offset: Offset(0, 0), + spreadRadius: 0, + ), + ], ), - ), - borderRadius: const BorderRadius.all(Radius.circular(4.0)), - ), child: Icon( isDisabled || value == false ? null diff --git a/pubspec.yaml b/pubspec.yaml index bed7d3d6..33d64578 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 2.0.0-beta.5 +version: 2.0.0-beta.6 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" diff --git a/test/buttons/checkbox_test.dart b/test/buttons/checkbox_test.dart index 09881007..7ba24553 100644 --- a/test/buttons/checkbox_test.dart +++ b/test/buttons/checkbox_test.dart @@ -62,7 +62,7 @@ void main() { [ 'state: "unchecked"', 'enabled', - 'size: 16.0', + 'size: 14.0', 'activeColor: null', 'disabledColor: quaternaryLabel(*color = Color(0x2d3c3c43)*, darkColor = Color(0x28ebebf5), highContrastColor = Color(0x423c3c43), darkHighContrastColor = Color(0x3debebf5), resolved by: UNRESOLVED)', 'offBorderColor: tertiaryLabel(*color = Color(0x4c3c3c43)*, darkColor = Color(0x4cebebf5), highContrastColor = Color(0x603c3c43), darkHighContrastColor = Color(0x60ebebf5), resolved by: UNRESOLVED)', From 331895a94fb04ebf4258a2fe0752d9c01ead1af8 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Sun, 9 Jul 2023 20:54:18 -0400 Subject: [PATCH 29/45] Reorganize gallery, add `MacosTypography.of(context)`, and update `MacosAlertDialog` (#451) * move colors page out of disclosure & remove disclosure * feat: reorganize gallery * further refine buttons page * fix app icon and button sizes in `MacosAlertDialog` * update version, readme, & changelog --- CHANGELOG.md | 9 + README.md | 8 +- example/lib/main.dart | 96 +- example/lib/pages/buttons_page.dart | 1501 ++++++++--------- example/lib/pages/colors_page.dart | 23 +- example/lib/pages/dialogs_page.dart | 93 +- example/lib/pages/fields_page.dart | 23 +- example/lib/pages/indicators_page.dart | 23 +- example/lib/pages/resizable_pane_page.dart | 90 + example/lib/pages/selectors_page.dart | 23 +- example/lib/pages/tabview_page.dart | 25 +- .../Flutter/GeneratedPluginRegistrant.swift | 2 + example/macos/Podfile.lock | 7 + .../macos/Runner/DebugProfile.entitlements | 2 + example/pubspec.lock | 150 +- example/pubspec.yaml | 7 +- lib/src/dialogs/macos_alert_dialog.dart | 49 +- lib/src/theme/typography.dart | 6 + pubspec.yaml | 2 +- 19 files changed, 1213 insertions(+), 926 deletions(-) create mode 100644 example/lib/pages/resizable_pane_page.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ab311b3..345ceb9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [2.0.0-beta.7] +✨ New ✨ +* You can now call `MacosTypography.of(context)` as a shorthand for retrieving the typography used in your `MacosTheme`. + +🔄 Updated 🔄 +* `MacosAlertDialog` now defines `primaryButton` and `secondaryButton` to be of type `PushButton`. +* `MacosAlertDialog` now requires that `primaryButton` and `secondaryButton` to have `controlSize`s of `ControlSize.large`. +* `MacosAlertDialog` docs now suggest that `appIcon` should be of size 64x64. + ## [2.0.0-beta.6] 🔄 Updated 🔄 * `MacosCheckbox` appearance more closely matches its native counterpart. diff --git a/README.md b/README.md index cf586f34..b9a911d1 100644 --- a/README.md +++ b/README.md @@ -703,9 +703,7 @@ Usage: showMacosAlertDialog( context: context, builder: (_) => MacosAlertDialog( - appIcon: FlutterLogo( - size: 56, - ), + appIcon: FlutterLogo(size: 64), title: Text( 'Alert Dialog with Primary Action', style: MacosTheme.of(context).typography.headline, @@ -713,10 +711,10 @@ showMacosAlertDialog( message: Text( 'This is an alert dialog with a primary action and no secondary action', textAlign: TextAlign.center, - style: MacosTheme.of(context).typography.headline, + style: MacosTypography.of(context).headline, ), primaryButton: PushButton( - buttonSize: ButtonSize.large, + controlSize: ControlSize.large, child: Text('Primary'), onPressed: () {}, ), diff --git a/example/lib/main.dart b/example/lib/main.dart index cba22164..3b7d2ed2 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -3,6 +3,7 @@ import 'package:example/pages/colors_page.dart'; import 'package:example/pages/dialogs_page.dart'; import 'package:example/pages/fields_page.dart'; import 'package:example/pages/indicators_page.dart'; +import 'package:example/pages/resizable_pane_page.dart'; import 'package:example/pages/selectors_page.dart'; import 'package:example/pages/sliver_toolbar_page.dart'; import 'package:example/pages/tabview_page.dart'; @@ -55,33 +56,21 @@ class WidgetGallery extends StatefulWidget { } class _WidgetGalleryState extends State { - double ratingValue = 0; - double sliderValue = 0; - bool value = false; - int pageIndex = 0; late final searchFieldController = TextEditingController(); final List pageBuilders = [ - (bool isVisible) => CupertinoTabView( - builder: (_) => const ButtonsPage(), - ), - (bool isVisible) => const IndicatorsPage(), - (bool isVisible) => const FieldsPage(), - (bool isVisible) => const ColorsPage(), - (bool isVisible) => const Center( - child: MacosIcon( - CupertinoIcons.add, - ), - ), - (bool isVisible) => const DialogsPage(), - (bool isVisible) => const ToolbarPage(), - (bool isVisible) => SliverToolbarPage( - isVisible: isVisible, - ), - (bool isVisible) => const TabViewPage(), - (bool isVisible) => const SelectorsPage(), + (_) => CupertinoTabView(builder: (_) => const ButtonsPage()), + (_) => const IndicatorsPage(), + (_) => const FieldsPage(), + (_) => const ColorsPage(), + (_) => const DialogsPage(), + (_) => const ToolbarPage(), + (isVisible) => SliverToolbarPage(isVisible: isVisible), + (_) => const TabViewPage(), + (_) => const ResizablePanePage(), + (_) => const SelectorsPage(), ]; @override @@ -152,7 +141,7 @@ class _WidgetGalleryState extends State { break; case 'Dialogs and Sheets': setState(() { - pageIndex = 5; + pageIndex = 4; searchFieldController.clear(); }); break; @@ -162,12 +151,18 @@ class _WidgetGalleryState extends State { searchFieldController.clear(); }); break; - case 'Selectors': + case 'ResizablePane': setState(() { pageIndex = 7; searchFieldController.clear(); }); break; + case 'Selectors': + setState(() { + pageIndex = 8; + searchFieldController.clear(); + }); + break; default: searchFieldController.clear(); } @@ -179,6 +174,7 @@ class _WidgetGalleryState extends State { SearchResultItem('Colors'), SearchResultItem('Dialogs and Sheets'), SearchResultItem('Toolbar'), + SearchResultItem('ResizablePane'), SearchResultItem('Selectors'), ], ), @@ -189,17 +185,14 @@ class _WidgetGalleryState extends State { onChanged: (i) => setState(() => pageIndex = i), scrollController: scrollController, itemSize: SidebarItemSize.large, - items: [ - const SidebarItem( - // leading: MacosIcon(CupertinoIcons.square_on_circle), + items: const [ + SidebarItem( leading: MacosImageIcon( - AssetImage( - 'assets/sf_symbols/button_programmable_2x.png', - ), + AssetImage('assets/sf_symbols/button_programmable_2x.png'), ), label: Text('Buttons'), ), - const SidebarItem( + SidebarItem( leading: MacosImageIcon( AssetImage( 'assets/sf_symbols/lines_measurement_horizontal_2x.png', @@ -207,7 +200,7 @@ class _WidgetGalleryState extends State { ), label: Text('Indicators'), ), - const SidebarItem( + SidebarItem( leading: MacosImageIcon( AssetImage( 'assets/sf_symbols/character_cursor_ibeam_2x.png', @@ -216,36 +209,16 @@ class _WidgetGalleryState extends State { label: Text('Fields'), ), SidebarItem( - leading: const MacosIcon(CupertinoIcons.folder), - label: const Text('Disclosure'), - trailing: Text( - '2', - style: TextStyle( - color: MacosTheme.brightnessOf(context) == Brightness.dark - ? MacosColors.tertiaryLabelColor.darkColor - : MacosColors.tertiaryLabelColor, - ), + leading: MacosImageIcon( + AssetImage('assets/sf_symbols/rectangle_3_group_2x.png'), ), - disclosureItems: [ - const SidebarItem( - leading: MacosImageIcon( - AssetImage( - 'assets/sf_symbols/rectangle_3_group_2x.png', - ), - ), - label: Text('Colors'), - ), - const SidebarItem( - leading: MacosIcon(CupertinoIcons.infinite), - label: Text('Item 3'), - ), - ], + label: Text('Colors'), ), - const SidebarItem( + SidebarItem( leading: MacosIcon(CupertinoIcons.square_on_square), label: Text('Dialogs & Sheets'), ), - const SidebarItem( + SidebarItem( leading: MacosImageIcon( AssetImage( 'assets/sf_symbols/macwindow.on.rectangle_2x.png', @@ -269,13 +242,16 @@ class _WidgetGalleryState extends State { leading: MacosIcon(CupertinoIcons.uiwindow_split_2x1), label: Text('TabView'), ), + SidebarItem( + leading: MacosIcon(CupertinoIcons.rectangle_split_3x1), + label: Text('ResizablePane'), + ), ], ), - const SidebarItem( + SidebarItem( leading: MacosImageIcon( AssetImage( - 'assets/sf_symbols/filemenu_and_selection_2x.png', - ), + 'assets/sf_symbols/filemenu_and_selection_2x.png'), ), label: Text('Selectors'), ), diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index 7c480ce9..d26501c3 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:provider/provider.dart'; @@ -59,779 +60,715 @@ class _ButtonsPageState extends State { ], ), children: [ - ResizablePane( - minSize: 180, - startSize: 200, - windowBreakpoint: 700, - resizableSide: ResizableSide.right, - builder: (_, __) { - return const Center( - child: Text('Resizable Pane'), - ); - }, - ), ContentArea( builder: (context, scrollController) { - return Column( - children: [ - Flexible( - fit: FlexFit.loose, - child: SingleChildScrollView( - controller: scrollController, - padding: const EdgeInsets.all(20), - child: Column( - children: [ - const Text('MacosBackButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosBackButton( - onPressed: () => debugPrint('click'), - fillColor: Colors.transparent, - ), - const SizedBox(width: 16.0), - MacosBackButton( - onPressed: () => debugPrint('click'), - ), - ], - ), - const SizedBox(height: 20), - const Text('MacosDisclosureButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosDisclosureButton( - isPressed: isDisclosureButtonPressed, - onPressed: () { - debugPrint('click'); - setState(() { - isDisclosureButtonPressed = - !isDisclosureButtonPressed; - }); - }), - ], - ), - const SizedBox(height: 20), - const Text('MacosIconButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosIconButton( - icon: const MacosIcon( - CupertinoIcons.star_fill, - ), - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(7), - onPressed: () {}, - ), - const SizedBox(width: 8), - const MacosIconButton( - icon: MacosIcon( - CupertinoIcons.plus_app, - ), - shape: BoxShape.circle, - //onPressed: () {}, - ), - const SizedBox(width: 8), - MacosIconButton( - icon: const MacosIcon( - CupertinoIcons.minus_square, - ), - backgroundColor: Colors.transparent, - onPressed: () {}, - ), - ], - ), - const SizedBox(height: 20), - const Text('Primary PushButton'), - const SizedBox(height: 8), - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - PushButton( - controlSize: ControlSize.mini, - child: const Text('Mini'), - onPressed: () { - MacosWindowScope.of(context) - .toggleSidebar(); - }, - ), - const SizedBox(width: 8), - PushButton( - controlSize: ControlSize.small, - child: const Text('Small'), - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) { - return MacosScaffold( - toolBar: const ToolBar( - title: Text('New page'), - ), - children: [ - ContentArea( - builder: (context, _) { - return Center( - child: PushButton( - controlSize: - ControlSize.regular, - child: - const Text('Go Back'), - onPressed: () { - Navigator.of(context) - .maybePop(); - }, - ), - ); - }, - ), - ResizablePane( - minSize: 180, - startSize: 200, - windowBreakpoint: 700, - resizableSide: - ResizableSide.left, - builder: (_, __) { - return const Center( - child: - Text('Resizable Pane'), - ); - }, - ), - ], - ); - }, - ), - ); - }, - ), - const SizedBox(width: 8), - PushButton( - controlSize: ControlSize.regular, - child: const Text('Regular'), - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) { - return MacosScaffold( - toolBar: const ToolBar( - title: Text('New page'), - ), - children: [ - ContentArea( - builder: (context, _) { - return Center( - child: PushButton( - controlSize: - ControlSize.regular, - child: - const Text('Go Back'), - onPressed: () { - Navigator.of(context) - .maybePop(); - }, - ), - ); - }, - ), - ResizablePane( - minSize: 180, - startSize: 200, - windowBreakpoint: 700, - resizableSide: - ResizableSide.left, - builder: (_, __) { - return const Center( - child: - Text('Resizable Pane'), - ); - }, - ), - ], - ); - }, - ), - ); - }, - ), - const SizedBox(width: 8), - PushButton( - controlSize: ControlSize.large, - child: const Text('Large'), - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) { - return MacosScaffold( - toolBar: const ToolBar( - title: Text('New page'), - ), - children: [ - ContentArea( - builder: (context, _) { - return Center( - child: PushButton( - controlSize: - ControlSize.regular, - child: - const Text('Go Back'), - onPressed: () { - Navigator.of(context) - .maybePop(); - }, - ), - ); - }, - ), - ResizablePane( - minSize: 180, - startSize: 200, - windowBreakpoint: 700, - resizableSide: - ResizableSide.left, - builder: (_, __) { - return const Center( - child: - Text('Resizable Pane'), - ); - }, - ), - ], - ); - }, - ), - ); - }, - ), - ], - ), - ], - ), - const SizedBox(height: 8), - const Text('Secondary PushButton'), - const SizedBox(height: 8), - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - PushButton( - controlSize: ControlSize.mini, - secondary: true, - child: const Text('Mini'), - onPressed: () { - MacosWindowScope.of(context) - .toggleSidebar(); - }, - ), - const SizedBox(width: 8), - PushButton( - controlSize: ControlSize.small, - secondary: true, - child: const Text('Small'), - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) { - return MacosScaffold( - toolBar: const ToolBar( - title: Text('New page'), - ), - children: [ - ContentArea( - builder: (context, _) { - return Center( - child: PushButton( - controlSize: - ControlSize.regular, - child: - const Text('Go Back'), - onPressed: () { - Navigator.of(context) - .maybePop(); - }, - ), - ); - }, - ), - ResizablePane( - minSize: 180, - startSize: 200, - windowBreakpoint: 700, - resizableSide: - ResizableSide.left, - builder: (_, __) { - return const Center( - child: - Text('Resizable Pane'), - ); - }, - ), - ], - ); - }, - ), - ); - }, - ), - const SizedBox(width: 8), - PushButton( - controlSize: ControlSize.regular, - secondary: true, - child: const Text('Regular'), - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) { - return MacosScaffold( - toolBar: const ToolBar( - title: Text('New page'), - ), - children: [ - ContentArea( - builder: (context, _) { - return Center( - child: PushButton( - controlSize: - ControlSize.regular, - child: - const Text('Go Back'), - onPressed: () { - Navigator.of(context) - .maybePop(); - }, - ), - ); - }, - ), - ResizablePane( - minSize: 180, - startSize: 200, - windowBreakpoint: 700, - resizableSide: - ResizableSide.left, - builder: (_, __) { - return const Center( - child: - Text('Resizable Pane'), - ); - }, - ), - ], - ); - }, - ), - ); - }, - ), - const SizedBox(width: 8), - PushButton( - controlSize: ControlSize.large, - secondary: true, - child: const Text('Large'), - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) { - return MacosScaffold( - toolBar: const ToolBar( - title: Text('New page'), - ), - children: [ - ContentArea( - builder: (context, _) { - return Center( - child: PushButton( - controlSize: - ControlSize.regular, - child: - const Text('Go Back'), - onPressed: () { - Navigator.of(context) - .maybePop(); - }, - ), - ); - }, - ), - ResizablePane( - minSize: 180, - startSize: 200, - windowBreakpoint: 700, - resizableSide: - ResizableSide.left, - builder: (_, __) { - return const Center( - child: - Text('Resizable Pane'), - ); - }, - ), - ], - ); - }, - ), - ); - }, - ), - ], - ), - ], - ), - const SizedBox(height: 8), - const Text('Disabled Primary PushButton'), - const SizedBox(height: 8), - const Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - PushButton( - controlSize: ControlSize.mini, - child: Text('Mini'), - ), - SizedBox(width: 8), - PushButton( - controlSize: ControlSize.small, - child: Text('Small'), - ), - SizedBox(width: 8), - PushButton( - controlSize: ControlSize.regular, - child: Text('Regular'), - ), - SizedBox(width: 8), - PushButton( - controlSize: ControlSize.large, - child: Text('Large'), - ), - ], - ), - ], - ), - const SizedBox(height: 8), - const Text('Disabled Secondary PushButton'), - const SizedBox(height: 8), - const Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - PushButton( - controlSize: ControlSize.mini, - secondary: true, - child: Text('Mini'), - ), - SizedBox(width: 8), - PushButton( - controlSize: ControlSize.small, - secondary: true, - child: Text('Small'), - ), - SizedBox(width: 8), - PushButton( - controlSize: ControlSize.regular, - secondary: true, - child: Text('Regular'), - ), - SizedBox(width: 8), - PushButton( - controlSize: ControlSize.large, - secondary: true, - child: Text('Large'), - ), - ], - ), - ], - ), - const SizedBox(height: 20), - const Text('MacosSwitch'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosSwitch( - value: switchValue, - size: ControlSize.mini, - onChanged: (value) { - setState(() => switchValue = value); + return SingleChildScrollView( + controller: scrollController, + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const WidgetTextTitle1(widgetName: 'PushButton'), + Divider(color: MacosTheme.of(context).dividerColor), + Text( + 'Primary', + style: MacosTypography.of(context).title2, + ), + Row( + children: [ + PushButton( + controlSize: ControlSize.mini, + child: const Text('Mini'), + onPressed: () { + MacosWindowScope.of(context).toggleSidebar(); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.small, + child: const Text('Small'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: ControlSize.regular, + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context).maybePop(); + }, + ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: ResizableSide.left, + builder: (_, __) { + return const Center( + child: Text('Resizable Pane'), + ); + }, + ), + ], + ); }, ), - const SizedBox(width: 16.0), - MacosSwitch( - value: switchValue, - size: ControlSize.small, - onChanged: (value) { - setState(() => switchValue = value); + ); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.regular, + child: const Text('Regular'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: ControlSize.regular, + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context).maybePop(); + }, + ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: ResizableSide.left, + builder: (_, __) { + return const Center( + child: Text('Resizable Pane'), + ); + }, + ), + ], + ); }, ), - const SizedBox(width: 16.0), - MacosSwitch( - value: switchValue, - onChanged: (value) { - setState(() => switchValue = value); + ); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.large, + child: const Text('Large'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: ControlSize.regular, + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context).maybePop(); + }, + ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: ResizableSide.left, + builder: (_, __) { + return const Center( + child: Text('Resizable Pane'), + ); + }, + ), + ], + ); }, ), - ], - ), - const SizedBox(height: 20), - const Text('MacosPulldownButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosPulldownButton( - title: 'PDF', - items: [ - MacosPulldownMenuItem( - title: const Text('Open in Preview'), - onTap: () => - debugPrint('Opening in preview...'), - ), - MacosPulldownMenuItem( - title: const Text('Save as PDF...'), - onTap: () => debugPrint('Saving as PDF...'), - ), - MacosPulldownMenuItem( - enabled: false, - title: const Text('Save as Postscript'), - onTap: () => - debugPrint('Saving as Postscript...'), - ), - const MacosPulldownMenuDivider(), - MacosPulldownMenuItem( - enabled: false, - title: const Text('Save to iCloud Drive'), - onTap: () => - debugPrint('Saving to iCloud...'), - ), - MacosPulldownMenuItem( - enabled: false, - title: const Text('Save to Web Receipts'), - onTap: () => - debugPrint('Saving to Web Receipts...'), - ), - MacosPulldownMenuItem( - title: const Text('Send in Mail...'), - onTap: () => - debugPrint('Sending via Mail...'), - ), - const MacosPulldownMenuDivider(), - MacosPulldownMenuItem( - title: const Text('Edit Menu...'), - onTap: () => debugPrint('Editing menu...'), - ), - ], - ), - const SizedBox(width: 20), - const MacosPulldownButton( - title: 'PDF', - disabledTitle: 'Disabled', - items: [], - ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosPulldownButton( - icon: CupertinoIcons.ellipsis_circle, - items: [ - MacosPulldownMenuItem( - title: const Text('New Folder'), - onTap: () => - debugPrint('Creating new folder...'), - ), - MacosPulldownMenuItem( - title: const Text('Open'), - onTap: () => debugPrint('Opening...'), - ), - MacosPulldownMenuItem( - title: const Text('Open with...'), - onTap: () => debugPrint('Opening with...'), - ), - MacosPulldownMenuItem( - title: const Text('Import from iPhone...'), - onTap: () => debugPrint('Importing...'), - ), - const MacosPulldownMenuDivider(), - MacosPulldownMenuItem( - enabled: false, - title: const Text('Remove'), - onTap: () => debugPrint('Deleting...'), - ), - MacosPulldownMenuItem( - title: const Text('Move to Bin'), - onTap: () => debugPrint('Moving to Bin...'), - ), - const MacosPulldownMenuDivider(), - MacosPulldownMenuItem( - title: const Text('Tags...'), - onTap: () => debugPrint('Tags...'), - ), - ], - ), - const SizedBox(width: 20), - const MacosPulldownButton( - icon: CupertinoIcons.square_grid_3x2, - items: [], - ), - ], - ), - const SizedBox(height: 20), - const Text('MacosPopupButton'), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MacosPopupButton( - value: popupValue, - onChanged: (String? newValue) { - setState(() => popupValue = newValue!); - }, - items: [ - 'One', - 'Two', - 'Three', - 'Four' - ].map>((String value) { - return MacosPopupMenuItem( - value: value, - child: Text(value), + ); + }, + ), + ], + ), + const SizedBox(height: 16), + Text( + 'Disabled Primary', + style: MacosTypography.of(context).title2, + ), + const Row( + children: [ + PushButton( + controlSize: ControlSize.mini, + child: Text('Mini'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.small, + child: Text('Small'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.regular, + child: Text('Regular'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.large, + child: Text('Large'), + ), + ], + ), + const SizedBox(height: 16), + Text( + 'Secondary', + style: MacosTypography.of(context).title2, + ), + Row( + children: [ + PushButton( + controlSize: ControlSize.mini, + secondary: true, + child: const Text('Mini'), + onPressed: () { + MacosWindowScope.of(context).toggleSidebar(); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.small, + secondary: true, + child: const Text('Small'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: ControlSize.regular, + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context).maybePop(); + }, + ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: ResizableSide.left, + builder: (_, __) { + return const Center( + child: Text('Resizable Pane'), + ); + }, + ), + ], ); - }).toList(), - ), - const SizedBox(width: 20), - MacosPopupButton( - disabledHint: const Text('Disabled'), - onChanged: null, - items: null, - ), - ], - ), - const SizedBox(height: 20), - MacosPopupButton( - value: languagePopupValue, - onChanged: (String? newValue) { - setState(() => languagePopupValue = newValue!); - }, - items: languages - .map>((String value) { - return MacosPopupMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - ), - const SizedBox(height: 20), - MacosCheckbox( - value: switchValue, - onChanged: (value) { - setState(() => switchValue = value); - }, - ), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('System Theme'), - const SizedBox(width: 8), - MacosRadioButton( - groupValue: context.watch().mode, - value: ThemeMode.system, - onChanged: (value) { - context.read().mode = value!; }, ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Light Theme'), - const SizedBox(width: 24), - MacosRadioButton( - groupValue: context.watch().mode, - value: ThemeMode.light, - onChanged: (value) { - context.read().mode = value!; + ); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.regular, + secondary: true, + child: const Text('Regular'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: ControlSize.regular, + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context).maybePop(); + }, + ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: ResizableSide.left, + builder: (_, __) { + return const Center( + child: Text('Resizable Pane'), + ); + }, + ), + ], + ); }, ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Dark Theme'), - const SizedBox(width: 26), - MacosRadioButton( - groupValue: context.watch().mode, - value: ThemeMode.dark, - onChanged: (value) { - context.read().mode = value!; + ); + }, + ), + const SizedBox(width: 8), + PushButton( + controlSize: ControlSize.large, + secondary: true, + child: const Text('Large'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) { + return MacosScaffold( + toolBar: const ToolBar( + title: Text('New page'), + ), + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: PushButton( + controlSize: ControlSize.regular, + child: const Text('Go Back'), + onPressed: () { + Navigator.of(context).maybePop(); + }, + ), + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: ResizableSide.left, + builder: (_, __) { + return const Center( + child: Text('Resizable Pane'), + ); + }, + ), + ], + ); }, ), - ], + ); + }, + ), + ], + ), + const SizedBox(height: 16), + Text( + 'Disabled Secondary', + style: MacosTypography.of(context).title2, + ), + const Row( + children: [ + PushButton( + controlSize: ControlSize.mini, + secondary: true, + child: Text('Mini'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.small, + secondary: true, + child: Text('Small'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.regular, + secondary: true, + child: Text('Regular'), + ), + SizedBox(width: 8), + PushButton( + controlSize: ControlSize.large, + secondary: true, + child: Text('Large'), + ), + ], + ), + const SizedBox(height: 16), + Text( + 'Icon Buttons', + style: MacosTypography.of(context).title1, + ), + Divider(color: MacosTheme.of(context).dividerColor), + const WidgetTextTitle2(widgetName: 'MacosBackButton'), + const SizedBox(height: 8), + Row( + children: [ + MacosBackButton( + onPressed: () => debugPrint('click'), + fillColor: Colors.transparent, + ), + const SizedBox(width: 16.0), + MacosBackButton( + onPressed: () => debugPrint('click'), + ), + ], + ), + const SizedBox(height: 20), + const WidgetTextTitle2(widgetName: 'MacosDisclosureButton'), + const SizedBox(height: 8), + Row( + children: [ + MacosDisclosureButton( + isPressed: isDisclosureButtonPressed, + onPressed: () { + debugPrint('click'); + setState(() { + isDisclosureButtonPressed = + !isDisclosureButtonPressed; + }); + }, + ), + ], + ), + const SizedBox(height: 20), + const WidgetTextTitle2(widgetName: 'MacosIconButton'), + const SizedBox(height: 8), + Row( + children: [ + MacosIconButton( + icon: const MacosIcon( + CupertinoIcons.star_fill, ), - const SizedBox(height: 20), - const Text('MacosSegmentedControl'), - const SizedBox(height: 8), - MacosSegmentedControl( - controller: _tabController, - tabs: [ - MacosTab( - label: 'Tab 1', - active: _tabController.index == 0, - ), - MacosTab( - label: 'Tab 2', - active: _tabController.index == 1, - ), - MacosTab( - label: 'Tab 3', - active: _tabController.index == 2, - ), - ], + shape: BoxShape.rectangle, + borderRadius: BorderRadius.circular(7), + onPressed: () {}, + ), + const SizedBox(width: 8), + const MacosIconButton( + icon: MacosIcon( + CupertinoIcons.plus_app, ), - ], - ), + shape: BoxShape.circle, + //onPressed: () {}, + ), + const SizedBox(width: 8), + MacosIconButton( + icon: const MacosIcon( + CupertinoIcons.minus_square, + ), + backgroundColor: Colors.transparent, + onPressed: () {}, + ), + ], ), - ), - ResizablePane( - minSize: 50, - startSize: 200, - //windowBreakpoint: 600, - builder: (_, __) { - return const Center( - child: Text('Resizable Pane'), - ); - }, - resizableSide: ResizableSide.top, - ) - ], - ); - }, - ), - ResizablePane( - minSize: 180, - startSize: 200, - windowBreakpoint: 800, - resizableSide: ResizableSide.left, - builder: (_, __) { - return const Center( - child: Text('Resizable Pane'), + const SizedBox(height: 20), + Text( + 'Switches, Checkboxes, & Radios', + style: MacosTypography.of(context).title1, + ), + Divider(color: MacosTheme.of(context).dividerColor), + const WidgetTextTitle2(widgetName: 'MacosSwitch'), + const SizedBox(height: 8), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + const Text('Mini'), + const SizedBox(width: 8), + MacosSwitch( + value: switchValue, + size: ControlSize.mini, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + ], + ), + const SizedBox(height: 8.0), + Row( + children: [ + const Text('Small'), + const SizedBox(width: 8), + MacosSwitch( + value: switchValue, + size: ControlSize.small, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + ], + ), + const SizedBox(height: 8.0), + Row( + children: [ + const Text('Regular'), + const SizedBox(width: 8), + MacosSwitch( + value: switchValue, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + ], + ), + ], + ), + const SizedBox(height: 16), + const WidgetTextTitle2(widgetName: 'MacosCheckbox'), + const SizedBox(height: 8), + MacosCheckbox( + value: switchValue, + onChanged: (value) { + setState(() => switchValue = value); + }, + ), + const SizedBox(height: 16), + const WidgetTextTitle2(widgetName: 'MacosRadioButton'), + const SizedBox(height: 8), + Row( + children: [ + const Text('System Theme'), + const SizedBox(width: 8), + MacosRadioButton( + groupValue: context.watch().mode, + value: ThemeMode.system, + onChanged: (value) { + context.read().mode = value!; + }, + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + const Text('Light Theme'), + const SizedBox(width: 24), + MacosRadioButton( + groupValue: context.watch().mode, + value: ThemeMode.light, + onChanged: (value) { + context.read().mode = value!; + }, + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + const Text('Dark Theme'), + const SizedBox(width: 26), + MacosRadioButton( + groupValue: context.watch().mode, + value: ThemeMode.dark, + onChanged: (value) { + context.read().mode = value!; + }, + ), + ], + ), + const SizedBox(height: 20), + Text( + 'Pulldown & Popup Buttons', + style: MacosTypography.of(context).title1, + ), + Divider(color: MacosTheme.of(context).dividerColor), + const WidgetTextTitle2(widgetName: 'MacosPulldownButton'), + const SizedBox(height: 8), + Row( + children: [ + MacosPulldownButton( + title: 'PDF', + items: [ + MacosPulldownMenuItem( + title: const Text('Open in Preview'), + onTap: () => debugPrint('Opening in preview...'), + ), + MacosPulldownMenuItem( + title: const Text('Save as PDF...'), + onTap: () => debugPrint('Saving as PDF...'), + ), + MacosPulldownMenuItem( + enabled: false, + title: const Text('Save as Postscript'), + onTap: () => debugPrint('Saving as Postscript...'), + ), + const MacosPulldownMenuDivider(), + MacosPulldownMenuItem( + enabled: false, + title: const Text('Save to iCloud Drive'), + onTap: () => debugPrint('Saving to iCloud...'), + ), + MacosPulldownMenuItem( + enabled: false, + title: const Text('Save to Web Receipts'), + onTap: () => + debugPrint('Saving to Web Receipts...'), + ), + MacosPulldownMenuItem( + title: const Text('Send in Mail...'), + onTap: () => debugPrint('Sending via Mail...'), + ), + const MacosPulldownMenuDivider(), + MacosPulldownMenuItem( + title: const Text('Edit Menu...'), + onTap: () => debugPrint('Editing menu...'), + ), + ], + ), + const SizedBox(width: 20), + const MacosPulldownButton( + title: 'PDF', + disabledTitle: 'Disabled', + items: [], + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + MacosPulldownButton( + icon: CupertinoIcons.ellipsis_circle, + items: [ + MacosPulldownMenuItem( + title: const Text('New Folder'), + onTap: () => debugPrint('Creating new folder...'), + ), + MacosPulldownMenuItem( + title: const Text('Open'), + onTap: () => debugPrint('Opening...'), + ), + MacosPulldownMenuItem( + title: const Text('Open with...'), + onTap: () => debugPrint('Opening with...'), + ), + MacosPulldownMenuItem( + title: const Text('Import from iPhone...'), + onTap: () => debugPrint('Importing...'), + ), + const MacosPulldownMenuDivider(), + MacosPulldownMenuItem( + enabled: false, + title: const Text('Remove'), + onTap: () => debugPrint('Deleting...'), + ), + MacosPulldownMenuItem( + title: const Text('Move to Bin'), + onTap: () => debugPrint('Moving to Bin...'), + ), + const MacosPulldownMenuDivider(), + MacosPulldownMenuItem( + title: const Text('Tags...'), + onTap: () => debugPrint('Tags...'), + ), + ], + ), + const SizedBox(width: 20), + const MacosPulldownButton( + icon: CupertinoIcons.square_grid_3x2, + items: [], + ), + ], + ), + const SizedBox(height: 20), + const WidgetTextTitle2(widgetName: 'MacosPopupButton'), + const SizedBox(height: 8), + Row( + children: [ + MacosPopupButton( + value: popupValue, + onChanged: (String? newValue) { + setState(() => popupValue = newValue!); + }, + items: ['One', 'Two', 'Three', 'Four'] + .map>((String value) { + return MacosPopupMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + const SizedBox(width: 20), + MacosPopupButton( + disabledHint: const Text('Disabled'), + onChanged: null, + items: null, + ), + ], + ), + const SizedBox(height: 20), + MacosPopupButton( + value: languagePopupValue, + onChanged: (String? newValue) { + setState(() => languagePopupValue = newValue!); + }, + items: languages + .map>((String value) { + return MacosPopupMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + const SizedBox(height: 20), + const WidgetTextTitle1(widgetName: 'MacosSegmentedControl'), + Divider(color: MacosTheme.of(context).dividerColor), + const SizedBox(height: 8), + MacosSegmentedControl( + controller: _tabController, + tabs: [ + MacosTab( + label: 'Tab 1', + active: _tabController.index == 0, + ), + MacosTab( + label: 'Tab 2', + active: _tabController.index == 1, + ), + MacosTab( + label: 'Tab 3', + active: _tabController.index == 2, + ), + ], + ), + ], + ), ); }, ), @@ -876,3 +813,57 @@ const languages = [ 'Romanian', 'Dutch' ]; + +class WidgetTextTitle1 extends StatelessWidget { + const WidgetTextTitle1({super.key, required this.widgetName}); + + final String widgetName; + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + color: MacosColors.systemGrayColor.withOpacity(0.5), + borderRadius: BorderRadius.circular(4.0), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 6.0, + ), + child: Text( + widgetName, + style: MacosTypography.of(context) + .title1 + .copyWith(fontFamily: GoogleFonts.jetBrainsMono().fontFamily), + ), + ), + ); + } +} + +class WidgetTextTitle2 extends StatelessWidget { + const WidgetTextTitle2({super.key, required this.widgetName}); + + final String widgetName; + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + color: MacosColors.systemGrayColor.withOpacity(0.5), + borderRadius: BorderRadius.circular(4.0), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 6.0, + ), + child: Text( + widgetName, + style: MacosTypography.of(context) + .title2 + .copyWith(fontFamily: GoogleFonts.jetBrainsMono().fontFamily), + ), + ), + ); + } +} diff --git a/example/lib/pages/colors_page.dart b/example/lib/pages/colors_page.dart index 7a9df4fe..6eb3eac8 100644 --- a/example/lib/pages/colors_page.dart +++ b/example/lib/pages/colors_page.dart @@ -15,16 +15,27 @@ class _ColorsPageState extends State { toolBar: ToolBar( title: const Text('Colors'), titleWidth: 150.0, - actions: [ - ToolBarIconButton( - label: 'Toggle Sidebar', - icon: const MacosIcon( + leading: MacosTooltip( + message: 'Toggle Sidebar', + useMousePosition: false, + child: MacosIconButton( + icon: MacosIcon( CupertinoIcons.sidebar_left, + color: MacosTheme.brightnessOf(context).resolve( + const Color.fromRGBO(0, 0, 0, 0.5), + const Color.fromRGBO(255, 255, 255, 0.5), + ), + size: 20.0, + ), + boxConstraints: const BoxConstraints( + minHeight: 20, + minWidth: 20, + maxWidth: 48, + maxHeight: 38, ), onPressed: () => MacosWindowScope.of(context).toggleSidebar(), - showLabel: false, ), - ], + ), ), children: [ ContentArea( diff --git a/example/lib/pages/dialogs_page.dart b/example/lib/pages/dialogs_page.dart index 9046b61c..1fea4908 100644 --- a/example/lib/pages/dialogs_page.dart +++ b/example/lib/pages/dialogs_page.dart @@ -2,6 +2,9 @@ import 'package:macos_ui/macos_ui.dart'; // ignore: implementation_imports import 'package:macos_ui/src/library.dart'; +const dialogMessage = + 'Description text about this alert is shown here, explaining to users what the options underneath are about and what to do.'; + class DialogsPage extends StatefulWidget { const DialogsPage({super.key}); @@ -16,16 +19,27 @@ class _DialogsPageState extends State { toolBar: ToolBar( title: const Text('Dialogs and Sheets'), titleWidth: 150.0, - actions: [ - ToolBarIconButton( - label: 'Toggle Sidebar', - icon: const MacosIcon( + leading: MacosTooltip( + message: 'Toggle Sidebar', + useMousePosition: false, + child: MacosIconButton( + icon: MacosIcon( CupertinoIcons.sidebar_left, + color: MacosTheme.brightnessOf(context).resolve( + const Color.fromRGBO(0, 0, 0, 0.5), + const Color.fromRGBO(255, 255, 255, 0.5), + ), + size: 20.0, + ), + boxConstraints: const BoxConstraints( + minHeight: 20, + minWidth: 20, + maxWidth: 48, + maxHeight: 38, ), onPressed: () => MacosWindowScope.of(context).toggleSidebar(), - showLabel: false, ), - ], + ), ), children: [ ContentArea( @@ -41,20 +55,14 @@ class _DialogsPageState extends State { onPressed: () => showMacosAlertDialog( context: context, builder: (context) => MacosAlertDialog( - appIcon: const FlutterLogo( - size: 56, - ), - title: const Text( - 'Alert Dialog with Primary Action', - ), - message: const Text( - 'This is an alert dialog with a primary action and no secondary action', - ), + appIcon: const FlutterLogo(size: 64), + title: const Text('Title'), + message: const Text(dialogMessage), //horizontalActions: false, primaryButton: PushButton( - controlSize: ControlSize.regular, + controlSize: ControlSize.large, onPressed: Navigator.of(context).pop, - child: const Text('Primary'), + child: const Text('Label'), ), ), ), @@ -66,27 +74,23 @@ class _DialogsPageState extends State { onPressed: () => showMacosAlertDialog( context: context, builder: (context) => MacosAlertDialog( - appIcon: const FlutterLogo( - size: 56, - ), - title: const Text( - 'Alert Dialog with Secondary Action', - ), + appIcon: const FlutterLogo(size: 64), + title: const Text('Title'), message: const Text( - 'This is an alert dialog with primary action and secondary action laid out horizontally', + dialogMessage, textAlign: TextAlign.center, ), //horizontalActions: false, primaryButton: PushButton( - controlSize: ControlSize.regular, + controlSize: ControlSize.large, onPressed: Navigator.of(context).pop, - child: const Text('Primary'), + child: const Text('Label'), ), secondaryButton: PushButton( - controlSize: ControlSize.regular, + controlSize: ControlSize.large, secondary: true, onPressed: Navigator.of(context).pop, - child: const Text('Secondary'), + child: const Text('Label'), ), ), ), @@ -98,27 +102,23 @@ class _DialogsPageState extends State { onPressed: () => showMacosAlertDialog( context: context, builder: (context) => MacosAlertDialog( - appIcon: const FlutterLogo( - size: 56, - ), - title: const Text( - 'Alert Dialog with Secondary Action', - ), + appIcon: const FlutterLogo(size: 64), + title: const Text('Title'), message: const Text( - 'This is an alert dialog with primary action and secondary action laid out vertically', + dialogMessage, textAlign: TextAlign.center, ), horizontalActions: false, primaryButton: PushButton( - controlSize: ControlSize.regular, + controlSize: ControlSize.large, onPressed: Navigator.of(context).pop, - child: const Text('Primary'), + child: const Text('Label'), ), secondaryButton: PushButton( - controlSize: ControlSize.regular, + controlSize: ControlSize.large, secondary: true, onPressed: Navigator.of(context).pop, - child: const Text('Secondary'), + child: const Text('Label'), ), ), ), @@ -130,25 +130,20 @@ class _DialogsPageState extends State { onPressed: () => showMacosAlertDialog( context: context, builder: (context) => MacosAlertDialog( - appIcon: const FlutterLogo( - size: 56, - ), - title: const Text( - 'Alert Dialog with Secondary Action', - ), + appIcon: const FlutterLogo(size: 64), + title: const Text('Title'), message: const Text( - 'This is an alert dialog with primary action and secondary ' - 'action laid out vertically. It also contains a "suppress" option.', + dialogMessage, textAlign: TextAlign.center, ), horizontalActions: false, primaryButton: PushButton( - controlSize: ControlSize.regular, + controlSize: ControlSize.large, onPressed: Navigator.of(context).pop, child: const Text('Primary'), ), secondaryButton: PushButton( - controlSize: ControlSize.regular, + controlSize: ControlSize.large, secondary: true, onPressed: Navigator.of(context).pop, child: const Text('Secondary'), diff --git a/example/lib/pages/fields_page.dart b/example/lib/pages/fields_page.dart index 00b2aa81..ff5ae256 100644 --- a/example/lib/pages/fields_page.dart +++ b/example/lib/pages/fields_page.dart @@ -15,16 +15,27 @@ class _FieldsPageState extends State { toolBar: ToolBar( title: const Text('Fields'), titleWidth: 150.0, - actions: [ - ToolBarIconButton( - label: 'Toggle Sidebar', - icon: const MacosIcon( + leading: MacosTooltip( + message: 'Toggle Sidebar', + useMousePosition: false, + child: MacosIconButton( + icon: MacosIcon( CupertinoIcons.sidebar_left, + color: MacosTheme.brightnessOf(context).resolve( + const Color.fromRGBO(0, 0, 0, 0.5), + const Color.fromRGBO(255, 255, 255, 0.5), + ), + size: 20.0, + ), + boxConstraints: const BoxConstraints( + minHeight: 20, + minWidth: 20, + maxWidth: 48, + maxHeight: 38, ), onPressed: () => MacosWindowScope.of(context).toggleSidebar(), - showLabel: false, ), - ], + ), ), children: [ ContentArea( diff --git a/example/lib/pages/indicators_page.dart b/example/lib/pages/indicators_page.dart index 1dd4caf5..55347f10 100644 --- a/example/lib/pages/indicators_page.dart +++ b/example/lib/pages/indicators_page.dart @@ -20,16 +20,27 @@ class _IndicatorsPageState extends State { toolBar: ToolBar( title: const Text('Indicators'), titleWidth: 150.0, - actions: [ - ToolBarIconButton( - label: 'Toggle Sidebar', - icon: const MacosIcon( + leading: MacosTooltip( + message: 'Toggle Sidebar', + useMousePosition: false, + child: MacosIconButton( + icon: MacosIcon( CupertinoIcons.sidebar_left, + color: MacosTheme.brightnessOf(context).resolve( + const Color.fromRGBO(0, 0, 0, 0.5), + const Color.fromRGBO(255, 255, 255, 0.5), + ), + size: 20.0, + ), + boxConstraints: const BoxConstraints( + minHeight: 20, + minWidth: 20, + maxWidth: 48, + maxHeight: 38, ), onPressed: () => MacosWindowScope.of(context).toggleSidebar(), - showLabel: false, ), - ], + ), ), children: [ ContentArea( diff --git a/example/lib/pages/resizable_pane_page.dart b/example/lib/pages/resizable_pane_page.dart new file mode 100644 index 00000000..b850a0c3 --- /dev/null +++ b/example/lib/pages/resizable_pane_page.dart @@ -0,0 +1,90 @@ +import 'package:flutter/cupertino.dart'; +import 'package:macos_ui/macos_ui.dart'; + +class ResizablePanePage extends StatefulWidget { + const ResizablePanePage({super.key}); + + @override + State createState() => _ResizablePanePageState(); +} + +class _ResizablePanePageState extends State { + @override + Widget build(BuildContext context) { + return MacosScaffold( + toolBar: ToolBar( + title: const Text('Resizable Pane'), + leading: MacosTooltip( + message: 'Toggle Sidebar', + useMousePosition: false, + child: MacosIconButton( + icon: MacosIcon( + CupertinoIcons.sidebar_left, + color: MacosTheme.brightnessOf(context).resolve( + const Color.fromRGBO(0, 0, 0, 0.5), + const Color.fromRGBO(255, 255, 255, 0.5), + ), + size: 20.0, + ), + boxConstraints: const BoxConstraints( + minHeight: 20, + minWidth: 20, + maxWidth: 48, + maxHeight: 38, + ), + onPressed: () => MacosWindowScope.of(context).toggleSidebar(), + ), + ), + ), + children: [ + ResizablePane( + minSize: 180, + startSize: 200, + windowBreakpoint: 700, + resizableSide: ResizableSide.right, + builder: (_, __) { + return const Center( + child: Text('Left Resizable Pane'), + ); + }, + ), + ContentArea( + builder: (_, __) { + return Column( + children: [ + const Flexible( + fit: FlexFit.loose, + child: Center( + child: Text('Content Area'), + ), + ), + ResizablePane( + minSize: 50, + startSize: 200, + //windowBreakpoint: 600, + builder: (_, __) { + return const Center( + child: Text('Bottom Resizable Pane'), + ); + }, + resizableSide: ResizableSide.top, + ), + ], + ); + }, + ), + ResizablePane( + minSize: 180, + startSize: 200, + // windowBreakpoint: 800, + resizableSide: ResizableSide.left, + builder: (_, __) { + return const Center( + child: Text('Right Resizable Pane'), + ); + }, + ), + ], + ); + } +} diff --git a/example/lib/pages/selectors_page.dart b/example/lib/pages/selectors_page.dart index 8256a33e..9c9a8364 100644 --- a/example/lib/pages/selectors_page.dart +++ b/example/lib/pages/selectors_page.dart @@ -15,16 +15,27 @@ class _SelectorsPageState extends State { toolBar: ToolBar( title: const Text('Selectors'), titleWidth: 150.0, - actions: [ - ToolBarIconButton( - label: 'Toggle Sidebar', - icon: const MacosIcon( + leading: MacosTooltip( + message: 'Toggle Sidebar', + useMousePosition: false, + child: MacosIconButton( + icon: MacosIcon( CupertinoIcons.sidebar_left, + color: MacosTheme.brightnessOf(context).resolve( + const Color.fromRGBO(0, 0, 0, 0.5), + const Color.fromRGBO(255, 255, 255, 0.5), + ), + size: 20.0, + ), + boxConstraints: const BoxConstraints( + minHeight: 20, + minWidth: 20, + maxWidth: 48, + maxHeight: 38, ), onPressed: () => MacosWindowScope.of(context).toggleSidebar(), - showLabel: false, ), - ], + ), ), children: [ ContentArea( diff --git a/example/lib/pages/tabview_page.dart b/example/lib/pages/tabview_page.dart index 64d4d8ff..5e224429 100644 --- a/example/lib/pages/tabview_page.dart +++ b/example/lib/pages/tabview_page.dart @@ -17,8 +17,29 @@ class _TabViewPageState extends State { @override Widget build(BuildContext context) { return MacosScaffold( - toolBar: const ToolBar( - title: Text('TabView'), + toolBar: ToolBar( + title: const Text('TabView'), + leading: MacosTooltip( + message: 'Toggle Sidebar', + useMousePosition: false, + child: MacosIconButton( + icon: MacosIcon( + CupertinoIcons.sidebar_left, + color: MacosTheme.brightnessOf(context).resolve( + const Color.fromRGBO(0, 0, 0, 0.5), + const Color.fromRGBO(255, 255, 255, 0.5), + ), + size: 20.0, + ), + boxConstraints: const BoxConstraints( + minHeight: 20, + minWidth: 20, + maxWidth: 48, + maxHeight: 38, + ), + onPressed: () => MacosWindowScope.of(context).toggleSidebar(), + ), + ), ), children: [ ContentArea( diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index b06f1179..15deecb4 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,8 +7,10 @@ import Foundation import macos_ui import macos_window_utils +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin")) MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 42b5eb53..97b47e1b 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -4,11 +4,15 @@ PODS: - FlutterMacOS - macos_window_utils (1.0.0): - FlutterMacOS + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - macos_ui (from `Flutter/ephemeral/.symlinks/plugins/macos_ui/macos`) - macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) EXTERNAL SOURCES: FlutterMacOS: @@ -17,11 +21,14 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/macos_ui/macos macos_window_utils: :path: Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 macos_ui: 6229a8922cd97bafb7d9636c8eb8dfb0744183ca macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 + path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 PODFILE CHECKSUM: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9 diff --git a/example/macos/Runner/DebugProfile.entitlements b/example/macos/Runner/DebugProfile.entitlements index dddb8a30..c946719a 100644 --- a/example/macos/Runner/DebugProfile.entitlements +++ b/example/macos/Runner/DebugProfile.entitlements @@ -8,5 +8,7 @@ com.apple.security.network.server + com.apple.security.network.client + diff --git a/example/pubspec.lock b/example/pubspec.lock index 2b2ea121..3f05abc6 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" cupertino_icons: dependency: "direct main" description: @@ -57,6 +65,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" flutter: dependency: "direct main" description: flutter @@ -66,15 +90,39 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: e20ff62b158b96f392bfc8afe29dee1503c94fbea2cbe8186fd59b756b8ae982 + url: "https://pub.dev" + source: hosted + version: "5.1.0" + http: + dependency: transitive + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" js: dependency: transitive description: @@ -97,7 +145,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.6" + version: "2.0.0-beta.7" macos_window_utils: dependency: transitive description: @@ -146,6 +194,78 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" + url: "https://pub.dev" + source: hosted + version: "2.0.15" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" + url: "https://pub.dev" + source: hosted + version: "2.0.27" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57 + url: "https://pub.dev" + source: hosted + version: "2.1.11" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + url: "https://pub.dev" + source: hosted + version: "2.0.6" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" + url: "https://pub.dev" + source: hosted + version: "2.1.7" + platform: + dependency: transitive + description: + name: platform + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + process: + dependency: transitive + description: + name: process + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" + source: hosted + version: "4.2.4" provider: dependency: "direct main" description: @@ -207,6 +327,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" vector_math: dependency: transitive description: @@ -215,6 +343,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + win32: + dependency: transitive + description: + name: win32 + sha256: dfdf0136e0aa7a1b474ea133e67cb0154a0acd2599c4f3ada3b49d38d38793ee + url: "https://pub.dev" + source: hosted + version: "5.0.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 + url: "https://pub.dev" + source: hosted + version: "1.0.0" sdks: dart: ">=3.0.0 <4.0.0" flutter: ">=3.10.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index e68c5535..79564134 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: '>=2.17.0 <3.0.0' + sdk: '>=3.0.0 <4.0.0' dependencies: flutter: @@ -13,12 +13,13 @@ dependencies: cupertino_icons: ^1.0.5 macos_ui: path: .. - provider: ^6.0.3 + provider: ^6.0.5 + google_fonts: ^5.1.0 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.1 + flutter_lints: ^2.0.2 flutter: assets: diff --git a/lib/src/dialogs/macos_alert_dialog.dart b/lib/src/dialogs/macos_alert_dialog.dart index 057f04df..0dab2588 100644 --- a/lib/src/dialogs/macos_alert_dialog.dart +++ b/lib/src/dialogs/macos_alert_dialog.dart @@ -3,6 +3,10 @@ import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; const _kDialogBorderRadius = BorderRadius.all(Radius.circular(12.0)); +const _kDefaultDialogConstraints = BoxConstraints( + minWidth: 260, + maxWidth: 260, +); /// A macOS-style AlertDialog. /// @@ -17,14 +21,12 @@ const _kDialogBorderRadius = BorderRadius.all(Radius.circular(12.0)); /// appIcon: FlutterLogo( /// size: 56, /// ), -/// title: Text( -/// 'Alert Dialog with Primary Action', -/// ), +/// title: Text('Alert Dialog with Primary Action'), /// message: Text( /// 'This is an alert dialog with a primary action and no secondary action', /// ), /// primaryButton: PushButton( -/// buttonSize: ButtonSize.large, +/// controlSize: ControlSize.large, /// child: Text('Primary'), /// onPressed: Navigator.of(context).pop, /// ), @@ -46,7 +48,7 @@ class MacosAlertDialog extends StatelessWidget { /// This should be your application's icon. /// - /// The size of this widget should be 56x56. + /// The size of this widget should be 64x64. final Widget appIcon; /// The title for the dialog. @@ -61,13 +63,13 @@ class MacosAlertDialog extends StatelessWidget { /// The primary action a user can take. /// - /// Typically a [PushButton]. - final Widget primaryButton; + /// Must a [PushButton] with a [ControlSize] of `large`. + final PushButton primaryButton; /// The secondary action a user can take. /// - /// Typically a [PushButton]. - final Widget? secondaryButton; + /// Must a [PushButton] with a [ControlSize] of `large`. + final PushButton? secondaryButton; /// Determines whether to lay out [primaryButton] and [secondaryButton] /// horizontally or vertically. @@ -116,6 +118,11 @@ class MacosAlertDialog extends StatelessWidget { @override Widget build(BuildContext context) { assert(debugCheckHasMacosTheme(context)); + assert(primaryButton.controlSize == ControlSize.large); + if (secondaryButton != null) { + assert(secondaryButton is PushButton); + assert(secondaryButton!.controlSize == ControlSize.large); + } final brightness = MacosTheme.brightnessOf(context); final outerBorderColor = brightness.resolve( @@ -153,39 +160,35 @@ class MacosAlertDialog extends StatelessWidget { borderRadius: _kDialogBorderRadius, ), child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 260, - ), + constraints: _kDefaultDialogConstraints, child: Column( mainAxisSize: MainAxisSize.min, children: [ - const SizedBox(height: 28), + const SizedBox(height: 20), ConstrainedBox( constraints: const BoxConstraints( - maxHeight: 56, - maxWidth: 56, + maxHeight: 64, + maxWidth: 64, ), child: appIcon, ), - const SizedBox(height: 28), + const SizedBox(height: 16), DefaultTextStyle( style: MacosTheme.of(context).typography.headline, textAlign: TextAlign.center, child: title, ), - const SizedBox(height: 16), + const SizedBox(height: 10), DefaultTextStyle( textAlign: TextAlign.center, style: MacosTheme.of(context).typography.headline, child: message, ), - const SizedBox(height: 18), + const SizedBox(height: 16), if (secondaryButton == null) ...[ Row( children: [ - Expanded( - child: primaryButton, - ), + Expanded(child: primaryButton), ], ), ] else ...[ @@ -193,9 +196,7 @@ class MacosAlertDialog extends StatelessWidget { Row( children: [ if (secondaryButton != null) ...[ - Expanded( - child: secondaryButton!, - ), + Expanded(child: secondaryButton!), const SizedBox(width: 8.0), ], Expanded( diff --git a/lib/src/theme/typography.dart b/lib/src/theme/typography.dart index d3efbeaf..c5a9eefd 100644 --- a/lib/src/theme/typography.dart +++ b/lib/src/theme/typography.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; +import 'package:macos_ui/src/theme/macos_theme.dart'; const _kDefaultFontFamily = '.AppleSystemUIFont'; @@ -230,6 +231,11 @@ class MacosTypography with Diagnosticable { ); } + static MacosTypography of(BuildContext context) { + final theme = MacosTheme.of(context); + return theme.typography; + } + @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); diff --git a/pubspec.yaml b/pubspec.yaml index 33d64578..5ce15348 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 2.0.0-beta.6 +version: 2.0.0-beta.7 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From 7343ee33880808acfee1a58a650c52569752c393 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Mon, 10 Jul 2023 17:09:54 -0400 Subject: [PATCH 30/45] More gallery improvements (#457) * chore: recreate native app * chore: use modern app icon * chore: improve indicators page * chore: improve fields page * chore: improve selectors page --- example/.metadata | 24 ++++- example/lib/pages/buttons_page.dart | 57 +--------- example/lib/pages/fields_page.dart | 18 ++-- example/lib/pages/indicators_page.dart | 100 +++++++++++++----- example/lib/pages/selectors_page.dart | 35 ++++-- example/lib/widgets/widget_text_title1.dart | 30 ++++++ example/lib/widgets/widget_text_title2.dart | 30 ++++++ .../AppIcon.appiconset/Contents.json | 64 +++++------ .../AppIcon.appiconset/app_icon_1024.png | Bin 46993 -> 102994 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 3276 -> 5680 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 1429 -> 520 bytes .../AppIcon.appiconset/app_icon_256 1.png | Bin 0 -> 14142 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 5933 -> 14142 bytes .../AppIcon.appiconset/app_icon_32 1.png | Bin 0 -> 1066 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 1243 -> 1066 bytes .../AppIcon.appiconset/app_icon_512 1.png | Bin 0 -> 36406 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 14800 -> 36406 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 1874 -> 2218 bytes example/macos/RunnerTests/RunnerTests.swift | 12 +++ 19 files changed, 240 insertions(+), 130 deletions(-) create mode 100644 example/lib/widgets/widget_text_title1.dart create mode 100644 example/lib/widgets/widget_text_title2.dart create mode 100644 example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256 1.png create mode 100644 example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32 1.png create mode 100644 example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512 1.png create mode 100644 example/macos/RunnerTests/RunnerTests.swift diff --git a/example/.metadata b/example/.metadata index 140b9294..53830e36 100644 --- a/example/.metadata +++ b/example/.metadata @@ -1,10 +1,30 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled and should not be manually edited. +# This file should be version controlled. version: - revision: 4d7946a68d26794349189cf21b3f68cc6fe61dcb + revision: 796c8ef79279f9c774545b3771238c3098dbefab channel: stable project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab + - platform: macos + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index d26501c3..80ff6cf8 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -1,6 +1,7 @@ +import 'package:example/widgets/widget_text_title1.dart'; +import 'package:example/widgets/widget_text_title2.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:provider/provider.dart'; @@ -813,57 +814,3 @@ const languages = [ 'Romanian', 'Dutch' ]; - -class WidgetTextTitle1 extends StatelessWidget { - const WidgetTextTitle1({super.key, required this.widgetName}); - - final String widgetName; - - @override - Widget build(BuildContext context) { - return DecoratedBox( - decoration: BoxDecoration( - color: MacosColors.systemGrayColor.withOpacity(0.5), - borderRadius: BorderRadius.circular(4.0), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 6.0, - ), - child: Text( - widgetName, - style: MacosTypography.of(context) - .title1 - .copyWith(fontFamily: GoogleFonts.jetBrainsMono().fontFamily), - ), - ), - ); - } -} - -class WidgetTextTitle2 extends StatelessWidget { - const WidgetTextTitle2({super.key, required this.widgetName}); - - final String widgetName; - - @override - Widget build(BuildContext context) { - return DecoratedBox( - decoration: BoxDecoration( - color: MacosColors.systemGrayColor.withOpacity(0.5), - borderRadius: BorderRadius.circular(4.0), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 6.0, - ), - child: Text( - widgetName, - style: MacosTypography.of(context) - .title2 - .copyWith(fontFamily: GoogleFonts.jetBrainsMono().fontFamily), - ), - ), - ); - } -} diff --git a/example/lib/pages/fields_page.dart b/example/lib/pages/fields_page.dart index ff5ae256..fd9183bb 100644 --- a/example/lib/pages/fields_page.dart +++ b/example/lib/pages/fields_page.dart @@ -1,4 +1,6 @@ +import 'package:example/widgets/widget_text_title1.dart'; import 'package:flutter/cupertino.dart' hide OverlayVisibilityMode; +import 'package:flutter/material.dart'; import 'package:macos_ui/macos_ui.dart'; class FieldsPage extends StatefulWidget { @@ -43,7 +45,10 @@ class _FieldsPageState extends State { return SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + const WidgetTextTitle1(widgetName: 'MacosTextField'), + Divider(color: MacosTheme.of(context).dividerColor), const SizedBox( width: 300.0, child: MacosTextField( @@ -95,6 +100,8 @@ class _FieldsPageState extends State { ), ), const SizedBox(height: 20), + const WidgetTextTitle1(widgetName: 'MacosSearchField'), + Divider(color: MacosTheme.of(context).dividerColor), SizedBox( width: 300.0, child: MacosSearchField( @@ -136,17 +143,6 @@ class _FieldsPageState extends State { ); }, ), - ResizablePane( - minSize: 180, - startSize: 200, - windowBreakpoint: 800, - resizableSide: ResizableSide.left, - builder: (_, __) { - return const Center( - child: Text('Resizable Pane'), - ); - }, - ), ], ); } diff --git a/example/lib/pages/indicators_page.dart b/example/lib/pages/indicators_page.dart index 55347f10..7e8a6b20 100644 --- a/example/lib/pages/indicators_page.dart +++ b/example/lib/pages/indicators_page.dart @@ -1,3 +1,4 @@ +import 'package:example/widgets/widget_text_title1.dart'; import 'package:macos_ui/macos_ui.dart'; // ignore: implementation_imports import 'package:macos_ui/src/library.dart'; @@ -48,47 +49,98 @@ class _IndicatorsPageState extends State { return SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - CapacityIndicator( - value: capacitorValue, - onChanged: (v) => setState(() => capacitorValue = v), - splits: 20, - discrete: true, + const WidgetTextTitle1(widgetName: 'CapacityIndicator'), + Divider(color: MacosTheme.of(context).dividerColor), + Row( + children: [ + const Text('Standard'), + const SizedBox(width: 8), + Expanded( + child: CapacityIndicator( + value: capacitorValue, + onChanged: (v) => setState(() => capacitorValue = v), + ), + ), + ], ), - const SizedBox(height: 20), - CapacityIndicator( - value: capacitorValue, - onChanged: (v) => setState(() => capacitorValue = v), + const SizedBox(height: 16), + Row( + children: [ + const Text('Discrete'), + const SizedBox(width: 8), + Expanded( + child: CapacityIndicator( + value: capacitorValue, + onChanged: (v) => setState(() => capacitorValue = v), + splits: 20, + discrete: true, + ), + ), + ], ), const SizedBox(height: 20), - MacosSlider( - value: sliderValue, - onChanged: (v) => setState(() => sliderValue = v), + const WidgetTextTitle1(widgetName: 'MacosSlider'), + Divider(color: MacosTheme.of(context).dividerColor), + Row( + children: [ + const Text('Standard'), + const SizedBox(width: 8), + Expanded( + child: MacosSlider( + value: sliderValue, + onChanged: (v) => setState(() => sliderValue = v), + ), + ), + ], ), - const SizedBox(height: 20), - MacosSlider( - value: sliderValue, - discrete: true, - onChanged: (v) => setState(() => sliderValue = v), + const SizedBox(height: 16), + Row( + children: [ + const Text('Discrete'), + const SizedBox(width: 8), + Expanded( + child: MacosSlider( + value: sliderValue, + discrete: true, + onChanged: (v) => setState(() => sliderValue = v), + ), + ), + ], ), const SizedBox(height: 20), + const WidgetTextTitle1(widgetName: 'RatingIndicator'), + Divider(color: MacosTheme.of(context).dividerColor), RatingIndicator( value: ratingValue, onChanged: (v) => setState(() => ratingValue = v), ), const SizedBox(height: 20), - const ProgressCircle(), + const WidgetTextTitle1(widgetName: 'ProgressCircle'), + Divider(color: MacosTheme.of(context).dividerColor), + const Row( + children: [ + Text('Indeterminate'), + SizedBox(width: 8), + ProgressCircle(), + ], + ), + const Row( + children: [ + Text('Determinate'), + SizedBox(width: 8), + ProgressCircle(value: 50), + ], + ), const SizedBox(height: 20), + const WidgetTextTitle1(widgetName: 'RelevanceIndicator'), + Divider(color: MacosTheme.of(context).dividerColor), + const SizedBox(height: 8), const RelevanceIndicator( value: 25, amount: 50, ), - const SizedBox(height: 20), - const Label( - icon: MacosIcon(CupertinoIcons.tag), - text: SelectableText('A determinate progress circle: '), - child: ProgressCircle(value: 50), - ), ], ), ); diff --git a/example/lib/pages/selectors_page.dart b/example/lib/pages/selectors_page.dart index 9c9a8364..fdc5fbeb 100644 --- a/example/lib/pages/selectors_page.dart +++ b/example/lib/pages/selectors_page.dart @@ -1,4 +1,7 @@ +import 'package:example/widgets/widget_text_title1.dart'; +import 'package:example/widgets/widget_text_title2.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:macos_ui/macos_ui.dart'; class SelectorsPage extends StatefulWidget { @@ -44,20 +47,40 @@ class _SelectorsPageState extends State { controller: scrollController, padding: const EdgeInsets.all(20), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text( + 'Date & Time Pickers', + style: MacosTypography.of(context).title1, + ), + Divider(color: MacosTheme.of(context).dividerColor), Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start, children: [ - MacosDatePicker( - onDateChanged: (date) => debugPrint('$date'), + Column( + children: [ + const WidgetTextTitle2(widgetName: 'MacosDatePicker'), + const SizedBox(height: 12), + MacosDatePicker( + onDateChanged: (date) => debugPrint('$date'), + ), + ], ), - MacosTimePicker( - onTimeChanged: (time) => debugPrint('$time'), + const SizedBox(width: 50), + Column( + children: [ + const WidgetTextTitle2(widgetName: 'MacosTimePicker'), + const SizedBox(height: 12), + MacosTimePicker( + onTimeChanged: (time) => debugPrint('$time'), + ), + ], ), ], ), - const SizedBox(height: 50), + const SizedBox(height: 20), + const WidgetTextTitle1(widgetName: 'MacosColorWell'), + Divider(color: MacosTheme.of(context).dividerColor), MacosColorWell( onColorSelected: (color) => debugPrint('$color'), ), diff --git a/example/lib/widgets/widget_text_title1.dart b/example/lib/widgets/widget_text_title1.dart new file mode 100644 index 00000000..d7dafc90 --- /dev/null +++ b/example/lib/widgets/widget_text_title1.dart @@ -0,0 +1,30 @@ +import 'package:flutter/cupertino.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:macos_ui/macos_ui.dart'; + +class WidgetTextTitle1 extends StatelessWidget { + const WidgetTextTitle1({super.key, required this.widgetName}); + + final String widgetName; + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + color: MacosColors.systemGrayColor.withOpacity(0.5), + borderRadius: BorderRadius.circular(4.0), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 6.0, + ), + child: Text( + widgetName, + style: MacosTypography.of(context) + .title1 + .copyWith(fontFamily: GoogleFonts.jetBrainsMono().fontFamily), + ), + ), + ); + } +} \ No newline at end of file diff --git a/example/lib/widgets/widget_text_title2.dart b/example/lib/widgets/widget_text_title2.dart new file mode 100644 index 00000000..8da1135d --- /dev/null +++ b/example/lib/widgets/widget_text_title2.dart @@ -0,0 +1,30 @@ +import 'package:flutter/cupertino.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:macos_ui/macos_ui.dart'; + +class WidgetTextTitle2 extends StatelessWidget { + const WidgetTextTitle2({super.key, required this.widgetName}); + + final String widgetName; + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + color: MacosColors.systemGrayColor.withOpacity(0.5), + borderRadius: BorderRadius.circular(4.0), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 6.0, + ), + child: Text( + widgetName, + style: MacosTypography.of(context) + .title2 + .copyWith(fontFamily: GoogleFonts.jetBrainsMono().fontFamily), + ), + ), + ); + } +} diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index a2ec33f1..0a0928ee 100644 --- a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,68 +1,68 @@ { "images" : [ { - "size" : "16x16", - "idiom" : "mac", "filename" : "app_icon_16.png", - "scale" : "1x" + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" }, { - "size" : "16x16", + "filename" : "app_icon_32 1.png", "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "2x" + "scale" : "2x", + "size" : "16x16" }, { - "size" : "32x32", - "idiom" : "mac", "filename" : "app_icon_32.png", - "scale" : "1x" + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" }, { - "size" : "32x32", - "idiom" : "mac", "filename" : "app_icon_64.png", - "scale" : "2x" + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" }, { - "size" : "128x128", - "idiom" : "mac", "filename" : "app_icon_128.png", - "scale" : "1x" + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" }, { - "size" : "128x128", + "filename" : "app_icon_256 1.png", "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "2x" + "scale" : "2x", + "size" : "128x128" }, { - "size" : "256x256", - "idiom" : "mac", "filename" : "app_icon_256.png", - "scale" : "1x" + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" }, { - "size" : "256x256", + "filename" : "app_icon_512 1.png", "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "2x" + "scale" : "2x", + "size" : "256x256" }, { - "size" : "512x512", - "idiom" : "mac", "filename" : "app_icon_512.png", - "scale" : "1x" + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" }, { - "size" : "512x512", - "idiom" : "mac", "filename" : "app_icon_1024.png", - "scale" : "2x" + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } } diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7ca84f0976aca34b7f2895d65fb94d1ea..82b6f9d9a33e198f5747104729e1fcef999772a5 100644 GIT binary patch literal 102994 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ⅅ8MglG&ER{i5lN315cO?EdHNLrg? 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 z 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 Pe7HEdAEg|=c=LY&YVNkY literal 46993 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 diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc16421680a50164ba74381b4b35ceaa0ccfc..13b35eba55c6dabc3aac36f33d859266c18fa0d0 100644 GIT binary patch literal 5680 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 literal 3276 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 N;OXk;vd$@?2>?>Ex^Vyi diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256 1.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256 1.png new file mode 100644 index 0000000000000000000000000000000000000000..bdb57226d5f2bd20f11934f4903f16459cf52379 GIT binary patch literal 14142 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 diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512 1.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512 1.png new file mode 100644 index 0000000000000000000000000000000000000000..326c0e72c9d820600887813b3b98d0dd69c5d4e8 GIT binary patch literal 36406 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 literal 0 HcmV?d00001 diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a726136a47ed24125c7efc79d68a4a01961b4..326c0e72c9d820600887813b3b98d0dd69c5d4e8 100644 GIT binary patch literal 36406 zcmeGE=RaKU_dbB`8KZ_EB%(x35TbX25d=Z>h)%Q!Av#fJM3Csc_g zC2I6x%$)80`Tkz#KRA!h1FzY`?0es3t!rKDT5EjPe6B=BLPr7s0GW!if;Ip^!AmGW zL;$`Vdre+|FA!I4r6)keFvAx3M#1`}ijBHDzy)3t0gwjl|qC2YB`SSxFKHr(oY#H$)x{L$LL zBdLKTlsOrmb>T0wd=&6l3+_Te>1!j0OU8%b%N342^opKmT)gni(wV($s(>V-fUv@0p8!f`=>PxC|9=nu ze{ToBBj8b<{PLfXV$h8YPgA~E!_sF9bl;QOF{o6t&JdsX?}rW!_&d`#wlB6T_h;Xf zl{4Tz5>qjF4kZgjO7ZiLPRz_~U@k5%?=30+nxEh9?s78gZ07YHB`FV`4%hlQlMJe@J`+e(qzy+h(9yY^ckv_* zb_E6o4p)ZaWfraIoB2)U7_@l(J0O%jm+Or>8}zSSTkM$ASG^w3F|I? z$+eHt7T~04(_WfKh27zqS$6* zzyy-ZyqvSIZ0!kkSvHknm_P*{5TKLQs8S6M=ONuKAUJWtpxbL#2(_huvY(v~Y%%#~ zYgsq$JbLLprKkV)32`liIT$KKEqs$iYxjFlHiRNvBhxbDg*3@Qefw4UM$>i${R5uB zhvTgmqQsKA{vrKN;TSJU2$f9q=y{$oH{<)woSeV>fkIz6D8@KB zf4M%v%f5U2?<8B(xn}xV+gWP?t&oiapJhJbfa;agtz-YM7=hrSuxl8lAc3GgFna#7 zNjX7;`d?oD`#AK+fQ=ZXqfIZFEk{ApzjJF0=yO~Yj{7oQfXl+6v!wNnoqwEvrs81a zGC?yXeSD2NV!ejp{LdZGEtd1TJ)3g{P6j#2jLR`cpo;YX}~_gU&Gd<+~SUJVh+$7S%`zLy^QqndN<_9 zrLwnXrLvW+ew9zX2)5qw7)zIYawgMrh`{_|(nx%u-ur1B7YcLp&WFa24gAuw~& zKJD3~^`Vp_SR$WGGBaMnttT)#fCc^+P$@UHIyBu+TRJWbcw4`CYL@SVGh!X&y%!x~ zaO*m-bTadEcEL6V6*{>irB8qT5Tqd54TC4`h`PVcd^AM6^Qf=GS->x%N70SY-u?qr>o2*OV7LQ=j)pQGv%4~z zz?X;qv*l$QSNjOuQZ>&WZs2^@G^Qas`T8iM{b19dS>DaXX~=jd4B2u`P;B}JjRBi# z_a@&Z5ev1-VphmKlZEZZd2-Lsw!+1S60YwW6@>+NQ=E5PZ+OUEXjgUaXL-E0fo(E* zsjQ{s>n33o#VZm0e%H{`KJi@2ghl8g>a~`?mFjw+$zlt|VJhSU@Y%0TWs>cnD&61fW4e0vFSaXZa4-c}U{4QR8U z;GV3^@(?Dk5uc@RT|+5C8-24->1snH6-?(nwXSnPcLn#X_}y3XS)MI_?zQ$ZAuyg+ z-pjqsw}|hg{$~f0FzmmbZzFC0He_*Vx|_uLc!Ffeb8#+@m#Z^AYcWcZF(^Os8&Z4g zG)y{$_pgrv#=_rV^D|Y<_b@ICleUv>c<0HzJDOsgJb#Rd-Vt@+EBDPyq7dUM9O{Yp zuGUrO?ma2wpuJuwl1M=*+tb|qx7Doj?!F-3Z>Dq_ihFP=d@_JO;vF{iu-6MWYn#=2 zRX6W=`Q`q-+q@Db|6_a1#8B|#%hskH82lS|9`im0UOJn?N#S;Y0$%xZw3*jR(1h5s z?-7D1tnIafviko>q6$UyqVDq1o@cwyCb*})l~x<@s$5D6N=-Uo1yc49p)xMzxwnuZ zHt!(hu-Ek;Fv4MyNTgbW%rPF*dB=;@r3YnrlFV{#-*gKS_qA(G-~TAlZ@Ti~Yxw;k za1EYyX_Up|`rpbZ0&Iv#$;eC|c0r4XGaQ-1mw@M_4p3vKIIpKs49a8Ns#ni)G314Z z8$Ei?AhiT5dQGWUYdCS|IC7r z=-8ol>V?u!n%F*J^^PZ(ONT&$Ph;r6X;pj|03HlDY6r~0g~X#zuzVU%a&!fs_f|m?qYvg^Z{y?9Qh7Rn?T*F%7lUtA6U&={HzhYEzA`knx1VH> z{tqv?p@I(&ObD5L4|YJV$QM>Nh-X3cx{I&!$FoPC_2iIEJfPk-$;4wz>adRu@n`_y z_R6aN|MDHdK;+IJmyw(hMoDCFCQ(6?hCAG5&7p{y->0Uckv# zvooVuu04$+pqof777ftk<#42@KQ((5DPcSMQyzGOJ{e9H$a9<2Qi_oHjl{#=FUL9d z+~0^2`tcvmp0hENwfHR`Ce|<1S@p;MNGInXCtHnrDPXCKmMTZQ{HVm_cZ>@?Wa6}O zHsJc7wE)mc@1OR2DWY%ZIPK1J2p6XDO$ar`$RXkbW}=@rFZ(t85AS>>U0!yt9f49^ zA9@pc0P#k;>+o5bJfx0t)Lq#v4`OcQn~av__dZ-RYOYu}F#pdsl31C^+Qgro}$q~5A<*c|kypzd} ziYGZ~?}5o`S5lw^B{O@laad9M_DuJle- z*9C7o=CJh#QL=V^sFlJ0c?BaB#4bV^T(DS6&Ne&DBM_3E$S^S13qC$7_Z?GYXTpR@wqr70wu$7+qvf-SEUa5mdHvFbu^7ew!Z1a^ zo}xKOuT*gtGws-a{Tx}{#(>G~Y_h&5P@Q8&p!{*s37^QX_Ibx<6XU*AtDOIvk|^{~ zPlS}&DM5$Ffyu-T&0|KS;Wnaqw{9DB&B3}vcO14wn;)O_e@2*9B&0I_ zZz{}CMxx`hv-XouY>^$Y@J(_INeM>lIQI@I>dBAqq1)}?Xmx(qRuX^i4IV%=MF306 z9g)i*79pP%_7Ex?m6ag-4Tlm=Z;?DQDyC-NpUIb#_^~V_tsL<~5<&;Gf2N+p?(msn zzUD~g>OoW@O}y0@Z;RN)wjam`CipmT&O7a|YljZqU=U86 zedayEdY)2F#BJ6xvmW8K&ffdS*0!%N<%RB!2~PAT4AD*$W7yzHbX#Eja9%3aD+Ah2 zf#T;XJW-GMxpE=d4Y>}jE=#U`IqgSoWcuvgaWQ9j1CKzG zDkoMDDT)B;Byl3R2PtC`ip=yGybfzmVNEx{xi_1|Cbqj>=FxQc{g`xj6fIfy`D8fA z##!-H_e6o0>6Su&$H2kQTujtbtyNFeKc}2=|4IfLTnye#@$Au7Kv4)dnA;-fz@D_8 z)>irG$)dkBY~zX zC!ZXLy*L3xr6cb70QqfN#Q>lFIc<>}>la4@3%7#>a1$PU&O^&VszpxLC%*!m-cO{B z-Y}rQr4$84(hvy#R69H{H zJ*O#uJh)TF6fbXy;fZkk%X=CjsTK}o5N1a`d7kgYYZLPxsHx%9*_XN8VWXEkVJZ%A z1A+5(B;0^{T4aPYr8%i@i32h)_)|q?9vws)r+=5u)1YNftF5mknwfd*%jXA2TeP}Z zQ!m?xJ3?9LpPM?_A3$hQ1QxNbR&}^m z!F999s?p^ak#C4NM_x2p9FoXWJ$>r?lJ)2bG)sX{gExgLA2s5RwHV!h6!C~d_H||J z>9{E{mEv{Z1z~65Vix@dqM4ZqiU|!)eWX$mwS5mLSufxbpBqqS!jShq1bmwCR6 z4uBri7ezMeS6ycaXPVu(i2up$L; zjpMtB`k~WaNrdgM_R=e#SN?Oa*u%nQy01?()h4A(jyfeNfx;5o+kX?maO4#1A^L}0 zYNyIh@QVXIFiS0*tE}2SWTrWNP3pH}1Vz1;E{@JbbgDFM-_Mky^7gH}LEhl~Ve5PexgbIyZ(IN%PqcaV@*_`ZFb=`EjspSz%5m2E34BVT)d=LGyHVz@-e%9Ova*{5@RD;7=Ebkc2GP%pIP^P7KzKapnh`UpH?@h z$RBpD*{b?vhohOKf-JG3?A|AX|2pQ?(>dwIbWhZ38GbTm4AImRNdv_&<99ySX;kJ| zo|5YgbHZC#HYgjBZrvGAT4NZYbp}qkVSa;C-LGsR26Co+i_HM&{awuO9l)Ml{G8zD zs$M8R`r+>PT#Rg!J(K6T4xHq7+tscU(}N$HY;Yz*cUObX7J7h0#u)S7b~t^Oj}TBF zuzsugnst;F#^1jm>22*AC$heublWtaQyM6RuaquFd8V#hJ60Z3j7@bAs&?dD#*>H0SJaDwp%U~27>zdtn+ z|8sZzklZy$%S|+^ie&P6++>zbrq&?+{Yy11Y>@_ce@vU4ZulS@6yziG6;iu3Iu`M= zf3rcWG<+3F`K|*(`0mE<$89F@jSq;j=W#E>(R}2drCB7D*0-|D;S;(;TwzIJkGs|q z2qH{m_zZ+el`b;Bv-#bQ>}*VPYC|7`rgBFf2oivXS^>v<&HHTypvd4|-zn|=h=TG{ z05TH2+{T%EnADO>3i|CB zCu60#qk`}GW{n4l-E$VrqgZGbI zbQW690KgZt4U3F^5@bdO1!xu~p@7Y~*_FfWg2CdvED5P5#w#V46LH`<&V0{t&Ml~4 zHNi7lIa+#i+^Z6EnxO7KJQw)wD)4~&S-Ki8)3=jpqxmx6c&zU&<&h%*c$I(5{1HZT zc9WE}ijcWJiVa^Q^xC|WX0habl89qycOyeViIbi(LFsEY_8a|+X^+%Qv+W4vzj>`y zpuRnjc-eHNkvXvI_f{=*FX=OKQzT?bck#2*qoKTHmDe>CDb&3AngA1O)1b}QJ1Tun z_<@yVEM>qG7664Pa@dzL@;DEh`#?yM+M|_fQS<7yv|i*pw)|Z8)9IR+QB7N3v3K(wv4OY*TXnH&X0nQB}?|h2XQeGL^q~N7N zDFa@x0E(UyN7k9g%IFq7Sf+EAfE#K%%#`)!90_)Dmy3Bll&e1vHQyPA87TaF(xbqMpDntVp?;8*$87STop$!EAnGhZ?>mqPJ(X zFsr336p3P{PpZCGn&^LP(JjnBbl_3P3Kcq+m}xVFMVr1zdCPJMDIV_ki#c=vvTwbU z*gKtfic&{<5ozL6Vfpx>o2Tts?3fkhWnJD&^$&+Mh5WGGyO7fG@6WDE`tEe(8<;+q z@Ld~g08XDzF8xtmpIj`#q^(Ty{Hq>t*v`pedHnuj(0%L(%sjkwp%s}wMd!a<*L~9T z9MM@s)Km~ogxlqEhIw5(lc46gCPsSosUFsgGDr8H{mj%OzJz{N#;bQ;KkV+ZWA1(9 zu0PXzyh+C<4OBYQ0v3z~Lr;=C@qmt8===Ov2lJ1=DeLfq*#jgT{YQCuwz?j{&3o_6 zsqp2Z_q-YWJg?C6=!Or|b@(zxTlg$ng2eUQzuC<+o)k<6^9ju_Z*#x+oioZ5T8Z_L zz9^A1h2eFS0O5muq8;LuDKwOv4A9pxmOjgb6L*i!-(0`Ie^d5Fsgspon%X|7 zC{RRXEmYn!5zP9XjG*{pLa)!2;PJB2<-tH@R7+E1cRo=Wz_5Ko8h8bB$QU%t9#vol zAoq?C$~~AsYC|AQQ)>>7BJ@{Cal)ZpqE=gjT+Juf!RD-;U0mbV1ED5PbvFD6M=qj1 zZ{QERT5@(&LQ~1X9xSf&@%r|3`S#ZCE=sWD`D4YQZ`MR`G&s>lN{y2+HqCfvgcw3E z-}Kp(dfGG?V|97kAHQX+OcKCZS`Q%}HD6u*e$~Ki&Vx53&FC!x94xJd4F2l^qQeFO z?&JdmgrdVjroKNJx64C!H&Vncr^w zzR#XI}Dn&o8jB~_YlVM^+#0W(G1LZH5K^|uYT@KSR z^Y5>^*Bc45E1({~EJB(t@4n9gb-eT#s@@7)J^^<_VV`Pm!h7av8XH6^5zO zOcQBhTGr;|MbRsgxCW69w{bl4EW#A~);L?d4*y#j8Ne=Z@fmJP0k4{_cQ~KA|Y#_#BuUiYx8y*za3_6Y}c=GSe7(2|KAfhdzud!Zq&}j)=o4 z7R|&&oX7~e@~HmyOOsCCwy`AR+deNjZ3bf6ijI_*tKP*_5JP3;0d;L_p(c>W1b%sG zJ*$wcO$ng^aW0E(5ldckV9unU7}OB7s?Wx(761?1^&8tA5y0_(ieV>(x-e@}1`lWC z-YH~G$D>#ud!SxK2_Iw{K%92=+{4yb-_XC>ji&j7)1ofp(OGa4jjF;Hd*`6YQL+Jf zffg+6CPc8F@EDPN{Kn96yip;?g@)qgkPo^nVKFqY?8!=h$G$V=<>%5J&iVjwR!7H0 z$@QL|_Q81I;Bnq8-5JyNRv$Y>`sWl{qhq>u+X|)@cMlsG!{*lu?*H`Tp|!uv z9oEPU1jUEj@ueBr}%Y)7Luyi)REaJV>eQ{+uy4uh0ep0){t;OU8D*RZ& zE-Z-&=BrWQLAD^A&qut&4{ZfhqK1ZQB0fACP)=zgx(0(o-`U62EzTkBkG@mXqbjXm z>w`HNeQM?Is&4xq@BB(K;wv5nI6EXas)XXAkUuf}5uSrZLYxRCQPefn-1^#OCd4aO zzF=dQ*CREEyWf@n6h7(uXLNgJIwGp#Xrsj6S<^bzQ7N0B0N{XlT;`=m9Olg<>KL}9 zlp>EKTx-h|%d1Ncqa=wnQEuE;sIO-f#%Bs?g4}&xS?$9MG?n$isHky0caj za8W+B^ERK#&h?(x)7LLpOqApV5F>sqB`sntV%SV>Q1;ax67qs+WcssfFeF3Xk=e4^ zjR2^(%K1oBq%0%Rf!y&WT;lu2Co(rHi|r1_uW)n{<7fGc-c=ft7Z0Q}r4W$o$@tQF#i?jDBwZ8h+=SC}3?anUp3mtRVv9l#H?-UD;HjTF zQ*>|}e=6gDrgI9p%c&4iMUkQa4zziS$bO&i#DI$Wu$7dz7-}XLk%!US^XUIFf2obO zFCTjVEtkvYSKWB;<0C;_B{HHs~ax_48^Cml*mjfBC5*7^HJZiLDir(3k&BerVIZF8zF;0q80eX8c zPN4tc+Dc5DqEAq$Y3B3R&XPZ=AQfFMXv#!RQnGecJONe0H;+!f^h5x0wS<+%;D}MpUbTNUBA}S2n&U59-_5HKr{L^jPsV8B^%NaH|tUr)mq=qCBv_- ziZ1xUp(ZzxUYTCF@C}To;u60?RIfTGS?#JnB8S8@j`TKPkAa)$My+6ziGaBcA@){d z91)%+v2_ba7gNecdj^8*I4#<11l!{XKl6s0zkXfJPxhP+@b+5ev{a>p*W-3*25c&} zmCf{g9mPWVQ$?Sp*4V|lT@~>RR)9iNdN^7KT@>*MU3&v^3e?=NTbG9!h6C|9zO097 zN{Qs6YwR-5$)~ z`b~qs`a1Dbx8P>%V=1XGjBptMf%P~sl1qbHVm1HYpY|-Z^Dar8^HqjIw}xaeRlsYa zJ_@Apy-??`gxPmb`m`0`z`#G7*_C}qiSZe~l2z65tE~IwMw$1|-u&t|z-8SxliH00 zlh1#kuqB56s+E&PWQ7Nz17?c}pN+A@-c^xLqh(j;mS|?>(Pf7(?qd z5q@jkc^nA&!K-}-1P=Ry0yyze0W!+h^iW}7jzC1{?|rEFFWbE^Yu7Y}t?jmP-D$f+ zmqFT7nTl0HL|4jwGm7w@a>9 zKD)V~+g~ysmei$OT5}%$&LK8?ib|8aY|>W3;P+0B;=oD=?1rg+PxKcP(d;OEzq1CKA&y#boc51P^ZJPPS)z5 zAZ)dd2$glGQXFj$`XBBJyl2y-aoBA8121JC9&~|_nY>nkmW>TLi%mWdn-^Jks-Jv| zSR*wij;A3Fcy8KsDjQ15?Z9oOj|Qw2;jgJiq>dxG(2I2RE- z$As!#zSFIskebqU2bnoM^N<4VWD2#>!;saPSsY8OaCCQqkCMdje$C?Sp%V}f2~tG5 z0whMYk6tcaABwu*x)ak@n4sMElGPX1_lmv@bgdI2jPdD|2-<~Jf`L`@>Lj7{<-uLQ zE3S_#3e10q-ra=vaDQ42QUY^@edh>tnTtpBiiDVUk5+Po@%RmuTntOlE29I4MeJI?;`7;{3e4Qst#i-RH6s;>e(Sc+ubF2_gwf5Qi%P!aa89fx6^{~A*&B4Q zKTF|Kx^NkiWx=RDhe<{PWXMQ;2)=SC=yZC&mh?T&CvFVz?5cW~ritRjG2?I0Av_cI z)=s!@MXpXbarYm>Kj0wOxl=eFMgSMc?62U#2gM^li@wKPK9^;;0_h7B>F>0>I3P`{ zr^ygPYp~WVm?Qbp6O3*O2)(`y)x>%ZXtztz zMAcwKDr=TCMY!S-MJ8|2MJCVNUBI0BkJV6?(!~W!_dC{TS=eh}t#X+2D>Kp&)ZN~q zvg!ogxUXu^y(P*;Q+y_rDoGeSCYxkaGPldDDx)k;ocJvvGO#1YKoQLHUf2h_pjm&1 zqh&!_KFH03FcJvSdfgUYMp=5EpigZ*8}7N_W%Ms^WSQ4hH`9>3061OEcxmf~TcYn5_oHtscWn zo5!ayj<_fZ)vHu3!A!7M;4y1QIr8YGy$P2qDD_4+T8^=^dB6uNsz|D>p~4pF3Nrb6 zcpRK*($<~JUqOya#M1=#IhOZ zG)W+rJS-x(6EoVz)P zsSo>JtnChdj9^);su%SkFG~_7JPM zEDz3gk2T7Y%x>1tWyia|op(ilEzvAujW?Xwlw>J6d7yEi8E zv30riR|a_MM%ZZX&n!qm0{2agq(s?x9E@=*tyT$nND+{Djpm7Rsy!+c$j+wqMwTOF zZL8BQ|I`<^bGW)5apO{lh(Asqen?_U`$_n0-Ob~Yd%^89oEe%9yGumQ_8Be+l2k+n zCxT%s?bMpv|AdWP7M1LQwLm|x+igA~;+iK-*+tClF&ueX_V}>=4gvZ01xpubQWXD_ zi?Un>&3=$fu)dgk-Z;0Ll}HK5_YM->l^Czrd0^cJ))(DwL2g3aZuza7ga9^|mT_70 z))}A}r1#-(9cxtn<9jGRwOB4hb9kK@YCgjfOM-90I$8@l=H^`K$cyhe2mTM|FY9vW znH~h)I<_aa#V1xmhk?Ng@$Jw-s%a!$BI4Us+Df+?J&gKAF-M`v}j`OWKP3>6`X`tEmhe#y*(Xm$_^Ybbs=%;L7h zp7q^C*qM}Krqsinq|WolR99>_!GL#Z71Hhz|IwQQv<>Ds09B?Je(lhI1(FInO8mc} zl$RyKCUmfku+Cd^8s0|t+e}5g7M{ZPJQH=UB3(~U&(w#Bz#@DTDHy>_UaS~AtN>4O zJ-I#U@R($fgupHebcpuEBX`SZ>kN!rW$#9>s{^3`86ZRQRtYTY)hiFm_9wU3c`SC8 z-5M%g)h}3Pt|wyj#F%}pGC@VL`9&>9P+_UbudCkS%y2w&*o})hBplrB*@Z?gel5q+ z%|*59(sR9GMk3xME}wd%&k?7~J)OL`rK#4d-haC7uaU8-L@?$K6(r<0e<;y83rK&` z3Q!1rD9WkcB8WBQ|WT|$u^lkr0UL4WH4EQTJyk@5gzHb18cOte4w zS`fLv8q;PvAZyY;*Go3Qw1~5#gP0D0ERla6M6#{; zr1l?bR}Nh+OC7)4bfAs(0ZD(axaw6j9v`^jh5>*Eo&$dAnt?c|Y*ckEORIiJXfGcM zEo`bmIq6rJm`XhkXR-^3d8^RTK2;nmVetHfUNugJG(4XLOu>HJA;0EWb~?&|0abr6 zxqVp@p=b3MN^|~?djPe!=eex(u!x>RYFAj|*T$cTi*Sd3Bme7Pri1tkK9N`KtRmXf zZYNBNtik97ct1R^vamQBfo9ZUR@k*LhIg8OR9d_{iv#t)LQV91^5}K5u{eyxwOFoU zHMVq$C>tfa@uNDW^_>EmO~WYQd(@!nKmAvSSIb&hPO|}g-3985t?|R&WZXvxS}Kt2i^eRe>WHb_;-K5cM4=@AN1>E&1c$k!w4O*oscx(f=<1K6l#8Exi)U(ZiZ zdr#YTP6?m1e1dOKysUjQ^>-MR={OuD00g6+(a^cvcmn#A_%Fh3Of%(qP5nvjS1=(> z|Ld8{u%(J}%2SY~+$4pjy{()5HN2MYUjg1X9umxOMFFPdM+IwOVEs4Z(olynvT%G) zt9|#VR}%O2@f6=+6uvbZv{3U)l;C{tuc zZ{K$rut=eS%3_~fQv^@$HV6#9)K9>|0qD$EV2$G^XUNBLM|5-ZmFF!KV)$4l^KVj@ zZ4fI}Knv*K%zPqK77}B-h_V{66VrmoZP2>@^euu8Rc}#qwRwt5uEBWcJJE5*5rT2t zA4Jpx`QQ~1Sh_n_a9x%Il!t1&B~J6p54zxAJx`REov${jeuL8h8x-z=?qwMAmPK5i z_*ES)BW(NZluu#Bmn1-NUKQip_X&_WzJy~J`WYxEJQ&Gu7DD< z&F9urE;}8S{x4{yB zaq~1Zrz%8)<`prSQv$eu5@1RY2WLu=waPTrn`WK%;G5(jt^FeM;gOdvXQjYhax~_> z{bS_`;t#$RYMu-;_Dd&o+LD<5Afg6v{NK?0d8dD5ohAN?QoocETBj?y{MB)jQ%UQ}#t3j&iL!qr@#6JEajR3@^k5wgLfI9S9dT2^f`2wd z%I#Q*@Ctk@w=(u)@QC}yBvUP&fFRR-uYKJ){Wp3&$s(o~W7OzgsUIPx0|ph2L1(r*_Pa@T@mcH^JxBjh09#fgo|W#gG7}|)k&uD1iZxb0 z@|Y)W79SKj9sS&EhmTD;uI#)FE6VwQ*YAr&foK$RI5H8_ripb$^=;U%gWbrrk4!5P zXDcyscEZoSH~n6VJu8$^6LE6)>+=o#Q-~*jmob^@191+Ot1w454e3)WMliLtY6~^w zW|n#R@~{5K#P+(w+XC%(+UcOrk|yzkEes=!qW%imu6>zjdb!B#`efaliKtN}_c!Jp zfyZa`n+Nx8;*AquvMT2;c8fnYszdDA*0(R`bsof1W<#O{v%O!1IO4WZe=>XBu_D%d zOwWDaEtX%@B>4V%f1+dKqcXT>m2!|&?}(GK8e&R=&w?V`*Vj)sCetWp9lr@@{xe6a zE)JL&;p}OnOO}Nw?vFyoccXT*z*?r}E8{uPtd;4<(hmX;d$rqJhEF}I+kD+m(ke;J z7Cm$W*CSdcD=RYEBhedg>tuT{PHqwCdDP*NkHv4rvQTXkzEn*Mb0oJz&+WfWIOS4@ zzpPJ|e%a-PIwOaOC7uQcHQ-q(SE(e@fj+7oC@34wzaBNaP;cw&gm{Z8yYX?V(lIv5 zKbg*zo1m5aGA4^lwJ|bAU=j3*d8S{vp!~fLFcK8s6%Ng55_qW_d*3R%e=34aDZPfD z&Le39j|ahp6E7B0*9OVdeMNrTErFatiE+=Z!XZ^tv0y%zZKXRTBuPyP&C{5(H?t)S zKV24_-TKpOmCPzU&by8R1Q5HY^@IDoeDA9MbgizgQ*F1Er~HVmvSU>vx}pZVQ&tr| zOtZl8vfY2#L<)gZ=ba&wG~EI*Vd?}lRMCf+!b5CDz$8~be-HKMo5omk$w7p4`Mym*IR8WiTz4^kKcUo^8Hkcsu14u z`Pkg`#-Y^A%CqJ0O@UF|caAulf68@(zhqp~YjzInh7qSN7Ov%Aj(Qz%{3zW|xubJ- ztNE_u_MO7Q_585r;xD?e=Er}@U1G@BKW5v$UM((eByhH2p!^g9W}99OD8VV@7d{#H zv)Eam+^K(5>-Ot~U!R$Um3prQmM)7DyK=iM%vy>BRX4#aH7*oCMmz07YB(EL!^%F7?CA#>zXqiYDhS;e?LYPTf(bte6B ztrfvDXYG*T;ExK-w?Knt{jNv)>KMk*sM^ngZ-WiUN;=0Ev^GIDMs=AyLg2V@3R z7ugNc45;4!RPxvzoT}3NCMeK$7j#q3r_xV(@t@OPRyoKBzHJ#IepkDsm$EJRxL)A* zf{_GQYttu^OXr$jHQn}zs$Eh|s|Z!r?Yi+bS-bi+PE*lH zo|6ztu6$r_?|B~S#m>imI!kQP9`6X426uHRri!wGcK;J;`%sFM(D#*Le~W*t2uH`Q z(HEO9-c_`mhA@4QhbW+tgtt9Pzx=_*3Kh~TB$SKmU4yx-Ay&)n%PZPKg#rD4H{%Ke zdMY@rf5EAFfqtrf?Vmk&N(_d-<=bvfOdPrYwY*;5%j@O6@O#Qj7LJTk-x3LN+dEKy+X z>~U8j3Ql`exr1jR>+S4nEy+4c2f{-Q!3_9)yY758tLGg7k^=nt<6h$YE$ltA+13S<}uOg#XHe6 zZHKdNsAnMQ_RIuB;mdoZ%RWpandzLR-BnjN2j@lkBbBd+?i ze*!5mC}!Qj(Q!rTu`KrRRqp22c=hF6<^v&iCDB`n7mHl;vdclcer%;{;=kA(PwdGG zdX#BWoC!leBC4);^J^tPkPbIe<)~nYb6R3u{HvC!NOQa?DC^Q`|_@ zcz;rk`a!4rSLAS>_=b@g?Yab4%=J3Cc7pRv8?_rHMl_aK*HSPU%0pG2Fyhef_biA!aW|-(( z*RIdG&Lmk(=(nk28Q1k1Oa$8Oa-phG%Mc6dT3>JIylcMMIc{&FsBYBD^n@#~>C?HG z*1&FpYVvXOU@~r2(BUa+KZv;tZ15#RewooEM0LFb>guQN;Z0EBFMFMZ=-m$a3;gVD z)2EBD4+*=6ZF?+)P`z@DOT;azK0Q4p4>NfwDR#Pd;no|{q_qB!zk1O8QojE;>zhPu z1Q=1z^0MYHo1*``H3ex|bW-Zy==5J4fE2;g6sq6YcXMYK5i|S^9(OSw#v!3^!EB<% zZF~J~CleS`V-peStyf*I%1^R88D;+8{{qN6-t!@gTARDg^w2`uSzFZbPQ!)q^oC}m zPo8VOQxq2BaIN`pAVFGu8!{p3}(+iZ`f4ck2ygVpEZMQW38nLpj3NQx+&sAkb8`}P3- zc>N*k6AG?r}bfO6_vccTuKX+*- z7W4Q#2``P0jIHYs)F>uG#AM#I6W2)!Nu2nD5{CRV_PmkDS2ditmbd#pggqEgAo%5oC?|CP zGa0CV)wA*ko!xC7pZYkqo{10CN_e00FX5SjWkI3?@XG}}bze!(&+k2$C-C`6temSk z_YyYpB^wh3woo`B zrMSTd4T?(X-jh`FeO76C(3xsOm9s2BP_b%ospg^!#*2*o9N;tf4(X9$qc_d(()yz5 zDk@1}u_Xd+86vy5RBs?LQCuYKCGPS;E4uFOi@V%1JTK&|eRf~lp$AV#;*#O}iRI2=i3rFL8{ zA^ptDZ0l6k-mq=hUJ0x$Y@J>UNfz~I5l63H(`~*v;qX`Z{zwsQQD-!wp0D&hyB8&Z z7$R07gIKGJ^%AvQ{4KM0edM39iFRx=P^6`!<1(s0t|JbB2tXs_B_IH9#ajH0C=-n+ z`nz`fKMBKLlf?2AC+|83M+0rqR%uhNGD;uKA6jOjp7YDe^4%0fRB<^bcjlS2KF~F; zu09wh1x0&4pG&76M;x8$u`b134t=dEPBn6PV|X29<#T4F1mxGF*HOgiWU8tN@cguI z_F@o+XL7FJztR63wC|j4x_DANzcX94r7Iz-O2x$({&qd*mdLG=-Rv)uZ}UlMR+F&q zU}=lkfb0p1>1Ho){o$@}mSKIV;h*$AND7~Dl)QzpFBlSM99Kx+F7GsVK5xcR? z_4Q(Z%cgk8ST}U;;=!LwyZVu^S$>B-Waeik%wzcKTIqeX=0FP(TGQ=nxi=dsS5BYF zl@?}NT!Y!Iyos^@v7XWXA{_bV~1lxz7gC?xuXxy0_?GaN!AhRRM5>)^t%&ODd;@HN5L{MD3 zc>i2keQZVm#?NrDwbfd}_<*5^U&w0zv~n-y8=GGN-!=_`FU^cM8oVCWRFxw?BM^YD zi=Vxz4q|jwPTg+?q7_XI)-S@gQkh>w0ZUB}a{^ z_i;`Y(~fvpI!vmW*A^|P7(6+@C4UeL2WATf{P1?H5rk`5{TL zcf!CgP6Mi{MvjZS)rfo7JLDZK7M7ANd$3`{j9baD*7{#Zu-33fOYUzjvtKzR2)_T1I1s7fe&z|=)QkX;=`zX8!Byw-veM#yr;|wjO^II>!B*B z0+w%;0(=*G3V@88t!}~zx)&do(uF=073Yeh*fEhZb3Vn>t!m(9p~Y_FdV3IgR)9eT z)~e9xpI%2deTWyHlXA(7srrfc_`7ACm!R>SoIgkuF8 z!wkOhrixFy9y@)GdxAntd!!7@=L_tFD2T5OdSUO)I%yj02le`qeQ=yKq$g^h)NG;# za(0J@#VBi^5YI|QI=rq{KlxwGabZJ0dKmfWDROkcM}lUN$@DV`K7fU?8CP2H23QPi zG?YF*=Vn=kTK*#Y_{AQN&oLju|0#E=fx%YVh>S{puu&K$b;BN*jIo@VYhqPiJPzzM>#kxoy0vW9i;ne2_BIG0zyRFp<3M(iY(%*M_>q0ulV2K}Tg zkG{EWKS{i%4DUuHi%DVKy%e+Q!~Uf`>>F6NgD{{I8~nO4!VgOvtFOc7(O)X`|7n*f zxBa4CJ-v9fUUH+`7sPVvpM_C*udZ@OTGTzx56QM5y~OlrZc&w9=)B?nmd@keRn+^= zvm~4sa5987LFDnU{(N|N zJAR8H@}p1fC+H(yTI4n#%~TbImMpuqYn9cQ<0QQ%=PzZItLkC*ef9WJUvfITKWh#D zc#__8`4am9%#NslIUw+<82#SR8AYG|woLfBg#!-&dqq}@P>|I0%lbdy0lSMmNe+}o zj0zZuFr6Wb?Y{Qy-S=|r`bdrDmhnmvkRnkdn`YCleU>Q$=je}LGhh>_QAj6aa_0Oc z%Swsmui;IRx7bN*=AAS@5yW&Y2hy;3&|HAiA8}!HT6!Z!RVn~MZg`RmI6&%#tBZDx zfD+y@Z~NWlk*4l13vmt3AK2wP!fQlnBbECL>?p)F?T)<`w&QN>cP_V>r7UTcsTaaP zTOb$f!P@zf$6>890NVKbIkG8rE?9!Y97sMSZjfF?A zYR8lp`LMoz~O?iaZN;gcX;LC-%Ia*R%A&SLx!YIf29?P+=XAAojK8!^OU*@?R&DK!#G_lsn!#;S375uZ&B0HH1|BO0R90$U>qs zSvHv>H~mAgNCcjo-e+;RjY6B9NCbQrZ|BHjTkehaU<9CSkdd>Vl*ifA2LNOP&R2Qdy3k3-TQ+ zbq=#vI43x`s=%~cGyN&y4Y!FxhwgDe@i6uv8^BLL&3z*SO=D0aLjih?gY4-9uWp5or)H+v~w6n5X#F-I52z=Z_p4JB(;M| zeaVFhuR2|3UD2MzVc~^nSoD2(dD#uL_1PdnIxeA{V5n`#3xf1Zx@4lw(DsQ&H$h zw#%3O<1173hjg2_nhKi!d1ej=h7y`hVjCNB6|HTnx>SWuCE-kgTnfT+YGX4_Lun({ zDv2`>d3vrS)tTf7ps_vvh!Cx^e1BFuWnEAh0(7fkNk|-3oU|iRWdsC6U)?Raft~HN z;^$U}vZK5O8|LV$>6X5T(uYkblv{zwPxnQBh(BQ5tA~J!vGiAMYP^_ki~pkIxDfOZ zUJDwq%O~WueeV6%uN<54&u*c&E4y431cklBNrb06zGOOy4XNT~JS-q(s6@)F@ovbe ze`fial(O4(-su%6@@1+V0MsdLLMyE8;)nou(7}czU(5ASaZYDT(kUZ0L(&g$nF^n9 z9-Pi`ZZLX&)^*M6As4_2Mmc9S7OT)F8KkL2NJ)KJcnCuWU=Wy402A&45#Q9Id~BBH z0cY*xlv!uXzKrXLH!xQu(OtJvEj|0-DmRj1vjFz{c*I4$Pe(+_V|^b~S!0xm{8lq= zZv)@NlcyL3Xdz+*|L137F7y6L-2VsrKw=q^S>F6i%<{Fr8zk06$Ay-(!L$fY@7mcng!2}L0t zgi|KxfB63Xtk_Q8#ZPipQ@!zgjdpEIbK_?q17Hoi4Eiyun$hrc>T(7pOLVLQE=lgGwA+A308p& z7@=09(|$>eLy5gLe{*|3b(M;1n;C^~v?o88jYib48eR4$QGsBFzd}3QuwO^_XE(=B zq+hMi0UFC|dB{LCwch7;zYT=NK})O%sgi0k#yV;My@24^B1+CuZmYOh0^b)5Ba_)) zC%i#_Iev&nsu%I|1N5=MVc#PrlunKAs&hY|3s5;@}`>sB>}gzxuB zB=2vrRyB3uiyW(hkDUNe1@&(b`;>ZvGgw|@s{zVC#_`HXIN_^J@Etb zA7A+F?ot37T{<-vTy8h&b3e+WKHE1oh;pUQrN4yRRrx?mT_9jRa2i4l1fUnLW^Cbl z!I1>VzyFe?VELWWhM?@?t-YPZkD-Qjo@bC2(o#ZtZmr{KZsdFWItV`rs$gp{724@C zL8K5}E0+DHcWcL^{BGei4>@J-3%a#$y6;I}=upc};-NDv-z#kPX26ylOpH)Ov1uU{ zkLj6oiH6l_s+B~_z;|Jc2oi?naS7#3H63~~lWj4rUnd=fCnKdkik<@R&kch9q##G{ z4u!%=rlM~Yp3jk*t8}1B`Sv6<%Z^}~1e@aq zg|JQ`QO2pSjAm-g*?IrNc$^~sIrNBo2$m|Sxanr?Mfs>2@Auu49 zGXlsS<9XS1&8h(dD*Hl&5HBDG!^pJ*lkau_Ur+7`7z;rcs$hT4we?3bT=7Fe<>{5( z2m2(c+hUz2BTHM8dCe*Z3XX&Av;b~a=$6EF>&^E8%nyxO@m_n!q&XD^A{SRjRZQ0L~qDeC=j&0$j6=LNIz@`ni^>ch|sv}^6 zlm>?28yPl@WmDPR?Y-A9X{U9Dv_IsbXJnzKCjkRksLOg#42uG2mE_acbTQ4)J|1V>%U@K(FP3AYhL0U zdeOCPN1qLv!|#c=p!_+%VNV(GHt`RuLRV^vz<5tt-r)yOK**kUWPspVAf|}ZL{LS= z@k(@@!P&W!>wwe`x{+GrFSWhHov7hu?{KuuT%kl#WO@*WX$i_@retlhQBj++SVNCx z5$78LxP>Z=^aJ)D280r_jj=zFfMJFXCIe^B{~V@d1rl_F(qo&AB4bC-vYL>x2jSKX zpuTG-6kgp3e^T&+dtV*i6a~)v@n?n*MffN59y}<0djUX zt27R+SE#hp8bzc#;rk$jw3r4)Q@eI$*`_)=Pvge8@8|8>H3X)<9YX6cXa=ii#Le;(qKm@%0-7$>2ShnYc`j#zJ7gu_FE^?uAkL|H)UIH#gPu^40!6^J=^ zr`}iwa^!4tzW~vOMZAaKF>*8A{^8m$i(VK)>?=#l`xrVe>wseSvM_aF zATNkY>kM_P3?1kE`uIq#mvr-wuTgUH0N<&JhF=(E9%^NS*HLm!4GZ4_XI zL=R5tlG5Mk_1rPfg)sk^llFuKPMPBhuU|L5q#yP_mzxp1o&pAzi-X31sgFpIHn@($ z_>=`AB5(8tP6p2zS5VEvH5J$M` z_much3>S7t3Yo`Yx!>83-hW9LYzDKP?mKdkD#QAK8*M((sx{eBQdrR<^3ZhFP81+& zBnJMUefQyNBji~$5d88Wfw1Lv59aJN9t2!pABLg;ewJ#LXL-10;QcJl+Y4Mtngb)k6JZlCf)3uD_u)J3sYyN;NN5hNbg$%W!i-GK%e&!Us)2IExWSss$YG(hm3kJ-h%yD z>8q^n$+4I(_y_mbT{du4P%h1j3oSpjhY97{+IZ`aA4ug!vNJ6*p?<2H(2w+GD3j$I z1TUXGyNzdf>_yB3grP~FZUs<2Quw;eEi*7s(-MiIkQ%@J^+WGdQvYSUN+TRiD-xto zJ=OUU+kxGYc!HCLNbCvR4lGTp~#L;DFzGd-#gJe*xf(P3hDQz|y)?b9mwU3WUVnpcqXM<@w%r-k*Wr^gzAv)8T^sqA=Ye z!7qy&exJmAcAt~CwS#@yNmjr8*T*!A6w4~E*ibaLRs0CFo(;R3=ODhDt6zWNodmo0 zXx&bT$6&+5c>a|WJ)F4G-^GjY0H#*tY=UNyYr_q5fsrcjk(c^~e*7Lf`!Jd`)p412 zn|^*hV= zFI4UbwA%X@smDd$cQOiMC%jfitTxTb+#`9`G=2rJDfK!E=5ra|So>lc{X1$~w28i+ z4p&cTGwZ#5VueiXS9O8#;RR$yg7tL9!^)Sz&pZYIzlSh}0}V{LxL$Cu%B4U5_}k}- zm~|CsD<076x@<>m=6w6N?WaThIBP`!u{-;WF)xc=2otx*lwf|5+MkdJePjh(B z9SH+%cHGCMAXNxB{_3^otDWdsV7Ob6n{0 z+&!(;iaHOX__5z_$Qk{%xYV%Ig@7iokGBwR`3642ZP#H#v9QGbWl8<|MS*=@qO@Uj z6+SZ_v9`1paUe5tFN~v(b#J3a_Lx0+;r9giZIx-A5TxdbG>xi#AZ5_z1V}B^n)sxT zz49}eK7EWb6wR!6-qQOrHQHkUvshvq%=G2d&@(#XM*Am1;WbnJ{X_!a{ZkphD$^TQ z=Iskb&}=lBm(RHiwJoGg`*NiQ6#RB$T#LF+>#ef;Jne&MxKPX!#r`&TVEFsp2jnNx>dClzpcPy&G&13a_<0qaR3i+k212~hoQ z8nMk{JP-t04I{GW5gUBqcJW-jSMrlw}>p)ptx?WKuCUV77taMiV zHok9V=6yv+Uts@fMY&A}amC=!Yj}eL@=e%XJ#%?agkt1jWF+10{(E9mHLDa>Ll7Vj zG=3cp%ljIB-6pC}6&`xJ*6WCP|IlglLWJ^?yviI8Ve)?V_i4%n;olzny62_`-|IGi z^=}p_O>Z8M;c4|RExu70E7ePW(HWVS&E$+LL6xSQgB`QfMQJ|4pCTFowA39p5P-|$ zUtM_H2HnP8_RoS~Vwk(FhbG zH41licj%=0a;Ln2STFBvU}Ne&O&%8bYKj!h1FA#sNM`232fX|U3QPp#3C?mN2;hE9 z;)!@5ixSPl<89^7gwhHc2YAX1KJK$#*3`KOMIQ253q7-*RJ5k)zp9GBO|Ga~X*^}US5oN@aG&waHV%vi~r{t^`ptTxb zL}q1W8S7*>7oWwvgV4uFLZ(@k`R*=LO_|Gu`prs~!WQXj-NLIa^2(7IHg>BG^N zc|i{-^=&Cek9dkJFQys|sjG9i>LLz|;yCv{^1i%c*h>8zF91kLvS9HBQi~ZU!JL`B zK8N+U0fr1*6??Ium)AF!6tc1eGhXIYL6IRT7rmKp7+>?%5Pa6zC5)KY$ycF0ZJ`G5nEQDG100U-jLkH8^UE4g6wq?sg%pP=-$&G#bcN`^?w3a6 z((s$6eRKcSEIslW-kk5Qi|5Mg-(xdLF}PxxVh$PuO}#aR6pW1kV4Af!Bqh*btXNNZ z>-4(IUl+L4dw+3LcpGut=qB45O+W)Q5?*zZ2A6rJcg`qkSvWA!j^r2mqKuCm6`Py? z@^T#Ux04HemPGd!Hs7NkZdVn1}8_j`o?)*OKZGS!`ff)gF zG?v-lj$wWNWCcw2Mg2o18D~1?3_b0XzdiKBNkYSDpcv@&kp0POmweJE2ZkIQ3B!a! zIgIoE+Xv?;34kyo^QYjZk+tEqZvq^#QG(OzX4~X+KtsoQoddTWUR(yo8R+ObEF1j<-syWOb>)JQ&Zbdu(sctU%Mt zW&YR0{ttY2TTXYZ?~WNU&cES1Z2q(7SrWDh``!J(JM+Nk$!hu&Y;(7E`ZNKTe0w+% zJc?Qnw2B+%UR}0;cB0Rufa(7-3FF}?629@LgTiEC&2uyL6NxexOp?AKT^aAx3gi(W zao>r>MPw0eQ3>IV02uLsC@>yK_epX6GRg4{NEL2wPPF9=*L2RV3yyK8DhuEK>rmmV z`&Q~#c`lgR&93TdOCja|ewOXmPNRh7!&dMT(1ett#iDr8HZW~VqWW@7fe9B6;7S+? zbC`d4@MEau&mKlOPKd>*10q0c{~^baw6!a*w^sY#0Xim{oOsiXiDOhbG&kl3c$$n1 zMRrD83&QucDSEcV*7LIp8VTA@F<%qe+_c`L;6on(>SjAU^}5c9!BCffT>$VQhe=)z z8(=Ej{5>jhmjB3{xDfj2R@VmHQ!CqjlO4KnuOmvHy3K#po$yp_V;p_MKjh1`(rzj6 zHW956k1yvntz{_g?Xbs`avK(IjlTnsu%htO;D7 z?J#x^EzuvVn&NA=!MEj7cwe5A-Z$Zk2LBZH$~%E* zf`((xH0?`}hs|HA%mtwfOEsZJxxrennkTYcwP#FKO5%Lpc^JXhSpV|ZH$Wr;`}`_( zIP==gd3LYyVtwD|*ZJGi{7~x8{=^bGVqu0RJ`n_BZH9+}kz%-4ZRsImi@rx%=ZEKs zcPnUXo6hbJV>fH;@1|bAHIe0ijYI*&kdT|HkDS$9No9 zCHo=*HWb~U+Dtzxr+Esao}6@|;Pf+E$ay0$kQp#s{wlw+7aIKbMdf`OqhoG*;Tco0 zjrP}VQG#Y2cJuqoJg&5({)S(BA}q9T1lGeWRyu=Je|)I!6a+aj!IP^1({)ZYe&x6w zt3a)Dq^TB+A7CdB0-}#z2Ur$W&h3YVw8==!xONy$uQmDWh-@15iEOt!q2m&?ZLA|w z8loSb(0}7y6Xu0?M5Uf4>VZGluB`wMf2oh;m)ghxVda>3m}4%V)r^0nVQ5V6f3>*) z0&VN!N0~GC^P}vj$`EDMZEmVV;N&RISY2C;$0;2(<{Lt&PKzqRByQdiEHGAbwtbS zPj`Da5%U6k1oEtVzI}QNw;!hT6F+~|@=c@$C4NtO@=xgP?|5MyZAyuCzcvq4rdAv@C06%gZ`9%I);R6UGiGJobfux+<0DLS&|MSG4UH z_~o{^^9>ixMg~mY!-@Fai{xaE4^;qy9iZN15Gbn5ZqHWf>Jc5Rv6(#n8`1NcCsdmG zab*dSXVPaE?)wCalD;$ivF%@nB#7D`@YG04p6ed9m}4iJW|pfVMLE<-c{=-8$e?cH zUdU#mCj4gb zZKA^b9p*9S(}8@tw~1RNPHr7tQr;P+-)D8|sq=*o)G%RGqt> zzP5yf`pVxb)I51D_G~Xp^GNK zVI6sAX)a9s)e{8N3?35YA6aQTXuyszK3ah~CemzA&CII#8F&F#KN41~8I^&_%}6MCNb{W87qAF`zj_Y^szhb> z3p3}KbOxotY|(lD=;)`fYE_*{S}x;f^SW#)SU&5X#o|-R|trpa|L5PS5aa0 zTHw8%SDSVtU4?vyrhnq+^@dgFS)|(y{~(4j%3UEiO-rBM9%`)8(dh33pMLiuurNY# z#10AsQ7%*0Cu_DSAU}P;X(JwA64~Q_^R%d_zSm^6Aux?Pn70PM>9EvLeOX z&w9c)pGmcL22;MO3C_B>=NC0RJpMp8?#ZUf=GWRvy z6RHq3B}=MGVg?9@iKFBpsvnkVh3{Vpp=`CcD=u~@ql{my|6?3ssi3mCOPnjI&E}VC zc@X+Yl>;;DNo0W0`0th!X{?luDhOC{E8N=?!w}K1{V=)+1={m(f`Oc|N=07>}3;z{-(A zm{JL=j?Sro5iecmE2-pWlRf(r%|HEQ7kgwQ9+kt=NBhtQI7OwcZ#3%$Uf%^r2nhjY zoQ08MfC%_X{O9~WcirMZMhn#z^ux4Erx-tf-6bHD)9eH&^L>^jvAd^9A^DCDs?0;k zkm7LE*KjP6`2d17MrQaaLqd_Rka}J$csvUec#hw78<=s(hyR>065~YCVCA9+#Q+; za(*L0IEw!r5P|@-;x33L$Lv9 zcuN8YG&g{<(SeJG18~(b!5yywSqQiLAX0;---;}mF5&b4lg|T?LwKREa{9YX_-zL@ZE?Zqi@HxK^2KO1>0LATu{te=T zprmHtY)bDVfxI1S}KBE7V zznP7KQ8HekWU#W6mw`dr-boV}pMQR==&5=Q5T=_q091jfc;R*jX#&=MQ%~@E@9^?`$v48ks<>(fI(F6L(5ppKy|$HWng*bKOb(4|cMUB&z$#ob#XV z5-mg)gmFIybZf=znm3ZPyUO^GJfxt0kmHjaTZ|sthsxXw&}Y)fOUSg=JhRSR^UjZ- zhqqb}Wsyw4zdnj6@#BAJa#-PdI4_dgafFXh85DsEQ_cT+5)XpZq$fZlBA_9UsE9r6 zEFec5?uqN@QhJ^IzwZrwl-5J`CmVPv{(YDTqEqWR^dI;5hXc~cxP%B3v&~s0`Ct89 z@S`i~a^c%V^N81dDT*ItFS*&IN;@O$EgzX0e7x&}TD=!zS}hTpezBLS>mdX(5< z)8DEI(-o_D)c-UX@dA1MuJ*yc>Hf4|`*B2S_O>w*-tbUwtiu`;W(Ud{HTty@(&x(T(F&;M zJ=?H>6`B7nf-90e8V`WSVp|0oEKB-P2M{}4ZDawzvM&a!y>`Y#jCsD%T_l``@ah(I2nJs~Q|%uSKu@k!m~*8B*IoA{*TgtF<(5sHCGG;n@NE%~Xt(G$^&<87u;}Na zx-8cq0g`uA(&RBFo=-4Y1GUZ<``Zw{xL4jfHkZw~%~wvtGueszcXt)_QwH8g!; z%s&3kSa~R$dO$-%L-)c@_hi7&>{6L_M>OZFkUQu;{sL_bUMStNrt{{&O(Wn~*zPOk zB>dnfszb29NSTf2pqIs68k|p-UrSrxgLHqi?3N-UFa!LHy9n1)=s>`yS+J{MEzS@ zNlfGtpma7kG&LR3JE@wB%rFA*h~~KitlO=IP)ZjN6dQLM6qsry zHkB#cyNh#n`)}bCrN1My*;k)^@>e4gJ`LJK?2)Pwp?4Tl4)4FA0(tvY+#1jOUM)xw zlMz4x-f@g^+yKUN`?Vu)|AwujArnM~Pa@y*Q9S8eS(u{-S%(Z5=R~pRl5ZGDjdqH% zC8rW&{##wOpU_oTIG4WXMk4&%2t1;lWcW5&!yxmOT*!hBcKyTqEcNoO+R2;Q?Yj+W z1-Y4?59fijz4(MIDwGe4-baYf08UCs;r|YefD-Md2ST;=cxwpgW=tR76-dQVAhn^= zG9Wk5lQk%jIR@KNU!UMp6@BfU;r+;y4VQ)D2!Il9HX%yW-9nOzV+m$YKzVaO`B8S7t z$!S2Mz`xw>V(RjE`0>bQp<0y&h~Y=M#jpy!#=dE>`=e_AjSZq6u!Dy1xJf~-7|0F! zPR9|n`e_7D2DIV2H(CESQ}hA>U>n|6`%z?YKEA~)BOVY%y=jPV zT=44R!L?J)736X#csn|lfBJ)o8ixaZclguWgrGO<`TN2FMfO}7;5}d+BlK0yTSH3* z4!=;5rOh85&2|x=46hkNaz?)U8&=bcfh=N_#8BNpZ2v$aVBo;sk^*X`v;4-LU;D>! zM*h12MxXIQy)SfAqE4;jY)wgnppazZkdNNVVF;(PLf^qK$FgY9+VFyBKE7UC|f z`R|?&egV11K3s$rJ6!GvoeW=jV*!-e(wA;x(2=d0E_e_%0x--0o8#~m^H1%AH5Z^B zn!TNPn927*bvaf0pt}zhK0o^V@WlGwwKo(*nQ|Q~4_;>~-8y20`HP>@UJa)3nEnGG z5Hwhs|FcmFG16ZVNb5hL`2Gc1{zWIMM{_OiKewV!hCi}U!VuE?s9wU-QbZ!)+Y^tS zGzp5OSi5iq6hmEr$w}&9DFgoB+i*`q`8TBi^MVS{SKEb8Aw%@K7@XCo(De2A`6%mf&a2#~y1N)+kJLD$1HCP!22)(U}xo2|j?WRzt(11j8Z_*v;P$R+Ug*Gy3VxV4K; zGGUGabnW*`Z}~`ydXL-l9e=GC$pY#z|63vy>E*m=$=j}iWP{sRTh0%H54`t>2xYH% zsk+M&u&pNgMCM@3e)Xc?jBWX-TIR_cQ1Z!RW7!B zBjZX=+^3}?SE)B+$EP+0oi1Fp5blDT?*}nsP>filqXH{ms zxU<$hetC`u)Wi+x|EKL-`y^#aQX+sDYIa{M;V%LqLrOk~lR>u0Q!+pyQSU4zY`?E^ z|5@)C)w6G_=i5YYC5SE_u(7hDNYr}uKT|@DSqF%S++lTIbIk^$a>{~0IH8KNFEy%+ zW#$&!ynpgNJh>6uR~?2c)ZMW+h0OKu231(7L_vETPaR+(P)Zy%0~yGm>E9?@@x!Jy z3PYgS}Q@b}x}E#F27@F+j}0=&Ql4gES&f8acMrPAVlVs9$97`FR))R5wI zc&}KFI1UIewh>3PkhnB7u zS3AT8_*|nexznG|Z*DU0c!K@jsI4J)5#DyNi#|e#`l1Vv1`1)*NVcy0LZ``aL0n8B zecupJ(rhq3u8bW0NIRhKYq$v1li+jp*4hfAd&wxYDE8vn1TQ7S@bTM|I2Ob z8vMOIxA7&_j{AKmD+O@EyXT`|dElt0pED^@IV0m)RPBUs*5jW60>>w1!@_G3aBKzG z_f(KfAPBk}-jQtR*Sroq!*3rbQ_m27e+YdzQjUb<_*k8vc_C)y!@cj5E>NxUhPu&g z@Z2<~esU`)ih+4opWe+K7sbN9n*9@n>#@n3*o z?xoROgDuvhq>jJ;Ve{6i<3roQNfgo5^4Q4(|GNExO2Dr7GjgA2zWuKp_K)K0R(6lv z!l$!zW-+T6mb3gQaAFviTQi{|*t%>{(mhTdy+y;Re4qT@kccy#{b z&zWy~kLO@>*WPj2k#H)|7L&gAJ37DmHQAme#@m;(Y8Nu^`D5vf8sZFW#+lA2!HK=( zJ)#hO6JD*`o~&c*&46d}g=Qj@SsoB5ikC z^1V8E+&<-OzuS_C`p5<<(A6fB`LXT(!kV^0_~hL6PpW4={l%|#xgdh?5EIk~lu8{D z2hiyhv3Yxij_#$Wu>P@7SYsl`-~3;}Ktx{34_NL^Kwin&=?!HDv3elQDbcU*qyYpN z(#yw~f1vFGK-t%CC-qa-4FYHbA^h>bag-I&*qaxwn?Qv|idE$<>1H|Gr6JtUu(he2$eg!N z@HTF@dG1)*y;4fxe)4_ZkpaBHH9hXp9p4|gLrRQyuevRd@gSS}JhRnWqrvm|U@>qM z=yl7RQROTKwQtzP3!zUF)_6Ld#NGA6v~2{J9Dd`h6{%+XsU#qGLh%`fB1Hc?wfayK zN`H4BpDp)npVQuu$DVW1qsBS&AJ2eP%6Qw>;k{)Z$8%HL=Q4(a$Ng2_vHw&vA!1L+9zc8vaX2GtqJ{L-;gvF0IR$em zMQ8@{Qp3+3Quk)TJ$?I<8KmwzD*7#(q<@Mc`dchngW}cRG14(Z6K7{T|LhFXwhqUQ;BET;cYqPcAcMgt6M$V9$(?jHo@Sud$an$U&5F zZ1QNh^ztt)E*d#Ij;<43oSKKnd+WNr$_r}+s_O_x6DZSB10*5Q{ourqq>mTl| zx4y^(cy+9;t@R=*j>3_dmm_m)$k$#937V(sllby&5)Xex^UD-|m|q<(jEd#@DV(of zAd7sSdmS*zUDqJ9|K%O2J2OfdUiK{{b{PCy)pi<;hp~7v1CQj&4-10 zgO<3dqhYH1#-Fa}Q{pjql5>>P6gZH21zLfxZ4$SK4T@7b!|`nWF9b*84Bq8&Eht;9 z*P72x&NUCZ7*@B$`FtE=hz5b}S`|c6Ey+j@D1ZibjJaRlR;{cxAWv z?Nqa>QqV*H-*zzaPvpLMHt~nl(x6?vrPpR?zn7~wow?oj*1TKmx4j71>$hvtC$DLD zUrz0^tiP0792U&dxJxNv@r}Elsjn^aSLUu=9#mD{&9n8|ayIL$!H3s>%KEvbchBFW z%cd?VU83mGF#Dar9*s~w&AnmQRQIOvR+uWsuZ?+|a=TzApXO@q^(r%8=}iv#wCnFq z=K9}JbqU@k99Q%j-}NNk+qLCP)jXfmOO|)@?mHcnynd6({mJisP1_}u7k)|eYHXWK z63eQ)E$ufFi!3CWUY2gw%e>omCv}qEX66aH-k&35f9`Q@Us|NPetVqe8=dX*VxJdn ze`q7b=Dn(UA(2sf&g)cOmQFhNJ#<-aMELJZbA#@to>25@kbW<)&!X01 z%NMJt>1ST)tyX)h@?`DxhbgCHr>S4wv}WC&Nw-!{+Z7$2D}74QAcXTvip=M0%Tp_N zor=k`)t|ra^ySr-+(|R9mB(E=`MX#y(wSw)$!iymzB;^c*>%&^*7HxTnRga=soSZT zdDl+9s;r!v8hk6POtzBaig4pRp7eWF(<8gufvNHPu6xs-=e{;mnHzJyGKE+8L0j}; z@%8-e^UCL5HhMiR>sD3Rve&yVZ#{Q1*CO8c+qSr^Z#CN;)(X5>tGG5yUw3<+CfhaL z%bP;hZ?jvgJU67BWyiy74_)6r)_nSxttxn0`0?HE^5(uydHVgP+HE$V?Lv)Leti43 zWA|;f-RqX``95>)^P-fw!Vi{3KNsII-*5f){gdxqd%gVdB1sOBNe=nEW%;i~g_P8J w!5uhoe-Jcg1nPN%MiEAtgE$;km@@t6ukO)1^!cY^83Pb_y85}Sb4q9e0FIsP9{>OV literal 14800 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 vNcLuCM3Rj?iBw@67X_p?_CF#jIyB-s Date: Tue, 11 Jul 2023 14:39:19 -0400 Subject: [PATCH 31/45] Typography improvments (#459) * fix: use `MacosColors.labelColor` as default typography color * chore: more accurate job step names for DCM action * fix gallery typography bold values * feat: Add `MacosFontWeight` so that Apple specific weights can be used Also updates the gallery to use the new weights --- .github/workflows/dart_code_metrics.yaml | 4 +- CHANGELOG.md | 7 + example/lib/main.dart | 6 + example/lib/pages/typography_page.dart | 394 +++++++++++++++++++++++ example/pubspec.lock | 2 +- lib/src/selectors/date_picker.dart | 4 +- lib/src/theme/macos_theme.dart | 5 +- lib/src/theme/typography.dart | 149 ++++++++- pubspec.yaml | 2 +- 9 files changed, 562 insertions(+), 11 deletions(-) create mode 100644 example/lib/pages/typography_page.dart diff --git a/.github/workflows/dart_code_metrics.yaml b/.github/workflows/dart_code_metrics.yaml index 1dd9587a..03336a6a 100644 --- a/.github/workflows/dart_code_metrics.yaml +++ b/.github/workflows/dart_code_metrics.yaml @@ -8,12 +8,12 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Run Dart Code Metrics + - name: Install Flutter uses: subosito/flutter-action@v2 with: channel: stable - - name: Install dependencies + - name: Set Up DCM run: flutter pub get - uses: CQLabs/setup-dcm@v1.0.0 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 345ceb9c..10ee2492 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.8] +✨ New ✨ +* `MacosFontWeight` allows using Apple-specific font weights like `w510`, `w590`, and `w860`. + +🛠️ Fixed 🛠️ +* `MacosTypography.black` and `MacosTypography.white` now conform to specification by using `MacosColors.labelColor` + ## [2.0.0-beta.7] ✨ New ✨ * You can now call `MacosTypography.of(context)` as a shorthand for retrieving the typography used in your `MacosTheme`. diff --git a/example/lib/main.dart b/example/lib/main.dart index 3b7d2ed2..04f48411 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -8,6 +8,7 @@ import 'package:example/pages/selectors_page.dart'; import 'package:example/pages/sliver_toolbar_page.dart'; import 'package:example/pages/tabview_page.dart'; import 'package:example/pages/toolbar_page.dart'; +import 'package:example/pages/typography_page.dart'; import 'package:flutter/cupertino.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:provider/provider.dart'; @@ -71,6 +72,7 @@ class _WidgetGalleryState extends State { (_) => const TabViewPage(), (_) => const ResizablePanePage(), (_) => const SelectorsPage(), + (_) => const TypographyPage(), ]; @override @@ -255,6 +257,10 @@ class _WidgetGalleryState extends State { ), label: Text('Selectors'), ), + SidebarItem( + leading: MacosIcon(CupertinoIcons.textformat_size), + label: Text('Typography'), + ), ], ); }, diff --git a/example/lib/pages/typography_page.dart b/example/lib/pages/typography_page.dart new file mode 100644 index 00000000..3779cb57 --- /dev/null +++ b/example/lib/pages/typography_page.dart @@ -0,0 +1,394 @@ +import 'package:flutter/cupertino.dart'; +import 'package:macos_ui/macos_ui.dart'; + +class TypographyPage extends StatelessWidget { + const TypographyPage({super.key}); + + @override + Widget build(BuildContext context) { + final typography = MacosTypography.of(context); + final secondaryTypography = MacosTypography( + color: MacosTheme.brightnessOf(context).isDark + ? MacosColors.secondaryLabelColor.darkColor + : MacosColors.secondaryLabelColor, + ); + final tertiaryTypography = MacosTypography( + color: MacosTheme.brightnessOf(context).isDark + ? MacosColors.tertiaryLabelColor.darkColor + : MacosColors.tertiaryLabelColor, + ); + + return MacosScaffold( + toolBar: const ToolBar( + title: Text('Typography'), + ), + children: [ + ContentArea( + builder: (context, scrollController) { + return ListView( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Label Color'), + const SizedBox(height: 42.0), + Text('LargeTitle', style: typography.largeTitle), + const SizedBox(height: 8.0), + Text( + 'LargeTitle', + style: typography.largeTitle + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text('Title1', style: typography.title1), + const SizedBox(height: 8.0), + Text( + 'Title1', + style: typography.title1 + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text('Title2', style: typography.title2), + const SizedBox(height: 8.0), + Text( + 'Title2', + style: typography.title2 + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text('Title3', style: typography.title3), + const SizedBox(height: 8.0), + Text( + 'Title3', + style: typography.title3 + .copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(height: 24.0), + Text('Headline', style: typography.headline), + const SizedBox(height: 8.0), + Text( + 'Headline', + style: typography.headline + .copyWith(fontWeight: MacosFontWeight.w860), + ), + const SizedBox(height: 24.0), + Text('Body', style: typography.body), + const SizedBox(height: 8.0), + Text( + 'Body', + style: typography.body + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text('Callout', style: typography.callout), + const SizedBox(height: 8.0), + Text( + 'Callout', + style: typography.callout + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text('Subheadline', style: typography.subheadline), + const SizedBox(height: 8.0), + Text( + 'Subheadline', + style: typography.subheadline + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text('Footnote', style: typography.subheadline), + const SizedBox(height: 8.0), + Text( + 'Footnote', + style: typography.subheadline + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text('Caption1', style: typography.caption1), + const SizedBox(height: 8.0), + Text( + 'Caption1', + style: typography.caption1 + .copyWith(fontWeight: MacosFontWeight.w510), + ), + const SizedBox(height: 24.0), + Text('Caption2', style: typography.caption2), + const SizedBox(height: 8.0), + Text( + 'Caption2', + style: typography.caption2 + .copyWith(fontWeight: MacosFontWeight.w590), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Secondary Label Color'), + const SizedBox(height: 42.0), + Text( + 'LargeTitle', + style: secondaryTypography.largeTitle, + ), + const SizedBox(height: 8.0), + Text( + 'LargeTitle', + style: secondaryTypography.largeTitle + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text( + 'Title1', + style: secondaryTypography.title1, + ), + const SizedBox(height: 8.0), + Text( + 'Title1', + style: secondaryTypography.title1 + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text( + 'Title2', + style: secondaryTypography.title2, + ), + const SizedBox(height: 8.0), + Text( + 'Title2', + style: secondaryTypography.title2 + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text( + 'Title3', + style: secondaryTypography.title3, + ), + const SizedBox(height: 8.0), + Text( + 'Title3', + style: secondaryTypography.title3 + .copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(height: 24.0), + Text( + 'Headline', + style: secondaryTypography.headline, + ), + const SizedBox(height: 8.0), + Text( + 'Headline', + style: secondaryTypography.headline + .copyWith(fontWeight: MacosFontWeight.w860), + ), + const SizedBox(height: 24.0), + Text( + 'Body', + style: secondaryTypography.body, + ), + const SizedBox(height: 8.0), + Text( + 'Body', + style: secondaryTypography.body + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text( + 'Callout', + style: secondaryTypography.callout, + ), + const SizedBox(height: 8.0), + Text( + 'Callout', + style: secondaryTypography.callout + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text( + 'Subheadline', + style: secondaryTypography.subheadline, + ), + const SizedBox(height: 8.0), + Text( + 'Subheadline', + style: secondaryTypography.subheadline + .copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(height: 24.0), + Text( + 'Footnote', + style: secondaryTypography.footnote, + ), + const SizedBox(height: 8.0), + Text( + 'Footnote', + style: secondaryTypography.footnote + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text( + 'Caption1', + style: secondaryTypography.caption1, + ), + const SizedBox(height: 8.0), + Text( + 'Caption1', + style: secondaryTypography.caption1 + .copyWith(fontWeight: MacosFontWeight.w510), + ), + const SizedBox(height: 24.0), + Text( + 'Caption2', + style: secondaryTypography.caption2, + ), + const SizedBox(height: 8.0), + Text( + 'Caption2', + style: secondaryTypography.caption2 + .copyWith(fontWeight: MacosFontWeight.w590), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Tertiary Label Color'), + const SizedBox(height: 42.0), + Text( + 'LargeTitle', + style: tertiaryTypography.largeTitle, + ), + const SizedBox(height: 8.0), + Text( + 'LargeTitle', + style: tertiaryTypography.largeTitle + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text( + 'Title1', + style: tertiaryTypography.title1, + ), + const SizedBox(height: 8.0), + Text( + 'Title1', + style: tertiaryTypography.title1 + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text( + 'Title2', + style: tertiaryTypography.title2, + ), + const SizedBox(height: 8.0), + Text( + 'Title2', + style: tertiaryTypography.title2 + .copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 24.0), + Text( + 'Title3', + style: tertiaryTypography.title3, + ), + const SizedBox(height: 8.0), + Text( + 'Title3', + style: tertiaryTypography.title3 + .copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(height: 24.0), + Text( + 'Headline', + style: tertiaryTypography.headline, + ), + const SizedBox(height: 8.0), + Text( + 'Headline', + style: tertiaryTypography.headline + .copyWith(fontWeight: MacosFontWeight.w860), + ), + const SizedBox(height: 24.0), + Text( + 'Body', + style: tertiaryTypography.body, + ), + const SizedBox(height: 8.0), + Text( + 'Body', + style: tertiaryTypography.body + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text( + 'Callout', + style: tertiaryTypography.callout, + ), + const SizedBox(height: 8.0), + Text( + 'Callout', + style: tertiaryTypography.callout + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text( + 'Subheadline', + style: tertiaryTypography.subheadline, + ), + const SizedBox(height: 8.0), + Text( + 'Subheadline', + style: tertiaryTypography.subheadline + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text( + 'Footnote', + style: tertiaryTypography.footnote, + ), + const SizedBox(height: 8.0), + Text( + 'Footnote', + style: tertiaryTypography.footnote + .copyWith(fontWeight: MacosFontWeight.w590), + ), + const SizedBox(height: 24.0), + Text( + 'Caption1', + style: tertiaryTypography.caption1, + ), + const SizedBox(height: 8.0), + Text( + 'Caption1', + style: tertiaryTypography.caption1 + .copyWith(fontWeight: MacosFontWeight.w510), + ), + const SizedBox(height: 24.0), + Text( + 'Caption2', + style: tertiaryTypography.caption2, + ), + const SizedBox(height: 8.0), + Text( + 'Caption2', + style: tertiaryTypography.caption2 + .copyWith(fontWeight: MacosFontWeight.w590), + ), + ], + ), + ], + ), + ), + ], + ); + }, + ), + ], + ); + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock index 3f05abc6..6ef37937 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -145,7 +145,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.7" + version: "2.0.0-beta.8" macos_window_utils: dependency: transitive description: diff --git a/lib/src/selectors/date_picker.dart b/lib/src/selectors/date_picker.dart index a3e20fc0..2b3cd9b0 100644 --- a/lib/src/selectors/date_picker.dart +++ b/lib/src/selectors/date_picker.dart @@ -107,9 +107,9 @@ class MacosDatePicker extends StatefulWidget { /// Allows for changing the order of day headers in the graphical Date Picker /// to Mo, Tu, We, Th, Fr, Sa, Su. - /// + /// /// This is useful for internationalization purposes, as many countries begin their weeks on Mondays. - /// + /// /// Defaults to `false`. final bool? startWeekOnMonday; diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index 6de99369..0cae2b9a 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -224,9 +224,8 @@ class MacosThemeData with Diagnosticable { ); pushButtonTheme ??= PushButtonThemeData( color: primaryColor, - secondaryColor: isDark - ? const Color.fromRGBO(110, 109, 112, 1.0) - : MacosColors.white, + secondaryColor: + isDark ? const Color.fromRGBO(110, 109, 112, 1.0) : MacosColors.white, disabledColor: isDark ? const Color.fromRGBO(255, 255, 255, 0.1) : const Color.fromRGBO(244, 245, 245, 1.0), diff --git a/lib/src/theme/typography.dart b/lib/src/theme/typography.dart index c5a9eefd..e600a3e0 100644 --- a/lib/src/theme/typography.dart +++ b/lib/src/theme/typography.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; +import 'package:macos_ui/src/theme/macos_colors.dart'; import 'package:macos_ui/src/theme/macos_theme.dart'; const _kDefaultFontFamily = '.AppleSystemUIFont'; @@ -142,9 +143,9 @@ class MacosTypography with Diagnosticable { }); static final MacosTypography black = - MacosTypography(color: CupertinoColors.black); + MacosTypography(color: MacosColors.labelColor.color); static final MacosTypography white = - MacosTypography(color: CupertinoColors.white); + MacosTypography(color: MacosColors.labelColor.darkColor); /// Style used for body text. final TextStyle body; @@ -297,3 +298,147 @@ class MacosTypography with Diagnosticable { )); } } + +/// The thickness of the glyphs used to draw the text. +/// +/// Implements [FontWeight] in order to provide the following custom weight +/// values that Apple use in some of their text styles: +/// * [w510] +/// * [w590] +/// * [w860] +/// +/// Reference: +/// * [macOS Sonoma Figma Kit](https://www.figma.com/file/IX6ph2VWrJiRoMTI1Byz0K/Apple-Design-Resources---macOS-(Community)?node-id=0%3A1745&mode=dev) +class MacosFontWeight implements FontWeight { + const MacosFontWeight._(this.index, this.value); + + /// The encoded integer value of this font weight. + @override + final int index; + + /// The thickness value of this font weight. + @override + final int value; + + /// Thin, the least thick + static const MacosFontWeight w100 = MacosFontWeight._(0, 100); + + /// Extra-light + static const MacosFontWeight w200 = MacosFontWeight._(1, 200); + + /// Light + static const MacosFontWeight w300 = MacosFontWeight._(2, 300); + + /// Normal / regular / plain + static const MacosFontWeight w400 = MacosFontWeight._(3, 400); + + /// Medium + static const MacosFontWeight w500 = MacosFontWeight._(4, 500); + + /// An Apple-specific font weight. + /// + /// When [MacosTypography.caption1] needs to be bolded, use this value. + static const MacosFontWeight w510 = MacosFontWeight._(5, 510); + + /// An Apple-specific font weight. + /// + /// When [MacosTypography.body], [MacosTypography.callout], + /// [MacosTypography.subheadline], [MacosTypography.footnote], or + /// [MacosTypography.caption2] need to be bolded, use this value. + static const MacosFontWeight w590 = MacosFontWeight._(6, 590); + + /// Semi-bold + static const MacosFontWeight w600 = MacosFontWeight._(7, 600); + + /// Bold + static const MacosFontWeight w700 = MacosFontWeight._(8, 700); + + /// Extra-bold + static const MacosFontWeight w800 = MacosFontWeight._(9, 800); + + /// An Apple-specific font weight. + /// + /// When [MacosTypography.title3] needs to be bolded, use this value. + static const MacosFontWeight w860 = MacosFontWeight._(10, 860); + + /// Black, the most thick + static const MacosFontWeight w900 = MacosFontWeight._(11, 900); + + /// The default font weight. + static const MacosFontWeight normal = w400; + + /// A commonly used font weight that is heavier than normal. + static const MacosFontWeight bold = w700; + + /// A list of all the font weights. + static const List values = [ + w100, + w200, + w300, + w400, + w500, + w510, + w590, + w600, + w700, + w800, + w860, + w900 + ]; + + /// Linearly interpolates between two font weights. + /// + /// Rather than using fractional weights, the interpolation rounds to the + /// nearest weight. + /// + /// If both `a` and `b` are null, then this method will return null. Otherwise, + /// any null values for `a` or `b` are interpreted as equivalent to [normal] + /// (also known as [w400]). + /// + /// The `t` argument represents position on the timeline, with 0.0 meaning + /// that the interpolation has not started, returning `a` (or something + /// equivalent to `a`), 1.0 meaning that the interpolation has finished, + /// returning `b` (or something equivalent to `b`), and values in between + /// meaning that the interpolation is at the relevant point on the timeline + /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and + /// 1.0, so negative values and values greater than 1.0 are valid (and can + /// easily be generated by curves such as [Curves.elasticInOut]). The result + /// is clamped to the range [w100]–[w900]. + /// + /// Values for `t` are usually obtained from an [Animation], such as + /// an [AnimationController]. + static MacosFontWeight? lerp( + MacosFontWeight? a, MacosFontWeight? b, double t) { + if (a == null && b == null) { + return null; + } + return values[_lerpInt((a ?? normal).index, (b ?? normal).index, t) + .round() + .clamp(0, 8)]; + } + + @override + String toString() { + return const { + 0: 'MacosFontWeight.w100', + 1: 'MacosFontWeight.w200', + 2: 'MacosFontWeight.w300', + 3: 'MacosFontWeight.w400', + 4: 'MacosFontWeight.w500', + 5: 'MacosFontWeight.w510', + 6: 'MacosFontWeight.w590', + 7: 'MacosFontWeight.w600', + 8: 'MacosFontWeight.w700', + 9: 'MacosFontWeight.w800', + 10: 'MacosFontWeight.w860', + 11: 'MacosFontWeight.w900', + }[index]!; + } +} + +/// Linearly interpolate between two integers. +/// +/// Same as [lerpDouble] but specialized for non-null `int` type. +double _lerpInt(int a, int b, double t) { + return a + (b - a) * t; +} diff --git a/pubspec.yaml b/pubspec.yaml index 5ce15348..4c2877e4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 2.0.0-beta.7 +version: 2.0.0-beta.8 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From de3e35cb2522eb3063649004caa6e4af4445e3ba Mon Sep 17 00:00:00 2001 From: st merlhin <77164238+stMerlHin@users.noreply.github.com> Date: Wed, 12 Jul 2023 13:31:55 +0000 Subject: [PATCH 32/45] Non-scrollable `ResizablePane` (#420) --- CHANGELOG.md | 3 + example/lib/pages/resizable_pane_page.dart | 12 +- example/lib/widgets/widget_text_title1.dart | 2 +- example/pubspec.lock | 2 +- lib/src/layout/resizable_pane.dart | 62 ++++- pubspec.yaml | 2 +- test/layout/resizeable_pane_test.dart | 240 +++++++++++++++++++- test/selectors/date_picker_test.dart | 56 ++--- 8 files changed, 330 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10ee2492..88c90a37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [2.0.0-beta.9] +* `ResizablePane` can now disallow the usage of its internal scrollbar via the `ReziablePane.noScrollBar` constructor. + ## [2.0.0-beta.8] ✨ New ✨ * `MacosFontWeight` allows using Apple-specific font weights like `w510`, `w590`, and `w860`. diff --git a/example/lib/pages/resizable_pane_page.dart b/example/lib/pages/resizable_pane_page.dart index b850a0c3..78ed3320 100644 --- a/example/lib/pages/resizable_pane_page.dart +++ b/example/lib/pages/resizable_pane_page.dart @@ -73,16 +73,12 @@ class _ResizablePanePageState extends State { ); }, ), - ResizablePane( + const ResizablePane.noScrollBar( minSize: 180, startSize: 200, - // windowBreakpoint: 800, - resizableSide: ResizableSide.left, - builder: (_, __) { - return const Center( - child: Text('Right Resizable Pane'), - ); - }, + windowBreakpoint: 700, + resizableSide: ResizableSide.right, + child: Center(child: Text('Right non-scrollable Resizable Pane')), ), ], ); diff --git a/example/lib/widgets/widget_text_title1.dart b/example/lib/widgets/widget_text_title1.dart index d7dafc90..77ab7314 100644 --- a/example/lib/widgets/widget_text_title1.dart +++ b/example/lib/widgets/widget_text_title1.dart @@ -27,4 +27,4 @@ class WidgetTextTitle1 extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/example/pubspec.lock b/example/pubspec.lock index 6ef37937..8872a184 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -145,7 +145,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.8" + version: "2.0.0-beta.9" macos_window_utils: dependency: transitive description: diff --git a/lib/src/layout/resizable_pane.dart b/lib/src/layout/resizable_pane.dart index b3f6b338..97836bdd 100644 --- a/lib/src/layout/resizable_pane.dart +++ b/lib/src/layout/resizable_pane.dart @@ -30,10 +30,15 @@ enum ResizableSide { /// The [startSize] is the initial width or height depending on the orientation of the pane. /// {@endtemplate} class ResizablePane extends StatefulWidget { - /// {@macro resizablePane} + /// Creates a [ResizablePane] with an internal [MacosScrollbar]. + /// + /// Consider using [ResizablePane.noScrollBar] constructor when the internal + /// [MacosScrollbar] is not needed or when working with widgets which do not + /// expose their scroll controllers. + /// {@macro resizablePane}. const ResizablePane({ super.key, - required this.builder, + required ScrollableWidgetBuilder this.builder, this.decoration, this.maxSize = 500.0, required this.minSize, @@ -41,7 +46,38 @@ class ResizablePane extends StatefulWidget { required this.resizableSide, this.windowBreakpoint, required this.startSize, - }) : assert( + }) : child = null, + useScrollBar = true, + assert( + maxSize >= minSize, + 'minSize should not be more than maxSize.', + ), + assert( + (startSize >= minSize) && (startSize <= maxSize), + 'startSize must not be less than minSize or more than maxWidth', + ); + + /// Creates a [ResizablePane] without an internal [MacosScrollbar]. + /// + /// Useful when working with widgets which do not expose their scroll + /// controllers or when not using the platform scroll bar is preferred. + /// + /// Consider using the default constructor if showing a [MacosScrollbar] + /// when scrolling the content of this widget is the expected behavior. + /// {@macro resizablePane}. + const ResizablePane.noScrollBar({ + super.key, + required Widget this.child, + this.decoration, + this.maxSize = 500.0, + required this.minSize, + this.isResizable = true, + required this.resizableSide, + this.windowBreakpoint, + required this.startSize, + }) : builder = null, + useScrollBar = false, + assert( maxSize >= minSize, 'minSize should not be more than maxSize.', ), @@ -55,7 +91,15 @@ class ResizablePane extends StatefulWidget { /// /// Pass the [scrollController] obtained from this method, to a scrollable /// widget used in this method to work with the internal [MacosScrollbar]. - final ScrollableWidgetBuilder builder; + final ScrollableWidgetBuilder? builder; + + /// The child to display in this widget. + /// + /// This is only referenced when the constructor used is [ResizablePane.noScrollbar]. + final Widget? child; + + /// Specify if this [ResizablePane] should have an internal [MacosScrollbar]. + final bool useScrollBar; /// The [BoxDecoration] to paint behind the child in the [builder]. final BoxDecoration? decoration; @@ -277,10 +321,12 @@ class _ResizablePaneState extends State { SafeArea( left: false, right: false, - child: MacosScrollbar( - controller: _scrollController, - child: widget.builder(context, _scrollController), - ), + child: widget.useScrollBar + ? MacosScrollbar( + controller: _scrollController, + child: widget.builder!(context, _scrollController), + ) + : widget.child!, ), if (widget.isResizable && !_resizeOnRight && !_resizeOnTop) Positioned( diff --git a/pubspec.yaml b/pubspec.yaml index 4c2877e4..c14c5d6b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 2.0.0-beta.8 +version: 2.0.0-beta.9 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" diff --git a/test/layout/resizeable_pane_test.dart b/test/layout/resizeable_pane_test.dart index 56164763..f3ac9f83 100644 --- a/test/layout/resizeable_pane_test.dart +++ b/test/layout/resizeable_pane_test.dart @@ -6,6 +6,10 @@ void main() { const matrix = ResizableSide.values; group('ResizablePane', () { + const double maxSize = 300; + const double minSize = 100; + const double startSize = 200; + for (var side in matrix) { bool verticallyResizable = side == ResizableSide.top; @@ -14,10 +18,6 @@ void main() { ? 'top' : (side == ResizableSide.left ? 'left' : 'right'), () { - const double maxSize = 300; - const double minSize = 100; - const double startSize = 200; - final resizablePane = ResizablePane( builder: (context, scrollController) => const Text('Hello there'), minSize: minSize, @@ -79,6 +79,238 @@ void main() { final double safeDelta = 50.0 * directionModifier; final double overflowDelta = 500.0 * directionModifier; + testWidgets( + 'Default ResizablePane Constructor comes with an internal MacosScrollBar', + (WidgetTester tester) async { + await tester.pumpWidget(view); + expect(find.byType(MacosScrollbar), findsOneWidget); + }, + ); + + testWidgets('initial size equals startSize', (tester) async { + await tester.pumpWidget(view); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var initialSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + + expect(initialSize, startSize); + }); + + testWidgets('dragging wider works $side', (tester) async { + await tester.pumpWidget(view); + + await tester.drag( + dragFinder, + verticallyResizable ? Offset(0, safeDelta) : Offset(safeDelta, 0), + ); + await tester.pump(); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + expect( + verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width, + startSize + safeDelta * directionModifier, + ); + }); + + testWidgets('dragging wider respects maxSize', (tester) async { + await tester.pumpWidget(view); + + await tester.drag( + dragFinder, + verticallyResizable + ? Offset(0, overflowDelta) + : Offset(overflowDelta, 0), + ); + await tester.pump(); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var currentSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + expect(currentSize, maxSize); + }); + + testWidgets( + 'drag events past maxSize have no effect $side', + (tester) async { + await tester.pumpWidget(view); + + final dragStartLocation = tester.getCenter(dragFinder); + final drag = await tester.startGesture(dragStartLocation); + await drag.moveBy( + verticallyResizable + ? Offset(0, overflowDelta) + : Offset(overflowDelta, 0), + ); + await drag.moveBy( + verticallyResizable + ? Offset(0, -10.0 * directionModifier) + : Offset(-10.0 * directionModifier, 0), + ); + await drag.up(); + await tester.pump(); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var currentSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + expect(currentSize, maxSize); + }, + ); + + testWidgets('dragging narrower works', (tester) async { + await tester.pumpWidget(view); + + await tester.drag( + dragFinder, + verticallyResizable + ? Offset(0, -safeDelta) + : Offset(-safeDelta, 0), + ); + await tester.pump(); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var currentSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + expect( + currentSize, + startSize - safeDelta * directionModifier, + ); + }); + + testWidgets('dragging narrower respects minSize', (tester) async { + await tester.pumpWidget(view); + + await tester.drag( + dragFinder, + verticallyResizable + ? Offset(0, -overflowDelta) + : Offset(-overflowDelta, 0), + ); + await tester.pump(); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var currentSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + expect(currentSize, minSize); + }); + + testWidgets( + 'drag events past minSize have no effect', + (tester) async { + await tester.pumpWidget(view); + + final dragStartLocation = tester.getCenter(dragFinder); + final drag = await tester.startGesture(dragStartLocation); + await drag.moveBy( + verticallyResizable + ? Offset(0, -overflowDelta) + : Offset(-overflowDelta, 0), + ); + await drag.moveBy( + verticallyResizable + ? Offset(0, 10.0 * directionModifier) + : Offset(10.0 * directionModifier, 0), + ); + await drag.up(); + await tester.pump(); + + var resizablePaneRenderObject = + tester.renderObject(resizablePaneFinder); + var currentSize = verticallyResizable + ? resizablePaneRenderObject.size.height + : resizablePaneRenderObject.size.width; + expect(currentSize, minSize); + }, + ); + }, + ); + group( + side == ResizableSide.top + ? 'top' + : (side == ResizableSide.left ? 'left' : 'right'), + () { + final resizablePane = ResizablePane.noScrollBar( + minSize: minSize, + startSize: startSize, + maxSize: maxSize, + resizableSide: side, + child: const Text('Hello there'), + ); + + final view = side == ResizableSide.top + ? MacosApp( + home: MacosWindow( + disableWallpaperTinting: true, + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, scrollController) { + return Column( + children: [ + const Flexible( + fit: FlexFit.loose, + child: Center( + child: Text('Hello there'), + ), + ), + resizablePane, + ], + ); + }, + ), + ], + ), + ), + ) + : MacosApp( + home: MacosWindow( + disableWallpaperTinting: true, + child: MacosScaffold( + children: [ + resizablePane, + ContentArea( + builder: (context, scrollController) { + return const Text('Hello there'); + }, + ), + ], + ), + ), + ); + + final resizablePaneFinder = find.byWidget(resizablePane); + final dragFinder = find.descendant( + of: resizablePaneFinder, + matching: find.byType(GestureDetector), + ); + + // No need to check if the resizable side is top because directionModifier + // would take -1 if it is the case + final directionModifier = side == ResizableSide.right ? 1 : -1; + final double safeDelta = 50.0 * directionModifier; + final double overflowDelta = 500.0 * directionModifier; + + testWidgets( + 'ResizablePane.noScrollBar Constructor does not come with an internal MacosScrollBar', + (WidgetTester tester) async { + await tester.pumpWidget(view); + expect(find.byType(MacosScrollbar), findsNothing); + }, + ); + testWidgets('initial size equals startSize', (tester) async { await tester.pumpWidget(view); diff --git a/test/selectors/date_picker_test.dart b/test/selectors/date_picker_test.dart index e5f4f495..f0996546 100644 --- a/test/selectors/date_picker_test.dart +++ b/test/selectors/date_picker_test.dart @@ -84,30 +84,32 @@ void main() { testWidgets( 'Textual MacosDatePicker renders the date with respect to "dateFormat" property', - (tester) async { + (tester) async { renderWidget(String dateFormat) => MacosApp( - home: MacosWindow( - disableWallpaperTinting: true, - child: MacosScaffold( - children: [ - ContentArea( - builder: (context, _) { - return Center( - child: MacosDatePicker( - initialDate: DateTime.parse('2023-04-01'), - onDateChanged: (date) {}, - dateFormat: dateFormat, - style: DatePickerStyle.textual, - ), - ); - }, + home: MacosWindow( + disableWallpaperTinting: true, + child: MacosScaffold( + children: [ + ContentArea( + builder: (context, _) { + return Center( + child: MacosDatePicker( + initialDate: DateTime.parse('2023-04-01'), + onDateChanged: (date) {}, + dateFormat: dateFormat, + style: DatePickerStyle.textual, + ), + ); + }, + ), + ], ), - ], - ), - ), - ); + ), + ); - getNthTextFromWidget(int index) => (find.byType(Text).at(index).evaluate().first.widget as Text).data as String; + getNthTextFromWidget(int index) => + (find.byType(Text).at(index).evaluate().first.widget as Text).data + as String; await tester.pumpWidget(renderWidget('dd.mm.yyyy')); String firstDateElement = getNthTextFromWidget(0); @@ -363,7 +365,7 @@ void main() { testWidgets( 'Graphical MacosDatePicker renders abbreviations based on "weekdayAbbreviations" and "monthAbbreviations" properties', - (tester) async { + (tester) async { await tester.pumpWidget( MacosApp( home: MacosWindow( @@ -424,7 +426,7 @@ void main() { testWidgets( 'Graphical MacosDatePicker with "startWeekOnMonday" set to true shows Monday as the first day of the week', - (tester) async { + (tester) async { await tester.pumpWidget( MacosApp( home: MacosWindow( @@ -453,7 +455,8 @@ void main() { matching: find.byType(Text), ); final firstWeekday = dayHeaders.first; - final firstWeekdayText = (firstWeekday.evaluate().first.widget as Text).data; + final firstWeekdayText = + (firstWeekday.evaluate().first.widget as Text).data; await tester.pumpAndSettle(); expect(firstWeekdayText, 'Mo'); @@ -474,7 +477,7 @@ void main() { // TODO: remove this once the issue is fixed and test starts failing testWidgets( 'Graphical MacosDatePicker still needs "startWeekOnMonday" to show Monday as the first day of the week, even when the locale is set to something other than "en_US"', - (tester) async { + (tester) async { await tester.pumpWidget( MacosApp( supportedLocales: const [ @@ -506,7 +509,8 @@ void main() { matching: find.byType(Text), ); final firstWeekday = dayHeaders.first; - final firstWeekdayText = (firstWeekday.evaluate().first.widget as Text).data; + final firstWeekdayText = + (firstWeekday.evaluate().first.widget as Text).data; await tester.pumpAndSettle(); // The result will be 'Tu' if the fix is no longer needed and can be removed From 64472fe21c9200bb1229455906be0dc7a2d961c1 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Wed, 19 Jul 2023 09:24:18 -0400 Subject: [PATCH 33/45] chore: add missing trailing commas & format --- lib/src/theme/typography.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/src/theme/typography.dart b/lib/src/theme/typography.dart index e600a3e0..2462208b 100644 --- a/lib/src/theme/typography.dart +++ b/lib/src/theme/typography.dart @@ -383,7 +383,7 @@ class MacosFontWeight implements FontWeight { w700, w800, w860, - w900 + w900, ]; /// Linearly interpolates between two font weights. @@ -408,7 +408,10 @@ class MacosFontWeight implements FontWeight { /// Values for `t` are usually obtained from an [Animation], such as /// an [AnimationController]. static MacosFontWeight? lerp( - MacosFontWeight? a, MacosFontWeight? b, double t) { + MacosFontWeight? a, + MacosFontWeight? b, + double t, + ) { if (a == null && b == null) { return null; } From 0e8231f7ce9bfff5af08051646c0dc81cd90fa52 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 19 Jul 2023 12:59:35 -0400 Subject: [PATCH 34/45] Fix broken web support, platform theming issues, and typography updates (#460) * start fixing gallery for web * additional platform safety, theme, and typographic fixes * update version --- CHANGELOG.md | 12 +++ example/lib/main.dart | 73 ++++++++++--------- example/lib/pages/selectors_page.dart | 13 ++-- example/lib/platform_menus.dart | 52 +++++++++++++ .../Flutter/GeneratedPluginRegistrant.swift | 2 + example/macos/Podfile.lock | 6 ++ example/pubspec.lock | 71 +++++++++++++++++- example/pubspec.yaml | 1 + lib/src/buttons/push_button.dart | 3 +- lib/src/layout/scaffold.dart | 46 ++++++++---- lib/src/layout/toolbar/toolbar.dart | 13 ++-- lib/src/layout/window.dart | 57 ++++++++++++++- lib/src/theme/macos_theme.dart | 7 +- lib/src/theme/typography.dart | 28 +++---- lib/src/utils.dart | 11 +-- pubspec.yaml | 2 +- 16 files changed, 301 insertions(+), 96 deletions(-) create mode 100644 example/lib/platform_menus.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 88c90a37..786279c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## [2.0.0-beta.10] +🛠️ Fixed 🛠️ +* Ensure builds targeting web do not utilize any `macos_window_utils` code +* Ensure builds targeting web are themed correctly + +🔄 Updated 🔄 +* `MacosTypography` white and black are now factory constructors called `darkOpaque()` and `lightOpaque()` to reflect +Apple's naming conventions. +* `PushButton` now uses the correct `body` text style instead of the incorrect `headline` +* `Toolbar` now uses the correct `title3` text style instead of the incorrect `headline` +* `MacosTheme` sets the global typography, per theme, more efficiently + ## [2.0.0-beta.9] * `ResizablePane` can now disallow the usage of its internal scrollbar via the `ReziablePane.noScrollBar` constructor. diff --git a/example/lib/main.dart b/example/lib/main.dart index 04f48411..8869a817 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:example/pages/buttons_page.dart'; import 'package:example/pages/colors_page.dart'; import 'package:example/pages/dialogs_page.dart'; @@ -9,9 +11,12 @@ import 'package:example/pages/sliver_toolbar_page.dart'; import 'package:example/pages/tabview_page.dart'; import 'package:example/pages/toolbar_page.dart'; import 'package:example/pages/typography_page.dart'; +import 'package:example/platform_menus.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; import 'theme.dart'; @@ -22,7 +27,11 @@ Future _configureMacosWindowUtils() async { } Future main() async { - await _configureMacosWindowUtils(); + if (!kIsWeb) { + if (Platform.isMacOS) { + await _configureMacosWindowUtils(); + } + } runApp(const MacosUIGalleryApp()); } @@ -78,38 +87,7 @@ class _WidgetGalleryState extends State { @override Widget build(BuildContext context) { return PlatformMenuBar( - menus: const [ - PlatformMenu( - label: 'macos_ui Widget Gallery', - menus: [ - PlatformProvidedMenuItem( - type: PlatformProvidedMenuItemType.about, - ), - PlatformProvidedMenuItem( - type: PlatformProvidedMenuItemType.quit, - ), - ], - ), - PlatformMenu( - label: 'View', - menus: [ - PlatformProvidedMenuItem( - type: PlatformProvidedMenuItemType.toggleFullScreen, - ), - ], - ), - PlatformMenu( - label: 'Window', - menus: [ - PlatformProvidedMenuItem( - type: PlatformProvidedMenuItemType.minimizeWindow, - ), - PlatformProvidedMenuItem( - type: PlatformProvidedMenuItemType.zoomWindow, - ), - ], - ), - ], + menus: menuBarItems(), child: MacosWindow( sidebar: Sidebar( top: MacosSearchField( @@ -184,7 +162,17 @@ class _WidgetGalleryState extends State { builder: (context, scrollController) { return SidebarItems( currentIndex: pageIndex, - onChanged: (i) => setState(() => pageIndex = i), + onChanged: (i) { + if (kIsWeb && i == 10) { + launchUrl( + Uri.parse( + 'https://www.figma.com/file/IX6ph2VWrJiRoMTI1Byz0K/Apple-Design-Resources---macOS-(Community)?node-id=0%3A1745&mode=dev', + ), + ); + } else { + setState(() => pageIndex = i); + } + }, scrollController: scrollController, itemSize: SidebarItemSize.large, items: const [ @@ -281,7 +269,7 @@ class _WidgetGalleryState extends State { ); }, ), - child: IndexedStack( + /*child: IndexedStack( index: pageIndex, children: pageBuilders .asMap() @@ -291,7 +279,20 @@ class _WidgetGalleryState extends State { }) .values .toList(), - ), + ),*/ + child: [ + CupertinoTabView(builder: (_) => const ButtonsPage()), + const IndicatorsPage(), + const FieldsPage(), + const ColorsPage(), + const DialogsPage(), + const ToolbarPage(), + const SliverToolbarPage(isVisible: !kIsWeb), + const TabViewPage(), + const ResizablePanePage(), + const SelectorsPage(), + const TypographyPage(), + ][pageIndex], ), ); } diff --git a/example/lib/pages/selectors_page.dart b/example/lib/pages/selectors_page.dart index fdc5fbeb..74ed4b47 100644 --- a/example/lib/pages/selectors_page.dart +++ b/example/lib/pages/selectors_page.dart @@ -1,6 +1,7 @@ import 'package:example/widgets/widget_text_title1.dart'; import 'package:example/widgets/widget_text_title2.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:macos_ui/macos_ui.dart'; @@ -79,11 +80,13 @@ class _SelectorsPageState extends State { ], ), const SizedBox(height: 20), - const WidgetTextTitle1(widgetName: 'MacosColorWell'), - Divider(color: MacosTheme.of(context).dividerColor), - MacosColorWell( - onColorSelected: (color) => debugPrint('$color'), - ), + if (!kIsWeb) ...[ + const WidgetTextTitle1(widgetName: 'MacosColorWell'), + Divider(color: MacosTheme.of(context).dividerColor), + MacosColorWell( + onColorSelected: (color) => debugPrint('$color'), + ), + ], ], ), ); diff --git a/example/lib/platform_menus.dart b/example/lib/platform_menus.dart new file mode 100644 index 00000000..91ba6a7c --- /dev/null +++ b/example/lib/platform_menus.dart @@ -0,0 +1,52 @@ +import 'package:flutter/foundation.dart' show kIsWeb; +import 'dart:io' as io; + +import 'package:flutter/widgets.dart' + show + PlatformMenu, + PlatformMenuItem, + PlatformProvidedMenuItem, + PlatformProvidedMenuItemType; + +List menuBarItems() { + if (kIsWeb) { + return []; + } else { + if (io.Platform.isMacOS) { + return const [ + PlatformMenu( + label: 'macos_ui Widget Gallery', + menus: [ + PlatformProvidedMenuItem( + type: PlatformProvidedMenuItemType.about, + ), + PlatformProvidedMenuItem( + type: PlatformProvidedMenuItemType.quit, + ), + ], + ), + PlatformMenu( + label: 'View', + menus: [ + PlatformProvidedMenuItem( + type: PlatformProvidedMenuItemType.toggleFullScreen, + ), + ], + ), + PlatformMenu( + label: 'Window', + menus: [ + PlatformProvidedMenuItem( + type: PlatformProvidedMenuItemType.minimizeWindow, + ), + PlatformProvidedMenuItem( + type: PlatformProvidedMenuItemType.zoomWindow, + ), + ], + ), + ]; + } else { + return []; + } + } +} diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 15deecb4..0179c12c 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,9 +8,11 @@ import Foundation import macos_ui import macos_window_utils import path_provider_foundation +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin")) MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 97b47e1b..2fa81174 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -7,12 +7,15 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS + - url_launcher_macos (0.0.1): + - FlutterMacOS DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - macos_ui (from `Flutter/ephemeral/.symlinks/plugins/macos_ui/macos`) - macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: FlutterMacOS: @@ -23,12 +26,15 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 macos_ui: 6229a8922cd97bafb7d9636c8eb8dfb0744183ca macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 + url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 PODFILE CHECKSUM: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9 diff --git a/example/pubspec.lock b/example/pubspec.lock index 8872a184..7e7d770a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -99,6 +99,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" google_fonts: dependency: "direct main" description: @@ -145,7 +150,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.9" + version: "2.0.0-beta.10" macos_window_utils: dependency: transitive description: @@ -335,6 +340,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e" + url: "https://pub.dev" + source: hosted + version: "6.1.12" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "15f5acbf0dce90146a0f5a2c4a002b1814a6303c4c5c075aa2623b2d16156f03" + url: "https://pub.dev" + source: hosted + version: "6.0.36" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea + url: "https://pub.dev" + source: hosted + version: "2.1.3" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4 + url: "https://pub.dev" + source: hosted + version: "2.0.18" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422" + url: "https://pub.dev" + source: hosted + version: "3.0.7" vector_math: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 79564134..0a008b51 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: path: .. provider: ^6.0.5 google_fonts: ^5.1.0 + url_launcher: ^6.1.12 dev_dependencies: flutter_test: diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 4fa2258a..19180016 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -324,8 +324,7 @@ class PushButtonState extends State ? const Color.fromRGBO(255, 255, 255, 0.25) : const Color.fromRGBO(0, 0, 0, 0.25); - final baseStyle = - theme.typography.headline.copyWith(color: foregroundColor); + final baseStyle = theme.typography.body.copyWith(color: foregroundColor); return MouseRegion( cursor: widget.mouseCursor!, diff --git a/lib/src/layout/scaffold.dart b/lib/src/layout/scaffold.dart index 2df5a94d..59678372 100644 --- a/lib/src/layout/scaffold.dart +++ b/lib/src/layout/scaffold.dart @@ -1,5 +1,6 @@ import 'dart:math' as math; +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; @@ -63,7 +64,7 @@ class _MacosScaffoldState extends State { ); final MacosThemeData theme = MacosTheme.of(context); - late Color backgroundColor = widget.backgroundColor ?? theme.canvasColor; + Color backgroundColor = widget.backgroundColor ?? theme.canvasColor; return LayoutBuilder( builder: (context, constraints) { @@ -76,19 +77,34 @@ class _MacosScaffoldState extends State { return Stack( children: [ - // Background color - Positioned.fill( - child: ColoredBox(color: backgroundColor), - ), - - // Content Area - Positioned( - top: 0, - width: width, - height: height, - child: WallpaperTintedArea( - backgroundColor: backgroundColor, - insertRepaintBoundary: true, + if (!kIsWeb) ...[ + // Content Area + Positioned( + top: 0, + width: width, + height: height, + child: WallpaperTintedArea( + backgroundColor: backgroundColor, + insertRepaintBoundary: true, + child: MediaQuery( + data: mediaQuery.copyWith( + padding: EdgeInsets.only(top: topPadding), + ), + child: _ScaffoldBody(children: children), + ), + ), + ), + ] else ...[ + // Background color + Positioned.fill( + child: ColoredBox(color: backgroundColor), + ), + + // Content Area + Positioned( + top: 0, + width: width, + height: height, child: MediaQuery( data: mediaQuery.copyWith( padding: EdgeInsets.only(top: topPadding), @@ -96,7 +112,7 @@ class _MacosScaffoldState extends State { child: _ScaffoldBody(children: children), ), ), - ), + ], // Toolbar if (widget.toolBar != null) diff --git a/lib/src/layout/toolbar/toolbar.dart b/lib/src/layout/toolbar/toolbar.dart index bad588c8..efd62e3c 100644 --- a/lib/src/layout/toolbar/toolbar.dart +++ b/lib/src/layout/toolbar/toolbar.dart @@ -223,13 +223,10 @@ class _ToolBarState extends State { title = SizedBox( width: widget.titleWidth, child: DefaultTextStyle( - style: MacosTheme.of(context).typography.headline.copyWith( - fontSize: 15, - fontWeight: FontWeight.w600, - color: theme.brightness.isDark - ? const Color(0xFFEAEAEA) - : const Color(0xFF4D4D4D), - ), + style: theme.typography.title3.copyWith( + fontSize: 15, + fontWeight: MacosFontWeight.w590, + ), child: title, ), ); @@ -266,7 +263,7 @@ class _ToolBarState extends State { ), ), child: _WallpaperTintedAreaOrBlurFilter( - enableWallpaperTintedArea: !widget.enableBlur, + enableWallpaperTintedArea: kIsWeb ? false : !widget.enableBlur, isWidgetVisible: widget.allowWallpaperTintingOverrides, backgroundColor: theme.canvasColor, widgetOpacity: widget.decoration?.color?.opacity, diff --git a/lib/src/layout/window.dart b/lib/src/layout/window.dart index 4d8b4cf5..85c20417 100644 --- a/lib/src/layout/window.dart +++ b/lib/src/layout/window.dart @@ -265,12 +265,65 @@ class _MacosWindowState extends State { minHeight: height, maxHeight: height, ).normalize(), - child: TransparentMacOSSidebar( + child: kIsWeb ? ColoredBox( + color: theme.canvasColor, + child: Column( + children: [ + // If an app is running on macOS, apply + // sidebar.topOffset as needed in order to avoid the + // traffic lights. Otherwise, position the sidebar + // by the top of the application's bounds based on + // the presence of sidebar.top. + if (!kIsWeb && sidebar.topOffset > 0) ...[ + SizedBox(height: sidebar.topOffset), + ] else if (sidebar.top != null) ...[ + const SizedBox(height: 12), + ] else + const SizedBox.shrink(), + if (_sidebarScrollController.hasClients && + _sidebarScrollController.offset > 0.0) + Divider(thickness: 1, height: 1, color: dividerColor), + if (sidebar.top != null && constraints.maxHeight > 81) + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8.0), + child: sidebar.top!, + ), + Expanded( + child: MacosScrollbar( + controller: _sidebarScrollController, + child: Padding( + padding: sidebar.padding, + child: sidebar.builder( + context, + _sidebarScrollController, + ), + ), + ), + ), + if (sidebar.bottom != null && + constraints.maxHeight > 141) + Padding( + padding: const EdgeInsets.all(16.0), + child: sidebar.bottom!, + ), + ], + ), + ) : TransparentMacOSSidebar( state: sidebarState, child: Column( children: [ - if ((sidebar.topOffset) > 0) + // If an app is running on macOS, apply + // sidebar.topOffset as needed in order to avoid the + // traffic lights. Otherwise, position the sidebar + // by the top of the application's bounds based on + // the presence of sidebar.top. + if (!kIsWeb && sidebar.topOffset > 0) ...[ SizedBox(height: sidebar.topOffset), + ] else if (sidebar.top != null) ...[ + const SizedBox(height: 12), + ] else + const SizedBox.shrink(), if (_sidebarScrollController.hasClients && _sidebarScrollController.offset > 0.0) Divider(thickness: 1, height: 1, color: dividerColor), diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index 0cae2b9a..87405882 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -217,11 +217,8 @@ class MacosThemeData with Diagnosticable { canvasColor ??= isDark ? const Color.fromRGBO(40, 40, 40, 1.0) : const Color.fromRGBO(246, 246, 246, 1.0); - typography ??= MacosTypography( - color: _brightness == Brightness.light - ? CupertinoColors.black - : CupertinoColors.white, - ); + typography ??= + isDark ? MacosTypography.lightOpaque() : MacosTypography.darkOpaque(); pushButtonTheme ??= PushButtonThemeData( color: primaryColor, secondaryColor: diff --git a/lib/src/theme/typography.dart b/lib/src/theme/typography.dart index 2462208b..7b2f72a7 100644 --- a/lib/src/theme/typography.dart +++ b/lib/src/theme/typography.dart @@ -18,8 +18,8 @@ const _kDefaultFontFamily = '.AppleSystemUIFont'; class MacosTypography with Diagnosticable { /// Creates a typography that uses the given values. /// - /// Rather than creating a new typography, consider using [MacosTypography.black] - /// or [MacosTypography.white]. + /// Rather than creating a new typography, consider using [MacosTypography.darkOpaque] + /// or [MacosTypography.lightOpaque]. /// /// If you do decide to create your own typography, consider using one of /// those predefined themes as a starting point for [copyWith]. @@ -67,18 +67,11 @@ class MacosTypography with Diagnosticable { ); headline ??= TextStyle( fontFamily: _kDefaultFontFamily, - fontWeight: FontWeight.w400, + fontWeight: FontWeight.w700, fontSize: 13, letterSpacing: -0.08, color: color, ); - subheadline ??= TextStyle( - fontFamily: _kDefaultFontFamily, - fontWeight: FontWeight.w400, - fontSize: 11, - letterSpacing: 0.06, - color: color, - ); body ??= TextStyle( fontFamily: _kDefaultFontFamily, fontWeight: FontWeight.w400, @@ -92,6 +85,13 @@ class MacosTypography with Diagnosticable { fontSize: 12, color: color, ); + subheadline ??= TextStyle( + fontFamily: _kDefaultFontFamily, + fontWeight: FontWeight.w400, + fontSize: 11, + letterSpacing: 0.06, + color: color, + ); footnote ??= TextStyle( fontFamily: _kDefaultFontFamily, fontWeight: FontWeight.w400, @@ -108,7 +108,7 @@ class MacosTypography with Diagnosticable { ); caption2 ??= TextStyle( fontFamily: _kDefaultFontFamily, - fontWeight: FontWeight.w400, + fontWeight: MacosFontWeight.w510, fontSize: 10, letterSpacing: 0.12, color: color, @@ -142,9 +142,9 @@ class MacosTypography with Diagnosticable { required this.caption2, }); - static final MacosTypography black = + factory MacosTypography.darkOpaque() => MacosTypography(color: MacosColors.labelColor.color); - static final MacosTypography white = + factory MacosTypography.lightOpaque() => MacosTypography(color: MacosColors.labelColor.darkColor); /// Style used for body text. @@ -240,7 +240,7 @@ class MacosTypography with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final defaultStyle = MacosTypography.black; + final defaultStyle = MacosTypography.darkOpaque(); properties.add(DiagnosticsProperty( 'largeTitle', largeTitle, diff --git a/lib/src/utils.dart b/lib/src/utils.dart index b99c1796..1f2d8a1e 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; @@ -77,13 +78,9 @@ class MacOSBrightnessOverrideHandler { /// [currentBrightness] differs from the value it had when this method was /// previously called. Therefore, it is safe to call this method frequently. static void ensureMatchingBrightness(Brightness currentBrightness) { - if (!Platform.isMacOS) { - return; - } - - if (currentBrightness == _lastBrightness) { - return; - } + if (kIsWeb) return; + if (!Platform.isMacOS) return; + if (currentBrightness == _lastBrightness) return; WindowManipulator.overrideMacOSBrightness(dark: currentBrightness.isDark); _lastBrightness = currentBrightness; diff --git a/pubspec.yaml b/pubspec.yaml index c14c5d6b..e76d7bfb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 2.0.0-beta.9 +version: 2.0.0-beta.10 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From d7eaf5b20b10c995a63a88ec3d25d3faad683d6e Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Wed, 19 Jul 2023 13:08:46 -0400 Subject: [PATCH 35/45] gallery: remove commented & deprecated code from `main.dart` --- example/lib/main.dart | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 8869a817..193bef1f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -70,20 +70,6 @@ class _WidgetGalleryState extends State { late final searchFieldController = TextEditingController(); - final List pageBuilders = [ - (_) => CupertinoTabView(builder: (_) => const ButtonsPage()), - (_) => const IndicatorsPage(), - (_) => const FieldsPage(), - (_) => const ColorsPage(), - (_) => const DialogsPage(), - (_) => const ToolbarPage(), - (isVisible) => SliverToolbarPage(isVisible: isVisible), - (_) => const TabViewPage(), - (_) => const ResizablePanePage(), - (_) => const SelectorsPage(), - (_) => const TypographyPage(), - ]; - @override Widget build(BuildContext context) { return PlatformMenuBar( @@ -269,17 +255,6 @@ class _WidgetGalleryState extends State { ); }, ), - /*child: IndexedStack( - index: pageIndex, - children: pageBuilders - .asMap() - .map((index, builder) { - final widget = builder(index == pageIndex); - return MapEntry(index, widget); - }) - .values - .toList(), - ),*/ child: [ CupertinoTabView(builder: (_) => const ButtonsPage()), const IndicatorsPage(), From be20db081beda08027617a03dcb4261fe86325d9 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 19 Jul 2023 14:19:33 -0400 Subject: [PATCH 36/45] 2.0: Readme updates (#461) * docs: update readme screenshots * chore: update gallery to match readme * fix: `HelpButton` sizing * chore: deprecate `RelevanceIndicator` * update version, changelog, & remove `RelevanceIndicator` from gallery & tests --- CHANGELOG.md | 7 ++ README.md | 91 ++++++++----------- example/lib/pages/buttons_page.dart | 4 + example/lib/pages/dialogs_page.dart | 79 ++++++++-------- example/lib/pages/indicators_page.dart | 16 +++- example/pubspec.lock | 2 +- lib/src/buttons/help_button.dart | 20 ++-- lib/src/indicators/relevance_indicator.dart | 1 + pubspec.yaml | 2 +- test/indicators/relevance_indicator_test.dart | 31 ------- 10 files changed, 112 insertions(+), 141 deletions(-) delete mode 100644 test/indicators/relevance_indicator_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 786279c3..397f0187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [2.0.0-beta.11] +🚨 Breaking Changes 🚨 +* `RelevanceIndicator` has been deprecated + +🔄 Updated 🔄 +* `HelpButton` now sizes itself according to specification + ## [2.0.0-beta.10] 🛠️ Fixed 🛠️ * Ensure builds targeting web do not utilize any `macos_window_utils` code diff --git a/README.md b/README.md index b9a911d1..482e67a5 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Guides, codelabs, and other documentation can be found at https://macosui.dev [![codecov](https://github.com/GroovinChip/macos_ui/actions/workflows/codecov.yaml/badge.svg)](https://github.com/GroovinChip/macos_ui/actions/workflows/codecov.yaml) [![codecov](https://codecov.io/gh/GroovinChip/macos_ui/branch/dev/graph/badge.svg?token=1SZGEVVMCH)](https://codecov.io/gh/GroovinChip/macos_ui) - + ## 🚨 Usage notes ### Flutter channel @@ -23,10 +23,13 @@ Guides, codelabs, and other documentation can be found at https://macosui.dev ### Platform Compatibility pub.dev shows that `macos_ui` only supports macOS. This is because `macos_ui` calls some native code, and therefore -specifies macOS as a plugin platform in the `pubspec.yaml` file. `macos_ui` _will_ work on any platform that -Flutter supports, **but you will get best results on macOS**. +specifies macOS as a plugin platform in the `pubspec.yaml` file. + +`macos_ui` _technically_ will work on any platform that +Flutter supports, **but you will get best results on macOS**. non-macOS platform support is ***not*** guaranteed. The features of `macos_ui` that will _not_ work on platforms other than macOS due to calling native code are: +* Anything related to `macos_window_utils` * The `MacosColors.controlAccentColor()` function * The `MacosColorWell` widget @@ -43,9 +46,8 @@ should avoid allowing your application window to be resized below the height of
Contributing & Resources -- [macos_ui](#macos_ui) - - [Contributing](#contributing) - - [Resources](#resources) +- [Contributing](#contributing) +- [Resources](#resources)
@@ -111,7 +113,6 @@ should avoid allowing your application window to be resized below the height of - [Level Indicators](#level-indicators) - [CapacityIndicator](#capacityindicator) - [RatingIndicator](#ratingindicator) - - [RelevanceIndicator](#relevanceindicator)
@@ -131,9 +132,9 @@ should avoid allowing your application window to be resized below the height of ## Resources +- [macOS Sonoma Figma kit](https://www.figma.com/file/M6K5L3GK0WJh6pnsASyVeE/macOS-Big-Sur-UI-Kit?node-id=1%3A2) +- [macOS Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/designing-for-macos) - [macOS Design Resources](https://developer.apple.com/design/resources/) -- [macOS Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/macos) -- [macOS Big Sur Figma kit](https://www.figma.com/file/M6K5L3GK0WJh6pnsASyVeE/macOS-Big-Sur-UI-Kit?node-id=1%3A2) # Layout @@ -158,7 +159,7 @@ A sidebar enables app navigation and provides quick access to top-level collecti Sidebars may be placed at the left or right of your app. To place a sidebar on the left, use the `MacosWindow.sidebar` property. To place a sidebar on the right, use the `MacosWindow.endSidebar` property. - + Example usage: @@ -217,21 +218,20 @@ covering the entire window. To push a route outside a `MacosScaffold` wrapped in See the documentation for customizations and `ToolBar` examples. - - - + - + ## Modern window look -A new look for macOS apps was introduced in Big Sur (macOS 11). To match that look -in your Flutter app, macos_ui relies on [macos_window_utils](https://pub.dev/packages/macos_window_utils), which requires a minimum macOS deployment target of 10.14.6. Therefore, make sure to open the `macos/Runner.xcworkspace` folder of your project using Xcode and search for `Runner.xcodeproj`. Go to `Info` > `Deployment Target` and set the `macOS Deployment Target` to `10.14.6` or above. Then, open your project's `Podfile` (if it doesn't show up in Xcode, you can find it in your project's `macos` directory via VS Code) and set the minimum deployment version in the first line to `10.14.6` or above: +A new look for macOS apps was introduced in Big Sur (macOS 11). To match that look in your Flutter app, macos_ui relies on [macos_window_utils](https://pub.dev/packages/macos_window_utils), which requires a minimum macOS deployment target of 10.14.6. Therefore, make sure to open the `macos/Runner.xcworkspace` folder of your project using Xcode and search for `Runner.xcodeproj`. Go to `Info` > `Deployment Target` and set the `macOS Deployment Target` to `10.14.6` or above. Then, open your project's `Podfile` (if it doesn't show up in Xcode, you can find it in your project's `macos` directory via VS Code) and set the minimum deployment version in the first line to `10.14.6` or above: ```podspec platform :osx, '10.14.6' ``` +You may also need to open up your app's `Runner.xcodeproj` in XCode and set the minimum deployment version there. + Now, configure your window inside your `main()` as follows: ```dart @@ -246,7 +246,7 @@ Future _configureMacosWindowUtils() async { void main() async { await _configureMacosWindowUtils(); - runApp(const MacosUIGalleryApp()); + runApp(const YourAppHere()); } ``` @@ -335,7 +335,7 @@ Other toolbar examples: - Toolbar with a pulldown button open: - + - Toolbar with title bar above (also see [the note above](#modern-window-look)): @@ -408,7 +408,7 @@ MacosListTile( ## MacosTabView A multipage interface that displays one page at a time. Must be used in a `StatefulWidget`. - + You can control the placement of the tabs using the `position` property. @@ -472,9 +472,9 @@ A checkbox is a type of button that lets the user choose between two opposite st checkbox is considered on when it contains a checkmark and off when it's empty. A checkbox is almost always followed by a title unless it appears in a checklist. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/buttons/checkboxes/) -| Off | On | Mixed | +| Unchecked | Checked | Mixed | | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | -| ![Off Checkbox](https://developer.apple.com/design/human-interface-guidelines/macos/images/CheckBoxes_Deselected.svg) | ![On Checkbox](https://developer.apple.com/design/human-interface-guidelines/macos/images/CheckBoxes_Selected.svg) | ![Mixed Checkbox](https://developer.apple.com/design/human-interface-guidelines/macos/images/CheckBoxes_Mixed.svg) | +| ![Unchecked Checkbox](https://imgur.com/Pu4EDAE.png) | ![Checked Checkbox](https://imgur.com/CB3Kmwo.png) | ![Mixed Checkbox](https://imgur.com/T44rV38.png) | Here's an example of how to create a basic checkbox: @@ -496,7 +496,7 @@ To make a checkbox in the `mixed` state, set `value` to `null`. A help button appears within a view and opens app-specific help documentation when clicked. All help buttons are circular, consistently sized buttons that contain a question mark icon. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/buttons/help-buttons/) -![HelpButton Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/buttonsHelp.png) +![HelpButton Example](https://imgur.com/DlP7uLV.png) Here's an example of how to create a help button: @@ -517,7 +517,7 @@ A radio button is a small, circular button followed by a title. Typically presen buttons provide the user a set of related but mutually exclusive choices. A radio button’s state is either on (a filled circle) or off (an empty circle). [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/buttons/radio-buttons/) -![RadioButton Preview](https://developer.apple.com/design/human-interface-guidelines/macos/images/radioButtons.png) +![RadioButton Preview](https://imgur.com/HI0eQsU.png) Here's an example of how to create a basic radio button: @@ -636,8 +636,8 @@ Push buttons are the standard button type in macOS. Push buttons contain text— complete a task. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/buttons/push-buttons/) | Dark Theme | Light Theme | -|--------------------------------------------|--------------------------------------------| -| | | +| ------------------------------------------ | ------------------------------------------ | +| | | ℹ️ **Note** ℹ️ Native push buttons can be styled as text-only, text with an icon, or icon-only. Currently, text-only push buttons are supported. To create an icon-only button, use the `MacosIconButton` widget. @@ -666,8 +666,8 @@ control sizes: * `regular` | Off | On | -|--------------------------------------------|--------------------------------------------| -| | | +| ------------------------------------------ | ------------------------------------------ | +| | | Here's an example of how to create a basic toggle switch: @@ -689,7 +689,7 @@ Learn more about switches [here](https://developer.apple.com/design/human-interf Displays one or more navigational tabs in a single horizontal group. Used by `MacosTabView` to navigate between the different tabs of the tab bar. - + The typical usage of this widget is by `MacosTabView`, to control the navigation of its children. You do not need to specify a `MacosSegmentedControl` with your `MacosTabView`, as it is built by that widget. @@ -722,9 +722,10 @@ showMacosAlertDialog( ); ``` -![](https://imgur.com/G3dcjew.png) -![](https://imgur.com/YHtgv59.png) -![](https://imgur.com/xuBR5qK.png) +![](https://imgur.com/4zbGsFi.png) +![](https://imgur.com/5fgkRU9.png) +![](https://imgur.com/jOyJrZO.png) +![](https://imgur.com/NX9taPj.png) ## MacosSheet @@ -736,7 +737,7 @@ showMacosSheet( ); ``` -![](https://imgur.com/NV0o5Ws.png) +![](https://imgur.com/Mnw2ywm.png) # Fields @@ -823,8 +824,6 @@ Progress indicators have two distinct styles: People don't interact with progress indicators; however, they are often accompanied by a button for canceling the corresponding operation. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/indicators/progress-indicators/) -![Progress Indicator Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/ProgressIndicators_Lead.png) - ### ProgressCircle A `ProgressCircle` can be either determinate or indeterminate. @@ -870,10 +869,8 @@ indicator styles, each with a different appearance, for communicating capacity, A capacity indicator illustrates the current level in relation to a finite capacity. Capacity indicators are often used when communicating factors like disk and battery usage. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/indicators/level-indicators#capacity-indicators) -| Continuous | Discrete | -| ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| ![Continuous CapacityIndicator Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/indicators-continous.png) | ![Discrete CapacityIndicator Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/indicators-discrete.png) | -| A horizontal translucent track that fills with a colored bar to indicate the current value. Tick marks are often displayed to provide context. | A horizontal row of separate, equally sized, rectangular segments. The number of segments matches the total capacity, and the segments fill completely—never partially—with color to indicate the current value. | + + Here's an example of how to create an interactive continuous capacity indicator: @@ -919,7 +916,7 @@ MacosSlider( A rating indicator uses a series of horizontally arranged graphical symbols to communicate a ranking level. The default symbol is a star. -![RatingIndicator Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/indicator-rating.png) +![RatingIndicator Example](https://imgur.com/ySQBpL6.png) A rating indicator doesn’t display partial symbols—its value is rounded in order to display complete symbols only. Within a rating indicator, symbols are always the same distance apart and don't expand or shrink to fit the control. @@ -939,22 +936,6 @@ RatingIndicator( ) ``` -### RelevanceIndicator - -A relevance indicator communicates relevancy using a series of vertical bars. It often appears in a list of search -results for reference when sorting and comparing multiple items. [Learn more](https://developer.apple.com/design/human-interface-guidelines/macos/indicators/level-indicators#relevance-indicators) - -![RelevanceIndicator Example](https://developer.apple.com/design/human-interface-guidelines/macos/images/indicator-relevance.png) - -Here's an example of how to create a relevance indicator: - -```dart -RelevanceIndicator( - value: 15, - amount: 20, -) -``` - # Selectors ## MacosDatePicker diff --git a/example/lib/pages/buttons_page.dart b/example/lib/pages/buttons_page.dart index 80ff6cf8..fd61774f 100644 --- a/example/lib/pages/buttons_page.dart +++ b/example/lib/pages/buttons_page.dart @@ -431,6 +431,10 @@ class _ButtonsPageState extends State { ], ), const SizedBox(height: 16), + const WidgetTextTitle1(widgetName: 'HelpButton'), + Divider(color: MacosTheme.of(context).dividerColor), + HelpButton(onPressed: () {}), + const SizedBox(height: 16), Text( 'Icon Buttons', style: MacosTypography.of(context).title1, diff --git a/example/lib/pages/dialogs_page.dart b/example/lib/pages/dialogs_page.dart index 1fea4908..33764bda 100644 --- a/example/lib/pages/dialogs_page.dart +++ b/example/lib/pages/dialogs_page.dart @@ -209,47 +209,50 @@ class DemoSheet extends StatelessWidget { @override Widget build(BuildContext context) { return MacosSheet( - child: Center( - child: Column( - children: [ - const SizedBox(height: 50), - const FlutterLogo( - size: 56, - ), - const SizedBox(height: 24), - Text( - 'Welcome to macos_ui', - style: MacosTheme.of(context).typography.largeTitle.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 24), - const MacosListTile( - leading: MacosIcon(CupertinoIcons.lightbulb), - title: Text( - 'A robust library of Flutter components for macOS', - //style: MacosTheme.of(context).typography.headline, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Center( + child: Column( + children: [ + const SizedBox(height: 50), + const FlutterLogo( + size: 56, ), - subtitle: Text( - 'Create native looking macOS applications using Flutter', + const SizedBox(height: 24), + Text( + 'Welcome to macos_ui', + style: MacosTheme.of(context).typography.largeTitle.copyWith( + fontWeight: FontWeight.bold, + ), ), - ), - const SizedBox(height: 16), - const MacosListTile( - leading: MacosIcon(CupertinoIcons.bolt), - title: Text( - 'Create beautiful macOS applications in minutes', - //style: MacosTheme.of(context).typography.headline, + const SizedBox(height: 24), + const MacosListTile( + leading: MacosIcon(CupertinoIcons.lightbulb), + title: Text( + 'A robust library of Flutter components for macOS', + //style: MacosTheme.of(context).typography.headline, + ), + subtitle: Text( + 'Create native looking macOS applications using Flutter', + ), ), - ), - const Spacer(), - PushButton( - controlSize: ControlSize.regular, - child: const Text('Get started'), - onPressed: () => Navigator.of(context).pop(), - ), - const SizedBox(height: 50), - ], + const SizedBox(height: 16), + const MacosListTile( + leading: MacosIcon(CupertinoIcons.bolt), + title: Text( + 'Create beautiful macOS applications in minutes', + //style: MacosTheme.of(context).typography.headline, + ), + ), + const Spacer(), + PushButton( + controlSize: ControlSize.regular, + child: const Text('Get started'), + onPressed: () => Navigator.of(context).pop(), + ), + const SizedBox(height: 50), + ], + ), ), ), ); diff --git a/example/lib/pages/indicators_page.dart b/example/lib/pages/indicators_page.dart index 7e8a6b20..511b40c6 100644 --- a/example/lib/pages/indicators_page.dart +++ b/example/lib/pages/indicators_page.dart @@ -1,4 +1,5 @@ import 'package:example/widgets/widget_text_title1.dart'; +import 'package:example/widgets/widget_text_title2.dart'; import 'package:macos_ui/macos_ui.dart'; // ignore: implementation_imports import 'package:macos_ui/src/library.dart'; @@ -117,8 +118,17 @@ class _IndicatorsPageState extends State { onChanged: (v) => setState(() => ratingValue = v), ), const SizedBox(height: 20), - const WidgetTextTitle1(widgetName: 'ProgressCircle'), + Text( + 'Progress Indicators', + style: MacosTypography.of(context).title1, + ), Divider(color: MacosTheme.of(context).dividerColor), + const WidgetTextTitle2(widgetName: 'ProgressBar'), + const SizedBox(height: 8), + const ProgressBar(value: 50), + const SizedBox(height: 16), + const WidgetTextTitle2(widgetName: 'ProgressCircle'), + const SizedBox(height: 8), const Row( children: [ Text('Indeterminate'), @@ -137,10 +147,6 @@ class _IndicatorsPageState extends State { const WidgetTextTitle1(widgetName: 'RelevanceIndicator'), Divider(color: MacosTheme.of(context).dividerColor), const SizedBox(height: 8), - const RelevanceIndicator( - value: 25, - amount: 50, - ), ], ), ); diff --git a/example/pubspec.lock b/example/pubspec.lock index 7e7d770a..45c959ef 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -150,7 +150,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.10" + version: "2.0.0-beta.11" macos_window_utils: dependency: transitive description: diff --git a/lib/src/buttons/help_button.dart b/lib/src/buttons/help_button.dart index 0c5d5ece..f825baf5 100644 --- a/lib/src/buttons/help_button.dart +++ b/lib/src/buttons/help_button.dart @@ -190,6 +190,8 @@ class HelpButtonState extends State constraints: const BoxConstraints( minWidth: 20, minHeight: 20, + maxWidth: 20, + maxHeight: 20, ), child: FadeTransition( opacity: _opacityAnimation, @@ -212,16 +214,14 @@ class HelpButtonState extends State ), ], ), - child: Padding( - padding: const EdgeInsets.all(8), - child: Align( - alignment: widget.alignment, - widthFactor: 1.0, - heightFactor: 1.0, - child: Icon( - CupertinoIcons.question, - color: foregroundColor, - ), + child: Align( + alignment: widget.alignment, + widthFactor: 1.0, + heightFactor: 1.0, + child: Icon( + CupertinoIcons.question, + color: foregroundColor, + size: 13, ), ), ), diff --git a/lib/src/indicators/relevance_indicator.dart b/lib/src/indicators/relevance_indicator.dart index fa4d6e19..5d8d0dd2 100644 --- a/lib/src/indicators/relevance_indicator.dart +++ b/lib/src/indicators/relevance_indicator.dart @@ -5,6 +5,7 @@ import 'package:macos_ui/src/library.dart'; /// A relevance indicator communicates relevancy using a series /// of vertical bars. It often appears in a list of search results /// for reference when sorting and comparing multiple items. +@Deprecated('Apple no longer supports this component.') class RelevanceIndicator extends StatelessWidget { /// Creates a relevance indicator. /// diff --git a/pubspec.yaml b/pubspec.yaml index e76d7bfb..9cf52acf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 2.0.0-beta.10 +version: 2.0.0-beta.11 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" diff --git a/test/indicators/relevance_indicator_test.dart b/test/indicators/relevance_indicator_test.dart deleted file mode 100644 index dbdea9ba..00000000 --- a/test/indicators/relevance_indicator_test.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:macos_ui/macos_ui.dart'; - -void main() { - testWidgets('debugFillProperties', (tester) async { - final builder = DiagnosticPropertiesBuilder(); - const RelevanceIndicator( - value: 50, - amount: 100, - ).debugFillProperties(builder); - - final description = builder.properties - .where((node) => !node.isFiltered(DiagnosticLevel.info)) - .map((node) => node.toString()) - .toList(); - - expect( - description, - [ - 'value: 50', - 'amount: 100', - 'barHeight: 20.0', - 'barWidth: 0.8', - 'selectedColor: label(*color = Color(0xff000000)*, darkColor = Color(0xffffffff), resolved by: UNRESOLVED)', - 'unselectedColor: secondaryLabel(*color = Color(0x993c3c43)*, darkColor = Color(0x99ebebf5), highContrastColor = Color(0xad3c3c43), darkHighContrastColor = Color(0xadebebf5), resolved by: UNRESOLVED)', - 'semanticLabel: null', - ], - ); - }); -} From 34fad11c7c7d83166f6a31e871b2c465fa9c14a2 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 19 Jul 2023 15:52:56 -0400 Subject: [PATCH 37/45] feat: version `2.0.0` (#462) * Merges beta changelog entries into a singular `2.0.0` entry * Updates the version to `2.0.0` * Fixes the codecov badge --- CHANGELOG.md | 117 ++++++++++++++++--------------------------- README.md | 2 +- example/pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 45 insertions(+), 78 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 397f0187..6a4ed815 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,86 +1,53 @@ -## [2.0.0-beta.11] -🚨 Breaking Changes 🚨 -* `RelevanceIndicator` has been deprecated - -🔄 Updated 🔄 -* `HelpButton` now sizes itself according to specification - -## [2.0.0-beta.10] -🛠️ Fixed 🛠️ -* Ensure builds targeting web do not utilize any `macos_window_utils` code -* Ensure builds targeting web are themed correctly - -🔄 Updated 🔄 -* `MacosTypography` white and black are now factory constructors called `darkOpaque()` and `lightOpaque()` to reflect -Apple's naming conventions. -* `PushButton` now uses the correct `body` text style instead of the incorrect `headline` -* `Toolbar` now uses the correct `title3` text style instead of the incorrect `headline` -* `MacosTheme` sets the global typography, per theme, more efficiently - -## [2.0.0-beta.9] -* `ResizablePane` can now disallow the usage of its internal scrollbar via the `ReziablePane.noScrollBar` constructor. - -## [2.0.0-beta.8] -✨ New ✨ -* `MacosFontWeight` allows using Apple-specific font weights like `w510`, `w590`, and `w860`. - -🛠️ Fixed 🛠️ -* `MacosTypography.black` and `MacosTypography.white` now conform to specification by using `MacosColors.labelColor` - -## [2.0.0-beta.7] -✨ New ✨ -* You can now call `MacosTypography.of(context)` as a shorthand for retrieving the typography used in your `MacosTheme`. - -🔄 Updated 🔄 -* `MacosAlertDialog` now defines `primaryButton` and `secondaryButton` to be of type `PushButton`. -* `MacosAlertDialog` now requires that `primaryButton` and `secondaryButton` to have `controlSize`s of `ControlSize.large`. -* `MacosAlertDialog` docs now suggest that `appIcon` should be of size 64x64. - -## [2.0.0-beta.6] -🔄 Updated 🔄 -* `MacosCheckbox` appearance more closely matches its native counterpart. +## [2.0.0] +### 🚨 Breaking Changes 🚨 +* Migrate `macos_ui` to [macos_window_utils](https://pub.dev/packages/macos_window_utils), which provides the following benefits: + * Window animation smoothness is drastically improved, particularly when miniaturizing and deminiaturizing the application window. + * Some visual artifacts that occurred while the window was being (de)miniaturized (such as the application's shadow going missing) no longer occur. + * The sidebar remains transparent when the app's brightness setting mismatches the OS setting. + * Wallpaper tinting is now supported. + * To migrate an existing application, please refer to the “Modern window look” section in the README. -## [2.0.0-beta.5] -🚨 Breaking Changes 🚨 +* Support for Flutter 3.10 and Dart 3 * `PushButton` has been updated to support the `ControlSize` enum. * The `buttonSize` property has been changed to `controlSize`. * Buttons can now be any of the following sizes: mini, small, regular, or large. -* `PushButton.isSecondary` is now `PushButton.secondary`. - -🔄 Updated 🔄 -* `PushButton`'s secondary and disabled colors more closely match their native counterparts. - -## [2.0.0-beta.4] -🛠️ Fixed 🛠️ -* `ToolBar`s in use where a `SideBar` is not present will now have their title's avoid the traffic lights (native window controls). - -## [2.0.0-beta.3] -✨ New ✨ -* Added support for `weekdayAbbreviations` and `monthAbbreviations` to `MacosDatePicker`. -* Added support for `dateFormat` to `MacosDatePicker`. -* Added support for `startWeekOnMonday` to `MacosDatePicker`. - -🛠️ Fixed 🛠️ -* Better UX of the click on the calendar elements in `MacosDatePicker` +* `PushButton.isSecondary` is now `PushButton.secondary`. +* `MacosAlertDialog`: `primaryButton` and `secondaryButton` are now declared to be of type `PushButton`. +* `RelevanceIndicator` has been deprecated +* `MacosTypography` white and black are now factory constructors called `darkOpaque()` and `lightOpaque()` to reflect + Apple's naming conventions. -## [2.0.0-beta.2] -✨New ✨ +### ✨ New ✨ * `MacosSwitch` has been completely rewritten and now matches the native macOS switch in appearance and behavior. * A `ControlSize` enum has been introduced, which will allow widgets to more closely match their native counterparts. +* `MacosTypography` + * You can now call `MacosTypography.of(context)` as a shorthand for retrieving the typography used in your `MacosTheme`. + * `MacosFontWeight` allows using Apple-specific font weights like `w510`, `w590`, and `w860`. +* Localization + * Added support for `weekdayAbbreviations` and `monthAbbreviations` to `MacosDatePicker`. + * Added support for `dateFormat` to `MacosDatePicker`. + * Added support for `startWeekOnMonday` to `MacosDatePicker`. + +### 🔄 Updated 🔄 +* `MacosColor` has been updated with some previously missing elements. +* `PushButton` + * Now uses the correct `body` text style instead of the incorrect `headline` +* `PushButton`'s secondary and disabled colors more closely match their native counterparts. +* `MacosCheckbox` appearance more closely matches its native counterpart. +* `MacosAlertDialog` + * `primaryButton` and `secondaryButton` are now required to have `controlSize`s of `ControlSize.large`. + * Docs now suggest that `appIcon` should be of size 64x64. +* `Toolbar` now uses the correct `title3` text style instead of the incorrect `headline` +* `MacosTheme` sets the global typography more efficiently +* `HelpButton` now sizes itself according to specification +* `ResizablePane` can now disallow the usage of its internal scrollbar via the `ReziablePane.noScrollBar` constructor. -🔄 Updated 🔄 -* Some previously missing elements of the `MacosColor` class have been added. - -## [2.0.0-beta.1] -🚨 Breaking Changes 🚨 -* Migrate `macos_ui` to [macos_window_utils](https://pub.dev/packages/macos_window_utils), which provides the following benefits: - * Window animation smoothness is drastically improved, particularly when miniaturizing and deminiaturizing the application window. - * Some visual artifacts that occurred while the window was being (de)miniaturized (such as the application's shadow going missing) no longer occur. - * The sidebar remains transparent when the app's brightness setting mismatches the OS setting. - * Wallpaper tinting is now supported. -* Support Flutter 3.10 and Dart 3 - -To migrate an existing application, please refer to the “Modern window look” section in the README. +### 🛠️ Fixed 🛠️ +* Clicking on the calendar elements in `MacosDatePicker` has better UX +* `ToolBar`s in use where a `SideBar` is not present will now have their title's avoid the traffic lights (native window controls). +* `MacosTypography.darkOpaque()` and `MacosTypography.lightOpaque()` now conform to specification by using `MacosColors.labelColor` +* Ensure builds targeting web do not utilize any `macos_window_utils` code +* Ensure builds targeting web are themed correctly ## [1.12.5] * Fixed a bug where the `Sidebar.key` parameter wasn't used, which caused certain layouts to be unachievable. diff --git a/README.md b/README.md index 482e67a5..23a54c9a 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Guides, codelabs, and other documentation can be found at https://macosui.dev [![Flutter Analysis](https://github.com/GroovinChip/macos_ui/actions/workflows/flutter_analysis.yml/badge.svg?branch=stable)](https://github.com/GroovinChip/macos_ui/actions/workflows/flutter_analysis.yml) [![Pana Analysis](https://github.com/GroovinChip/macos_ui/actions/workflows/pana_analysis.yml/badge.svg)](https://github.com/GroovinChip/macos_ui/actions/workflows/pana_analysis.yml) [![codecov](https://github.com/GroovinChip/macos_ui/actions/workflows/codecov.yaml/badge.svg)](https://github.com/GroovinChip/macos_ui/actions/workflows/codecov.yaml) -[![codecov](https://codecov.io/gh/GroovinChip/macos_ui/branch/dev/graph/badge.svg?token=1SZGEVVMCH)](https://codecov.io/gh/GroovinChip/macos_ui) +[![codecov](https://codecov.io/gh/macosui/macos_ui/branch/dev/graph/badge.svg?token=1SZGEVVMCH)](https://codecov.io/gh/macosui/macos_ui) diff --git a/example/pubspec.lock b/example/pubspec.lock index 45c959ef..dc586a2a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -150,7 +150,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0-beta.11" + version: "2.0.0" macos_window_utils: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9cf52acf..13865b31 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 2.0.0-beta.11 +version: 2.0.0 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From e4d3645718d2ffb2406e95d206db1d983a073e46 Mon Sep 17 00:00:00 2001 From: Reuben Turner Date: Wed, 19 Jul 2023 15:54:25 -0400 Subject: [PATCH 38/45] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a4ed815..0c59bae9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## [2.0.0] ### 🚨 Breaking Changes 🚨 -* Migrate `macos_ui` to [macos_window_utils](https://pub.dev/packages/macos_window_utils), which provides the following benefits: +* `macos_ui` has been migrated to utilize [macos_window_utils](https://pub.dev/packages/macos_window_utils) under the hood, which provides the following benefits: * Window animation smoothness is drastically improved, particularly when miniaturizing and deminiaturizing the application window. * Some visual artifacts that occurred while the window was being (de)miniaturized (such as the application's shadow going missing) no longer occur. * The sidebar remains transparent when the app's brightness setting mismatches the OS setting. From f7dc715b9c77b096425f159ddd2b503275d6e5cb Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Mon, 31 Jul 2023 17:10:01 -0500 Subject: [PATCH 39/45] Update CONTRIBUTING.md Fixes typo in contributing doc --- CONTRIBUTING.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 15075873..97e7a96c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,8 +25,7 @@ This repository uses [conventional commits](https://www.conventionalcommits.org/ ## Pull Requests As mentioned above, all pull requests should target `dev`. -Before opening your pull request, please ensure that the following -following requirements are met: +Before opening your pull request, please ensure that the following requirements are met: * You have run `flutter pub get` at the package level * You have incremented the version number in `pubspec.yaml` properly * You have updated the `CHANGELOG.md` file accordingly @@ -38,4 +37,4 @@ A note for authorized maintainers: Pull requests should **always** be merged via ### Versioning -`macos_ui` uses semantic versioning. Please ensure your updates follow this method accordingly. \ No newline at end of file +`macos_ui` uses semantic versioning. Please ensure your updates follow this method accordingly. From 8c250edb0b1015a9db9ef09033f0d5ab15a144ce Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Mon, 31 Jul 2023 17:21:48 -0500 Subject: [PATCH 40/45] Alter copy of mock_canvas to unblock moving to flutter_test --- CHANGELOG.md | 3 + example/pubspec.lock | 46 +++++++------- pubspec.lock | 62 ++++++++++++------- test/indicators/capacity_indicators_test.dart | 4 +- test/mock_canvas.dart | 8 +-- 5 files changed, 71 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c59bae9..02a7bb2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [2.0.1] +* Modified mock_canvas copy from the framework to unblock move to flutter_test + ## [2.0.0] ### 🚨 Breaking Changes 🚨 * `macos_ui` has been migrated to utilize [macos_window_utils](https://pub.dev/packages/macos_window_utils) under the hood, which provides the following benefits: diff --git a/example/pubspec.lock b/example/pubspec.lock index dc586a2a..3dbde41a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.18.0" crypto: dependency: transitive description: @@ -128,14 +128,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" lints: dependency: transitive description: @@ -163,18 +155,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -288,26 +280,26 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -328,10 +320,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.1" typed_data: dependency: transitive description: @@ -412,6 +404,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" win32: dependency: transitive description: @@ -429,5 +429,5 @@ packages: source: hosted version: "1.0.0" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.0" diff --git a/pubspec.lock b/pubspec.lock index f80cd1d5..708421c4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "8880b4cfe7b5b17d57c052a5a3a8cc1d4f546261c7cc8fbd717bd53f48db0568" + sha256: "0816708f5fbcacca324d811297153fe3c8e047beb5c6752e12292d2974c17045" url: "https://pub.dev" source: hosted - version: "59.0.0" + version: "62.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: a89627f49b0e70e068130a36571409726b04dab12da7e5625941d2c8ec278b96 + sha256: "21862995c9932cd082f89d72ae5f5e2c110d1a0204ad06e4ebaee8307b76b834" url: "https://pub.dev" source: hosted - version: "5.11.1" + version: "6.0.0" args: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.18.0" convert: dependency: transitive description: @@ -89,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + dart_internal: + dependency: transitive + description: + name: dart_internal + sha256: "689dccc3d5f62affd339534cca548dce12b3a6b32f0f10861569d3025efc0567" + url: "https://pub.dev" + source: hosted + version: "0.2.9" fake_async: dependency: transitive description: @@ -199,18 +207,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -332,26 +340,26 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -372,26 +380,26 @@ packages: dependency: transitive description: name: test - sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" + sha256: b9a384c4b9c4966dbf7215e7c033a78db1da7e5dcaf8da9232c5f24735f65652 url: "https://pub.dev" source: hosted - version: "1.24.1" + version: "1.24.5" test_api: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.1" test_core: dependency: transitive description: name: test_core - sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" + sha256: c6a536288535efef8526eea8adfa4e25fdd2849fa7f457ecb2a52099998ce8f7 url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.5.5" typed_data: dependency: transitive description: @@ -424,6 +432,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -449,5 +465,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <3.3.0" flutter: ">=3.10.0" diff --git a/test/indicators/capacity_indicators_test.dart b/test/indicators/capacity_indicators_test.dart index 5f5eb33a..3286af26 100644 --- a/test/indicators/capacity_indicators_test.dart +++ b/test/indicators/capacity_indicators_test.dart @@ -81,7 +81,7 @@ void main() { expect( find.byType(CapacityIndicator), // each discrete segment is drawn 3 times, two times with fill, last time with stroke - paintsExactlyCountTimes(#drawRRect, 20 * 3), + paintedExactlyCountTimes(#drawRRect, 20 * 3), ); }, ); @@ -110,7 +110,7 @@ void main() { // each discrete segment is drawn 3 times, background - fill - stroke // a filled segment is drawn by fromLTRBR with LTRB=0,0,8,16 // an empty segment is drawnby fromLTRBAndCorners with LTRB=0,0,0,16 - paints + painted ..rrect( rrect: RRect.fromLTRBR( 0.0, diff --git a/test/mock_canvas.dart b/test/mock_canvas.dart index c86c7482..7c7cd5f7 100644 --- a/test/mock_canvas.dart +++ b/test/mock_canvas.dart @@ -40,16 +40,16 @@ import 'recording_canvas.dart'; /// To match something which paints nothing, see [paintsNothing]. /// /// To match something which asserts instead of painting, see [paintsAssertion]. -PaintPattern get paints => _TestRecordingCanvasPatternMatcher(); +PaintPattern get painted => _TestRecordingCanvasPatternMatcher(); /// Matches objects or functions that does not paint anything on the canvas. -Matcher get paintsNothing => _TestRecordingCanvasPaintsNothingMatcher(); +Matcher get paintedNothing => _TestRecordingCanvasPaintsNothingMatcher(); /// Matches objects or functions that assert when they try to paint. -Matcher get paintsAssertion => _TestRecordingCanvasPaintsAssertionMatcher(); +Matcher get paintedAssertion => _TestRecordingCanvasPaintsAssertionMatcher(); /// Matches objects or functions that draw `methodName` exactly `count` number of times. -Matcher paintsExactlyCountTimes(Symbol methodName, int count) { +Matcher paintedExactlyCountTimes(Symbol methodName, int count) { return _TestRecordingCanvasPaintsCountMatcher(methodName, count); } From a5e85324e8831633084bfcb4169f3f0ba4bffb62 Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Wed, 2 Aug 2023 20:06:40 -0500 Subject: [PATCH 41/45] Fix compilation errors from mock canvas copy (#468) * Alter copy of mock_canvas to unblock moving to flutter_test * Fix compilation errors * changelog * Update changelog --- CHANGELOG.md | 3 - test/indicators/capacity_indicators_test.dart | 1 + test/mock_canvas.dart | 60 +++++++++---------- test/recording_canvas.dart | 28 ++++----- 4 files changed, 45 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02a7bb2a..0c59bae9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,3 @@ -## [2.0.1] -* Modified mock_canvas copy from the framework to unblock move to flutter_test - ## [2.0.0] ### 🚨 Breaking Changes 🚨 * `macos_ui` has been migrated to utilize [macos_window_utils](https://pub.dev/packages/macos_window_utils) under the hood, which provides the following benefits: diff --git a/test/indicators/capacity_indicators_test.dart b/test/indicators/capacity_indicators_test.dart index 3286af26..1780a362 100644 --- a/test/indicators/capacity_indicators_test.dart +++ b/test/indicators/capacity_indicators_test.dart @@ -5,6 +5,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:macos_ui/macos_ui.dart'; +// TODO(): Remove once mock_canvas in flutter_test reaches stable. import '../mock_canvas.dart'; void main() { diff --git a/test/mock_canvas.dart b/test/mock_canvas.dart index 7c7cd5f7..ee18e4f3 100644 --- a/test/mock_canvas.dart +++ b/test/mock_canvas.dart @@ -564,7 +564,7 @@ class _MismatchedCall { const _MismatchedCall(this.message, this.callIntroduction, this.call); final String message; final String callIntroduction; - final RecordedInvocation call; + final RecordInvocation call; } bool _evaluatePainter(Object? object, Canvas canvas, PaintingContext context) { @@ -593,9 +593,9 @@ bool _evaluatePainter(Object? object, Canvas canvas, PaintingContext context) { abstract class _TestRecordingCanvasMatcher extends Matcher { @override bool matches(Object? object, Map matchState) { - final TestRecordingCanvas canvas = TestRecordingCanvas(); - final TestRecordingPaintingContext context = - TestRecordingPaintingContext(canvas); + final TestRecordCanvas canvas = TestRecordCanvas(); + final TestRecordPaintingContext context = + TestRecordPaintingContext(canvas); final StringBuffer description = StringBuffer(); String prefixMessage = 'unexpectedly failed.'; bool result = false; @@ -618,7 +618,7 @@ abstract class _TestRecordingCanvasMatcher extends Matcher { if (!result) { if (canvas.invocations.isNotEmpty) { description.write('The complete display list was:'); - for (final RecordedInvocation call in canvas.invocations) { + for (final RecordInvocation call in canvas.invocations) { description.write('\n * $call'); } } @@ -628,7 +628,7 @@ abstract class _TestRecordingCanvasMatcher extends Matcher { } bool _evaluatePredicates( - Iterable calls, StringBuffer description); + Iterable calls, StringBuffer description); @override Description describeMismatch( @@ -658,9 +658,9 @@ class _TestRecordingCanvasPaintsCountMatcher @override bool _evaluatePredicates( - Iterable calls, StringBuffer description) { + Iterable calls, StringBuffer description) { int count = 0; - for (final RecordedInvocation call in calls) { + for (final RecordInvocation call in calls) { if (call.invocation.isMethod && call.invocation.memberName == _methodName) { count++; @@ -683,8 +683,8 @@ class _TestRecordingCanvasPaintsNothingMatcher @override bool _evaluatePredicates( - Iterable calls, StringBuffer description) { - final Iterable paintingCalls = + Iterable calls, StringBuffer description) { + final Iterable paintingCalls = _filterCanvasCalls(calls); if (paintingCalls.isEmpty) { return true; @@ -702,10 +702,10 @@ class _TestRecordingCanvasPaintsNothingMatcher ]; // Filters out canvas calls that are not painting anything. - static Iterable _filterCanvasCalls( - Iterable canvasCalls) { + static Iterable _filterCanvasCalls( + Iterable canvasCalls) { return canvasCalls.where( - (RecordedInvocation canvasCall) => + (RecordInvocation canvasCall) => !_nonPaintingOperations.contains(canvasCall.invocation.memberName), ); } @@ -714,9 +714,9 @@ class _TestRecordingCanvasPaintsNothingMatcher class _TestRecordingCanvasPaintsAssertionMatcher extends Matcher { @override bool matches(Object? object, Map matchState) { - final TestRecordingCanvas canvas = TestRecordingCanvas(); - final TestRecordingPaintingContext context = - TestRecordingPaintingContext(canvas); + final TestRecordCanvas canvas = TestRecordCanvas(); + final TestRecordPaintingContext context = + TestRecordPaintingContext(canvas); final StringBuffer description = StringBuffer(); String prefixMessage = 'unexpectedly failed.'; bool result = false; @@ -738,7 +738,7 @@ class _TestRecordingCanvasPaintsAssertionMatcher extends Matcher { if (!result) { if (canvas.invocations.isNotEmpty) { description.write('The complete display list was:'); - for (final RecordedInvocation call in canvas.invocations) { + for (final RecordInvocation call in canvas.invocations) { description.write('\n * $call'); } } @@ -1017,7 +1017,7 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher @override bool _evaluatePredicates( - Iterable calls, StringBuffer description) { + Iterable calls, StringBuffer description) { if (calls.isEmpty) { description.writeln('It painted nothing.'); return false; @@ -1030,7 +1030,7 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher return false; } final Iterator<_PaintPredicate> predicate = _predicates.iterator; - final Iterator call = calls.iterator..moveNext(); + final Iterator call = calls.iterator..moveNext(); try { while (predicate.moveNext()) { predicate.current.match(call); @@ -1056,12 +1056,12 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher } abstract class _PaintPredicate { - void match(Iterator call); + void match(Iterator call); @protected - void checkMethod(Iterator call, Symbol symbol) { + void checkMethod(Iterator call, Symbol symbol) { int others = 0; - final RecordedInvocation firstCall = call.current; + final RecordInvocation firstCall = call.current; while (!call.current.invocation.isMethod || call.current.invocation.memberName != symbol) { others += 1; @@ -1108,7 +1108,7 @@ abstract class _DrawCommandPaintPredicate extends _PaintPredicate { String get methodName => _symbolName(symbol); @override - void match(Iterator call) { + void match(Iterator call) { checkMethod(call, symbol); final int actualArgumentCount = call.current.invocation.positionalArguments.length; @@ -1605,7 +1605,7 @@ class _ShadowPredicate extends _PaintPredicate { } @override - void match(Iterator call) { + void match(Iterator call) { checkMethod(call, symbol); verifyArguments(call.current.invocation.positionalArguments); call.moveNext(); @@ -1771,8 +1771,8 @@ class _SomethingPaintPredicate extends _PaintPredicate { final PaintPatternPredicate predicate; @override - void match(Iterator call) { - RecordedInvocation currentCall; + void match(Iterator call) { + RecordInvocation currentCall; bool testedAllCalls = false; do { if (testedAllCalls) { @@ -1807,9 +1807,9 @@ class _EverythingPaintPredicate extends _PaintPredicate { final PaintPatternPredicate predicate; @override - void match(Iterator call) { + void match(Iterator call) { do { - final RecordedInvocation currentCall = call.current; + final RecordInvocation currentCall = call.current; if (!currentCall.invocation.isMethod) { throw 'It called $currentCall, which was not a method, when the paint pattern expected a method call'; } @@ -1842,7 +1842,7 @@ class _FunctionPaintPredicate extends _PaintPredicate { final List arguments; @override - void match(Iterator call) { + void match(Iterator call) { checkMethod(call, symbol); if (call.current.invocation.positionalArguments.length != arguments.length) { @@ -1874,7 +1874,7 @@ class _FunctionPaintPredicate extends _PaintPredicate { class _SaveRestorePairPaintPredicate extends _PaintPredicate { @override - void match(Iterator call) { + void match(Iterator call) { checkMethod(call, #save); int depth = 1; while (depth > 0) { diff --git a/test/recording_canvas.dart b/test/recording_canvas.dart index d2bd2f33..8ff6abe4 100644 --- a/test/recording_canvas.dart +++ b/test/recording_canvas.dart @@ -7,9 +7,9 @@ import 'package:flutter/rendering.dart'; /// An [Invocation] and the [stack] trace that led to it. /// /// Used by [TestRecordingCanvas] to trace canvas calls. -class RecordedInvocation { +class RecordInvocation { /// Create a record for an invocation list. - const RecordedInvocation(this.invocation, {required this.stack}); + const RecordInvocation(this.invocation, {required this.stack}); /// The method that was called and its arguments. /// @@ -37,13 +37,13 @@ class RecordedInvocation { /// A [Canvas] for tests that records its method calls. /// -/// This class can be used in conjunction with [TestRecordingPaintingContext] +/// This class can be used in conjunction with [TestRecordPaintingContext] /// to record the [Canvas] method calls made by a renderer. For example: /// /// ```dart /// RenderBox box = tester.renderObject(find.text('ABC')); -/// TestRecordingCanvas canvas = TestRecordingCanvas(); -/// TestRecordingPaintingContext context = TestRecordingPaintingContext(canvas); +/// TestRecordCanvas canvas = TestRecordCanvas(); +/// TestRecordPaintingContext context = TestRecordPaintingContext(canvas); /// box.paint(context, Offset.zero); /// // Now test the expected canvas.invocations. /// ``` @@ -53,10 +53,10 @@ class RecordedInvocation { /// that the test requires. /// /// For simple tests, consider using the [paints] matcher, which overlays a -/// pattern matching API over [TestRecordingCanvas]. -class TestRecordingCanvas implements Canvas { +/// pattern matching API over [TestRecordCanvas]. +class TestRecordCanvas implements Canvas { /// All of the method calls on this canvas. - final List invocations = []; + final List invocations = []; int _saveCount = 0; @@ -67,13 +67,13 @@ class TestRecordingCanvas implements Canvas { void save() { _saveCount += 1; invocations - .add(RecordedInvocation(_MethodCall(#save), stack: StackTrace.current)); + .add(RecordInvocation(_MethodCall(#save), stack: StackTrace.current)); } @override void saveLayer(Rect? bounds, Paint paint) { _saveCount += 1; - invocations.add(RecordedInvocation( + invocations.add(RecordInvocation( _MethodCall(#saveLayer, [bounds, paint]), stack: StackTrace.current)); } @@ -83,20 +83,20 @@ class TestRecordingCanvas implements Canvas { _saveCount -= 1; assert(_saveCount >= 0); invocations.add( - RecordedInvocation(_MethodCall(#restore), stack: StackTrace.current)); + RecordInvocation(_MethodCall(#restore), stack: StackTrace.current)); } @override void noSuchMethod(Invocation invocation) { - invocations.add(RecordedInvocation(invocation, stack: StackTrace.current)); + invocations.add(RecordInvocation(invocation, stack: StackTrace.current)); } } /// A [PaintingContext] for tests that use [TestRecordingCanvas]. -class TestRecordingPaintingContext extends ClipContext +class TestRecordPaintingContext extends ClipContext implements PaintingContext { /// Creates a [PaintingContext] for tests that use [TestRecordingCanvas]. - TestRecordingPaintingContext(this.canvas); + TestRecordPaintingContext(this.canvas); @override final Canvas canvas; From 1c405dc843417156951f127c69b6e29de9951741 Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Mon, 7 Aug 2023 12:43:17 -0400 Subject: [PATCH 42/45] run pub get --- example/pubspec.lock | 46 ++++++++++++++++---------------- pubspec.lock | 62 ++++++++++++++++---------------------------- 2 files changed, 46 insertions(+), 62 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 3dbde41a..dc586a2a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.1" crypto: dependency: transitive description: @@ -128,6 +128,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" lints: dependency: transitive description: @@ -155,18 +163,18 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.2.0" meta: dependency: transitive description: @@ -280,26 +288,26 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -320,10 +328,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.5.1" typed_data: dependency: transitive description: @@ -404,14 +412,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - web: - dependency: transitive - description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 - url: "https://pub.dev" - source: hosted - version: "0.1.4-beta" win32: dependency: transitive description: @@ -429,5 +429,5 @@ packages: source: hosted version: "1.0.0" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" + dart: ">=3.0.0 <4.0.0" flutter: ">=3.10.0" diff --git a/pubspec.lock b/pubspec.lock index 708421c4..dfa8a61c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0816708f5fbcacca324d811297153fe3c8e047beb5c6752e12292d2974c17045" + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a url: "https://pub.dev" source: hosted - version: "62.0.0" + version: "61.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "21862995c9932cd082f89d72ae5f5e2c110d1a0204ad06e4ebaee8307b76b834" + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "5.13.0" args: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.1" convert: dependency: transitive description: @@ -89,14 +89,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" - dart_internal: - dependency: transitive - description: - name: dart_internal - sha256: "689dccc3d5f62affd339534cca548dce12b3a6b32f0f10861569d3025efc0567" - url: "https://pub.dev" - source: hosted - version: "0.2.9" fake_async: dependency: transitive description: @@ -207,18 +199,18 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.2.0" meta: dependency: transitive description: @@ -340,26 +332,26 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -380,26 +372,26 @@ packages: dependency: transitive description: name: test - sha256: b9a384c4b9c4966dbf7215e7c033a78db1da7e5dcaf8da9232c5f24735f65652 + sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" url: "https://pub.dev" source: hosted - version: "1.24.5" + version: "1.24.1" test_api: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.5.1" test_core: dependency: transitive description: name: test_core - sha256: c6a536288535efef8526eea8adfa4e25fdd2849fa7f457ecb2a52099998ce8f7 + sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" url: "https://pub.dev" source: hosted - version: "0.5.5" + version: "0.5.1" typed_data: dependency: transitive description: @@ -432,14 +424,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - web: - dependency: transitive - description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 - url: "https://pub.dev" - source: hosted - version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -465,5 +449,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.0-185.0.dev <3.3.0" + dart: ">=3.0.0 <4.0.0" flutter: ">=3.10.0" From 9f006f2c2e06af348c8d8308fa6531858db50b49 Mon Sep 17 00:00:00 2001 From: Adrian Samoticha <86920182+Adrian-Samoticha@users.noreply.github.com> Date: Sat, 19 Aug 2023 19:55:15 +0200 Subject: [PATCH 43/45] Resolve #445 (#471) --- CHANGELOG.md | 5 + .../Flutter/GeneratedPluginRegistrant.swift | 2 + example/macos/Podfile.lock | 8 +- example/pubspec.lock | 68 ++- lib/src/buttons/popup_button.dart | 11 +- lib/src/buttons/push_button.dart | 515 ++++++++++++++---- lib/src/enums/accent_color.dart | 27 + lib/src/library.dart | 2 +- lib/src/theme/macos_theme.dart | 7 +- lib/src/theme/push_button_theme.dart | 41 +- lib/src/utils/accent_color_listener.dart | 160 ++++++ .../macos_brightness_override_handler.dart | 25 + lib/src/{ => utils}/utils.dart | 28 +- lib/src/utils/window_main_state_listener.dart | 109 ++++ pubspec.lock | 74 ++- pubspec.yaml | 6 +- test/buttons/push_button_test.dart | 1 - test/theme/push_button_theme_test.dart | 97 ---- 18 files changed, 919 insertions(+), 267 deletions(-) create mode 100644 lib/src/enums/accent_color.dart create mode 100644 lib/src/utils/accent_color_listener.dart create mode 100644 lib/src/utils/macos_brightness_override_handler.dart rename lib/src/{ => utils}/utils.dart (67%) create mode 100644 lib/src/utils/window_main_state_listener.dart delete mode 100644 test/theme/push_button_theme_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c59bae9..f27d69cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [2.0.1] +### 🔄 Updated 🔄 +* `PushButton` has received a facelift. It now mimics the look and feel of native macOS buttons more closely. + * **Note:** As a result, its `pressedOpacity` property and the `PushButtonTheme` class have been deprecated. + ## [2.0.0] ### 🚨 Breaking Changes 🚨 * `macos_ui` has been migrated to utilize [macos_window_utils](https://pub.dev/packages/macos_window_utils) under the hood, which provides the following benefits: diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 0179c12c..1e64f18d 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,14 @@ import FlutterMacOS import Foundation +import appkit_ui_element_colors import macos_ui import macos_window_utils import path_provider_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AppkitUiElementColorsPlugin.register(with: registry.registrar(forPlugin: "AppkitUiElementColorsPlugin")) MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin")) MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 2fa81174..6aa1d86e 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - appkit_ui_element_colors (1.0.0): + - FlutterMacOS - FlutterMacOS (1.0.0) - macos_ui (0.1.0): - FlutterMacOS @@ -11,6 +13,7 @@ PODS: - FlutterMacOS DEPENDENCIES: + - appkit_ui_element_colors (from `Flutter/ephemeral/.symlinks/plugins/appkit_ui_element_colors/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - macos_ui (from `Flutter/ephemeral/.symlinks/plugins/macos_ui/macos`) - macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`) @@ -18,6 +21,8 @@ DEPENDENCIES: - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: + appkit_ui_element_colors: + :path: Flutter/ephemeral/.symlinks/plugins/appkit_ui_element_colors/macos FlutterMacOS: :path: Flutter/ephemeral macos_ui: @@ -30,6 +35,7 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: + appkit_ui_element_colors: 39bb2d80be3f19b152ccf4c70d5bbe6cba43d74a FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 macos_ui: 6229a8922cd97bafb7d9636c8eb8dfb0744183ca macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 @@ -38,4 +44,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9 -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/example/pubspec.lock b/example/pubspec.lock index dc586a2a..f61168d0 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + appkit_ui_element_colors: + dependency: transitive + description: + name: appkit_ui_element_colors + sha256: c3e50f900aae314d339de489535736238627071457c4a4a2dbbb1545b4f04f22 + url: "https://pub.dev" + source: hosted + version: "1.0.0" async: dependency: transitive description: @@ -37,10 +45,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" crypto: dependency: transitive description: @@ -57,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -112,6 +128,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" + gradient_borders: + dependency: transitive + description: + name: gradient_borders + sha256: "69eeaff519d145a4c6c213ada1abae386bcc8981a4970d923e478ce7ba19e309" + url: "https://pub.dev" + source: hosted + version: "1.0.0" http: dependency: transitive description: @@ -128,14 +152,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" lints: dependency: transitive description: @@ -150,31 +166,31 @@ packages: path: ".." relative: true source: path - version: "2.0.0" + version: "2.0.1" macos_window_utils: dependency: transitive description: name: macos_window_utils - sha256: b78a210aa70ca7ccad6e7b7b810fb4689c507f4a46e299214900b2a1eb70ea23 + sha256: "43a90473f8786f00f07203e6819dab67e032f8896dafa4a6f85fbc71fba32c0b" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.2.0" matcher: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -288,10 +304,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -328,10 +344,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" typed_data: dependency: transitive description: @@ -412,6 +428,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" win32: dependency: transitive description: @@ -429,5 +453,5 @@ packages: source: hosted version: "1.0.0" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.0" diff --git a/lib/src/buttons/popup_button.dart b/lib/src/buttons/popup_button.dart index 8158e63c..37c6559b 100644 --- a/lib/src/buttons/popup_button.dart +++ b/lib/src/buttons/popup_button.dart @@ -1194,10 +1194,13 @@ class _MacosPopupButtonState extends State> } hintIndex = items.length; - items.add(IgnorePointer( - ignoringSemantics: false, - child: displayedHint, - )); + items.add( + ExcludeSemantics( + child: IgnorePointer( + child: displayedHint, + ), + ), + ); } // If value is null (then _selectedIndex is null) then we diff --git a/lib/src/buttons/push_button.dart b/lib/src/buttons/push_button.dart index 19180016..df0cf4fe 100644 --- a/lib/src/buttons/push_button.dart +++ b/lib/src/buttons/push_button.dart @@ -2,7 +2,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; +import 'package:gradient_borders/gradient_borders.dart'; import 'package:macos_ui/macos_ui.dart'; +import 'package:macos_ui/src/enums/accent_color.dart'; import 'package:macos_ui/src/library.dart'; const _kMiniButtonSize = Size(26.0, 11.0); @@ -169,6 +171,9 @@ class PushButton extends StatefulWidget { /// /// This defaults to 0.4. If null, opacity will not change on pressed if using /// your own custom effects is desired. + @Deprecated("'PushButton' animations now match their native macOS’ " + "counterparts. Therefore, its opacity no longer changes when it is " + "pressed.") final double? pressedOpacity; /// The radius of the button's corners when it has a background color. @@ -195,8 +200,7 @@ class PushButton extends StatefulWidget { /// Whether the button is used as a secondary action button (e.g. Cancel buttons in dialogs) /// - /// Sets its background color to [PushButtonThemeData]'s [secondaryColor] attributes (defaults - /// are gray colors). Can still be overridden if the [color] attribute is non-null. + /// Can still be overridden if the [color] attribute is non-null. final bool? secondary; /// Whether the button is enabled or disabled. Buttons are disabled by default. To @@ -209,7 +213,6 @@ class PushButton extends StatefulWidget { properties.add(EnumProperty('controlSize', controlSize)); properties.add(ColorProperty('color', color)); properties.add(ColorProperty('disabledColor', disabledColor)); - properties.add(DoubleProperty('pressedOpacity', pressedOpacity)); properties.add(DiagnosticsProperty('alignment', alignment)); properties.add(StringProperty('semanticLabel', semanticLabel)); properties.add(DiagnosticsProperty('borderRadius', borderRadius)); @@ -227,104 +230,102 @@ class PushButton extends StatefulWidget { class PushButtonState extends State with SingleTickerProviderStateMixin { - // Eyeballed values. Feel free to tweak. - static const Duration kFadeOutDuration = Duration(milliseconds: 10); - static const Duration kFadeInDuration = Duration(milliseconds: 100); - final Tween _opacityTween = Tween(begin: 1.0); - - late AnimationController _animationController; - late Animation _opacityAnimation; - - @override - void initState() { - super.initState(); - _animationController = AnimationController( - duration: const Duration(milliseconds: 200), - value: 0.0, - vsync: this, - ); - _opacityAnimation = _animationController - .drive(CurveTween(curve: Curves.decelerate)) - .drive(_opacityTween); - _setTween(); - } - @override void didUpdateWidget(PushButton oldWidget) { super.didUpdateWidget(oldWidget); - _setTween(); - } - - void _setTween() { - _opacityTween.end = widget.pressedOpacity ?? 1.0; } void _handleTapDown(TapDownDetails event) { if (!buttonHeldDown) { - buttonHeldDown = true; - _animate(); + setState(() => buttonHeldDown = true); } } void _handleTapUp(TapUpDetails event) { if (buttonHeldDown) { - buttonHeldDown = false; - _animate(); + setState(() => buttonHeldDown = false); } } void _handleTapCancel() { if (buttonHeldDown) { - buttonHeldDown = false; - _animate(); + setState(() => buttonHeldDown = false); } } - void _animate() { - if (_animationController.isAnimating) return; - final bool wasHeldDown = buttonHeldDown; - final TickerFuture ticker = buttonHeldDown - ? _animationController.animateTo(1.0, duration: kFadeOutDuration) - : _animationController.animateTo(0.0, duration: kFadeInDuration); - ticker.then((void value) { - if (mounted && wasHeldDown != buttonHeldDown) _animate(); - }); - } - - @override - void dispose() { - _animationController.dispose(); - super.dispose(); - } - @visibleForTesting bool buttonHeldDown = false; - @override - Widget build(BuildContext context) { - assert(debugCheckHasMacosTheme(context)); + AccentColor get _accentColor => + AccentColorListener.instance.currentAccentColor ?? AccentColor.blue; + + BoxDecoration _getBoxDecoration() { + // If the window isn’t currently the main window (that is, it is not in + // focus), make the button look as if it was a secondary button. + final isMainWindow = WindowMainStateListener.instance.isMainWindow; + + return _BoxDecorationBuilder.buildBoxDecoration( + accentColor: _accentColor, + isEnabled: widget.enabled, + isDarkModeEnabled: MacosTheme.of(context).brightness.isDark, + isSecondary: !isMainWindow || (widget.secondary ?? false), + ); + } + + Color _getBackgroundColor() { final bool enabled = widget.enabled; final bool isSecondary = widget.secondary != null && widget.secondary!; final MacosThemeData theme = MacosTheme.of(context); - final Color backgroundColor = MacosDynamicColor.resolve( + + // If the window isn’t currently the main window (that is, it is not in + // focus), make the button look as if it was a secondary button. + final isWindowMain = WindowMainStateListener.instance.isMainWindow; + + return MacosDynamicColor.resolve( widget.color ?? - (isSecondary - ? theme.pushButtonTheme.secondaryColor! - : theme.pushButtonTheme.color!), + _BoxDecorationBuilder.getGradientColors( + accentColor: _accentColor, + isEnabled: enabled, + isDarkModeEnabled: theme.brightness.isDark, + isSecondary: isSecondary || !isWindowMain, + ).first, context, ); + } - final disabledColor = !isSecondary - ? backgroundColor.withOpacity(0.5) - : backgroundColor.withOpacity(0.25); + Color _getForegroundColor(Color backgroundColor) { + final MacosThemeData theme = MacosTheme.of(context); - final Color foregroundColor = widget.enabled - ? textLuminance(backgroundColor) - : theme.brightness.isDark - ? const Color.fromRGBO(255, 255, 255, 0.25) - : const Color.fromRGBO(0, 0, 0, 0.25); + final blendedBackgroundColor = Color.lerp( + theme.canvasColor, + backgroundColor, + backgroundColor.opacity, + )!; - final baseStyle = theme.typography.body.copyWith(color: foregroundColor); + return widget.enabled + ? textLuminance(blendedBackgroundColor) + : textLuminance(blendedBackgroundColor).withOpacity(0.25); + } + + BoxDecoration _getClickEffectBoxDecoration() { + final MacosThemeData theme = MacosTheme.of(context); + final isDark = theme.brightness.isDark; + + final color = isDark + ? const MacosColor.fromRGBO(255, 255, 255, 0.15) + : const MacosColor.fromRGBO(0, 0, 0, 0.06); + + return BoxDecoration( + color: color, + borderRadius: widget.controlSize.borderRadius, + ); + } + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMacosTheme(context)); + final bool enabled = widget.enabled; + final MacosThemeData theme = MacosTheme.of(context); return MouseRegion( cursor: widget.mouseCursor!, @@ -339,29 +340,45 @@ class PushButtonState extends State label: widget.semanticLabel, child: ConstrainedBox( constraints: widget.controlSize.constraints, - child: FadeTransition( - opacity: _opacityAnimation, - child: DecoratedBox( - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - borderRadius: widget.controlSize.borderRadius, - ), - // color: !enabled ? disabledColor : backgroundColor, - color: enabled ? backgroundColor : disabledColor, - ), - child: Padding( - padding: widget.controlSize.padding, - child: Align( - alignment: widget.alignment, - widthFactor: 1.0, - heightFactor: 1.0, - child: DefaultTextStyle( - style: widget.controlSize.textStyle(baseStyle), - child: widget.child, - ), - ), - ), - ), + child: StreamBuilder( + stream: AccentColorListener.instance.onChanged, + builder: (context, _) { + return StreamBuilder( + stream: WindowMainStateListener.instance.onChanged, + builder: (context, _) { + final Color backgroundColor = _getBackgroundColor(); + + final Color foregroundColor = + _getForegroundColor(backgroundColor); + + final baseStyle = + theme.typography.body.copyWith(color: foregroundColor); + + return DecoratedBox( + decoration: _getBoxDecoration().copyWith( + borderRadius: widget.controlSize.borderRadius, + ), + child: Container( + foregroundDecoration: buttonHeldDown + ? _getClickEffectBoxDecoration() + : const BoxDecoration(), + child: Padding( + padding: widget.controlSize.padding, + child: Align( + alignment: widget.alignment, + widthFactor: 1.0, + heightFactor: 1.0, + child: DefaultTextStyle( + style: widget.controlSize.textStyle(baseStyle), + child: widget.child, + ), + ), + ), + ), + ); + }, + ); + }, ), ), ), @@ -369,3 +386,317 @@ class PushButtonState extends State ); } } + +class _BoxDecorationBuilder { + /// Gets the colors to use for the [BoxDecoration]’s gradient based on the + /// provided [accentColor], [isEnabled], and [isDarkModeEnabled] properties. + static List getGradientColors({ + required AccentColor accentColor, + required bool isEnabled, + required bool isDarkModeEnabled, + required bool isSecondary, + }) { + final isEnabledFactor = isEnabled ? 1.0 : 0.5; + + if (isSecondary) { + return isDarkModeEnabled + ? [ + MacosColor.fromRGBO(255, 255, 255, 0.251 * isEnabledFactor), + MacosColor.fromRGBO(255, 255, 255, 0.251 * isEnabledFactor), + ] + : [ + MacosColor.fromRGBO(255, 255, 255, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(255, 255, 255, 1.0 * isEnabledFactor), + ]; + } + + if (isDarkModeEnabled) { + switch (accentColor) { + case AccentColor.blue: + return [ + MacosColor.fromRGBO(0, 114, 238, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(0, 94, 211, 1.0 * isEnabledFactor), + ]; + + case AccentColor.purple: + return [ + MacosColor.fromRGBO(135, 65, 131, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(120, 57, 116, 1.0 * isEnabledFactor), + ]; + + case AccentColor.pink: + return [ + MacosColor.fromRGBO(188, 52, 105, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(168, 46, 93, 1.0 * isEnabledFactor), + ]; + + case AccentColor.red: + return [ + MacosColor.fromRGBO(186, 53, 46, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(166, 48, 41, 1.0 * isEnabledFactor), + ]; + + case AccentColor.orange: + return [ + MacosColor.fromRGBO(212, 133, 33, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(189, 118, 30, 1.0 * isEnabledFactor), + ]; + + case AccentColor.yellow: + return [ + MacosColor.fromRGBO(229, 203, 35, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(204, 179, 21, 1.0 * isEnabledFactor), + ]; + + case AccentColor.green: + return [ + MacosColor.fromRGBO(58, 138, 46, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(52, 123, 39, 1.0 * isEnabledFactor), + ]; + + case AccentColor.graphite: + return [ + MacosColor.fromRGBO(64, 64, 64, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(57, 57, 57, 1.0 * isEnabledFactor), + ]; + + default: + throw UnimplementedError(); + } + } else { + switch (accentColor) { + case AccentColor.blue: + return [ + MacosColor.fromRGBO(39, 125, 255, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(1, 101, 255, 1.0 * isEnabledFactor), + ]; + + case AccentColor.purple: + return [ + MacosColor.fromRGBO(148, 73, 143, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(128, 39, 121, 1.0 * isEnabledFactor), + ]; + + case AccentColor.pink: + return [ + MacosColor.fromRGBO(212, 71, 125, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(203, 36, 101, 1.0 * isEnabledFactor), + ]; + + case AccentColor.red: + return [ + MacosColor.fromRGBO(198, 64, 57, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(188, 29, 21, 1.0 * isEnabledFactor), + ]; + + case AccentColor.orange: + return [ + MacosColor.fromRGBO(237, 154, 51, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(234, 136, 13, 1.0 * isEnabledFactor), + ]; + + case AccentColor.yellow: + return [ + MacosColor.fromRGBO(242, 211, 61, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(240, 203, 25, 1.0 * isEnabledFactor), + ]; + + case AccentColor.green: + return [ + MacosColor.fromRGBO(77, 161, 63, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(45, 143, 28, 1.0 * isEnabledFactor), + ]; + + case AccentColor.graphite: + return [ + MacosColor.fromRGBO(86, 86, 86, 1.0 * isEnabledFactor), + MacosColor.fromRGBO(55, 55, 55, 1.0 * isEnabledFactor), + ]; + + default: + throw UnimplementedError(); + } + } + } + + /// Gets the shadow to use for the [BoxDecoration] based on the provided + /// [accentColor], [isEnabled], and [isDarkModeEnabled] properties. + static List _getShadow({ + required AccentColor accentColor, + required bool isEnabled, + required bool isDarkModeEnabled, + required bool isSecondary, + }) { + final isEnabledFactor = isEnabled ? 1.0 : 0.5; + + if (isSecondary) { + return isDarkModeEnabled + ? [ + BoxShadow( + color: MacosColor.fromRGBO(0, 0, 0, 0.4 * isEnabledFactor), + blurRadius: 0.5, + offset: Offset.zero, + spreadRadius: 0.0, + blurStyle: BlurStyle.outer, + ), + ] + : [ + BoxShadow( + color: MacosColor.fromRGBO(0, 0, 0, 0.4 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + } + + if (isDarkModeEnabled) { + return [ + BoxShadow( + color: MacosColor.fromRGBO(0, 0, 0, 0.4 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + } else { + switch (accentColor) { + case AccentColor.blue: + return [ + BoxShadow( + color: MacosColor.fromRGBO(0, 103, 255, 0.21 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.purple: + return [ + BoxShadow( + color: MacosColor.fromRGBO(139, 29, 125, 0.21 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.pink: + return [ + BoxShadow( + color: MacosColor.fromRGBO(222, 0, 101, 0.21 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.red: + return [ + BoxShadow( + color: MacosColor.fromRGBO(188, 29, 21, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.orange: + return [ + BoxShadow( + color: MacosColor.fromRGBO(234, 136, 13, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.yellow: + return [ + BoxShadow( + color: MacosColor.fromRGBO(240, 203, 25, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.green: + return [ + BoxShadow( + color: MacosColor.fromRGBO(45, 143, 28, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + case AccentColor.graphite: + return [ + BoxShadow( + color: MacosColor.fromRGBO(55, 55, 55, 0.35 * isEnabledFactor), + blurRadius: 0.5, + offset: isEnabled ? const Offset(0.0, 0.3) : Offset.zero, + spreadRadius: 0.0, + blurStyle: isEnabled ? BlurStyle.normal : BlurStyle.outer, + ), + ]; + + default: + throw UnimplementedError(); + } + } + } + + /// Builds a [BoxDecoration] for a [MacosPushButton]. + static BoxDecoration buildBoxDecoration({ + required AccentColor accentColor, + required bool isEnabled, + required bool isDarkModeEnabled, + required bool isSecondary, + }) { + final isEnabledFactor = isEnabled ? 1.0 : 0.5; + + return BoxDecoration( + border: isDarkModeEnabled + ? GradientBoxBorder( + gradient: LinearGradient( + colors: [ + MacosColor.fromRGBO(255, 255, 255, 0.43 * isEnabledFactor), + const MacosColor.fromRGBO(255, 255, 255, 0.0), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: const [0.0, 0.2], + ), + width: 0.7, + ) + : null, + gradient: LinearGradient( + colors: getGradientColors( + accentColor: accentColor, + isEnabled: isEnabled, + isDarkModeEnabled: isDarkModeEnabled, + isSecondary: isSecondary, + ), + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + boxShadow: _getShadow( + accentColor: accentColor, + isEnabled: isEnabled, + isDarkModeEnabled: isDarkModeEnabled, + isSecondary: isSecondary, + ), + ); + } +} diff --git a/lib/src/enums/accent_color.dart b/lib/src/enums/accent_color.dart new file mode 100644 index 00000000..95ab8ea2 --- /dev/null +++ b/lib/src/enums/accent_color.dart @@ -0,0 +1,27 @@ +/// The macOS accent color which can be changed by the user in *System Settings* +/// → *Appearance* → *Accent color*. +enum AccentColor { + /// The blue accent color. + blue, + + /// The purple accent color. + purple, + + /// The pink accent color. + pink, + + /// The red accent color. + red, + + /// The orange accent color. + orange, + + /// The yellow accent color. + yellow, + + /// The green accent color. + green, + + /// The graphite accent color. + graphite, +} diff --git a/lib/src/library.dart b/lib/src/library.dart index 37fb1228..2273d447 100644 --- a/lib/src/library.dart +++ b/lib/src/library.dart @@ -32,4 +32,4 @@ export 'package:flutter/material.dart' MaterialState; export 'package:flutter/widgets.dart'; -export 'utils.dart'; +export 'utils/utils.dart'; diff --git a/lib/src/theme/macos_theme.dart b/lib/src/theme/macos_theme.dart index 87405882..0bda4d11 100644 --- a/lib/src/theme/macos_theme.dart +++ b/lib/src/theme/macos_theme.dart @@ -514,8 +514,7 @@ class MacosThemeData with Diagnosticable { typography: MacosTypography.lerp(a.typography, b.typography, t), helpButtonTheme: HelpButtonThemeData.lerp(a.helpButtonTheme, b.helpButtonTheme, t), - pushButtonTheme: - PushButtonThemeData.lerp(a.pushButtonTheme, b.pushButtonTheme, t), + pushButtonTheme: a.pushButtonTheme, tooltipTheme: MacosTooltipThemeData.lerp(a.tooltipTheme, b.tooltipTheme, t), visualDensity: VisualDensity.lerp(a.visualDensity, b.visualDensity, t), @@ -581,7 +580,7 @@ class MacosThemeData with Diagnosticable { canvasColor: canvasColor ?? this.canvasColor, dividerColor: dividerColor ?? this.dividerColor, typography: this.typography.merge(typography), - pushButtonTheme: this.pushButtonTheme.merge(pushButtonTheme), + pushButtonTheme: this.pushButtonTheme, helpButtonTheme: this.helpButtonTheme.merge(helpButtonTheme), tooltipTheme: this.tooltipTheme.merge(tooltipTheme), visualDensity: visualDensity ?? this.visualDensity, @@ -605,7 +604,7 @@ class MacosThemeData with Diagnosticable { canvasColor: other.canvasColor, dividerColor: other.dividerColor, typography: typography.merge(other.typography), - pushButtonTheme: pushButtonTheme.merge(other.pushButtonTheme), + pushButtonTheme: pushButtonTheme, helpButtonTheme: helpButtonTheme.merge(other.helpButtonTheme), tooltipTheme: tooltipTheme.merge(other.tooltipTheme), visualDensity: other.visualDensity, diff --git a/lib/src/theme/push_button_theme.dart b/lib/src/theme/push_button_theme.dart index c7d27039..0c69aebd 100644 --- a/lib/src/theme/push_button_theme.dart +++ b/lib/src/theme/push_button_theme.dart @@ -9,8 +9,6 @@ import 'package:macos_ui/src/library.dart'; /// * [PushButtonThemeData], which is used to configure this theme. class PushButtonTheme extends InheritedTheme { /// Create a [PushButtonTheme]. - /// - /// The [data] parameter must not be null. const PushButtonTheme({ super.key, required this.data, @@ -18,6 +16,9 @@ class PushButtonTheme extends InheritedTheme { }); /// The configuration of this theme. + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") final PushButtonThemeData data; /// The closest instance of this class that encloses the given context. @@ -30,6 +31,9 @@ class PushButtonTheme extends InheritedTheme { /// ```dart /// PushButtonTheme theme = PushButtonTheme.of(context); /// ``` + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") static PushButtonThemeData of(BuildContext context) { final PushButtonTheme? buttonTheme = context.dependOnInheritedWidgetOfExactType(); @@ -37,11 +41,17 @@ class PushButtonTheme extends InheritedTheme { } @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") Widget wrap(BuildContext context, Widget child) { return PushButtonTheme(data: data, child: child); } @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") bool updateShouldNotify(PushButtonTheme oldWidget) => data != oldWidget.data; } @@ -63,15 +73,27 @@ class PushButtonThemeData with Diagnosticable { }); /// The default background color for [PushButton] + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") final Color? color; /// The default disabled color for [PushButton] + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") final Color? disabledColor; /// The default secondary color (e.g. Cancel/Go back buttons) for [PushButton] + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") final Color? secondaryColor; /// Copies this [PushButtonThemeData] into another. + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") PushButtonThemeData copyWith({ Color? color, Color? disabledColor, @@ -87,6 +109,9 @@ class PushButtonThemeData with Diagnosticable { /// Linearly interpolate between two [PushButtonThemeData]. /// /// All the properties must be non-null. + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") static PushButtonThemeData lerp( PushButtonThemeData a, PushButtonThemeData b, @@ -100,6 +125,9 @@ class PushButtonThemeData with Diagnosticable { } @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") bool operator ==(Object other) => identical(this, other) || other is PushButtonThemeData && @@ -109,9 +137,15 @@ class PushButtonThemeData with Diagnosticable { secondaryColor?.value == other.secondaryColor?.value; @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") int get hashCode => color.hashCode ^ disabledColor.hashCode; @override + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(ColorProperty('color', color)); @@ -120,6 +154,9 @@ class PushButtonThemeData with Diagnosticable { } /// Merges this [PushButtonThemeData] with another. + @Deprecated( + "'PushButton' no longer uses singular colors and therefore cannot " + "be themed using a 'PushButtonTheme'.") PushButtonThemeData merge(PushButtonThemeData? other) { if (other == null) return this; return copyWith( diff --git a/lib/src/utils/accent_color_listener.dart b/lib/src/utils/accent_color_listener.dart new file mode 100644 index 00000000..5965e3ab --- /dev/null +++ b/lib/src/utils/accent_color_listener.dart @@ -0,0 +1,160 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; + +import 'package:appkit_ui_element_colors/appkit_ui_element_colors.dart'; +import 'package:flutter/foundation.dart'; +import 'package:macos_ui/src/enums/accent_color.dart'; + +/// A class that listens for changes to the user’s selected system accent color. +/// +/// Native macOS applications respond to such changes immediately. +/// +/// Example using [StreamBuilder]: +/// +/// ```dart +/// StreamBuilder( +/// stream: AccentColorListener.instance.onChanged, +/// builder: (context, _) { +/// final AccentColor? accentColor = +/// AccentColorListener.instance.currentAccentColor; +/// +/// return SomeWidget( +/// accentColor: accentColor, +/// child: … +/// ); +/// }, +/// ); +/// ``` +class AccentColorListener { + /// A class that listens to accent color changes. + AccentColorListener() { + _init(); + } + + /// A shared instance of [AccentColorListener]. + static final instance = AccentColorListener(); + + /// A map which maps hue components of the [UiElementColor.controlAccentColor] + /// color captured with the [NSAppearanceName.aqua] appearance in the + /// [NSColorSpace.genericRGB] color space to the corresponding [AccentColor]. + static final hueComponentToAccentColor = { + 0.6085324903200698: AccentColor.blue, + 0.8285987697113538: AccentColor.purple, + 0.9209523937489168: AccentColor.pink, + 0.9861913496946438: AccentColor.red, + 0.06543037411201169: AccentColor.orange, + 0.11813830353929083: AccentColor.yellow, + 0.29428158007138466: AccentColor.green, + 0.0: AccentColor.graphite, + }; + + /// The active accent color selection. + AccentColor? _currentAccentColor; + + /// The currently active accent color. + AccentColor? get currentAccentColor => _currentAccentColor; + + /// Notifies listeners when the accent color changes. + final _accentColorStreamController = StreamController.broadcast(); + + /// Streams the user’s system accent color selection. + /// + /// Emits a new value whenever the system accent color selection changes. + Stream get onChanged => _accentColorStreamController.stream; + + /// A stream subscription for the [SystemColorObserver] stream. + StreamSubscription? _systemColorObserverStreamSubscription; + + /// Initializes this class. + void _init() { + if (kIsWeb) return; + if (!Platform.isMacOS) return; + + _initCurrentAccentColor(); + _initSystemColorObserver(); + } + + /// Disposes this listener. + void dispose() { + _systemColorObserverStreamSubscription?.cancel(); + } + + /// Initializes the current accent color. This method is to be called whenever + /// a change is detected. + Future _initCurrentAccentColor() async { + final hueComponent = await _getHueComponent(); + _currentAccentColor = _resolveAccentColorFromHueComponent(hueComponent); + _accentColorStreamController.add(null); + } + + /// Initializes the current system color observer. This method may only be + /// called once. + void _initSystemColorObserver() { + assert(_systemColorObserverStreamSubscription == null); + + _systemColorObserverStreamSubscription = + AppkitUiElementColors.systemColorObserver.stream.listen((_) { + _initCurrentAccentColor(); + _accentColorStreamController.add(null); + }); + } + + /// Returns the hue component of the active accent color selection on macOS. + Future _getHueComponent() async { + final color = await AppkitUiElementColors.getColorComponents( + uiElementColor: UiElementColor.controlAccentColor, + components: const { + NSColorComponent.hueComponent, + }, + colorSpace: NSColorSpace.genericRGB, + appearance: NSAppearanceName.aqua, + ); + + assert(color.containsKey("hueComponent")); + + return color["hueComponent"]!; + } + + /// Returns the [AccentColor] which corresponds to the provided + /// [hueComponent]. + AccentColor _resolveAccentColorFromHueComponent(double hueComponent) { + if (hueComponentToAccentColor.containsKey(hueComponent)) { + return hueComponentToAccentColor[hueComponent]!; + } + + debugPrint( + 'Warning: Falling back on slow accent color resolution. It’s possible ' + 'that the accent colors have changed in a recent version of macOS, thus ' + 'invalidating macos_ui’s accent colors, which were captured on macOS ' + 'Ventura. If you see this message, please notify a maintainer of the ' + 'macos_ui package.', + ); + + return _slowlyResolveAccentColorFromHueComponent(hueComponent); + } + + /// This is a fallback method in case [_resolveAccentColorFromHueComponent] + /// fails. + AccentColor _slowlyResolveAccentColorFromHueComponent(double hueComponent) { + final entries = hueComponentToAccentColor.entries; + var lowestDistance = double.maxFinite; + var toBeReturnedAccentColor = AccentColor.values.first; + + for (final entry in entries) { + final distance = _distanceBetweenHueComponents(hueComponent, entry.key); + if (distance < lowestDistance) { + lowestDistance = distance; + toBeReturnedAccentColor = entry.value; + } + } + + return toBeReturnedAccentColor; + } + + /// Returns the distance between two hue components. + double _distanceBetweenHueComponents(double component1, double component2) { + final rawDifference = (component1 - component2).abs(); + return sin(rawDifference * pi); + } +} diff --git a/lib/src/utils/macos_brightness_override_handler.dart b/lib/src/utils/macos_brightness_override_handler.dart new file mode 100644 index 00000000..3fad912b --- /dev/null +++ b/lib/src/utils/macos_brightness_override_handler.dart @@ -0,0 +1,25 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:macos_ui/macos_ui.dart'; + +/// A class that ensures that the application’s macOS window’s brightness +/// matches the given brightness. +class MacOSBrightnessOverrideHandler { + static Brightness? _lastBrightness; + + /// Ensures that the application’s macOS window’s brightness matches + /// [currentBrightness]. + /// + /// For performance reasons, the brightness setting will only be overridden if + /// [currentBrightness] differs from the value it had when this method was + /// previously called. Therefore, it is safe to call this method frequently. + static void ensureMatchingBrightness(Brightness currentBrightness) { + if (kIsWeb) return; + if (!Platform.isMacOS) return; + if (currentBrightness == _lastBrightness) return; + + WindowManipulator.overrideMacOSBrightness(dark: currentBrightness.isDark); + _lastBrightness = currentBrightness; + } +} diff --git a/lib/src/utils.dart b/lib/src/utils/utils.dart similarity index 67% rename from lib/src/utils.dart rename to lib/src/utils/utils.dart index 1f2d8a1e..3d63855c 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils/utils.dart @@ -1,9 +1,10 @@ -import 'dart:io'; - -import 'package:flutter/foundation.dart'; import 'package:macos_ui/macos_ui.dart'; import 'package:macos_ui/src/library.dart'; +export 'window_main_state_listener.dart'; +export 'accent_color_listener.dart'; +export 'macos_brightness_override_handler.dart'; + /// Asserts that the given context has a [MacosTheme] ancestor. /// /// To call this function, use the following pattern, typically in the @@ -65,24 +66,3 @@ class Unsupported { final String message; } - -/// A class that ensures that the application's macOS window's brightness -/// matches the given brightness. -class MacOSBrightnessOverrideHandler { - static Brightness? _lastBrightness; - - /// Ensures that the application's macOS window's brightness matches - /// [currentBrightness]. - /// - /// For performance reasons, the brightness setting will only be overridden if - /// [currentBrightness] differs from the value it had when this method was - /// previously called. Therefore, it is safe to call this method frequently. - static void ensureMatchingBrightness(Brightness currentBrightness) { - if (kIsWeb) return; - if (!Platform.isMacOS) return; - if (currentBrightness == _lastBrightness) return; - - WindowManipulator.overrideMacOSBrightness(dark: currentBrightness.isDark); - _lastBrightness = currentBrightness; - } -} diff --git a/lib/src/utils/window_main_state_listener.dart b/lib/src/utils/window_main_state_listener.dart new file mode 100644 index 00000000..c926f2b3 --- /dev/null +++ b/lib/src/utils/window_main_state_listener.dart @@ -0,0 +1,109 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:macos_ui/macos_ui.dart'; + +/// A class that listens for changes to the application’s main window. +/// +/// A common use-case for responding to such changes would be to mute the colors +/// of certain primary UI elements when the window is no longer in focus, which +/// is something native macOS applications do out of the box. +/// +/// Example using [StreamBuilder]: +/// +/// ```dart +/// StreamBuilder( +/// stream: WindowMainStateListener.instance.onChanged, +/// builder: (context, _) { +/// final bool isMainWindow +/// = WindowMainStateListener.instance.isMainWindow; +/// +/// return SomeWidget( +/// isMainWindow: isMainWindow, +/// child: … +/// ); +/// }, +/// ); +/// ``` +class WindowMainStateListener { + /// A class that listens for changes to the application’s window being the + /// main window, and notifies listeners. + WindowMainStateListener() { + _init(); + } + + /// A shared instance of [WindowMainStateListener]. + static final instance = WindowMainStateListener(); + + /// A [NSWindowDelegateHandle], to be used when disposing the listener. + NSWindowDelegateHandle? handle; + + /// Whether the window is currently the main window. + bool _isMainWindow = true; + + /// Whether the window is currently the main window. + bool get isMainWindow => _isMainWindow; + + /// Notifies listeners when the window’s main state changes. + final _windowMainStateStreamController = StreamController.broadcast(); + + /// A stream of the window’s main state. Emits a new value whenever the state + /// changes. + Stream get onChanged => _windowMainStateStreamController.stream; + + /// Initializes the listener. This should only be called once. + void _init() { + if (kIsWeb) return; + if (!Platform.isMacOS) return; + + _initDelegate(); + _initIsWindowMain(); + } + + /// Initializes the [NSWindowDelegate] to listen for main window changes. + void _initDelegate() { + final delegate = _WindowMainStateListenerDelegate( + onWindowDidBecomeMain: () { + _isMainWindow = true; + _windowMainStateStreamController.add(true); + }, + onWindowDidResignMain: () { + _isMainWindow = false; + _windowMainStateStreamController.add(false); + }, + ); + handle = WindowManipulator.addNSWindowDelegate(delegate); + } + + /// Initializes the [_isMainWindow] variable. + Future _initIsWindowMain() async { + _isMainWindow = await WindowManipulator.isMainWindow(); + _windowMainStateStreamController.add(_isMainWindow); + } + + /// Disposes this listener. + void dispose() { + handle?.removeFromHandler(); + } +} + +/// The [NSWindowDelegate] used by [WindowMainStateListener]. +class _WindowMainStateListenerDelegate extends NSWindowDelegate { + _WindowMainStateListenerDelegate({ + required this.onWindowDidBecomeMain, + required this.onWindowDidResignMain, + }); + + /// Called when the window becomes the main window. + final void Function() onWindowDidBecomeMain; + + /// Called when the window resigns as the main window. + final void Function() onWindowDidResignMain; + + @override + void windowDidBecomeMain() => onWindowDidBecomeMain(); + + @override + void windowDidResignMain() => onWindowDidResignMain(); +} diff --git a/pubspec.lock b/pubspec.lock index dfa8a61c..62a3f534 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.13.0" + appkit_ui_element_colors: + dependency: "direct main" + description: + name: appkit_ui_element_colors + sha256: c3e50f900aae314d339de489535736238627071457c4a4a2dbbb1545b4f04f22 + url: "https://pub.dev" + source: hosted + version: "1.0.0" args: dependency: transitive description: @@ -61,10 +69,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" convert: dependency: transitive description: @@ -89,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -139,6 +155,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + gradient_borders: + dependency: "direct main" + description: + name: gradient_borders + sha256: "69eeaff519d145a4c6c213ada1abae386bcc8981a4970d923e478ce7ba19e309" + url: "https://pub.dev" + source: hosted + version: "1.0.0" http_multi_server: dependency: transitive description: @@ -191,26 +215,26 @@ packages: dependency: "direct main" description: name: macos_window_utils - sha256: b78a210aa70ca7ccad6e7b7b810fb4689c507f4a46e299214900b2a1eb70ea23 + sha256: "43a90473f8786f00f07203e6819dab67e032f8896dafa4a6f85fbc71fba32c0b" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.2.0" matcher: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -259,6 +283,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" pool: dependency: transitive description: @@ -332,10 +364,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -372,26 +404,26 @@ packages: dependency: transitive description: name: test - sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" + sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46" url: "https://pub.dev" source: hosted - version: "1.24.1" + version: "1.24.3" test_api: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" test_core: dependency: transitive description: name: test_core - sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" + sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.5.3" typed_data: dependency: transitive description: @@ -424,6 +456,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -449,5 +489,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index 13865b31..8ffa130b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 2.0.0 +version: 2.0.1 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" @@ -11,7 +11,9 @@ environment: dependencies: flutter: sdk: flutter - macos_window_utils: ^1.1.3 + macos_window_utils: ^1.2.0 + gradient_borders: ^1.0.0 + appkit_ui_element_colors: ^1.0.0 dev_dependencies: flutter_test: diff --git a/test/buttons/push_button_test.dart b/test/buttons/push_button_test.dart index 8bb294a0..981c21df 100644 --- a/test/buttons/push_button_test.dart +++ b/test/buttons/push_button_test.dart @@ -99,7 +99,6 @@ void main() { 'controlSize: regular', 'color: null', 'disabledColor: null', - 'pressedOpacity: 0.4', 'alignment: Alignment.center', 'semanticLabel: null', 'borderRadius: BorderRadius.circular(4.0)', diff --git a/test/theme/push_button_theme_test.dart b/test/theme/push_button_theme_test.dart deleted file mode 100644 index 2ff90243..00000000 --- a/test/theme/push_button_theme_test.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:macos_ui/macos_ui.dart'; -import 'package:macos_ui/src/library.dart'; - -void main() { - group('PushButton theme tests', () { - test('lerps from light to dark', () { - final actual = - PushButtonThemeData.lerp(_pushButtonTheme, _pushButtonThemeDark, 1); - - expect(actual, _pushButtonThemeDark); - }); - - test('lerps from dark to light', () { - final actual = - PushButtonThemeData.lerp(_pushButtonThemeDark, _pushButtonTheme, 1); - - expect(actual, _pushButtonTheme); - }); - - test('copyWith, hashCode, ==', () { - expect( - const PushButtonThemeData(), - const PushButtonThemeData().copyWith(), - ); - expect( - const PushButtonThemeData().hashCode, - const PushButtonThemeData().copyWith().hashCode, - ); - }); - - testWidgets('debugFillProperties', (tester) async { - final builder = DiagnosticPropertiesBuilder(); - PushButtonThemeData( - color: MacosColors.appleBlue, - disabledColor: MacosColors.systemGrayColor.color, - secondaryColor: MacosColors.controlColor.color, - ).debugFillProperties(builder); - - final description = builder.properties - .where((node) => !node.isFiltered(DiagnosticLevel.info)) - .map((node) => node.toString()) - .toList(); - - expect( - description, - [ - 'color: MacosColor(0xff0433ff)', - 'disabledColor: MacosColor(0xff8e8e93)', - 'secondaryColor: Color(0x19000000)', - ], - ); - }); - - testWidgets('Default values in widget tree', (tester) async { - late BuildContext capturedContext; - await tester.pumpWidget( - MacosApp( - home: MacosWindow( - disableWallpaperTinting: true, - child: MacosScaffold( - children: [ - ContentArea( - builder: (context, _) { - capturedContext = context; - return const PushButton( - controlSize: ControlSize.regular, - child: Text('Push me'), - ); - }, - ), - ], - ), - ), - ), - ); - - final theme = PushButtonTheme.of(capturedContext); - expect(theme.color, const Color(0xff007aff)); - expect(theme.disabledColor, const Color.fromRGBO(244, 245, 245, 1.0)); - expect(theme.secondaryColor, MacosColors.white); - }); - }); -} - -final _pushButtonTheme = PushButtonThemeData( - color: MacosColors.appleRed, - disabledColor: MacosColors.systemGrayColor.color, - secondaryColor: MacosColors.controlColor.color, -); - -final _pushButtonThemeDark = PushButtonThemeData( - color: MacosColors.appleBlue, - disabledColor: MacosColors.systemGrayColor.darkColor, - secondaryColor: MacosColors.controlColor.darkColor, -); From c6d37d88e38bddc0723fee9eb57c146aa9f0053d Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 12 Oct 2023 10:13:18 -0700 Subject: [PATCH 44/45] Remove usages of '@image' directive (#483) * Remove usages of '@image' directive * changelog --- CHANGELOG.md | 4 ++++ lib/src/indicators/slider.dart | 1 - lib/src/layout/sidebar/sidebar_item.dart | 2 +- lib/src/layout/tab_view/tab_view.dart | 2 +- pubspec.yaml | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f27d69cf..b6ddedda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [2.0.2] +### 🛠️ Fixed 🛠️ +* Fixed images in generated documentation. + ## [2.0.1] ### 🔄 Updated 🔄 * `PushButton` has received a facelift. It now mimics the look and feel of native macOS buttons more closely. diff --git a/lib/src/indicators/slider.dart b/lib/src/indicators/slider.dart index 9e5e7514..e32fc360 100644 --- a/lib/src/indicators/slider.dart +++ b/lib/src/indicators/slider.dart @@ -20,7 +20,6 @@ const double _kDiscreteThumbBorderRadius = 8; /// The slider doesn't maintain any state itself, instead the user is expected to /// update this widget with a new [value] whenever the slider changes. /// -/// {@image } /// {@endtemplate} class MacosSlider extends StatelessWidget { /// {@macro macosSlider} diff --git a/lib/src/layout/sidebar/sidebar_item.dart b/lib/src/layout/sidebar/sidebar_item.dart index b7e2d487..1454ccbd 100644 --- a/lib/src/layout/sidebar/sidebar_item.dart +++ b/lib/src/layout/sidebar/sidebar_item.dart @@ -62,7 +62,7 @@ class SidebarItem with Diagnosticable { /// /// Typically a text indicator of a count of items, like in this /// screenshots from the Apple Notes app: - /// {@image } + /// final Widget? trailing; @override diff --git a/lib/src/layout/tab_view/tab_view.dart b/lib/src/layout/tab_view/tab_view.dart index fa32c734..cca58d2a 100644 --- a/lib/src/layout/tab_view/tab_view.dart +++ b/lib/src/layout/tab_view/tab_view.dart @@ -26,7 +26,7 @@ enum MacosTabPosition { /// {@template macosTabView} /// A multipage interface that displays one page at a time. /// -/// {@image } +/// /// /// A tab view contains a row of navigational items, [tabs], that move the /// user through the provided views ([children]). The user selects the desired diff --git a/pubspec.yaml b/pubspec.yaml index 8ffa130b..8d81c2da 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: macos_ui description: Flutter widgets and themes implementing the current macOS design language. -version: 2.0.1 +version: 2.0.2 homepage: "https://macosui.dev" repository: "https://github.com/GroovinChip/macos_ui" From 328f9cbf805b4365288ef1ee7304906239df8b9f Mon Sep 17 00:00:00 2001 From: GroovinChip Date: Thu, 12 Oct 2023 13:14:51 -0400 Subject: [PATCH 45/45] Run `pub get` --- example/pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index f61168d0..253f08cb 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -166,7 +166,7 @@ packages: path: ".." relative: true source: path - version: "2.0.1" + version: "2.0.2" macos_window_utils: dependency: transitive description: