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}
194
195impl SettingValue {
196	fn raw_value(&self) -> &str {
197		match self {
198			Self::Group { .. } => "group",
199			Self::Select { .. } => "select",
200			Self::MultiSelect { .. } => "multi-select",
201			Self::Toggle { .. } => "switch",
202			Self::Stepper { .. } => "stepper",
203			Self::Segment { .. } => "segment",
204			Self::Text { .. } => "text",
205			Self::Button => "button",
206			Self::Link { .. } => "link",
207			Self::Login { .. } => "login",
208			Self::Page { .. } => "page",
209			Self::EditableList { .. } => "editable-list",
210		}
211	}
212}
213
214#[derive(Debug, Clone, PartialEq)]
215pub enum PageIcon {
216	System {
217		name: String,
218		color: String,
219		inset: Option<i32>,
220	},
221	Url(String),
222}
223
224impl Serialize for PageIcon {
225	fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
226	where
227		S: serde::Serializer,
228	{
229		let mut state = serializer.serialize_struct(
230			"Setting",
231			match self {
232				Self::System { .. } => 2,
233				Self::Url(_) => 1,
234			},
235		)?;
236		match self {
237			Self::System { name, color, inset } => {
238				state.serialize_field("type", "system")?;
239				state.serialize_field("name", name)?;
240				state.serialize_field("color", color)?;
241				state.serialize_field("inset", inset)?;
242			}
243			Self::Url(url) => {
244				state.serialize_field("type", "url")?;
245				state.serialize_field("url", url)?;
246			}
247		}
248		state.end()
249	}
250}
251
252macro_rules! create_setting_struct {
253	(
254		$struct_name:ident,
255		$setting_kind:ident,
256		$doc_comment:expr,
257		{ $($(#[$field_meta:meta])* $field_name:ident: $field_type:ty),* $(,)? },
258		{ $($def_field_name:ident: $default_value:expr),* $(,)? }
259	) => {
260		#[doc = $doc_comment]
261		///
262		/// This setting can be converted into a generic [Setting] using the [Into] trait.
263		#[derive(Debug, Clone, PartialEq)]
264		pub struct $struct_name {
265			/// The unique key that identifies this setting.
266			pub key: Cow<'static, str>,
267			/// The display title for this setting.
268			pub title: Cow<'static, str>,
269			/// Optional notification to send to the source when this setting is changed.
270			pub notification: Option<Cow<'static, str>>,
271			/// Optional key of another setting that must be enabled for this setting to be enabled.
272			pub requires: Option<Cow<'static, str>>,
273			/// Optional key of another setting that must be disabled for this setting to be enabled.
274			pub requires_false: Option<Cow<'static, str>>,
275			/// Optional list of items that should be refreshed when this setting changes.
276			///
277			/// The valid options are:
278			/// - `content`
279			/// - `listings`
280			/// - `settings`
281			/// - `filters`
282			pub refreshes: Option<Vec<Cow<'static, str>>>,
283			$(
284				$(#[$field_meta])*
285				pub $field_name: $field_type
286			),*
287		}
288
289		impl From<$struct_name> for Setting {
290			fn from(source: $struct_name) -> Self {
291				Setting {
292					key: source.key,
293					title: source.title,
294					notification: source.notification,
295					requires: source.requires,
296					requires_false: source.requires_false,
297					refreshes: source.refreshes,
298					value: SettingValue::$setting_kind {
299						$($field_name: source.$field_name),*
300					},
301				}
302			}
303		}
304
305		impl Default for $struct_name {
306			fn default() -> Self {
307				Self {
308					key: Cow::Borrowed(stringify!($struct_name)),
309					title: Cow::Borrowed(stringify!($struct_name)),
310					notification: None,
311					requires: None,
312					requires_false: None,
313					refreshes: None,
314					$($def_field_name: $default_value),*
315				}
316			}
317		}
318	};
319}
320
321create_setting_struct!(
322	GroupSetting,
323	Group,
324	"A group of settings.",
325	{
326		/// Optional footer text for the group.
327		footer: Option<Cow<'static, str>>,
328		/// The settings contained in this group.
329		items: Vec<Setting>,
330	},
331	{
332		footer: None,
333		items: Vec::new(),
334	}
335);
336
337create_setting_struct!(
338	SelectSetting,
339	Select,
340	"A page that allows selection of a single value.",
341	{
342		/// The values of the options.
343		values: Vec<Cow<'static, str>>,
344		/// Optional display titles for the options. If not provided, the values will be used as titles.
345		titles: Option<Vec<Cow<'static, str>>>,
346		/// Whether to require authentication to open the page for this setting.
347		auth_to_open: Option<bool>,
348		/// The default selected value. If not provided, the first value will be selected.
349		default: Option<String>,
350	},
351	{
352		values: Vec::new(),
353		titles: None,
354		auth_to_open: None,
355		default: None,
356	}
357);
358
359create_setting_struct!(
360	MultiSelectSetting,
361	MultiSelect,
362	"A page that allows selection of multiple values.",
363	{
364		/// The values of the options.
365		values: Vec<Cow<'static, str>>,
366		/// Optional display titles for the options. If not provided, the values will be used as titles.
367		titles: Option<Vec<Cow<'static, str>>>,
368		/// Whether to require authentication to open the page for this setting.
369		auth_to_open: Option<bool>,
370		/// The default selected value(s).
371		default: Option<Vec<String>>,
372	},
373	{
374		values: Vec::new(),
375		titles: None,
376		auth_to_open: None,
377		default: None,
378	}
379);
380
381create_setting_struct!(
382	ToggleSetting,
383	Toggle,
384	"A toggle switch.",
385	{
386		/// Optional subtitle text.
387		subtitle: Option<Cow<'static, str>>,
388		/// Whether to require authentication to turn the toggle off.
389		auth_to_disable: Option<bool>,
390		/// The default state of the toggle.
391		default: bool,
392	},
393	{
394		subtitle: None,
395		auth_to_disable: None,
396		default: false,
397	}
398);
399
400create_setting_struct!(
401	StepperSetting,
402	Stepper,
403	"A numeric stepper control.",
404	{
405		/// The minimum allowed value.
406		minimum_value: f64,
407		/// The maximum allowed value.
408		maximum_value: f64,
409		/// Optional step increment value.
410		step_value: Option<f64>,
411		/// The default value.
412		default: Option<f64>,
413	},
414	{
415		minimum_value: 1.0,
416		maximum_value: 10.0,
417		step_value: None,
418		default: None,
419	}
420);
421
422create_setting_struct!(
423	SegmentSetting,
424	Segment,
425	"A segmented control.",
426	{
427		/// The options show on the segments.
428		options: Vec<Cow<'static, str>>,
429		/// The default selected segment index.
430		default: Option<i32>,
431	},
432	{
433		options: Vec::new(),
434		default: None,
435	}
436);
437
438create_setting_struct!(
439	TextSetting,
440	Text,
441	"A text input field.",
442	{
443		/// Optional placeholder text when the field is empty.
444		placeholder: Option<Cow<'static, str>>,
445		/// The autocapitalization type.
446		autocapitalization_type: Option<i32>,
447		/// The keyboard type.
448		keyboard_type: Option<i32>,
449		/// The return key type.
450		return_key_type: Option<i32>,
451		/// Whether autocorrection should be disabled.
452		autocorrection_disabled: Option<bool>,
453		/// Whether the text field is for secure entry (password).
454		secure: Option<bool>,
455		/// The default text value.
456		default: Option<Cow<'static, str>>,
457	},
458	{
459		placeholder: None,
460		autocapitalization_type: None,
461		keyboard_type: None,
462		return_key_type: None,
463		autocorrection_disabled: None,
464		secure: None,
465		default: None,
466	}
467);
468
469create_setting_struct!(
470	LinkSetting,
471	Link,
472	"A link to a URL.",
473	{
474		/// The URL to open on press.
475		url: Cow<'static, str>,
476		/// Whether the link should open in an external browser.
477		external: Option<bool>,
478	},
479	{
480		url: "".into(),
481		external: None,
482	}
483);
484
485create_setting_struct!(
486	LoginSetting,
487	Login,
488	"A login control.",
489	{
490		/// The authentication method to use.
491		method: LoginMethod,
492		/// The authentication URL.
493		url: Option<Cow<'static, str>>,
494		/// An optional defaults key to fetch the URL from.
495		url_key: Option<Cow<'static, str>>,
496		/// The title for the logout button. If not provided, the title will be "Log Out".
497		logout_title: Option<Cow<'static, str>>,
498		/// Whether to use PKCE for the OAuth flow.
499		pkce: bool,
500		/// The token URL for OAuth.
501		token_url: Option<Cow<'static, str>>,
502		/// The callback scheme for OAuth.
503		callback_scheme: Option<Cow<'static, str>>,
504		/// Whether to prompt for an email instead of username for basic authentication.
505		use_email: bool,
506		/// An array of localStorage keys to extract after login.
507		local_storage_keys: Option<Vec<String>>,
508	},
509	{
510		method: LoginMethod::OAuth,
511		url: None,
512		url_key: None,
513		logout_title: None,
514		pkce: false,
515		token_url: None,
516		callback_scheme: None,
517		use_email: false,
518		local_storage_keys: None,
519	}
520);
521
522create_setting_struct!(
523	PageSetting,
524	Page,
525	"A page of settings.",
526	{
527		/// The settings contained in this page.
528		items: Vec<Setting>,
529		/// Whether to display the title inline.
530		inline_title: Option<bool>,
531		/// Whether to require authentication to open the page.
532		auth_to_open: Option<bool>,
533		/// An icon to be displayed along with the page title.
534		icon: Option<PageIcon>,
535		/// An optional string to display under the title in a header view (an icon must also be provided).
536		info: Option<String>,
537	},
538	{
539		items: Vec::new(),
540		inline_title: None,
541		auth_to_open: None,
542		icon: None,
543		info: None,
544	}
545);
546
547create_setting_struct!(
548	EditableListSetting,
549	EditableList,
550	"A list that can be edited by the user.",
551	{
552		/// Optional maximum number of lines.
553		line_limit: Option<i32>,
554		/// Whether to display the list inline instead of in a separate page.
555		inline: bool,
556		/// Optional placeholder text for new items.
557		placeholder: Option<Cow<'static, str>>,
558		/// The default list items.
559		default: Option<Vec<Cow<'static, str>>>,
560	},
561	{
562		line_limit: None,
563		inline: false,
564		placeholder: None,
565		default: None,
566	}
567);
568
569/// A button that notifies the source when pressed.
570///
571/// This setting can be converted into a generic [Setting] using the [Into] trait.
572#[derive(Debug, Clone, PartialEq)]
573pub struct ButtonSetting {
574	/// The unique key that identifies this setting.
575	pub key: Cow<'static, str>,
576	/// The display title for this setting.
577	pub title: Cow<'static, str>,
578	/// Optional notification text to display when this setting is changed.
579	pub notification: Option<Cow<'static, str>>,
580	/// Optional key of another setting that must be enabled for this setting to be enabled.
581	pub requires: Option<Cow<'static, str>>,
582	/// Optional key of another setting that must be disabled for this setting to be enabled.
583	pub requires_false: Option<Cow<'static, str>>,
584	/// Optional list of setting keys that should be refreshed when this setting changes.
585	pub refreshes: Option<Vec<Cow<'static, str>>>,
586}
587
588impl From<ButtonSetting> for Setting {
589	fn from(button: ButtonSetting) -> Self {
590		Setting {
591			key: button.key,
592			title: button.title,
593			notification: button.notification,
594			requires: button.requires,
595			requires_false: button.requires_false,
596			refreshes: button.refreshes,
597			value: SettingValue::Button,
598		}
599	}
600}
601
602impl Default for ButtonSetting {
603	fn default() -> Self {
604		Self {
605			key: Cow::Borrowed(stringify!($struct_name)),
606			title: Cow::Borrowed(stringify!($struct_name)),
607			notification: None,
608			requires: None,
609			requires_false: None,
610			refreshes: None,
611		}
612	}
613}