build method

  1. @override
Widget build(
  1. BuildContext context,
  2. SettingsBuilder model
)
override

Builds the UI according to the state in model.

Implementation

@override
Widget build(BuildContext context, SettingsBuilder model) => Scaffold(
	appBar: AppBar(title: const Text("Settings")),
	body: Column(children: [
      Expanded(child: ListView(
        padding: const EdgeInsets.all(12),
        children: [
          ValueEditor<NetworkSettings>(
            name: "Network settings",
            children: [
              SocketEditor(name: "Subsystems socket", model: model.network.dataSocket),
              SocketEditor(name: "Video socket", model: model.network.videoSocket),
              SocketEditor(name: "Autonomy socket", model: model.network.autonomySocket),
              SocketEditor(name: "Tank IP address", model: model.network.tankSocket, editPort: false),
              NumberEditor(name: "Heartbeats per second", model: model.network.connectionTimeout),
              if (Platform.isWindows) ListTile(
                title: const Text("Open Windows network settings"),
                subtitle: const Text("You may need to change these if the rover will not connect"),
                trailing: const Icon(Icons.lan_outlined),
                onTap: () {
                  launchUrl(Uri.parse("ms-settings:network-ethernet"));
                  showDialog<void>(
                    context: context,
                    builder: (context) => AlertDialog(
                      content: const SelectableText(
                        "Click on IP Assignment, select Manual, then IPv4, then set:\n"
                        "\n- IP address: 192.168.1.10"
                        "\n- Subnet 255.255.255.0 (or Subnet length: 24)"
                        "\n- Gateway: 192.168.1.1"
                        "\n- Preferred DNS: 192.168.1.1"
                      ),
                      title: const Text("Set your IP settings"),
                      actions: [
                        TextButton(
                          child: const Text("Ok"),
                          onPressed: () => Navigator.of(context).pop(),
                        ),
                      ],
                    ),
                  );
                },
              ),
            ],
          ),
          const Divider(),
          ValueEditor<ArmSettings>(
            name: "Arm settings",
            children: [
              NumberEditor(name: "Swivel increment", model: model.arm.swivel),
              NumberEditor(name: "Shoulder increment", model: model.arm.shoulder),
              NumberEditor(name: "Elbow increment", model: model.arm.elbow),
              NumberEditor(name: "Wrist rotate increment", model: model.arm.rotate),
              NumberEditor(name: "Wrist lift increment", model: model.arm.lift),
              NumberEditor(name: "Pinch increment", model: model.arm.pinch),
            ],
          ),
          const Divider(),
          ValueEditor<ScienceSettings>(
            name: "Science settings",
            children: [
              NumberEditor(
                name: "Number of samples",
                model: model.science.numSamples,
              ),
              SwitchListTile(
                title: const Text("Scrollable graphs"),
                subtitle: const Text("Graphs can either be forced to fit the page or allowed to scroll\nMight be inconvenient for desktop users"),
                value: model.science.scrollableGraphs,
                onChanged: model.science.updateScrollableGraphs,
              ),
            ],
          ),
          const Divider(),
          ValueEditor<DashboardSettings>(
            name: "Dashboard Settings",
            children: [
              NumberEditor(
                name: "Frames per second",
                subtitle: "This does not affect the rover's cameras. Useful for limiting the CPU of the dashboard",
                model: model.dashboard.fps,
              ),
              NumberEditor(
                name: "Block size",
                subtitle: "The precision of the GPS grid",
                model: model.dashboard.blockSize,
              ),
              SwitchListTile(
                title: const Text("Split camera controls"),
                subtitle: const Text("If enabled, cameras can only be controlled by a separate operator"),
                value: model.dashboard.splitCameras,
                onChanged: model.dashboard.updateCameras,
              ),
              SwitchListTile(
                title: const Text("Prefer tank controls"),
                subtitle: const Text("Default to tank controls instead of modern drive controls"),
                value: model.dashboard.preferTankControls,
                onChanged: model.dashboard.updateTank,
              ),
              SwitchListTile(
                title: const Text("Require version checking"),
                subtitle: const Text("Default to version checking on"),
                value: model.dashboard.versionChecking,
                onChanged: model.dashboard.updateVersionChecking,
              ),
              Row(children: [
                const SizedBox(
                  width: 200,
                  child: ListTile(
                    title: Text("Split mode"),
                  ),
                ),
                const Spacer(),
                DropdownMenu<SplitMode>(
                  initialSelection: model.dashboard.splitMode,
                  onSelected: model.dashboard.updateSplitMode,
                  dropdownMenuEntries: [
                    for (final value in SplitMode.values) DropdownMenuEntry(
                      value: value,
                      label: value.humanName,
                    ),
                  ],
                ),
              ],),
              Row(children: [
                const SizedBox(
                  width: 200,
                  child: ListTile(
                    title: Text("Theme mode"),
                  ),
                ),
                const Spacer(),
                DropdownMenu<ThemeMode>(
                  initialSelection: model.dashboard.themeMode,
                  onSelected: model.dashboard.updateThemeMode,
                  dropdownMenuEntries: [
                    for (final value in ThemeMode.values) DropdownMenuEntry<ThemeMode>(
                      value: value,
                      label: value.humanName,
                    ),
                  ],
                ),
              ],),
            ],
          ),
          const Divider(),
          ValueEditor<EasterEggsSettings>(
            name: "Easter eggs",
            children: [
              SwitchListTile(
                title: const Text("Enable SEGA Intro"),
                value: model.easterEggs.segaIntro,
                onChanged: model.easterEggs.updateSegaIntro,
              ),
              // Disabled because the sound is horrible. Please find a better sound :)
              // SwitchListTile(
              //   title: const Text("Enable SEGA sound"),
              //   subtitle: const Text('Says "Binghamton" in the SEGA style'),
              //   value: model.easterEggs.segaSound,
              //   onChanged: model.easterEggs.segaIntro ? model.easterEggs.updateSegaSound : null,
              // ),
              SwitchListTile(
                title: const Text("Enable Clippy"),
                value: model.easterEggs.enableClippy,
                onChanged: model.easterEggs.updateClippy,
              ),
              SwitchListTile(
                title: const Text("Bad Apple in the Map"),
                value: model.easterEggs.badApple,
                onChanged: model.easterEggs.updateBadApple,
              ),
            ],
          ),
          const Divider(),
          Text("Misc", style: Theme.of(context).textTheme.titleLarge),
          ListTile(
            title: const Text("Open session output"),
            subtitle: const Text("Opens all files created by this session"),
            trailing: const Icon(Icons.folder_open),
            onTap: () => launchUrl(services.files.loggingDir.uri),
          ),
          ListTile(
            title: const Text("Open the output folder"),
            subtitle: const Text("Contains logs, screenshots, and settings"),
            trailing: const Icon(Icons.folder_open),
            onTap: () => launchUrl(services.files.outputDir.uri),
          ),
          ListTile(
            title: const Text("Set a timer"),
            subtitle: const Text("Shows a timer for the current mission"),
            trailing: const Icon(Icons.alarm),
            onTap: () => showDialog<void>(context: context, builder: (_) => TimerEditor()),
          ),
          ListTile(
            title: const Text("About"),
            subtitle: const Text("Show contributor and version information"),
            trailing: const Icon(Icons.info_outline),
            onTap: () => showAboutDialog(
              context: context,
              applicationName: "Binghamton University Rover Team Dashboard",
              applicationVersion: models.home.version,
              applicationIcon: Image.asset("assets/logo.png", scale: 4),
              applicationLegalese: [
                "Firmware versions:",
                for (final metrics in models.rover.metrics.allMetrics)
                  "  ${metrics.name}: Supports ${metrics.supportedVersion.format()}. Rover: ${metrics.version.format()}",
              ].join("\n"),
              children: [
                const SizedBox(height: 24),
                Center(child: TextButton(
                  onPressed: () => launchUrl(Uri.parse("https://github.com/BinghamtonRover/Dashboard/graphs/contributors")),
                  child: const Text("Click to see contributions"),
                ),),
              ],
            ),
          ),
        ],
      ),),
      Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text("Cancel"),
          ),
          const SizedBox(width: 4),
          ElevatedButton.icon(
            onPressed: !model.isValid ? null : () async {
              await model.save();
              if (context.mounted) Navigator.of(context).pop();
            },
            label: const Text("Save"),
            icon: model.isLoading
              ? const SizedBox(height: 24, width: 24, child: CircularProgressIndicator())
              : const Icon(Icons.save),
          ),
          const SizedBox(width: 4),
        ],
      ),
      const SizedBox(height: 12),
    ],),
);