diff --git a/assets/images/icons/printer-network.png b/assets/images/icons/printer-network.png new file mode 100644 index 00000000..207ffa29 Binary files /dev/null and b/assets/images/icons/printer-network.png differ diff --git a/assets/images/icons/printer-network.svg b/assets/images/icons/printer-network.svg new file mode 100644 index 00000000..fbc4c792 --- /dev/null +++ b/assets/images/icons/printer-network.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/icons/printer.png b/assets/images/icons/printer.png new file mode 100644 index 00000000..251a4c7e Binary files /dev/null and b/assets/images/icons/printer.png differ diff --git a/assets/images/icons/printer.svg b/assets/images/icons/printer.svg new file mode 100644 index 00000000..5ce86f0e --- /dev/null +++ b/assets/images/icons/printer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/view/pages/page_items.dart b/lib/view/pages/page_items.dart index a756db8b..b065f254 100644 --- a/lib/view/pages/page_items.dart +++ b/lib/view/pages/page_items.dart @@ -18,6 +18,7 @@ import 'package:settings/view/pages/multitasking/multi_tasking_page.dart'; import 'package:settings/view/pages/notifications/notifications_page.dart'; import 'package:settings/view/pages/online_accounts/online_accounts_page.dart'; import 'package:settings/view/pages/power/power_page.dart'; +import 'package:settings/view/pages/printers/printers_page.dart'; import 'package:settings/view/pages/privacy/privacy_page.dart'; import 'package:settings/view/pages/region_and_language/region_and_language_page.dart'; import 'package:settings/view/pages/removable_media/removable_media_page.dart'; @@ -139,10 +140,11 @@ List getPageItems(BuildContext context) => [ hasAppBar: false, ), SettingsPageItem( - titleBuilder: (context) => const Text('Printers'), + titleBuilder: PrintersPage.createTitle, iconBuilder: (context, selected) => const Icon(YaruIcons.printer), - builder: (_) => const Center(child: Text('Printers')), + builder: PrintersPage.create, title: context.l10n.printersPageTitle, + searchMatches: PrintersPage.searchMatches, ), SettingsPageItem( titleBuilder: RemovableMediaPage.createTitle, diff --git a/lib/view/pages/printers/printers_model.dart b/lib/view/pages/printers/printers_model.dart new file mode 100644 index 00000000..3ff7516a --- /dev/null +++ b/lib/view/pages/printers/printers_model.dart @@ -0,0 +1,23 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:safe_change_notifier/safe_change_notifier.dart'; + +class PrintersModel extends SafeChangeNotifier { + void openPrinterSettings() { + Process.run('system-config-printer', ['&']); + } + + Future> loadPrinters() async { + final printerNames = []; + await Process.run('lpinfo', ['-v']).then((value) { + printerNames.addAll(const LineSplitter().convert(value.stdout)); + printerNames.retainWhere( + (element) => + element.contains('network dnssd://') || + element.contains('network ipps://'), + ); + }); + return printerNames; + } +} diff --git a/lib/view/pages/printers/printers_page.dart b/lib/view/pages/printers/printers_page.dart new file mode 100644 index 00000000..f11ffade --- /dev/null +++ b/lib/view/pages/printers/printers_page.dart @@ -0,0 +1,282 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:settings/constants.dart'; +import 'package:settings/l10n/l10n.dart'; +import 'package:settings/view/pages/printers/printers_model.dart'; +import 'package:settings/view/pages/settings_page.dart'; +import 'package:yaru_icons/yaru_icons.dart'; +import 'package:yaru_widgets/yaru_widgets.dart'; + +class PrintersPage extends StatefulWidget { + const PrintersPage({super.key}); + + static Widget create(BuildContext context) { + // final service = Provider.of(context, listen: false); + return ChangeNotifierProvider( + create: (_) => PrintersModel(), + child: const PrintersPage(), + ); + } + + static Widget createTitle(BuildContext context) => + Text(context.l10n.printersPageTitle); + + static bool searchMatches(String value, BuildContext context) => + value.isNotEmpty + ? context.l10n.printersPageTitle + .toLowerCase() + .contains(value.toLowerCase()) + : false; + + @override + State createState() => _PrintersPageState(); +} + +class _PrintersPageState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + final model = context.watch(); + return SettingsPage( + children: [ + ChangeNotifierProvider.value( + value: model, + child: const _Header(), + ), + const SizedBox( + height: 20, + ), + SizedBox( + width: kDefaultWidth, + child: FutureBuilder>( + future: model.loadPrinters(), + builder: (context, snapshot) { + return snapshot.hasData + ? ListView.separated( + itemCount: snapshot.data!.length, + itemBuilder: (context, index) { + return _PrinterSection(name: snapshot.data![index]); + }, + shrinkWrap: true, + separatorBuilder: (context, index) { + return const SizedBox( + height: kYaruPagePadding, + ); + }, + ) + : const YaruLinearProgressIndicator(); + }, + ), + ) + ], + ); + } +} + +class _PrinterSection extends StatelessWidget { + const _PrinterSection({ + required this.name, + }); + + final String name; + + @override + Widget build(BuildContext context) { + return YaruSection( + headline: Text(name), + child: YaruTile( + trailing: Row( + children: [ + SizedBox( + width: 70, + child: Image.asset( + 'assets/images/icons/printer.png', + fit: BoxFit.fill, + ), + ), + const SizedBox( + width: 20, + ), + const SizedBox( + height: 100, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Text( + 'Model XYZ', + overflow: TextOverflow.ellipsis, + ), + ), // printer.type + SizedBox( + height: 3, + ), + Flexible( + child: Text('bla', overflow: TextOverflow.ellipsis), + ), // printer.location + // printer.status + ], + ), + ) + ], + ), + leading: Row( + children: [ + const SizedBox( + height: 40, + child: OutlinedButton( + onPressed: null, // printer.activejobs ? + child: Text( + 'No active jobs', + // style: TextStyle( + // color: Theme.of(context).disabledColor), + ), + ), + ), + const SizedBox( + width: 10, + ), + YaruOptionButton( + child: const Icon(YaruIcons.settings), + onPressed: () => {}, + ), + ], + ), + enabled: true, + ), + ); + } +} + +class _Header extends StatelessWidget { + const _Header(); + + @override + Widget build(BuildContext context) { + final model = context.watch(); + return SizedBox( + width: kDefaultWidth, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () { + showDialog( + barrierDismissible: false, + context: context, + builder: (_) => StatefulBuilder( + builder: (context, setState) { + return AddPrinterDialog( + onConfirm: () { + Navigator.of(context).pop(); + return ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Icon( + YaruIcons.printer, + color: Theme.of(context).colorScheme.primary, + ), + ), + ); + }, + title: 'Add Printer', + children: [ + Icon( + YaruIcons.printer, + color: Theme.of(context).colorScheme.primary, + size: 50, + ) + ], + ); + }, + ), + ); + }, + child: const Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Icon(YaruIcons.plus), + Padding( + padding: EdgeInsets.only(left: 12.0, right: 2), + child: Text('Add a printer'), + ) + ], + ), + ), + const SizedBox( + width: 12, + ), + YaruOptionButton( + onPressed: model.openPrinterSettings, + child: const Icon(YaruIcons.settings), + ) + ], + ), + ); + } +} + +class AddPrinterDialog extends StatelessWidget { + const AddPrinterDialog({ + super.key, + this.onConfirm, + this.title, + this.children, + }); + + final Function()? onConfirm; + final String? title; + final List? children; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 500, + child: SimpleDialog( + titlePadding: EdgeInsets.zero, + title: YaruDialogTitleBar( + title: Text(title ?? ''), + ), + contentPadding: EdgeInsets.zero, + children: [ + const Icon( + YaruIcons.printer, + size: 80, + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + width: kDefaultWidth / 3, + child: Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(context.l10n.cancel), + ), + ), + const SizedBox( + width: 8, + ), + Expanded( + child: ElevatedButton( + onPressed: onConfirm, + child: Text( + context.l10n.confirm, + ), + ), + ) + ], + ), + ), + ) + ], + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index ac512f3b..6f679057 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -80,3 +80,4 @@ flutter: - assets/images/appearance/auto-hide-dock-mode/ - assets/rive/ - assets/pdf_assets/ + - assets/images/icons/ \ No newline at end of file