1use qframe::prelude::*;
4use qframe::widgets::{Field as FormFieldWidget, Form, Modal, TextInput};
5
6use super::Msg;
7use crate::config::Provider;
8use crate::provider::{Key, KeyError};
9
10pub const FIRST: &str = "provider-address";
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum Field {
16 Address,
18 Model,
20 KeyFile,
22 KeyHeader,
24}
25
26impl Field {
27 const ALL: [Self; 4] = [Self::Address, Self::Model, Self::KeyFile, Self::KeyHeader];
28
29 fn id(self) -> &'static str {
30 match self {
31 Self::Address => FIRST,
32 Self::Model => "provider-model",
33 Self::KeyFile => "provider-key-file",
34 Self::KeyHeader => "provider-key-header",
35 }
36 }
37
38 fn label(self) -> String {
39 match self {
40 Self::Address => t!("provider.address"),
41 Self::Model => t!("provider.model"),
42 Self::KeyFile => t!("provider.key-file"),
43 Self::KeyHeader => t!("provider.key-header"),
44 }
45 }
46
47 fn hint(self) -> String {
48 match self {
49 Self::Address => t!("provider.address-hint"),
50 Self::Model => t!("provider.model-hint"),
51 Self::KeyFile => t!("provider.key-file-hint"),
52 Self::KeyHeader => t!("provider.key-header-hint"),
53 }
54 }
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
59enum Problem {
60 Empty,
62 Key(KeyError),
64 Unsaved(String),
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct ProviderForm {
71 provider: Provider,
72 problems: Vec<(Field, Problem)>,
73}
74
75impl ProviderForm {
76 #[must_use]
78 pub fn from_provider(provider: &Provider) -> Self {
79 Self { provider: provider.clone(), problems: Vec::new() }
80 }
81
82 pub fn set(&mut self, field: Field, value: String) {
84 match field {
85 Field::Address => self.provider.address = value,
86 Field::Model => self.provider.model = value,
87 Field::KeyFile => self.provider.key_file = value,
88 Field::KeyHeader => self.provider.key_header = value,
89 }
90 }
91
92 fn value(&self, field: Field) -> &str {
93 match field {
94 Field::Address => &self.provider.address,
95 Field::Model => &self.provider.model,
96 Field::KeyFile => &self.provider.key_file,
97 Field::KeyHeader => &self.provider.key_header,
98 }
99 }
100
101 pub fn check(&mut self) -> Option<Provider> {
104 let provider = Provider {
105 address: self.provider.address.trim().to_owned(),
106 model: self.provider.model.trim().to_owned(),
107 key_file: self.provider.key_file.trim().to_owned(),
108 key_header: self.provider.key_header.trim().to_owned(),
109 };
110 self.problems = [Field::Address, Field::Model, Field::KeyFile]
111 .into_iter()
112 .filter(|field| {
113 let value = match field {
114 Field::Address => &provider.address,
115 Field::Model => &provider.model,
116 _ => &provider.key_file,
117 };
118 value.is_empty()
119 })
120 .map(|field| (field, Problem::Empty))
121 .collect();
122 if !provider.key_file.is_empty()
123 && let Err(error) = Key::from_file(&provider.key_path())
124 {
125 self.problems.push((Field::KeyFile, Problem::Key(error)));
126 }
127 self.problems.is_empty().then_some(provider)
128 }
129
130 #[must_use]
132 pub fn first_problem(&self) -> Option<&'static str> {
133 Field::ALL.into_iter().find(|field| self.problems.iter().any(|(with, _)| with == field)).map(Field::id)
134 }
135
136 pub fn failed(&mut self, error: &std::io::Error) {
138 self.problems = vec![(Field::Address, Problem::Unsaved(error.to_string()))];
139 }
140
141 fn problem(&self, field: Field) -> Option<String> {
142 self.problems.iter().find(|(with, _)| *with == field).map(|(_, problem)| match problem {
143 Problem::Empty => t!("provider.empty"),
144 Problem::Key(KeyError::Unreadable { path, reason }) => {
145 t!("provider.key-unreadable", path = path.display().to_string(), reason = reason.clone())
146 }
147 Problem::Key(KeyError::NotAKey { path }) => t!("provider.not-a-key", path = path.display().to_string()),
148 Problem::Unsaved(reason) => t!("provider.unsaved", reason = reason.clone()),
149 })
150 }
151
152 pub fn view(&self, ui: &mut View<'_, Msg>) {
154 let dialog = Modal::new()
155 .title(t!("provider.title"))
156 .width(72)
157 .on_close(Msg::CloseProvider)
158 .action(Button::new(t!("provider.cancel")).on_press(Msg::CloseProvider))
159 .action(Button::new(t!("provider.save")).variant("primary").on_press(Msg::SaveProvider));
160 ui.add_with(dialog, |ui| {
161 ui.add(Text::new(t!("provider.intro")).role("secondary"));
162 Form::new().show(ui, |fields| {
163 for field in Field::ALL {
164 let widget = FormFieldWidget::new(field.label())
165 .required(field != Field::KeyHeader)
166 .hint(field.hint())
167 .error(self.problem(field));
168 fields.field(widget, |ui| {
169 ui.add(
170 TextInput::new(self.value(field))
171 .invalid(self.problem(field).is_some())
172 .on_change(move |value| Msg::Form(field, value))
173 .on_submit(|_| Msg::SaveProvider),
174 )
175 .id(field.id())
176 .fill_width();
177 });
178 }
179 });
180 });
181 }
182}