mal/handlers/
mod.rs

1mod anime;
2pub mod common;
3mod display_block;
4mod help;
5mod input;
6mod option;
7mod user;
8use crate::api::model::{AnimeRankingType, MangaRankingType, Media};
9use crate::app::{
10    ActiveBlock, ActiveDisplayBlock, App, Data, SelectedSearchTab, TopThreeBlock,
11    ANIME_OPTIONS_RANGE, GENERAL_OPTIONS_RANGE, USER_OPTIONS_RANGE,
12};
13use crate::event::Key;
14use crate::network::IoEvent;
15
16use common::get_lowercase_key;
17pub use input::handler as input_handler;
18use log::warn;
19
20pub fn handle_app(key: Key, app: &mut App) {
21    // First handle any global event and then move to block event
22    if app.exit_confirmation_popup {
23        if key == Key::Esc || key == Key::Char('n') {
24            app.exit_confirmation_popup = false;
25            return;
26        } else if key == Key::Enter || get_lowercase_key(key) == Key::Char('y') {
27            app.exit_flag = true;
28            return;
29        }
30    }
31    match key {
32        Key::Esc => app.load_previous_route(),
33
34        _ if key == app.app_config.keys.next_state => app.load_next_route(),
35
36        _ if key == app.app_config.keys.help => {
37            app.active_display_block = ActiveDisplayBlock::Help;
38        }
39
40        _ if key == app.app_config.keys.search => {
41            app.input = vec![];
42            app.input_idx = 0;
43            app.input_cursor_position = 0;
44            app.active_block = ActiveBlock::Input;
45        }
46
47        _ => handle_block_events(key, app),
48    }
49}
50
51// Handler event for the current active block
52fn handle_block_events(key: Key, app: &mut App) {
53    let current_block = app.active_block;
54    match current_block {
55        ActiveBlock::Input => input::handler(key, app),
56
57        ActiveBlock::Anime => anime::handler(key, app),
58
59        ActiveBlock::User => user::handler(key, app),
60
61        ActiveBlock::Option => option::handler(key, app),
62
63        ActiveBlock::Error => {}
64
65        ActiveBlock::TopThree => display_block::top_three::handler(key, app),
66
67        ActiveBlock::DisplayBlock => display_block::handle_display_block(key, app),
68    }
69}
70
71pub fn handle_tab(app: &mut App) {
72    match app.active_block {
73        ActiveBlock::Input => {
74            // todo: anything else to handle ? like when exiting the input state.
75            app.library.selected_index = ANIME_OPTIONS_RANGE.start;
76            app.active_block = ActiveBlock::Anime;
77        }
78
79        ActiveBlock::Anime => {
80            app.library.selected_index = USER_OPTIONS_RANGE.start;
81            app.active_block = ActiveBlock::User;
82        }
83
84        ActiveBlock::User => {
85            app.library.selected_index = GENERAL_OPTIONS_RANGE.start;
86            app.active_block = ActiveBlock::Option;
87        }
88
89        ActiveBlock::Option => {
90            app.library.selected_index = 10; // out of range to not display anything
91            app.active_block = ActiveBlock::TopThree;
92        }
93
94        ActiveBlock::TopThree => {
95            app.active_block = ActiveBlock::DisplayBlock;
96        }
97
98        ActiveBlock::DisplayBlock => {
99            if !app.popup {
100                app.active_block = ActiveBlock::Input;
101            }
102        }
103        _ => {}
104    }
105}
106
107pub fn handle_back_tab(app: &mut App) {
108    match app.active_block {
109        ActiveBlock::Input => {
110            app.library.selected_index = 10; // out of range to not display anything
111            app.active_block = ActiveBlock::DisplayBlock;
112        }
113
114        ActiveBlock::DisplayBlock => {
115            if !app.popup {
116                app.active_block = ActiveBlock::TopThree;
117            }
118        }
119
120        ActiveBlock::TopThree => {
121            app.library.selected_index = GENERAL_OPTIONS_RANGE.end;
122            app.active_block = ActiveBlock::Option;
123        }
124
125        ActiveBlock::Option => {
126            app.library.selected_index = USER_OPTIONS_RANGE.end;
127            app.active_block = ActiveBlock::User;
128        }
129
130        ActiveBlock::User => {
131            app.library.selected_index = ANIME_OPTIONS_RANGE.end;
132            app.active_block = ActiveBlock::Anime;
133        }
134
135        ActiveBlock::Anime => {
136            app.library.selected_index = 10; // out of range to not display anything
137            app.active_block = ActiveBlock::Input;
138        }
139        _ => {}
140    }
141}
142
143pub fn is_data_available(
144    app: &App,
145    data: &Data,
146    block: ActiveDisplayBlock,
147) -> (bool, Option<usize>) {
148    for (i, route) in app.navigator.data.iter().enumerate() {
149        if route.1.block == block
150            && route.1.data.is_some()
151            && std::mem::discriminant(data)
152                == std::mem::discriminant(route.1.data.as_ref().unwrap())
153        {
154            return (true, Some(i));
155        }
156    }
157    (false, None)
158}
159
160pub fn get_media_detail_page(app: &mut App) {
161    let index = app.search_results.selected_display_card_index.unwrap_or(0)
162        + app.start_card_list_index as usize;
163
164    match app.active_block {
165        ActiveBlock::DisplayBlock => {
166            match app.active_display_block {
167                ActiveDisplayBlock::AnimeRanking => {
168                    let data = app.anime_ranking_data.as_ref().unwrap().data.get(index);
169
170                    if let Some(data) = data {
171                        let (is_data_available, is_next, index) =
172                            is_media_data_available(app, &Media::Anime(&data.node));
173                        if is_next {
174                            app.load_next_route();
175                            return;
176                        }
177                        if is_data_available {
178                            app.load_route(index.unwrap());
179                        } else {
180                            app.active_display_block = ActiveDisplayBlock::Loading;
181                            app.dispatch(IoEvent::GetAnime(data.node.id));
182                        }
183                        app.active_block = ActiveBlock::DisplayBlock;
184                    }
185                }
186
187                ActiveDisplayBlock::MangaRanking => {
188                    let data = app.manga_ranking_data.as_ref().unwrap().data.get(index);
189
190                    if let Some(data) = data {
191                        let (is_data_available, is_next, index) =
192                            is_media_data_available(app, &Media::Manga(&data.node));
193                        if is_next {
194                            app.load_next_route();
195                            return;
196                        }
197                        if is_data_available {
198                            app.load_route(index.unwrap());
199                        } else {
200                            app.active_display_block = ActiveDisplayBlock::Loading;
201                            app.dispatch(IoEvent::GetManga(data.node.id));
202                        }
203                        app.active_block = ActiveBlock::DisplayBlock;
204                    }
205                }
206
207                ActiveDisplayBlock::SearchResultBlock => match app.search_results.selected_tab {
208                    SelectedSearchTab::Anime => {
209                        let data = app.search_results.anime.as_ref().unwrap().data.get(index);
210
211                        if let Some(data) = data {
212                            let (is_data_available, is_next, index) =
213                                is_media_data_available(app, &Media::Anime(&data.node));
214                            if is_next {
215                                app.load_next_route();
216                                return;
217                            }
218                            if is_data_available {
219                                app.load_route(index.unwrap());
220                            } else {
221                                app.active_display_block = ActiveDisplayBlock::Loading;
222                                app.dispatch(IoEvent::GetAnime(data.node.id));
223                            }
224                            app.active_block = ActiveBlock::DisplayBlock;
225                        }
226                    }
227                    SelectedSearchTab::Manga => {
228                        let data = app.search_results.manga.as_ref().unwrap().data.get(index);
229
230                        if let Some(data) = data {
231                            let (is_data_available, is_next, index) =
232                                is_media_data_available(app, &Media::Manga(&data.node));
233                            if is_next {
234                                app.load_next_route();
235                                return;
236                            }
237                            if is_data_available {
238                                app.load_route(index.unwrap());
239                            } else {
240                                app.active_display_block = ActiveDisplayBlock::Loading;
241                                app.dispatch(IoEvent::GetManga(data.node.id));
242                            }
243                            app.active_block = ActiveBlock::DisplayBlock;
244                        }
245                    }
246                },
247
248                ActiveDisplayBlock::Suggestions => {
249                    let data = app.search_results.anime.as_ref().unwrap().data.get(index);
250
251                    if let Some(data) = data {
252                        let (is_data_available, is_next, index) =
253                            is_media_data_available(app, &Media::Anime(&data.node));
254                        if is_next {
255                            app.load_next_route();
256                            return;
257                        }
258                        if is_data_available {
259                            app.load_route(index.unwrap());
260                        } else {
261                            app.active_display_block = ActiveDisplayBlock::Loading;
262                            app.dispatch(IoEvent::GetAnime(data.node.id));
263                        }
264                        app.active_block = ActiveBlock::DisplayBlock;
265                    }
266                }
267
268                ActiveDisplayBlock::UserAnimeList => {
269                    let data = app.search_results.anime.as_ref().unwrap().data.get(index);
270
271                    if let Some(data) = data {
272                        let (is_data_available, is_next, index) =
273                            is_media_data_available(app, &Media::Anime(&data.node));
274                        if is_next {
275                            app.load_next_route();
276                            return;
277                        }
278                        if is_data_available {
279                            app.load_route(index.unwrap());
280                        } else {
281                            app.active_display_block = ActiveDisplayBlock::Loading;
282                            app.dispatch(IoEvent::GetAnime(data.node.id));
283                        }
284                        app.active_block = ActiveBlock::DisplayBlock;
285                    }
286                }
287
288                ActiveDisplayBlock::UserMangaList => {
289                    let data = app.search_results.manga.as_ref().unwrap().data.get(index);
290
291                    if let Some(data) = data {
292                        let (is_data_available, is_next, index) =
293                            is_media_data_available(app, &Media::Manga(&data.node));
294                        if is_next {
295                            app.load_next_route();
296                            return;
297                        }
298                        if is_data_available {
299                            app.load_route(index.unwrap());
300                        } else {
301                            app.active_display_block = ActiveDisplayBlock::Loading;
302                            app.dispatch(IoEvent::GetManga(data.node.id));
303                        }
304                        app.active_block = ActiveBlock::DisplayBlock;
305                    }
306                }
307
308                ActiveDisplayBlock::Seasonal => {
309                    let data = app.search_results.anime.as_ref().unwrap().data.get(index);
310
311                    if let Some(data) = data {
312                        let (is_data_available, is_next, index) =
313                            is_media_data_available(app, &Media::Anime(&data.node));
314                        if is_next {
315                            app.load_next_route();
316                            return;
317                        }
318                        if is_data_available {
319                            app.load_route(index.unwrap());
320                        } else {
321                            app.active_display_block = ActiveDisplayBlock::Loading;
322                            app.dispatch(IoEvent::GetAnime(data.node.id));
323                        }
324                        app.active_block = ActiveBlock::DisplayBlock;
325                    }
326                }
327
328                _ => {}
329            };
330        }
331        ActiveBlock::TopThree => match &app.active_top_three {
332            TopThreeBlock::Anime(anime_ranking_type) => {
333                let index = app.selected_top_three as usize % 3;
334                let mut anime = None;
335                match anime_ranking_type {
336                    AnimeRankingType::Airing => {
337                        if let Some(data) = &app.top_three_anime.airing {
338                            anime = Some(&data[index]);
339                        }
340                    }
341                    AnimeRankingType::All => {
342                        if let Some(data) = &app.top_three_anime.all {
343                            anime = Some(&data[index]);
344                        }
345                    }
346
347                    AnimeRankingType::Upcoming => {
348                        if let Some(data) = &app.top_three_anime.upcoming {
349                            anime = Some(&data[index]);
350                        }
351                    }
352                    AnimeRankingType::Favorite => {
353                        if let Some(data) = &app.top_three_anime.favourite {
354                            anime = Some(&data[index]);
355                        }
356                    }
357                    AnimeRankingType::Movie => {
358                        if let Some(data) = &app.top_three_anime.movie {
359                            anime = Some(&data[index]);
360                        }
361                    }
362
363                    AnimeRankingType::OVA => {
364                        if let Some(data) = &app.top_three_anime.ova {
365                            anime = Some(&data[index]);
366                        }
367                    }
368                    AnimeRankingType::TV => {
369                        if let Some(data) = &app.top_three_anime.tv {
370                            anime = Some(&data[index]);
371                        }
372                    }
373                    AnimeRankingType::ByPopularity => {
374                        if let Some(data) = &app.top_three_anime.popular {
375                            anime = Some(&data[index]);
376                        }
377                    }
378                    AnimeRankingType::Special => {
379                        if let Some(data) = &app.top_three_anime.special {
380                            anime = Some(&data[index]);
381                        }
382                    }
383                    AnimeRankingType::Other(_) => {
384                        warn!("other anime ranking type was specified")
385                    }
386                }
387                let anime = match anime {
388                    Some(data) => data,
389                    None => {
390                        // push error
391                        app.api_error = "Error: Not Found".to_string();
392                        app.active_display_block = ActiveDisplayBlock::Error;
393                        return;
394                    }
395                };
396                let (is_data_available, is_next, index) =
397                    is_media_data_available(app, &Media::Anime(anime));
398                if is_next {
399                    app.load_next_route();
400                    return;
401                }
402                if is_data_available {
403                    app.load_route(index.unwrap());
404                } else {
405                    app.active_display_block = ActiveDisplayBlock::Loading;
406                    app.dispatch(IoEvent::GetAnime(anime.id));
407                }
408            }
409            TopThreeBlock::Manga(manga_ranking_type) => {
410                let index = app.selected_top_three as usize % 3;
411                let mut manga = None;
412                match manga_ranking_type {
413                    MangaRankingType::All => {
414                        if let Some(data) = &app.top_three_manga.all {
415                            manga = Some(&data[index]);
416                        }
417                    }
418                    MangaRankingType::Manga => {
419                        if let Some(data) = &app.top_three_manga.manga {
420                            manga = Some(&data[index]);
421                        }
422                    }
423                    MangaRankingType::OneShots => {
424                        if let Some(data) = &app.top_three_manga.oneshots {
425                            manga = Some(&data[index]);
426                        }
427                    }
428                    MangaRankingType::ByPopularity => {
429                        if let Some(data) = &app.top_three_manga.popular {
430                            manga = Some(&data[index]);
431                        }
432                    }
433                    MangaRankingType::Favorite => {
434                        if let Some(data) = &app.top_three_manga.favourite {
435                            manga = Some(&data[index]);
436                        }
437                    }
438                    MangaRankingType::Manhua => {
439                        if let Some(data) = &app.top_three_manga.manhua {
440                            manga = Some(&data[index]);
441                        }
442                    }
443                    MangaRankingType::Manhwa => {
444                        if let Some(data) = &app.top_three_manga.manhwa {
445                            manga = Some(&data[index]);
446                        }
447                    }
448                    MangaRankingType::Novels => {
449                        if let Some(data) = &app.top_three_manga.novels {
450                            manga = Some(&data[index]);
451                        }
452                    }
453                    MangaRankingType::Doujinshi => {
454                        if let Some(data) = &app.top_three_manga.doujin {
455                            manga = Some(&data[index]);
456                        }
457                    }
458                    MangaRankingType::Other(_) => {
459                        warn!("other manga ranking type was specified")
460                    } // PUSH ERROR
461                }
462
463                let manga = match manga {
464                    Some(data) => data,
465                    None => {
466                        // push error
467                        app.api_error = "Error: Not Found".to_string();
468                        app.active_display_block = ActiveDisplayBlock::Error;
469                        return;
470                    }
471                };
472
473                let (is_data_available, is_next, index) =
474                    is_media_data_available(app, &Media::Manga(manga));
475                if is_next {
476                    app.load_next_route();
477                    return;
478                }
479                if is_data_available {
480                    app.load_route(index.unwrap());
481                } else {
482                    app.active_display_block = ActiveDisplayBlock::Loading;
483                    app.dispatch(IoEvent::GetManga(manga.id));
484                }
485            }
486            _ => {}
487        },
488        _ => {}
489    }
490}
491
492fn is_media_data_available(app: &App, data: &Media) -> (bool, bool, Option<u16>) {
493    match data {
494        Media::Anime(data) => {
495            for i in 0..(app.navigator.history.len()) {
496                let page_id = app.navigator.history[i];
497                if app.navigator.data[&page_id].block == ActiveDisplayBlock::AnimeDetails
498                    && app.navigator.data[&page_id].data.is_some()
499                {
500                    if let Data::Anime(d) = app.navigator.data[&page_id].data.as_ref().unwrap() {
501                        if d.id == data.id {
502                            let is_next = app.navigator.index + 1 == i;
503                            return (true, is_next, Some(page_id));
504                        }
505                    }
506                }
507            }
508        }
509        Media::Manga(data) => {
510            for i in 0..(app.navigator.history.len()) {
511                let id = app.navigator.history[i];
512                if app.navigator.data[&id].block == ActiveDisplayBlock::MangaDetails
513                    && app.navigator.data[&id].data.is_some()
514                {
515                    if let Data::Manga(d) = app.navigator.data[&id].data.as_ref().unwrap() {
516                        if d.id == data.id {
517                            let is_next = app.navigator.index + 1 == i;
518                            return (true, is_next, Some(id));
519                        }
520                    }
521                }
522            }
523        }
524    }
525    (false, false, None)
526}