tauri_plugin_android_fs/api/
progress_notification_guard.rs1use 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 start_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 error: true,
61 }),
62 update_throttler: Throttler::with_delay(std::time::Duration::from_millis(1100)),
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 ) {
276
277 #[cfg(target_os = "android")] {
278 let title = title.map(|s| s.to_string());
279 let text = text.map(|s| s.to_string());
280 let sub_text = sub_text.map(|s| s.to_string());
281
282 *self.inner.lock_drop_behavior() = DropBehavior {
283 title: Some(Box::new(move |_| title)),
284 text: Some(Box::new(move |_| text)),
285 sub_text: Some(Box::new(move |_| sub_text)),
286 error: false,
287 };
288 }
289 }
290
291 #[always_sync]
292 pub fn set_drop_behavior_to_fail(
293 &self,
294 title: Option<&str>,
295 text: Option<&str>,
296 sub_text: Option<&str>
297 ) {
298
299 #[cfg(target_os = "android")] {
300 let title = title.map(|s| s.to_string());
301 let text = text.map(|s| s.to_string());
302 let sub_text = sub_text.map(|s| s.to_string());
303
304 *self.inner.lock_drop_behavior() = DropBehavior {
305 title: Some(Box::new(move |_| title)),
306 text: Some(Box::new(move |_| text)),
307 sub_text: Some(Box::new(move |_| sub_text)),
308 error: true,
309 };
310 }
311 }
312
313 #[always_sync]
314 #[allow(dead_code)]
315 #[cfg(target_os = "android")]
316 pub(crate) fn set_drop_behavior_to_fail_with(
317 &self,
318 title: impl 'static + Send + Sync + FnOnce(&CurrentState) -> Option<String>,
319 text: impl 'static + Send + Sync + FnOnce(&CurrentState) -> Option<String>,
320 sub_text: impl 'static + Send + Sync + FnOnce(&CurrentState) -> Option<String>,
321 ) {
322
323 #[cfg(target_os = "android")] {
324 *self.inner.lock_drop_behavior() = DropBehavior {
325 title: Some(Box::new(title)),
326 text: Some(Box::new(text)),
327 sub_text: Some(Box::new(sub_text)),
328 error: true,
329 };
330 }
331 }
332
333 #[maybe_async]
334 pub fn complete(
335 self,
336 title: Option<&str>,
337 text: Option<&str>,
338 sub_text: Option<&str>,
339 ) -> Result<()> {
340
341 #[cfg(not(target_os = "android"))] {
342 Ok(())
343 }
344 #[cfg(target_os = "android")] {
345 self.finish_notification(title, text, sub_text, false).await
346 }
347 }
348
349 #[maybe_async]
350 pub fn fail(
351 self,
352 title: Option<&str>,
353 text: Option<&str>,
354 sub_text: Option<&str>,
355 ) -> Result<()> {
356
357 #[cfg(not(target_os = "android"))] {
358 Ok(())
359 }
360 #[cfg(target_os = "android")] {
361 self.finish_notification(title, text, sub_text, true).await
362 }
363 }
364
365
366 #[cfg(target_os = "android")]
367 #[maybe_async]
368 fn finish_notification(
369 mut self,
370 title: Option<&str>,
371 text: Option<&str>,
372 sub_text: Option<&str>,
373 error: bool
374 ) -> Result<()> {
375
376 self.impls().finish_progress_notification(
377 self.inner.id,
378 self.inner.icon,
379 title,
380 text,
381 sub_text,
382 error
383 ).await?;
384
385 self.inner.is_finished = true;
386 Ok(())
387 }
388
389 #[cfg(target_os = "android")]
390 #[maybe_async]
391 fn update_notification(&self) -> Result<()> {
392 if self.inner.update_throttler.check_and_mark() {
393 let state = self.inner.lock_current_state().clone();
394 let (progress, progress_max) = normalize_progress_and_max(state.progress, state.progress_max);
395
396 self.impls().update_progress_notification(
397 self.inner.id,
398 self.inner.icon,
399 state.title.as_deref(),
400 state.text.as_deref(),
401 state.sub_text.as_deref(),
402 progress,
403 progress_max,
404 ).await?;
405 }
406
407 Ok(())
408 }
409}
410
411
412#[cfg(target_os = "android")]
413struct Inner<R: tauri::Runtime> {
414 id: i32,
415 icon: ProgressNotificationIcon,
416 is_finished: bool,
417 drop_behavior: std::sync::Mutex<DropBehavior>,
418 current_state: std::sync::Mutex<CurrentState>,
419 update_throttler: Throttler,
420 handle: tauri::plugin::PluginHandle<R>,
421}
422
423#[cfg(target_os = "android")]
424struct DropBehavior {
425 title: Option<Box<dyn Sync + Send + 'static + FnOnce(&CurrentState) -> Option<String>>>,
426 text: Option<Box<dyn Sync + Send + 'static + FnOnce(&CurrentState) -> Option<String>>>,
427 sub_text: Option<Box<dyn Sync + Send + 'static + FnOnce(&CurrentState) -> Option<String>>>,
428 error: bool,
429}
430
431#[cfg(target_os = "android")]
432#[derive(Clone)]
433pub(crate) struct CurrentState {
434 pub title: Option<String>,
435 pub text: Option<String>,
436 pub sub_text: Option<String>,
437 pub progress: Option<u64>,
438 pub progress_max: Option<u64>,
439}
440
441#[cfg(target_os = "android")]
442impl<R: tauri::Runtime> Inner<R> {
443
444 fn lock_drop_behavior<'a>(&'a self) -> std::sync::MutexGuard<'a, DropBehavior> {
445 self.drop_behavior.lock().unwrap_or_else(|e| e.into_inner())
446 }
447
448 fn lock_current_state<'a>(&'a self) -> std::sync::MutexGuard<'a, CurrentState> {
449 self.current_state.lock().unwrap_or_else(|e| e.into_inner())
450 }
451}
452
453#[cfg(target_os = "android")]
454impl<R: tauri::Runtime> Drop for Inner<R> {
455
456 fn drop(&mut self) {
457 if self.is_finished {
458 return
459 }
460
461 let handle = self.handle.clone();
462 let id = self.id;
463 let icon = self.icon;
464 let current_state = self.lock_current_state().clone();
465 let (error, title, text, sub_text) = {
466 let mut d = self.lock_drop_behavior();
467 (d.error, d.title.take(), d.text.take(), d.sub_text.take())
468 };
469
470 tauri::async_runtime::spawn(async move {
471 let impls = impls::AsyncImpls { handle: &handle };
472 impls.finish_progress_notification(
473 id,
474 icon,
475 title.and_then(|f| f(¤t_state)).as_deref(),
476 text.and_then(|f| f(¤t_state)).as_deref(),
477 sub_text.and_then(|f| f(¤t_state)).as_deref(),
478 error
479 ).await.ok();
480 });
481 }
482}
483
484#[cfg(target_os = "android")]
485struct Throttler {
486 next: std::sync::Mutex<std::time::Instant>,
487 interval: std::time::Duration,
488}
489
490#[cfg(target_os = "android")]
491impl Throttler {
492
493 pub fn with_delay(interval: std::time::Duration) -> Self {
494 Self {
495 next: std::sync::Mutex::new(std::time::Instant::now() + interval),
496 interval,
497 }
498 }
499
500 pub fn check_and_mark(&self) -> bool {
501 let mut next = self.next.lock().unwrap_or_else(|e| e.into_inner());
502 let now = std::time::Instant::now();
503
504 if now < *next {
505 return false
506 }
507
508 *next = now + self.interval;
509 true
510 }
511}
512
513#[cfg(target_os = "android")]
514fn normalize_progress_and_max(
515 progress: Option<u64>,
516 progress_max: Option<u64>,
517) -> (Option<i32>, Option<i32>) {
518
519 let Some((progress, progress_max)) = Option::zip(progress, progress_max) else {
520 return (None, None)
521 };
522
523 const PROGRESS_MAX: i32 = 100_000;
524
525 if progress_max == 0 {
526 return (Some(0), Some(0));
527 }
528
529 let ratio = progress as f64 / progress_max as f64;
530 let scaled_progress = (ratio * PROGRESS_MAX as f64) as i32;
531
532 (Some(i32::min(scaled_progress, PROGRESS_MAX)), Some(PROGRESS_MAX))
533}