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

voiceapiai / ralertsinua / 9362326829

04 Jun 2024 06:30AM UTC coverage: 33.685% (+2.5%) from 31.191%
9362326829

Pull #15

github

web-flow
Merge bcf710e7c into 3b8c3f1fb
Pull Request #15: chore: linux musl build (cross-compile improvements)

98 of 162 new or added lines in 8 files covered. (60.49%)

6 existing lines in 3 files now uncovered.

702 of 2084 relevant lines covered (33.69%)

4.58 hits per line

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

0.0
/src/components/map.rs
1
use geo::Rect as GeoRect;
2
use ralertsinua_geo::*;
3
use ralertsinua_models::*;
4
use ratatui::widgets::canvas::Canvas;
5
use ratatui::{prelude::*, widgets::*};
6
use rust_i18n::t;
7
use std::fmt::Debug;
8
use tokio::sync::mpsc::UnboundedSender;
9
#[allow(unused)]
10
use tracing::debug;
11
// use tui_popup::Popup;
12

13
use super::{Component, Frame, Result, WithPlacement};
14
use crate::{action::*, config::*, layout::*, tui_helpers::*};
15

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

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

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

65
    #[inline]
66
    pub fn get_location_by<P>(&self, mut predicate: P) -> Option<Location>
×
67
    where
×
68
        P: FnMut(&Location) -> bool,
×
69
    {
×
70
        self.locations.iter().find(|r| predicate(r)).cloned()
×
71
    }
×
72

73
    #[inline]
74
    pub fn get_selected_location(&self) -> Option<Location> {
×
75
        self.get_location_by(|l| l.location_uid == self.selected_location_uid)
×
76
    }
×
77

78
    #[inline]
79
    pub fn get_selected_alert_status(&self) -> Option<AirRaidAlertOblastStatus> {
×
80
        self.oblast_statuses
×
81
            .get_by_location_uid(self.selected_location_uid)
×
82
    }
×
83

84
    #[inline]
85
    pub fn get_selected_alert(&self) -> Option<Alert> {
×
86
        self.alerts
×
87
            .get_alerts_by_location_uid(self.selected_location_uid)
×
88
            .first()
×
89
            .cloned()
×
90
    }
×
91
}
92

93
impl WithPlacement<'_> for Map<'_> {
94
    #[inline]
95
    fn placement(&self) -> &LayoutPoint {
×
96
        &self.placement
×
97
    }
×
98
}
99

100
impl WithBoundingRect for Map<'_> {
101
    #[inline]
102
    fn bounding_rect(&self) -> geo::Rect {
×
103
        self.bounding_rect
×
104
    }
×
105
}
106

107
impl<'a> Component<'a> for Map<'a> {
108
    fn init(&mut self, r: Rect) -> Result<()> {
×
109
        self.set_grid_size(r.width, r.height);
×
110
        Ok(())
×
111
    }
×
112

113
    fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
×
114
        self.command_tx = Some(tx);
×
115
        Ok(())
×
116
    }
×
117

118
    fn register_config_handler(&mut self, config: Config) -> Result<()> {
×
119
        self.config = config;
×
120
        Ok(())
×
121
    }
×
122

123
    fn update(&mut self, action: Action) -> Result<Option<Action>> {
×
124
        match action {
×
125
            Action::Tick => {}
×
126
            Action::Resize(width, heith) => self.set_grid_size(width, heith),
×
127
            Action::GetBoundaries(boundary) => {
×
128
                self.boundary = boundary;
×
129
            }
×
130
            Action::GetLocations(locations) => {
×
131
                self.locations = locations;
×
132
            }
×
133
            Action::GetAirRaidAlertOblastStatuses(data) => {
×
134
                self.oblast_statuses = data;
×
135
            }
×
136
            Action::GetActiveAlerts(data) => {
×
137
                self.alerts = data;
×
138
            }
×
139
            Action::SelectLocationByUid(a) => match a {
×
140
                Some(location_uid) => {
×
141
                    self.selected_location_uid = location_uid as i32;
×
142
                    debug!(target:"app", "Map: selected_location_uid: {}", location_uid);
×
143
                }
144
                None => {
×
145
                    self.selected_location_uid = -1;
×
146
                }
×
147
            },
148
            Action::Online(online) => {
×
149
                self.title = get_title_with_online_status(
×
150
                    t!("views.Map.title"),
×
151
                    self.config.online(),
×
152
                )
×
153
                .alignment(Alignment::Left);
×
154
            }
×
155
            _ => {}
×
156
        }
157
        Ok(None)
×
158
    }
×
159

160
    fn draw(&mut self, f: &mut Frame) -> Result<()> {
×
161
        let size: Rect = f.size();
×
162
        let area: Rect = self.get_area(size)?;
×
163
        let (x_bounds, y_bounds) = self.get_x_y_bounds();
×
164
        let selected_location = self.get_selected_location();
×
165
        let selected_alert_status = self.get_selected_alert_status();
×
166
        let selected_alert = self.get_selected_alert();
×
167
        let title = self.title.clone();
×
168
        let widget = Canvas::default()
×
169
            .block(Block::bordered().title(title))
×
170
            .marker(Marker::Braille)
×
171
            .x_bounds(x_bounds)
×
172
            .y_bounds(y_bounds)
×
173
            .paint(move |ctx| {
×
174
                //  Draw country borders with ctx
×
175
                ctx.draw(&self.boundary);
×
176

×
177
                // Draw & Print selected location with ctx
×
178
                self.locations.iter().for_each(|l| {
×
179
                    // Draw location
×
180
                    ctx.draw(l);
×
181
                    // Print location name
×
182
                    let (x, y) = l.center();
×
183
                    let text = l
×
184
                        .get_name_by_locale(self.config.get_locale())
×
185
                        .split(' ')
×
186
                        .next()
×
187
                        .unwrap_or("");
×
188
                    let status: &AlertStatus = self
×
189
                        .oblast_statuses
×
190
                        .iter()
×
191
                        .find(|&os| os.location_uid == l.location_uid)
×
192
                        .unwrap()
×
193
                        .status();
×
194
                    let is_selected = (l.location_uid) == self.selected_location_uid;
×
195
                    let line = get_styled_line_icon_by_status(status, &is_selected);
×
196
                    ctx.print(x, y, line);
×
197
                });
×
198
            })
×
199
            .background_color(Color::Reset);
×
200
        f.render_widget(widget, area);
×
201

×
202
        let popup_area = get_bottom_left_rect(area, 30, 20);
×
203
        let mut popup_bg = Color::Reset;
×
204
        let mut lines: Vec<Line> = vec![
×
205
            "No details".into(),
×
206
            "To view details, select a location on the map using Up/Down arrow keys".into(),
×
207
            "↓: move down".into(),
×
208
            "↑: move up".into(),
×
209
        ];
×
210

211
        // popup
212
        if let Some(sas) = selected_alert_status {
×
213
            popup_bg = get_color_by_status(sas.status());
×
214
            lines = vec![
×
215
                sas.location_title_en().to_string().into(),
×
216
                sas.status().to_string().into(),
×
217
            ];
×
218
            if let Some(sa) = selected_alert {
×
219
                let d = dur::Duration::from_std(sa.get_alert_duration());
×
220
                lines = vec![
×
221
                    sas.location_title_en().to_string().into(),
×
222
                    sa.alert_type.to_string().into(),
×
223
                    d.to_string().into(),
×
224
                    sa.notes.unwrap_or_default().into(),
×
225
                ];
×
226
            };
×
227
        };
×
NEW
228
        let paragraph = Paragraph::new(Text::from(lines)).white().left_aligned();
×
229
        let block = Block::bordered()
×
230
            .bg(popup_bg)
×
231
            .title("Alert Details:".white().bold().italic());
×
232
        f.render_widget(paragraph.block(block), popup_area);
×
233
        Ok(())
×
234
    }
×
235
}
236

237
/* #[cfg(test)]
238
mod tests {
239
    use super::*;
240
    use geo::HasDimensions;
241

242
    #[test]
243
    fn test_map_new() {
244
        let map = Map::new(Ukraine::new_arc(), Arc::new(Config::init().unwrap()));
245
        assert!(map.command_tx.is_none());
246
        assert!(!map.map.boundary().is_empty());
247
        assert!(map.ukraine.read().unwrap().locations().is_empty());
248
        // match map.boundary.try_from()
249
    }
250
} */
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