Merge branch 'release-candidate' into release
This commit is contained in:
		
						commit
						fffacfbe26
					
				
							
								
								
									
										4
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| # Who owns all the code by default | # Who owns all the fish by default | ||||||
| 
 | 
 | ||||||
| * @skotopes @DrZlo13 | * @skotopes @DrZlo13 | ||||||
| 
 | 
 | ||||||
| @ -8,7 +8,7 @@ applications/accessor/** @skotopes @DrZlo13 | |||||||
| applications/loader/** @skotopes @DrZlo13 @gornekich | applications/loader/** @skotopes @DrZlo13 @gornekich | ||||||
| applications/bt/** @skotopes @DrZlo13 | applications/bt/** @skotopes @DrZlo13 | ||||||
| applications/cli/** @skotopes @DrZlo13 | applications/cli/** @skotopes @DrZlo13 | ||||||
| applications/dolphin/** @skotopes @DrZlo13 @itsyourbedtime | applications/dolphin/** @skotopes @DrZlo13 | ||||||
| applications/gpio-tester/** @skotopes @DrZlo13 | applications/gpio-tester/** @skotopes @DrZlo13 | ||||||
| applications/gui/** @skotopes @DrZlo13 | applications/gui/** @skotopes @DrZlo13 | ||||||
| applications/gui-test/** @skotopes @DrZlo13 | applications/gui-test/** @skotopes @DrZlo13 | ||||||
|  | |||||||
| @ -89,6 +89,12 @@ static DialogMessageButton hw_version_screen(DialogsApp* dialogs, DialogMessage* | |||||||
|         furi_hal_version_get_hw_connect(), |         furi_hal_version_get_hw_connect(), | ||||||
|         my_name ? my_name : "Unknown"); |         my_name ? my_name : "Unknown"); | ||||||
| 
 | 
 | ||||||
|  |     string_cat_printf(buffer, "Serial number:\n"); | ||||||
|  |     const uint8_t* uid = furi_hal_version_uid(); | ||||||
|  |     for(size_t i = 0; i < furi_hal_version_uid_size(); i++) { | ||||||
|  |         string_cat_printf(buffer, "%02X", uid[i]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     dialog_message_set_header(message, "HW Version info:", 0, 0, AlignLeft, AlignTop); |     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); |     dialog_message_set_text(message, string_get_cstr(buffer), 0, 13, AlignLeft, AlignTop); | ||||||
|     result = dialog_message_show(dialogs, message); |     result = dialog_message_show(dialogs, message); | ||||||
|  | |||||||
| @ -25,4 +25,5 @@ struct ArchiveApp { | |||||||
|     ArchiveBrowserView* browser; |     ArchiveBrowserView* browser; | ||||||
|     TextInput* text_input; |     TextInput* text_input; | ||||||
|     char text_store[MAX_NAME_LEN]; |     char text_store[MAX_NAME_LEN]; | ||||||
|  |     char file_extension[MAX_EXT_LEN + 1]; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -272,7 +272,6 @@ void archive_enter_dir(ArchiveBrowserView* browser, string_t name) { | |||||||
|     with_view_model( |     with_view_model( | ||||||
|         browser->view, (ArchiveBrowserViewModel * model) { |         browser->view, (ArchiveBrowserViewModel * model) { | ||||||
|             model->last_idx = model->idx; |             model->last_idx = model->idx; | ||||||
|             model->last_offset = model->list_offset; |  | ||||||
|             model->idx = 0; |             model->idx = 0; | ||||||
|             model->depth = CLAMP(model->depth + 1, MAX_DEPTH, 0); |             model->depth = CLAMP(model->depth + 1, MAX_DEPTH, 0); | ||||||
|             return false; |             return false; | ||||||
|  | |||||||
| @ -31,6 +31,40 @@ uint16_t archive_favorites_count(void* context) { | |||||||
|     return lines; |     return lines; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static bool archive_favourites_rescan() { | ||||||
|  |     string_t buffer; | ||||||
|  |     string_init(buffer); | ||||||
|  |     FileWorker* file_worker = file_worker_alloc(true); | ||||||
|  | 
 | ||||||
|  |     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||||
|  |     if(result) { | ||||||
|  |         while(1) { | ||||||
|  |             if(!file_worker_read_until(file_worker, buffer, '\n')) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(!string_size(buffer)) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             bool file_exists = false; | ||||||
|  |             file_worker_is_file_exist(file_worker, string_get_cstr(buffer), &file_exists); | ||||||
|  |             if(file_exists) { | ||||||
|  |                 archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(buffer)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(buffer); | ||||||
|  | 
 | ||||||
|  |     file_worker_close(file_worker); | ||||||
|  |     file_worker_remove(file_worker, ARCHIVE_FAV_PATH); | ||||||
|  |     file_worker_rename(file_worker, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH); | ||||||
|  | 
 | ||||||
|  |     file_worker_free(file_worker); | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool archive_favorites_read(void* context) { | bool archive_favorites_read(void* context) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| 
 | 
 | ||||||
| @ -41,6 +75,8 @@ bool archive_favorites_read(void* context) { | |||||||
|     FileInfo file_info; |     FileInfo file_info; | ||||||
|     string_init(buffer); |     string_init(buffer); | ||||||
| 
 | 
 | ||||||
|  |     bool need_refresh = false; | ||||||
|  | 
 | ||||||
|     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); |     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||||
| 
 | 
 | ||||||
|     if(result) { |     if(result) { | ||||||
| @ -52,13 +88,24 @@ bool archive_favorites_read(void* context) { | |||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             archive_add_item(browser, &file_info, string_get_cstr(buffer)); |             bool file_exists = false; | ||||||
|  |             file_worker_is_file_exist(file_worker, string_get_cstr(buffer), &file_exists); | ||||||
|  | 
 | ||||||
|  |             if(file_exists) | ||||||
|  |                 archive_add_item(browser, &file_info, string_get_cstr(buffer)); | ||||||
|  |             else | ||||||
|  |                 need_refresh = true; | ||||||
|             string_reset(buffer); |             string_reset(buffer); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     string_clear(buffer); |     string_clear(buffer); | ||||||
|     file_worker_close(file_worker); |     file_worker_close(file_worker); | ||||||
|     file_worker_free(file_worker); |     file_worker_free(file_worker); | ||||||
|  | 
 | ||||||
|  |     if(need_refresh) { | ||||||
|  |         archive_favourites_rescan(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -30,6 +30,14 @@ void archive_trim_file_path(char* name, bool ext) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void archive_get_file_extension(char* name, char* ext) { | ||||||
|  |     char* dot = strrchr(name, '.'); | ||||||
|  |     if(dot == NULL) | ||||||
|  |         *ext = '\0'; | ||||||
|  |     else | ||||||
|  |         strncpy(ext, dot, MAX_EXT_LEN); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void set_file_type(ArchiveFile_t* file, FileInfo* file_info) { | void set_file_type(ArchiveFile_t* file, FileInfo* file_info) { | ||||||
|     furi_assert(file); |     furi_assert(file); | ||||||
|     furi_assert(file_info); |     furi_assert(file_info); | ||||||
|  | |||||||
| @ -50,6 +50,7 @@ ARRAY_DEF( | |||||||
| bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name); | bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name); | ||||||
| void set_file_type(ArchiveFile_t* file, FileInfo* file_info); | void set_file_type(ArchiveFile_t* file, FileInfo* file_info); | ||||||
| void archive_trim_file_path(char* name, bool ext); | void archive_trim_file_path(char* name, bool ext); | ||||||
|  | void archive_get_file_extension(char* name, char* ext); | ||||||
| bool archive_get_filenames(void* context, const char* path); | bool archive_get_filenames(void* context, const char* path); | ||||||
| bool archive_dir_empty(void* context, const char* path); | bool archive_dir_empty(void* context, const char* path); | ||||||
| bool archive_read_dir(void* context, const char* path); | bool archive_read_dir(void* context, const char* path); | ||||||
|  | |||||||
| @ -18,6 +18,7 @@ void archive_scene_rename_on_enter(void* context) { | |||||||
|     ArchiveFile_t* current = archive_get_current_file(archive->browser); |     ArchiveFile_t* current = archive_get_current_file(archive->browser); | ||||||
|     strlcpy(archive->text_store, string_get_cstr(current->name), MAX_NAME_LEN); |     strlcpy(archive->text_store, string_get_cstr(current->name), MAX_NAME_LEN); | ||||||
| 
 | 
 | ||||||
|  |     archive_get_file_extension(archive->text_store, archive->file_extension); | ||||||
|     archive_trim_file_path(archive->text_store, true); |     archive_trim_file_path(archive->text_store, true); | ||||||
| 
 | 
 | ||||||
|     text_input_set_header_text(text_input, "Rename:"); |     text_input_set_header_text(text_input, "Rename:"); | ||||||
| @ -30,6 +31,10 @@ void archive_scene_rename_on_enter(void* context) { | |||||||
|         MAX_TEXT_INPUT_LEN, |         MAX_TEXT_INPUT_LEN, | ||||||
|         false); |         false); | ||||||
| 
 | 
 | ||||||
|  |     ValidatorIsFile* validator_is_file = | ||||||
|  |         validator_is_file_alloc_init(archive_get_path(archive->browser), archive->file_extension); | ||||||
|  |     text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); | ||||||
|  | 
 | ||||||
|     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewTextInput); |     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewTextInput); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -74,6 +79,11 @@ bool archive_scene_rename_on_event(void* context, SceneManagerEvent event) { | |||||||
| 
 | 
 | ||||||
| void archive_scene_rename_on_exit(void* context) { | void archive_scene_rename_on_exit(void* context) { | ||||||
|     ArchiveApp* archive = (ArchiveApp*)context; |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
|  | 
 | ||||||
|     // Clear view
 |     // Clear view
 | ||||||
|     text_input_clean(archive->text_input); |     void* validator_context = text_input_get_validator_callback_context(archive->text_input); | ||||||
|  |     text_input_set_validator(archive->text_input, NULL, NULL); | ||||||
|  |     validator_is_file_free(validator_context); | ||||||
|  | 
 | ||||||
|  |     text_input_reset(archive->text_input); | ||||||
| } | } | ||||||
|  | |||||||
| @ -11,6 +11,7 @@ | |||||||
| 
 | 
 | ||||||
| #define MAX_LEN_PX 110 | #define MAX_LEN_PX 110 | ||||||
| #define MAX_NAME_LEN 255 | #define MAX_NAME_LEN 255 | ||||||
|  | #define MAX_EXT_LEN 6 | ||||||
| #define FRAME_HEIGHT 12 | #define FRAME_HEIGHT 12 | ||||||
| #define MENU_ITEMS 4 | #define MENU_ITEMS 4 | ||||||
| #define MAX_DEPTH 32 | #define MAX_DEPTH 32 | ||||||
|  | |||||||
| @ -71,7 +71,7 @@ void bad_usb_app_free(BadUsbApp* app) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int32_t bad_usb_app(void* p) { | int32_t bad_usb_app(void* p) { | ||||||
|     UsbInterface* usb_mode_prev = furi_hal_usb_get_config(); |     FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); | ||||||
|     furi_hal_usb_set_config(&usb_hid); |     furi_hal_usb_set_config(&usb_hid); | ||||||
| 
 | 
 | ||||||
|     BadUsbApp* bad_usb_app = bad_usb_app_alloc(); |     BadUsbApp* bad_usb_app = bad_usb_app_alloc(); | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ | |||||||
| #include <furi_hal_usb_hid.h> | #include <furi_hal_usb_hid.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| #include "bad_usb_script.h" | #include "bad_usb_script.h" | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
| 
 | 
 | ||||||
| #define TAG "BadUSB" | #define TAG "BadUSB" | ||||||
| #define WORKER_TAG TAG "Worker" | #define WORKER_TAG TAG "Worker" | ||||||
| @ -442,6 +443,7 @@ static int32_t bad_usb_worker(void* context) { | |||||||
|             if(flags & WorkerEvtEnd) { |             if(flags & WorkerEvtEnd) { | ||||||
|                 break; |                 break; | ||||||
|             } else if(flags & WorkerEvtToggle) { // Start executing script
 |             } else if(flags & WorkerEvtToggle) { // Start executing script
 | ||||||
|  |                 DOLPHIN_DEED(DolphinDeedBadUsbPlayScript); | ||||||
|                 delay_val = 0; |                 delay_val = 0; | ||||||
|                 bad_usb->buf_len = 0; |                 bad_usb->buf_len = 0; | ||||||
|                 bad_usb->st.line_cur = 0; |                 bad_usb->st.line_cur = 0; | ||||||
|  | |||||||
| @ -2,7 +2,8 @@ | |||||||
| #include "battery_service.h" | #include "battery_service.h" | ||||||
| #include "bt_keys_storage.h" | #include "bt_keys_storage.h" | ||||||
| 
 | 
 | ||||||
| #include <applications/notification/notification_messages.h> | #include <notification/notification_messages.h> | ||||||
|  | #include <gui/elements.h> | ||||||
| 
 | 
 | ||||||
| #define TAG "BtSrv" | #define TAG "BtSrv" | ||||||
| 
 | 
 | ||||||
| @ -29,17 +30,46 @@ static ViewPort* bt_statusbar_view_port_alloc(Bt* bt) { | |||||||
|     return statusbar_view_port; |     return statusbar_view_port; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void bt_pin_code_show_event_handler(Bt* bt, uint32_t pin) { | static void bt_pin_code_view_port_draw_callback(Canvas* canvas, void* context) { | ||||||
|     furi_assert(bt); |     furi_assert(context); | ||||||
|  |     Bt* bt = context; | ||||||
|  |     char pin_code_info[24]; | ||||||
|  |     canvas_draw_icon(canvas, 0, 0, &I_BLE_Pairing_128x64); | ||||||
|  |     snprintf(pin_code_info, sizeof(pin_code_info), "Pairing code\n%06ld", bt->pin_code); | ||||||
|  |     elements_multiline_text_aligned(canvas, 64, 4, AlignCenter, AlignTop, pin_code_info); | ||||||
|  |     elements_button_left(canvas, "Quit"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bt_pin_code_view_port_input_callback(InputEvent* event, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     Bt* bt = context; | ||||||
|  |     if(event->type == InputTypeShort) { | ||||||
|  |         if(event->key == InputKeyLeft || event->key == InputKeyBack) { | ||||||
|  |             view_port_enabled_set(bt->pin_code_view_port, false); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static ViewPort* bt_pin_code_view_port_alloc(Bt* bt) { | ||||||
|  |     ViewPort* view_port = view_port_alloc(); | ||||||
|  |     view_port_draw_callback_set(view_port, bt_pin_code_view_port_draw_callback, bt); | ||||||
|  |     view_port_input_callback_set(view_port, bt_pin_code_view_port_input_callback, bt); | ||||||
|  |     view_port_enabled_set(view_port, false); | ||||||
|  |     return view_port; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void bt_pin_code_show(Bt* bt, uint32_t pin_code) { | ||||||
|  |     bt->pin_code = pin_code; | ||||||
|     notification_message(bt->notification, &sequence_display_on); |     notification_message(bt->notification, &sequence_display_on); | ||||||
|     string_t pin_str; |     gui_view_port_send_to_front(bt->gui, bt->pin_code_view_port); | ||||||
|     dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0); |     view_port_enabled_set(bt->pin_code_view_port, true); | ||||||
|     string_init_printf(pin_str, "Pairing code\n%06d", pin); | } | ||||||
|     dialog_message_set_text( | 
 | ||||||
|         bt->dialog_message, string_get_cstr(pin_str), 64, 4, AlignCenter, AlignTop); | static void bt_pin_code_hide(Bt* bt) { | ||||||
|     dialog_message_set_buttons(bt->dialog_message, "Quit", NULL, NULL); |     bt->pin_code = 0; | ||||||
|     dialog_message_show(bt->dialogs, bt->dialog_message); |     if(view_port_is_enabled(bt->pin_code_view_port)) { | ||||||
|     string_clear(pin_str); |         view_port_enabled_set(bt->pin_code_view_port, false); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) { | static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) { | ||||||
| @ -84,11 +114,14 @@ Bt* bt_alloc() { | |||||||
| 
 | 
 | ||||||
|     // Setup statusbar view port
 |     // Setup statusbar view port
 | ||||||
|     bt->statusbar_view_port = bt_statusbar_view_port_alloc(bt); |     bt->statusbar_view_port = bt_statusbar_view_port_alloc(bt); | ||||||
|  |     // Pin code view port
 | ||||||
|  |     bt->pin_code_view_port = bt_pin_code_view_port_alloc(bt); | ||||||
|     // Notification
 |     // Notification
 | ||||||
|     bt->notification = furi_record_open("notification"); |     bt->notification = furi_record_open("notification"); | ||||||
|     // Gui
 |     // Gui
 | ||||||
|     bt->gui = furi_record_open("gui"); |     bt->gui = furi_record_open("gui"); | ||||||
|     gui_add_view_port(bt->gui, bt->statusbar_view_port, GuiLayerStatusBarLeft); |     gui_add_view_port(bt->gui, bt->statusbar_view_port, GuiLayerStatusBarLeft); | ||||||
|  |     gui_add_view_port(bt->gui, bt->pin_code_view_port, GuiLayerFullscreen); | ||||||
| 
 | 
 | ||||||
|     // Dialogs
 |     // Dialogs
 | ||||||
|     bt->dialogs = furi_record_open("dialogs"); |     bt->dialogs = furi_record_open("dialogs"); | ||||||
| @ -162,7 +195,7 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { | |||||||
|     if(event.type == GapEventTypeConnected) { |     if(event.type == GapEventTypeConnected) { | ||||||
|         // Update status bar
 |         // Update status bar
 | ||||||
|         bt->status = BtStatusConnected; |         bt->status = BtStatusConnected; | ||||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; |         BtMessage message = {.type = BtMessageTypeUpdateStatus}; | ||||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); |         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|         if(bt->profile == BtProfileSerial) { |         if(bt->profile == BtProfileSerial) { | ||||||
|             // Open RPC session
 |             // Open RPC session
 | ||||||
| @ -192,12 +225,12 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { | |||||||
|         ret = true; |         ret = true; | ||||||
|     } else if(event.type == GapEventTypeStartAdvertising) { |     } else if(event.type == GapEventTypeStartAdvertising) { | ||||||
|         bt->status = BtStatusAdvertising; |         bt->status = BtStatusAdvertising; | ||||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; |         BtMessage message = {.type = BtMessageTypeUpdateStatus}; | ||||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); |         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|         ret = true; |         ret = true; | ||||||
|     } else if(event.type == GapEventTypeStopAdvertising) { |     } else if(event.type == GapEventTypeStopAdvertising) { | ||||||
|         bt->status = BtStatusOff; |         bt->status = BtStatusOff; | ||||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; |         BtMessage message = {.type = BtMessageTypeUpdateStatus}; | ||||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); |         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|         ret = true; |         ret = true; | ||||||
|     } else if(event.type == GapEventTypePinCodeShow) { |     } else if(event.type == GapEventTypePinCodeShow) { | ||||||
| @ -313,9 +346,10 @@ int32_t bt_srv() { | |||||||
|     BtMessage message; |     BtMessage message; | ||||||
|     while(1) { |     while(1) { | ||||||
|         furi_check(osMessageQueueGet(bt->message_queue, &message, NULL, osWaitForever) == osOK); |         furi_check(osMessageQueueGet(bt->message_queue, &message, NULL, osWaitForever) == osOK); | ||||||
|         if(message.type == BtMessageTypeUpdateStatusbar) { |         if(message.type == BtMessageTypeUpdateStatus) { | ||||||
|             // Update statusbar
 |             // Update view ports
 | ||||||
|             bt_statusbar_update(bt); |             bt_statusbar_update(bt); | ||||||
|  |             bt_pin_code_hide(bt); | ||||||
|             if(bt->status_changed_cb) { |             if(bt->status_changed_cb) { | ||||||
|                 bt->status_changed_cb(bt->status, bt->status_changed_ctx); |                 bt->status_changed_cb(bt->status, bt->status_changed_ctx); | ||||||
|             } |             } | ||||||
| @ -324,11 +358,13 @@ int32_t bt_srv() { | |||||||
|             furi_hal_bt_update_battery_level(message.data.battery_level); |             furi_hal_bt_update_battery_level(message.data.battery_level); | ||||||
|         } else if(message.type == BtMessageTypePinCodeShow) { |         } else if(message.type == BtMessageTypePinCodeShow) { | ||||||
|             // Display PIN code
 |             // Display PIN code
 | ||||||
|             bt_pin_code_show_event_handler(bt, message.data.pin_code); |             bt_pin_code_show(bt, message.data.pin_code); | ||||||
|         } else if(message.type == BtMessageTypeKeysStorageUpdated) { |         } else if(message.type == BtMessageTypeKeysStorageUpdated) { | ||||||
|             bt_save_key_storage(bt); |             bt_save_key_storage(bt); | ||||||
|         } else if(message.type == BtMessageTypeSetProfile) { |         } else if(message.type == BtMessageTypeSetProfile) { | ||||||
|             bt_change_profile(bt, &message); |             bt_change_profile(bt, &message); | ||||||
|  |         } else if(message.type == BtMessageTypeForgetBondedDevices) { | ||||||
|  |             bt_delete_key_storage(bt); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     return 0; |     return 0; | ||||||
|  | |||||||
| @ -41,6 +41,13 @@ bool bt_set_profile(Bt* bt, BtProfile profile); | |||||||
|  */ |  */ | ||||||
| void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, void* context); | void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, void* context); | ||||||
| 
 | 
 | ||||||
|  | /** Forget bonded devices
 | ||||||
|  |  * @note Leads to wipe ble key storage and deleting bt.keys | ||||||
|  |  * | ||||||
|  |  * @param bt        Bt instance | ||||||
|  |  */ | ||||||
|  | void bt_forget_bonded_devices(Bt* bt); | ||||||
|  | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | |||||||
| @ -20,3 +20,9 @@ void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, vo | |||||||
|     bt->status_changed_cb = callback; |     bt->status_changed_cb = callback; | ||||||
|     bt->status_changed_ctx = context; |     bt->status_changed_ctx = context; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void bt_forget_bonded_devices(Bt* bt) { | ||||||
|  |     furi_assert(bt); | ||||||
|  |     BtMessage message = {.type = BtMessageTypeForgetBondedDevices}; | ||||||
|  |     furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|  | } | ||||||
|  | |||||||
| @ -19,11 +19,12 @@ | |||||||
| #define BT_API_UNLOCK_EVENT (1UL << 0) | #define BT_API_UNLOCK_EVENT (1UL << 0) | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     BtMessageTypeUpdateStatusbar, |     BtMessageTypeUpdateStatus, | ||||||
|     BtMessageTypeUpdateBatteryLevel, |     BtMessageTypeUpdateBatteryLevel, | ||||||
|     BtMessageTypePinCodeShow, |     BtMessageTypePinCodeShow, | ||||||
|     BtMessageTypeKeysStorageUpdated, |     BtMessageTypeKeysStorageUpdated, | ||||||
|     BtMessageTypeSetProfile, |     BtMessageTypeSetProfile, | ||||||
|  |     BtMessageTypeForgetBondedDevices, | ||||||
| } BtMessageType; | } BtMessageType; | ||||||
| 
 | 
 | ||||||
| typedef union { | typedef union { | ||||||
| @ -49,6 +50,8 @@ struct Bt { | |||||||
|     NotificationApp* notification; |     NotificationApp* notification; | ||||||
|     Gui* gui; |     Gui* gui; | ||||||
|     ViewPort* statusbar_view_port; |     ViewPort* statusbar_view_port; | ||||||
|  |     ViewPort* pin_code_view_port; | ||||||
|  |     uint32_t pin_code; | ||||||
|     DialogsApp* dialogs; |     DialogsApp* dialogs; | ||||||
|     DialogMessage* dialog_message; |     DialogMessage* dialog_message; | ||||||
|     Power* power; |     Power* power; | ||||||
|  | |||||||
| @ -39,3 +39,16 @@ bool bt_save_key_storage(Bt* bt) { | |||||||
|     file_worker_free(file_worker); |     file_worker_free(file_worker); | ||||||
|     return file_saved; |     return file_saved; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | bool bt_delete_key_storage(Bt* bt) { | ||||||
|  |     furi_assert(bt); | ||||||
|  |     bool delete_succeed = false; | ||||||
|  | 
 | ||||||
|  |     furi_hal_bt_stop_advertising(); | ||||||
|  |     delete_succeed = furi_hal_bt_clear_white_list(); | ||||||
|  |     if(bt->bt_settings.enabled) { | ||||||
|  |         furi_hal_bt_start_advertising(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return delete_succeed; | ||||||
|  | } | ||||||
|  | |||||||
| @ -5,3 +5,5 @@ | |||||||
| bool bt_load_key_storage(Bt* bt); | bool bt_load_key_storage(Bt* bt); | ||||||
| 
 | 
 | ||||||
| bool bt_save_key_storage(Bt* bt); | bool bt_save_key_storage(Bt* bt); | ||||||
|  | 
 | ||||||
|  | bool bt_delete_key_storage(Bt* bt); | ||||||
|  | |||||||
| @ -18,7 +18,9 @@ BtSettingsApp* bt_settings_app_alloc() { | |||||||
|     // Load settings
 |     // Load settings
 | ||||||
|     bt_settings_load(&app->settings); |     bt_settings_load(&app->settings); | ||||||
|     app->gui = furi_record_open("gui"); |     app->gui = furi_record_open("gui"); | ||||||
|  |     app->bt = furi_record_open("bt"); | ||||||
| 
 | 
 | ||||||
|  |     // View Dispatcher and Scene Manager
 | ||||||
|     app->view_dispatcher = view_dispatcher_alloc(); |     app->view_dispatcher = view_dispatcher_alloc(); | ||||||
|     app->scene_manager = scene_manager_alloc(&bt_settings_scene_handlers, app); |     app->scene_manager = scene_manager_alloc(&bt_settings_scene_handlers, app); | ||||||
|     view_dispatcher_enable_queue(app->view_dispatcher); |     view_dispatcher_enable_queue(app->view_dispatcher); | ||||||
| @ -31,26 +33,45 @@ BtSettingsApp* bt_settings_app_alloc() { | |||||||
| 
 | 
 | ||||||
|     view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); |     view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); | ||||||
| 
 | 
 | ||||||
|  |     // Gui Modules
 | ||||||
|     app->var_item_list = variable_item_list_alloc(); |     app->var_item_list = variable_item_list_alloc(); | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         app->view_dispatcher, |         app->view_dispatcher, | ||||||
|         BtSettingsAppViewVarItemList, |         BtSettingsAppViewVarItemList, | ||||||
|         variable_item_list_get_view(app->var_item_list)); |         variable_item_list_get_view(app->var_item_list)); | ||||||
| 
 | 
 | ||||||
|  |     app->dialog = dialog_ex_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, BtSettingsAppViewDialog, dialog_ex_get_view(app->dialog)); | ||||||
|  | 
 | ||||||
|  |     app->popup = popup_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, BtSettingsAppViewPopup, popup_get_view(app->popup)); | ||||||
|  | 
 | ||||||
|  |     // Set first scene
 | ||||||
|     scene_manager_next_scene(app->scene_manager, BtSettingsAppSceneStart); |     scene_manager_next_scene(app->scene_manager, BtSettingsAppSceneStart); | ||||||
|     return app; |     return app; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_settings_app_free(BtSettingsApp* app) { | void bt_settings_app_free(BtSettingsApp* app) { | ||||||
|     furi_assert(app); |     furi_assert(app); | ||||||
|     // Variable item list
 |     // Gui modules
 | ||||||
|     view_dispatcher_remove_view(app->view_dispatcher, BtSettingsAppViewVarItemList); |     view_dispatcher_remove_view(app->view_dispatcher, BtSettingsAppViewVarItemList); | ||||||
|     variable_item_list_free(app->var_item_list); |     variable_item_list_free(app->var_item_list); | ||||||
|     // View dispatcher
 | 
 | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, BtSettingsAppViewDialog); | ||||||
|  |     dialog_ex_free(app->dialog); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, BtSettingsAppViewPopup); | ||||||
|  |     popup_free(app->popup); | ||||||
|  | 
 | ||||||
|  |     // View Dispatcher and Scene Manager
 | ||||||
|     view_dispatcher_free(app->view_dispatcher); |     view_dispatcher_free(app->view_dispatcher); | ||||||
|     scene_manager_free(app->scene_manager); |     scene_manager_free(app->scene_manager); | ||||||
|  | 
 | ||||||
|     // Records
 |     // Records
 | ||||||
|     furi_record_close("gui"); |     furi_record_close("gui"); | ||||||
|  |     furi_record_close("bt"); | ||||||
|     free(app); |     free(app); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,22 +1,41 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
|  | #include <bt/bt_service/bt.h> | ||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
| #include <gui/view_dispatcher.h> | #include <gui/view_dispatcher.h> | ||||||
| #include <gui/scene_manager.h> | #include <gui/scene_manager.h> | ||||||
| 
 | 
 | ||||||
| #include <gui/modules/variable_item_list.h> | #include <gui/modules/variable_item_list.h> | ||||||
|  | #include <gui/modules/dialog_ex.h> | ||||||
|  | #include <gui/modules/popup.h> | ||||||
| 
 | 
 | ||||||
| #include "../bt_settings.h" | #include "../bt_settings.h" | ||||||
| #include "scenes/bt_settings_scene.h" | #include "scenes/bt_settings_scene.h" | ||||||
| 
 | 
 | ||||||
|  | enum BtSettingsCustomEvent { | ||||||
|  |     // Keep first 10 events reserved for button types and indexes
 | ||||||
|  |     BtSettingsCustomEventReserved = 10, | ||||||
|  | 
 | ||||||
|  |     BtSettingsCustomEventForgetDevices, | ||||||
|  |     BtSettingsCustomEventExitView, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     BtSettings settings; |     BtSettings settings; | ||||||
|  |     Bt* bt; | ||||||
|     Gui* gui; |     Gui* gui; | ||||||
|     SceneManager* scene_manager; |     SceneManager* scene_manager; | ||||||
|     ViewDispatcher* view_dispatcher; |     ViewDispatcher* view_dispatcher; | ||||||
|  | 
 | ||||||
|     VariableItemList* var_item_list; |     VariableItemList* var_item_list; | ||||||
|  |     DialogEx* dialog; | ||||||
|  |     Popup* popup; | ||||||
| } BtSettingsApp; | } BtSettingsApp; | ||||||
| 
 | 
 | ||||||
| typedef enum { BtSettingsAppViewVarItemList } BtSettingsAppView; | typedef enum { | ||||||
|  |     BtSettingsAppViewVarItemList, | ||||||
|  |     BtSettingsAppViewDialog, | ||||||
|  |     BtSettingsAppViewPopup, | ||||||
|  | } BtSettingsAppView; | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								applications/bt/bt_settings_app/scenes/bt_settings_scene_config.h
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										2
									
								
								applications/bt/bt_settings_app/scenes/bt_settings_scene_config.h
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @ -1 +1,3 @@ | |||||||
| ADD_SCENE(bt_settings, start, Start) | ADD_SCENE(bt_settings, start, Start) | ||||||
|  | ADD_SCENE(bt_settings, forget_dev_confirm, ForgetDevConfirm) | ||||||
|  | ADD_SCENE(bt_settings, forget_dev_success, ForgetDevSuccess) | ||||||
|  | |||||||
| @ -0,0 +1,44 @@ | |||||||
|  | #include "../bt_settings_app.h" | ||||||
|  | #include "furi_hal_bt.h" | ||||||
|  | 
 | ||||||
|  | void bt_settings_scene_forget_dev_confirm_dialog_callback(DialogExResult result, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     BtSettingsApp* app = context; | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void bt_settings_scene_forget_dev_confirm_on_enter(void* context) { | ||||||
|  |     BtSettingsApp* app = context; | ||||||
|  |     DialogEx* dialog = app->dialog; | ||||||
|  |     dialog_ex_set_header(dialog, "Unpair all devices?", 64, 3, AlignCenter, AlignTop); | ||||||
|  |     dialog_ex_set_text( | ||||||
|  |         dialog, "All previous pairings\nwill be lost.", 64, 22, AlignCenter, AlignTop); | ||||||
|  |     dialog_ex_set_left_button_text(dialog, "Back"); | ||||||
|  |     dialog_ex_set_right_button_text(dialog, "Unpair"); | ||||||
|  |     dialog_ex_set_context(dialog, app); | ||||||
|  |     dialog_ex_set_result_callback(dialog, bt_settings_scene_forget_dev_confirm_dialog_callback); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, BtSettingsAppViewDialog); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool bt_settings_scene_forget_dev_confirm_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     BtSettingsApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == DialogExResultLeft) { | ||||||
|  |             consumed = scene_manager_previous_scene(app->scene_manager); | ||||||
|  |         } else if(event.event == DialogExResultRight) { | ||||||
|  |             bt_forget_bonded_devices(app->bt); | ||||||
|  |             scene_manager_next_scene(app->scene_manager, BtSettingsAppSceneForgetDevSuccess); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void bt_settings_scene_forget_dev_confirm_on_exit(void* context) { | ||||||
|  |     BtSettingsApp* app = context; | ||||||
|  |     dialog_ex_reset(app->dialog); | ||||||
|  | } | ||||||
| @ -0,0 +1,41 @@ | |||||||
|  | #include "../bt_settings_app.h" | ||||||
|  | #include "furi_hal_bt.h" | ||||||
|  | 
 | ||||||
|  | void bt_settings_app_scene_forget_dev_success_popup_callback(void* context) { | ||||||
|  |     BtSettingsApp* app = context; | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, BtSettingsCustomEventExitView); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void bt_settings_scene_forget_dev_success_on_enter(void* context) { | ||||||
|  |     BtSettingsApp* app = context; | ||||||
|  |     Popup* popup = app->popup; | ||||||
|  | 
 | ||||||
|  |     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||||
|  |     popup_set_header(popup, "Done", 14, 15, AlignLeft, AlignTop); | ||||||
|  |     popup_set_timeout(popup, 1500); | ||||||
|  |     popup_set_context(popup, app); | ||||||
|  |     popup_set_callback(popup, bt_settings_app_scene_forget_dev_success_popup_callback); | ||||||
|  |     popup_enable_timeout(popup); | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, BtSettingsAppViewPopup); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool bt_settings_scene_forget_dev_success_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     BtSettingsApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == BtSettingsCustomEventExitView) { | ||||||
|  |             if(scene_manager_has_previous_scene(app->scene_manager, BtSettingsAppSceneStart)) { | ||||||
|  |                 consumed = scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |                     app->scene_manager, BtSettingsAppSceneStart); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void bt_settings_scene_forget_dev_success_on_exit(void* context) { | ||||||
|  |     BtSettingsApp* app = context; | ||||||
|  |     popup_reset(app->popup); | ||||||
|  | } | ||||||
| @ -7,9 +7,14 @@ enum BtSetting { | |||||||
|     BtSettingNum, |     BtSettingNum, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | enum BtSettingIndex { | ||||||
|  |     BtSettingIndexSwitchBt, | ||||||
|  |     BtSettingIndexForgetDev, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| const char* const bt_settings_text[BtSettingNum] = { | const char* const bt_settings_text[BtSettingNum] = { | ||||||
|     "Off", |     "OFF", | ||||||
|     "On", |     "ON", | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static void bt_settings_scene_start_var_list_change_callback(VariableItem* item) { | static void bt_settings_scene_start_var_list_change_callback(VariableItem* item) { | ||||||
| @ -20,6 +25,15 @@ static void bt_settings_scene_start_var_list_change_callback(VariableItem* item) | |||||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, index); |     view_dispatcher_send_custom_event(app->view_dispatcher, index); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void bt_settings_scene_start_var_list_enter_callback(void* context, uint32_t index) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     BtSettingsApp* app = context; | ||||||
|  |     if(index == BtSettingIndexForgetDev) { | ||||||
|  |         view_dispatcher_send_custom_event( | ||||||
|  |             app->view_dispatcher, BtSettingsCustomEventForgetDevices); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void bt_settings_scene_start_on_enter(void* context) { | void bt_settings_scene_start_on_enter(void* context) { | ||||||
|     BtSettingsApp* app = context; |     BtSettingsApp* app = context; | ||||||
|     VariableItemList* var_item_list = app->var_item_list; |     VariableItemList* var_item_list = app->var_item_list; | ||||||
| @ -40,6 +54,9 @@ void bt_settings_scene_start_on_enter(void* context) { | |||||||
|             variable_item_set_current_value_index(item, BtSettingOff); |             variable_item_set_current_value_index(item, BtSettingOff); | ||||||
|             variable_item_set_current_value_text(item, bt_settings_text[BtSettingOff]); |             variable_item_set_current_value_text(item, bt_settings_text[BtSettingOff]); | ||||||
|         } |         } | ||||||
|  |         variable_item_list_add(var_item_list, "Forget all paired devices", 1, NULL, NULL); | ||||||
|  |         variable_item_list_set_enter_callback( | ||||||
|  |             var_item_list, bt_settings_scene_start_var_list_enter_callback, app); | ||||||
|     } else { |     } else { | ||||||
|         item = variable_item_list_add(var_item_list, "Bluetooth", 1, NULL, NULL); |         item = variable_item_list_add(var_item_list, "Bluetooth", 1, NULL, NULL); | ||||||
|         variable_item_set_current_value_text(item, "Broken"); |         variable_item_set_current_value_text(item, "Broken"); | ||||||
| @ -56,16 +73,20 @@ bool bt_settings_scene_start_on_event(void* context, SceneManagerEvent event) { | |||||||
|         if(event.event == BtSettingOn) { |         if(event.event == BtSettingOn) { | ||||||
|             furi_hal_bt_start_advertising(); |             furi_hal_bt_start_advertising(); | ||||||
|             app->settings.enabled = true; |             app->settings.enabled = true; | ||||||
|  |             consumed = true; | ||||||
|         } else if(event.event == BtSettingOff) { |         } else if(event.event == BtSettingOff) { | ||||||
|             app->settings.enabled = false; |             app->settings.enabled = false; | ||||||
|             furi_hal_bt_stop_advertising(); |             furi_hal_bt_stop_advertising(); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == BtSettingsCustomEventForgetDevices) { | ||||||
|  |             scene_manager_next_scene(app->scene_manager, BtSettingsAppSceneForgetDevConfirm); | ||||||
|  |             consumed = true; | ||||||
|         } |         } | ||||||
|         consumed = true; |  | ||||||
|     } |     } | ||||||
|     return consumed; |     return consumed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bt_settings_scene_start_on_exit(void* context) { | void bt_settings_scene_start_on_exit(void* context) { | ||||||
|     BtSettingsApp* app = context; |     BtSettingsApp* app = context; | ||||||
|     variable_item_list_clean(app->var_item_list); |     variable_item_list_reset(app->var_item_list); | ||||||
| } | } | ||||||
|  | |||||||
| @ -41,7 +41,7 @@ int32_t usb_mouse_app(void* p) { | |||||||
|     furi_check(event_queue); |     furi_check(event_queue); | ||||||
|     ViewPort* view_port = view_port_alloc(); |     ViewPort* view_port = view_port_alloc(); | ||||||
| 
 | 
 | ||||||
|     UsbInterface* usb_mode_prev = furi_hal_usb_get_config(); |     FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); | ||||||
|     furi_hal_usb_set_config(&usb_hid); |     furi_hal_usb_set_config(&usb_hid); | ||||||
| 
 | 
 | ||||||
|     view_port_draw_callback_set(view_port, usb_mouse_render_callback, NULL); |     view_port_draw_callback_set(view_port, usb_mouse_render_callback, NULL); | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ typedef struct { | |||||||
| typedef enum { | typedef enum { | ||||||
|     UsbTestSubmenuIndexEnable, |     UsbTestSubmenuIndexEnable, | ||||||
|     UsbTestSubmenuIndexDisable, |     UsbTestSubmenuIndexDisable, | ||||||
|  |     UsbTestSubmenuIndexRestart, | ||||||
|     UsbTestSubmenuIndexVcpSingle, |     UsbTestSubmenuIndexVcpSingle, | ||||||
|     UsbTestSubmenuIndexVcpDual, |     UsbTestSubmenuIndexVcpDual, | ||||||
|     UsbTestSubmenuIndexHid, |     UsbTestSubmenuIndexHid, | ||||||
| @ -28,6 +29,8 @@ void usb_test_submenu_callback(void* context, uint32_t index) { | |||||||
|         furi_hal_usb_enable(); |         furi_hal_usb_enable(); | ||||||
|     } else if(index == UsbTestSubmenuIndexDisable) { |     } else if(index == UsbTestSubmenuIndexDisable) { | ||||||
|         furi_hal_usb_disable(); |         furi_hal_usb_disable(); | ||||||
|  |     } else if(index == UsbTestSubmenuIndexRestart) { | ||||||
|  |         furi_hal_usb_reinit(); | ||||||
|     } else if(index == UsbTestSubmenuIndexVcpSingle) { |     } else if(index == UsbTestSubmenuIndexVcpSingle) { | ||||||
|         furi_hal_usb_set_config(&usb_cdc_single); |         furi_hal_usb_set_config(&usb_cdc_single); | ||||||
|     } else if(index == UsbTestSubmenuIndexVcpDual) { |     } else if(index == UsbTestSubmenuIndexVcpDual) { | ||||||
| @ -60,6 +63,8 @@ UsbTestApp* usb_test_app_alloc() { | |||||||
|         app->submenu, "Enable", UsbTestSubmenuIndexEnable, usb_test_submenu_callback, app); |         app->submenu, "Enable", UsbTestSubmenuIndexEnable, usb_test_submenu_callback, app); | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         app->submenu, "Disable", UsbTestSubmenuIndexDisable, usb_test_submenu_callback, app); |         app->submenu, "Disable", UsbTestSubmenuIndexDisable, usb_test_submenu_callback, app); | ||||||
|  |     submenu_add_item( | ||||||
|  |         app->submenu, "Restart", UsbTestSubmenuIndexRestart, usb_test_submenu_callback, app); | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         app->submenu, "Single VCP", UsbTestSubmenuIndexVcpSingle, usb_test_submenu_callback, app); |         app->submenu, "Single VCP", UsbTestSubmenuIndexVcpSingle, usb_test_submenu_callback, app); | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|  | |||||||
| @ -1,23 +1,30 @@ | |||||||
| #include "animation_manager.h" | #include <gui/view_stack.h> | ||||||
| #include "furi_hal_delay.h" |  | ||||||
| #include "portmacro.h" |  | ||||||
| #include "views/bubble_animation_view.h" |  | ||||||
| #include "animation_storage.h" |  | ||||||
| 
 |  | ||||||
| #include <cmsis_os2.h> |  | ||||||
| #include <dolphin/dolphin.h> |  | ||||||
| #include <furi/check.h> |  | ||||||
| #include <furi/pubsub.h> |  | ||||||
| #include <furi/record.h> |  | ||||||
| #include <m-string.h> |  | ||||||
| #include <power/power_service/power.h> |  | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
|  | #include <furi.h> | ||||||
|  | #include <m-string.h> | ||||||
|  | #include <portmacro.h> | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
|  | #include <power/power_service/power.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| #include <dolphin/dolphin_i.h> | #include <assets_icons.h> | ||||||
| #include <storage/filesystem_api_defines.h> | 
 | ||||||
|  | #include "views/bubble_animation_view.h" | ||||||
|  | #include "views/one_shot_animation_view.h" | ||||||
|  | #include "animation_storage.h" | ||||||
|  | #include "animation_manager.h" | ||||||
| 
 | 
 | ||||||
| #define TAG "AnimationManager" | #define TAG "AnimationManager" | ||||||
| 
 | 
 | ||||||
|  | #define HARDCODED_ANIMATION_NAME "L1_Tv_128x47" | ||||||
|  | #define NO_SD_ANIMATION_NAME "L1_NoSd_128x49" | ||||||
|  | #define BAD_BATTERY_ANIMATION_NAME "L1_BadBattery_128x47" | ||||||
|  | 
 | ||||||
|  | #define NO_DB_ANIMATION_NAME "L0_NoDb_128x51" | ||||||
|  | #define BAD_SD_ANIMATION_NAME "L0_SdBad_128x51" | ||||||
|  | #define SD_OK_ANIMATION_NAME "L0_SdOk_128x51" | ||||||
|  | #define URL_ANIMATION_NAME "L0_Url_128x51" | ||||||
|  | #define NEW_MAIL_ANIMATION_NAME "L0_NewMail_128x51" | ||||||
|  | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     AnimationManagerStateIdle, |     AnimationManagerStateIdle, | ||||||
|     AnimationManagerStateBlocked, |     AnimationManagerStateBlocked, | ||||||
| @ -29,10 +36,13 @@ struct AnimationManager { | |||||||
|     bool sd_show_url; |     bool sd_show_url; | ||||||
|     bool sd_shown_no_db; |     bool sd_shown_no_db; | ||||||
|     bool sd_shown_sd_ok; |     bool sd_shown_sd_ok; | ||||||
|  |     bool levelup_pending; | ||||||
|  |     bool levelup_active; | ||||||
|     AnimationManagerState state; |     AnimationManagerState state; | ||||||
|     FuriPubSubSubscription* pubsub_subscription_storage; |     FuriPubSubSubscription* pubsub_subscription_storage; | ||||||
|     FuriPubSubSubscription* pubsub_subscription_dolphin; |     FuriPubSubSubscription* pubsub_subscription_dolphin; | ||||||
|     BubbleAnimationView* animation_view; |     BubbleAnimationView* animation_view; | ||||||
|  |     OneShotView* one_shot_view; | ||||||
|     osTimerId_t idle_animation_timer; |     osTimerId_t idle_animation_timer; | ||||||
|     StorageAnimation* current_animation; |     StorageAnimation* current_animation; | ||||||
|     AnimationManagerInteractCallback interact_callback; |     AnimationManagerInteractCallback interact_callback; | ||||||
| @ -41,6 +51,7 @@ struct AnimationManager { | |||||||
|     void* context; |     void* context; | ||||||
|     string_t freezed_animation_name; |     string_t freezed_animation_name; | ||||||
|     int32_t freezed_animation_time_left; |     int32_t freezed_animation_time_left; | ||||||
|  |     ViewStack* view_stack; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static StorageAnimation* | static StorageAnimation* | ||||||
| @ -50,6 +61,11 @@ static void animation_manager_replace_current_animation( | |||||||
|     StorageAnimation* storage_animation); |     StorageAnimation* storage_animation); | ||||||
| static void animation_manager_start_new_idle(AnimationManager* animation_manager); | static void animation_manager_start_new_idle(AnimationManager* animation_manager); | ||||||
| static bool animation_manager_check_blocking(AnimationManager* animation_manager); | static bool animation_manager_check_blocking(AnimationManager* animation_manager); | ||||||
|  | static bool animation_manager_is_valid_idle_animation( | ||||||
|  |     const StorageAnimationManifestInfo* info, | ||||||
|  |     const DolphinStats* stats); | ||||||
|  | static void animation_manager_switch_to_one_shot_view(AnimationManager* animation_manager); | ||||||
|  | static void animation_manager_switch_to_animation_view(AnimationManager* animation_manager); | ||||||
| 
 | 
 | ||||||
| void animation_manager_set_context(AnimationManager* animation_manager, void* context) { | void animation_manager_set_context(AnimationManager* animation_manager, void* context) { | ||||||
|     furi_assert(animation_manager); |     furi_assert(animation_manager); | ||||||
| @ -101,12 +117,26 @@ static void animation_manager_interact_callback(void* context) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* reaction to animation_manager->interact_callback() */ | /* reaction to animation_manager->check_blocking_callback() */ | ||||||
| void animation_manager_check_blocking_process(AnimationManager* animation_manager) { | void animation_manager_check_blocking_process(AnimationManager* animation_manager) { | ||||||
|     furi_assert(animation_manager); |     furi_assert(animation_manager); | ||||||
| 
 | 
 | ||||||
|     if(animation_manager->state == AnimationManagerStateIdle) { |     if(animation_manager->state == AnimationManagerStateIdle) { | ||||||
|         animation_manager_check_blocking(animation_manager); |         bool blocked = animation_manager_check_blocking(animation_manager); | ||||||
|  | 
 | ||||||
|  |         if(!blocked) { | ||||||
|  |             Dolphin* dolphin = furi_record_open("dolphin"); | ||||||
|  |             DolphinStats stats = dolphin_stats(dolphin); | ||||||
|  |             furi_record_close("dolphin"); | ||||||
|  | 
 | ||||||
|  |             const StorageAnimationManifestInfo* manifest_info = | ||||||
|  |                 animation_storage_get_meta(animation_manager->current_animation); | ||||||
|  |             bool valid = animation_manager_is_valid_idle_animation(manifest_info, &stats); | ||||||
|  | 
 | ||||||
|  |             if(!valid) { | ||||||
|  |                 animation_manager_start_new_idle(animation_manager); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -119,13 +149,24 @@ void animation_manager_new_idle_process(AnimationManager* animation_manager) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* reaction to animation_manager->check_blocking_callback() */ | /* reaction to animation_manager->interact_callback() */ | ||||||
| void animation_manager_interact_process(AnimationManager* animation_manager) { | void animation_manager_interact_process(AnimationManager* animation_manager) { | ||||||
|     furi_assert(animation_manager); |     furi_assert(animation_manager); | ||||||
| 
 | 
 | ||||||
|     if(animation_manager->state == AnimationManagerStateBlocked) { |     if(animation_manager->levelup_pending) { | ||||||
|         /* check if new blocking animation has to be displayed */ |         animation_manager->levelup_pending = false; | ||||||
|  |         animation_manager->levelup_active = true; | ||||||
|  |         animation_manager_switch_to_one_shot_view(animation_manager); | ||||||
|  |         Dolphin* dolphin = furi_record_open("dolphin"); | ||||||
|  |         dolphin_upgrade_level(dolphin); | ||||||
|  |         furi_record_close("dolphin"); | ||||||
|  |     } else if(animation_manager->levelup_active) { | ||||||
|  |         animation_manager->levelup_active = false; | ||||||
|  |         animation_manager_start_new_idle(animation_manager); | ||||||
|  |         animation_manager_switch_to_animation_view(animation_manager); | ||||||
|  |     } else if(animation_manager->state == AnimationManagerStateBlocked) { | ||||||
|         bool blocked = animation_manager_check_blocking(animation_manager); |         bool blocked = animation_manager_check_blocking(animation_manager); | ||||||
|  | 
 | ||||||
|         if(!blocked) { |         if(!blocked) { | ||||||
|             animation_manager_start_new_idle(animation_manager); |             animation_manager_start_new_idle(animation_manager); | ||||||
|         } |         } | ||||||
| @ -152,22 +193,26 @@ static bool animation_manager_check_blocking(AnimationManager* animation_manager | |||||||
| 
 | 
 | ||||||
|     if(sd_status == FSE_INTERNAL) { |     if(sd_status == FSE_INTERNAL) { | ||||||
|         blocking_animation = animation_storage_find_animation(BAD_SD_ANIMATION_NAME); |         blocking_animation = animation_storage_find_animation(BAD_SD_ANIMATION_NAME); | ||||||
|  |         furi_assert(blocking_animation); | ||||||
|     } else if(sd_status == FSE_NOT_READY) { |     } else if(sd_status == FSE_NOT_READY) { | ||||||
|         animation_manager->sd_shown_sd_ok = false; |         animation_manager->sd_shown_sd_ok = false; | ||||||
|         animation_manager->sd_shown_no_db = false; |         animation_manager->sd_shown_no_db = false; | ||||||
|     } else if(sd_status == FSE_OK) { |     } else if(sd_status == FSE_OK) { | ||||||
|         if(!animation_manager->sd_shown_sd_ok) { |         if(!animation_manager->sd_shown_sd_ok) { | ||||||
|             blocking_animation = animation_storage_find_animation(SD_OK_ANIMATION_NAME); |             blocking_animation = animation_storage_find_animation(SD_OK_ANIMATION_NAME); | ||||||
|  |             furi_assert(blocking_animation); | ||||||
|             animation_manager->sd_shown_sd_ok = true; |             animation_manager->sd_shown_sd_ok = true; | ||||||
|         } else if(!animation_manager->sd_shown_no_db) { |         } else if(!animation_manager->sd_shown_no_db) { | ||||||
|             bool db_exists = storage_common_stat(storage, "/ext/Manifest", NULL) == FSE_OK; |             bool db_exists = storage_common_stat(storage, "/ext/Manifest", NULL) == FSE_OK; | ||||||
|             if(!db_exists) { |             if(!db_exists) { | ||||||
|                 blocking_animation = animation_storage_find_animation(NO_DB_ANIMATION_NAME); |                 blocking_animation = animation_storage_find_animation(NO_DB_ANIMATION_NAME); | ||||||
|  |                 furi_assert(blocking_animation); | ||||||
|                 animation_manager->sd_shown_no_db = true; |                 animation_manager->sd_shown_no_db = true; | ||||||
|                 animation_manager->sd_show_url = true; |                 animation_manager->sd_show_url = true; | ||||||
|             } |             } | ||||||
|         } else if(animation_manager->sd_show_url) { |         } else if(animation_manager->sd_show_url) { | ||||||
|             blocking_animation = animation_storage_find_animation(URL_ANIMATION_NAME); |             blocking_animation = animation_storage_find_animation(URL_ANIMATION_NAME); | ||||||
|  |             furi_assert(blocking_animation); | ||||||
|             animation_manager->sd_show_url = false; |             animation_manager->sd_show_url = false; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -176,13 +221,17 @@ static bool animation_manager_check_blocking(AnimationManager* animation_manager | |||||||
|     DolphinStats stats = dolphin_stats(dolphin); |     DolphinStats stats = dolphin_stats(dolphin); | ||||||
|     furi_record_close("dolphin"); |     furi_record_close("dolphin"); | ||||||
|     if(!blocking_animation && stats.level_up_is_pending) { |     if(!blocking_animation && stats.level_up_is_pending) { | ||||||
|         blocking_animation = animation_storage_find_animation(LEVELUP_ANIMATION_NAME); |         blocking_animation = animation_storage_find_animation(NEW_MAIL_ANIMATION_NAME); | ||||||
|  |         furi_assert(blocking_animation); | ||||||
|  |         if(blocking_animation) { | ||||||
|  |             animation_manager->levelup_pending = true; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(blocking_animation) { |     if(blocking_animation) { | ||||||
|         osTimerStop(animation_manager->idle_animation_timer); |         osTimerStop(animation_manager->idle_animation_timer); | ||||||
|         animation_manager_replace_current_animation(animation_manager, blocking_animation); |         animation_manager_replace_current_animation(animation_manager, blocking_animation); | ||||||
|         /* no starting timer because its blocking animation */ |         /* no timer starting because this is blocking animation */ | ||||||
|         animation_manager->state = AnimationManagerStateBlocked; |         animation_manager->state = AnimationManagerStateBlocked; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -199,7 +248,7 @@ static void animation_manager_replace_current_animation( | |||||||
| 
 | 
 | ||||||
|     const BubbleAnimation* animation = animation_storage_get_bubble_animation(storage_animation); |     const BubbleAnimation* animation = animation_storage_get_bubble_animation(storage_animation); | ||||||
|     bubble_animation_view_set_animation(animation_manager->animation_view, animation); |     bubble_animation_view_set_animation(animation_manager->animation_view, animation); | ||||||
|     const char* new_name = string_get_cstr(animation_storage_get_meta(storage_animation)->name); |     const char* new_name = animation_storage_get_meta(storage_animation)->name; | ||||||
|     FURI_LOG_I(TAG, "Select \'%s\' animation", new_name); |     FURI_LOG_I(TAG, "Select \'%s\' animation", new_name); | ||||||
|     animation_manager->current_animation = storage_animation; |     animation_manager->current_animation = storage_animation; | ||||||
| 
 | 
 | ||||||
| @ -209,9 +258,11 @@ static void animation_manager_replace_current_animation( | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| AnimationManager* animation_manager_alloc(void) { | AnimationManager* animation_manager_alloc(void) { | ||||||
|     animation_storage_initialize_internal_animations(); |  | ||||||
|     AnimationManager* animation_manager = furi_alloc(sizeof(AnimationManager)); |     AnimationManager* animation_manager = furi_alloc(sizeof(AnimationManager)); | ||||||
|     animation_manager->animation_view = bubble_animation_view_alloc(); |     animation_manager->animation_view = bubble_animation_view_alloc(); | ||||||
|  |     animation_manager->view_stack = view_stack_alloc(); | ||||||
|  |     View* animation_view = bubble_animation_get_view(animation_manager->animation_view); | ||||||
|  |     view_stack_add_view(animation_manager->view_stack, animation_view); | ||||||
|     string_init(animation_manager->freezed_animation_name); |     string_init(animation_manager->freezed_animation_name); | ||||||
| 
 | 
 | ||||||
|     animation_manager->idle_animation_timer = |     animation_manager->idle_animation_timer = | ||||||
| @ -251,6 +302,8 @@ void animation_manager_free(AnimationManager* animation_manager) { | |||||||
|     furi_record_close("storage"); |     furi_record_close("storage"); | ||||||
| 
 | 
 | ||||||
|     string_clear(animation_manager->freezed_animation_name); |     string_clear(animation_manager->freezed_animation_name); | ||||||
|  |     View* animation_view = bubble_animation_get_view(animation_manager->animation_view); | ||||||
|  |     view_stack_remove_view(animation_manager->view_stack, animation_view); | ||||||
|     bubble_animation_view_free(animation_manager->animation_view); |     bubble_animation_view_free(animation_manager->animation_view); | ||||||
|     osTimerDelete(animation_manager->idle_animation_timer); |     osTimerDelete(animation_manager->idle_animation_timer); | ||||||
| } | } | ||||||
| @ -258,7 +311,39 @@ void animation_manager_free(AnimationManager* animation_manager) { | |||||||
| View* animation_manager_get_animation_view(AnimationManager* animation_manager) { | View* animation_manager_get_animation_view(AnimationManager* animation_manager) { | ||||||
|     furi_assert(animation_manager); |     furi_assert(animation_manager); | ||||||
| 
 | 
 | ||||||
|     return bubble_animation_get_view(animation_manager->animation_view); |     return view_stack_get_view(animation_manager->view_stack); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool animation_manager_is_valid_idle_animation( | ||||||
|  |     const StorageAnimationManifestInfo* info, | ||||||
|  |     const DolphinStats* stats) { | ||||||
|  |     furi_assert(info); | ||||||
|  |     furi_assert(info->name); | ||||||
|  | 
 | ||||||
|  |     bool result = true; | ||||||
|  | 
 | ||||||
|  |     if(!strcmp(info->name, BAD_BATTERY_ANIMATION_NAME)) { | ||||||
|  |         Power* power = furi_record_open("power"); | ||||||
|  |         bool battery_is_well = power_is_battery_healthy(power); | ||||||
|  |         furi_record_close("power"); | ||||||
|  | 
 | ||||||
|  |         result = !battery_is_well; | ||||||
|  |     } | ||||||
|  |     if(!strcmp(info->name, NO_SD_ANIMATION_NAME)) { | ||||||
|  |         Storage* storage = furi_record_open("storage"); | ||||||
|  |         FS_Error sd_status = storage_sd_status(storage); | ||||||
|  |         furi_record_close("storage"); | ||||||
|  | 
 | ||||||
|  |         result = (sd_status == FSE_NOT_READY); | ||||||
|  |     } | ||||||
|  |     if((stats->butthurt < info->min_butthurt) || (stats->butthurt > info->max_butthurt)) { | ||||||
|  |         result = false; | ||||||
|  |     } | ||||||
|  |     if((stats->level < info->min_level) || (stats->level > info->max_level)) { | ||||||
|  |         result = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static StorageAnimation* | static StorageAnimation* | ||||||
| @ -267,40 +352,25 @@ static StorageAnimation* | |||||||
|     StorageAnimationList_init(animation_list); |     StorageAnimationList_init(animation_list); | ||||||
|     animation_storage_fill_animation_list(&animation_list); |     animation_storage_fill_animation_list(&animation_list); | ||||||
| 
 | 
 | ||||||
|     Power* power = furi_record_open("power"); |  | ||||||
|     bool battery_is_well = power_is_battery_healthy(power); |  | ||||||
|     furi_record_close("power"); |  | ||||||
| 
 |  | ||||||
|     Storage* storage = furi_record_open("storage"); |  | ||||||
|     FS_Error sd_status = storage_sd_status(storage); |  | ||||||
|     furi_record_close("storage"); |  | ||||||
| 
 |  | ||||||
|     Dolphin* dolphin = furi_record_open("dolphin"); |     Dolphin* dolphin = furi_record_open("dolphin"); | ||||||
|     DolphinStats stats = dolphin_stats(dolphin); |     DolphinStats stats = dolphin_stats(dolphin); | ||||||
|  |     furi_record_close("dolphin"); | ||||||
|     uint32_t whole_weight = 0; |     uint32_t whole_weight = 0; | ||||||
| 
 | 
 | ||||||
|     StorageAnimationList_it_t it; |     StorageAnimationList_it_t it; | ||||||
|     for(StorageAnimationList_it(it, animation_list); !StorageAnimationList_end_p(it);) { |     for(StorageAnimationList_it(it, animation_list); !StorageAnimationList_end_p(it);) { | ||||||
|         StorageAnimation* storage_animation = *StorageAnimationList_ref(it); |         StorageAnimation* storage_animation = *StorageAnimationList_ref(it); | ||||||
|         const StorageAnimationMeta* meta = animation_storage_get_meta(storage_animation); |         const StorageAnimationManifestInfo* manifest_info = | ||||||
|         bool skip_animation = false; |             animation_storage_get_meta(storage_animation); | ||||||
|         if(battery_is_well && !string_cmp_str(meta->name, BAD_BATTERY_ANIMATION_NAME)) { |         bool valid = animation_manager_is_valid_idle_animation(manifest_info, &stats); | ||||||
|             skip_animation = true; |  | ||||||
|         } else if((sd_status != FSE_NOT_READY) && !string_cmp_str(meta->name, NO_SD_ANIMATION_NAME)) { |  | ||||||
|             skip_animation = true; |  | ||||||
|         } else if((stats.butthurt < meta->min_butthurt) || (stats.butthurt > meta->max_butthurt)) { |  | ||||||
|             skip_animation = true; |  | ||||||
|         } else if((stats.level < meta->min_level) || (stats.level > meta->max_level)) { |  | ||||||
|             skip_animation = true; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         if(skip_animation) { |         if(valid) { | ||||||
|  |             whole_weight += manifest_info->weight; | ||||||
|  |             StorageAnimationList_next(it); | ||||||
|  |         } else { | ||||||
|             animation_storage_free_storage_animation(&storage_animation); |             animation_storage_free_storage_animation(&storage_animation); | ||||||
|             /* remove and increase iterator */ |             /* remove and increase iterator */ | ||||||
|             StorageAnimationList_remove(animation_list, it); |             StorageAnimationList_remove(animation_list, it); | ||||||
|         } else { |  | ||||||
|             whole_weight += meta->weight; |  | ||||||
|             StorageAnimationList_next(it); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -325,11 +395,10 @@ static StorageAnimation* | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     StorageAnimationList_clear(animation_list); |     StorageAnimationList_clear(animation_list); | ||||||
|     furi_record_close("dolphin"); |  | ||||||
| 
 | 
 | ||||||
|     /* cache animation, if failed - choose reliable animation */ |     /* cache animation, if failed - choose reliable animation */ | ||||||
|     if(!animation_storage_get_bubble_animation(selected)) { |     if(!animation_storage_get_bubble_animation(selected)) { | ||||||
|         const char* name = string_get_cstr(animation_storage_get_meta(selected)->name); |         const char* name = animation_storage_get_meta(selected)->name; | ||||||
|         FURI_LOG_E(TAG, "Can't upload animation described in manifest: \'%s\'", name); |         FURI_LOG_E(TAG, "Can't upload animation described in manifest: \'%s\'", name); | ||||||
|         animation_storage_free_storage_animation(&selected); |         animation_storage_free_storage_animation(&selected); | ||||||
|         selected = animation_storage_find_animation(HARDCODED_ANIMATION_NAME); |         selected = animation_storage_find_animation(HARDCODED_ANIMATION_NAME); | ||||||
| @ -362,9 +431,15 @@ void animation_manager_unload_and_stall_animation(AnimationManager* animation_ma | |||||||
|         furi_assert(0); |         furi_assert(0); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     StorageAnimationMeta* meta = animation_storage_get_meta(animation_manager->current_animation); |     FURI_LOG_I( | ||||||
|  |         TAG, | ||||||
|  |         "Unload animation \'%s\'", | ||||||
|  |         animation_storage_get_meta(animation_manager->current_animation)->name); | ||||||
|  | 
 | ||||||
|  |     StorageAnimationManifestInfo* meta = | ||||||
|  |         animation_storage_get_meta(animation_manager->current_animation); | ||||||
|     /* copy str, not move, because it can be internal animation */ |     /* copy str, not move, because it can be internal animation */ | ||||||
|     string_set(animation_manager->freezed_animation_name, meta->name); |     string_set_str(animation_manager->freezed_animation_name, meta->name); | ||||||
| 
 | 
 | ||||||
|     bubble_animation_freeze(animation_manager->animation_view); |     bubble_animation_freeze(animation_manager->animation_view); | ||||||
|     animation_storage_free_storage_animation(&animation_manager->current_animation); |     animation_storage_free_storage_animation(&animation_manager->current_animation); | ||||||
| @ -394,18 +469,27 @@ void animation_manager_load_and_continue_animation(AnimationManager* animation_m | |||||||
|             StorageAnimation* restore_animation = animation_storage_find_animation( |             StorageAnimation* restore_animation = animation_storage_find_animation( | ||||||
|                 string_get_cstr(animation_manager->freezed_animation_name)); |                 string_get_cstr(animation_manager->freezed_animation_name)); | ||||||
|             if(restore_animation) { |             if(restore_animation) { | ||||||
|                 animation_manager_replace_current_animation(animation_manager, restore_animation); |                 Dolphin* dolphin = furi_record_open("dolphin"); | ||||||
|                 animation_manager->state = AnimationManagerStateIdle; |                 DolphinStats stats = dolphin_stats(dolphin); | ||||||
|  |                 furi_record_close("dolphin"); | ||||||
|  |                 const StorageAnimationManifestInfo* manifest_info = | ||||||
|  |                     animation_storage_get_meta(restore_animation); | ||||||
|  |                 bool valid = animation_manager_is_valid_idle_animation(manifest_info, &stats); | ||||||
|  |                 if(valid) { | ||||||
|  |                     animation_manager_replace_current_animation( | ||||||
|  |                         animation_manager, restore_animation); | ||||||
|  |                     animation_manager->state = AnimationManagerStateIdle; | ||||||
| 
 | 
 | ||||||
|                 if(animation_manager->freezed_animation_time_left) { |                     if(animation_manager->freezed_animation_time_left) { | ||||||
|                     osTimerStart( |                         osTimerStart( | ||||||
|                         animation_manager->idle_animation_timer, |                             animation_manager->idle_animation_timer, | ||||||
|                         animation_manager->freezed_animation_time_left); |                             animation_manager->freezed_animation_time_left); | ||||||
|                 } else { |                     } else { | ||||||
|                     const BubbleAnimation* animation = animation_storage_get_bubble_animation( |                         const BubbleAnimation* animation = animation_storage_get_bubble_animation( | ||||||
|                         animation_manager->current_animation); |                             animation_manager->current_animation); | ||||||
|                     osTimerStart( |                         osTimerStart( | ||||||
|                         animation_manager->idle_animation_timer, animation->duration * 1000); |                             animation_manager->idle_animation_timer, animation->duration * 1000); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|                 FURI_LOG_E( |                 FURI_LOG_E( | ||||||
| @ -423,12 +507,47 @@ void animation_manager_load_and_continue_animation(AnimationManager* animation_m | |||||||
|     if(!animation_manager->current_animation) { |     if(!animation_manager->current_animation) { | ||||||
|         animation_manager_start_new_idle(animation_manager); |         animation_manager_start_new_idle(animation_manager); | ||||||
|     } |     } | ||||||
|     FURI_LOG_D( |     FURI_LOG_I( | ||||||
|         TAG, |         TAG, | ||||||
|         "Load & Continue with \'%s\'", |         "Load animation \'%s\'", | ||||||
|         string_get_cstr(animation_storage_get_meta(animation_manager->current_animation)->name)); |         animation_storage_get_meta(animation_manager->current_animation)->name); | ||||||
| 
 | 
 | ||||||
|     bubble_animation_unfreeze(animation_manager->animation_view); |     bubble_animation_unfreeze(animation_manager->animation_view); | ||||||
|     string_reset(animation_manager->freezed_animation_name); |     string_reset(animation_manager->freezed_animation_name); | ||||||
|     furi_assert(animation_manager->current_animation); |     furi_assert(animation_manager->current_animation); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | static void animation_manager_switch_to_one_shot_view(AnimationManager* animation_manager) { | ||||||
|  |     furi_assert(animation_manager); | ||||||
|  |     furi_assert(!animation_manager->one_shot_view); | ||||||
|  |     Dolphin* dolphin = furi_record_open("dolphin"); | ||||||
|  |     DolphinStats stats = dolphin_stats(dolphin); | ||||||
|  |     furi_record_close("dolphin"); | ||||||
|  | 
 | ||||||
|  |     animation_manager->one_shot_view = one_shot_view_alloc(); | ||||||
|  |     one_shot_view_set_interact_callback( | ||||||
|  |         animation_manager->one_shot_view, animation_manager_interact_callback, animation_manager); | ||||||
|  |     View* prev_view = bubble_animation_get_view(animation_manager->animation_view); | ||||||
|  |     View* next_view = one_shot_view_get_view(animation_manager->one_shot_view); | ||||||
|  |     view_stack_remove_view(animation_manager->view_stack, prev_view); | ||||||
|  |     view_stack_add_view(animation_manager->view_stack, next_view); | ||||||
|  |     if(stats.level == 1) { | ||||||
|  |         one_shot_view_start_animation(animation_manager->one_shot_view, &A_Levelup1_128x64); | ||||||
|  |     } else if(stats.level == 2) { | ||||||
|  |         one_shot_view_start_animation(animation_manager->one_shot_view, &A_Levelup2_128x64); | ||||||
|  |     } else { | ||||||
|  |         furi_assert(0); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void animation_manager_switch_to_animation_view(AnimationManager* animation_manager) { | ||||||
|  |     furi_assert(animation_manager); | ||||||
|  |     furi_assert(animation_manager->one_shot_view); | ||||||
|  | 
 | ||||||
|  |     View* prev_view = one_shot_view_get_view(animation_manager->one_shot_view); | ||||||
|  |     View* next_view = bubble_animation_get_view(animation_manager->animation_view); | ||||||
|  |     view_stack_remove_view(animation_manager->view_stack, prev_view); | ||||||
|  |     view_stack_add_view(animation_manager->view_stack, next_view); | ||||||
|  |     one_shot_view_free(animation_manager->one_shot_view); | ||||||
|  |     animation_manager->one_shot_view = NULL; | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,34 +1,35 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "dolphin/dolphin.h" |  | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
|  | #include <gui/icon_i.h> | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
| 
 | 
 | ||||||
| typedef struct AnimationManager AnimationManager; | typedef struct AnimationManager AnimationManager; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint8_t x; |     uint8_t x; | ||||||
|     uint8_t y; |     uint8_t y; | ||||||
|     const char* str; |     const char* text; | ||||||
|     Align horizontal; |     Align align_h; | ||||||
|     Align vertical; |     Align align_v; | ||||||
| } Bubble; | } Bubble; | ||||||
| 
 | 
 | ||||||
| typedef struct FrameBubble { | typedef struct FrameBubble { | ||||||
|     Bubble bubble; |     Bubble bubble; | ||||||
|     uint8_t starts_at_frame; |     uint8_t start_frame; | ||||||
|     uint8_t ends_at_frame; |     uint8_t end_frame; | ||||||
|     struct FrameBubble* next_bubble; |     const struct FrameBubble* next_bubble; | ||||||
| } FrameBubble; | } FrameBubble; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     FrameBubble** frame_bubbles; |     const FrameBubble* const* frame_bubble_sequences; | ||||||
|     uint8_t frame_bubbles_count; |     uint8_t frame_bubble_sequences_count; | ||||||
|     const Icon** icons; |     const Icon icon_animation; | ||||||
|  |     const uint8_t* frame_order; | ||||||
|     uint8_t passive_frames; |     uint8_t passive_frames; | ||||||
|     uint8_t active_frames; |     uint8_t active_frames; | ||||||
|     uint8_t active_cycles; |     uint8_t active_cycles; | ||||||
|     uint8_t frame_rate; |  | ||||||
|     uint16_t duration; |     uint16_t duration; | ||||||
|     uint16_t active_cooldown; |     uint16_t active_cooldown; | ||||||
| } BubbleAnimation; | } BubbleAnimation; | ||||||
|  | |||||||
| @ -1,34 +1,86 @@ | |||||||
| #include "animation_manager.h" |  | ||||||
| #include "file_worker.h" |  | ||||||
| #include "flipper_file.h" |  | ||||||
| #include "furi/common_defines.h" |  | ||||||
| #include "furi/memmgr.h" |  | ||||||
| #include "furi/record.h" |  | ||||||
| #include "animation_storage.h" |  | ||||||
| #include "gui/canvas.h" |  | ||||||
| #include "m-string.h" |  | ||||||
| #include "pb.h" |  | ||||||
| #include "pb_decode.h" |  | ||||||
| #include "storage/filesystem_api_defines.h" |  | ||||||
| #include "storage/storage.h" |  | ||||||
| #include "animation_storage_i.h" |  | ||||||
| #include <stdint.h> |  | ||||||
| #include <gui/icon_i.h> |  | ||||||
| 
 | 
 | ||||||
| // Read documentation before using it
 | #include <stdint.h> | ||||||
|  | #include <flipper_file.h> | ||||||
|  | #include <furi.h> | ||||||
| #include <furi/dangerous_defines.h> | #include <furi/dangerous_defines.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | #include <gui/icon_i.h> | ||||||
|  | #include <m-string.h> | ||||||
|  | 
 | ||||||
|  | #include "animation_manager.h" | ||||||
|  | #include "animation_storage.h" | ||||||
|  | #include "animation_storage_i.h" | ||||||
|  | #include <assets_dolphin_internal.h> | ||||||
|  | #include <assets_dolphin_blocking.h> | ||||||
| 
 | 
 | ||||||
| #define ANIMATION_META_FILE "meta.txt" | #define ANIMATION_META_FILE "meta.txt" | ||||||
| #define ANIMATION_DIR "/ext/dolphin/animations" | #define ANIMATION_DIR "/ext/dolphin" | ||||||
| #define ANIMATION_MANIFEST_FILE ANIMATION_DIR "/manifest.txt" | #define ANIMATION_MANIFEST_FILE ANIMATION_DIR "/manifest.txt" | ||||||
| #define TAG "AnimationStorage" | #define TAG "AnimationStorage" | ||||||
| #define DEBUG_PB 0 |  | ||||||
| 
 | 
 | ||||||
| static void animation_storage_free_bubbles(BubbleAnimation* animation); | static void animation_storage_free_bubbles(BubbleAnimation* animation); | ||||||
| static void animation_storage_free_frames(BubbleAnimation* animation); | static void animation_storage_free_frames(BubbleAnimation* animation); | ||||||
| static void animation_storage_free_animation(BubbleAnimation** storage_animation); | static void animation_storage_free_animation(BubbleAnimation** storage_animation); | ||||||
| static BubbleAnimation* animation_storage_load_animation(const char* name); | static BubbleAnimation* animation_storage_load_animation(const char* name); | ||||||
| 
 | 
 | ||||||
|  | static bool animation_storage_load_single_manifest_info( | ||||||
|  |     StorageAnimationManifestInfo* manifest_info, | ||||||
|  |     const char* name) { | ||||||
|  |     furi_assert(manifest_info); | ||||||
|  | 
 | ||||||
|  |     bool result = false; | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     FlipperFile* file = flipper_file_alloc(storage); | ||||||
|  |     flipper_file_set_strict_mode(file, true); | ||||||
|  |     string_t read_string; | ||||||
|  |     string_init(read_string); | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         uint32_t u32value; | ||||||
|  |         if(FSE_OK != storage_sd_status(storage)) break; | ||||||
|  |         if(!flipper_file_open_existing(file, ANIMATION_MANIFEST_FILE)) break; | ||||||
|  | 
 | ||||||
|  |         if(!flipper_file_read_header(file, read_string, &u32value)) break; | ||||||
|  |         if(string_cmp_str(read_string, "Flipper Animation Manifest")) break; | ||||||
|  | 
 | ||||||
|  |         manifest_info->name = NULL; | ||||||
|  | 
 | ||||||
|  |         /* skip other animation names */ | ||||||
|  |         flipper_file_set_strict_mode(file, false); | ||||||
|  |         while(flipper_file_read_string(file, "Name", read_string) && | ||||||
|  |               string_cmp_str(read_string, name)) | ||||||
|  |             ; | ||||||
|  |         if(string_cmp_str(read_string, name)) break; | ||||||
|  |         flipper_file_set_strict_mode(file, true); | ||||||
|  | 
 | ||||||
|  |         manifest_info->name = furi_alloc(string_size(read_string) + 1); | ||||||
|  |         strcpy((char*)manifest_info->name, string_get_cstr(read_string)); | ||||||
|  | 
 | ||||||
|  |         if(!flipper_file_read_uint32(file, "Min butthurt", &u32value, 1)) break; | ||||||
|  |         manifest_info->min_butthurt = u32value; | ||||||
|  |         if(!flipper_file_read_uint32(file, "Max butthurt", &u32value, 1)) break; | ||||||
|  |         manifest_info->max_butthurt = u32value; | ||||||
|  |         if(!flipper_file_read_uint32(file, "Min level", &u32value, 1)) break; | ||||||
|  |         manifest_info->min_level = u32value; | ||||||
|  |         if(!flipper_file_read_uint32(file, "Max level", &u32value, 1)) break; | ||||||
|  |         manifest_info->max_level = u32value; | ||||||
|  |         if(!flipper_file_read_uint32(file, "Weight", &u32value, 1)) break; | ||||||
|  |         manifest_info->weight = u32value; | ||||||
|  |         result = true; | ||||||
|  |     } while(0); | ||||||
|  | 
 | ||||||
|  |     if(!result && manifest_info->name) { | ||||||
|  |         free((void*)manifest_info->name); | ||||||
|  |     } | ||||||
|  |     string_clear(read_string); | ||||||
|  |     flipper_file_close(file); | ||||||
|  |     flipper_file_free(file); | ||||||
|  | 
 | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void animation_storage_fill_animation_list(StorageAnimationList_t* animation_list) { | void animation_storage_fill_animation_list(StorageAnimationList_t* animation_list) { | ||||||
|     furi_assert(sizeof(StorageAnimationList_t) == sizeof(void*)); |     furi_assert(sizeof(StorageAnimationList_t) == sizeof(void*)); | ||||||
|     furi_assert(!StorageAnimationList_size(*animation_list)); |     furi_assert(!StorageAnimationList_size(*animation_list)); | ||||||
| @ -37,8 +89,8 @@ void animation_storage_fill_animation_list(StorageAnimationList_t* animation_lis | |||||||
|     FlipperFile* file = flipper_file_alloc(storage); |     FlipperFile* file = flipper_file_alloc(storage); | ||||||
|     /* Forbid skipping fields */ |     /* Forbid skipping fields */ | ||||||
|     flipper_file_set_strict_mode(file, true); |     flipper_file_set_strict_mode(file, true); | ||||||
|     string_t header; |     string_t read_string; | ||||||
|     string_init(header); |     string_init(read_string); | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         uint32_t u32value; |         uint32_t u32value; | ||||||
| @ -46,24 +98,28 @@ void animation_storage_fill_animation_list(StorageAnimationList_t* animation_lis | |||||||
| 
 | 
 | ||||||
|         if(FSE_OK != storage_sd_status(storage)) break; |         if(FSE_OK != storage_sd_status(storage)) break; | ||||||
|         if(!flipper_file_open_existing(file, ANIMATION_MANIFEST_FILE)) break; |         if(!flipper_file_open_existing(file, ANIMATION_MANIFEST_FILE)) break; | ||||||
|         if(!flipper_file_read_header(file, header, &u32value)) break; |         if(!flipper_file_read_header(file, read_string, &u32value)) break; | ||||||
|         if(string_cmp_str(header, "Flipper Animation Manifest")) break; |         if(string_cmp_str(read_string, "Flipper Animation Manifest")) break; | ||||||
|         do { |         do { | ||||||
|             storage_animation = furi_alloc(sizeof(StorageAnimation)); |             storage_animation = furi_alloc(sizeof(StorageAnimation)); | ||||||
|             storage_animation->external = true; |             storage_animation->external = true; | ||||||
|             storage_animation->animation = NULL; |             storage_animation->animation = NULL; | ||||||
|  |             storage_animation->manifest_info.name = NULL; | ||||||
|  | 
 | ||||||
|  |             if(!flipper_file_read_string(file, "Name", read_string)) break; | ||||||
|  |             storage_animation->manifest_info.name = furi_alloc(string_size(read_string) + 1); | ||||||
|  |             strcpy((char*)storage_animation->manifest_info.name, string_get_cstr(read_string)); | ||||||
| 
 | 
 | ||||||
|             if(!flipper_file_read_string(file, "Name", storage_animation->meta.name)) break; |  | ||||||
|             if(!flipper_file_read_uint32(file, "Min butthurt", &u32value, 1)) break; |             if(!flipper_file_read_uint32(file, "Min butthurt", &u32value, 1)) break; | ||||||
|             storage_animation->meta.min_butthurt = u32value; |             storage_animation->manifest_info.min_butthurt = u32value; | ||||||
|             if(!flipper_file_read_uint32(file, "Max butthurt", &u32value, 1)) break; |             if(!flipper_file_read_uint32(file, "Max butthurt", &u32value, 1)) break; | ||||||
|             storage_animation->meta.max_butthurt = u32value; |             storage_animation->manifest_info.max_butthurt = u32value; | ||||||
|             if(!flipper_file_read_uint32(file, "Min level", &u32value, 1)) break; |             if(!flipper_file_read_uint32(file, "Min level", &u32value, 1)) break; | ||||||
|             storage_animation->meta.min_level = u32value; |             storage_animation->manifest_info.min_level = u32value; | ||||||
|             if(!flipper_file_read_uint32(file, "Max level", &u32value, 1)) break; |             if(!flipper_file_read_uint32(file, "Max level", &u32value, 1)) break; | ||||||
|             storage_animation->meta.max_level = u32value; |             storage_animation->manifest_info.max_level = u32value; | ||||||
|             if(!flipper_file_read_uint32(file, "Weight", &u32value, 1)) break; |             if(!flipper_file_read_uint32(file, "Weight", &u32value, 1)) break; | ||||||
|             storage_animation->meta.weight = u32value; |             storage_animation->manifest_info.weight = u32value; | ||||||
| 
 | 
 | ||||||
|             StorageAnimationList_push_back(*animation_list, storage_animation); |             StorageAnimationList_push_back(*animation_list, storage_animation); | ||||||
|         } while(1); |         } while(1); | ||||||
| @ -71,13 +127,13 @@ void animation_storage_fill_animation_list(StorageAnimationList_t* animation_lis | |||||||
|         animation_storage_free_storage_animation(&storage_animation); |         animation_storage_free_storage_animation(&storage_animation); | ||||||
|     } while(0); |     } while(0); | ||||||
| 
 | 
 | ||||||
|     string_clear(header); |     string_clear(read_string); | ||||||
|     flipper_file_close(file); |     flipper_file_close(file); | ||||||
|     flipper_file_free(file); |     flipper_file_free(file); | ||||||
| 
 | 
 | ||||||
|     // add hard-coded animations
 |     // add hard-coded animations
 | ||||||
|     for(int i = 0; i < COUNT_OF(StorageAnimationInternal); ++i) { |     for(int i = 0; i < dolphin_internal_size; ++i) { | ||||||
|         StorageAnimationList_push_back(*animation_list, &StorageAnimationInternal[i]); |         StorageAnimationList_push_back(*animation_list, (StorageAnimation*)&dolphin_internal[i]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     furi_record_close("storage"); |     furi_record_close("storage"); | ||||||
| @ -88,41 +144,45 @@ StorageAnimation* animation_storage_find_animation(const char* name) { | |||||||
|     furi_assert(strlen(name)); |     furi_assert(strlen(name)); | ||||||
|     StorageAnimation* storage_animation = NULL; |     StorageAnimation* storage_animation = NULL; | ||||||
| 
 | 
 | ||||||
|     /* look through internal animations */ |     for(int i = 0; i < dolphin_blocking_size; ++i) { | ||||||
|     for(int i = 0; i < COUNT_OF(StorageAnimationInternal); ++i) { |         if(!strcmp(dolphin_blocking[i].manifest_info.name, name)) { | ||||||
|         if(!string_cmp_str(StorageAnimationInternal[i].meta.name, name)) { |             storage_animation = (StorageAnimation*)&dolphin_blocking[i]; | ||||||
|             storage_animation = &StorageAnimationInternal[i]; |  | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if(!storage_animation) { | ||||||
|  |         for(int i = 0; i < dolphin_internal_size; ++i) { | ||||||
|  |             if(!strcmp(dolphin_internal[i].manifest_info.name, name)) { | ||||||
|  |                 storage_animation = (StorageAnimation*)&dolphin_internal[i]; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /* look through external animations */ |     /* look through external animations */ | ||||||
|     if(!storage_animation) { |     if(!storage_animation) { | ||||||
|         BubbleAnimation* animation = animation_storage_load_animation(name); |         storage_animation = furi_alloc(sizeof(StorageAnimation)); | ||||||
|  |         storage_animation->external = true; | ||||||
| 
 | 
 | ||||||
|         if(animation != NULL) { |         bool result = false; | ||||||
|             storage_animation = furi_alloc(sizeof(StorageAnimation)); |         result = | ||||||
|             storage_animation->animation = animation; |             animation_storage_load_single_manifest_info(&storage_animation->manifest_info, name); | ||||||
|             storage_animation->external = true; |         if(result) { | ||||||
|             /* meta data takes part in random animation selection, so it
 |             storage_animation->animation = animation_storage_load_animation(name); | ||||||
|              * doesn't need here as we exactly know which animation we need, |             result = !!storage_animation->animation; | ||||||
|              * that's why we can ignore reading manifest.txt file |         } | ||||||
|              * filling meta data by zeroes */ |         if(!result) { | ||||||
|             storage_animation->meta.min_butthurt = 0; |             animation_storage_free_storage_animation(&storage_animation); | ||||||
|             storage_animation->meta.max_butthurt = 0; |  | ||||||
|             storage_animation->meta.min_level = 0; |  | ||||||
|             storage_animation->meta.max_level = 0; |  | ||||||
|             storage_animation->meta.weight = 0; |  | ||||||
|             string_init_set_str(storage_animation->meta.name, name); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return storage_animation; |     return storage_animation; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| StorageAnimationMeta* animation_storage_get_meta(StorageAnimation* storage_animation) { | StorageAnimationManifestInfo* animation_storage_get_meta(StorageAnimation* storage_animation) { | ||||||
|     furi_assert(storage_animation); |     furi_assert(storage_animation); | ||||||
|     return &storage_animation->meta; |     return &storage_animation->manifest_info; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const BubbleAnimation* | const BubbleAnimation* | ||||||
| @ -138,7 +198,7 @@ void animation_storage_cache_animation(StorageAnimation* storage_animation) { | |||||||
|     if(storage_animation->external) { |     if(storage_animation->external) { | ||||||
|         if(!storage_animation->animation) { |         if(!storage_animation->animation) { | ||||||
|             storage_animation->animation = |             storage_animation->animation = | ||||||
|                 animation_storage_load_animation(string_get_cstr(storage_animation->meta.name)); |                 animation_storage_load_animation(storage_animation->manifest_info.name); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -149,6 +209,9 @@ static void animation_storage_free_animation(BubbleAnimation** animation) { | |||||||
|     if(*animation) { |     if(*animation) { | ||||||
|         animation_storage_free_bubbles(*animation); |         animation_storage_free_bubbles(*animation); | ||||||
|         animation_storage_free_frames(*animation); |         animation_storage_free_frames(*animation); | ||||||
|  |         if((*animation)->frame_order) { | ||||||
|  |             free((void*)(*animation)->frame_order); | ||||||
|  |         } | ||||||
|         free(*animation); |         free(*animation); | ||||||
|         *animation = NULL; |         *animation = NULL; | ||||||
|     } |     } | ||||||
| @ -161,7 +224,9 @@ void animation_storage_free_storage_animation(StorageAnimation** storage_animati | |||||||
|     if((*storage_animation)->external) { |     if((*storage_animation)->external) { | ||||||
|         animation_storage_free_animation((BubbleAnimation**)&(*storage_animation)->animation); |         animation_storage_free_animation((BubbleAnimation**)&(*storage_animation)->animation); | ||||||
| 
 | 
 | ||||||
|         string_clear((*storage_animation)->meta.name); |         if((*storage_animation)->manifest_info.name) { | ||||||
|  |             free((void*)(*storage_animation)->manifest_info.name); | ||||||
|  |         } | ||||||
|         free(*storage_animation); |         free(*storage_animation); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -188,41 +253,15 @@ static bool animation_storage_cast_align(string_t align_str, Align* align) { | |||||||
| 
 | 
 | ||||||
| static void animation_storage_free_frames(BubbleAnimation* animation) { | static void animation_storage_free_frames(BubbleAnimation* animation) { | ||||||
|     furi_assert(animation); |     furi_assert(animation); | ||||||
|     furi_assert(animation->icons); |  | ||||||
| 
 | 
 | ||||||
|     const Icon** icons = animation->icons; |     const Icon* icon = &animation->icon_animation; | ||||||
|     uint16_t frames = animation->active_frames + animation->passive_frames; |     for(int i = 0; i < icon->frame_count; ++i) { | ||||||
|     furi_assert(frames > 0); |         if(icon->frames[i]) { | ||||||
| 
 |             free((void*)icon->frames[i]); | ||||||
|     for(int i = 0; i < frames; ++i) { |  | ||||||
|         if(!icons[i]) continue; |  | ||||||
| 
 |  | ||||||
|         const Icon* icon = icons[i]; |  | ||||||
|         free((void*)icon->frames[0]); |  | ||||||
|         free(icon->frames); |  | ||||||
|         free((void*)icon); |  | ||||||
|         for(int j = i; j < frames; ++j) { |  | ||||||
|             if(icons[j] == icon) { |  | ||||||
|                 icons[j] = NULL; |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     free(animation->icons); |  | ||||||
|     animation->icons = NULL; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static Icon* animation_storage_alloc_icon(size_t frame_size) { |  | ||||||
|     Icon* icon = furi_alloc(sizeof(Icon)); |  | ||||||
|     icon->frames = furi_alloc(sizeof(const uint8_t*)); |  | ||||||
|     icon->frames[0] = furi_alloc(frame_size); |  | ||||||
|     return icon; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void animation_storage_free_icon(Icon* icon) { |  | ||||||
|     free((void*)icon->frames[0]); |  | ||||||
|     free(icon->frames); |     free(icon->frames); | ||||||
|     free(icon); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool animation_storage_load_frames( | static bool animation_storage_load_frames( | ||||||
| @ -230,24 +269,37 @@ static bool animation_storage_load_frames( | |||||||
|     const char* name, |     const char* name, | ||||||
|     BubbleAnimation* animation, |     BubbleAnimation* animation, | ||||||
|     uint32_t* frame_order, |     uint32_t* frame_order, | ||||||
|     uint32_t width, |     uint8_t width, | ||||||
|     uint32_t height) { |     uint8_t height) { | ||||||
|     furi_assert(!animation->icons); |     uint16_t frame_order_count = animation->passive_frames + animation->active_frames; | ||||||
|     uint16_t frame_order_size = animation->passive_frames + animation->active_frames; | 
 | ||||||
|  |     /* The frames should go in order (0...N), without omissions */ | ||||||
|  |     size_t max_frame_count = 0; | ||||||
|  |     for(int i = 0; i < frame_order_count; ++i) { | ||||||
|  |         max_frame_count = MAX(max_frame_count, frame_order[i]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if((max_frame_count >= frame_order_count) || (max_frame_count >= 256 /* max uint8_t */)) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Icon* icon = (Icon*)&animation->icon_animation; | ||||||
|  |     FURI_CONST_ASSIGN(icon->frame_count, max_frame_count + 1); | ||||||
|  |     FURI_CONST_ASSIGN(icon->frame_rate, 0); | ||||||
|  |     FURI_CONST_ASSIGN(icon->height, height); | ||||||
|  |     FURI_CONST_ASSIGN(icon->width, width); | ||||||
|  |     icon->frames = furi_alloc(sizeof(const uint8_t*) * icon->frame_count); | ||||||
| 
 | 
 | ||||||
|     bool frames_ok = false; |     bool frames_ok = false; | ||||||
|     animation->icons = furi_alloc(sizeof(const Icon*) * frame_order_size); |  | ||||||
|     File* file = storage_file_alloc(storage); |     File* file = storage_file_alloc(storage); | ||||||
|     FileInfo file_info; |     FileInfo file_info; | ||||||
|     string_t filename; |     string_t filename; | ||||||
|     string_init(filename); |     string_init(filename); | ||||||
|     size_t max_filesize = ROUND_UP_TO(width, 8) * height + 1; |     size_t max_filesize = ROUND_UP_TO(width, 8) * height + 1; | ||||||
| 
 | 
 | ||||||
|     for(int i = 0; i < frame_order_size; ++i) { |     for(int i = 0; i < icon->frame_count; ++i) { | ||||||
|         if(animation->icons[i]) continue; |  | ||||||
| 
 |  | ||||||
|         frames_ok = false; |         frames_ok = false; | ||||||
|         string_printf(filename, ANIMATION_DIR "/%s/frame_%d.bm", name, frame_order[i]); |         string_printf(filename, ANIMATION_DIR "/%s/frame_%d.bm", name, i); | ||||||
| 
 | 
 | ||||||
|         if(storage_common_stat(storage, string_get_cstr(filename), &file_info) != FSE_OK) break; |         if(storage_common_stat(storage, string_get_cstr(filename), &file_info) != FSE_OK) break; | ||||||
|         if(file_info.size > max_filesize) { |         if(file_info.size > max_filesize) { | ||||||
| @ -265,24 +317,12 @@ static bool animation_storage_load_frames( | |||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         Icon* icon = animation_storage_alloc_icon(file_info.size); |         icon->frames[i] = furi_alloc(file_info.size); | ||||||
|         if(storage_file_read(file, (void*)icon->frames[0], file_info.size) != file_info.size) { |         if(storage_file_read(file, (void*)icon->frames[i], file_info.size) != file_info.size) { | ||||||
|             FURI_LOG_E(TAG, "Read failed: \'%s\'", string_get_cstr(filename)); |             FURI_LOG_E(TAG, "Read failed: \'%s\'", string_get_cstr(filename)); | ||||||
|             animation_storage_free_icon(icon); |  | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|         storage_file_close(file); |         storage_file_close(file); | ||||||
|         FURI_CONST_ASSIGN(icon->frame_count, 1); |  | ||||||
|         FURI_CONST_ASSIGN(icon->frame_rate, 0); |  | ||||||
|         FURI_CONST_ASSIGN(icon->height, height); |  | ||||||
|         FURI_CONST_ASSIGN(icon->width, width); |  | ||||||
| 
 |  | ||||||
|         /* Claim 1 allocation for 1 files blob and several links to it */ |  | ||||||
|         for(int j = i; j < frame_order_size; ++j) { |  | ||||||
|             if(frame_order[i] == frame_order[j]) { |  | ||||||
|                 animation->icons[j] = icon; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         frames_ok = true; |         frames_ok = true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -295,11 +335,10 @@ static bool animation_storage_load_frames( | |||||||
|             height, |             height, | ||||||
|             file_info.size); |             file_info.size); | ||||||
|         animation_storage_free_frames(animation); |         animation_storage_free_frames(animation); | ||||||
|         animation->icons = NULL; |  | ||||||
|     } else { |     } else { | ||||||
|         for(int i = 0; i < frame_order_size; ++i) { |         furi_check(animation->icon_animation.frames); | ||||||
|             furi_check(animation->icons[i]); |         for(int i = 0; i < animation->icon_animation.frame_count; ++i) { | ||||||
|             furi_check(animation->icons[i]->frames[0]); |             furi_check(animation->icon_animation.frames[i]); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -314,72 +353,73 @@ static bool animation_storage_load_bubbles(BubbleAnimation* animation, FlipperFi | |||||||
|     string_t str; |     string_t str; | ||||||
|     string_init(str); |     string_init(str); | ||||||
|     bool success = false; |     bool success = false; | ||||||
|     furi_assert(!animation->frame_bubbles); |     furi_assert(!animation->frame_bubble_sequences); | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         if(!flipper_file_read_uint32(ff, "Bubble slots", &u32value, 1)) break; |         if(!flipper_file_read_uint32(ff, "Bubble slots", &u32value, 1)) break; | ||||||
|         if(u32value > 20) break; |         if(u32value > 20) break; | ||||||
|         animation->frame_bubbles_count = u32value; |         animation->frame_bubble_sequences_count = u32value; | ||||||
|         if(animation->frame_bubbles_count == 0) { |         if(animation->frame_bubble_sequences_count == 0) { | ||||||
|             animation->frame_bubbles = NULL; |             animation->frame_bubble_sequences = NULL; | ||||||
|             success = true; |             success = true; | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|         animation->frame_bubbles = |         animation->frame_bubble_sequences = | ||||||
|             furi_alloc(sizeof(FrameBubble*) * animation->frame_bubbles_count); |             furi_alloc(sizeof(FrameBubble*) * animation->frame_bubble_sequences_count); | ||||||
| 
 | 
 | ||||||
|         uint32_t current_slot = 0; |         uint32_t current_slot = 0; | ||||||
|         for(int i = 0; i < animation->frame_bubbles_count; ++i) { |         for(int i = 0; i < animation->frame_bubble_sequences_count; ++i) { | ||||||
|             animation->frame_bubbles[i] = furi_alloc(sizeof(FrameBubble)); |             FURI_CONST_ASSIGN_PTR( | ||||||
|  |                 animation->frame_bubble_sequences[i], furi_alloc(sizeof(FrameBubble))); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         FrameBubble* bubble = animation->frame_bubbles[0]; |         const FrameBubble* bubble = animation->frame_bubble_sequences[0]; | ||||||
|         int8_t index = -1; |         int8_t index = -1; | ||||||
|         for(;;) { |         for(;;) { | ||||||
|             if(!flipper_file_read_uint32(ff, "Slot", ¤t_slot, 1)) break; |             if(!flipper_file_read_uint32(ff, "Slot", ¤t_slot, 1)) break; | ||||||
|             if((current_slot != 0) && (index == -1)) break; |             if((current_slot != 0) && (index == -1)) break; | ||||||
| 
 | 
 | ||||||
|             if(current_slot == index) { |             if(current_slot == index) { | ||||||
|                 bubble->next_bubble = furi_alloc(sizeof(FrameBubble)); |                 FURI_CONST_ASSIGN_PTR(bubble->next_bubble, furi_alloc(sizeof(FrameBubble))); | ||||||
|                 bubble = bubble->next_bubble; |                 bubble = bubble->next_bubble; | ||||||
|             } else if(current_slot == index + 1) { |             } else if(current_slot == index + 1) { | ||||||
|                 ++index; |                 ++index; | ||||||
|                 bubble = animation->frame_bubbles[index]; |                 bubble = animation->frame_bubble_sequences[index]; | ||||||
|             } else { |             } else { | ||||||
|                 /* slots have to start from 0, be ascending sorted, and
 |                 /* slots have to start from 0, be ascending sorted, and
 | ||||||
|                  * have exact number of slots as specified in "Bubble slots" */ |                  * have exact number of slots as specified in "Bubble slots" */ | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             if(index >= animation->frame_bubbles_count) break; |             if(index >= animation->frame_bubble_sequences_count) break; | ||||||
| 
 | 
 | ||||||
|             if(!flipper_file_read_uint32(ff, "X", &u32value, 1)) break; |             if(!flipper_file_read_uint32(ff, "X", &u32value, 1)) break; | ||||||
|             bubble->bubble.x = u32value; |             FURI_CONST_ASSIGN(bubble->bubble.x, u32value); | ||||||
|             if(!flipper_file_read_uint32(ff, "Y", &u32value, 1)) break; |             if(!flipper_file_read_uint32(ff, "Y", &u32value, 1)) break; | ||||||
|             bubble->bubble.y = u32value; |             FURI_CONST_ASSIGN(bubble->bubble.y, u32value); | ||||||
| 
 | 
 | ||||||
|             if(!flipper_file_read_string(ff, "Text", str)) break; |             if(!flipper_file_read_string(ff, "Text", str)) break; | ||||||
|             if(string_size(str) > 100) break; |             if(string_size(str) > 100) break; | ||||||
| 
 | 
 | ||||||
|             string_replace_all_str(str, "\\n", "\n"); |             string_replace_all_str(str, "\\n", "\n"); | ||||||
| 
 | 
 | ||||||
|             bubble->bubble.str = furi_alloc(string_size(str) + 1); |             FURI_CONST_ASSIGN_PTR(bubble->bubble.text, furi_alloc(string_size(str) + 1)); | ||||||
|             strcpy((char*)bubble->bubble.str, string_get_cstr(str)); |             strcpy((char*)bubble->bubble.text, string_get_cstr(str)); | ||||||
| 
 | 
 | ||||||
|             if(!flipper_file_read_string(ff, "AlignH", str)) break; |             if(!flipper_file_read_string(ff, "AlignH", str)) break; | ||||||
|             if(!animation_storage_cast_align(str, &bubble->bubble.horizontal)) break; |             if(!animation_storage_cast_align(str, (Align*)&bubble->bubble.align_h)) break; | ||||||
|             if(!flipper_file_read_string(ff, "AlignV", str)) break; |             if(!flipper_file_read_string(ff, "AlignV", str)) break; | ||||||
|             if(!animation_storage_cast_align(str, &bubble->bubble.vertical)) break; |             if(!animation_storage_cast_align(str, (Align*)&bubble->bubble.align_v)) break; | ||||||
| 
 | 
 | ||||||
|             if(!flipper_file_read_uint32(ff, "StartFrame", &u32value, 1)) break; |             if(!flipper_file_read_uint32(ff, "StartFrame", &u32value, 1)) break; | ||||||
|             bubble->starts_at_frame = u32value; |             FURI_CONST_ASSIGN(bubble->start_frame, u32value); | ||||||
|             if(!flipper_file_read_uint32(ff, "EndFrame", &u32value, 1)) break; |             if(!flipper_file_read_uint32(ff, "EndFrame", &u32value, 1)) break; | ||||||
|             bubble->ends_at_frame = u32value; |             FURI_CONST_ASSIGN(bubble->end_frame, u32value); | ||||||
|         } |         } | ||||||
|         success = (index + 1) == animation->frame_bubbles_count; |         success = (index + 1) == animation->frame_bubble_sequences_count; | ||||||
|     } while(0); |     } while(0); | ||||||
| 
 | 
 | ||||||
|     if(!success) { |     if(!success) { | ||||||
|         if(animation->frame_bubbles) { |         if(animation->frame_bubble_sequences) { | ||||||
|             FURI_LOG_E(TAG, "Failed to load animation bubbles"); |             FURI_LOG_E(TAG, "Failed to load animation bubbles"); | ||||||
|             animation_storage_free_bubbles(animation); |             animation_storage_free_bubbles(animation); | ||||||
|         } |         } | ||||||
| @ -402,7 +442,7 @@ static BubbleAnimation* animation_storage_load_animation(const char* name) { | |||||||
|     flipper_file_set_strict_mode(ff, true); |     flipper_file_set_strict_mode(ff, true); | ||||||
|     string_t str; |     string_t str; | ||||||
|     string_init(str); |     string_init(str); | ||||||
|     animation->frame_bubbles = NULL; |     animation->frame_bubble_sequences = NULL; | ||||||
| 
 | 
 | ||||||
|     bool success = false; |     bool success = false; | ||||||
|     do { |     do { | ||||||
| @ -424,8 +464,18 @@ static BubbleAnimation* animation_storage_load_animation(const char* name) { | |||||||
|         animation->active_frames = u32value; |         animation->active_frames = u32value; | ||||||
| 
 | 
 | ||||||
|         uint8_t frames = animation->passive_frames + animation->active_frames; |         uint8_t frames = animation->passive_frames + animation->active_frames; | ||||||
|  |         uint32_t count = 0; | ||||||
|  |         if(!flipper_file_get_value_count(ff, "Frames order", &count)) break; | ||||||
|  |         if(count != frames) { | ||||||
|  |             FURI_LOG_E(TAG, "Error loading animation: frames order"); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|         u32array = furi_alloc(sizeof(uint32_t) * frames); |         u32array = furi_alloc(sizeof(uint32_t) * frames); | ||||||
|         if(!flipper_file_read_uint32(ff, "Frames order", u32array, frames)) break; |         if(!flipper_file_read_uint32(ff, "Frames order", u32array, frames)) break; | ||||||
|  |         animation->frame_order = furi_alloc(sizeof(uint8_t) * frames); | ||||||
|  |         for(int i = 0; i < frames; ++i) { | ||||||
|  |             FURI_CONST_ASSIGN(animation->frame_order[i], u32array[i]); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         /* passive and active frames must be loaded up to this point */ |         /* passive and active frames must be loaded up to this point */ | ||||||
|         if(!animation_storage_load_frames(storage, name, animation, u32array, width, height)) |         if(!animation_storage_load_frames(storage, name, animation, u32array, width, height)) | ||||||
| @ -434,7 +484,7 @@ static BubbleAnimation* animation_storage_load_animation(const char* name) { | |||||||
|         if(!flipper_file_read_uint32(ff, "Active cycles", &u32value, 1)) break; |         if(!flipper_file_read_uint32(ff, "Active cycles", &u32value, 1)) break; | ||||||
|         animation->active_cycles = u32value; |         animation->active_cycles = u32value; | ||||||
|         if(!flipper_file_read_uint32(ff, "Frame rate", &u32value, 1)) break; |         if(!flipper_file_read_uint32(ff, "Frame rate", &u32value, 1)) break; | ||||||
|         animation->frame_rate = u32value; |         FURI_CONST_ASSIGN(animation->icon_animation.frame_rate, u32value); | ||||||
|         if(!flipper_file_read_uint32(ff, "Duration", &u32value, 1)) break; |         if(!flipper_file_read_uint32(ff, "Duration", &u32value, 1)) break; | ||||||
|         animation->duration = u32value; |         animation->duration = u32value; | ||||||
|         if(!flipper_file_read_uint32(ff, "Active cooldown", &u32value, 1)) break; |         if(!flipper_file_read_uint32(ff, "Active cooldown", &u32value, 1)) break; | ||||||
| @ -452,6 +502,9 @@ static BubbleAnimation* animation_storage_load_animation(const char* name) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(!success) { |     if(!success) { | ||||||
|  |         if(animation->frame_order) { | ||||||
|  |             free((void*)animation->frame_order); | ||||||
|  |         } | ||||||
|         free(animation); |         free(animation); | ||||||
|         animation = NULL; |         animation = NULL; | ||||||
|     } |     } | ||||||
| @ -460,10 +513,10 @@ static BubbleAnimation* animation_storage_load_animation(const char* name) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void animation_storage_free_bubbles(BubbleAnimation* animation) { | static void animation_storage_free_bubbles(BubbleAnimation* animation) { | ||||||
|     if(!animation->frame_bubbles) return; |     if(!animation->frame_bubble_sequences) return; | ||||||
| 
 | 
 | ||||||
|     for(int i = 0; i < animation->frame_bubbles_count;) { |     for(int i = 0; i < animation->frame_bubble_sequences_count;) { | ||||||
|         FrameBubble** bubble = &animation->frame_bubbles[i]; |         const FrameBubble* const* bubble = &animation->frame_bubble_sequences[i]; | ||||||
| 
 | 
 | ||||||
|         if((*bubble) == NULL) break; |         if((*bubble) == NULL) break; | ||||||
| 
 | 
 | ||||||
| @ -471,15 +524,15 @@ static void animation_storage_free_bubbles(BubbleAnimation* animation) { | |||||||
|             bubble = &(*bubble)->next_bubble; |             bubble = &(*bubble)->next_bubble; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if((*bubble)->bubble.str) { |         if((*bubble)->bubble.text) { | ||||||
|             free((void*)(*bubble)->bubble.str); |             free((void*)(*bubble)->bubble.text); | ||||||
|         } |         } | ||||||
|         if((*bubble) == animation->frame_bubbles[i]) { |         if((*bubble) == animation->frame_bubble_sequences[i]) { | ||||||
|             ++i; |             ++i; | ||||||
|         } |         } | ||||||
|         free(*bubble); |         free((void*)*bubble); | ||||||
|         *bubble = NULL; |         FURI_CONST_ASSIGN_PTR(*bubble, NULL); | ||||||
|     } |     } | ||||||
|     free(animation->frame_bubbles); |     free((void*)animation->frame_bubble_sequences); | ||||||
|     animation->frame_bubbles = NULL; |     animation->frame_bubble_sequences = NULL; | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,15 +4,6 @@ | |||||||
| #include "views/bubble_animation_view.h" | #include "views/bubble_animation_view.h" | ||||||
| #include <m-string.h> | #include <m-string.h> | ||||||
| 
 | 
 | ||||||
| #define HARDCODED_ANIMATION_NAME "tv" |  | ||||||
| #define NO_SD_ANIMATION_NAME "no_sd" |  | ||||||
| #define BAD_BATTERY_ANIMATION_NAME "bad_battery" |  | ||||||
| #define NO_DB_ANIMATION_NAME "no_db" |  | ||||||
| #define BAD_SD_ANIMATION_NAME "bad_sd" |  | ||||||
| #define SD_OK_ANIMATION_NAME "sd_ok" |  | ||||||
| #define URL_ANIMATION_NAME "url" |  | ||||||
| #define LEVELUP_ANIMATION_NAME "level" |  | ||||||
| 
 |  | ||||||
| /** Main structure to handle animation data.
 | /** Main structure to handle animation data.
 | ||||||
|  * Contains all, including animation playing data (BubbleAnimation), |  * Contains all, including animation playing data (BubbleAnimation), | ||||||
|  * data for random animation selection (StorageAnimationMeta) and |  * data for random animation selection (StorageAnimationMeta) and | ||||||
| @ -20,13 +11,13 @@ | |||||||
| typedef struct StorageAnimation StorageAnimation; | typedef struct StorageAnimation StorageAnimation; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     string_t name; |     const char* name; | ||||||
|     uint8_t min_butthurt; |     uint8_t min_butthurt; | ||||||
|     uint8_t max_butthurt; |     uint8_t max_butthurt; | ||||||
|     uint8_t min_level; |     uint8_t min_level; | ||||||
|     uint8_t max_level; |     uint8_t max_level; | ||||||
|     uint8_t weight; |     uint8_t weight; | ||||||
| } StorageAnimationMeta; | } StorageAnimationManifestInfo; | ||||||
| 
 | 
 | ||||||
| /** Container to return available animations list */ | /** Container to return available animations list */ | ||||||
| LIST_DEF(StorageAnimationList, StorageAnimation*, M_PTR_OPLIST) | LIST_DEF(StorageAnimationList, StorageAnimation*, M_PTR_OPLIST) | ||||||
| @ -81,7 +72,7 @@ StorageAnimation* animation_storage_find_animation(const char* name); | |||||||
|  * @storage_animation       item of whom we have to extract meta. |  * @storage_animation       item of whom we have to extract meta. | ||||||
|  * @return                  meta itself |  * @return                  meta itself | ||||||
|  */ |  */ | ||||||
| StorageAnimationMeta* animation_storage_get_meta(StorageAnimation* storage_animation); | StorageAnimationManifestInfo* animation_storage_get_meta(StorageAnimation* storage_animation); | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Free storage_animation, which previously acquired |  * Free storage_animation, which previously acquired | ||||||
|  | |||||||
| @ -1,195 +1,9 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include "animation_storage.h" | #include "animation_storage.h" | ||||||
| #include "assets_icons.h" |  | ||||||
| #include "animation_manager.h" | #include "animation_manager.h" | ||||||
| #include "gui/canvas.h" |  | ||||||
| 
 | 
 | ||||||
| struct StorageAnimation { | struct StorageAnimation { | ||||||
|     const BubbleAnimation* animation; |     const BubbleAnimation* animation; | ||||||
|     bool external; |     bool external; | ||||||
|     StorageAnimationMeta meta; |     StorageAnimationManifestInfo manifest_info; | ||||||
| }; | }; | ||||||
| 
 |  | ||||||
| // Hard-coded, always available idle animation
 |  | ||||||
| FrameBubble tv_bubble1 = { |  | ||||||
|     .bubble = |  | ||||||
|         {.x = 1, |  | ||||||
|          .y = 23, |  | ||||||
|          .str = "Take the red pill", |  | ||||||
|          .horizontal = AlignRight, |  | ||||||
|          .vertical = AlignBottom}, |  | ||||||
|     .starts_at_frame = 7, |  | ||||||
|     .ends_at_frame = 9, |  | ||||||
|     .next_bubble = NULL, |  | ||||||
| }; |  | ||||||
| FrameBubble tv_bubble2 = { |  | ||||||
|     .bubble = |  | ||||||
|         {.x = 1, |  | ||||||
|          .y = 23, |  | ||||||
|          .str = "I can joke better", |  | ||||||
|          .horizontal = AlignRight, |  | ||||||
|          .vertical = AlignBottom}, |  | ||||||
|     .starts_at_frame = 7, |  | ||||||
|     .ends_at_frame = 9, |  | ||||||
|     .next_bubble = NULL, |  | ||||||
| }; |  | ||||||
| FrameBubble* tv_bubbles[] = {&tv_bubble1, &tv_bubble2}; |  | ||||||
| const Icon* tv_icons[] = { |  | ||||||
|     &I_tv1, |  | ||||||
|     &I_tv2, |  | ||||||
|     &I_tv3, |  | ||||||
|     &I_tv4, |  | ||||||
|     &I_tv5, |  | ||||||
|     &I_tv6, |  | ||||||
|     &I_tv7, |  | ||||||
|     &I_tv8, |  | ||||||
| }; |  | ||||||
| const BubbleAnimation tv_bubble_animation = { |  | ||||||
|     .icons = tv_icons, |  | ||||||
|     .frame_bubbles = tv_bubbles, |  | ||||||
|     .frame_bubbles_count = COUNT_OF(tv_bubbles), |  | ||||||
|     .passive_frames = 6, |  | ||||||
|     .active_frames = 2, |  | ||||||
|     .active_cycles = 2, |  | ||||||
|     .frame_rate = 2, |  | ||||||
|     .duration = 3600, |  | ||||||
|     .active_cooldown = 5, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // System animation - no SD card
 |  | ||||||
| const Icon* no_sd_icons[] = { |  | ||||||
|     &I_no_sd1, |  | ||||||
|     &I_no_sd2, |  | ||||||
|     &I_no_sd1, |  | ||||||
|     &I_no_sd2, |  | ||||||
|     &I_no_sd1, |  | ||||||
|     &I_no_sd3, |  | ||||||
|     &I_no_sd4, |  | ||||||
|     &I_no_sd5, |  | ||||||
|     &I_no_sd4, |  | ||||||
|     &I_no_sd6, |  | ||||||
| }; |  | ||||||
| FrameBubble no_sd_bubble = { |  | ||||||
|     .bubble = |  | ||||||
|         {.x = 40, |  | ||||||
|          .y = 18, |  | ||||||
|          .str = "Need an\nSD card", |  | ||||||
|          .horizontal = AlignRight, |  | ||||||
|          .vertical = AlignBottom}, |  | ||||||
|     .starts_at_frame = 0, |  | ||||||
|     .ends_at_frame = 9, |  | ||||||
|     .next_bubble = NULL, |  | ||||||
| }; |  | ||||||
| FrameBubble* no_sd_bubbles[] = {&no_sd_bubble}; |  | ||||||
| const BubbleAnimation no_sd_bubble_animation = { |  | ||||||
|     .icons = no_sd_icons, |  | ||||||
|     .frame_bubbles = no_sd_bubbles, |  | ||||||
|     .frame_bubbles_count = COUNT_OF(no_sd_bubbles), |  | ||||||
|     .passive_frames = 10, |  | ||||||
|     .active_frames = 0, |  | ||||||
|     .frame_rate = 2, |  | ||||||
|     .duration = 3600, |  | ||||||
|     .active_cooldown = 0, |  | ||||||
|     .active_cycles = 0, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // BLOCKING ANIMATION - no_db, bad_sd, sd_ok, url
 |  | ||||||
| const Icon* no_db_icons[] = { |  | ||||||
|     &I_no_databases1, |  | ||||||
|     &I_no_databases2, |  | ||||||
|     &I_no_databases3, |  | ||||||
|     &I_no_databases4, |  | ||||||
| }; |  | ||||||
| const BubbleAnimation no_db_bubble_animation = { |  | ||||||
|     .icons = no_db_icons, |  | ||||||
|     .passive_frames = COUNT_OF(no_db_icons), |  | ||||||
|     .frame_rate = 2, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const Icon* bad_sd_icons[] = { |  | ||||||
|     &I_card_bad1, |  | ||||||
|     &I_card_bad2, |  | ||||||
| }; |  | ||||||
| const BubbleAnimation bad_sd_bubble_animation = { |  | ||||||
|     .icons = bad_sd_icons, |  | ||||||
|     .passive_frames = COUNT_OF(bad_sd_icons), |  | ||||||
|     .frame_rate = 2, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const Icon* url_icons[] = { |  | ||||||
|     &I_url1, |  | ||||||
|     &I_url2, |  | ||||||
|     &I_url3, |  | ||||||
|     &I_url4, |  | ||||||
| }; |  | ||||||
| const BubbleAnimation url_bubble_animation = { |  | ||||||
|     .icons = url_icons, |  | ||||||
|     .passive_frames = COUNT_OF(url_icons), |  | ||||||
|     .frame_rate = 2, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const Icon* sd_ok_icons[] = { |  | ||||||
|     &I_card_ok1, |  | ||||||
|     &I_card_ok2, |  | ||||||
|     &I_card_ok3, |  | ||||||
|     &I_card_ok4, |  | ||||||
| }; |  | ||||||
| const BubbleAnimation sd_ok_bubble_animation = { |  | ||||||
|     .icons = sd_ok_icons, |  | ||||||
|     .passive_frames = COUNT_OF(sd_ok_icons), |  | ||||||
|     .frame_rate = 2, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static StorageAnimation StorageAnimationInternal[] = { |  | ||||||
|     {.animation = &tv_bubble_animation, |  | ||||||
|      .external = false, |  | ||||||
|      .meta = |  | ||||||
|          { |  | ||||||
|              .min_butthurt = 0, |  | ||||||
|              .max_butthurt = 11, |  | ||||||
|              .min_level = 1, |  | ||||||
|              .max_level = 3, |  | ||||||
|              .weight = 3, |  | ||||||
|          }}, |  | ||||||
|     {.animation = &no_sd_bubble_animation, |  | ||||||
|      .external = false, |  | ||||||
|      .meta = |  | ||||||
|          { |  | ||||||
|              .min_butthurt = 0, |  | ||||||
|              .max_butthurt = 14, |  | ||||||
|              .min_level = 1, |  | ||||||
|              .max_level = 3, |  | ||||||
|              .weight = 6, |  | ||||||
|          }}, |  | ||||||
|     { |  | ||||||
|         .animation = &no_db_bubble_animation, |  | ||||||
|         .external = false, |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         .animation = &bad_sd_bubble_animation, |  | ||||||
|         .external = false, |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         .animation = &sd_ok_bubble_animation, |  | ||||||
|         .external = false, |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|         .animation = &url_bubble_animation, |  | ||||||
|         .external = false, |  | ||||||
|     }, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| void animation_storage_initialize_internal_animations(void) { |  | ||||||
|     /* not in constructor - no memory pool yet */ |  | ||||||
|     /* called in 1 thread - no need in double check */ |  | ||||||
|     static bool initialized = false; |  | ||||||
|     if(!initialized) { |  | ||||||
|         initialized = true; |  | ||||||
|         string_init_set_str(StorageAnimationInternal[0].meta.name, HARDCODED_ANIMATION_NAME); |  | ||||||
|         string_init_set_str(StorageAnimationInternal[1].meta.name, NO_SD_ANIMATION_NAME); |  | ||||||
|         string_init_set_str(StorageAnimationInternal[2].meta.name, NO_DB_ANIMATION_NAME); |  | ||||||
|         string_init_set_str(StorageAnimationInternal[3].meta.name, BAD_SD_ANIMATION_NAME); |  | ||||||
|         string_init_set_str(StorageAnimationInternal[4].meta.name, SD_OK_ANIMATION_NAME); |  | ||||||
|         string_init_set_str(StorageAnimationInternal[5].meta.name, URL_ANIMATION_NAME); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -1,23 +1,19 @@ | |||||||
| 
 | 
 | ||||||
| #include "cmsis_os2.h" |  | ||||||
| #include "../animation_manager.h" | #include "../animation_manager.h" | ||||||
| #include "../animation_storage.h" | #include "../animation_storage.h" | ||||||
| #include "furi_hal_delay.h" | #include "bubble_animation_view.h" | ||||||
| #include "furi_hal_resources.h" | 
 | ||||||
| #include "furi/check.h" | #include <furi_hal.h> | ||||||
| #include "furi/memmgr.h" |  | ||||||
| #include "gui/canvas.h" |  | ||||||
| #include "gui/elements.h" |  | ||||||
| #include "gui/view.h" |  | ||||||
| #include "input/input.h" |  | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include "portmacro.h" | #include <gui/canvas.h> | ||||||
| #include <gui/icon.h> | #include <gui/elements.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <gui/icon_i.h> | ||||||
|  | #include <input/input.h> | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <FreeRTOS.h> | #include <FreeRTOS.h> | ||||||
| #include <timers.h> | #include <timers.h> | ||||||
| #include "bubble_animation_view.h" | #include <furi/dangerous_defines.h> | ||||||
| #include <gui/icon_i.h> |  | ||||||
| 
 | 
 | ||||||
| #define ACTIVE_SHIFT 2 | #define ACTIVE_SHIFT 2 | ||||||
| 
 | 
 | ||||||
| @ -43,7 +39,7 @@ struct BubbleAnimationView { | |||||||
| static void bubble_animation_activate(BubbleAnimationView* view, bool force); | static void bubble_animation_activate(BubbleAnimationView* view, bool force); | ||||||
| static void bubble_animation_activate_right_now(BubbleAnimationView* view); | static void bubble_animation_activate_right_now(BubbleAnimationView* view); | ||||||
| 
 | 
 | ||||||
| static uint8_t bubble_animation_get_icon_index(BubbleAnimationViewModel* model) { | static uint8_t bubble_animation_get_frame_index(BubbleAnimationViewModel* model) { | ||||||
|     furi_assert(model); |     furi_assert(model); | ||||||
|     uint8_t icon_index = 0; |     uint8_t icon_index = 0; | ||||||
|     const BubbleAnimation* animation = model->current; |     const BubbleAnimation* animation = model->current; | ||||||
| @ -57,7 +53,7 @@ static uint8_t bubble_animation_get_icon_index(BubbleAnimationViewModel* model) | |||||||
|     } |     } | ||||||
|     furi_assert(icon_index < (animation->passive_frames + animation->active_frames)); |     furi_assert(icon_index < (animation->passive_frames + animation->active_frames)); | ||||||
| 
 | 
 | ||||||
|     return icon_index; |     return animation->frame_order[icon_index]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void bubble_animation_draw_callback(Canvas* canvas, void* model_) { | static void bubble_animation_draw_callback(Canvas* canvas, void* model_) { | ||||||
| @ -79,23 +75,26 @@ static void bubble_animation_draw_callback(Canvas* canvas, void* model_) { | |||||||
| 
 | 
 | ||||||
|     furi_assert(model->current_frame < 255); |     furi_assert(model->current_frame < 255); | ||||||
| 
 | 
 | ||||||
|     const Icon* icon = animation->icons[bubble_animation_get_icon_index(model)]; |     uint8_t index = bubble_animation_get_frame_index(model); | ||||||
|     furi_assert(icon); |     uint8_t width = icon_get_width(&animation->icon_animation); | ||||||
|     uint8_t y_offset = canvas_height(canvas) - icon_get_height(icon); |     uint8_t height = icon_get_height(&animation->icon_animation); | ||||||
|     canvas_draw_icon(canvas, 0, y_offset, icon); |     uint8_t y_offset = canvas_height(canvas) - height; | ||||||
|  |     canvas_draw_bitmap( | ||||||
|  |         canvas, 0, y_offset, width, height, animation->icon_animation.frames[index]); | ||||||
| 
 | 
 | ||||||
|     const FrameBubble* bubble = model->current_bubble; |     const FrameBubble* bubble = model->current_bubble; | ||||||
|     if(bubble) { |     if(bubble) { | ||||||
|         if((model->current_frame >= bubble->starts_at_frame) && |         if((model->current_frame >= bubble->start_frame) && | ||||||
|            (model->current_frame <= bubble->ends_at_frame)) { |            (model->current_frame <= bubble->end_frame)) { | ||||||
|             const Bubble* b = &bubble->bubble; |             const Bubble* b = &bubble->bubble; | ||||||
|             elements_bubble_str(canvas, b->x, b->y, b->str, b->horizontal, b->vertical); |             elements_bubble_str(canvas, b->x, b->y, b->text, b->align_h, b->align_v); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static FrameBubble* bubble_animation_pick_bubble(BubbleAnimationViewModel* model, bool active) { | static const FrameBubble* | ||||||
|     FrameBubble* bubble = NULL; |     bubble_animation_pick_bubble(BubbleAnimationViewModel* model, bool active) { | ||||||
|  |     const FrameBubble* bubble = NULL; | ||||||
| 
 | 
 | ||||||
|     if((model->active_bubbles == 0) && (model->passive_bubbles == 0)) { |     if((model->active_bubbles == 0) && (model->passive_bubbles == 0)) { | ||||||
|         return NULL; |         return NULL; | ||||||
| @ -104,10 +103,11 @@ static FrameBubble* bubble_animation_pick_bubble(BubbleAnimationViewModel* model | |||||||
|     uint8_t index = random() % (active ? model->active_bubbles : model->passive_bubbles); |     uint8_t index = random() % (active ? model->active_bubbles : model->passive_bubbles); | ||||||
|     const BubbleAnimation* animation = model->current; |     const BubbleAnimation* animation = model->current; | ||||||
| 
 | 
 | ||||||
|     for(int i = 0; i < animation->frame_bubbles_count; ++i) { |     for(int i = 0; i < animation->frame_bubble_sequences_count; ++i) { | ||||||
|         if((animation->frame_bubbles[i]->starts_at_frame < animation->passive_frames) ^ active) { |         if((animation->frame_bubble_sequences[i]->start_frame < animation->passive_frames) ^ | ||||||
|  |            active) { | ||||||
|             if(!index) { |             if(!index) { | ||||||
|                 bubble = animation->frame_bubbles[i]; |                 bubble = animation->frame_bubble_sequences[i]; | ||||||
|             } |             } | ||||||
|             --index; |             --index; | ||||||
|         } |         } | ||||||
| @ -135,10 +135,6 @@ static bool bubble_animation_input_callback(InputEvent* event, void* context) { | |||||||
|                 animation_view->interact_callback(animation_view->interact_callback_context); |                 animation_view->interact_callback(animation_view->interact_callback_context); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } else if(event->key == InputKeyBack) { |  | ||||||
|         /* Prevent back button to fall down to common handler - leaving
 |  | ||||||
|          * application, so consume */ |  | ||||||
|         consumed = true; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return consumed; |     return consumed; | ||||||
| @ -190,7 +186,7 @@ static void bubble_animation_activate_right_now(BubbleAnimationView* view) { | |||||||
|     if(model->current && (model->current->active_frames > 0) && (!model->freeze_frame)) { |     if(model->current && (model->current->active_frames > 0) && (!model->freeze_frame)) { | ||||||
|         model->current_frame = model->current->passive_frames; |         model->current_frame = model->current->passive_frames; | ||||||
|         model->current_bubble = bubble_animation_pick_bubble(model, true); |         model->current_bubble = bubble_animation_pick_bubble(model, true); | ||||||
|         frame_rate = model->current->frame_rate; |         frame_rate = model->current->icon_animation.frame_rate; | ||||||
|     } |     } | ||||||
|     view_commit_model(view->view, true); |     view_commit_model(view->view, true); | ||||||
| 
 | 
 | ||||||
| @ -222,7 +218,7 @@ static void bubble_animation_next_frame(BubbleAnimationViewModel* model) { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if(model->current_bubble) { |         if(model->current_bubble) { | ||||||
|             if(model->current_frame > model->current_bubble->ends_at_frame) { |             if(model->current_frame > model->current_bubble->end_frame) { | ||||||
|                 model->current_bubble = model->current_bubble->next_bubble; |                 model->current_bubble = model->current_bubble->next_bubble; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -251,7 +247,11 @@ static void bubble_animation_timer_callback(void* context) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static Icon* bubble_animation_clone_frame(const Icon* icon_orig) { | /* always freeze first passive frame, because
 | ||||||
|  |  * animation is always activated at unfreezing and played | ||||||
|  |  * passive frame first, and 2 frames after - active | ||||||
|  |  */ | ||||||
|  | static Icon* bubble_animation_clone_first_frame(const Icon* icon_orig) { | ||||||
|     furi_assert(icon_orig); |     furi_assert(icon_orig); | ||||||
|     furi_assert(icon_orig->frames); |     furi_assert(icon_orig->frames); | ||||||
|     furi_assert(icon_orig->frames[0]); |     furi_assert(icon_orig->frames[0]); | ||||||
| @ -268,6 +268,7 @@ static Icon* bubble_animation_clone_frame(const Icon* icon_orig) { | |||||||
|     size_t max_bitmap_size = ROUND_UP_TO(icon_orig->width, 8) * icon_orig->height + 1; |     size_t max_bitmap_size = ROUND_UP_TO(icon_orig->width, 8) * icon_orig->height + 1; | ||||||
|     icon_clone->frames[0] = furi_alloc(max_bitmap_size); |     icon_clone->frames[0] = furi_alloc(max_bitmap_size); | ||||||
|     memcpy((void*)icon_clone->frames[0], icon_orig->frames[0], max_bitmap_size); |     memcpy((void*)icon_clone->frames[0], icon_orig->frames[0], max_bitmap_size); | ||||||
|  |     FURI_CONST_ASSIGN(icon_clone->frame_count, 1); | ||||||
| 
 | 
 | ||||||
|     return icon_clone; |     return icon_clone; | ||||||
| } | } | ||||||
| @ -288,7 +289,7 @@ static void bubble_animation_enter(void* context) { | |||||||
|     bubble_animation_activate(view, false); |     bubble_animation_activate(view, false); | ||||||
| 
 | 
 | ||||||
|     BubbleAnimationViewModel* model = view_get_model(view->view); |     BubbleAnimationViewModel* model = view_get_model(view->view); | ||||||
|     uint8_t frame_rate = model->current->frame_rate; |     uint8_t frame_rate = model->current->icon_animation.frame_rate; | ||||||
|     view_commit_model(view->view, false); |     view_commit_model(view->view, false); | ||||||
| 
 | 
 | ||||||
|     if(frame_rate) { |     if(frame_rate) { | ||||||
| @ -353,8 +354,8 @@ void bubble_animation_view_set_animation( | |||||||
|     model->active_ended_at = xTaskGetTickCount() - (model->current->active_cooldown * 1000); |     model->active_ended_at = xTaskGetTickCount() - (model->current->active_cooldown * 1000); | ||||||
|     model->active_bubbles = 0; |     model->active_bubbles = 0; | ||||||
|     model->passive_bubbles = 0; |     model->passive_bubbles = 0; | ||||||
|     for(int i = 0; i < new_animation->frame_bubbles_count; ++i) { |     for(int i = 0; i < new_animation->frame_bubble_sequences_count; ++i) { | ||||||
|         if(new_animation->frame_bubbles[i]->starts_at_frame < new_animation->passive_frames) { |         if(new_animation->frame_bubble_sequences[i]->start_frame < new_animation->passive_frames) { | ||||||
|             ++model->passive_bubbles; |             ++model->passive_bubbles; | ||||||
|         } else { |         } else { | ||||||
|             ++model->active_bubbles; |             ++model->active_bubbles; | ||||||
| @ -367,7 +368,7 @@ void bubble_animation_view_set_animation( | |||||||
|     model->active_cycle = 0; |     model->active_cycle = 0; | ||||||
|     view_commit_model(view->view, true); |     view_commit_model(view->view, true); | ||||||
| 
 | 
 | ||||||
|     osTimerStart(view->timer, 1000 / new_animation->frame_rate); |     osTimerStart(view->timer, 1000 / new_animation->icon_animation.frame_rate); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void bubble_animation_freeze(BubbleAnimationView* view) { | void bubble_animation_freeze(BubbleAnimationView* view) { | ||||||
| @ -376,12 +377,7 @@ void bubble_animation_freeze(BubbleAnimationView* view) { | |||||||
|     BubbleAnimationViewModel* model = view_get_model(view->view); |     BubbleAnimationViewModel* model = view_get_model(view->view); | ||||||
|     furi_assert(model->current); |     furi_assert(model->current); | ||||||
|     furi_assert(!model->freeze_frame); |     furi_assert(!model->freeze_frame); | ||||||
|     /* always freeze first passive frame, because
 |     model->freeze_frame = bubble_animation_clone_first_frame(&model->current->icon_animation); | ||||||
|      * animation is always activated at unfreezing and played |  | ||||||
|      * passive frame first, and 2 frames after - active |  | ||||||
|      */ |  | ||||||
|     uint8_t icon_index = 0; |  | ||||||
|     model->freeze_frame = bubble_animation_clone_frame(model->current->icons[icon_index]); |  | ||||||
|     model->current = NULL; |     model->current = NULL; | ||||||
|     view_commit_model(view->view, false); |     view_commit_model(view->view, false); | ||||||
|     osTimerStop(view->timer); |     osTimerStop(view->timer); | ||||||
| @ -395,8 +391,7 @@ void bubble_animation_unfreeze(BubbleAnimationView* view) { | |||||||
|     furi_assert(model->freeze_frame); |     furi_assert(model->freeze_frame); | ||||||
|     bubble_animation_release_frame(&model->freeze_frame); |     bubble_animation_release_frame(&model->freeze_frame); | ||||||
|     furi_assert(model->current); |     furi_assert(model->current); | ||||||
|     furi_assert(model->current->icons); |     frame_rate = model->current->icon_animation.frame_rate; | ||||||
|     frame_rate = model->current->frame_rate; |  | ||||||
|     view_commit_model(view->view, true); |     view_commit_model(view->view, true); | ||||||
| 
 | 
 | ||||||
|     osTimerStart(view->timer, 1000 / frame_rate); |     osTimerStart(view->timer, 1000 / frame_rate); | ||||||
|  | |||||||
							
								
								
									
										130
									
								
								applications/desktop/animations/views/one_shot_animation_view.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								applications/desktop/animations/views/one_shot_animation_view.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,130 @@ | |||||||
|  | 
 | ||||||
|  | #include "one_shot_animation_view.h" | ||||||
|  | #include <furi.h> | ||||||
|  | #include <portmacro.h> | ||||||
|  | #include <gui/canvas.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <gui/icon_i.h> | ||||||
|  | #include <stdint.h> | ||||||
|  | 
 | ||||||
|  | typedef void (*OneShotInteractCallback)(void*); | ||||||
|  | 
 | ||||||
|  | struct OneShotView { | ||||||
|  |     View* view; | ||||||
|  |     TimerHandle_t update_timer; | ||||||
|  |     OneShotInteractCallback interact_callback; | ||||||
|  |     void* interact_callback_context; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     const Icon* icon; | ||||||
|  |     uint32_t index; | ||||||
|  |     bool block_input; | ||||||
|  | } OneShotViewModel; | ||||||
|  | 
 | ||||||
|  | static void one_shot_view_update_timer_callback(TimerHandle_t xTimer) { | ||||||
|  |     OneShotView* view = (void*)pvTimerGetTimerID(xTimer); | ||||||
|  | 
 | ||||||
|  |     OneShotViewModel* model = view_get_model(view->view); | ||||||
|  |     if((model->index + 1) < model->icon->frame_count) { | ||||||
|  |         ++model->index; | ||||||
|  |     } else { | ||||||
|  |         model->block_input = false; | ||||||
|  |         model->index = model->icon->frame_count - 2; | ||||||
|  |     } | ||||||
|  |     view_commit_model(view->view, true); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void one_shot_view_draw(Canvas* canvas, void* model_) { | ||||||
|  |     furi_assert(canvas); | ||||||
|  |     furi_assert(model_); | ||||||
|  | 
 | ||||||
|  |     OneShotViewModel* model = model_; | ||||||
|  |     furi_check(model->index < model->icon->frame_count); | ||||||
|  |     uint8_t y_offset = canvas_height(canvas) - model->icon->height; | ||||||
|  |     canvas_draw_bitmap( | ||||||
|  |         canvas, | ||||||
|  |         0, | ||||||
|  |         y_offset, | ||||||
|  |         model->icon->width, | ||||||
|  |         model->icon->height, | ||||||
|  |         model->icon->frames[model->index]); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool one_shot_view_input(InputEvent* event, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     furi_assert(event); | ||||||
|  | 
 | ||||||
|  |     OneShotView* view = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     OneShotViewModel* model = view_get_model(view->view); | ||||||
|  |     consumed = model->block_input; | ||||||
|  |     view_commit_model(view->view, false); | ||||||
|  | 
 | ||||||
|  |     if(!consumed) { | ||||||
|  |         if(event->key == InputKeyRight) { | ||||||
|  |             /* Right button reserved for animation activation, so consume */ | ||||||
|  |             consumed = true; | ||||||
|  |             if(event->type == InputTypeShort) { | ||||||
|  |                 if(view->interact_callback) { | ||||||
|  |                     view->interact_callback(view->interact_callback_context); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | OneShotView* one_shot_view_alloc(void) { | ||||||
|  |     OneShotView* view = furi_alloc(sizeof(OneShotView)); | ||||||
|  |     view->view = view_alloc(); | ||||||
|  |     view->update_timer = | ||||||
|  |         xTimerCreate("Update timer", 1000, pdTRUE, view, one_shot_view_update_timer_callback); | ||||||
|  | 
 | ||||||
|  |     view_allocate_model(view->view, ViewModelTypeLocking, sizeof(OneShotViewModel)); | ||||||
|  |     view_set_context(view->view, view); | ||||||
|  |     view_set_draw_callback(view->view, one_shot_view_draw); | ||||||
|  |     view_set_input_callback(view->view, one_shot_view_input); | ||||||
|  | 
 | ||||||
|  |     return view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void one_shot_view_free(OneShotView* view) { | ||||||
|  |     furi_assert(view); | ||||||
|  | 
 | ||||||
|  |     xTimerDelete(view->update_timer, portMAX_DELAY); | ||||||
|  |     view_free(view->view); | ||||||
|  |     view->view = NULL; | ||||||
|  |     free(view); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void one_shot_view_set_interact_callback( | ||||||
|  |     OneShotView* view, | ||||||
|  |     OneShotInteractCallback callback, | ||||||
|  |     void* context) { | ||||||
|  |     furi_assert(view); | ||||||
|  | 
 | ||||||
|  |     view->interact_callback_context = context; | ||||||
|  |     view->interact_callback = callback; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void one_shot_view_start_animation(OneShotView* view, const Icon* icon) { | ||||||
|  |     furi_assert(view); | ||||||
|  |     furi_assert(icon); | ||||||
|  |     furi_check(icon->frame_count >= 2); | ||||||
|  | 
 | ||||||
|  |     OneShotViewModel* model = view_get_model(view->view); | ||||||
|  |     model->index = 0; | ||||||
|  |     model->icon = icon; | ||||||
|  |     model->block_input = true; | ||||||
|  |     view_commit_model(view->view, true); | ||||||
|  |     xTimerChangePeriod(view->update_timer, 1000 / model->icon->frame_rate, portMAX_DELAY); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | View* one_shot_view_get_view(OneShotView* view) { | ||||||
|  |     furi_assert(view); | ||||||
|  | 
 | ||||||
|  |     return view->view; | ||||||
|  | } | ||||||
| @ -0,0 +1,17 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <furi.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <stdint.h> | ||||||
|  | 
 | ||||||
|  | typedef void (*OneShotInteractCallback)(void*); | ||||||
|  | typedef struct OneShotView OneShotView; | ||||||
|  | 
 | ||||||
|  | OneShotView* one_shot_view_alloc(void); | ||||||
|  | void one_shot_view_free(OneShotView* view); | ||||||
|  | void one_shot_view_set_interact_callback( | ||||||
|  |     OneShotView* view, | ||||||
|  |     OneShotInteractCallback callback, | ||||||
|  |     void* context); | ||||||
|  | void one_shot_view_start_animation(OneShotView* view, const Icon* icon); | ||||||
|  | View* one_shot_view_get_view(OneShotView* view); | ||||||
| @ -1,17 +1,16 @@ | |||||||
| #include "assets_icons.h" |  | ||||||
| #include "cmsis_os2.h" |  | ||||||
| #include "desktop/desktop.h" |  | ||||||
| #include "desktop_i.h" |  | ||||||
| #include "gui/view_composed.h" |  | ||||||
| #include <dolphin/dolphin.h> |  | ||||||
| #include <furi/pubsub.h> |  | ||||||
| #include <furi/record.h> |  | ||||||
| #include "portmacro.h" |  | ||||||
| #include "storage/filesystem_api_defines.h" |  | ||||||
| #include "storage/storage.h" |  | ||||||
| #include <stdint.h> |  | ||||||
| #include <power/power_service/power.h> |  | ||||||
| #include "animations/animation_manager.h" | #include "animations/animation_manager.h" | ||||||
|  | #include "desktop/scenes/desktop_scene.h" | ||||||
|  | #include "desktop/scenes/desktop_scene_i.h" | ||||||
|  | #include "desktop/views/desktop_locked.h" | ||||||
|  | #include "desktop_i.h" | ||||||
|  | 
 | ||||||
|  | #include <storage/storage.h> | ||||||
|  | #include <assets_icons.h> | ||||||
|  | #include <gui/view_stack.h> | ||||||
|  | #include <furi.h> | ||||||
|  | #include <furi_hal.h> | ||||||
|  | #include <portmacro.h> | ||||||
|  | #include <stdint.h> | ||||||
| 
 | 
 | ||||||
| static void desktop_lock_icon_callback(Canvas* canvas, void* context) { | static void desktop_lock_icon_callback(Canvas* canvas, void* context) { | ||||||
|     furi_assert(canvas); |     furi_assert(canvas); | ||||||
| @ -50,44 +49,27 @@ Desktop* desktop_alloc() { | |||||||
|     view_dispatcher_set_navigation_event_callback( |     view_dispatcher_set_navigation_event_callback( | ||||||
|         desktop->view_dispatcher, desktop_back_event_callback); |         desktop->view_dispatcher, desktop_back_event_callback); | ||||||
| 
 | 
 | ||||||
|     desktop->dolphin_view = animation_manager_get_animation_view(desktop->animation_manager); |  | ||||||
| 
 |  | ||||||
|     desktop->main_view_composed = view_composed_alloc(); |  | ||||||
|     desktop->main_view = desktop_main_alloc(); |  | ||||||
|     view_composed_tie_views( |  | ||||||
|         desktop->main_view_composed, |  | ||||||
|         desktop->dolphin_view, |  | ||||||
|         desktop_main_get_view(desktop->main_view)); |  | ||||||
|     view_composed_top_enable(desktop->main_view_composed, true); |  | ||||||
| 
 |  | ||||||
|     desktop->locked_view_composed = view_composed_alloc(); |  | ||||||
|     desktop->locked_view = desktop_locked_alloc(); |     desktop->locked_view = desktop_locked_alloc(); | ||||||
|     view_composed_tie_views( |  | ||||||
|         desktop->locked_view_composed, |  | ||||||
|         desktop->dolphin_view, |  | ||||||
|         desktop_locked_get_view(desktop->locked_view)); |  | ||||||
|     view_composed_top_enable(desktop->locked_view_composed, true); |  | ||||||
| 
 |  | ||||||
|     desktop->lock_menu = desktop_lock_menu_alloc(); |     desktop->lock_menu = desktop_lock_menu_alloc(); | ||||||
|     desktop->debug_view = desktop_debug_alloc(); |     desktop->debug_view = desktop_debug_alloc(); | ||||||
|     desktop->first_start_view = desktop_first_start_alloc(); |     desktop->first_start_view = desktop_first_start_alloc(); | ||||||
|     desktop->hw_mismatch_popup = popup_alloc(); |     desktop->hw_mismatch_popup = popup_alloc(); | ||||||
|     desktop->code_input = code_input_alloc(); |     desktop->code_input = code_input_alloc(); | ||||||
|  |     desktop->main_view_stack = view_stack_alloc(); | ||||||
|  |     desktop->main_view = desktop_main_alloc(); | ||||||
|  |     View* dolphin_view = animation_manager_get_animation_view(desktop->animation_manager); | ||||||
|  |     view_stack_add_view(desktop->main_view_stack, desktop_main_get_view(desktop->main_view)); | ||||||
|  |     view_stack_add_view(desktop->main_view_stack, dolphin_view); | ||||||
|  |     view_stack_add_view(desktop->main_view_stack, desktop_locked_get_view(desktop->locked_view)); | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         desktop->view_dispatcher, |         desktop->view_dispatcher, DesktopViewMain, view_stack_get_view(desktop->main_view_stack)); | ||||||
|         DesktopViewMain, |  | ||||||
|         view_composed_get_view(desktop->main_view_composed)); |  | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         desktop->view_dispatcher, |         desktop->view_dispatcher, | ||||||
|         DesktopViewLockMenu, |         DesktopViewLockMenu, | ||||||
|         desktop_lock_menu_get_view(desktop->lock_menu)); |         desktop_lock_menu_get_view(desktop->lock_menu)); | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         desktop->view_dispatcher, DesktopViewDebug, desktop_debug_get_view(desktop->debug_view)); |         desktop->view_dispatcher, DesktopViewDebug, desktop_debug_get_view(desktop->debug_view)); | ||||||
|     view_dispatcher_add_view( |  | ||||||
|         desktop->view_dispatcher, |  | ||||||
|         DesktopViewLocked, |  | ||||||
|         view_composed_get_view(desktop->locked_view_composed)); |  | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         desktop->view_dispatcher, |         desktop->view_dispatcher, | ||||||
|         DesktopViewFirstStart, |         DesktopViewFirstStart, | ||||||
| @ -123,8 +105,8 @@ void desktop_free(Desktop* desktop) { | |||||||
|     scene_manager_free(desktop->scene_manager); |     scene_manager_free(desktop->scene_manager); | ||||||
| 
 | 
 | ||||||
|     animation_manager_free(desktop->animation_manager); |     animation_manager_free(desktop->animation_manager); | ||||||
|     view_composed_free(desktop->main_view_composed); |     view_stack_free(desktop->main_view_stack); | ||||||
|     view_composed_free(desktop->locked_view_composed); |     view_stack_free(desktop->locked_view_stack); | ||||||
|     desktop_main_free(desktop->main_view); |     desktop_main_free(desktop->main_view); | ||||||
|     desktop_lock_menu_free(desktop->lock_menu); |     desktop_lock_menu_free(desktop->lock_menu); | ||||||
|     desktop_locked_free(desktop->locked_view); |     desktop_locked_free(desktop->locked_view); | ||||||
| @ -163,15 +145,14 @@ int32_t desktop_srv(void* p) { | |||||||
|         SAVE_DESKTOP_SETTINGS(&desktop->settings); |         SAVE_DESKTOP_SETTINGS(&desktop->settings); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); |  | ||||||
| 
 |  | ||||||
|     if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) { |     if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) { | ||||||
|         furi_hal_usb_disable(); |         furi_hal_usb_disable(); | ||||||
|         scene_manager_set_scene_state( |         scene_manager_set_scene_state( | ||||||
|             desktop->scene_manager, DesktopSceneLocked, DesktopLockedWithPin); |             desktop->scene_manager, DesktopSceneMain, DesktopMainSceneStateLockedWithPin); | ||||||
|         scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); | ||||||
|  | 
 | ||||||
|     if(desktop_is_first_start()) { |     if(desktop_is_first_start()) { | ||||||
|         scene_manager_next_scene(desktop->scene_manager, DesktopSceneFirstStart); |         scene_manager_next_scene(desktop->scene_manager, DesktopSceneFirstStart); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,31 +1,21 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "cmsis_os2.h" |  | ||||||
| #include "desktop.h" | #include "desktop.h" | ||||||
| 
 |  | ||||||
| #include "animations/animation_manager.h" | #include "animations/animation_manager.h" | ||||||
| #include "gui/view_composed.h" |  | ||||||
| #include <furi.h> |  | ||||||
| #include <furi_hal.h> |  | ||||||
| 
 |  | ||||||
| #include <gui/gui.h> |  | ||||||
| #include <gui/view_dispatcher.h> |  | ||||||
| #include <gui/modules/popup.h> |  | ||||||
| #include <gui/modules/code_input.h> |  | ||||||
| #include <gui/scene_manager.h> |  | ||||||
| #include <assets_icons.h> |  | ||||||
| #include <storage/storage.h> |  | ||||||
| #include <power/power_service/power.h> |  | ||||||
| 
 |  | ||||||
| #include "views/desktop_main.h" | #include "views/desktop_main.h" | ||||||
| #include "views/desktop_first_start.h" | #include "views/desktop_first_start.h" | ||||||
| #include "views/desktop_lock_menu.h" | #include "views/desktop_lock_menu.h" | ||||||
| #include "views/desktop_locked.h" | #include "views/desktop_locked.h" | ||||||
| #include "views/desktop_debug.h" | #include "views/desktop_debug.h" | ||||||
| 
 |  | ||||||
| #include "scenes/desktop_scene.h" |  | ||||||
| #include "desktop/desktop_settings/desktop_settings.h" | #include "desktop/desktop_settings/desktop_settings.h" | ||||||
| #include <gui/icon.h> | 
 | ||||||
|  | #include <furi.h> | ||||||
|  | #include <gui/gui.h> | ||||||
|  | #include <gui/view_stack.h> | ||||||
|  | #include <gui/view_dispatcher.h> | ||||||
|  | #include <gui/modules/popup.h> | ||||||
|  | #include <gui/modules/code_input.h> | ||||||
|  | #include <gui/scene_manager.h> | ||||||
| 
 | 
 | ||||||
| #define STATUS_BAR_Y_SHIFT 13 | #define STATUS_BAR_Y_SHIFT 13 | ||||||
| 
 | 
 | ||||||
| @ -54,12 +44,11 @@ struct Desktop { | |||||||
|     DesktopDebugView* debug_view; |     DesktopDebugView* debug_view; | ||||||
|     CodeInput* code_input; |     CodeInput* code_input; | ||||||
| 
 | 
 | ||||||
|     View* dolphin_view; |  | ||||||
|     DesktopMainView* main_view; |     DesktopMainView* main_view; | ||||||
|     DesktopLockedView* locked_view; |     DesktopLockedView* locked_view; | ||||||
| 
 | 
 | ||||||
|     ViewComposed* main_view_composed; |     ViewStack* main_view_stack; | ||||||
|     ViewComposed* locked_view_composed; |     ViewStack* locked_view_stack; | ||||||
| 
 | 
 | ||||||
|     DesktopSettings settings; |     DesktopSettings settings; | ||||||
|     PinCode pincode_buffer; |     PinCode pincode_buffer; | ||||||
| @ -69,8 +58,6 @@ struct Desktop { | |||||||
|     AnimationManager* animation_manager; |     AnimationManager* animation_manager; | ||||||
|     osSemaphoreId_t unload_animation_semaphore; |     osSemaphoreId_t unload_animation_semaphore; | ||||||
|     FuriPubSubSubscription* app_start_stop_subscription; |     FuriPubSubSubscription* app_start_stop_subscription; | ||||||
| 
 |  | ||||||
|     char* text_buffer; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| Desktop* desktop_alloc(); | Desktop* desktop_alloc(); | ||||||
|  | |||||||
| @ -1,4 +1,6 @@ | |||||||
| #include "desktop_settings_app.h" | #include "desktop_settings_app.h" | ||||||
|  | #include <furi.h> | ||||||
|  | #include "scenes/desktop_settings_scene.h" | ||||||
| 
 | 
 | ||||||
| static bool desktop_settings_custom_event_callback(void* context, uint32_t event) { | static bool desktop_settings_custom_event_callback(void* context, uint32_t event) { | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <furi.h> |  | ||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| #include <gui/view.h> |  | ||||||
| #include <gui/view_dispatcher.h> | #include <gui/view_dispatcher.h> | ||||||
| #include <gui/scene_manager.h> | #include <gui/scene_manager.h> | ||||||
| #include <gui/modules/submenu.h> | #include <gui/modules/submenu.h> | ||||||
| @ -10,8 +8,6 @@ | |||||||
| 
 | 
 | ||||||
| #include "desktop_settings.h" | #include "desktop_settings.h" | ||||||
| 
 | 
 | ||||||
| #include "scenes/desktop_settings_scene.h" |  | ||||||
| 
 |  | ||||||
| typedef enum { | typedef enum { | ||||||
|     CodeEventsSetPin, |     CodeEventsSetPin, | ||||||
|     CodeEventsChangePin, |     CodeEventsChangePin, | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| #include "../desktop_settings_app.h" | #include "../desktop_settings_app.h" | ||||||
| #include "applications.h" | #include "applications.h" | ||||||
| #include "desktop/desktop_settings/desktop_settings.h" | #include "desktop_settings_scene.h" | ||||||
| 
 | 
 | ||||||
| static void desktop_settings_scene_favorite_submenu_callback(void* context, uint32_t index) { | static void desktop_settings_scene_favorite_submenu_callback(void* context, uint32_t index) { | ||||||
|     DesktopSettingsApp* app = context; |     DesktopSettingsApp* app = context; | ||||||
| @ -10,7 +10,7 @@ static void desktop_settings_scene_favorite_submenu_callback(void* context, uint | |||||||
| void desktop_settings_scene_favorite_on_enter(void* context) { | void desktop_settings_scene_favorite_on_enter(void* context) { | ||||||
|     DesktopSettingsApp* app = context; |     DesktopSettingsApp* app = context; | ||||||
|     Submenu* submenu = app->submenu; |     Submenu* submenu = app->submenu; | ||||||
|     submenu_clean(submenu); |     submenu_reset(submenu); | ||||||
| 
 | 
 | ||||||
|     for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { |     for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { | ||||||
|         submenu_add_item( |         submenu_add_item( | ||||||
| @ -45,5 +45,5 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e | |||||||
| void desktop_settings_scene_favorite_on_exit(void* context) { | void desktop_settings_scene_favorite_on_exit(void* context) { | ||||||
|     DesktopSettingsApp* app = context; |     DesktopSettingsApp* app = context; | ||||||
|     SAVE_DESKTOP_SETTINGS(&app->settings); |     SAVE_DESKTOP_SETTINGS(&app->settings); | ||||||
|     submenu_clean(app->submenu); |     submenu_reset(app->submenu); | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| #include "../desktop_settings_app.h" | #include "../desktop_settings_app.h" | ||||||
| #include "desktop/desktop_settings/desktop_settings.h" | #include "desktop_settings_scene.h" | ||||||
| 
 | 
 | ||||||
| #define SCENE_EXIT_EVENT (0U) | #define SCENE_EXIT_EVENT (0U) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| #include "../desktop_settings_app.h" | #include "../desktop_settings_app.h" | ||||||
| #include "applications.h" | #include "applications.h" | ||||||
|  | #include "desktop_settings_scene.h" | ||||||
| 
 | 
 | ||||||
| static void desktop_settings_scene_pincode_menu_submenu_callback(void* context, uint32_t index) { | static void desktop_settings_scene_pincode_menu_submenu_callback(void* context, uint32_t index) { | ||||||
|     DesktopSettingsApp* app = context; |     DesktopSettingsApp* app = context; | ||||||
| @ -9,7 +10,7 @@ static void desktop_settings_scene_pincode_menu_submenu_callback(void* context, | |||||||
| void desktop_settings_scene_pincode_menu_on_enter(void* context) { | void desktop_settings_scene_pincode_menu_on_enter(void* context) { | ||||||
|     DesktopSettingsApp* app = context; |     DesktopSettingsApp* app = context; | ||||||
|     Submenu* submenu = app->submenu; |     Submenu* submenu = app->submenu; | ||||||
|     submenu_clean(submenu); |     submenu_reset(submenu); | ||||||
| 
 | 
 | ||||||
|     if(!app->settings.pincode.length) { |     if(!app->settings.pincode.length) { | ||||||
|         submenu_add_item( |         submenu_add_item( | ||||||
| @ -74,5 +75,5 @@ bool desktop_settings_scene_pincode_menu_on_event(void* context, SceneManagerEve | |||||||
| 
 | 
 | ||||||
| void desktop_settings_scene_pincode_menu_on_exit(void* context) { | void desktop_settings_scene_pincode_menu_on_exit(void* context) { | ||||||
|     DesktopSettingsApp* app = context; |     DesktopSettingsApp* app = context; | ||||||
|     submenu_clean(app->submenu); |     submenu_reset(app->submenu); | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										3
									
								
								applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -1,5 +1,6 @@ | |||||||
| #include "../desktop_settings_app.h" | #include "../desktop_settings_app.h" | ||||||
| #include "applications.h" | #include "applications.h" | ||||||
|  | #include "desktop_settings_scene.h" | ||||||
| 
 | 
 | ||||||
| enum DesktopSettingsStartSubmenuIndex { | enum DesktopSettingsStartSubmenuIndex { | ||||||
|     DesktopSettingsStartSubmenuIndexFavorite, |     DesktopSettingsStartSubmenuIndexFavorite, | ||||||
| @ -53,5 +54,5 @@ bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent even | |||||||
| 
 | 
 | ||||||
| void desktop_settings_scene_start_on_exit(void* context) { | void desktop_settings_scene_start_on_exit(void* context) { | ||||||
|     DesktopSettingsApp* app = context; |     DesktopSettingsApp* app = context; | ||||||
|     submenu_clean(app->submenu); |     submenu_reset(app->submenu); | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| ADD_SCENE(desktop, main, Main) | ADD_SCENE(desktop, main, Main) | ||||||
| ADD_SCENE(desktop, lock_menu, LockMenu) | ADD_SCENE(desktop, lock_menu, LockMenu) | ||||||
| ADD_SCENE(desktop, locked, Locked) |  | ||||||
| ADD_SCENE(desktop, debug, Debug) | ADD_SCENE(desktop, debug, Debug) | ||||||
| ADD_SCENE(desktop, first_start, FirstStart) | ADD_SCENE(desktop, first_start, FirstStart) | ||||||
| ADD_SCENE(desktop, hw_mismatch, HwMismatch) | ADD_SCENE(desktop, hw_mismatch, HwMismatch) | ||||||
|  | |||||||
| @ -1,8 +1,11 @@ | |||||||
| #include "../desktop_i.h" | 
 | ||||||
| #include "../views/desktop_debug.h" |  | ||||||
| #include <dolphin/dolphin.h> | #include <dolphin/dolphin.h> | ||||||
| #include <dolphin/helpers/dolphin_deed.h> | #include <dolphin/helpers/dolphin_deed.h> | ||||||
| 
 | 
 | ||||||
|  | #include "../desktop_i.h" | ||||||
|  | #include "../views/desktop_debug.h" | ||||||
|  | #include "desktop_scene.h" | ||||||
|  | 
 | ||||||
| void desktop_scene_debug_callback(DesktopEvent event, void* context) { | void desktop_scene_debug_callback(DesktopEvent event, void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); |     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||||
| @ -31,13 +34,13 @@ bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) { | |||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         case DesktopDebugEventDeed: |         case DesktopDebugEventDeed: | ||||||
|             dolphin_deed(dolphin, DolphinDeedIButtonEmulate); |             dolphin_deed(dolphin, DolphinDeedIbuttonEmulate); | ||||||
|             desktop_debug_get_dolphin_data(desktop->debug_view); |             desktop_debug_get_dolphin_data(desktop->debug_view); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         case DesktopDebugEventWrongDeed: |         case DesktopDebugEventWrongDeed: | ||||||
|             dolphin_deed(dolphin, DolphinDeedWrong); |             dolphin_deed(dolphin, DolphinDeedIbuttonRead); | ||||||
|             desktop_debug_get_dolphin_data(desktop->debug_view); |             desktop_debug_get_dolphin_data(desktop->debug_view); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | #include <furi_hal.h> | ||||||
|  | 
 | ||||||
| #include "../desktop_i.h" | #include "../desktop_i.h" | ||||||
| 
 | 
 | ||||||
| #define DesktopFaultEventExit 0x00FF00FF | #define DesktopFaultEventExit 0x00FF00FF | ||||||
|  | |||||||
| @ -1,3 +1,6 @@ | |||||||
|  | #include <power/power_service/power.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | 
 | ||||||
| #include "../desktop_i.h" | #include "../desktop_i.h" | ||||||
| #include "../views/desktop_first_start.h" | #include "../views/desktop_first_start.h" | ||||||
| #include "../views/desktop_events.h" | #include "../views/desktop_events.h" | ||||||
|  | |||||||
| @ -1,6 +1,9 @@ | |||||||
| #include "../desktop_i.h" | #include <gui/scene_manager.h> | ||||||
| #include <furi_hal_version.h> | #include <furi_hal_version.h> | ||||||
| 
 | 
 | ||||||
|  | #include "desktop_scene.h" | ||||||
|  | #include "../desktop_i.h" | ||||||
|  | 
 | ||||||
| #define HW_MISMATCH_BACK_EVENT (0UL) | #define HW_MISMATCH_BACK_EVENT (0UL) | ||||||
| 
 | 
 | ||||||
| void desktop_scene_hw_mismatch_callback(void* context) { | void desktop_scene_hw_mismatch_callback(void* context) { | ||||||
| @ -11,11 +14,14 @@ void desktop_scene_hw_mismatch_callback(void* context) { | |||||||
| void desktop_scene_hw_mismatch_on_enter(void* context) { | void desktop_scene_hw_mismatch_on_enter(void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
|     furi_assert(desktop); |     furi_assert(desktop); | ||||||
|     furi_assert(!desktop->text_buffer); |  | ||||||
|     Popup* popup = desktop->hw_mismatch_popup; |     Popup* popup = desktop->hw_mismatch_popup; | ||||||
|     desktop->text_buffer = furi_alloc(256); | 
 | ||||||
|  |     char* text_buffer = furi_alloc(256); | ||||||
|  |     scene_manager_set_scene_state( | ||||||
|  |         desktop->scene_manager, DesktopSceneHwMismatch, (uint32_t)text_buffer); | ||||||
|  | 
 | ||||||
|     snprintf( |     snprintf( | ||||||
|         desktop->text_buffer, |         text_buffer, | ||||||
|         256, |         256, | ||||||
|         "HW target: %d\nFW target: %d", |         "HW target: %d\nFW target: %d", | ||||||
|         furi_hal_version_get_hw_target(), |         furi_hal_version_get_hw_target(), | ||||||
| @ -23,8 +29,7 @@ void desktop_scene_hw_mismatch_on_enter(void* context) { | |||||||
|     popup_set_context(popup, desktop); |     popup_set_context(popup, desktop); | ||||||
|     popup_set_header( |     popup_set_header( | ||||||
|         popup, "!!!! HW Mismatch !!!!", 60, 14 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); |         popup, "!!!! HW Mismatch !!!!", 60, 14 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); | ||||||
|     popup_set_text( |     popup_set_text(popup, text_buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); | ||||||
|         popup, desktop->text_buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); |  | ||||||
|     popup_set_callback(popup, desktop_scene_hw_mismatch_callback); |     popup_set_callback(popup, desktop_scene_hw_mismatch_callback); | ||||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewHwMismatch); |     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewHwMismatch); | ||||||
| } | } | ||||||
| @ -50,12 +55,13 @@ bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event) | |||||||
| void desktop_scene_hw_mismatch_on_exit(void* context) { | void desktop_scene_hw_mismatch_on_exit(void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
|     furi_assert(desktop); |     furi_assert(desktop); | ||||||
|     furi_assert(desktop->text_buffer); |  | ||||||
|     Popup* popup = desktop->hw_mismatch_popup; |     Popup* popup = desktop->hw_mismatch_popup; | ||||||
|     popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); |     popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); | ||||||
|     popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); |     popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); | ||||||
|     popup_set_callback(popup, NULL); |     popup_set_callback(popup, NULL); | ||||||
|     popup_set_context(popup, NULL); |     popup_set_context(popup, NULL); | ||||||
|     free(desktop->text_buffer); |     char* text_buffer = | ||||||
|     desktop->text_buffer = NULL; |         (char*)scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneHwMismatch); | ||||||
|  |     free(text_buffer); | ||||||
|  |     scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneHwMismatch, 0); | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										7
									
								
								applications/desktop/scenes/desktop_scene_i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								applications/desktop/scenes/desktop_scene_i.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     DesktopMainSceneStateUnlocked, | ||||||
|  |     DesktopMainSceneStateLockedWithPin, | ||||||
|  |     DesktopMainSceneStateLockedNoPin, | ||||||
|  | } DesktopMainSceneState; | ||||||
| @ -1,8 +1,11 @@ | |||||||
| #include "../desktop_i.h" |  | ||||||
| #include "../views/desktop_lock_menu.h" |  | ||||||
| #include <toolbox/saved_struct.h> | #include <toolbox/saved_struct.h> | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
| 
 | 
 | ||||||
|  | #include "../desktop_i.h" | ||||||
|  | #include "../views/desktop_lock_menu.h" | ||||||
|  | #include "desktop_scene_i.h" | ||||||
|  | #include "desktop_scene.h" | ||||||
|  | 
 | ||||||
| void desktop_scene_lock_menu_callback(DesktopEvent event, void* context) { | void desktop_scene_lock_menu_callback(DesktopEvent event, void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |     Desktop* desktop = (Desktop*)context; | ||||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); |     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||||
| @ -15,6 +18,9 @@ void desktop_scene_lock_menu_on_enter(void* context) { | |||||||
| 
 | 
 | ||||||
|     desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop); |     desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop); | ||||||
|     desktop_lock_menu_pin_set(desktop->lock_menu, desktop->settings.pincode.length > 0); |     desktop_lock_menu_pin_set(desktop->lock_menu, desktop->settings.pincode.length > 0); | ||||||
|  | 
 | ||||||
|  |     uint8_t idx = scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLockMenu); | ||||||
|  |     desktop_lock_menu_set_idx(desktop->lock_menu, idx); | ||||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLockMenu); |     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLockMenu); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -26,24 +32,25 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { | |||||||
|         switch(event.event) { |         switch(event.event) { | ||||||
|         case DesktopLockMenuEventLock: |         case DesktopLockMenuEventLock: | ||||||
|             scene_manager_set_scene_state( |             scene_manager_set_scene_state( | ||||||
|                 desktop->scene_manager, DesktopSceneLocked, DesktopLockedNoPin); |                 desktop->scene_manager, DesktopSceneMain, DesktopMainSceneStateLockedNoPin); | ||||||
|             scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); |             scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); | ||||||
|  |             scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
|         case DesktopLockMenuEventPinLock: |         case DesktopLockMenuEventPinLock: | ||||||
|             if(desktop->settings.pincode.length > 0) { |             if(desktop->settings.pincode.length > 0) { | ||||||
|                 furi_hal_rtc_set_flag(FuriHalRtcFlagLock); |  | ||||||
|                 furi_hal_usb_disable(); |  | ||||||
|                 scene_manager_set_scene_state( |                 scene_manager_set_scene_state( | ||||||
|                     desktop->scene_manager, DesktopSceneLocked, DesktopLockedWithPin); |                     desktop->scene_manager, DesktopSceneMain, DesktopMainSceneStateLockedWithPin); | ||||||
|                 scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); |                 scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); | ||||||
|             } else { |             } else { | ||||||
|  |                 scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 1); | ||||||
|                 scene_manager_next_scene(desktop->scene_manager, DesktopScenePinSetup); |                 scene_manager_next_scene(desktop->scene_manager, DesktopScenePinSetup); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
|         case DesktopLockMenuEventExit: |         case DesktopLockMenuEventExit: | ||||||
|  |             scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); | ||||||
|             scene_manager_search_and_switch_to_previous_scene( |             scene_manager_search_and_switch_to_previous_scene( | ||||||
|                 desktop->scene_manager, DesktopSceneMain); |                 desktop->scene_manager, DesktopSceneMain); | ||||||
|             consumed = true; |             consumed = true; | ||||||
| @ -56,6 +63,4 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void desktop_scene_lock_menu_on_exit(void* context) { | void desktop_scene_lock_menu_on_exit(void* context) { | ||||||
|     Desktop* desktop = (Desktop*)context; |  | ||||||
|     desktop_lock_menu_reset_idx(desktop->lock_menu); |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,101 +0,0 @@ | |||||||
| #include "../desktop_i.h" |  | ||||||
| #include "../views/desktop_locked.h" |  | ||||||
| #include "desktop/views/desktop_main.h" |  | ||||||
| 
 |  | ||||||
| void desktop_scene_locked_callback(DesktopEvent event, void* context) { |  | ||||||
|     Desktop* desktop = (Desktop*)context; |  | ||||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void desktop_scene_locked_new_idle_animation_callback(void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     Desktop* desktop = context; |  | ||||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopLockedEventCheckAnimation); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_scene_locked_on_enter(void* context) { |  | ||||||
|     Desktop* desktop = (Desktop*)context; |  | ||||||
|     DesktopLockedView* locked_view = desktop->locked_view; |  | ||||||
| 
 |  | ||||||
|     animation_manager_set_new_idle_callback( |  | ||||||
|         desktop->animation_manager, desktop_scene_locked_new_idle_animation_callback); |  | ||||||
|     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); |  | ||||||
| 
 |  | ||||||
|     uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopViewLocked); |  | ||||||
| 
 |  | ||||||
|     desktop_locked_with_pin(desktop->locked_view, state == DesktopLockedWithPin); |  | ||||||
| 
 |  | ||||||
|     view_port_enabled_set(desktop->lock_viewport, true); |  | ||||||
|     osTimerStart(locked_view->timer, osKernelGetTickFreq() / 16); |  | ||||||
| 
 |  | ||||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLocked); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool desktop_scene_locked_check_pin(Desktop* desktop, DesktopEvent event) { |  | ||||||
|     bool match = false; |  | ||||||
| 
 |  | ||||||
|     size_t length = desktop->pincode_buffer.length; |  | ||||||
|     length = code_input_push(desktop->pincode_buffer.data, length, event); |  | ||||||
|     desktop->pincode_buffer.length = length; |  | ||||||
| 
 |  | ||||||
|     match = code_input_compare( |  | ||||||
|         desktop->pincode_buffer.data, |  | ||||||
|         length, |  | ||||||
|         desktop->settings.pincode.data, |  | ||||||
|         desktop->settings.pincode.length); |  | ||||||
| 
 |  | ||||||
|     if(match) { |  | ||||||
|         desktop->pincode_buffer.length = 0; |  | ||||||
|         furi_hal_usb_enable(); |  | ||||||
|         furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); |  | ||||||
|         desktop_main_unlocked(desktop->main_view); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return match; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 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; |  | ||||||
|             break; |  | ||||||
|         case DesktopLockedEventInputReset: |  | ||||||
|             desktop->pincode_buffer.length = 0; |  | ||||||
|             break; |  | ||||||
|         case DesktopLockedEventCheckAnimation: |  | ||||||
|             animation_manager_check_blocking_process(desktop->animation_manager); |  | ||||||
|             consumed = true; |  | ||||||
|             break; |  | ||||||
|         default: |  | ||||||
|             if(desktop_scene_locked_check_pin(desktop, event.event)) { |  | ||||||
|                 scene_manager_set_scene_state( |  | ||||||
|                     desktop->scene_manager, DesktopSceneMain, DesktopMainEventUnlocked); |  | ||||||
|                 scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); |  | ||||||
|                 consumed = true; |  | ||||||
|             } |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return consumed; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_scene_locked_on_exit(void* context) { |  | ||||||
|     Desktop* desktop = (Desktop*)context; |  | ||||||
|     animation_manager_set_new_idle_callback(desktop->animation_manager, NULL); |  | ||||||
|     desktop_locked_reset_counter(desktop->locked_view); |  | ||||||
|     osTimerStop(desktop->locked_view->timer); |  | ||||||
| } |  | ||||||
| @ -1,17 +1,14 @@ | |||||||
| #include "../desktop_i.h" | #include <furi.h> | ||||||
| #include "../views/desktop_main.h" | #include <furi_hal.h> | ||||||
| #include "applications.h" | #include <applications.h> | ||||||
| #include "assets_icons.h" | #include <assets_icons.h> | ||||||
| #include "cmsis_os2.h" |  | ||||||
| #include "desktop/desktop.h" |  | ||||||
| #include "desktop/views/desktop_events.h" |  | ||||||
| #include "dolphin/dolphin.h" |  | ||||||
| #include "furi/pubsub.h" |  | ||||||
| #include "furi/record.h" |  | ||||||
| #include "furi/thread.h" |  | ||||||
| #include "storage/storage_glue.h" |  | ||||||
| #include <loader/loader.h> | #include <loader/loader.h> | ||||||
| #include <m-list.h> | 
 | ||||||
|  | #include "desktop/desktop_i.h" | ||||||
|  | #include "desktop/views/desktop_main.h" | ||||||
|  | #include "desktop_scene.h" | ||||||
|  | #include "desktop_scene_i.h" | ||||||
|  | 
 | ||||||
| #define MAIN_VIEW_DEFAULT (0UL) | #define MAIN_VIEW_DEFAULT (0UL) | ||||||
| 
 | 
 | ||||||
| static void desktop_scene_main_app_started_callback(const void* message, void* context) { | static void desktop_scene_main_app_started_callback(const void* message, void* context) { | ||||||
| @ -81,17 +78,31 @@ void desktop_scene_main_on_enter(void* context) { | |||||||
|         desktop->animation_manager, desktop_scene_main_check_animation_callback); |         desktop->animation_manager, desktop_scene_main_check_animation_callback); | ||||||
|     animation_manager_set_interact_callback( |     animation_manager_set_interact_callback( | ||||||
|         desktop->animation_manager, desktop_scene_main_interact_animation_callback); |         desktop->animation_manager, desktop_scene_main_interact_animation_callback); | ||||||
|  |     desktop_locked_set_callback(desktop->locked_view, desktop_scene_main_callback, desktop); | ||||||
| 
 | 
 | ||||||
|     furi_assert(osSemaphoreGetCount(desktop->unload_animation_semaphore) == 0); |     furi_assert(osSemaphoreGetCount(desktop->unload_animation_semaphore) == 0); | ||||||
|  |     Loader* loader = furi_record_open("loader"); | ||||||
|     desktop->app_start_stop_subscription = furi_pubsub_subscribe( |     desktop->app_start_stop_subscription = furi_pubsub_subscribe( | ||||||
|         loader_get_pubsub(), desktop_scene_main_app_started_callback, desktop); |         loader_get_pubsub(loader), desktop_scene_main_app_started_callback, desktop); | ||||||
|  |     furi_record_close("loader"); | ||||||
| 
 | 
 | ||||||
|     desktop_main_set_callback(main_view, desktop_scene_main_callback, desktop); |     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) == |     DesktopMainSceneState state = | ||||||
|        DesktopMainEventUnlocked) { |         scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneMain); | ||||||
|         desktop_main_unlocked(desktop->main_view); |     if(state == DesktopMainSceneStateLockedNoPin) { | ||||||
|  |         desktop_locked_lock(desktop->locked_view); | ||||||
|  |         view_port_enabled_set(desktop->lock_viewport, true); | ||||||
|  |     } else if(state == DesktopMainSceneStateLockedWithPin) { | ||||||
|  |         LOAD_DESKTOP_SETTINGS(&desktop->settings); | ||||||
|  |         furi_assert(desktop->settings.pincode.length > 0); | ||||||
|  |         desktop_locked_lock_pincode(desktop->locked_view, desktop->settings.pincode); | ||||||
|  |         view_port_enabled_set(desktop->lock_viewport, true); | ||||||
|  |         furi_hal_rtc_set_flag(FuriHalRtcFlagLock); | ||||||
|  |         furi_hal_usb_disable(); | ||||||
|  |     } else { | ||||||
|  |         furi_assert(state == DesktopMainSceneStateUnlocked); | ||||||
|  |         view_port_enabled_set(desktop->lock_viewport, false); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain); |     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain); | ||||||
| @ -120,22 +131,22 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { | |||||||
| 
 | 
 | ||||||
|         case DesktopMainEventOpenArchive: |         case DesktopMainEventOpenArchive: | ||||||
| #ifdef APP_ARCHIVE | #ifdef APP_ARCHIVE | ||||||
|             animation_manager_unload_and_stall_animation(desktop->animation_manager); |  | ||||||
|             desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE); |             desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE); | ||||||
|             animation_manager_load_and_continue_animation(desktop->animation_manager); |  | ||||||
| #endif | #endif | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         case DesktopMainEventOpenFavorite: |         case DesktopMainEventOpenFavorite: | ||||||
|             LOAD_DESKTOP_SETTINGS(&desktop->settings); |             LOAD_DESKTOP_SETTINGS(&desktop->settings); | ||||||
|             animation_manager_unload_and_stall_animation(desktop->animation_manager); |  | ||||||
|             if(desktop->settings.favorite < FLIPPER_APPS_COUNT) { |             if(desktop->settings.favorite < FLIPPER_APPS_COUNT) { | ||||||
|                 desktop_switch_to_app(desktop, &FLIPPER_APPS[desktop->settings.favorite]); |                 Loader* loader = furi_record_open("loader"); | ||||||
|  |                 LoaderStatus status = | ||||||
|  |                     loader_start(loader, FLIPPER_APPS[desktop->settings.favorite].name, NULL); | ||||||
|  |                 furi_check(status == LoaderStatusOk); | ||||||
|  |                 furi_record_close("loader"); | ||||||
|             } else { |             } else { | ||||||
|                 FURI_LOG_E("DesktopSrv", "Can't find favorite application"); |                 FURI_LOG_E("DesktopSrv", "Can't find favorite application"); | ||||||
|             } |             } | ||||||
|             animation_manager_load_and_continue_animation(desktop->animation_manager); |  | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
| @ -160,6 +171,18 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { | |||||||
|             animation_manager_load_and_continue_animation(desktop->animation_manager); |             animation_manager_load_and_continue_animation(desktop->animation_manager); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
|  |         case DesktopMainEventUnlocked: | ||||||
|  |             consumed = true; | ||||||
|  |             furi_hal_rtc_reset_flag(FuriHalRtcFlagLock); | ||||||
|  |             furi_hal_usb_enable(); | ||||||
|  |             view_port_enabled_set(desktop->lock_viewport, false); | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 desktop->scene_manager, DesktopSceneMain, DesktopMainSceneStateUnlocked); | ||||||
|  |             break; | ||||||
|  |         case DesktopMainEventUpdate: | ||||||
|  |             desktop_locked_update(desktop->locked_view); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
| 
 | 
 | ||||||
|         default: |         default: | ||||||
|             break; |             break; | ||||||
| @ -177,7 +200,9 @@ void desktop_scene_main_on_exit(void* context) { | |||||||
|      * is finished, that's why we can be sure there is no task waiting |      * is finished, that's why we can be sure there is no task waiting | ||||||
|      * for start/stop semaphore |      * for start/stop semaphore | ||||||
|      */ |      */ | ||||||
|     furi_pubsub_unsubscribe(loader_get_pubsub(), desktop->app_start_stop_subscription); |     Loader* loader = furi_record_open("loader"); | ||||||
|  |     furi_pubsub_unsubscribe(loader_get_pubsub(loader), desktop->app_start_stop_subscription); | ||||||
|  |     furi_record_close("loader"); | ||||||
|     furi_assert(osSemaphoreGetCount(desktop->unload_animation_semaphore) == 0); |     furi_assert(osSemaphoreGetCount(desktop->unload_animation_semaphore) == 0); | ||||||
| 
 | 
 | ||||||
|     animation_manager_set_new_idle_callback(desktop->animation_manager, NULL); |     animation_manager_set_new_idle_callback(desktop->animation_manager, NULL); | ||||||
| @ -185,5 +210,4 @@ void desktop_scene_main_on_exit(void* context) { | |||||||
|     animation_manager_set_interact_callback(desktop->animation_manager, NULL); |     animation_manager_set_interact_callback(desktop->animation_manager, NULL); | ||||||
|     animation_manager_set_context(desktop->animation_manager, desktop); |     animation_manager_set_context(desktop->animation_manager, desktop); | ||||||
|     scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneMain, MAIN_VIEW_DEFAULT); |     scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneMain, MAIN_VIEW_DEFAULT); | ||||||
|     desktop_main_reset_hint(desktop->main_view); |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,7 +1,9 @@ | |||||||
|  | #include <toolbox/version.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
|  | #include <furi_hal.h> | ||||||
|  | 
 | ||||||
| #include "../desktop_i.h" | #include "../desktop_i.h" | ||||||
| #include "desktop_debug.h" | #include "desktop_debug.h" | ||||||
| 
 |  | ||||||
| #include "dolphin/helpers/dolphin_state.h" | #include "dolphin/helpers/dolphin_state.h" | ||||||
| #include "dolphin/dolphin.h" | #include "dolphin/dolphin.h" | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,12 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <gui/gui_i.h> | #include <stdint.h> | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
| #include <gui/canvas.h> |  | ||||||
| #include <gui/elements.h> |  | ||||||
| #include <furi.h> |  | ||||||
| #include <storage/storage.h> |  | ||||||
| #include <time.h> |  | ||||||
| #include "desktop_events.h" | #include "desktop_events.h" | ||||||
| 
 | 
 | ||||||
| typedef struct DesktopDebugView DesktopDebugView; | typedef struct DesktopDebugView DesktopDebugView; | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ typedef enum { | |||||||
|     DesktopMainEventOpenFavorite, |     DesktopMainEventOpenFavorite, | ||||||
|     DesktopMainEventOpenMenu, |     DesktopMainEventOpenMenu, | ||||||
|     DesktopMainEventOpenDebug, |     DesktopMainEventOpenDebug, | ||||||
|  |     DesktopMainEventUpdate, | ||||||
|     DesktopMainEventUnlocked, |     DesktopMainEventUnlocked, | ||||||
|     DesktopMainEventRightShort, |     DesktopMainEventRightShort, | ||||||
|     DesktopMainEventCheckAnimation, |     DesktopMainEventCheckAnimation, | ||||||
| @ -14,8 +15,6 @@ typedef enum { | |||||||
|     DesktopMainEventBeforeAppStarted, |     DesktopMainEventBeforeAppStarted, | ||||||
|     DesktopMainEventAfterAppFinished, |     DesktopMainEventAfterAppFinished, | ||||||
|     DesktopLockedEventUnlock, |     DesktopLockedEventUnlock, | ||||||
|     DesktopLockedEventUpdate, |  | ||||||
|     DesktopLockedEventInputReset, |  | ||||||
|     DesktopLockedEventCheckAnimation, |     DesktopLockedEventCheckAnimation, | ||||||
|     DesktopLockedEventMax, |     DesktopLockedEventMax, | ||||||
|     DesktopDebugEventDeed, |     DesktopDebugEventDeed, | ||||||
|  | |||||||
| @ -1,4 +1,6 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
|  | #include <furi_hal.h> | ||||||
|  | #include <gui/elements.h> | ||||||
| #include "../desktop_i.h" | #include "../desktop_i.h" | ||||||
| #include "desktop_first_start.h" | #include "desktop_first_start.h" | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,10 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <gui/gui_i.h> |  | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
| #include <gui/canvas.h> | 
 | ||||||
| #include <gui/elements.h> |  | ||||||
| #include <furi.h> |  | ||||||
| #include "desktop_events.h" | #include "desktop_events.h" | ||||||
| 
 | 
 | ||||||
| typedef struct DesktopFirstStartView DesktopFirstStartView; | typedef struct DesktopFirstStartView DesktopFirstStartView; | ||||||
|  | |||||||
| @ -1,7 +1,11 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
|  | #include <gui/elements.h> | ||||||
|  | 
 | ||||||
| #include "../desktop_i.h" | #include "../desktop_i.h" | ||||||
| #include "desktop_lock_menu.h" | #include "desktop_lock_menu.h" | ||||||
| 
 | 
 | ||||||
|  | #define LOCK_MENU_ITEMS_NB 3 | ||||||
|  | 
 | ||||||
| void desktop_lock_menu_set_callback( | void desktop_lock_menu_set_callback( | ||||||
|     DesktopLockMenuView* lock_menu, |     DesktopLockMenuView* lock_menu, | ||||||
|     DesktopLockMenuViewCallback callback, |     DesktopLockMenuViewCallback callback, | ||||||
| @ -20,10 +24,11 @@ void desktop_lock_menu_pin_set(DesktopLockMenuView* lock_menu, bool pin_is_set) | |||||||
|         }); |         }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu) { | void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx) { | ||||||
|  |     furi_assert(idx < LOCK_MENU_ITEMS_NB); | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         lock_menu->view, (DesktopLockMenuViewModel * model) { |         lock_menu->view, (DesktopLockMenuViewModel * model) { | ||||||
|             model->idx = 0; |             model->idx = idx; | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
| } | } | ||||||
| @ -49,7 +54,7 @@ static void lock_menu_callback(void* context, uint8_t index) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void desktop_lock_menu_render(Canvas* canvas, void* model) { | void desktop_lock_menu_render(Canvas* canvas, void* model) { | ||||||
|     const char* Lockmenu_Items[3] = {"Lock", "Lock with PIN", "DUMB mode"}; |     const char* Lockmenu_Items[LOCK_MENU_ITEMS_NB] = {"Lock", "Lock with PIN", "DUMB mode"}; | ||||||
| 
 | 
 | ||||||
|     DesktopLockMenuViewModel* m = model; |     DesktopLockMenuViewModel* m = model; | ||||||
|     canvas_clear(canvas); |     canvas_clear(canvas); | ||||||
| @ -58,14 +63,15 @@ void desktop_lock_menu_render(Canvas* canvas, void* model) { | |||||||
|     canvas_draw_icon(canvas, 116, 0 + STATUS_BAR_Y_SHIFT, &I_DoorRight_70x55); |     canvas_draw_icon(canvas, 116, 0 + STATUS_BAR_Y_SHIFT, &I_DoorRight_70x55); | ||||||
|     canvas_set_font(canvas, FontSecondary); |     canvas_set_font(canvas, FontSecondary); | ||||||
| 
 | 
 | ||||||
|     for(uint8_t i = 0; i < 3; ++i) { |     for(uint8_t i = 0; i < LOCK_MENU_ITEMS_NB; ++i) { | ||||||
|         const char* str = Lockmenu_Items[i]; |         const char* str = Lockmenu_Items[i]; | ||||||
| 
 | 
 | ||||||
|         if(i == 1 && !m->pin_set) str = "Set PIN"; |         if(i == 1 && !m->pin_set) str = "Set PIN"; | ||||||
|         if(m->hint_timeout && m->idx == 2 && m->idx == i) str = "Not implemented"; |         if(m->hint_timeout && m->idx == 2 && m->idx == i) str = "Not implemented"; | ||||||
| 
 | 
 | ||||||
|         canvas_draw_str_aligned( |         if(str != NULL) | ||||||
|             canvas, 64, 9 + (i * 17) + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter, str); |             canvas_draw_str_aligned( | ||||||
|  |                 canvas, 64, 9 + (i * 17) + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter, str); | ||||||
| 
 | 
 | ||||||
|         if(m->idx == i) elements_frame(canvas, 15, 1 + (i * 17) + STATUS_BAR_Y_SHIFT, 98, 15); |         if(m->idx == i) elements_frame(canvas, 15, 1 + (i * 17) + STATUS_BAR_Y_SHIFT, 98, 15); | ||||||
|     } |     } | ||||||
| @ -88,9 +94,9 @@ bool desktop_lock_menu_input(InputEvent* event, void* context) { | |||||||
|         lock_menu->view, (DesktopLockMenuViewModel * model) { |         lock_menu->view, (DesktopLockMenuViewModel * model) { | ||||||
|             model->hint_timeout = 0; // clear hint timeout
 |             model->hint_timeout = 0; // clear hint timeout
 | ||||||
|             if(event->key == InputKeyUp) { |             if(event->key == InputKeyUp) { | ||||||
|                 model->idx = CLAMP(model->idx - 1, 2, 0); |                 model->idx = CLAMP(model->idx - 1, LOCK_MENU_ITEMS_NB - 1, 0); | ||||||
|             } else if(event->key == InputKeyDown) { |             } else if(event->key == InputKeyDown) { | ||||||
|                 model->idx = CLAMP(model->idx + 1, 2, 0); |                 model->idx = CLAMP(model->idx + 1, LOCK_MENU_ITEMS_NB - 1, 0); | ||||||
|             } |             } | ||||||
|             idx = model->idx; |             idx = model->idx; | ||||||
|             return true; |             return true; | ||||||
|  | |||||||
| @ -1,10 +1,6 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <gui/gui_i.h> |  | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
| #include <gui/canvas.h> |  | ||||||
| #include <gui/elements.h> |  | ||||||
| #include <furi.h> |  | ||||||
| #include "desktop_events.h" | #include "desktop_events.h" | ||||||
| 
 | 
 | ||||||
| #define HINT_TIMEOUT 2 | #define HINT_TIMEOUT 2 | ||||||
| @ -32,6 +28,6 @@ void desktop_lock_menu_set_callback( | |||||||
| 
 | 
 | ||||||
| View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu); | View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu); | ||||||
| void desktop_lock_menu_pin_set(DesktopLockMenuView* lock_menu, bool pin_is_set); | void desktop_lock_menu_pin_set(DesktopLockMenuView* lock_menu, bool pin_is_set); | ||||||
| void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu); | void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx); | ||||||
| DesktopLockMenuView* desktop_lock_menu_alloc(); | DesktopLockMenuView* desktop_lock_menu_alloc(); | ||||||
| void desktop_lock_menu_free(DesktopLockMenuView* lock_menu); | void desktop_lock_menu_free(DesktopLockMenuView* lock_menu); | ||||||
|  | |||||||
| @ -1,6 +1,42 @@ | |||||||
|  | #include "desktop/desktop_settings/desktop_settings.h" | ||||||
|  | #include "furi/check.h" | ||||||
|  | #include "gui/view.h" | ||||||
|  | #include "portmacro.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
|  | #include <gui/gui_i.h> | ||||||
|  | #include <gui/elements.h> | ||||||
| #include "../desktop_i.h" | #include "../desktop_i.h" | ||||||
| #include "desktop_locked.h" | #include "desktop_locked.h" | ||||||
|  | #include <stdint.h> | ||||||
|  | 
 | ||||||
|  | #define DOOR_MOVING_INTERVAL_MS (1000 / 16) | ||||||
|  | #define UNLOCKED_HINT_TIMEOUT_MS (2000) | ||||||
|  | 
 | ||||||
|  | struct DesktopLockedView { | ||||||
|  |     View* view; | ||||||
|  |     DesktopLockedViewCallback callback; | ||||||
|  |     void* context; | ||||||
|  | 
 | ||||||
|  |     TimerHandle_t timer; | ||||||
|  |     uint8_t lock_count; | ||||||
|  |     uint32_t lock_lastpress; | ||||||
|  | 
 | ||||||
|  |     PinCode pincode; | ||||||
|  |     PinCode pincode_input; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint32_t hint_icon_expire_at; | ||||||
|  |     bool unlocked_hint; | ||||||
|  |     bool locked; | ||||||
|  |     bool pin_locked; | ||||||
|  | 
 | ||||||
|  |     int8_t door_left_x; | ||||||
|  |     int8_t door_right_x; | ||||||
|  |     bool animation_seq_end; | ||||||
|  | } DesktopLockedViewModel; | ||||||
|  | 
 | ||||||
|  | static void desktop_locked_unlock(DesktopLockedView* locked_view); | ||||||
| 
 | 
 | ||||||
| void desktop_locked_set_callback( | void desktop_locked_set_callback( | ||||||
|     DesktopLockedView* locked_view, |     DesktopLockedView* locked_view, | ||||||
| @ -12,98 +48,69 @@ void desktop_locked_set_callback( | |||||||
|     locked_view->context = context; |     locked_view->context = context; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void locked_view_timer_callback(void* context) { | void locked_view_timer_callback(TimerHandle_t timer) { | ||||||
|     DesktopLockedView* locked_view = context; |     DesktopLockedView* locked_view = pvTimerGetTimerID(timer); | ||||||
|     locked_view->callback(DesktopLockedEventUpdate, locked_view->context); |     locked_view->callback(DesktopMainEventUpdate, locked_view->context); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view) { | static void desktop_locked_update_hint_icon_timeout(DesktopLockedView* locked_view) { | ||||||
|     with_view_model( |     DesktopLockedViewModel* model = view_get_model(locked_view->view); | ||||||
|         locked_view->view, (DesktopLockedViewModel * model) { |     model->hint_icon_expire_at = osKernelGetTickCount() + osKernelGetTickFreq(); | ||||||
|             model->hint_expire_at = osKernelGetTickCount() + osKernelGetTickFreq(); |     view_commit_model(locked_view->view, true); | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void desktop_locked_reset_door_pos(DesktopLockedView* locked_view) { | static void desktop_locked_reset_door_pos(DesktopLockedView* locked_view) { | ||||||
|     with_view_model( |     DesktopLockedViewModel* model = view_get_model(locked_view->view); | ||||||
|         locked_view->view, (DesktopLockedViewModel * model) { |     model->animation_seq_end = false; | ||||||
|             model->animation_seq_end = false; |     model->door_left_x = DOOR_L_POS; | ||||||
|             model->door_left_x = DOOR_L_POS; |     model->door_right_x = DOOR_R_POS; | ||||||
|             model->door_right_x = DOOR_R_POS; |     view_commit_model(locked_view->view, true); | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void desktop_locked_manage_redraw(DesktopLockedView* locked_view) { | void desktop_locked_update(DesktopLockedView* locked_view) { | ||||||
|     bool animation_seq_end; |     bool stop_timer = false; | ||||||
| 
 | 
 | ||||||
|     with_view_model( |     DesktopLockedViewModel* model = view_get_model(locked_view->view); | ||||||
|         locked_view->view, (DesktopLockedViewModel * model) { |     if(model->locked) { | ||||||
|             model->animation_seq_end = !model->door_left_x; |         if(model->door_left_x != DOOR_L_POS_MAX) { | ||||||
|             animation_seq_end = model->animation_seq_end; |             model->door_left_x = CLAMP(model->door_left_x + 5, DOOR_L_POS_MAX, DOOR_L_POS); | ||||||
|  |             model->door_right_x = CLAMP(model->door_right_x - 5, DOOR_R_POS, DOOR_R_POS_MIN); | ||||||
|  |         } else { | ||||||
|  |             model->animation_seq_end = true; | ||||||
|  |         } | ||||||
|  |         stop_timer = model->animation_seq_end; | ||||||
|  |     } else { | ||||||
|  |         model->unlocked_hint = false; | ||||||
|  |         stop_timer = true; | ||||||
|  |     } | ||||||
|  |     view_commit_model(locked_view->view, true); | ||||||
| 
 | 
 | ||||||
|             if(!model->animation_seq_end) { |     if(stop_timer) { | ||||||
|                 model->door_left_x = CLAMP(model->door_left_x + 5, DOOR_L_POS_MAX, DOOR_L_POS); |         xTimerStop(locked_view->timer, portMAX_DELAY); | ||||||
|                 model->door_right_x = CLAMP(model->door_right_x - 5, DOOR_R_POS, DOOR_R_POS_MIN); |  | ||||||
|             } else { |  | ||||||
|                 model->hint_expire_at = !model->hint_expire_at; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     if(animation_seq_end) { |  | ||||||
|         osTimerStop(locked_view->timer); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void desktop_locked_reset_counter(DesktopLockedView* locked_view) { | void desktop_locked_draw(Canvas* canvas, void* model) { | ||||||
|     locked_view->lock_count = 0; |  | ||||||
|     locked_view->lock_lastpress = 0; |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         locked_view->view, (DesktopLockedViewModel * model) { |  | ||||||
|             model->hint_expire_at = 0; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_locked_with_pin(DesktopLockedView* locked_view, bool locked) { |  | ||||||
|     with_view_model( |  | ||||||
|         locked_view->view, (DesktopLockedViewModel * model) { |  | ||||||
|             model->pin_lock = locked; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_locked_render(Canvas* canvas, void* model) { |  | ||||||
|     DesktopLockedViewModel* m = model; |     DesktopLockedViewModel* m = model; | ||||||
|     uint32_t now = osKernelGetTickCount(); |     uint32_t now = osKernelGetTickCount(); | ||||||
|     canvas_set_color(canvas, ColorBlack); |     canvas_set_color(canvas, ColorBlack); | ||||||
| 
 | 
 | ||||||
|     if(!m->animation_seq_end) { |     if(m->locked) { | ||||||
|         canvas_draw_icon(canvas, m->door_left_x, 0 + STATUS_BAR_Y_SHIFT, &I_DoorLeft_70x55); |  | ||||||
|         canvas_draw_icon(canvas, m->door_right_x, 0 + STATUS_BAR_Y_SHIFT, &I_DoorRight_70x55); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(m->animation && m->animation_seq_end) { |  | ||||||
|         if(m->status_bar_background_black) { |  | ||||||
|             canvas_draw_box(canvas, 0, 0, GUI_STATUS_BAR_WIDTH, GUI_STATUS_BAR_HEIGHT); |  | ||||||
|         } |  | ||||||
|         canvas_draw_icon_animation(canvas, 0, 0 + STATUS_BAR_Y_SHIFT, m->animation); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(now < m->hint_expire_at) { |  | ||||||
|         if(!m->animation_seq_end) { |         if(!m->animation_seq_end) { | ||||||
|  |             canvas_draw_icon(canvas, m->door_left_x, 0 + STATUS_BAR_Y_SHIFT, &I_DoorLeft_70x55); | ||||||
|  |             canvas_draw_icon(canvas, m->door_right_x, 0 + STATUS_BAR_Y_SHIFT, &I_DoorRight_70x55); | ||||||
|             canvas_set_font(canvas, FontPrimary); |             canvas_set_font(canvas, FontPrimary); | ||||||
|             elements_multiline_text_framed(canvas, 42, 30 + STATUS_BAR_Y_SHIFT, "Locked"); |             elements_multiline_text_framed(canvas, 42, 30 + STATUS_BAR_Y_SHIFT, "Locked"); | ||||||
| 
 |         } else if((now < m->hint_icon_expire_at) && !m->pin_locked) { | ||||||
|         } else if(!m->pin_lock) { |  | ||||||
|             canvas_set_font(canvas, FontSecondary); |             canvas_set_font(canvas, FontSecondary); | ||||||
|             canvas_draw_icon(canvas, 13, 2 + STATUS_BAR_Y_SHIFT, &I_LockPopup_100x49); |             canvas_draw_icon(canvas, 13, 2 + STATUS_BAR_Y_SHIFT, &I_LockPopup_100x49); | ||||||
|             elements_multiline_text(canvas, 65, 20 + STATUS_BAR_Y_SHIFT, "To unlock\npress:"); |             elements_multiline_text(canvas, 65, 20 + STATUS_BAR_Y_SHIFT, "To unlock\npress:"); | ||||||
|         } |         } | ||||||
|  |     } else { | ||||||
|  |         if(m->unlocked_hint) { | ||||||
|  |             canvas_set_font(canvas, FontPrimary); | ||||||
|  |             elements_multiline_text_framed(canvas, 42, 30 + STATUS_BAR_Y_SHIFT, "Unlocked"); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -116,85 +123,72 @@ bool desktop_locked_input(InputEvent* event, void* context) { | |||||||
|     furi_assert(event); |     furi_assert(event); | ||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     DesktopLockedView* locked_view = context; |     DesktopLockedView* locked_view = context; | ||||||
| 
 |     bool locked = false; | ||||||
|     uint32_t press_time = 0; |  | ||||||
|     bool locked_with_pin = false; |     bool locked_with_pin = false; | ||||||
|  |     uint32_t press_time = xTaskGetTickCount(); | ||||||
| 
 | 
 | ||||||
|     with_view_model( |     { | ||||||
|         locked_view->view, (DesktopLockedViewModel * model) { |         DesktopLockedViewModel* model = view_get_model(locked_view->view); | ||||||
|             locked_with_pin = model->pin_lock; |         bool changed = false; | ||||||
|             return false; |         locked = model->locked; | ||||||
|         }); |         locked_with_pin = model->pin_locked; | ||||||
| 
 |         if(!locked && model->unlocked_hint && event->type == InputTypePress) { | ||||||
|     if(event->type == InputTypeShort) { |             model->unlocked_hint = false; | ||||||
|         if(locked_with_pin) { |             changed = true; | ||||||
|             press_time = osKernelGetTickCount(); |  | ||||||
| 
 |  | ||||||
|             if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT * 3) { |  | ||||||
|                 locked_view->lock_lastpress = press_time; |  | ||||||
|                 locked_view->callback(DesktopLockedEventInputReset, locked_view->context); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             locked_view->callback(event->key, locked_view->context); |  | ||||||
|         } else { |  | ||||||
|             desktop_locked_update_hint_timeout(locked_view); |  | ||||||
| 
 |  | ||||||
|             if(event->key == InputKeyBack) { |  | ||||||
|                 press_time = osKernelGetTickCount(); |  | ||||||
|                 // check if pressed sequentially
 |  | ||||||
|                 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); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|  |         view_commit_model(locked_view->view, changed); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) { |     if(!locked || (event->type != InputTypeShort)) { | ||||||
|  |         return locked; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) { | ||||||
|  |         locked_view->lock_lastpress = press_time; | ||||||
|  |         locked_view->lock_count = 0; | ||||||
|  |         locked_view->pincode_input.length = 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(locked_with_pin) { | ||||||
|  |         locked_view->pincode_input.length = code_input_push( | ||||||
|  |             locked_view->pincode_input.data, locked_view->pincode_input.length, event->key); | ||||||
|  |         bool match = code_input_compare( | ||||||
|  |             locked_view->pincode_input.data, | ||||||
|  |             locked_view->pincode_input.length, | ||||||
|  |             locked_view->pincode.data, | ||||||
|  |             locked_view->pincode.length); | ||||||
|  | 
 | ||||||
|  |         if(match) { | ||||||
|  |             desktop_locked_unlock(locked_view); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         if(event->key == InputKeyBack) { | ||||||
|             locked_view->lock_lastpress = press_time; |             locked_view->lock_lastpress = press_time; | ||||||
|  |             locked_view->lock_count++; | ||||||
|  |             if(locked_view->lock_count == UNLOCK_CNT) { | ||||||
|  |                 desktop_locked_unlock(locked_view); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             desktop_locked_update_hint_icon_timeout(locked_view); | ||||||
|             locked_view->lock_count = 0; |             locked_view->lock_count = 0; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     // All events consumed
 |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| void desktop_locked_enter(void* context) { |     locked_view->lock_lastpress = press_time; | ||||||
|     DesktopLockedView* locked_view = context; |  | ||||||
| 
 | 
 | ||||||
|     with_view_model( |     return locked; | ||||||
|         locked_view->view, (DesktopLockedViewModel * model) { |  | ||||||
|             if(model->animation) icon_animation_start(model->animation); |  | ||||||
|             return false; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_locked_exit(void* context) { |  | ||||||
|     DesktopLockedView* locked_view = context; |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         locked_view->view, (DesktopLockedViewModel * model) { |  | ||||||
|             if(model->animation) icon_animation_stop(model->animation); |  | ||||||
|             return false; |  | ||||||
|         }); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| DesktopLockedView* desktop_locked_alloc() { | DesktopLockedView* desktop_locked_alloc() { | ||||||
|     DesktopLockedView* locked_view = furi_alloc(sizeof(DesktopLockedView)); |     DesktopLockedView* locked_view = furi_alloc(sizeof(DesktopLockedView)); | ||||||
|     locked_view->view = view_alloc(); |     locked_view->view = view_alloc(); | ||||||
|     locked_view->timer = |     locked_view->timer = | ||||||
|         osTimerNew(locked_view_timer_callback, osTimerPeriodic, locked_view, NULL); |         xTimerCreate("Locked view", 1000 / 16, pdTRUE, locked_view, locked_view_timer_callback); | ||||||
| 
 | 
 | ||||||
|     view_allocate_model(locked_view->view, ViewModelTypeLocking, sizeof(DesktopLockedViewModel)); |     view_allocate_model(locked_view->view, ViewModelTypeLocking, sizeof(DesktopLockedViewModel)); | ||||||
|     view_set_context(locked_view->view, locked_view); |     view_set_context(locked_view->view, locked_view); | ||||||
|     view_set_draw_callback(locked_view->view, (ViewDrawCallback)desktop_locked_render); |     view_set_draw_callback(locked_view->view, (ViewDrawCallback)desktop_locked_draw); | ||||||
|     view_set_input_callback(locked_view->view, desktop_locked_input); |     view_set_input_callback(locked_view->view, desktop_locked_input); | ||||||
|     view_set_enter_callback(locked_view->view, desktop_locked_enter); |  | ||||||
|     view_set_exit_callback(locked_view->view, desktop_locked_exit); |  | ||||||
| 
 | 
 | ||||||
|     return locked_view; |     return locked_view; | ||||||
| } | } | ||||||
| @ -205,3 +199,49 @@ void desktop_locked_free(DesktopLockedView* locked_view) { | |||||||
|     view_free(locked_view->view); |     view_free(locked_view->view); | ||||||
|     free(locked_view); |     free(locked_view); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void desktop_locked_lock(DesktopLockedView* locked_view) { | ||||||
|  |     locked_view->pincode.length = 0; | ||||||
|  |     DesktopLockedViewModel* model = view_get_model(locked_view->view); | ||||||
|  |     model->locked = true; | ||||||
|  |     model->pin_locked = false; | ||||||
|  |     view_commit_model(locked_view->view, true); | ||||||
|  |     desktop_locked_reset_door_pos(locked_view); | ||||||
|  |     xTimerChangePeriod(locked_view->timer, DOOR_MOVING_INTERVAL_MS, portMAX_DELAY); | ||||||
|  | 
 | ||||||
|  |     Gui* gui = furi_record_open("gui"); | ||||||
|  |     gui_set_lockdown(gui, true); | ||||||
|  |     furi_record_close("gui"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_locked_lock_pincode(DesktopLockedView* locked_view, PinCode pincode) { | ||||||
|  |     locked_view->pincode = pincode; | ||||||
|  |     locked_view->pincode_input.length = 0; | ||||||
|  |     DesktopLockedViewModel* model = view_get_model(locked_view->view); | ||||||
|  |     model->locked = true; | ||||||
|  |     model->pin_locked = true; | ||||||
|  |     view_commit_model(locked_view->view, true); | ||||||
|  |     desktop_locked_reset_door_pos(locked_view); | ||||||
|  |     xTimerChangePeriod(locked_view->timer, DOOR_MOVING_INTERVAL_MS, portMAX_DELAY); | ||||||
|  | 
 | ||||||
|  |     Gui* gui = furi_record_open("gui"); | ||||||
|  |     gui_set_lockdown(gui, true); | ||||||
|  |     furi_record_close("gui"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void desktop_locked_unlock(DesktopLockedView* locked_view) { | ||||||
|  |     furi_assert(locked_view); | ||||||
|  | 
 | ||||||
|  |     locked_view->lock_count = 0; | ||||||
|  |     DesktopLockedViewModel* model = view_get_model(locked_view->view); | ||||||
|  |     model->locked = false; | ||||||
|  |     model->pin_locked = false; | ||||||
|  |     model->unlocked_hint = true; | ||||||
|  |     view_commit_model(locked_view->view, true); | ||||||
|  |     locked_view->callback(DesktopMainEventUnlocked, locked_view->context); | ||||||
|  |     xTimerChangePeriod(locked_view->timer, UNLOCKED_HINT_TIMEOUT_MS, portMAX_DELAY); | ||||||
|  | 
 | ||||||
|  |     Gui* gui = furi_record_open("gui"); | ||||||
|  |     gui_set_lockdown(gui, false); | ||||||
|  |     furi_record_close("gui"); | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,14 +1,11 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <gui/gui_i.h> | #include <desktop/desktop_settings/desktop_settings.h> | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
| #include <gui/canvas.h> |  | ||||||
| #include <gui/elements.h> |  | ||||||
| #include <furi.h> |  | ||||||
| #include "desktop_events.h" | #include "desktop_events.h" | ||||||
| 
 | 
 | ||||||
| #define UNLOCK_RST_TIMEOUT 300 | #define UNLOCK_RST_TIMEOUT 300 | ||||||
| #define UNLOCK_CNT 2 // 3 actually
 | #define UNLOCK_CNT 3 | ||||||
| 
 | 
 | ||||||
| #define DOOR_L_POS -57 | #define DOOR_L_POS -57 | ||||||
| #define DOOR_L_POS_MAX 0 | #define DOOR_L_POS_MAX 0 | ||||||
| @ -24,40 +21,16 @@ typedef struct DesktopLockedView DesktopLockedView; | |||||||
| 
 | 
 | ||||||
| typedef void (*DesktopLockedViewCallback)(DesktopEvent event, void* context); | typedef void (*DesktopLockedViewCallback)(DesktopEvent 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; |  | ||||||
|     uint32_t hint_expire_at; |  | ||||||
| 
 |  | ||||||
|     bool status_bar_background_black; |  | ||||||
|     uint8_t scene_num; |  | ||||||
|     int8_t door_left_x; |  | ||||||
|     int8_t door_right_x; |  | ||||||
|     bool animation_seq_end; |  | ||||||
| 
 |  | ||||||
|     bool pin_lock; |  | ||||||
| } DesktopLockedViewModel; |  | ||||||
| 
 |  | ||||||
| void desktop_locked_set_callback( | void desktop_locked_set_callback( | ||||||
|     DesktopLockedView* locked_view, |     DesktopLockedView* locked_view, | ||||||
|     DesktopLockedViewCallback callback, |     DesktopLockedViewCallback callback, | ||||||
|     void* context); |     void* context); | ||||||
| 
 | 
 | ||||||
| void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view); | void desktop_locked_update(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); | View* desktop_locked_get_view(DesktopLockedView* locked_view); | ||||||
| DesktopLockedView* desktop_locked_alloc(); | DesktopLockedView* desktop_locked_alloc(); | ||||||
| void desktop_locked_free(DesktopLockedView* locked_view); | void desktop_locked_free(DesktopLockedView* locked_view); | ||||||
| void desktop_locked_with_pin(DesktopLockedView* lock_menu, bool locked); | 
 | ||||||
|  | void desktop_locked_lock_pincode(DesktopLockedView* locked_view, PinCode pincode); | ||||||
|  | void desktop_locked_lock(DesktopLockedView* locked_view); | ||||||
|  | |||||||
| @ -1,13 +1,19 @@ | |||||||
| #include "dolphin/dolphin.h" | #include <gui/gui_i.h> | ||||||
| #include "furi/record.h" | #include <gui/view.h> | ||||||
| #include "gui/canvas.h" | #include <gui/elements.h> | ||||||
| #include "gui/view.h" | #include <gui/canvas.h> | ||||||
| #include "gui/view_composed.h" |  | ||||||
| #include "input/input.h" |  | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
|  | #include <input/input.h> | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
|  | 
 | ||||||
| #include "../desktop_i.h" | #include "../desktop_i.h" | ||||||
| #include "desktop_main.h" | #include "desktop_main.h" | ||||||
| //#include "../animations/views/bubble_animation_view.h"
 | 
 | ||||||
|  | struct DesktopMainView { | ||||||
|  |     View* view; | ||||||
|  |     DesktopMainViewCallback callback; | ||||||
|  |     void* context; | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| void desktop_main_set_callback( | void desktop_main_set_callback( | ||||||
|     DesktopMainView* main_view, |     DesktopMainView* main_view, | ||||||
| @ -19,59 +25,6 @@ void desktop_main_set_callback( | |||||||
|     main_view->context = context; |     main_view->context = context; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void desktop_main_reset_hint(DesktopMainView* main_view) { |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (DesktopMainViewModel * model) { |  | ||||||
|             model->hint_expire_at = 0; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_main_switch_dolphin_animation( |  | ||||||
|     DesktopMainView* main_view, |  | ||||||
|     const Icon* icon, |  | ||||||
|     bool status_bar_background_black) { |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (DesktopMainViewModel * model) { |  | ||||||
|             if(model->animation) icon_animation_free(model->animation); |  | ||||||
|             model->animation = icon_animation_alloc(icon); |  | ||||||
|             view_tie_icon_animation(main_view->view, model->animation); |  | ||||||
|             icon_animation_start(model->animation); |  | ||||||
|             model->icon = NULL; |  | ||||||
|             model->status_bar_background_black = status_bar_background_black; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_main_switch_dolphin_icon(DesktopMainView* main_view, const Icon* icon) { |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (DesktopMainViewModel * model) { |  | ||||||
|             if(model->animation) icon_animation_free(model->animation); |  | ||||||
|             model->animation = NULL; |  | ||||||
|             model->icon = icon; |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_main_render(Canvas* canvas, void* model) { |  | ||||||
|     DesktopMainViewModel* m = model; |  | ||||||
|     uint32_t now = osKernelGetTickCount(); |  | ||||||
| 
 |  | ||||||
|     if(m->status_bar_background_black) { |  | ||||||
|         canvas_draw_box(canvas, 0, 0, GUI_STATUS_BAR_WIDTH, GUI_STATUS_BAR_HEIGHT); |  | ||||||
|     } |  | ||||||
|     if(m->icon) { |  | ||||||
|         canvas_draw_icon(canvas, 0, 0 + STATUS_BAR_Y_SHIFT, m->icon); |  | ||||||
|     } else if(m->animation) { |  | ||||||
|         canvas_draw_icon_animation(canvas, 0, 0 + STATUS_BAR_Y_SHIFT, m->animation); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(now < m->hint_expire_at) { |  | ||||||
|         canvas_set_font(canvas, FontPrimary); |  | ||||||
|         elements_multiline_text_framed(canvas, 42, 30 + STATUS_BAR_Y_SHIFT, "Unlocked"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| View* desktop_main_get_view(DesktopMainView* main_view) { | View* desktop_main_get_view(DesktopMainView* main_view) { | ||||||
|     furi_assert(main_view); |     furi_assert(main_view); | ||||||
|     return main_view->view; |     return main_view->view; | ||||||
| @ -82,58 +35,35 @@ bool desktop_main_input(InputEvent* event, void* context) { | |||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| 
 | 
 | ||||||
|     DesktopMainView* main_view = context; |     DesktopMainView* main_view = context; | ||||||
|     bool consumed = false; |  | ||||||
| 
 | 
 | ||||||
|     if(event->key == InputKeyOk && event->type == InputTypeShort) { |     if(event->type == InputTypeShort) { | ||||||
|         main_view->callback(DesktopMainEventOpenMenu, main_view->context); |         if(event->key == InputKeyOk) { | ||||||
|     } else if(event->key == InputKeyDown && event->type == InputTypeLong) { |             main_view->callback(DesktopMainEventOpenMenu, main_view->context); | ||||||
|         main_view->callback(DesktopMainEventOpenDebug, main_view->context); |         } else if(event->key == InputKeyUp) { | ||||||
|     } else if(event->key == InputKeyUp && event->type == InputTypeShort) { |             main_view->callback(DesktopMainEventOpenLockMenu, main_view->context); | ||||||
|         main_view->callback(DesktopMainEventOpenLockMenu, main_view->context); |         } else if(event->key == InputKeyDown) { | ||||||
|     } else if(event->key == InputKeyDown && event->type == InputTypeShort) { |             main_view->callback(DesktopMainEventOpenArchive, main_view->context); | ||||||
|         main_view->callback(DesktopMainEventOpenArchive, main_view->context); |         } else if(event->key == InputKeyLeft) { | ||||||
|     } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { |             main_view->callback(DesktopMainEventOpenFavorite, main_view->context); | ||||||
|         main_view->callback(DesktopMainEventOpenFavorite, main_view->context); |         } else if(event->key == InputKeyRight) { | ||||||
|     } else if(event->key == InputKeyRight && event->type == InputTypeShort) { |             main_view->callback(DesktopMainEventRightShort, main_view->context); | ||||||
|         main_view->callback(DesktopMainEventRightShort, main_view->context); |         } | ||||||
|     } else if(event->key == InputKeyBack && event->type == InputTypeShort) { |     } else if(event->type == InputTypeLong) { | ||||||
|         consumed = true; |         if(event->key == InputKeyDown) { | ||||||
|  |             main_view->callback(DesktopMainEventOpenDebug, main_view->context); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     desktop_main_reset_hint(main_view); |     return true; | ||||||
| 
 |  | ||||||
|     return consumed; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_main_enter(void* context) { |  | ||||||
|     DesktopMainView* main_view = context; |  | ||||||
| 
 |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (DesktopMainViewModel * model) { |  | ||||||
|             if(model->animation) icon_animation_start(model->animation); |  | ||||||
|             return false; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_main_exit(void* context) { |  | ||||||
|     DesktopMainView* main_view = context; |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (DesktopMainViewModel * model) { |  | ||||||
|             if(model->animation) icon_animation_stop(model->animation); |  | ||||||
|             return false; |  | ||||||
|         }); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| DesktopMainView* desktop_main_alloc() { | DesktopMainView* desktop_main_alloc() { | ||||||
|     DesktopMainView* main_view = furi_alloc(sizeof(DesktopMainView)); |     DesktopMainView* main_view = furi_alloc(sizeof(DesktopMainView)); | ||||||
| 
 | 
 | ||||||
|     main_view->view = view_alloc(); |     main_view->view = view_alloc(); | ||||||
|     view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(DesktopMainViewModel)); |     view_allocate_model(main_view->view, ViewModelTypeLockFree, 1); | ||||||
|     view_set_context(main_view->view, main_view); |     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); |     view_set_input_callback(main_view->view, desktop_main_input); | ||||||
|     view_set_enter_callback(main_view->view, desktop_main_enter); |  | ||||||
|     view_set_exit_callback(main_view->view, desktop_main_exit); |  | ||||||
| 
 | 
 | ||||||
|     return main_view; |     return main_view; | ||||||
| } | } | ||||||
| @ -143,11 +73,3 @@ void desktop_main_free(DesktopMainView* main_view) { | |||||||
|     view_free(main_view->view); |     view_free(main_view->view); | ||||||
|     free(main_view); |     free(main_view); | ||||||
| } | } | ||||||
| 
 |  | ||||||
| void desktop_main_unlocked(DesktopMainView* main_view) { |  | ||||||
|     with_view_model( |  | ||||||
|         main_view->view, (DesktopMainViewModel * model) { |  | ||||||
|             model->hint_expire_at = osKernelGetTickCount() + osKernelGetTickFreq(); |  | ||||||
|             return true; |  | ||||||
|         }); |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -1,31 +1,12 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "gui/view_composed.h" |  | ||||||
| #include <gui/gui_i.h> |  | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
| #include <gui/canvas.h> |  | ||||||
| #include <gui/elements.h> |  | ||||||
| #include <furi.h> |  | ||||||
| #include "desktop_events.h" | #include "desktop_events.h" | ||||||
| 
 | 
 | ||||||
| typedef struct DesktopMainView DesktopMainView; | typedef struct DesktopMainView DesktopMainView; | ||||||
| 
 | 
 | ||||||
| typedef void (*DesktopMainViewCallback)(DesktopEvent event, void* context); | typedef void (*DesktopMainViewCallback)(DesktopEvent event, void* context); | ||||||
| 
 | 
 | ||||||
| struct DesktopMainView { |  | ||||||
|     View* view; |  | ||||||
|     DesktopMainViewCallback callback; |  | ||||||
|     void* context; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     IconAnimation* animation; |  | ||||||
|     const Icon* icon; |  | ||||||
|     uint8_t scene_num; |  | ||||||
|     bool status_bar_background_black; |  | ||||||
|     uint32_t hint_expire_at; |  | ||||||
| } DesktopMainViewModel; |  | ||||||
| 
 |  | ||||||
| void desktop_main_set_callback( | void desktop_main_set_callback( | ||||||
|     DesktopMainView* main_view, |     DesktopMainView* main_view, | ||||||
|     DesktopMainViewCallback callback, |     DesktopMainViewCallback callback, | ||||||
| @ -34,10 +15,3 @@ void desktop_main_set_callback( | |||||||
| View* desktop_main_get_view(DesktopMainView* main_view); | View* desktop_main_get_view(DesktopMainView* main_view); | ||||||
| DesktopMainView* desktop_main_alloc(); | DesktopMainView* desktop_main_alloc(); | ||||||
| void desktop_main_free(DesktopMainView* main_view); | void desktop_main_free(DesktopMainView* main_view); | ||||||
| void desktop_main_switch_dolphin_animation( |  | ||||||
|     DesktopMainView* main_view, |  | ||||||
|     const Icon* icon, |  | ||||||
|     bool status_bar_background_black); |  | ||||||
| void desktop_main_unlocked(DesktopMainView* main_view); |  | ||||||
| void desktop_main_reset_hint(DesktopMainView* main_view); |  | ||||||
| void desktop_main_switch_dolphin_icon(DesktopMainView* main_view, const Icon* icon); |  | ||||||
|  | |||||||
| @ -1,13 +1,18 @@ | |||||||
| #include "dolphin/dolphin.h" | #include "dolphin/dolphin.h" | ||||||
| #include "desktop/desktop.h" |  | ||||||
| #include "dolphin/helpers/dolphin_state.h" | #include "dolphin/helpers/dolphin_state.h" | ||||||
| #include "dolphin_i.h" | #include "dolphin_i.h" | ||||||
| #include "furi/pubsub.h" | #include "portmacro.h" | ||||||
| #include "sys/_stdint.h" | #include "projdefs.h" | ||||||
|  | #include <furi_hal.h> | ||||||
|  | #include <stdint.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #define DOLPHIN_TIMEGATE 86400 // one day
 |  | ||||||
| #define DOLPHIN_LOCK_EVENT_FLAG (0x1) | #define DOLPHIN_LOCK_EVENT_FLAG (0x1) | ||||||
| 
 | 
 | ||||||
|  | #define TAG "Dolphin" | ||||||
|  | #define HOURS_IN_TICKS(x) ((x)*60 * 60 * 1000) | ||||||
|  | 
 | ||||||
|  | static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin); | ||||||
|  | 
 | ||||||
| void dolphin_deed(Dolphin* dolphin, DolphinDeed deed) { | void dolphin_deed(Dolphin* dolphin, DolphinDeed deed) { | ||||||
|     furi_assert(dolphin); |     furi_assert(dolphin); | ||||||
|     DolphinEvent event; |     DolphinEvent event; | ||||||
| @ -39,12 +44,51 @@ void dolphin_flush(Dolphin* dolphin) { | |||||||
|     dolphin_event_send_wait(dolphin, &event); |     dolphin_event_send_wait(dolphin, &event); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void dolphin_butthurt_timer_callback(TimerHandle_t xTimer) { | ||||||
|  |     Dolphin* dolphin = pvTimerGetTimerID(xTimer); | ||||||
|  |     furi_assert(dolphin); | ||||||
|  | 
 | ||||||
|  |     DolphinEvent event; | ||||||
|  |     event.type = DolphinEventTypeIncreaseButthurt; | ||||||
|  |     dolphin_event_send_async(dolphin, &event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void dolphin_flush_timer_callback(TimerHandle_t xTimer) { | ||||||
|  |     Dolphin* dolphin = pvTimerGetTimerID(xTimer); | ||||||
|  |     furi_assert(dolphin); | ||||||
|  | 
 | ||||||
|  |     DolphinEvent event; | ||||||
|  |     event.type = DolphinEventTypeFlush; | ||||||
|  |     dolphin_event_send_async(dolphin, &event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void dolphin_clear_limits_timer_callback(TimerHandle_t xTimer) { | ||||||
|  |     Dolphin* dolphin = pvTimerGetTimerID(xTimer); | ||||||
|  |     furi_assert(dolphin); | ||||||
|  | 
 | ||||||
|  |     xTimerChangePeriod(dolphin->clear_limits_timer, HOURS_IN_TICKS(24), portMAX_DELAY); | ||||||
|  | 
 | ||||||
|  |     DolphinEvent event; | ||||||
|  |     event.type = DolphinEventTypeClearLimits; | ||||||
|  |     dolphin_event_send_async(dolphin, &event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| Dolphin* dolphin_alloc() { | Dolphin* dolphin_alloc() { | ||||||
|     Dolphin* dolphin = furi_alloc(sizeof(Dolphin)); |     Dolphin* dolphin = furi_alloc(sizeof(Dolphin)); | ||||||
| 
 | 
 | ||||||
|     dolphin->state = dolphin_state_alloc(); |     dolphin->state = dolphin_state_alloc(); | ||||||
|     dolphin->event_queue = osMessageQueueNew(8, sizeof(DolphinEvent), NULL); |     dolphin->event_queue = osMessageQueueNew(8, sizeof(DolphinEvent), NULL); | ||||||
|     dolphin->pubsub = furi_pubsub_alloc(); |     dolphin->pubsub = furi_pubsub_alloc(); | ||||||
|  |     dolphin->butthurt_timer = xTimerCreate( | ||||||
|  |         "Butthurt timer", HOURS_IN_TICKS(2 * 24), pdTRUE, dolphin, dolphin_butthurt_timer_callback); | ||||||
|  |     dolphin->flush_timer = | ||||||
|  |         xTimerCreate("Flush timer", 30 * 1000, pdFALSE, dolphin, dolphin_flush_timer_callback); | ||||||
|  |     dolphin->clear_limits_timer = xTimerCreate( | ||||||
|  |         "Clear limits timer", | ||||||
|  |         HOURS_IN_TICKS(24), | ||||||
|  |         pdTRUE, | ||||||
|  |         dolphin, | ||||||
|  |         dolphin_clear_limits_timer_callback); | ||||||
| 
 | 
 | ||||||
|     return dolphin; |     return dolphin; | ||||||
| } | } | ||||||
| @ -83,50 +127,72 @@ void dolphin_event_release(Dolphin* dolphin, DolphinEvent* event) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void dolphin_check_butthurt(DolphinState* state) { |  | ||||||
|     furi_assert(state); |  | ||||||
|     float diff_time = difftime(state->data.timestamp, dolphin_state_timestamp()); |  | ||||||
| 
 |  | ||||||
|     if((fabs(diff_time)) > DOLPHIN_TIMEGATE) { |  | ||||||
|         dolphin_state_butthurted(state); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin) { | FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin) { | ||||||
|     return dolphin->pubsub; |     return dolphin->pubsub; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin) { | ||||||
|  |     furi_assert(dolphin); | ||||||
|  |     TickType_t now_ticks = xTaskGetTickCount(); | ||||||
|  |     TickType_t timer_expires_at = xTimerGetExpiryTime(dolphin->clear_limits_timer); | ||||||
|  | 
 | ||||||
|  |     if((timer_expires_at - now_ticks) > HOURS_IN_TICKS(0.1)) { | ||||||
|  |         FuriHalRtcDateTime date; | ||||||
|  |         furi_hal_rtc_get_datetime(&date); | ||||||
|  |         TickType_t now_time_in_ms = ((date.hour * 60 + date.minute) * 60 + date.second) * 1000; | ||||||
|  |         TickType_t time_to_clear_limits = 0; | ||||||
|  | 
 | ||||||
|  |         if(date.hour < 5) { | ||||||
|  |             time_to_clear_limits = HOURS_IN_TICKS(5) - now_time_in_ms; | ||||||
|  |         } else { | ||||||
|  |             time_to_clear_limits = HOURS_IN_TICKS(24 + 5) - now_time_in_ms; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         xTimerChangePeriod(dolphin->clear_limits_timer, time_to_clear_limits, portMAX_DELAY); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| int32_t dolphin_srv(void* p) { | int32_t dolphin_srv(void* p) { | ||||||
|     Dolphin* dolphin = dolphin_alloc(); |     Dolphin* dolphin = dolphin_alloc(); | ||||||
|     furi_record_create("dolphin", dolphin); |     furi_record_create("dolphin", dolphin); | ||||||
| 
 | 
 | ||||||
|     dolphin_state_load(dolphin->state); |     dolphin_state_load(dolphin->state); | ||||||
|  |     xTimerReset(dolphin->butthurt_timer, portMAX_DELAY); | ||||||
|  |     dolphin_update_clear_limits_timer_period(dolphin); | ||||||
|  |     xTimerReset(dolphin->clear_limits_timer, portMAX_DELAY); | ||||||
| 
 | 
 | ||||||
|     DolphinEvent event; |     DolphinEvent event; | ||||||
|     while(1) { |     while(1) { | ||||||
|         if(osMessageQueueGet(dolphin->event_queue, &event, NULL, 60000) == osOK) { |         if(osMessageQueueGet(dolphin->event_queue, &event, NULL, HOURS_IN_TICKS(1)) == osOK) { | ||||||
|             if(event.type == DolphinEventTypeDeed) { |             if(event.type == DolphinEventTypeDeed) { | ||||||
|                 if(dolphin_state_on_deed(dolphin->state, event.deed)) { |                 dolphin_state_on_deed(dolphin->state, event.deed); | ||||||
|                     DolphinPubsubEvent event = DolphinPubsubEventUpdate; |                 DolphinPubsubEvent event = DolphinPubsubEventUpdate; | ||||||
|                     furi_pubsub_publish(dolphin->pubsub, &event); |                 furi_pubsub_publish(dolphin->pubsub, &event); | ||||||
|                 } |                 xTimerReset(dolphin->butthurt_timer, portMAX_DELAY); | ||||||
|  |                 xTimerReset(dolphin->flush_timer, portMAX_DELAY); | ||||||
|             } else if(event.type == DolphinEventTypeStats) { |             } else if(event.type == DolphinEventTypeStats) { | ||||||
|                 // TODO: correct icounter/butthurt changing, stub till then
 |                 event.stats->icounter = dolphin->state->data.icounter; | ||||||
|                 event.stats->icounter = 0; |                 event.stats->butthurt = dolphin->state->data.butthurt; | ||||||
|                 event.stats->butthurt = 0; |  | ||||||
|                 event.stats->timestamp = dolphin->state->data.timestamp; |                 event.stats->timestamp = dolphin->state->data.timestamp; | ||||||
|                 event.stats->level = 1; |                 event.stats->level = dolphin_get_level(dolphin->state->data.icounter); | ||||||
|                 event.stats->level_up_is_pending = 0; |                 event.stats->level_up_is_pending = | ||||||
|  |                     !dolphin_state_xp_to_levelup(dolphin->state->data.icounter); | ||||||
|             } else if(event.type == DolphinEventTypeFlush) { |             } else if(event.type == DolphinEventTypeFlush) { | ||||||
|                 // TODO: correct icounter/butthurt changing, stub till then
 |                 FURI_LOG_I(TAG, "Flush stats"); | ||||||
|                 dolphin->state->data.butthurt = 0; |                 dolphin_state_save(dolphin->state); | ||||||
|                 dolphin->state->data.icounter = 0; |             } else if(event.type == DolphinEventTypeClearLimits) { | ||||||
|  |                 FURI_LOG_I(TAG, "Clear limits"); | ||||||
|  |                 dolphin_state_clear_limits(dolphin->state); | ||||||
|  |                 dolphin_state_save(dolphin->state); | ||||||
|  |             } else if(event.type == DolphinEventTypeIncreaseButthurt) { | ||||||
|  |                 FURI_LOG_I(TAG, "Increase butthurt"); | ||||||
|  |                 dolphin_state_butthurted(dolphin->state); | ||||||
|                 dolphin_state_save(dolphin->state); |                 dolphin_state_save(dolphin->state); | ||||||
|             } |             } | ||||||
|             dolphin_event_release(dolphin, &event); |             dolphin_event_release(dolphin, &event); | ||||||
|         } else { |         } else { | ||||||
|             dolphin_check_butthurt(dolphin->state); |             /* once per hour check rtc time is not changed */ | ||||||
|             dolphin_state_save(dolphin->state); |             dolphin_update_clear_limits_timer_period(dolphin); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,6 +5,10 @@ | |||||||
| #include "helpers/dolphin_deed.h" | #include "helpers/dolphin_deed.h" | ||||||
| #include <stdbool.h> | #include <stdbool.h> | ||||||
| 
 | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| typedef struct Dolphin Dolphin; | typedef struct Dolphin Dolphin; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| @ -19,6 +23,13 @@ typedef enum { | |||||||
|     DolphinPubsubEventUpdate, |     DolphinPubsubEventUpdate, | ||||||
| } DolphinPubsubEvent; | } DolphinPubsubEvent; | ||||||
| 
 | 
 | ||||||
|  | #define DOLPHIN_DEED(deed)                                        \ | ||||||
|  |     do {                                                          \ | ||||||
|  |         Dolphin* dolphin = (Dolphin*)furi_record_open("dolphin"); \ | ||||||
|  |         dolphin_deed(dolphin, deed);                              \ | ||||||
|  |         furi_record_close("dolphin");                             \ | ||||||
|  |     } while(0) | ||||||
|  | 
 | ||||||
| /** Deed complete notification. Call it on deed completion.
 | /** Deed complete notification. Call it on deed completion.
 | ||||||
|  * See dolphin_deed.h for available deeds. In futures it will become part of assets. |  * See dolphin_deed.h for available deeds. In futures it will become part of assets. | ||||||
|  * Thread safe, async |  * Thread safe, async | ||||||
| @ -38,3 +49,7 @@ void dolphin_flush(Dolphin* dolphin); | |||||||
| void dolphin_upgrade_level(Dolphin* dolphin); | void dolphin_upgrade_level(Dolphin* dolphin); | ||||||
| 
 | 
 | ||||||
| FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin); | FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | |||||||
| @ -11,9 +11,8 @@ typedef enum { | |||||||
|     DolphinEventTypeDeed, |     DolphinEventTypeDeed, | ||||||
|     DolphinEventTypeStats, |     DolphinEventTypeStats, | ||||||
|     DolphinEventTypeFlush, |     DolphinEventTypeFlush, | ||||||
|     DolphinEventTypeAnimationStartNewIdle, |     DolphinEventTypeIncreaseButthurt, | ||||||
|     DolphinEventTypeAnimationCheckBlocking, |     DolphinEventTypeClearLimits, | ||||||
|     DolphinEventTypeAnimationInteract, |  | ||||||
| } DolphinEventType; | } DolphinEventType; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| @ -31,6 +30,9 @@ struct Dolphin { | |||||||
|     // Queue
 |     // Queue
 | ||||||
|     osMessageQueueId_t event_queue; |     osMessageQueueId_t event_queue; | ||||||
|     FuriPubSub* pubsub; |     FuriPubSub* pubsub; | ||||||
|  |     TimerHandle_t butthurt_timer; | ||||||
|  |     TimerHandle_t flush_timer; | ||||||
|  |     TimerHandle_t clear_limits_timer; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| Dolphin* dolphin_alloc(); | Dolphin* dolphin_alloc(); | ||||||
|  | |||||||
| @ -1,14 +1,65 @@ | |||||||
| #include "dolphin_deed.h" | #include "dolphin_deed.h" | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| 
 | 
 | ||||||
| static const DolphinDeedWeight dolphin_deed_weights[DolphinDeedMax] = { | static const DolphinDeedWeight dolphin_deed_weights[] = { | ||||||
|     {1, -1, 60}, |     {1, DolphinAppSubGhz}, // DolphinDeedSubGhzReceiverInfo
 | ||||||
|     {1, -1, 60}, |     {3, DolphinAppSubGhz}, // DolphinDeedSubGhzSave
 | ||||||
|     {1, -1, 60}, |     {1, DolphinAppSubGhz}, // DolphinDeedSubGhzRawRec
 | ||||||
|     {-1, 1, 60}, |     {2, DolphinAppSubGhz}, // DolphinDeedSubGhzAddManually
 | ||||||
|  |     {2, DolphinAppSubGhz}, // DolphinDeedSubGhzSend
 | ||||||
|  |     {1, DolphinAppSubGhz}, // DolphinDeedSubGhzFrequencyAnalyzer
 | ||||||
|  | 
 | ||||||
|  |     {1, DolphinAppRfid}, // DolphinDeedRfidRead
 | ||||||
|  |     {3, DolphinAppRfid}, // DolphinDeedRfidReadSuccess
 | ||||||
|  |     {3, DolphinAppRfid}, // DolphinDeedRfidSave
 | ||||||
|  |     {2, DolphinAppRfid}, // DolphinDeedRfidEmulate
 | ||||||
|  |     {2, DolphinAppRfid}, // DolphinDeedRfidAdd
 | ||||||
|  | 
 | ||||||
|  |     {1, DolphinAppNfc}, // DolphinDeedNfcRead
 | ||||||
|  |     {3, DolphinAppNfc}, // DolphinDeedNfcReadSuccess
 | ||||||
|  |     {3, DolphinAppNfc}, // DolphinDeedNfcSave
 | ||||||
|  |     {2, DolphinAppNfc}, // DolphinDeedNfcEmulate
 | ||||||
|  |     {2, DolphinAppNfc}, // DolphinDeedNfcAdd
 | ||||||
|  | 
 | ||||||
|  |     {1, DolphinAppIr}, // DolphinDeedIrSend
 | ||||||
|  |     {3, DolphinAppIr}, // DolphinDeedIrLearnSuccess
 | ||||||
|  |     {3, DolphinAppIr}, // DolphinDeedIrSave
 | ||||||
|  |     {2, DolphinAppIr}, // DolphinDeedIrBruteForce
 | ||||||
|  | 
 | ||||||
|  |     {1, DolphinAppIbutton}, // DolphinDeedIbuttonRead
 | ||||||
|  |     {3, DolphinAppIbutton}, // DolphinDeedIbuttonReadSuccess
 | ||||||
|  |     {3, DolphinAppIbutton}, // DolphinDeedIbuttonSave
 | ||||||
|  |     {2, DolphinAppIbutton}, // DolphinDeedIbuttonEmulate
 | ||||||
|  |     {2, DolphinAppIbutton}, // DolphinDeedIbuttonAdd
 | ||||||
|  | 
 | ||||||
|  |     {3, DolphinAppBadusb}, // DolphinDeedBadUsbPlayScript
 | ||||||
|  |     {3, DolphinAppU2f}, // DolphinDeedU2fAuthorized
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const DolphinDeedWeight* dolphin_deed_weight(DolphinDeed deed) { | static uint8_t dolphin_deed_limits[] = { | ||||||
|     furi_assert(deed < DolphinDeedMax); |     15, // DolphinAppSubGhz
 | ||||||
|     return &dolphin_deed_weights[deed]; |     15, // DolphinAppRfid
 | ||||||
|  |     15, // DolphinAppNfc
 | ||||||
|  |     15, // DolphinAppIr
 | ||||||
|  |     15, // DolphinAppIbutton
 | ||||||
|  |     15, // DolphinAppBadusb
 | ||||||
|  |     15, // DolphinAppU2f
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | _Static_assert(COUNT_OF(dolphin_deed_weights) == DolphinDeedMAX, "dolphin_deed_weights size error"); | ||||||
|  | _Static_assert(COUNT_OF(dolphin_deed_limits) == DolphinAppMAX, "dolphin_deed_limits size error"); | ||||||
|  | 
 | ||||||
|  | uint8_t dolphin_deed_get_weight(DolphinDeed deed) { | ||||||
|  |     furi_check(deed < DolphinDeedMAX); | ||||||
|  |     return dolphin_deed_weights[deed].icounter; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | DolphinApp dolphin_deed_get_app(DolphinDeed deed) { | ||||||
|  |     furi_check(deed < DolphinDeedMAX); | ||||||
|  |     return dolphin_deed_weights[deed].app; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint8_t dolphin_deed_get_app_limit(DolphinApp app) { | ||||||
|  |     furi_check(app < DolphinAppMAX); | ||||||
|  |     return dolphin_deed_limits[app]; | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,23 +2,73 @@ | |||||||
| 
 | 
 | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| 
 | 
 | ||||||
| /* Countable deed that affects icounter*/ | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     // iButton
 |     DolphinAppSubGhz, | ||||||
|     DolphinDeedIButtonRead, |     DolphinAppRfid, | ||||||
|     DolphinDeedIButtonWrite, |     DolphinAppNfc, | ||||||
|     DolphinDeedIButtonEmulate, |     DolphinAppIr, | ||||||
|     // for debug
 |     DolphinAppIbutton, | ||||||
|     DolphinDeedWrong, |     DolphinAppBadusb, | ||||||
|     // Special value, do not use
 |     DolphinAppU2f, | ||||||
|     DolphinDeedMax |     DolphinAppMAX, | ||||||
|  | } DolphinApp; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     DolphinDeedSubGhzReceiverInfo, | ||||||
|  |     DolphinDeedSubGhzSave, | ||||||
|  |     DolphinDeedSubGhzRawRec, | ||||||
|  |     DolphinDeedSubGhzAddManually, | ||||||
|  |     DolphinDeedSubGhzSend, | ||||||
|  |     DolphinDeedSubGhzFrequencyAnalyzer, | ||||||
|  | 
 | ||||||
|  |     DolphinDeedRfidRead, | ||||||
|  |     DolphinDeedRfidReadSuccess, | ||||||
|  |     DolphinDeedRfidSave, | ||||||
|  |     DolphinDeedRfidEmulate, | ||||||
|  |     DolphinDeedRfidAdd, | ||||||
|  | 
 | ||||||
|  |     DolphinDeedNfcRead, | ||||||
|  |     DolphinDeedNfcReadSuccess, | ||||||
|  |     DolphinDeedNfcSave, | ||||||
|  |     DolphinDeedNfcEmulate, | ||||||
|  |     DolphinDeedNfcAdd, | ||||||
|  | 
 | ||||||
|  |     DolphinDeedIrSend, | ||||||
|  |     DolphinDeedIrLearnSuccess, | ||||||
|  |     DolphinDeedIrSave, | ||||||
|  |     DolphinDeedIrBruteForce, | ||||||
|  | 
 | ||||||
|  |     DolphinDeedIbuttonRead, | ||||||
|  |     DolphinDeedIbuttonReadSuccess, | ||||||
|  |     DolphinDeedIbuttonSave, | ||||||
|  |     DolphinDeedIbuttonEmulate, | ||||||
|  |     DolphinDeedIbuttonAdd, | ||||||
|  | 
 | ||||||
|  |     DolphinDeedBadUsbPlayScript, | ||||||
|  | 
 | ||||||
|  |     DolphinDeedU2fAuthorized, | ||||||
|  | 
 | ||||||
|  |     DolphinDeedMAX | ||||||
| } DolphinDeed; | } DolphinDeed; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     int32_t icounter; // how many icounter get by Deed
 |     uint8_t icounter; | ||||||
|     int32_t butthurt; // how many icounter get by Deed
 |     DolphinApp app; | ||||||
|     uint32_t limit_value; // how many deeds in limit interval
 |  | ||||||
|     uint32_t limit_interval; // interval, in minutes
 |  | ||||||
| } DolphinDeedWeight; | } DolphinDeedWeight; | ||||||
| 
 | 
 | ||||||
| const DolphinDeedWeight* dolphin_deed_weight(DolphinDeed deed); | typedef struct { | ||||||
|  |     DolphinApp app; | ||||||
|  |     uint8_t icounter_limit; | ||||||
|  | } DolphinDeedLimits; | ||||||
|  | 
 | ||||||
|  | DolphinApp dolphin_deed_get_app(DolphinDeed deed); | ||||||
|  | uint8_t dolphin_deed_get_app_limit(DolphinApp app); | ||||||
|  | uint8_t dolphin_deed_get_weight(DolphinDeed deed); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "dolphin_state.h" | #include "dolphin_state.h" | ||||||
|  | #include "dolphin/helpers/dolphin_deed.h" | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
| #include <storage/storage.h> | #include <storage/storage.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| @ -10,9 +11,8 @@ | |||||||
| #define DOLPHIN_STATE_PATH "/int/dolphin.state" | #define DOLPHIN_STATE_PATH "/int/dolphin.state" | ||||||
| #define DOLPHIN_STATE_HEADER_MAGIC 0xD0 | #define DOLPHIN_STATE_HEADER_MAGIC 0xD0 | ||||||
| #define DOLPHIN_STATE_HEADER_VERSION 0x01 | #define DOLPHIN_STATE_HEADER_VERSION 0x01 | ||||||
| #define DOLPHIN_LVL_THRESHOLD 20.0f | #define LEVEL2_THRESHOLD 735 | ||||||
| #define LEVEL2_THRESHOLD 20 | #define LEVEL3_THRESHOLD 2940 | ||||||
| #define LEVEL3_THRESHOLD 100 |  | ||||||
| #define BUTTHURT_MAX 14 | #define BUTTHURT_MAX 14 | ||||||
| #define BUTTHURT_MIN 0 | #define BUTTHURT_MIN 0 | ||||||
| 
 | 
 | ||||||
| @ -125,50 +125,68 @@ uint32_t dolphin_state_xp_to_levelup(uint32_t icounter) { | |||||||
|     return threshold - icounter; |     return threshold - icounter; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) { | void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) { | ||||||
|     const DolphinDeedWeight* deed_weight = dolphin_deed_weight(deed); |     DolphinApp app = dolphin_deed_get_app(deed); | ||||||
|     int32_t icounter = dolphin_state->data.icounter + deed_weight->icounter; |     int8_t weight_limit = | ||||||
|     bool level_up = false; |         dolphin_deed_get_app_limit(app) - dolphin_state->data.icounter_daily_limit[app]; | ||||||
|     bool mood_changed = false; |     uint8_t deed_weight = CLAMP(dolphin_deed_get_weight(deed), weight_limit, 0); | ||||||
| 
 |  | ||||||
|     if(icounter <= 0) { |  | ||||||
|         icounter = 0; |  | ||||||
|         if(dolphin_state->data.icounter == 0) { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     uint8_t xp_to_levelup = dolphin_state_xp_to_levelup(dolphin_state->data.icounter); |     uint8_t xp_to_levelup = dolphin_state_xp_to_levelup(dolphin_state->data.icounter); | ||||||
|     if(xp_to_levelup) { |     if(xp_to_levelup) { | ||||||
|         level_up = true; |         deed_weight = MIN(xp_to_levelup, deed_weight); | ||||||
|         dolphin_state->data.icounter += MIN(xp_to_levelup, deed_weight->icounter); |         dolphin_state->data.icounter += deed_weight; | ||||||
|  |         dolphin_state->data.icounter_daily_limit[app] += deed_weight; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     uint32_t new_butthurt = CLAMP( |     /* decrease butthurt:
 | ||||||
|         ((int32_t)dolphin_state->data.butthurt) + deed_weight->butthurt, |      * 0 deeds accumulating --> 0 butthurt | ||||||
|         BUTTHURT_MAX, |      * +1....+15 deeds accumulating --> -1 butthurt | ||||||
|         BUTTHURT_MIN); |      * +16...+30 deeds accumulating --> -1 butthurt | ||||||
|  |      * +31...+45 deeds accumulating --> -1 butthurt | ||||||
|  |      * +46...... deeds accumulating --> -1 butthurt | ||||||
|  |      * -4 butthurt per day is maximum | ||||||
|  |      * */ | ||||||
|  |     uint8_t butthurt_icounter_level_old = dolphin_state->data.butthurt_daily_limit / 15 + | ||||||
|  |                                           !!(dolphin_state->data.butthurt_daily_limit % 15); | ||||||
|  |     dolphin_state->data.butthurt_daily_limit = | ||||||
|  |         CLAMP(dolphin_state->data.butthurt_daily_limit + deed_weight, 46, 0); | ||||||
|  |     uint8_t butthurt_icounter_level_new = dolphin_state->data.butthurt_daily_limit / 15 + | ||||||
|  |                                           !!(dolphin_state->data.butthurt_daily_limit % 15); | ||||||
|  |     int32_t new_butthurt = ((int32_t)dolphin_state->data.butthurt) - | ||||||
|  |                            (butthurt_icounter_level_old != butthurt_icounter_level_new); | ||||||
|  |     new_butthurt = CLAMP(new_butthurt, BUTTHURT_MAX, BUTTHURT_MIN); | ||||||
| 
 | 
 | ||||||
|     if(!!dolphin_state->data.butthurt != !!new_butthurt) { |  | ||||||
|         mood_changed = true; |  | ||||||
|     } |  | ||||||
|     dolphin_state->data.butthurt = new_butthurt; |     dolphin_state->data.butthurt = new_butthurt; | ||||||
|     dolphin_state->data.timestamp = dolphin_state_timestamp(); |     dolphin_state->data.timestamp = dolphin_state_timestamp(); | ||||||
|     dolphin_state->dirty = true; |     dolphin_state->dirty = true; | ||||||
| 
 | 
 | ||||||
|     return level_up || mood_changed; |     FURI_LOG_D( | ||||||
|  |         TAG, | ||||||
|  |         "icounter %d, butthurt %d", | ||||||
|  |         dolphin_state->data.icounter, | ||||||
|  |         dolphin_state->data.butthurt); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void dolphin_state_butthurted(DolphinState* dolphin_state) { | void dolphin_state_butthurted(DolphinState* dolphin_state) { | ||||||
|     if(dolphin_state->data.butthurt < BUTTHURT_MAX) { |     if(dolphin_state->data.butthurt < BUTTHURT_MAX) { | ||||||
|         dolphin_state->data.butthurt++; |         dolphin_state->data.butthurt++; | ||||||
|         FURI_LOG_I("DolphinState", "Increasing butthurt"); |  | ||||||
|         dolphin_state->data.timestamp = dolphin_state_timestamp(); |         dolphin_state->data.timestamp = dolphin_state_timestamp(); | ||||||
|         dolphin_state->dirty = true; |         dolphin_state->dirty = true; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void dolphin_state_increase_level(DolphinState* dolphin_state) { | void dolphin_state_increase_level(DolphinState* dolphin_state) { | ||||||
|  |     furi_assert(dolphin_state_is_levelup(dolphin_state->data.icounter)); | ||||||
|     ++dolphin_state->data.icounter; |     ++dolphin_state->data.icounter; | ||||||
|     dolphin_state->dirty = true; |     dolphin_state->dirty = true; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void dolphin_state_clear_limits(DolphinState* dolphin_state) { | ||||||
|  |     furi_assert(dolphin_state); | ||||||
|  | 
 | ||||||
|  |     for(int i = 0; i < DolphinAppMAX; ++i) { | ||||||
|  |         dolphin_state->data.icounter_daily_limit[i] = 0; | ||||||
|  |     } | ||||||
|  |     dolphin_state->data.butthurt_daily_limit = 0; | ||||||
|  |     dolphin_state->dirty = true; | ||||||
|  | } | ||||||
|  | |||||||
| @ -7,10 +7,8 @@ | |||||||
| 
 | 
 | ||||||
| typedef struct DolphinState DolphinState; | typedef struct DolphinState DolphinState; | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint32_t limit_ibutton; |     uint8_t icounter_daily_limit[DolphinAppMAX]; | ||||||
|     uint32_t limit_nfc; |     uint8_t butthurt_daily_limit; | ||||||
|     uint32_t limit_ir; |  | ||||||
|     uint32_t limit_rfid; |  | ||||||
| 
 | 
 | ||||||
|     uint32_t flags; |     uint32_t flags; | ||||||
|     uint32_t icounter; |     uint32_t icounter; | ||||||
| @ -31,11 +29,11 @@ bool dolphin_state_save(DolphinState* dolphin_state); | |||||||
| 
 | 
 | ||||||
| bool dolphin_state_load(DolphinState* dolphin_state); | bool dolphin_state_load(DolphinState* dolphin_state); | ||||||
| 
 | 
 | ||||||
| void dolphin_state_clear(DolphinState* dolphin_state); | void dolphin_state_clear_limits(DolphinState* dolphin_state); | ||||||
| 
 | 
 | ||||||
| uint64_t dolphin_state_timestamp(); | uint64_t dolphin_state_timestamp(); | ||||||
| 
 | 
 | ||||||
| bool dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed); | void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed); | ||||||
| 
 | 
 | ||||||
| void dolphin_state_butthurted(DolphinState* dolphin_state); | void dolphin_state_butthurted(DolphinState* dolphin_state); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -95,5 +95,5 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) { | |||||||
| 
 | 
 | ||||||
| void gpio_scene_start_on_exit(void* context) { | void gpio_scene_start_on_exit(void* context) { | ||||||
|     GpioApp* app = context; |     GpioApp* app = context; | ||||||
|     variable_item_list_clean(app->var_item_list); |     variable_item_list_reset(app->var_item_list); | ||||||
| } | } | ||||||
|  | |||||||
| @ -135,6 +135,6 @@ void gpio_scene_usb_uart_cfg_on_exit(void* context) { | |||||||
|         app->scene_manager, |         app->scene_manager, | ||||||
|         GpioAppViewUsbUartCfg, |         GpioAppViewUsbUartCfg, | ||||||
|         variable_item_list_get_selected_item_index(app->var_item_list)); |         variable_item_list_get_selected_item_index(app->var_item_list)); | ||||||
|     variable_item_list_clean(app->var_item_list); |     variable_item_list_reset(app->var_item_list); | ||||||
|     free(cfg_set); |     free(cfg_set); | ||||||
| } | } | ||||||
|  | |||||||
| @ -154,7 +154,7 @@ static int32_t usb_uart_worker(void* context) { | |||||||
|     furi_thread_set_context(usb_uart->tx_thread, usb_uart); |     furi_thread_set_context(usb_uart->tx_thread, usb_uart); | ||||||
|     furi_thread_set_callback(usb_uart->tx_thread, usb_uart_tx_thread); |     furi_thread_set_callback(usb_uart->tx_thread, usb_uart_tx_thread); | ||||||
| 
 | 
 | ||||||
|     UsbInterface* usb_mode_prev = furi_hal_usb_get_config(); |     FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); | ||||||
|     usb_uart_vcp_init(usb_uart, usb_uart->cfg.vcp_ch); |     usb_uart_vcp_init(usb_uart, usb_uart->cfg.vcp_ch); | ||||||
|     usb_uart_serial_init(usb_uart, usb_uart->cfg.uart_ch); |     usb_uart_serial_init(usb_uart, usb_uart->cfg.uart_ch); | ||||||
|     usb_uart_set_baudrate(usb_uart, usb_uart->cfg.baudrate); |     usb_uart_set_baudrate(usb_uart, usb_uart->cfg.baudrate); | ||||||
|  | |||||||
							
								
								
									
										17
									
								
								applications/gui/canvas.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										17
									
								
								applications/gui/canvas.c
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -4,6 +4,7 @@ | |||||||
| 
 | 
 | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <furi_hal.h> | #include <furi_hal.h> | ||||||
|  | #include <stdint.h> | ||||||
| #include <u8g2_glue.h> | #include <u8g2_glue.h> | ||||||
| 
 | 
 | ||||||
| const CanvasFontParameters canvas_font_params[FontTotalNumber] = { | const CanvasFontParameters canvas_font_params[FontTotalNumber] = { | ||||||
| @ -202,6 +203,22 @@ uint8_t canvas_glyph_width(Canvas* canvas, char symbol) { | |||||||
|     return u8g2_GetGlyphWidth(&canvas->fb, symbol); |     return u8g2_GetGlyphWidth(&canvas->fb, symbol); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void canvas_draw_bitmap( | ||||||
|  |     Canvas* canvas, | ||||||
|  |     uint8_t x, | ||||||
|  |     uint8_t y, | ||||||
|  |     uint8_t width, | ||||||
|  |     uint8_t height, | ||||||
|  |     const uint8_t* compressed_bitmap_data) { | ||||||
|  |     furi_assert(canvas); | ||||||
|  | 
 | ||||||
|  |     x += canvas->offset_x; | ||||||
|  |     y += canvas->offset_y; | ||||||
|  |     uint8_t* bitmap_data = NULL; | ||||||
|  |     furi_hal_compress_icon_decode(compressed_bitmap_data, &bitmap_data); | ||||||
|  |     u8g2_DrawXBM(&canvas->fb, x, y, width, height, bitmap_data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void canvas_draw_icon_animation( | void canvas_draw_icon_animation( | ||||||
|     Canvas* canvas, |     Canvas* canvas, | ||||||
|     uint8_t x, |     uint8_t x, | ||||||
|  | |||||||
| @ -178,6 +178,23 @@ uint16_t canvas_string_width(Canvas* canvas, const char* str); | |||||||
|  */ |  */ | ||||||
| uint8_t canvas_glyph_width(Canvas* canvas, char symbol); | uint8_t canvas_glyph_width(Canvas* canvas, char symbol); | ||||||
| 
 | 
 | ||||||
|  | /** Draw bitmap picture at position defined by x,y.
 | ||||||
|  |  * | ||||||
|  |  * @param      canvas                   Canvas instance | ||||||
|  |  * @param      x                        x coordinate | ||||||
|  |  * @param      y                        y coordinate | ||||||
|  |  * @param      width                    width of bitmap | ||||||
|  |  * @param      height                   height of bitmap | ||||||
|  |  * @param      compressed_bitmap_data   compressed bitmap data | ||||||
|  |  */ | ||||||
|  | void canvas_draw_bitmap( | ||||||
|  |     Canvas* canvas, | ||||||
|  |     uint8_t x, | ||||||
|  |     uint8_t y, | ||||||
|  |     uint8_t width, | ||||||
|  |     uint8_t height, | ||||||
|  |     const uint8_t* compressed_bitmap_data); | ||||||
|  | 
 | ||||||
| /** Draw animation at position defined by x,y.
 | /** Draw animation at position defined by x,y.
 | ||||||
|  * |  * | ||||||
|  * @param      canvas          Canvas instance |  * @param      canvas          Canvas instance | ||||||
|  | |||||||
| @ -45,7 +45,7 @@ bool gui_redraw_fs(Gui* gui) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void gui_redraw_status_bar(Gui* gui) { | static void gui_redraw_status_bar(Gui* gui, bool need_attention) { | ||||||
|     ViewPortArray_it_t it; |     ViewPortArray_it_t it; | ||||||
|     uint8_t x; |     uint8_t x; | ||||||
|     uint8_t x_used = 0; |     uint8_t x_used = 0; | ||||||
| @ -140,6 +140,30 @@ void gui_redraw_status_bar(Gui* gui) { | |||||||
|         } |         } | ||||||
|         ViewPortArray_next(it); |         ViewPortArray_next(it); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     if(need_attention) { | ||||||
|  |         width = icon_get_width(&I_Attention_5x8); | ||||||
|  |         canvas_frame_set(gui->canvas, 0, GUI_STATUS_BAR_Y, x + width + 5, GUI_STATUS_BAR_HEIGHT); | ||||||
|  |         canvas_draw_rframe( | ||||||
|  |             gui->canvas, 0, 0, canvas_width(gui->canvas), canvas_height(gui->canvas), 1); | ||||||
|  |         canvas_draw_line(gui->canvas, 1, 1, 1, canvas_height(gui->canvas) - 2); | ||||||
|  |         canvas_draw_line( | ||||||
|  |             gui->canvas, | ||||||
|  |             2, | ||||||
|  |             canvas_height(gui->canvas) - 2, | ||||||
|  |             canvas_width(gui->canvas) - 2, | ||||||
|  |             canvas_height(gui->canvas) - 2); | ||||||
|  | 
 | ||||||
|  |         canvas_frame_set(gui->canvas, x, GUI_STATUS_BAR_Y, width + 5, GUI_STATUS_BAR_HEIGHT); | ||||||
|  | 
 | ||||||
|  |         canvas_set_color(gui->canvas, ColorWhite); | ||||||
|  |         canvas_draw_box(gui->canvas, 2, 1, width + 2, 10); | ||||||
|  |         canvas_set_color(gui->canvas, ColorBlack); | ||||||
|  | 
 | ||||||
|  |         canvas_frame_set( | ||||||
|  |             gui->canvas, x + 3, GUI_STATUS_BAR_Y + 2, width, GUI_STATUS_BAR_WORKAREA_HEIGHT); | ||||||
|  |         canvas_draw_icon(gui->canvas, 0, 0, &I_Attention_5x8); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool gui_redraw_window(Gui* gui) { | bool gui_redraw_window(Gui* gui) { | ||||||
| @ -171,11 +195,19 @@ void gui_redraw(Gui* gui) { | |||||||
| 
 | 
 | ||||||
|     canvas_reset(gui->canvas); |     canvas_reset(gui->canvas); | ||||||
| 
 | 
 | ||||||
|     if(!gui_redraw_fs(gui)) { |     if(gui->lockdown) { | ||||||
|         if(!gui_redraw_window(gui)) { |         gui_redraw_desktop(gui); | ||||||
|             gui_redraw_desktop(gui); |         bool need_attention = | ||||||
|  |             (gui_view_port_find_enabled(gui->layers[GuiLayerWindow]) != 0 || | ||||||
|  |              gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]) != 0); | ||||||
|  |         gui_redraw_status_bar(gui, need_attention); | ||||||
|  |     } else { | ||||||
|  |         if(!gui_redraw_fs(gui)) { | ||||||
|  |             if(!gui_redraw_window(gui)) { | ||||||
|  |                 gui_redraw_desktop(gui); | ||||||
|  |             } | ||||||
|  |             gui_redraw_status_bar(gui, false); | ||||||
|         } |         } | ||||||
|         gui_redraw_status_bar(gui); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     canvas_commit(gui->canvas); |     canvas_commit(gui->canvas); | ||||||
| @ -210,9 +242,15 @@ void gui_input(Gui* gui, InputEvent* input_event) { | |||||||
| 
 | 
 | ||||||
|     gui_lock(gui); |     gui_lock(gui); | ||||||
| 
 | 
 | ||||||
|     ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]); |     ViewPort* view_port = NULL; | ||||||
|     if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerWindow]); | 
 | ||||||
|     if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]); |     if(gui->lockdown) { | ||||||
|  |         view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]); | ||||||
|  |     } else { | ||||||
|  |         view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]); | ||||||
|  |         if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerWindow]); | ||||||
|  |         if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     if(!(gui->ongoing_input & ~key_bit) && input_event->type == InputTypePress) { |     if(!(gui->ongoing_input & ~key_bit) && input_event->type == InputTypePress) { | ||||||
|         gui->ongoing_input_view_port = view_port; |         gui->ongoing_input_view_port = view_port; | ||||||
| @ -366,10 +404,18 @@ void gui_set_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, vo | |||||||
|     gui_unlock(gui); |     gui_unlock(gui); | ||||||
| 
 | 
 | ||||||
|     if(callback != NULL) { |     if(callback != NULL) { | ||||||
|         gui_redraw(gui); |         gui_update(gui); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void gui_set_lockdown(Gui* gui, bool lockdown) { | ||||||
|  |     furi_assert(gui); | ||||||
|  |     gui_lock(gui); | ||||||
|  |     gui->lockdown = lockdown; | ||||||
|  |     gui_unlock(gui); | ||||||
|  |     gui_update(gui); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| Gui* gui_alloc() { | Gui* gui_alloc() { | ||||||
|     Gui* gui = furi_alloc(sizeof(Gui)); |     Gui* gui = furi_alloc(sizeof(Gui)); | ||||||
|     // Thread ID
 |     // Thread ID
 | ||||||
|  | |||||||
| @ -79,6 +79,16 @@ void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port); | |||||||
|  */ |  */ | ||||||
| void gui_set_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context); | void gui_set_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context); | ||||||
| 
 | 
 | ||||||
|  | /** Set lockdown mode
 | ||||||
|  |  * | ||||||
|  |  * When lockdown mode is enabled, only GuiLayerDesktop is shown. | ||||||
|  |  * This feature prevents services from showing sensitive information when flipper is locked. | ||||||
|  |  * | ||||||
|  |  * @param      gui       Gui instance | ||||||
|  |  * @param      lockdown  bool, true if enabled | ||||||
|  |  */ | ||||||
|  | void gui_set_lockdown(Gui* gui, bool lockdown); | ||||||
|  | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | |||||||
| @ -49,6 +49,7 @@ struct Gui { | |||||||
|     osMutexId_t mutex; |     osMutexId_t mutex; | ||||||
| 
 | 
 | ||||||
|     // Layers and Canvas
 |     // Layers and Canvas
 | ||||||
|  |     bool lockdown; | ||||||
|     ViewPortArray_t layers[GuiLayerMAX]; |     ViewPortArray_t layers[GuiLayerMAX]; | ||||||
|     Canvas* canvas; |     Canvas* canvas; | ||||||
|     GuiCanvasCommitCallback canvas_callback; |     GuiCanvasCommitCallback canvas_callback; | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ | |||||||
|  * GUI: internal Icon API |  * GUI: internal Icon API | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
|  | #pragma once | ||||||
| #include "icon.h" | #include "icon.h" | ||||||
| 
 | 
 | ||||||
| struct Icon { | struct Icon { | ||||||
|  | |||||||
| @ -241,7 +241,7 @@ View* button_menu_get_view(ButtonMenu* button_menu) { | |||||||
|     return button_menu->view; |     return button_menu->view; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void button_menu_clean(ButtonMenu* button_menu) { | void button_menu_reset(ButtonMenu* button_menu) { | ||||||
|     furi_assert(button_menu); |     furi_assert(button_menu); | ||||||
| 
 | 
 | ||||||
|     with_view_model( |     with_view_model( | ||||||
|  | |||||||
| @ -39,7 +39,7 @@ View* button_menu_get_view(ButtonMenu* button_menu); | |||||||
|  * |  * | ||||||
|  * @param      button_menu  ButtonMenu instance |  * @param      button_menu  ButtonMenu instance | ||||||
|  */ |  */ | ||||||
| void button_menu_clean(ButtonMenu* button_menu); | void button_menu_reset(ButtonMenu* button_menu); | ||||||
| 
 | 
 | ||||||
| /** Add item to button menu instance
 | /** Add item to button menu instance
 | ||||||
|  * |  * | ||||||
|  | |||||||
| @ -112,7 +112,7 @@ void button_panel_reserve(ButtonPanel* button_panel, size_t reserve_x, size_t re | |||||||
| void button_panel_free(ButtonPanel* button_panel) { | void button_panel_free(ButtonPanel* button_panel) { | ||||||
|     furi_assert(button_panel); |     furi_assert(button_panel); | ||||||
| 
 | 
 | ||||||
|     button_panel_clean(button_panel); |     button_panel_reset(button_panel); | ||||||
| 
 | 
 | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         button_panel->view, (ButtonPanelModel * model) { |         button_panel->view, (ButtonPanelModel * model) { | ||||||
| @ -125,7 +125,7 @@ void button_panel_free(ButtonPanel* button_panel) { | |||||||
|     free(button_panel); |     free(button_panel); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void button_panel_clean(ButtonPanel* button_panel) { | void button_panel_reset(ButtonPanel* button_panel) { | ||||||
|     furi_assert(button_panel); |     furi_assert(button_panel); | ||||||
| 
 | 
 | ||||||
|     with_view_model( |     with_view_model( | ||||||
|  | |||||||
| @ -39,7 +39,7 @@ void button_panel_free(ButtonPanel* button_panel); | |||||||
|  * |  * | ||||||
|  * @param      button_panel  ButtonPanel instance |  * @param      button_panel  ButtonPanel instance | ||||||
|  */ |  */ | ||||||
| void button_panel_clean(ButtonPanel* button_panel); | void button_panel_reset(ButtonPanel* button_panel); | ||||||
| 
 | 
 | ||||||
| /** Reserve space for adding items.
 | /** Reserve space for adding items.
 | ||||||
|  * |  * | ||||||
|  | |||||||
| @ -244,7 +244,7 @@ void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text) { | |||||||
|         }); |         }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void dialog_ex_clean(DialogEx* dialog_ex) { | void dialog_ex_reset(DialogEx* dialog_ex) { | ||||||
|     furi_assert(dialog_ex); |     furi_assert(dialog_ex); | ||||||
|     TextElement clean_text_el = { |     TextElement clean_text_el = { | ||||||
|         .text = NULL, .x = 0, .y = 0, .horizontal = AlignLeft, .vertical = AlignLeft}; |         .text = NULL, .x = 0, .y = 0, .horizontal = AlignLeft, .vertical = AlignLeft}; | ||||||
|  | |||||||
| @ -143,7 +143,7 @@ void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text); | |||||||
|  * |  * | ||||||
|  * @param      dialog_ex  DialogEx instance |  * @param      dialog_ex  DialogEx instance | ||||||
|  */ |  */ | ||||||
| void dialog_ex_clean(DialogEx* dialog_ex); | void dialog_ex_reset(DialogEx* dialog_ex); | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
|  | |||||||
| @ -77,7 +77,7 @@ static bool file_select_input_callback(InputEvent* event, void* context) { | |||||||
|     FileSelect* file_select = (FileSelect*)context; |     FileSelect* file_select = (FileSelect*)context; | ||||||
|     bool consumed = false; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event->type == InputTypeShort) { |     if((event->type == InputTypeShort) | (event->type == InputTypeRepeat)) { | ||||||
|         if(!file_select->init_completed) { |         if(!file_select->init_completed) { | ||||||
|             if(!file_select_init_inner(file_select)) { |             if(!file_select_init_inner(file_select)) { | ||||||
|                 file_select->callback(false, file_select->context); |                 file_select->callback(false, file_select->context); | ||||||
|  | |||||||
| @ -87,6 +87,14 @@ static bool menu_input_callback(InputEvent* event, void* context) { | |||||||
|             consumed = true; |             consumed = true; | ||||||
|             menu_process_ok(menu); |             menu_process_ok(menu); | ||||||
|         } |         } | ||||||
|  |     } else if(event->type == InputTypeRepeat) { | ||||||
|  |         if(event->key == InputKeyUp) { | ||||||
|  |             consumed = true; | ||||||
|  |             menu_process_up(menu); | ||||||
|  |         } else if(event->key == InputKeyDown) { | ||||||
|  |             consumed = true; | ||||||
|  |             menu_process_down(menu); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return consumed; |     return consumed; | ||||||
| @ -138,7 +146,7 @@ Menu* menu_alloc() { | |||||||
| 
 | 
 | ||||||
| void menu_free(Menu* menu) { | void menu_free(Menu* menu) { | ||||||
|     furi_assert(menu); |     furi_assert(menu); | ||||||
|     menu_clean(menu); |     menu_reset(menu); | ||||||
|     view_free(menu->view); |     view_free(menu->view); | ||||||
|     free(menu); |     free(menu); | ||||||
| } | } | ||||||
| @ -172,7 +180,7 @@ void menu_add_item( | |||||||
|         }); |         }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void menu_clean(Menu* menu) { | void menu_reset(Menu* menu) { | ||||||
|     furi_assert(menu); |     furi_assert(menu); | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         menu->view, (MenuModel * model) { |         menu->view, (MenuModel * model) { | ||||||
|  | |||||||
| @ -59,7 +59,7 @@ void menu_add_item( | |||||||
|  * |  * | ||||||
|  * @param      menu  Menu instance |  * @param      menu  Menu instance | ||||||
|  */ |  */ | ||||||
| void menu_clean(Menu* menu); | void menu_reset(Menu* menu); | ||||||
| 
 | 
 | ||||||
| /** Set current menu item
 | /** Set current menu item
 | ||||||
|  * |  * | ||||||
|  | |||||||
| @ -227,3 +227,19 @@ void popup_enable_timeout(Popup* popup) { | |||||||
| void popup_disable_timeout(Popup* popup) { | void popup_disable_timeout(Popup* popup) { | ||||||
|     popup->timer_enabled = false; |     popup->timer_enabled = false; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void popup_reset(Popup* popup) { | ||||||
|  |     furi_assert(popup); | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         popup->view, (PopupModel * model) { | ||||||
|  |             memset(&model->header, 0, sizeof(model->header)); | ||||||
|  |             memset(&model->text, 0, sizeof(model->text)); | ||||||
|  |             memset(&model->icon, 0, sizeof(model->icon)); | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  |     popup->callback = NULL; | ||||||
|  |     popup->context = NULL; | ||||||
|  |     popup->timer_enabled = false; | ||||||
|  |     popup->timer_period_in_ms = 0; | ||||||
|  | } | ||||||
|  | |||||||
| @ -123,6 +123,12 @@ void popup_enable_timeout(Popup* popup); | |||||||
|  */ |  */ | ||||||
| void popup_disable_timeout(Popup* popup); | void popup_disable_timeout(Popup* popup); | ||||||
| 
 | 
 | ||||||
|  | /** Reset popup instance state
 | ||||||
|  |  * | ||||||
|  |  * @param       popup Popup instance | ||||||
|  |  */ | ||||||
|  | void popup_reset(Popup* popup); | ||||||
|  | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | |||||||
| @ -106,6 +106,14 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) { | |||||||
|         default: |         default: | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|  |     } else if(event->type == InputTypeRepeat) { | ||||||
|  |         if(event->key == InputKeyUp) { | ||||||
|  |             consumed = true; | ||||||
|  |             submenu_process_up(submenu); | ||||||
|  |         } else if(event->key == InputKeyDown) { | ||||||
|  |             consumed = true; | ||||||
|  |             submenu_process_down(submenu); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return consumed; |     return consumed; | ||||||
| @ -169,7 +177,7 @@ void submenu_add_item( | |||||||
|         }); |         }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void submenu_clean(Submenu* submenu) { | void submenu_reset(Submenu* submenu) { | ||||||
|     furi_assert(submenu); |     furi_assert(submenu); | ||||||
| 
 | 
 | ||||||
|     with_view_model( |     with_view_model( | ||||||
|  | |||||||
| @ -57,7 +57,7 @@ void submenu_add_item( | |||||||
|  * |  * | ||||||
|  * @param      submenu  Submenu instance |  * @param      submenu  Submenu instance | ||||||
|  */ |  */ | ||||||
| void submenu_clean(Submenu* submenu); | void submenu_reset(Submenu* submenu); | ||||||
| 
 | 
 | ||||||
| /** Set submenu item selector
 | /** Set submenu item selector
 | ||||||
|  * |  * | ||||||
|  | |||||||
| @ -164,7 +164,7 @@ View* text_box_get_view(TextBox* text_box) { | |||||||
|     return text_box->view; |     return text_box->view; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void text_box_clean(TextBox* text_box) { | void text_box_reset(TextBox* text_box) { | ||||||
|     furi_assert(text_box); |     furi_assert(text_box); | ||||||
| 
 | 
 | ||||||
|     with_view_model( |     with_view_model( | ||||||
|  | |||||||
| @ -44,7 +44,7 @@ View* text_box_get_view(TextBox* text_box); | |||||||
|  * |  * | ||||||
|  * @param      text_box  TextBox instance |  * @param      text_box  TextBox instance | ||||||
|  */ |  */ | ||||||
| void text_box_clean(TextBox* text_box); | void text_box_reset(TextBox* text_box); | ||||||
| 
 | 
 | ||||||
| /** Set text for text_box
 | /** Set text for text_box
 | ||||||
|  * |  * | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ | |||||||
| 
 | 
 | ||||||
| struct TextInput { | struct TextInput { | ||||||
|     View* view; |     View* view; | ||||||
|  |     osTimerId_t timer; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| @ -23,6 +24,11 @@ typedef struct { | |||||||
| 
 | 
 | ||||||
|     uint8_t selected_row; |     uint8_t selected_row; | ||||||
|     uint8_t selected_column; |     uint8_t selected_column; | ||||||
|  | 
 | ||||||
|  |     TextInputValidatorCallback validator_callback; | ||||||
|  |     void* validator_callback_context; | ||||||
|  |     string_t validator_text; | ||||||
|  |     bool valadator_message_visible; | ||||||
| } TextInputModel; | } TextInputModel; | ||||||
| 
 | 
 | ||||||
| static const uint8_t keyboard_origin_x = 1; | static const uint8_t keyboard_origin_x = 1; | ||||||
| @ -236,6 +242,17 @@ static void text_input_view_draw_callback(Canvas* canvas, void* _model) { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     if(model->valadator_message_visible) { | ||||||
|  |         canvas_set_font(canvas, FontSecondary); | ||||||
|  |         canvas_set_color(canvas, ColorWhite); | ||||||
|  |         canvas_draw_box(canvas, 8, 10, 110, 48); | ||||||
|  |         canvas_set_color(canvas, ColorBlack); | ||||||
|  |         canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42); | ||||||
|  |         canvas_draw_rframe(canvas, 8, 8, 112, 50, 3); | ||||||
|  |         canvas_draw_rframe(canvas, 9, 9, 110, 48, 2); | ||||||
|  |         elements_multiline_text(canvas, 62, 20, string_get_cstr(model->validator_text)); | ||||||
|  |         canvas_set_font(canvas, FontKeyboard); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void text_input_handle_up(TextInput* text_input) { | static void text_input_handle_up(TextInput* text_input) { | ||||||
| @ -295,7 +312,13 @@ static void text_input_handle_ok(TextInput* text_input) { | |||||||
|             uint8_t text_length = strlen(model->text_buffer); |             uint8_t text_length = strlen(model->text_buffer); | ||||||
| 
 | 
 | ||||||
|             if(selected == ENTER_KEY) { |             if(selected == ENTER_KEY) { | ||||||
|                 if(model->callback != 0 && text_length > 0) { |                 if(model->validator_callback && (!model->validator_callback( | ||||||
|  |                                                     model->text_buffer, | ||||||
|  |                                                     model->validator_text, | ||||||
|  |                                                     model->validator_callback_context))) { | ||||||
|  |                     model->valadator_message_visible = true; | ||||||
|  |                     osTimerStart(text_input->timer, osKernelGetTickFreq() * 4); | ||||||
|  |                 } else if(model->callback != 0 && text_length > 0) { | ||||||
|                     model->callback(model->callback_context); |                     model->callback(model->callback_context); | ||||||
|                 } |                 } | ||||||
|             } else if(selected == BACKSPACE_KEY) { |             } else if(selected == BACKSPACE_KEY) { | ||||||
| @ -321,6 +344,16 @@ static bool text_input_view_input_callback(InputEvent* event, void* context) { | |||||||
|     bool consumed = false; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event->type == InputTypeShort || event->type == InputTypeRepeat) { |     if(event->type == InputTypeShort || event->type == InputTypeRepeat) { | ||||||
|  |         with_view_model( | ||||||
|  |             text_input->view, (TextInputModel * model) { | ||||||
|  |                 if(model->valadator_message_visible) { | ||||||
|  |                     if(event->key == InputKeyBack) { | ||||||
|  |                         consumed = true; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 model->valadator_message_visible = false; | ||||||
|  |                 return false; | ||||||
|  |             }); | ||||||
|         switch(event->key) { |         switch(event->key) { | ||||||
|         case InputKeyUp: |         case InputKeyUp: | ||||||
|             text_input_handle_up(text_input); |             text_input_handle_up(text_input); | ||||||
| @ -351,7 +384,11 @@ static bool text_input_view_input_callback(InputEvent* event, void* context) { | |||||||
|        event->key == InputKeyBack) { |        event->key == InputKeyBack) { | ||||||
|         with_view_model( |         with_view_model( | ||||||
|             text_input->view, (TextInputModel * model) { |             text_input->view, (TextInputModel * model) { | ||||||
|                 text_input_backspace_cb(model); |                 if(model->valadator_message_visible) { | ||||||
|  |                     model->valadator_message_visible = false; | ||||||
|  |                 } else { | ||||||
|  |                     text_input_backspace_cb(model); | ||||||
|  |                 } | ||||||
|                 return true; |                 return true; | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
| @ -361,6 +398,17 @@ static bool text_input_view_input_callback(InputEvent* event, void* context) { | |||||||
|     return consumed; |     return consumed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void text_input_timer_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     TextInput* text_input = context; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         text_input->view, (TextInputModel * model) { | ||||||
|  |             model->valadator_message_visible = false; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| TextInput* text_input_alloc() { | TextInput* text_input_alloc() { | ||||||
|     TextInput* text_input = furi_alloc(sizeof(TextInput)); |     TextInput* text_input = furi_alloc(sizeof(TextInput)); | ||||||
|     text_input->view = view_alloc(); |     text_input->view = view_alloc(); | ||||||
| @ -369,18 +417,40 @@ TextInput* text_input_alloc() { | |||||||
|     view_set_draw_callback(text_input->view, text_input_view_draw_callback); |     view_set_draw_callback(text_input->view, text_input_view_draw_callback); | ||||||
|     view_set_input_callback(text_input->view, text_input_view_input_callback); |     view_set_input_callback(text_input->view, text_input_view_input_callback); | ||||||
| 
 | 
 | ||||||
|     text_input_clean(text_input); |     text_input->timer = osTimerNew(text_input_timer_callback, osTimerOnce, text_input, NULL); | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         text_input->view, (TextInputModel * model) { | ||||||
|  |             string_init(model->validator_text); | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     text_input_reset(text_input); | ||||||
| 
 | 
 | ||||||
|     return text_input; |     return text_input; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void text_input_free(TextInput* text_input) { | void text_input_free(TextInput* text_input) { | ||||||
|     furi_assert(text_input); |     furi_assert(text_input); | ||||||
|  |     with_view_model( | ||||||
|  |         text_input->view, (TextInputModel * model) { | ||||||
|  |             string_clear(model->validator_text); | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     // Send stop command
 | ||||||
|  |     osTimerStop(text_input->timer); | ||||||
|  |     // Wait till timer stop
 | ||||||
|  |     while(osTimerIsRunning(text_input->timer)) osDelay(1); | ||||||
|  |     // Release allocated memory
 | ||||||
|  |     osTimerDelete(text_input->timer); | ||||||
|  | 
 | ||||||
|     view_free(text_input->view); |     view_free(text_input->view); | ||||||
|  | 
 | ||||||
|     free(text_input); |     free(text_input); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void text_input_clean(TextInput* text_input) { | void text_input_reset(TextInput* text_input) { | ||||||
|     furi_assert(text_input); |     furi_assert(text_input); | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         text_input->view, (TextInputModel * model) { |         text_input->view, (TextInputModel * model) { | ||||||
| @ -393,6 +463,10 @@ void text_input_clean(TextInput* text_input) { | |||||||
|             model->text_buffer_size = 0; |             model->text_buffer_size = 0; | ||||||
|             model->callback = NULL; |             model->callback = NULL; | ||||||
|             model->callback_context = NULL; |             model->callback_context = NULL; | ||||||
|  |             model->validator_callback = NULL; | ||||||
|  |             model->validator_callback_context = NULL; | ||||||
|  |             string_reset(model->validator_text); | ||||||
|  |             model->valadator_message_visible = false; | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
| } | } | ||||||
| @ -425,6 +499,38 @@ void text_input_set_result_callback( | |||||||
|         }); |         }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void text_input_set_validator( | ||||||
|  |     TextInput* text_input, | ||||||
|  |     TextInputValidatorCallback callback, | ||||||
|  |     void* callback_context) { | ||||||
|  |     with_view_model( | ||||||
|  |         text_input->view, (TextInputModel * model) { | ||||||
|  |             model->validator_callback = callback; | ||||||
|  |             model->validator_callback_context = callback_context; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | TextInputValidatorCallback text_input_get_validator_callback(TextInput* text_input) { | ||||||
|  |     TextInputValidatorCallback validator_callback = NULL; | ||||||
|  |     with_view_model( | ||||||
|  |         text_input->view, (TextInputModel * model) { | ||||||
|  |             validator_callback = model->validator_callback; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  |     return validator_callback; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void* text_input_get_validator_callback_context(TextInput* text_input) { | ||||||
|  |     void* validator_callback_context = NULL; | ||||||
|  |     with_view_model( | ||||||
|  |         text_input->view, (TextInputModel * model) { | ||||||
|  |             validator_callback_context = model->validator_callback_context; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  |     return validator_callback_context; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void text_input_set_header_text(TextInput* text_input, const char* text) { | void text_input_set_header_text(TextInput* text_input, const char* text) { | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         text_input->view, (TextInputModel * model) { |         text_input->view, (TextInputModel * model) { | ||||||
|  | |||||||
| @ -6,6 +6,8 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
|  | #include "validators.h" | ||||||
|  | #include <m-string.h> | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| @ -14,6 +16,7 @@ extern "C" { | |||||||
| /** Text input anonymous structure */ | /** Text input anonymous structure */ | ||||||
| typedef struct TextInput TextInput; | typedef struct TextInput TextInput; | ||||||
| typedef void (*TextInputCallback)(void* context); | typedef void (*TextInputCallback)(void* context); | ||||||
|  | typedef bool (*TextInputValidatorCallback)(const char* text, string_t error, void* context); | ||||||
| 
 | 
 | ||||||
| /** Allocate and initialize text input 
 | /** Allocate and initialize text input 
 | ||||||
|  *  |  *  | ||||||
| @ -33,7 +36,7 @@ void text_input_free(TextInput* text_input); | |||||||
|  * |  * | ||||||
|  * @param      text_input  Text input instance |  * @param      text_input  Text input instance | ||||||
|  */ |  */ | ||||||
| void text_input_clean(TextInput* text_input); | void text_input_reset(TextInput* text_input); | ||||||
| 
 | 
 | ||||||
| /** Get text input view
 | /** Get text input view
 | ||||||
|  * |  * | ||||||
| @ -63,6 +66,15 @@ void text_input_set_result_callback( | |||||||
|     size_t text_buffer_size, |     size_t text_buffer_size, | ||||||
|     bool clear_default_text); |     bool clear_default_text); | ||||||
| 
 | 
 | ||||||
|  | void text_input_set_validator( | ||||||
|  |     TextInput* text_input, | ||||||
|  |     TextInputValidatorCallback callback, | ||||||
|  |     void* callback_context); | ||||||
|  | 
 | ||||||
|  | TextInputValidatorCallback text_input_get_validator_callback(TextInput* text_input); | ||||||
|  | 
 | ||||||
|  | void* text_input_get_validator_callback_context(TextInput* text_input); | ||||||
|  | 
 | ||||||
| /** Set text input header text
 | /** Set text input header text
 | ||||||
|  * |  * | ||||||
|  * @param      text_input  TextInput instance |  * @param      text_input  TextInput instance | ||||||
|  | |||||||
							
								
								
									
										42
									
								
								applications/gui/modules/validators.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								applications/gui/modules/validators.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | #include <furi.h> | ||||||
|  | #include "validators.h" | ||||||
|  | #include "applications/storage/storage.h" | ||||||
|  | 
 | ||||||
|  | struct ValidatorIsFile { | ||||||
|  |     const char* app_path_folder; | ||||||
|  |     const char* app_extension; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | bool validator_is_file_callback(const char* text, string_t error, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     ValidatorIsFile* instance = context; | ||||||
|  |     bool ret = true; | ||||||
|  |     string_t path; | ||||||
|  |     string_init_printf(path, "%s/%s%s", instance->app_path_folder, text, instance->app_extension); | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     if(storage_common_stat(storage, string_get_cstr(path), NULL) == FSE_OK) { | ||||||
|  |         ret = false; | ||||||
|  |         string_printf(error, "This name\nexists!\nChoose\nanother one."); | ||||||
|  |     } else { | ||||||
|  |         ret = true; | ||||||
|  |     } | ||||||
|  |     string_clear(path); | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ValidatorIsFile* | ||||||
|  |     validator_is_file_alloc_init(const char* app_path_folder, const char* app_extension) { | ||||||
|  |     ValidatorIsFile* instance = furi_alloc(sizeof(ValidatorIsFile)); | ||||||
|  | 
 | ||||||
|  |     instance->app_path_folder = app_path_folder; | ||||||
|  |     instance->app_extension = app_extension; | ||||||
|  | 
 | ||||||
|  |     return instance; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void validator_is_file_free(ValidatorIsFile* instance) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     free(instance); | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								applications/gui/modules/validators.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								applications/gui/modules/validators.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | // #include <gui/view.h>
 | ||||||
|  | #include <m-string.h> | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | typedef struct ValidatorIsFile ValidatorIsFile; | ||||||
|  | 
 | ||||||
|  | ValidatorIsFile* | ||||||
|  |     validator_is_file_alloc_init(const char* app_path_folder, const char* app_extension); | ||||||
|  | 
 | ||||||
|  | void validator_is_file_free(ValidatorIsFile* instance); | ||||||
|  | 
 | ||||||
|  | bool validator_is_file_callback(const char* text, string_t error, void* context); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
| @ -71,7 +71,13 @@ static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { | |||||||
|                 canvas_draw_str(canvas, 73, item_text_y, "<"); |                 canvas_draw_str(canvas, 73, item_text_y, "<"); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             canvas_draw_str(canvas, 80, item_text_y, string_get_cstr(item->current_value_text)); |             canvas_draw_str_aligned( | ||||||
|  |                 canvas, | ||||||
|  |                 (115 + 73) / 2 + 1, | ||||||
|  |                 item_text_y, | ||||||
|  |                 AlignCenter, | ||||||
|  |                 AlignBottom, | ||||||
|  |                 string_get_cstr(item->current_value_text)); | ||||||
| 
 | 
 | ||||||
|             if(item->current_value_index < (item->values_count - 1)) { |             if(item->current_value_index < (item->values_count - 1)) { | ||||||
|                 canvas_draw_str(canvas, 115, item_text_y, ">"); |                 canvas_draw_str(canvas, 115, item_text_y, ">"); | ||||||
| @ -147,6 +153,27 @@ static bool variable_item_list_input_callback(InputEvent* event, void* context) | |||||||
|         default: |         default: | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|  |     } else if(event->type == InputTypeRepeat) { | ||||||
|  |         switch(event->key) { | ||||||
|  |         case InputKeyUp: | ||||||
|  |             consumed = true; | ||||||
|  |             variable_item_list_process_up(variable_item_list); | ||||||
|  |             break; | ||||||
|  |         case InputKeyDown: | ||||||
|  |             consumed = true; | ||||||
|  |             variable_item_list_process_down(variable_item_list); | ||||||
|  |             break; | ||||||
|  |         case InputKeyLeft: | ||||||
|  |             consumed = true; | ||||||
|  |             variable_item_list_process_left(variable_item_list); | ||||||
|  |             break; | ||||||
|  |         case InputKeyRight: | ||||||
|  |             consumed = true; | ||||||
|  |             variable_item_list_process_right(variable_item_list); | ||||||
|  |             break; | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return consumed; |     return consumed; | ||||||
| @ -285,7 +312,7 @@ void variable_item_list_free(VariableItemList* variable_item_list) { | |||||||
|     free(variable_item_list); |     free(variable_item_list); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void variable_item_list_clean(VariableItemList* variable_item_list) { | void variable_item_list_reset(VariableItemList* variable_item_list) { | ||||||
|     furi_assert(variable_item_list); |     furi_assert(variable_item_list); | ||||||
| 
 | 
 | ||||||
|     with_view_model( |     with_view_model( | ||||||
|  | |||||||
| @ -32,7 +32,7 @@ void variable_item_list_free(VariableItemList* variable_item_list); | |||||||
|  * |  * | ||||||
|  * @param      variable_item_list  VariableItemList instance |  * @param      variable_item_list  VariableItemList instance | ||||||
|  */ |  */ | ||||||
| void variable_item_list_clean(VariableItemList* variable_item_list); | void variable_item_list_reset(VariableItemList* variable_item_list); | ||||||
| 
 | 
 | ||||||
| /** Get VariableItemList View instance
 | /** Get VariableItemList View instance
 | ||||||
|  * |  * | ||||||
|  | |||||||
| @ -1,166 +0,0 @@ | |||||||
| #include "gui/view.h" |  | ||||||
| #include "furi/memmgr.h" |  | ||||||
| #include "view_composed.h" |  | ||||||
| #include "view_i.h" |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     View* bottom; |  | ||||||
|     View* top; |  | ||||||
|     bool top_enabled; |  | ||||||
| } ViewComposedModel; |  | ||||||
| 
 |  | ||||||
| struct ViewComposed { |  | ||||||
|     View* view; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static void view_composed_draw(Canvas* canvas, void* model); |  | ||||||
| static bool view_composed_input(InputEvent* event, void* context); |  | ||||||
| 
 |  | ||||||
| static void view_composed_update_callback(View* view_top_or_bottom, void* context) { |  | ||||||
|     furi_assert(view_top_or_bottom); |  | ||||||
|     furi_assert(context); |  | ||||||
| 
 |  | ||||||
|     View* view_composed_view = context; |  | ||||||
|     view_composed_view->update_callback( |  | ||||||
|         view_composed_view, view_composed_view->update_callback_context); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void view_composed_enter(void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
| 
 |  | ||||||
|     ViewComposed* view_composed = context; |  | ||||||
|     ViewComposedModel* model = view_get_model(view_composed->view); |  | ||||||
| 
 |  | ||||||
|     /* if more than 1 composed views hold same view it has to reassign update_callback_context */ |  | ||||||
|     if(model->bottom) { |  | ||||||
|         view_set_update_callback_context(model->bottom, view_composed->view); |  | ||||||
|         if(model->bottom->enter_callback) { |  | ||||||
|             model->bottom->enter_callback(model->bottom->context); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     if(model->top) { |  | ||||||
|         view_set_update_callback_context(model->top, view_composed->view); |  | ||||||
|         if(model->top->enter_callback) { |  | ||||||
|             model->top->enter_callback(model->top->context); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     view_commit_model(view_composed->view, false); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void view_composed_exit(void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
| 
 |  | ||||||
|     ViewComposed* view_composed = context; |  | ||||||
|     ViewComposedModel* model = view_get_model(view_composed->view); |  | ||||||
| 
 |  | ||||||
|     if(model->bottom) { |  | ||||||
|         if(model->bottom->exit_callback) { |  | ||||||
|             model->bottom->exit_callback(model->bottom->context); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     if(model->top) { |  | ||||||
|         if(model->top->exit_callback) { |  | ||||||
|             model->top->exit_callback(model->top->context); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     view_commit_model(view_composed->view, false); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ViewComposed* view_composed_alloc(void) { |  | ||||||
|     ViewComposed* view_composed = furi_alloc(sizeof(ViewComposed)); |  | ||||||
|     view_composed->view = view_alloc(); |  | ||||||
| 
 |  | ||||||
|     view_allocate_model(view_composed->view, ViewModelTypeLocking, sizeof(ViewComposedModel)); |  | ||||||
|     view_set_draw_callback(view_composed->view, view_composed_draw); |  | ||||||
|     view_set_input_callback(view_composed->view, view_composed_input); |  | ||||||
|     view_set_context(view_composed->view, view_composed); |  | ||||||
|     view_set_enter_callback(view_composed->view, view_composed_enter); |  | ||||||
|     view_set_exit_callback(view_composed->view, view_composed_exit); |  | ||||||
|     return view_composed; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void view_composed_free(ViewComposed* view_composed) { |  | ||||||
|     furi_assert(view_composed); |  | ||||||
| 
 |  | ||||||
|     ViewComposedModel* view_composed_model = view_get_model(view_composed->view); |  | ||||||
|     view_set_update_callback(view_composed_model->bottom, NULL); |  | ||||||
|     view_set_update_callback_context(view_composed_model->bottom, NULL); |  | ||||||
|     view_set_update_callback(view_composed_model->top, NULL); |  | ||||||
|     view_set_update_callback_context(view_composed_model->top, NULL); |  | ||||||
|     view_commit_model(view_composed->view, true); |  | ||||||
| 
 |  | ||||||
|     view_free(view_composed->view); |  | ||||||
|     free(view_composed); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void view_composed_draw(Canvas* canvas, void* model) { |  | ||||||
|     furi_assert(model); |  | ||||||
| 
 |  | ||||||
|     ViewComposedModel* view_composed_model = model; |  | ||||||
| 
 |  | ||||||
|     view_draw(view_composed_model->bottom, canvas); |  | ||||||
|     if(view_composed_model->top_enabled && view_composed_model->top) { |  | ||||||
|         view_draw(view_composed_model->top, canvas); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool view_composed_input(InputEvent* event, void* context) { |  | ||||||
|     furi_assert(event); |  | ||||||
|     furi_assert(context); |  | ||||||
| 
 |  | ||||||
|     ViewComposed* view_composed = context; |  | ||||||
|     ViewComposedModel* view_composed_model = view_get_model(view_composed->view); |  | ||||||
|     bool consumed = false; |  | ||||||
| 
 |  | ||||||
|     if(view_composed_model->top_enabled && view_composed_model->top) { |  | ||||||
|         consumed = view_input(view_composed_model->top, event); |  | ||||||
|     } |  | ||||||
|     if(!consumed) { |  | ||||||
|         consumed = view_input(view_composed_model->bottom, event); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     view_commit_model(view_composed->view, false); |  | ||||||
| 
 |  | ||||||
|     return consumed; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void view_composed_top_enable(ViewComposed* view_composed, bool enable) { |  | ||||||
|     furi_assert(view_composed); |  | ||||||
| 
 |  | ||||||
|     ViewComposedModel* view_composed_model = view_get_model(view_composed->view); |  | ||||||
|     bool update = (view_composed_model->top_enabled != enable); |  | ||||||
|     view_composed_model->top_enabled = enable; |  | ||||||
|     view_commit_model(view_composed->view, update); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void view_composed_tie_views(ViewComposed* view_composed, View* view_bottom, View* view_top) { |  | ||||||
|     furi_assert(view_composed); |  | ||||||
|     furi_assert(view_bottom); |  | ||||||
| 
 |  | ||||||
|     ViewComposedModel* view_composed_model = view_get_model(view_composed->view); |  | ||||||
| 
 |  | ||||||
|     if(view_composed_model->bottom) { |  | ||||||
|         view_set_update_callback(view_composed_model->bottom, NULL); |  | ||||||
|         view_set_update_callback_context(view_composed_model->bottom, NULL); |  | ||||||
|     } |  | ||||||
|     if(view_composed_model->top) { |  | ||||||
|         view_set_update_callback(view_composed_model->top, NULL); |  | ||||||
|         view_set_update_callback_context(view_composed_model->top, NULL); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     view_composed_model->bottom = view_bottom; |  | ||||||
|     view_set_update_callback(view_bottom, view_composed_update_callback); |  | ||||||
|     view_set_update_callback_context(view_bottom, view_composed->view); |  | ||||||
|     view_composed_model->top = view_top; |  | ||||||
|     view_set_update_callback(view_top, view_composed_update_callback); |  | ||||||
|     view_set_update_callback_context(view_top, view_composed->view); |  | ||||||
| 
 |  | ||||||
|     view_commit_model(view_composed->view, true); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| View* view_composed_get_view(ViewComposed* view_composed) { |  | ||||||
|     furi_assert(view_composed); |  | ||||||
|     return view_composed->view; |  | ||||||
| } |  | ||||||
| @ -1,12 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <stdbool.h> |  | ||||||
| #include "view.h" |  | ||||||
| 
 |  | ||||||
| typedef struct ViewComposed ViewComposed; |  | ||||||
| 
 |  | ||||||
| ViewComposed* view_composed_alloc(void); |  | ||||||
| void view_composed_free(ViewComposed* view_composed); |  | ||||||
| void view_composed_top_enable(ViewComposed* view_composed, bool enable); |  | ||||||
| void view_composed_tie_views(ViewComposed* view_composed, View* view_bottom, View* view_top); |  | ||||||
| View* view_composed_get_view(ViewComposed* view_composed); |  | ||||||
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