build method

  1. @override
Widget build(
  1. BuildContext context,
  2. WidgetRef ref
)
override

Describes the part of the user interface represented by this widget.

The framework calls this method when this widget is inserted into the tree in a given BuildContext and when the dependencies of this widget change (e.g., an InheritedWidget referenced by this widget changes). This method can potentially be called in every frame and should not have any side effects beyond building a widget.

The framework replaces the subtree below this widget with the widget returned by this method, either by updating the existing subtree or by removing the subtree and inflating a new subtree, depending on whether the widget returned by this method can update the root of the existing subtree, as determined by calling Widget.canUpdate.

Typically implementations return a newly created constellation of widgets that are configured with information from this widget's constructor and from the given BuildContext.

The given BuildContext contains information about the location in the tree at which this widget is being built. For example, the context provides the set of inherited widgets for this location in the tree. A given widget might be built with multiple different BuildContext arguments over time if the widget is moved around the tree or if the widget is inserted into the tree in multiple places at once.

The implementation of this method must only depend on:

If a widget's build method is to depend on anything else, use a StatefulWidget instead.

See also:

  • StatelessWidget, which contains the discussion on performance considerations.

Implementation

@override
Widget build(BuildContext context, WidgetRef ref) {
  imageCache.clear();
  imageCache.clearLiveImages();
  final ScrollController scrollController = ScrollController();

  return Scaffold(
    appBar: AppBar(
      centerTitle: false,
      title: pageDoc == null
          ? Text(pageTitle)
          : MarkdownBody(
              data: wordWrap('''
                    **$pageTitle**

                    $pageDoc
                    '''),
              styleSheet: MarkdownStyleSheet(
                // Force left alignment for paragraph text.
                p: Theme.of(context).textTheme.bodyMedium ??
                    const TextStyle(),
                textAlign: WrapAlignment.start,
                strong: const TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.w500,
                ),
              ),
              onTapLink: (text, href, title) {
                if (href != null && href.isNotEmpty) {
                  launchUrl(Uri.parse(href));
                }
              },
            ),
    ),
    body: Column(
      children: [
        Expanded(
          child: Scrollbar(
            controller: scrollController,
            thumbVisibility: true,
            thickness: 8,
            radius: const Radius.circular(
              4,
            ), // Optional: customize scrollbar appearance
            child: ListView.builder(
              controller: scrollController,
              scrollDirection: Axis.horizontal,
              itemCount: paths.length,
              itemBuilder: (context, index) {
                return FutureBuilder<Uint8List?>(
                  future: _loadImageBytes(paths[index]),
                  builder: (context, snapshot) {
                    if (snapshot.hasError) {
                      return Center(child: Text('Error: ${snapshot.error}'));
                    } else if (snapshot.connectionState ==
                        ConnectionState.waiting) {
                      return const Center(child: CircularProgressIndicator());
                    } else if (snapshot.data == null ||
                        snapshot.data!.isEmpty) {
                      return const Center(
                        child: Text(
                          'Image not (yet) available',
                          style: TextStyle(
                            fontSize: 20,
                            fontWeight: FontWeight.bold,
                            color: Colors.redAccent,
                          ),
                        ),
                      );
                    } else {
                      final bytes = snapshot.data!;

                      return Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: SingleChildScrollView(
                          child: Column(
                            mainAxisSize: MainAxisSize.min,
                            children: [
                              Row(
                                mainAxisSize: MainAxisSize.min,
                                crossAxisAlignment: CrossAxisAlignment.center,
                                children: [
                                  Flexible(
                                    fit: FlexFit.loose,
                                    child: MarkdownBody(
                                      data: wordWrap(titles[index]),
                                      selectable: true,
                                      onTapLink: (text, href, title) {
                                        final Uri url = Uri.parse(href ?? '');
                                        launchUrl(url);
                                      },
                                    ),
                                  ),
                                  const Gap(8),
                                  MarkdownTooltip(
                                    message: '''
                                              **Enlarge.** Tap here to view the plot enlarged to the
                                              maximum size within the app.
                                              ''',
                                    child: IconButton(
                                      icon: const Icon(
                                        Icons.zoom_out_map,
                                        color: Colors.blue,
                                      ),
                                      onPressed: () {
                                        showImageDialog(context, bytes);
                                      },
                                    ),
                                  ),
                                  const Gap(8),
                                  MarkdownTooltip(
                                    message: '''
                                              **Open.** Tap here to open the plot in a separate window
                                              to the Rattle app itself. This allows you to retain a
                                              view of the plot while you navigate through other plots
                                              and analyses. If you choose the external app to be
                                              **Inkscape**, for example, then you can edit the plot,
                                              including the text, colours, etc. Note that Inkscape can
                                              be a little slow to startup. The choice of app depends
                                              on your operating system settings and can be overridden
                                              in the Rattle **Settings**.
                                              ''',
                                    child: IconButton(
                                      icon: const Icon(
                                        Icons.open_in_new,
                                        color: Colors.blue,
                                      ),
                                      onPressed: () async {
                                        String fileName =
                                            'plot_${Random().nextInt(10000)}.svg';
                                        File tempFile = File(
                                          '$tempDir/$fileName',
                                        );

                                        await File(
                                          paths[index],
                                        ).copy(tempFile.path);

                                        final prefs = await SharedPreferences
                                            .getInstance();
                                        final savedImageViewer =
                                            prefs.getString('imageViewerApp');

                                        // If the shared preferences image viewer app is null(not set),
                                        // use the provider default.

                                        final imageViewerApp =
                                            savedImageViewer ??
                                                ref.read(
                                                  imageViewerSettingProvider,
                                                );

                                        Platform.isWindows
                                            ? Process.run(
                                                imageViewerApp!,
                                                [
                                                  tempFile.path,
                                                ],
                                                runInShell: true,
                                              )
                                            : Process.run(imageViewerApp!, [
                                                tempFile.path,
                                              ]);
                                      },
                                    ),
                                  ),
                                  const Gap(8),
                                  MarkdownTooltip(
                                    message: '''
                                              **Save.** Tap here to save the plot in your preferred
                                              format (**svg**, **pdf**, or **png**). You can directly
                                              choose your desired format by replacing the default
                                              *svg* filename extension with either *pdf* or *png*. The
                                              file is saved to your local storage. Perfect for
                                              including in reports or keeping for future
                                              reference. The **svg** format is particularly convenient
                                              as you can edit all details of the plot with an
                                              application like **Inkscape**.
                                              ''',
                                    child: IconButton(
                                      icon: const Icon(
                                        Icons.save,
                                        color: Colors.blue,
                                      ),
                                      onPressed: () async {
                                        String fileName =
                                            paths[index].split('/').last;
                                        String? pathToSave = await selectFile(
                                          defaultFileName: fileName,
                                          allowedExtensions: [
                                            'svg',
                                            'pdf',
                                            'png',
                                          ],
                                        );
                                        if (pathToSave != null) {
                                          String extension = pathToSave
                                              .split('.')
                                              .last
                                              .toLowerCase();
                                          if (extension == 'svg') {
                                            await File(
                                              paths[index],
                                            ).copy(pathToSave);
                                          } else if (extension == 'pdf') {
                                            await _exportToPdf(
                                              paths[index],
                                              pathToSave,
                                            );
                                          } else if (extension == 'png') {
                                            await _exportToPng(
                                              paths[index],
                                              pathToSave,
                                            );
                                          } else if (context.mounted) {
                                            showOk(
                                              title: 'Error',
                                              context: context,
                                              content: '''
                                            An unsupported filename extension was
                                            provided: .$extension. Please try again
                                            and select a filename with one of the
                                            supported extensions: .svg, .pdf, or .png.
                                            ''',
                                            );
                                          } else {
                                            return;
                                          }
                                        }
                                      },
                                    ),
                                  ),
                                  const Gap(5),
                                ],
                              ),
                              const Gap(8),
                              Container(
                                constraints: const BoxConstraints(
                                  maxHeight: 450,
                                ),
                                child: InteractiveViewer(
                                  maxScale: 5,
                                  alignment: Alignment.topCenter,
                                  child: svgImage
                                      ? SvgPicture.memory(
                                          bytes,
                                          fit: BoxFit.scaleDown,
                                        )
                                      : Image.memory(bytes),
                                ),
                              ),
                            ],
                          ),
                        ),
                      );
                    }
                  },
                );
              },
            ),
          ),
        ),
      ],
    ),
  );
}