Skip to main content

tauri_plugin_android_fs/api/
progress_notification_guard.rs

1use sync_async::sync_async;
2use crate::*;
3use super::*;
4
5
6#[sync_async]
7pub struct ProgressNotificationGuard<R: tauri::Runtime> {
8    #[cfg(target_os = "android")]
9    inner: Inner<R>,
10
11    #[cfg(not(target_os = "android"))]
12    inner: std::marker::PhantomData<fn() -> R>,
13}
14
15#[cfg(target_os = "android")]
16#[sync_async(
17    use(if_sync) impls::SyncImpls as Impls;
18    use(if_async) impls::AsyncImpls as Impls;
19)]
20impl<R: tauri::Runtime> ProgressNotificationGuard<R> {
21
22    #[maybe_async]
23    pub(crate) fn with_new_notification(
24        icon: ProgressNotificationIcon,
25        title: Option<String>,
26        text: Option<String>,
27        sub_text: Option<String>,
28        progress: Option<u64>,
29        progress_max: Option<u64>,
30        handle: tauri::plugin::PluginHandle<R>,
31    ) -> Result<Self> {
32
33        let impls = Impls { handle: &handle };
34
35        let (n_progress, n_progress_max) = normalize_progress_and_max(progress, progress_max);
36        let id = impls
37            .start_progress_notification(
38                icon, 
39                title.as_deref(), 
40                text.as_deref(), 
41                sub_text.as_deref(),
42                n_progress, 
43                n_progress_max
44            )
45            .await?;
46
47        Ok(Self {
48            inner: Inner {
49                current_state: std::sync::Mutex::new(CurrentState {
50                    title: title.clone(),
51                    text,
52                    sub_text,
53                    progress,
54                    progress_max
55                }),
56                drop_behavior: std::sync::Mutex::new(DropBehavior { 
57                    title: None,
58                    text: None,
59                    sub_text: None,
60                    share_src: None,
61                    error: true,
62                }),
63                id,
64                icon,
65                handle,
66                is_finished: false,
67            }
68        })
69    }
70
71    #[always_sync]
72    fn impls(&self) -> Impls<'_, R> {
73        Impls { handle: &self.inner.handle }
74    }
75}
76
77#[sync_async(
78    use(if_async) api_async::{AndroidFs, Utils};
79    use(if_sync) api_sync::{AndroidFs, Utils};
80)]
81impl<R: tauri::Runtime> ProgressNotificationGuard<R> {
82
83    #[always_sync]
84    pub fn into_sync(self) -> SyncProgressNotificationGuard<R> {
85        SyncProgressNotificationGuard { inner: self.inner }
86    }
87
88    #[always_sync]
89    pub fn into_async(self) -> AsyncProgressNotificationGuard<R> {
90        AsyncProgressNotificationGuard { inner: self.inner }
91    }
92
93    #[always_sync]
94    pub fn title(&self) -> Option<String> {
95        #[cfg(not(target_os = "android"))] {
96            None
97        }
98        #[cfg(target_os = "android")] {
99            self.inner.lock_current_state().title.clone()
100        }
101    }
102
103    #[always_sync]
104    pub fn text(&self) -> Option<String> {
105        #[cfg(not(target_os = "android"))] {
106            None
107        }
108        #[cfg(target_os = "android")] {
109            self.inner.lock_current_state().text.clone()
110        }
111    }
112
113    #[always_sync]
114    pub fn sub_text(&self) -> Option<String> {
115        #[cfg(not(target_os = "android"))] {
116            None
117        }
118        #[cfg(target_os = "android")] {
119            self.inner.lock_current_state().sub_text.clone()
120        }
121    }
122
123    #[always_sync]
124    pub fn progress(&self) -> Option<u64> {
125        #[cfg(not(target_os = "android"))] {
126            None
127        }
128        #[cfg(target_os = "android")] {
129            self.inner.lock_current_state().progress
130        }
131    }
132
133    #[always_sync]
134    pub fn progress_max(&self) -> Option<u64> {
135        #[cfg(not(target_os = "android"))] {
136            None
137        }
138        #[cfg(target_os = "android")] {
139            self.inner.lock_current_state().progress_max
140        }
141    }
142
143    #[maybe_async]
144    pub fn update_progress_by(&self, addend: u64) -> Result<()> {
145        #[cfg(not(target_os = "android"))] {
146            Ok(())
147        }
148        #[cfg(target_os = "android")] {
149            {
150                let mut state = self.inner.lock_current_state();
151                state.progress = Some(state.progress.unwrap_or(0).saturating_add(addend));
152            }
153
154            self.update_notification().await?;
155            Ok(())
156        }
157    }
158
159    #[maybe_async]
160    pub fn update_progress(&self, progress: Option<u64>) -> Result<()> {
161        #[cfg(not(target_os = "android"))] {
162            Ok(())
163        }
164        #[cfg(target_os = "android")] {
165            {
166                let mut state = self.inner.lock_current_state();
167                state.progress = progress;
168            }
169
170            self.update_notification().await?;
171            Ok(())
172        }
173    }
174
175    #[maybe_async]
176    pub fn update_progress_max(&self, progress_max: Option<u64>) -> Result<()> {
177        #[cfg(not(target_os = "android"))] {
178            Ok(())
179        }
180        #[cfg(target_os = "android")] {
181            {
182                let mut state = self.inner.lock_current_state();
183                state.progress_max = progress_max;
184            }
185
186            self.update_notification().await?;
187            Ok(())
188        }
189    }
190
191    #[maybe_async]
192    pub fn update_title(&self, title: Option<&str>) -> Result<()> {
193        #[cfg(not(target_os = "android"))] {
194            Ok(())
195        }
196        #[cfg(target_os = "android")] {
197            {
198                let mut state = self.inner.lock_current_state();
199                state.title = title.map(|s| s.to_string());
200            }
201
202            self.update_notification().await?;
203            Ok(())
204        }
205    }
206
207    #[maybe_async]
208    pub fn update_text(&self, text: Option<&str>) -> Result<()> {
209        #[cfg(not(target_os = "android"))] {
210            Ok(())
211        }
212        #[cfg(target_os = "android")] {
213           {
214                let mut state = self.inner.lock_current_state();
215                state.text = text.map(|s| s.to_string());
216            }
217
218            self.update_notification().await?;
219            Ok(())
220        }
221    }
222
223    #[maybe_async]
224    pub fn update_sub_text(&self, sub_text: Option<&str>) -> Result<()> {
225        #[cfg(not(target_os = "android"))] {
226            Ok(())
227        }
228        #[cfg(target_os = "android")] {
229            {
230                let mut state = self.inner.lock_current_state();
231                state.sub_text = sub_text.map(|s| s.to_string());
232            };
233
234            self.update_notification().await?;
235            Ok(())
236        }
237    }
238
239    #[maybe_async]
240    pub fn update(
241        &self,
242        title: Option<&str>,
243        text: Option<&str>,
244        sub_text: Option<&str>,
245        progress: Option<u64>,
246        progress_max: Option<u64>,
247    ) -> Result<()> {
248
249        #[cfg(not(target_os = "android"))] {
250            Ok(())
251        }
252        #[cfg(target_os = "android")] {
253            {
254                let mut state = self.inner.lock_current_state();
255                *state = CurrentState {
256                    title: title.map(|s| s.to_string()),
257                    text: text.map(|s| s.to_string()),
258                    sub_text: sub_text.map(|s| s.to_string()),
259                    progress,
260                    progress_max
261                };
262            }
263
264            self.update_notification().await?;
265            Ok(())
266        }
267    }
268
269    #[always_sync]
270    pub fn set_drop_behavior_to_complete(
271        &self,
272        title: Option<&str>,
273        text: Option<&str>,
274        sub_text: Option<&str>,
275        share_src: Option<&FileUri>
276    ) {
277
278        #[cfg(target_os = "android")] {
279            let title = title.map(|s| s.to_string());
280            let text = text.map(|s| s.to_string());
281            let sub_text = sub_text.map(|s| s.to_string());
282            let share_src = share_src.map(|s| s.clone());
283
284            *self.inner.lock_drop_behavior() = DropBehavior { 
285                title: Some(Box::new(move || title)),
286                text: Some(Box::new(move || text)),
287                sub_text: Some(Box::new(move || sub_text)),
288                share_src: Some(Box::new(move || share_src)),
289                error: false,
290            };
291        }
292    }
293
294    #[always_sync]
295    pub fn set_drop_behavior_to_complete_with(
296        &self,
297        title: impl 'static + Send + FnOnce() -> Option<String>,
298        text: impl 'static + Send + FnOnce() -> Option<String>,
299        sub_text: impl 'static + Send + FnOnce() -> Option<String>,
300        share_src: impl 'static + Send + FnOnce() -> Option<FileUri>,
301    ) {
302        
303        #[cfg(target_os = "android")] {
304            *self.inner.lock_drop_behavior() = DropBehavior { 
305                title: Some(Box::new(title)),
306                text: Some(Box::new(text)),
307                sub_text: Some(Box::new(sub_text)),
308                share_src: Some(Box::new(share_src)),
309                error: false,
310            };
311        }
312    }
313
314    #[always_sync]
315    pub fn set_drop_behavior_to_fail(
316        &self,
317        title: Option<&str>,
318        text: Option<&str>,
319        sub_text: Option<&str>
320    ) {
321
322        #[cfg(target_os = "android")] {
323            let title = title.map(|s| s.to_string());
324            let text = text.map(|s| s.to_string());
325            let sub_text = sub_text.map(|s| s.to_string());
326
327            *self.inner.lock_drop_behavior() = DropBehavior { 
328                title: Some(Box::new(move || title)),
329                text: Some(Box::new(move || text)),
330                sub_text: Some(Box::new(move || sub_text)),
331                share_src: None,
332                error: true,
333            };
334        }
335    }
336
337    #[always_sync]
338    pub fn set_drop_behavior_to_fail_with(
339        &self,
340        title: impl 'static + Send + FnOnce() -> Option<String>,
341        text: impl 'static + Send + FnOnce() -> Option<String>,
342        sub_text: impl 'static + Send + FnOnce() -> Option<String>,
343    ) {
344        
345        #[cfg(target_os = "android")] {
346            *self.inner.lock_drop_behavior() = DropBehavior { 
347                title: Some(Box::new(title)),
348                text: Some(Box::new(text)),
349                sub_text: Some(Box::new(sub_text)),
350                share_src: None,
351                error: true,
352            };
353        }
354    }
355
356    #[maybe_async]
357    pub fn complete(
358        self,
359        title: Option<&str>,
360        text: Option<&str>,
361        sub_text: Option<&str>,
362        share_src: Option<&FileUri>,
363    ) -> Result<()> {
364
365        #[cfg(not(target_os = "android"))] {
366            Ok(())
367        }
368        #[cfg(target_os = "android")] {
369            self.finish_notification(title, text, sub_text, share_src, false).await
370        }
371    }
372
373    #[maybe_async]
374    pub fn fail(
375        self, 
376        title: Option<&str>, 
377        text: Option<&str>,
378        sub_text: Option<&str>,
379    ) -> Result<()> {
380
381        #[cfg(not(target_os = "android"))] {
382            Ok(())
383        }
384        #[cfg(target_os = "android")] { 
385            self.finish_notification(title, text, sub_text, None, true).await
386        }
387    }
388    
389    
390    #[cfg(target_os = "android")] 
391    #[maybe_async]
392    fn finish_notification(
393        mut self,
394        title: Option<&str>, 
395        text: Option<&str>,
396        sub_text: Option<&str>,
397        share_src: Option<&FileUri>,
398        error: bool
399    ) -> Result<()> {
400
401        self.impls().finish_progress_notification(
402            self.inner.id, 
403            self.inner.icon,
404            title,
405            text,
406            sub_text,
407            share_src,
408            error,
409        ).await?;
410            
411        self.inner.is_finished = true;
412        Ok(())
413    }
414
415    #[cfg(target_os = "android")] 
416    #[maybe_async]
417    fn update_notification(&self) -> Result<()> {
418        let state = self.inner.lock_current_state().clone();
419        let (progress, progress_max) = normalize_progress_and_max(state.progress, state.progress_max);
420            
421        self.impls().update_progress_notification(
422            self.inner.id,
423            self.inner.icon, 
424            state.title.as_deref(),
425            state.text.as_deref(), 
426            state.sub_text.as_deref(), 
427            progress,
428            progress_max,
429        ).await?;
430
431        Ok(())
432    }
433}
434
435
436#[cfg(target_os = "android")]
437struct Inner<R: tauri::Runtime> {
438    id: i32,
439    icon: ProgressNotificationIcon,
440    is_finished: bool,
441    drop_behavior: std::sync::Mutex<DropBehavior>,
442    current_state: std::sync::Mutex<CurrentState>,
443    handle: tauri::plugin::PluginHandle<R>,
444}
445
446#[cfg(target_os = "android")]
447struct DropBehavior {
448    title: Option<Box<dyn Send + 'static + FnOnce() -> Option<String>>>,
449    text: Option<Box<dyn Send + 'static + FnOnce() -> Option<String>>>,
450    sub_text: Option<Box<dyn Send + 'static + FnOnce() -> Option<String>>>,
451    share_src: Option<Box<dyn Send + 'static + FnOnce() -> Option<FileUri>>>,
452    error: bool,
453}
454
455#[cfg(target_os = "android")]
456#[derive(Clone)]
457struct CurrentState {
458    title: Option<String>,
459    text: Option<String>,
460    sub_text: Option<String>,
461    progress: Option<u64>,
462    progress_max: Option<u64>,
463}
464
465#[cfg(target_os = "android")]
466impl<R: tauri::Runtime> Inner<R> {
467
468    fn lock_drop_behavior<'a>(&'a self) -> std::sync::MutexGuard<'a, DropBehavior> {
469        self.drop_behavior.lock().unwrap_or_else(|e| e.into_inner())
470    }
471
472    fn lock_current_state<'a>(&'a self) -> std::sync::MutexGuard<'a, CurrentState> {
473        self.current_state.lock().unwrap_or_else(|e| e.into_inner())
474    }
475}
476
477#[cfg(target_os = "android")]
478impl<R: tauri::Runtime> Drop for Inner<R> {
479
480    fn drop(&mut self) {
481        if self.is_finished {
482            return
483        }
484
485        let handle = self.handle.clone();
486        let id = self.id;
487        let icon = self.icon;
488        let (error, title, text, sub_text, share_src) = {
489            let mut d = self.lock_drop_behavior();
490            (d.error, d.title.take(), d.text.take(), d.sub_text.take(), d.share_src.take())
491        };
492
493        tauri::async_runtime::spawn(async move {
494            let impls = impls::AsyncImpls { handle: &handle };
495            impls.finish_progress_notification(
496                id, 
497                icon,
498                title.and_then(|f| f()).as_deref(),
499                text.and_then(|f| f()).as_deref(),
500                sub_text.and_then(|f| f()).as_deref(),
501                share_src.and_then(|f| f()).as_ref(),
502                error
503            ).await.ok();
504        });
505    }
506}
507
508#[cfg(target_os = "android")]
509fn normalize_progress_and_max(
510    progress: Option<u64>,
511    progress_max: Option<u64>,
512) -> (Option<i32>, Option<i32>) {
513
514    let Some((progress, progress_max)) = Option::zip(progress, progress_max) else {
515        return (None, None)
516    };
517    
518    const PROGRESS_MAX: i32 = 100_000;
519
520    if progress_max == 0 {
521        return (Some(0), Some(0)); 
522    }
523
524    let ratio = progress as f64 / progress_max as f64;
525    let scaled_progress = (ratio * PROGRESS_MAX as f64) as i32;
526
527    (Some(i32::min(scaled_progress, PROGRESS_MAX)), Some(PROGRESS_MAX))
528}