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: 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}