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