Skip to main content

freya_radio/hooks/
use_radio.rs

1use std::{
2    cell::RefCell,
3    collections::HashMap,
4    hash::Hash,
5    ops::{
6        Deref,
7        DerefMut,
8    },
9    rc::Rc,
10};
11
12use freya_core::{
13    integration::FxHashSet,
14    prelude::*,
15};
16
17#[cfg(feature = "tracing")]
18pub trait RadioChannel<T>: 'static + PartialEq + Eq + Clone + Hash + std::fmt::Debug + Ord {
19    fn derive_channel(self, _radio: &T) -> Vec<Self> {
20        vec![self]
21    }
22}
23
24/// Defines a channel for radio communication.
25/// Channels are used to subscribe to specific changes in the global state.
26/// Each channel must implement this trait to be used with [`RadioStation`] and [`Radio`].
27///
28/// Channels allow fine-grained control over which components re-render when the state changes.
29/// Components only re-render when a channel they are subscribed to is notified.
30///
31/// # Example
32///
33/// ```rust, no_run
34/// # use freya::radio::*;
35/// # struct Data;
36/// #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
37/// pub enum DataChannel {
38///     ListCreation,
39///     SpecificListItemUpdate(usize),
40/// }
41///
42/// impl RadioChannel<Data> for DataChannel {}
43/// ```
44#[cfg(not(feature = "tracing"))]
45pub trait RadioChannel<T>: 'static + PartialEq + Eq + Clone + Hash {
46    /// Derive additional channels based on the current state value.
47    /// This allows a single write operation to notify multiple channels.
48    ///
49    /// By default, returns a vector containing only `self`.
50    ///
51    /// Declaring related channels here is a best practice enforced by design.
52    /// Making every write list them by hand is verbose and easy to get wrong.
53    /// Doing it once here keeps every write consistent.
54    ///
55    /// # Example
56    ///
57    /// ```rust, no_run
58    /// # use freya::radio::*;
59    /// # struct Data { items: Vec<u32> }
60    /// #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
61    /// pub enum DataChannel {
62    ///     All,
63    ///     Specific(usize),
64    /// }
65    ///
66    /// impl RadioChannel<Data> for DataChannel {
67    ///     fn derive_channel(self, data: &Data) -> Vec<Self> {
68    ///         match self {
69    ///             DataChannel::All => {
70    ///                 let mut channels = vec![DataChannel::All];
71    ///                 channels.extend((0..data.items.len()).map(DataChannel::Specific));
72    ///                 channels
73    ///             }
74    ///             DataChannel::Specific(_) => vec![self],
75    ///         }
76    ///     }
77    /// }
78    /// ```
79    fn derive_channel(self, _radio: &T) -> Vec<Self> {
80        vec![self]
81    }
82}
83
84/// The central hub for global state management in Freya applications.
85/// A `RadioStation` holds the global state value and manages subscriptions to different channels.
86/// Components can subscribe to specific channels to receive notifications when the state changes.
87///
88/// RadioStations can be shared across multiple windows or components using [`use_share_radio`].
89///
90/// # Examples
91///
92/// ## Basic usage
93///
94/// ```rust, no_run
95/// # use freya::prelude::*;
96/// # use freya::radio::*;
97/// #[derive(Default)]
98/// struct AppState {
99///     count: i32,
100/// }
101///
102/// #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)]
103/// enum AppChannel {
104///     Count,
105/// }
106///
107/// impl RadioChannel<AppState> for AppChannel {}
108///
109/// fn app() -> impl IntoElement {
110///     // Create a radio station (scoped to this component tree)
111///     use_init_radio_station::<AppState, AppChannel>(AppState::default);
112///
113///     let mut radio = use_radio(AppChannel::Count);
114///
115///     rect()
116///         .child(label().text(format!("Count: {}", radio.read().count)))
117///         .child(
118///             Button::new()
119///                 .on_press(move |_| radio.write().count += 1)
120///                 .child("Increment"),
121///         )
122/// }
123/// ```
124///
125/// ## Global radio station for multi-window apps
126///
127/// ```rust, ignore
128/// # use freya::prelude::*;
129/// # use freya::radio::*;
130///
131/// let radio_station = RadioStation::create_global(AppState::default);
132///
133/// launch(
134///     LaunchConfig::new()
135///         .with_window(WindowConfig::new(Window1 { radio_station }))
136///         .with_window(WindowConfig::new(Window2 { radio_station })),
137/// );
138/// ```
139pub struct RadioStation<Value, Channel>
140where
141    Channel: RadioChannel<Value>,
142    Value: 'static,
143{
144    pub(crate) value: State<Value>,
145    listeners: State<HashMap<Channel, Rc<RefCell<FxHashSet<ReactiveContext>>>>>,
146}
147
148impl<Value, Channel> Clone for RadioStation<Value, Channel>
149where
150    Channel: RadioChannel<Value>,
151{
152    fn clone(&self) -> Self {
153        *self
154    }
155}
156
157impl<Value, Channel> Copy for RadioStation<Value, Channel> where Channel: RadioChannel<Value> {}
158
159impl<Value, Channel> RadioStation<Value, Channel>
160where
161    Channel: RadioChannel<Value>,
162{
163    pub(crate) fn create(init_value: Value) -> Self {
164        RadioStation {
165            value: State::create(init_value),
166            listeners: State::create(HashMap::default()),
167        }
168    }
169
170    /// Create a global `RadioStation` that lives for the entire application lifetime.
171    /// This is useful for sharing state across multiple windows.
172    ///
173    /// This is **not** a hook, do not use it inside components like you would [`use_radio`].
174    /// You would usually want to call this in your `main` function, not anywhere else.
175    ///
176    /// # Example
177    ///
178    /// ```rust, ignore
179    /// # use freya::prelude::*;
180    /// # use freya::radio::*;
181    /// let radio_station = RadioStation::create_global(AppState::default);
182    ///
183    /// launch(
184    ///     LaunchConfig::new()
185    ///         .with_window(WindowConfig::new(Window1 { radio_station }))
186    ///         .with_window(WindowConfig::new(Window2 { radio_station })),
187    /// );
188    /// ```
189    pub fn create_global(init_value: Value) -> Self {
190        RadioStation {
191            value: State::create_global(init_value),
192            listeners: State::create_global(HashMap::default()),
193        }
194    }
195
196    pub(crate) fn is_listening(
197        &self,
198        channel: &Channel,
199        reactive_context: &ReactiveContext,
200    ) -> bool {
201        let listeners = self.listeners.peek();
202        listeners
203            .get(channel)
204            .map(|contexts| contexts.borrow().contains(reactive_context))
205            .unwrap_or_default()
206    }
207
208    pub(crate) fn listen(&self, channel: Channel, mut reactive_context: ReactiveContext) {
209        let mut listeners = self.listeners.write_unchecked();
210        let listeners = listeners.entry(channel).or_default();
211        reactive_context.subscribe(listeners);
212    }
213
214    pub(crate) fn notify_listeners(&self, channel: &Channel) {
215        let listeners = self.listeners.write_unchecked();
216
217        #[cfg(feature = "tracing")]
218        tracing::info!("Notifying {channel:?}");
219
220        for (listener_channel, listeners) in listeners.iter() {
221            if listener_channel == channel {
222                for reactive_context in listeners.borrow().iter() {
223                    reactive_context.notify();
224                }
225            }
226        }
227    }
228
229    /// Read the current state value and subscribe to all channel changes.
230    /// Any component calling this will re-render when any channel is notified.
231    ///
232    /// # Example
233    ///
234    /// ```rust, ignore
235    /// # use freya::radio::*;
236    /// let value = radio_station.read();
237    /// ```
238    #[track_caller]
239    pub fn read(&'_ self) -> ReadRef<'_, Value> {
240        self.value.read()
241    }
242
243    #[track_caller]
244    pub fn peek_unchecked(&self) -> ReadRef<'static, Value> {
245        self.value.peek()
246    }
247
248    /// Read the current state value without subscribing to changes.
249    /// Components using this will not re-render when the state changes.
250    ///
251    /// # Example
252    ///
253    /// ```rust, ignore
254    /// # use freya::radio::*;
255    /// let value = radio_station.peek();
256    /// ```
257    #[track_caller]
258    pub fn peek(&'_ self) -> ReadRef<'_, Value> {
259        self.value.peek()
260    }
261
262    pub(crate) fn cleanup(&self) {
263        let mut listeners = self.listeners.write_unchecked();
264
265        // Clean up those channels with no reactive contexts
266        listeners.retain(|_, listeners| !listeners.borrow().is_empty());
267
268        #[cfg(feature = "tracing")]
269        {
270            use itertools::Itertools;
271            use tracing::{
272                Level,
273                info,
274                span,
275            };
276
277            let mut channels_subscribers = HashMap::<&Channel, usize>::new();
278
279            for (channel, listeners) in listeners.iter() {
280                *channels_subscribers.entry(&channel).or_default() = listeners.borrow().len();
281            }
282
283            let span = span!(Level::DEBUG, "Radio Station Metrics");
284            let _enter = span.enter();
285
286            for (channel, count) in channels_subscribers.iter().sorted() {
287                info!(" {count} subscribers for {channel:?}")
288            }
289        }
290    }
291
292    /// Modify the state using a specific channel.
293    /// This will notify all subscribers to that channel (and any derived channels).
294    ///
295    /// Returns a [`RadioGuard`] that allows direct mutation of the state.
296    /// The guard automatically notifies listeners when dropped.
297    ///
298    /// # Example
299    ///
300    /// ```rust, ignore
301    /// # use freya::radio::*;
302    /// radio_station.write_channel(MyChannel::Update).count += 1;
303    /// ```
304    #[track_caller]
305    pub fn write_channel(&mut self, channel: Channel) -> RadioGuard<Value, Channel> {
306        let value = self.value.write_unchecked();
307        RadioGuard {
308            channels: channel.derive_channel(&*value),
309            station: *self,
310            value,
311        }
312    }
313}
314
315pub struct RadioAntenna<Value, Channel>
316where
317    Channel: RadioChannel<Value>,
318    Value: 'static,
319{
320    pub(crate) channel: Channel,
321    pub(crate) station: RadioStation<Value, Channel>,
322}
323
324impl<Value, Channel> RadioAntenna<Value, Channel>
325where
326    Channel: RadioChannel<Value>,
327{
328    pub(crate) fn new(
329        channel: Channel,
330        station: RadioStation<Value, Channel>,
331    ) -> RadioAntenna<Value, Channel> {
332        RadioAntenna { channel, station }
333    }
334}
335impl<Value, Channel> Clone for RadioAntenna<Value, Channel>
336where
337    Channel: RadioChannel<Value>,
338{
339    fn clone(&self) -> Self {
340        Self {
341            channel: self.channel.clone(),
342            station: self.station,
343        }
344    }
345}
346
347pub struct RadioGuard<Value, Channel>
348where
349    Channel: RadioChannel<Value>,
350    Value: 'static,
351{
352    pub(crate) station: RadioStation<Value, Channel>,
353    pub(crate) channels: Vec<Channel>,
354    pub(crate) value: WriteRef<'static, Value>,
355}
356
357impl<Value, Channel> Drop for RadioGuard<Value, Channel>
358where
359    Channel: RadioChannel<Value>,
360{
361    fn drop(&mut self) {
362        for channel in &mut self.channels {
363            self.station.notify_listeners(channel)
364        }
365        if !self.channels.is_empty() {
366            self.station.cleanup();
367        }
368    }
369}
370
371impl<Value, Channel> Deref for RadioGuard<Value, Channel>
372where
373    Channel: RadioChannel<Value>,
374{
375    type Target = WriteRef<'static, Value>;
376
377    fn deref(&self) -> &Self::Target {
378        &self.value
379    }
380}
381
382impl<Value, Channel> DerefMut for RadioGuard<Value, Channel>
383where
384    Channel: RadioChannel<Value>,
385{
386    fn deref_mut(&mut self) -> &mut WriteRef<'static, Value> {
387        &mut self.value
388    }
389}
390
391/// A reactive handle to the global state for a specific channel.
392/// `Radio` provides methods to read and write the global state, and automatically subscribes
393/// the current component to re-render when the associated channel is notified.
394///
395/// Each `Radio` instance is tied to a specific channel, allowing fine-grained control
396/// over which components update when the state changes.
397///
398/// # Examples
399///
400/// ## Basic usage
401///
402/// ```rust, ignore
403/// # use freya::prelude::*;
404/// # use freya::radio::*;
405/// #[derive(PartialEq)]
406/// struct MyComponent {}
407///
408/// impl Component for MyComponent {
409///     fn render(&self) -> impl IntoElement {
410///         let mut radio = use_radio(MyChannel::Count);
411///
412///         rect()
413///             .child(label().text(format!("Count: {}", radio.read().count)))
414///             .child(
415///                 Button::new()
416///                     .on_press(move |_| radio.write().count += 1)
417///                     .child("Increment"),
418///             )
419///     }
420/// }
421/// ```
422///
423/// ## Using reducers
424///
425/// ```rust, ignore
426/// # use freya::prelude::*;
427/// # use freya::radio::*;
428/// #[derive(Clone)]
429/// struct CounterState {
430///     count: i32,
431/// }
432///
433/// impl DataReducer for CounterState {
434///     type Channel = CounterChannel;
435///     type Action = CounterAction;
436///
437///     fn reduce(&mut self, action: CounterAction) -> ChannelSelection<CounterChannel> {
438///         match action {
439///             CounterAction::Increment => self.count += 1,
440///             CounterAction::Decrement => self.count -= 1,
441///         }
442///         ChannelSelection::Current
443///     }
444/// }
445///
446/// #[derive(PartialEq)]
447/// struct CounterComponent {}
448///
449/// impl Component for CounterComponent {
450///     fn render(&self) -> impl IntoElement {
451///         let mut radio = use_radio(CounterChannel::Count);
452///
453///         rect()
454///             .child(
455///                 Button::new()
456///                     .on_press(move |_| radio.apply(CounterAction::Increment))
457///                     .child("+"),
458///             )
459///             .child(label().text(format!("{}", radio.read().count)))
460///             .child(
461///                 Button::new()
462///                     .on_press(move |_| radio.apply(CounterAction::Decrement))
463///                     .child("-"),
464///             )
465///     }
466/// }
467/// ```
468pub struct Radio<Value, Channel>
469where
470    Channel: RadioChannel<Value>,
471    Value: 'static,
472{
473    pub(crate) antenna: State<RadioAntenna<Value, Channel>>,
474}
475
476impl<Value, Channel> Clone for Radio<Value, Channel>
477where
478    Channel: RadioChannel<Value>,
479{
480    fn clone(&self) -> Self {
481        *self
482    }
483}
484impl<Value, Channel> Copy for Radio<Value, Channel> where Channel: RadioChannel<Value> {}
485
486impl<Value, Channel> PartialEq for Radio<Value, Channel>
487where
488    Channel: RadioChannel<Value>,
489{
490    fn eq(&self, other: &Self) -> bool {
491        self.antenna == other.antenna
492    }
493}
494
495impl<Value, Channel> Radio<Value, Channel>
496where
497    Channel: RadioChannel<Value>,
498{
499    pub(crate) fn new(antenna: State<RadioAntenna<Value, Channel>>) -> Radio<Value, Channel> {
500        Radio { antenna }
501    }
502
503    pub(crate) fn subscribe_if_not(&self) {
504        if let Some(rc) = ReactiveContext::try_current() {
505            let antenna = &self.antenna.write_unchecked();
506            let channel = antenna.channel.clone();
507            let is_listening = antenna.station.is_listening(&channel, &rc);
508
509            // Subscribe the reader reactive context to the channel if it wasn't already
510            if !is_listening {
511                antenna.station.listen(channel, rc);
512            }
513        }
514    }
515
516    /// Read the current state value and subscribe the current component to changes
517    /// on this radio's channel. The component will re-render when this channel is notified.
518    ///
519    /// # Example
520    ///
521    /// ```rust, ignore
522    /// # use freya::radio::*;
523    /// let count = radio.read().count;
524    /// ```
525    #[track_caller]
526    pub fn read(&'_ self) -> ReadRef<'_, Value> {
527        self.subscribe_if_not();
528        self.antenna.peek().station.value.peek()
529    }
530
531    /// Read the current state value inside a callback.
532    ///
533    /// Example:
534    ///
535    /// ```rust, ignore
536    /// # use freya::radio::*;
537    /// radio.with(|value| {
538    ///     // Do something with `value`
539    /// });
540    /// ```
541    #[track_caller]
542    pub fn with(&self, cb: impl FnOnce(ReadRef<Value>)) {
543        self.subscribe_if_not();
544        let value = self.antenna.peek().station.value;
545        let borrow = value.read();
546        cb(borrow);
547    }
548
549    /// Get a mutable reference to the state for writing.
550    /// Changes will notify subscribers to this radio's channel.
551    ///
552    /// Returns a [`RadioGuard`] that allows direct mutation of the state.
553    ///
554    /// # Example
555    ///
556    /// ```rust, ignore
557    /// # use freya::radio::*;
558    /// radio.write().count += 1;
559    /// ```
560    #[track_caller]
561    pub fn write(&mut self) -> RadioGuard<Value, Channel> {
562        let value = self.antenna.peek().station.value.write_unchecked();
563        let channel = self.antenna.peek().channel.clone();
564        RadioGuard {
565            channels: channel.derive_channel(&*value),
566            station: self.antenna.read().station,
567            value,
568        }
569    }
570
571    /// Get a mutable reference to the current state value, inside a callback.
572    ///
573    /// Example:
574    ///
575    /// ```rust, ignore
576    /// # use freya::radio::*;
577    /// radio.write_with(|value| {
578    ///     // Modify `value`
579    /// });
580    /// ```
581    #[track_caller]
582    pub fn write_with(&mut self, cb: impl FnOnce(RadioGuard<Value, Channel>)) {
583        let guard = self.write();
584        cb(guard);
585    }
586
587    /// Modify the state using a custom Channel.
588    ///
589    /// ## Example:
590    /// ```rust, ignore
591    /// # use freya::radio::*;
592    /// radio.write(Channel::Whatever).value = 1;
593    /// ```
594    #[track_caller]
595    pub fn write_channel(&mut self, channel: Channel) -> RadioGuard<Value, Channel> {
596        let value = self.antenna.peek().station.value.write_unchecked();
597        RadioGuard {
598            channels: channel.derive_channel(&*value),
599            station: self.antenna.read().station,
600            value,
601        }
602    }
603
604    /// Get a mutable reference to the current state value, inside a callback.
605    ///
606    /// Example:
607    ///
608    /// ```rust, ignore
609    /// # use freya::radio::*;
610    /// radio.write_channel_with(Channel::Whatever, |value| {
611    ///     // Modify `value`
612    /// });
613    /// ```
614    #[track_caller]
615    pub fn write_channel_with(
616        &mut self,
617        channel: Channel,
618        cb: impl FnOnce(RadioGuard<Value, Channel>),
619    ) {
620        let guard = self.write_channel(channel);
621        cb(guard);
622    }
623
624    /// Get a mutable reference to the current state value, inside a callback that returns the channel to be used.
625    ///
626    /// Example:
627    ///
628    /// ```rust, ignore
629    /// # use freya::radio::*;
630    /// radio.write_with_channel_selection(|value| {
631    ///     // Modify `value`
632    ///     if value.cool {
633    ///         ChannelSelection::Select(Channel::Whatever)
634    ///     } else {
635    ///         ChannelSelection::Silence
636    ///     }
637    /// });
638    /// ```
639    #[track_caller]
640    pub fn write_with_channel_selection(
641        &mut self,
642        cb: impl FnOnce(&mut Value) -> ChannelSelection<Channel>,
643    ) -> ChannelSelection<Channel> {
644        let value = self.antenna.peek().station.value.write_unchecked();
645        let mut guard = RadioGuard {
646            channels: Vec::default(),
647            station: self.antenna.read().station,
648            value,
649        };
650        let channel_selection = cb(&mut guard.value);
651        let channel = match channel_selection.clone() {
652            ChannelSelection::Current => Some(self.antenna.peek().channel.clone()),
653            ChannelSelection::Silence => None,
654            ChannelSelection::Select(c) => Some(c),
655        };
656        if let Some(channel) = channel {
657            for channel in channel.derive_channel(&guard.value) {
658                self.antenna.peek().station.notify_listeners(&channel)
659            }
660            self.antenna.peek().station.cleanup();
661        }
662
663        channel_selection
664    }
665
666    /// Modify the state silently, no component will be notified.
667    ///
668    /// This is not recommended, the only intended usage for this is inside [RadioAsyncReducer].
669    #[track_caller]
670    pub fn write_silently(&mut self) -> RadioGuard<Value, Channel> {
671        let value = self.antenna.peek().station.value.write_unchecked();
672        RadioGuard {
673            channels: Vec::default(),
674            station: self.antenna.read().station,
675            value,
676        }
677    }
678}
679
680impl<Value, Channel> WritableUtils<Value> for Radio<Value, Channel>
681where
682    Channel: RadioChannel<Value>,
683    Value: 'static,
684{
685    fn write_state(&mut self) -> WriteRef<'static, Value> {
686        let antenna = self.antenna.peek();
687        let channel = antenna.channel.clone();
688        let value = antenna.station.value.write_unchecked();
689        for ch in channel.derive_channel(&*value) {
690            antenna.station.notify_listeners(&ch);
691        }
692        antenna.station.cleanup();
693        value
694    }
695
696    fn peek_state(&self) -> ReadRef<'static, Value> {
697        self.antenna.peek().station.peek_unchecked()
698    }
699}
700
701impl<Channel> Copy for ChannelSelection<Channel> where Channel: Copy {}
702
703#[derive(Clone)]
704pub enum ChannelSelection<Channel> {
705    /// Notify the channel associated with the used [Radio].
706    Current,
707    /// Notify a given `Channel`.
708    Select(Channel),
709    /// No subscriber will be notified.
710    Silence,
711}
712
713impl<Channel> ChannelSelection<Channel> {
714    /// Change to [ChannelSelection::Current]
715    pub fn current(&mut self) {
716        *self = Self::Current
717    }
718
719    /// Change to [ChannelSelection::Select]
720    pub fn select(&mut self, channel: Channel) {
721        *self = Self::Select(channel)
722    }
723
724    /// Change to [ChannelSelection::Silence]
725    pub fn silence(&mut self) {
726        *self = Self::Silence
727    }
728
729    /// Check if it is of type [ChannelSelection::Current]
730    pub fn is_current(&self) -> bool {
731        matches!(self, Self::Current)
732    }
733
734    /// Check if it is of type [ChannelSelection::Select] and return the channel.
735    pub fn is_select(&self) -> Option<&Channel> {
736        match self {
737            Self::Select(channel) => Some(channel),
738            _ => None,
739        }
740    }
741
742    /// Check if it is of type [ChannelSelection::Silence]
743    pub fn is_silence(&self) -> bool {
744        matches!(self, Self::Silence)
745    }
746}
747
748/// Provide an existing [`RadioStation`] to descendant components.
749/// This is useful for sharing the same global state across different parts of the component tree
750/// or across multiple windows.
751pub fn use_share_radio<Value, Channel>(radio: impl FnOnce() -> RadioStation<Value, Channel>)
752where
753    Channel: RadioChannel<Value>,
754    Value: 'static,
755{
756    use_provide_context(radio);
757}
758
759/// Subscribe to the global state for a specific channel.
760/// Returns a [`Radio`] handle that allows reading and writing the state.
761/// The current component will re-render whenever the specified channel is notified.
762///
763/// This hook must be called within a component that has access to a [`RadioStation`]
764/// (either through [`use_init_radio_station`] or [`use_share_radio`]).
765///
766/// # Example
767///
768/// ```rust, ignore
769/// # use freya::prelude::*;
770/// # use freya::radio::*;
771///
772/// fn app() -> impl IntoElement {
773///     use_init_radio_station::<AppState, AppChannel>(AppState::default);
774///
775///     rect().child(Counter {})
776/// }
777///
778/// #[derive(PartialEq)]
779/// struct Counter {}
780///
781/// impl Component for Counter {
782///     fn render(&self) -> impl IntoElement {
783///         let mut radio = use_radio(AppChannel::Count);
784///
785///         rect()
786///             .child(label().text(format!("Count: {}", radio.read().count)))
787///             .child(
788///                 Button::new()
789///                     .on_press(move |_| radio.write().count += 1)
790///                     .child("+"),
791///             )
792///     }
793/// }
794/// ```
795pub fn use_radio<Value, Channel>(channel: Channel) -> Radio<Value, Channel>
796where
797    Channel: RadioChannel<Value>,
798    Value: 'static,
799{
800    let station = use_consume::<RadioStation<Value, Channel>>();
801
802    let mut radio = use_hook(|| {
803        let antenna = RadioAntenna::new(channel.clone(), station);
804        Radio::new(State::create(antenna))
805    });
806
807    if radio.antenna.peek().channel != channel {
808        radio.antenna.write().channel = channel;
809    }
810
811    radio
812}
813
814/// Initialize a new radio station in the current component tree.
815/// This provides the global state to all descendant components.
816///
817/// Returns the [`RadioStation`] instance for direct access if needed.
818///
819/// # Example
820///
821/// ```rust, ignore
822/// # use freya::prelude::*;
823/// # use freya::radio::*;
824///
825/// fn app() -> impl IntoElement {
826///     use_init_radio_station::<AppState, AppChannel>(AppState::default);
827///
828///     rect().child(MyComponent {})
829/// }
830/// ```
831pub fn use_init_radio_station<Value, Channel>(
832    init_value: impl FnOnce() -> Value,
833) -> RadioStation<Value, Channel>
834where
835    Channel: RadioChannel<Value>,
836    Value: 'static,
837{
838    use_provide_context(|| RadioStation::create(init_value()))
839}
840
841pub fn use_radio_station<Value, Channel>() -> RadioStation<Value, Channel>
842where
843    Channel: RadioChannel<Value>,
844    Value: 'static,
845{
846    use_consume::<RadioStation<Value, Channel>>()
847}
848
849/// Trait for implementing a reducer pattern on your state.
850/// Reducers allow you to define actions that modify the state in a controlled way.
851///
852/// Implement this trait on your state type to enable the [`RadioReducer`] functionality.
853///
854/// # Example
855///
856/// ```rust, ignore
857/// # use freya::radio::*;
858///
859/// #[derive(Clone)]
860/// struct Counter {
861///     count: i32,
862/// }
863///
864/// #[derive(Clone)]
865/// enum CounterAction {
866///     Increment,
867///     Decrement,
868///     Set(i32),
869/// }
870///
871/// impl DataReducer for Counter {
872///     type Channel = CounterChannel;
873///     type Action = CounterAction;
874///
875///     fn reduce(&mut self, action: Self::Action) -> ChannelSelection<Self::Channel> {
876///         match action {
877///             CounterAction::Increment => self.count += 1,
878///             CounterAction::Decrement => self.count -= 1,
879///             CounterAction::Set(value) => self.count = value,
880///         }
881///         ChannelSelection::Current
882///     }
883/// }
884/// ```
885pub trait DataReducer {
886    type Channel;
887    type Action;
888
889    fn reduce(&mut self, action: Self::Action) -> ChannelSelection<Self::Channel>;
890}
891
892pub trait RadioReducer {
893    type Action;
894    type Channel;
895
896    fn apply(&mut self, action: Self::Action) -> ChannelSelection<Self::Channel>;
897}
898
899impl<Data: DataReducer<Channel = Channel, Action = Action>, Channel: RadioChannel<Data>, Action>
900    RadioReducer for Radio<Data, Channel>
901{
902    type Action = Action;
903    type Channel = Channel;
904
905    fn apply(&mut self, action: Action) -> ChannelSelection<Channel> {
906        self.write_with_channel_selection(|data| data.reduce(action))
907    }
908}
909
910pub trait DataAsyncReducer {
911    type Channel;
912    type Action;
913
914    #[allow(async_fn_in_trait)]
915    async fn async_reduce(
916        _radio: &mut Radio<Self, Self::Channel>,
917        _action: Self::Action,
918    ) -> ChannelSelection<Self::Channel>
919    where
920        Self::Channel: RadioChannel<Self>,
921        Self: Sized;
922}
923
924pub trait RadioAsyncReducer {
925    type Action;
926
927    fn async_apply(&mut self, _action: Self::Action)
928    where
929        Self::Action: 'static;
930}
931
932impl<
933    Data: DataAsyncReducer<Channel = Channel, Action = Action>,
934    Channel: RadioChannel<Data>,
935    Action,
936> RadioAsyncReducer for Radio<Data, Channel>
937{
938    type Action = Action;
939
940    fn async_apply(&mut self, action: Self::Action)
941    where
942        Self::Action: 'static,
943    {
944        let mut radio = *self;
945        spawn(async move {
946            let channel = Data::async_reduce(&mut radio, action).await;
947            radio.write_with_channel_selection(|_| channel);
948        });
949    }
950}