2024-01-13 15:32:52 +01:00
mod jnovel ;
2024-01-13 16:20:03 +01:00
use iced ::{ Application , Command , Element , executor , Length , Padding , Pixels , Settings , Subscription , subscription , Theme , time } ;
2024-01-13 15:32:52 +01:00
use iced ::widget ::{ button , column , text , Column , container , toggler ::{ Toggler } , pick_list , row , Row , text_input } ;
use std ::fmt ::{ Display , Formatter } ;
2024-01-13 16:20:03 +01:00
use chrono ::{ Local } ;
2024-01-13 15:32:52 +01:00
use serde ::{ Deserialize } ;
#[ derive(Debug, Clone) ]
struct CalibreWebImporter {
jnc : jnovel ::State ,
settings : AppSettings ,
current_menu : Menu ,
previous_menu : Menu ,
2024-01-11 11:41:04 +01:00
}
2024-01-13 15:32:52 +01:00
impl Default for CalibreWebImporter {
fn default ( ) -> Self {
Self {
jnc : jnovel ::State ::default ( ) ,
settings : AppSettings ::default ( ) ,
2024-01-13 16:16:34 +01:00
current_menu : Menu ::JNovel ,
previous_menu : Menu ::JNovel ,
2024-01-13 15:32:52 +01:00
}
}
}
#[ derive(Debug, Clone) ]
struct AppSettings {
theme : AppTheme ,
}
impl Default for AppSettings {
fn default ( ) -> Self {
Self {
theme : AppTheme ::System
}
}
}
#[ derive(Debug, Clone, Copy) ]
enum Menu {
Settings ,
JNovel ,
Calibre
}
#[ derive(Debug, Clone, Eq, PartialEq) ]
enum AppTheme {
Light ,
Dark ,
System
}
impl Display for AppTheme {
fn fmt ( & self , f : & mut Formatter < '_ > ) -> std ::fmt ::Result {
let text = match self {
AppTheme ::Light = > " Light " ,
AppTheme ::Dark = > " Dark " ,
AppTheme ::System = > " System " ,
} ;
write! ( f , " {text} " )
}
}
impl AppTheme {
fn get_iced_theme ( & self ) -> Theme {
match self {
AppTheme ::Light = > Theme ::Light ,
AppTheme ::Dark = > Theme ::Dark ,
AppTheme ::System = > system_theme_mode ( ) . get_iced_theme ( ) ,
}
}
}
#[ derive(Debug, Clone) ]
enum Message {
JncAction ( jnovel ::Message ) ,
SetTheme ( AppTheme ) ,
Navigate ( Menu ) ,
}
impl Application for CalibreWebImporter {
type Executor = executor ::Default ;
type Message = Message ;
type Theme = Theme ;
type Flags = ( ) ;
fn new ( _flags : Self ::Flags ) -> ( Self , Command < Message > ) {
(
Self {
settings : AppSettings {
theme : AppTheme ::System ,
.. Default ::default ( )
} ,
.. Default ::default ( )
} ,
Command ::none ( )
)
}
fn title ( & self ) -> String {
String ::from ( " Calibre Web Importer " )
}
fn update ( & mut self , message : Message ) -> Command < Message > {
match message {
Message ::JncAction ( jnc_message ) = > {
match jnc_message {
jnovel ::Message ::Login = > {
self . jnc . login_state = jnovel ::LoginState ::LoggingIn ;
let _ = open ::that ( " https://j-novel.club/user/otp " ) ;
2024-01-13 16:16:34 +01:00
Command ::perform ( jnovel ::State ::login ( ) , | status | Message ::JncAction ( jnovel ::Message ::LoginState ( status ) ) )
2024-01-13 15:32:52 +01:00
}
jnovel ::Message ::Logout = > {
println! ( " Logging out from J-Novel Club " ) ;
self . jnc . token = None ;
self . jnc . login_state = jnovel ::LoginState ::LoggedOut ;
2024-01-13 16:20:03 +01:00
Command ::perform ( jnovel ::State ::logout ( self . jnc . clone ( ) ) , | ( ) | Message ::JncAction ( jnovel ::Message ::LoginState ( jnovel ::LoginState ::LoggedOut ) ) )
2024-01-13 15:32:52 +01:00
}
jnovel ::Message ::LoginState ( status ) = > {
if let jnovel ::LoginState ::Success ( token ) = status {
self . jnc . token = Some ( token ) ;
self . jnc . login_state = status ;
if self . jnc . library_state = = jnovel ::LibraryState ::Unloaded {
2024-01-13 16:16:34 +01:00
Command ::perform ( jnovel ::State ::load_library ( self . jnc . clone ( ) ) , | status | Message ::JncAction ( jnovel ::Message ::LibraryState ( status ) ) )
2024-01-13 15:32:52 +01:00
}
else {
Command ::none ( )
}
}
else {
self . jnc . login_state = status ;
Command ::none ( )
}
}
jnovel ::Message ::LibraryState ( status ) = > {
match status {
jnovel ::LibraryState ::Unloaded = > {
Command ::none ( )
}
jnovel ::LibraryState ::Loading = > {
Command ::none ( )
}
jnovel ::LibraryState ::Loaded = > {
Command ::none ( )
}
jnovel ::LibraryState ::Error ( err ) = > {
Command ::none ( )
}
}
}
}
} ,
Message ::Navigate ( menu ) = > {
self . previous_menu = self . current_menu ;
self . current_menu = menu ;
Command ::none ( )
}
Message ::SetTheme ( theme ) = > {
self . settings . theme = theme ;
Command ::none ( )
} ,
}
}
fn view ( & self ) -> Element < Message > {
// TODO: add collapsible sidebar for navigation
let menu_content = match self . current_menu {
Menu ::Settings = > {
let theme_options = vec! [ AppTheme ::Light , AppTheme ::Dark , AppTheme ::System ] ;
let theme_selector = pick_list ( theme_options , Some ( self . settings . theme . clone ( ) ) , Message ::SetTheme ) ;
column ( vec! [
theme_selector . into ( ) ,
button ( " Close " ) . on_press ( Message ::Navigate ( self . previous_menu ) ) . into ( )
] )
} ,
Menu ::JNovel = > {
let jnc_login_button : Element < _ , _ > = match & self . jnc . login_state {
jnovel ::LoginState ::Success ( _ ) = > Element ::from ( button ( " Logout " ) . on_press ( Message ::JncAction ( jnovel ::Message ::Logout ) ) ) ,
jnovel ::LoginState ::LoggedOut = > Element ::from ( button ( " Login " ) . on_press ( Message ::JncAction ( jnovel ::Message ::Login ) ) ) ,
jnovel ::LoginState ::LoggingIn = > Element ::from ( text ( " Logging in " ) ) ,
jnovel ::LoginState ::AwaitingConfirmation ( code , start ) = > Element ::from ( text ( format! ( " Login Code: {} . Expiring: {} s " , code . otp , 600 - ( Local ::now ( ) - start ) . num_seconds ( ) ) ) ) ,
2024-01-13 16:16:34 +01:00
jnovel ::LoginState ::Error ( err ) = > Element ::from ( row! [
button ( " Error " ) . on_press ( Message ::JncAction ( jnovel ::Message ::LoginState ( jnovel ::LoginState ::LoggedOut ) ) ) ,
text ( err ) ,
] ) ,
2024-01-13 15:32:52 +01:00
} ;
column ( vec! [ Element ::from (
row ( vec! [
jnc_login_button . into ( )
] )
) ] )
} ,
Menu ::Calibre = > {
column ( vec! [
text ( " Not Implemented " ) . into ( )
] )
}
}
. width ( Length ::Fill )
. padding ( Padding ::new ( 10.0 ) ) ;
let mut menu = Column ::new ( ) ;
menu = menu
. push ( button ( " Settings " ) . on_press ( Message ::Navigate ( Menu ::Settings ) ) )
. push ( button ( " JNC " ) . on_press ( Message ::Navigate ( Menu ::JNovel ) ) )
. push ( button ( " Calibre " ) . on_press ( Message ::Navigate ( Menu ::Calibre ) ) )
. width ( Length ::Shrink )
. height ( Length ::Fill )
. padding ( Padding ::new ( 10.0 ) )
. spacing ( Pixels ( 10.0 ) ) ;
let app_content : Row < Message > = row ( vec! [ menu . into ( ) , menu_content . into ( ) ] )
. width ( Length ::Fill )
. spacing ( Pixels ( 10.0 ) ) ;
container ( app_content )
. width ( Length ::Fill )
. height ( Length ::Fill )
. center_x ( )
. center_y ( )
. into ( )
}
fn theme ( & self ) -> Self ::Theme {
self . settings . theme . get_iced_theme ( )
}
fn subscription ( & self ) -> Subscription < Self ::Message > {
let system_theme = match self . settings . theme = = AppTheme ::System {
true = > time ::every ( time ::Duration ::from_secs ( 1 ) )
. map ( | _ | Message ::SetTheme ( AppTheme ::System ) ) ,
false = > Subscription ::none ( )
} ;
let jnc_otp_check = match self . jnc . login_state . clone ( ) {
jnovel ::LoginState ::AwaitingConfirmation ( otp , start ) = > {
subscription ::unfold (
" otp_login " ,
jnovel ::OtpState ::Starting ( otp , start ) ,
| state | async move {
jnovel ::Otp ::check ( state ) . await
}
)
. map ( | state | Message ::JncAction ( jnovel ::Message ::LoginState ( state ) ) )
} ,
_ = > Subscription ::none ( ) ,
} ;
Subscription ::batch ( [
system_theme ,
jnc_otp_check
] )
}
}
fn system_theme_mode ( ) -> AppTheme {
match dark_light ::detect ( ) {
dark_light ::Mode ::Light | dark_light ::Mode ::Default = > AppTheme ::Light ,
dark_light ::Mode ::Dark = > AppTheme ::Dark ,
}
}
#[ tokio::main ]
async fn main ( ) -> iced ::Result {
CalibreWebImporter ::run ( Settings ::default ( ) )
}