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 input_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 power_observer_srv(void* p); | ||||
| extern int32_t power_srv(void* p); | ||||
| extern int32_t storage_srv(void* p); | ||||
| extern int32_t desktop_srv(void* p); | ||||
| 
 | ||||
| // Apps
 | ||||
| extern int32_t accessor_app(void* p); | ||||
| @ -46,11 +46,15 @@ extern void lfrfid_cli_init(); | ||||
| extern void nfc_cli_init(); | ||||
| extern void storage_cli_init(); | ||||
| extern void subghz_cli_init(); | ||||
| extern void power_cli_init(); | ||||
| 
 | ||||
| // Settings
 | ||||
| extern int32_t notification_settings_app(void* p); | ||||
| extern int32_t storage_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[] = { | ||||
| /* Services */ | ||||
| @ -70,6 +74,10 @@ const FlipperApplication FLIPPER_SERVICES[] = { | ||||
|     {.app = dolphin_srv, .name = "Dolphin", .stack_size = 1024, .icon = NULL}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_DESKTOP | ||||
|     {.app = desktop_srv, .name = "Desktop", .stack_size = 1024, .icon = NULL}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_GUI | ||||
|     {.app = gui_srv, .name = "Gui", .stack_size = 8192, .icon = NULL}, | ||||
| #endif | ||||
| @ -78,8 +86,7 @@ const FlipperApplication FLIPPER_SERVICES[] = { | ||||
|     {.app = input_srv, .name = "Input", .stack_size = 1024, .icon = NULL}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_MENU | ||||
|     {.app = menu_srv, .name = "Menu", .stack_size = 1024, .icon = NULL}, | ||||
| #ifdef SRV_LOADER | ||||
|     {.app = loader_srv, .name = "Loader", .stack_size = 1024, .icon = NULL}, | ||||
| #endif | ||||
| 
 | ||||
| @ -98,43 +105,6 @@ const FlipperApplication FLIPPER_SERVICES[] = { | ||||
| #ifdef SRV_STORAGE | ||||
|     {.app = storage_srv, .name = "Storage", .stack_size = 4096, .icon = NULL}, | ||||
| #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); | ||||
| @ -142,18 +112,14 @@ const size_t FLIPPER_SERVICES_COUNT = sizeof(FLIPPER_SERVICES) / sizeof(FlipperA | ||||
| // Main menu APP
 | ||||
| const FlipperApplication FLIPPER_APPS[] = { | ||||
| 
 | ||||
| #ifdef APP_IBUTTON | ||||
|     {.app = ibutton_app, .name = "iButton", .stack_size = 2048, .icon = &A_iButton_14}, | ||||
| #ifdef APP_SUBGHZ | ||||
|     {.app = subghz_app, .name = "Sub-GHz", .stack_size = 2048, .icon = &A_Sub1ghz_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_NFC | ||||
|     {.app = nfc_app, .name = "NFC", .stack_size = 4096, .icon = &A_NFC_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_SUBGHZ | ||||
|     {.app = subghz_app, .name = "Sub-GHz", .stack_size = 2048, .icon = &A_Sub1ghz_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_LF_RFID | ||||
|     {.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 2048, .icon = &A_125khz_14}, | ||||
| #endif | ||||
| @ -162,6 +128,10 @@ const FlipperApplication FLIPPER_APPS[] = { | ||||
|     {.app = irda_app, .name = "Infrared", .stack_size = 1024 * 3, .icon = &A_Infrared_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_IBUTTON | ||||
|     {.app = ibutton_app, .name = "iButton", .stack_size = 2048, .icon = &A_iButton_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_GPIO_TEST | ||||
|     {.app = gpio_test_app, .name = "GPIO", .stack_size = 1024, .icon = &A_GPIO_14}, | ||||
| #endif | ||||
| @ -175,22 +145,35 @@ const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = { | ||||
| #ifdef SRV_CLI | ||||
|     crypto_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_IRDA | ||||
|     irda_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_NFC | ||||
|     nfc_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_SUBGHZ | ||||
|     subghz_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_LF_RFID | ||||
|     lfrfid_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_IBUTTON | ||||
|     ibutton_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_BT | ||||
|     bt_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_POWER | ||||
|     power_cli_init, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_STORAGE | ||||
|     storage_cli_init, | ||||
| #endif | ||||
| @ -257,16 +240,31 @@ const FlipperApplication FLIPPER_ARCHIVE = | ||||
| 
 | ||||
| // Settings menu
 | ||||
| const FlipperApplication FLIPPER_SETTINGS_APPS[] = { | ||||
| #ifdef SRV_BT | ||||
|     {.app = bt_settings_app, .name = "Bluetooth", .stack_size = 1024, .icon = NULL}, | ||||
| #endif | ||||
| 
 | ||||
| #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 | ||||
| 
 | ||||
| #ifdef SRV_STORAGE | ||||
|     {.app = storage_settings_app, .name = "Storage", .stack_size = 2048, .icon = NULL}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_BT | ||||
|     {.app = bt_settings_app, .name = "Bluetooth", .stack_size = 1024, .icon = NULL}, | ||||
| #ifdef SRV_POWER | ||||
|     {.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 | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -6,9 +6,6 @@ C_SOURCES   += $(shell find $(APP_DIR) -name *.c) | ||||
| CPP_SOURCES	+= $(shell find $(APP_DIR) -name *.cpp) | ||||
| 
 | ||||
| 
 | ||||
| # Use SRV_* for autostart app
 | ||||
| # Use APP_* for add app to build
 | ||||
| 
 | ||||
| APP_RELEASE ?= 1 | ||||
| ifeq ($(APP_RELEASE), 1) | ||||
| # Services
 | ||||
| @ -18,13 +15,14 @@ SRV_DIALOGS = 1 | ||||
| SRV_DOLPHIN	= 1 | ||||
| SRV_GUI		= 1 | ||||
| SRV_INPUT	= 1 | ||||
| SRV_MENU = 1 | ||||
| SRV_LOADER	= 1 | ||||
| SRV_NOTIFICATION = 1 | ||||
| SRV_POWER	= 1 | ||||
| SRV_POWER_OBSERVER = 1 | ||||
| SRV_STORAGE	= 1 | ||||
| 
 | ||||
| # Apps
 | ||||
| SRV_DESKTOP	= 1 | ||||
| APP_ARCHIVE	= 1 | ||||
| APP_GPIO_TEST = 1 | ||||
| APP_IBUTTON	= 1 | ||||
| @ -32,6 +30,7 @@ APP_IRDA  = 1 | ||||
| APP_LF_RFID	= 1 | ||||
| APP_NFC		= 1 | ||||
| APP_SUBGHZ	= 1 | ||||
| APP_ABOUT	= 1 | ||||
| 
 | ||||
| # Plugins
 | ||||
| APP_MUSIC_PLAYER = 1 | ||||
| @ -47,218 +46,204 @@ APP_VIBRO_DEMO = 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_BT ?= 0 | ||||
| ifeq ($(SRV_BT), 1) | ||||
| SRV_CLI		= 1 | ||||
| CFLAGS		+= -DSRV_BT | ||||
| endif | ||||
| # Applications
 | ||||
| # that will be shown in menu
 | ||||
| # Prefix with APP_*
 | ||||
| 
 | ||||
| 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 | ||||
| ifeq ($(APP_IRDA_MONITOR), 1) | ||||
| CFLAGS		+= -DAPP_IRDA_MONITOR | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_UNIT_TESTS	?= 0 | ||||
| ifeq ($(APP_UNIT_TESTS), 1) | ||||
| CFLAGS		+= -DAPP_UNIT_TESTS | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_ARCHIVE ?= 0 | ||||
| ifeq ($(APP_NFC), 1) | ||||
| ifeq ($(APP_ARCHIVE), 1) | ||||
| CFLAGS		+= -DAPP_ARCHIVE | ||||
| APP_ARCHIVE = 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_BLINK ?= 0 | ||||
| ifeq ($(SRV_BLINK), 1) | ||||
| CFLAGS		+= -DSRV_BLINK | ||||
| APP_BLINK = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_BLINK ?= 0 | ||||
| ifeq ($(APP_BLINK), 1) | ||||
| CFLAGS		+= -DAPP_BLINK | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI		= 1 | ||||
| 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) | ||||
| CFLAGS		+= -DAPP_SUBGHZ | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI		= 1 | ||||
| SRV_CLI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_LF_RFID ?= 0 | ||||
| ifeq ($(SRV_LF_RFID), 1) | ||||
| CFLAGS		+= -DSRV_LF_RFID | ||||
| APP_LF_RFID = 1 | ||||
| 
 | ||||
| APP_ABOUT ?= 0 | ||||
| ifeq ($(APP_ABOUT), 1) | ||||
| CFLAGS		+= -DAPP_ABOUT | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_LF_RFID ?= 0 | ||||
| ifeq ($(APP_LF_RFID), 1) | ||||
| CFLAGS		+= -DAPP_LF_RFID | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_NFC ?= 0 | ||||
| ifeq ($(APP_NFC), 1) | ||||
| CFLAGS		+= -DAPP_NFC | ||||
| SRV_MENU = 1 | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_IRDA ?= 0 | ||||
| ifeq ($(SRV_IRDA), 1) | ||||
| CFLAGS		+= -DSRV_IRDA | ||||
| APP_IRDA = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_IRDA ?= 0 | ||||
| ifeq ($(APP_IRDA), 1) | ||||
| CFLAGS		+= -DAPP_IRDA | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| APP_VIBRO_DEMO ?= 0 | ||||
| ifeq ($(APP_VIBRO_DEMO), 1) | ||||
| CFLAGS		+= -DAPP_VIBRO_DEMO | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_KEYPAD_TEST ?= 0 | ||||
| ifeq ($(SRV_KEYPAD_TEST), 1) | ||||
| CFLAGS		+= -DSRV_KEYPAD_TEST | ||||
| APP_KEYPAD_TEST = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_KEYPAD_TEST ?= 0 | ||||
| ifeq ($(APP_KEYPAD_TEST), 1) | ||||
| CFLAGS		+= -DAPP_KEYPAD_TEST | ||||
| APP_KEYPAD_TEST = 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_ACCESSOR ?= 0 | ||||
| ifeq ($(SRV_ACCESSOR), 1) | ||||
| CFLAGS		+= -DSRV_ACCESSOR | ||||
| APP_ACCESSOR = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_ACCESSOR ?= 0 | ||||
| ifeq ($(APP_ACCESSOR), 1) | ||||
| CFLAGS		+= -DAPP_ACCESSOR | ||||
| APP_ACCESSOR = 1 | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_GPIO_TEST ?= 0 | ||||
| ifeq ($(SRV_GPIO_TEST), 1) | ||||
| CFLAGS		+= -DSRV_GPIO_TEST | ||||
| APP_GPIO_TEST = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_GPIO_TEST ?= 0 | ||||
| ifeq ($(APP_GPIO_TEST), 1) | ||||
| CFLAGS		+= -DAPP_GPIO_TEST | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_MUSIC_PLAYER ?= 0 | ||||
| ifeq ($(SRV_MUSIC_PLAYER), 1) | ||||
| CFLAGS		+= -DSRV_MUSIC_PLAYER | ||||
| APP_MUSIC_PLAYER = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_MUSIC_PLAYER ?= 0 | ||||
| ifeq ($(APP_MUSIC_PLAYER), 1) | ||||
| CFLAGS		+= -DAPP_MUSIC_PLAYER | ||||
| SRV_GUI		= 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_IBUTTON ?= 0 | ||||
| ifeq ($(SRV_IBUTTON), 1) | ||||
| CFLAGS		+= -DSRV_IBUTTON | ||||
| APP_IBUTTON = 1 | ||||
| endif | ||||
| 
 | ||||
| APP_IBUTTON ?= 0 | ||||
| ifeq ($(APP_IBUTTON), 1) | ||||
| 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 | ||||
| 
 | ||||
| #
 | ||||
| # Essential services
 | ||||
| #
 | ||||
| 
 | ||||
| SRV_GUI	?= 0 | ||||
| ifeq ($(SRV_GUI), 1) | ||||
| CFLAGS		+= -DSRV_GUI | ||||
| SRV_INPUT	= 1 | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_INPUT	?= 0 | ||||
| ifeq ($(SRV_INPUT), 1) | ||||
| CFLAGS		+= -DSRV_INPUT | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_CLI ?= 0 | ||||
| ifeq ($(SRV_CLI), 1) | ||||
| SRV_GUI		= 1 | ||||
| CFLAGS		+= -DSRV_CLI | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_NOTIFICATION ?= 0 | ||||
| ifeq ($(SRV_NOTIFICATION), 1) | ||||
| CFLAGS		+= -DSRV_NOTIFICATION | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| SRV_STORAGE ?= 0 | ||||
| ifeq ($(SRV_STORAGE), 1) | ||||
| CFLAGS		+= -DSRV_STORAGE | ||||
| 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( | ||||
|         archive->view_dispatcher, archive_back_event_callback); | ||||
| 
 | ||||
|     archive->main_view = main_view_alloc(); | ||||
|     archive->browser = browser_alloc(); | ||||
| 
 | ||||
|     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( | ||||
|         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_free(archive->view_dispatcher); | ||||
|     scene_manager_free(archive->scene_manager); | ||||
|     main_view_free(archive->main_view); | ||||
|     browser_free(archive->browser); | ||||
| 
 | ||||
|     text_input_free(archive->text_input); | ||||
| 
 | ||||
|  | ||||
| @ -9,72 +9,20 @@ | ||||
| #include <gui/modules/text_input.h> | ||||
| #include <loader/loader.h> | ||||
| 
 | ||||
| #include <m-string.h> | ||||
| #include <m-array.h> | ||||
| #include <storage/storage.h> | ||||
| #include "applications.h" | ||||
| #include "file-worker.h" | ||||
| 
 | ||||
| #include "views/archive_main_view.h" | ||||
| #include "views/archive_browser_view.h" | ||||
| #include "scenes/archive_scene.h" | ||||
| 
 | ||||
| #define MAX_FILE_SIZE 128 | ||||
| 
 | ||||
| typedef enum { | ||||
|     ArchiveViewBrowser, | ||||
|     ArchiveViewTextInput, | ||||
|     ArchiveViewTotal, | ||||
| } 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 { | ||||
|     Gui* gui; | ||||
|     ViewDispatcher* view_dispatcher; | ||||
|     SceneManager* scene_manager; | ||||
|     ArchiveMainView* main_view; | ||||
|     ArchiveBrowserView* browser; | ||||
|     TextInput* text_input; | ||||
|     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_files.h" | ||||
| #include "../views/archive_main_view.h" | ||||
| #include "archive_browser.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) { | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     ArchiveMainView* archive_view = context; | ||||
|     ArchiveBrowserView* archive_view = context; | ||||
|     FileWorker* file_worker = file_worker_alloc(true); | ||||
| 
 | ||||
|     string_t buffer; | ||||
|     FileInfo file_info; | ||||
|     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) { | ||||
|         while(1) { | ||||
| @ -23,7 +52,7 @@ bool archive_favorites_read(void* context) { | ||||
|                 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); | ||||
|         } | ||||
|     } | ||||
| @ -33,18 +62,19 @@ bool archive_favorites_read(void* context) { | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool archive_favorites_delete(const char* file_path, const char* name) { | ||||
|     furi_assert(file_path); | ||||
|     furi_assert(name); | ||||
| bool archive_favorites_delete(const char* format, ...) { | ||||
|     va_list args; | ||||
|     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); | ||||
| 
 | ||||
|     string_t path; | ||||
|     string_t 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); | ||||
|     if(result) { | ||||
|         while(1) { | ||||
| @ -55,17 +85,13 @@ bool archive_favorites_delete(const char* file_path, const char* name) { | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             if(string_search(buffer, path)) { | ||||
|                 string_t temp; | ||||
|                 string_init_printf(temp, "%s\r\n", string_get_cstr(buffer)); | ||||
|                 archive_file_append(ARCHIVE_FAV_TEMP_PATH, temp); | ||||
|                 string_clear(temp); | ||||
|             if(string_search_str(buffer, filename)) { | ||||
|                 archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\r\n", string_get_cstr(buffer)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     string_clear(buffer); | ||||
|     string_clear(path); | ||||
| 
 | ||||
|     file_worker_close(file_worker); | ||||
|     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; | ||||
| } | ||||
| 
 | ||||
| bool archive_is_favorite(const char* file_path, const char* name) { | ||||
|     furi_assert(file_path); | ||||
|     furi_assert(name); | ||||
| bool archive_is_favorite(const char* format, ...) { | ||||
|     va_list args; | ||||
|     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); | ||||
| 
 | ||||
|     string_t path; | ||||
|     string_t buffer; | ||||
|     string_init(buffer); | ||||
|     bool found = false; | ||||
| 
 | ||||
|     string_init_printf(path, "%s/%s", file_path, name); | ||||
|     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_ALWAYS); | ||||
|     bool found = false; | ||||
|     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
| 
 | ||||
|     if(result) { | ||||
|         while(1) { | ||||
| @ -98,7 +125,7 @@ bool archive_is_favorite(const char* file_path, const char* name) { | ||||
|             if(!string_size(buffer)) { | ||||
|                 break; | ||||
|             } | ||||
|             if(!string_search(buffer, path)) { | ||||
|             if(!string_search_str(buffer, filename)) { | ||||
|                 found = true; | ||||
|                 break; | ||||
|             } | ||||
| @ -106,7 +133,6 @@ bool archive_is_favorite(const char* file_path, const char* name) { | ||||
|     } | ||||
| 
 | ||||
|     string_clear(buffer); | ||||
|     string_clear(path); | ||||
|     file_worker_close(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 buffer; | ||||
|     string_t temp; | ||||
| 
 | ||||
|     string_init(buffer); | ||||
|     string_init(temp); | ||||
|     string_init(path); | ||||
| 
 | ||||
|     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)) { | ||||
|                 break; | ||||
|             } | ||||
|             string_printf( | ||||
|                 temp, "%s\r\n", string_search(buffer, path) ? string_get_cstr(buffer) : dst); | ||||
|             archive_file_append(ARCHIVE_FAV_TEMP_PATH, temp); | ||||
|             string_clean(temp); | ||||
| 
 | ||||
|             archive_file_append( | ||||
|                 ARCHIVE_FAV_TEMP_PATH, | ||||
|                 "%s\r\n", | ||||
|                 string_search(buffer, path) ? string_get_cstr(buffer) : dst); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     string_clear(temp); | ||||
|     string_clear(buffer); | ||||
|     string_clear(path); | ||||
| 
 | ||||
| @ -159,13 +183,8 @@ bool archive_favorites_rename(const char* file_path, const char* src, const char | ||||
|     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(name); | ||||
| 
 | ||||
|     string_t buffer_src; | ||||
| 
 | ||||
|     string_init_printf(buffer_src, "%s/%s\r\n", file_path, name); | ||||
|     archive_file_append(ARCHIVE_FAV_PATH, buffer_src); | ||||
|     string_clear(buffer_src); | ||||
|     archive_file_append(ARCHIVE_FAV_PATH, "%s\r\n", file_path); | ||||
| } | ||||
|  | ||||
| @ -4,8 +4,9 @@ | ||||
| #define ARCHIVE_FAV_PATH "/any/favorites.txt" | ||||
| #define ARCHIVE_FAV_TEMP_PATH "/any/favorites.tmp" | ||||
| 
 | ||||
| uint16_t archive_favorites_count(void* context); | ||||
| bool archive_favorites_read(void* context); | ||||
| bool archive_favorites_delete(const char* file_path, const char* name); | ||||
| bool archive_is_favorite(const char* file_path, const char* name); | ||||
| bool archive_favorites_delete(const char* format, ...); | ||||
| bool archive_is_favorite(const char* format, ...); | ||||
| 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_favorites.h" | ||||
| #include "../archive_i.h" | ||||
| #include "archive_browser.h" | ||||
| 
 | ||||
| bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name) { | ||||
|     furi_assert(file_info); | ||||
| @ -20,14 +19,12 @@ bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* n | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void archive_trim_file_ext(char* name) { | ||||
|     size_t str_len = strlen(name); | ||||
|     char* end = name + str_len; | ||||
|     while(end > name && *end != '.' && *end != '\\' && *end != '/') { | ||||
|         --end; | ||||
|     } | ||||
|     if((end > name && *end == '.') && (*(end - 1) != '\\' && *(end - 1) != '/')) { | ||||
|         *end = '\0'; | ||||
| void archive_trim_file_path(char* name, bool ext) { | ||||
|     char* slash = strrchr(name, '/') + 1; | ||||
|     if(strlen(slash)) strlcpy(name, slash, strlen(slash) + 1); | ||||
|     if(ext) { | ||||
|         char* dot = strrchr(name, '.'); | ||||
|         if(strlen(dot)) *dot = '\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); | ||||
| 
 | ||||
|     ArchiveMainView* main_view = context; | ||||
|     archive_file_array_clean(main_view); | ||||
|     bool res; | ||||
|     ArchiveBrowserView* browser = context; | ||||
|     archive_file_array_rm_all(browser); | ||||
| 
 | ||||
|     if(tab_id != ArchiveTabFavorites) { | ||||
|         archive_read_dir(main_view, path); | ||||
|     if(archive_get_tab(browser) != ArchiveTabFavorites) { | ||||
|         res = archive_read_dir(browser, path); | ||||
|     } 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); | ||||
| 
 | ||||
|     ArchiveMainView* main_view = context; | ||||
|     FileInfo file_info; | ||||
|     Storage* fs_api = furi_record_open("storage"); | ||||
|     File* directory = storage_file_alloc(fs_api); | ||||
| @ -78,17 +75,52 @@ bool archive_read_dir(void* context, const char* path) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     bool files_found = false; | ||||
|     while(1) { | ||||
|         if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) { | ||||
|             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) { | ||||
|             break; | ||||
|         } 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 { | ||||
|             storage_dir_close(directory); | ||||
|             storage_file_free(directory); | ||||
| @ -103,9 +135,15 @@ bool archive_read_dir(void* context, const char* path) { | ||||
|     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(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); | ||||
| 
 | ||||
| @ -113,7 +151,7 @@ void archive_file_append(const char* path, string_t string) { | ||||
|         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"); | ||||
|     } | ||||
| 
 | ||||
| @ -125,19 +163,22 @@ void archive_delete_file(void* context, string_t path, string_t name) { | ||||
|     furi_assert(context); | ||||
|     furi_assert(path); | ||||
|     furi_assert(name); | ||||
|     ArchiveMainView* main_view = context; | ||||
|     FileWorker* file_worker = file_worker_alloc(false); | ||||
|     ArchiveBrowserView* browser = context; | ||||
|     FileWorker* file_worker = file_worker_alloc(true); | ||||
| 
 | ||||
|     string_t full_path; | ||||
|     string_init(full_path); | ||||
|     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); | ||||
|     string_init_printf(full_path, "%s/%s", string_get_cstr(path), string_get_cstr(name)); | ||||
| 
 | ||||
|     if(archive_is_favorite(string_get_cstr(path), string_get_cstr(name))) { | ||||
|         archive_favorites_delete(string_get_cstr(path), string_get_cstr(name)); | ||||
|     bool res = file_worker_remove(file_worker, string_get_cstr(full_path)); | ||||
|     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); | ||||
| void set_file_type(ArchiveFile_t* file, FileInfo* file_info); | ||||
| void archive_trim_file_ext(char* name); | ||||
| bool archive_get_filenames(void* context, uint8_t tab_id, const char* path); | ||||
| void archive_trim_file_path(char* name, bool ext); | ||||
| 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); | ||||
| 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); | ||||
| @ -1,32 +1,114 @@ | ||||
| #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) { | ||||
|     ArchiveApp* archive = (ArchiveApp*)context; | ||||
|     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; | ||||
|     ArchiveMainView* main_view = archive->main_view; | ||||
|     ArchiveBrowserView* browser = archive->browser; | ||||
| 
 | ||||
|     archive_browser_set_callback(main_view, archive_scene_browser_callback, archive); | ||||
|     archive_browser_update(main_view); | ||||
|     archive_browser_set_callback(browser, archive_scene_browser_callback, archive); | ||||
|     archive_update_focus(browser, archive->text_store); | ||||
|     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; | ||||
|     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) { | ||||
|         switch(event.event) { | ||||
|         case ArchiveBrowserEventRename: | ||||
|             scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneRename); | ||||
|         case ArchiveBrowserEventFileMenuOpen: | ||||
|             archive_show_file_menu(browser, true); | ||||
|             consumed = true; | ||||
|             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: | ||||
|             if(archive_get_depth(browser)) { | ||||
|                 archive_leave_dir(browser); | ||||
|             } else { | ||||
|                 view_dispatcher_stop(archive->view_dispatcher); | ||||
|             } | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
| @ -37,6 +119,6 @@ const bool archive_scene_browser_on_event(void* context, SceneManagerEvent event | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| const void archive_scene_browser_on_exit(void* context) { | ||||
| void archive_scene_browser_on_exit(void* context) { | ||||
|     // ArchiveApp* archive = (ArchiveApp*)context;
 | ||||
| } | ||||
|  | ||||
| @ -1,22 +1,24 @@ | ||||
| #include "../archive_i.h" | ||||
| #include "../helpers/archive_favorites.h" | ||||
| #include "../helpers/archive_files.h" | ||||
| #include "../helpers/archive_browser.h" | ||||
| 
 | ||||
| #define SCENE_RENAME_CUSTOM_EVENT (0UL) | ||||
| #define MAX_TEXT_INPUT_LEN 22 | ||||
| 
 | ||||
| void archive_scene_rename_text_input_callback(void* context) { | ||||
|     ArchiveApp* archive = (ArchiveApp*)context; | ||||
|     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; | ||||
| 
 | ||||
|     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); | ||||
| 
 | ||||
|     archive_trim_file_ext(archive->text_store); | ||||
|     archive_trim_file_path(archive->text_store, true); | ||||
| 
 | ||||
|     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, | ||||
|         archive->text_store, | ||||
|         MAX_NAME_LEN, | ||||
|         MAX_TEXT_INPUT_LEN, | ||||
|         false); | ||||
| 
 | ||||
|     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; | ||||
|     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_dst; | ||||
| 
 | ||||
|             const char* path = archive_get_path(archive->main_view); | ||||
|             const char* name = archive_get_name(archive->main_view); | ||||
|             const char* path = archive_get_path(archive->browser); | ||||
|             const char* name = archive_get_name(archive->browser); | ||||
| 
 | ||||
|             string_init_printf(buffer_src, "%s/%s", path, name); | ||||
|             string_init_printf(buffer_dst, "%s/%s", path, archive->text_store); | ||||
| 
 | ||||
|             archive_set_name(archive->main_view, archive->text_store); | ||||
| 
 | ||||
|             // 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]); | ||||
|             storage_common_rename( | ||||
| @ -72,9 +72,8 @@ const bool archive_scene_rename_on_event(void* context, SceneManagerEvent event) | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| const void archive_scene_rename_on_exit(void* context) { | ||||
| void archive_scene_rename_on_exit(void* context) { | ||||
|     ArchiveApp* archive = (ArchiveApp*)context; | ||||
|     // Clear view
 | ||||
|     text_input_set_header_text(archive->text_input, NULL); | ||||
|     text_input_set_result_callback(archive->text_input, NULL, NULL, NULL, 0, false); | ||||
|     text_input_clean(archive->text_input); | ||||
| } | ||||
|  | ||||
							
								
								
									
										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 <furi.h> | ||||
| #include <furi-hal.h> | ||||
| #include "bt_settings.h" | ||||
| 
 | ||||
| void bt_cli_init() { | ||||
|     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_init(buffer); | ||||
|     furi_hal_bt_dump_state(buffer); | ||||
|     printf(string_get_cstr(buffer)); | ||||
|     printf("%s", string_get_cstr(buffer)); | ||||
|     string_clear(buffer); | ||||
| } | ||||
| 
 | ||||
| void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) { | ||||
|     uint16_t channel; | ||||
|     uint16_t power; | ||||
|     BtSettings bt_settings; | ||||
|     bt_settings_load(&bt_settings); | ||||
| 
 | ||||
|     int ret = sscanf(string_get_cstr(args), "%hu %hu", &channel, &power); | ||||
|     if(ret != 2) { | ||||
|         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); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     furi_hal_bt_stop_advertising(); | ||||
|     printf("Transmitting carrier at %hu channel at %hu dB power\r\n", channel, power); | ||||
|     printf("Press CTRL+C to stop\r\n"); | ||||
|     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); | ||||
|     } | ||||
|     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) { | ||||
|     uint16_t channel; | ||||
|     BtSettings bt_settings; | ||||
|     bt_settings_load(&bt_settings); | ||||
|     int ret = sscanf(string_get_cstr(args), "%hu", &channel); | ||||
|     if(ret != 1) { | ||||
|         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); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     furi_hal_bt_stop_advertising(); | ||||
|     printf("Receiving carrier at %hu channel\r\n", channel); | ||||
|     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(); | ||||
|     if(bt_settings.enabled) { | ||||
|         furi_hal_bt_start_advertising(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) { | ||||
|     uint16_t channel; | ||||
|     uint16_t pattern; | ||||
|     uint16_t datarate; | ||||
|     BtSettings bt_settings; | ||||
|     bt_settings_load(&bt_settings); | ||||
|     int ret = sscanf(string_get_cstr(args), "%hu %hu %hu", &channel, &pattern, &datarate); | ||||
|     if(ret != 3) { | ||||
|         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; | ||||
|     } | ||||
| 
 | ||||
|     furi_hal_bt_stop_advertising(); | ||||
|     printf( | ||||
|         "Transmitting %hu pattern packet at %hu channel at %hu M datarate\r\n", | ||||
|         pattern, | ||||
| @ -118,11 +137,16 @@ void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) { | ||||
|     } | ||||
|     furi_hal_bt_stop_packet_test(); | ||||
|     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) { | ||||
|     uint16_t channel; | ||||
|     uint16_t datarate; | ||||
|     BtSettings bt_settings; | ||||
|     bt_settings_load(&bt_settings); | ||||
|     int ret = sscanf(string_get_cstr(args), "%hu %hu", &channel, &datarate); | ||||
|     if(ret != 2) { | ||||
|         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); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     furi_hal_bt_stop_advertising(); | ||||
|     printf("Receiving packets at %hu channel at %hu M datarate\r\n", channel, datarate); | ||||
|     printf("Press CTRL+C to stop\r\n"); | ||||
|     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(); | ||||
|     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); | ||||
| } | ||||
| 
 | ||||
| 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 = furi_alloc(sizeof(Bt)); | ||||
|     // Load settings
 | ||||
| @ -45,6 +56,11 @@ Bt* bt_alloc() { | ||||
|     bt->dialogs = furi_record_open("dialogs"); | ||||
|     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; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -9,7 +9,8 @@ | ||||
| #include <gui/view_port.h> | ||||
| #include <gui/view.h> | ||||
| 
 | ||||
| #include <applications/dialogs/dialogs.h> | ||||
| #include <dialogs/dialogs.h> | ||||
| #include <power/power_service/power.h> | ||||
| 
 | ||||
| #include "../bt_settings.h" | ||||
| 
 | ||||
| @ -36,4 +37,5 @@ struct Bt { | ||||
|     ViewPort* statusbar_view_port; | ||||
|     DialogsApp* dialogs; | ||||
|     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); | ||||
|             cli->cursor_position = string_size(cli->line); | ||||
|             // 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 == 'C') { | ||||
|  | ||||
| @ -138,7 +138,7 @@ void cli_command_help(Cli* cli, string_t args, void* context) { | ||||
|         } | ||||
|         // Right Column
 | ||||
|         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); | ||||
|         } | ||||
|     }; | ||||
| @ -146,7 +146,7 @@ void cli_command_help(Cli* cli, string_t args, void* context) { | ||||
|     if(string_size(args) > 0) { | ||||
|         cli_nl(); | ||||
|         printf("Also I have no clue what '"); | ||||
|         printf(string_get_cstr(args)); | ||||
|         printf("%s", string_get_cstr(args)); | ||||
|         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 | ||||
| #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) \ | ||||
|     osSemaphoreAcquire(_lock, osWaitForever);      \ | ||||
|     osSemaphoreDelete(_lock); | ||||
| 
 | ||||
| #define API_LOCK_UNLOCK(_lock) osSemaphoreRelease(_lock); | ||||
|     API_LOCK_WAIT_UNTIL_UNLOCK(_lock);             \ | ||||
|     API_LOCK_FREE(_lock); | ||||
|  | ||||
| @ -10,8 +10,8 @@ bool dialog_file_select_show( | ||||
|     char* result, | ||||
|     uint8_t result_size, | ||||
|     const char* preselected_filename) { | ||||
|     osSemaphoreId_t semaphore = API_LOCK_INIT_LOCKED(); | ||||
|     furi_check(semaphore != NULL); | ||||
|     FuriApiLock lock = API_LOCK_INIT_LOCKED(); | ||||
|     furi_check(lock != NULL); | ||||
| 
 | ||||
|     DialogsAppData data = { | ||||
|         .file_select = { | ||||
| @ -24,14 +24,14 @@ bool dialog_file_select_show( | ||||
| 
 | ||||
|     DialogsAppReturn return_data; | ||||
|     DialogsAppMessage message = { | ||||
|         .semaphore = semaphore, | ||||
|         .lock = lock, | ||||
|         .command = DialogsAppCommandFileOpen, | ||||
|         .data = &data, | ||||
|         .return_data = &return_data, | ||||
|     }; | ||||
| 
 | ||||
|     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; | ||||
| } | ||||
| @ -39,8 +39,8 @@ bool dialog_file_select_show( | ||||
| /****************** Message ******************/ | ||||
| 
 | ||||
| DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage* dialog_message) { | ||||
|     osSemaphoreId_t semaphore = API_LOCK_INIT_LOCKED(); | ||||
|     furi_check(semaphore != NULL); | ||||
|     FuriApiLock lock = API_LOCK_INIT_LOCKED(); | ||||
|     furi_check(lock != NULL); | ||||
| 
 | ||||
|     DialogsAppData data = { | ||||
|         .dialog = { | ||||
| @ -49,14 +49,14 @@ DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage | ||||
| 
 | ||||
|     DialogsAppReturn return_data; | ||||
|     DialogsAppMessage message = { | ||||
|         .semaphore = semaphore, | ||||
|         .lock = lock, | ||||
|         .command = DialogsAppCommandDialog, | ||||
|         .data = &data, | ||||
|         .return_data = &return_data, | ||||
|     }; | ||||
| 
 | ||||
|     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; | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include "dialogs-i.h" | ||||
| #include "dialogs-api-lock.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| @ -34,7 +35,7 @@ typedef enum { | ||||
| } DialogsAppCommand; | ||||
| 
 | ||||
| typedef struct { | ||||
|     osSemaphoreId_t semaphore; | ||||
|     FuriApiLock lock; | ||||
|     DialogsAppCommand command; | ||||
|     DialogsAppData* data; | ||||
|     DialogsAppReturn* return_data; | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| #include <gui/modules/file_select.h> | ||||
| 
 | ||||
| typedef struct { | ||||
|     osSemaphoreId_t semaphore; | ||||
|     FuriApiLock lock; | ||||
|     bool result; | ||||
| } DialogsAppFileSelectContext; | ||||
| 
 | ||||
| @ -11,14 +11,14 @@ static void dialogs_app_file_select_back_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     DialogsAppFileSelectContext* file_select_context = context; | ||||
|     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) { | ||||
|     furi_assert(context); | ||||
|     DialogsAppFileSelectContext* file_select_context = context; | ||||
|     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) { | ||||
| @ -27,7 +27,7 @@ bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelec | ||||
| 
 | ||||
|     DialogsAppFileSelectContext* file_select_context = | ||||
|         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(); | ||||
|     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_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; | ||||
| 
 | ||||
|     free(file_select_context); | ||||
|     view_holder_stop(view_holder); | ||||
|     view_holder_free(view_holder); | ||||
|     file_select_free(file_select); | ||||
|     API_LOCK_FREE(file_select_context->lock); | ||||
|     free(file_select_context); | ||||
|     furi_record_close("gui"); | ||||
| 
 | ||||
|     return ret; | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| #include <gui/modules/dialog_ex.h> | ||||
| 
 | ||||
| typedef struct { | ||||
|     osSemaphoreId_t semaphore; | ||||
|     FuriApiLock lock; | ||||
|     DialogMessageButton result; | ||||
| } DialogsAppMessageContext; | ||||
| 
 | ||||
| @ -30,7 +30,7 @@ static void dialogs_app_message_back_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     DialogsAppMessageContext* message_context = context; | ||||
|     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) { | ||||
| @ -47,7 +47,7 @@ static void dialogs_app_message_callback(DialogExResult result, void* context) { | ||||
|         message_context->result = DialogMessageButtonCenter; | ||||
|         break; | ||||
|     } | ||||
|     API_LOCK_UNLOCK(message_context->semaphore); | ||||
|     API_LOCK_UNLOCK(message_context->lock); | ||||
| } | ||||
| 
 | ||||
| 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"); | ||||
|     const DialogMessage* message = data->message; | ||||
|     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(); | ||||
|     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_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; | ||||
| 
 | ||||
|     free(message_context); | ||||
|     view_holder_stop(view_holder); | ||||
|     view_holder_free(view_holder); | ||||
|     dialog_ex_free(dialog_ex); | ||||
|     API_LOCK_FREE(message_context->lock); | ||||
|     free(message_context); | ||||
|     furi_record_close("gui"); | ||||
| 
 | ||||
|     return ret; | ||||
|  | ||||
| @ -21,7 +21,7 @@ static void dialogs_app_process_message(DialogsApp* app, DialogsAppMessage* mess | ||||
|             dialogs_app_process_module_message(&message->data->dialog); | ||||
|         break; | ||||
|     } | ||||
|     API_LOCK_UNLOCK(message->semaphore); | ||||
|     API_LOCK_UNLOCK(message->lock); | ||||
| } | ||||
| 
 | ||||
| int32_t dialogs_srv(void* p) { | ||||
|  | ||||
| @ -1,389 +1,9 @@ | ||||
| #include "dolphin_i.h" | ||||
| #include <stdlib.h> | ||||
| #include "applications.h" | ||||
| #include <furi.h> | ||||
| 
 | ||||
| const Icon* idle_scenes[] = {&A_Wink_128x64, &A_WatchingTV_128x64}; | ||||
| 
 | ||||
| static void dolphin_switch_to_app(Dolphin* dolphin, const FlipperApplication* flipper_app) { | ||||
| bool dolphin_load(Dolphin* dolphin) { | ||||
|     furi_assert(dolphin); | ||||
|     furi_assert(flipper_app); | ||||
|     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); | ||||
|     return dolphin_state_load(dolphin->state); | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| 
 | ||||
| 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(); | ||||
| 
 | ||||
|     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); | ||||
| 
 | ||||
|     if(!furi_hal_version_do_i_belong_here()) { | ||||
|         view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewHwMismatch); | ||||
|     } | ||||
| 
 | ||||
|     DolphinEvent event; | ||||
|     while(1) { | ||||
|         furi_check(osMessageQueueGet(dolphin->event_queue, &event, NULL, osWaitForever) == osOK); | ||||
| 
 | ||||
|         DolphinViewLockMenuModel* lock_model = view_get_model(dolphin->view_lockmenu); | ||||
| 
 | ||||
|         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) { | ||||
|         switch(event.type) { | ||||
|         case DolphinEventTypeDeed: | ||||
|             dolphin_state_on_deed(dolphin->state, event.deed); | ||||
|             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; | ||||
|                 }); | ||||
|         } else if(event.type == DolphinEventTypeSave) { | ||||
|             break; | ||||
| 
 | ||||
|         case DolphinEventTypeSave: | ||||
|             dolphin_state_save(dolphin->state); | ||||
|             break; | ||||
| 
 | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     dolphin_free(dolphin); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| @ -1,11 +1,30 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "dolphin_deed.h" | ||||
| #include "helpers/dolphin_deed.h" | ||||
| 
 | ||||
| typedef struct Dolphin Dolphin; | ||||
| 
 | ||||
| /* Load Dolphin state
 | ||||
|  * Thread safe | ||||
|  */ | ||||
| 
 | ||||
| bool dolphin_load(Dolphin* dolphin); | ||||
| 
 | ||||
| /* Deed complete notification. Call it on deed completion.
 | ||||
|  * See dolphin_deed.h for available deeds. In futures it will become part of assets. | ||||
|  * Thread safe | ||||
|  */ | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| #include "dolphin.h" | ||||
| #include "dolphin_state.h" | ||||
| #include "dolphin_views.h" | ||||
| 
 | ||||
| #include <furi.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 <stdint.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)
 | ||||
| #include "dolphin.h" | ||||
| #include "helpers/dolphin_state.h" | ||||
| 
 | ||||
| typedef enum { | ||||
|     DolphinEventTypeDeed, | ||||
| @ -32,36 +20,12 @@ typedef struct { | ||||
| } DolphinEvent; | ||||
| 
 | ||||
| struct Dolphin { | ||||
|     // Internal message queue
 | ||||
|     osMessageQueueId_t event_queue; | ||||
|     // State
 | ||||
|     DolphinState* state; | ||||
|     // Menu
 | ||||
|     ValueMutex* menu_vm; | ||||
|     // 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; | ||||
|     // Queue
 | ||||
|     osMessageQueueId_t event_queue; | ||||
| }; | ||||
| 
 | ||||
| Dolphin* dolphin_alloc(); | ||||
| 
 | ||||
| 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 { | ||||
|     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_interval; // interval, in minutes
 | ||||
| } DolphinDeedWeight; | ||||
| @ -18,6 +18,7 @@ static const GpioItem GPIO_PINS[] = { | ||||
|     {"1.7: PC3", &gpio_ext_pc3}, | ||||
|     {"2.7: PC1", &gpio_ext_pc1}, | ||||
|     {"2.8: PC0", &gpio_ext_pc0}, | ||||
|     {"*.*: ALL", NULL}, | ||||
| }; | ||||
| 
 | ||||
| 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) { | ||||
|     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_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 { | ||||
|             if(event.key == InputKeyOk) { | ||||
|                 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); | ||||
|                 } 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); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @ -327,6 +327,17 @@ void elements_slightly_rounded_box( | ||||
|     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) { | ||||
|     furi_assert(canvas); | ||||
|     furi_assert(string); | ||||
|  | ||||
| @ -8,8 +8,7 @@ | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw progress bar. | ||||
| /** Draw progress bar.
 | ||||
|  * @param x - progress bar position on X axis | ||||
|  * @param y - progress bar position on Y axis | ||||
|  * @param width - progress bar width | ||||
| @ -24,8 +23,7 @@ void elements_progress_bar( | ||||
|     uint8_t progress, | ||||
|     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 y - scrollbar position on Y axis | ||||
|  * @param height - scrollbar height | ||||
| @ -40,41 +38,35 @@ void elements_scrollbar_pos( | ||||
|     uint16_t pos, | ||||
|     uint16_t total); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw scrollbar on canvas. | ||||
| /** Draw scrollbar on canvas.
 | ||||
|  * width 3px, height equal to canvas height | ||||
|  * @param pos - current element of total elements | ||||
|  * @param total - total elements | ||||
|  */ | ||||
| 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 width, height - frame width and 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 | ||||
|  */ | ||||
| void elements_button_left(Canvas* canvas, const char* str); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw button in right corner | ||||
| /** Draw button in right corner
 | ||||
|  * @param str - button text | ||||
|  */ | ||||
| void elements_button_right(Canvas* canvas, const char* str); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw button in center | ||||
| /** Draw button in center
 | ||||
|  * @param str - button text | ||||
|  */ | ||||
| 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 horizontal, vertical - aligment of multiline text | ||||
|  * @param text - string (possible multiline) | ||||
| @ -87,22 +79,19 @@ void elements_multiline_text_aligned( | ||||
|     Align vertical, | ||||
|     const char* text); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw multiline text | ||||
| /** Draw multiline text
 | ||||
|  * @param x, y - top left corner coordinates | ||||
|  * @param text - string (possible multiline) | ||||
|  */ | ||||
| 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 text - string (possible multiline) | ||||
|  */ | ||||
| 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 width, height - size of frame | ||||
|  */ | ||||
| @ -113,8 +102,7 @@ void elements_slightly_rounded_frame( | ||||
|     uint8_t width, | ||||
|     uint8_t height); | ||||
| 
 | ||||
| /*
 | ||||
|  * Draw slightly rounded box | ||||
| /** Draw slightly rounded box
 | ||||
|  * @param x, y - top left corner coordinates | ||||
|  * @param width, height - size of box | ||||
|  */ | ||||
| @ -125,8 +113,15 @@ void elements_slightly_rounded_box( | ||||
|     uint8_t width, | ||||
|     uint8_t height); | ||||
| 
 | ||||
| /*
 | ||||
|  * Trim string buffer to fit width in pixels | ||||
| /** Draw bubble frame for text
 | ||||
|  * @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 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 <gui/elements.h> | ||||
| #include <m-string.h> | ||||
| #include <sys/param.h> | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| #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 "gui/canvas.h" | ||||
| 
 | ||||
| #include <m-array.h> | ||||
| #include <furi.h> | ||||
| #include <gui/elements.h> | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| struct SubmenuItem { | ||||
|     const char* label; | ||||
|     uint32_t index; | ||||
|     SubmenuItemCallback callback; | ||||
|     void* callback_context; | ||||
| }; | ||||
| 
 | ||||
| ARRAY_DEF(SubmenuItemArray, SubmenuItem, M_POD_OPLIST); | ||||
| #include <furi.h> | ||||
| 
 | ||||
| struct Submenu { | ||||
|     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 { | ||||
|     SubmenuItemArray_t items; | ||||
|     const char* header; | ||||
| @ -149,7 +148,7 @@ View* submenu_get_view(Submenu* submenu) { | ||||
|     return submenu->view; | ||||
| } | ||||
| 
 | ||||
| SubmenuItem* submenu_add_item( | ||||
| void submenu_add_item( | ||||
|     Submenu* submenu, | ||||
|     const char* label, | ||||
|     uint32_t index, | ||||
| @ -168,8 +167,6 @@ SubmenuItem* submenu_add_item( | ||||
|             item->callback_context = callback_context; | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     return item; | ||||
| } | ||||
| 
 | ||||
| void submenu_clean(Submenu* submenu) { | ||||
|  | ||||
| @ -7,7 +7,6 @@ extern "C" { | ||||
| 
 | ||||
| /* Submenu anonymous structure */ | ||||
| typedef struct Submenu Submenu; | ||||
| typedef struct SubmenuItem SubmenuItem; | ||||
| 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 callback - menu item callback | ||||
|  * @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, | ||||
|     const char* label, | ||||
|     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_input_callback(text_input->view, text_input_view_input_callback); | ||||
| 
 | ||||
|     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; | ||||
|             return true; | ||||
|         }); | ||||
|     text_input_clean(text_input); | ||||
| 
 | ||||
|     return text_input; | ||||
| } | ||||
| @ -388,6 +380,23 @@ void text_input_free(TextInput* 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) { | ||||
|     furi_assert(text_input); | ||||
|     return text_input->view; | ||||
| @ -407,6 +416,11 @@ void text_input_set_result_callback( | ||||
|             model->text_buffer = text_buffer; | ||||
|             model->text_buffer_size = text_buffer_size; | ||||
|             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; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @ -9,32 +9,31 @@ extern "C" { | ||||
| typedef struct TextInput TextInput; | ||||
| typedef void (*TextInputCallback)(void* context); | ||||
| 
 | ||||
| /** 
 | ||||
|  * @brief Allocate and initialize text input | ||||
| /** Allocate and initialize text input
 | ||||
|  * This text input is used to enter string | ||||
|  *  | ||||
|  * @return TextInput instance | ||||
|  */ | ||||
| TextInput* text_input_alloc(); | ||||
| 
 | ||||
| /** 
 | ||||
|  * @brief Deinitialize and free text input | ||||
|  *  | ||||
|  * @param text_input - Text input instance | ||||
| /** Deinitialize and free text input
 | ||||
|  * @param text_input - TextInput instance | ||||
|  */ | ||||
| void text_input_free(TextInput* text_input); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Get text input view | ||||
|  *  | ||||
| /** Clean text input view
 | ||||
|  * Note: this function does not free memory | ||||
|  * @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 | ||||
|  */ | ||||
| View* text_input_get_view(TextInput* text_input); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Set text input result callback | ||||
|  *  | ||||
|  * @param text_input - Text input instance | ||||
| /** Set text input result callback
 | ||||
|  * @param text_input - TextInput instance | ||||
|  * @param callback - callback fn | ||||
|  * @param callback_context - callback context | ||||
|  * @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, | ||||
|     bool clear_default_text); | ||||
| 
 | ||||
| /** 
 | ||||
|  * @brief Set text input header text | ||||
|  *  | ||||
|  * @param text input - Text input instance | ||||
| /** Set text input header text
 | ||||
|  * @param text_input - TextInput instance | ||||
|  * @param text - text to be shown | ||||
|  */ | ||||
| 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(); | ||||
| 
 | ||||
|     // start pulse
 | ||||
|     pulse_data[0] = metakom_period_full * 4; | ||||
|     pulse_data[0] = metakom_period_full; | ||||
| 
 | ||||
|     // start triplet
 | ||||
|     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) { | ||||
|     // pulldown lf-rfid pins to prevent interference
 | ||||
|     // TODO open record
 | ||||
|     GpioPin rfid_pull_pin = {.port = RFID_PULL_GPIO_Port, .pin = RFID_PULL_Pin}; | ||||
|     hal_gpio_init(&rfid_pull_pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); | ||||
|     hal_gpio_write(&rfid_pull_pin, false); | ||||
|     hal_gpio_init(&gpio_rfid_pull, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); | ||||
|     hal_gpio_write(&gpio_rfid_pull, false); | ||||
| 
 | ||||
|     // TODO open record
 | ||||
|     GpioPin rfid_out_pin = {.port = RFID_OUT_GPIO_Port, .pin = RFID_OUT_Pin}; | ||||
|     hal_gpio_init(&rfid_out_pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); | ||||
|     hal_gpio_write(&rfid_out_pin, false); | ||||
|     hal_gpio_init(&gpio_rfid_carrier_out, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); | ||||
|     hal_gpio_write(&gpio_rfid_carrier_out, false); | ||||
| 
 | ||||
|     comparator_callback_pointer = | ||||
|         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( | ||||
|             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(); | ||||
|     } | ||||
| 
 | ||||
|     HAL_NVIC_SetPriority(TIM1_UP_TIM16_IRQn, 5, 0); | ||||
|     HAL_NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn); | ||||
| 
 | ||||
|     hal_gpio_init(&ibutton_gpio, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); | ||||
|  | ||||
| @ -248,6 +248,8 @@ void onewire_cli_search(Cli* cli) { | ||||
|     printf("Search started\r\n"); | ||||
| 
 | ||||
|     onewire.start(); | ||||
|     furi_hal_power_enable_otg(); | ||||
| 
 | ||||
|     while(!done) { | ||||
|         if(onewire.search(address, true) != 1) { | ||||
|             printf("Search finished\r\n"); | ||||
| @ -263,6 +265,8 @@ void onewire_cli_search(Cli* cli) { | ||||
|         } | ||||
|         delay(100); | ||||
|     } | ||||
| 
 | ||||
|     furi_hal_power_disable_otg(); | ||||
|     onewire.stop(); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -48,8 +48,7 @@ bool iButtonSceneSaveName::on_event(iButtonApp* app, iButtonEvent* event) { | ||||
| 
 | ||||
| void iButtonSceneSaveName::on_exit(iButtonApp* app) { | ||||
|     TextInput* text_input = app->get_view_manager()->get_text_input(); | ||||
|     text_input_set_header_text(text_input, ""); | ||||
|     text_input_set_result_callback(text_input, NULL, NULL, NULL, 0, false); | ||||
|     text_input_clean(text_input); | ||||
| } | ||||
| 
 | ||||
| void iButtonSceneSaveName::text_input_callback(void* context) { | ||||
|  | ||||
							
								
								
									
										248
									
								
								applications/loader/loader.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										248
									
								
								applications/loader/loader.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @ -1,8 +1,11 @@ | ||||
| #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 void loader_menu_callback(void* _ctx) { | ||||
| static void loader_menu_callback(void* _ctx, uint32_t index) { | ||||
|     const FlipperApplication* flipper_app = _ctx; | ||||
| 
 | ||||
|     furi_assert(flipper_app->app); | ||||
| @ -27,6 +30,11 @@ static void loader_menu_callback(void* _ctx) { | ||||
|     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) { | ||||
|     furi_assert(_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) { | ||||
|         FURI_LOG_E(LOADER_LOG_TAG, "Can't find application with name %s", name); | ||||
|         return false; | ||||
| @ -73,9 +90,11 @@ bool loader_start(Loader* instance, const char* name, const char* args) { | ||||
|     } | ||||
| 
 | ||||
|     instance->current_app = flipper_app; | ||||
|     void* thread_args = NULL; | ||||
|     if(args) { | ||||
|         string_set_str(instance->args, 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); | ||||
|     } else { | ||||
|         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_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); | ||||
| 
 | ||||
|     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() { | ||||
|     Loader* instance = furi_alloc(sizeof(Loader)); | ||||
| 
 | ||||
| @ -150,10 +177,45 @@ static Loader* loader_alloc() { | ||||
| 
 | ||||
|     instance->mutex = osMutexNew(NULL); | ||||
| 
 | ||||
|     instance->menu_vm = furi_record_open("menu"); | ||||
| 
 | ||||
|     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; | ||||
| } | ||||
| 
 | ||||
| @ -162,133 +224,111 @@ static void loader_free(Loader* instance) { | ||||
| 
 | ||||
|     furi_record_close("cli"); | ||||
| 
 | ||||
|     furi_record_close("menu"); | ||||
| 
 | ||||
|     osMutexDelete(instance->mutex); | ||||
| 
 | ||||
|     string_clear(instance->args); | ||||
| 
 | ||||
|     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); | ||||
|     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() { | ||||
|     FURI_LOG_I(LOADER_LOG_TAG, "Building main menu"); | ||||
|     with_value_mutex( | ||||
|         loader_instance->menu_vm, (Menu * menu) { | ||||
|             for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { | ||||
|                 // Add menu item
 | ||||
|                 menu_item_add( | ||||
|                     menu, | ||||
|                     menu_item_alloc_function( | ||||
|     size_t i; | ||||
|     for(i = 0; i < FLIPPER_APPS_COUNT; i++) { | ||||
|         loader_add_cli_command((FlipperApplication*)&FLIPPER_APPS[i]); | ||||
|         menu_add_item( | ||||
|             loader_instance->primary_menu, | ||||
|             FLIPPER_APPS[i].name, | ||||
|             FLIPPER_APPS[i].icon ? icon_animation_alloc(FLIPPER_APPS[i].icon) : NULL, | ||||
|             i, | ||||
|             loader_menu_callback, | ||||
|                         (void*)&FLIPPER_APPS[i])); | ||||
| 
 | ||||
|                 // Add cli command
 | ||||
|                 string_t cli_name; | ||||
|                 string_init_set_str(cli_name, "app_"); | ||||
|                 string_cat_str(cli_name, FLIPPER_APPS[i].name); | ||||
|                 cli_add_command( | ||||
|                     loader_instance->cli, | ||||
|                     string_get_cstr(cli_name), | ||||
|                     CliCommandFlagDefault, | ||||
|                     loader_cli_callback, | ||||
|             (void*)&FLIPPER_APPS[i]); | ||||
|                 string_clear(cli_name); | ||||
|     } | ||||
|         }); | ||||
|     menu_add_item( | ||||
|         loader_instance->primary_menu, | ||||
|         "Plugins", | ||||
|         icon_animation_alloc(&A_Plugins_14), | ||||
|         i++, | ||||
|         loader_submenu_callback, | ||||
|         (void*)LoaderMenuViewPlugins); | ||||
|     menu_add_item( | ||||
|         loader_instance->primary_menu, | ||||
|         "Debug tools", | ||||
|         icon_animation_alloc(&A_Debug_14), | ||||
|         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"); | ||||
|     with_value_mutex( | ||||
|         loader_instance->menu_vm, (Menu * menu) { | ||||
|             MenuItem* menu_plugins = | ||||
|                 menu_item_alloc_menu("Plugins", icon_animation_alloc(&A_Plugins_14)); | ||||
| 
 | ||||
|             for(size_t i = 0; i < FLIPPER_PLUGINS_COUNT; i++) { | ||||
|                 // Add menu item
 | ||||
|                 menu_item_subitem_add( | ||||
|                     menu_plugins, | ||||
|                     menu_item_alloc_function( | ||||
|     for(i = 0; i < FLIPPER_PLUGINS_COUNT; i++) { | ||||
|         loader_add_cli_command((FlipperApplication*)&FLIPPER_PLUGINS[i]); | ||||
|         submenu_add_item( | ||||
|             loader_instance->plugins_menu, | ||||
|             FLIPPER_PLUGINS[i].name, | ||||
|                         FLIPPER_PLUGINS[i].icon ? icon_animation_alloc(FLIPPER_PLUGINS[i].icon) : | ||||
|                                                   NULL, | ||||
|             i, | ||||
|             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"); | ||||
|     with_value_mutex( | ||||
|         loader_instance->menu_vm, (Menu * menu) { | ||||
|             MenuItem* menu_debug = | ||||
|                 menu_item_alloc_menu("Debug tools", icon_animation_alloc(&A_Debug_14)); | ||||
| 
 | ||||
|             for(size_t i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) { | ||||
|                 // Add menu item
 | ||||
|                 menu_item_subitem_add( | ||||
|                     menu_debug, | ||||
|                     menu_item_alloc_function( | ||||
|     for(i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) { | ||||
|         loader_add_cli_command((FlipperApplication*)&FLIPPER_DEBUG_APPS[i]); | ||||
|         submenu_add_item( | ||||
|             loader_instance->debug_menu, | ||||
|             FLIPPER_DEBUG_APPS[i].name, | ||||
|                         FLIPPER_DEBUG_APPS[i].icon ? | ||||
|                             icon_animation_alloc(FLIPPER_DEBUG_APPS[i].icon) : | ||||
|                             NULL, | ||||
|             i, | ||||
|             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"); | ||||
|     with_value_mutex( | ||||
|         loader_instance->menu_vm, (Menu * menu) { | ||||
|             MenuItem* menu_debug = | ||||
|                 menu_item_alloc_menu("Settings", icon_animation_alloc(&A_Settings_14)); | ||||
| 
 | ||||
|             for(size_t i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { | ||||
|                 // Add menu item
 | ||||
|                 menu_item_subitem_add( | ||||
|                     menu_debug, | ||||
|                     menu_item_alloc_function( | ||||
|     for(i = 0; i < FLIPPER_SETTINGS_APPS_COUNT; i++) { | ||||
|         submenu_add_item( | ||||
|             loader_instance->settings_menu, | ||||
|             FLIPPER_SETTINGS_APPS[i].name, | ||||
|                         FLIPPER_SETTINGS_APPS[i].icon ? | ||||
|                             icon_animation_alloc(FLIPPER_SETTINGS_APPS[i].icon) : | ||||
|                             NULL, | ||||
|             i, | ||||
|             loader_menu_callback, | ||||
|                         (void*)&FLIPPER_SETTINGS_APPS[i])); | ||||
|             (void*)&FLIPPER_SETTINGS_APPS[i]); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|             menu_item_add(menu, menu_debug); | ||||
|         }); | ||||
| void loader_show_menu() { | ||||
|     furi_assert(loader_instance); | ||||
|     osThreadFlagsSet(loader_instance->loader_thread, LOADER_THREAD_FLAG_SHOW_MENU); | ||||
| } | ||||
| 
 | ||||
| int32_t loader_srv(void* p) { | ||||
| @ -300,15 +340,25 @@ int32_t loader_srv(void* p) { | ||||
| 
 | ||||
|     // Call on start hooks
 | ||||
|     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_record_create("loader", loader_instance); | ||||
| 
 | ||||
| #ifdef LOADER_AUTOSTART | ||||
|     loader_start(loader_instance, LOADER_AUTOSTART, NULL); | ||||
| #endif | ||||
| 
 | ||||
|     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); | ||||
|  | ||||
| @ -18,3 +18,6 @@ bool loader_lock(Loader* instance); | ||||
| 
 | ||||
| /** Unlock application start */ | ||||
| void loader_unlock(Loader* instance); | ||||
| 
 | ||||
| /** Show primary loader */ | ||||
| void loader_show_menu(); | ||||
|  | ||||
| @ -3,20 +3,39 @@ | ||||
| #include <furi.h> | ||||
| #include <furi-hal.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 <assets_icons.h> | ||||
| 
 | ||||
| #define LOADER_LOG_TAG "loader" | ||||
| 
 | ||||
| struct Loader { | ||||
|     osThreadId_t loader_thread; | ||||
|     FuriThread* thread; | ||||
|     const FlipperApplication* current_app; | ||||
|     string_t args; | ||||
|     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; | ||||
|     osMutexId_t mutex; | ||||
|     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) { | ||||
|             printf("Found %d devices\r\n", dev_cnt); | ||||
|             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) { | ||||
|                     printf("type: %s, ", nfc_get_nfca_type(dev_list[i].dev.nfca.type)); | ||||
|                 } | ||||
|  | ||||
| @ -6,19 +6,33 @@ | ||||
| #include <gui/view_dispatcher.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) { | ||||
|         return "NFC-A may be:"; | ||||
|         return "NFC-A"; | ||||
|     } else if(type == RFAL_NFC_LISTEN_TYPE_NFCB) { | ||||
|         return "NFC-B may be:"; | ||||
|         return "NFC-B"; | ||||
|     } else if(type == RFAL_NFC_LISTEN_TYPE_NFCF) { | ||||
|         return "NFC-F may be:"; | ||||
|         return "NFC-F"; | ||||
|     } else if(type == RFAL_NFC_LISTEN_TYPE_NFCV) { | ||||
|         return "NFC-V may be:"; | ||||
|         return "NFC-V"; | ||||
|     } else if(type == RFAL_NFC_LISTEN_TYPE_ST25TB) { | ||||
|         return "NFC-ST25TB may be:"; | ||||
|         return "NFC-ST25TB"; | ||||
|     } 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 { | ||||
|         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); | ||||
| } | ||||
| 
 | ||||
| const void nfc_scene_card_menu_on_enter(void* context) { | ||||
| void nfc_scene_card_menu_on_enter(void* context) { | ||||
|     Nfc* nfc = (Nfc*)context; | ||||
|     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); | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
| @ -78,7 +78,7 @@ const bool nfc_scene_card_menu_on_event(void* context, SceneManagerEvent event) | ||||
|     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; | ||||
| 
 | ||||
|     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); | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
| @ -85,7 +85,7 @@ const bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| const void nfc_scene_delete_on_exit(void* context) { | ||||
| void nfc_scene_delete_on_exit(void* context) { | ||||
|     Nfc* nfc = (Nfc*)context; | ||||
| 
 | ||||
|     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