diff --git a/example/lib/home_page.dart b/example/lib/home_page.dart index e1e5344..ce9f3fa 100644 --- a/example/lib/home_page.dart +++ b/example/lib/home_page.dart @@ -1,3 +1,4 @@ +import 'package:example/pages/about_dialog_page.dart'; import 'package:example/pages/avatar_page.dart'; import 'package:example/pages/counter_page.dart'; import 'package:example/pages/flap_page.dart'; @@ -114,38 +115,40 @@ class _MyHomePageState extends State { 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'), - actions: AdwActions( - onClose: Navigator.of(context).pop, - onHeaderDrag: appWindow?.startDragging, - onDoubleTap: appWindow?.maximizeOrRestore, - ), - credits: [ - AdwPreferencesGroup.credits( - title: 'Developers', - children: developers.entries - .map( - (e) => AdwActionRow( - title: e.key, - onActivated: () => launchUrl( - Uri.parse('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.', - ), - ), - ), + onPressed: () { + // showDialog( + // context: context, + // builder: (ctx) => AdwAboutWindow( + // issueTrackerLink: + // 'https://github.com/gtk-flutter/libadwaita/issues', + // appIcon: Image.asset('assets/logo.png'), + // actions: AdwActions( + // onClose: Navigator.of(context).pop, + // onHeaderDrag: appWindow?.startDragging, + // onDoubleTap: appWindow?.maximizeOrRestore, + // ), + // credits: [ + // AdwPreferencesGroup.credits( + // title: 'Developers', + // children: developers.entries + // .map( + // (e) => AdwActionRow( + // title: e.key, + // onActivated: () => launchUrl( + // Uri.parse('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), @@ -182,6 +185,9 @@ class _MyHomePageState extends State { ), AdwSidebarItem( label: 'Style Classes', + ), + AdwSidebarItem( + label: 'About Window', ) ], onSelected: (index) => setState(() => _currentIndex = index), @@ -198,6 +204,7 @@ class _MyHomePageState extends State { const ViewSwitcherPage(), const SettingsPage(), const StyleClassesPage(), + const AboutDialogPage(), ], ), ); diff --git a/example/lib/pages/about_dialog_page.dart b/example/lib/pages/about_dialog_page.dart new file mode 100644 index 0000000..a78a32d --- /dev/null +++ b/example/lib/pages/about_dialog_page.dart @@ -0,0 +1,73 @@ +import 'package:example/pages/run_demo_screen.dart'; +import 'package:flutter/material.dart'; +import 'package:libadwaita/libadwaita.dart'; + +class AboutDialogPage extends StatelessWidget { + const AboutDialogPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return DemoScreen( + image: const Icon( + Icons.view_sidebar_rounded, + size: 130, + ), + title: 'About Dialog', + description: 'Something something', + footer: AdwButton.pill( + child: const Text('Run the demo'), + onPressed: () { + showDialog( + context: context, + builder: (context) => AdwAboutWindow( + supportUrl: 'https://example.org', + issueUrl: 'https://example.org', + developers: [ + AboutWindowPerson( + name: 'Angela Avery', + url: 'mailto:angela@example.com', + ), + ], + designers: [ + AboutWindowPerson( + name: 'Ben Dover', + url: 'mailto:ben@dover.com', + ), + ], + acknowledgements: [ + AboutWindowAcknowledgementSection( + name: 'Special Thanks to', + people: [ + AboutWindowPerson( + name: 'My Cat', + ), + ], + ), + ], + details: AboutWindowDetails( + comments: """ +Typeset is an app that doesn't exist and is used as an example content for this about window.""", + links: { + 'Website': 'https://example.org', + 'Documentation': 'https://example.org', + }, + ), + legalSections: [ + AboutWindowLegalSection( + title: 'This Application', + copyright: '© 2022', + license: ''' +This application comes with absolutely no warranty. See the GNU Lesser General Public License, version 2.1 or later for details''', + ), + AboutWindowLegalSection( + title: 'Fonts', + copyright: '© 2022', + license: 'This application uses font data from somewhere.'), + ], + ), + ); + }, + ), + ); + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock index 40331bd..2398f05 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -21,7 +21,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" bitsdojo_window_linux: dependency: transitive description: @@ -63,7 +63,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" charcode: dependency: transitive description: @@ -77,7 +77,7 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: @@ -105,7 +105,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" ffi: dependency: transitive description: @@ -197,21 +197,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" package_info_plus: dependency: transitive description: @@ -260,7 +260,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" path_drawing: dependency: transitive description: @@ -321,7 +321,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -342,21 +342,21 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" typed_data: dependency: transitive description: diff --git a/lib/src/models/about_window/about_window_acknowledgement_section.dart b/lib/src/models/about_window/about_window_acknowledgement_section.dart new file mode 100644 index 0000000..728155c --- /dev/null +++ b/lib/src/models/about_window/about_window_acknowledgement_section.dart @@ -0,0 +1,8 @@ +import 'package:libadwaita/libadwaita.dart'; + +class AboutWindowAcknowledgementSection { + AboutWindowAcknowledgementSection({required this.name, required this.people}); + + final String name; + final List people; +} diff --git a/lib/src/models/about_window/about_window_details.dart b/lib/src/models/about_window/about_window_details.dart new file mode 100644 index 0000000..e673489 --- /dev/null +++ b/lib/src/models/about_window/about_window_details.dart @@ -0,0 +1,6 @@ +class AboutWindowDetails { + AboutWindowDetails({this.comments, this.links}); + + final String? comments; + final Map? links; +} diff --git a/lib/src/models/about_window/about_window_legal.dart b/lib/src/models/about_window/about_window_legal.dart new file mode 100644 index 0000000..f0148da --- /dev/null +++ b/lib/src/models/about_window/about_window_legal.dart @@ -0,0 +1,8 @@ +class AboutWindowLegalSection { + AboutWindowLegalSection( + {required this.title, required this.copyright, required this.license,}); + + final String title; + final String copyright; + final String license; +} diff --git a/lib/src/models/about_window/about_window_person.dart b/lib/src/models/about_window/about_window_person.dart new file mode 100644 index 0000000..55c796a --- /dev/null +++ b/lib/src/models/about_window/about_window_person.dart @@ -0,0 +1,6 @@ +class AboutWindowPerson { + AboutWindowPerson({required this.name, this.url}); + + final String name; + final String? url; +} diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart index 5a9cbb4..63bcda7 100644 --- a/lib/src/models/models.dart +++ b/lib/src/models/models.dart @@ -1,2 +1,7 @@ +export 'about_window/about_window_acknowledgement_section.dart'; +export 'about_window/about_window_details.dart'; +export 'about_window/about_window_legal.dart'; +export 'about_window/about_window_person.dart'; +export 'about_window/about_window_person.dart'; export 'view_switcher_data.dart'; export 'view_switcher_policy.dart'; diff --git a/lib/src/widgets/adw/about_window.dart b/lib/src/widgets/adw/about_window.dart index ff5d764..4cf65d5 100644 --- a/lib/src/widgets/adw/about_window.dart +++ b/lib/src/widgets/adw/about_window.dart @@ -1,217 +1,622 @@ import 'package:flutter/material.dart'; import 'package:libadwaita/libadwaita.dart'; -import 'package:libadwaita_core/libadwaita_core.dart'; -import 'package:package_info_plus/package_info_plus.dart'; -import 'package:url_launcher/url_launcher.dart'; +import 'package:url_launcher/url_launcher_string.dart'; /// The About window for your app in libadwaita style /// Use this with [showDialog] and onPressed / onTap / onActivated /// parameter of a button /// Example: -/// ``` -/// showDialog( -/// context: context, -/// builder: (ctx) => AdwAboutWindow( -/// issueTrackerLink: 'link', -/// appIcon: Image.asset('assets/logo.png'), -/// credits: [], -/// ), -/// ), -/// ``` +/// ``` showDialog( context: context, builder: (ctx) => AdwAboutWindow( issueTrackerLink: 'link', appIcon: Image.asset('assets/logo.png'), credits: [],),), ``` class AdwAboutWindow extends StatefulWidget { const AdwAboutWindow({ Key? key, - required this.appIcon, - this.appName, - this.appVersion, - this.nextPageIcon, - this.launchEndIcon, - this.width = 360, - @Deprecated('headerbar is deprecated, use the properties separately') - AdwHeaderBar? Function(Widget?)? headerbar, - this.headerBarStyle, - this.start, - this.end, - this.actions, - this.controls, - this.copyright, - this.issueTrackerLink, - this.license, - this.credits, + this.details, + this.supportUrl, + this.issueUrl, + this.developers, + this.designers, + this.artists, + this.documentors, + this.translators, + this.acknowledgements, + this.legalSections, }) : super(key: key); - final HeaderBarStyle? headerBarStyle; + final AboutWindowDetails? details; + final String? supportUrl; + final String? issueUrl; + final List? developers; + final List? designers; + final List? artists; + final List? documentors; + final List? translators; + final List? acknowledgements; + final List? legalSections; - final List? start; - final List? end; - - final AdwActions? actions; - final AdwControls? controls; + @override + State createState() => _AdwAboutWindowState(); +} - /// The width of the about window dialog - final double width; +class _AdwAboutWindowState extends State { + late GlobalKey navigatorKey; - /// The app icon to show in the about window - final Widget appIcon; + String currentRoute = ''; - /// The app name to show in the about window, not required - final String? appName; + @override + void initState() { + navigatorKey = GlobalKey(); + super.initState(); + } - /// The app version to show in the about window, not required - final String? appVersion; + bool isVisible(String name) { + return name != '/' && name != ''; + } - /// The end icon of The Credits and Legal button, - /// defaults to chevron_right Material Icon - final Widget? nextPageIcon; + void goTo(String route, BuildContext context) { + setState(() { + Navigator.of(context).pushNamed(route); + }); + } - /// The end icon of Report an issue button - final Widget? launchEndIcon; + @override + Widget build(BuildContext context) { + return GtkDialog( + width: 300, + start: [ + AnimatedOpacity( + opacity: isVisible(currentRoute) ? 1 : 0, + duration: const Duration(milliseconds: 300), + child: AbsorbPointer( + absorbing: !isVisible(currentRoute), + child: AdwHeaderButton( + icon: const Icon( + Icons.chevron_left, + size: 24, + ), + onPressed: () { + navigatorKey.currentState?.pop(); + setState(() { + currentRoute = '/'; + }); + }, + ), + ), + ) + ], + child: Navigator( + key: navigatorKey, + initialRoute: '/', + onGenerateRoute: (settings) { + currentRoute = settings.name ?? ''; - /// The Copyright notice for Legal Screen - final String? copyright; + Widget page = Builder( + builder: (context) => _AdwAboutDialogHome( + goTo: (name) => goTo(name, context), + details: widget.details, + supportUrl: widget.supportUrl, + issueUrl: widget.issueUrl, + acknowledgements: widget.acknowledgements, + legalSections: widget.legalSections, + ), + ); - /// The link for the issue tracker - final String? issueTrackerLink; + switch (settings.name) { + case '/': + break; + case '/new': + page = const _AdwAboutDialogPatchNotes(); + break; + case '/details': + page = _AdwAboutDialogDetails(details: widget.details!); + break; + case '/credits': + page = _AdwAboutDialogCredits( + artists: widget.artists, + developers: widget.developers, + designers: widget.designers, + documentors: widget.documentors, + translators: widget.translators, + ); + break; + case '/acknowledgements': + page = _AdwAboutDialogAcknowledgements( + sections: widget.acknowledgements!, + ); + break; + case '/legal': + page = _AdwAboutDialogLegal( + legalSections: widget.legalSections!, + ); + break; + } - /// The License for the app - final Text? license; + return PageRouteBuilder( + pageBuilder: (context, _, __) => page, + transitionsBuilder: + (context, animation, secondaryAnimation, child) { + const begin = Offset(1, 0); + const end = Offset.zero; + const curve = Curves.ease; - /// The content's of Credits screen - final List? credits; + final tween = + Tween(begin: begin, end: end).chain(CurveTween(curve: curve)); - @override - State createState() => _AdwAboutWindowState(); + return SlideTransition( + position: animation.drive(tween), + child: child, + ); + }, + settings: settings, + ); + }, + ), + ); + } } -class _AdwAboutWindowState extends State { - int currentPage = 0; +class _AdwAboutDialogHome extends StatelessWidget { + const _AdwAboutDialogHome({ + Key? key, + required this.goTo, + this.details, + this.supportUrl, + this.issueUrl, + this.acknowledgements, + this.legalSections, + }) : super(key: key); + + final Function(String) goTo; + final AboutWindowDetails? details; + final String? supportUrl; + final String? issueUrl; + final List? acknowledgements; + final List? legalSections; + + bool hasTroubleshooting() { + return supportUrl != null || issueUrl != null; + } + + bool hasAcknowledgements() { + return acknowledgements != null && acknowledgements!.isNotEmpty; + } + + bool hasLegal() { + return legalSections != null && legalSections!.isNotEmpty; + } + @override Widget build(BuildContext context) { - const commonPadding = EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ); - final leading = currentPage != 0 - ? AdwHeaderButton( - icon: const Icon(Icons.chevron_left), - onPressed: () => setState(() => currentPage = 0), - ) - : const SizedBox(); - final text = currentPage != 0 - ? Text(currentPage == 1 ? 'Credits' : 'Legal') - : const SizedBox(); - - return FutureBuilder( - future: PackageInfo.fromPlatform(), - builder: (context, snapshot) { - final data = snapshot.hasData ? snapshot.data : null; - final isNotNull = data != null; - return GtkDialog( - constraints: BoxConstraints( - maxWidth: widget.width, - minHeight: 350, - maxHeight: 400, + return ListView( + padding: const EdgeInsets.symmetric(horizontal: 8), + children: [ + Align( + child: Container( + margin: const EdgeInsets.symmetric(vertical: 3), + child: Image.asset( + 'assets/logo.png', + width: 100, + ), ), - title: text, - start: [ - leading, - if (widget.start != null) ...widget.start!, - ], - end: widget.end ?? [], - actions: widget.actions ?? - AdwActions( - onClose: Navigator.of(context).pop, + ), + const SizedBox( + height: 5, + ), + const Align( + child: Text( + 'Typeset', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox( + height: 8, + ), + const Align( + child: Text( + 'Angela Avery', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w300, + ), + ), + ), + const SizedBox( + height: 12, + ), + Align( + child: AdwButton.pill( + padding: const EdgeInsets.only( + top: 6, + bottom: 8, + left: 18, + right: 18, + ), + textStyle: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primary, + ), + backgroundColor: Theme.of(context).colorScheme.secondary, + child: const Text('1.2.3'), + ), + ), + const SizedBox(height: 20), + AdwPreferencesGroup( + children: [ + AdwActionRow( + title: "What's new", + titleStyle: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 13, ), - controls: widget.controls, - headerBarStyle: widget.headerBarStyle ?? - const HeaderBarStyle( - isTransparent: true, + onActivated: () { + goTo('/new'); + }, + end: const Icon( + Icons.arrow_forward_ios, + size: 16, ), - padding: commonPadding, - children: currentPage == 0 - ? [ - Container( - margin: const EdgeInsets.symmetric(vertical: 3), - width: 80, - child: widget.appIcon, - ), - Text( - widget.appName ?? (isNotNull ? data!.appName : '---'), - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, + ), + if (details != null) + AdwActionRow( + title: 'Details', + titleStyle: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 13, + ), + onActivated: () { + goTo('/details'); + }, + end: const Icon( + Icons.arrow_forward_ios, + size: 16, + ), + ), + ], + ), + if (hasTroubleshooting()) + Column( + children: [ + const SizedBox(height: 8), + AdwPreferencesGroup( + children: [ + if (supportUrl != null) + AdwActionRow( + title: 'Support Questions', + titleStyle: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 13, + ), + onActivated: () { + launchUrlString(supportUrl!); + }, + end: const Icon( + Icons.open_in_new_rounded, + size: 16, + ), ), - ), - const SizedBox(height: 6), - AdwPreferencesGroup( - children: [ - AdwActionRow( - title: 'Version', - end: Text( - widget.appVersion ?? - (isNotNull ? data!.version : '0'), - ), + if (issueUrl != null) + AdwActionRow( + title: 'Report an issue', + titleStyle: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 13, + ), + onActivated: () { + launchUrlString(issueUrl!); + }, + end: const Icon( + Icons.open_in_new_rounded, + size: 16, ), - if (widget.issueTrackerLink != null) - AdwActionRow( - title: 'Report an issue', - onActivated: () => launchUrl( - Uri.parse(widget.issueTrackerLink!), - ), - end: widget.launchEndIcon ?? - const Icon( - Icons.open_in_new_outlined, - size: 20, - ), - ), - ], - ), - if ((widget.credits != null) || - widget.copyright != null || - widget.license != null) ...[ - const SizedBox(height: 8), - AdwPreferencesGroup( - children: [ - if (widget.credits != null) - AdwActionRow( - title: 'Credits', - onActivated: () => setState(() => currentPage = 1), - end: widget.nextPageIcon ?? - const Icon( - Icons.chevron_right, - ), - ), - if (widget.copyright != null || widget.license != null) - AdwActionRow( - title: 'Legal', - onActivated: () => setState(() => currentPage = 2), - end: widget.nextPageIcon ?? - const Icon( - Icons.chevron_right, - ), - ), - ], ), - ], - ] - : currentPage == 1 - ? widget.credits! - .map( - (e) => Padding( - padding: const EdgeInsets.only( - bottom: 10, - ), - child: e, + ], + ), + ], + ), + const SizedBox(height: 8), + AdwPreferencesGroup( + children: [ + AdwActionRow( + title: 'Credits', + titleStyle: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 13, + ), + onActivated: () { + goTo('/credits'); + }, + end: const Icon( + Icons.arrow_forward_ios, + size: 16, + ), + ), + if (hasLegal()) + AdwActionRow( + title: 'Legal', + titleStyle: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 13, + ), + onActivated: () { + goTo('/legal'); + }, + end: const Icon( + Icons.arrow_forward_ios, + size: 16, + ), + ), + if (hasAcknowledgements()) + AdwActionRow( + title: 'Acknowledgements', + titleStyle: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 13, + ), + onActivated: () { + goTo('/acknowledgements'); + }, + end: const Icon( + Icons.arrow_forward_ios, + size: 16, + ), + ), + ], + ), + const SizedBox( + height: 8, + ) + ], + ); + } +} + +class _AdwAboutDialogPatchNotes extends StatelessWidget { + const _AdwAboutDialogPatchNotes({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + color: Theme.of(context).dialogBackgroundColor, + height: double.infinity, + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 12), + children: const [ + Text( + 'Version 1.2.0', + style: TextStyle( + fontWeight: FontWeight.w500, + ), + ), + SizedBox( + height: 16, + ), + Text( + ''' +This release adds the following features: + +• Added a way to export fonts. +• Better support for monospace fonts. +• Added a way to preview italic text. +• Bug fixes and performance improvements. +• Translation updates. + ''', + ), + ], + ), + ); + } +} + +class _AdwAboutDialogDetails extends StatelessWidget { + const _AdwAboutDialogDetails({Key? key, required this.details}) + : super(key: key); + + final AboutWindowDetails details; + + @override + Widget build(BuildContext context) { + return Container( + color: Theme.of(context).dialogBackgroundColor, + height: double.infinity, + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + children: [ + if (details.comments != null) + Text( + details.comments!, + ), + const SizedBox(height: 16), + AdwPreferencesGroup( + children: details.links?.entries + .map( + (entry) => AdwActionRow( + title: entry.key, + titleStyle: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 13, ), - ) - .toList() - : [ - if (widget.copyright != null) Text(widget.copyright!), - const SizedBox(height: 5), - if (widget.license != null) widget.license!, - ], - ); - }, + onActivated: () {}, + end: const Icon( + Icons.open_in_new, + size: 16, + ), + ), + ) + .toList() ?? + [], + ) + ], + ), + ); + } +} + +class _AdwAboutDialogCredits extends StatelessWidget { + const _AdwAboutDialogCredits({ + Key? key, + this.developers, + this.designers, + this.artists, + this.documentors, + this.translators, + }) : super(key: key); + + final List? developers; + final List? designers; + final List? artists; + final List? documentors; + final List? translators; + + @override + Widget build(BuildContext context) { + return Container( + color: Theme.of(context).dialogBackgroundColor, + height: double.infinity, + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + children: [ + if (developers != null) + _CreditSection(type: 'Developed by', people: developers!), + if (designers != null) + _CreditSection(type: 'Design by', people: designers!), + if (artists != null) + _CreditSection(type: 'Artwork by', people: artists!), + if (documentors != null) + _CreditSection(type: 'Documented by', people: documentors!), + if (translators != null) + _CreditSection(type: 'Translated by', people: translators!), + ], + ), + ); + } +} + +class _AdwAboutDialogAcknowledgements extends StatelessWidget { + const _AdwAboutDialogAcknowledgements({ + Key? key, + required this.sections, + }) : super(key: key); + + final List sections; + + @override + Widget build(BuildContext context) { + return Container( + color: Theme.of(context).dialogBackgroundColor, + height: double.infinity, + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + children: sections + .map( + (e) => _CreditSection( + type: e.name, + people: e.people, + ), + ) + .toList(), + ), + ); + } +} + +class _CreditSection extends StatelessWidget { + const _CreditSection({Key? key, required this.type, required this.people}) + : super(key: key); + + final String type; + final List people; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 5, + ), + child: AdwPreferencesGroup.credits( + title: type, + children: [ + ...people + .map( + (e) => AdwActionRow( + title: e.name, + titleStyle: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 13, + ), + onActivated: + e.url != null ? () => launchUrlString(e.url!) : null, + end: e.url != null + ? const Icon( + Icons.open_in_new, + size: 16, + ) + : null, + ), + ) + .toList() + ], + ), + ); + } +} + +class _AdwAboutDialogLegal extends StatelessWidget { + const _AdwAboutDialogLegal({Key? key, required this.legalSections}) + : super(key: key); + + final List legalSections; + + @override + Widget build(BuildContext context) { + return Container( + color: Theme.of(context).dialogBackgroundColor, + height: double.infinity, + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 12), + children: legalSections + .map((e) => _AdwAboutDialogLegalSection(info: e)) + .toList(), + ), + ); + } +} + +class _AdwAboutDialogLegalSection extends StatelessWidget { + const _AdwAboutDialogLegalSection({Key? key, required this.info}) + : super(key: key); + + final AboutWindowLegalSection info; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + info.title, + style: const TextStyle( + fontWeight: FontWeight.w500, + ), + ), + const SizedBox( + height: 16, + ), + Text( + info.copyright, + ), + const SizedBox( + height: 12, + ), + Text( + info.license, + ), + const SizedBox( + height: 20, + ), + ], ); } } diff --git a/lib/src/widgets/adw/action_row.dart b/lib/src/widgets/adw/action_row.dart index 73312a2..be1f32f 100644 --- a/lib/src/widgets/adw/action_row.dart +++ b/lib/src/widgets/adw/action_row.dart @@ -6,6 +6,7 @@ class AdwActionRow extends StatelessWidget { this.start, this.end, required this.title, + this.titleStyle, this.onActivated, this.subtitle, this.horizontalTitleGap = 8, @@ -23,6 +24,9 @@ class AdwActionRow extends StatelessWidget { /// The title of this row final String title; + /// The [TextStyle] of the title + final TextStyle? titleStyle; + /// The subtitle of this row final String? subtitle; @@ -53,7 +57,10 @@ class AdwActionRow extends StatelessWidget { ? SizedBox(height: double.infinity, child: start) : null, onTap: onActivated, - title: Text(title), + title: Text( + title, + style: titleStyle, + ), subtitle: subtitle != null && subtitle!.isNotEmpty ? Text(subtitle!) : null, trailing: end, diff --git a/lib/src/widgets/gtk/dialog.dart b/lib/src/widgets/gtk/dialog.dart index 6835bd5..130c4d6 100644 --- a/lib/src/widgets/gtk/dialog.dart +++ b/lib/src/widgets/gtk/dialog.dart @@ -15,7 +15,7 @@ class GtkDialog extends StatelessWidget { this.constraints, this.height, this.width, - required this.children, + required this.child, }) : super(key: key); final HeaderBarStyle? headerBarStyle; @@ -28,7 +28,7 @@ class GtkDialog extends StatelessWidget { final AdwControls? controls; final EdgeInsets? padding; - final List children; + final Widget child; final BoxConstraints? constraints; final double? height; @@ -38,14 +38,11 @@ class GtkDialog extends StatelessWidget { Widget build(BuildContext context) { return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: ClipRRect( - borderRadius: BorderRadius.circular(12), - child: ConstrainedBox( - constraints: constraints ?? - BoxConstraints( - maxWidth: width ?? 600, - maxHeight: height ?? 600, - ), + child: SizedBox( + width: width ?? 600, + height: height ?? 600, + child: ClipRRect( + borderRadius: BorderRadius.circular(12), child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -65,13 +62,7 @@ class GtkDialog extends StatelessWidget { ), ), Flexible( - child: SingleChildScrollView( - padding: padding, - child: Column( - mainAxisSize: MainAxisSize.min, - children: children, - ), - ), + child: child, ), ], ),