Skip to main content

recovery_ui/
network.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::button::{Button, ButtonMessages, ButtonOptions, ButtonShape, SceneBuilderButtonExt};
6use crate::constants::constants::{
7    ADD_NETWORK_BUTTON_COLOR, BACKGROUND_WHITE, ICONS_PATH, ICON_ADD, ICON_ADD_SIZE,
8    ICON_ARROW_BACK, ICON_ARROW_FORWARD, ICON_ARROW_SIZE, ICON_LOCK, ICON_LOCK_SIZE,
9    ICON_WIFI_FULL_SIGNAL, ICON_WIFI_MID_SIGNAL, ICON_WIFI_NO_SIGNAL, ICON_WIFI_SIZE,
10    ICON_WIFI_WEAK_SIGNAL, LEFT_MARGIN_SPACE, TEXT_COLOR, TEXT_FONT_SIZE, THIN_LINE_COLOR,
11    TITLE_COLOR, TITLE_SMALL_FONT_SIZE,
12};
13use anyhow::Error;
14use carnelian::drawing::{load_font, FontFace};
15use carnelian::render::rive::load_rive;
16use carnelian::render::Context as RenderContext;
17use carnelian::scene::facets::{
18    RiveFacet, TextFacetOptions, TextHorizontalAlignment, TextVerticalAlignment,
19};
20use carnelian::scene::layout::{
21    Alignment, CrossAxisAlignment, Flex, FlexOptions, MainAxisAlignment, MainAxisSize,
22};
23use carnelian::scene::scene::{Scene, SceneBuilder};
24use carnelian::{
25    input, make_message, AppSender, Message, MessageTarget, Point, Size, ViewAssistant,
26    ViewAssistantContext, ViewKey,
27};
28use euclid::{size2, Size2D, UnknownUnit};
29use fidl_fuchsia_wlan_policy::SecurityType;
30use recovery_util::ota::state_machine::Event;
31use recovery_util::wlan::NetworkInfo;
32use rive_rs::File;
33use std::path::PathBuf;
34
35const ADD_WIFI_NETWORK: &str = "  Add Wi-Fi network";
36const ARROW_BUTTON_SIZE: Size2D<f32, UnknownUnit> = size2(375.0, 50.0);
37const FONT_PATH: &str = "/pkg/data/fonts/Roboto-Regular.ttf";
38
39pub struct SceneDetails {
40    pub(crate) scene: Scene,
41    buttons: Vec<Button>,
42}
43
44pub struct NetworkViewAssistant {
45    app_sender: AppSender,
46    view_key: ViewKey,
47    scene_details: Option<SceneDetails>,
48    icon_file: Option<File>,
49    font_face: FontFace,
50    networks: Vec<NetworkInfo>,
51    first_network: i16,
52}
53
54impl NetworkViewAssistant {
55    pub fn new(
56        app_sender: AppSender,
57        view_key: ViewKey,
58        networks: Vec<NetworkInfo>,
59    ) -> Result<NetworkViewAssistant, Error> {
60        let icon_file = load_rive(ICONS_PATH).ok();
61        let font_face = load_font(PathBuf::from(FONT_PATH)).expect("Font");
62        Ok(NetworkViewAssistant {
63            app_sender: app_sender,
64            scene_details: None,
65            view_key,
66            icon_file,
67            font_face,
68            networks,
69            first_network: 0,
70        })
71    }
72
73    fn button_press(&mut self, text: &str) {
74        #[cfg(feature = "debug_logging")]
75        println!("====== Button pressed {}", text);
76        match text {
77            "<" => self.show_networks(-3),
78            ">" => self.show_networks(3),
79            ADD_WIFI_NETWORK => self
80                .app_sender
81                .queue_message(MessageTarget::View(self.view_key), make_message(Event::AddNetwork)),
82            network => {
83                let event = if self.is_secure(network) {
84                    Event::UserInput(network.to_string())
85                } else {
86                    Event::UserInputUnsecuredNetwork(network.to_string())
87                };
88                self.app_sender
89                    .queue_message(MessageTarget::View(self.view_key), make_message(event))
90            }
91        }
92    }
93
94    fn is_secure(&self, network: &str) -> bool {
95        for network_info in &self.networks {
96            if &network_info.ssid == network {
97                return network_info.security_type != SecurityType::None;
98            }
99        }
100        eprintln!("Should not happen: Checking security of an unknown network {}", network);
101        false
102    }
103
104    fn show_networks(&mut self, adjust_by: i16) {
105        // Adjust first_network between the start (0) and three before the end
106        let length = self.networks.len() as i16;
107        self.first_network = num_traits::clamp(self.first_network + adjust_by, 0, length - 3);
108        self.scene_details = None;
109    }
110
111    fn network_button(
112        &self,
113        builder: &mut SceneBuilder,
114        network: &NetworkInfo,
115        width: f32,
116    ) -> Button {
117        let network_name = network.ssid.as_str();
118        builder.start_group(
119            "button_row",
120            Flex::with_options_ptr(FlexOptions::row(
121                MainAxisSize::Max,
122                MainAxisAlignment::Start,
123                CrossAxisAlignment::Center,
124            )),
125        );
126        let wifi_icon_name = match network.rssi {
127            -59..=0 => ICON_WIFI_FULL_SIGNAL,
128            -67..=-60 => ICON_WIFI_MID_SIGNAL,
129            -79..=-68 => ICON_WIFI_WEAK_SIGNAL,
130            _ => ICON_WIFI_NO_SIGNAL,
131        };
132        builder.add_facet(self.get_facet(Some(wifi_icon_name), ICON_WIFI_SIZE));
133        builder.space(size2(10.0, 1.0));
134        let button = builder.button(
135            network_name,
136            None,
137            ButtonOptions {
138                padding: 20.0,
139                shape: ButtonShape::Square,
140                bg_fg_swapped: true,
141                bg_size: Some(size2(width - 175.0, 50.0)),
142                ..ButtonOptions::default()
143            },
144        );
145        if network.security_type != SecurityType::None {
146            builder.add_facet(self.get_facet(Some(ICON_LOCK), ICON_LOCK_SIZE));
147        }
148        builder.end_group(); // button_row
149        button
150    }
151
152    fn add_line_below(&self, builder: &mut SceneBuilder, width: f32) {
153        let line_size = size2(width, 1.0);
154        builder.space(size2(10.0, 15.0));
155        builder.rectangle(line_size, THIN_LINE_COLOR);
156        builder.space(size2(10.0, 10.0));
157    }
158
159    fn network_scene(&mut self, context: &ViewAssistantContext) -> SceneDetails {
160        let target_size = context.size;
161        let mut builder = SceneBuilder::new().background_color(BACKGROUND_WHITE);
162        let mut buttons = Vec::new();
163        builder.group().column().max_size().main_align(MainAxisAlignment::Start).contents(
164            |builder| {
165                builder.start_group(
166                    "left row for margin space",
167                    Flex::with_options_ptr(FlexOptions::row(
168                        MainAxisSize::Min,
169                        MainAxisAlignment::Start,
170                        CrossAxisAlignment::Start,
171                    )),
172                );
173                builder.space(size2(LEFT_MARGIN_SPACE, 10.0));
174                builder.start_group(
175                    "main column",
176                    Flex::with_options_ptr(FlexOptions::column(
177                        MainAxisSize::Min,
178                        MainAxisAlignment::Start,
179                        CrossAxisAlignment::Start,
180                    )),
181                );
182                builder.space(size2(10.0, 50.0));
183                builder.text(
184                    self.font_face.clone(),
185                    "Connect to Wi-Fi to reinstall software",
186                    TITLE_SMALL_FONT_SIZE,
187                    Point::zero(),
188                    TextFacetOptions {
189                        horizontal_alignment: TextHorizontalAlignment::Left,
190                        vertical_alignment: TextVerticalAlignment::Center,
191                        color: TITLE_COLOR,
192                        ..TextFacetOptions::default()
193                    },
194                );
195                builder.space(size2(1.0, 20.0));
196                builder.start_row("status row", MainAxisAlignment::Start);
197                let text = if self.networks.len() == 0 {
198                    "Searching for networks"
199                } else {
200                    "Available networks"
201                };
202                builder.text(
203                    self.font_face.clone(),
204                    text,
205                    TEXT_FONT_SIZE,
206                    Point::zero(),
207                    TextFacetOptions {
208                        horizontal_alignment: TextHorizontalAlignment::Left,
209                        vertical_alignment: TextVerticalAlignment::Center,
210                        color: TEXT_COLOR,
211                        ..TextFacetOptions::default()
212                    },
213                );
214                builder.end_row(); // status row
215                builder.space(size2(1.0, 10.0));
216                for i in self.first_network as usize..(self.first_network + 3) as usize {
217                    if i < self.networks.len() {
218                        builder.space(size2(10.0, 10.0));
219                        buttons.push(self.network_button(
220                            builder,
221                            &self.networks[i],
222                            target_size.width,
223                        ));
224                        self.add_line_below(builder, target_size.width - 2.0 * LEFT_MARGIN_SPACE);
225                    }
226                }
227                if self.networks.len() > 3 {
228                    builder.start_row("control row", MainAxisAlignment::Start);
229                    buttons.push(builder.button(
230                        "<",
231                        self.get_facet(Some(ICON_ARROW_BACK), ICON_ARROW_SIZE),
232                        ButtonOptions {
233                            padding: 0.0,
234                            hide_text: true,
235                            bg_fg_swapped: true,
236                            bg_size: Some(ARROW_BUTTON_SIZE),
237                            ..ButtonOptions::default()
238                        },
239                    ));
240                    builder.text(
241                        self.font_face.clone(),
242                        "More networks",
243                        TEXT_FONT_SIZE,
244                        Point::zero(),
245                        TextFacetOptions {
246                            horizontal_alignment: TextHorizontalAlignment::Left,
247                            vertical_alignment: TextVerticalAlignment::Center,
248                            color: TEXT_COLOR,
249                            ..TextFacetOptions::default()
250                        },
251                    );
252                    buttons.push(builder.button(
253                        ">",
254                        self.get_facet(Some(ICON_ARROW_FORWARD), ICON_ARROW_SIZE),
255                        ButtonOptions {
256                            padding: 0.0,
257                            hide_text: true,
258                            bg_fg_swapped: true,
259                            bg_size: Some(ARROW_BUTTON_SIZE),
260                            text_alignment: Some(Alignment::center_right()),
261                            ..ButtonOptions::default()
262                        },
263                    ));
264                    builder.end_row(); // control row
265                    self.add_line_below(builder, target_size.width - 2.0 * LEFT_MARGIN_SPACE);
266                }
267                builder.space(size2(1.0, 20.0));
268                builder.start_row("add network row", MainAxisAlignment::Start);
269                buttons.push(builder.button(
270                    ADD_WIFI_NETWORK,
271                    self.get_facet(Some(ICON_ADD), ICON_ADD_SIZE),
272                    ButtonOptions {
273                        padding: 0.0,
274                        shape: ButtonShape::Square,
275                        bg_fg_swapped: true,
276                        bg_color: ADD_NETWORK_BUTTON_COLOR,
277                        text_alignment: Some(Alignment::center_left()),
278                        ..ButtonOptions::default()
279                    },
280                ));
281                builder.end_row(); // add network row
282                builder.end_group(); // main column
283            },
284        );
285        let mut scene = builder.build();
286        scene.layout(target_size);
287        for button in &mut buttons {
288            button.set_focused(&mut scene, true);
289        }
290        SceneDetails { scene, buttons }
291    }
292
293    fn get_facet(
294        &self,
295        artboard_name: Option<&'static str>,
296        logo_size: Size2D<f32, UnknownUnit>,
297    ) -> Option<RiveFacet> {
298        if artboard_name.is_none() {
299            return None;
300        }
301        if let Some(icon_file) = &self.icon_file {
302            Some(
303                RiveFacet::new_from_file(logo_size, icon_file, artboard_name)
304                    .expect("facet_from_file"),
305            )
306        } else {
307            None
308        }
309    }
310}
311
312pub trait SceneBuilderExtentions {
313    fn add_facet(&mut self, facet: Option<RiveFacet>);
314    fn start_row(&mut self, label: &str, main_align: MainAxisAlignment);
315    fn end_row(&mut self);
316}
317
318impl SceneBuilderExtentions for SceneBuilder {
319    fn add_facet(&mut self, facet: Option<RiveFacet>) {
320        if let Some(facet) = facet {
321            self.facet(Box::new(facet));
322        }
323    }
324    fn start_row(&mut self, label: &str, main_align: MainAxisAlignment) {
325        self.start_group(
326            label,
327            Flex::with_options_ptr(FlexOptions::row(
328                MainAxisSize::Max,
329                main_align,
330                CrossAxisAlignment::Center,
331            )),
332        );
333    }
334    fn end_row(&mut self) {
335        self.end_group();
336    }
337}
338
339impl ViewAssistant for NetworkViewAssistant {
340    fn resize(&mut self, _new_size: &Size) -> Result<(), Error> {
341        self.scene_details = None;
342        Ok(())
343    }
344
345    fn render(
346        &mut self,
347        render_context: &mut RenderContext,
348        ready_event: zx::Event,
349        context: &ViewAssistantContext,
350    ) -> Result<(), Error> {
351        let mut scene_details =
352            self.scene_details.take().unwrap_or_else(|| self.network_scene(context));
353        scene_details.scene.render(render_context, ready_event, context)?;
354        self.scene_details = Some(scene_details);
355        context.request_render();
356        Ok(())
357    }
358
359    fn handle_pointer_event(
360        &mut self,
361        context: &mut ViewAssistantContext,
362        _event: &input::Event,
363        pointer_event: &input::pointer::Event,
364    ) -> Result<(), Error> {
365        if let Some(scene_details) = self.scene_details.as_mut() {
366            for button in scene_details.buttons.iter_mut() {
367                button.handle_pointer_event(&mut scene_details.scene, context, &pointer_event);
368            }
369        }
370        Ok(())
371    }
372
373    fn handle_message(&mut self, message: Message) {
374        if let Some(button_message) = message.downcast_ref::<ButtonMessages>() {
375            match button_message {
376                ButtonMessages::Pressed(_time, button_text) => {
377                    self.button_press(button_text);
378                }
379            }
380        }
381    }
382}