1use 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 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
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(); 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(); 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(); builder.end_group(); },
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}