Skip to main content

aidoku/structs/
setting.rs

1use serde::{Serialize, ser::SerializeStruct};
2
3extern crate alloc;
4use alloc::{borrow::Cow, string::String, vec::Vec};
5
6/// A setting that is shown in the source settings page.
7///
8/// This struct shouldn't be constructed directly. Instead, use the individual
9/// settings structs and call [into](Into::into).
10#[derive(Debug, Clone, PartialEq)]
11pub struct Setting {
12	pub key: Cow<'static, str>,
13	pub title: Cow<'static, str>,
14	pub notification: Option<Cow<'static, str>>,
15	pub requires: Option<Cow<'static, str>>,
16	pub requires_false: Option<Cow<'static, str>>,
17	pub refreshes: Option<Vec<Cow<'static, str>>>,
18	pub value: SettingValue,
19}
20
21impl Serialize for Setting {
22	fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
23	where
24		S: serde::Serializer,
25	{
26		let mut state = serializer.serialize_struct("Setting", 8)?;
27		state.serialize_field("type", &self.value.raw_value())?;
28		state.serialize_field("key", &self.key)?;
29		state.serialize_field("title", &self.title)?;
30		state.serialize_field("notification", &self.notification)?;
31		state.serialize_field("requires", &self.requires)?;
32		state.serialize_field("requires_false", &self.requires_false)?;
33		state.serialize_field("refreshes", &self.refreshes)?;
34		state.serialize_field("value", &self.value)?;
35		state.end()
36	}
37}
38
39/// A login method.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum LoginMethod {
42	/// Basic authentication with username and password.
43	Basic,
44	/// OAuth authentication.
45	OAuth,
46	/// Authentication via a web view.
47	Web,
48}
49
50impl Serialize for LoginMethod {
51	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
52	where
53		S: serde::Serializer,
54	{
55		match self {
56			Self::Basic => serializer.serialize_str("basic"),
57			Self::OAuth => serializer.serialize_str("oauth"),
58			Self::Web => serializer.serialize_str("web"),
59		}
60	}
61}
62
63/// The kind of setting.
64#[derive(Debug, Clone, PartialEq, Serialize)]
65pub enum SettingValue {
66	/// A group of settings.
67	Group {
68		/// Optional footer text for the group.
69		footer: Option<Cow<'static, str>>,
70		/// The settings contained in this group.
71		items: Vec<Setting>,
72	},
73	/// A page that allows selection of a single value.
74	Select {
75		/// The values of the options.
76		values: Vec<Cow<'static, str>>,
77		/// Optional display titles for the options. If not provided, the values will be used as titles.
78		titles: Option<Vec<Cow<'static, str>>>,
79		/// Whether to require authentication to open the page for this setting.
80		auth_to_open: Option<bool>,
81		/// The default selected value.
82		default: Option<String>,
83	},
84	/// A page that allows selection of multiple values.
85	MultiSelect {
86		/// The values of the options.
87		values: Vec<Cow<'static, str>>,
88		/// Optional display titles for the options. If not provided, the values will be used as titles.
89		titles: Option<Vec<Cow<'static, str>>>,
90		/// Whether to require authentication to open the page for this setting.
91		auth_to_open: Option<bool>,
92		/// The default selected value(s).
93		default: Option<Vec<String>>,
94	},
95	/// A toggle switch.
96	Toggle {
97		/// Optional subtitle text.
98		subtitle: Option<Cow<'static, str>>,
99		/// Whether to require authentication to turn the toggle off.
100		auth_to_disable: Option<bool>,
101		/// The default state of the toggle.
102		default: bool,
103	},
104	/// A numeric stepper control.
105	Stepper {
106		/// The minimum allowed value.
107		minimum_value: f64,
108		/// The maximum allowed value.
109		maximum_value: f64,
110		/// Optional step increment value.
111		step_value: Option<f64>,
112		/// The default value.
113		default: Option<f64>,
114	},
115	/// A segmented control.
116	Segment {
117		/// The options shown on the segments.
118		options: Vec<Cow<'static, str>>,
119		/// The default selected segment index.
120		default: Option<i32>,
121	},
122	/// A text input field.
123	Text {
124		/// Optional placeholder text when the field is empty.
125		placeholder: Option<Cow<'static, str>>,
126		/// The autocapitalization type.
127		autocapitalization_type: Option<i32>,
128		/// Whether autocorrection should be disabled.
129		autocorrection_disabled: Option<bool>,
130		/// The keyboard type.
131		keyboard_type: Option<i32>,
132		/// The return key type.
133		return_key_type: Option<i32>,
134		/// Whether the text field is for secure entry (password).
135		secure: Option<bool>,
136		/// The default text value.
137		default: Option<Cow<'static, str>>,
138	},
139	/// A clickable button.
140	Button,
141	/// A link to a URL.
142	Link {
143		/// The URL to open on press.
144		url: Cow<'static, str>,
145		/// Whether the link should open in an external browser.
146		external: Option<bool>,
147	},
148	/// A login control.
149	Login {
150		/// The authentication method to use.
151		method: LoginMethod,
152		/// The authentication URL.
153		url: Option<Cow<'static, str>>,
154		/// An optional defaults key to fetch the URL from.
155		url_key: Option<Cow<'static, str>>,
156		/// The title for the logout button. If not provided, the title will be "Log Out".
157		logout_title: Option<Cow<'static, str>>,
158		/// Whether to use PKCE for the OAuth flow.
159		pkce: bool,
160		/// The token URL for OAuth.
161		token_url: Option<Cow<'static, str>>,
162		/// The callback scheme for OAuth.
163		callback_scheme: Option<Cow<'static, str>>,
164		/// Whether to prompt for an email instead of username for basic authentication.
165		use_email: bool,
166		/// An array of localStorage keys to extract after login.
167		local_storage_keys: Option<Vec<String>>,
168	},
169	/// A page of settings.
170	Page {
171		/// The settings contained in this page.
172		items: Vec<Setting>,
173		/// Whether to display the title inline.
174		inline_title: Option<bool>,
175		/// Whether to require authentication to open the page.
176		auth_to_open: Option<bool>,
177		/// An icon to be displayed along with the page title.
178		icon: Option<PageIcon>,
179		/// An optional string to display under the title in a header view (an icon must also be provided).
180		info: Option<String>,
181	},
182	/// A list that can be edited by the user.
183	EditableList {
184		/// Optional maximum number of lines.
185		line_limit: Option<i32>,
186		/// Whether to display the list inline.
187		inline: bool,
188		/// Optional placeholder text for new items.
189		placeholder: Option<Cow<'static, str>>,
190		/// The default list items.
191		default: Option<Vec<Cow<'static, str>>>,
192	},
193	/// An inline picker that allows selection of a single value.
194	Picker {
195		/// The values of the options.
196		values: Vec<Cow<'static, str>>,
197		/// Optional display titles for the options. If not provided, the values will be used as titles.
198		titles: Option<Vec<Cow<'static, str>>>,
199		/// The default selected value.
200		default: Option<String>,
201	},
202}
203
204impl SettingValue {
205	fn raw_value(&self) -> &str {
206		match self {
207			Self::Group { .. } => "group",
208			Self::Select { .. } => "select",
209			Self::MultiSelect { .. } => "multi-select",
210			Self::Toggle { .. } => "switch",
211			Self::Stepper { .. } => "stepper",
212			Self::Segment { .. } => "segment",
213			Self::Text { .. } => "text",
214			Self::Button => "button",
215			Self::Link { .. } => "link",
216			Self::Login { .. } => "login",
217			Self::Page { .. } => "page",
218			Self::EditableList { .. } => "editable-list",
219			Self::Picker { .. } => "picker",
220		}
221	}
222}
223
224#[derive(Debug, Clone, PartialEq)]
225pub enum PageIcon {
226	System {
227		name: String,
228		color: String,
229		inset: Option<i32>,
230	},
231	Url(String),
232}
233
234impl Serialize for PageIcon {
235	fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
236	where
237		S: serde::Serializer,
238	{
239		let mut state = serializer.serialize_struct(
240			"Setting",
241			match self {
242				Self::System { .. } => 2,
243				Self::Url(_) => 1,
244			},
245		)?;
246		match self {
247			Self::System { name, color, inset } => {
248				state.serialize_field("type", "system")?;
249				state.serialize_field("name", name)?;
250				state.serialize_field("color", color)?;
251				state.serialize_field("inset", inset)?;
252			}
253			Self::Url(url) => {
254				state.serialize_field("type", "url")?;
255				state.serialize_field("url", url)?;
256			}
257		}
258		state.end()
259	}
260}
261
262macro_rules! create_setting_struct {
263	(
264		$struct_name:ident,
265		$setting_kind:ident,
266		$doc_comment:expr,
267		{ $($(#[$field_meta:meta])* $field_name:ident: $field_type:ty),* $(,)? },
268		{ $($def_field_name:ident: $default_value:expr),* $(,)? }
269	) => {
270		#[doc = $doc_comment]
271		///
272		/// This setting can be converted into a generic [Setting] using the [Into] trait.
273		#[derive(Debug, Clone, PartialEq)]
274		pub struct $struct_name {
275			/// The unique key that identifies this setting.
276			pub key: Cow<'static, str>,
277			/// The display title for this setting.
278			pub title: Cow<'static, str>,
279			/// Optional notification to send to the source when this setting is changed.
280			pub notification: Option<Cow<'static, str>>,
281			/// Optional key of another setting that must be enabled for this setting to be enabled.
282			pub requires: Option<Cow<'static, str>>,
283			/// Optional key of another setting that must be disabled for this setting to be enabled.
284			pub requires_false: Option<Cow<'static, str>>,
285			/// Optional list of items that should be refreshed when this setting changes.
286			///
287			/// The valid options are:
288			/// - `content`
289			/// - `listings`
290			/// - `settings`
291			/// - `filters`
292			pub refreshes: Option<Vec<Cow<'static, str>>>,
293			$(
294				$(#[$field_meta])*
295				pub $field_name: $field_type
296			),*
297		}
298
299		impl From<$struct_name> for Setting {
300			fn from(source: $struct_name) -> Self {
301				Setting {
302					key: source.key,
303					title: source.title,
304					notification: source.notification,
305					requires: source.requires,
306					requires_false: source.requires_false,
307					refreshes: source.refreshes,
308					value: SettingValue::$setting_kind {
309						$($field_name: source.$field_name),*
310					},
311				}
312			}
313		}
314
315		impl Default for $struct_name {
316			fn default() -> Self {
317				Self {
318					key: Cow::Borrowed(stringify!($struct_name)),
319					title: Cow::Borrowed(stringify!($struct_name)),
320					notification: None,
321					requires: None,
322					requires_false: None,
323					refreshes: None,
324					$($def_field_name: $default_value),*
325				}
326			}
327		}
328	};
329}
330
331create_setting_struct!(
332	GroupSetting,
333	Group,
334	"A group of settings.",
335	{
336		/// Optional footer text for the group.
337		footer: Option<Cow<'static, str>>,
338		/// The settings contained in this group.
339		items: Vec<Setting>,
340	},
341	{
342		footer: None,
343		items: Vec::new(),
344	}
345);
346
347create_setting_struct!(
348	SelectSetting,
349	Select,
350	"A page that allows selection of a single value.",
351	{
352		/// The values of the options.
353		values: Vec<Cow<'static, str>>,
354		/// Optional display titles for the options. If not provided, the values will be used as titles.
355		titles: Option<Vec<Cow<'static, str>>>,
356		/// Whether to require authentication to open the page for this setting.
357		auth_to_open: Option<bool>,
358		/// The default selected value. If not provided, the first value will be selected.
359		default: Option<String>,
360	},
361	{
362		values: Vec::new(),
363		titles: None,
364		auth_to_open: None,
365		default: None,
366	}
367);
368
369create_setting_struct!(
370	MultiSelectSetting,
371	MultiSelect,
372	"A page that allows selection of multiple values.",
373	{
374		/// The values of the options.
375		values: Vec<Cow<'static, str>>,
376		/// Optional display titles for the options. If not provided, the values will be used as titles.
377		titles: Option<Vec<Cow<'static, str>>>,
378		/// Whether to require authentication to open the page for this setting.
379		auth_to_open: Option<bool>,
380		/// The default selected value(s).
381		default: Option<Vec<String>>,
382	},
383	{
384		values: Vec::new(),
385		titles: None,
386		auth_to_open: None,
387		default: None,
388	}
389);
390
391create_setting_struct!(
392	ToggleSetting,
393	Toggle,
394	"A toggle switch.",
395	{
396		/// Optional subtitle text.
397		subtitle: Option<Cow<'static, str>>,
398		/// Whether to require authentication to turn the toggle off.
399		auth_to_disable: Option<bool>,
400		/// The default state of the toggle.
401		default: bool,
402	},
403	{
404		subtitle: None,
405		auth_to_disable: None,
406		default: false,
407	}
408);
409
410create_setting_struct!(
411	StepperSetting,
412	Stepper,
413	"A numeric stepper control.",
414	{
415		/// The minimum allowed value.
416		minimum_value: f64,
417		/// The maximum allowed value.
418		maximum_value: f64,
419		/// Optional step increment value.
420		step_value: Option<f64>,
421		/// The default value.
422		default: Option<f64>,
423	},
424	{
425		minimum_value: 1.0,
426		maximum_value: 10.0,
427		step_value: None,
428		default: None,
429	}
430);
431
432create_setting_struct!(
433	SegmentSetting,
434	Segment,
435	"A segmented control.",
436	{
437		/// The options show on the segments.
438		options: Vec<Cow<'static, str>>,
439		/// The default selected segment index.
440		default: Option<i32>,
441	},
442	{
443		options: Vec::new(),
444		default: None,
445	}
446);
447
448create_setting_struct!(
449	TextSetting,
450	Text,
451	"A text input field.",
452	{
453		/// Optional placeholder text when the field is empty.
454		placeholder: Option<Cow<'static, str>>,
455		/// The autocapitalization type.
456		autocapitalization_type: Option<i32>,
457		/// The keyboard type.
458		keyboard_type: Option<i32>,
459		/// The return key type.
460		return_key_type: Option<i32>,
461		/// Whether autocorrection should be disabled.
462		autocorrection_disabled: Option<bool>,
463		/// Whether the text field is for secure entry (password).
464		secure: Option<bool>,
465		/// The default text value.
466		default: Option<Cow<'static, str>>,
467	},
468	{
469		placeholder: None,
470		autocapitalization_type: None,
471		keyboard_type: None,
472		return_key_type: None,
473		autocorrection_disabled: None,
474		secure: None,
475		default: None,
476	}
477);
478
479create_setting_struct!(
480	LinkSetting,
481	Link,
482	"A link to a URL.",
483	{
484		/// The URL to open on press.
485		url: Cow<'static, str>,
486		/// Whether the link should open in an external browser.
487		external: Option<bool>,
488	},
489	{
490		url: "".into(),
491		external: None,
492	}
493);
494
495create_setting_struct!(
496	LoginSetting,
497	Login,
498	"A login control.",
499	{
500		/// The authentication method to use.
501		method: LoginMethod,
502		/// The authentication URL.
503		url: Option<Cow<'static, str>>,
504		/// An optional defaults key to fetch the URL from.
505		url_key: Option<Cow<'static, str>>,
506		/// The title for the logout button. If not provided, the title will be "Log Out".
507		logout_title: Option<Cow<'static, str>>,
508		/// Whether to use PKCE for the OAuth flow.
509		pkce: bool,
510		/// The token URL for OAuth.
511		token_url: Option<Cow<'static, str>>,
512		/// The callback scheme for OAuth.
513		callback_scheme: Option<Cow<'static, str>>,
514		/// Whether to prompt for an email instead of username for basic authentication.
515		use_email: bool,
516		/// An array of localStorage keys to extract after login.
517		local_storage_keys: Option<Vec<String>>,
518	},
519	{
520		method: LoginMethod::OAuth,
521		url: None,
522		url_key: None,
523		logout_title: None,
524		pkce: false,
525		token_url: None,
526		callback_scheme: None,
527		use_email: false,
528		local_storage_keys: None,
529	}
530);
531
532create_setting_struct!(
533	PageSetting,
534	Page,
535	"A page of settings.",
536	{
537		/// The settings contained in this page.
538		items: Vec<Setting>,
539		/// Whether to display the title inline.
540		inline_title: Option<bool>,
541		/// Whether to require authentication to open the page.
542		auth_to_open: Option<bool>,
543		/// An icon to be displayed along with the page title.
544		icon: Option<PageIcon>,
545		/// An optional string to display under the title in a header view (an icon must also be provided).
546		info: Option<String>,
547	},
548	{
549		items: Vec::new(),
550		inline_title: None,
551		auth_to_open: None,
552		icon: None,
553		info: None,
554	}
555);
556
557create_setting_struct!(
558	EditableListSetting,
559	EditableList,
560	"A list that can be edited by the user.",
561	{
562		/// Optional maximum number of lines.
563		line_limit: Option<i32>,
564		/// Whether to display the list inline instead of in a separate page.
565		inline: bool,
566		/// Optional placeholder text for new items.
567		placeholder: Option<Cow<'static, str>>,
568		/// The default list items.
569		default: Option<Vec<Cow<'static, str>>>,
570	},
571	{
572		line_limit: None,
573		inline: false,
574		placeholder: None,
575		default: None,
576	}
577);
578
579create_setting_struct!(
580	PickerSetting,
581	Picker,
582	"An inline picker that allows selection of a single value.",
583	{
584		/// The values of the options.
585		values: Vec<Cow<'static, str>>,
586		/// Optional display titles for the options. If not provided, the values will be used as titles.
587		titles: Option<Vec<Cow<'static, str>>>,
588		/// The default selected value. If not provided, the first value will be selected.
589		default: Option<String>,
590	},
591	{
592		values: Vec::new(),
593		titles: None,
594		default: None,
595	}
596);
597
598/// A button that notifies the source when pressed.
599///
600/// This setting can be converted into a generic [Setting] using the [Into] trait.
601#[derive(Debug, Clone, PartialEq)]
602pub struct ButtonSetting {
603	/// The unique key that identifies this setting.
604	pub key: Cow<'static, str>,
605	/// The display title for this setting.
606	pub title: Cow<'static, str>,
607	/// Optional notification text to display when this setting is changed.
608	pub notification: Option<Cow<'static, str>>,
609	/// Optional key of another setting that must be enabled for this setting to be enabled.
610	pub requires: Option<Cow<'static, str>>,
611	/// Optional key of another setting that must be disabled for this setting to be enabled.
612	pub requires_false: Option<Cow<'static, str>>,
613	/// Optional list of setting keys that should be refreshed when this setting changes.
614	pub refreshes: Option<Vec<Cow<'static, str>>>,
615}
616
617impl From<ButtonSetting> for Setting {
618	fn from(button: ButtonSetting) -> Self {
619		Setting {
620			key: button.key,
621			title: button.title,
622			notification: button.notification,
623			requires: button.requires,
624			requires_false: button.requires_false,
625			refreshes: button.refreshes,
626			value: SettingValue::Button,
627		}
628	}
629}
630
631impl Default for ButtonSetting {
632	fn default() -> Self {
633		Self {
634			key: Cow::Borrowed(stringify!($struct_name)),
635			title: Cow::Borrowed(stringify!($struct_name)),
636			notification: None,
637			requires: None,
638			requires_false: None,
639			refreshes: None,
640		}
641	}
642}