diff --git a/ippevetool/images/default-icon.png b/ippevetool/images/default-icon.png new file mode 100644 index 0000000..f443548 Binary files /dev/null and b/ippevetool/images/default-icon.png differ diff --git a/ippevetool/images/missing-icon.png b/ippevetool/images/missing-icon.png new file mode 100644 index 0000000..941c33b Binary files /dev/null and b/ippevetool/images/missing-icon.png differ diff --git a/ippevetool/ios/Podfile.lock b/ippevetool/ios/Podfile.lock new file mode 100644 index 0000000..a815b42 --- /dev/null +++ b/ippevetool/ios/Podfile.lock @@ -0,0 +1,28 @@ +PODS: + - Flutter (1.0.0) + - nsd_ios (0.0.1): + - Flutter + - url_launcher_ios (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `Flutter`) + - nsd_ios (from `.symlinks/plugins/nsd_ios/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + nsd_ios: + :path: ".symlinks/plugins/nsd_ios/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + +SPEC CHECKSUMS: + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + nsd_ios: 8c37babdc6538e3350dbed3a52674d2edde98173 + url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586 + +PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048 + +COCOAPODS: 1.11.3 diff --git a/ippevetool/ios/Runner.xcodeproj/project.pbxproj b/ippevetool/ios/Runner.xcodeproj/project.pbxproj index 78bf25e..d383ffc 100644 --- a/ippevetool/ios/Runner.xcodeproj/project.pbxproj +++ b/ippevetool/ios/Runner.xcodeproj/project.pbxproj @@ -8,8 +8,8 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 397FE4DD88782CAF0CC41D73 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0403A1EA6F858123B72BA360 /* Pods_Runner.framework */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 6D31E54F80760CEF04EA28BB /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 55A58647BE0354792F66A4E9 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -30,16 +30,14 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0403A1EA6F858123B72BA360 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 16B9A82208C1AC0D3D7FD372 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 55A58647BE0354792F66A4E9 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 5BA66091D22CA2FA3B736B6F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - 657987B5FD9A103860CED496 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 96F2A5109C9D557BDB232624 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -47,6 +45,8 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A1734E00AB82CF25054362C9 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + F83C84EEB56E7149D2DBF3AD /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -54,13 +54,32 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 6D31E54F80760CEF04EA28BB /* Pods_Runner.framework in Frameworks */, + 397FE4DD88782CAF0CC41D73 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 6C127CA3C58D973B330CFE5E /* Pods */ = { + isa = PBXGroup; + children = ( + 16B9A82208C1AC0D3D7FD372 /* Pods-Runner.debug.xcconfig */, + A1734E00AB82CF25054362C9 /* Pods-Runner.release.xcconfig */, + F83C84EEB56E7149D2DBF3AD /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 906FECEBDA7E6E9E4A94A6F7 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 0403A1EA6F858123B72BA360 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -78,8 +97,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - B6125DA08609227B36A6C3F7 /* Pods */, - E4D9A3228420C29FE0215475 /* Frameworks */, + 6C127CA3C58D973B330CFE5E /* Pods */, + 906FECEBDA7E6E9E4A94A6F7 /* Frameworks */, ); sourceTree = ""; }; @@ -106,25 +125,6 @@ path = Runner; sourceTree = ""; }; - B6125DA08609227B36A6C3F7 /* Pods */ = { - isa = PBXGroup; - children = ( - 96F2A5109C9D557BDB232624 /* Pods-Runner.debug.xcconfig */, - 657987B5FD9A103860CED496 /* Pods-Runner.release.xcconfig */, - 5BA66091D22CA2FA3B736B6F /* Pods-Runner.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; - E4D9A3228420C29FE0215475 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 55A58647BE0354792F66A4E9 /* Pods_Runner.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -132,14 +132,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 7B5F914E0E3F3C73D9648421 /* [CP] Check Pods Manifest.lock */, + E794F00F453635415E8006A7 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - D6B65FE002AC44307B4C9D57 /* [CP] Embed Pods Frameworks */, + 90BB2752E01D58CF153883E3 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -214,26 +214,21 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 7B5F914E0E3F3C73D9648421 /* [CP] Check Pods Manifest.lock */ = { + 90BB2752E01D58CF153883E3 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { @@ -251,21 +246,26 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - D6B65FE002AC44307B4C9D57 /* [CP] Embed Pods Frameworks */ = { + E794F00F453635415E8006A7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ diff --git a/ippevetool/lib/ipptool.dart b/ippevetool/lib/ipptool.dart index 2371c8b..e9db505 100644 --- a/ippevetool/lib/ipptool.dart +++ b/ippevetool/lib/ipptool.dart @@ -1,5 +1,5 @@ // -// ipptool support functions. +// IPP Everywhere Tool (ippevetool) support functions. // // Copyright © 2024 by the IEEE-ISTO Printer Working Group. // @@ -17,17 +17,27 @@ import 'package:xml/xml.dart'; Future> ipptoolGetAttributes({required String printerUri}) async { var process = await Process.start("ipptool", ["-j", printerUri, "get-printer-attributes.test"]); var json = ""; - process.stderr.pipe(stderr); + var error = ""; await process.stdout .transform(utf8.decoder) .forEach((text) { json = json + text; }); - var result = process.exitCode; + await process.stderr + .transform(utf8.decoder) + .forEach((text) { + error = error + text.replaceAll("\n", " "); + }); + //var result = process.exitCode; //print("result=$result\n"); //print("json=$json\n"); + //print("error=$error\n"); + if (json == "") { + json = "[{},{\"ipptool-error\":\"$error\"}]"; + } + const JsonDecoder decoder = JsonDecoder(); return (decoder.convert(json)[1]); @@ -59,10 +69,10 @@ Future ipptoolRunTest({required String printerUri, required String .forEach((text) { plist = plist + text; }); - var result = process.exitCode; +// var result = process.exitCode; - print("result=$result\n"); - print("plist=$plist\n"); +// print("result=$result\n"); +// print("plist=$plist\n"); return (XmlDocument.parse(plist)); } diff --git a/ippevetool/lib/main.dart b/ippevetool/lib/main.dart index 26d0a68..1fadb29 100644 --- a/ippevetool/lib/main.dart +++ b/ippevetool/lib/main.dart @@ -1,5 +1,16 @@ +// +// IPP Everywhere Tool (ippevetool) main UI. +// +// Copyright © 2022-2024 by the ISTO Printer Working Group. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + import 'dart:async'; +import 'dart:collection'; import 'dart:convert'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:nsd/nsd.dart'; @@ -7,291 +18,391 @@ import 'package:url_launcher/url_launcher.dart'; //import 'package:xml/xml.dart'; import 'ipptool.dart'; + +// Main entry - run the main application window... void main() { - runApp(const IppEveToolApp()); + runApp(const IppEveToolApp()); } +void _showAlert(BuildContext context, String message) { + // set up the button + Widget okButton = TextButton( + child: const Text("OK"), + onPressed: () { Navigator.of(context).pop(); }, + ); + + // set up the AlertDialog + AlertDialog alert = AlertDialog( + title: const Text("Alert"), + content: Text(message), + actions: [ + okButton, + ], + ); + + // show the dialog + showDialog( + context: context, + builder: (BuildContext context) { + return alert; + }, + ); +} + + +// Tap handler - copy to the clipboard and open URLs... void _tapValue(BuildContext context, String value) { - if (value.startsWith("http://") || value.startsWith("https://")) { - // Open URL... - launchUrl(Uri.parse(value)); - } - - // Copy to clipboard and let the user know what happened... - Clipboard.setData(ClipboardData(text: value)); - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Copied "$value" to clipboard.'), duration: const Duration(seconds: 1))); + if (value.startsWith("http://") || value.startsWith("https://")) { + // Open URL... + launchUrl(Uri.parse(value)); + } + + // Copy to clipboard and let the user know what happened... + Clipboard.setData(ClipboardData(text: value)); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: + Text('Copied "$value" to clipboard.'), + duration: const Duration(seconds: 1), + ) + ); } +// Main application window... class IppEveToolApp extends StatelessWidget { - const IppEveToolApp({super.key}); - - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'IPP Everywhere™ Tool', - theme: ThemeData( - // Theme colors are based on the PWG logo colors, which are also used on the PWG web page... - primarySwatch: const MaterialColor(0xff4b5aa8, { - 50: Color(0xff181814), - 100: Color(0xff262d54), - 200: Color(0xff2B397F), - 300: Color(0xff394ba8), - 400: Color(0xff445299), - 500: Color(0xff4b5aa8), - 600: Color(0xff626CA8), - 700: Color(0xff94a4ff), - 800: Color(0xffb8c3ff), - 900: Color(0xffccccc0), - }), - ), - debugShowCheckedModeBanner: false, - home: const IppEveHomePage(title: 'IPP Everywhere™ Tool'), - ); - } + const IppEveToolApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'IPP Everywhere™ Tool', + theme: ThemeData( + // Theme colors are based on the PWG logo colors, which are also used on the PWG web page... + primarySwatch: const MaterialColor(0xff4b5aa8, { + 50: Color(0xff181814), + 100: Color(0xff262d54), + 200: Color(0xff2B397F), + 300: Color(0xff394ba8), + 400: Color(0xff445299), + 500: Color(0xff4b5aa8), + 600: Color(0xff626CA8), + 700: Color(0xff94a4ff), + 800: Color(0xffb8c3ff), + 900: Color(0xffccccc0), + }), + ), + debugShowCheckedModeBanner: false, + home: const IppEveHomePage(title: 'IPP Everywhere™ Tool'), + ); + } } // Home page with list of printers... class IppEveHomePage extends StatefulWidget { - const IppEveHomePage({super.key, required this.title}); + const IppEveHomePage({super.key, required this.title}); - final String title; + final String title; - @override - State createState() => _IppEveHomePageState(); + @override + State createState() => _IppEveHomePageState(); } class _IppEveHomePageState extends State { - final printers = []; - - Future _startDiscovery() async { - final discovery = await startDiscovery("_ipp._tcp.", autoResolve: true); - discovery.addServiceListener((service, status) { - // print("${service.name} => ${status}"); - if (status == ServiceStatus.found) { - setState((){ printers.add(service); }); - } - }); - return; - } - - @override - void initState() { - super.initState(); - - _startDiscovery(); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // Here we take the value from the IppEveHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: _buildList(context), - ); - } - - _buildList(BuildContext context) { - if (printers.isEmpty) { - return const Center( - child: CircularProgressIndicator(), - ); - } else { - return ListView.builder( - itemBuilder: (context_, index) => GestureDetector( - onTap: () => _onPrinterTap(context, printers[index]), - child: ListTile( - leading: const Icon(Icons.print), - title: Text(printers[index].name ?? "Invalid printer name"), - ), - ), - itemCount: printers.length, - ); + final printers = []; + + Future _startDiscovery() async { + final discovery = await startDiscovery("_ipp._tcp.", autoResolve: true); + discovery.addServiceListener((service, status) { + // print("${service.name} => ${status}"); + if (status == ServiceStatus.found) { + setState((){ + printers.add(service); + printers.sort((a, b) => a.name!.compareTo(b.name!)); + }); + } + }); + return; } - } - _onPrinterTap(BuildContext context, Service printer) { -// print(printer.name); - Navigator.push( - context, - MaterialPageRoute(builder: (context) => IppEveDetailsPage(printer: printer,)), - ); - } + @override + void initState() { + super.initState(); + + _startDiscovery(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + // Here we take the value from the IppEveHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: _buildList(context), + ); + } + + _buildList(BuildContext context) { + if (printers.isEmpty) { + return const Center( + child: CircularProgressIndicator(), + ); + } else { + return ListView.builder( + itemBuilder: (context_, index) => GestureDetector( + onTap: () => _onPrinterTap(context, printers[index]), + child: ListTile( + leading: const Icon(Icons.print), + title: Text(printers[index].name ?? "Invalid printer name"), + ), + ), + itemCount: printers.length, + ); + } + } + + _onPrinterTap(BuildContext context, Service printer) { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => IppEveDetailsPage(printer: printer,)), + ); + } } // Details page for printers... class IppEveDetailsPage extends StatefulWidget { - const IppEveDetailsPage({super.key, required this.printer}); + const IppEveDetailsPage({super.key, required this.printer}); - final Service printer; + final Service printer; - @override - State createState() => _IppEveDetailsPageState(); + @override + State createState() => _IppEveDetailsPageState(); } class _IppEveDetailsPageState extends State { - var attrList = []; - - @override - void initState() { - super.initState(); - - _getAttrList(widget.printer); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(widget.printer.name ?? "Unknown"), - ), - body: SingleChildScrollView( - child: Expanded(child: Column( - children: [ - Row(children: [ - Image.network("http://www.pwg.org/ipp/ipp-everywhere.png", - width: 160, - ), - const Text( - "idle, accepting jobs\n'printer state message'\nmedia-empty, toner-low, cover-open", - textScaleFactor: 1.5, - ), - ]), - const Row(children: [ - Text(" TXT Record:", textAlign: TextAlign.left, style: TextStyle(fontWeight: FontWeight.bold)), - ]), - Row(children: [ - DataTable(columns: const [ - DataColumn(label: SizedBox(width: 100,child: Text("Key"),)), - DataColumn(label: Expanded(child: Text("Value"))), - ], - rows: _buildTxtRows(widget.printer.txt), - ), - ]), - - // IPP Attributes - const Row(children: [ - Text(" IPP Attributes:", textAlign: TextAlign.left, style: TextStyle(fontWeight: FontWeight.bold)), - ]), - Row(children: [ - DataTable(columns: const [ - DataColumn(label: SizedBox(width: 100,child: Text("Name"),)), - DataColumn(label: Expanded(child: Text("Value"))), + Map printerAttrs = {}; + var printerIcon = Image.asset("images/default-icon.png", width:160); + var printerState = "Unknown"; + var printerUri = "unknown"; + TextEditingController txtController = TextEditingController(); + String txtSearch = ""; + TextEditingController ippController = TextEditingController(); + String ippSearch = ""; + + @override + void initState() { + super.initState(); + + _getAttrList(widget.printer); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.printer.name ?? "Unknown"), + actions: [ + TextButton( + onPressed: () { + _showAlert(context, "Print File UI"); + }, + child: const Text('Print File'), + ), + TextButton( + onPressed: () { + _showAlert(context, "Run Test UI"); + }, + child: const Text('Run Test'), + ), + TextButton( + onPressed: () { + _showAlert(context, "Run Self-Cert UI"); + }, + child: const Text('Run Self-Cert'), + ), ], - rows: attrList, - ), - ]), - -// ListView(// Print File -// children: [ -// ], -// ), -// ListView(// Self-Certification -// children: [ -// ], -// ), -// ], -// ), - ]), - ), - ), - bottomNavigationBar: BottomNavigationBar( - items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.home), - label: "Overview", - ), - BottomNavigationBarItem( - icon: Icon(Icons.list), - label: "IPP", - tooltip: "IPP Attributes", - ), - BottomNavigationBarItem( - icon: Icon(Icons.print), - label: "Print", - tooltip: "Print File", - ), - BottomNavigationBarItem( - icon: Icon(Icons.run_circle), - label: "Self-Cert", - tooltip: "Self-Certification", - ), - ], - currentIndex: 0, - selectedItemColor: const Color(0xff4b5aa8), - unselectedItemColor: const Color(0xff94a4ff), - ), - ); - } - - // Build a list of attribute name and value data rows - Future _getAttrList(Service printer) async { - if (printer.host != null && printer.port != null && printer.txt != null) { - // Have a hostname, port, and TXT record... - var rp = "/ipp/print"; - - if (printer.txt!["rp"] != null) { - // Use the "rp" value from the TXT record... - Uint8List rpraw = printer.txt!["rp"]!; - rp = const Utf8Decoder().convert(rpraw); - if (rp[0] != '/') { - rp = "/$rp"; - } - } + ), + body: SingleChildScrollView(child: Column(children: [ + Row(children: [ + const Spacer(), + printerIcon, + const Spacer(), + ]), + Row(children: [ + const Spacer(), + const Text("State: ", style: TextStyle(fontWeight: FontWeight.bold)), + Text(printerState), + const Spacer(), + ]), + Row(children: [ + const Spacer(), + const Text("URI: ", style: TextStyle(fontWeight: FontWeight.bold)), + GestureDetector( + child: Text(printerUri, style: const TextStyle(color: Color(0xff4b5aa8))), + onTap: (){ _tapValue(context, printerUri); }, + ), + const Spacer(), + ]), + + // Divider between sections... + const Divider(color: Color(0xff4b5aa8), height: 20.0, thickness: 2.0), + + Row(children: [ + const Spacer(), + const Text("TXT Record: ", style: TextStyle(fontWeight: FontWeight.bold)), + SizedBox( + width: 400, + child: CupertinoSearchTextField( + controller: txtController, + onChanged: (String value) async { + setState(() { + txtSearch = value; + }); + }, + ), + ), + const Spacer(), + ]), + DataTable( + columns: const [ + DataColumn(label: SizedBox(width: 100,child: Text("Key"),)), + DataColumn(label: Expanded(child: Text("Value"))), + ], + rows: _buildTxtRows(widget.printer.txt, txtSearch), + ), + + // Divider between sections... + const Divider(color: Color(0xff4b5aa8), height: 20.0, thickness: 2.0), + + // IPP Attributes + Row(children: [ + const Spacer(), + const Text("IPP Attributes: ", style: TextStyle(fontWeight: FontWeight.bold)), + SizedBox( + width: 400, + child: CupertinoSearchTextField( + controller: ippController, + onChanged: (String value) async { + setState(() { + ippSearch = value; + }); + }, + ), + ), + const Spacer(), + ]), + DataTable( + columns: const [ + DataColumn(label: SizedBox(width: 100,child: Text("Name"),)), + DataColumn(label: Expanded(child: Text("Value"))), + ], + rows: _buildAttrRows(printerAttrs, ippSearch), + ), + ])), + ); + } - // Build the printer URI... - var uri = "ipp://${printer.host}:${printer.port}$rp"; - //print("printer URI = $uri\n"); - // Get the attributes as a JSON object - ipptoolGetAttributes(printerUri: uri).then((attrs){ + // Build the filtered rows of attributes + List _buildAttrRows(Map attrs, String search) { var list = []; - attrs.forEach((key,value){ - //print("'$key' = '$value'\n"); + var sortedAttrs = SplayTreeMap.from(attrs, (k1, k2) => k1.compareTo(k2)); + + sortedAttrs.forEach((key,value){ + if (key != "group-tag" && (search == "" || key.contains(search) || "$value".contains(search))) { + list.add(DataRow(cells: [ + DataCell(Text(key)), + DataCell(Expanded(child: Text("$value", softWrap: true)), onTap:(){ _tapValue(context, "$value"); }), + ])); + } + }); - if (key != "group-tag") { + return (list); + } + + + // Build the filtered rows of TXT record keys/values + List _buildTxtRows(Map? txt, String search) { + var list = [ ]; + + if (txt != null) { + var sortedTxt = SplayTreeMap.from(txt, (k1, k2) => k1.compareTo(k2)); + sortedTxt.forEach((key,value){ + var svalue = ""; + if (value != null) { + svalue = const Utf8Decoder().convert(value); + } + + // Filter using search string + if (search == "" || key.contains(search) || svalue.contains(search)) { list.add(DataRow(cells: [ - DataCell(Text(key)), - DataCell(Expanded(child: Text("$value", softWrap: true)), onTap:(){ _tapValue(context, "$value"); }), + DataCell(Text(key)), + DataCell(Expanded(child: Text(svalue, softWrap: true)), onTap:(){ _tapValue(context, svalue); }), ])); } }); + } - setState((){ - attrList = list; - }); - }); + return (list); } - } - List _buildTxtRows(Map? txt) { - var list = [ ]; - if (txt != null) { - txt.forEach((key,value){ - var svalue = ""; - if (value != null) { - svalue = const Utf8Decoder().convert(value); + // Get a list of attribute name and value data rows + Future _getAttrList(Service printer) async { + if (printer.host != null && printer.port != null && printer.txt != null) { + // Have a hostname, port, and TXT record... + var rp = "/ipp/print"; + + if (printer.txt!["rp"] != null) { + // Use the "rp" value from the TXT record... + Uint8List rpraw = printer.txt!["rp"]!; + rp = const Utf8Decoder().convert(rpraw); + if (rp[0] != '/') { + rp = "/$rp"; + } } - list.add(DataRow(cells: [ - DataCell(Text(key)), - DataCell(Expanded(child: Text(svalue, softWrap: true)), onTap:(){ _tapValue(context, svalue); }), - ])); - }); + // Build the printer URI... + var uri = "ipp://${printer.host}:${printer.port}$rp"; + //print("printer URI = $uri\n"); + + // Get the attributes as a JSON object + ipptoolGetAttributes(printerUri: uri).then((attrs){ + var icon = ""; + var state = "Unknown"; + var stateReasons = ""; + + attrs.forEach((key,value){ + //print("'$key' = '$value'\n"); + + if (key == "printer-icons") { + icon = value.last; + } else if (key == "printer-state") { + if (value == 3) { + state = "Idle"; + } else if (value == 4) { + state = "Processing"; + } else { + state = "Stopped"; + } + } else if (key == "printer-state-reasons" && value != "none") { + stateReasons = ", $value"; + } + }); + + setState((){ + printerAttrs = attrs; + if (icon != "") { + printerIcon = Image.network(icon, width: 160); + } else { + printerIcon = Image.asset("images/missing-icon.png", width: 160); + } + printerState = "$state$stateReasons"; + printerUri = uri; + }); + }); + } } - - return (list); - } } diff --git a/ippevetool/pubspec.yaml b/ippevetool/pubspec.yaml index 889f59f..0af2b37 100644 --- a/ippevetool/pubspec.yaml +++ b/ippevetool/pubspec.yaml @@ -71,9 +71,8 @@ flutter: uses-material-design: true assets: - - ../libcups/tools/printer.png - - ../libcups/tools/printer-lg.png - - ../libcups/tools/printer-sm.png + - images/default-icon.png + - images/missing-icon.png # To add assets to your application, add an assets section, like this: # assets: