blob: f26c0f1f20d5ace91bcf0d1b1b8d235e102eade6 [file] [edit]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::button::{Button, ButtonMessages, ButtonOptions, ButtonShape, SceneBuilderButtonExt};
use crate::constants::constants::{
ADD_NETWORK_BUTTON_COLOR, BACKGROUND_WHITE, ICONS_PATH, ICON_ADD, ICON_ADD_SIZE,
ICON_ARROW_BACK, ICON_ARROW_FORWARD, ICON_ARROW_SIZE, ICON_LOCK, ICON_LOCK_SIZE,
ICON_WIFI_FULL_SIGNAL, ICON_WIFI_MID_SIGNAL, ICON_WIFI_NO_SIGNAL, ICON_WIFI_SIZE,
ICON_WIFI_WEAK_SIGNAL, LEFT_MARGIN_SPACE, TEXT_COLOR, TEXT_FONT_SIZE, THIN_LINE_COLOR,
TITLE_COLOR, TITLE_SMALL_FONT_SIZE,
};
use anyhow::Error;
use carnelian::drawing::{load_font, FontFace};
use carnelian::render::rive::load_rive;
use carnelian::render::Context as RenderContext;
use carnelian::scene::facets::{
RiveFacet, TextFacetOptions, TextHorizontalAlignment, TextVerticalAlignment,
};
use carnelian::scene::layout::{
Alignment, CrossAxisAlignment, Flex, FlexOptions, MainAxisAlignment, MainAxisSize,
};
use carnelian::scene::scene::{Scene, SceneBuilder};
use carnelian::{
input, make_message, AppSender, Message, MessageTarget, Point, Size, ViewAssistant,
ViewAssistantContext, ViewKey,
};
use euclid::{size2, Size2D, UnknownUnit};
use fidl_fuchsia_wlan_policy::SecurityType;
use recovery_util::ota::state_machine::Event;
use recovery_util::wlan::NetworkInfo;
use rive_rs::File;
use std::path::PathBuf;
const ADD_WIFI_NETWORK: &str = " Add Wi-Fi network";
const ARROW_BUTTON_SIZE: Size2D<f32, UnknownUnit> = size2(375.0, 50.0);
const FONT_PATH: &str = "/pkg/data/fonts/Roboto-Regular.ttf";
pub struct SceneDetails {
pub(crate) scene: Scene,
buttons: Vec<Button>,
}
pub struct NetworkViewAssistant {
app_sender: AppSender,
view_key: ViewKey,
scene_details: Option<SceneDetails>,
icon_file: Option<File>,
font_face: FontFace,
networks: Vec<NetworkInfo>,
first_network: i16,
}
impl NetworkViewAssistant {
pub fn new(
app_sender: AppSender,
view_key: ViewKey,
networks: Vec<NetworkInfo>,
) -> Result<NetworkViewAssistant, Error> {
let icon_file = load_rive(ICONS_PATH).ok();
let font_face = load_font(PathBuf::from(FONT_PATH)).expect("Font");
Ok(NetworkViewAssistant {
app_sender: app_sender,
scene_details: None,
view_key,
icon_file,
font_face,
networks,
first_network: 0,
})
}
fn button_press(&mut self, text: &str) {
#[cfg(feature = "debug_logging")]
println!("====== Button pressed {}", text);
match text {
"<" => self.show_networks(-3),
">" => self.show_networks(3),
ADD_WIFI_NETWORK => self
.app_sender
.queue_message(MessageTarget::View(self.view_key), make_message(Event::AddNetwork)),
network => {
let event = if self.is_secure(network) {
Event::UserInput(network.to_string())
} else {
Event::UserInputUnsecuredNetwork(network.to_string())
};
self.app_sender
.queue_message(MessageTarget::View(self.view_key), make_message(event))
}
}
}
fn is_secure(&self, network: &str) -> bool {
for network_info in &self.networks {
if &network_info.ssid == network {
return network_info.security_type != SecurityType::None;
}
}
eprintln!("Should not happen: Checking security of an unknown network {}", network);
false
}
fn show_networks(&mut self, adjust_by: i16) {
// Adjust first_network between the start (0) and three before the end
let length = self.networks.len() as i16;
self.first_network = num_traits::clamp(self.first_network + adjust_by, 0, length - 3);
self.scene_details = None;
}
fn network_button(
&self,
builder: &mut SceneBuilder,
network: &NetworkInfo,
width: f32,
) -> Button {
let network_name = network.ssid.as_str();
builder.start_group(
"button_row",
Flex::with_options_ptr(FlexOptions::row(
MainAxisSize::Max,
MainAxisAlignment::Start,
CrossAxisAlignment::Center,
)),
);
let wifi_icon_name = match network.rssi {
-59..=0 => ICON_WIFI_FULL_SIGNAL,
-67..=-60 => ICON_WIFI_MID_SIGNAL,
-79..=-68 => ICON_WIFI_WEAK_SIGNAL,
_ => ICON_WIFI_NO_SIGNAL,
};
builder.add_facet(self.get_facet(Some(wifi_icon_name), ICON_WIFI_SIZE));
builder.space(size2(10.0, 1.0));
let button = builder.button(
network_name,
None,
ButtonOptions {
padding: 20.0,
shape: ButtonShape::Square,
bg_fg_swapped: true,
bg_size: Some(size2(width - 175.0, 50.0)),
..ButtonOptions::default()
},
);
if network.security_type != SecurityType::None {
builder.add_facet(self.get_facet(Some(ICON_LOCK), ICON_LOCK_SIZE));
}
builder.end_group(); // button_row
button
}
fn add_line_below(&self, builder: &mut SceneBuilder, width: f32) {
let line_size = size2(width, 1.0);
builder.space(size2(10.0, 15.0));
builder.rectangle(line_size, THIN_LINE_COLOR);
builder.space(size2(10.0, 10.0));
}
fn network_scene(&mut self, context: &ViewAssistantContext) -> SceneDetails {
let target_size = context.size;
let mut builder = SceneBuilder::new().background_color(BACKGROUND_WHITE);
let mut buttons = Vec::new();
builder.group().column().max_size().main_align(MainAxisAlignment::Start).contents(
|builder| {
builder.start_group(
"left row for margin space",
Flex::with_options_ptr(FlexOptions::row(
MainAxisSize::Min,
MainAxisAlignment::Start,
CrossAxisAlignment::Start,
)),
);
builder.space(size2(LEFT_MARGIN_SPACE, 10.0));
builder.start_group(
"main column",
Flex::with_options_ptr(FlexOptions::column(
MainAxisSize::Min,
MainAxisAlignment::Start,
CrossAxisAlignment::Start,
)),
);
builder.space(size2(10.0, 50.0));
builder.text(
self.font_face.clone(),
"Connect to Wi-Fi to reinstall software",
TITLE_SMALL_FONT_SIZE,
Point::zero(),
TextFacetOptions {
horizontal_alignment: TextHorizontalAlignment::Left,
vertical_alignment: TextVerticalAlignment::Center,
color: TITLE_COLOR,
..TextFacetOptions::default()
},
);
builder.space(size2(1.0, 20.0));
builder.start_row("status row", MainAxisAlignment::Start);
let text = if self.networks.len() == 0 {
"Searching for networks"
} else {
"Available networks"
};
builder.text(
self.font_face.clone(),
text,
TEXT_FONT_SIZE,
Point::zero(),
TextFacetOptions {
horizontal_alignment: TextHorizontalAlignment::Left,
vertical_alignment: TextVerticalAlignment::Center,
color: TEXT_COLOR,
..TextFacetOptions::default()
},
);
builder.end_row(); // status row
builder.space(size2(1.0, 10.0));
for i in self.first_network as usize..(self.first_network + 3) as usize {
if i < self.networks.len() {
builder.space(size2(10.0, 10.0));
buttons.push(self.network_button(
builder,
&self.networks[i],
target_size.width,
));
self.add_line_below(builder, target_size.width - 2.0 * LEFT_MARGIN_SPACE);
}
}
if self.networks.len() > 3 {
builder.start_row("control row", MainAxisAlignment::Start);
buttons.push(builder.button(
"<",
self.get_facet(Some(ICON_ARROW_BACK), ICON_ARROW_SIZE),
ButtonOptions {
padding: 0.0,
hide_text: true,
bg_fg_swapped: true,
bg_size: Some(ARROW_BUTTON_SIZE),
..ButtonOptions::default()
},
));
builder.text(
self.font_face.clone(),
"More networks",
TEXT_FONT_SIZE,
Point::zero(),
TextFacetOptions {
horizontal_alignment: TextHorizontalAlignment::Left,
vertical_alignment: TextVerticalAlignment::Center,
color: TEXT_COLOR,
..TextFacetOptions::default()
},
);
buttons.push(builder.button(
">",
self.get_facet(Some(ICON_ARROW_FORWARD), ICON_ARROW_SIZE),
ButtonOptions {
padding: 0.0,
hide_text: true,
bg_fg_swapped: true,
bg_size: Some(ARROW_BUTTON_SIZE),
text_alignment: Some(Alignment::center_right()),
..ButtonOptions::default()
},
));
builder.end_row(); // control row
self.add_line_below(builder, target_size.width - 2.0 * LEFT_MARGIN_SPACE);
}
builder.space(size2(1.0, 20.0));
builder.start_row("add network row", MainAxisAlignment::Start);
buttons.push(builder.button(
ADD_WIFI_NETWORK,
self.get_facet(Some(ICON_ADD), ICON_ADD_SIZE),
ButtonOptions {
padding: 0.0,
shape: ButtonShape::Square,
bg_fg_swapped: true,
bg_color: ADD_NETWORK_BUTTON_COLOR,
text_alignment: Some(Alignment::center_left()),
..ButtonOptions::default()
},
));
builder.end_row(); // add network row
builder.end_group(); // main column
},
);
let mut scene = builder.build();
scene.layout(target_size);
for button in &mut buttons {
button.set_focused(&mut scene, true);
}
SceneDetails { scene, buttons }
}
fn get_facet(
&self,
artboard_name: Option<&'static str>,
logo_size: Size2D<f32, UnknownUnit>,
) -> Option<RiveFacet> {
if artboard_name.is_none() {
return None;
}
if let Some(icon_file) = &self.icon_file {
Some(
RiveFacet::new_from_file(logo_size, icon_file, artboard_name)
.expect("facet_from_file"),
)
} else {
None
}
}
}
pub trait SceneBuilderExtentions {
fn add_facet(&mut self, facet: Option<RiveFacet>);
fn start_row(&mut self, label: &str, main_align: MainAxisAlignment);
fn end_row(&mut self);
}
impl SceneBuilderExtentions for SceneBuilder {
fn add_facet(&mut self, facet: Option<RiveFacet>) {
if let Some(facet) = facet {
self.facet(Box::new(facet));
}
}
fn start_row(&mut self, label: &str, main_align: MainAxisAlignment) {
self.start_group(
label,
Flex::with_options_ptr(FlexOptions::row(
MainAxisSize::Max,
main_align,
CrossAxisAlignment::Center,
)),
);
}
fn end_row(&mut self) {
self.end_group();
}
}
impl ViewAssistant for NetworkViewAssistant {
fn resize(&mut self, _new_size: &Size) -> Result<(), Error> {
self.scene_details = None;
Ok(())
}
fn render(
&mut self,
render_context: &mut RenderContext,
ready_event: zx::Event,
context: &ViewAssistantContext,
) -> Result<(), Error> {
let mut scene_details =
self.scene_details.take().unwrap_or_else(|| self.network_scene(context));
scene_details.scene.render(render_context, ready_event, context)?;
self.scene_details = Some(scene_details);
context.request_render();
Ok(())
}
fn handle_pointer_event(
&mut self,
context: &mut ViewAssistantContext,
_event: &input::Event,
pointer_event: &input::pointer::Event,
) -> Result<(), Error> {
if let Some(scene_details) = self.scene_details.as_mut() {
for button in scene_details.buttons.iter_mut() {
button.handle_pointer_event(&mut scene_details.scene, context, &pointer_event);
}
}
Ok(())
}
fn handle_message(&mut self, message: Message) {
if let Some(button_message) = message.downcast_ref::<ButtonMessages>() {
match button_message {
ButtonMessages::Pressed(_time, button_text) => {
self.button_press(button_text);
}
}
}
}
}