numples 1.2.0

Yet another colourful sudoku playing game.
Documentation
use std::fs;

use bevy::prelude::*;
use native_dialog::{DialogBuilder, MessageLevel};

use crate::events::NumplesEvent;
use crate::game::{Board, Level};
use crate::gameplay::{Clock, Gameplay};
use crate::states::GameState;

use super::payload::Payload;

#[derive(Debug, Clone, Default, Resource)]
pub struct Persistence {
    pub load: bool,
    pub save: bool,
}

impl Persistence {

    pub fn load_board(
        mut this: ResMut<Self>,
        mut commands: Commands,
        mut event_writer: EventWriter<NumplesEvent>,
        mut board_query: Query<&mut Board>,
        mut level_query: Query<&mut Level>,
        mut clock: ResMut<Clock>,
        mut next_state: ResMut<NextState<GameState>>,
        current_state: Res<State<GameState>>,
    ) {
        if !this.load {
            return;
        }
        this.load = false;
        if let Some(filename) = this.load_file_name() {
            match fs::read_to_string(filename) {
                Ok(payload) => match serde_json::from_str::<Payload>(&payload) {
                        Ok(payload) => {
                            if let Ok(mut board) = board_query.single_mut() {
                                *board = payload.board;
                            } else {
                                commands.spawn((Gameplay, payload.board));
                            }
                            if let Ok(mut level) = level_query.single_mut() {
                                *level = payload.level;
                            } else {
                                commands.spawn((Gameplay, payload.level));
                            }
                            *clock = payload.clock;
                            if *current_state == GameState::Title {
                                next_state.set(GameState::Playing);
                            }
                            event_writer.write(NumplesEvent::RenderBoard);
                        }

                        Err(err) => {
                            eprintln!("{:?}", err);
                            eprintln!("content: {}", payload);
                            this.dialog(&err.to_string(), MessageLevel::Error);
                        }
                    }
                Err(err) => {
                    eprintln!("{:?}", err);
                    this.dialog(&err.to_string(), MessageLevel::Error);
                }
            }
        }
    }

    pub fn save_board(
        board: Single<&Board>,
        clock: Res<Clock>,
        level: Single<&Level>,
        mut persistence: ResMut<Self>,
    ) {
        if !persistence.save {
            return;
        }
        persistence.save = false;
        if let Some(filename) = persistence.save_file_name(**level) {
            let payload = Payload {
                board: board.clone(),
                clock: clock.clone(),
                level: **level,
            };
            match serde_json::to_string(&payload) {
                Ok(payload) => match fs::write(filename, payload) {
                    Ok(_) => (),
                    Err(err) => {
                        eprintln!("{:?}", err);
                        persistence.dialog(&err.to_string(), MessageLevel::Error);
                    }
                },
                Err(err) => {
                    eprintln!("{:?}", err);
                    persistence.dialog(&err.to_string(), MessageLevel::Error);
                }
            }
        }
    }

    fn dialog(&self, message: &str, level: MessageLevel) {
        DialogBuilder::message()
            .set_title("Numplës Message")
            .set_text(message)
            .set_level(level)
            .alert()
            .show()
            .expect("error showing dialog");
    }

    fn load_file_name(&self) -> Option<String> {
        let path = DialogBuilder::file()
            .set_title("Open Board")
            .set_location(env!["HOME"])
            //.add_filter("JSON", &["json", "jsonc"])
            .open_single_file()
            .show();

        match path {
            Ok(Some(path)) => Some(path.to_string_lossy().into_owned()),
            Ok(None) | Err(_) => None,
        }
    }

    fn save_file_name(&self, level: Level) -> Option<String> {
        let path = DialogBuilder::file()
            .set_title("Save Board")
            .set_location(env!["HOME"])
            .set_filename(format!("{}.json", level.to_string()))
            .add_filter("JSON", &["json", "jsonc"])
            .save_single_file()
            .show();

        match path {
            Ok(Some(path)) => Some(path.to_string_lossy().into_owned()),
            Ok(None) | Err(_) => None,
        }
    }
}