Erdnaxela3/bioptim_gui

View on GitHub
gui/lib/screens/load_existing_page.dart

Summary

Maintainability
Test Coverage
import 'dart:convert';
import 'dart:io';

import 'package:bioptim_gui/models/api_config.dart';
import 'package:bioptim_gui/models/python_interface.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

class LoadExisting extends StatefulWidget {
  const LoadExisting({super.key, this.columnWidth = 400.0});

  final double columnWidth;

  @override
  State<LoadExisting> createState() => _LoadExistingState();
}

class _LoadExistingState extends State<LoadExisting> {
  List<File> pickedFiles = [];
  String bestFile = '';
  List<String> toDiscard = [];

  void requestBestAndDiscard() {
    final url = Uri.parse('${APIConfig.url}/load_existing/load');
    final request = http.MultipartRequest('POST', url);
    request.files.addAll(pickedFiles.map((file) => http.MultipartFile.fromBytes(
          'files',
          file.readAsBytesSync(),
          filename: file.path,
        )));

    setState(() {
      bestFile = '';
      toDiscard = [];
    });

    request.send().then((response) async {
      if (response.statusCode != 200) {
        if (kDebugMode) {
          throw Exception(
              'Error while loading existing solution ${await response.stream.bytesToString()}');
        }
      }

      final jsonResponse = await response.stream.bytesToString();
      final jsonData = jsonDecode(jsonResponse);

      setState(() {
        bestFile = jsonData['best'] ?? '';
        toDiscard = List<String>.from(jsonData['to_discard']);
      });
    });
  }

  void handleFilePickerOnPressed() async {
    final results = await FilePicker.platform.pickFiles(
      type: FileType.custom,
      allowedExtensions: ["pkl"],
      allowMultiple: true,
    );
    if (results == null) return;

    setState(() {
      pickedFiles = results.paths.map((path) => File(path!)).toList();
    });

    requestBestAndDiscard();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          const SizedBox(height: 12),
          Center(
            child: SizedBox(
              width: widget.columnWidth,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text('Select the pickle(s) to load and compare:'),
                  const SizedBox(height: 12),
                  ElevatedButton(
                    onPressed: handleFilePickerOnPressed,
                    child: const Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Text('Load best solution'),
                        Icon(Icons.file_upload_outlined),
                      ],
                    ),
                  ),
                  const SizedBox(height: 12),
                  if (pickedFiles.isNotEmpty)
                    Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        const Text(
                          'Loaded files:',
                          style: TextStyle(fontWeight: FontWeight.bold),
                        ),
                        const SizedBox(height: 12),
                        ListTile(
                          contentPadding: const EdgeInsets.symmetric(
                              horizontal: 8, vertical: 4),
                          trailing: const Row(
                            mainAxisSize: MainAxisSize.min,
                            children: [
                              Text('Create graph'),
                              Icon(Icons.bar_chart),
                            ],
                          ),
                          title: Column(children: [
                            for (final file in pickedFiles)
                              Text(file.path.split('/').last),
                          ]),
                          onTap: () async {
                            final process = await PythonInterface.instance
                                .runCreateGraph(
                                    pickedFiles.map((e) => e.path).toList());

                            if (process == null) {
                              if (kDebugMode) {
                                print('Failed to run create graph');
                              }
                            }
                          },
                        ),
                        const SizedBox(height: 12),
                        // Best
                        const Text(
                          'Best solution:',
                          style: TextStyle(fontWeight: FontWeight.bold),
                        ),
                        const SizedBox(height: 12),
                        if (bestFile.isNotEmpty)
                          ListTile(
                            contentPadding: const EdgeInsets.symmetric(
                                horizontal: 8, vertical: 4),
                            trailing: const Row(
                              mainAxisSize: MainAxisSize.min,
                              children: [
                                Text('Save video'),
                                Icon(Icons.video_file),
                              ],
                            ),
                            title: Text(
                              bestFile.split('/').last,
                            ),
                            onTap: () async {
                              final process = await PythonInterface.instance
                                  .runAnimateSolution(bestFile);

                              if (process == null) {
                                if (kDebugMode) {
                                  print('Failed to run animate solution');
                                }
                              }
                            },
                          ),
                        const SizedBox(height: 12),
                        // To discard
                        const Text(
                          'To discard:',
                          style: TextStyle(fontWeight: FontWeight.bold),
                        ),
                        const SizedBox(height: 12),
                        Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            for (final file in toDiscard)
                              ListTile(
                                contentPadding: const EdgeInsets.symmetric(
                                    horizontal: 8, vertical: 4),
                                trailing: const Icon(Icons.delete),
                                title: Text(
                                  file.split('/').last,
                                ),
                                onTap: () {
                                  File fileToDelete = File(file);
                                  fileToDelete.deleteSync();

                                  setState(() {
                                    toDiscard.remove(file);
                                  });
                                },
                              ),
                          ],
                        )
                      ],
                    ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}