• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

voiceapiai / ralertsinua / 9051836966

12 May 2024 01:42PM UTC coverage: 29.893%. First build
9051836966

push

github

web-flow
feat: location rendering and improvements (#8)

The project updates focus on enhancing code quality, expanding functionality, refining data structures and drawing on Canvas functionality. Notable changes include integrating new benchmarking capabilities, refining geographic data handling, and updating API endpoints to reflect these changes. The adjustments in serialization and error handling across various modules aim to improve performance and maintainability.
Finally, this update draws each oblast's borders & prints icons using Context in Map component

74 of 551 new or added lines in 22 files covered. (13.43%)

530 of 1773 relevant lines covered (29.89%)

5.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/components/map.rs
1
// use cached::proc_macro::cached;
2
use color_eyre::eyre::Result;
3
use geo::Rect as GeoRect;
4
use ralertsinua_geo::*;
5
use ralertsinua_models::{AirRaidAlertOblastStatuses, AlertStatus};
6
use ratatui::{
7
    prelude::*,
8
    widgets::{
9
        canvas::{Canvas, Context},
10
        *,
11
    },
12
};
13
use rust_i18n::t;
14
use tokio::sync::mpsc::UnboundedSender;
15
#[allow(unused)]
16
use tracing::debug;
17

18
use super::{Component, Frame, WithPlacement};
19
use crate::{action::*, config::*, draw::*, layout::*};
20

21
#[derive(Debug)]
22
pub struct Map<'a> {
23
    command_tx: Option<UnboundedSender<Action>>,
24
    placement: LayoutPoint,
25
    #[allow(unused)]
26
    title: Line<'a>,
27
    #[allow(unused)]
28
    config: Config,
29
    bounding_rect: GeoRect,
30
    boundary: CountryBoundary,
31
    locations: [Location; 27],
32
    selected_location_uid: i32,
33
    oblast_statuses: AirRaidAlertOblastStatuses,
34
    //
35
    width: u16,
36
    height: u16,
37
    resolution: (f64, f64),
38
}
39

40
impl<'a> Map<'a> {
41
    #[inline]
NEW
42
    pub fn new() -> Self {
×
NEW
43
        let context = Context::new(0, 0, [0.0, 0.0], [0.0, 0.0], Marker::Braille);
×
44
        Self {
×
45
            command_tx: Option::default(),
×
NEW
46
            placement: LayoutPoint(LayoutArea::Left, Some(LayoutTab::Tab1)),
×
NEW
47
            title: Line::default(),
×
NEW
48
            config: Config::default(),
×
NEW
49
            boundary: CountryBoundary::default(),
×
NEW
50
            bounding_rect: *UKRAINE_BBOX,
×
NEW
51
            locations: core::array::from_fn(|_| Location::default()),
×
NEW
52
            selected_location_uid: -1,
×
NEW
53
            oblast_statuses: AirRaidAlertOblastStatuses::default(),
×
NEW
54
            //
×
NEW
55
            width: 0,
×
NEW
56
            height: 0,
×
NEW
57
            resolution: (0.0, 0.0),
×
58
        }
×
59
    }
×
60

61
    #[inline]
NEW
62
    pub fn set_grid_size(&mut self, width: u16, height: u16) {
×
NEW
63
        self.width = width;
×
NEW
64
        self.height = height;
×
NEW
65
        self.resolution = (f64::from(width) * 2.0, f64::from(height) * 4.0);
×
NEW
66
        debug!(target:"app", "Map grid size: width: {}, height: {}, x_Y_bounds: {:?}, resolution: {:?}", width, height, self.get_x_y_bounds(), self.resolution);
×
NEW
67
    }
×
68

69
    #[inline]
NEW
70
    pub fn get_location_by<P>(&self, mut predicate: P) -> Option<Location>
×
NEW
71
    where
×
NEW
72
        P: FnMut(&Location) -> bool,
×
NEW
73
    {
×
NEW
74
        self.locations.iter().find(|r| predicate(r)).cloned()
×
75
    }
×
76
}
77

78
impl WithPlacement for Map<'_> {
79
    #[inline]
NEW
80
    fn placement(&self) -> &LayoutPoint {
×
NEW
81
        &self.placement
×
82
    }
×
83
}
84

85
impl WithBoundingRect for Map<'_> {
86
    #[inline]
NEW
87
    fn bounding_rect(&self) -> geo::Rect {
×
NEW
88
        self.bounding_rect
×
89
    }
×
90
}
91

92
impl<'a> WithLineItems for Map<'a> {}
93

94
impl<'a> Component<'a> for Map<'a> {
NEW
95
    fn init(&mut self, r: Rect) -> Result<()> {
×
NEW
96
        self.set_grid_size(r.width, r.height);
×
NEW
97
        Ok(())
×
98
    }
×
99

100
    fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
×
101
        self.command_tx = Some(tx);
×
102
        Ok(())
×
103
    }
×
104

105
    fn update(&mut self, action: Action) -> Result<Option<Action>> {
×
106
        match action {
×
107
            Action::Tick => {}
×
NEW
108
            Action::Resize(width, heith) => self.set_grid_size(width, heith),
×
NEW
109
            Action::GetBoundaries(boundary) => {
×
NEW
110
                self.boundary = boundary;
×
NEW
111
            }
×
NEW
112
            Action::GetLocations(locations) => {
×
NEW
113
                self.locations = locations;
×
NEW
114
            }
×
NEW
115
            Action::GetAirRaidAlertOblastStatuses(data) => {
×
NEW
116
                self.oblast_statuses = data;
×
NEW
117
            }
×
NEW
118
            Action::SelectLocationByUid(a) => match a {
×
NEW
119
                Some(location_uid) => {
×
NEW
120
                    self.selected_location_uid = location_uid as i32;
×
NEW
121
                    debug!(target:"app", "Map: selected_location_uid: {}", location_uid);
×
122
                }
123
                None => {
×
NEW
124
                    self.selected_location_uid = -1;
×
125
                }
×
126
            },
127
            _ => {}
×
128
        }
129
        Ok(None)
×
130
    }
×
131

NEW
132
    fn draw(&mut self, f: &mut Frame) -> Result<()> {
×
NEW
133
        let size: Rect = f.size();
×
NEW
134
        let area: Rect = self.get_area(size)?;
×
NEW
135
        let (x_bounds, y_bounds) = self.get_x_y_bounds();
×
136

×
137
        let widget = Canvas::default()
×
138
            .block(
×
139
                Block::default()
×
140
                    .borders(Borders::ALL)
×
141
                    .title(t!("views.Map.title").to_string().light_blue())
×
142
                    .title_alignment(Alignment::Center),
×
143
            )
×
144
            .marker(Marker::Braille)
×
145
            .x_bounds(x_bounds)
×
146
            .y_bounds(y_bounds)
×
NEW
147
            .paint(move |ctx| {
×
NEW
148
                //  Draw country borders with ctx
×
NEW
149
                ctx.draw(&self.boundary);
×
NEW
150

×
NEW
151
                // Draw & Print selected location with ctx
×
NEW
152
                self.locations.iter().for_each(|l| {
×
NEW
153
                    // Draw location
×
NEW
154
                    ctx.draw(l);
×
NEW
155
                    // Print location name
×
NEW
156
                    let (x, y) = l.center();
×
NEW
157
                    let text = l
×
NEW
158
                        .get_name_by_locale(self.config.get_locale())
×
NEW
159
                        .split(' ')
×
NEW
160
                        .next()
×
NEW
161
                        .unwrap_or("");
×
NEW
162
                    let status: &AlertStatus = self
×
NEW
163
                        .oblast_statuses
×
NEW
164
                        .iter()
×
NEW
165
                        .find(|&os| os.location_uid == l.location_uid)
×
NEW
166
                        .unwrap()
×
NEW
167
                        .status();
×
NEW
168
                    let is_selected = (l.location_uid) == self.selected_location_uid;
×
NEW
169
                    let line = Self::get_styled_line_icon_by_status(status, is_selected);
×
NEW
170
                    ctx.print(x, y, line);
×
NEW
171
                });
×
172
            })
×
173
            .background_color(Color::Reset);
×
174
        f.render_widget(widget, area);
×
175
        Ok(())
×
176
    }
×
177
}
178

179
/* #[cfg(test)]
180
mod tests {
181
    use super::*;
182
    use geo::HasDimensions;
183

184
    #[test]
185
    fn test_map_new() {
186
        let map = Map::new(Ukraine::new_arc(), Arc::new(Config::init().unwrap()));
187
        assert!(map.command_tx.is_none());
188
        assert!(!map.map.boundary().is_empty());
189
        assert!(map.ukraine.read().unwrap().locations().is_empty());
190
        // match map.boundary.try_from()
191
    }
192
} */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc