Merge branch 'release-candidate' into release
This commit is contained in:
		
						commit
						20deb6458a
					
				
							
								
								
									
										225
									
								
								applications/about/about.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								applications/about/about.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,225 @@ | |||||||
|  | #include <furi.h> | ||||||
|  | #include <dialogs/dialogs.h> | ||||||
|  | #include <gui/gui.h> | ||||||
|  | #include <gui/view_dispatcher.h> | ||||||
|  | #include <gui/modules/empty_screen.h> | ||||||
|  | #include <m-string.h> | ||||||
|  | #include <furi-hal-version.h> | ||||||
|  | 
 | ||||||
|  | typedef DialogMessageButton (*AboutDialogScreen)(DialogsApp* dialogs, DialogMessage* message); | ||||||
|  | 
 | ||||||
|  | static DialogMessageButton product_screen(DialogsApp* dialogs, DialogMessage* message) { | ||||||
|  |     DialogMessageButton result; | ||||||
|  | 
 | ||||||
|  |     const char* screen_header = "Product: Flipper Zero\n" | ||||||
|  |                                 "Model: FZ.1\n"; | ||||||
|  |     const char* screen_text = "FCC ID: 2A2V6-FZ\n" | ||||||
|  |                               "IC ID: 27624-FZ"; | ||||||
|  | 
 | ||||||
|  |     dialog_message_set_header(message, screen_header, 0, 0, AlignLeft, AlignTop); | ||||||
|  |     dialog_message_set_text(message, screen_text, 0, 26, AlignLeft, AlignTop); | ||||||
|  |     result = dialog_message_show(dialogs, message); | ||||||
|  |     dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop); | ||||||
|  |     dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static DialogMessageButton address_screen(DialogsApp* dialogs, DialogMessage* message) { | ||||||
|  |     DialogMessageButton result; | ||||||
|  | 
 | ||||||
|  |     const char* screen_text = "Flipper Devices Inc\n" | ||||||
|  |                               "Suite B #551, 2803\n" | ||||||
|  |                               "Philadelphia Pike, Claymont\n" | ||||||
|  |                               "DE, USA 19703\n"; | ||||||
|  | 
 | ||||||
|  |     dialog_message_set_text(message, screen_text, 0, 0, AlignLeft, AlignTop); | ||||||
|  |     result = dialog_message_show(dialogs, message); | ||||||
|  |     dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static DialogMessageButton compliance_screen(DialogsApp* dialogs, DialogMessage* message) { | ||||||
|  |     DialogMessageButton result; | ||||||
|  | 
 | ||||||
|  |     const char* screen_text = "For all compliance\n" | ||||||
|  |                               "certificates please visit\n" | ||||||
|  |                               "www.flipp.dev/compliance"; | ||||||
|  | 
 | ||||||
|  |     dialog_message_set_text(message, screen_text, 0, 0, AlignLeft, AlignTop); | ||||||
|  |     result = dialog_message_show(dialogs, message); | ||||||
|  |     dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static DialogMessageButton icon1_screen(DialogsApp* dialogs, DialogMessage* message) { | ||||||
|  |     DialogMessageButton result; | ||||||
|  | 
 | ||||||
|  |     dialog_message_set_icon(message, &I_Certification1_103x23, 12, 12); | ||||||
|  |     result = dialog_message_show(dialogs, message); | ||||||
|  |     dialog_message_set_icon(message, NULL, 0, 0); | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static DialogMessageButton icon2_screen(DialogsApp* dialogs, DialogMessage* message) { | ||||||
|  |     DialogMessageButton result; | ||||||
|  | 
 | ||||||
|  |     dialog_message_set_icon(message, &I_Certification2_119x30, 4, 9); | ||||||
|  |     result = dialog_message_show(dialogs, message); | ||||||
|  |     dialog_message_set_icon(message, NULL, 0, 0); | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static DialogMessageButton hw_version_screen(DialogsApp* dialogs, DialogMessage* message) { | ||||||
|  |     DialogMessageButton result; | ||||||
|  |     string_t buffer; | ||||||
|  |     string_init(buffer); | ||||||
|  |     const char* my_name = furi_hal_version_get_name_ptr(); | ||||||
|  | 
 | ||||||
|  |     string_cat_printf( | ||||||
|  |         buffer, | ||||||
|  |         "%d.F%dB%dC%d %s\n", | ||||||
|  |         furi_hal_version_get_hw_version(), | ||||||
|  |         furi_hal_version_get_hw_target(), | ||||||
|  |         furi_hal_version_get_hw_body(), | ||||||
|  |         furi_hal_version_get_hw_connect(), | ||||||
|  |         my_name ? my_name : "Unknown"); | ||||||
|  | 
 | ||||||
|  |     dialog_message_set_header(message, "HW Version info:", 0, 0, AlignLeft, AlignTop); | ||||||
|  |     dialog_message_set_text(message, string_get_cstr(buffer), 0, 13, AlignLeft, AlignTop); | ||||||
|  |     result = dialog_message_show(dialogs, message); | ||||||
|  |     dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); | ||||||
|  |     dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop); | ||||||
|  |     string_clear(buffer); | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage* message) { | ||||||
|  |     DialogMessageButton result; | ||||||
|  |     string_t buffer; | ||||||
|  |     string_init(buffer); | ||||||
|  |     const Version* ver = furi_hal_version_get_firmware_version(); | ||||||
|  | 
 | ||||||
|  |     if(!ver) { | ||||||
|  |         string_cat_printf(buffer, "No info\n"); | ||||||
|  |     } else { | ||||||
|  |         string_cat_printf( | ||||||
|  |             buffer, | ||||||
|  |             "%s [%s]\n%s [%s]\n[%s] %s", | ||||||
|  |             version_get_version(ver), | ||||||
|  |             version_get_builddate(ver), | ||||||
|  |             version_get_githash(ver), | ||||||
|  |             version_get_gitbranchnum(ver), | ||||||
|  |             version_get_target(ver), | ||||||
|  |             version_get_gitbranch(ver)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     dialog_message_set_header(message, "FW Version info:", 0, 0, AlignLeft, AlignTop); | ||||||
|  |     dialog_message_set_text(message, string_get_cstr(buffer), 0, 13, AlignLeft, AlignTop); | ||||||
|  |     result = dialog_message_show(dialogs, message); | ||||||
|  |     dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); | ||||||
|  |     dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop); | ||||||
|  |     string_clear(buffer); | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static DialogMessageButton boot_version_screen(DialogsApp* dialogs, DialogMessage* message) { | ||||||
|  |     DialogMessageButton result; | ||||||
|  |     string_t buffer; | ||||||
|  |     string_init(buffer); | ||||||
|  |     const Version* ver = furi_hal_version_get_boot_version(); | ||||||
|  | 
 | ||||||
|  |     if(!ver) { | ||||||
|  |         string_cat_printf(buffer, "No info\n"); | ||||||
|  |     } else { | ||||||
|  |         string_cat_printf( | ||||||
|  |             buffer, | ||||||
|  |             "%s [%s]\n%s [%s]\n[%s] %s", | ||||||
|  |             version_get_version(ver), | ||||||
|  |             version_get_builddate(ver), | ||||||
|  |             version_get_githash(ver), | ||||||
|  |             version_get_gitbranchnum(ver), | ||||||
|  |             version_get_target(ver), | ||||||
|  |             version_get_gitbranch(ver)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     dialog_message_set_header(message, "Boot Version info:", 0, 0, AlignLeft, AlignTop); | ||||||
|  |     dialog_message_set_text(message, string_get_cstr(buffer), 0, 13, AlignLeft, AlignTop); | ||||||
|  |     result = dialog_message_show(dialogs, message); | ||||||
|  |     dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop); | ||||||
|  |     dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop); | ||||||
|  |     string_clear(buffer); | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const AboutDialogScreen about_screens[] = { | ||||||
|  |     product_screen, | ||||||
|  |     compliance_screen, | ||||||
|  |     address_screen, | ||||||
|  |     icon1_screen, | ||||||
|  |     icon2_screen, | ||||||
|  |     hw_version_screen, | ||||||
|  |     fw_version_screen, | ||||||
|  |     boot_version_screen}; | ||||||
|  | 
 | ||||||
|  | const size_t about_screens_count = sizeof(about_screens) / sizeof(AboutDialogScreen); | ||||||
|  | 
 | ||||||
|  | int32_t about_settings_app(void* p) { | ||||||
|  |     DialogsApp* dialogs = furi_record_open("dialogs"); | ||||||
|  |     DialogMessage* message = dialog_message_alloc(); | ||||||
|  | 
 | ||||||
|  |     Gui* gui = furi_record_open("gui"); | ||||||
|  |     ViewDispatcher* view_dispatcher = view_dispatcher_alloc(); | ||||||
|  |     EmptyScreen* empty_screen = empty_screen_alloc(); | ||||||
|  |     const uint32_t empty_screen_index = 0; | ||||||
|  | 
 | ||||||
|  |     size_t screen_index = 0; | ||||||
|  |     DialogMessageButton screen_result; | ||||||
|  | 
 | ||||||
|  |     // draw empty screen to prevent menu flickering
 | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         view_dispatcher, empty_screen_index, empty_screen_get_view(empty_screen)); | ||||||
|  |     view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen); | ||||||
|  |     view_dispatcher_switch_to_view(view_dispatcher, empty_screen_index); | ||||||
|  | 
 | ||||||
|  |     while(1) { | ||||||
|  |         if(screen_index >= about_screens_count - 1) { | ||||||
|  |             dialog_message_set_buttons(message, "Back", NULL, NULL); | ||||||
|  |         } else { | ||||||
|  |             dialog_message_set_buttons(message, "Back", NULL, "Next"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         screen_result = about_screens[screen_index](dialogs, message); | ||||||
|  | 
 | ||||||
|  |         if(screen_result == DialogMessageButtonLeft) { | ||||||
|  |             if(screen_index <= 0) { | ||||||
|  |                 break; | ||||||
|  |             } else { | ||||||
|  |                 screen_index--; | ||||||
|  |             } | ||||||
|  |         } else if(screen_result == DialogMessageButtonRight) { | ||||||
|  |             if(screen_index < about_screens_count) { | ||||||
|  |                 screen_index++; | ||||||
|  |             } | ||||||
|  |         } else if(screen_result == DialogMessageButtonBack) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     dialog_message_free(message); | ||||||
|  |     furi_record_close("dialogs"); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_remove_view(view_dispatcher, empty_screen_index); | ||||||
|  |     view_dispatcher_free(view_dispatcher); | ||||||
|  |     empty_screen_free(empty_screen); | ||||||
|  |     furi_record_close("gui"); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
							
								
								
									
										96
									
								
								applications/applications.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										96
									
								
								applications/applications.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @ -9,11 +9,11 @@ extern int32_t dolphin_srv(void* p); | |||||||
| extern int32_t gui_srv(void* p); | extern int32_t gui_srv(void* p); | ||||||
| extern int32_t input_srv(void* p); | extern int32_t input_srv(void* p); | ||||||
| extern int32_t loader_srv(void* p); | extern int32_t loader_srv(void* p); | ||||||
| extern int32_t menu_srv(void* p); |  | ||||||
| extern int32_t notification_srv(void* p); | extern int32_t notification_srv(void* p); | ||||||
| extern int32_t power_observer_srv(void* p); | extern int32_t power_observer_srv(void* p); | ||||||
| extern int32_t power_srv(void* p); | extern int32_t power_srv(void* p); | ||||||
| extern int32_t storage_srv(void* p); | extern int32_t storage_srv(void* p); | ||||||
|  | extern int32_t desktop_srv(void* p); | ||||||
| 
 | 
 | ||||||
| // Apps
 | // Apps
 | ||||||
| extern int32_t accessor_app(void* p); | extern int32_t accessor_app(void* p); | ||||||
| @ -46,11 +46,15 @@ extern void lfrfid_cli_init(); | |||||||
| extern void nfc_cli_init(); | extern void nfc_cli_init(); | ||||||
| extern void storage_cli_init(); | extern void storage_cli_init(); | ||||||
| extern void subghz_cli_init(); | extern void subghz_cli_init(); | ||||||
|  | extern void power_cli_init(); | ||||||
| 
 | 
 | ||||||
| // Settings
 | // Settings
 | ||||||
| extern int32_t notification_settings_app(void* p); | extern int32_t notification_settings_app(void* p); | ||||||
| extern int32_t storage_settings_app(void* p); | extern int32_t storage_settings_app(void* p); | ||||||
| extern int32_t bt_settings_app(void* p); | extern int32_t bt_settings_app(void* p); | ||||||
|  | extern int32_t desktop_settings_app(void* p); | ||||||
|  | extern int32_t about_settings_app(void* p); | ||||||
|  | extern int32_t power_settings_app(void* p); | ||||||
| 
 | 
 | ||||||
| const FlipperApplication FLIPPER_SERVICES[] = { | const FlipperApplication FLIPPER_SERVICES[] = { | ||||||
| /* Services */ | /* Services */ | ||||||
| @ -70,6 +74,10 @@ const FlipperApplication FLIPPER_SERVICES[] = { | |||||||
|     {.app = dolphin_srv, .name = "Dolphin", .stack_size = 1024, .icon = NULL}, |     {.app = dolphin_srv, .name = "Dolphin", .stack_size = 1024, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  | #ifdef SRV_DESKTOP | ||||||
|  |     {.app = desktop_srv, .name = "Desktop", .stack_size = 1024, .icon = NULL}, | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| #ifdef SRV_GUI | #ifdef SRV_GUI | ||||||
|     {.app = gui_srv, .name = "Gui", .stack_size = 8192, .icon = NULL}, |     {.app = gui_srv, .name = "Gui", .stack_size = 8192, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| @ -78,8 +86,7 @@ const FlipperApplication FLIPPER_SERVICES[] = { | |||||||
|     {.app = input_srv, .name = "Input", .stack_size = 1024, .icon = NULL}, |     {.app = input_srv, .name = "Input", .stack_size = 1024, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_MENU | #ifdef SRV_LOADER | ||||||
|     {.app = menu_srv, .name = "Menu", .stack_size = 1024, .icon = NULL}, |  | ||||||
|     {.app = loader_srv, .name = "Loader", .stack_size = 1024, .icon = NULL}, |     {.app = loader_srv, .name = "Loader", .stack_size = 1024, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| @ -98,43 +105,6 @@ const FlipperApplication FLIPPER_SERVICES[] = { | |||||||
| #ifdef SRV_STORAGE | #ifdef SRV_STORAGE | ||||||
|     {.app = storage_srv, .name = "Storage", .stack_size = 4096, .icon = NULL}, |     {.app = storage_srv, .name = "Storage", .stack_size = 4096, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 |  | ||||||
| /* Fake services (autorun) */ |  | ||||||
| #ifdef SRV_BLINK |  | ||||||
|     {.app = blink_test_app, .name = "Blink", .stack_size = 1024, .icon = &A_Plugins_14}, |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| #ifdef SRV_LF_RFID |  | ||||||
|     {.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 2048, .icon = &A_Plugins_14}, |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| #ifdef SRV_IRDA |  | ||||||
|     {.app = irda_app, .name = "Infrared", .stack_size = 1024 * 3, .icon = &A_Plugins_14}, |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| #ifdef SRV_MUSIC_PLAYER |  | ||||||
|     {.app = music_player_app, .name = "Music Player", .stack_size = 1024, .icon = &A_Plugins_14}, |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| #ifdef SRV_IBUTTON |  | ||||||
|     {.app = ibutton_app, .name = "iButton", .stack_size = 2048, .icon = &A_Plugins_14}, |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| #ifdef SRV_GPIO_TEST |  | ||||||
|     {.app = gpio_test_app, .name = "GPIO Test", .stack_size = 1024, .icon = &A_Plugins_14}, |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| #ifdef SRV_KEYPAD_TEST |  | ||||||
|     {.app = keypad_test_app, .name = "Keypad Test", .stack_size = 1024, .icon = &A_Plugins_14}, |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| #ifdef SRV_ACCESSOR |  | ||||||
|     {.app = accessor_app, .name = "Accessor", .stack_size = 4096, .icon = &A_Plugins_14}, |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| #ifdef SRV_STORAGE_TEST |  | ||||||
|     {.app = storage_test_app, .name = "Storage Test", .stack_size = 1024, .icon = &A_Plugins_14}, |  | ||||||
| #endif |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const size_t FLIPPER_SERVICES_COUNT = sizeof(FLIPPER_SERVICES) / sizeof(FlipperApplication); | const size_t FLIPPER_SERVICES_COUNT = sizeof(FLIPPER_SERVICES) / sizeof(FlipperApplication); | ||||||
| @ -142,18 +112,14 @@ const size_t FLIPPER_SERVICES_COUNT = sizeof(FLIPPER_SERVICES) / sizeof(FlipperA | |||||||
| // Main menu APP
 | // Main menu APP
 | ||||||
| const FlipperApplication FLIPPER_APPS[] = { | const FlipperApplication FLIPPER_APPS[] = { | ||||||
| 
 | 
 | ||||||
| #ifdef APP_IBUTTON | #ifdef APP_SUBGHZ | ||||||
|     {.app = ibutton_app, .name = "iButton", .stack_size = 2048, .icon = &A_iButton_14}, |     {.app = subghz_app, .name = "Sub-GHz", .stack_size = 2048, .icon = &A_Sub1ghz_14}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef APP_NFC | #ifdef APP_NFC | ||||||
|     {.app = nfc_app, .name = "NFC", .stack_size = 4096, .icon = &A_NFC_14}, |     {.app = nfc_app, .name = "NFC", .stack_size = 4096, .icon = &A_NFC_14}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef APP_SUBGHZ |  | ||||||
|     {.app = subghz_app, .name = "Sub-GHz", .stack_size = 2048, .icon = &A_Sub1ghz_14}, |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| #ifdef APP_LF_RFID | #ifdef APP_LF_RFID | ||||||
|     {.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 2048, .icon = &A_125khz_14}, |     {.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 2048, .icon = &A_125khz_14}, | ||||||
| #endif | #endif | ||||||
| @ -162,6 +128,10 @@ const FlipperApplication FLIPPER_APPS[] = { | |||||||
|     {.app = irda_app, .name = "Infrared", .stack_size = 1024 * 3, .icon = &A_Infrared_14}, |     {.app = irda_app, .name = "Infrared", .stack_size = 1024 * 3, .icon = &A_Infrared_14}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  | #ifdef APP_IBUTTON | ||||||
|  |     {.app = ibutton_app, .name = "iButton", .stack_size = 2048, .icon = &A_iButton_14}, | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| #ifdef APP_GPIO_TEST | #ifdef APP_GPIO_TEST | ||||||
|     {.app = gpio_test_app, .name = "GPIO", .stack_size = 1024, .icon = &A_GPIO_14}, |     {.app = gpio_test_app, .name = "GPIO", .stack_size = 1024, .icon = &A_GPIO_14}, | ||||||
| #endif | #endif | ||||||
| @ -175,22 +145,35 @@ const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = { | |||||||
| #ifdef SRV_CLI | #ifdef SRV_CLI | ||||||
|     crypto_cli_init, |     crypto_cli_init, | ||||||
| #endif | #endif | ||||||
|  | 
 | ||||||
|  | #ifdef APP_IRDA | ||||||
|     irda_cli_init, |     irda_cli_init, | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| #ifdef APP_NFC | #ifdef APP_NFC | ||||||
|     nfc_cli_init, |     nfc_cli_init, | ||||||
| #endif | #endif | ||||||
|  | 
 | ||||||
| #ifdef APP_SUBGHZ | #ifdef APP_SUBGHZ | ||||||
|     subghz_cli_init, |     subghz_cli_init, | ||||||
| #endif | #endif | ||||||
|  | 
 | ||||||
| #ifdef APP_LF_RFID | #ifdef APP_LF_RFID | ||||||
|     lfrfid_cli_init, |     lfrfid_cli_init, | ||||||
| #endif | #endif | ||||||
|  | 
 | ||||||
| #ifdef APP_IBUTTON | #ifdef APP_IBUTTON | ||||||
|     ibutton_cli_init, |     ibutton_cli_init, | ||||||
| #endif | #endif | ||||||
|  | 
 | ||||||
| #ifdef SRV_BT | #ifdef SRV_BT | ||||||
|     bt_cli_init, |     bt_cli_init, | ||||||
| #endif | #endif | ||||||
|  | 
 | ||||||
|  | #ifdef SRV_POWER | ||||||
|  |     power_cli_init, | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| #ifdef SRV_STORAGE | #ifdef SRV_STORAGE | ||||||
|     storage_cli_init, |     storage_cli_init, | ||||||
| #endif | #endif | ||||||
| @ -257,16 +240,31 @@ const FlipperApplication FLIPPER_ARCHIVE = | |||||||
| 
 | 
 | ||||||
| // Settings menu
 | // Settings menu
 | ||||||
| const FlipperApplication FLIPPER_SETTINGS_APPS[] = { | const FlipperApplication FLIPPER_SETTINGS_APPS[] = { | ||||||
|  | #ifdef SRV_BT | ||||||
|  |     {.app = bt_settings_app, .name = "Bluetooth", .stack_size = 1024, .icon = NULL}, | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| #ifdef SRV_NOTIFICATION | #ifdef SRV_NOTIFICATION | ||||||
|     {.app = notification_settings_app, .name = "Notification", .stack_size = 1024, .icon = NULL}, |     {.app = notification_settings_app, | ||||||
|  |      .name = "LCD and notifications", | ||||||
|  |      .stack_size = 1024, | ||||||
|  |      .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_STORAGE | #ifdef SRV_STORAGE | ||||||
|     {.app = storage_settings_app, .name = "Storage", .stack_size = 2048, .icon = NULL}, |     {.app = storage_settings_app, .name = "Storage", .stack_size = 2048, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_BT | #ifdef SRV_POWER | ||||||
|     {.app = bt_settings_app, .name = "Bluetooth", .stack_size = 1024, .icon = NULL}, |     {.app = power_settings_app, .name = "Power", .stack_size = 1024, .icon = NULL}, | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #ifdef SRV_DESKTOP | ||||||
|  |     {.app = desktop_settings_app, .name = "Desktop", .stack_size = 1024, .icon = NULL}, | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #ifdef APP_ABOUT | ||||||
|  |     {.app = about_settings_app, .name = "About", .stack_size = 1024, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,264 +1,249 @@ | |||||||
| APP_DIR		= $(PROJECT_ROOT)/applications | APP_DIR		= $(PROJECT_ROOT)/applications | ||||||
| LIB_DIR 	= $(PROJECT_ROOT)/lib | LIB_DIR		= $(PROJECT_ROOT)/lib | ||||||
| 
 | 
 | ||||||
| CFLAGS		+= -I$(APP_DIR) | CFLAGS		+= -I$(APP_DIR) | ||||||
| C_SOURCES   += $(shell find $(APP_DIR) -name *.c) | C_SOURCES	+= $(shell find $(APP_DIR) -name *.c) | ||||||
| CPP_SOURCES += $(shell find $(APP_DIR) -name *.cpp) | CPP_SOURCES	+= $(shell find $(APP_DIR) -name *.cpp) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # Use SRV_* for autostart app
 |  | ||||||
| # Use APP_* for add app to build
 |  | ||||||
| 
 |  | ||||||
| APP_RELEASE ?= 1 | APP_RELEASE ?= 1 | ||||||
| ifeq ($(APP_RELEASE), 1) | ifeq ($(APP_RELEASE), 1) | ||||||
| # Services
 | # Services
 | ||||||
| SRV_BT = 1 | SRV_BT		= 1 | ||||||
| SRV_CLI = 1 | SRV_CLI		= 1 | ||||||
| SRV_DIALOGS = 1 | SRV_DIALOGS	= 1 | ||||||
| SRV_DOLPHIN = 1 | SRV_DOLPHIN	= 1 | ||||||
| SRV_GUI = 1 | SRV_GUI		= 1 | ||||||
| SRV_INPUT = 1 | SRV_INPUT	= 1 | ||||||
| SRV_MENU = 1 | SRV_LOADER	= 1 | ||||||
| SRV_NOTIFICATION = 1 | SRV_NOTIFICATION = 1 | ||||||
| SRV_POWER = 1 | SRV_POWER	= 1 | ||||||
| SRV_POWER_OBSERVER = 1 | SRV_POWER_OBSERVER = 1 | ||||||
| SRV_STORAGE = 1 | SRV_STORAGE	= 1 | ||||||
| 
 | 
 | ||||||
| # Apps
 | # Apps
 | ||||||
| APP_ARCHIVE = 1 | SRV_DESKTOP	= 1 | ||||||
|  | APP_ARCHIVE	= 1 | ||||||
| APP_GPIO_TEST = 1 | APP_GPIO_TEST = 1 | ||||||
| APP_IBUTTON = 1 | APP_IBUTTON	= 1 | ||||||
| APP_IRDA  = 1 | APP_IRDA	= 1 | ||||||
| APP_LF_RFID = 1 | APP_LF_RFID	= 1 | ||||||
| APP_NFC = 1 | APP_NFC		= 1 | ||||||
| APP_SUBGHZ = 1 | APP_SUBGHZ	= 1 | ||||||
|  | APP_ABOUT	= 1 | ||||||
| 
 | 
 | ||||||
| # Plugins
 | # Plugins
 | ||||||
| APP_MUSIC_PLAYER = 1 | APP_MUSIC_PLAYER = 1 | ||||||
| 
 | 
 | ||||||
| # Debug
 | # Debug
 | ||||||
| APP_ACCESSOR = 1 | APP_ACCESSOR = 1 | ||||||
| APP_BLINK = 1 | APP_BLINK	= 1 | ||||||
| APP_IRDA_MONITOR = 1 | APP_IRDA_MONITOR = 1 | ||||||
| APP_KEYPAD_TEST = 1 | APP_KEYPAD_TEST = 1 | ||||||
| APP_SD_TEST = 1 | APP_SD_TEST	= 1 | ||||||
| APP_UNIT_TESTS = 0 | APP_UNIT_TESTS = 0 | ||||||
| APP_VIBRO_DEMO = 1 | APP_VIBRO_DEMO = 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| SRV_BT ?= 0 | # Applications
 | ||||||
| ifeq ($(SRV_BT), 1) | # that will be shown in menu
 | ||||||
| SRV_CLI		= 1 | # Prefix with APP_*
 | ||||||
| CFLAGS		+= -DSRV_BT |  | ||||||
| endif |  | ||||||
| 
 | 
 | ||||||
| SRV_DOLPHIN ?= 0 |  | ||||||
| ifeq ($(SRV_DOLPHIN), 1) |  | ||||||
| SRV_MENU	= 1 |  | ||||||
| CFLAGS		+= -DSRV_DOLPHIN |  | ||||||
| endif |  | ||||||
| 
 |  | ||||||
| SRV_POWER ?= 0 |  | ||||||
| ifeq ($(SRV_POWER), 1) |  | ||||||
| SRV_GUI		= 1 |  | ||||||
| SRV_CLI		= 1 |  | ||||||
| CFLAGS		+= -DSRV_POWER |  | ||||||
| endif |  | ||||||
| 
 |  | ||||||
| SRV_POWER_OBSERVER ?= 0 |  | ||||||
| ifeq ($(SRV_POWER_OBSERVER), 1) |  | ||||||
| SRV_POWER	= 1 |  | ||||||
| CFLAGS		+= -DSRV_POWER_OBSERVER |  | ||||||
| endif |  | ||||||
| 
 |  | ||||||
| SRV_MENU ?= 0 |  | ||||||
| ifeq ($(SRV_MENU), 1) |  | ||||||
| CFLAGS += -DSRV_MENU |  | ||||||
| APP_MENU = 1 |  | ||||||
| endif |  | ||||||
| APP_MENU ?= 0 |  | ||||||
| ifeq ($(APP_MENU), 1) |  | ||||||
| SRV_INPUT	= 1 |  | ||||||
| SRV_GUI		= 1 |  | ||||||
| CFLAGS		+= -DAPP_MENU |  | ||||||
| endif |  | ||||||
| 
 | 
 | ||||||
| APP_IRDA_MONITOR	?= 0 | APP_IRDA_MONITOR	?= 0 | ||||||
| ifeq ($(APP_IRDA_MONITOR), 1) | ifeq ($(APP_IRDA_MONITOR), 1) | ||||||
| CFLAGS		+= -DAPP_IRDA_MONITOR | CFLAGS		+= -DAPP_IRDA_MONITOR | ||||||
|  | SRV_GUI		= 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| APP_UNIT_TESTS	?= 0 | APP_UNIT_TESTS	?= 0 | ||||||
| ifeq ($(APP_UNIT_TESTS), 1) | ifeq ($(APP_UNIT_TESTS), 1) | ||||||
| CFLAGS		+= -DAPP_UNIT_TESTS | CFLAGS		+= -DAPP_UNIT_TESTS | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| APP_ARCHIVE ?= 0 | APP_ARCHIVE ?= 0 | ||||||
| ifeq ($(APP_NFC), 1) | ifeq ($(APP_ARCHIVE), 1) | ||||||
| CFLAGS		+= -DAPP_ARCHIVE | CFLAGS		+= -DAPP_ARCHIVE | ||||||
| APP_ARCHIVE = 1 | SRV_GUI		= 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| SRV_BLINK ?= 0 | 
 | ||||||
| ifeq ($(SRV_BLINK), 1) |  | ||||||
| CFLAGS		+= -DSRV_BLINK |  | ||||||
| APP_BLINK = 1 |  | ||||||
| endif |  | ||||||
| APP_BLINK ?= 0 | APP_BLINK ?= 0 | ||||||
| ifeq ($(APP_BLINK), 1) | ifeq ($(APP_BLINK), 1) | ||||||
| CFLAGS		+= -DAPP_BLINK | CFLAGS		+= -DAPP_BLINK | ||||||
| SRV_INPUT = 1 | SRV_GUI		= 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| SRV_UART_WRITE ?= 0 |  | ||||||
| ifeq ($(SRV_UART_WRITE), 1) |  | ||||||
| CFLAGS		+= -DSRV_UART_WRITE |  | ||||||
| APP_UART_WRITE = 1 |  | ||||||
| endif |  | ||||||
| APP_UART_WRITE ?= 0 |  | ||||||
| ifeq ($(APP_UART_WRITE), 1) |  | ||||||
| CFLAGS		+= -DAPP_UART_WRITE |  | ||||||
| endif |  | ||||||
| 
 |  | ||||||
| SRV_IPC ?= 0 |  | ||||||
| ifeq ($(SRV_IPC), 1) |  | ||||||
| CFLAGS		+= -DSRV_IPC |  | ||||||
| APP_IPC = 1 |  | ||||||
| endif |  | ||||||
| APP_IPC ?= 0 |  | ||||||
| ifeq ($(APP_IPC), 1) |  | ||||||
| CFLAGS		+= -DAPP_IPC |  | ||||||
| endif |  | ||||||
| 
 | 
 | ||||||
|  | APP_SUBGHZ ?= 0 | ||||||
| ifeq ($(APP_SUBGHZ), 1) | ifeq ($(APP_SUBGHZ), 1) | ||||||
| CFLAGS		+= -DAPP_SUBGHZ | CFLAGS		+= -DAPP_SUBGHZ | ||||||
| SRV_INPUT = 1 | SRV_GUI		= 1 | ||||||
| SRV_GUI = 1 | SRV_CLI		= 1 | ||||||
| SRV_CLI = 1 |  | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| SRV_LF_RFID ?= 0 | 
 | ||||||
| ifeq ($(SRV_LF_RFID), 1) | APP_ABOUT ?= 0 | ||||||
| CFLAGS		+= -DSRV_LF_RFID | ifeq ($(APP_ABOUT), 1) | ||||||
| APP_LF_RFID = 1 | CFLAGS		+= -DAPP_ABOUT | ||||||
|  | SRV_GUI		= 1 | ||||||
| endif | endif | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| APP_LF_RFID ?= 0 | APP_LF_RFID ?= 0 | ||||||
| ifeq ($(APP_LF_RFID), 1) | ifeq ($(APP_LF_RFID), 1) | ||||||
| CFLAGS		+= -DAPP_LF_RFID | CFLAGS		+= -DAPP_LF_RFID | ||||||
| SRV_INPUT = 1 | SRV_GUI		= 1 | ||||||
| SRV_GUI = 1 |  | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| APP_NFC ?= 0 | APP_NFC ?= 0 | ||||||
| ifeq ($(APP_NFC), 1) | ifeq ($(APP_NFC), 1) | ||||||
| CFLAGS		+= -DAPP_NFC | CFLAGS		+= -DAPP_NFC | ||||||
| SRV_MENU = 1 | SRV_GUI		= 1 | ||||||
| SRV_INPUT = 1 |  | ||||||
| SRV_GUI = 1 |  | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| SRV_IRDA ?= 0 | 
 | ||||||
| ifeq ($(SRV_IRDA), 1) |  | ||||||
| CFLAGS		+= -DSRV_IRDA |  | ||||||
| APP_IRDA = 1 |  | ||||||
| endif |  | ||||||
| APP_IRDA ?= 0 | APP_IRDA ?= 0 | ||||||
| ifeq ($(APP_IRDA), 1) | ifeq ($(APP_IRDA), 1) | ||||||
| CFLAGS		+= -DAPP_IRDA | CFLAGS		+= -DAPP_IRDA | ||||||
| SRV_INPUT = 1 | SRV_GUI		= 1 | ||||||
| SRV_GUI = 1 |  | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| APP_VIBRO_DEMO ?= 0 | APP_VIBRO_DEMO ?= 0 | ||||||
| ifeq ($(APP_VIBRO_DEMO), 1) | ifeq ($(APP_VIBRO_DEMO), 1) | ||||||
| CFLAGS		+= -DAPP_VIBRO_DEMO | CFLAGS		+= -DAPP_VIBRO_DEMO | ||||||
| SRV_INPUT = 1 | SRV_GUI		= 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| SRV_KEYPAD_TEST ?= 0 | 
 | ||||||
| ifeq ($(SRV_KEYPAD_TEST), 1) |  | ||||||
| CFLAGS		+= -DSRV_KEYPAD_TEST |  | ||||||
| APP_KEYPAD_TEST = 1 |  | ||||||
| endif |  | ||||||
| APP_KEYPAD_TEST ?= 0 | APP_KEYPAD_TEST ?= 0 | ||||||
| ifeq ($(APP_KEYPAD_TEST), 1) | ifeq ($(APP_KEYPAD_TEST), 1) | ||||||
| CFLAGS		+= -DAPP_KEYPAD_TEST | CFLAGS		+= -DAPP_KEYPAD_TEST | ||||||
| APP_KEYPAD_TEST = 1 | SRV_GUI		= 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| SRV_ACCESSOR ?= 0 | 
 | ||||||
| ifeq ($(SRV_ACCESSOR), 1) |  | ||||||
| CFLAGS		+= -DSRV_ACCESSOR |  | ||||||
| APP_ACCESSOR = 1 |  | ||||||
| endif |  | ||||||
| APP_ACCESSOR ?= 0 | APP_ACCESSOR ?= 0 | ||||||
| ifeq ($(APP_ACCESSOR), 1) | ifeq ($(APP_ACCESSOR), 1) | ||||||
| CFLAGS		+= -DAPP_ACCESSOR | CFLAGS		+= -DAPP_ACCESSOR | ||||||
| APP_ACCESSOR = 1 | SRV_GUI		= 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| SRV_GPIO_TEST ?= 0 | 
 | ||||||
| ifeq ($(SRV_GPIO_TEST), 1) |  | ||||||
| CFLAGS		+= -DSRV_GPIO_TEST |  | ||||||
| APP_GPIO_TEST = 1 |  | ||||||
| endif |  | ||||||
| APP_GPIO_TEST ?= 0 | APP_GPIO_TEST ?= 0 | ||||||
| ifeq ($(APP_GPIO_TEST), 1) | ifeq ($(APP_GPIO_TEST), 1) | ||||||
| CFLAGS		+= -DAPP_GPIO_TEST | CFLAGS		+= -DAPP_GPIO_TEST | ||||||
|  | SRV_GUI		= 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| SRV_MUSIC_PLAYER ?= 0 | 
 | ||||||
| ifeq ($(SRV_MUSIC_PLAYER), 1) |  | ||||||
| CFLAGS		+= -DSRV_MUSIC_PLAYER |  | ||||||
| APP_MUSIC_PLAYER = 1 |  | ||||||
| endif |  | ||||||
| APP_MUSIC_PLAYER ?= 0 | APP_MUSIC_PLAYER ?= 0 | ||||||
| ifeq ($(APP_MUSIC_PLAYER), 1) | ifeq ($(APP_MUSIC_PLAYER), 1) | ||||||
| CFLAGS		+= -DAPP_MUSIC_PLAYER | CFLAGS		+= -DAPP_MUSIC_PLAYER | ||||||
|  | SRV_GUI		= 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| SRV_IBUTTON ?= 0 | 
 | ||||||
| ifeq ($(SRV_IBUTTON), 1) |  | ||||||
| CFLAGS		+= -DSRV_IBUTTON |  | ||||||
| APP_IBUTTON = 1 |  | ||||||
| endif |  | ||||||
| APP_IBUTTON ?= 0 | APP_IBUTTON ?= 0 | ||||||
| ifeq ($(APP_IBUTTON), 1) | ifeq ($(APP_IBUTTON), 1) | ||||||
| CFLAGS		+= -DAPP_IBUTTON | CFLAGS		+= -DAPP_IBUTTON | ||||||
|  | SRV_GUI		= 1 | ||||||
|  | endif | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Services
 | ||||||
|  | # that will start with OS
 | ||||||
|  | # Prefix with SRV_*
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | SRV_BT ?= 0 | ||||||
|  | ifeq ($(SRV_BT), 1) | ||||||
|  | CFLAGS		+= -DSRV_BT | ||||||
|  | SRV_CLI		= 1 | ||||||
|  | endif | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | SRV_DESKTOP ?= 0 | ||||||
|  | ifeq ($(SRV_DESKTOP), 1) | ||||||
|  | CFLAGS		+= -DSRV_DESKTOP | ||||||
|  | SRV_DOLPHIN	= 1 | ||||||
|  | SRV_STORAGE	= 1 | ||||||
|  | SRV_GUI		= 1 | ||||||
|  | endif | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | SRV_DOLPHIN ?= 0 | ||||||
|  | ifeq ($(SRV_DOLPHIN), 1) | ||||||
|  | CFLAGS		+= -DSRV_DOLPHIN | ||||||
|  | endif | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | SRV_POWER_OBSERVER ?= 0 | ||||||
|  | ifeq ($(SRV_POWER_OBSERVER), 1) | ||||||
|  | CFLAGS		+= -DSRV_POWER_OBSERVER | ||||||
|  | SRV_POWER	= 1 | ||||||
|  | endif | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | SRV_POWER ?= 0 | ||||||
|  | ifeq ($(SRV_POWER), 1) | ||||||
|  | CFLAGS		+= -DSRV_POWER | ||||||
|  | SRV_GUI		= 1 | ||||||
|  | SRV_CLI		= 1 | ||||||
|  | endif | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | SRV_LOADER ?= 0 | ||||||
|  | ifeq ($(SRV_LOADER), 1) | ||||||
|  | CFLAGS		+= -DSRV_LOADER | ||||||
|  | SRV_GUI		= 1 | ||||||
|  | # Loader autostart hook
 | ||||||
|  | LOADER_AUTOSTART ?= "" | ||||||
|  | ifneq ($(strip $(LOADER_AUTOSTART)), "") | ||||||
|  | CFLAGS		+= -DLOADER_AUTOSTART="\"$(LOADER_AUTOSTART)\"" | ||||||
|  | endif | ||||||
|  | # Loader autostart hook END
 | ||||||
|  | endif | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | SRV_DIALOGS ?= 0 | ||||||
|  | ifeq ($(SRV_DIALOGS), 1) | ||||||
|  | CFLAGS		+= -DSRV_DIALOGS | ||||||
|  | SRV_GUI		= 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
| #
 |  | ||||||
| # Essential services
 |  | ||||||
| #
 |  | ||||||
| 
 | 
 | ||||||
| SRV_GUI	?= 0 | SRV_GUI	?= 0 | ||||||
| ifeq ($(SRV_GUI), 1) | ifeq ($(SRV_GUI), 1) | ||||||
| CFLAGS		+= -DSRV_GUI | CFLAGS		+= -DSRV_GUI | ||||||
|  | SRV_INPUT	= 1 | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| SRV_INPUT	?= 0 | SRV_INPUT	?= 0 | ||||||
| ifeq ($(SRV_INPUT), 1) | ifeq ($(SRV_INPUT), 1) | ||||||
| CFLAGS		+= -DSRV_INPUT | CFLAGS		+= -DSRV_INPUT | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| SRV_CLI ?= 0 | SRV_CLI ?= 0 | ||||||
| ifeq ($(SRV_CLI), 1) | ifeq ($(SRV_CLI), 1) | ||||||
| SRV_GUI		= 1 |  | ||||||
| CFLAGS		+= -DSRV_CLI | CFLAGS		+= -DSRV_CLI | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| SRV_NOTIFICATION ?= 0 | SRV_NOTIFICATION ?= 0 | ||||||
| ifeq ($(SRV_NOTIFICATION), 1) | ifeq ($(SRV_NOTIFICATION), 1) | ||||||
| CFLAGS		+= -DSRV_NOTIFICATION | CFLAGS		+= -DSRV_NOTIFICATION | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| SRV_STORAGE ?= 0 | SRV_STORAGE ?= 0 | ||||||
| ifeq ($(SRV_STORAGE), 1) | ifeq ($(SRV_STORAGE), 1) | ||||||
| CFLAGS		+= -DSRV_STORAGE | CFLAGS		+= -DSRV_STORAGE | ||||||
| endif | endif | ||||||
| 
 |  | ||||||
| SRV_DIALOGS ?= 0 |  | ||||||
| ifeq ($(SRV_DIALOGS), 1) |  | ||||||
| CFLAGS		+= -DSRV_DIALOGS |  | ||||||
| endif |  | ||||||
|  | |||||||
| @ -31,10 +31,10 @@ ArchiveApp* archive_alloc() { | |||||||
|     view_dispatcher_set_navigation_event_callback( |     view_dispatcher_set_navigation_event_callback( | ||||||
|         archive->view_dispatcher, archive_back_event_callback); |         archive->view_dispatcher, archive_back_event_callback); | ||||||
| 
 | 
 | ||||||
|     archive->main_view = main_view_alloc(); |     archive->browser = browser_alloc(); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         archive->view_dispatcher, ArchiveViewBrowser, archive_main_get_view(archive->main_view)); |         archive->view_dispatcher, ArchiveViewBrowser, archive_browser_get_view(archive->browser)); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         archive->view_dispatcher, ArchiveViewTextInput, text_input_get_view(archive->text_input)); |         archive->view_dispatcher, ArchiveViewTextInput, text_input_get_view(archive->text_input)); | ||||||
| @ -49,7 +49,7 @@ void archive_free(ArchiveApp* archive) { | |||||||
|     view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewTextInput); |     view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewTextInput); | ||||||
|     view_dispatcher_free(archive->view_dispatcher); |     view_dispatcher_free(archive->view_dispatcher); | ||||||
|     scene_manager_free(archive->scene_manager); |     scene_manager_free(archive->scene_manager); | ||||||
|     main_view_free(archive->main_view); |     browser_free(archive->browser); | ||||||
| 
 | 
 | ||||||
|     text_input_free(archive->text_input); |     text_input_free(archive->text_input); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -9,72 +9,20 @@ | |||||||
| #include <gui/modules/text_input.h> | #include <gui/modules/text_input.h> | ||||||
| #include <loader/loader.h> | #include <loader/loader.h> | ||||||
| 
 | 
 | ||||||
| #include <m-string.h> | #include "views/archive_browser_view.h" | ||||||
| #include <m-array.h> |  | ||||||
| #include <storage/storage.h> |  | ||||||
| #include "applications.h" |  | ||||||
| #include "file-worker.h" |  | ||||||
| 
 |  | ||||||
| #include "views/archive_main_view.h" |  | ||||||
| #include "scenes/archive_scene.h" | #include "scenes/archive_scene.h" | ||||||
| 
 | 
 | ||||||
| #define MAX_FILE_SIZE 128 |  | ||||||
| 
 |  | ||||||
| typedef enum { | typedef enum { | ||||||
|     ArchiveViewBrowser, |     ArchiveViewBrowser, | ||||||
|     ArchiveViewTextInput, |     ArchiveViewTextInput, | ||||||
|     ArchiveViewTotal, |     ArchiveViewTotal, | ||||||
| } ArchiveViewEnum; | } ArchiveViewEnum; | ||||||
| 
 | 
 | ||||||
| static const char* tab_default_paths[] = { |  | ||||||
|     [ArchiveTabFavorites] = "/any/favorites", |  | ||||||
|     [ArchiveTabIButton] = "/any/ibutton", |  | ||||||
|     [ArchiveTabNFC] = "/any/nfc", |  | ||||||
|     [ArchiveTabSubGhz] = "/any/subghz/saved", |  | ||||||
|     [ArchiveTabLFRFID] = "/any/lfrfid", |  | ||||||
|     [ArchiveTabIrda] = "/any/irda", |  | ||||||
|     [ArchiveTabBrowser] = "/any", |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static inline const char* get_default_path(ArchiveFileTypeEnum type) { |  | ||||||
|     switch(type) { |  | ||||||
|     case ArchiveFileTypeIButton: |  | ||||||
|         return tab_default_paths[ArchiveTabIButton]; |  | ||||||
|     case ArchiveFileTypeNFC: |  | ||||||
|         return tab_default_paths[ArchiveTabNFC]; |  | ||||||
|     case ArchiveFileTypeSubGhz: |  | ||||||
|         return tab_default_paths[ArchiveTabSubGhz]; |  | ||||||
|     case ArchiveFileTypeLFRFID: |  | ||||||
|         return tab_default_paths[ArchiveTabLFRFID]; |  | ||||||
|     case ArchiveFileTypeIrda: |  | ||||||
|         return tab_default_paths[ArchiveTabIrda]; |  | ||||||
|     default: |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static inline const char* get_favorites_path() { |  | ||||||
|     return tab_default_paths[ArchiveTabFavorites]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| typedef enum { |  | ||||||
|     EventTypeTick, |  | ||||||
|     EventTypeKey, |  | ||||||
|     EventTypeExit, |  | ||||||
| } EventType; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     union { |  | ||||||
|         InputEvent input; |  | ||||||
|     } value; |  | ||||||
|     EventType type; |  | ||||||
| } AppEvent; |  | ||||||
| 
 |  | ||||||
| struct ArchiveApp { | struct ArchiveApp { | ||||||
|     Gui* gui; |     Gui* gui; | ||||||
|     ViewDispatcher* view_dispatcher; |     ViewDispatcher* view_dispatcher; | ||||||
|     SceneManager* scene_manager; |     SceneManager* scene_manager; | ||||||
|     ArchiveMainView* main_view; |     ArchiveBrowserView* browser; | ||||||
|     TextInput* text_input; |     TextInput* text_input; | ||||||
|     char text_store[MAX_NAME_LEN]; |     char text_store[MAX_NAME_LEN]; | ||||||
| }; | }; | ||||||
|  | |||||||
							
								
								
									
										260
									
								
								applications/archive/helpers/archive_browser.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										260
									
								
								applications/archive/helpers/archive_browser.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,260 @@ | |||||||
|  | #include "archive_browser.h" | ||||||
|  | #include "math.h" | ||||||
|  | 
 | ||||||
|  | void archive_update_offset(ArchiveBrowserView* browser) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |             size_t array_size = files_array_size(model->files); | ||||||
|  |             uint16_t bounds = array_size > 3 ? 2 : array_size; | ||||||
|  | 
 | ||||||
|  |             if(array_size > 3 && model->idx >= array_size - 1) { | ||||||
|  |                 model->list_offset = model->idx - 3; | ||||||
|  |             } else if(model->last_offset && model->last_offset != model->list_offset) { | ||||||
|  |                 model->list_offset = model->last_offset; | ||||||
|  |                 model->last_offset = !model->last_offset; | ||||||
|  |             } else if(model->list_offset < model->idx - bounds) { | ||||||
|  |                 model->list_offset = CLAMP(model->idx - 2, array_size - bounds, 0); | ||||||
|  |             } else if(model->list_offset > model->idx - bounds) { | ||||||
|  |                 model->list_offset = CLAMP(model->idx - 1, array_size - bounds, 0); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void archive_update_focus(ArchiveBrowserView* browser, const char* target) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     furi_assert(target); | ||||||
|  | 
 | ||||||
|  |     archive_get_filenames(browser, string_get_cstr(browser->path)); | ||||||
|  | 
 | ||||||
|  |     if(!archive_file_array_size(browser) && !archive_get_depth(browser)) { | ||||||
|  |         archive_switch_tab(browser, DEFAULT_TAB_DIR); | ||||||
|  |     } else { | ||||||
|  |         with_view_model( | ||||||
|  |             browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |                 uint16_t idx = 0; | ||||||
|  |                 while(idx < files_array_size(model->files)) { | ||||||
|  |                     ArchiveFile_t* current = files_array_get(model->files, idx); | ||||||
|  |                     if(!string_search(current->name, target)) { | ||||||
|  |                         model->idx = idx; | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                     ++idx; | ||||||
|  |                 } | ||||||
|  |                 return false; | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |         archive_update_offset(browser); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t archive_file_array_size(ArchiveBrowserView* browser) { | ||||||
|  |     uint16_t size = 0; | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |             size = files_array_size(model->files); | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  |     return size; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void archive_file_array_rm_selected(ArchiveBrowserView* browser) { | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |             files_array_remove_v(model->files, model->idx, model->idx + 1); | ||||||
|  |             model->idx = CLAMP(model->idx, files_array_size(model->files) - 1, 0); | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     if(!archive_file_array_size(browser) && !archive_get_depth(browser)) { | ||||||
|  |         archive_switch_tab(browser, DEFAULT_TAB_DIR); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     archive_update_offset(browser); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void archive_file_array_rm_all(ArchiveBrowserView* browser) { | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |             files_array_clean(model->files); | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser) { | ||||||
|  |     ArchiveFile_t* selected; | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |             selected = files_array_size(model->files) ? files_array_get(model->files, model->idx) : | ||||||
|  |                                                         NULL; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  |     return selected; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser) { | ||||||
|  |     ArchiveTabEnum tab_id; | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |             tab_id = model->tab_idx; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  |     return tab_id; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint8_t archive_get_depth(ArchiveBrowserView* browser) { | ||||||
|  |     uint8_t depth; | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |             depth = model->depth; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     return depth; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const char* archive_get_path(ArchiveBrowserView* browser) { | ||||||
|  |     return string_get_cstr(browser->path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const char* archive_get_name(ArchiveBrowserView* browser) { | ||||||
|  |     ArchiveFile_t* selected = archive_get_current_file(browser); | ||||||
|  |     return string_get_cstr(selected->name); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void archive_set_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab) { | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |             model->tab_idx = tab; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | void archive_set_last_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab) { | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |             model->last_tab = model->tab_idx; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void archive_add_item(ArchiveBrowserView* browser, FileInfo* file_info, const char* name) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     furi_assert(file_info); | ||||||
|  |     furi_assert(name); | ||||||
|  | 
 | ||||||
|  |     ArchiveFile_t item; | ||||||
|  | 
 | ||||||
|  |     if(filter_by_extension(file_info, get_tab_ext(archive_get_tab(browser)), name)) { | ||||||
|  |         ArchiveFile_t_init(&item); | ||||||
|  |         string_init_set_str(item.name, name); | ||||||
|  |         set_file_type(&item, file_info); | ||||||
|  | 
 | ||||||
|  |         with_view_model( | ||||||
|  |             browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |                 files_array_push_back(model->files, item); | ||||||
|  |                 return false; | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |         ArchiveFile_t_clear(&item); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void archive_show_file_menu(ArchiveBrowserView* browser, bool show) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |             model->menu = show; | ||||||
|  |             model->menu_idx = 0; | ||||||
|  | 
 | ||||||
|  |             if(show) { | ||||||
|  |                 ArchiveFile_t* selected = files_array_get(model->files, model->idx); | ||||||
|  |                 selected->fav = archive_is_favorite( | ||||||
|  |                     "%s/%s", string_get_cstr(browser->path), string_get_cstr(selected->name)); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void archive_switch_dir(ArchiveBrowserView* browser, const char* path) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     furi_assert(path); | ||||||
|  | 
 | ||||||
|  |     string_set(browser->path, path); | ||||||
|  |     archive_get_filenames(browser, string_get_cstr(browser->path)); | ||||||
|  |     archive_update_offset(browser); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     ArchiveTabEnum tab = archive_get_tab(browser); | ||||||
|  | 
 | ||||||
|  |     if(key == InputKeyLeft) { | ||||||
|  |         tab = ((tab - 1) + ArchiveTabTotal) % ArchiveTabTotal; | ||||||
|  |     } else if(key == InputKeyRight) { | ||||||
|  |         tab = (tab + 1) % ArchiveTabTotal; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     archive_set_tab(browser, tab); | ||||||
|  | 
 | ||||||
|  |     if((tab != ArchiveTabFavorites && | ||||||
|  |         !archive_dir_empty(browser, archive_get_default_path(tab))) || | ||||||
|  |        (tab == ArchiveTabFavorites && !archive_favorites_count(browser))) { | ||||||
|  |         if(tab != ArchiveTabBrowser) { | ||||||
|  |             archive_switch_tab(browser, key); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         with_view_model( | ||||||
|  |             browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |                 if(model->last_tab != model->tab_idx) { | ||||||
|  |                     model->idx = 0; | ||||||
|  |                     model->depth = 0; | ||||||
|  |                 } | ||||||
|  |                 return false; | ||||||
|  |             }); | ||||||
|  |         archive_switch_dir(browser, archive_get_default_path(tab)); | ||||||
|  |     } | ||||||
|  |     archive_set_last_tab(browser, tab); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void archive_enter_dir(ArchiveBrowserView* browser, string_t name) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     furi_assert(name); | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |             model->last_idx = model->idx; | ||||||
|  |             model->last_offset = model->list_offset; | ||||||
|  |             model->idx = 0; | ||||||
|  |             model->depth = CLAMP(model->depth + 1, MAX_DEPTH, 0); | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     string_cat(browser->path, "/"); | ||||||
|  |     string_cat(browser->path, name); | ||||||
|  | 
 | ||||||
|  |     archive_switch_dir(browser, string_get_cstr(browser->path)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void archive_leave_dir(ArchiveBrowserView* browser) { | ||||||
|  |     furi_assert(browser); | ||||||
|  | 
 | ||||||
|  |     const char* path = archive_get_path(browser); | ||||||
|  |     char* last_char_ptr = strrchr(path, '/'); | ||||||
|  | 
 | ||||||
|  |     if(last_char_ptr) { | ||||||
|  |         size_t pos = last_char_ptr - path; | ||||||
|  |         string_left(browser->path, pos); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |             model->depth = CLAMP(model->depth - 1, MAX_DEPTH, 0); | ||||||
|  |             model->idx = model->last_idx; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     archive_switch_dir(browser, path); | ||||||
|  | } | ||||||
							
								
								
									
										68
									
								
								applications/archive/helpers/archive_browser.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								applications/archive/helpers/archive_browser.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "../archive_i.h" | ||||||
|  | 
 | ||||||
|  | #define DEFAULT_TAB_DIR InputKeyRight //default tab swith direction
 | ||||||
|  | 
 | ||||||
|  | static const char* tab_default_paths[] = { | ||||||
|  |     [ArchiveTabFavorites] = "/any/favorites", | ||||||
|  |     [ArchiveTabIButton] = "/any/ibutton", | ||||||
|  |     [ArchiveTabNFC] = "/any/nfc", | ||||||
|  |     [ArchiveTabSubGhz] = "/any/subghz/saved", | ||||||
|  |     [ArchiveTabLFRFID] = "/any/lfrfid", | ||||||
|  |     [ArchiveTabIrda] = "/any/irda", | ||||||
|  |     [ArchiveTabBrowser] = "/any", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const char* known_ext[] = { | ||||||
|  |     [ArchiveFileTypeIButton] = ".ibtn", | ||||||
|  |     [ArchiveFileTypeNFC] = ".nfc", | ||||||
|  |     [ArchiveFileTypeSubGhz] = ".sub", | ||||||
|  |     [ArchiveFileTypeLFRFID] = ".rfid", | ||||||
|  |     [ArchiveFileTypeIrda] = ".ir", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static inline const char* get_tab_ext(ArchiveTabEnum tab) { | ||||||
|  |     switch(tab) { | ||||||
|  |     case ArchiveTabIButton: | ||||||
|  |         return known_ext[ArchiveFileTypeIButton]; | ||||||
|  |     case ArchiveTabNFC: | ||||||
|  |         return known_ext[ArchiveFileTypeNFC]; | ||||||
|  |     case ArchiveTabSubGhz: | ||||||
|  |         return known_ext[ArchiveFileTypeSubGhz]; | ||||||
|  |     case ArchiveTabLFRFID: | ||||||
|  |         return known_ext[ArchiveFileTypeLFRFID]; | ||||||
|  |     case ArchiveTabIrda: | ||||||
|  |         return known_ext[ArchiveFileTypeIrda]; | ||||||
|  |     default: | ||||||
|  |         return "*"; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline const char* archive_get_default_path(ArchiveTabEnum tab) { | ||||||
|  |     return tab_default_paths[tab]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | inline bool is_known_app(ArchiveFileTypeEnum type) { | ||||||
|  |     return (type != ArchiveFileTypeFolder && type != ArchiveFileTypeUnknown); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void archive_update_offset(ArchiveBrowserView* browser); | ||||||
|  | void archive_update_focus(ArchiveBrowserView* browser, const char* target); | ||||||
|  | 
 | ||||||
|  | size_t archive_file_array_size(ArchiveBrowserView* browser); | ||||||
|  | void archive_file_array_rm_selected(ArchiveBrowserView* browser); | ||||||
|  | void archive_file_array_rm_all(ArchiveBrowserView* browser); | ||||||
|  | 
 | ||||||
|  | ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser); | ||||||
|  | ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser); | ||||||
|  | uint8_t archive_get_depth(ArchiveBrowserView* browser); | ||||||
|  | const char* archive_get_path(ArchiveBrowserView* browser); | ||||||
|  | const char* archive_get_name(ArchiveBrowserView* browser); | ||||||
|  | 
 | ||||||
|  | void archive_add_item(ArchiveBrowserView* browser, FileInfo* file_info, const char* name); | ||||||
|  | void archive_show_file_menu(ArchiveBrowserView* browser, bool show); | ||||||
|  | 
 | ||||||
|  | void archive_switch_tab(ArchiveBrowserView* browser, InputKey key); | ||||||
|  | void archive_enter_dir(ArchiveBrowserView* browser, string_t name); | ||||||
|  | void archive_leave_dir(ArchiveBrowserView* browser); | ||||||
| @ -1,18 +1,47 @@ | |||||||
|  | 
 | ||||||
| #include "archive_favorites.h" | #include "archive_favorites.h" | ||||||
| #include "archive_files.h" | #include "archive_browser.h" | ||||||
| #include "../views/archive_main_view.h" | 
 | ||||||
|  | uint16_t archive_favorites_count(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
|  | 
 | ||||||
|  |     string_t buffer; | ||||||
|  |     string_init(buffer); | ||||||
|  | 
 | ||||||
|  |     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||||
|  |     uint16_t lines = 0; | ||||||
|  | 
 | ||||||
|  |     if(result) { | ||||||
|  |         while(1) { | ||||||
|  |             if(!file_worker_read_until(file_worker, buffer, '\n')) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(!string_size(buffer)) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             ++lines; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(buffer); | ||||||
|  |     file_worker_close(file_worker); | ||||||
|  |     file_worker_free(file_worker); | ||||||
|  |     return lines; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| bool archive_favorites_read(void* context) { | bool archive_favorites_read(void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| 
 | 
 | ||||||
|     ArchiveMainView* archive_view = context; |     ArchiveBrowserView* archive_view = context; | ||||||
|     FileWorker* file_worker = file_worker_alloc(true); |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
| 
 | 
 | ||||||
|     string_t buffer; |     string_t buffer; | ||||||
|     FileInfo file_info; |     FileInfo file_info; | ||||||
|     string_init(buffer); |     string_init(buffer); | ||||||
| 
 | 
 | ||||||
|     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_ALWAYS); |     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||||
| 
 | 
 | ||||||
|     if(result) { |     if(result) { | ||||||
|         while(1) { |         while(1) { | ||||||
| @ -23,7 +52,7 @@ bool archive_favorites_read(void* context) { | |||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             archive_view_add_item(archive_view, &file_info, string_get_cstr(buffer)); |             archive_add_item(archive_view, &file_info, string_get_cstr(buffer)); | ||||||
|             string_clean(buffer); |             string_clean(buffer); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -33,18 +62,19 @@ bool archive_favorites_read(void* context) { | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool archive_favorites_delete(const char* file_path, const char* name) { | bool archive_favorites_delete(const char* format, ...) { | ||||||
|     furi_assert(file_path); |     va_list args; | ||||||
|     furi_assert(name); |     va_start(args, format); | ||||||
|  |     uint8_t len = vsnprintf(NULL, 0, format, args); | ||||||
|  |     char filename[len + 1]; | ||||||
|  |     vsnprintf(filename, len + 1, format, args); | ||||||
|  |     va_end(args); | ||||||
| 
 | 
 | ||||||
|     FileWorker* file_worker = file_worker_alloc(true); |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
| 
 | 
 | ||||||
|     string_t path; |  | ||||||
|     string_t buffer; |     string_t buffer; | ||||||
|     string_init(buffer); |     string_init(buffer); | ||||||
| 
 | 
 | ||||||
|     string_init_printf(path, "%s/%s", file_path, name); |  | ||||||
| 
 |  | ||||||
|     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); |     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||||
|     if(result) { |     if(result) { | ||||||
|         while(1) { |         while(1) { | ||||||
| @ -55,17 +85,13 @@ bool archive_favorites_delete(const char* file_path, const char* name) { | |||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if(string_search(buffer, path)) { |             if(string_search_str(buffer, filename)) { | ||||||
|                 string_t temp; |                 archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\r\n", string_get_cstr(buffer)); | ||||||
|                 string_init_printf(temp, "%s\r\n", string_get_cstr(buffer)); |  | ||||||
|                 archive_file_append(ARCHIVE_FAV_TEMP_PATH, temp); |  | ||||||
|                 string_clear(temp); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     string_clear(buffer); |     string_clear(buffer); | ||||||
|     string_clear(path); |  | ||||||
| 
 | 
 | ||||||
|     file_worker_close(file_worker); |     file_worker_close(file_worker); | ||||||
|     file_worker_remove(file_worker, ARCHIVE_FAV_PATH); |     file_worker_remove(file_worker, ARCHIVE_FAV_PATH); | ||||||
| @ -76,19 +102,20 @@ bool archive_favorites_delete(const char* file_path, const char* name) { | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool archive_is_favorite(const char* file_path, const char* name) { | bool archive_is_favorite(const char* format, ...) { | ||||||
|     furi_assert(file_path); |     va_list args; | ||||||
|     furi_assert(name); |     va_start(args, format); | ||||||
|  |     uint8_t len = vsnprintf(NULL, 0, format, args); | ||||||
|  |     char filename[len + 1]; | ||||||
|  |     vsnprintf(filename, len + 1, format, args); | ||||||
|  |     va_end(args); | ||||||
| 
 | 
 | ||||||
|     FileWorker* file_worker = file_worker_alloc(true); |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
| 
 |  | ||||||
|     string_t path; |  | ||||||
|     string_t buffer; |     string_t buffer; | ||||||
|     string_init(buffer); |     string_init(buffer); | ||||||
|     bool found = false; |  | ||||||
| 
 | 
 | ||||||
|     string_init_printf(path, "%s/%s", file_path, name); |     bool found = false; | ||||||
|     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_ALWAYS); |     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||||
| 
 | 
 | ||||||
|     if(result) { |     if(result) { | ||||||
|         while(1) { |         while(1) { | ||||||
| @ -98,7 +125,7 @@ bool archive_is_favorite(const char* file_path, const char* name) { | |||||||
|             if(!string_size(buffer)) { |             if(!string_size(buffer)) { | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             if(!string_search(buffer, path)) { |             if(!string_search_str(buffer, filename)) { | ||||||
|                 found = true; |                 found = true; | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
| @ -106,7 +133,6 @@ bool archive_is_favorite(const char* file_path, const char* name) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     string_clear(buffer); |     string_clear(buffer); | ||||||
|     string_clear(path); |  | ||||||
|     file_worker_close(file_worker); |     file_worker_close(file_worker); | ||||||
|     file_worker_free(file_worker); |     file_worker_free(file_worker); | ||||||
| 
 | 
 | ||||||
| @ -122,10 +148,8 @@ bool archive_favorites_rename(const char* file_path, const char* src, const char | |||||||
| 
 | 
 | ||||||
|     string_t path; |     string_t path; | ||||||
|     string_t buffer; |     string_t buffer; | ||||||
|     string_t temp; |  | ||||||
| 
 | 
 | ||||||
|     string_init(buffer); |     string_init(buffer); | ||||||
|     string_init(temp); |  | ||||||
|     string_init(path); |     string_init(path); | ||||||
| 
 | 
 | ||||||
|     string_printf(path, "%s/%s", file_path, src); |     string_printf(path, "%s/%s", file_path, src); | ||||||
| @ -139,14 +163,14 @@ bool archive_favorites_rename(const char* file_path, const char* src, const char | |||||||
|             if(!string_size(buffer)) { |             if(!string_size(buffer)) { | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             string_printf( | 
 | ||||||
|                 temp, "%s\r\n", string_search(buffer, path) ? string_get_cstr(buffer) : dst); |             archive_file_append( | ||||||
|             archive_file_append(ARCHIVE_FAV_TEMP_PATH, temp); |                 ARCHIVE_FAV_TEMP_PATH, | ||||||
|             string_clean(temp); |                 "%s\r\n", | ||||||
|  |                 string_search(buffer, path) ? string_get_cstr(buffer) : dst); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     string_clear(temp); |  | ||||||
|     string_clear(buffer); |     string_clear(buffer); | ||||||
|     string_clear(path); |     string_clear(path); | ||||||
| 
 | 
 | ||||||
| @ -159,13 +183,8 @@ bool archive_favorites_rename(const char* file_path, const char* src, const char | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void archive_add_to_favorites(const char* file_path, const char* name) { | void archive_add_to_favorites(const char* file_path) { | ||||||
|     furi_assert(file_path); |     furi_assert(file_path); | ||||||
|     furi_assert(name); |  | ||||||
| 
 | 
 | ||||||
|     string_t buffer_src; |     archive_file_append(ARCHIVE_FAV_PATH, "%s\r\n", file_path); | ||||||
| 
 |  | ||||||
|     string_init_printf(buffer_src, "%s/%s\r\n", file_path, name); |  | ||||||
|     archive_file_append(ARCHIVE_FAV_PATH, buffer_src); |  | ||||||
|     string_clear(buffer_src); |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,8 +4,9 @@ | |||||||
| #define ARCHIVE_FAV_PATH "/any/favorites.txt" | #define ARCHIVE_FAV_PATH "/any/favorites.txt" | ||||||
| #define ARCHIVE_FAV_TEMP_PATH "/any/favorites.tmp" | #define ARCHIVE_FAV_TEMP_PATH "/any/favorites.tmp" | ||||||
| 
 | 
 | ||||||
|  | uint16_t archive_favorites_count(void* context); | ||||||
| bool archive_favorites_read(void* context); | bool archive_favorites_read(void* context); | ||||||
| bool archive_favorites_delete(const char* file_path, const char* name); | bool archive_favorites_delete(const char* format, ...); | ||||||
| bool archive_is_favorite(const char* file_path, const char* name); | bool archive_is_favorite(const char* format, ...); | ||||||
| bool archive_favorites_rename(const char* file_path, const char* src, const char* dst); | bool archive_favorites_rename(const char* file_path, const char* src, const char* dst); | ||||||
| void archive_add_to_favorites(const char* file_path, const char* name); | void archive_add_to_favorites(const char* file_path); | ||||||
| @ -1,6 +1,5 @@ | |||||||
| #include "archive_files.h" | #include "archive_files.h" | ||||||
| #include "archive_favorites.h" | #include "archive_browser.h" | ||||||
| #include "../archive_i.h" |  | ||||||
| 
 | 
 | ||||||
| bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name) { | bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name) { | ||||||
|     furi_assert(file_info); |     furi_assert(file_info); | ||||||
| @ -20,14 +19,12 @@ bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* n | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void archive_trim_file_ext(char* name) { | void archive_trim_file_path(char* name, bool ext) { | ||||||
|     size_t str_len = strlen(name); |     char* slash = strrchr(name, '/') + 1; | ||||||
|     char* end = name + str_len; |     if(strlen(slash)) strlcpy(name, slash, strlen(slash) + 1); | ||||||
|     while(end > name && *end != '.' && *end != '\\' && *end != '/') { |     if(ext) { | ||||||
|         --end; |         char* dot = strrchr(name, '.'); | ||||||
|     } |         if(strlen(dot)) *dot = '\0'; | ||||||
|     if((end > name && *end == '.') && (*(end - 1) != '\\' && *(end - 1) != '/')) { |  | ||||||
|         *end = '\0'; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -49,24 +46,24 @@ void set_file_type(ArchiveFile_t* file, FileInfo* file_info) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool archive_get_filenames(void* context, uint8_t tab_id, const char* path) { | bool archive_get_filenames(void* context, const char* path) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| 
 | 
 | ||||||
|     ArchiveMainView* main_view = context; |     bool res; | ||||||
|     archive_file_array_clean(main_view); |     ArchiveBrowserView* browser = context; | ||||||
|  |     archive_file_array_rm_all(browser); | ||||||
| 
 | 
 | ||||||
|     if(tab_id != ArchiveTabFavorites) { |     if(archive_get_tab(browser) != ArchiveTabFavorites) { | ||||||
|         archive_read_dir(main_view, path); |         res = archive_read_dir(browser, path); | ||||||
|     } else { |     } else { | ||||||
|         archive_favorites_read(main_view); |         res = archive_favorites_read(browser); | ||||||
|     } |     } | ||||||
|     return true; |     return res; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool archive_read_dir(void* context, const char* path) { | bool archive_dir_empty(void* context, const char* path) { // can be simpler?
 | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| 
 | 
 | ||||||
|     ArchiveMainView* main_view = context; |  | ||||||
|     FileInfo file_info; |     FileInfo file_info; | ||||||
|     Storage* fs_api = furi_record_open("storage"); |     Storage* fs_api = furi_record_open("storage"); | ||||||
|     File* directory = storage_file_alloc(fs_api); |     File* directory = storage_file_alloc(fs_api); | ||||||
| @ -78,17 +75,52 @@ bool archive_read_dir(void* context, const char* path) { | |||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     bool files_found = false; | ||||||
|     while(1) { |     while(1) { | ||||||
|         if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) { |         if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) { | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|  |         if(files_found) { | ||||||
|  |             break; | ||||||
|  |         } else if(storage_file_get_error(directory) == FSE_OK) { | ||||||
|  |             files_found = name[0]; | ||||||
|  |         } else { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     storage_dir_close(directory); | ||||||
|  |     storage_file_free(directory); | ||||||
| 
 | 
 | ||||||
|         uint16_t files_cnt = archive_file_array_size(main_view); |     furi_record_close("storage"); | ||||||
| 
 | 
 | ||||||
|  |     return files_found; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool archive_read_dir(void* context, const char* path) { | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     ArchiveBrowserView* browser = context; | ||||||
|  |     FileInfo file_info; | ||||||
|  |     Storage* fs_api = furi_record_open("storage"); | ||||||
|  |     File* directory = storage_file_alloc(fs_api); | ||||||
|  |     char name[MAX_NAME_LEN]; | ||||||
|  |     size_t files_cnt = 0; | ||||||
|  | 
 | ||||||
|  |     if(!storage_dir_open(directory, path)) { | ||||||
|  |         storage_dir_close(directory); | ||||||
|  |         storage_file_free(directory); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     while(1) { | ||||||
|  |         if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|         if(files_cnt > MAX_FILES) { |         if(files_cnt > MAX_FILES) { | ||||||
|             break; |             break; | ||||||
|         } else if(storage_file_get_error(directory) == FSE_OK) { |         } else if(storage_file_get_error(directory) == FSE_OK) { | ||||||
|             archive_view_add_item(main_view, &file_info, name); |             archive_add_item(browser, &file_info, name); | ||||||
|  |             ++files_cnt; | ||||||
|         } else { |         } else { | ||||||
|             storage_dir_close(directory); |             storage_dir_close(directory); | ||||||
|             storage_file_free(directory); |             storage_file_free(directory); | ||||||
| @ -103,9 +135,15 @@ bool archive_read_dir(void* context, const char* path) { | |||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void archive_file_append(const char* path, string_t string) { | void archive_file_append(const char* path, const char* format, ...) { | ||||||
|     furi_assert(path); |     furi_assert(path); | ||||||
|     furi_assert(string); | 
 | ||||||
|  |     va_list args; | ||||||
|  |     va_start(args, format); | ||||||
|  |     uint8_t len = vsnprintf(NULL, 0, format, args); | ||||||
|  |     char cstr_buff[len + 1]; | ||||||
|  |     vsnprintf(cstr_buff, len + 1, format, args); | ||||||
|  |     va_end(args); | ||||||
| 
 | 
 | ||||||
|     FileWorker* file_worker = file_worker_alloc(false); |     FileWorker* file_worker = file_worker_alloc(false); | ||||||
| 
 | 
 | ||||||
| @ -113,7 +151,7 @@ void archive_file_append(const char* path, string_t string) { | |||||||
|         FURI_LOG_E("Archive", "Append open error"); |         FURI_LOG_E("Archive", "Append open error"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(!file_worker_write(file_worker, string_get_cstr(string), string_size(string))) { |     if(!file_worker_write(file_worker, cstr_buff, strlen(cstr_buff))) { | ||||||
|         FURI_LOG_E("Archive", "Append write error"); |         FURI_LOG_E("Archive", "Append write error"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -125,19 +163,22 @@ void archive_delete_file(void* context, string_t path, string_t name) { | |||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     furi_assert(path); |     furi_assert(path); | ||||||
|     furi_assert(name); |     furi_assert(name); | ||||||
|     ArchiveMainView* main_view = context; |     ArchiveBrowserView* browser = context; | ||||||
|     FileWorker* file_worker = file_worker_alloc(false); |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
| 
 | 
 | ||||||
|     string_t full_path; |     string_t full_path; | ||||||
|     string_init(full_path); |     string_init_printf(full_path, "%s/%s", string_get_cstr(path), string_get_cstr(name)); | ||||||
|     string_printf(full_path, "%s/%s", string_get_cstr(path), string_get_cstr(name)); |  | ||||||
|     file_worker_remove(file_worker, string_get_cstr(full_path)); |  | ||||||
|     file_worker_free(file_worker); |  | ||||||
|     string_clear(full_path); |  | ||||||
| 
 | 
 | ||||||
|     if(archive_is_favorite(string_get_cstr(path), string_get_cstr(name))) { |     bool res = file_worker_remove(file_worker, string_get_cstr(full_path)); | ||||||
|         archive_favorites_delete(string_get_cstr(path), string_get_cstr(name)); |     file_worker_free(file_worker); | ||||||
|  | 
 | ||||||
|  |     if(archive_is_favorite(string_get_cstr(full_path))) { | ||||||
|  |         archive_favorites_delete(string_get_cstr(full_path)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     archive_file_array_remove_selected(main_view); |     if(res) { | ||||||
|  |         archive_file_array_rm_selected(browser); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(full_path); | ||||||
| } | } | ||||||
|  | |||||||
| @ -49,8 +49,9 @@ ARRAY_DEF( | |||||||
| 
 | 
 | ||||||
| bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name); | bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name); | ||||||
| void set_file_type(ArchiveFile_t* file, FileInfo* file_info); | void set_file_type(ArchiveFile_t* file, FileInfo* file_info); | ||||||
| void archive_trim_file_ext(char* name); | void archive_trim_file_path(char* name, bool ext); | ||||||
| bool archive_get_filenames(void* context, uint8_t tab_id, const char* path); | bool archive_get_filenames(void* context, const char* path); | ||||||
|  | bool archive_dir_empty(void* context, const char* path); | ||||||
| bool archive_read_dir(void* context, const char* path); | bool archive_read_dir(void* context, const char* path); | ||||||
| void archive_file_append(const char* path, string_t string); | void archive_file_append(const char* path, const char* format, ...); | ||||||
| void archive_delete_file(void* context, string_t path, string_t name); | void archive_delete_file(void* context, string_t path, string_t name); | ||||||
| @ -1,32 +1,114 @@ | |||||||
| #include "../archive_i.h" | #include "../archive_i.h" | ||||||
| #include "../views/archive_main_view.h" | #include "../helpers/archive_files.h" | ||||||
|  | #include "../helpers/archive_favorites.h" | ||||||
|  | #include "../helpers/archive_browser.h" | ||||||
|  | #include "../views/archive_browser_view.h" | ||||||
|  | 
 | ||||||
|  | static const char* flipper_app_name[] = { | ||||||
|  |     [ArchiveFileTypeIButton] = "iButton", | ||||||
|  |     [ArchiveFileTypeNFC] = "NFC", | ||||||
|  |     [ArchiveFileTypeSubGhz] = "Sub-GHz", | ||||||
|  |     [ArchiveFileTypeLFRFID] = "125 kHz RFID", | ||||||
|  |     [ArchiveFileTypeIrda] = "Infrared", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void archive_run_in_app( | ||||||
|  |     ArchiveBrowserView* browser, | ||||||
|  |     ArchiveFile_t* selected, | ||||||
|  |     bool full_path_provided) { | ||||||
|  |     Loader* loader = furi_record_open("loader"); | ||||||
|  | 
 | ||||||
|  |     string_t full_path; | ||||||
|  |     if(!full_path_provided) { | ||||||
|  |         string_init_printf( | ||||||
|  |             full_path, "%s/%s", string_get_cstr(browser->path), string_get_cstr(selected->name)); | ||||||
|  |     } else { | ||||||
|  |         string_init_set(full_path, selected->name); | ||||||
|  |     } | ||||||
|  |     loader_start(loader, flipper_app_name[selected->type], string_get_cstr(full_path)); | ||||||
|  | 
 | ||||||
|  |     string_clear(full_path); | ||||||
|  |     furi_record_close("loader"); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| void archive_scene_browser_callback(ArchiveBrowserEvent event, void* context) { | void archive_scene_browser_callback(ArchiveBrowserEvent event, void* context) { | ||||||
|     ArchiveApp* archive = (ArchiveApp*)context; |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
|     view_dispatcher_send_custom_event(archive->view_dispatcher, event); |     view_dispatcher_send_custom_event(archive->view_dispatcher, event); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const void archive_scene_browser_on_enter(void* context) { | void archive_scene_browser_on_enter(void* context) { | ||||||
|     ArchiveApp* archive = (ArchiveApp*)context; |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
|     ArchiveMainView* main_view = archive->main_view; |     ArchiveBrowserView* browser = archive->browser; | ||||||
| 
 | 
 | ||||||
|     archive_browser_set_callback(main_view, archive_scene_browser_callback, archive); |     archive_browser_set_callback(browser, archive_scene_browser_callback, archive); | ||||||
|     archive_browser_update(main_view); |     archive_update_focus(browser, archive->text_store); | ||||||
|     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewBrowser); |     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewBrowser); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { | bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { | ||||||
|     ArchiveApp* archive = (ArchiveApp*)context; |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
|     bool consumed; |     ArchiveBrowserView* browser = archive->browser; | ||||||
|  |     ArchiveFile_t* selected = archive_get_current_file(browser); | ||||||
|  | 
 | ||||||
|  |     const char* path = archive_get_path(browser); | ||||||
|  |     const char* name = archive_get_name(browser); | ||||||
|  |     bool known_app = is_known_app(selected->type); | ||||||
|  |     bool favorites = archive_get_tab(browser) == ArchiveTabFavorites; | ||||||
|  |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|         switch(event.event) { |         switch(event.event) { | ||||||
|         case ArchiveBrowserEventRename: |         case ArchiveBrowserEventFileMenuOpen: | ||||||
|             scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneRename); |             archive_show_file_menu(browser, true); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
|  |         case ArchiveBrowserEventFileMenuClose: | ||||||
|  |             archive_show_file_menu(browser, false); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case ArchiveBrowserEventFileMenuRun: | ||||||
|  |             if(known_app) { | ||||||
|  |                 archive_run_in_app(browser, selected, favorites); | ||||||
|  |             } | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case ArchiveBrowserEventFileMenuPin: | ||||||
|  |             if(favorites) { | ||||||
|  |                 archive_favorites_delete(name); | ||||||
|  |                 archive_file_array_rm_selected(browser); | ||||||
|  |             } else if(known_app) { | ||||||
|  |                 if(archive_is_favorite("%s/%s", path, name)) { | ||||||
|  |                     archive_favorites_delete("%s/%s", path, name); | ||||||
|  |                 } else { | ||||||
|  |                     archive_file_append(ARCHIVE_FAV_PATH, "%s/%s\r\n", path, name); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             archive_show_file_menu(browser, false); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         case ArchiveBrowserEventFileMenuRename: | ||||||
|  |             if(known_app && !favorites) { | ||||||
|  |                 scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneRename); | ||||||
|  |             } | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case ArchiveBrowserEventFileMenuDelete: | ||||||
|  |             archive_delete_file(browser, browser->path, selected->name); | ||||||
|  |             archive_show_file_menu(browser, false); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case ArchiveBrowserEventEnterDir: | ||||||
|  |             archive_enter_dir(browser, selected->name); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|         case ArchiveBrowserEventExit: |         case ArchiveBrowserEventExit: | ||||||
|             view_dispatcher_stop(archive->view_dispatcher); |             if(archive_get_depth(browser)) { | ||||||
|  |                 archive_leave_dir(browser); | ||||||
|  |             } else { | ||||||
|  |                 view_dispatcher_stop(archive->view_dispatcher); | ||||||
|  |             } | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
| @ -37,6 +119,6 @@ const bool archive_scene_browser_on_event(void* context, SceneManagerEvent event | |||||||
|     return consumed; |     return consumed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const void archive_scene_browser_on_exit(void* context) { | void archive_scene_browser_on_exit(void* context) { | ||||||
|     // ArchiveApp* archive = (ArchiveApp*)context;
 |     // ArchiveApp* archive = (ArchiveApp*)context;
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,22 +1,24 @@ | |||||||
| #include "../archive_i.h" | #include "../archive_i.h" | ||||||
| #include "../helpers/archive_favorites.h" | #include "../helpers/archive_favorites.h" | ||||||
| #include "../helpers/archive_files.h" | #include "../helpers/archive_files.h" | ||||||
|  | #include "../helpers/archive_browser.h" | ||||||
| 
 | 
 | ||||||
| #define SCENE_RENAME_CUSTOM_EVENT (0UL) | #define SCENE_RENAME_CUSTOM_EVENT (0UL) | ||||||
|  | #define MAX_TEXT_INPUT_LEN 22 | ||||||
| 
 | 
 | ||||||
| void archive_scene_rename_text_input_callback(void* context) { | void archive_scene_rename_text_input_callback(void* context) { | ||||||
|     ArchiveApp* archive = (ArchiveApp*)context; |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
|     view_dispatcher_send_custom_event(archive->view_dispatcher, SCENE_RENAME_CUSTOM_EVENT); |     view_dispatcher_send_custom_event(archive->view_dispatcher, SCENE_RENAME_CUSTOM_EVENT); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const void archive_scene_rename_on_enter(void* context) { | void archive_scene_rename_on_enter(void* context) { | ||||||
|     ArchiveApp* archive = (ArchiveApp*)context; |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
| 
 | 
 | ||||||
|     TextInput* text_input = archive->text_input; |     TextInput* text_input = archive->text_input; | ||||||
|     ArchiveFile_t* current = archive_get_current_file(archive->main_view); |     ArchiveFile_t* current = archive_get_current_file(archive->browser); | ||||||
|     strlcpy(archive->text_store, string_get_cstr(current->name), MAX_NAME_LEN); |     strlcpy(archive->text_store, string_get_cstr(current->name), MAX_NAME_LEN); | ||||||
| 
 | 
 | ||||||
|     archive_trim_file_ext(archive->text_store); |     archive_trim_file_path(archive->text_store, true); | ||||||
| 
 | 
 | ||||||
|     text_input_set_header_text(text_input, "Rename:"); |     text_input_set_header_text(text_input, "Rename:"); | ||||||
| 
 | 
 | ||||||
| @ -25,13 +27,13 @@ const void archive_scene_rename_on_enter(void* context) { | |||||||
|         archive_scene_rename_text_input_callback, |         archive_scene_rename_text_input_callback, | ||||||
|         archive, |         archive, | ||||||
|         archive->text_store, |         archive->text_store, | ||||||
|         MAX_NAME_LEN, |         MAX_TEXT_INPUT_LEN, | ||||||
|         false); |         false); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewTextInput); |     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewTextInput); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const bool archive_scene_rename_on_event(void* context, SceneManagerEvent event) { | bool archive_scene_rename_on_event(void* context, SceneManagerEvent event) { | ||||||
|     ArchiveApp* archive = (ArchiveApp*)context; |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
|     bool consumed = false; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
| @ -42,16 +44,14 @@ const bool archive_scene_rename_on_event(void* context, SceneManagerEvent event) | |||||||
|             string_t buffer_src; |             string_t buffer_src; | ||||||
|             string_t buffer_dst; |             string_t buffer_dst; | ||||||
| 
 | 
 | ||||||
|             const char* path = archive_get_path(archive->main_view); |             const char* path = archive_get_path(archive->browser); | ||||||
|             const char* name = archive_get_name(archive->main_view); |             const char* name = archive_get_name(archive->browser); | ||||||
| 
 | 
 | ||||||
|             string_init_printf(buffer_src, "%s/%s", path, name); |             string_init_printf(buffer_src, "%s/%s", path, name); | ||||||
|             string_init_printf(buffer_dst, "%s/%s", path, archive->text_store); |             string_init_printf(buffer_dst, "%s/%s", path, archive->text_store); | ||||||
| 
 | 
 | ||||||
|             archive_set_name(archive->main_view, archive->text_store); |  | ||||||
| 
 |  | ||||||
|             // append extension
 |             // append extension
 | ||||||
|             ArchiveFile_t* file = archive_get_current_file(archive->main_view); |             ArchiveFile_t* file = archive_get_current_file(archive->browser); | ||||||
| 
 | 
 | ||||||
|             string_cat(buffer_dst, known_ext[file->type]); |             string_cat(buffer_dst, known_ext[file->type]); | ||||||
|             storage_common_rename( |             storage_common_rename( | ||||||
| @ -72,9 +72,8 @@ const bool archive_scene_rename_on_event(void* context, SceneManagerEvent event) | |||||||
|     return consumed; |     return consumed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const void archive_scene_rename_on_exit(void* context) { | void archive_scene_rename_on_exit(void* context) { | ||||||
|     ArchiveApp* archive = (ArchiveApp*)context; |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
|     // Clear view
 |     // Clear view
 | ||||||
|     text_input_set_header_text(archive->text_input, NULL); |     text_input_clean(archive->text_input); | ||||||
|     text_input_set_result_callback(archive->text_input, NULL, NULL, NULL, 0, false); |  | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										294
									
								
								applications/archive/views/archive_browser_view.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										294
									
								
								applications/archive/views/archive_browser_view.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,294 @@ | |||||||
|  | #include <furi.h> | ||||||
|  | #include "../archive_i.h" | ||||||
|  | #include "archive_browser_view.h" | ||||||
|  | #include "../helpers/archive_browser.h" | ||||||
|  | 
 | ||||||
|  | static const char* ArchiveTabNames[] = { | ||||||
|  |     [ArchiveTabFavorites] = "Favorites", | ||||||
|  |     [ArchiveTabIButton] = "iButton", | ||||||
|  |     [ArchiveTabNFC] = "NFC", | ||||||
|  |     [ArchiveTabSubGhz] = "Sub-GHz", | ||||||
|  |     [ArchiveTabLFRFID] = "RFID LF", | ||||||
|  |     [ArchiveTabIrda] = "Infrared", | ||||||
|  |     [ArchiveTabBrowser] = "Browser"}; | ||||||
|  | 
 | ||||||
|  | static const Icon* ArchiveItemIcons[] = { | ||||||
|  |     [ArchiveFileTypeIButton] = &I_ibutt_10px, | ||||||
|  |     [ArchiveFileTypeNFC] = &I_Nfc_10px, | ||||||
|  |     [ArchiveFileTypeSubGhz] = &I_sub1_10px, | ||||||
|  |     [ArchiveFileTypeLFRFID] = &I_125_10px, | ||||||
|  |     [ArchiveFileTypeIrda] = &I_ir_10px, | ||||||
|  |     [ArchiveFileTypeFolder] = &I_dir_10px, | ||||||
|  |     [ArchiveFileTypeUnknown] = &I_unknown_10px, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void archive_browser_set_callback( | ||||||
|  |     ArchiveBrowserView* browser, | ||||||
|  |     ArchiveBrowserViewCallback callback, | ||||||
|  |     void* context) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     furi_assert(callback); | ||||||
|  |     browser->callback = callback; | ||||||
|  |     browser->context = context; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) { | ||||||
|  |     canvas_set_color(canvas, ColorWhite); | ||||||
|  |     canvas_draw_box(canvas, 71, 17, 57, 46); | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  |     elements_slightly_rounded_frame(canvas, 70, 16, 58, 48); | ||||||
|  | 
 | ||||||
|  |     string_t menu[MENU_ITEMS]; | ||||||
|  | 
 | ||||||
|  |     string_init_set_str(menu[0], "Run in app"); | ||||||
|  |     string_init_set_str(menu[1], "Pin"); | ||||||
|  |     string_init_set_str(menu[2], "Rename"); | ||||||
|  |     string_init_set_str(menu[3], "Delete"); | ||||||
|  | 
 | ||||||
|  |     ArchiveFile_t* selected = files_array_get(model->files, model->idx); | ||||||
|  | 
 | ||||||
|  |     if(!is_known_app(selected->type)) { | ||||||
|  |         string_set_str(menu[0], "---"); | ||||||
|  |         string_set_str(menu[1], "---"); | ||||||
|  |         string_set_str(menu[2], "---"); | ||||||
|  |     } else if(selected->fav) { | ||||||
|  |         string_set_str(menu[1], "Unpin"); | ||||||
|  |     } else if(model->tab_idx == ArchiveTabFavorites) { | ||||||
|  |         string_set_str(menu[1], "Unpin"); | ||||||
|  |         string_set_str(menu[2], "---"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for(size_t i = 0; i < MENU_ITEMS; i++) { | ||||||
|  |         canvas_draw_str(canvas, 82, 27 + i * 11, string_get_cstr(menu[i])); | ||||||
|  |         string_clear(menu[i]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     canvas_draw_icon(canvas, 74, 20 + model->menu_idx * 11, &I_ButtonRight_4x7); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void archive_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  |     canvas_draw_box(canvas, 0, 15 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT); | ||||||
|  | 
 | ||||||
|  |     canvas_set_color(canvas, ColorWhite); | ||||||
|  |     canvas_draw_dot(canvas, 0, 15 + idx * FRAME_HEIGHT); | ||||||
|  |     canvas_draw_dot(canvas, 1, 15 + idx * FRAME_HEIGHT); | ||||||
|  |     canvas_draw_dot(canvas, 0, (15 + idx * FRAME_HEIGHT) + 1); | ||||||
|  | 
 | ||||||
|  |     canvas_draw_dot(canvas, 0, (15 + idx * FRAME_HEIGHT) + 11); | ||||||
|  |     canvas_draw_dot(canvas, scrollbar ? 121 : 126, 15 + idx * FRAME_HEIGHT); | ||||||
|  |     canvas_draw_dot(canvas, scrollbar ? 121 : 126, (15 + idx * FRAME_HEIGHT) + 11); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { | ||||||
|  |     furi_assert(model); | ||||||
|  | 
 | ||||||
|  |     size_t array_size = files_array_size(model->files); | ||||||
|  |     bool scrollbar = array_size > 4; | ||||||
|  | 
 | ||||||
|  |     for(size_t i = 0; i < MIN(array_size, MENU_ITEMS); ++i) { | ||||||
|  |         string_t str_buff; | ||||||
|  |         char cstr_buff[MAX_NAME_LEN]; | ||||||
|  | 
 | ||||||
|  |         size_t idx = CLAMP(i + model->list_offset, array_size, 0); | ||||||
|  |         ArchiveFile_t* file = files_array_get(model->files, CLAMP(idx, array_size - 1, 0)); | ||||||
|  | 
 | ||||||
|  |         strlcpy(cstr_buff, string_get_cstr(file->name), string_size(file->name) + 1); | ||||||
|  |         archive_trim_file_path(cstr_buff, is_known_app(file->type)); | ||||||
|  |         string_init_set_str(str_buff, cstr_buff); | ||||||
|  |         elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); | ||||||
|  | 
 | ||||||
|  |         if(model->idx == idx) { | ||||||
|  |             archive_draw_frame(canvas, i, scrollbar); | ||||||
|  |         } else { | ||||||
|  |             canvas_set_color(canvas, ColorBlack); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         canvas_draw_icon(canvas, 2, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file->type]); | ||||||
|  |         canvas_draw_str(canvas, 15, 24 + i * FRAME_HEIGHT, string_get_cstr(str_buff)); | ||||||
|  |         string_clear(str_buff); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(scrollbar) { | ||||||
|  |         elements_scrollbar_pos(canvas, 126, 15, 49, model->idx, array_size); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(model->menu) { | ||||||
|  |         render_item_menu(canvas, model); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void archive_render_status_bar(Canvas* canvas, ArchiveBrowserViewModel* model) { | ||||||
|  |     furi_assert(model); | ||||||
|  | 
 | ||||||
|  |     const char* tab_name = ArchiveTabNames[model->tab_idx]; | ||||||
|  | 
 | ||||||
|  |     canvas_draw_icon(canvas, 0, 0, &I_Background_128x11); | ||||||
|  | 
 | ||||||
|  |     canvas_set_color(canvas, ColorWhite); | ||||||
|  |     canvas_draw_box(canvas, 0, 0, 50, 13); | ||||||
|  |     canvas_draw_box(canvas, 107, 0, 20, 13); | ||||||
|  | 
 | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  |     canvas_draw_frame(canvas, 1, 0, 50, 12); | ||||||
|  |     canvas_draw_line(canvas, 0, 1, 0, 11); | ||||||
|  |     canvas_draw_line(canvas, 1, 12, 49, 12); | ||||||
|  |     canvas_draw_str_aligned(canvas, 26, 9, AlignCenter, AlignBottom, tab_name); | ||||||
|  | 
 | ||||||
|  |     canvas_draw_frame(canvas, 108, 0, 20, 12); | ||||||
|  |     canvas_draw_line(canvas, 107, 1, 107, 11); | ||||||
|  |     canvas_draw_line(canvas, 108, 12, 126, 12); | ||||||
|  | 
 | ||||||
|  |     canvas_draw_icon(canvas, 112, 2, &I_ButtonLeft_4x7); | ||||||
|  |     canvas_draw_icon(canvas, 120, 2, &I_ButtonRight_4x7); | ||||||
|  | 
 | ||||||
|  |     canvas_set_color(canvas, ColorWhite); | ||||||
|  |     canvas_draw_dot(canvas, 50, 0); | ||||||
|  |     canvas_draw_dot(canvas, 127, 0); | ||||||
|  | 
 | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void archive_view_render(Canvas* canvas, void* model) { | ||||||
|  |     ArchiveBrowserViewModel* m = model; | ||||||
|  | 
 | ||||||
|  |     archive_render_status_bar(canvas, model); | ||||||
|  | 
 | ||||||
|  |     if(files_array_size(m->files)) { | ||||||
|  |         draw_list(canvas, m); | ||||||
|  |     } else { | ||||||
|  |         canvas_draw_str_aligned( | ||||||
|  |             canvas, GUI_DISPLAY_WIDTH / 2, 40, AlignCenter, AlignCenter, "Empty"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | View* archive_browser_get_view(ArchiveBrowserView* browser) { | ||||||
|  |     furi_assert(browser); | ||||||
|  |     return browser->view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool archive_view_input(InputEvent* event, void* context) { | ||||||
|  |     furi_assert(event); | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     ArchiveBrowserView* browser = context; | ||||||
|  | 
 | ||||||
|  |     bool in_menu; | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |             in_menu = model->menu; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     if(in_menu) { | ||||||
|  |         if(event->type == InputTypeShort) { | ||||||
|  |             if(event->key == InputKeyUp || event->key == InputKeyDown) { | ||||||
|  |                 with_view_model( | ||||||
|  |                     browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |                         if(event->key == InputKeyUp) { | ||||||
|  |                             model->menu_idx = ((model->menu_idx - 1) + MENU_ITEMS) % MENU_ITEMS; | ||||||
|  |                         } else if(event->key == InputKeyDown) { | ||||||
|  |                             model->menu_idx = (model->menu_idx + 1) % MENU_ITEMS; | ||||||
|  |                         } | ||||||
|  |                         return true; | ||||||
|  |                     }); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if(event->key == InputKeyOk) { | ||||||
|  |                 uint8_t idx; | ||||||
|  |                 with_view_model( | ||||||
|  |                     browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |                         idx = model->menu_idx; | ||||||
|  |                         return false; | ||||||
|  |                     }); | ||||||
|  |                 browser->callback(file_menu_actions[idx], browser->context); | ||||||
|  |             } else if(event->key == InputKeyBack) { | ||||||
|  |                 browser->callback(ArchiveBrowserEventFileMenuClose, browser->context); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } else { | ||||||
|  |         if(event->type == InputTypeShort) { | ||||||
|  |             if(event->key == InputKeyLeft || event->key == InputKeyRight) { | ||||||
|  |                 archive_switch_tab(browser, event->key); | ||||||
|  |             } else if(event->key == InputKeyBack) { | ||||||
|  |                 browser->callback(ArchiveBrowserEventExit, browser->context); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if(event->key == InputKeyUp || event->key == InputKeyDown) { | ||||||
|  |             with_view_model( | ||||||
|  |                 browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |                     uint16_t num_elements = (uint16_t)files_array_size(model->files); | ||||||
|  |                     if((event->type == InputTypeShort || event->type == InputTypeRepeat)) { | ||||||
|  |                         if(event->key == InputKeyUp) { | ||||||
|  |                             model->idx = ((model->idx - 1) + num_elements) % num_elements; | ||||||
|  |                         } else if(event->key == InputKeyDown) { | ||||||
|  |                             model->idx = (model->idx + 1) % num_elements; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     return true; | ||||||
|  |                 }); | ||||||
|  |             archive_update_offset(browser); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(event->key == InputKeyOk) { | ||||||
|  |             ArchiveFile_t* selected = archive_get_current_file(browser); | ||||||
|  | 
 | ||||||
|  |             if(selected) { | ||||||
|  |                 bool favorites = archive_get_tab(browser) == ArchiveTabFavorites; | ||||||
|  |                 bool folder = selected->type == ArchiveFileTypeFolder; | ||||||
|  | 
 | ||||||
|  |                 if(event->type == InputTypeShort) { | ||||||
|  |                     if(favorites) { | ||||||
|  |                         browser->callback(ArchiveBrowserEventFileMenuRun, browser->context); | ||||||
|  |                     } else if(folder) { | ||||||
|  |                         browser->callback(ArchiveBrowserEventEnterDir, browser->context); | ||||||
|  |                     } else { | ||||||
|  |                         browser->callback(ArchiveBrowserEventFileMenuOpen, browser->context); | ||||||
|  |                     } | ||||||
|  |                 } else if(event->type == InputTypeLong) { | ||||||
|  |                     if(folder || favorites) { | ||||||
|  |                         browser->callback(ArchiveBrowserEventFileMenuOpen, browser->context); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ArchiveBrowserView* browser_alloc() { | ||||||
|  |     ArchiveBrowserView* browser = furi_alloc(sizeof(ArchiveBrowserView)); | ||||||
|  |     browser->view = view_alloc(); | ||||||
|  |     view_allocate_model(browser->view, ViewModelTypeLocking, sizeof(ArchiveBrowserViewModel)); | ||||||
|  |     view_set_context(browser->view, browser); | ||||||
|  |     view_set_draw_callback(browser->view, (ViewDrawCallback)archive_view_render); | ||||||
|  |     view_set_input_callback(browser->view, archive_view_input); | ||||||
|  | 
 | ||||||
|  |     string_init(browser->path); | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |             files_array_init(model->files); | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     return browser; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void browser_free(ArchiveBrowserView* browser) { | ||||||
|  |     furi_assert(browser); | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|  |             files_array_clear(model->files); | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     string_clear(browser->path); | ||||||
|  | 
 | ||||||
|  |     view_free(browser->view); | ||||||
|  |     free(browser); | ||||||
|  | } | ||||||
							
								
								
									
										89
									
								
								applications/archive/views/archive_browser_view.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								applications/archive/views/archive_browser_view.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/gui_i.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <gui/canvas.h> | ||||||
|  | #include <gui/elements.h> | ||||||
|  | #include <furi.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | #include "../helpers/archive_files.h" | ||||||
|  | #include "../helpers/archive_favorites.h" | ||||||
|  | 
 | ||||||
|  | #define MAX_LEN_PX 110 | ||||||
|  | #define MAX_NAME_LEN 255 | ||||||
|  | #define FRAME_HEIGHT 12 | ||||||
|  | #define MENU_ITEMS 4 | ||||||
|  | #define MAX_DEPTH 32 | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     ArchiveTabFavorites, | ||||||
|  |     ArchiveTabLFRFID, | ||||||
|  |     ArchiveTabSubGhz, | ||||||
|  |     ArchiveTabNFC, | ||||||
|  |     ArchiveTabIButton, | ||||||
|  |     ArchiveTabIrda, | ||||||
|  |     ArchiveTabBrowser, | ||||||
|  |     ArchiveTabTotal, | ||||||
|  | } ArchiveTabEnum; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     ArchiveBrowserEventFileMenuOpen, | ||||||
|  |     ArchiveBrowserEventFileMenuClose, | ||||||
|  |     ArchiveBrowserEventFileMenuRun, | ||||||
|  |     ArchiveBrowserEventFileMenuPin, | ||||||
|  |     ArchiveBrowserEventFileMenuRename, | ||||||
|  |     ArchiveBrowserEventFileMenuDelete, | ||||||
|  |     ArchiveBrowserEventEnterDir, | ||||||
|  |     ArchiveBrowserEventExit, | ||||||
|  | } ArchiveBrowserEvent; | ||||||
|  | 
 | ||||||
|  | static const uint8_t file_menu_actions[MENU_ITEMS] = { | ||||||
|  |     [0] = ArchiveBrowserEventFileMenuRun, | ||||||
|  |     [1] = ArchiveBrowserEventFileMenuPin, | ||||||
|  |     [2] = ArchiveBrowserEventFileMenuRename, | ||||||
|  |     [3] = ArchiveBrowserEventFileMenuDelete, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef struct ArchiveBrowserView ArchiveBrowserView; | ||||||
|  | 
 | ||||||
|  | typedef void (*ArchiveBrowserViewCallback)(ArchiveBrowserEvent event, void* context); | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     BrowserActionBrowse, | ||||||
|  |     BrowserActionItemMenu, | ||||||
|  |     BrowserActionTotal, | ||||||
|  | } BrowserActionEnum; | ||||||
|  | 
 | ||||||
|  | struct ArchiveBrowserView { | ||||||
|  |     View* view; | ||||||
|  |     ArchiveBrowserViewCallback callback; | ||||||
|  |     void* context; | ||||||
|  | 
 | ||||||
|  |     string_t path; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     ArchiveTabEnum tab_idx; | ||||||
|  |     ArchiveTabEnum last_tab; | ||||||
|  |     files_array_t files; | ||||||
|  | 
 | ||||||
|  |     uint8_t menu_idx; | ||||||
|  |     bool menu; | ||||||
|  | 
 | ||||||
|  |     uint16_t idx; | ||||||
|  |     uint16_t last_idx; | ||||||
|  |     uint16_t list_offset; | ||||||
|  |     uint16_t last_offset; | ||||||
|  |     uint8_t depth; | ||||||
|  | 
 | ||||||
|  | } ArchiveBrowserViewModel; | ||||||
|  | 
 | ||||||
|  | void archive_browser_set_callback( | ||||||
|  |     ArchiveBrowserView* browser, | ||||||
|  |     ArchiveBrowserViewCallback callback, | ||||||
|  |     void* context); | ||||||
|  | 
 | ||||||
|  | View* archive_browser_get_view(ArchiveBrowserView* browser); | ||||||
|  | 
 | ||||||
|  | ArchiveBrowserView* browser_alloc(); | ||||||
|  | void browser_free(ArchiveBrowserView* browser); | ||||||
| @ -1,641 +0,0 @@ | |||||||
| #include <furi.h> |  | ||||||
| #include "../archive_i.h" |  | ||||||
| #include "archive_main_view.h" |  | ||||||
| 
 |  | ||||||
| static const char* flipper_app_name[] = { |  | ||||||
|     [ArchiveFileTypeIButton] = "iButton", |  | ||||||
|     [ArchiveFileTypeNFC] = "NFC", |  | ||||||
|     [ArchiveFileTypeSubGhz] = "Sub-GHz", |  | ||||||
|     [ArchiveFileTypeLFRFID] = "125 kHz RFID", |  | ||||||
|     [ArchiveFileTypeIrda] = "Infrared", |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static const char* ArchiveTabNames[] = { |  | ||||||
|     [ArchiveTabFavorites] = "Favorites", |  | ||||||
|     [ArchiveTabIButton] = "iButton", |  | ||||||
|     [ArchiveTabNFC] = "NFC", |  | ||||||
|     [ArchiveTabSubGhz] = "Sub-GHz", |  | ||||||
|     [ArchiveTabLFRFID] = "RFID LF", |  | ||||||
|     [ArchiveTabIrda] = "Infrared", |  | ||||||
|     [ArchiveTabBrowser] = "Browser"}; |  | ||||||
| 
 |  | ||||||
| static const Icon* ArchiveItemIcons[] = { |  | ||||||
|     [ArchiveFileTypeIButton] = &I_ibutt_10px, |  | ||||||
|     [ArchiveFileTypeNFC] = &I_Nfc_10px, |  | ||||||
|     [ArchiveFileTypeSubGhz] = &I_sub1_10px, |  | ||||||
|     [ArchiveFileTypeLFRFID] = &I_125_10px, |  | ||||||
|     [ArchiveFileTypeIrda] = &I_ir_10px, |  | ||||||
|     [ArchiveFileTypeFolder] = &I_dir_10px, |  | ||||||
|     [ArchiveFileTypeUnknown] = &I_unknown_10px, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| void archive_browser_set_callback( |  | ||||||
|     ArchiveMainView* main_view, |  | ||||||
|     ArchiveMainViewCallback callback, |  | ||||||
|     void* context) { |  | ||||||
|     furi_assert(main_view); |  | ||||||
|     furi_assert(callback); |  | ||||||
|     main_view->callback = callback; |  | ||||||
|     main_view->context = context; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void update_offset(ArchiveMainView* main_view) { |  | ||||||
|     furi_assert(main_view); |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             size_t array_size = files_array_size(model->files); |  | ||||||
|             uint16_t bounds = array_size > 3 ? 2 : array_size; |  | ||||||
| 
 |  | ||||||
|             if(array_size > 3 && model->idx >= array_size - 1) { |  | ||||||
|                 model->list_offset = model->idx - 3; |  | ||||||
|             } else if(model->list_offset < model->idx - bounds) { |  | ||||||
|                 model->list_offset = CLAMP(model->idx - 2, array_size - bounds, 0); |  | ||||||
|             } else if(model->list_offset > model->idx - bounds) { |  | ||||||
|                 model->list_offset = CLAMP(model->idx - 1, array_size - bounds, 0); |  | ||||||
|             } |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| size_t archive_file_array_size(ArchiveMainView* main_view) { |  | ||||||
|     uint16_t size = 0; |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             size = files_array_size(model->files); |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
|     return size; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void archive_file_array_remove_selected(ArchiveMainView* main_view) { |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             files_array_remove_v(model->files, model->idx, model->idx + 1); |  | ||||||
|             model->idx = CLAMP(model->idx, files_array_size(model->files) - 1, 0); |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     update_offset(main_view); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void archive_file_array_clean(ArchiveMainView* main_view) { |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             files_array_clean(model->files); |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ArchiveFile_t* archive_get_current_file(ArchiveMainView* main_view) { |  | ||||||
|     ArchiveFile_t* selected; |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             selected = files_array_size(model->files) > 0 ? |  | ||||||
|                            files_array_get(model->files, model->idx) : |  | ||||||
|                            NULL; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
|     return selected; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ArchiveTabEnum archive_get_tab(ArchiveMainView* main_view) { |  | ||||||
|     ArchiveTabEnum tab_id; |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             tab_id = model->tab_idx; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
|     return tab_id; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void archive_set_tab(ArchiveMainView* main_view, ArchiveTabEnum tab) { |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             model->tab_idx = tab; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| uint8_t archive_get_depth(ArchiveMainView* main_view) { |  | ||||||
|     uint8_t depth; |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             depth = model->depth; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     return depth; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const char* archive_get_path(ArchiveMainView* main_view) { |  | ||||||
|     return string_get_cstr(main_view->path); |  | ||||||
| } |  | ||||||
| const char* archive_get_name(ArchiveMainView* main_view) { |  | ||||||
|     ArchiveFile_t* selected = archive_get_current_file(main_view); |  | ||||||
|     return string_get_cstr(selected->name); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void archive_set_name(ArchiveMainView* main_view, const char* name) { |  | ||||||
|     furi_assert(main_view); |  | ||||||
|     furi_assert(name); |  | ||||||
| 
 |  | ||||||
|     string_set(main_view->name, name); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void archive_browser_update(ArchiveMainView* main_view) { |  | ||||||
|     furi_assert(main_view); |  | ||||||
| 
 |  | ||||||
|     archive_get_filenames(main_view, archive_get_tab(main_view), string_get_cstr(main_view->path)); |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             uint16_t idx = 0; |  | ||||||
|             while(idx < files_array_size(model->files)) { |  | ||||||
|                 ArchiveFile_t* current = files_array_get(model->files, idx); |  | ||||||
|                 if(!string_search(current->name, string_get_cstr(main_view->name))) { |  | ||||||
|                     model->idx = idx; |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|                 ++idx; |  | ||||||
|             } |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     update_offset(main_view); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void archive_view_add_item(ArchiveMainView* main_view, FileInfo* file_info, const char* name) { |  | ||||||
|     furi_assert(main_view); |  | ||||||
|     furi_assert(file_info); |  | ||||||
|     furi_assert(name); |  | ||||||
| 
 |  | ||||||
|     ArchiveFile_t item; |  | ||||||
| 
 |  | ||||||
|     if(filter_by_extension(file_info, get_tab_ext(archive_get_tab(main_view)), name)) { |  | ||||||
|         ArchiveFile_t_init(&item); |  | ||||||
|         string_init_set_str(item.name, name); |  | ||||||
|         set_file_type(&item, file_info); |  | ||||||
| 
 |  | ||||||
|         with_view_model( |  | ||||||
|             main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|                 files_array_push_back(model->files, item); |  | ||||||
|                 return true; |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|         ArchiveFile_t_clear(&item); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void render_item_menu(Canvas* canvas, ArchiveMainViewModel* model) { |  | ||||||
|     canvas_set_color(canvas, ColorWhite); |  | ||||||
|     canvas_draw_box(canvas, 71, 17, 57, 46); |  | ||||||
|     canvas_set_color(canvas, ColorBlack); |  | ||||||
|     elements_slightly_rounded_frame(canvas, 70, 16, 58, 48); |  | ||||||
| 
 |  | ||||||
|     string_t menu[MENU_ITEMS]; |  | ||||||
| 
 |  | ||||||
|     string_init_set_str(menu[0], "Run in app"); |  | ||||||
|     string_init_set_str(menu[1], "Pin"); |  | ||||||
|     string_init_set_str(menu[2], "Rename"); |  | ||||||
|     string_init_set_str(menu[3], "Delete"); |  | ||||||
| 
 |  | ||||||
|     ArchiveFile_t* selected = files_array_get(model->files, model->idx); |  | ||||||
| 
 |  | ||||||
|     if(!is_known_app(selected->type)) { |  | ||||||
|         string_set_str(menu[0], "---"); |  | ||||||
|         string_set_str(menu[1], "---"); |  | ||||||
|         string_set_str(menu[2], "---"); |  | ||||||
|     } else if(selected->fav) { |  | ||||||
|         string_set_str(menu[1], "Unpin"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     for(size_t i = 0; i < MENU_ITEMS; i++) { |  | ||||||
|         canvas_draw_str(canvas, 82, 27 + i * 11, string_get_cstr(menu[i])); |  | ||||||
|         string_clear(menu[i]); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     canvas_draw_icon(canvas, 74, 20 + model->menu_idx * 11, &I_ButtonRight_4x7); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { |  | ||||||
|     canvas_set_color(canvas, ColorBlack); |  | ||||||
|     canvas_draw_box(canvas, 0, 15 + idx * FRAME_HEIGHT, scrollbar ? 122 : 127, FRAME_HEIGHT); |  | ||||||
| 
 |  | ||||||
|     canvas_set_color(canvas, ColorWhite); |  | ||||||
|     canvas_draw_dot(canvas, 0, 15 + idx * FRAME_HEIGHT); |  | ||||||
|     canvas_draw_dot(canvas, 1, 15 + idx * FRAME_HEIGHT); |  | ||||||
|     canvas_draw_dot(canvas, 0, (15 + idx * FRAME_HEIGHT) + 1); |  | ||||||
| 
 |  | ||||||
|     canvas_draw_dot(canvas, 0, (15 + idx * FRAME_HEIGHT) + 11); |  | ||||||
|     canvas_draw_dot(canvas, scrollbar ? 121 : 126, 15 + idx * FRAME_HEIGHT); |  | ||||||
|     canvas_draw_dot(canvas, scrollbar ? 121 : 126, (15 + idx * FRAME_HEIGHT) + 11); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void draw_list(Canvas* canvas, ArchiveMainViewModel* model) { |  | ||||||
|     furi_assert(model); |  | ||||||
| 
 |  | ||||||
|     size_t array_size = files_array_size(model->files); |  | ||||||
|     bool scrollbar = array_size > 4; |  | ||||||
| 
 |  | ||||||
|     for(size_t i = 0; i < MIN(array_size, MENU_ITEMS); ++i) { |  | ||||||
|         string_t str_buff; |  | ||||||
|         char cstr_buff[MAX_NAME_LEN]; |  | ||||||
| 
 |  | ||||||
|         size_t idx = CLAMP(i + model->list_offset, array_size, 0); |  | ||||||
|         ArchiveFile_t* file = files_array_get(model->files, CLAMP(idx, array_size - 1, 0)); |  | ||||||
| 
 |  | ||||||
|         string_init_set(str_buff, file->name); |  | ||||||
|         string_right(str_buff, string_search_rchar(str_buff, '/') + 1); |  | ||||||
|         strlcpy(cstr_buff, string_get_cstr(str_buff), string_size(str_buff) + 1); |  | ||||||
| 
 |  | ||||||
|         if(is_known_app(file->type)) archive_trim_file_ext(cstr_buff); |  | ||||||
| 
 |  | ||||||
|         string_clean(str_buff); |  | ||||||
|         string_set_str(str_buff, cstr_buff); |  | ||||||
| 
 |  | ||||||
|         elements_string_fit_width(canvas, str_buff, scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX); |  | ||||||
| 
 |  | ||||||
|         if(model->idx == idx) { |  | ||||||
|             archive_draw_frame(canvas, i, scrollbar); |  | ||||||
|         } else { |  | ||||||
|             canvas_set_color(canvas, ColorBlack); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         canvas_draw_icon(canvas, 2, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file->type]); |  | ||||||
|         canvas_draw_str(canvas, 15, 24 + i * FRAME_HEIGHT, string_get_cstr(str_buff)); |  | ||||||
|         string_clear(str_buff); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(scrollbar) { |  | ||||||
|         elements_scrollbar_pos(canvas, 126, 15, 49, model->idx, array_size); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(model->action == BrowserActionItemMenu) { |  | ||||||
|         render_item_menu(canvas, model); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_render_status_bar(Canvas* canvas, ArchiveMainViewModel* model) { |  | ||||||
|     furi_assert(model); |  | ||||||
| 
 |  | ||||||
|     const char* tab_name = ArchiveTabNames[model->tab_idx]; |  | ||||||
| 
 |  | ||||||
|     canvas_draw_icon(canvas, 0, 0, &I_Background_128x11); |  | ||||||
| 
 |  | ||||||
|     canvas_set_color(canvas, ColorWhite); |  | ||||||
|     canvas_draw_box(canvas, 0, 0, 50, 13); |  | ||||||
|     canvas_draw_box(canvas, 107, 0, 20, 13); |  | ||||||
| 
 |  | ||||||
|     canvas_set_color(canvas, ColorBlack); |  | ||||||
|     canvas_draw_frame(canvas, 1, 0, 50, 12); |  | ||||||
|     canvas_draw_line(canvas, 0, 1, 0, 11); |  | ||||||
|     canvas_draw_line(canvas, 1, 12, 49, 12); |  | ||||||
|     canvas_draw_str_aligned(canvas, 26, 9, AlignCenter, AlignBottom, tab_name); |  | ||||||
| 
 |  | ||||||
|     canvas_draw_frame(canvas, 108, 0, 20, 12); |  | ||||||
|     canvas_draw_line(canvas, 107, 1, 107, 11); |  | ||||||
|     canvas_draw_line(canvas, 108, 12, 126, 12); |  | ||||||
| 
 |  | ||||||
|     if(model->tab_idx > 0) { |  | ||||||
|         canvas_draw_icon(canvas, 112, 2, &I_ButtonLeft_4x7); |  | ||||||
|     } |  | ||||||
|     if(model->tab_idx < SIZEOF_ARRAY(ArchiveTabNames) - 1) { |  | ||||||
|         canvas_draw_icon(canvas, 120, 2, &I_ButtonRight_4x7); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     canvas_set_color(canvas, ColorWhite); |  | ||||||
|     canvas_draw_dot(canvas, 50, 0); |  | ||||||
|     canvas_draw_dot(canvas, 127, 0); |  | ||||||
| 
 |  | ||||||
|     canvas_set_color(canvas, ColorBlack); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void archive_view_render(Canvas* canvas, void* model) { |  | ||||||
|     ArchiveMainViewModel* m = model; |  | ||||||
| 
 |  | ||||||
|     archive_render_status_bar(canvas, model); |  | ||||||
| 
 |  | ||||||
|     if(files_array_size(m->files) > 0) { |  | ||||||
|         draw_list(canvas, m); |  | ||||||
|     } else { |  | ||||||
|         canvas_draw_str_aligned( |  | ||||||
|             canvas, GUI_DISPLAY_WIDTH / 2, 40, AlignCenter, AlignCenter, "Empty"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| View* archive_main_get_view(ArchiveMainView* main_view) { |  | ||||||
|     furi_assert(main_view); |  | ||||||
|     return main_view->view; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_show_file_menu(ArchiveMainView* main_view) { |  | ||||||
|     furi_assert(main_view); |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             ArchiveFile_t* selected; |  | ||||||
|             selected = files_array_get(model->files, model->idx); |  | ||||||
|             model->action = BrowserActionItemMenu; |  | ||||||
|             model->menu_idx = 0; |  | ||||||
|             selected->fav = is_known_app(selected->type) ? archive_is_favorite( |  | ||||||
|                                                                string_get_cstr(main_view->path), |  | ||||||
|                                                                string_get_cstr(selected->name)) : |  | ||||||
|                                                            false; |  | ||||||
| 
 |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_close_file_menu(ArchiveMainView* main_view) { |  | ||||||
|     furi_assert(main_view); |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             model->action = BrowserActionBrowse; |  | ||||||
|             model->menu_idx = 0; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_run_in_app( |  | ||||||
|     ArchiveMainView* main_view, |  | ||||||
|     ArchiveFile_t* selected, |  | ||||||
|     bool full_path_provided) { |  | ||||||
|     Loader* loader = furi_record_open("loader"); |  | ||||||
| 
 |  | ||||||
|     string_t full_path; |  | ||||||
| 
 |  | ||||||
|     if(!full_path_provided) { |  | ||||||
|         string_init_printf( |  | ||||||
|             full_path, "%s/%s", string_get_cstr(main_view->path), string_get_cstr(selected->name)); |  | ||||||
|     } else { |  | ||||||
|         string_init_set(full_path, selected->name); |  | ||||||
|     } |  | ||||||
|     loader_start(loader, flipper_app_name[selected->type], string_get_cstr(full_path)); |  | ||||||
| 
 |  | ||||||
|     string_clear(full_path); |  | ||||||
|     furi_record_close("loader"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_file_menu_callback(ArchiveMainView* main_view) { |  | ||||||
|     furi_assert(main_view); |  | ||||||
| 
 |  | ||||||
|     ArchiveFile_t* selected = archive_get_current_file(main_view); |  | ||||||
|     const char* path = archive_get_path(main_view); |  | ||||||
|     const char* name = archive_get_name(main_view); |  | ||||||
| 
 |  | ||||||
|     uint8_t idx; |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             idx = model->menu_idx; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     switch(idx) { |  | ||||||
|     case 0: |  | ||||||
|         if(is_known_app(selected->type)) { |  | ||||||
|             archive_run_in_app(main_view, selected, false); |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|     case 1: |  | ||||||
|         if(is_known_app(selected->type)) { |  | ||||||
|             if(!archive_is_favorite(path, name)) { |  | ||||||
|                 archive_set_name(main_view, string_get_cstr(selected->name)); |  | ||||||
|                 archive_add_to_favorites(path, name); |  | ||||||
|             } else { |  | ||||||
|                 // delete from favorites
 |  | ||||||
|                 archive_favorites_delete(path, name); |  | ||||||
|             } |  | ||||||
|             archive_close_file_menu(main_view); |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|     case 2: |  | ||||||
|         // open rename view
 |  | ||||||
|         if(is_known_app(selected->type)) { |  | ||||||
|             main_view->callback(ArchiveBrowserEventRename, main_view->context); |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|     case 3: |  | ||||||
|         // confirmation?
 |  | ||||||
|         archive_delete_file(main_view, main_view->path, selected->name); |  | ||||||
|         archive_close_file_menu(main_view); |  | ||||||
|         break; |  | ||||||
| 
 |  | ||||||
|     default: |  | ||||||
|         archive_close_file_menu(main_view); |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     selected = NULL; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_switch_dir(ArchiveMainView* main_view, const char* path) { |  | ||||||
|     furi_assert(main_view); |  | ||||||
|     furi_assert(path); |  | ||||||
| 
 |  | ||||||
|     string_set(main_view->path, path); |  | ||||||
|     archive_get_filenames(main_view, archive_get_tab(main_view), string_get_cstr(main_view->path)); |  | ||||||
|     update_offset(main_view); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void archive_switch_tab(ArchiveMainView* main_view) { |  | ||||||
|     furi_assert(main_view); |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             model->idx = 0; |  | ||||||
|             model->depth = 0; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     archive_switch_dir(main_view, tab_default_paths[archive_get_tab(main_view)]); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_enter_dir(ArchiveMainView* main_view, string_t name) { |  | ||||||
|     furi_assert(main_view); |  | ||||||
|     furi_assert(name); |  | ||||||
| 
 |  | ||||||
|     // update last index
 |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             model->last_idx[model->depth] = |  | ||||||
|                 CLAMP(model->idx, files_array_size(model->files) - 1, 0); |  | ||||||
|             model->idx = 0; |  | ||||||
|             model->depth = CLAMP(model->depth + 1, MAX_DEPTH, 0); |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     string_cat(main_view->path, "/"); |  | ||||||
|     string_cat(main_view->path, main_view->name); |  | ||||||
| 
 |  | ||||||
|     archive_switch_dir(main_view, string_get_cstr(main_view->path)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void archive_leave_dir(ArchiveMainView* main_view) { |  | ||||||
|     furi_assert(main_view); |  | ||||||
| 
 |  | ||||||
|     char* last_char_ptr = strrchr(string_get_cstr(main_view->path), '/'); |  | ||||||
| 
 |  | ||||||
|     if(last_char_ptr) { |  | ||||||
|         size_t pos = last_char_ptr - string_get_cstr(main_view->path); |  | ||||||
|         string_left(main_view->path, pos); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             model->depth = CLAMP(model->depth - 1, MAX_DEPTH, 0); |  | ||||||
|             model->idx = model->last_idx[model->depth]; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     archive_switch_dir(main_view, string_get_cstr(main_view->path)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool archive_view_input(InputEvent* event, void* context) { |  | ||||||
|     furi_assert(event); |  | ||||||
|     furi_assert(context); |  | ||||||
| 
 |  | ||||||
|     ArchiveMainView* main_view = context; |  | ||||||
| 
 |  | ||||||
|     BrowserActionEnum action; |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             action = model->action; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     switch(action) { |  | ||||||
|     case BrowserActionItemMenu: |  | ||||||
| 
 |  | ||||||
|         if(event->type == InputTypeShort) { |  | ||||||
|             if(event->key == InputKeyUp || event->key == InputKeyDown) { |  | ||||||
|                 with_view_model( |  | ||||||
|                     main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|                         if(event->key == InputKeyUp) { |  | ||||||
|                             model->menu_idx = ((model->menu_idx - 1) + MENU_ITEMS) % MENU_ITEMS; |  | ||||||
|                         } else if(event->key == InputKeyDown) { |  | ||||||
|                             model->menu_idx = (model->menu_idx + 1) % MENU_ITEMS; |  | ||||||
|                         } |  | ||||||
|                         return true; |  | ||||||
|                     }); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if(event->key == InputKeyOk) { |  | ||||||
|                 archive_file_menu_callback(main_view); |  | ||||||
|             } else if(event->key == InputKeyBack) { |  | ||||||
|                 archive_close_file_menu(main_view); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
| 
 |  | ||||||
|     case BrowserActionBrowse: |  | ||||||
| 
 |  | ||||||
|         if(event->type == InputTypeShort) { |  | ||||||
|             if(event->key == InputKeyLeft) { |  | ||||||
|                 ArchiveTabEnum tab = archive_get_tab(main_view); |  | ||||||
|                 if(tab) { |  | ||||||
|                     archive_set_tab(main_view, CLAMP(tab - 1, ArchiveTabTotal, 0)); |  | ||||||
|                     archive_switch_tab(main_view); |  | ||||||
|                     return true; |  | ||||||
|                 } |  | ||||||
|             } else if(event->key == InputKeyRight) { |  | ||||||
|                 ArchiveTabEnum tab = archive_get_tab(main_view); |  | ||||||
| 
 |  | ||||||
|                 if(tab < ArchiveTabTotal - 1) { |  | ||||||
|                     archive_set_tab(main_view, CLAMP(tab + 1, ArchiveTabTotal - 1, 0)); |  | ||||||
|                     archive_switch_tab(main_view); |  | ||||||
|                     return true; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|             } else if(event->key == InputKeyBack) { |  | ||||||
|                 if(!archive_get_depth(main_view)) { |  | ||||||
|                     main_view->callback(ArchiveBrowserEventExit, main_view->context); |  | ||||||
|                 } else { |  | ||||||
|                     archive_leave_dir(main_view); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if(event->key == InputKeyUp || event->key == InputKeyDown) { |  | ||||||
|             with_view_model( |  | ||||||
|                 main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|                     uint16_t num_elements = (uint16_t)files_array_size(model->files); |  | ||||||
|                     if((event->type == InputTypeShort || event->type == InputTypeRepeat)) { |  | ||||||
|                         if(event->key == InputKeyUp) { |  | ||||||
|                             model->idx = ((model->idx - 1) + num_elements) % num_elements; |  | ||||||
|                         } else if(event->key == InputKeyDown) { |  | ||||||
|                             model->idx = (model->idx + 1) % num_elements; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     return true; |  | ||||||
|                 }); |  | ||||||
|             update_offset(main_view); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(event->key == InputKeyOk) { |  | ||||||
|             ArchiveFile_t* selected = archive_get_current_file(main_view); |  | ||||||
| 
 |  | ||||||
|             if(selected) { |  | ||||||
|                 archive_set_name(main_view, string_get_cstr(selected->name)); |  | ||||||
|                 if(selected->type == ArchiveFileTypeFolder) { |  | ||||||
|                     if(event->type == InputTypeShort) { |  | ||||||
|                         archive_enter_dir(main_view, main_view->name); |  | ||||||
|                     } else if(event->type == InputTypeLong) { |  | ||||||
|                         archive_show_file_menu(main_view); |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     if(event->type == InputTypeShort) { |  | ||||||
|                         if(archive_get_tab(main_view) == ArchiveTabFavorites) { |  | ||||||
|                             if(is_known_app(selected->type)) { |  | ||||||
|                                 archive_run_in_app(main_view, selected, true); |  | ||||||
|                             } |  | ||||||
|                         } else { |  | ||||||
|                             archive_show_file_menu(main_view); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|     default: |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ArchiveMainView* main_view_alloc() { |  | ||||||
|     ArchiveMainView* main_view = furi_alloc(sizeof(ArchiveMainView)); |  | ||||||
|     main_view->view = view_alloc(); |  | ||||||
|     view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(ArchiveMainViewModel)); |  | ||||||
|     view_set_context(main_view->view, main_view); |  | ||||||
|     view_set_draw_callback(main_view->view, (ViewDrawCallback)archive_view_render); |  | ||||||
|     view_set_input_callback(main_view->view, archive_view_input); |  | ||||||
| 
 |  | ||||||
|     string_init(main_view->name); |  | ||||||
|     string_init(main_view->path); |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             files_array_init(model->files); |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     return main_view; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void main_view_free(ArchiveMainView* main_view) { |  | ||||||
|     furi_assert(main_view); |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (ArchiveMainViewModel * model) { |  | ||||||
|             files_array_clear(model->files); |  | ||||||
|             return false; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     string_clear(main_view->name); |  | ||||||
|     string_clear(main_view->path); |  | ||||||
| 
 |  | ||||||
|     view_free(main_view->view); |  | ||||||
|     free(main_view); |  | ||||||
| } |  | ||||||
| @ -1,117 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <gui/gui_i.h> |  | ||||||
| #include <gui/view.h> |  | ||||||
| #include <gui/canvas.h> |  | ||||||
| #include <gui/elements.h> |  | ||||||
| #include <furi.h> |  | ||||||
| #include <storage/storage.h> |  | ||||||
| #include "../helpers/archive_files.h" |  | ||||||
| #include "../helpers/archive_favorites.h" |  | ||||||
| 
 |  | ||||||
| #define MAX_LEN_PX 110 |  | ||||||
| #define MAX_NAME_LEN 255 |  | ||||||
| #define FRAME_HEIGHT 12 |  | ||||||
| #define MENU_ITEMS 4 |  | ||||||
| #define MAX_DEPTH 32 |  | ||||||
| 
 |  | ||||||
| typedef enum { |  | ||||||
|     ArchiveTabFavorites, |  | ||||||
|     ArchiveTabLFRFID, |  | ||||||
|     ArchiveTabSubGhz, |  | ||||||
|     ArchiveTabNFC, |  | ||||||
|     ArchiveTabIButton, |  | ||||||
|     ArchiveTabIrda, |  | ||||||
|     ArchiveTabBrowser, |  | ||||||
|     ArchiveTabTotal, |  | ||||||
| } ArchiveTabEnum; |  | ||||||
| 
 |  | ||||||
| static const char* known_ext[] = { |  | ||||||
|     [ArchiveFileTypeIButton] = ".ibtn", |  | ||||||
|     [ArchiveFileTypeNFC] = ".nfc", |  | ||||||
|     [ArchiveFileTypeSubGhz] = ".sub", |  | ||||||
|     [ArchiveFileTypeLFRFID] = ".rfid", |  | ||||||
|     [ArchiveFileTypeIrda] = ".ir", |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static inline const char* get_tab_ext(ArchiveTabEnum tab) { |  | ||||||
|     switch(tab) { |  | ||||||
|     case ArchiveTabIButton: |  | ||||||
|         return known_ext[ArchiveFileTypeIButton]; |  | ||||||
|     case ArchiveTabNFC: |  | ||||||
|         return known_ext[ArchiveFileTypeNFC]; |  | ||||||
|     case ArchiveTabSubGhz: |  | ||||||
|         return known_ext[ArchiveFileTypeSubGhz]; |  | ||||||
|     case ArchiveTabLFRFID: |  | ||||||
|         return known_ext[ArchiveFileTypeLFRFID]; |  | ||||||
|     case ArchiveTabIrda: |  | ||||||
|         return known_ext[ArchiveFileTypeIrda]; |  | ||||||
|     default: |  | ||||||
|         return "*"; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| typedef enum { |  | ||||||
|     ArchiveBrowserEventRename, |  | ||||||
|     ArchiveBrowserEventExit, |  | ||||||
|     ArchiveBrowserEventLeaveDir, |  | ||||||
| } ArchiveBrowserEvent; |  | ||||||
| 
 |  | ||||||
| typedef struct ArchiveMainView ArchiveMainView; |  | ||||||
| 
 |  | ||||||
| typedef void (*ArchiveMainViewCallback)(ArchiveBrowserEvent event, void* context); |  | ||||||
| 
 |  | ||||||
| typedef enum { |  | ||||||
|     BrowserActionBrowse, |  | ||||||
|     BrowserActionItemMenu, |  | ||||||
|     BrowserActionTotal, |  | ||||||
| } BrowserActionEnum; |  | ||||||
| 
 |  | ||||||
| struct ArchiveMainView { |  | ||||||
|     View* view; |  | ||||||
|     ArchiveMainViewCallback callback; |  | ||||||
|     void* context; |  | ||||||
| 
 |  | ||||||
|     string_t name; |  | ||||||
|     string_t path; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     ArchiveTabEnum tab_idx; |  | ||||||
|     BrowserActionEnum action; |  | ||||||
|     files_array_t files; |  | ||||||
| 
 |  | ||||||
|     uint8_t depth; |  | ||||||
|     uint8_t menu_idx; |  | ||||||
| 
 |  | ||||||
|     uint16_t idx; |  | ||||||
|     uint16_t last_idx[MAX_DEPTH]; |  | ||||||
|     uint16_t list_offset; |  | ||||||
| 
 |  | ||||||
| } ArchiveMainViewModel; |  | ||||||
| 
 |  | ||||||
| void archive_browser_set_callback( |  | ||||||
|     ArchiveMainView* main_view, |  | ||||||
|     ArchiveMainViewCallback callback, |  | ||||||
|     void* context); |  | ||||||
| 
 |  | ||||||
| View* archive_main_get_view(ArchiveMainView* main_view); |  | ||||||
| 
 |  | ||||||
| ArchiveMainView* main_view_alloc(); |  | ||||||
| void main_view_free(ArchiveMainView* main_view); |  | ||||||
| 
 |  | ||||||
| void archive_file_array_remove_selected(ArchiveMainView* main_view); |  | ||||||
| void archive_file_array_clean(ArchiveMainView* main_view); |  | ||||||
| 
 |  | ||||||
| void archive_view_add_item(ArchiveMainView* main_view, FileInfo* file_info, const char* name); |  | ||||||
| void archive_browser_update(ArchiveMainView* main_view); |  | ||||||
| 
 |  | ||||||
| size_t archive_file_array_size(ArchiveMainView* main_view); |  | ||||||
| ArchiveFile_t* archive_get_current_file(ArchiveMainView* main_view); |  | ||||||
| const char* archive_get_path(ArchiveMainView* main_view); |  | ||||||
| const char* archive_get_name(ArchiveMainView* main_view); |  | ||||||
| void archive_set_name(ArchiveMainView* main_view, const char* name); |  | ||||||
| 
 |  | ||||||
| static inline bool is_known_app(ArchiveFileTypeEnum type) { |  | ||||||
|     return (type != ArchiveFileTypeFolder && type != ArchiveFileTypeUnknown); |  | ||||||
| } |  | ||||||
| @ -1,6 +1,7 @@ | |||||||
| #include "bt_cli.h" | #include "bt_cli.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi-hal.h> | ||||||
|  | #include "bt_settings.h" | ||||||
| 
 | 
 | ||||||
| void bt_cli_init() { | void bt_cli_init() { | ||||||
|     Cli* cli = furi_record_open("cli"); |     Cli* cli = furi_record_open("cli"); | ||||||
| @ -18,13 +19,16 @@ void bt_cli_command_info(Cli* cli, string_t args, void* context) { | |||||||
|     string_t buffer; |     string_t buffer; | ||||||
|     string_init(buffer); |     string_init(buffer); | ||||||
|     furi_hal_bt_dump_state(buffer); |     furi_hal_bt_dump_state(buffer); | ||||||
|     printf(string_get_cstr(buffer)); |     printf("%s", string_get_cstr(buffer)); | ||||||
|     string_clear(buffer); |     string_clear(buffer); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) { | void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) { | ||||||
|     uint16_t channel; |     uint16_t channel; | ||||||
|     uint16_t power; |     uint16_t power; | ||||||
|  |     BtSettings bt_settings; | ||||||
|  |     bt_settings_load(&bt_settings); | ||||||
|  | 
 | ||||||
|     int ret = sscanf(string_get_cstr(args), "%hu %hu", &channel, &power); |     int ret = sscanf(string_get_cstr(args), "%hu %hu", &channel, &power); | ||||||
|     if(ret != 2) { |     if(ret != 2) { | ||||||
|         printf("sscanf returned %d, channel: %hu, power: %hu\r\n", ret, channel, power); |         printf("sscanf returned %d, channel: %hu, power: %hu\r\n", ret, channel, power); | ||||||
| @ -39,6 +43,8 @@ void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) { | |||||||
|         printf("Power must be in 0...6 dB range, not %hu\r\n", power); |         printf("Power must be in 0...6 dB range, not %hu\r\n", power); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     furi_hal_bt_stop_advertising(); | ||||||
|     printf("Transmitting carrier at %hu channel at %hu dB power\r\n", channel, power); |     printf("Transmitting carrier at %hu channel at %hu dB power\r\n", channel, power); | ||||||
|     printf("Press CTRL+C to stop\r\n"); |     printf("Press CTRL+C to stop\r\n"); | ||||||
|     furi_hal_bt_start_tone_tx(channel, 0x19 + power); |     furi_hal_bt_start_tone_tx(channel, 0x19 + power); | ||||||
| @ -47,10 +53,15 @@ void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) { | |||||||
|         osDelay(250); |         osDelay(250); | ||||||
|     } |     } | ||||||
|     furi_hal_bt_stop_tone_tx(); |     furi_hal_bt_stop_tone_tx(); | ||||||
|  |     if(bt_settings.enabled) { | ||||||
|  |         furi_hal_bt_start_advertising(); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) { | void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) { | ||||||
|     uint16_t channel; |     uint16_t channel; | ||||||
|  |     BtSettings bt_settings; | ||||||
|  |     bt_settings_load(&bt_settings); | ||||||
|     int ret = sscanf(string_get_cstr(args), "%hu", &channel); |     int ret = sscanf(string_get_cstr(args), "%hu", &channel); | ||||||
|     if(ret != 1) { |     if(ret != 1) { | ||||||
|         printf("sscanf returned %d, channel: %hu\r\n", ret, channel); |         printf("sscanf returned %d, channel: %hu\r\n", ret, channel); | ||||||
| @ -61,6 +72,8 @@ void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) { | |||||||
|         printf("Channel number must be in 0...39 range, not %hu\r\n", channel); |         printf("Channel number must be in 0...39 range, not %hu\r\n", channel); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     furi_hal_bt_stop_advertising(); | ||||||
|     printf("Receiving carrier at %hu channel\r\n", channel); |     printf("Receiving carrier at %hu channel\r\n", channel); | ||||||
|     printf("Press CTRL+C to stop\r\n"); |     printf("Press CTRL+C to stop\r\n"); | ||||||
| 
 | 
 | ||||||
| @ -73,12 +86,17 @@ void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     furi_hal_bt_stop_packet_test(); |     furi_hal_bt_stop_packet_test(); | ||||||
|  |     if(bt_settings.enabled) { | ||||||
|  |         furi_hal_bt_start_advertising(); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) { | void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) { | ||||||
|     uint16_t channel; |     uint16_t channel; | ||||||
|     uint16_t pattern; |     uint16_t pattern; | ||||||
|     uint16_t datarate; |     uint16_t datarate; | ||||||
|  |     BtSettings bt_settings; | ||||||
|  |     bt_settings_load(&bt_settings); | ||||||
|     int ret = sscanf(string_get_cstr(args), "%hu %hu %hu", &channel, &pattern, &datarate); |     int ret = sscanf(string_get_cstr(args), "%hu %hu %hu", &channel, &pattern, &datarate); | ||||||
|     if(ret != 3) { |     if(ret != 3) { | ||||||
|         printf("sscanf returned %d, channel: %hu %hu %hu\r\n", ret, channel, pattern, datarate); |         printf("sscanf returned %d, channel: %hu %hu %hu\r\n", ret, channel, pattern, datarate); | ||||||
| @ -105,6 +123,7 @@ void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     furi_hal_bt_stop_advertising(); | ||||||
|     printf( |     printf( | ||||||
|         "Transmitting %hu pattern packet at %hu channel at %hu M datarate\r\n", |         "Transmitting %hu pattern packet at %hu channel at %hu M datarate\r\n", | ||||||
|         pattern, |         pattern, | ||||||
| @ -118,11 +137,16 @@ void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) { | |||||||
|     } |     } | ||||||
|     furi_hal_bt_stop_packet_test(); |     furi_hal_bt_stop_packet_test(); | ||||||
|     printf("Transmitted %lu packets", furi_hal_bt_get_transmitted_packets()); |     printf("Transmitted %lu packets", furi_hal_bt_get_transmitted_packets()); | ||||||
|  |     if(bt_settings.enabled) { | ||||||
|  |         furi_hal_bt_start_advertising(); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) { | void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) { | ||||||
|     uint16_t channel; |     uint16_t channel; | ||||||
|     uint16_t datarate; |     uint16_t datarate; | ||||||
|  |     BtSettings bt_settings; | ||||||
|  |     bt_settings_load(&bt_settings); | ||||||
|     int ret = sscanf(string_get_cstr(args), "%hu %hu", &channel, &datarate); |     int ret = sscanf(string_get_cstr(args), "%hu %hu", &channel, &datarate); | ||||||
|     if(ret != 2) { |     if(ret != 2) { | ||||||
|         printf("sscanf returned %d, channel: %hu datarate: %hu\r\n", ret, channel, datarate); |         printf("sscanf returned %d, channel: %hu datarate: %hu\r\n", ret, channel, datarate); | ||||||
| @ -137,6 +161,8 @@ void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) { | |||||||
|         printf("Datarate must be in 1 or 2 Mb, not %hu\r\n", datarate); |         printf("Datarate must be in 1 or 2 Mb, not %hu\r\n", datarate); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     furi_hal_bt_stop_advertising(); | ||||||
|     printf("Receiving packets at %hu channel at %hu M datarate\r\n", channel, datarate); |     printf("Receiving packets at %hu channel at %hu M datarate\r\n", channel, datarate); | ||||||
|     printf("Press CTRL+C to stop\r\n"); |     printf("Press CTRL+C to stop\r\n"); | ||||||
|     furi_hal_bt_start_packet_rx(channel, datarate); |     furi_hal_bt_start_packet_rx(channel, datarate); | ||||||
| @ -150,4 +176,7 @@ void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) { | |||||||
|     } |     } | ||||||
|     uint16_t packets_received = furi_hal_bt_stop_packet_test(); |     uint16_t packets_received = furi_hal_bt_stop_packet_test(); | ||||||
|     printf("Received %hu packets", packets_received); |     printf("Received %hu packets", packets_received); | ||||||
|  |     if(bt_settings.enabled) { | ||||||
|  |         furi_hal_bt_start_advertising(); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -26,6 +26,17 @@ static void bt_pin_code_show_event_handler(Bt* bt, uint32_t pin) { | |||||||
|     string_clear(pin_str); |     string_clear(pin_str); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void bt_battery_level_changed_callback(const void* _event, void* context) { | ||||||
|  |     furi_assert(_event); | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     Bt* bt = context; | ||||||
|  |     const PowerEvent* event = _event; | ||||||
|  |     if(event->type == PowerEventTypeBatteryLevelChanged) { | ||||||
|  |         bt_update_battery_level(bt, event->data.battery_level); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| Bt* bt_alloc() { | Bt* bt_alloc() { | ||||||
|     Bt* bt = furi_alloc(sizeof(Bt)); |     Bt* bt = furi_alloc(sizeof(Bt)); | ||||||
|     // Load settings
 |     // Load settings
 | ||||||
| @ -45,6 +56,11 @@ Bt* bt_alloc() { | |||||||
|     bt->dialogs = furi_record_open("dialogs"); |     bt->dialogs = furi_record_open("dialogs"); | ||||||
|     bt->dialog_message = dialog_message_alloc(); |     bt->dialog_message = dialog_message_alloc(); | ||||||
| 
 | 
 | ||||||
|  |     // Power
 | ||||||
|  |     bt->power = furi_record_open("power"); | ||||||
|  |     PubSub* power_pubsub = power_get_pubsub(bt->power); | ||||||
|  |     subscribe_pubsub(power_pubsub, bt_battery_level_changed_callback, bt); | ||||||
|  | 
 | ||||||
|     return bt; |     return bt; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -9,7 +9,8 @@ | |||||||
| #include <gui/view_port.h> | #include <gui/view_port.h> | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
| 
 | 
 | ||||||
| #include <applications/dialogs/dialogs.h> | #include <dialogs/dialogs.h> | ||||||
|  | #include <power/power_service/power.h> | ||||||
| 
 | 
 | ||||||
| #include "../bt_settings.h" | #include "../bt_settings.h" | ||||||
| 
 | 
 | ||||||
| @ -36,4 +37,5 @@ struct Bt { | |||||||
|     ViewPort* statusbar_view_port; |     ViewPort* statusbar_view_port; | ||||||
|     DialogsApp* dialogs; |     DialogsApp* dialogs; | ||||||
|     DialogMessage* dialog_message; |     DialogMessage* dialog_message; | ||||||
|  |     Power* power; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -276,7 +276,7 @@ static void cli_handle_escape(Cli* cli, char c) { | |||||||
|             string_set(cli->line, cli->last_line); |             string_set(cli->line, cli->last_line); | ||||||
|             cli->cursor_position = string_size(cli->line); |             cli->cursor_position = string_size(cli->line); | ||||||
|             // Show new line to user
 |             // Show new line to user
 | ||||||
|             printf(string_get_cstr(cli->line)); |             printf("%s", string_get_cstr(cli->line)); | ||||||
|         } |         } | ||||||
|     } else if(c == 'B') { |     } else if(c == 'B') { | ||||||
|     } else if(c == 'C') { |     } else if(c == 'C') { | ||||||
|  | |||||||
| @ -138,7 +138,7 @@ void cli_command_help(Cli* cli, string_t args, void* context) { | |||||||
|         } |         } | ||||||
|         // Right Column
 |         // Right Column
 | ||||||
|         if(!CliCommandTree_end_p(it_right)) { |         if(!CliCommandTree_end_p(it_right)) { | ||||||
|             printf(string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr)); |             printf("%s", string_get_cstr(*CliCommandTree_ref(it_right)->key_ptr)); | ||||||
|             CliCommandTree_next(it_right); |             CliCommandTree_next(it_right); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| @ -146,7 +146,7 @@ void cli_command_help(Cli* cli, string_t args, void* context) { | |||||||
|     if(string_size(args) > 0) { |     if(string_size(args) > 0) { | ||||||
|         cli_nl(); |         cli_nl(); | ||||||
|         printf("Also I have no clue what '"); |         printf("Also I have no clue what '"); | ||||||
|         printf(string_get_cstr(args)); |         printf("%s", string_get_cstr(args)); | ||||||
|         printf("' is."); |         printf("' is."); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										131
									
								
								applications/desktop/desktop.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								applications/desktop/desktop.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,131 @@ | |||||||
|  | #include "desktop_i.h" | ||||||
|  | 
 | ||||||
|  | static void desktop_lock_icon_callback(Canvas* canvas, void* context) { | ||||||
|  |     furi_assert(canvas); | ||||||
|  |     canvas_draw_icon(canvas, 0, 0, &I_Lock_8x8); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_custom_event_callback(void* context, uint32_t event) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     return scene_manager_handle_custom_event(desktop->scene_manager, event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_back_event_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     return scene_manager_handle_back_event(desktop->scene_manager); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Desktop* desktop_alloc() { | ||||||
|  |     Desktop* desktop = furi_alloc(sizeof(Desktop)); | ||||||
|  | 
 | ||||||
|  |     desktop->gui = furi_record_open("gui"); | ||||||
|  |     desktop->scene_thread = furi_thread_alloc(); | ||||||
|  |     desktop->view_dispatcher = view_dispatcher_alloc(); | ||||||
|  |     desktop->scene_manager = scene_manager_alloc(&desktop_scene_handlers, desktop); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_enable_queue(desktop->view_dispatcher); | ||||||
|  |     view_dispatcher_attach_to_gui( | ||||||
|  |         desktop->view_dispatcher, desktop->gui, ViewDispatcherTypeWindow); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_set_event_callback_context(desktop->view_dispatcher, desktop); | ||||||
|  |     view_dispatcher_set_custom_event_callback( | ||||||
|  |         desktop->view_dispatcher, desktop_custom_event_callback); | ||||||
|  |     view_dispatcher_set_navigation_event_callback( | ||||||
|  |         desktop->view_dispatcher, desktop_back_event_callback); | ||||||
|  | 
 | ||||||
|  |     desktop->main_view = desktop_main_alloc(); | ||||||
|  |     desktop->lock_menu = desktop_lock_menu_alloc(); | ||||||
|  |     desktop->locked_view = desktop_locked_alloc(); | ||||||
|  |     desktop->debug_view = desktop_debug_alloc(); | ||||||
|  |     desktop->first_start_view = desktop_first_start_alloc(); | ||||||
|  |     desktop->hw_mismatch_view = desktop_hw_mismatch_alloc(); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         desktop->view_dispatcher, DesktopViewMain, desktop_main_get_view(desktop->main_view)); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         desktop->view_dispatcher, | ||||||
|  |         DesktopViewLockMenu, | ||||||
|  |         desktop_lock_menu_get_view(desktop->lock_menu)); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         desktop->view_dispatcher, DesktopViewDebug, desktop_debug_get_view(desktop->debug_view)); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         desktop->view_dispatcher, | ||||||
|  |         DesktopViewLocked, | ||||||
|  |         desktop_locked_get_view(desktop->locked_view)); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         desktop->view_dispatcher, | ||||||
|  |         DesktopViewFirstStart, | ||||||
|  |         desktop_first_start_get_view(desktop->first_start_view)); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         desktop->view_dispatcher, | ||||||
|  |         DesktopViewHwMismatch, | ||||||
|  |         desktop_hw_mismatch_get_view(desktop->hw_mismatch_view)); | ||||||
|  | 
 | ||||||
|  |     // Lock icon
 | ||||||
|  |     desktop->lock_viewport = view_port_alloc(); | ||||||
|  |     view_port_set_width(desktop->lock_viewport, icon_get_width(&I_Lock_8x8)); | ||||||
|  |     view_port_draw_callback_set(desktop->lock_viewport, desktop_lock_icon_callback, desktop); | ||||||
|  |     view_port_enabled_set(desktop->lock_viewport, false); | ||||||
|  |     gui_add_view_port(desktop->gui, desktop->lock_viewport, GuiLayerStatusBarLeft); | ||||||
|  | 
 | ||||||
|  |     return desktop; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_free(Desktop* desktop) { | ||||||
|  |     furi_assert(desktop); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewMain); | ||||||
|  |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLockMenu); | ||||||
|  |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLocked); | ||||||
|  |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewDebug); | ||||||
|  |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewFirstStart); | ||||||
|  |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewHwMismatch); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_free(desktop->view_dispatcher); | ||||||
|  |     scene_manager_free(desktop->scene_manager); | ||||||
|  | 
 | ||||||
|  |     desktop_main_free(desktop->main_view); | ||||||
|  |     desktop_lock_menu_free(desktop->lock_menu); | ||||||
|  |     desktop_locked_free(desktop->locked_view); | ||||||
|  |     desktop_debug_free(desktop->debug_view); | ||||||
|  |     desktop_first_start_free(desktop->first_start_view); | ||||||
|  |     desktop_hw_mismatch_free(desktop->hw_mismatch_view); | ||||||
|  | 
 | ||||||
|  |     furi_record_close("gui"); | ||||||
|  |     desktop->gui = NULL; | ||||||
|  | 
 | ||||||
|  |     furi_thread_free(desktop->scene_thread); | ||||||
|  | 
 | ||||||
|  |     furi_record_close("menu"); | ||||||
|  | 
 | ||||||
|  |     free(desktop); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool desktop_is_first_start() { | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     bool exists = storage_common_stat(storage, "/int/first_start", NULL) == FSE_OK; | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | 
 | ||||||
|  |     return exists; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int32_t desktop_srv(void* p) { | ||||||
|  |     Desktop* desktop = desktop_alloc(); | ||||||
|  | 
 | ||||||
|  |     scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); | ||||||
|  | 
 | ||||||
|  |     if(desktop_is_first_start()) { | ||||||
|  |         scene_manager_next_scene(desktop->scene_manager, DesktopSceneFirstStart); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(!furi_hal_version_do_i_belong_here()) { | ||||||
|  |         scene_manager_next_scene(desktop->scene_manager, DesktopSceneHwMismatch); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_run(desktop->view_dispatcher); | ||||||
|  |     desktop_free(desktop); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								applications/desktop/desktop.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								applications/desktop/desktop.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | typedef struct Desktop Desktop; | ||||||
							
								
								
									
										59
									
								
								applications/desktop/desktop_i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								applications/desktop/desktop_i.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "desktop.h" | ||||||
|  | 
 | ||||||
|  | #include <furi.h> | ||||||
|  | #include <furi-hal.h> | ||||||
|  | 
 | ||||||
|  | #include <gui/gui.h> | ||||||
|  | #include <gui/view_dispatcher.h> | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | #include <assets_icons.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | 
 | ||||||
|  | #include "views/desktop_main.h" | ||||||
|  | #include "views/desktop_first_start.h" | ||||||
|  | #include "views/desktop_hw_mismatch.h" | ||||||
|  | #include "views/desktop_lock_menu.h" | ||||||
|  | #include "views/desktop_locked.h" | ||||||
|  | #include "views/desktop_debug.h" | ||||||
|  | 
 | ||||||
|  | #include "scenes/desktop_scene.h" | ||||||
|  | 
 | ||||||
|  | #include "desktop/desktop_settings/desktop_settings.h" | ||||||
|  | 
 | ||||||
|  | #define HINT_TIMEOUT_L 2 | ||||||
|  | #define HINT_TIMEOUT_H 11 | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     DesktopViewMain, | ||||||
|  |     DesktopViewLockMenu, | ||||||
|  |     DesktopViewLocked, | ||||||
|  |     DesktopViewDebug, | ||||||
|  |     DesktopViewFirstStart, | ||||||
|  |     DesktopViewHwMismatch, | ||||||
|  |     DesktopViewTotal, | ||||||
|  | } DesktopViewEnum; | ||||||
|  | 
 | ||||||
|  | struct Desktop { | ||||||
|  |     // Scene
 | ||||||
|  |     FuriThread* scene_thread; | ||||||
|  |     // GUI
 | ||||||
|  |     Gui* gui; | ||||||
|  |     ViewDispatcher* view_dispatcher; | ||||||
|  |     SceneManager* scene_manager; | ||||||
|  | 
 | ||||||
|  |     DesktopFirstStartView* first_start_view; | ||||||
|  |     DesktopHwMismatchView* hw_mismatch_view; | ||||||
|  |     DesktopMainView* main_view; | ||||||
|  |     DesktopLockMenuView* lock_menu; | ||||||
|  |     DesktopLockedView* locked_view; | ||||||
|  |     DesktopDebugView* debug_view; | ||||||
|  |     DesktopSettings settings; | ||||||
|  | 
 | ||||||
|  |     ViewPort* lock_viewport; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | Desktop* desktop_alloc(); | ||||||
|  | 
 | ||||||
|  | void desktop_free(Desktop* desktop); | ||||||
							
								
								
									
										49
									
								
								applications/desktop/desktop_settings/desktop_settings.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								applications/desktop/desktop_settings/desktop_settings.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | |||||||
|  | #include <furi.h> | ||||||
|  | #include <file-worker.h> | ||||||
|  | #include "desktop_settings.h" | ||||||
|  | 
 | ||||||
|  | #define DESKTOP_SETTINGS_TAG "Desktop settings" | ||||||
|  | #define DESKTOP_SETTINGS_PATH "/int/desktop.settings" | ||||||
|  | 
 | ||||||
|  | bool desktop_settings_load(DesktopSettings* desktop_settings) { | ||||||
|  |     furi_assert(desktop_settings); | ||||||
|  |     bool file_loaded = false; | ||||||
|  |     DesktopSettings settings = {}; | ||||||
|  | 
 | ||||||
|  |     FURI_LOG_I(DESKTOP_SETTINGS_TAG, "Loading settings from \"%s\"", DESKTOP_SETTINGS_PATH); | ||||||
|  |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
|  |     if(file_worker_open(file_worker, DESKTOP_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||||
|  |         if(file_worker_read(file_worker, &settings, sizeof(settings))) { | ||||||
|  |             file_loaded = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     file_worker_free(file_worker); | ||||||
|  | 
 | ||||||
|  |     if(file_loaded) { | ||||||
|  |         if(settings.version != DESKTOP_SETTINGS_VER) { | ||||||
|  |             FURI_LOG_E(DESKTOP_SETTINGS_TAG, "Settings version mismatch"); | ||||||
|  |         } else { | ||||||
|  |             osKernelLock(); | ||||||
|  |             *desktop_settings = settings; | ||||||
|  |             osKernelUnlock(); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(DESKTOP_SETTINGS_TAG, "Settings load failed"); | ||||||
|  |     } | ||||||
|  |     return file_loaded; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_settings_save(DesktopSettings* desktop_settings) { | ||||||
|  |     furi_assert(desktop_settings); | ||||||
|  |     bool result = false; | ||||||
|  | 
 | ||||||
|  |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
|  |     if(file_worker_open(file_worker, DESKTOP_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) { | ||||||
|  |         if(file_worker_write(file_worker, desktop_settings, sizeof(DesktopSettings))) { | ||||||
|  |             FURI_LOG_I(DESKTOP_SETTINGS_TAG, "Settings saved to \"%s\"", DESKTOP_SETTINGS_PATH); | ||||||
|  |             result = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     file_worker_free(file_worker); | ||||||
|  |     return result; | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								applications/desktop/desktop_settings/desktop_settings.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								applications/desktop/desktop_settings/desktop_settings.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <stdint.h> | ||||||
|  | #include <stdbool.h> | ||||||
|  | 
 | ||||||
|  | #define DESKTOP_SETTINGS_VER (0) | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t version; | ||||||
|  |     uint16_t favorite; | ||||||
|  | } DesktopSettings; | ||||||
|  | 
 | ||||||
|  | bool desktop_settings_load(DesktopSettings* desktop_settings); | ||||||
|  | 
 | ||||||
|  | bool desktop_settings_save(DesktopSettings* desktop_settings); | ||||||
							
								
								
									
										65
									
								
								applications/desktop/desktop_settings/desktop_settings_app.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								applications/desktop/desktop_settings/desktop_settings_app.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,65 @@ | |||||||
|  | #include "desktop_settings_app.h" | ||||||
|  | 
 | ||||||
|  | static bool desktop_settings_custom_event_callback(void* context, uint32_t event) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     return scene_manager_handle_custom_event(app->scene_manager, event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool desktop_settings_back_event_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     return scene_manager_handle_back_event(app->scene_manager); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | DesktopSettingsApp* desktop_settings_app_alloc() { | ||||||
|  |     DesktopSettingsApp* app = furi_alloc(sizeof(DesktopSettingsApp)); | ||||||
|  | 
 | ||||||
|  |     app->settings.version = DESKTOP_SETTINGS_VER; | ||||||
|  |     desktop_settings_load(&app->settings); | ||||||
|  | 
 | ||||||
|  |     app->gui = furi_record_open("gui"); | ||||||
|  |     app->view_dispatcher = view_dispatcher_alloc(); | ||||||
|  |     app->scene_manager = scene_manager_alloc(&desktop_settings_scene_handlers, app); | ||||||
|  |     view_dispatcher_enable_queue(app->view_dispatcher); | ||||||
|  |     view_dispatcher_set_event_callback_context(app->view_dispatcher, app); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_set_custom_event_callback( | ||||||
|  |         app->view_dispatcher, desktop_settings_custom_event_callback); | ||||||
|  |     view_dispatcher_set_navigation_event_callback( | ||||||
|  |         app->view_dispatcher, desktop_settings_back_event_callback); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); | ||||||
|  | 
 | ||||||
|  |     app->submenu = submenu_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, DesktopSettingsAppViewMain, submenu_get_view(app->submenu)); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, DesktopSettingsAppViewFavorite, submenu_get_view(app->submenu)); | ||||||
|  | 
 | ||||||
|  |     scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneStart); | ||||||
|  |     return app; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_settings_app_free(DesktopSettingsApp* app) { | ||||||
|  |     furi_assert(app); | ||||||
|  |     // Variable item list
 | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMain); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewFavorite); | ||||||
|  |     submenu_free(app->submenu); | ||||||
|  |     // View dispatcher
 | ||||||
|  |     view_dispatcher_free(app->view_dispatcher); | ||||||
|  |     scene_manager_free(app->scene_manager); | ||||||
|  |     // Records
 | ||||||
|  |     furi_record_close("gui"); | ||||||
|  |     free(app); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | extern int32_t desktop_settings_app(void* p) { | ||||||
|  |     DesktopSettingsApp* app = desktop_settings_app_alloc(); | ||||||
|  |     view_dispatcher_run(app->view_dispatcher); | ||||||
|  |     desktop_settings_save(&app->settings); | ||||||
|  |     desktop_settings_app_free(app); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								applications/desktop/desktop_settings/desktop_settings_app.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								applications/desktop/desktop_settings/desktop_settings_app.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <furi.h> | ||||||
|  | #include <gui/gui.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <gui/view_dispatcher.h> | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | #include <gui/modules/submenu.h> | ||||||
|  | 
 | ||||||
|  | #include "desktop_settings.h" | ||||||
|  | #include "scenes/desktop_settings_scene.h" | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     DesktopSettingsAppViewMain, | ||||||
|  |     DesktopSettingsAppViewFavorite, | ||||||
|  | } DesktopSettingsAppView; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     DesktopSettings settings; | ||||||
|  |     Gui* gui; | ||||||
|  |     SceneManager* scene_manager; | ||||||
|  |     ViewDispatcher* view_dispatcher; | ||||||
|  |     Submenu* submenu; | ||||||
|  | 
 | ||||||
|  | } DesktopSettingsApp; | ||||||
| @ -0,0 +1,30 @@ | |||||||
|  | #include "desktop_settings_scene.h" | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, | ||||||
|  | void (*const desktop_settings_on_enter_handlers[])(void*) = { | ||||||
|  | #include "desktop_settings_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_event handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, | ||||||
|  | bool (*const desktop_settings_on_event_handlers[])(void* context, SceneManagerEvent event) = { | ||||||
|  | #include "desktop_settings_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_exit handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, | ||||||
|  | void (*const desktop_settings_on_exit_handlers[])(void* context) = { | ||||||
|  | #include "desktop_settings_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Initialize scene handlers configuration structure
 | ||||||
|  | const SceneManagerHandlers desktop_settings_scene_handlers = { | ||||||
|  |     .on_enter_handlers = desktop_settings_on_enter_handlers, | ||||||
|  |     .on_event_handlers = desktop_settings_on_event_handlers, | ||||||
|  |     .on_exit_handlers = desktop_settings_on_exit_handlers, | ||||||
|  |     .scene_num = DesktopSettingsAppSceneNum, | ||||||
|  | }; | ||||||
| @ -0,0 +1,29 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | 
 | ||||||
|  | // Generate scene id and total number
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) DesktopSettingsAppScene##id, | ||||||
|  | typedef enum { | ||||||
|  | #include "desktop_settings_scene_config.h" | ||||||
|  |     DesktopSettingsAppSceneNum, | ||||||
|  | } DesktopSettingsAppScene; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | extern const SceneManagerHandlers desktop_settings_scene_handlers; | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); | ||||||
|  | #include "desktop_settings_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_event handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) \ | ||||||
|  |     bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); | ||||||
|  | #include "desktop_settings_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_exit handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); | ||||||
|  | #include "desktop_settings_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
| @ -0,0 +1,2 @@ | |||||||
|  | ADD_SCENE(desktop_settings, start, Start) | ||||||
|  | ADD_SCENE(desktop_settings, favorite, Favorite) | ||||||
| @ -0,0 +1,47 @@ | |||||||
|  | #include "../desktop_settings_app.h" | ||||||
|  | #include "applications.h" | ||||||
|  | 
 | ||||||
|  | static void desktop_settings_scene_favorite_submenu_callback(void* context, uint32_t index) { | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, index); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_settings_scene_favorite_on_enter(void* context) { | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     Submenu* submenu = app->submenu; | ||||||
|  |     submenu_clean(submenu); | ||||||
|  | 
 | ||||||
|  |     for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { | ||||||
|  |         submenu_add_item( | ||||||
|  |             submenu, | ||||||
|  |             FLIPPER_APPS[i].name, | ||||||
|  |             i, | ||||||
|  |             desktop_settings_scene_favorite_submenu_callback, | ||||||
|  |             app); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     submenu_set_header(app->submenu, "Quick access app:"); | ||||||
|  |     submenu_set_selected_item(app->submenu, app->settings.favorite); | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewFavorite); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         default: | ||||||
|  |             app->settings.favorite = event.event; | ||||||
|  |             scene_manager_previous_scene(app->scene_manager); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_settings_scene_favorite_on_exit(void* context) { | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     submenu_clean(app->submenu); | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										53
									
								
								applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,53 @@ | |||||||
|  | #include "../desktop_settings_app.h" | ||||||
|  | #include "applications.h" | ||||||
|  | 
 | ||||||
|  | enum DesktopSettingsStartSubmenuIndex { | ||||||
|  |     DesktopSettingsStartSubmenuIndexFavorite, | ||||||
|  |     DesktopSettingsStartSubmenuIndexPinSetup, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void desktop_settings_scene_start_submenu_callback(void* context, uint32_t index) { | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, index); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_settings_scene_start_on_enter(void* context) { | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     Submenu* submenu = app->submenu; | ||||||
|  | 
 | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Favorite App", | ||||||
|  |         DesktopSettingsStartSubmenuIndexFavorite, | ||||||
|  |         desktop_settings_scene_start_submenu_callback, | ||||||
|  |         app); | ||||||
|  | 
 | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "PIN Setup", | ||||||
|  |         DesktopSettingsStartSubmenuIndexPinSetup, | ||||||
|  |         desktop_settings_scene_start_submenu_callback, | ||||||
|  |         app); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMain); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case DesktopSettingsStartSubmenuIndexFavorite: | ||||||
|  |             scene_manager_next_scene(app->scene_manager, DesktopSettingsAppViewFavorite); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_settings_scene_start_on_exit(void* context) { | ||||||
|  |     DesktopSettingsApp* app = context; | ||||||
|  |     submenu_clean(app->submenu); | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								applications/desktop/scenes/desktop_scene.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								applications/desktop/scenes/desktop_scene.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | #include "desktop_scene.h" | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, | ||||||
|  | void (*const desktop_on_enter_handlers[])(void*) = { | ||||||
|  | #include "desktop_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_event handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, | ||||||
|  | bool (*const desktop_on_event_handlers[])(void* context, SceneManagerEvent event) = { | ||||||
|  | #include "desktop_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_exit handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, | ||||||
|  | void (*const desktop_on_exit_handlers[])(void* context) = { | ||||||
|  | #include "desktop_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Initialize scene handlers configuration structure
 | ||||||
|  | const SceneManagerHandlers desktop_scene_handlers = { | ||||||
|  |     .on_enter_handlers = desktop_on_enter_handlers, | ||||||
|  |     .on_event_handlers = desktop_on_event_handlers, | ||||||
|  |     .on_exit_handlers = desktop_on_exit_handlers, | ||||||
|  |     .scene_num = DesktopSceneNum, | ||||||
|  | }; | ||||||
							
								
								
									
										29
									
								
								applications/desktop/scenes/desktop_scene.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								applications/desktop/scenes/desktop_scene.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | 
 | ||||||
|  | // Generate scene id and total number
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) DesktopScene##id, | ||||||
|  | typedef enum { | ||||||
|  | #include "desktop_scene_config.h" | ||||||
|  |     DesktopSceneNum, | ||||||
|  | } DesktopScene; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | extern const SceneManagerHandlers desktop_scene_handlers; | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); | ||||||
|  | #include "desktop_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_event handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) \ | ||||||
|  |     bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); | ||||||
|  | #include "desktop_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_exit handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); | ||||||
|  | #include "desktop_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
							
								
								
									
										6
									
								
								applications/desktop/scenes/desktop_scene_config.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								applications/desktop/scenes/desktop_scene_config.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | ADD_SCENE(desktop, main, Main) | ||||||
|  | ADD_SCENE(desktop, lock_menu, LockMenu) | ||||||
|  | ADD_SCENE(desktop, locked, Locked) | ||||||
|  | ADD_SCENE(desktop, debug, Debug) | ||||||
|  | ADD_SCENE(desktop, first_start, FirstStart) | ||||||
|  | ADD_SCENE(desktop, hw_mismatch, HwMismatch) | ||||||
							
								
								
									
										62
									
								
								applications/desktop/scenes/desktop_scene_debug.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								applications/desktop/scenes/desktop_scene_debug.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | |||||||
|  | #include "../desktop_i.h" | ||||||
|  | #include "../views/desktop_debug.h" | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
|  | #include <dolphin/helpers/dolphin_deed.h> | ||||||
|  | 
 | ||||||
|  | void desktop_scene_debug_callback(DesktopDebugEvent event, void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_debug_on_enter(void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  | 
 | ||||||
|  |     desktop_debug_get_dolphin_data(desktop->debug_view); | ||||||
|  | 
 | ||||||
|  |     desktop_debug_set_callback(desktop->debug_view, desktop_scene_debug_callback, desktop); | ||||||
|  |     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewDebug); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     Dolphin* dolphin = furi_record_open("dolphin"); | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case DesktopDebugEventExit: | ||||||
|  |             scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); | ||||||
|  |             dolphin_save(dolphin); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         case DesktopDebugEventDeed: | ||||||
|  |             dolphin_deed(dolphin, DolphinDeedIButtonEmulate); | ||||||
|  |             desktop_debug_get_dolphin_data(desktop->debug_view); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         case DesktopDebugEventWrongDeed: | ||||||
|  |             dolphin_deed(dolphin, DolphinDeedWrong); | ||||||
|  |             desktop_debug_get_dolphin_data(desktop->debug_view); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         case DesktopDebugEventSaveState: | ||||||
|  |             dolphin_save(dolphin); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     furi_record_close("dolphin"); | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_debug_on_exit(void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     desktop_debug_reset_screen_idx(desktop->debug_view); | ||||||
|  | } | ||||||
							
								
								
									
										42
									
								
								applications/desktop/scenes/desktop_scene_first_start.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								applications/desktop/scenes/desktop_scene_first_start.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | #include "../desktop_i.h" | ||||||
|  | #include "../views/desktop_first_start.h" | ||||||
|  | 
 | ||||||
|  | void desktop_scene_first_start_callback(DesktopFirstStartEvent event, void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_first_start_on_enter(void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     DesktopFirstStartView* first_start_view = desktop->first_start_view; | ||||||
|  | 
 | ||||||
|  |     desktop_first_start_set_callback( | ||||||
|  |         first_start_view, desktop_scene_first_start_callback, desktop); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewFirstStart); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_scene_first_start_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     bool consumed = false; | ||||||
|  |     Storage* storage = NULL; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case DesktopFirstStartCompleted: | ||||||
|  |             storage = furi_record_open("storage"); | ||||||
|  |             storage_common_remove(storage, "/int/first_start"); | ||||||
|  |             furi_record_close("storage"); | ||||||
|  |             scene_manager_previous_scene(desktop->scene_manager); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_first_start_on_exit(void* context) { | ||||||
|  | } | ||||||
							
								
								
									
										37
									
								
								applications/desktop/scenes/desktop_scene_hw_mismatch.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								applications/desktop/scenes/desktop_scene_hw_mismatch.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | |||||||
|  | #include "../desktop_i.h" | ||||||
|  | #include "../views/desktop_hw_mismatch.h" | ||||||
|  | 
 | ||||||
|  | void desktop_scene_hw_mismatch_callback(DesktopHwMismatchEvent event, void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_hw_mismatch_on_enter(void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  | 
 | ||||||
|  |     desktop_hw_mismatch_set_callback( | ||||||
|  |         desktop->hw_mismatch_view, desktop_scene_hw_mismatch_callback, desktop); | ||||||
|  |     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewHwMismatch); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case DesktopHwMismatchEventExit: | ||||||
|  |             scene_manager_previous_scene(desktop->scene_manager); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_hw_mismatch_on_exit(void* context) { | ||||||
|  |     // Desktop* desktop = (Desktop*)context;
 | ||||||
|  | } | ||||||
							
								
								
									
										42
									
								
								applications/desktop/scenes/desktop_scene_lock_menu.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								applications/desktop/scenes/desktop_scene_lock_menu.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | #include "../desktop_i.h" | ||||||
|  | #include "../views/desktop_lock_menu.h" | ||||||
|  | 
 | ||||||
|  | void desktop_scene_lock_menu_callback(DesktopLockMenuEvent event, void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_lock_menu_on_enter(void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  | 
 | ||||||
|  |     desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop); | ||||||
|  |     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLockMenu); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case DesktopLockMenuEventLock: | ||||||
|  |             scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         case DesktopLockMenuEventExit: | ||||||
|  |             scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_lock_menu_on_exit(void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     desktop_lock_menu_reset_idx(desktop->lock_menu); | ||||||
|  | } | ||||||
							
								
								
									
										51
									
								
								applications/desktop/scenes/desktop_scene_locked.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								applications/desktop/scenes/desktop_scene_locked.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | |||||||
|  | #include "../desktop_i.h" | ||||||
|  | #include "../views/desktop_locked.h" | ||||||
|  | 
 | ||||||
|  | void desktop_scene_locked_callback(DesktopLockedEvent event, void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_locked_on_enter(void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     DesktopLockedView* locked_view = desktop->locked_view; | ||||||
|  | 
 | ||||||
|  |     desktop_locked_set_callback(locked_view, desktop_scene_locked_callback, desktop); | ||||||
|  |     desktop_locked_reset_door_pos(locked_view); | ||||||
|  |     desktop_locked_update_hint_timeout(locked_view); | ||||||
|  | 
 | ||||||
|  |     view_port_enabled_set(desktop->lock_viewport, true); | ||||||
|  |     osTimerStart(locked_view->timer, 63); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLocked); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  | 
 | ||||||
|  |     bool consumed = false; | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case DesktopLockedEventUnlock: | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 desktop->scene_manager, DesktopSceneMain, DesktopMainEventUnlocked); | ||||||
|  |             scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case DesktopLockedEventUpdate: | ||||||
|  |             desktop_locked_manage_redraw(desktop->locked_view); | ||||||
|  |             consumed = true; | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_locked_on_exit(void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     DesktopLockedView* locked_view = desktop->locked_view; | ||||||
|  |     desktop_locked_reset_counter(desktop->locked_view); | ||||||
|  |     osTimerStop(locked_view->timer); | ||||||
|  | } | ||||||
							
								
								
									
										88
									
								
								applications/desktop/scenes/desktop_scene_main.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								applications/desktop/scenes/desktop_scene_main.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,88 @@ | |||||||
|  | #include "../desktop_i.h" | ||||||
|  | #include "../views/desktop_main.h" | ||||||
|  | #include "applications.h" | ||||||
|  | #include <loader/loader.h> | ||||||
|  | #define MAIN_VIEW_DEFAULT (0UL) | ||||||
|  | 
 | ||||||
|  | static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* flipper_app) { | ||||||
|  |     furi_assert(desktop); | ||||||
|  |     furi_assert(flipper_app); | ||||||
|  |     furi_assert(flipper_app->app); | ||||||
|  |     furi_assert(flipper_app->name); | ||||||
|  | 
 | ||||||
|  |     if(furi_thread_get_state(desktop->scene_thread) != FuriThreadStateStopped) { | ||||||
|  |         FURI_LOG_E("Desktop", "Thread is already running"); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     furi_thread_set_name(desktop->scene_thread, flipper_app->name); | ||||||
|  |     furi_thread_set_stack_size(desktop->scene_thread, flipper_app->stack_size); | ||||||
|  |     furi_thread_set_callback(desktop->scene_thread, flipper_app->app); | ||||||
|  | 
 | ||||||
|  |     furi_thread_start(desktop->scene_thread); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_main_callback(DesktopMainEvent event, void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_main_on_enter(void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     DesktopMainView* main_view = desktop->main_view; | ||||||
|  | 
 | ||||||
|  |     desktop_main_set_callback(main_view, desktop_scene_main_callback, desktop); | ||||||
|  |     view_port_enabled_set(desktop->lock_viewport, false); | ||||||
|  | 
 | ||||||
|  |     if(scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneMain) == | ||||||
|  |        DesktopMainEventUnlocked) { | ||||||
|  |         desktop_main_unlocked(desktop->main_view); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case DesktopMainEventOpenMenu: | ||||||
|  |             loader_show_menu(); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         case DesktopMainEventOpenLockMenu: | ||||||
|  |             scene_manager_next_scene(desktop->scene_manager, DesktopSceneLockMenu); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         case DesktopMainEventOpenDebug: | ||||||
|  |             scene_manager_next_scene(desktop->scene_manager, DesktopViewDebug); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         case DesktopMainEventOpenArchive: | ||||||
|  |             desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case DesktopMainEventOpenFavorite: | ||||||
|  |             desktop_settings_load(&desktop->settings); | ||||||
|  |             desktop_switch_to_app(desktop, &FLIPPER_APPS[desktop->settings.favorite]); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_main_on_exit(void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneMain, MAIN_VIEW_DEFAULT); | ||||||
|  |     desktop_main_reset_hint(desktop->main_view); | ||||||
|  | } | ||||||
							
								
								
									
										166
									
								
								applications/desktop/views/desktop_debug.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								applications/desktop/views/desktop_debug.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,166 @@ | |||||||
|  | #include <furi.h> | ||||||
|  | #include "../desktop_i.h" | ||||||
|  | #include "desktop_debug.h" | ||||||
|  | 
 | ||||||
|  | #include "applications/dolphin/helpers/dolphin_state.h" | ||||||
|  | #include "applications/dolphin/dolphin.h" | ||||||
|  | 
 | ||||||
|  | void desktop_debug_set_callback( | ||||||
|  |     DesktopDebugView* debug_view, | ||||||
|  |     DesktopDebugViewCallback callback, | ||||||
|  |     void* context) { | ||||||
|  |     furi_assert(debug_view); | ||||||
|  |     furi_assert(callback); | ||||||
|  |     debug_view->callback = callback; | ||||||
|  |     debug_view->context = context; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_debug_render(Canvas* canvas, void* model) { | ||||||
|  |     canvas_clear(canvas); | ||||||
|  |     DesktopDebugViewModel* m = model; | ||||||
|  |     const Version* ver; | ||||||
|  |     char buffer[64]; | ||||||
|  | 
 | ||||||
|  |     static const char* headers[] = {"FW Version info:", "Boot Version info:", "Desktop info:"}; | ||||||
|  | 
 | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  |     canvas_set_font(canvas, FontPrimary); | ||||||
|  |     canvas_draw_str(canvas, 2, 13, headers[m->screen]); | ||||||
|  |     canvas_set_font(canvas, FontSecondary); | ||||||
|  | 
 | ||||||
|  |     if(m->screen != DesktopViewStatsMeta) { | ||||||
|  |         // Hardware version
 | ||||||
|  |         const char* my_name = furi_hal_version_get_name_ptr(); | ||||||
|  |         snprintf( | ||||||
|  |             buffer, | ||||||
|  |             sizeof(buffer), | ||||||
|  |             "HW: %d.F%dB%dC%d %s", | ||||||
|  |             furi_hal_version_get_hw_version(), | ||||||
|  |             furi_hal_version_get_hw_target(), | ||||||
|  |             furi_hal_version_get_hw_body(), | ||||||
|  |             furi_hal_version_get_hw_connect(), | ||||||
|  |             my_name ? my_name : "Unknown"); | ||||||
|  |         canvas_draw_str(canvas, 5, 23, buffer); | ||||||
|  | 
 | ||||||
|  |         ver = m->screen == DesktopViewStatsBoot ? furi_hal_version_get_boot_version() : | ||||||
|  |                                                   furi_hal_version_get_firmware_version(); | ||||||
|  | 
 | ||||||
|  |         if(!ver) { | ||||||
|  |             canvas_draw_str(canvas, 5, 33, "No info"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         snprintf( | ||||||
|  |             buffer, | ||||||
|  |             sizeof(buffer), | ||||||
|  |             "%s [%s]", | ||||||
|  |             version_get_version(ver), | ||||||
|  |             version_get_builddate(ver)); | ||||||
|  |         canvas_draw_str(canvas, 5, 33, buffer); | ||||||
|  | 
 | ||||||
|  |         snprintf( | ||||||
|  |             buffer, | ||||||
|  |             sizeof(buffer), | ||||||
|  |             "%s [%s]", | ||||||
|  |             version_get_githash(ver), | ||||||
|  |             version_get_gitbranchnum(ver)); | ||||||
|  |         canvas_draw_str(canvas, 5, 43, buffer); | ||||||
|  | 
 | ||||||
|  |         snprintf( | ||||||
|  |             buffer, sizeof(buffer), "[%s] %s", version_get_target(ver), version_get_gitbranch(ver)); | ||||||
|  |         canvas_draw_str(canvas, 5, 53, buffer); | ||||||
|  | 
 | ||||||
|  |     } else { | ||||||
|  |         char buffer[64]; | ||||||
|  | 
 | ||||||
|  |         canvas_set_font(canvas, FontSecondary); | ||||||
|  |         snprintf(buffer, 64, "Icounter: %ld", m->icounter); | ||||||
|  |         canvas_draw_str(canvas, 5, 30, buffer); | ||||||
|  |         snprintf(buffer, 64, "Butthurt: %ld", m->butthurt); | ||||||
|  |         canvas_draw_str(canvas, 5, 40, buffer); | ||||||
|  |         canvas_draw_str(canvas, 0, 53, "[< >] icounter value   [ok] save"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | View* desktop_debug_get_view(DesktopDebugView* debug_view) { | ||||||
|  |     furi_assert(debug_view); | ||||||
|  |     return debug_view->view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_debug_input(InputEvent* event, void* context) { | ||||||
|  |     furi_assert(event); | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     DesktopDebugView* debug_view = context; | ||||||
|  | 
 | ||||||
|  |     if(event->type != InputTypeShort) return false; | ||||||
|  |     DesktopViewStatsScreens current = 0; | ||||||
|  |     with_view_model( | ||||||
|  |         debug_view->view, (DesktopDebugViewModel * model) { | ||||||
|  |             if(event->key == InputKeyDown) { | ||||||
|  |                 model->screen = (model->screen + 1) % DesktopViewStatsTotalCount; | ||||||
|  |             } else if(event->key == InputKeyUp) { | ||||||
|  |                 model->screen = ((model->screen - 1) + DesktopViewStatsTotalCount) % | ||||||
|  |                                 DesktopViewStatsTotalCount; | ||||||
|  |             } | ||||||
|  |             current = model->screen; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     if(current == DesktopViewStatsMeta) { | ||||||
|  |         if(event->key == InputKeyLeft) { | ||||||
|  |             debug_view->callback(DesktopDebugEventWrongDeed, debug_view->context); | ||||||
|  |         } else if(event->key == InputKeyRight) { | ||||||
|  |             debug_view->callback(DesktopDebugEventDeed, debug_view->context); | ||||||
|  |         } else if(event->key == InputKeyOk) { | ||||||
|  |             debug_view->callback(DesktopDebugEventSaveState, debug_view->context); | ||||||
|  |         } else { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(event->key == InputKeyBack) { | ||||||
|  |         debug_view->callback(DesktopDebugEventExit, debug_view->context); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | DesktopDebugView* desktop_debug_alloc() { | ||||||
|  |     DesktopDebugView* debug_view = furi_alloc(sizeof(DesktopDebugView)); | ||||||
|  |     debug_view->view = view_alloc(); | ||||||
|  |     view_allocate_model(debug_view->view, ViewModelTypeLocking, sizeof(DesktopDebugViewModel)); | ||||||
|  |     view_set_context(debug_view->view, debug_view); | ||||||
|  |     view_set_draw_callback(debug_view->view, (ViewDrawCallback)desktop_debug_render); | ||||||
|  |     view_set_input_callback(debug_view->view, desktop_debug_input); | ||||||
|  | 
 | ||||||
|  |     return debug_view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_debug_free(DesktopDebugView* debug_view) { | ||||||
|  |     furi_assert(debug_view); | ||||||
|  | 
 | ||||||
|  |     view_free(debug_view->view); | ||||||
|  |     free(debug_view); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_debug_get_dolphin_data(DesktopDebugView* debug_view) { | ||||||
|  |     Dolphin* dolphin = furi_record_open("dolphin"); | ||||||
|  |     DolphinDeedWeight stats = dolphin_stats(dolphin); | ||||||
|  |     with_view_model( | ||||||
|  |         debug_view->view, (DesktopDebugViewModel * model) { | ||||||
|  |             model->icounter = stats.icounter; | ||||||
|  |             model->butthurt = stats.butthurt; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     furi_record_close("dolphin"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_debug_reset_screen_idx(DesktopDebugView* debug_view) { | ||||||
|  |     with_view_model( | ||||||
|  |         debug_view->view, (DesktopDebugViewModel * model) { | ||||||
|  |             model->screen = 0; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
							
								
								
									
										52
									
								
								applications/desktop/views/desktop_debug.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								applications/desktop/views/desktop_debug.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/gui_i.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <gui/canvas.h> | ||||||
|  | #include <gui/elements.h> | ||||||
|  | #include <furi.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     DesktopDebugEventDeed, | ||||||
|  |     DesktopDebugEventWrongDeed, | ||||||
|  |     DesktopDebugEventSaveState, | ||||||
|  |     DesktopDebugEventExit, | ||||||
|  | } DesktopDebugEvent; | ||||||
|  | 
 | ||||||
|  | typedef struct DesktopDebugView DesktopDebugView; | ||||||
|  | 
 | ||||||
|  | typedef void (*DesktopDebugViewCallback)(DesktopDebugEvent event, void* context); | ||||||
|  | 
 | ||||||
|  | // Debug info
 | ||||||
|  | typedef enum { | ||||||
|  |     DesktopViewStatsFw, | ||||||
|  |     DesktopViewStatsBoot, | ||||||
|  |     DesktopViewStatsMeta, | ||||||
|  |     DesktopViewStatsTotalCount, | ||||||
|  | } DesktopViewStatsScreens; | ||||||
|  | 
 | ||||||
|  | struct DesktopDebugView { | ||||||
|  |     View* view; | ||||||
|  |     DesktopDebugViewCallback callback; | ||||||
|  |     void* context; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint32_t icounter; | ||||||
|  |     uint32_t butthurt; | ||||||
|  |     DesktopViewStatsScreens screen; | ||||||
|  | } DesktopDebugViewModel; | ||||||
|  | 
 | ||||||
|  | void desktop_debug_set_callback( | ||||||
|  |     DesktopDebugView* debug_view, | ||||||
|  |     DesktopDebugViewCallback callback, | ||||||
|  |     void* context); | ||||||
|  | 
 | ||||||
|  | View* desktop_debug_get_view(DesktopDebugView* debug_view); | ||||||
|  | 
 | ||||||
|  | DesktopDebugView* desktop_debug_alloc(); | ||||||
|  | void desktop_debug_free(DesktopDebugView* debug_view); | ||||||
|  | 
 | ||||||
|  | void desktop_debug_get_dolphin_data(DesktopDebugView* debug_view); | ||||||
|  | void desktop_debug_reset_screen_idx(DesktopDebugView* debug_view); | ||||||
							
								
								
									
										107
									
								
								applications/desktop/views/desktop_first_start.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								applications/desktop/views/desktop_first_start.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | |||||||
|  | #include <furi.h> | ||||||
|  | #include "../desktop_i.h" | ||||||
|  | #include "desktop_first_start.h" | ||||||
|  | 
 | ||||||
|  | void desktop_first_start_set_callback( | ||||||
|  |     DesktopFirstStartView* first_start_view, | ||||||
|  |     DesktopFirstStartViewCallback callback, | ||||||
|  |     void* context) { | ||||||
|  |     furi_assert(first_start_view); | ||||||
|  |     furi_assert(callback); | ||||||
|  |     first_start_view->callback = callback; | ||||||
|  |     first_start_view->context = context; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_first_start_render(Canvas* canvas, void* model) { | ||||||
|  |     DesktopFirstStartViewModel* m = model; | ||||||
|  | 
 | ||||||
|  |     canvas_clear(canvas); | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  |     canvas_set_font(canvas, FontSecondary); | ||||||
|  |     uint8_t width = canvas_width(canvas); | ||||||
|  |     uint8_t height = canvas_height(canvas); | ||||||
|  |     const char* my_name = furi_hal_version_get_name_ptr(); | ||||||
|  |     if(m->page == 0) { | ||||||
|  |         canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart0_70x53); | ||||||
|  |         elements_multiline_text_framed(canvas, 75, 20, "Hey m8,\npress > to\ncontinue"); | ||||||
|  |     } else if(m->page == 1) { | ||||||
|  |         canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart1_59x53); | ||||||
|  |         elements_multiline_text_framed(canvas, 64, 20, "First Of All,\n...      >"); | ||||||
|  |     } else if(m->page == 2) { | ||||||
|  |         canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart2_59x51); | ||||||
|  |         elements_multiline_text_framed(canvas, 64, 20, "Thank you\nfor your\nsupport! >"); | ||||||
|  |     } else if(m->page == 3) { | ||||||
|  |         canvas_draw_icon(canvas, width - 57, height - 48, &I_DolphinFirstStart3_57x48); | ||||||
|  |         elements_multiline_text_framed(canvas, 0, 20, "Kickstarter\ncampaign\nwas INSANE! >"); | ||||||
|  |     } else if(m->page == 4) { | ||||||
|  |         canvas_draw_icon(canvas, width - 67, height - 50, &I_DolphinFirstStart4_67x53); | ||||||
|  |         elements_multiline_text_framed(canvas, 0, 17, "Now\nallow me\nto introduce\nmyself >"); | ||||||
|  |     } else if(m->page == 5) { | ||||||
|  |         char buf[64]; | ||||||
|  |         snprintf( | ||||||
|  |             buf, | ||||||
|  |             64, | ||||||
|  |             "%s %s%s", | ||||||
|  |             "I am", | ||||||
|  |             my_name ? my_name : "Unknown", | ||||||
|  |             ",\ncyberdesktop\nliving in your\npocket >"); | ||||||
|  |         canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart5_54x49); | ||||||
|  |         elements_multiline_text_framed(canvas, 60, 17, buf); | ||||||
|  |     } else if(m->page == 6) { | ||||||
|  |         canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart6_58x54); | ||||||
|  |         elements_multiline_text_framed( | ||||||
|  |             canvas, 63, 17, "I can grow\nsmart'n'cool\nif you use me\noften >"); | ||||||
|  |     } else if(m->page == 7) { | ||||||
|  |         canvas_draw_icon(canvas, width - 61, height - 48, &I_DolphinFirstStart7_61x51); | ||||||
|  |         elements_multiline_text_framed( | ||||||
|  |             canvas, 0, 17, "As long as\nyou read, write\nand emulate >"); | ||||||
|  |     } else if(m->page == 8) { | ||||||
|  |         canvas_draw_icon(canvas, width - 56, height - 48, &I_DolphinFirstStart8_56x51); | ||||||
|  |         elements_multiline_text_framed( | ||||||
|  |             canvas, 0, 17, "You can check\nmy level and\nmood in the\nPassport menu"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | View* desktop_first_start_get_view(DesktopFirstStartView* first_start_view) { | ||||||
|  |     furi_assert(first_start_view); | ||||||
|  |     return first_start_view->view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_first_start_input(InputEvent* event, void* context) { | ||||||
|  |     furi_assert(event); | ||||||
|  |     DesktopFirstStartView* first_start_view = context; | ||||||
|  | 
 | ||||||
|  |     if(event->type == InputTypeShort) { | ||||||
|  |         DesktopFirstStartViewModel* model = view_get_model(first_start_view->view); | ||||||
|  |         if(event->key == InputKeyLeft) { | ||||||
|  |             if(model->page > 0) model->page--; | ||||||
|  |         } else if(event->key == InputKeyRight) { | ||||||
|  |             uint32_t page = ++model->page; | ||||||
|  |             if(page > 8) { | ||||||
|  |                 first_start_view->callback(DesktopFirstStartCompleted, first_start_view->context); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         view_commit_model(first_start_view->view, true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | DesktopFirstStartView* desktop_first_start_alloc() { | ||||||
|  |     DesktopFirstStartView* first_start_view = furi_alloc(sizeof(DesktopFirstStartView)); | ||||||
|  |     first_start_view->view = view_alloc(); | ||||||
|  |     view_allocate_model( | ||||||
|  |         first_start_view->view, ViewModelTypeLocking, sizeof(DesktopFirstStartViewModel)); | ||||||
|  |     view_set_context(first_start_view->view, first_start_view); | ||||||
|  |     view_set_draw_callback(first_start_view->view, (ViewDrawCallback)desktop_first_start_render); | ||||||
|  |     view_set_input_callback(first_start_view->view, desktop_first_start_input); | ||||||
|  | 
 | ||||||
|  |     return first_start_view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_first_start_free(DesktopFirstStartView* first_start_view) { | ||||||
|  |     furi_assert(first_start_view); | ||||||
|  | 
 | ||||||
|  |     view_free(first_start_view->view); | ||||||
|  |     free(first_start_view); | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								applications/desktop/views/desktop_first_start.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								applications/desktop/views/desktop_first_start.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/gui_i.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <gui/canvas.h> | ||||||
|  | #include <gui/elements.h> | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     DesktopFirstStartCompleted, | ||||||
|  | } DesktopFirstStartEvent; | ||||||
|  | 
 | ||||||
|  | typedef struct DesktopFirstStartView DesktopFirstStartView; | ||||||
|  | 
 | ||||||
|  | typedef void (*DesktopFirstStartViewCallback)(DesktopFirstStartEvent event, void* context); | ||||||
|  | 
 | ||||||
|  | struct DesktopFirstStartView { | ||||||
|  |     View* view; | ||||||
|  |     DesktopFirstStartViewCallback callback; | ||||||
|  |     void* context; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t page; | ||||||
|  | } DesktopFirstStartViewModel; | ||||||
|  | 
 | ||||||
|  | void desktop_first_start_set_callback( | ||||||
|  |     DesktopFirstStartView* main_view, | ||||||
|  |     DesktopFirstStartViewCallback callback, | ||||||
|  |     void* context); | ||||||
|  | 
 | ||||||
|  | View* desktop_first_start_get_view(DesktopFirstStartView* main_view); | ||||||
|  | 
 | ||||||
|  | DesktopFirstStartView* desktop_first_start_alloc(); | ||||||
|  | void desktop_first_start_free(DesktopFirstStartView* main_view); | ||||||
							
								
								
									
										66
									
								
								applications/desktop/views/desktop_hw_mismatch.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								applications/desktop/views/desktop_hw_mismatch.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | |||||||
|  | #include <furi.h> | ||||||
|  | #include "../desktop_i.h" | ||||||
|  | #include <furi-hal.h> | ||||||
|  | #include <furi-hal-version.h> | ||||||
|  | 
 | ||||||
|  | #include "desktop_hw_mismatch.h" | ||||||
|  | 
 | ||||||
|  | void desktop_hw_mismatch_set_callback( | ||||||
|  |     DesktopHwMismatchView* main_view, | ||||||
|  |     DesktopHwMismatchViewCallback callback, | ||||||
|  |     void* context) { | ||||||
|  |     furi_assert(main_view); | ||||||
|  |     furi_assert(callback); | ||||||
|  |     main_view->callback = callback; | ||||||
|  |     main_view->context = context; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_hw_mismatch_render(Canvas* canvas, void* model) { | ||||||
|  |     canvas_clear(canvas); | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  |     canvas_set_font(canvas, FontPrimary); | ||||||
|  |     canvas_draw_str(canvas, 2, 15, "!!!! HW Mismatch !!!!"); | ||||||
|  | 
 | ||||||
|  |     char buffer[64]; | ||||||
|  |     canvas_set_font(canvas, FontSecondary); | ||||||
|  |     snprintf(buffer, 64, "HW target: F%d", furi_hal_version_get_hw_target()); | ||||||
|  |     canvas_draw_str(canvas, 5, 27, buffer); | ||||||
|  |     canvas_draw_str(canvas, 5, 38, "FW target: " TARGET); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | View* desktop_hw_mismatch_get_view(DesktopHwMismatchView* hw_mismatch_view) { | ||||||
|  |     furi_assert(hw_mismatch_view); | ||||||
|  |     return hw_mismatch_view->view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_hw_mismatch_input(InputEvent* event, void* context) { | ||||||
|  |     furi_assert(event); | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     DesktopHwMismatchView* hw_mismatch_view = context; | ||||||
|  | 
 | ||||||
|  |     if(event->type == InputTypeShort) { | ||||||
|  |         hw_mismatch_view->callback(DesktopHwMismatchEventExit, hw_mismatch_view->context); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | DesktopHwMismatchView* desktop_hw_mismatch_alloc() { | ||||||
|  |     DesktopHwMismatchView* hw_mismatch_view = furi_alloc(sizeof(DesktopHwMismatchView)); | ||||||
|  |     hw_mismatch_view->view = view_alloc(); | ||||||
|  |     view_allocate_model( | ||||||
|  |         hw_mismatch_view->view, ViewModelTypeLocking, sizeof(DesktopHwMismatchViewModel)); | ||||||
|  |     view_set_context(hw_mismatch_view->view, hw_mismatch_view); | ||||||
|  |     view_set_draw_callback(hw_mismatch_view->view, (ViewDrawCallback)desktop_hw_mismatch_render); | ||||||
|  |     view_set_input_callback(hw_mismatch_view->view, desktop_hw_mismatch_input); | ||||||
|  | 
 | ||||||
|  |     return hw_mismatch_view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_hw_mismatch_free(DesktopHwMismatchView* hw_mismatch_view) { | ||||||
|  |     furi_assert(hw_mismatch_view); | ||||||
|  | 
 | ||||||
|  |     view_free(hw_mismatch_view->view); | ||||||
|  |     free(hw_mismatch_view); | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								applications/desktop/views/desktop_hw_mismatch.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								applications/desktop/views/desktop_hw_mismatch.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/gui_i.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <gui/canvas.h> | ||||||
|  | #include <gui/elements.h> | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     DesktopHwMismatchEventExit, | ||||||
|  | } DesktopHwMismatchEvent; | ||||||
|  | 
 | ||||||
|  | typedef struct DesktopHwMismatchView DesktopHwMismatchView; | ||||||
|  | 
 | ||||||
|  | typedef void (*DesktopHwMismatchViewCallback)(DesktopHwMismatchEvent event, void* context); | ||||||
|  | 
 | ||||||
|  | struct DesktopHwMismatchView { | ||||||
|  |     View* view; | ||||||
|  |     DesktopHwMismatchViewCallback callback; | ||||||
|  |     void* context; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     IconAnimation* animation; | ||||||
|  |     uint8_t scene_num; | ||||||
|  |     uint8_t hint_timeout; | ||||||
|  |     bool locked; | ||||||
|  | } DesktopHwMismatchViewModel; | ||||||
|  | 
 | ||||||
|  | void desktop_hw_mismatch_set_callback( | ||||||
|  |     DesktopHwMismatchView* hw_mismatch_view, | ||||||
|  |     DesktopHwMismatchViewCallback callback, | ||||||
|  |     void* context); | ||||||
|  | 
 | ||||||
|  | View* desktop_hw_mismatch_get_view(DesktopHwMismatchView* hw_mismatch_view); | ||||||
|  | 
 | ||||||
|  | DesktopHwMismatchView* desktop_hw_mismatch_alloc(); | ||||||
|  | void desktop_hw_mismatch_free(DesktopHwMismatchView* hw_mismatch_view); | ||||||
							
								
								
									
										111
									
								
								applications/desktop/views/desktop_lock_menu.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								applications/desktop/views/desktop_lock_menu.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,111 @@ | |||||||
|  | #include <furi.h> | ||||||
|  | #include "../desktop_i.h" | ||||||
|  | #include "desktop_lock_menu.h" | ||||||
|  | 
 | ||||||
|  | void desktop_lock_menu_set_callback( | ||||||
|  |     DesktopLockMenuView* lock_menu, | ||||||
|  |     DesktopLockMenuViewCallback callback, | ||||||
|  |     void* context) { | ||||||
|  |     furi_assert(lock_menu); | ||||||
|  |     furi_assert(callback); | ||||||
|  |     lock_menu->callback = callback; | ||||||
|  |     lock_menu->context = context; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu) { | ||||||
|  |     with_view_model( | ||||||
|  |         lock_menu->view, (DesktopLockMenuViewModel * model) { | ||||||
|  |             model->idx = 0; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void lock_menu_callback(void* context, uint8_t index) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     DesktopLockMenuView* lock_menu = context; | ||||||
|  |     switch(index) { | ||||||
|  |     case 0: // lock
 | ||||||
|  |         lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context); | ||||||
|  |     default: // wip message
 | ||||||
|  |         with_view_model( | ||||||
|  |             lock_menu->view, (DesktopLockMenuViewModel * model) { | ||||||
|  |                 model->hint_timeout = HINT_TIMEOUT_L; | ||||||
|  |                 return true; | ||||||
|  |             }); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_lock_menu_render(Canvas* canvas, void* model) { | ||||||
|  |     const char* Lockmenu_Items[3] = {"Lock", "Set PIN", "DUMB mode"}; | ||||||
|  | 
 | ||||||
|  |     DesktopLockMenuViewModel* m = model; | ||||||
|  |     canvas_clear(canvas); | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  |     canvas_draw_icon(canvas, -57, 0, &I_DoorLeft_70x55); | ||||||
|  |     canvas_draw_icon(canvas, 115, 0, &I_DoorRight_70x55); | ||||||
|  |     canvas_set_font(canvas, FontSecondary); | ||||||
|  | 
 | ||||||
|  |     for(uint8_t i = 0; i < 3; ++i) { | ||||||
|  |         canvas_draw_str_aligned( | ||||||
|  |             canvas, | ||||||
|  |             64, | ||||||
|  |             13 + (i * 17), | ||||||
|  |             AlignCenter, | ||||||
|  |             AlignCenter, | ||||||
|  |             (m->hint_timeout && m->idx == i && m->idx) ? "Not implemented" : Lockmenu_Items[i]); | ||||||
|  |         if(m->idx == i) elements_frame(canvas, 15, 5 + (i * 17), 98, 15); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu) { | ||||||
|  |     furi_assert(lock_menu); | ||||||
|  |     return lock_menu->view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_lock_menu_input(InputEvent* event, void* context) { | ||||||
|  |     furi_assert(event); | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     DesktopLockMenuView* lock_menu = context; | ||||||
|  |     uint8_t idx; | ||||||
|  | 
 | ||||||
|  |     if(event->type != InputTypeShort) return false; | ||||||
|  |     with_view_model( | ||||||
|  |         lock_menu->view, (DesktopLockMenuViewModel * model) { | ||||||
|  |             model->hint_timeout = 0; // clear hint timeout
 | ||||||
|  |             if(event->key == InputKeyUp) { | ||||||
|  |                 model->idx = CLAMP(model->idx - 1, 2, 0); | ||||||
|  |             } else if(event->key == InputKeyDown) { | ||||||
|  |                 model->idx = CLAMP(model->idx + 1, 2, 0); | ||||||
|  |             } | ||||||
|  |             idx = model->idx; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     if(event->key == InputKeyBack) { | ||||||
|  |         lock_menu->callback(DesktopLockMenuEventExit, lock_menu->context); | ||||||
|  |     } else if(event->key == InputKeyOk) { | ||||||
|  |         lock_menu_callback(lock_menu, idx); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | DesktopLockMenuView* desktop_lock_menu_alloc() { | ||||||
|  |     DesktopLockMenuView* lock_menu = furi_alloc(sizeof(DesktopLockMenuView)); | ||||||
|  |     lock_menu->view = view_alloc(); | ||||||
|  |     view_allocate_model(lock_menu->view, ViewModelTypeLocking, sizeof(DesktopLockMenuViewModel)); | ||||||
|  |     view_set_context(lock_menu->view, lock_menu); | ||||||
|  |     view_set_draw_callback(lock_menu->view, (ViewDrawCallback)desktop_lock_menu_render); | ||||||
|  |     view_set_input_callback(lock_menu->view, desktop_lock_menu_input); | ||||||
|  | 
 | ||||||
|  |     return lock_menu; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_lock_menu_free(DesktopLockMenuView* lock_menu_view) { | ||||||
|  |     furi_assert(lock_menu_view); | ||||||
|  | 
 | ||||||
|  |     view_free(lock_menu_view->view); | ||||||
|  |     free(lock_menu_view); | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								applications/desktop/views/desktop_lock_menu.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								applications/desktop/views/desktop_lock_menu.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/gui_i.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <gui/canvas.h> | ||||||
|  | #include <gui/elements.h> | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     DesktopLockMenuEventLock, | ||||||
|  |     DesktopLockMenuEventUnlock, | ||||||
|  |     DesktopLockMenuEventExit, | ||||||
|  | } DesktopLockMenuEvent; | ||||||
|  | 
 | ||||||
|  | typedef struct DesktopLockMenuView DesktopLockMenuView; | ||||||
|  | 
 | ||||||
|  | typedef void (*DesktopLockMenuViewCallback)(DesktopLockMenuEvent event, void* context); | ||||||
|  | 
 | ||||||
|  | struct DesktopLockMenuView { | ||||||
|  |     View* view; | ||||||
|  |     DesktopLockMenuViewCallback callback; | ||||||
|  |     void* context; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t idx; | ||||||
|  |     uint8_t hint_timeout; | ||||||
|  |     bool locked; | ||||||
|  | } DesktopLockMenuViewModel; | ||||||
|  | 
 | ||||||
|  | void desktop_lock_menu_set_callback( | ||||||
|  |     DesktopLockMenuView* lock_menu, | ||||||
|  |     DesktopLockMenuViewCallback callback, | ||||||
|  |     void* context); | ||||||
|  | 
 | ||||||
|  | View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu); | ||||||
|  | void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu); | ||||||
|  | DesktopLockMenuView* desktop_lock_menu_alloc(); | ||||||
|  | void desktop_lock_menu_free(DesktopLockMenuView* lock_menu); | ||||||
							
								
								
									
										171
									
								
								applications/desktop/views/desktop_locked.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								applications/desktop/views/desktop_locked.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,171 @@ | |||||||
|  | #include <furi.h> | ||||||
|  | #include "../desktop_i.h" | ||||||
|  | #include "desktop_locked.h" | ||||||
|  | 
 | ||||||
|  | static const Icon* idle_scenes[] = {&A_Wink_128x64, &A_WatchingTV_128x64}; | ||||||
|  | 
 | ||||||
|  | void desktop_locked_set_callback( | ||||||
|  |     DesktopLockedView* locked_view, | ||||||
|  |     DesktopLockedViewCallback callback, | ||||||
|  |     void* context) { | ||||||
|  |     furi_assert(locked_view); | ||||||
|  |     furi_assert(callback); | ||||||
|  |     locked_view->callback = callback; | ||||||
|  |     locked_view->context = context; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void locked_view_timer_callback(void* context) { | ||||||
|  |     DesktopLockedView* locked_view = context; | ||||||
|  |     locked_view->callback(DesktopLockedEventUpdate, locked_view->context); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // temporary locked screen animation managment
 | ||||||
|  | static void | ||||||
|  |     desktop_scene_handler_set_scene(DesktopLockedView* locked_view, const Icon* icon_data) { | ||||||
|  |     with_view_model( | ||||||
|  |         locked_view->view, (DesktopLockedViewModel * model) { | ||||||
|  |             if(model->animation) icon_animation_free(model->animation); | ||||||
|  |             model->animation = icon_animation_alloc(icon_data); | ||||||
|  |             icon_animation_start(model->animation); | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view) { | ||||||
|  |     with_view_model( | ||||||
|  |         locked_view->view, (DesktopLockedViewModel * model) { | ||||||
|  |             model->hint_timeout = HINT_TIMEOUT_H; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_locked_reset_door_pos(DesktopLockedView* locked_view) { | ||||||
|  |     with_view_model( | ||||||
|  |         locked_view->view, (DesktopLockedViewModel * model) { | ||||||
|  |             model->animation_seq_end = false; | ||||||
|  |             model->door_left_x = -57; | ||||||
|  |             model->door_right_x = 115; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_locked_manage_redraw(DesktopLockedView* locked_view) { | ||||||
|  |     bool animation_seq_end; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         locked_view->view, (DesktopLockedViewModel * model) { | ||||||
|  |             model->animation_seq_end = !model->door_left_x; | ||||||
|  |             animation_seq_end = model->animation_seq_end; | ||||||
|  | 
 | ||||||
|  |             if(!model->animation_seq_end) { | ||||||
|  |                 model->door_left_x = CLAMP(model->door_left_x + 5, 0, -57); | ||||||
|  |                 model->door_right_x = CLAMP(model->door_right_x - 5, 115, 60); | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     if(animation_seq_end) { | ||||||
|  |         osTimerStop(locked_view->timer); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_locked_reset_counter(DesktopLockedView* locked_view) { | ||||||
|  |     locked_view->lock_count = 0; | ||||||
|  |     locked_view->lock_lastpress = 0; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         locked_view->view, (DesktopLockedViewModel * model) { | ||||||
|  |             model->hint_timeout = 0; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_locked_render(Canvas* canvas, void* model) { | ||||||
|  |     DesktopLockedViewModel* m = model; | ||||||
|  | 
 | ||||||
|  |     canvas_clear(canvas); | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  | 
 | ||||||
|  |     if(!m->animation_seq_end) { | ||||||
|  |         canvas_draw_icon(canvas, m->door_left_x, 0, &I_DoorLeft_70x55); | ||||||
|  |         canvas_draw_icon(canvas, m->door_right_x, 0, &I_DoorRight_70x55); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(m->animation && m->animation_seq_end) { | ||||||
|  |         canvas_draw_icon_animation(canvas, 0, -3, m->animation); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(m->hint_timeout) { | ||||||
|  |         m->hint_timeout--; | ||||||
|  | 
 | ||||||
|  |         if(!m->animation_seq_end) { | ||||||
|  |             canvas_set_font(canvas, FontPrimary); | ||||||
|  |             elements_multiline_text_framed(canvas, 42, 30, "Locked"); | ||||||
|  |         } else { | ||||||
|  |             canvas_set_font(canvas, FontSecondary); | ||||||
|  |             canvas_draw_icon(canvas, 13, 5, &I_LockPopup_100x49); | ||||||
|  |             elements_multiline_text(canvas, 65, 20, "To unlock\npress:"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | View* desktop_locked_get_view(DesktopLockedView* locked_view) { | ||||||
|  |     furi_assert(locked_view); | ||||||
|  |     return locked_view->view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_locked_input(InputEvent* event, void* context) { | ||||||
|  |     furi_assert(event); | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     DesktopLockedView* locked_view = context; | ||||||
|  |     if(event->type == InputTypeShort) { | ||||||
|  |         with_view_model( | ||||||
|  |             locked_view->view, (DesktopLockedViewModel * model) { | ||||||
|  |                 model->hint_timeout = HINT_TIMEOUT_L; | ||||||
|  |                 return true; | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |         if(event->key == InputKeyBack) { | ||||||
|  |             uint32_t press_time = HAL_GetTick(); | ||||||
|  | 
 | ||||||
|  |             // check if pressed sequentially
 | ||||||
|  |             if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) { | ||||||
|  |                 locked_view->lock_lastpress = press_time; | ||||||
|  |                 locked_view->lock_count = 0; | ||||||
|  |             } else if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) { | ||||||
|  |                 locked_view->lock_lastpress = press_time; | ||||||
|  |                 locked_view->lock_count++; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if(locked_view->lock_count == UNLOCK_CNT) { | ||||||
|  |                 locked_view->lock_count = 0; | ||||||
|  |                 locked_view->callback(DesktopLockedEventUnlock, locked_view->context); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     // All events consumed
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | DesktopLockedView* desktop_locked_alloc() { | ||||||
|  |     DesktopLockedView* locked_view = furi_alloc(sizeof(DesktopLockedView)); | ||||||
|  |     locked_view->view = view_alloc(); | ||||||
|  |     locked_view->timer = | ||||||
|  |         osTimerNew(locked_view_timer_callback, osTimerPeriodic, locked_view, NULL); | ||||||
|  | 
 | ||||||
|  |     view_allocate_model(locked_view->view, ViewModelTypeLocking, sizeof(DesktopLockedViewModel)); | ||||||
|  |     view_set_context(locked_view->view, locked_view); | ||||||
|  |     view_set_draw_callback(locked_view->view, (ViewDrawCallback)desktop_locked_render); | ||||||
|  |     view_set_input_callback(locked_view->view, desktop_locked_input); | ||||||
|  | 
 | ||||||
|  |     desktop_scene_handler_set_scene(locked_view, idle_scenes[random() % COUNT_OF(idle_scenes)]); | ||||||
|  |     return locked_view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_locked_free(DesktopLockedView* locked_view) { | ||||||
|  |     furi_assert(locked_view); | ||||||
|  |     osTimerDelete(locked_view->timer); | ||||||
|  |     view_free(locked_view->view); | ||||||
|  |     free(locked_view); | ||||||
|  | } | ||||||
							
								
								
									
										55
									
								
								applications/desktop/views/desktop_locked.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								applications/desktop/views/desktop_locked.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/gui_i.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <gui/canvas.h> | ||||||
|  | #include <gui/elements.h> | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | #define UNLOCK_RST_TIMEOUT 200 | ||||||
|  | #define UNLOCK_CNT 2 // 3 actually
 | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     DesktopLockedEventUnlock, | ||||||
|  |     DesktopLockedEventUpdate, | ||||||
|  | } DesktopLockedEvent; | ||||||
|  | 
 | ||||||
|  | typedef struct DesktopLockedView DesktopLockedView; | ||||||
|  | 
 | ||||||
|  | typedef void (*DesktopLockedViewCallback)(DesktopLockedEvent event, void* context); | ||||||
|  | 
 | ||||||
|  | struct DesktopLockedView { | ||||||
|  |     View* view; | ||||||
|  |     DesktopLockedViewCallback callback; | ||||||
|  |     void* context; | ||||||
|  | 
 | ||||||
|  |     osTimerId_t timer; | ||||||
|  |     uint8_t lock_count; | ||||||
|  |     uint32_t lock_lastpress; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     IconAnimation* animation; | ||||||
|  |     uint8_t scene_num; | ||||||
|  |     int8_t door_left_x; | ||||||
|  |     int8_t door_right_x; | ||||||
|  |     uint8_t hint_timeout; | ||||||
|  |     bool animation_seq_end; | ||||||
|  | 
 | ||||||
|  | } DesktopLockedViewModel; | ||||||
|  | 
 | ||||||
|  | void desktop_locked_set_callback( | ||||||
|  |     DesktopLockedView* locked_view, | ||||||
|  |     DesktopLockedViewCallback callback, | ||||||
|  |     void* context); | ||||||
|  | 
 | ||||||
|  | void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view); | ||||||
|  | void desktop_locked_reset_counter(DesktopLockedView* locked_view); | ||||||
|  | void desktop_locked_reset_door_pos(DesktopLockedView* locked_view); | ||||||
|  | void desktop_locked_manage_redraw(DesktopLockedView* locked_view); | ||||||
|  | 
 | ||||||
|  | View* desktop_locked_get_view(DesktopLockedView* locked_view); | ||||||
|  | DesktopLockedView* desktop_locked_alloc(); | ||||||
|  | void desktop_locked_free(DesktopLockedView* locked_view); | ||||||
|  | void desktop_main_unlocked(DesktopMainView* main_view); | ||||||
|  | void desktop_main_reset_hint(DesktopMainView* main_view); | ||||||
							
								
								
									
										116
									
								
								applications/desktop/views/desktop_main.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								applications/desktop/views/desktop_main.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,116 @@ | |||||||
|  | #include <furi.h> | ||||||
|  | #include "../desktop_i.h" | ||||||
|  | #include "desktop_main.h" | ||||||
|  | 
 | ||||||
|  | static const Icon* idle_scenes[] = {&A_Wink_128x64, &A_WatchingTV_128x64}; | ||||||
|  | 
 | ||||||
|  | void desktop_main_set_callback( | ||||||
|  |     DesktopMainView* main_view, | ||||||
|  |     DesktopMainViewCallback callback, | ||||||
|  |     void* context) { | ||||||
|  |     furi_assert(main_view); | ||||||
|  |     furi_assert(callback); | ||||||
|  |     main_view->callback = callback; | ||||||
|  |     main_view->context = context; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_main_reset_hint(DesktopMainView* main_view) { | ||||||
|  |     with_view_model( | ||||||
|  |         main_view->view, (DesktopMainViewModel * model) { | ||||||
|  |             model->hint_timeout = 0; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | // temporary main screen animation managment
 | ||||||
|  | void desktop_scene_handler_set_scene(DesktopMainView* main_view, const Icon* icon_data) { | ||||||
|  |     with_view_model( | ||||||
|  |         main_view->view, (DesktopMainViewModel * model) { | ||||||
|  |             if(model->animation) icon_animation_free(model->animation); | ||||||
|  |             model->animation = icon_animation_alloc(icon_data); | ||||||
|  |             icon_animation_start(model->animation); | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_handler_switch_scene(DesktopMainView* main_view) { | ||||||
|  |     with_view_model( | ||||||
|  |         main_view->view, (DesktopMainViewModel * model) { | ||||||
|  |             if(icon_animation_is_last_frame(model->animation)) { | ||||||
|  |                 if(model->animation) icon_animation_free(model->animation); | ||||||
|  |                 model->animation = icon_animation_alloc(idle_scenes[model->scene_num]); | ||||||
|  |                 icon_animation_start(model->animation); | ||||||
|  |                 model->scene_num = random() % COUNT_OF(idle_scenes); | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_main_render(Canvas* canvas, void* model) { | ||||||
|  |     canvas_clear(canvas); | ||||||
|  |     DesktopMainViewModel* m = model; | ||||||
|  | 
 | ||||||
|  |     if(m->animation) { | ||||||
|  |         canvas_draw_icon_animation(canvas, 0, -3, m->animation); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(m->unlocked && m->hint_timeout) { | ||||||
|  |         m->hint_timeout = CLAMP(m->hint_timeout - 1, 2, 0); | ||||||
|  |         canvas_set_font(canvas, FontPrimary); | ||||||
|  |         elements_multiline_text_framed(canvas, 42, 30, "Unlocked"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | View* desktop_main_get_view(DesktopMainView* main_view) { | ||||||
|  |     furi_assert(main_view); | ||||||
|  |     return main_view->view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_main_input(InputEvent* event, void* context) { | ||||||
|  |     furi_assert(event); | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     DesktopMainView* main_view = context; | ||||||
|  | 
 | ||||||
|  |     if(event->key == InputKeyOk && event->type == InputTypeShort) { | ||||||
|  |         main_view->callback(DesktopMainEventOpenMenu, main_view->context); | ||||||
|  |     } else if(event->key == InputKeyDown && event->type == InputTypeLong) { | ||||||
|  |         main_view->callback(DesktopMainEventOpenDebug, main_view->context); | ||||||
|  |     } else if(event->key == InputKeyUp && event->type == InputTypeShort) { | ||||||
|  |         main_view->callback(DesktopMainEventOpenLockMenu, main_view->context); | ||||||
|  |     } else if(event->key == InputKeyDown && event->type == InputTypeShort) { | ||||||
|  |         main_view->callback(DesktopMainEventOpenArchive, main_view->context); | ||||||
|  |     } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { | ||||||
|  |         main_view->callback(DesktopMainEventOpenFavorite, main_view->context); | ||||||
|  |     } | ||||||
|  |     desktop_main_reset_hint(main_view); | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | DesktopMainView* desktop_main_alloc() { | ||||||
|  |     DesktopMainView* main_view = furi_alloc(sizeof(DesktopMainView)); | ||||||
|  |     main_view->view = view_alloc(); | ||||||
|  |     view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(DesktopMainViewModel)); | ||||||
|  |     view_set_context(main_view->view, main_view); | ||||||
|  |     view_set_draw_callback(main_view->view, (ViewDrawCallback)desktop_main_render); | ||||||
|  |     view_set_input_callback(main_view->view, desktop_main_input); | ||||||
|  | 
 | ||||||
|  |     desktop_scene_handler_set_scene(main_view, idle_scenes[random() % COUNT_OF(idle_scenes)]); | ||||||
|  | 
 | ||||||
|  |     return main_view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_main_free(DesktopMainView* main_view) { | ||||||
|  |     furi_assert(main_view); | ||||||
|  |     view_free(main_view->view); | ||||||
|  |     free(main_view); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_main_unlocked(DesktopMainView* main_view) { | ||||||
|  |     with_view_model( | ||||||
|  |         main_view->view, (DesktopMainViewModel * model) { | ||||||
|  |             model->unlocked = true; | ||||||
|  |             model->hint_timeout = 2; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
							
								
								
									
										43
									
								
								applications/desktop/views/desktop_main.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								applications/desktop/views/desktop_main.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/gui_i.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <gui/canvas.h> | ||||||
|  | #include <gui/elements.h> | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     DesktopMainEventOpenMenu, | ||||||
|  |     DesktopMainEventOpenLockMenu, | ||||||
|  |     DesktopMainEventOpenDebug, | ||||||
|  |     DesktopMainEventUnlocked, | ||||||
|  |     DesktopMainEventOpenArchive, | ||||||
|  |     DesktopMainEventOpenFavorite, | ||||||
|  | } DesktopMainEvent; | ||||||
|  | 
 | ||||||
|  | typedef struct DesktopMainView DesktopMainView; | ||||||
|  | 
 | ||||||
|  | typedef void (*DesktopMainViewCallback)(DesktopMainEvent event, void* context); | ||||||
|  | 
 | ||||||
|  | struct DesktopMainView { | ||||||
|  |     View* view; | ||||||
|  |     DesktopMainViewCallback callback; | ||||||
|  |     void* context; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     IconAnimation* animation; | ||||||
|  |     uint8_t scene_num; | ||||||
|  |     uint8_t hint_timeout; | ||||||
|  |     bool unlocked; | ||||||
|  | } DesktopMainViewModel; | ||||||
|  | 
 | ||||||
|  | void desktop_main_set_callback( | ||||||
|  |     DesktopMainView* main_view, | ||||||
|  |     DesktopMainViewCallback callback, | ||||||
|  |     void* context); | ||||||
|  | 
 | ||||||
|  | View* desktop_main_get_view(DesktopMainView* main_view); | ||||||
|  | 
 | ||||||
|  | DesktopMainView* desktop_main_alloc(); | ||||||
|  | void desktop_main_free(DesktopMainView* main_view); | ||||||
| @ -1,8 +1,18 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #define API_LOCK_INIT_LOCKED() osSemaphoreNew(1, 0, NULL); | 
 | ||||||
|  | typedef osEventFlagsId_t FuriApiLock; | ||||||
|  | 
 | ||||||
|  | #define API_LOCK_EVENT (1U << 0) | ||||||
|  | 
 | ||||||
|  | #define API_LOCK_INIT_LOCKED() osEventFlagsNew(NULL); | ||||||
|  | 
 | ||||||
|  | #define API_LOCK_WAIT_UNTIL_UNLOCK(_lock) \ | ||||||
|  |     osEventFlagsWait(_lock, API_LOCK_EVENT, osFlagsWaitAny, osWaitForever); | ||||||
|  | 
 | ||||||
|  | #define API_LOCK_FREE(_lock) osEventFlagsDelete(_lock); | ||||||
|  | 
 | ||||||
|  | #define API_LOCK_UNLOCK(_lock) osEventFlagsSet(_lock, API_LOCK_EVENT); | ||||||
| 
 | 
 | ||||||
| #define API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(_lock) \ | #define API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(_lock) \ | ||||||
|     osSemaphoreAcquire(_lock, osWaitForever);      \ |     API_LOCK_WAIT_UNTIL_UNLOCK(_lock);             \ | ||||||
|     osSemaphoreDelete(_lock); |     API_LOCK_FREE(_lock); | ||||||
| 
 |  | ||||||
| #define API_LOCK_UNLOCK(_lock) osSemaphoreRelease(_lock); |  | ||||||
|  | |||||||
| @ -10,8 +10,8 @@ bool dialog_file_select_show( | |||||||
|     char* result, |     char* result, | ||||||
|     uint8_t result_size, |     uint8_t result_size, | ||||||
|     const char* preselected_filename) { |     const char* preselected_filename) { | ||||||
|     osSemaphoreId_t semaphore = API_LOCK_INIT_LOCKED(); |     FuriApiLock lock = API_LOCK_INIT_LOCKED(); | ||||||
|     furi_check(semaphore != NULL); |     furi_check(lock != NULL); | ||||||
| 
 | 
 | ||||||
|     DialogsAppData data = { |     DialogsAppData data = { | ||||||
|         .file_select = { |         .file_select = { | ||||||
| @ -24,14 +24,14 @@ bool dialog_file_select_show( | |||||||
| 
 | 
 | ||||||
|     DialogsAppReturn return_data; |     DialogsAppReturn return_data; | ||||||
|     DialogsAppMessage message = { |     DialogsAppMessage message = { | ||||||
|         .semaphore = semaphore, |         .lock = lock, | ||||||
|         .command = DialogsAppCommandFileOpen, |         .command = DialogsAppCommandFileOpen, | ||||||
|         .data = &data, |         .data = &data, | ||||||
|         .return_data = &return_data, |         .return_data = &return_data, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     furi_check(osMessageQueuePut(context->message_queue, &message, 0, osWaitForever) == osOK); |     furi_check(osMessageQueuePut(context->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|     API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(semaphore); |     API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(lock); | ||||||
| 
 | 
 | ||||||
|     return return_data.bool_value; |     return return_data.bool_value; | ||||||
| } | } | ||||||
| @ -39,8 +39,8 @@ bool dialog_file_select_show( | |||||||
| /****************** Message ******************/ | /****************** Message ******************/ | ||||||
| 
 | 
 | ||||||
| DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage* dialog_message) { | DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage* dialog_message) { | ||||||
|     osSemaphoreId_t semaphore = API_LOCK_INIT_LOCKED(); |     FuriApiLock lock = API_LOCK_INIT_LOCKED(); | ||||||
|     furi_check(semaphore != NULL); |     furi_check(lock != NULL); | ||||||
| 
 | 
 | ||||||
|     DialogsAppData data = { |     DialogsAppData data = { | ||||||
|         .dialog = { |         .dialog = { | ||||||
| @ -49,14 +49,14 @@ DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage | |||||||
| 
 | 
 | ||||||
|     DialogsAppReturn return_data; |     DialogsAppReturn return_data; | ||||||
|     DialogsAppMessage message = { |     DialogsAppMessage message = { | ||||||
|         .semaphore = semaphore, |         .lock = lock, | ||||||
|         .command = DialogsAppCommandDialog, |         .command = DialogsAppCommandDialog, | ||||||
|         .data = &data, |         .data = &data, | ||||||
|         .return_data = &return_data, |         .return_data = &return_data, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     furi_check(osMessageQueuePut(context->message_queue, &message, 0, osWaitForever) == osOK); |     furi_check(osMessageQueuePut(context->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|     API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(semaphore); |     API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(lock); | ||||||
| 
 | 
 | ||||||
|     return return_data.dialog_value; |     return return_data.dialog_value; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include "dialogs-i.h" | #include "dialogs-i.h" | ||||||
|  | #include "dialogs-api-lock.h" | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| @ -34,7 +35,7 @@ typedef enum { | |||||||
| } DialogsAppCommand; | } DialogsAppCommand; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     osSemaphoreId_t semaphore; |     FuriApiLock lock; | ||||||
|     DialogsAppCommand command; |     DialogsAppCommand command; | ||||||
|     DialogsAppData* data; |     DialogsAppData* data; | ||||||
|     DialogsAppReturn* return_data; |     DialogsAppReturn* return_data; | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| #include <gui/modules/file_select.h> | #include <gui/modules/file_select.h> | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     osSemaphoreId_t semaphore; |     FuriApiLock lock; | ||||||
|     bool result; |     bool result; | ||||||
| } DialogsAppFileSelectContext; | } DialogsAppFileSelectContext; | ||||||
| 
 | 
 | ||||||
| @ -11,14 +11,14 @@ static void dialogs_app_file_select_back_callback(void* context) { | |||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     DialogsAppFileSelectContext* file_select_context = context; |     DialogsAppFileSelectContext* file_select_context = context; | ||||||
|     file_select_context->result = false; |     file_select_context->result = false; | ||||||
|     API_LOCK_UNLOCK(file_select_context->semaphore); |     API_LOCK_UNLOCK(file_select_context->lock); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void dialogs_app_file_select_callback(bool result, void* context) { | static void dialogs_app_file_select_callback(bool result, void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     DialogsAppFileSelectContext* file_select_context = context; |     DialogsAppFileSelectContext* file_select_context = context; | ||||||
|     file_select_context->result = result; |     file_select_context->result = result; | ||||||
|     API_LOCK_UNLOCK(file_select_context->semaphore); |     API_LOCK_UNLOCK(file_select_context->lock); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data) { | bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data) { | ||||||
| @ -27,7 +27,7 @@ bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelec | |||||||
| 
 | 
 | ||||||
|     DialogsAppFileSelectContext* file_select_context = |     DialogsAppFileSelectContext* file_select_context = | ||||||
|         furi_alloc(sizeof(DialogsAppFileSelectContext)); |         furi_alloc(sizeof(DialogsAppFileSelectContext)); | ||||||
|     file_select_context->semaphore = API_LOCK_INIT_LOCKED(); |     file_select_context->lock = API_LOCK_INIT_LOCKED(); | ||||||
| 
 | 
 | ||||||
|     ViewHolder* view_holder = view_holder_alloc(); |     ViewHolder* view_holder = view_holder_alloc(); | ||||||
|     view_holder_attach_to_gui(view_holder, gui); |     view_holder_attach_to_gui(view_holder, gui); | ||||||
| @ -45,14 +45,15 @@ bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelec | |||||||
| 
 | 
 | ||||||
|     view_holder_set_view(view_holder, file_select_get_view(file_select)); |     view_holder_set_view(view_holder, file_select_get_view(file_select)); | ||||||
|     view_holder_start(view_holder); |     view_holder_start(view_holder); | ||||||
|     API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(file_select_context->semaphore); |     API_LOCK_WAIT_UNTIL_UNLOCK(file_select_context->lock); | ||||||
| 
 | 
 | ||||||
|     ret = file_select_context->result; |     ret = file_select_context->result; | ||||||
| 
 | 
 | ||||||
|     free(file_select_context); |  | ||||||
|     view_holder_stop(view_holder); |     view_holder_stop(view_holder); | ||||||
|     view_holder_free(view_holder); |     view_holder_free(view_holder); | ||||||
|     file_select_free(file_select); |     file_select_free(file_select); | ||||||
|  |     API_LOCK_FREE(file_select_context->lock); | ||||||
|  |     free(file_select_context); | ||||||
|     furi_record_close("gui"); |     furi_record_close("gui"); | ||||||
| 
 | 
 | ||||||
|     return ret; |     return ret; | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| #include <gui/modules/dialog_ex.h> | #include <gui/modules/dialog_ex.h> | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     osSemaphoreId_t semaphore; |     FuriApiLock lock; | ||||||
|     DialogMessageButton result; |     DialogMessageButton result; | ||||||
| } DialogsAppMessageContext; | } DialogsAppMessageContext; | ||||||
| 
 | 
 | ||||||
| @ -30,7 +30,7 @@ static void dialogs_app_message_back_callback(void* context) { | |||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     DialogsAppMessageContext* message_context = context; |     DialogsAppMessageContext* message_context = context; | ||||||
|     message_context->result = DialogMessageButtonBack; |     message_context->result = DialogMessageButtonBack; | ||||||
|     API_LOCK_UNLOCK(message_context->semaphore); |     API_LOCK_UNLOCK(message_context->lock); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void dialogs_app_message_callback(DialogExResult result, void* context) { | static void dialogs_app_message_callback(DialogExResult result, void* context) { | ||||||
| @ -47,7 +47,7 @@ static void dialogs_app_message_callback(DialogExResult result, void* context) { | |||||||
|         message_context->result = DialogMessageButtonCenter; |         message_context->result = DialogMessageButtonCenter; | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|     API_LOCK_UNLOCK(message_context->semaphore); |     API_LOCK_UNLOCK(message_context->lock); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDataDialog* data) { | DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDataDialog* data) { | ||||||
| @ -55,7 +55,7 @@ DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDa | |||||||
|     Gui* gui = furi_record_open("gui"); |     Gui* gui = furi_record_open("gui"); | ||||||
|     const DialogMessage* message = data->message; |     const DialogMessage* message = data->message; | ||||||
|     DialogsAppMessageContext* message_context = furi_alloc(sizeof(DialogsAppMessageContext)); |     DialogsAppMessageContext* message_context = furi_alloc(sizeof(DialogsAppMessageContext)); | ||||||
|     message_context->semaphore = API_LOCK_INIT_LOCKED(); |     message_context->lock = API_LOCK_INIT_LOCKED(); | ||||||
| 
 | 
 | ||||||
|     ViewHolder* view_holder = view_holder_alloc(); |     ViewHolder* view_holder = view_holder_alloc(); | ||||||
|     view_holder_attach_to_gui(view_holder, gui); |     view_holder_attach_to_gui(view_holder, gui); | ||||||
| @ -85,14 +85,15 @@ DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDa | |||||||
| 
 | 
 | ||||||
|     view_holder_set_view(view_holder, dialog_ex_get_view(dialog_ex)); |     view_holder_set_view(view_holder, dialog_ex_get_view(dialog_ex)); | ||||||
|     view_holder_start(view_holder); |     view_holder_start(view_holder); | ||||||
|     API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(message_context->semaphore); |     API_LOCK_WAIT_UNTIL_UNLOCK(message_context->lock); | ||||||
| 
 | 
 | ||||||
|     ret = message_context->result; |     ret = message_context->result; | ||||||
| 
 | 
 | ||||||
|     free(message_context); |  | ||||||
|     view_holder_stop(view_holder); |     view_holder_stop(view_holder); | ||||||
|     view_holder_free(view_holder); |     view_holder_free(view_holder); | ||||||
|     dialog_ex_free(dialog_ex); |     dialog_ex_free(dialog_ex); | ||||||
|  |     API_LOCK_FREE(message_context->lock); | ||||||
|  |     free(message_context); | ||||||
|     furi_record_close("gui"); |     furi_record_close("gui"); | ||||||
| 
 | 
 | ||||||
|     return ret; |     return ret; | ||||||
|  | |||||||
| @ -21,7 +21,7 @@ static void dialogs_app_process_message(DialogsApp* app, DialogsAppMessage* mess | |||||||
|             dialogs_app_process_module_message(&message->data->dialog); |             dialogs_app_process_module_message(&message->data->dialog); | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|     API_LOCK_UNLOCK(message->semaphore); |     API_LOCK_UNLOCK(message->lock); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int32_t dialogs_srv(void* p) { | int32_t dialogs_srv(void* p) { | ||||||
|  | |||||||
| @ -1,389 +1,9 @@ | |||||||
| #include "dolphin_i.h" | #include "dolphin_i.h" | ||||||
| #include <stdlib.h> | #include <furi.h> | ||||||
| #include "applications.h" |  | ||||||
| 
 | 
 | ||||||
| const Icon* idle_scenes[] = {&A_Wink_128x64, &A_WatchingTV_128x64}; | bool dolphin_load(Dolphin* dolphin) { | ||||||
| 
 |  | ||||||
| static void dolphin_switch_to_app(Dolphin* dolphin, const FlipperApplication* flipper_app) { |  | ||||||
|     furi_assert(dolphin); |     furi_assert(dolphin); | ||||||
|     furi_assert(flipper_app); |     return dolphin_state_load(dolphin->state); | ||||||
|     furi_assert(flipper_app->app); |  | ||||||
|     furi_assert(flipper_app->name); |  | ||||||
| 
 |  | ||||||
|     if(furi_thread_get_state(dolphin->scene_thread) != FuriThreadStateStopped) { |  | ||||||
|         FURI_LOG_E("Dolphin", "Thread is already running"); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     furi_thread_set_name(dolphin->scene_thread, flipper_app->name); |  | ||||||
|     furi_thread_set_stack_size(dolphin->scene_thread, flipper_app->stack_size); |  | ||||||
|     furi_thread_set_callback(dolphin->scene_thread, flipper_app->app); |  | ||||||
| 
 |  | ||||||
|     furi_thread_start(dolphin->scene_thread); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // temporary main screen animation managment
 |  | ||||||
| void dolphin_scene_handler_set_scene(Dolphin* dolphin, const Icon* icon_data) { |  | ||||||
|     with_view_model( |  | ||||||
|         dolphin->idle_view_main, (DolphinViewMainModel * model) { |  | ||||||
|             if(model->animation) icon_animation_free(model->animation); |  | ||||||
|             model->animation = icon_animation_alloc(icon_data); |  | ||||||
|             icon_animation_start(model->animation); |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void dolphin_scene_handler_switch_scene(Dolphin* dolphin) { |  | ||||||
|     with_view_model( |  | ||||||
|         dolphin->idle_view_main, (DolphinViewMainModel * model) { |  | ||||||
|             if(icon_animation_is_last_frame(model->animation)) { |  | ||||||
|                 if(model->animation) icon_animation_free(model->animation); |  | ||||||
|                 model->animation = icon_animation_alloc(idle_scenes[model->scene_num]); |  | ||||||
|                 icon_animation_start(model->animation); |  | ||||||
|                 model->scene_num = random() % COUNT_OF(idle_scenes); |  | ||||||
|             } |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool dolphin_view_first_start_input(InputEvent* event, void* context) { |  | ||||||
|     furi_assert(event); |  | ||||||
|     furi_assert(context); |  | ||||||
|     Dolphin* dolphin = context; |  | ||||||
|     if(event->type == InputTypeShort) { |  | ||||||
|         DolphinViewFirstStartModel* model = view_get_model(dolphin->idle_view_first_start); |  | ||||||
|         if(event->key == InputKeyLeft) { |  | ||||||
|             if(model->page > 0) model->page--; |  | ||||||
|         } else if(event->key == InputKeyRight) { |  | ||||||
|             uint32_t page = ++model->page; |  | ||||||
|             if(page > 8) { |  | ||||||
|                 dolphin_save(dolphin); |  | ||||||
|                 view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         view_commit_model(dolphin->idle_view_first_start, true); |  | ||||||
|     } |  | ||||||
|     // All evennts cosumed
 |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void dolphin_lock_handler(InputEvent* event, Dolphin* dolphin) { |  | ||||||
|     furi_assert(event); |  | ||||||
|     furi_assert(dolphin); |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         dolphin->idle_view_main, (DolphinViewMainModel * model) { |  | ||||||
|             model->hint_timeout = HINT_TIMEOUT_L; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     if(event->key == InputKeyBack && event->type == InputTypeShort) { |  | ||||||
|         uint32_t press_time = HAL_GetTick(); |  | ||||||
| 
 |  | ||||||
|         // check if pressed sequentially
 |  | ||||||
|         if(press_time - dolphin->lock_lastpress > UNLOCK_RST_TIMEOUT) { |  | ||||||
|             dolphin->lock_lastpress = press_time; |  | ||||||
|             dolphin->lock_count = 0; |  | ||||||
|         } else if(press_time - dolphin->lock_lastpress < UNLOCK_RST_TIMEOUT) { |  | ||||||
|             dolphin->lock_lastpress = press_time; |  | ||||||
|             dolphin->lock_count++; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(dolphin->lock_count == 2) { |  | ||||||
|             dolphin->locked = false; |  | ||||||
|             dolphin->lock_count = 0; |  | ||||||
| 
 |  | ||||||
|             with_view_model( |  | ||||||
|                 dolphin->view_lockmenu, (DolphinViewLockMenuModel * model) { |  | ||||||
|                     model->locked = false; |  | ||||||
|                     model->door_left_x = -57; // move doors to default pos
 |  | ||||||
|                     model->door_right_x = 115; |  | ||||||
|                     return true; |  | ||||||
|                 }); |  | ||||||
| 
 |  | ||||||
|             with_view_model( |  | ||||||
|                 dolphin->idle_view_main, (DolphinViewMainModel * model) { |  | ||||||
|                     model->hint_timeout = HINT_TIMEOUT_L; // "unlocked" hint timeout
 |  | ||||||
|                     model->locked = false; |  | ||||||
|                     return true; |  | ||||||
|                 }); |  | ||||||
| 
 |  | ||||||
|             view_port_enabled_set(dolphin->lock_viewport, false); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool dolphin_view_idle_main_input(InputEvent* event, void* context) { |  | ||||||
|     furi_assert(event); |  | ||||||
|     furi_assert(context); |  | ||||||
|     Dolphin* dolphin = context; |  | ||||||
|     // unlocked
 |  | ||||||
|     if(!dolphin->locked) { |  | ||||||
|         if(event->key == InputKeyOk && event->type == InputTypeShort) { |  | ||||||
|             with_value_mutex( |  | ||||||
|                 dolphin->menu_vm, (Menu * menu) { menu_ok(menu); }); |  | ||||||
|         } else if(event->key == InputKeyUp && event->type == InputTypeShort) { |  | ||||||
|             osTimerStart(dolphin->timeout_timer, 64); |  | ||||||
|             view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewLockMenu); |  | ||||||
|         } else if(event->key == InputKeyDown && event->type == InputTypeShort) { |  | ||||||
|             dolphin_switch_to_app(dolphin, &FLIPPER_ARCHIVE); |  | ||||||
|         } else if(event->key == InputKeyDown && event->type == InputTypeLong) { |  | ||||||
|             view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewStats); |  | ||||||
|         } else if(event->key == InputKeyBack && event->type == InputTypeShort) { |  | ||||||
|             view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         with_view_model( |  | ||||||
|             dolphin->idle_view_main, (DolphinViewMainModel * model) { |  | ||||||
|                 model->hint_timeout = 0; // clear hint timeout
 |  | ||||||
|                 return true; |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|     } else { |  | ||||||
|         // locked
 |  | ||||||
| 
 |  | ||||||
|         dolphin_lock_handler(event, dolphin); |  | ||||||
|         dolphin_scene_handler_switch_scene(dolphin); |  | ||||||
|     } |  | ||||||
|     // All events consumed
 |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void lock_menu_refresh_handler(void* p) { |  | ||||||
|     osMessageQueueId_t event_queue = p; |  | ||||||
|     DolphinEvent event; |  | ||||||
|     event.type = DolphinEventTypeTick; |  | ||||||
|     // Some tick events may lost and we don't care.
 |  | ||||||
|     osMessageQueuePut(event_queue, &event, 0, 0); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void lock_menu_callback(void* context, uint8_t index) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     Dolphin* dolphin = context; |  | ||||||
|     switch(index) { |  | ||||||
|     // lock
 |  | ||||||
|     case 0: |  | ||||||
|         dolphin->locked = true; |  | ||||||
| 
 |  | ||||||
|         with_view_model( |  | ||||||
|             dolphin->view_lockmenu, (DolphinViewLockMenuModel * model) { |  | ||||||
|                 model->locked = true; |  | ||||||
|                 model->exit_timeout = HINT_TIMEOUT_H; |  | ||||||
|                 return true; |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|         with_view_model( |  | ||||||
|             dolphin->idle_view_main, (DolphinViewMainModel * model) { |  | ||||||
|                 model->locked = true; |  | ||||||
|                 return true; |  | ||||||
|             }); |  | ||||||
|         break; |  | ||||||
| 
 |  | ||||||
|     default: |  | ||||||
|         // wip message
 |  | ||||||
|         with_view_model( |  | ||||||
|             dolphin->view_lockmenu, (DolphinViewLockMenuModel * model) { |  | ||||||
|                 model->hint_timeout = HINT_TIMEOUT_H; |  | ||||||
|                 return true; |  | ||||||
|             }); |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void lock_icon_callback(Canvas* canvas, void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     Dolphin* dolphin = context; |  | ||||||
|     canvas_draw_icon_animation(canvas, 0, 0, dolphin->lock_icon); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool dolphin_view_lockmenu_input(InputEvent* event, void* context) { |  | ||||||
|     furi_assert(event); |  | ||||||
|     furi_assert(context); |  | ||||||
|     Dolphin* dolphin = context; |  | ||||||
| 
 |  | ||||||
|     if(event->type != InputTypeShort) return false; |  | ||||||
| 
 |  | ||||||
|     DolphinViewLockMenuModel* model = view_get_model(dolphin->view_lockmenu); |  | ||||||
| 
 |  | ||||||
|     model->hint_timeout = 0; // clear hint timeout
 |  | ||||||
| 
 |  | ||||||
|     if(event->key == InputKeyUp) { |  | ||||||
|         model->idx = CLAMP(model->idx - 1, 2, 0); |  | ||||||
|     } else if(event->key == InputKeyDown) { |  | ||||||
|         model->idx = CLAMP(model->idx + 1, 2, 0); |  | ||||||
|     } else if(event->key == InputKeyOk) { |  | ||||||
|         lock_menu_callback(context, model->idx); |  | ||||||
|     } else if(event->key == InputKeyBack) { |  | ||||||
|         model->idx = 0; |  | ||||||
|         view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain); |  | ||||||
| 
 |  | ||||||
|         if(random() % 100 > 50) |  | ||||||
|             dolphin_scene_handler_set_scene( |  | ||||||
|                 dolphin, idle_scenes[random() % COUNT_OF(idle_scenes)]); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     view_commit_model(dolphin->view_lockmenu, true); |  | ||||||
| 
 |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool dolphin_view_idle_down_input(InputEvent* event, void* context) { |  | ||||||
|     furi_assert(event); |  | ||||||
|     furi_assert(context); |  | ||||||
|     Dolphin* dolphin = context; |  | ||||||
|     DolphinViewStatsScreens current; |  | ||||||
| 
 |  | ||||||
|     if(event->type != InputTypeShort) return false; |  | ||||||
| 
 |  | ||||||
|     DolphinViewStatsModel* model = view_get_model(dolphin->idle_view_dolphin_stats); |  | ||||||
| 
 |  | ||||||
|     current = model->screen; |  | ||||||
| 
 |  | ||||||
|     if(event->key == InputKeyDown) { |  | ||||||
|         model->screen = (model->screen + 1) % DolphinViewStatsTotalCount; |  | ||||||
|     } else if(event->key == InputKeyUp) { |  | ||||||
|         model->screen = |  | ||||||
|             ((model->screen - 1) + DolphinViewStatsTotalCount) % DolphinViewStatsTotalCount; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     view_commit_model(dolphin->idle_view_dolphin_stats, true); |  | ||||||
| 
 |  | ||||||
|     if(current == DolphinViewStatsMeta) { |  | ||||||
|         if(event->key == InputKeyLeft) { |  | ||||||
|             dolphin_deed(dolphin, DolphinDeedWrong); |  | ||||||
|         } else if(event->key == InputKeyRight) { |  | ||||||
|             dolphin_deed(dolphin, DolphinDeedIButtonRead); |  | ||||||
|         } else if(event->key == InputKeyOk) { |  | ||||||
|             dolphin_save(dolphin); |  | ||||||
|         } else { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(event->key == InputKeyBack) { |  | ||||||
|         view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| Dolphin* dolphin_alloc() { |  | ||||||
|     Dolphin* dolphin = furi_alloc(sizeof(Dolphin)); |  | ||||||
|     // Message queue
 |  | ||||||
|     dolphin->event_queue = osMessageQueueNew(8, sizeof(DolphinEvent), NULL); |  | ||||||
|     furi_check(dolphin->event_queue); |  | ||||||
|     // State
 |  | ||||||
|     dolphin->state = dolphin_state_alloc(); |  | ||||||
|     // Menu
 |  | ||||||
|     dolphin->menu_vm = furi_record_open("menu"); |  | ||||||
|     // Scene thread
 |  | ||||||
|     dolphin->scene_thread = furi_thread_alloc(); |  | ||||||
|     // GUI
 |  | ||||||
|     dolphin->gui = furi_record_open("gui"); |  | ||||||
|     // Dispatcher
 |  | ||||||
|     dolphin->idle_view_dispatcher = view_dispatcher_alloc(); |  | ||||||
| 
 |  | ||||||
|     // First start View
 |  | ||||||
|     dolphin->idle_view_first_start = view_alloc(); |  | ||||||
|     view_allocate_model( |  | ||||||
|         dolphin->idle_view_first_start, ViewModelTypeLockFree, sizeof(DolphinViewFirstStartModel)); |  | ||||||
|     view_set_context(dolphin->idle_view_first_start, dolphin); |  | ||||||
|     view_set_draw_callback(dolphin->idle_view_first_start, dolphin_view_first_start_draw); |  | ||||||
|     view_set_input_callback(dolphin->idle_view_first_start, dolphin_view_first_start_input); |  | ||||||
|     view_dispatcher_add_view( |  | ||||||
|         dolphin->idle_view_dispatcher, DolphinViewFirstStart, dolphin->idle_view_first_start); |  | ||||||
| 
 |  | ||||||
|     // Main Idle View
 |  | ||||||
|     dolphin->idle_view_main = view_alloc(); |  | ||||||
|     view_set_context(dolphin->idle_view_main, dolphin); |  | ||||||
|     view_allocate_model( |  | ||||||
|         dolphin->idle_view_main, ViewModelTypeLockFree, sizeof(DolphinViewMainModel)); |  | ||||||
| 
 |  | ||||||
|     view_set_draw_callback(dolphin->idle_view_main, dolphin_view_idle_main_draw); |  | ||||||
|     view_set_input_callback(dolphin->idle_view_main, dolphin_view_idle_main_input); |  | ||||||
|     view_dispatcher_add_view( |  | ||||||
|         dolphin->idle_view_dispatcher, DolphinViewIdleMain, dolphin->idle_view_main); |  | ||||||
| 
 |  | ||||||
|     // Lock Menu View
 |  | ||||||
|     dolphin->view_lockmenu = view_alloc(); |  | ||||||
|     view_set_context(dolphin->view_lockmenu, dolphin); |  | ||||||
|     view_allocate_model( |  | ||||||
|         dolphin->view_lockmenu, ViewModelTypeLockFree, sizeof(DolphinViewLockMenuModel)); |  | ||||||
|     view_set_draw_callback(dolphin->view_lockmenu, dolphin_view_lockmenu_draw); |  | ||||||
|     view_set_input_callback(dolphin->view_lockmenu, dolphin_view_lockmenu_input); |  | ||||||
|     view_set_previous_callback(dolphin->view_lockmenu, dolphin_view_idle_back); |  | ||||||
|     view_dispatcher_add_view( |  | ||||||
|         dolphin->idle_view_dispatcher, DolphinViewLockMenu, dolphin->view_lockmenu); |  | ||||||
| 
 |  | ||||||
|     // default doors xpos
 |  | ||||||
|     with_view_model( |  | ||||||
|         dolphin->view_lockmenu, (DolphinViewLockMenuModel * model) { |  | ||||||
|             model->door_left_x = -57; // defaults
 |  | ||||||
|             model->door_right_x = 115; // defaults
 |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     dolphin->timeout_timer = |  | ||||||
|         osTimerNew(lock_menu_refresh_handler, osTimerPeriodic, dolphin->event_queue, NULL); |  | ||||||
| 
 |  | ||||||
|     // Stats Idle View
 |  | ||||||
|     dolphin->idle_view_dolphin_stats = view_alloc(); |  | ||||||
|     view_set_context(dolphin->idle_view_dolphin_stats, dolphin); |  | ||||||
|     view_allocate_model( |  | ||||||
|         dolphin->idle_view_dolphin_stats, ViewModelTypeLockFree, sizeof(DolphinViewStatsModel)); |  | ||||||
|     view_set_draw_callback(dolphin->idle_view_dolphin_stats, dolphin_view_idle_down_draw); |  | ||||||
|     view_set_input_callback(dolphin->idle_view_dolphin_stats, dolphin_view_idle_down_input); |  | ||||||
|     view_set_previous_callback(dolphin->idle_view_dolphin_stats, dolphin_view_idle_back); |  | ||||||
|     view_dispatcher_add_view( |  | ||||||
|         dolphin->idle_view_dispatcher, DolphinViewStats, dolphin->idle_view_dolphin_stats); |  | ||||||
|     // HW Mismatch
 |  | ||||||
|     dolphin->view_hw_mismatch = view_alloc(); |  | ||||||
|     view_set_draw_callback(dolphin->view_hw_mismatch, dolphin_view_hw_mismatch_draw); |  | ||||||
|     view_set_previous_callback(dolphin->view_hw_mismatch, dolphin_view_idle_back); |  | ||||||
|     view_dispatcher_add_view( |  | ||||||
|         dolphin->idle_view_dispatcher, DolphinViewHwMismatch, dolphin->view_hw_mismatch); |  | ||||||
| 
 |  | ||||||
|     // Lock icon
 |  | ||||||
|     dolphin->lock_icon = icon_animation_alloc(&I_Lock_8x8); |  | ||||||
|     dolphin->lock_viewport = view_port_alloc(); |  | ||||||
|     view_port_set_width(dolphin->lock_viewport, icon_animation_get_width(dolphin->lock_icon)); |  | ||||||
|     view_port_draw_callback_set(dolphin->lock_viewport, lock_icon_callback, dolphin); |  | ||||||
|     view_port_enabled_set(dolphin->lock_viewport, false); |  | ||||||
| 
 |  | ||||||
|     // Main screen animation
 |  | ||||||
|     dolphin_scene_handler_set_scene(dolphin, idle_scenes[random() % COUNT_OF(idle_scenes)]); |  | ||||||
| 
 |  | ||||||
|     view_dispatcher_attach_to_gui( |  | ||||||
|         dolphin->idle_view_dispatcher, dolphin->gui, ViewDispatcherTypeWindow); |  | ||||||
|     gui_add_view_port(dolphin->gui, dolphin->lock_viewport, GuiLayerStatusBarLeft); |  | ||||||
| 
 |  | ||||||
|     return dolphin; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void dolphin_free(Dolphin* dolphin) { |  | ||||||
|     furi_assert(dolphin); |  | ||||||
| 
 |  | ||||||
|     gui_remove_view_port(dolphin->gui, dolphin->lock_viewport); |  | ||||||
|     view_port_free(dolphin->lock_viewport); |  | ||||||
|     icon_animation_free(dolphin->lock_icon); |  | ||||||
| 
 |  | ||||||
|     osTimerDelete(dolphin->timeout_timer); |  | ||||||
| 
 |  | ||||||
|     view_dispatcher_free(dolphin->idle_view_dispatcher); |  | ||||||
| 
 |  | ||||||
|     furi_record_close("gui"); |  | ||||||
|     dolphin->gui = NULL; |  | ||||||
| 
 |  | ||||||
|     furi_thread_free(dolphin->scene_thread); |  | ||||||
| 
 |  | ||||||
|     furi_record_close("menu"); |  | ||||||
|     dolphin->menu_vm = NULL; |  | ||||||
| 
 |  | ||||||
|     dolphin_state_free(dolphin->state); |  | ||||||
| 
 |  | ||||||
|     osMessageQueueDelete(dolphin->event_queue); |  | ||||||
| 
 |  | ||||||
|     free(dolphin); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void dolphin_save(Dolphin* dolphin) { | void dolphin_save(Dolphin* dolphin) { | ||||||
| @ -401,56 +21,53 @@ void dolphin_deed(Dolphin* dolphin, DolphinDeed deed) { | |||||||
|     furi_check(osMessageQueuePut(dolphin->event_queue, &event, 0, osWaitForever) == osOK); |     furi_check(osMessageQueuePut(dolphin->event_queue, &event, 0, osWaitForever) == osOK); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int32_t dolphin_srv() { | DolphinDeedWeight dolphin_stats(Dolphin* dolphin) { | ||||||
|  |     DolphinDeedWeight stats; | ||||||
|  |     stats.butthurt = dolphin_state_get_butthurt(dolphin->state); | ||||||
|  |     stats.icounter = dolphin_state_get_icounter(dolphin->state); | ||||||
|  | 
 | ||||||
|  |     return stats; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Dolphin* dolphin_alloc() { | ||||||
|  |     Dolphin* dolphin = furi_alloc(sizeof(Dolphin)); | ||||||
|  | 
 | ||||||
|  |     dolphin->state = dolphin_state_alloc(); | ||||||
|  |     dolphin->event_queue = osMessageQueueNew(8, sizeof(DolphinEvent), NULL); | ||||||
|  | 
 | ||||||
|  |     return dolphin; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void dolphin_free(Dolphin* dolphin) { | ||||||
|  |     furi_assert(dolphin); | ||||||
|  | 
 | ||||||
|  |     dolphin_state_free(dolphin->state); | ||||||
|  |     osMessageQueueDelete(dolphin->event_queue); | ||||||
|  | 
 | ||||||
|  |     free(dolphin); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int32_t dolphin_srv(void* p) { | ||||||
|     Dolphin* dolphin = dolphin_alloc(); |     Dolphin* dolphin = dolphin_alloc(); | ||||||
| 
 |  | ||||||
|     if(dolphin_state_load(dolphin->state)) { |  | ||||||
|         view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain); |  | ||||||
|     } else { |  | ||||||
|         view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewFirstStart); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         dolphin->idle_view_dolphin_stats, (DolphinViewStatsModel * model) { |  | ||||||
|             model->icounter = dolphin_state_get_icounter(dolphin->state); |  | ||||||
|             model->butthurt = dolphin_state_get_butthurt(dolphin->state); |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     furi_record_create("dolphin", dolphin); |     furi_record_create("dolphin", dolphin); | ||||||
| 
 | 
 | ||||||
|     if(!furi_hal_version_do_i_belong_here()) { |  | ||||||
|         view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewHwMismatch); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     DolphinEvent event; |     DolphinEvent event; | ||||||
|     while(1) { |     while(1) { | ||||||
|         furi_check(osMessageQueueGet(dolphin->event_queue, &event, NULL, osWaitForever) == osOK); |         furi_check(osMessageQueueGet(dolphin->event_queue, &event, NULL, osWaitForever) == osOK); | ||||||
| 
 |         switch(event.type) { | ||||||
|         DolphinViewLockMenuModel* lock_model = view_get_model(dolphin->view_lockmenu); |         case DolphinEventTypeDeed: | ||||||
| 
 |  | ||||||
|         if(lock_model->locked && lock_model->exit_timeout == 0 && |  | ||||||
|            osTimerIsRunning(dolphin->timeout_timer)) { |  | ||||||
|             osTimerStop(dolphin->timeout_timer); |  | ||||||
|             osDelay(1); // smol enterprise delay
 |  | ||||||
|             view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(event.type == DolphinEventTypeTick) { |  | ||||||
|             view_commit_model(dolphin->view_lockmenu, true); |  | ||||||
| 
 |  | ||||||
|         } else if(event.type == DolphinEventTypeDeed) { |  | ||||||
|             dolphin_state_on_deed(dolphin->state, event.deed); |             dolphin_state_on_deed(dolphin->state, event.deed); | ||||||
|             with_view_model( |             break; | ||||||
|                 dolphin->idle_view_dolphin_stats, (DolphinViewStatsModel * model) { | 
 | ||||||
|                     model->icounter = dolphin_state_get_icounter(dolphin->state); |         case DolphinEventTypeSave: | ||||||
|                     model->butthurt = dolphin_state_get_butthurt(dolphin->state); |  | ||||||
|                     return true; |  | ||||||
|                 }); |  | ||||||
|         } else if(event.type == DolphinEventTypeSave) { |  | ||||||
|             dolphin_state_save(dolphin->state); |             dolphin_state_save(dolphin->state); | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     dolphin_free(dolphin); |     dolphin_free(dolphin); | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,11 +1,30 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "dolphin_deed.h" | #include "helpers/dolphin_deed.h" | ||||||
| 
 | 
 | ||||||
| typedef struct Dolphin Dolphin; | typedef struct Dolphin Dolphin; | ||||||
| 
 | 
 | ||||||
|  | /* Load Dolphin state
 | ||||||
|  |  * Thread safe | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | bool dolphin_load(Dolphin* dolphin); | ||||||
|  | 
 | ||||||
| /* Deed complete notification. Call it on deed completion.
 | /* Deed complete notification. Call it on deed completion.
 | ||||||
|  * See dolphin_deed.h for available deeds. In futures it will become part of assets. |  * See dolphin_deed.h for available deeds. In futures it will become part of assets. | ||||||
|  * Thread safe |  * Thread safe | ||||||
|  */ |  */ | ||||||
|  | 
 | ||||||
| void dolphin_deed(Dolphin* dolphin, DolphinDeed deed); | void dolphin_deed(Dolphin* dolphin, DolphinDeed deed); | ||||||
|  | 
 | ||||||
|  | /* Save Dolphin state (write to permanent memory)
 | ||||||
|  |  * Thread safe | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | void dolphin_save(Dolphin* dolphin); | ||||||
|  | 
 | ||||||
|  | /* Retrieve dolphin's icounter and butthurt values
 | ||||||
|  |  * Thread safe | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | DolphinDeedWeight dolphin_stats(Dolphin* dolphin); | ||||||
| @ -1,22 +1,10 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "dolphin.h" |  | ||||||
| #include "dolphin_state.h" |  | ||||||
| #include "dolphin_views.h" |  | ||||||
| 
 |  | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi-hal.h> | ||||||
| #include <gui/gui.h> |  | ||||||
| #include <gui/view_dispatcher.h> |  | ||||||
| #include <gui/canvas.h> |  | ||||||
| #include <menu/menu.h> |  | ||||||
| 
 | 
 | ||||||
| #include <assets_icons.h> | #include "dolphin.h" | ||||||
| #include <stdint.h> | #include "helpers/dolphin_state.h" | ||||||
| 
 |  | ||||||
| #define UNLOCK_RST_TIMEOUT 500 // keypress counter reset timeout (ms)
 |  | ||||||
| #define HINT_TIMEOUT_L 3 // low refresh rate timeout (app ticks)
 |  | ||||||
| #define HINT_TIMEOUT_H 40 // high refresh rate timeout (app ticks)
 |  | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     DolphinEventTypeDeed, |     DolphinEventTypeDeed, | ||||||
| @ -32,36 +20,12 @@ typedef struct { | |||||||
| } DolphinEvent; | } DolphinEvent; | ||||||
| 
 | 
 | ||||||
| struct Dolphin { | struct Dolphin { | ||||||
|     // Internal message queue
 |  | ||||||
|     osMessageQueueId_t event_queue; |  | ||||||
|     // State
 |     // State
 | ||||||
|     DolphinState* state; |     DolphinState* state; | ||||||
|     // Menu
 |     // Queue
 | ||||||
|     ValueMutex* menu_vm; |     osMessageQueueId_t event_queue; | ||||||
|     // Scene
 |  | ||||||
|     FuriThread* scene_thread; |  | ||||||
|     // GUI
 |  | ||||||
|     Gui* gui; |  | ||||||
|     ViewDispatcher* idle_view_dispatcher; |  | ||||||
|     View* idle_view_first_start; |  | ||||||
|     View* idle_view_main; |  | ||||||
|     View* idle_view_dolphin_stats; |  | ||||||
|     View* view_hw_mismatch; |  | ||||||
|     View* view_lockmenu; |  | ||||||
|     ViewPort* lock_viewport; |  | ||||||
|     IconAnimation* lock_icon; |  | ||||||
| 
 |  | ||||||
|     bool locked; |  | ||||||
|     uint8_t lock_count; |  | ||||||
|     uint32_t lock_lastpress; |  | ||||||
|     osTimerId_t timeout_timer; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| Dolphin* dolphin_alloc(); | Dolphin* dolphin_alloc(); | ||||||
| 
 | 
 | ||||||
| void dolphin_free(Dolphin* dolphin); | void dolphin_free(Dolphin* dolphin); | ||||||
| 
 |  | ||||||
| /* Save Dolphin state (write to permanent memory)
 |  | ||||||
|  * Thread safe |  | ||||||
|  */ |  | ||||||
| void dolphin_save(Dolphin* dolphin); |  | ||||||
|  | |||||||
| @ -1,197 +0,0 @@ | |||||||
| #include "dolphin_views.h" |  | ||||||
| #include <gui/view.h> |  | ||||||
| #include <gui/gui.h> |  | ||||||
| #include <gui/elements.h> |  | ||||||
| #include <furi-hal.h> |  | ||||||
| #include <furi-hal-version.h> |  | ||||||
| 
 |  | ||||||
| static char* Lockmenu_Items[3] = {"Lock", "Set PIN", "DUMB mode"}; |  | ||||||
| 
 |  | ||||||
| void dolphin_view_first_start_draw(Canvas* canvas, void* model) { |  | ||||||
|     DolphinViewFirstStartModel* m = model; |  | ||||||
|     canvas_clear(canvas); |  | ||||||
|     canvas_set_color(canvas, ColorBlack); |  | ||||||
|     canvas_set_font(canvas, FontSecondary); |  | ||||||
|     uint8_t width = canvas_width(canvas); |  | ||||||
|     uint8_t height = canvas_height(canvas); |  | ||||||
|     const char* my_name = furi_hal_version_get_name_ptr(); |  | ||||||
|     if(m->page == 0) { |  | ||||||
|         canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart0_70x53); |  | ||||||
|         elements_multiline_text_framed(canvas, 75, 20, "Hey m8,\npress > to\ncontinue"); |  | ||||||
|     } else if(m->page == 1) { |  | ||||||
|         canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart1_59x53); |  | ||||||
|         elements_multiline_text_framed(canvas, 64, 20, "First Of All,\n...      >"); |  | ||||||
|     } else if(m->page == 2) { |  | ||||||
|         canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart2_59x51); |  | ||||||
|         elements_multiline_text_framed(canvas, 64, 20, "Thank you\nfor your\nsupport! >"); |  | ||||||
|     } else if(m->page == 3) { |  | ||||||
|         canvas_draw_icon(canvas, width - 57, height - 48, &I_DolphinFirstStart3_57x48); |  | ||||||
|         elements_multiline_text_framed(canvas, 0, 20, "Kickstarter\ncampaign\nwas INSANE! >"); |  | ||||||
|     } else if(m->page == 4) { |  | ||||||
|         canvas_draw_icon(canvas, width - 67, height - 50, &I_DolphinFirstStart4_67x53); |  | ||||||
|         elements_multiline_text_framed(canvas, 0, 17, "Now\nallow me\nto introduce\nmyself >"); |  | ||||||
|     } else if(m->page == 5) { |  | ||||||
|         char buf[64]; |  | ||||||
|         snprintf( |  | ||||||
|             buf, |  | ||||||
|             64, |  | ||||||
|             "%s %s%s", |  | ||||||
|             "I am", |  | ||||||
|             my_name ? my_name : "Unknown", |  | ||||||
|             ",\ncyberdolphin\nliving in your\npocket >"); |  | ||||||
|         canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart5_54x49); |  | ||||||
|         elements_multiline_text_framed(canvas, 60, 17, buf); |  | ||||||
|     } else if(m->page == 6) { |  | ||||||
|         canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart6_58x54); |  | ||||||
|         elements_multiline_text_framed( |  | ||||||
|             canvas, 63, 17, "I can grow\nsmart'n'cool\nif you use me\noften >"); |  | ||||||
|     } else if(m->page == 7) { |  | ||||||
|         canvas_draw_icon(canvas, width - 61, height - 48, &I_DolphinFirstStart7_61x51); |  | ||||||
|         elements_multiline_text_framed( |  | ||||||
|             canvas, 0, 17, "As long as\nyou read, write\nand emulate >"); |  | ||||||
|     } else if(m->page == 8) { |  | ||||||
|         canvas_draw_icon(canvas, width - 56, height - 48, &I_DolphinFirstStart8_56x51); |  | ||||||
|         elements_multiline_text_framed( |  | ||||||
|             canvas, 0, 17, "You can check\nmy level and\nmood in the\nPassport menu"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void dolphin_view_idle_main_draw(Canvas* canvas, void* model) { |  | ||||||
|     canvas_clear(canvas); |  | ||||||
|     DolphinViewMainModel* m = model; |  | ||||||
|     if(m->animation) { |  | ||||||
|         canvas_draw_icon_animation(canvas, 0, -3, m->animation); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(m->hint_timeout) { |  | ||||||
|         m->hint_timeout--; |  | ||||||
|         if(m->locked) { |  | ||||||
|             canvas_draw_icon(canvas, 13, 5, &I_LockPopup_100x49); |  | ||||||
|             elements_multiline_text(canvas, 65, 20, "To unlock\npress:"); |  | ||||||
|         } else { |  | ||||||
|             canvas_set_font(canvas, FontPrimary); |  | ||||||
|             elements_multiline_text_framed(canvas, 42, 30, "Unlocked"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void dolphin_view_lockmenu_draw(Canvas* canvas, void* model) { |  | ||||||
|     DolphinViewLockMenuModel* m = model; |  | ||||||
|     canvas_clear(canvas); |  | ||||||
|     canvas_set_color(canvas, ColorBlack); |  | ||||||
|     canvas_draw_icon(canvas, m->door_left_x, 0, &I_DoorLeft_70x55); |  | ||||||
|     canvas_draw_icon(canvas, m->door_right_x, 0, &I_DoorRight_70x55); |  | ||||||
|     canvas_set_font(canvas, FontSecondary); |  | ||||||
| 
 |  | ||||||
|     if(m->locked) { |  | ||||||
|         m->exit_timeout--; |  | ||||||
| 
 |  | ||||||
|         m->door_left_x = CLAMP(m->door_left_x + 5, 0, -57); |  | ||||||
|         m->door_right_x = CLAMP(m->door_right_x - 5, 115, 60); |  | ||||||
| 
 |  | ||||||
|         if(m->door_left_x > -10) { |  | ||||||
|             canvas_set_font(canvas, FontPrimary); |  | ||||||
|             elements_multiline_text_framed(canvas, 42, 30, "Locked"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|     } else { |  | ||||||
|         if(m->door_left_x == -57) { |  | ||||||
|             for(uint8_t i = 0; i < 3; ++i) { |  | ||||||
|                 canvas_draw_str_aligned( |  | ||||||
|                     canvas, |  | ||||||
|                     64, |  | ||||||
|                     13 + (i * 17), |  | ||||||
|                     AlignCenter, |  | ||||||
|                     AlignCenter, |  | ||||||
|                     (m->hint_timeout && m->idx == i) ? "Not implemented" : Lockmenu_Items[i]); |  | ||||||
|                 if(m->idx == i) elements_frame(canvas, 15, 5 + (i * 17), 98, 15); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(m->hint_timeout) { |  | ||||||
|             m->hint_timeout--; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void dolphin_view_idle_down_draw(Canvas* canvas, void* model) { |  | ||||||
|     DolphinViewStatsModel* m = model; |  | ||||||
|     const Version* ver; |  | ||||||
|     char buffer[64]; |  | ||||||
| 
 |  | ||||||
|     static const char* headers[] = {"FW Version info:", "Boot Version info:", "Dolphin info:"}; |  | ||||||
| 
 |  | ||||||
|     canvas_set_color(canvas, ColorBlack); |  | ||||||
|     canvas_set_font(canvas, FontPrimary); |  | ||||||
|     canvas_draw_str(canvas, 2, 13, headers[m->screen]); |  | ||||||
|     canvas_set_font(canvas, FontSecondary); |  | ||||||
| 
 |  | ||||||
|     if(m->screen != DolphinViewStatsMeta) { |  | ||||||
|         // Hardware version
 |  | ||||||
|         const char* my_name = furi_hal_version_get_name_ptr(); |  | ||||||
|         snprintf( |  | ||||||
|             buffer, |  | ||||||
|             sizeof(buffer), |  | ||||||
|             "HW: %d.F%dB%dC%d %s", |  | ||||||
|             furi_hal_version_get_hw_version(), |  | ||||||
|             furi_hal_version_get_hw_target(), |  | ||||||
|             furi_hal_version_get_hw_body(), |  | ||||||
|             furi_hal_version_get_hw_connect(), |  | ||||||
|             my_name ? my_name : "Unknown"); |  | ||||||
|         canvas_draw_str(canvas, 5, 23, buffer); |  | ||||||
| 
 |  | ||||||
|         ver = m->screen == DolphinViewStatsBoot ? furi_hal_version_get_boot_version() : |  | ||||||
|                                                   furi_hal_version_get_firmware_version(); |  | ||||||
| 
 |  | ||||||
|         if(!ver) { |  | ||||||
|             canvas_draw_str(canvas, 5, 33, "No info"); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         snprintf( |  | ||||||
|             buffer, |  | ||||||
|             sizeof(buffer), |  | ||||||
|             "%s [%s]", |  | ||||||
|             version_get_version(ver), |  | ||||||
|             version_get_builddate(ver)); |  | ||||||
|         canvas_draw_str(canvas, 5, 33, buffer); |  | ||||||
| 
 |  | ||||||
|         snprintf( |  | ||||||
|             buffer, |  | ||||||
|             sizeof(buffer), |  | ||||||
|             "%s [%s]", |  | ||||||
|             version_get_githash(ver), |  | ||||||
|             version_get_gitbranchnum(ver)); |  | ||||||
|         canvas_draw_str(canvas, 5, 43, buffer); |  | ||||||
| 
 |  | ||||||
|         snprintf( |  | ||||||
|             buffer, sizeof(buffer), "[%s] %s", version_get_target(ver), version_get_gitbranch(ver)); |  | ||||||
|         canvas_draw_str(canvas, 5, 53, buffer); |  | ||||||
| 
 |  | ||||||
|     } else { |  | ||||||
|         char buffer[64]; |  | ||||||
|         canvas_set_font(canvas, FontSecondary); |  | ||||||
|         snprintf(buffer, 64, "Icounter: %ld", m->icounter); |  | ||||||
|         canvas_draw_str(canvas, 5, 30, buffer); |  | ||||||
|         snprintf(buffer, 64, "Butthurt: %ld", m->butthurt); |  | ||||||
|         canvas_draw_str(canvas, 5, 40, buffer); |  | ||||||
|         canvas_draw_str(canvas, 0, 53, "[< >] icounter value   [ok] save"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void dolphin_view_hw_mismatch_draw(Canvas* canvas, void* model) { |  | ||||||
|     canvas_clear(canvas); |  | ||||||
|     canvas_set_color(canvas, ColorBlack); |  | ||||||
|     canvas_set_font(canvas, FontPrimary); |  | ||||||
|     canvas_draw_str(canvas, 2, 15, "!!!! HW Mismatch !!!!"); |  | ||||||
| 
 |  | ||||||
|     char buffer[64]; |  | ||||||
|     canvas_set_font(canvas, FontSecondary); |  | ||||||
|     snprintf(buffer, 64, "HW target: F%d", furi_hal_version_get_hw_target()); |  | ||||||
|     canvas_draw_str(canvas, 5, 27, buffer); |  | ||||||
|     canvas_draw_str(canvas, 5, 38, "FW target: " TARGET); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| uint32_t dolphin_view_idle_back(void* context) { |  | ||||||
|     return DolphinViewIdleMain; |  | ||||||
| } |  | ||||||
| @ -1,67 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <stdint.h> |  | ||||||
| #include <stdbool.h> |  | ||||||
| #include <gui/canvas.h> |  | ||||||
| #include <input/input.h> |  | ||||||
| #include <furi.h> |  | ||||||
| 
 |  | ||||||
| // Idle screen
 |  | ||||||
| typedef enum { |  | ||||||
|     DolphinViewIdleMain, |  | ||||||
|     DolphinViewFirstStart, |  | ||||||
|     DolphinViewStats, |  | ||||||
|     DolphinViewHwMismatch, |  | ||||||
|     DolphinViewLockMenu, |  | ||||||
| } DolphinViewIdle; |  | ||||||
| 
 |  | ||||||
| // Debug info
 |  | ||||||
| typedef enum { |  | ||||||
|     DolphinViewStatsFw, |  | ||||||
|     DolphinViewStatsBoot, |  | ||||||
|     DolphinViewStatsMeta, |  | ||||||
|     DolphinViewStatsTotalCount, |  | ||||||
| } DolphinViewStatsScreens; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     uint32_t page; |  | ||||||
| } DolphinViewFirstStartModel; |  | ||||||
| 
 |  | ||||||
| void dolphin_view_first_start_draw(Canvas* canvas, void* model); |  | ||||||
| bool dolphin_view_first_start_input(InputEvent* event, void* context); |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     uint32_t icounter; |  | ||||||
|     uint32_t butthurt; |  | ||||||
|     DolphinViewStatsScreens screen; |  | ||||||
| } DolphinViewStatsModel; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     uint8_t idx; |  | ||||||
|     int8_t door_left_x; |  | ||||||
|     int8_t door_right_x; |  | ||||||
|     uint8_t exit_timeout; |  | ||||||
|     uint8_t hint_timeout; |  | ||||||
| 
 |  | ||||||
|     bool locked; |  | ||||||
| } DolphinViewLockMenuModel; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     IconAnimation* animation; |  | ||||||
|     uint8_t scene_num; |  | ||||||
|     uint8_t hint_timeout; |  | ||||||
|     bool locked; |  | ||||||
| } DolphinViewMainModel; |  | ||||||
| 
 |  | ||||||
| void dolphin_view_idle_main_draw(Canvas* canvas, void* model); |  | ||||||
| bool dolphin_view_idle_main_input(InputEvent* event, void* context); |  | ||||||
| 
 |  | ||||||
| void dolphin_view_idle_up_draw(Canvas* canvas, void* model); |  | ||||||
| 
 |  | ||||||
| void dolphin_view_lockmenu_draw(Canvas* canvas, void* model); |  | ||||||
| 
 |  | ||||||
| void dolphin_view_idle_down_draw(Canvas* canvas, void* model); |  | ||||||
| 
 |  | ||||||
| void dolphin_view_hw_mismatch_draw(Canvas* canvas, void* model); |  | ||||||
| 
 |  | ||||||
| uint32_t dolphin_view_idle_back(void* context); |  | ||||||
| @ -16,6 +16,7 @@ typedef enum { | |||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     int32_t icounter; // how many icounter get by Deed
 |     int32_t icounter; // how many icounter get by Deed
 | ||||||
|  |     int32_t butthurt; // how many icounter get by Deed
 | ||||||
|     uint32_t limit_value; // how many deeds in limit interval
 |     uint32_t limit_value; // how many deeds in limit interval
 | ||||||
|     uint32_t limit_interval; // interval, in minutes
 |     uint32_t limit_interval; // interval, in minutes
 | ||||||
| } DolphinDeedWeight; | } DolphinDeedWeight; | ||||||
| @ -18,6 +18,7 @@ static const GpioItem GPIO_PINS[] = { | |||||||
|     {"1.7: PC3", &gpio_ext_pc3}, |     {"1.7: PC3", &gpio_ext_pc3}, | ||||||
|     {"2.7: PC1", &gpio_ext_pc1}, |     {"2.7: PC1", &gpio_ext_pc1}, | ||||||
|     {"2.8: PC0", &gpio_ext_pc0}, |     {"2.8: PC0", &gpio_ext_pc0}, | ||||||
|  |     {"*.*: ALL", NULL}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static const size_t GPIO_PINS_COUNT = sizeof(GPIO_PINS) / sizeof(GPIO_PINS[0]); | static const size_t GPIO_PINS_COUNT = sizeof(GPIO_PINS) / sizeof(GPIO_PINS[0]); | ||||||
| @ -49,8 +50,20 @@ static void gpio_test_input_callback(InputEvent* input_event, void* ctx) { | |||||||
| 
 | 
 | ||||||
| static void gpio_test_configure_pins(GpioMode mode) { | static void gpio_test_configure_pins(GpioMode mode) { | ||||||
|     for(size_t i = 0; i < GPIO_PINS_COUNT; i++) { |     for(size_t i = 0; i < GPIO_PINS_COUNT; i++) { | ||||||
|  |         if(!GPIO_PINS[i].pin) continue; | ||||||
|         hal_gpio_write(GPIO_PINS[i].pin, false); |         hal_gpio_write(GPIO_PINS[i].pin, false); | ||||||
|         hal_gpio_init(GPIO_PINS[i].pin, mode, GpioPullNo, GpioSpeedLow); |         hal_gpio_init(GPIO_PINS[i].pin, mode, GpioPullNo, GpioSpeedVeryHigh); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void gpio_test_set_pin(uint8_t index, bool level) { | ||||||
|  |     if(GPIO_PINS[index].pin) { | ||||||
|  |         hal_gpio_write(GPIO_PINS[index].pin, level); | ||||||
|  |     } else { | ||||||
|  |         for(size_t i = 0; i < GPIO_PINS_COUNT; i++) { | ||||||
|  |             if(!GPIO_PINS[i].pin) continue; | ||||||
|  |             hal_gpio_write(GPIO_PINS[i].pin, level); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -117,10 +130,10 @@ int32_t gpio_test_app(void* p) { | |||||||
|         } else { |         } else { | ||||||
|             if(event.key == InputKeyOk) { |             if(event.key == InputKeyOk) { | ||||||
|                 if(event.type == InputTypePress) { |                 if(event.type == InputTypePress) { | ||||||
|                     hal_gpio_write(GPIO_PINS[gpio_test->gpio_index].pin, true); |                     gpio_test_set_pin(gpio_test->gpio_index, true); | ||||||
|                     notification_message(gpio_test->notification, &sequence_set_green_255); |                     notification_message(gpio_test->notification, &sequence_set_green_255); | ||||||
|                 } else if(event.type == InputTypeRelease) { |                 } else if(event.type == InputTypeRelease) { | ||||||
|                     hal_gpio_write(GPIO_PINS[gpio_test->gpio_index].pin, false); |                     gpio_test_set_pin(gpio_test->gpio_index, false); | ||||||
|                     notification_message(gpio_test->notification, &sequence_reset_green); |                     notification_message(gpio_test->notification, &sequence_reset_green); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -327,6 +327,17 @@ void elements_slightly_rounded_box( | |||||||
|     canvas_draw_rbox(canvas, x, y, width, height, 1); |     canvas_draw_rbox(canvas, x, y, width, height, 1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height) { | ||||||
|  |     furi_assert(canvas); | ||||||
|  |     canvas_draw_rframe(canvas, x + 4, y, width, height, 3); | ||||||
|  |     uint8_t y_corner = y + height * 2 / 3; | ||||||
|  |     canvas_draw_line(canvas, x, y_corner, x + 4, y_corner - 4); | ||||||
|  |     canvas_draw_line(canvas, x, y_corner, x + 4, y_corner + 4); | ||||||
|  |     canvas_set_color(canvas, ColorWhite); | ||||||
|  |     canvas_draw_line(canvas, x + 4, y_corner - 3, x + 4, y_corner + 3); | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width) { | void elements_string_fit_width(Canvas* canvas, string_t string, uint8_t width) { | ||||||
|     furi_assert(canvas); |     furi_assert(canvas); | ||||||
|     furi_assert(string); |     furi_assert(string); | ||||||
|  | |||||||
| @ -8,8 +8,7 @@ | |||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| /*
 | /** Draw progress bar.
 | ||||||
|  * Draw progress bar. |  | ||||||
|  * @param x - progress bar position on X axis |  * @param x - progress bar position on X axis | ||||||
|  * @param y - progress bar position on Y axis |  * @param y - progress bar position on Y axis | ||||||
|  * @param width - progress bar width |  * @param width - progress bar width | ||||||
| @ -24,8 +23,7 @@ void elements_progress_bar( | |||||||
|     uint8_t progress, |     uint8_t progress, | ||||||
|     uint8_t total); |     uint8_t total); | ||||||
| 
 | 
 | ||||||
| /*
 | /** Draw scrollbar on canvas at specific position.
 | ||||||
|  * Draw scrollbar on canvas at specific position. |  | ||||||
|  * @param x - scrollbar position on X axis |  * @param x - scrollbar position on X axis | ||||||
|  * @param y - scrollbar position on Y axis |  * @param y - scrollbar position on Y axis | ||||||
|  * @param height - scrollbar height |  * @param height - scrollbar height | ||||||
| @ -40,41 +38,35 @@ void elements_scrollbar_pos( | |||||||
|     uint16_t pos, |     uint16_t pos, | ||||||
|     uint16_t total); |     uint16_t total); | ||||||
| 
 | 
 | ||||||
| /*
 | /** Draw scrollbar on canvas.
 | ||||||
|  * Draw scrollbar on canvas. |  | ||||||
|  * width 3px, height equal to canvas height |  * width 3px, height equal to canvas height | ||||||
|  * @param pos - current element of total elements |  * @param pos - current element of total elements | ||||||
|  * @param total - total elements |  * @param total - total elements | ||||||
|  */ |  */ | ||||||
| void elements_scrollbar(Canvas* canvas, uint16_t pos, uint16_t total); | void elements_scrollbar(Canvas* canvas, uint16_t pos, uint16_t total); | ||||||
| 
 | 
 | ||||||
| /*
 | /** Draw rounded frame
 | ||||||
|  * Draw rounded frame |  | ||||||
|  * @param x, y - top left corner coordinates |  * @param x, y - top left corner coordinates | ||||||
|  * @param width, height - frame width and height |  * @param width, height - frame width and height | ||||||
|  */ |  */ | ||||||
| void elements_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); | void elements_frame(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); | ||||||
| 
 | 
 | ||||||
| /*
 | /** Draw button in left corner
 | ||||||
|  * Draw button in left corner |  | ||||||
|  * @param str - button text |  * @param str - button text | ||||||
|  */ |  */ | ||||||
| void elements_button_left(Canvas* canvas, const char* str); | void elements_button_left(Canvas* canvas, const char* str); | ||||||
| 
 | 
 | ||||||
| /*
 | /** Draw button in right corner
 | ||||||
|  * Draw button in right corner |  | ||||||
|  * @param str - button text |  * @param str - button text | ||||||
|  */ |  */ | ||||||
| void elements_button_right(Canvas* canvas, const char* str); | void elements_button_right(Canvas* canvas, const char* str); | ||||||
| 
 | 
 | ||||||
| /*
 | /** Draw button in center
 | ||||||
|  * Draw button in center |  | ||||||
|  * @param str - button text |  * @param str - button text | ||||||
|  */ |  */ | ||||||
| void elements_button_center(Canvas* canvas, const char* str); | void elements_button_center(Canvas* canvas, const char* str); | ||||||
| 
 | 
 | ||||||
| /*
 | /** Draw aligned multiline text
 | ||||||
|  * Draw aligned multiline text |  | ||||||
|  * @param x, y - coordinates based on align param |  * @param x, y - coordinates based on align param | ||||||
|  * @param horizontal, vertical - aligment of multiline text |  * @param horizontal, vertical - aligment of multiline text | ||||||
|  * @param text - string (possible multiline) |  * @param text - string (possible multiline) | ||||||
| @ -87,22 +79,19 @@ void elements_multiline_text_aligned( | |||||||
|     Align vertical, |     Align vertical, | ||||||
|     const char* text); |     const char* text); | ||||||
| 
 | 
 | ||||||
| /*
 | /** Draw multiline text
 | ||||||
|  * Draw multiline text |  | ||||||
|  * @param x, y - top left corner coordinates |  * @param x, y - top left corner coordinates | ||||||
|  * @param text - string (possible multiline) |  * @param text - string (possible multiline) | ||||||
|  */ |  */ | ||||||
| void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, const char* text); | void elements_multiline_text(Canvas* canvas, uint8_t x, uint8_t y, const char* text); | ||||||
| 
 | 
 | ||||||
| /*
 | /** Draw framed multiline text
 | ||||||
|  * Draw framed multiline text |  | ||||||
|  * @param x, y - top left corner coordinates |  * @param x, y - top left corner coordinates | ||||||
|  * @param text - string (possible multiline) |  * @param text - string (possible multiline) | ||||||
|  */ |  */ | ||||||
| void elements_multiline_text_framed(Canvas* canvas, uint8_t x, uint8_t y, const char* text); | void elements_multiline_text_framed(Canvas* canvas, uint8_t x, uint8_t y, const char* text); | ||||||
| 
 | 
 | ||||||
| /*
 | /** Draw slightly rounded frame
 | ||||||
|  * Draw slightly rounded frame |  | ||||||
|  * @param x, y - top left corner coordinates |  * @param x, y - top left corner coordinates | ||||||
|  * @param width, height - size of frame |  * @param width, height - size of frame | ||||||
|  */ |  */ | ||||||
| @ -113,8 +102,7 @@ void elements_slightly_rounded_frame( | |||||||
|     uint8_t width, |     uint8_t width, | ||||||
|     uint8_t height); |     uint8_t height); | ||||||
| 
 | 
 | ||||||
| /*
 | /** Draw slightly rounded box
 | ||||||
|  * Draw slightly rounded box |  | ||||||
|  * @param x, y - top left corner coordinates |  * @param x, y - top left corner coordinates | ||||||
|  * @param width, height - size of box |  * @param width, height - size of box | ||||||
|  */ |  */ | ||||||
| @ -125,8 +113,15 @@ void elements_slightly_rounded_box( | |||||||
|     uint8_t width, |     uint8_t width, | ||||||
|     uint8_t height); |     uint8_t height); | ||||||
| 
 | 
 | ||||||
| /*
 | /** Draw bubble frame for text
 | ||||||
|  * Trim string buffer to fit width in pixels |  * @param x - left x coordinates | ||||||
|  |  * @param y - top y coordinate | ||||||
|  |  * @param width - bubble width | ||||||
|  |  * @param height - bubble height | ||||||
|  |  */ | ||||||
|  | void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height); | ||||||
|  | 
 | ||||||
|  | /** Trim string buffer to fit width in pixels
 | ||||||
|  * @param string - string to trim |  * @param string - string to trim | ||||||
|  * @param width - max width |  * @param width - max width | ||||||
|  */ |  */ | ||||||
|  | |||||||
							
								
								
									
										34
									
								
								applications/gui/modules/empty_screen.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								applications/gui/modules/empty_screen.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | |||||||
|  | #include "empty_screen.h" | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | struct EmptyScreen { | ||||||
|  |     View* view; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void empty_screen_view_draw_callback(Canvas* canvas, void* _model) { | ||||||
|  |     canvas_clear(canvas); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool empty_screen_view_input_callback(InputEvent* event, void* context) { | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | EmptyScreen* empty_screen_alloc() { | ||||||
|  |     EmptyScreen* empty_screen = furi_alloc(sizeof(EmptyScreen)); | ||||||
|  |     empty_screen->view = view_alloc(); | ||||||
|  |     view_set_context(empty_screen->view, empty_screen); | ||||||
|  |     view_set_draw_callback(empty_screen->view, empty_screen_view_draw_callback); | ||||||
|  |     view_set_input_callback(empty_screen->view, empty_screen_view_input_callback); | ||||||
|  |     return empty_screen; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void empty_screen_free(EmptyScreen* empty_screen) { | ||||||
|  |     furi_assert(empty_screen); | ||||||
|  |     view_free(empty_screen->view); | ||||||
|  |     free(empty_screen); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | View* empty_screen_get_view(EmptyScreen* empty_screen) { | ||||||
|  |     furi_assert(empty_screen); | ||||||
|  |     return empty_screen->view; | ||||||
|  | } | ||||||
							
								
								
									
										29
									
								
								applications/gui/modules/empty_screen.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								applications/gui/modules/empty_screen.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <gui/view.h> | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | /* Empty screen anonymous structure */ | ||||||
|  | typedef struct EmptyScreen EmptyScreen; | ||||||
|  | 
 | ||||||
|  | /* Allocate and initialize empty screen
 | ||||||
|  |  * This empty screen used to ask simple questions like Yes/ | ||||||
|  |  */ | ||||||
|  | EmptyScreen* empty_screen_alloc(); | ||||||
|  | 
 | ||||||
|  | /* Deinitialize and free empty screen
 | ||||||
|  |  * @param empty_screen - Empty screen instance | ||||||
|  |  */ | ||||||
|  | void empty_screen_free(EmptyScreen* empty_screen); | ||||||
|  | 
 | ||||||
|  | /* Get empty screen view
 | ||||||
|  |  * @param empty_screen - Empty screen instance | ||||||
|  |  * @return View instance that can be used for embedding | ||||||
|  |  */ | ||||||
|  | View* empty_screen_get_view(EmptyScreen* empty_screen); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
| @ -1,7 +1,6 @@ | |||||||
| #include "file_select.h" | #include "file_select.h" | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| #include <m-string.h> | #include <m-string.h> | ||||||
| #include <sys/param.h> |  | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| 
 | 
 | ||||||
| #define FILENAME_COUNT 4 | #define FILENAME_COUNT 4 | ||||||
|  | |||||||
							
								
								
									
										211
									
								
								applications/gui/modules/menu.c
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										211
									
								
								applications/gui/modules/menu.c
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,211 @@ | |||||||
|  | #include "menu.h" | ||||||
|  | 
 | ||||||
|  | #include <m-array.h> | ||||||
|  | #include <gui/elements.h> | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | struct Menu { | ||||||
|  |     View* view; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     const char* label; | ||||||
|  |     IconAnimation* icon; | ||||||
|  |     uint32_t index; | ||||||
|  |     MenuItemCallback callback; | ||||||
|  |     void* callback_context; | ||||||
|  | } MenuItem; | ||||||
|  | 
 | ||||||
|  | ARRAY_DEF(MenuItemArray, MenuItem, M_POD_OPLIST); | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     MenuItemArray_t items; | ||||||
|  |     uint8_t position; | ||||||
|  | } MenuModel; | ||||||
|  | 
 | ||||||
|  | static void menu_process_up(Menu* menu); | ||||||
|  | static void menu_process_down(Menu* menu); | ||||||
|  | static void menu_process_ok(Menu* menu); | ||||||
|  | 
 | ||||||
|  | static void menu_draw_callback(Canvas* canvas, void* _model) { | ||||||
|  |     MenuModel* model = _model; | ||||||
|  | 
 | ||||||
|  |     canvas_clear(canvas); | ||||||
|  | 
 | ||||||
|  |     uint8_t position = model->position; | ||||||
|  |     size_t items_count = MenuItemArray_size(model->items); | ||||||
|  |     if(items_count) { | ||||||
|  |         MenuItem* item; | ||||||
|  |         size_t shift_position; | ||||||
|  |         // First line
 | ||||||
|  |         canvas_set_font(canvas, FontSecondary); | ||||||
|  |         shift_position = (0 + position + items_count - 1) % items_count; | ||||||
|  |         item = MenuItemArray_get(model->items, shift_position); | ||||||
|  |         if(item->icon) { | ||||||
|  |             canvas_draw_icon_animation(canvas, 4, 3, item->icon); | ||||||
|  |             icon_animation_stop(item->icon); | ||||||
|  |         } | ||||||
|  |         canvas_draw_str(canvas, 22, 14, item->label); | ||||||
|  |         // Second line main
 | ||||||
|  |         canvas_set_font(canvas, FontPrimary); | ||||||
|  |         shift_position = (1 + position + items_count - 1) % items_count; | ||||||
|  |         item = MenuItemArray_get(model->items, shift_position); | ||||||
|  |         if(item->icon) { | ||||||
|  |             canvas_draw_icon_animation(canvas, 4, 25, item->icon); | ||||||
|  |             icon_animation_start(item->icon); | ||||||
|  |         } | ||||||
|  |         canvas_draw_str(canvas, 22, 36, item->label); | ||||||
|  |         // Third line
 | ||||||
|  |         canvas_set_font(canvas, FontSecondary); | ||||||
|  |         shift_position = (2 + position + items_count - 1) % items_count; | ||||||
|  |         item = MenuItemArray_get(model->items, shift_position); | ||||||
|  |         if(item->icon) { | ||||||
|  |             canvas_draw_icon_animation(canvas, 4, 47, item->icon); | ||||||
|  |             icon_animation_stop(item->icon); | ||||||
|  |         } | ||||||
|  |         canvas_draw_str(canvas, 22, 58, item->label); | ||||||
|  |         // Frame and scrollbar
 | ||||||
|  |         elements_frame(canvas, 0, 21, 128 - 5, 21); | ||||||
|  |         elements_scrollbar(canvas, position, items_count); | ||||||
|  |     } else { | ||||||
|  |         canvas_draw_str(canvas, 2, 32, "Empty"); | ||||||
|  |         elements_scrollbar(canvas, 0, 0); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool menu_input_callback(InputEvent* event, void* context) { | ||||||
|  |     Menu* menu = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event->type == InputTypeShort) { | ||||||
|  |         if(event->key == InputKeyUp) { | ||||||
|  |             consumed = true; | ||||||
|  |             menu_process_up(menu); | ||||||
|  |         } else if(event->key == InputKeyDown) { | ||||||
|  |             consumed = true; | ||||||
|  |             menu_process_down(menu); | ||||||
|  |         } else if(event->key == InputKeyOk) { | ||||||
|  |             consumed = true; | ||||||
|  |             menu_process_ok(menu); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Menu* menu_alloc() { | ||||||
|  |     Menu* menu = furi_alloc(sizeof(Menu)); | ||||||
|  |     menu->view = view_alloc(menu->view); | ||||||
|  |     view_set_context(menu->view, menu); | ||||||
|  |     view_allocate_model(menu->view, ViewModelTypeLocking, sizeof(MenuModel)); | ||||||
|  |     view_set_draw_callback(menu->view, menu_draw_callback); | ||||||
|  |     view_set_input_callback(menu->view, menu_input_callback); | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         menu->view, (MenuModel * model) { | ||||||
|  |             MenuItemArray_init(model->items); | ||||||
|  |             model->position = 0; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     return menu; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void menu_free(Menu* menu) { | ||||||
|  |     furi_assert(menu); | ||||||
|  |     with_view_model( | ||||||
|  |         menu->view, (MenuModel * model) { | ||||||
|  |             MenuItemArray_clear(model->items); | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  |     view_free(menu->view); | ||||||
|  |     free(menu); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | View* menu_get_view(Menu* menu) { | ||||||
|  |     furi_assert(menu); | ||||||
|  |     return (menu->view); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void menu_add_item( | ||||||
|  |     Menu* menu, | ||||||
|  |     const char* label, | ||||||
|  |     IconAnimation* icon, | ||||||
|  |     uint32_t index, | ||||||
|  |     MenuItemCallback callback, | ||||||
|  |     void* context) { | ||||||
|  |     furi_assert(menu); | ||||||
|  |     furi_assert(label); | ||||||
|  | 
 | ||||||
|  |     MenuItem* item = NULL; | ||||||
|  |     with_view_model( | ||||||
|  |         menu->view, (MenuModel * model) { | ||||||
|  |             item = MenuItemArray_push_new(model->items); | ||||||
|  |             item->label = label; | ||||||
|  |             item->icon = icon; | ||||||
|  |             item->index = index; | ||||||
|  |             item->callback = callback; | ||||||
|  |             item->callback_context = context; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void menu_clean(Menu* menu) { | ||||||
|  |     furi_assert(menu); | ||||||
|  |     with_view_model( | ||||||
|  |         menu->view, (MenuModel * model) { | ||||||
|  |             MenuItemArray_clean(model->items); | ||||||
|  |             model->position = 0; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void menu_set_selected_item(Menu* menu, uint32_t index) { | ||||||
|  |     with_view_model( | ||||||
|  |         menu->view, (MenuModel * model) { | ||||||
|  |             if(index >= MenuItemArray_size(model->items)) { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             model->position = index; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void menu_process_up(Menu* menu) { | ||||||
|  |     with_view_model( | ||||||
|  |         menu->view, (MenuModel * model) { | ||||||
|  |             if(model->position > 0) { | ||||||
|  |                 model->position--; | ||||||
|  |             } else { | ||||||
|  |                 model->position = MenuItemArray_size(model->items) - 1; | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void menu_process_down(Menu* menu) { | ||||||
|  |     with_view_model( | ||||||
|  |         menu->view, (MenuModel * model) { | ||||||
|  |             if(model->position < MenuItemArray_size(model->items) - 1) { | ||||||
|  |                 model->position++; | ||||||
|  |             } else { | ||||||
|  |                 model->position = 0; | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void menu_process_ok(Menu* menu) { | ||||||
|  |     MenuItem* item = NULL; | ||||||
|  |     with_view_model( | ||||||
|  |         menu->view, (MenuModel * model) { | ||||||
|  |             if(model->position < MenuItemArray_size(model->items)) { | ||||||
|  |                 item = MenuItemArray_get(model->items, model->position); | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  |     if(item && item->callback) { | ||||||
|  |         item->callback(item->callback_context, item->index); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										58
									
								
								applications/gui/modules/menu.h
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										58
									
								
								applications/gui/modules/menu.h
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,58 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <gui/view.h> | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | /** Menu anonymous structure */ | ||||||
|  | typedef struct Menu Menu; | ||||||
|  | typedef void (*MenuItemCallback)(void* context, uint32_t index); | ||||||
|  | 
 | ||||||
|  | /** Menu allocation and initialization
 | ||||||
|  |  * @return Menu instance | ||||||
|  |  */ | ||||||
|  | Menu* menu_alloc(); | ||||||
|  | 
 | ||||||
|  | /** Free menu
 | ||||||
|  |  * @param menu - Menu instance | ||||||
|  |  */ | ||||||
|  | void menu_free(Menu* menu); | ||||||
|  | 
 | ||||||
|  | /** Get Menu view
 | ||||||
|  |  * @param menu - Menu instance | ||||||
|  |  * @return View instance | ||||||
|  |  */ | ||||||
|  | View* menu_get_view(Menu* menu); | ||||||
|  | 
 | ||||||
|  | /** Add item to menu
 | ||||||
|  |  * @param menu - Menu instance | ||||||
|  |  * @param label - menu item string label | ||||||
|  |  * @param icon - IconAnimation instance | ||||||
|  |  * @param index - menu item index | ||||||
|  |  * @param callback - MenuItemCallback instance | ||||||
|  |  * @param context - pointer to context | ||||||
|  |  */ | ||||||
|  | void menu_add_item( | ||||||
|  |     Menu* menu, | ||||||
|  |     const char* label, | ||||||
|  |     IconAnimation* icon, | ||||||
|  |     uint32_t index, | ||||||
|  |     MenuItemCallback callback, | ||||||
|  |     void* context); | ||||||
|  | 
 | ||||||
|  | /** Clean menu
 | ||||||
|  |  * Note: this function does not free menu instance | ||||||
|  |  * @param menu - Menu instance | ||||||
|  |  */ | ||||||
|  | void menu_clean(Menu* menu); | ||||||
|  | 
 | ||||||
|  | /** Set current menu item
 | ||||||
|  |  * @param submenu | ||||||
|  |  * @param index | ||||||
|  |  */ | ||||||
|  | void menu_set_selected_item(Menu* menu, uint32_t index); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
| @ -1,23 +1,22 @@ | |||||||
| #include "submenu.h" | #include "submenu.h" | ||||||
| #include "gui/canvas.h" | 
 | ||||||
| #include <m-array.h> | #include <m-array.h> | ||||||
| #include <furi.h> |  | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| #include <stdint.h> | #include <furi.h> | ||||||
| 
 |  | ||||||
| struct SubmenuItem { |  | ||||||
|     const char* label; |  | ||||||
|     uint32_t index; |  | ||||||
|     SubmenuItemCallback callback; |  | ||||||
|     void* callback_context; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| ARRAY_DEF(SubmenuItemArray, SubmenuItem, M_POD_OPLIST); |  | ||||||
| 
 | 
 | ||||||
| struct Submenu { | struct Submenu { | ||||||
|     View* view; |     View* view; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | typedef struct { | ||||||
|  |     const char* label; | ||||||
|  |     uint32_t index; | ||||||
|  |     SubmenuItemCallback callback; | ||||||
|  |     void* callback_context; | ||||||
|  | } SubmenuItem; | ||||||
|  | 
 | ||||||
|  | ARRAY_DEF(SubmenuItemArray, SubmenuItem, M_POD_OPLIST); | ||||||
|  | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     SubmenuItemArray_t items; |     SubmenuItemArray_t items; | ||||||
|     const char* header; |     const char* header; | ||||||
| @ -149,7 +148,7 @@ View* submenu_get_view(Submenu* submenu) { | |||||||
|     return submenu->view; |     return submenu->view; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| SubmenuItem* submenu_add_item( | void submenu_add_item( | ||||||
|     Submenu* submenu, |     Submenu* submenu, | ||||||
|     const char* label, |     const char* label, | ||||||
|     uint32_t index, |     uint32_t index, | ||||||
| @ -168,8 +167,6 @@ SubmenuItem* submenu_add_item( | |||||||
|             item->callback_context = callback_context; |             item->callback_context = callback_context; | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
| 
 |  | ||||||
|     return item; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void submenu_clean(Submenu* submenu) { | void submenu_clean(Submenu* submenu) { | ||||||
|  | |||||||
| @ -7,7 +7,6 @@ extern "C" { | |||||||
| 
 | 
 | ||||||
| /* Submenu anonymous structure */ | /* Submenu anonymous structure */ | ||||||
| typedef struct Submenu Submenu; | typedef struct Submenu Submenu; | ||||||
| typedef struct SubmenuItem SubmenuItem; |  | ||||||
| typedef void (*SubmenuItemCallback)(void* context, uint32_t index); | typedef void (*SubmenuItemCallback)(void* context, uint32_t index); | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
| @ -36,9 +35,8 @@ View* submenu_get_view(Submenu* submenu); | |||||||
|  * @param index - menu item index, used for callback, may be the same with other items |  * @param index - menu item index, used for callback, may be the same with other items | ||||||
|  * @param callback - menu item callback |  * @param callback - menu item callback | ||||||
|  * @param callback_context - menu item callback context |  * @param callback_context - menu item callback context | ||||||
|  * @return SubmenuItem instance that can be used to modify or delete that item |  | ||||||
|  */ |  */ | ||||||
| SubmenuItem* submenu_add_item( | void submenu_add_item( | ||||||
|     Submenu* submenu, |     Submenu* submenu, | ||||||
|     const char* label, |     const char* label, | ||||||
|     uint32_t index, |     uint32_t index, | ||||||
|  | |||||||
| @ -369,15 +369,7 @@ TextInput* text_input_alloc() { | |||||||
|     view_set_draw_callback(text_input->view, text_input_view_draw_callback); |     view_set_draw_callback(text_input->view, text_input_view_draw_callback); | ||||||
|     view_set_input_callback(text_input->view, text_input_view_input_callback); |     view_set_input_callback(text_input->view, text_input_view_input_callback); | ||||||
| 
 | 
 | ||||||
|     with_view_model( |     text_input_clean(text_input); | ||||||
|         text_input->view, (TextInputModel * model) { |  | ||||||
|             model->text_buffer_size = 0; |  | ||||||
|             model->header = ""; |  | ||||||
|             model->selected_row = 0; |  | ||||||
|             model->selected_column = 0; |  | ||||||
|             model->clear_default_text = false; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|     return text_input; |     return text_input; | ||||||
| } | } | ||||||
| @ -388,6 +380,23 @@ void text_input_free(TextInput* text_input) { | |||||||
|     free(text_input); |     free(text_input); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void text_input_clean(TextInput* text_input) { | ||||||
|  |     furi_assert(text_input); | ||||||
|  |     with_view_model( | ||||||
|  |         text_input->view, (TextInputModel * model) { | ||||||
|  |             model->text_buffer_size = 0; | ||||||
|  |             model->header = ""; | ||||||
|  |             model->selected_row = 0; | ||||||
|  |             model->selected_column = 0; | ||||||
|  |             model->clear_default_text = false; | ||||||
|  |             model->text_buffer = NULL; | ||||||
|  |             model->text_buffer_size = 0; | ||||||
|  |             model->callback = NULL; | ||||||
|  |             model->callback_context = NULL; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| View* text_input_get_view(TextInput* text_input) { | View* text_input_get_view(TextInput* text_input) { | ||||||
|     furi_assert(text_input); |     furi_assert(text_input); | ||||||
|     return text_input->view; |     return text_input->view; | ||||||
| @ -407,6 +416,11 @@ void text_input_set_result_callback( | |||||||
|             model->text_buffer = text_buffer; |             model->text_buffer = text_buffer; | ||||||
|             model->text_buffer_size = text_buffer_size; |             model->text_buffer_size = text_buffer_size; | ||||||
|             model->clear_default_text = clear_default_text; |             model->clear_default_text = clear_default_text; | ||||||
|  |             if(text_buffer && text_buffer[0] != '\0') { | ||||||
|  |                 // Set focus on Save
 | ||||||
|  |                 model->selected_row = 2; | ||||||
|  |                 model->selected_column = 8; | ||||||
|  |             } | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
| } | } | ||||||
|  | |||||||
| @ -9,32 +9,31 @@ extern "C" { | |||||||
| typedef struct TextInput TextInput; | typedef struct TextInput TextInput; | ||||||
| typedef void (*TextInputCallback)(void* context); | typedef void (*TextInputCallback)(void* context); | ||||||
| 
 | 
 | ||||||
| /** 
 | /** Allocate and initialize text input
 | ||||||
|  * @brief Allocate and initialize text input |  * This text input is used to enter string | ||||||
|  *        This text input is used to enter string |  * @return TextInput instance | ||||||
|  *  |  | ||||||
|  */ |  */ | ||||||
| TextInput* text_input_alloc(); | TextInput* text_input_alloc(); | ||||||
| 
 | 
 | ||||||
| /** 
 | /** Deinitialize and free text input
 | ||||||
|  * @brief Deinitialize and free text input |  * @param text_input - TextInput instance | ||||||
|  *  |  | ||||||
|  * @param text_input - Text input instance |  | ||||||
|  */ |  */ | ||||||
| void text_input_free(TextInput* text_input); | void text_input_free(TextInput* text_input); | ||||||
| 
 | 
 | ||||||
| /**
 | /** Clean text input view
 | ||||||
|  * @brief Get text input view |  * Note: this function does not free memory | ||||||
|  *  |  | ||||||
|  * @param text_input - Text input instance |  * @param text_input - Text input instance | ||||||
|  |  */ | ||||||
|  | void text_input_clean(TextInput* text_input); | ||||||
|  | 
 | ||||||
|  | /** Get text input view
 | ||||||
|  |  * @param text_input - TextInput instance | ||||||
|  * @return View instance that can be used for embedding |  * @return View instance that can be used for embedding | ||||||
|  */ |  */ | ||||||
| View* text_input_get_view(TextInput* text_input); | View* text_input_get_view(TextInput* text_input); | ||||||
| 
 | 
 | ||||||
| /**
 | /** Set text input result callback
 | ||||||
|  * @brief Set text input result callback |  * @param text_input - TextInput instance | ||||||
|  *  |  | ||||||
|  * @param text_input - Text input instance |  | ||||||
|  * @param callback - callback fn |  * @param callback - callback fn | ||||||
|  * @param callback_context - callback context |  * @param callback_context - callback context | ||||||
|  * @param text_buffer - pointer to YOUR text buffer, that we going to modify |  * @param text_buffer - pointer to YOUR text buffer, that we going to modify | ||||||
| @ -49,10 +48,8 @@ void text_input_set_result_callback( | |||||||
|     size_t text_buffer_size, |     size_t text_buffer_size, | ||||||
|     bool clear_default_text); |     bool clear_default_text); | ||||||
| 
 | 
 | ||||||
| /** 
 | /** Set text input header text
 | ||||||
|  * @brief Set text input header text |  * @param text_input - TextInput instance | ||||||
|  *  |  | ||||||
|  * @param text input - Text input instance |  | ||||||
|  * @param text - text to be shown |  * @param text - text to be shown | ||||||
|  */ |  */ | ||||||
| void text_input_set_header_text(TextInput* text_input, const char* text); | void text_input_set_header_text(TextInput* text_input, const char* text); | ||||||
|  | |||||||
| @ -144,7 +144,7 @@ void KeyEmulator::start_metakom_emulate(iButtonKey* key) { | |||||||
|     uint8_t* key_data = key->get_data(); |     uint8_t* key_data = key->get_data(); | ||||||
| 
 | 
 | ||||||
|     // start pulse
 |     // start pulse
 | ||||||
|     pulse_data[0] = metakom_period_full * 4; |     pulse_data[0] = metakom_period_full; | ||||||
| 
 | 
 | ||||||
|     // start triplet
 |     // start triplet
 | ||||||
|     set_pulse_data_metakom(pd_index, metakom_period_zero); |     set_pulse_data_metakom(pd_index, metakom_period_zero); | ||||||
|  | |||||||
| @ -116,15 +116,11 @@ bool KeyReader::verify_key(iButtonKeyType key_type, const uint8_t* const data, u | |||||||
| 
 | 
 | ||||||
| void KeyReader::start_comaparator(void) { | void KeyReader::start_comaparator(void) { | ||||||
|     // pulldown lf-rfid pins to prevent interference
 |     // pulldown lf-rfid pins to prevent interference
 | ||||||
|     // TODO open record
 |     hal_gpio_init(&gpio_rfid_pull, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); | ||||||
|     GpioPin rfid_pull_pin = {.port = RFID_PULL_GPIO_Port, .pin = RFID_PULL_Pin}; |     hal_gpio_write(&gpio_rfid_pull, false); | ||||||
|     hal_gpio_init(&rfid_pull_pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); |  | ||||||
|     hal_gpio_write(&rfid_pull_pin, false); |  | ||||||
| 
 | 
 | ||||||
|     // TODO open record
 |     hal_gpio_init(&gpio_rfid_carrier_out, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); | ||||||
|     GpioPin rfid_out_pin = {.port = RFID_OUT_GPIO_Port, .pin = RFID_OUT_Pin}; |     hal_gpio_write(&gpio_rfid_carrier_out, false); | ||||||
|     hal_gpio_init(&rfid_out_pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); |  | ||||||
|     hal_gpio_write(&rfid_out_pin, false); |  | ||||||
| 
 | 
 | ||||||
|     comparator_callback_pointer = |     comparator_callback_pointer = | ||||||
|         cbc::obtain_connector(this, &KeyReader::comparator_trigger_callback); |         cbc::obtain_connector(this, &KeyReader::comparator_trigger_callback); | ||||||
| @ -149,7 +145,7 @@ void KeyReader::comparator_trigger_callback(void* hcomp, void* comp_ctx) { | |||||||
|         _this->metakom_decoder.process_front( |         _this->metakom_decoder.process_front( | ||||||
|             hal_gpio_get_rfid_in_level(), current_dwt_value - last_dwt_value); |             hal_gpio_get_rfid_in_level(), current_dwt_value - last_dwt_value); | ||||||
| 
 | 
 | ||||||
|         last_dwt_value = DWT->CYCCNT; |         last_dwt_value = current_dwt_value; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -54,7 +54,6 @@ void PulseSequencer::init_timer(uint32_t period) { | |||||||
|         Error_Handler(); |         Error_Handler(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     HAL_NVIC_SetPriority(TIM1_UP_TIM16_IRQn, 5, 0); |  | ||||||
|     HAL_NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn); |     HAL_NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn); | ||||||
| 
 | 
 | ||||||
|     hal_gpio_init(&ibutton_gpio, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); |     hal_gpio_init(&ibutton_gpio, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); | ||||||
|  | |||||||
| @ -248,6 +248,8 @@ void onewire_cli_search(Cli* cli) { | |||||||
|     printf("Search started\r\n"); |     printf("Search started\r\n"); | ||||||
| 
 | 
 | ||||||
|     onewire.start(); |     onewire.start(); | ||||||
|  |     furi_hal_power_enable_otg(); | ||||||
|  | 
 | ||||||
|     while(!done) { |     while(!done) { | ||||||
|         if(onewire.search(address, true) != 1) { |         if(onewire.search(address, true) != 1) { | ||||||
|             printf("Search finished\r\n"); |             printf("Search finished\r\n"); | ||||||
| @ -263,6 +265,8 @@ void onewire_cli_search(Cli* cli) { | |||||||
|         } |         } | ||||||
|         delay(100); |         delay(100); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     furi_hal_power_disable_otg(); | ||||||
|     onewire.stop(); |     onewire.stop(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -48,8 +48,7 @@ bool iButtonSceneSaveName::on_event(iButtonApp* app, iButtonEvent* event) { | |||||||
| 
 | 
 | ||||||
| void iButtonSceneSaveName::on_exit(iButtonApp* app) { | void iButtonSceneSaveName::on_exit(iButtonApp* app) { | ||||||
|     TextInput* text_input = app->get_view_manager()->get_text_input(); |     TextInput* text_input = app->get_view_manager()->get_text_input(); | ||||||
|     text_input_set_header_text(text_input, ""); |     text_input_clean(text_input); | ||||||
|     text_input_set_result_callback(text_input, NULL, NULL, NULL, 0, false); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void iButtonSceneSaveName::text_input_callback(void* context) { | void iButtonSceneSaveName::text_input_callback(void* context) { | ||||||
|  | |||||||
							
								
								
									
										280
									
								
								applications/loader/loader.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										280
									
								
								applications/loader/loader.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @ -1,8 +1,11 @@ | |||||||
| #include "loader_i.h" | #include "loader_i.h" | ||||||
| 
 | 
 | ||||||
|  | #define LOADER_THREAD_FLAG_SHOW_MENU (1 << 0) | ||||||
|  | #define LOADER_THREAD_FLAG_ALL (LOADER_THREAD_FLAG_SHOW_MENU) | ||||||
|  | 
 | ||||||
| static Loader* loader_instance = NULL; | static Loader* loader_instance = NULL; | ||||||
| 
 | 
 | ||||||
| static void loader_menu_callback(void* _ctx) { | static void loader_menu_callback(void* _ctx, uint32_t index) { | ||||||
|     const FlipperApplication* flipper_app = _ctx; |     const FlipperApplication* flipper_app = _ctx; | ||||||
| 
 | 
 | ||||||
|     furi_assert(flipper_app->app); |     furi_assert(flipper_app->app); | ||||||
| @ -27,6 +30,11 @@ static void loader_menu_callback(void* _ctx) { | |||||||
|     furi_thread_start(loader_instance->thread); |     furi_thread_start(loader_instance->thread); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void loader_submenu_callback(void* context, uint32_t index) { | ||||||
|  |     uint32_t view_id = (uint32_t)context; | ||||||
|  |     view_dispatcher_switch_to_view(loader_instance->view_dispatcher, view_id); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static void loader_cli_callback(Cli* cli, string_t args, void* _ctx) { | static void loader_cli_callback(Cli* cli, string_t args, void* _ctx) { | ||||||
|     furi_assert(_ctx); |     furi_assert(_ctx); | ||||||
|     const FlipperApplication* flipper_app = (FlipperApplication*)_ctx; |     const FlipperApplication* flipper_app = (FlipperApplication*)_ctx; | ||||||
| @ -60,6 +68,15 @@ bool loader_start(Loader* instance, const char* name, const char* args) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if(!flipper_app) { | ||||||
|  |         for(size_t i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) { | ||||||
|  |             if(strcmp(FLIPPER_DEBUG_APPS[i].name, name) == 0) { | ||||||
|  |                 flipper_app = &FLIPPER_DEBUG_APPS[i]; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     if(!flipper_app) { |     if(!flipper_app) { | ||||||
|         FURI_LOG_E(LOADER_LOG_TAG, "Can't find application with name %s", name); |         FURI_LOG_E(LOADER_LOG_TAG, "Can't find application with name %s", name); | ||||||
|         return false; |         return false; | ||||||
| @ -73,9 +90,11 @@ bool loader_start(Loader* instance, const char* name, const char* args) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     instance->current_app = flipper_app; |     instance->current_app = flipper_app; | ||||||
|  |     void* thread_args = NULL; | ||||||
|     if(args) { |     if(args) { | ||||||
|         string_set_str(instance->args, args); |         string_set_str(instance->args, args); | ||||||
|         string_strim(instance->args); |         string_strim(instance->args); | ||||||
|  |         thread_args = (void*)string_get_cstr(instance->args); | ||||||
|         FURI_LOG_I(LOADER_LOG_TAG, "Start %s app with args: %s", name, args); |         FURI_LOG_I(LOADER_LOG_TAG, "Start %s app with args: %s", name, args); | ||||||
|     } else { |     } else { | ||||||
|         string_clean(instance->args); |         string_clean(instance->args); | ||||||
| @ -84,7 +103,7 @@ bool loader_start(Loader* instance, const char* name, const char* args) { | |||||||
| 
 | 
 | ||||||
|     furi_thread_set_name(instance->thread, flipper_app->name); |     furi_thread_set_name(instance->thread, flipper_app->name); | ||||||
|     furi_thread_set_stack_size(instance->thread, flipper_app->stack_size); |     furi_thread_set_stack_size(instance->thread, flipper_app->stack_size); | ||||||
|     furi_thread_set_context(instance->thread, (void*)string_get_cstr(instance->args)); |     furi_thread_set_context(instance->thread, thread_args); | ||||||
|     furi_thread_set_callback(instance->thread, flipper_app->app); |     furi_thread_set_callback(instance->thread, flipper_app->app); | ||||||
| 
 | 
 | ||||||
|     return furi_thread_start(instance->thread); |     return furi_thread_start(instance->thread); | ||||||
| @ -138,6 +157,14 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static uint32_t loader_hide_menu(void* context) { | ||||||
|  |     return VIEW_NONE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static uint32_t loader_back_to_primary_menu(void* context) { | ||||||
|  |     return LoaderMenuViewPrimary; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static Loader* loader_alloc() { | static Loader* loader_alloc() { | ||||||
|     Loader* instance = furi_alloc(sizeof(Loader)); |     Loader* instance = furi_alloc(sizeof(Loader)); | ||||||
| 
 | 
 | ||||||
| @ -150,10 +177,45 @@ static Loader* loader_alloc() { | |||||||
| 
 | 
 | ||||||
|     instance->mutex = osMutexNew(NULL); |     instance->mutex = osMutexNew(NULL); | ||||||
| 
 | 
 | ||||||
|     instance->menu_vm = furi_record_open("menu"); |  | ||||||
| 
 |  | ||||||
|     instance->cli = furi_record_open("cli"); |     instance->cli = furi_record_open("cli"); | ||||||
| 
 | 
 | ||||||
|  |     instance->loader_thread = osThreadGetId(); | ||||||
|  | 
 | ||||||
|  |     // Gui
 | ||||||
|  |     instance->gui = furi_record_open("gui"); | ||||||
|  |     instance->view_dispatcher = view_dispatcher_alloc(); | ||||||
|  |     view_dispatcher_attach_to_gui( | ||||||
|  |         instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen); | ||||||
|  |     // Primary menu
 | ||||||
|  |     instance->primary_menu = menu_alloc(); | ||||||
|  |     view_set_previous_callback(menu_get_view(instance->primary_menu), loader_hide_menu); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         instance->view_dispatcher, LoaderMenuViewPrimary, menu_get_view(instance->primary_menu)); | ||||||
|  |     // Plugins menu
 | ||||||
|  |     instance->plugins_menu = submenu_alloc(); | ||||||
|  |     view_set_previous_callback( | ||||||
|  |         submenu_get_view(instance->plugins_menu), loader_back_to_primary_menu); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         instance->view_dispatcher, | ||||||
|  |         LoaderMenuViewPlugins, | ||||||
|  |         submenu_get_view(instance->plugins_menu)); | ||||||
|  |     // Debug menu
 | ||||||
|  |     instance->debug_menu = submenu_alloc(); | ||||||
|  |     view_set_previous_callback( | ||||||
|  |         submenu_get_view(instance->debug_menu), loader_back_to_primary_menu); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         instance->view_dispatcher, LoaderMenuViewDebug, submenu_get_view(instance->debug_menu)); | ||||||
|  |     // Settings menu
 | ||||||
|  |     instance->settings_menu = submenu_alloc(); | ||||||
|  |     view_set_previous_callback( | ||||||
|  |         submenu_get_view(instance->settings_menu), loader_back_to_primary_menu); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         instance->view_dispatcher, | ||||||
|  |         LoaderMenuViewSettings, | ||||||
|  |         submenu_get_view(instance->settings_menu)); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_enable_queue(instance->view_dispatcher); | ||||||
|  | 
 | ||||||
|     return instance; |     return instance; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -162,133 +224,111 @@ static void loader_free(Loader* instance) { | |||||||
| 
 | 
 | ||||||
|     furi_record_close("cli"); |     furi_record_close("cli"); | ||||||
| 
 | 
 | ||||||
|     furi_record_close("menu"); |  | ||||||
| 
 |  | ||||||
|     osMutexDelete(instance->mutex); |     osMutexDelete(instance->mutex); | ||||||
| 
 | 
 | ||||||
|     string_clear(instance->args); |     string_clear(instance->args); | ||||||
| 
 | 
 | ||||||
|     furi_thread_free(instance->thread); |     furi_thread_free(instance->thread); | ||||||
| 
 | 
 | ||||||
|  |     menu_free(loader_instance->primary_menu); | ||||||
|  |     view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPrimary); | ||||||
|  |     submenu_free(loader_instance->plugins_menu); | ||||||
|  |     view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPlugins); | ||||||
|  |     submenu_free(loader_instance->debug_menu); | ||||||
|  |     view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewDebug); | ||||||
|  |     submenu_free(loader_instance->settings_menu); | ||||||
|  |     view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewSettings); | ||||||
|  |     view_dispatcher_free(loader_instance->view_dispatcher); | ||||||
|  | 
 | ||||||
|  |     furi_record_close("gui"); | ||||||
|  | 
 | ||||||
|     free(instance); |     free(instance); | ||||||
|  |     instance = NULL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void loader_add_cli_command(FlipperApplication* app) { | ||||||
|  |     string_t cli_name; | ||||||
|  |     string_init_printf(cli_name, "app_%s", app->name); | ||||||
|  |     cli_add_command( | ||||||
|  |         loader_instance->cli, | ||||||
|  |         string_get_cstr(cli_name), | ||||||
|  |         CliCommandFlagDefault, | ||||||
|  |         loader_cli_callback, | ||||||
|  |         app); | ||||||
|  |     string_clear(cli_name); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void loader_build_menu() { | static void loader_build_menu() { | ||||||
|     FURI_LOG_I(LOADER_LOG_TAG, "Building main menu"); |     FURI_LOG_I(LOADER_LOG_TAG, "Building main menu"); | ||||||
|     with_value_mutex( |     size_t i; | ||||||
|         loader_instance->menu_vm, (Menu * menu) { |     for(i = 0; i < FLIPPER_APPS_COUNT; i++) { | ||||||
|             for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { |         loader_add_cli_command((FlipperApplication*)&FLIPPER_APPS[i]); | ||||||
|                 // Add menu item
 |         menu_add_item( | ||||||
|                 menu_item_add( |             loader_instance->primary_menu, | ||||||
|                     menu, |             FLIPPER_APPS[i].name, | ||||||
|                     menu_item_alloc_function( |             FLIPPER_APPS[i].icon ? icon_animation_alloc(FLIPPER_APPS[i].icon) : NULL, | ||||||
|                         FLIPPER_APPS[i].name, |             i, | ||||||
|                         FLIPPER_APPS[i].icon ? icon_animation_alloc(FLIPPER_APPS[i].icon) : NULL, |             loader_menu_callback, | ||||||
|                         loader_menu_callback, |             (void*)&FLIPPER_APPS[i]); | ||||||
|                         (void*)&FLIPPER_APPS[i])); |     } | ||||||
| 
 |     menu_add_item( | ||||||
|                 // Add cli command
 |         loader_instance->primary_menu, | ||||||
|                 string_t cli_name; |         "Plugins", | ||||||
|                 string_init_set_str(cli_name, "app_"); |         icon_animation_alloc(&A_Plugins_14), | ||||||
|                 string_cat_str(cli_name, FLIPPER_APPS[i].name); |         i++, | ||||||
|                 cli_add_command( |         loader_submenu_callback, | ||||||
|                     loader_instance->cli, |         (void*)LoaderMenuViewPlugins); | ||||||
|                     string_get_cstr(cli_name), |     menu_add_item( | ||||||
|                     CliCommandFlagDefault, |         loader_instance->primary_menu, | ||||||
|                     loader_cli_callback, |         "Debug tools", | ||||||
|                     (void*)&FLIPPER_APPS[i]); |         icon_animation_alloc(&A_Debug_14), | ||||||
|                 string_clear(cli_name); |         i++, | ||||||
|             } |         loader_submenu_callback, | ||||||
|         }); |         (void*)LoaderMenuViewDebug); | ||||||
|  |     menu_add_item( | ||||||
|  |         loader_instance->primary_menu, | ||||||
|  |         "Settings", | ||||||
|  |         icon_animation_alloc(&A_Settings_14), | ||||||
|  |         i++, | ||||||
|  |         loader_submenu_callback, | ||||||
|  |         (void*)LoaderMenuViewSettings); | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I(LOADER_LOG_TAG, "Building plugins menu"); |     FURI_LOG_I(LOADER_LOG_TAG, "Building plugins menu"); | ||||||
|     with_value_mutex( |     for(i = 0; i < FLIPPER_PLUGINS_COUNT; i++) { | ||||||
|         loader_instance->menu_vm, (Menu * menu) { |         loader_add_cli_command((FlipperApplication*)&FLIPPER_PLUGINS[i]); | ||||||
|             MenuItem* menu_plugins = |         submenu_add_item( | ||||||
|                 menu_item_alloc_menu("Plugins", icon_animation_alloc(&A_Plugins_14)); |             loader_instance->plugins_menu, | ||||||
| 
 |             FLIPPER_PLUGINS[i].name, | ||||||
|             for(size_t i = 0; i < FLIPPER_PLUGINS_COUNT; i++) { |             i, | ||||||
|                 // Add menu item
 |             loader_menu_callback, | ||||||
|                 menu_item_subitem_add( |             (void*)&FLIPPER_PLUGINS[i]); | ||||||
|                     menu_plugins, |     } | ||||||
|                     menu_item_alloc_function( |  | ||||||
|                         FLIPPER_PLUGINS[i].name, |  | ||||||
|                         FLIPPER_PLUGINS[i].icon ? icon_animation_alloc(FLIPPER_PLUGINS[i].icon) : |  | ||||||
|                                                   NULL, |  | ||||||
|                         loader_menu_callback, |  | ||||||
|                         (void*)&FLIPPER_PLUGINS[i])); |  | ||||||
| 
 |  | ||||||
|                 // Add cli command
 |  | ||||||
|                 string_t cli_name; |  | ||||||
|                 string_init_set_str(cli_name, "app_"); |  | ||||||
|                 string_cat_str(cli_name, FLIPPER_PLUGINS[i].name); |  | ||||||
|                 cli_add_command( |  | ||||||
|                     loader_instance->cli, |  | ||||||
|                     string_get_cstr(cli_name), |  | ||||||
|                     CliCommandFlagDefault, |  | ||||||
|                     loader_cli_callback, |  | ||||||
|                     (void*)&FLIPPER_PLUGINS[i]); |  | ||||||
|                 string_clear(cli_name); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             menu_item_add(menu, menu_plugins); |  | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I(LOADER_LOG_TAG, "Building debug menu"); |     FURI_LOG_I(LOADER_LOG_TAG, "Building debug menu"); | ||||||
|     with_value_mutex( |     for(i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) { | ||||||
|         loader_instance->menu_vm, (Menu * menu) { |         loader_add_cli_command((FlipperApplication*)&FLIPPER_DEBUG_APPS[i]); | ||||||
|             MenuItem* menu_debug = |         submenu_add_item( | ||||||
|                 menu_item_alloc_menu("Debug tools", icon_animation_alloc(&A_Debug_14)); |             loader_instance->debug_menu, | ||||||
| 
 |             FLIPPER_DEBUG_APPS[i].name, | ||||||
|             for(size_t i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) { |             i, | ||||||
|                 // Add menu item
 |             loader_menu_callback, | ||||||
|                 menu_item_subitem_add( |             (void*)&FLIPPER_DEBUG_APPS[i]); | ||||||
|                     menu_debug, |     } | ||||||
|                     menu_item_alloc_function( |  | ||||||
|                         FLIPPER_DEBUG_APPS[i].name, |  | ||||||
|                         FLIPPER_DEBUG_APPS[i].icon ? |  | ||||||
|                             icon_animation_alloc(FLIPPER_DEBUG_APPS[i].icon) : |  | ||||||
|                             NULL, |  | ||||||
|                         loader_menu_callback, |  | ||||||
|                         (void*)&FLIPPER_DEBUG_APPS[i])); |  | ||||||
| 
 |  | ||||||
|                 // Add cli command
 |  | ||||||
|                 string_t cli_name; |  | ||||||
|                 string_init_set_str(cli_name, "app_"); |  | ||||||
|                 string_cat_str(cli_name, FLIPPER_DEBUG_APPS[i].name); |  | ||||||
|                 cli_add_command( |  | ||||||
|                     loader_instance->cli, |  | ||||||
|                     string_get_cstr(cli_name), |  | ||||||
|                     CliCommandFlagDefault, |  | ||||||
|                     loader_cli_callback, |  | ||||||
|                     (void*)&FLIPPER_DEBUG_APPS[i]); |  | ||||||
|                 string_clear(cli_name); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             menu_item_add(menu, menu_debug); |  | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I(LOADER_LOG_TAG, "Building settings menu"); |     FURI_LOG_I(LOADER_LOG_TAG, "Building settings menu"); | ||||||
|     with_value_mutex( |     for(i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { | ||||||
|         loader_instance->menu_vm, (Menu * menu) { |         submenu_add_item( | ||||||
|             MenuItem* menu_debug = |             loader_instance->settings_menu, | ||||||
|                 menu_item_alloc_menu("Settings", icon_animation_alloc(&A_Settings_14)); |             FLIPPER_SETTINGS_APPS[i].name, | ||||||
|  |             i, | ||||||
|  |             loader_menu_callback, | ||||||
|  |             (void*)&FLIPPER_SETTINGS_APPS[i]); | ||||||
|  |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|             for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { | void loader_show_menu() { | ||||||
|                 // Add menu item
 |     furi_assert(loader_instance); | ||||||
|                 menu_item_subitem_add( |     osThreadFlagsSet(loader_instance->loader_thread, LOADER_THREAD_FLAG_SHOW_MENU); | ||||||
|                     menu_debug, |  | ||||||
|                     menu_item_alloc_function( |  | ||||||
|                         FLIPPER_SETTINGS_APPS[i].name, |  | ||||||
|                         FLIPPER_SETTINGS_APPS[i].icon ? |  | ||||||
|                             icon_animation_alloc(FLIPPER_SETTINGS_APPS[i].icon) : |  | ||||||
|                             NULL, |  | ||||||
|                         loader_menu_callback, |  | ||||||
|                         (void*)&FLIPPER_SETTINGS_APPS[i])); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             menu_item_add(menu, menu_debug); |  | ||||||
|         }); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int32_t loader_srv(void* p) { | int32_t loader_srv(void* p) { | ||||||
| @ -300,15 +340,25 @@ int32_t loader_srv(void* p) { | |||||||
| 
 | 
 | ||||||
|     // Call on start hooks
 |     // Call on start hooks
 | ||||||
|     for(size_t i = 0; i < FLIPPER_ON_SYSTEM_START_COUNT; i++) { |     for(size_t i = 0; i < FLIPPER_ON_SYSTEM_START_COUNT; i++) { | ||||||
|         (*FLIPPER_ON_SYSTEM_START[i])(); |         FLIPPER_ON_SYSTEM_START[i](); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I(LOADER_LOG_TAG, "Started"); |     FURI_LOG_I(LOADER_LOG_TAG, "Started"); | ||||||
| 
 | 
 | ||||||
|     furi_record_create("loader", loader_instance); |     furi_record_create("loader", loader_instance); | ||||||
| 
 | 
 | ||||||
|  | #ifdef LOADER_AUTOSTART | ||||||
|  |     loader_start(loader_instance, LOADER_AUTOSTART, NULL); | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|     while(1) { |     while(1) { | ||||||
|         osThreadSuspend(osThreadGetId()); |         uint32_t flags = osThreadFlagsWait(LOADER_THREAD_FLAG_ALL, osFlagsWaitAny, osWaitForever); | ||||||
|  |         if(flags & LOADER_THREAD_FLAG_SHOW_MENU) { | ||||||
|  |             menu_set_selected_item(loader_instance->primary_menu, 0); | ||||||
|  |             view_dispatcher_switch_to_view( | ||||||
|  |                 loader_instance->view_dispatcher, LoaderMenuViewPrimary); | ||||||
|  |             view_dispatcher_run(loader_instance->view_dispatcher); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     loader_free(loader_instance); |     loader_free(loader_instance); | ||||||
|  | |||||||
| @ -18,3 +18,6 @@ bool loader_lock(Loader* instance); | |||||||
| 
 | 
 | ||||||
| /** Unlock application start */ | /** Unlock application start */ | ||||||
| void loader_unlock(Loader* instance); | void loader_unlock(Loader* instance); | ||||||
|  | 
 | ||||||
|  | /** Show primary loader */ | ||||||
|  | void loader_show_menu(); | ||||||
|  | |||||||
| @ -3,20 +3,39 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi-hal.h> | #include <furi-hal.h> | ||||||
| #include <cli/cli.h> | #include <cli/cli.h> | ||||||
| #include <menu/menu.h> | 
 | ||||||
| #include <menu/menu_item.h> | #include <gui/view_dispatcher.h> | ||||||
|  | 
 | ||||||
|  | #include <gui/modules/menu.h> | ||||||
|  | #include <gui/modules/submenu.h> | ||||||
|  | 
 | ||||||
| #include <applications.h> | #include <applications.h> | ||||||
| #include <assets_icons.h> | #include <assets_icons.h> | ||||||
| 
 | 
 | ||||||
| #define LOADER_LOG_TAG "loader" | #define LOADER_LOG_TAG "loader" | ||||||
| 
 | 
 | ||||||
| struct Loader { | struct Loader { | ||||||
|  |     osThreadId_t loader_thread; | ||||||
|     FuriThread* thread; |     FuriThread* thread; | ||||||
|     const FlipperApplication* current_app; |     const FlipperApplication* current_app; | ||||||
|     string_t args; |     string_t args; | ||||||
|     Cli* cli; |     Cli* cli; | ||||||
|     ValueMutex* menu_vm; |     Gui* gui; | ||||||
|  | 
 | ||||||
|  |     ViewDispatcher* view_dispatcher; | ||||||
|  |     Menu* primary_menu; | ||||||
|  |     Submenu* plugins_menu; | ||||||
|  |     Submenu* debug_menu; | ||||||
|  |     Submenu* settings_menu; | ||||||
|  | 
 | ||||||
|     size_t free_heap_size; |     size_t free_heap_size; | ||||||
|     osMutexId_t mutex; |     osMutexId_t mutex; | ||||||
|     volatile uint8_t lock_semaphore; |     volatile uint8_t lock_semaphore; | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     LoaderMenuViewPrimary, | ||||||
|  |     LoaderMenuViewPlugins, | ||||||
|  |     LoaderMenuViewDebug, | ||||||
|  |     LoaderMenuViewSettings, | ||||||
|  | } LoaderMenuView; | ||||||
|  | |||||||
| @ -1,337 +0,0 @@ | |||||||
| #include "menu.h" |  | ||||||
| #include <stdio.h> |  | ||||||
| #include <stdbool.h> |  | ||||||
| 
 |  | ||||||
| #include <furi.h> |  | ||||||
| #include <gui/gui.h> |  | ||||||
| #include <gui/elements.h> |  | ||||||
| 
 |  | ||||||
| #include "menu_event.h" |  | ||||||
| #include "menu_item.h" |  | ||||||
| #include <assets_icons.h> |  | ||||||
| 
 |  | ||||||
| struct Menu { |  | ||||||
|     MenuEvent* event; |  | ||||||
| 
 |  | ||||||
|     // GUI
 |  | ||||||
|     Gui* gui; |  | ||||||
|     ViewPort* view_port; |  | ||||||
|     IconAnimation* icon; |  | ||||||
| 
 |  | ||||||
|     // State
 |  | ||||||
|     MenuItem* root; |  | ||||||
|     MenuItem* settings; |  | ||||||
|     MenuItem* current; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| void menu_view_port_callback(Canvas* canvas, void* context); |  | ||||||
| 
 |  | ||||||
| ValueMutex* menu_init() { |  | ||||||
|     Menu* menu = furi_alloc(sizeof(Menu)); |  | ||||||
| 
 |  | ||||||
|     // Event dispatcher
 |  | ||||||
|     menu->event = menu_event_alloc(); |  | ||||||
| 
 |  | ||||||
|     ValueMutex* menu_mutex = furi_alloc(sizeof(ValueMutex)); |  | ||||||
|     if(menu_mutex == NULL || !init_mutex(menu_mutex, menu, sizeof(Menu))) { |  | ||||||
|         printf("[menu_task] cannot create menu mutex\r\n"); |  | ||||||
|         furi_crash(NULL); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // OpenGui record
 |  | ||||||
|     menu->gui = furi_record_open("gui"); |  | ||||||
| 
 |  | ||||||
|     // Allocate and configure view_port
 |  | ||||||
|     menu->view_port = view_port_alloc(); |  | ||||||
| 
 |  | ||||||
|     // Open GUI and register fullscreen view_port
 |  | ||||||
|     gui_add_view_port(menu->gui, menu->view_port, GuiLayerFullscreen); |  | ||||||
| 
 |  | ||||||
|     view_port_enabled_set(menu->view_port, false); |  | ||||||
|     view_port_draw_callback_set(menu->view_port, menu_view_port_callback, menu_mutex); |  | ||||||
|     view_port_input_callback_set(menu->view_port, menu_event_input_callback, menu->event); |  | ||||||
| 
 |  | ||||||
|     return menu_mutex; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_build_main(Menu* menu) { |  | ||||||
|     furi_assert(menu); |  | ||||||
|     // Root point
 |  | ||||||
|     menu->root = menu_item_alloc_menu(NULL, NULL); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_item_add(Menu* menu, MenuItem* item) { |  | ||||||
|     menu_item_subitem_add(menu->root, item); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_settings_item_add(Menu* menu, MenuItem* item) { |  | ||||||
|     menu_item_subitem_add(menu->settings, item); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_draw_primary(Menu* menu, Canvas* canvas) { |  | ||||||
|     size_t position = menu_item_get_position(menu->current); |  | ||||||
|     MenuItemArray_t* items = menu_item_get_subitems(menu->current); |  | ||||||
|     size_t items_count = MenuItemArray_size(*items); |  | ||||||
|     if(items_count) { |  | ||||||
|         MenuItem* item; |  | ||||||
|         size_t shift_position; |  | ||||||
|         // First line
 |  | ||||||
|         canvas_set_font(canvas, FontSecondary); |  | ||||||
|         shift_position = (0 + position + items_count - 1) % (MenuItemArray_size(*items)); |  | ||||||
|         item = *MenuItemArray_get(*items, shift_position); |  | ||||||
|         canvas_draw_icon_animation(canvas, 4, 3, menu_item_get_icon(item)); |  | ||||||
|         canvas_draw_str(canvas, 22, 14, menu_item_get_label(item)); |  | ||||||
|         // Second line main
 |  | ||||||
|         canvas_set_font(canvas, FontPrimary); |  | ||||||
|         shift_position = (1 + position + items_count - 1) % (MenuItemArray_size(*items)); |  | ||||||
|         item = *MenuItemArray_get(*items, shift_position); |  | ||||||
|         canvas_draw_icon_animation(canvas, 4, 25, menu_item_get_icon(item)); |  | ||||||
|         canvas_draw_str(canvas, 22, 36, menu_item_get_label(item)); |  | ||||||
|         // Third line
 |  | ||||||
|         canvas_set_font(canvas, FontSecondary); |  | ||||||
|         shift_position = (2 + position + items_count - 1) % (MenuItemArray_size(*items)); |  | ||||||
|         item = *MenuItemArray_get(*items, shift_position); |  | ||||||
|         canvas_draw_icon_animation(canvas, 4, 47, menu_item_get_icon(item)); |  | ||||||
|         canvas_draw_str(canvas, 22, 58, menu_item_get_label(item)); |  | ||||||
|         // Frame and scrollbar
 |  | ||||||
|         // elements_frame(canvas, 0, 0, 128 - 5, 21);
 |  | ||||||
|         elements_frame(canvas, 0, 21, 128 - 5, 21); |  | ||||||
|         // elements_frame(canvas, 0, 42, 128 - 5, 21);
 |  | ||||||
|         elements_scrollbar(canvas, position, items_count); |  | ||||||
|     } else { |  | ||||||
|         canvas_draw_str(canvas, 2, 32, "Empty"); |  | ||||||
|         elements_scrollbar(canvas, 0, 0); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_draw_secondary(Menu* menu, Canvas* canvas) { |  | ||||||
|     size_t position = 0; |  | ||||||
|     size_t selected_position = menu_item_get_position(menu->current); |  | ||||||
|     size_t window_position = menu_item_get_window_position(menu->current); |  | ||||||
|     MenuItemArray_t* items = menu_item_get_subitems(menu->current); |  | ||||||
|     const uint8_t items_on_screen = 4; |  | ||||||
|     const uint8_t item_height = 16; |  | ||||||
|     const uint8_t item_width = 123; |  | ||||||
|     size_t items_count = MenuItemArray_size(*items); |  | ||||||
|     MenuItemArray_it_t it; |  | ||||||
| 
 |  | ||||||
|     canvas_set_font(canvas, FontSecondary); |  | ||||||
|     for(MenuItemArray_it(it, *items); !MenuItemArray_end_p(it); MenuItemArray_next(it)) { |  | ||||||
|         size_t item_position = position - window_position; |  | ||||||
| 
 |  | ||||||
|         if(item_position < items_on_screen) { |  | ||||||
|             if(position == selected_position) { |  | ||||||
|                 canvas_set_color(canvas, ColorBlack); |  | ||||||
|                 elements_slightly_rounded_box( |  | ||||||
|                     canvas, 0, (item_position * item_height) + 1, item_width, item_height - 2); |  | ||||||
|                 canvas_set_color(canvas, ColorWhite); |  | ||||||
|             } else { |  | ||||||
|                 canvas_set_color(canvas, ColorBlack); |  | ||||||
|             } |  | ||||||
|             canvas_draw_str( |  | ||||||
|                 canvas, |  | ||||||
|                 6, |  | ||||||
|                 (item_position * item_height) + item_height - 4, |  | ||||||
|                 menu_item_get_label(*MenuItemArray_ref(it))); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         position++; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     elements_scrollbar(canvas, selected_position, items_count); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_view_port_callback(Canvas* canvas, void* context) { |  | ||||||
|     furi_assert(canvas); |  | ||||||
|     furi_assert(context); |  | ||||||
| 
 |  | ||||||
|     Menu* menu = acquire_mutex((ValueMutex*)context, 100); // wait 10 ms to get mutex
 |  | ||||||
|     if(menu == NULL) return; // redraw fail
 |  | ||||||
| 
 |  | ||||||
|     furi_assert(menu->current); |  | ||||||
| 
 |  | ||||||
|     canvas_clear(canvas); |  | ||||||
|     canvas_set_color(canvas, ColorBlack); |  | ||||||
| 
 |  | ||||||
|     // if top level
 |  | ||||||
|     if(menu_item_get_parent(menu->current) == NULL) { |  | ||||||
|         menu_draw_primary(menu, canvas); |  | ||||||
|     } else { |  | ||||||
|         menu_draw_secondary(menu, canvas); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     release_mutex((ValueMutex*)context, menu); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_set_icon(Menu* menu, IconAnimation* icon) { |  | ||||||
|     furi_assert(menu); |  | ||||||
| 
 |  | ||||||
|     if(menu->icon) { |  | ||||||
|         icon_animation_stop(menu->icon); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     menu->icon = icon; |  | ||||||
| 
 |  | ||||||
|     if(menu->icon) { |  | ||||||
|         icon_animation_start(menu->icon); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_update(Menu* menu) { |  | ||||||
|     furi_assert(menu); |  | ||||||
| 
 |  | ||||||
|     if(menu->current) { |  | ||||||
|         size_t position = menu_item_get_position(menu->current); |  | ||||||
|         MenuItemArray_t* items = menu_item_get_subitems(menu->current); |  | ||||||
|         size_t items_count = MenuItemArray_size(*items); |  | ||||||
|         if(items_count) { |  | ||||||
|             MenuItem* item = *MenuItemArray_get(*items, position); |  | ||||||
|             menu_set_icon(menu, menu_item_get_icon(item)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     menu_event_activity_notify(menu->event); |  | ||||||
|     view_port_update(menu->view_port); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_up(Menu* menu) { |  | ||||||
|     furi_assert(menu); |  | ||||||
|     size_t position = menu_item_get_position(menu->current); |  | ||||||
|     size_t window_position = menu_item_get_window_position(menu->current); |  | ||||||
|     MenuItemArray_t* items = menu_item_get_subitems(menu->current); |  | ||||||
| 
 |  | ||||||
|     const uint8_t items_on_screen = 4; |  | ||||||
| 
 |  | ||||||
|     if(position > 0) { |  | ||||||
|         position--; |  | ||||||
|         if(((position - window_position) < 1) && window_position > 0) { |  | ||||||
|             window_position--; |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         position = MenuItemArray_size(*items) - 1; |  | ||||||
|         if(position > (items_on_screen - 1)) { |  | ||||||
|             window_position = position - (items_on_screen - 1); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     menu_item_set_position(menu->current, position); |  | ||||||
|     menu_item_set_window_position(menu->current, window_position); |  | ||||||
|     menu_update(menu); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_down(Menu* menu) { |  | ||||||
|     furi_assert(menu); |  | ||||||
|     size_t position = menu_item_get_position(menu->current); |  | ||||||
|     size_t window_position = menu_item_get_window_position(menu->current); |  | ||||||
|     MenuItemArray_t* items = menu_item_get_subitems(menu->current); |  | ||||||
| 
 |  | ||||||
|     const uint8_t items_on_screen = 4; |  | ||||||
|     if(position < (MenuItemArray_size(*items) - 1)) { |  | ||||||
|         position++; |  | ||||||
|         if((position - window_position) > (items_on_screen - 2) && |  | ||||||
|            window_position < (MenuItemArray_size(*items) - items_on_screen)) { |  | ||||||
|             window_position++; |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         position = 0; |  | ||||||
|         window_position = 0; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     menu_item_set_position(menu->current, position); |  | ||||||
|     menu_item_set_window_position(menu->current, window_position); |  | ||||||
|     menu_update(menu); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_ok(Menu* menu) { |  | ||||||
|     furi_assert(menu); |  | ||||||
| 
 |  | ||||||
|     if(!menu->current) { |  | ||||||
|         view_port_enabled_set(menu->view_port, true); |  | ||||||
|         menu->current = menu->root; |  | ||||||
|         menu_item_set_position(menu->current, 0); |  | ||||||
|         menu_item_set_window_position(menu->current, 0); |  | ||||||
|         menu_update(menu); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     MenuItemArray_t* items = menu_item_get_subitems(menu->current); |  | ||||||
|     if(!items || MenuItemArray_size(*items) == 0) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     size_t position = menu_item_get_position(menu->current); |  | ||||||
|     MenuItem* item = *MenuItemArray_get(*items, position); |  | ||||||
|     MenuItemType type = menu_item_get_type(item); |  | ||||||
| 
 |  | ||||||
|     if(type == MenuItemTypeMenu) { |  | ||||||
|         menu->current = item; |  | ||||||
|         menu_item_set_position(menu->current, 0); |  | ||||||
|         menu_item_set_window_position(menu->current, 0); |  | ||||||
|         menu_update(menu); |  | ||||||
|     } else if(type == MenuItemTypeFunction) { |  | ||||||
|         menu_item_function_call(item); |  | ||||||
|         gui_send_view_port_back(menu->gui, menu->view_port); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_back(Menu* menu) { |  | ||||||
|     furi_assert(menu); |  | ||||||
|     MenuItem* parent = menu_item_get_parent(menu->current); |  | ||||||
|     if(parent) { |  | ||||||
|         menu->current = parent; |  | ||||||
|         menu_update(menu); |  | ||||||
|     } else { |  | ||||||
|         menu_exit(menu); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_exit(Menu* menu) { |  | ||||||
|     furi_assert(menu); |  | ||||||
|     view_port_enabled_set(menu->view_port, false); |  | ||||||
|     menu->current = NULL; |  | ||||||
|     menu_update(menu); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| int32_t menu_srv(void* p) { |  | ||||||
|     ValueMutex* menu_mutex = menu_init(); |  | ||||||
| 
 |  | ||||||
|     MenuEvent* menu_event = NULL; |  | ||||||
|     { |  | ||||||
|         Menu* menu = acquire_mutex_block(menu_mutex); |  | ||||||
|         furi_check(menu); |  | ||||||
| 
 |  | ||||||
|         menu_build_main(menu); |  | ||||||
| 
 |  | ||||||
|         // immutable thread-safe object
 |  | ||||||
|         menu_event = menu->event; |  | ||||||
| 
 |  | ||||||
|         release_mutex(menu_mutex, menu); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     furi_record_create("menu", menu_mutex); |  | ||||||
| 
 |  | ||||||
|     while(1) { |  | ||||||
|         MenuMessage m = menu_event_next(menu_event); |  | ||||||
| 
 |  | ||||||
|         Menu* menu = acquire_mutex_block(menu_mutex); |  | ||||||
| 
 |  | ||||||
|         if(!menu->current && m.type != MenuMessageTypeOk) { |  | ||||||
|         } else if(m.type == MenuMessageTypeUp) { |  | ||||||
|             menu_up(menu); |  | ||||||
|         } else if(m.type == MenuMessageTypeDown) { |  | ||||||
|             menu_down(menu); |  | ||||||
|         } else if(m.type == MenuMessageTypeOk) { |  | ||||||
|             menu_ok(menu); |  | ||||||
|         } else if(m.type == MenuMessageTypeBack) { |  | ||||||
|             menu_back(menu); |  | ||||||
|         } else if(m.type == MenuMessageTypeIdle) { |  | ||||||
|             menu_exit(menu); |  | ||||||
|         } else { |  | ||||||
|             // TODO: fail somehow?
 |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         release_mutex(menu_mutex, menu); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return 0; |  | ||||||
| } |  | ||||||
| @ -1,19 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include "menu/menu_item.h" |  | ||||||
| 
 |  | ||||||
| typedef struct Menu Menu; |  | ||||||
| typedef struct MenuItem MenuItem; |  | ||||||
| 
 |  | ||||||
| // Add menu item to root menu
 |  | ||||||
| void menu_item_add(Menu* menu, MenuItem* item); |  | ||||||
| 
 |  | ||||||
| // Add menu item to settings menu
 |  | ||||||
| void menu_settings_item_add(Menu* menu, MenuItem* item); |  | ||||||
| 
 |  | ||||||
| // Menu controls
 |  | ||||||
| void menu_up(Menu* menu); |  | ||||||
| void menu_down(Menu* menu); |  | ||||||
| void menu_ok(Menu* menu); |  | ||||||
| void menu_back(Menu* menu); |  | ||||||
| void menu_exit(Menu* menu); |  | ||||||
| @ -1,65 +0,0 @@ | |||||||
| #include "menu_event.h" |  | ||||||
| 
 |  | ||||||
| #include <string.h> |  | ||||||
| #include <stdlib.h> |  | ||||||
| 
 |  | ||||||
| #include <furi.h> |  | ||||||
| 
 |  | ||||||
| #define MENU_MESSAGE_MQUEUE_SIZE 8 |  | ||||||
| 
 |  | ||||||
| struct MenuEvent { |  | ||||||
|     osMessageQueueId_t mqueue; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| void MenuEventimeout_callback(void* arg) { |  | ||||||
|     MenuEvent* menu_event = arg; |  | ||||||
|     MenuMessage message; |  | ||||||
|     message.type = MenuMessageTypeIdle; |  | ||||||
|     osMessageQueuePut(menu_event->mqueue, &message, 0, osWaitForever); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| MenuEvent* menu_event_alloc() { |  | ||||||
|     MenuEvent* menu_event = furi_alloc(sizeof(MenuEvent)); |  | ||||||
|     menu_event->mqueue = osMessageQueueNew(MENU_MESSAGE_MQUEUE_SIZE, sizeof(MenuMessage), NULL); |  | ||||||
|     furi_check(menu_event->mqueue); |  | ||||||
|     return menu_event; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_event_free(MenuEvent* menu_event) { |  | ||||||
|     furi_assert(menu_event); |  | ||||||
|     furi_check(osMessageQueueDelete(menu_event->mqueue) == osOK); |  | ||||||
|     free(menu_event); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_event_activity_notify(MenuEvent* menu_event) { |  | ||||||
|     furi_assert(menu_event); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| MenuMessage menu_event_next(MenuEvent* menu_event) { |  | ||||||
|     furi_assert(menu_event); |  | ||||||
|     MenuMessage message; |  | ||||||
|     while(osMessageQueueGet(menu_event->mqueue, &message, NULL, osWaitForever) != osOK) { |  | ||||||
|     }; |  | ||||||
|     return message; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_event_input_callback(InputEvent* input_event, void* context) { |  | ||||||
|     MenuEvent* menu_event = context; |  | ||||||
|     MenuMessage message; |  | ||||||
| 
 |  | ||||||
|     if(input_event->type != InputTypeShort) return; |  | ||||||
| 
 |  | ||||||
|     if(input_event->key == InputKeyUp) { |  | ||||||
|         message.type = MenuMessageTypeUp; |  | ||||||
|     } else if(input_event->key == InputKeyDown) { |  | ||||||
|         message.type = MenuMessageTypeDown; |  | ||||||
|     } else if(input_event->key == InputKeyOk) { |  | ||||||
|         message.type = MenuMessageTypeOk; |  | ||||||
|     } else if(input_event->key == InputKeyBack) { |  | ||||||
|         message.type = MenuMessageTypeBack; |  | ||||||
|     } else { |  | ||||||
|         message.type = MenuMessageTypeUnknown; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     osMessageQueuePut(menu_event->mqueue, &message, 0, osWaitForever); |  | ||||||
| } |  | ||||||
| @ -1,32 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <stdint.h> |  | ||||||
| #include <input/input.h> |  | ||||||
| 
 |  | ||||||
| typedef enum { |  | ||||||
|     MenuMessageTypeUp = 0x00, |  | ||||||
|     MenuMessageTypeDown = 0x01, |  | ||||||
|     MenuMessageTypeLeft = 0x02, |  | ||||||
|     MenuMessageTypeRight = 0x03, |  | ||||||
|     MenuMessageTypeOk = 0x04, |  | ||||||
|     MenuMessageTypeBack = 0x05, |  | ||||||
|     MenuMessageTypeIdle = 0x06, |  | ||||||
|     MenuMessageTypeUnknown = 0xFF, |  | ||||||
| } MenuMessageType; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     MenuMessageType type; |  | ||||||
|     void* data; |  | ||||||
| } MenuMessage; |  | ||||||
| 
 |  | ||||||
| typedef struct MenuEvent MenuEvent; |  | ||||||
| 
 |  | ||||||
| MenuEvent* menu_event_alloc(); |  | ||||||
| 
 |  | ||||||
| void menu_event_free(MenuEvent* menu_event); |  | ||||||
| 
 |  | ||||||
| void menu_event_activity_notify(MenuEvent* menu_event); |  | ||||||
| 
 |  | ||||||
| MenuMessage menu_event_next(MenuEvent* menu_event); |  | ||||||
| 
 |  | ||||||
| void menu_event_input_callback(InputEvent* input_event, void* context); |  | ||||||
| @ -1,135 +0,0 @@ | |||||||
| #include "menu_item.h" |  | ||||||
| #include <stdlib.h> |  | ||||||
| #include <string.h> |  | ||||||
| #include <furi.h> |  | ||||||
| 
 |  | ||||||
| struct MenuItem { |  | ||||||
|     MenuItemType type; |  | ||||||
| 
 |  | ||||||
|     const char* label; |  | ||||||
|     IconAnimation* icon; |  | ||||||
| 
 |  | ||||||
|     size_t position; |  | ||||||
|     size_t window_position; |  | ||||||
|     MenuItem* parent; |  | ||||||
|     void* data; |  | ||||||
| 
 |  | ||||||
|     // callback related
 |  | ||||||
|     MenuItemCallback callback; |  | ||||||
|     void* callback_context; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| MenuItem* menu_item_alloc() { |  | ||||||
|     MenuItem* menu_item = furi_alloc(sizeof(MenuItem)); |  | ||||||
|     return menu_item; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| MenuItem* menu_item_alloc_menu(const char* label, IconAnimation* icon) { |  | ||||||
|     MenuItem* menu_item = menu_item_alloc(); |  | ||||||
| 
 |  | ||||||
|     menu_item->type = MenuItemTypeMenu; |  | ||||||
|     menu_item->label = label; |  | ||||||
|     menu_item->icon = icon; |  | ||||||
| 
 |  | ||||||
|     MenuItemArray_t* items = furi_alloc(sizeof(MenuItemArray_t)); |  | ||||||
|     MenuItemArray_init(*items); |  | ||||||
|     menu_item->data = items; |  | ||||||
| 
 |  | ||||||
|     return menu_item; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| MenuItem* menu_item_alloc_function( |  | ||||||
|     const char* label, |  | ||||||
|     IconAnimation* icon, |  | ||||||
|     MenuItemCallback callback, |  | ||||||
|     void* context) { |  | ||||||
|     MenuItem* menu_item = menu_item_alloc(); |  | ||||||
| 
 |  | ||||||
|     menu_item->type = MenuItemTypeFunction; |  | ||||||
|     menu_item->label = label; |  | ||||||
|     menu_item->icon = icon; |  | ||||||
|     menu_item->callback = callback; |  | ||||||
|     menu_item->callback_context = context; |  | ||||||
|     menu_item->parent = NULL; |  | ||||||
| 
 |  | ||||||
|     return menu_item; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_item_release(MenuItem* menu_item) { |  | ||||||
|     furi_assert(menu_item); |  | ||||||
|     if(menu_item->type == MenuItemTypeMenu) { |  | ||||||
|         //TODO: iterate and release
 |  | ||||||
|         free(menu_item->data); |  | ||||||
|     } |  | ||||||
|     free(menu_item); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| MenuItem* menu_item_get_parent(MenuItem* menu_item) { |  | ||||||
|     furi_assert(menu_item); |  | ||||||
|     return menu_item->parent; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_item_subitem_add(MenuItem* menu_item, MenuItem* sub_item) { |  | ||||||
|     furi_assert(menu_item); |  | ||||||
|     furi_check(menu_item->type == MenuItemTypeMenu); |  | ||||||
|     MenuItemArray_t* items = menu_item->data; |  | ||||||
|     sub_item->parent = menu_item; |  | ||||||
|     MenuItemArray_push_back(*items, sub_item); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| uint8_t menu_item_get_type(MenuItem* menu_item) { |  | ||||||
|     furi_assert(menu_item); |  | ||||||
|     return menu_item->type; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_item_set_position(MenuItem* menu_item, size_t position) { |  | ||||||
|     furi_assert(menu_item); |  | ||||||
|     menu_item->position = position; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| size_t menu_item_get_position(MenuItem* menu_item) { |  | ||||||
|     furi_assert(menu_item); |  | ||||||
|     return menu_item->position; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_item_set_window_position(MenuItem* menu_item, size_t window_position) { |  | ||||||
|     furi_assert(menu_item); |  | ||||||
|     menu_item->window_position = window_position; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| size_t menu_item_get_window_position(MenuItem* menu_item) { |  | ||||||
|     furi_assert(menu_item); |  | ||||||
|     return menu_item->window_position; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_item_set_label(MenuItem* menu_item, const char* label) { |  | ||||||
|     furi_assert(menu_item); |  | ||||||
|     menu_item->label = label; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const char* menu_item_get_label(MenuItem* menu_item) { |  | ||||||
|     furi_assert(menu_item); |  | ||||||
|     return menu_item->label; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_item_set_icon(MenuItem* menu_item, IconAnimation* icon) { |  | ||||||
|     furi_assert(menu_item); |  | ||||||
|     menu_item->icon = icon; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| IconAnimation* menu_item_get_icon(MenuItem* menu_item) { |  | ||||||
|     furi_assert(menu_item); |  | ||||||
|     return menu_item->icon; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| MenuItemArray_t* menu_item_get_subitems(MenuItem* menu_item) { |  | ||||||
|     furi_assert(menu_item); |  | ||||||
|     furi_check(menu_item->type == MenuItemTypeMenu); |  | ||||||
|     return menu_item->data; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void menu_item_function_call(MenuItem* menu_item) { |  | ||||||
|     furi_assert(menu_item); |  | ||||||
|     furi_check(menu_item->type == MenuItemTypeFunction); |  | ||||||
|     if(menu_item->callback) menu_item->callback(menu_item->callback_context); |  | ||||||
| } |  | ||||||
| @ -1,47 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <stdint.h> |  | ||||||
| #include <m-array.h> |  | ||||||
| #include <gui/icon_animation.h> |  | ||||||
| 
 |  | ||||||
| typedef enum { |  | ||||||
|     MenuItemTypeMenu = 0x00, |  | ||||||
|     MenuItemTypeFunction = 0x01, |  | ||||||
| } MenuItemType; |  | ||||||
| 
 |  | ||||||
| typedef struct MenuItem MenuItem; |  | ||||||
| typedef void (*MenuItemCallback)(void* context); |  | ||||||
| 
 |  | ||||||
| ARRAY_DEF(MenuItemArray, MenuItem*, M_PTR_OPLIST); |  | ||||||
| 
 |  | ||||||
| MenuItem* menu_item_alloc_menu(const char* label, IconAnimation* icon); |  | ||||||
| 
 |  | ||||||
| MenuItem* menu_item_alloc_function( |  | ||||||
|     const char* label, |  | ||||||
|     IconAnimation* icon, |  | ||||||
|     MenuItemCallback callback, |  | ||||||
|     void* context); |  | ||||||
| 
 |  | ||||||
| void menu_item_release(MenuItem* menu_item); |  | ||||||
| 
 |  | ||||||
| MenuItem* menu_item_get_parent(MenuItem* menu_item); |  | ||||||
| 
 |  | ||||||
| void menu_item_subitem_add(MenuItem* menu_item, MenuItem* sub_item); |  | ||||||
| 
 |  | ||||||
| MenuItemType menu_item_get_type(MenuItem* menu_item); |  | ||||||
| 
 |  | ||||||
| void menu_item_set_position(MenuItem* menu_item, size_t position); |  | ||||||
| size_t menu_item_get_position(MenuItem* menu_item); |  | ||||||
| 
 |  | ||||||
| void menu_item_set_window_position(MenuItem* menu_item, size_t window_position); |  | ||||||
| size_t menu_item_get_window_position(MenuItem* menu_item); |  | ||||||
| 
 |  | ||||||
| void menu_item_set_label(MenuItem* menu_item, const char* label); |  | ||||||
| const char* menu_item_get_label(MenuItem* menu_item); |  | ||||||
| 
 |  | ||||||
| void menu_item_set_icon(MenuItem* menu_item, IconAnimation* icon); |  | ||||||
| IconAnimation* menu_item_get_icon(MenuItem* menu_item); |  | ||||||
| 
 |  | ||||||
| MenuItemArray_t* menu_item_get_subitems(MenuItem* menu_item); |  | ||||||
| 
 |  | ||||||
| void menu_item_function_call(MenuItem* menu_item); |  | ||||||
| @ -27,7 +27,7 @@ void nfc_cli_detect(Cli* cli, string_t args, void* context) { | |||||||
|         if(dev_cnt > 0) { |         if(dev_cnt > 0) { | ||||||
|             printf("Found %d devices\r\n", dev_cnt); |             printf("Found %d devices\r\n", dev_cnt); | ||||||
|             for(uint8_t i = 0; i < dev_cnt; i++) { |             for(uint8_t i = 0; i < dev_cnt; i++) { | ||||||
|                 printf("%d found: %s ", i + 1, nfc_get_dev_type(dev_list[i].type)); |                 printf("%d found: %s ", i + 1, nfc_get_rfal_type(dev_list[i].type)); | ||||||
|                 if(dev_list[i].type == RFAL_NFC_LISTEN_TYPE_NFCA) { |                 if(dev_list[i].type == RFAL_NFC_LISTEN_TYPE_NFCA) { | ||||||
|                     printf("type: %s, ", nfc_get_nfca_type(dev_list[i].dev.nfca.type)); |                     printf("type: %s, ", nfc_get_nfca_type(dev_list[i].dev.nfca.type)); | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -6,19 +6,33 @@ | |||||||
| #include <gui/view_dispatcher.h> | #include <gui/view_dispatcher.h> | ||||||
| #include "nfc_worker.h" | #include "nfc_worker.h" | ||||||
| 
 | 
 | ||||||
| static inline const char* nfc_get_dev_type(rfalNfcDevType type) { | static inline const char* nfc_get_rfal_type(rfalNfcDevType type) { | ||||||
|     if(type == RFAL_NFC_LISTEN_TYPE_NFCA) { |     if(type == RFAL_NFC_LISTEN_TYPE_NFCA) { | ||||||
|         return "NFC-A may be:"; |         return "NFC-A"; | ||||||
|     } else if(type == RFAL_NFC_LISTEN_TYPE_NFCB) { |     } else if(type == RFAL_NFC_LISTEN_TYPE_NFCB) { | ||||||
|         return "NFC-B may be:"; |         return "NFC-B"; | ||||||
|     } else if(type == RFAL_NFC_LISTEN_TYPE_NFCF) { |     } else if(type == RFAL_NFC_LISTEN_TYPE_NFCF) { | ||||||
|         return "NFC-F may be:"; |         return "NFC-F"; | ||||||
|     } else if(type == RFAL_NFC_LISTEN_TYPE_NFCV) { |     } else if(type == RFAL_NFC_LISTEN_TYPE_NFCV) { | ||||||
|         return "NFC-V may be:"; |         return "NFC-V"; | ||||||
|     } else if(type == RFAL_NFC_LISTEN_TYPE_ST25TB) { |     } else if(type == RFAL_NFC_LISTEN_TYPE_ST25TB) { | ||||||
|         return "NFC-ST25TB may be:"; |         return "NFC-ST25TB"; | ||||||
|     } else if(type == RFAL_NFC_LISTEN_TYPE_AP2P) { |     } else if(type == RFAL_NFC_LISTEN_TYPE_AP2P) { | ||||||
|         return "NFC-AP2P may be:"; |         return "NFC-AP2P"; | ||||||
|  |     } else { | ||||||
|  |         return "Unknown"; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline const char* nfc_get_dev_type(NfcDeviceType type) { | ||||||
|  |     if(type == NfcDeviceNfca) { | ||||||
|  |         return "NFC-A may be:"; | ||||||
|  |     } else if(type == NfcDeviceNfcb) { | ||||||
|  |         return "NFC-B may be:"; | ||||||
|  |     } else if(type == NfcDeviceNfcf) { | ||||||
|  |         return "NFC-F may be:"; | ||||||
|  |     } else if(type == NfcDeviceNfcv) { | ||||||
|  |         return "NFC-V may be:"; | ||||||
|     } else { |     } else { | ||||||
|         return "Unknown"; |         return "Unknown"; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ void nfc_scene_card_menu_submenu_callback(void* context, uint32_t index) { | |||||||
|     view_dispatcher_send_custom_event(nfc->view_dispatcher, index); |     view_dispatcher_send_custom_event(nfc->view_dispatcher, index); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const void nfc_scene_card_menu_on_enter(void* context) { | void nfc_scene_card_menu_on_enter(void* context) { | ||||||
|     Nfc* nfc = (Nfc*)context; |     Nfc* nfc = (Nfc*)context; | ||||||
|     Submenu* submenu = nfc->submenu; |     Submenu* submenu = nfc->submenu; | ||||||
| 
 | 
 | ||||||
| @ -41,7 +41,7 @@ const void nfc_scene_card_menu_on_enter(void* context) { | |||||||
|     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const bool nfc_scene_card_menu_on_event(void* context, SceneManagerEvent event) { | bool nfc_scene_card_menu_on_event(void* context, SceneManagerEvent event) { | ||||||
|     Nfc* nfc = (Nfc*)context; |     Nfc* nfc = (Nfc*)context; | ||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
| @ -78,7 +78,7 @@ const bool nfc_scene_card_menu_on_event(void* context, SceneManagerEvent event) | |||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const void nfc_scene_card_menu_on_exit(void* context) { | void nfc_scene_card_menu_on_exit(void* context) { | ||||||
|     Nfc* nfc = (Nfc*)context; |     Nfc* nfc = (Nfc*)context; | ||||||
| 
 | 
 | ||||||
|     submenu_clean(nfc->submenu); |     submenu_clean(nfc->submenu); | ||||||
|  | |||||||
| @ -66,7 +66,7 @@ void nfc_scene_delete_on_enter(void* context) { | |||||||
|     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { | bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { | ||||||
|     Nfc* nfc = (Nfc*)context; |     Nfc* nfc = (Nfc*)context; | ||||||
| 
 | 
 | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
| @ -85,7 +85,7 @@ const bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { | |||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const void nfc_scene_delete_on_exit(void* context) { | void nfc_scene_delete_on_exit(void* context) { | ||||||
|     Nfc* nfc = (Nfc*)context; |     Nfc* nfc = (Nfc*)context; | ||||||
| 
 | 
 | ||||||
|     widget_clear(nfc->widget); |     widget_clear(nfc->widget); | ||||||
|  | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Aleksandr Kutuzov
						Aleksandr Kutuzov