diff --git a/src/pages/exams.rs b/src/pages/exams.rs index 009ac38..2d3733a 100644 --- a/src/pages/exams.rs +++ b/src/pages/exams.rs @@ -1,6 +1,6 @@ use crate::app::AppState; use crate::data::Id; -use crate::data::entities::{DeckSettings, EntityHandle, Exam, ExamEntry}; +use crate::data::entities::{DeckSettings, EntityHandle, Exam, ExamEntry, WordData}; use crate::data::repositories::{BatchedRepository, Repository}; use crate::navigation::Page::PreviousPage; use crate::navigation::{NavigatedPage, Page, RootMessage}; @@ -9,11 +9,18 @@ use crate::pages::exam::ExamState; use crate::pages::exams::ExamsMessage::*; use crate::pages::repetitions::Style; use crate::push_error; -use crate::styling::{BUTTON_RADIUS, DEFAULT_SPACING, HALF_SPACING, back_overlay, cta_button}; +use crate::styling::{ + BUTTON_RADIUS, DEFAULT_SPACING, HALF_SPACING, QUARTER_SPACING, back_overlay, cta_button, +}; use crate::{jl_button, push_error_task}; use chrono::{Local, Utc}; use iced::widget::button::Status; -use iced::widget::{Column, button, center_x, column, container, row, rule, scrollable, space, text, tooltip}; +use iced::widget::container::bordered_box; +use iced::widget::space::horizontal; +use iced::widget::{ + Column, button, center, center_x, column, container, row, rule, scrollable, space, text, + tooltip, +}; use iced::{Element, Task}; use iced_core::Alignment::*; use iced_core::Length::{Fill, FillPortion}; @@ -46,7 +53,11 @@ impl NavigatedPage for ExamsState { if let Some(selected_exam_id) = self.selected_exam_id && GotoExam == *message { - return Some(Page::Exam(ExamState::new(selected_exam_id.id, self.deck.clone(), &self.state))); + return Some(Page::Exam(ExamState::new( + selected_exam_id.id, + self.deck.clone(), + &self.state, + ))); } None } @@ -90,7 +101,7 @@ impl NavigatedPage for ExamsState { back_overlay( column![ text(format!("Экзамены для колоды {}", self.deck.name)), - row![row![ + row![ column![ scrollable(self.exams_list()).height(Fill), jl_button!("Добавить").width(Fill).on_press(CreateExam), @@ -98,7 +109,7 @@ impl NavigatedPage for ExamsState { .spacing(DEFAULT_SPACING) .width(FillPortion(1)), self.selected_exam_view(), - ]] + ].spacing(DEFAULT_SPACING) ] .into(), Back, @@ -172,23 +183,33 @@ impl ExamsState { for (handle, deck) in self.exams.iter() { column = column.push( - jl_button!(text!("{}", &deck.timestamp.with_timezone(&Local).format("%d/%m/%Y %H:%M"))) - .on_press_with(move || SelectExam(*handle)) - .style(move |_x: &Theme, status: Status| Style { - background: if status == Status::Hovered { - Some(Background::Color(Color::WHITE.scale_alpha(0.2))) - } else { - None - }, - text_color: if deck.finished() { _x.palette().success } else { _x.palette().primary}, - border: Border { - color: Default::default(), - width: 0.0, - radius: BUTTON_RADIUS.into(), - }, - shadow: Shadow::default(), - snap: false, - }), + jl_button!(text!( + "{}", + &deck + .timestamp + .with_timezone(&Local) + .format("%d/%m/%Y %H:%M") + )) + .on_press_with(move || SelectExam(*handle)) + .style(move |_x: &Theme, status: Status| Style { + background: if status == Status::Hovered { + Some(Background::Color(Color::WHITE.scale_alpha(0.2))) + } else { + None + }, + text_color: if deck.finished() { + _x.palette().success + } else { + _x.palette().primary + }, + border: Border { + color: Default::default(), + width: 0.0, + radius: BUTTON_RADIUS.into(), + }, + shadow: Shadow::default(), + snap: false, + }), ); } @@ -199,7 +220,9 @@ impl ExamsState { return column![ center_x(text!( "Экзамен от {}", - exam.timestamp.with_timezone(&Local).format("%d/%m/%Y %H:%M") + exam.timestamp + .with_timezone(&Local) + .format("%d/%m/%Y %H:%M") )), row![text!("{} карточек", exam.total), Self::exam_bar(exam)] .height(30) @@ -284,7 +307,35 @@ impl ExamsState { } fn exam_finished_view(&self, exam: &ExamExtended) -> Element<'_, ExamsMessage> { - column![].spacing(DEFAULT_SPACING).into() + let result = (exam.answered as f64 / exam.total as f64 * 1000.0).round() / 10.0; + let words_repo = self.state.dictionary.read(); + column![ + center_x(text!("Итоговый результат: {}%", result).size(28)), + row![ + Self::words_list( + "Правильно", + exam.answered_list + .iter() + .map(|handle| words_repo.get_ref_static(*handle).unwrap()) + .collect::>() + .as_slice() + ) + .width(Fill), + Self::words_list( + "Не получилось", + exam.unknows_list + .iter() + .map(|handle| words_repo.get_ref_static(*handle).unwrap()) + .collect::>() + .as_slice() + ) + .width(Fill) + ] + .width(Fill) + .spacing(QUARTER_SPACING) + ] + .spacing(DEFAULT_SPACING) + .into() } fn exam_ongoing_view(&self, exam: &ExamExtended) -> Element<'_, ExamsMessage> { @@ -295,6 +346,26 @@ impl ExamsState { ) .into() } + + fn words_list<'a>(name: &str, words: &[&WordData]) -> Column<'a, ExamsMessage> { + let mut column = column![]; + for word in words { + let container = container( + column![text!("{}", word.key), text!("{}", word.value)].padding(QUARTER_SPACING), + ) + .width(Fill) + .style(bordered_box); + column = column.push(container); + } + column![ + text!("{}", name), + scrollable(row![ + column.spacing(QUARTER_SPACING), + horizontal().width(DEFAULT_SPACING) + ]) + ] + .align_x(Center) + } } struct ExamExtended { @@ -302,6 +373,8 @@ struct ExamExtended { total: u32, answered: u32, unknown: u32, + answered_list: Vec>, + unknows_list: Vec>, } impl ExamExtended { @@ -310,6 +383,8 @@ impl ExamExtended { let mut total = 0; let mut answered = 0; let mut unknown = 0; + let mut answered_list = Vec::with_capacity(128); + let mut unknows_list = Vec::with_capacity(128); entries .iter() .filter(|e| e.exam_id == exam.id) @@ -320,8 +395,10 @@ impl ExamExtended { Some(s) => { if s { answered += 1; + answered_list.push(EntityHandle::from_id(e.word_id)); } else { unknown += 1; + unknows_list.push(EntityHandle::from_id(e.word_id)); } } } @@ -329,10 +406,11 @@ impl ExamExtended { Self { exam, - total, answered, unknown, + answered_list, + unknows_list, } } fn solved(&self) -> u32 {