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