diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec133a7..80abd99 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: flutter analyze . example - name: Build (Linux) run: | - sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev + sudo apt-get update -y && sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev flutter config --enable-linux-desktop cd example flutter create --platforms=linux . @@ -55,4 +55,4 @@ jobs: flutter create --platforms=windows . flutter doctor flutter build windows - if: matrix.os == 'windows-latest' \ No newline at end of file + if: matrix.os == 'windows-latest' diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bb6f3f..0857e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,21 @@ +# Changelog + ## 1.0.0 ### **BREAKING** - `AdwHeaderBar.minimal` is now `AdwHeaderBar.custom` - Remove `label` parameter from `AdwTextField` +- `ViewSwitcherStyle` is now `ViewSwitcherPolicy` +- `ViewSwitcherStyle.desktop` and `ViewSwitcherStyle.mobile` are also renamed to `ViewSwitcherPolicy.wide` and `ViewSwitcherPolicy.narrow` ### **Changes to widgets** +**ComboRow** +- Dropdown is now scrollable if too many elements are there + +**Flap** +- Renamed `flapController` to `controller` +- Moved most of the things into `FlapStyle` class to simplify its usage in `AdwScaffold` + **HeaderBar** - Now the `AdwHeaderBar` is not dependent on any package, `windowDecor` object is now optional - Add `isTransparent` parameter => Makes `AdwHeaderBar`'s background and border color @@ -12,6 +23,11 @@ **Popover** - Revisit popup menu by using `popover_gtk` package (popover package with fade transition) (#35) +**TextField** +- Add `autofocus` parameter +- Add `prefixIcon` parameter +- Add `onSubmitted` parameter + **ViewSwitcher** - Add `badge` in `AdwViewSwitcher` @@ -59,4 +75,4 @@ - Replace trailing with end - Replace center with title -For older Changelog visit: https://pub.dev/packages/gtk/changelog +Older CHANGELOG can be found [here](https://pub.dev/packages/gtk/changelog) diff --git a/README.md b/README.md index 9562147..9538c07 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Libadwaita's widgets for Flutter. Following Gnome HIG and available on all platf ## Usage - This only provides widgets, for theming you should consider [adwaita](https://pub.dev/packages/adwaita) or [yaru](https://github.com/ubuntu/yaru.dart) package. -- If you want custom titlebar then you can follow the steps for that on [`bitsdojo_window`](https://pub.dev/packages/bitsdojo_window) package. +- If you want custom titlebar then you can follow the steps for that on [`libadwaita_bitsdojo`](https://pub.dev/packages/libadwaita_bitsdojo) package. - Here is the list of widgets imported from libadwaita library : [widgets.dart](https://github.com/gtk-flutter/libadwaita/blob/main/lib/src/widgets/widgets.dart). See the example app in the `example` folder for more info. @@ -36,14 +36,14 @@ If you want to use default adwaita style window icons or icons using window_deco | Widget | Docs | | ------ | ---- | | `AdwHeaderBar` | Default HeaderBar | -| `AdwHeaderBar.bitsdojo` | HeaderBar to be used with [`bitsdojo`](#bitsdojo_window) package | +| `AdwHeaderBar.bitsdojo` | HeaderBar to be used with [`bitsdojo`](#libadwaita_bitsdojo) package | | `AdwHeaderBar.nativeshell` | HeaderBar to be used with [`nativeshell`](#nativeshell) package | If you want to have a custom icon for window button then you have to use any one of the following HeaderBar's: | Widget | Docs | | ------ | ---- | | `AdwHeaderBar.custom` | HeaderBar with custom icon | -| `AdwHeaderBar.customBitsdojo` | HeaderBar to be used with [`bitsdojo`](#bitsdojo_window) package with custom icon | +| `AdwHeaderBar.customBitsdojo` | HeaderBar to be used with [`bitsdojo`](#libadwaita_bitsdojo) package with custom icon | | `AdwHeaderBar.customNativeshell` | HeaderBar to be used with [`nativeshell`](#nativeshell) package with custom icon | ## Relavant Links @@ -62,14 +62,14 @@ For theming #### [**`adwaita_icons`**](https://pub.dev/packages/adwaita_icons) For Adwaita Icons -#### [**`bitsdojo_window`**](https://pub.dev/packages/bitsdojo_window) +#### [**`libadwaita_bitsdojo`**](https://pub.dev/packages/libadwaita_bitsdojo) Can be used with - `AdwHeaderBar.bitsdojo` - `AdwHeaderBar.customBitsdojo` Example: ```dart -import 'package:bitsdojo_window/bitsdojo_window.dart'; +import 'package:libadwaita_bitsdojo/libadwaita_bitsdojo.dart'; AdwHeaderBar.bitsdojo( ... @@ -78,6 +78,20 @@ AdwHeaderBar.bitsdojo( ) ``` +#### [**`libadwaita_searchbar`**](https://pub.dev/packages/libadwaita_searchbar) +Example: +```dart +import 'package:libadwaita_searchbar/libadwaita_searchbar.dart'; + +bool searchedTerm = ''; + +AdwSearchBar( + suggestions: const ['Hi', 'Hello'], + onSubmitted: (str) => setState(() => searchedTerm = str), + controller: const TextEditingController(), +) +``` + #### [**`nativeshell`**](https://pub.dev/packages/nativeshell ) Can be used with - `AdwHeaderBar.nativeshell` diff --git a/assets/icons/close.svg b/assets/icons/close.svg index 93f90dc..95d0d83 100644 --- a/assets/icons/close.svg +++ b/assets/icons/close.svg @@ -1,27 +1,5 @@ - - - - Gnome Symbolic Icon Theme - - - - image/svg+xml - - Gnome Symbolic Icon Theme - - - - - - - - - - - - + + + - - - - \ No newline at end of file + diff --git a/assets/icons/maximize.svg b/assets/icons/maximize.svg index e000ad3..79b4615 100644 --- a/assets/icons/maximize.svg +++ b/assets/icons/maximize.svg @@ -1,30 +1,5 @@ - - - - - - - image/svg+xml - - Gnome Symbolic Icon Theme - - - - - - - Gnome Symbolic Icon Theme - - - - - - - - - - - - + + + - \ No newline at end of file + diff --git a/assets/icons/minimize.svg b/assets/icons/minimize.svg index 94f9b5f..7600084 100644 --- a/assets/icons/minimize.svg +++ b/assets/icons/minimize.svg @@ -1,30 +1,5 @@ - - - - - - - image/svg+xml - - Gnome Symbolic Icon Theme - - - - - - - Gnome Symbolic Icon Theme - - - - - - - - - - - - + + + - \ No newline at end of file + diff --git a/example/example.md b/example/example.md new file mode 100644 index 0000000..bbaafbe --- /dev/null +++ b/example/example.md @@ -0,0 +1,180 @@ +# Examples for libadwaita flutter package + +## Demo App +[Here]('https://github.com/gtk-flutter/libadwaita/tree/main/example') is a demo app made with libadwaita package. + +## Minimal [`libadwaita_bitsdojo`](https://pub.dev/packages/libadwaita_bitsdojo) usage +```yaml +#pubspec.yaml +dependencies: + adwaita: + libadwaita: + libadwaita_bitsdojo: +``` + +```dart +// main.dart + +import 'package:adwaita/adwaita.dart'; +import 'package:flutter/material.dart'; +import 'package:libadwaita/libadwaita.dart'; +import 'package:libadwaita_bitsdojo/libadwaita_bitsdojo.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + theme: AdwaitaThemeData.light(), + darkTheme: AdwaitaThemeData.dark(), + home: MyHomePage(), + ); + } +} + +class MyHomePage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return AdwScaffold( + headerbar: (_) => AdwHeaderBar.bitsdojo( + appWindow: appWindow, + start: const [ + AdwHeaderButton( + icon: Icon(Icons.nightlight_round, size: 15), + ), + ], + title: const Text('Bitsdojo Window'), + ), + body: const Center( + child: Text('Welcome to Bitsdojo Window Example!'), + ), + ); + } +} +``` + +## Minimal [`nativeshell`](https://pub.dev/packages/nativeshell) usage +```yaml +#pubspec.yaml +dependencies: + adwaita: + libadwaita: + nativeshell: +``` + +```dart +// main.dart + +import 'package:adwaita/adwaita.dart'; +import 'package:flutter/material.dart'; +import 'package:libadwaita/libadwaita.dart'; +import 'package:nativeshell/nativeshell.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + theme: AdwaitaThemeData.light(), + darkTheme: AdwaitaThemeData.dark(), + home: WindowWidget( + onCreateState: (dynamic _) { + WindowState? state; + return state ??= MainWindowState(); + }, + ), + ); + } +} + +class MainWindowState extends WindowState { + @override + Future initializeWindow(Size contentSize) async { + await window.setStyle(WindowStyle(frame: WindowFrame.noTitle)); + await window.show(); + } + + @override + WindowSizingMode get windowSizingMode => + WindowSizingMode.atLeastIntrinsicSize; + + @override + Widget build(BuildContext context) { + return WindowLayoutProbe( + child: AdwScaffold( + headerbar: (_) => AdwHeaderBar.nativeshell( + window: window, + start: const [ + AdwHeaderButton( + icon: Icon(Icons.nightlight_round, size: 15), + ), + ], + title: const Text('Nativeshell'), + ), + body: const Center( + child: Text('Welcome to NativeShell Example!'), + ), + ), + ); + } +} +``` + +## Minimal [`window_decorations`](https://pub.dev/packages/window_decorations) usage +```yaml +#pubspec.yaml +dependencies: + adwaita: + libadwaita: + window_decorations: +``` + +```dart +// main.dart + +import 'package:adwaita/adwaita.dart'; +import 'package:flutter/material.dart'; +import 'package:libadwaita/libadwaita.dart'; +import 'package:window_decorations/window_decorations.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + theme: AdwaitaThemeData.light(), + darkTheme: AdwaitaThemeData.dark(), + home: MyHomePage(), + ); + } +} + +class MyHomePage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return AdwScaffold( + headerbar: (_) => AdwHeaderBar( + windowDecor: windowDecor, + onClose: () {}, + onMaximize: () {}, + onMinimize: () {}, + start: const [ + AdwHeaderButton( + icon: Icon(Icons.nightlight_round, size: 15), + ), + ], + title: const Text('Window Decorations'), + ), + body: const Center( + child: Text('Welcome to Window Decorations Example!'), + ), + ); + } +} +``` diff --git a/example/lib/flap/flap_home_page.dart b/example/lib/flap/flap_home_page.dart index 5d327c1..eaf7b2a 100644 --- a/example/lib/flap/flap_home_page.dart +++ b/example/lib/flap/flap_home_page.dart @@ -1,6 +1,6 @@ -import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:flutter/material.dart'; import 'package:libadwaita/libadwaita.dart'; +import 'package:libadwaita_bitsdojo/libadwaita_bitsdojo.dart'; class FlapHomePage extends StatefulWidget { const FlapHomePage({Key? key, required this.themeNotifier}) : super(key: key); @@ -20,6 +20,7 @@ class _FlapHomePageState extends State { FlapPosition flapPosition = FlapPosition.start; FoldPolicy foldPolicy = FoldPolicy.auto; bool locked = false; + int selectionIndex = 0; @override void initState() { @@ -45,157 +46,129 @@ class _FlapHomePageState extends State { @override Widget build(BuildContext context) { - return Column( - children: [ - AdwHeaderBar.bitsdojo( - appWindow: appWindow, - start: [ - Builder( - builder: (context) { - return AdwHeaderButton( - icon: const Icon(Icons.view_sidebar, size: 15), - isActive: _flapController.isOpen, - onPressed: () => _flapController.toggle(), - ); - }, - ), - AdwHeaderButton( - icon: const Icon(Icons.nightlight_round, size: 15), - onPressed: changeTheme, - ), - ], - title: const Text('AdwFlap Demo'), - ), - Expanded( - child: AdwScaffold( - flapController: _flapController, - drawer: Drawer( - child: AdwSidebar( - currentIndex: _currentIndex, - children: const [ - AdwSidebarItem( - label: 'Folding', - ), - AdwSidebarItem( - label: 'Layout', - ), - AdwSidebarItem( - label: 'Interaction', - ) - ], - onSelected: (index) { - setState(() { - _currentIndex = index; - Navigator.of(context).pop(); - }); - }, - ), - ), - body: AdwFlap( - locked: locked, - flapController: _flapController, - flapPosition: flapPosition, - foldPolicy: foldPolicy, - flap: AdwSidebar( - currentIndex: _currentIndex, - children: const [ - AdwSidebarItem( - label: 'Folding', - ), - AdwSidebarItem( - label: 'Layout', - ), - AdwSidebarItem( - label: 'Interaction', - ) - ], - onSelected: (index) { - setState(() { - _currentIndex = index; - }); - }, - ), - child: AdwViewStack( - index: _currentIndex, - children: [ - AdwClamp.scrollable( - child: AdwPreferencesGroup( - children: [ - const AdwComboRow( - title: 'Fold Policy', - choices: ['auto', 'always', 'never'], - ), - AdwActionRow( - title: 'Locked', - subtitle: """ + return AdwScaffold( + flapController: _flapController, + headerbar: (_) => AdwHeaderBar.bitsdojo( + appWindow: appWindow, + start: [ + Builder( + builder: (context) { + return AdwHeaderButton( + icon: const Icon(Icons.view_sidebar, size: 15), + isActive: _flapController.isOpen, + onPressed: () => _flapController.toggle(), + ); + }, + ), + AdwHeaderButton( + icon: const Icon(Icons.nightlight_round, size: 15), + onPressed: changeTheme, + ), + ], + title: const Text('AdwFlap Demo'), + ), + flap: (isDrawer) => AdwSidebar( + currentIndex: _currentIndex, + isDrawer: isDrawer, + children: const [ + AdwSidebarItem( + label: 'Folding', + ), + AdwSidebarItem( + label: 'Layout', + ), + AdwSidebarItem( + label: 'Interaction', + ) + ], + onSelected: (index) => setState(() => _currentIndex = index), + ), + flapStyle: FlapStyle( + locked: locked, + flapPosition: flapPosition, + foldPolicy: FoldPolicy.values[selectionIndex], + ), + body: AdwViewStack( + index: _currentIndex, + children: [ + AdwClamp.scrollable( + child: AdwPreferencesGroup( + children: [ + AdwComboRow( + title: 'Fold Policy', + selectedIndex: selectionIndex, + onSelected: (val) => setState(() => selectionIndex = val), + choices: FoldPolicy.values.map((e) => e.name).toList(), + ), + AdwActionRow( + title: 'Locked', + subtitle: """ Sidebar visibility doesn't change when fold state changes""", - end: AdwSwitch( - value: locked, - onChanged: (val) { - locked = val; - setState(() {}); - }, - ), - ) - ], - ), + end: AdwSwitch( + value: locked, + onChanged: (val) { + locked = val; + setState(() {}); + }, ), - AdwClamp.scrollable( - child: AdwPreferencesGroup( - children: [ - AdwActionRow( - title: 'Flap position', - end: AdwToggleButton( - isSelected: [ - flapPosition.index == 0, - flapPosition.index == 1 - ], - onPressed: (val) => setState(() { - if (val == 0) { - flapPosition = FlapPosition.start; - } else { - flapPosition = FlapPosition.end; - } - }), - children: const [ - Text('Start'), - Text('End'), - ], - ), - ), - const AdwActionRow( - title: 'Transition type', - end: Text('Over'), - ) - ], - ), + ) + ], + ), + ), + AdwClamp.scrollable( + child: AdwPreferencesGroup( + children: [ + AdwActionRow( + title: 'Flap position', + end: AdwToggleButton( + isSelected: [ + flapPosition.index == 0, + flapPosition.index == 1 + ], + onPressed: (val) => setState(() { + if (val == 0) { + flapPosition = FlapPosition.start; + } else { + flapPosition = FlapPosition.end; + } + }), + children: const [ + Text('Start'), + Text('End'), + ], ), - AdwClamp.scrollable( - child: AdwPreferencesGroup( - children: [ - AdwActionRow( - title: 'Modal', - subtitle: ''' + ), + AdwComboRow( + title: 'Transition type', + selectedIndex: 0, + onSelected: (val) {}, + choices: const ['Over', 'Under', 'Slide'], + ), + ], + ), + ), + AdwClamp.scrollable( + child: AdwPreferencesGroup( + children: [ + AdwActionRow( + title: 'Modal', + subtitle: ''' Clicking outside the sidebar or pressing Esc will close it when folded''', - end: AdwSwitch(value: true, onChanged: (val) {}), - ), - AdwActionRow( - title: 'Swipe to Open', - end: AdwSwitch(value: true, onChanged: (val) {}), - ), - AdwActionRow( - title: 'Swipe to Close', - end: AdwSwitch(value: true, onChanged: (val) {}), - ), - ], - ), - ), - ], - ), + end: AdwSwitch(value: true, onChanged: (val) {}), + ), + AdwActionRow( + title: 'Swipe to Open', + end: AdwSwitch(value: true, onChanged: (val) {}), + ), + AdwActionRow( + title: 'Swipe to Close', + end: AdwSwitch(value: true, onChanged: (val) {}), + ), + ], ), ), - ), - ], + ], + ), ); } } diff --git a/example/lib/home_page.dart b/example/lib/home_page.dart index 59afa0b..002f06a 100644 --- a/example/lib/home_page.dart +++ b/example/lib/home_page.dart @@ -1,4 +1,3 @@ -import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:example/pages/avatar_page.dart'; import 'package:example/pages/counter_page.dart'; import 'package:example/pages/flap_page.dart'; @@ -7,8 +6,12 @@ import 'package:example/pages/settings_page.dart'; import 'package:example/pages/style_classes_page.dart'; import 'package:example/pages/view_switcher_page.dart'; import 'package:example/pages/welcome.dart'; + import 'package:flutter/material.dart'; + import 'package:libadwaita/libadwaita.dart'; +import 'package:libadwaita_bitsdojo/libadwaita_bitsdojo.dart'; + import 'package:url_launcher/url_launcher.dart'; class MyHomePage extends StatefulWidget { @@ -60,194 +63,139 @@ class _MyHomePageState extends State { 'Polo': 'pablojimpas', }; - return Column( - children: [ - AdwHeaderBar.bitsdojo( - appWindow: appWindow, - start: [ - Builder( - builder: (context) { - return AdwHeaderButton( - icon: const Icon(Icons.view_sidebar, size: 15), - isActive: _flapController.isOpen, + return AdwScaffold( + flapController: _flapController, + headerbar: (_) => AdwHeaderBar.bitsdojo( + appWindow: appWindow, + start: [ + AdwHeaderButton( + icon: const Icon(Icons.view_sidebar_outlined, size: 19), + isActive: _flapController.isOpen, + onPressed: () => _flapController.toggle(), + ), + AdwHeaderButton( + icon: const Icon(Icons.nightlight_round, size: 15), + onPressed: changeTheme, + ), + ], + title: const Text('Libadwaita Demo'), + end: [ + AdwPopupMenu( + body: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + AdwButton.flat( onPressed: () { - _flapController.toggle(); + counter.value = 0; + Navigator.of(context).pop(); }, - ); - }, - ), - AdwHeaderButton( - icon: const Icon(Icons.nightlight_round, size: 15), - onPressed: changeTheme, - ), - ], - title: const Text('Libadwaita Demo'), - end: [ - AdwPopupMenu( - body: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - AdwButton.flat( - onPressed: () { - counter.value = 0; - Navigator.of(context).pop(); - }, - padding: AdwButton.defaultButtonPadding.copyWith( - top: 10, - bottom: 10, - ), - child: const Text( - 'Reset Counter', - style: TextStyle(fontSize: 15), - ), - ), - const Divider(), - AdwButton.flat( - padding: AdwButton.defaultButtonPadding.copyWith( - top: 10, - bottom: 10, - ), - child: const Text( - 'Preferences', - style: TextStyle(fontSize: 15), - ), - ), - AdwButton.flat( - padding: AdwButton.defaultButtonPadding.copyWith( - top: 10, - bottom: 10, - ), - onPressed: () => showDialog( - context: context, - builder: (ctx) => AdwAboutWindow( - issueTrackerLink: - 'https://github.com/gtk-flutter/libadwaita/issues', - appIcon: Image.asset('assets/logo.png'), - credits: [ - AdwPreferencesGroup.credits( - title: 'Developers', - children: developers.entries - .map( - (e) => AdwActionRow( - title: e.key, - onActivated: () => - launch('https://github.com/${e.value}'), - ), - ) - .toList(), - ), - ], - copyright: 'Copyright 2021-2022 Gtk-Flutter Developers', - license: const Text( - 'GNU LGPL-3.0, This program comes with no warranty.', + padding: AdwButton.defaultButtonPadding.copyWith( + top: 10, + bottom: 10, + ), + child: const Text( + 'Reset Counter', + style: TextStyle(fontSize: 15), + ), + ), + const Divider(), + AdwButton.flat( + padding: AdwButton.defaultButtonPadding.copyWith( + top: 10, + bottom: 10, + ), + child: const Text( + 'Preferences', + style: TextStyle(fontSize: 15), + ), + ), + AdwButton.flat( + padding: AdwButton.defaultButtonPadding.copyWith( + top: 10, + bottom: 10, + ), + onPressed: () => showDialog( + context: context, + builder: (ctx) => AdwAboutWindow( + issueTrackerLink: + 'https://github.com/gtk-flutter/libadwaita/issues', + appIcon: Image.asset('assets/logo.png'), + credits: [ + AdwPreferencesGroup.credits( + title: 'Developers', + children: developers.entries + .map( + (e) => AdwActionRow( + title: e.key, + onActivated: () => + launch('https://github.com/${e.value}'), + ), + ) + .toList(), ), + ], + copyright: 'Copyright 2021-2022 Gtk-Flutter Developers', + license: const Text( + 'GNU LGPL-3.0, This program comes with no warranty.', ), ), - child: const Text( - 'About this Demo', - style: TextStyle(fontSize: 15), - ), - ), - ], - ), - ), - ], - ), - Expanded( - child: AdwScaffold( - flapController: _flapController, - drawer: Drawer( - child: AdwSidebar( - currentIndex: _currentIndex, - children: const [ - AdwSidebarItem( - label: 'Welcome', - ), - AdwSidebarItem( - label: 'Counter', - ), - AdwSidebarItem( - label: 'Lists', ), - AdwSidebarItem( - label: 'Avatar', + child: const Text( + 'About this Demo', + style: TextStyle(fontSize: 15), ), - AdwSidebarItem( - label: 'Flap', - ), - AdwSidebarItem( - label: 'View Switcher', - ), - AdwSidebarItem( - label: 'Settings', - ), - AdwSidebarItem( - label: 'Style Classes', - ) - ], - onSelected: (index) { - setState(() { - _currentIndex = index; - Navigator.of(context).pop(); - }); - }, - ), - ), - body: AdwFlap( - flapController: _flapController, - flap: AdwSidebar( - currentIndex: _currentIndex, - children: const [ - AdwSidebarItem( - label: 'Welcome', - ), - AdwSidebarItem( - label: 'Counter', - ), - AdwSidebarItem( - label: 'Lists', - ), - AdwSidebarItem( - label: 'Avatar', - ), - AdwSidebarItem( - label: 'Flap', - ), - AdwSidebarItem( - label: 'View Switcher', - ), - AdwSidebarItem( - label: 'Settings', - ), - AdwSidebarItem( - label: 'Style Classes', - ) - ], - onSelected: (index) { - setState(() { - _currentIndex = index; - }); - }, - ), - child: AdwViewStack( - animationDuration: const Duration(milliseconds: 100), - index: _currentIndex, - children: [ - const WelcomePage(), - CounterPage(counter: counter), - const ListsPage(), - const AvatarPage(), - const FlapPage(), - const ViewSwitcherPage(), - const SettingsPage(), - const StyleClassesPage(), - ], - ), + ), + ], ), ), - ), - ], + ], + ), + flap: (isDrawer) => AdwSidebar( + currentIndex: _currentIndex, + isDrawer: isDrawer, + children: const [ + AdwSidebarItem( + label: 'Welcome', + ), + AdwSidebarItem( + label: 'Counter', + ), + AdwSidebarItem( + label: 'Lists', + ), + AdwSidebarItem( + label: 'Avatar', + ), + AdwSidebarItem( + label: 'Flap', + ), + AdwSidebarItem( + label: 'View Switcher', + ), + AdwSidebarItem( + label: 'Settings', + ), + AdwSidebarItem( + label: 'Style Classes', + ) + ], + onSelected: (index) => setState(() => _currentIndex = index), + ), + body: AdwViewStack( + animationDuration: const Duration(milliseconds: 100), + index: _currentIndex, + children: [ + const WelcomePage(), + CounterPage(counter: counter), + const ListsPage(), + const AvatarPage(), + const FlapPage(), + const ViewSwitcherPage(), + const SettingsPage(), + const StyleClassesPage(), + ], + ), ); } } diff --git a/example/lib/pages/counter_page.dart b/example/lib/pages/counter_page.dart index 041e529..c7ca347 100644 --- a/example/lib/pages/counter_page.dart +++ b/example/lib/pages/counter_page.dart @@ -1,3 +1,4 @@ +import 'package:example/pages/run_demo_screen.dart'; import 'package:flutter/material.dart'; import 'package:libadwaita/libadwaita.dart'; @@ -15,28 +16,19 @@ class _CounterPageState extends State { @override Widget build(BuildContext context) { - return Center( - child: AdwClamp.scrollable( - child: ValueListenableBuilder( - valueListenable: widget.counter, - builder: (_, value, child) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('You have pushed the add button this many times:'), - Text( - '$value', - style: Theme.of(context).textTheme.headline4, - ), - AdwButton.pill( - onPressed: _incrementCounter, - child: const Text('Add'), - ), - ], - ); - }, - ), - ), + return ValueListenableBuilder( + valueListenable: widget.counter, + builder: (context, val, _) { + return DemoScreen( + title: 'Counter Example', + description: 'You have pushed the add button this many times:', + secondDescription: '$val', + footer: AdwButton.pill( + onPressed: _incrementCounter, + child: const Text('Add'), + ), + ); + }, ); } } diff --git a/example/lib/pages/flap_page.dart b/example/lib/pages/flap_page.dart index 976d0f3..34af964 100644 --- a/example/lib/pages/flap_page.dart +++ b/example/lib/pages/flap_page.dart @@ -1,40 +1,16 @@ -import 'dart:convert'; - -import 'package:desktop_multi_window/desktop_multi_window.dart'; +import 'package:example/pages/run_demo_screen.dart'; import 'package:flutter/material.dart'; -import 'package:libadwaita/libadwaita.dart'; class FlapPage extends StatelessWidget { const FlapPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return Center( - child: AdwClamp.scrollable( - child: Column( - children: [ - Text( - 'Flap', - style: Theme.of(context).textTheme.headline6, - ), - const SizedBox(height: 10), - const Text( - 'A widget showing a flap next or above the content.', - ), - const SizedBox(height: 16), - AdwButton.pill( - onPressed: () async => await DesktopMultiWindow.createWindow( - jsonEncode({'name': 'flap'}), - ) - ..setFrame(Offset.zero & const Size(1280, 720)) - ..center() - ..setTitle('Flap Example') - ..show(), - child: const Text('Run the demo'), - ), - ], - ), - ), + return DemoScreen.runDemo( + icon: Icons.view_sidebar_rounded, + title: 'Flap', + description: 'A widget showing a flap next or above the content.', + nextPageViewName: 'flap', ); } } diff --git a/example/lib/pages/lists_page.dart b/example/lib/pages/lists_page.dart index 095d7a6..7b877bc 100644 --- a/example/lib/pages/lists_page.dart +++ b/example/lib/pages/lists_page.dart @@ -1,3 +1,4 @@ +import 'package:example/pages/run_demo_screen.dart'; import 'package:flutter/material.dart'; import 'package:libadwaita/libadwaita.dart'; @@ -7,24 +8,18 @@ class ListsPage extends StatelessWidget { @override Widget build(BuildContext context) { final switchVal = ValueNotifier(false); - return AdwClamp.scrollable( - child: Column( + const choices = ['Test', 'Second', 'Third and a long name']; + final selectionIndex = ValueNotifier(0); + + return DemoScreen( + image: const Icon( + Icons.list_rounded, + size: 150, + ), + title: 'Lists', + description: 'Rows and helpers for GtkListBox.', + footer: Column( children: [ - const Icon( - Icons.list_rounded, - size: 150, - ), - Text( - 'Lists', - style: Theme.of(context) - .textTheme - .headline5 - ?.copyWith(fontWeight: FontWeight.bold), - ), - const Text('Rows and helpers for GtkListBox.'), - const SizedBox( - height: 10, - ), AdwPreferencesGroup( children: [ const AdwActionRow( @@ -41,11 +36,18 @@ class ListsPage extends StatelessWidget { ) ], ), - const AdwPreferencesGroup( + AdwPreferencesGroup( children: [ - AdwComboRow( - choices: ['Test', 'Second', 'Third and a long name'], - title: 'Combo row', + ValueListenableBuilder( + valueListenable: selectionIndex, + builder: (context, val, _) { + return AdwComboRow( + choices: choices, + title: 'Combo row', + selectedIndex: val, + onSelected: (val) => selectionIndex.value = val, + ); + }, ) ], ), diff --git a/example/lib/pages/run_demo_screen.dart b/example/lib/pages/run_demo_screen.dart new file mode 100644 index 0000000..3289e39 --- /dev/null +++ b/example/lib/pages/run_demo_screen.dart @@ -0,0 +1,80 @@ +// ignore_for_file: unawaited_futures + +import 'dart:convert'; + +import 'package:desktop_multi_window/desktop_multi_window.dart'; +import 'package:flutter/material.dart'; +import 'package:libadwaita/libadwaita.dart'; + +class DemoScreen extends StatelessWidget { + const DemoScreen({ + Key? key, + required this.title, + required this.description, + this.secondDescription, + this.image, + this.footer, + }) : super(key: key); + + DemoScreen.runDemo({ + Key? key, + required this.title, + required this.description, + this.secondDescription, + required IconData icon, + required String nextPageViewName, + }) : image = Icon( + icon, + size: 130, + ), + footer = AdwButton.pill( + onPressed: () async => await DesktopMultiWindow.createWindow( + jsonEncode({'name': nextPageViewName}), + ) + ..setFrame(Offset.zero & const Size(1280, 720)) + ..center() + ..setTitle('$title Example') + ..show(), + child: const Text('Run the demo'), + ), + super(key: key); + + final String title; + final String description; + final String? secondDescription; + final Widget? image; + final Widget? footer; + + @override + Widget build(BuildContext context) { + return Center( + child: AdwClamp.scrollable( + child: Column( + children: [ + if (image != null) ...[ + image!, + const SizedBox(height: 30), + ], + Text( + title, + style: Theme.of(context).textTheme.headline1, + ), + const SizedBox(height: 15), + Text(description), + if (secondDescription != null) ...[ + const SizedBox(height: 20), + Text( + secondDescription!, + style: Theme.of(context).textTheme.headline2, + ), + ], + if (footer != null) ...[ + SizedBox(height: secondDescription != null ? 20 : 40), + footer!, + ], + ], + ), + ), + ); + } +} diff --git a/example/lib/pages/style_classes_page.dart b/example/lib/pages/style_classes_page.dart index 72ebdd7..f7f7b54 100644 --- a/example/lib/pages/style_classes_page.dart +++ b/example/lib/pages/style_classes_page.dart @@ -29,7 +29,7 @@ class StyleClassesPage extends StatelessWidget { AdwButton( opaque: true, margin: const EdgeInsets.symmetric(vertical: 4), - backgroundColor: AdwColors.blue.backgroundColor, + backgroundColor: AdwDefaultColors.blue, textStyle: const TextStyle(color: Colors.white), child: const Text('Suggested'), ), diff --git a/example/lib/pages/view_switcher_page.dart b/example/lib/pages/view_switcher_page.dart index 4a8b18c..a76c2f6 100644 --- a/example/lib/pages/view_switcher_page.dart +++ b/example/lib/pages/view_switcher_page.dart @@ -1,40 +1,16 @@ -import 'dart:convert'; - -import 'package:desktop_multi_window/desktop_multi_window.dart'; +import 'package:example/pages/run_demo_screen.dart'; import 'package:flutter/material.dart'; -import 'package:libadwaita/libadwaita.dart'; class ViewSwitcherPage extends StatelessWidget { const ViewSwitcherPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return Center( - child: AdwClamp.scrollable( - child: Column( - children: [ - Text( - 'View Switcher', - style: Theme.of(context).textTheme.headline6, - ), - const SizedBox(height: 10), - const Text( - "Widgets to switch the window's view.", - ), - const SizedBox(height: 16), - AdwButton.pill( - onPressed: () async => await DesktopMultiWindow.createWindow( - jsonEncode({'name': 'views'}), - ) - ..setFrame(Offset.zero & const Size(1280, 720)) - ..center() - ..setTitle('ViewSwitcher Example') - ..show(), - child: const Text('Run the demo'), - ), - ], - ), - ), + return DemoScreen.runDemo( + title: 'View Switcher', + description: "Widgets to switch the window's view.", + icon: Icons.table_chart, + nextPageViewName: 'views', ); } } diff --git a/example/lib/pages/welcome.dart b/example/lib/pages/welcome.dart index ccc8bbf..569c209 100644 --- a/example/lib/pages/welcome.dart +++ b/example/lib/pages/welcome.dart @@ -1,3 +1,4 @@ +import 'package:example/pages/run_demo_screen.dart'; import 'package:flutter/material.dart'; class WelcomePage extends StatelessWidget { @@ -5,31 +6,13 @@ class WelcomePage extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Image( - image: AssetImage('assets/logo.png'), - height: 150, - ), - Text( - 'Welcome to Adwaita flutter Demo.', - style: Theme.of(context) - .textTheme - .headline5 - ?.copyWith(fontWeight: FontWeight.bold), - ), - const Text( - 'This is a tour of the features this library has to offer.', - ), - ] - .map( - (e) => Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: e, - ), - ) - .toList(), + return const DemoScreen( + image: Image( + image: AssetImage('assets/logo.png'), + height: 130, + ), + title: 'Welcome to Adwaita flutter Demo.', + description: 'This is a tour of the features this library has to offer.', ); } } diff --git a/example/lib/view_switcher/view_switcher_home_page.dart b/example/lib/view_switcher/view_switcher_home_page.dart index 3ca950e..817555d 100644 --- a/example/lib/view_switcher/view_switcher_home_page.dart +++ b/example/lib/view_switcher/view_switcher_home_page.dart @@ -1,6 +1,6 @@ -import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:flutter/material.dart'; import 'package:libadwaita/libadwaita.dart'; +import 'package:libadwaita_bitsdojo/libadwaita_bitsdojo.dart'; class ViewSwitcherHomePage extends StatefulWidget { const ViewSwitcherHomePage({Key? key}) : super(key: key); @@ -16,60 +16,53 @@ class _ViewSwitcherHomePageState extends State { Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: index, - builder: (context, int value, child) { - return Column( - children: [ - AdwHeaderBar.bitsdojo( - appWindow: appWindow, - title: AdwViewSwitcher( - tabs: const [ - ViewSwitcherData( - title: 'World', - icon: Icons.public, - ), - ViewSwitcherData( - title: 'Alarm', - icon: Icons.alarm, - ), - ViewSwitcherData( - title: 'Stopwatch', - icon: Icons.stop, - badge: '9', - ), - ViewSwitcherData( - title: 'Timer', - icon: Icons.timer, - badge: '1', - ), - ], - onViewChanged: (idx) => index.value = idx, - currentIndex: value, - ), + builder: (context, int value, child) => AdwScaffold( + headerbar: (viewSwitcher) => AdwHeaderBar.bitsdojo( + appWindow: appWindow, + title: viewSwitcher, + ), + viewSwitcher: AdwViewSwitcher( + tabs: const [ + ViewSwitcherData( + title: 'World', + icon: Icons.public, ), - Expanded( - child: AdwScaffold( - body: AdwViewStack( - index: value, - children: const [ - Center( - child: Text('World'), - ), - Center( - child: Text('Alarm'), - ), - Center( - child: Text('Stopwatch'), - ), - Center( - child: Text('Timer'), - ), - ], - ), - ), + ViewSwitcherData( + title: 'Alarm', + icon: Icons.alarm, + ), + ViewSwitcherData( + title: 'Stopwatch', + icon: Icons.stop, + badge: '9', + ), + ViewSwitcherData( + title: 'Timer', + icon: Icons.timer, + badge: '1', + ), + ], + onViewChanged: (idx) => index.value = idx, + currentIndex: value, + ), + body: AdwViewStack( + index: value, + children: const [ + Center( + child: Text('World'), + ), + Center( + child: Text('Alarm'), + ), + Center( + child: Text('Stopwatch'), + ), + Center( + child: Text('Timer'), ), ], - ); - }, + ), + ), ); } } diff --git a/example/pubspec.lock b/example/pubspec.lock index 07475ad..45ceb67 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,7 +7,7 @@ packages: name: adwaita url: "https://pub.dartlang.org" source: hosted - version: "0.1.0" + version: "0.5.1" args: dependency: transitive description: @@ -22,13 +22,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.8.2" - bitsdojo_window: - dependency: "direct main" - description: - name: bitsdojo_window - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.1+1" bitsdojo_window_linux: dependency: transitive description: @@ -98,7 +91,7 @@ packages: name: dbus url: "https://pub.dartlang.org" source: hosted - version: "0.6.6" + version: "0.7.1" desktop_multi_window: dependency: "direct main" description: @@ -155,7 +148,7 @@ packages: name: gsettings url: "https://pub.dartlang.org" source: hosted - version: "0.2.3" + version: "0.2.5" http: dependency: transitive description: @@ -184,6 +177,13 @@ packages: relative: true source: path version: "1.0.0" + libadwaita_bitsdojo: + dependency: "direct main" + description: + name: libadwaita_bitsdojo + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.0" matcher: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 3591c68..c69b1c8 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -2,19 +2,19 @@ name: example description: A new Flutter project. publish_to: none -version: 1.0.0+1 +version: 1.0.0+2 environment: sdk: ">=2.12.0 <3.0.0" dependencies: - adwaita: "0.1.0" - bitsdojo_window: ">=0.1.1+1 <1.0.0" + adwaita: "0.5.1" desktop_multi_window: ">=0.0.1 <1.0.0" flutter: sdk: flutter libadwaita: path: ../ + libadwaita_bitsdojo: ">=0.5.0 <1.0.0" dev_dependencies: flutter_test: diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart index e0ffc24..5a9cbb4 100644 --- a/lib/src/models/models.dart +++ b/lib/src/models/models.dart @@ -1,2 +1,2 @@ export 'view_switcher_data.dart'; -export 'view_switcher_style.dart'; +export 'view_switcher_policy.dart'; diff --git a/lib/src/models/view_switcher_policy.dart b/lib/src/models/view_switcher_policy.dart new file mode 100644 index 0000000..858cd48 --- /dev/null +++ b/lib/src/models/view_switcher_policy.dart @@ -0,0 +1 @@ +enum ViewSwitcherPolicy { wide, narrow } diff --git a/lib/src/models/view_switcher_style.dart b/lib/src/models/view_switcher_style.dart deleted file mode 100644 index 0fa9143..0000000 --- a/lib/src/models/view_switcher_style.dart +++ /dev/null @@ -1 +0,0 @@ -enum ViewSwitcherStyle { desktop, mobile } diff --git a/lib/src/utils/colors.dart b/lib/src/utils/colors.dart index b733c3b..3cee49b 100644 --- a/lib/src/utils/colors.dart +++ b/lib/src/utils/colors.dart @@ -23,13 +23,18 @@ extension ColorBrightness on Color { } } -Color borderLight = Colors.black.withOpacity(0.18); -Color borderDark = const Color(0xFF454545); +class AdwDefaultColors { + static Color borderLight = Colors.black.withOpacity(0.18); + static Color borderDark = const Color(0xFF454545); + + static Color blue = const Color(0xFF3584e4); +} extension BorderContext on BuildContext { bool get _isDark => Theme.of(this).brightness == Brightness.dark; - Color get borderColor => _isDark ? borderDark : borderLight; + Color get borderColor => + _isDark ? AdwDefaultColors.borderDark : AdwDefaultColors.borderLight; Color get checkboxColor => _isDark ? const Color(0xFF535353) : const Color(0xFFE0E0E0); diff --git a/lib/src/widgets/adw/about_window.dart b/lib/src/widgets/adw/about_window.dart index fd22c54..d5a0db2 100644 --- a/lib/src/widgets/adw/about_window.dart +++ b/lib/src/widgets/adw/about_window.dart @@ -102,6 +102,7 @@ class _AdwAboutWindowState extends State { widget.headerbar?.call([leading], text) ?? AdwHeaderBar( start: [leading], + autoPositionWindowButtons: false, onClose: Navigator.of(context).pop, isTransparent: true, title: text, @@ -182,6 +183,15 @@ class _AdwAboutWindowState extends State { ] : currentPage == 1 ? widget.credits! + .map( + (e) => Padding( + padding: const EdgeInsets.only( + bottom: 10, + ), + child: e, + ), + ) + .toList() : [ if (widget.copyright != null) Text(widget.copyright!), diff --git a/lib/src/widgets/adw/action_row.dart b/lib/src/widgets/adw/action_row.dart index bb05688..906bd85 100644 --- a/lib/src/widgets/adw/action_row.dart +++ b/lib/src/widgets/adw/action_row.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -class AdwActionRowStyle {} - class AdwActionRow extends StatelessWidget { const AdwActionRow({ Key? key, @@ -12,19 +10,33 @@ class AdwActionRow extends StatelessWidget { this.subtitle, this.autofocus = false, this.enabled = true, - this.style, this.contentPadding, }) : super(key: key); + /// The starting elemets of this row final Widget? start; + + /// The ending elements of this row final Widget? end; + + /// The title of this row final String title; + + /// The subtitle of this row final String? subtitle; + + /// Executed when this Action row is pressed final VoidCallback? onActivated; + + /// Whether to focus automatically when this widget is visible + /// defaults to false final bool autofocus; + + /// Whether this action row is enabled or not, defaults to true final bool enabled; + + /// The padding b/w content of this Action row final EdgeInsets? contentPadding; - final AdwActionRowStyle? style; @override Widget build(BuildContext context) { diff --git a/lib/src/widgets/adw/clamp.dart b/lib/src/widgets/adw/clamp.dart index 830b687..ff9c2ae 100644 --- a/lib/src/widgets/adw/clamp.dart +++ b/lib/src/widgets/adw/clamp.dart @@ -23,12 +23,25 @@ class AdwClamp extends StatefulWidget { }) : isScrollable = true, super(key: key); + /// Whether this clamp is scrollable or not final bool isScrollable; + + /// The controller for the scrollable clamp final ScrollController? controller; + + /// Whether to center the elements horizontally of the clamp final bool center; + + /// The child of this clamp final Widget child; + + /// Sets the maximum size allocated to the child. final double maximumSize; + + /// The padding around this clamp final EdgeInsets padding; + + /// The margin around this clamp final EdgeInsets margin; @override diff --git a/lib/src/widgets/adw/combo_row.dart b/lib/src/widgets/adw/combo_row.dart index d8448cc..b72c51b 100644 --- a/lib/src/widgets/adw/combo_row.dart +++ b/lib/src/widgets/adw/combo_row.dart @@ -3,13 +3,14 @@ import 'package:libadwaita/src/utils/colors.dart'; import 'package:libadwaita/src/widgets/adw/button.dart'; import 'package:popover_gtk/popover_gtk.dart'; -/// This is the stateful widget that the main application instantiates. class AdwComboRow extends StatefulWidget { const AdwComboRow({ Key? key, this.choices = const [], this.start, this.end, + required this.selectedIndex, + required this.onSelected, required this.title, this.subtitle, this.autofocus = false, @@ -17,47 +18,49 @@ class AdwComboRow extends StatefulWidget { this.contentPadding, }) : super(key: key); + /// The choices available for this combo row final List choices; + + /// The index of the selected choice + final int selectedIndex; + + /// Executed when a choice is selected + final ValueSetter onSelected; + + /// The starting elemets of this row final Widget? start; + + /// The ending elements of this row final Widget? end; + + /// The title of this row final String title; + + /// The subtitle of this row final String? subtitle; + + /// Whether to focus automatically when this widget is visible + /// defaults to false final bool autofocus; + + /// Whether this combo row is enabled or not, defaults to true final bool enabled; + + /// The padding b/w content of this Combo row final EdgeInsets? contentPadding; @override State createState() => _AdwComboRowState(); } -/// This is the private State class that goes with MyStatefulWidget. class _AdwComboRowState extends State { _AdwComboRowState(); - bool taped = false; - int selected = 0; final GlobalKey<_AdwComboButtonState> _comboButtonState = GlobalKey<_AdwComboButtonState>(); late AdwComboButton button; - @override - void initState() { - button = AdwComboButton( - key: _comboButtonState, - choices: widget.choices, - getSelected: () { - return selected; - }, - setSelected: (select) { - setState(() { - selected = select; - }); - }, - ); - super.initState(); - } - @override Widget build(BuildContext context) { return InkWell( @@ -92,12 +95,19 @@ class _AdwComboRowState extends State { const SizedBox(width: 10), Flexible( child: Text( - widget.choices[selected], + widget.choices[widget.selectedIndex], overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 5), - button, + AdwComboButton( + key: _comboButtonState, + choices: widget.choices, + selectedIndex: widget.selectedIndex, + onSelected: (val) { + widget.onSelected(val); + }, + ), const SizedBox(width: 10), ], ), @@ -112,13 +122,18 @@ class AdwComboButton extends StatefulWidget { const AdwComboButton({ Key? key, this.choices = const [], - required this.setSelected, - required this.getSelected, + required this.onSelected, + required this.selectedIndex, }) : super(key: key); + /// The choices available for this combo row final List choices; - final ValueSetter setSelected; - final ValueGetter getSelected; + + /// Executed when a choice is selected + final ValueSetter onSelected; + + /// The index of the selected choice + final int selectedIndex; @override State createState() => _AdwComboButtonState(); @@ -131,13 +146,12 @@ class _AdwComboButtonState extends State { bool active = false; @override - Widget build(BuildContext context) { - return const Icon(Icons.arrow_drop_down); - } + Widget build(BuildContext context) => const Icon(Icons.arrow_drop_down); void show() { showPopover( context: context, + arrowHeight: 14, barrierColor: Colors.transparent, shadow: [ BoxShadow( @@ -145,35 +159,35 @@ class _AdwComboButtonState extends State { blurRadius: 6, ), ], - bodyBuilder: (_) => Column( - mainAxisSize: MainAxisSize.min, - children: List.generate(widget.choices.length, (int index) { - return AdwButton.flat( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Text( - widget.choices[index], - overflow: TextOverflow.ellipsis, + bodyBuilder: (_) => SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: List.generate( + widget.choices.length, + (int index) => AdwButton.flat( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + widget.choices[index], + overflow: TextOverflow.ellipsis, + ), ), - ), - if (index == widget.getSelected()) - const Icon(Icons.check, size: 20), - ], + if (index == widget.selectedIndex) + const Icon(Icons.check, size: 20), + ], + ), + onPressed: () { + widget.onSelected(index); + setState(() {}); + Navigator.of(context).pop(); + }, ), - onPressed: () { - setState(() { - widget.setSelected(index); - //if you want to assign the index somewhere to check - }); - Navigator.of(context).pop(); - }, - ); - }), + ), + ), ), width: 200, - height: null, backgroundColor: Theme.of(context).cardColor, ).whenComplete(() => setState(() => active = false)); } diff --git a/lib/src/widgets/adw/flap.dart b/lib/src/widgets/adw/flap.dart index cad2539..f8b9011 100644 --- a/lib/src/widgets/adw/flap.dart +++ b/lib/src/widgets/adw/flap.dart @@ -7,28 +7,24 @@ import 'package:libadwaita/src/utils/colors.dart'; enum FoldPolicy { never, always, auto } enum FlapPosition { start, end } -class AdwFlap extends StatefulWidget { - const AdwFlap({ - Key? key, - required this.flap, - required this.child, - this.locked = false, - this.flapController, +class FlapStyle { + FlapStyle({ this.seperator, - this.foldPolicy = FoldPolicy.auto, - this.flapPosition = FlapPosition.start, + this.locked = false, this.breakpoint = 900, this.flapWidth = 270.0, - }) : super(key: key); + this.foldPolicy = FoldPolicy.auto, + this.flapPosition = FlapPosition.start, + }); - final Widget flap; - final Widget child; + /// The seperator b/w flap and the content final Widget? seperator; + /// The FoldPolicy of this flap, defaults to auto final FoldPolicy foldPolicy; - final FlapPosition flapPosition; - final FlapController? flapController; + /// The FlapPosition of this flap, defaults to start + final FlapPosition flapPosition; /// The breakpoint for small devices final double breakpoint; @@ -41,6 +37,29 @@ class AdwFlap extends StatefulWidget { /// state when screen is resized or /// not final bool locked; +} + +class AdwFlap extends StatefulWidget { + AdwFlap({ + Key? key, + required this.flap, + required this.child, + this.controller, + FlapStyle? style, + }) : style = style ?? FlapStyle(), + super(key: key); + + /// The flap widget itself, Mainly is a `AdwSidebar` instance + final Widget flap; + + /// The content of the page + final Widget child; + + /// The style of this flap + final FlapStyle style; + + /// The controller for this flap + final FlapController? controller; @override _AdwFlapState createState() => _AdwFlapState(); @@ -57,10 +76,10 @@ class _AdwFlapState extends State { void initState() { super.initState(); - if (widget.flapController == null) { + if (widget.controller == null) { _controller = FlapController(); } else { - _controller = widget.flapController!; + _controller = widget.controller!; } _controller.addListener(rebuild); @@ -70,9 +89,9 @@ class _AdwFlapState extends State { void updateFlapData() { _controller - ..policy = widget.foldPolicy - ..position = widget.flapPosition - ..locked = widget.locked; + ..policy = widget.style.foldPolicy + ..position = widget.style.flapPosition + ..locked = widget.style.locked; } @override @@ -98,7 +117,7 @@ class _AdwFlapState extends State { final flap = SlideHide( isHidden: _controller.shouldHide(), - width: widget.flapWidth, + width: widget.style.flapWidth, child: SingleChildScrollView( physics: const NeverScrollableScrollPhysics(), scrollDirection: Axis.horizontal, @@ -106,13 +125,13 @@ class _AdwFlapState extends State { ), ); - final seperator = widget.seperator ?? + final seperator = widget.style.seperator ?? Container( width: 1, color: context.borderColor, ); - final widgets = widget.flapPosition == FlapPosition.start + final widgets = widget.style.flapPosition == FlapPosition.start ? [flap, seperator, content] : [content, seperator, flap]; @@ -125,10 +144,10 @@ class _AdwFlapState extends State { // affected by window resizes. // If FoldPolicy is auto, then close / open the sidebar depending on the // state - final isMobile = size.width < widget.breakpoint; + final isMobile = size.width < widget.style.breakpoint; _controller.updateModalState(context, state: isMobile); - switch (widget.foldPolicy) { + switch (widget.style.foldPolicy) { case FoldPolicy.never: case FoldPolicy.always: break; diff --git a/lib/src/widgets/adw/header_bar.dart b/lib/src/widgets/adw/header_bar.dart index 9ac7245..7b8f2ae 100644 --- a/lib/src/widgets/adw/header_bar.dart +++ b/lib/src/widgets/adw/header_bar.dart @@ -8,6 +8,12 @@ import 'package:gsettings/gsettings.dart'; import 'package:libadwaita/src/utils/colors.dart'; import 'package:libadwaita/src/widgets/widgets.dart'; +/// To be used with nativeshell package as it doesn't have this method +Future _maximizeOrRestore(dynamic window) async { + final dynamic flags = await window.getWindowStateFlags(); + await window.setMaximized(!(flags.maximized as bool)); +} + class AdwHeaderBar extends StatefulWidget { /// If you use with window_decorations /// If you don't want that. use AdwHeaderBar.custom @@ -23,9 +29,10 @@ class AdwHeaderBar extends StatefulWidget { this.onDoubleTap, this.onHeaderDrag, this.start = const [], - this.title = const SizedBox(), + this.title, this.end = const [], this.textStyle, + this.autoPositionWindowButtons = true, this.isTransparent = false, this.padding = const EdgeInsets.only(left: 3, right: 5), this.titlebarSpace = 6, @@ -37,19 +44,19 @@ class AdwHeaderBar extends StatefulWidget { onPressed: onClose, themeType: themeType, windowDecor: windowDecor, - buttonType: AdwWindowButtonType.close, + buttonType: WindowButtonType.close, ), maximizeBtn = AdwWindowButton( onPressed: onMaximize, themeType: themeType, windowDecor: windowDecor, - buttonType: AdwWindowButtonType.maximize, + buttonType: WindowButtonType.maximize, ), minimizeBtn = AdwWindowButton( themeType: themeType, onPressed: onMinimize, windowDecor: windowDecor, - buttonType: AdwWindowButtonType.minimize, + buttonType: WindowButtonType.minimize, ), super(key: key); @@ -61,6 +68,7 @@ class AdwHeaderBar extends StatefulWidget { this.title = const SizedBox(), this.end = const [], this.textStyle, + this.autoPositionWindowButtons = true, this.isTransparent = false, this.padding = const EdgeInsets.only(left: 3, right: 5), this.titlebarSpace = 6, @@ -73,7 +81,7 @@ class AdwHeaderBar extends StatefulWidget { AdwHeaderBar.bitsdojo({ Key? key, - /// The appWindow object from bitsdojo_window package + /// The appWindow object from libadwaita_bitsdojo package required dynamic appWindow, Widget Function( String name, @@ -86,6 +94,7 @@ class AdwHeaderBar extends StatefulWidget { this.title = const SizedBox(), this.end = const [], this.textStyle, + this.autoPositionWindowButtons = true, this.isTransparent = false, this.padding = const EdgeInsets.only(left: 3, right: 5), this.titlebarSpace = 6, @@ -98,7 +107,7 @@ class AdwHeaderBar extends StatefulWidget { onPressed: appWindow?.close as void Function()?, themeType: themeType, windowDecor: windowDecor, - buttonType: AdwWindowButtonType.close, + buttonType: WindowButtonType.close, ) : null, maximizeBtn = showMaximize @@ -106,7 +115,7 @@ class AdwHeaderBar extends StatefulWidget { onPressed: appWindow?.maximize as void Function()?, themeType: themeType, windowDecor: windowDecor, - buttonType: AdwWindowButtonType.maximize, + buttonType: WindowButtonType.maximize, ) : null, minimizeBtn = showMinimize @@ -114,7 +123,7 @@ class AdwHeaderBar extends StatefulWidget { onPressed: appWindow?.minimize as void Function()?, themeType: themeType, windowDecor: windowDecor, - buttonType: AdwWindowButtonType.minimize, + buttonType: WindowButtonType.minimize, ) : null, onHeaderDrag = appWindow?.startDragging as void Function()?, @@ -124,12 +133,13 @@ class AdwHeaderBar extends StatefulWidget { AdwHeaderBar.customBitsdojo({ Key? key, - /// The appWindow object from bitsdojo_window package + /// The appWindow object from libadwaita_bitsdojo package required dynamic appWindow, this.start = const [], this.title = const SizedBox(), this.end = const [], this.textStyle, + this.autoPositionWindowButtons = true, this.isTransparent = false, this.padding = const EdgeInsets.only(left: 3, right: 5), this.titlebarSpace = 6, @@ -161,20 +171,33 @@ class AdwHeaderBar extends StatefulWidget { this.title = const SizedBox(), this.end = const [], this.textStyle, + this.autoPositionWindowButtons = true, this.isTransparent = false, this.padding = const EdgeInsets.only(left: 3, right: 5), this.titlebarSpace = 6, this.height = 51, + bool showMinimize = true, + bool showMaximize = true, bool showClose = true, }) : onHeaderDrag = window?.performDrag as void Function()?, onDoubleTap = null, - minimizeBtn = null, - maximizeBtn = null, + minimizeBtn = AdwWindowButton( + onPressed: showMinimize ? () => window.setMinimized(true) : null, + themeType: themeType, + windowDecor: windowDecor, + buttonType: WindowButtonType.minimize, + ), + maximizeBtn = AdwWindowButton( + onPressed: showMaximize ? () => _maximizeOrRestore(window) : null, + themeType: themeType, + windowDecor: windowDecor, + buttonType: WindowButtonType.maximize, + ), closeBtn = AdwWindowButton( onPressed: showClose ? window.close as void Function()? : null, themeType: themeType, windowDecor: windowDecor, - buttonType: AdwWindowButtonType.close, + buttonType: WindowButtonType.close, ), super(key: key); @@ -187,15 +210,18 @@ class AdwHeaderBar extends StatefulWidget { this.title = const SizedBox(), this.end = const [], this.textStyle, + this.autoPositionWindowButtons = true, this.isTransparent = false, this.padding = const EdgeInsets.only(left: 3, right: 5), this.titlebarSpace = 6, this.height = 51, + Widget Function(VoidCallback onTap)? minimizeBtn, + Widget Function(VoidCallback onTap)? maximizeBtn, Widget Function(VoidCallback onTap)? closeBtn, }) : onHeaderDrag = window?.performDrag as void Function()?, - onDoubleTap = null, - minimizeBtn = null, - maximizeBtn = null, + onDoubleTap = (() => _maximizeOrRestore(window)), + minimizeBtn = minimizeBtn?.call(() => window.setMinimized(true)), + maximizeBtn = maximizeBtn?.call(() => _maximizeOrRestore(window)), closeBtn = closeBtn?.call(window.close as void Function()), super(key: key); @@ -203,7 +229,7 @@ class AdwHeaderBar extends StatefulWidget { final List start; /// The center widget for the headerbar - final Widget title; + final Widget? title; /// The trailing widget for the headerbar final List end; @@ -224,6 +250,12 @@ class AdwHeaderBar extends StatefulWidget { /// The height of the headerbar final double height; + /// Whether to automatically place the window buttons according to + /// the Platform, defaults to true + /// If false then it will follow the general Windows like window buttons + /// placement + final bool autoPositionWindowButtons; + /// The padding inside the headerbar final EdgeInsets padding; @@ -246,33 +278,39 @@ class _AdwHeaderBarState extends State { widget.minimizeBtn != null || widget.maximizeBtn != null; - late ValueNotifier> seperator = - ValueNotifier(['', 'minimize,maximize,close']); + late ValueNotifier> seperator = ValueNotifier([ + '', + 'minimize,maximize,close', + ]); @override void initState() { super.initState(); - late final order = ValueNotifier(':minimize,maximize,close'); - void updateSep() { - if (mounted) { - seperator.value = order.value.split(':'); + if (widget.autoPositionWindowButtons) { + void updateSep(String order) { + if (!mounted) return; + seperator.value = order.split(':'); } - } - if (Platform.isLinux) { - final buttonLayout = ValueNotifier(null); - final schema = GSettings('org.gnome.desktop.wm.preferences'); - WidgetsBinding.instance?.addPostFrameCallback((_) async { - buttonLayout.value = await schema.get('button-layout') as DBusString; - if (buttonLayout.value != null) { - order.value = buttonLayout.value!.value; - } - updateSep(); - }); - } else if (Platform.isMacOS) { - order.value = 'close,maximize,minimize:'; - updateSep(); + if (Platform.isWindows) { + updateSep(':minimize,maximize,close'); + } else if (Platform.isMacOS) { + updateSep('close,maximize,minimize:'); + } else if (Platform.isLinux) { + updateSep(':close'); + + final schema = GSettings('org.gnome.desktop.wm.preferences'); + + WidgetsBinding.instance?.addPostFrameCallback((_) async { + final buttonLayout = await schema.get('button-layout') as DBusString?; + if (buttonLayout != null) { + updateSep(buttonLayout.value); + } + }); + } else { + updateSep(':'); + } } } @@ -315,8 +353,8 @@ class _AdwHeaderBarState extends State { valueListenable: seperator, builder: (context, sep, _) => DefaultTextStyle.merge( style: const TextStyle( - fontWeight: FontWeight.bold, fontSize: 14, + fontWeight: FontWeight.bold, ).merge(widget.textStyle), child: NavigationToolbar( leading: Row( @@ -326,26 +364,14 @@ class _AdwHeaderBarState extends State { SizedBox(width: widget.titlebarSpace), for (var i in sep[0].split(',')) if (windowButtons[i] != null) windowButtons[i]!, - ...widget.start.map( - (e) => Padding( - padding: - const EdgeInsets.symmetric(horizontal: 3), - child: e, - ), - ), + ...widget.start ], ), middle: widget.title, trailing: Row( mainAxisSize: MainAxisSize.min, children: [ - ...widget.end.map( - (e) => Padding( - padding: - const EdgeInsets.symmetric(horizontal: 3), - child: e, - ), - ), + ...widget.end, if (hasWindowControls && sep[1].split(',').isNotEmpty) SizedBox(width: widget.titlebarSpace), for (var i in sep[1].split(',')) diff --git a/lib/src/widgets/adw/new/scaffold.dart b/lib/src/widgets/adw/new/scaffold.dart index f553749..555cdca 100644 --- a/lib/src/widgets/adw/new/scaffold.dart +++ b/lib/src/widgets/adw/new/scaffold.dart @@ -3,24 +3,27 @@ import 'package:libadwaita/src/controllers/flap_controller.dart'; import 'package:libadwaita/src/widgets/widgets.dart'; class AdwScaffold extends StatefulWidget { - AdwScaffold({ + const AdwScaffold({ Key? key, required this.body, + this.flap, + this.flapStyle, this.flapController, - Widget? drawer, - this.bottomNavigationBar, - }) : - - /// Use less width to match libadwaita - drawer = drawer != null ? SizedBox(width: 200, child: drawer) : null, - super(key: key); + this.headerbar, + this.viewSwitcher, + }) : super(key: key); final Widget body; + + final AdwSidebar Function(bool isDrawer)? flap; + final FlapController? flapController; - final Widget? drawer; - /// Remove this when ViewSwitcher becomes adaptive - final Widget? bottomNavigationBar; + final FlapStyle? flapStyle; + + final Widget? Function(Widget? viewSwitcher)? headerbar; + + final Widget? viewSwitcher; @override _AdwScaffoldState createState() => _AdwScaffoldState(); @@ -32,24 +35,59 @@ class _AdwScaffoldState extends State { @override void initState() { super.initState(); - if (widget.body is AdwFlap) { - _flapController = widget.flapController ?? FlapController(); - } + _flapController = widget.flapController ?? FlapController(); } @override Widget build(BuildContext context) { - return Scaffold( - drawerEnableOpenDragGesture: - _flapController?.shouldEnableDrawerGesture(FlapPosition.start) ?? - false, - endDrawerEnableOpenDragGesture: - _flapController?.shouldEnableDrawerGesture(FlapPosition.end) ?? false, - onDrawerChanged: _flapController?.onDrawerChanged, - drawer: widget.drawer, - endDrawer: widget.drawer, - body: widget.body, - bottomNavigationBar: widget.bottomNavigationBar, + final isMobile = MediaQuery.of(context).size.width <= 600; + final headerbar = + widget.headerbar?.call(!isMobile ? widget.viewSwitcher : null); + final flap = widget.flap != null + ? SizedBox( + width: 200, + child: Drawer(elevation: 25, child: widget.flap!(true)), + ) + : null; + + return SafeArea( + child: Column( + children: [ + if (headerbar != null) headerbar, + Expanded( + child: Scaffold( + drawerEnableOpenDragGesture: _flapController + ?.shouldEnableDrawerGesture(FlapPosition.start) ?? + false, + endDrawerEnableOpenDragGesture: _flapController + ?.shouldEnableDrawerGesture(FlapPosition.end) ?? + false, + onDrawerChanged: _flapController?.onDrawerChanged, + drawer: flap, + endDrawer: flap, + body: widget.flap != null + ? AdwFlap( + flap: widget.flap!(false), + controller: widget.flapController, + style: widget.flapStyle, + child: widget.body, + ) + : widget.body, + bottomNavigationBar: widget.viewSwitcher != null && isMobile + ? SizedBox( + height: 51, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + widget.viewSwitcher!, + ], + ), + ) + : null, + ), + ) + ], + ), ); } } diff --git a/lib/src/widgets/adw/new/sidebar.dart b/lib/src/widgets/adw/new/sidebar.dart index 83749ad..6cda8d8 100644 --- a/lib/src/widgets/adw/new/sidebar.dart +++ b/lib/src/widgets/adw/new/sidebar.dart @@ -16,7 +16,7 @@ class AdwSidebar extends StatelessWidget { required this.onSelected, this.width = 270.0, this.color, - this.border, + this.isDrawer = false, this.controller, this.padding = const EdgeInsets.symmetric(vertical: 6, horizontal: 6), required List children, @@ -24,6 +24,7 @@ class AdwSidebar extends StatelessWidget { children.length, (index) => _AdwSidebarItemBuilder( item: (context) => children[index], + isDrawer: isDrawer, isSelected: index == currentIndex, onSelected: () => onSelected(index), ), @@ -36,7 +37,7 @@ class AdwSidebar extends StatelessWidget { required this.onSelected, this.width = 270.0, this.color, - this.border, + this.isDrawer = false, this.controller, this.padding = const EdgeInsets.symmetric(vertical: 6, horizontal: 6), required AdwSidebarItem Function( @@ -53,6 +54,7 @@ class AdwSidebar extends StatelessWidget { item: (context) => itemBuilder(context, index, currentIndex == index), isSelected: currentIndex == index, + isDrawer: isDrawer, onSelected: () => onSelected(index), ), ), @@ -72,6 +74,9 @@ class AdwSidebar extends StatelessWidget { /// Called when one of the Sidebar item is selected. final Function(int index) onSelected; + /// Is the Sidebar present in the Drawer of the Scaffold + final bool isDrawer; + /// The width of the sidebar. /// /// Defaults to `270.0`. @@ -80,9 +85,6 @@ class AdwSidebar extends StatelessWidget { /// The background color of the sidebar. final Color? color; - /// The border around the sidebar. - final Border? border; - /// Delegate in charge of supplying children to the internal list /// of this widget. final List _childrenDelegate; @@ -153,12 +155,14 @@ class _AdwSidebarItemBuilder extends StatelessWidget { Key? key, required this.item, required this.isSelected, + required this.isDrawer, this.onSelected, }) : super(key: key); final AdwSidebarItem Function(BuildContext context) item; final bool isSelected; final VoidCallback? onSelected; + final bool isDrawer; @override Widget build(BuildContext context) { @@ -169,7 +173,12 @@ class _AdwSidebarItemBuilder extends StatelessWidget { margin: const EdgeInsets.only(bottom: 2), textStyle: const TextStyle(fontWeight: FontWeight.normal), padding: currentItem.padding, - onPressed: onSelected, + onPressed: () { + onSelected?.call(); + if (isDrawer) { + Navigator.of(context).pop(); + } + }, isActive: isSelected, child: Row( children: [ diff --git a/lib/src/widgets/adw/new/text_field.dart b/lib/src/widgets/adw/new/text_field.dart index b141d5d..b094525 100644 --- a/lib/src/widgets/adw/new/text_field.dart +++ b/lib/src/widgets/adw/new/text_field.dart @@ -7,13 +7,34 @@ class AdwTextField extends StatelessWidget { this.keyboardType, this.onChanged, this.icon, + this.prefixIcon, + this.onSubmitted, this.initialValue, + this.autofocus = false, }) : super(key: key); + /// Will automatically focus on this field when it's visible + final bool autofocus; + + /// To be run when you submit this field using Enter key + final ValueChanged? onSubmitted; + + /// The controller for the field final TextEditingController? controller; + + /// Keyboard Type for this field final TextInputType? keyboardType; + + /// Runs when value is changed from this field final Function(String)? onChanged; + + /// The suffix icon for this field final IconData? icon; + + /// The prefix icon for this field + final IconData? prefixIcon; + + /// The initial value (if any) for this field final String? initialValue; @override @@ -21,25 +42,16 @@ class AdwTextField extends StatelessWidget { return TextFormField( initialValue: initialValue, controller: controller, + autofocus: autofocus, + onFieldSubmitted: onSubmitted, keyboardType: keyboardType, decoration: InputDecoration( - enabledBorder: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular(8), - ), - borderSide: BorderSide( - color: Colors.transparent, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: const BorderRadius.all( - Radius.circular(8), - ), - borderSide: BorderSide( - color: Theme.of(context).colorScheme.secondary, - ), - ), - isDense: true, + prefixIcon: prefixIcon != null + ? Icon( + prefixIcon, + color: Theme.of(context).textTheme.headline1?.color, + ) + : null, suffixIcon: icon != null ? Icon( icon, diff --git a/lib/src/widgets/adw/new/window_button.dart b/lib/src/widgets/adw/new/window_button.dart index cc8be66..6599c40 100644 --- a/lib/src/widgets/adw/new/window_button.dart +++ b/lib/src/widgets/adw/new/window_button.dart @@ -3,7 +3,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:libadwaita/src/utils/colors.dart'; import 'package:libadwaita/src/widgets/widgets.dart'; -enum AdwWindowButtonType { close, maximize, minimize } +enum WindowButtonType { close, maximize, minimize } class AdwWindowButton extends StatelessWidget { const AdwWindowButton({ @@ -14,15 +14,21 @@ class AdwWindowButton extends StatelessWidget { this.windowDecor, }) : super(key: key); + /// The ThemeType from the window_decorations package, nullable final dynamic themeType; + /// The windowDecor object from window_decorations package final Widget Function( String name, dynamic type, void Function()? windowDecor, )? windowDecor; + + /// Executed when this button is pressed final VoidCallback? onPressed; - final AdwWindowButtonType buttonType; + + /// The WindowButtonType for this window + final WindowButtonType buttonType; @override Widget build(BuildContext context) { diff --git a/lib/src/widgets/adw/preferences_group.dart b/lib/src/widgets/adw/preferences_group.dart index 3b86e21..b7733e4 100644 --- a/lib/src/widgets/adw/preferences_group.dart +++ b/lib/src/widgets/adw/preferences_group.dart @@ -49,7 +49,7 @@ class AdwPreferencesGroup extends StatelessWidget { if (title != null) ...[ Text( title!, - style: titleStyle ?? Theme.of(context).textTheme.headline6, + style: titleStyle ?? Theme.of(context).textTheme.headline5, ), if (description != null) Text( @@ -57,7 +57,7 @@ class AdwPreferencesGroup extends StatelessWidget { style: descriptionStyle ?? Theme.of(context).textTheme.bodyText2, ), - const SizedBox(height: 8), + const SizedBox(height: 12), ], ], ), diff --git a/lib/src/widgets/adw/switch.dart b/lib/src/widgets/adw/switch.dart index 9f6447c..3e08890 100644 --- a/lib/src/widgets/adw/switch.dart +++ b/lib/src/widgets/adw/switch.dart @@ -9,7 +9,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:libadwaita/src/utils/colors.dart'; -import 'package:libadwaita/src/widgets/widgets.dart'; // Examples can assume: // bool _lights = false; @@ -313,8 +312,8 @@ class _AdwSwitchState extends State with TickerProviderStateMixin { activeColor: widget.activeColor ?? (switchTheme.thumbColor != null ? switchTheme.thumbColor!.resolve({MaterialState.selected}) ?? - AdwColors.blue.backgroundColor - : AdwColors.blue.backgroundColor), + AdwDefaultColors.blue + : AdwDefaultColors.blue), trackColor: widget.trackColor ?? (switchTheme.trackColor != null ? switchTheme.trackColor!.resolve({MaterialState.focused}) ?? diff --git a/lib/src/widgets/adw/view_switcher.dart b/lib/src/widgets/adw/view_switcher.dart index 4f6eb9b..330a0df 100644 --- a/lib/src/widgets/adw/view_switcher.dart +++ b/lib/src/widgets/adw/view_switcher.dart @@ -9,7 +9,7 @@ class AdwViewSwitcher extends StatelessWidget { required this.onViewChanged, required this.currentIndex, this.badgeColor, - this.style, + this.policy, @Deprecated('This parameter is no longer in use') double height = 55, @Deprecated( @@ -17,23 +17,37 @@ class AdwViewSwitcher extends StatelessWidget { 'This feature was deprecated after v1.0.0-rc.2', ) bool? expanded, + this.paddingIcon, }) : assert(tabs.length >= 2, 'Minimum 2 tabs are required'), super(key: key); + /// The color of the badge at the top right of the tab's icon final Color? badgeColor; + + /// The tabs of this view switcher final List tabs; + + /// Executed when the view switcher tab is changed final ValueChanged onViewChanged; - final ViewSwitcherStyle? style; + + /// The Policy of this view switcher defaults to wide for desktop and + /// narrow for mobile + final ViewSwitcherPolicy? policy; + + /// The current index of the selected view final int currentIndex; + /// The Padding for the icon of the view switcher tab + final EdgeInsets? paddingIcon; + @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { - final newStyle = style ?? + final newPolicy = policy ?? (constraints.maxWidth > 600 - ? ViewSwitcherStyle.desktop - : ViewSwitcherStyle.mobile); + ? ViewSwitcherPolicy.wide + : ViewSwitcherPolicy.narrow); return Row( mainAxisSize: MainAxisSize.min, @@ -41,8 +55,9 @@ class AdwViewSwitcher extends StatelessWidget { for (final tab in tabs.asMap().entries) AdwViewSwitcherTab( data: tab.value, + paddingIcon: paddingIcon, badgeColor: badgeColor, - style: newStyle, + policy: newPolicy, isSelected: tab.key == currentIndex, onSelected: currentIndex != tab.key ? () => onViewChanged(tab.key) diff --git a/lib/src/widgets/adw/view_switcher_tab.dart b/lib/src/widgets/adw/view_switcher_tab.dart index ce31d6c..17d5ad8 100644 --- a/lib/src/widgets/adw/view_switcher_tab.dart +++ b/lib/src/widgets/adw/view_switcher_tab.dart @@ -1,26 +1,38 @@ import 'package:flutter/material.dart'; -import 'package:libadwaita/src/models/models.dart'; -import 'package:libadwaita/src/widgets/widgets.dart'; +import 'package:libadwaita/libadwaita.dart'; class AdwViewSwitcherTab extends StatelessWidget { const AdwViewSwitcherTab({ Key? key, required this.data, - required this.style, + required this.policy, this.badgeColor, this.isSelected = false, this.onSelected, + this.paddingIcon, }) : super(key: key); + /// The color of the badge at the top right of the tab's icon final Color? badgeColor; + + /// The of this view switcher tab final ViewSwitcherData data; - final ViewSwitcherStyle style; + + /// The Policy of the parent view switcher, either narrow or widget + final ViewSwitcherPolicy policy; + + /// Whether this tab is selected or not, defaults to false final bool isSelected; + + /// Executed when this tab is selected final VoidCallback? onSelected; + /// The Padding for the icon of the view switcher tab + final EdgeInsets? paddingIcon; + @override Widget build(BuildContext context) { - final isDesktop = style == ViewSwitcherStyle.desktop; + final isDesktop = policy == ViewSwitcherPolicy.wide; return AdwButton.flat( constraints: isDesktop @@ -39,9 +51,10 @@ class AdwViewSwitcherTab extends StatelessWidget { Stack( children: [ Padding( - padding: isDesktop - ? const EdgeInsets.only(top: 4, bottom: 4, right: 8) - : const EdgeInsets.only(top: 3.5, right: 4, left: 4), + padding: paddingIcon ?? + (isDesktop + ? const EdgeInsets.only(top: 4, bottom: 4, right: 8) + : const EdgeInsets.only(top: 3.5, right: 4, left: 4)), child: Icon(data.icon, size: 17), ), if (data.badge != null) @@ -52,8 +65,7 @@ class AdwViewSwitcherTab extends StatelessWidget { height: 14, width: 14, child: CircleAvatar( - backgroundColor: - badgeColor ?? AdwColors.blue.backgroundColor, + backgroundColor: badgeColor ?? AdwDefaultColors.blue, child: Text( data.badge!, style: const TextStyle( @@ -69,13 +81,7 @@ class AdwViewSwitcherTab extends StatelessWidget { ), if (data.icon != null && data.title != null) const SizedBox(height: 1.5), - if (data.title != null) - Padding( - padding: isDesktop - ? const EdgeInsets.symmetric(vertical: 4) - : EdgeInsets.zero, - child: Text(data.title!), - ), + if (data.title != null) Text(data.title!), ], ), ); diff --git a/lib/src/widgets/gtk/popup_menu.dart b/lib/src/widgets/gtk/popup_menu.dart index 1ef459c..b796bc6 100644 --- a/lib/src/widgets/gtk/popup_menu.dart +++ b/lib/src/widgets/gtk/popup_menu.dart @@ -40,6 +40,7 @@ class _AdwPopupMenuState extends State { setState(() => isActive = true); showPopover( context: context, + arrowHeight: 14, shadow: [ BoxShadow( color: context.borderColor, diff --git a/pubspec.yaml b/pubspec.yaml index 0a99791..130f9d0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,18 +7,25 @@ environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=1.17.0" +platforms: + android: + ios: + linux: + macos: + web: + windows: + dependencies: - dbus: ">=0.6.6 <1.0.0" + dbus: ">=0.7.1 <1.0.0" flutter: sdk: flutter flutter_svg: ">=1.0.3 < 2.0.0" - gsettings: ">=0.2.1 <1.0.0" + gsettings: ">=0.2.5 <1.0.0" package_info_plus: ">=1.3.0 < 2.0.0" popover_gtk: ">=0.2.6+3 < 1.0.0" url_launcher: ">=6.0.18 < 7.0.0" dev_dependencies: - svg_to_paint: ^1.0.10 very_good_analysis: ^2.4.0 flutter: