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