@ -2,12 +2,13 @@
use std ::thread ::JoinHandle ;
use std ::thread ::JoinHandle ;
use eframe ::egui ::epaint ::Shadow ;
use eframe ::egui ::epaint ::Shadow ;
use eframe ::egui ::{ self , Color32 , Pos2 , Rect , Response , Stroke };
use eframe ::egui ::{ self , Color32 , Pos2 , Rect , Response , Stroke , Vec2 };
use eyre ::Result ;
use eyre ::Result ;
use rand ::seq ::SliceRandom ;
use rand ::seq ::SliceRandom ;
use super ::super ::platform ::platform_colors ;
use super ::super ::platform ::platform_colors ;
use super ::super ::widgets ::background ::{ shadow_background , AnimatedBackground } ;
use super ::super ::widgets ::background ::{ shadow_background , AnimatedBackground } ;
use super ::Textures ;
use super ::{ StateUIAction , StateUIVariant } ;
use super ::{ StateUIAction , StateUIVariant } ;
use crate ::types ::Config ;
use crate ::types ::Config ;
use crate ::{
use crate ::{
@ -41,6 +42,10 @@ pub struct ImporterUI {
pub done_importing : bool ,
pub done_importing : bool ,
/// Any errors during importing
/// Any errors during importing
pub importer_error : Option < eyre ::Report > ,
pub importer_error : Option < eyre ::Report > ,
/// On macOS, we lack the permission to the mail folder. This can be
/// fixed in preferences. We don't `cfg(...)` this to simplify the implementation
/// with less `cfg(...)`
missing_permissions : bool ,
}
}
impl ImporterUI {
impl ImporterUI {
@ -91,15 +96,16 @@ impl ImporterUI {
progress_divisions ,
progress_divisions ,
done_importing : false ,
done_importing : false ,
importer_error : None ,
importer_error : None ,
missing_permissions : false ,
} )
} )
}
}
}
}
impl StateUIVariant for ImporterUI {
impl StateUIVariant for ImporterUI {
fn update_panel ( & mut self , ctx : & egui ::CtxRef ) -> StateUIAction {
fn update_panel ( & mut self , ctx : & egui ::CtxRef , textures : & Option < Textures > ) -> StateUIAction {
egui ::CentralPanel ::default ( )
egui ::CentralPanel ::default ( )
. frame ( egui ::containers ::Frame ::none ( ) )
. frame ( egui ::containers ::Frame ::none ( ) )
. show ( ctx , | ui | {
. show ( ctx , | ui | {
ui . add ( | ui : & mut egui ::Ui | self . ui ( ui )) ;
ui . add ( | ui : & mut egui ::Ui | self . ui ( ui , textures )) ;
} ) ;
} ) ;
// If we generated an action above, return it
// If we generated an action above, return it
match ( self . importer_error . take ( ) , self . done_importing ) {
match ( self . importer_error . take ( ) , self . done_importing ) {
@ -116,7 +122,7 @@ impl StateUIVariant for ImporterUI {
}
}
impl ImporterUI {
impl ImporterUI {
fn ui ( & mut self , ui : & mut egui ::Ui ) -> Response {
fn ui ( & mut self , ui : & mut egui ::Ui , textures : & Option < Textures > ) -> Response {
// The speed with which we initially scale down.
// The speed with which we initially scale down.
self . intro_timer + = ( ui . input ( ) . unstable_dt as f64 ) * 2.0 ;
self . intro_timer + = ( ui . input ( ) . unstable_dt as f64 ) * 2.0 ;
let growth = self . intro_timer . clamp ( 0.0 , 1.0 ) ;
let growth = self . intro_timer . clamp ( 0.0 , 1.0 ) ;
@ -124,7 +130,21 @@ impl ImporterUI {
let available = ui . available_size ( ) ;
let available = ui . available_size ( ) ;
let ( label , progress , writing , done ) = match self . handle_adapter ( ) {
let ( label , progress , writing , done ) = match self . handle_adapter ( ) {
Ok ( n ) = > n ,
Ok ( state ) = > {
#[ cfg(target_os = " macos " ) ]
if state . missing_permissions {
self . missing_permissions = true ;
}
let InternalAdapterState {
label ,
progress ,
writing ,
done ,
..
} = state ;
( label , progress , writing , done )
}
Err ( e ) = > {
Err ( e ) = > {
// Generate a response signifying we're done - as there was an error
// Generate a response signifying we're done - as there was an error
let response = ( format! ( "Error {}" , & e ) , 1.0 , false , true ) ;
let response = ( format! ( "Error {}" , & e ) , 1.0 , false , true ) ;
@ -137,7 +157,7 @@ impl ImporterUI {
self . importer_error = Some ( error ) ;
self . importer_error = Some ( error ) ;
}
}
if done {
if done & & ! self . missing_permissions {
// if we're done, the join handle should not lock
// if we're done, the join handle should not lock
if let Some ( handle ) = self . handle . take ( ) {
if let Some ( handle ) = self . handle . take ( ) {
self . importer_error = handle . join ( ) . ok ( ) . map ( | e | e . err ( ) ) . flatten ( ) ;
self . importer_error = handle . join ( ) . ok ( ) . map ( | e | e . err ( ) ) . flatten ( ) ;
@ -149,13 +169,15 @@ impl ImporterUI {
let n = n . min ( self . progress_blocks . len ( ) ) ;
let n = n . min ( self . progress_blocks . len ( ) ) ;
let slice = & self . progress_blocks [ 0 .. n ] ;
let slice = & self . progress_blocks [ 0 .. n ] ;
AnimatedBackground {
if ! self . missing_permissions {
divisions : self . animation_divisions ,
AnimatedBackground {
animate_progress : Some ( ( slice , self . progress_divisions ) ) ,
divisions : self . animation_divisions ,
timer : & mut self . timer ,
animate_progress : Some ( ( slice , self . progress_divisions ) ) ,
offset_counter : & mut self . offset_counter ,
timer : & mut self . timer ,
offset_counter : & mut self . offset_counter ,
}
. draw_background ( ui , available ) ;
}
}
. draw_background ( ui , available ) ;
let desired_height = 370.0 - ( 220.0 * growth ) as f32 ;
let desired_height = 370.0 - ( 220.0 * growth ) as f32 ;
let desired_size = egui ::vec2 ( 330.0 , desired_height ) ;
let desired_size = egui ::vec2 ( 330.0 , desired_height ) ;
@ -174,41 +196,93 @@ impl ImporterUI {
let colors = platform_colors ( ) ;
let colors = platform_colors ( ) ;
// Draw a backround with a shadow
// Draw a backround with a shadow
shadow_background (
if self . missing_permissions {
ui . painter ( ) ,
self . permission_ui ( ui , textures )
paint_rect ,
} else {
colors . window_background_dark ,
shadow_background (
Stroke ::new ( 1.0 , Color32 ::from_gray ( 90 ) ) ,
ui . painter ( ) ,
12.0 ,
paint_rect ,
Shadow ::big_dark ( ) ,
colors . window_background_dark ,
) ;
Stroke ::new ( 1.0 , Color32 ::from_gray ( 90 ) ) ,
12.0 ,
Shadow ::big_dark ( ) ,
) ;
ui . allocate_ui_at_rect ( center , | ui | {
ui . allocate_ui_at_rect ( center , | ui | {
ui . centered_and_justified ( | ui | {
ui . centered_and_justified ( | ui | {
ui . vertical_centered_justified ( | ui | {
if self . missing_permissions {
ui . heading ( "Import in Progress" ) ;
ui . add_space ( 10.0 ) ;
if writing {
let bar = egui ::widgets ::ProgressBar ::new ( 1.0 ) . animate ( false ) ;
ui . add ( bar ) ;
let bar = egui ::widgets ::ProgressBar ::new ( progress ) . animate ( true ) ;
ui . add ( bar ) ;
} else {
} else {
let bar = egui ::widgets ::ProgressBar ::new ( progress ) . animate ( true ) ;
self . default_ui ( ui , writing , progress , label ) ;
ui . add ( bar ) ;
ui . add_space ( 20.0 ) ;
}
}
ui . small ( label ) ;
} )
} ) ;
} )
} )
. response
}
}
fn default_ui (
& mut self ,
ui : & mut egui ::Ui ,
writing : bool ,
progress : f32 ,
label : String ,
) -> Response {
ui . vertical_centered_justified ( | ui | {
ui . heading ( "Import in Progress" ) ;
ui . add_space ( 10.0 ) ;
if writing {
let bar = egui ::widgets ::ProgressBar ::new ( 1.0 ) . animate ( false ) ;
ui . add ( bar ) ;
let bar = egui ::widgets ::ProgressBar ::new ( progress ) . animate ( true ) ;
ui . add ( bar ) ;
} else {
let bar = egui ::widgets ::ProgressBar ::new ( progress ) . animate ( true ) ;
ui . add ( bar ) ;
ui . add_space ( 20.0 ) ;
}
ui . small ( label ) ;
} )
} )
. response
. response
}
}
#[ cfg(target_os = " macos " ) ]
fn permission_ui ( & mut self , ui : & mut egui ::Ui , textures : & Option < Textures > ) -> Response {
let available = ui . available_size ( ) ;
ui . vertical_centered_justified ( | ui | {
ui . set_width ( available . x - 50.0 ) ;
ui . add_space ( 25.0 ) ;
if let Some ( textures ) = textures {
let s = textures . missing_permissions_image . 0 ;
let s = Vec2 ::new ( s . x / 4.5 , s . y / 4.5 ) ;
ui . image ( textures . missing_permissions_image . 1 , s ) ;
}
ui . heading ( "Missing Mail Permissions" ) ;
ui . add_space ( 10.0 ) ;
ui . label ( "You need to give `Postsack` Full Disk Access permissions so that it can access your mails." ) ;
ui . label ( "You can do this in the System Preferences. See the following Screenshot." ) ;
ui . add_space ( 10.0 ) ;
ui . label ( "Afterwards, restart Postsack" ) ;
ui . add_space ( 5.0 ) ;
if ui . add_sized ( ( 100.0 , 30.0 ) , egui ::Button ::new ( "Quit" ) ) . clicked ( ) {
std ::process ::exit ( 0 ) ;
}
} ) . response
}
}
struct InternalAdapterState {
label : String ,
progress : f32 ,
writing : bool ,
done : bool ,
#[ cfg(target_os = " macos " ) ]
missing_permissions : bool ,
}
}
impl ImporterUI {
impl ImporterUI {
/// Returns the current label, the progress (0-1), writing? (true), and done? (true)
/// Returns the current the adapter state.
fn handle_adapter ( & mut self ) -> Result < ( String , f32 , bool , bool ) > {
fn handle_adapter ( & mut self ) -> Result < InternalAdapterState > {
let ( mut label , progress , writing ) = {
let ( mut label , progress , writing ) = {
let write = self . adapter . write_count ( ) ? ;
let write = self . adapter . write_count ( ) ? ;
if write . count > 0 {
if write . count > 0 {
@ -227,10 +301,24 @@ impl ImporterUI {
}
}
} ;
} ;
let State { done , finishing } = self . adapter . finished ( ) ? ;
#[ cfg(target_os = " macos " ) ]
let State {
done ,
finishing ,
#[ cfg(target_os = " macos " ) ]
missing_permissions ,
} = self . adapter . finished ( ) ? ;
if finishing {
if finishing {
label = format! ( "Finishing Up" ) ;
label = format! ( "Finishing Up" ) ;
}
}
Ok ( ( label , progress , writing , done ) )
Ok ( InternalAdapterState {
label ,
progress ,
writing ,
done ,
#[ cfg(target_os = " macos " ) ]
missing_permissions ,
} )
}
}
}
}