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 | ||||
| 
 | ||||
| @ -8,7 +8,7 @@ applications/accessor/** @skotopes @DrZlo13 | ||||
| applications/loader/** @skotopes @DrZlo13 @gornekich | ||||
| applications/bt/** @skotopes @DrZlo13 | ||||
| applications/cli/** @skotopes @DrZlo13 | ||||
| applications/dolphin/** @skotopes @DrZlo13 @itsyourbedtime | ||||
| applications/dolphin/** @skotopes @DrZlo13 | ||||
| applications/gpio-tester/** @skotopes @DrZlo13 | ||||
| applications/gui/** @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(), | ||||
|         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_text(message, string_get_cstr(buffer), 0, 13, AlignLeft, AlignTop); | ||||
|     result = dialog_message_show(dialogs, message); | ||||
|  | ||||
| @ -25,4 +25,5 @@ struct ArchiveApp { | ||||
|     ArchiveBrowserView* browser; | ||||
|     TextInput* text_input; | ||||
|     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( | ||||
|         browser->view, (ArchiveBrowserViewModel * model) { | ||||
|             model->last_idx = model->idx; | ||||
|             model->last_offset = model->list_offset; | ||||
|             model->idx = 0; | ||||
|             model->depth = CLAMP(model->depth + 1, MAX_DEPTH, 0); | ||||
|             return false; | ||||
|  | ||||
| @ -31,6 +31,40 @@ uint16_t archive_favorites_count(void* context) { | ||||
|     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) { | ||||
|     furi_assert(context); | ||||
| 
 | ||||
| @ -41,6 +75,8 @@ bool archive_favorites_read(void* context) { | ||||
|     FileInfo file_info; | ||||
|     string_init(buffer); | ||||
| 
 | ||||
|     bool need_refresh = false; | ||||
| 
 | ||||
|     bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
| 
 | ||||
|     if(result) { | ||||
| @ -52,13 +88,24 @@ bool archive_favorites_read(void* context) { | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             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_clear(buffer); | ||||
|     file_worker_close(file_worker); | ||||
|     file_worker_free(file_worker); | ||||
| 
 | ||||
|     if(need_refresh) { | ||||
|         archive_favourites_rescan(); | ||||
|     } | ||||
| 
 | ||||
|     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) { | ||||
|     furi_assert(file); | ||||
|     furi_assert(file_info); | ||||
|  | ||||
| @ -50,6 +50,7 @@ ARRAY_DEF( | ||||
| bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name); | ||||
| void set_file_type(ArchiveFile_t* file, FileInfo* file_info); | ||||
| void archive_trim_file_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_dir_empty(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); | ||||
|     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); | ||||
| 
 | ||||
|     text_input_set_header_text(text_input, "Rename:"); | ||||
| @ -30,6 +31,10 @@ void archive_scene_rename_on_enter(void* context) { | ||||
|         MAX_TEXT_INPUT_LEN, | ||||
|         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); | ||||
| } | ||||
| 
 | ||||
| @ -74,6 +79,11 @@ bool archive_scene_rename_on_event(void* context, SceneManagerEvent event) { | ||||
| 
 | ||||
| void archive_scene_rename_on_exit(void* context) { | ||||
|     ArchiveApp* archive = (ArchiveApp*)context; | ||||
| 
 | ||||
|     // 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_NAME_LEN 255 | ||||
| #define MAX_EXT_LEN 6 | ||||
| #define FRAME_HEIGHT 12 | ||||
| #define MENU_ITEMS 4 | ||||
| #define MAX_DEPTH 32 | ||||
|  | ||||
| @ -71,7 +71,7 @@ void bad_usb_app_free(BadUsbApp* app) { | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
| 
 | ||||
|     BadUsbApp* bad_usb_app = bad_usb_app_alloc(); | ||||
|  | ||||
| @ -6,6 +6,7 @@ | ||||
| #include <furi_hal_usb_hid.h> | ||||
| #include <storage/storage.h> | ||||
| #include "bad_usb_script.h" | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| #define TAG "BadUSB" | ||||
| #define WORKER_TAG TAG "Worker" | ||||
| @ -442,6 +443,7 @@ static int32_t bad_usb_worker(void* context) { | ||||
|             if(flags & WorkerEvtEnd) { | ||||
|                 break; | ||||
|             } else if(flags & WorkerEvtToggle) { // Start executing script
 | ||||
|                 DOLPHIN_DEED(DolphinDeedBadUsbPlayScript); | ||||
|                 delay_val = 0; | ||||
|                 bad_usb->buf_len = 0; | ||||
|                 bad_usb->st.line_cur = 0; | ||||
|  | ||||
| @ -2,7 +2,8 @@ | ||||
| #include "battery_service.h" | ||||
| #include "bt_keys_storage.h" | ||||
| 
 | ||||
| #include <applications/notification/notification_messages.h> | ||||
| #include <notification/notification_messages.h> | ||||
| #include <gui/elements.h> | ||||
| 
 | ||||
| #define TAG "BtSrv" | ||||
| 
 | ||||
| @ -29,17 +30,46 @@ static ViewPort* bt_statusbar_view_port_alloc(Bt* bt) { | ||||
|     return statusbar_view_port; | ||||
| } | ||||
| 
 | ||||
| static void bt_pin_code_show_event_handler(Bt* bt, uint32_t pin) { | ||||
|     furi_assert(bt); | ||||
| static void bt_pin_code_view_port_draw_callback(Canvas* canvas, void* context) { | ||||
|     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); | ||||
|     string_t pin_str; | ||||
|     dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0); | ||||
|     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); | ||||
|     dialog_message_set_buttons(bt->dialog_message, "Quit", NULL, NULL); | ||||
|     dialog_message_show(bt->dialogs, bt->dialog_message); | ||||
|     string_clear(pin_str); | ||||
|     gui_view_port_send_to_front(bt->gui, bt->pin_code_view_port); | ||||
|     view_port_enabled_set(bt->pin_code_view_port, true); | ||||
| } | ||||
| 
 | ||||
| static void bt_pin_code_hide(Bt* bt) { | ||||
|     bt->pin_code = 0; | ||||
|     if(view_port_is_enabled(bt->pin_code_view_port)) { | ||||
|         view_port_enabled_set(bt->pin_code_view_port, false); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) { | ||||
| @ -84,11 +114,14 @@ Bt* bt_alloc() { | ||||
| 
 | ||||
|     // Setup statusbar view port
 | ||||
|     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
 | ||||
|     bt->notification = furi_record_open("notification"); | ||||
|     // 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->pin_code_view_port, GuiLayerFullscreen); | ||||
| 
 | ||||
|     // 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) { | ||||
|         // Update status bar
 | ||||
|         bt->status = BtStatusConnected; | ||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; | ||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatus}; | ||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||
|         if(bt->profile == BtProfileSerial) { | ||||
|             // Open RPC session
 | ||||
| @ -192,12 +225,12 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { | ||||
|         ret = true; | ||||
|     } else if(event.type == GapEventTypeStartAdvertising) { | ||||
|         bt->status = BtStatusAdvertising; | ||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; | ||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatus}; | ||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||
|         ret = true; | ||||
|     } else if(event.type == GapEventTypeStopAdvertising) { | ||||
|         bt->status = BtStatusOff; | ||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatusbar}; | ||||
|         BtMessage message = {.type = BtMessageTypeUpdateStatus}; | ||||
|         furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK); | ||||
|         ret = true; | ||||
|     } else if(event.type == GapEventTypePinCodeShow) { | ||||
| @ -313,9 +346,10 @@ int32_t bt_srv() { | ||||
|     BtMessage message; | ||||
|     while(1) { | ||||
|         furi_check(osMessageQueueGet(bt->message_queue, &message, NULL, osWaitForever) == osOK); | ||||
|         if(message.type == BtMessageTypeUpdateStatusbar) { | ||||
|             // Update statusbar
 | ||||
|         if(message.type == BtMessageTypeUpdateStatus) { | ||||
|             // Update view ports
 | ||||
|             bt_statusbar_update(bt); | ||||
|             bt_pin_code_hide(bt); | ||||
|             if(bt->status_changed_cb) { | ||||
|                 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); | ||||
|         } else if(message.type == BtMessageTypePinCodeShow) { | ||||
|             // 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) { | ||||
|             bt_save_key_storage(bt); | ||||
|         } else if(message.type == BtMessageTypeSetProfile) { | ||||
|             bt_change_profile(bt, &message); | ||||
|         } else if(message.type == BtMessageTypeForgetBondedDevices) { | ||||
|             bt_delete_key_storage(bt); | ||||
|         } | ||||
|     } | ||||
|     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); | ||||
| 
 | ||||
| /** 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 | ||||
| } | ||||
| #endif | ||||
|  | ||||
| @ -20,3 +20,9 @@ void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, vo | ||||
|     bt->status_changed_cb = callback; | ||||
|     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) | ||||
| 
 | ||||
| typedef enum { | ||||
|     BtMessageTypeUpdateStatusbar, | ||||
|     BtMessageTypeUpdateStatus, | ||||
|     BtMessageTypeUpdateBatteryLevel, | ||||
|     BtMessageTypePinCodeShow, | ||||
|     BtMessageTypeKeysStorageUpdated, | ||||
|     BtMessageTypeSetProfile, | ||||
|     BtMessageTypeForgetBondedDevices, | ||||
| } BtMessageType; | ||||
| 
 | ||||
| typedef union { | ||||
| @ -49,6 +50,8 @@ struct Bt { | ||||
|     NotificationApp* notification; | ||||
|     Gui* gui; | ||||
|     ViewPort* statusbar_view_port; | ||||
|     ViewPort* pin_code_view_port; | ||||
|     uint32_t pin_code; | ||||
|     DialogsApp* dialogs; | ||||
|     DialogMessage* dialog_message; | ||||
|     Power* power; | ||||
|  | ||||
| @ -39,3 +39,16 @@ bool bt_save_key_storage(Bt* bt) { | ||||
|     file_worker_free(file_worker); | ||||
|     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_save_key_storage(Bt* bt); | ||||
| 
 | ||||
| bool bt_delete_key_storage(Bt* bt); | ||||
|  | ||||
| @ -18,7 +18,9 @@ BtSettingsApp* bt_settings_app_alloc() { | ||||
|     // Load settings
 | ||||
|     bt_settings_load(&app->settings); | ||||
|     app->gui = furi_record_open("gui"); | ||||
|     app->bt = furi_record_open("bt"); | ||||
| 
 | ||||
|     // View Dispatcher and Scene Manager
 | ||||
|     app->view_dispatcher = view_dispatcher_alloc(); | ||||
|     app->scene_manager = scene_manager_alloc(&bt_settings_scene_handlers, app); | ||||
|     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); | ||||
| 
 | ||||
|     // Gui Modules
 | ||||
|     app->var_item_list = variable_item_list_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         app->view_dispatcher, | ||||
|         BtSettingsAppViewVarItemList, | ||||
|         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); | ||||
|     return app; | ||||
| } | ||||
| 
 | ||||
| void bt_settings_app_free(BtSettingsApp* app) { | ||||
|     furi_assert(app); | ||||
|     // Variable item list
 | ||||
|     // Gui modules
 | ||||
|     view_dispatcher_remove_view(app->view_dispatcher, BtSettingsAppViewVarItemList); | ||||
|     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); | ||||
|     scene_manager_free(app->scene_manager); | ||||
| 
 | ||||
|     // Records
 | ||||
|     furi_record_close("gui"); | ||||
|     furi_record_close("bt"); | ||||
|     free(app); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,22 +1,41 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <furi.h> | ||||
| #include <bt/bt_service/bt.h> | ||||
| #include <gui/gui.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/view_dispatcher.h> | ||||
| #include <gui/scene_manager.h> | ||||
| 
 | ||||
| #include <gui/modules/variable_item_list.h> | ||||
| #include <gui/modules/dialog_ex.h> | ||||
| #include <gui/modules/popup.h> | ||||
| 
 | ||||
| #include "../bt_settings.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 { | ||||
|     BtSettings settings; | ||||
|     Bt* bt; | ||||
|     Gui* gui; | ||||
|     SceneManager* scene_manager; | ||||
|     ViewDispatcher* view_dispatcher; | ||||
| 
 | ||||
|     VariableItemList* var_item_list; | ||||
|     DialogEx* dialog; | ||||
|     Popup* popup; | ||||
| } 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, 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, | ||||
| }; | ||||
| 
 | ||||
| enum BtSettingIndex { | ||||
|     BtSettingIndexSwitchBt, | ||||
|     BtSettingIndexForgetDev, | ||||
| }; | ||||
| 
 | ||||
| const char* const bt_settings_text[BtSettingNum] = { | ||||
|     "Off", | ||||
|     "On", | ||||
|     "OFF", | ||||
|     "ON", | ||||
| }; | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| 
 | ||||
| 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) { | ||||
|     BtSettingsApp* app = context; | ||||
|     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_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 { | ||||
|         item = variable_item_list_add(var_item_list, "Bluetooth", 1, NULL, NULL); | ||||
|         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) { | ||||
|             furi_hal_bt_start_advertising(); | ||||
|             app->settings.enabled = true; | ||||
|             consumed = true; | ||||
|         } else if(event.event == BtSettingOff) { | ||||
|             app->settings.enabled = false; | ||||
|             furi_hal_bt_stop_advertising(); | ||||
|         } | ||||
|             consumed = true; | ||||
|         } else if(event.event == BtSettingsCustomEventForgetDevices) { | ||||
|             scene_manager_next_scene(app->scene_manager, BtSettingsAppSceneForgetDevConfirm); | ||||
|             consumed = true; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void bt_settings_scene_start_on_exit(void* 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); | ||||
|     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); | ||||
| 
 | ||||
|     view_port_draw_callback_set(view_port, usb_mouse_render_callback, NULL); | ||||
|  | ||||
| @ -15,6 +15,7 @@ typedef struct { | ||||
| typedef enum { | ||||
|     UsbTestSubmenuIndexEnable, | ||||
|     UsbTestSubmenuIndexDisable, | ||||
|     UsbTestSubmenuIndexRestart, | ||||
|     UsbTestSubmenuIndexVcpSingle, | ||||
|     UsbTestSubmenuIndexVcpDual, | ||||
|     UsbTestSubmenuIndexHid, | ||||
| @ -28,6 +29,8 @@ void usb_test_submenu_callback(void* context, uint32_t index) { | ||||
|         furi_hal_usb_enable(); | ||||
|     } else if(index == UsbTestSubmenuIndexDisable) { | ||||
|         furi_hal_usb_disable(); | ||||
|     } else if(index == UsbTestSubmenuIndexRestart) { | ||||
|         furi_hal_usb_reinit(); | ||||
|     } else if(index == UsbTestSubmenuIndexVcpSingle) { | ||||
|         furi_hal_usb_set_config(&usb_cdc_single); | ||||
|     } else if(index == UsbTestSubmenuIndexVcpDual) { | ||||
| @ -60,6 +63,8 @@ UsbTestApp* usb_test_app_alloc() { | ||||
|         app->submenu, "Enable", UsbTestSubmenuIndexEnable, usb_test_submenu_callback, app); | ||||
|     submenu_add_item( | ||||
|         app->submenu, "Disable", UsbTestSubmenuIndexDisable, usb_test_submenu_callback, app); | ||||
|     submenu_add_item( | ||||
|         app->submenu, "Restart", UsbTestSubmenuIndexRestart, usb_test_submenu_callback, app); | ||||
|     submenu_add_item( | ||||
|         app->submenu, "Single VCP", UsbTestSubmenuIndexVcpSingle, usb_test_submenu_callback, app); | ||||
|     submenu_add_item( | ||||
|  | ||||
| @ -1,23 +1,30 @@ | ||||
| #include "animation_manager.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 <gui/view_stack.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 <dolphin/dolphin_i.h> | ||||
| #include <storage/filesystem_api_defines.h> | ||||
| #include <assets_icons.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 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 { | ||||
|     AnimationManagerStateIdle, | ||||
|     AnimationManagerStateBlocked, | ||||
| @ -29,10 +36,13 @@ struct AnimationManager { | ||||
|     bool sd_show_url; | ||||
|     bool sd_shown_no_db; | ||||
|     bool sd_shown_sd_ok; | ||||
|     bool levelup_pending; | ||||
|     bool levelup_active; | ||||
|     AnimationManagerState state; | ||||
|     FuriPubSubSubscription* pubsub_subscription_storage; | ||||
|     FuriPubSubSubscription* pubsub_subscription_dolphin; | ||||
|     BubbleAnimationView* animation_view; | ||||
|     OneShotView* one_shot_view; | ||||
|     osTimerId_t idle_animation_timer; | ||||
|     StorageAnimation* current_animation; | ||||
|     AnimationManagerInteractCallback interact_callback; | ||||
| @ -41,6 +51,7 @@ struct AnimationManager { | ||||
|     void* context; | ||||
|     string_t freezed_animation_name; | ||||
|     int32_t freezed_animation_time_left; | ||||
|     ViewStack* view_stack; | ||||
| }; | ||||
| 
 | ||||
| static StorageAnimation* | ||||
| @ -50,6 +61,11 @@ static void animation_manager_replace_current_animation( | ||||
|     StorageAnimation* storage_animation); | ||||
| static void animation_manager_start_new_idle(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) { | ||||
|     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) { | ||||
|     furi_assert(animation_manager); | ||||
| 
 | ||||
|     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) { | ||||
|     furi_assert(animation_manager); | ||||
| 
 | ||||
|     if(animation_manager->state == AnimationManagerStateBlocked) { | ||||
|         /* check if new blocking animation has to be displayed */ | ||||
|     if(animation_manager->levelup_pending) { | ||||
|         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); | ||||
| 
 | ||||
|         if(!blocked) { | ||||
|             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) { | ||||
|         blocking_animation = animation_storage_find_animation(BAD_SD_ANIMATION_NAME); | ||||
|         furi_assert(blocking_animation); | ||||
|     } else if(sd_status == FSE_NOT_READY) { | ||||
|         animation_manager->sd_shown_sd_ok = false; | ||||
|         animation_manager->sd_shown_no_db = false; | ||||
|     } else if(sd_status == FSE_OK) { | ||||
|         if(!animation_manager->sd_shown_sd_ok) { | ||||
|             blocking_animation = animation_storage_find_animation(SD_OK_ANIMATION_NAME); | ||||
|             furi_assert(blocking_animation); | ||||
|             animation_manager->sd_shown_sd_ok = true; | ||||
|         } else if(!animation_manager->sd_shown_no_db) { | ||||
|             bool db_exists = storage_common_stat(storage, "/ext/Manifest", NULL) == FSE_OK; | ||||
|             if(!db_exists) { | ||||
|                 blocking_animation = animation_storage_find_animation(NO_DB_ANIMATION_NAME); | ||||
|                 furi_assert(blocking_animation); | ||||
|                 animation_manager->sd_shown_no_db = true; | ||||
|                 animation_manager->sd_show_url = true; | ||||
|             } | ||||
|         } else if(animation_manager->sd_show_url) { | ||||
|             blocking_animation = animation_storage_find_animation(URL_ANIMATION_NAME); | ||||
|             furi_assert(blocking_animation); | ||||
|             animation_manager->sd_show_url = false; | ||||
|         } | ||||
|     } | ||||
| @ -176,13 +221,17 @@ static bool animation_manager_check_blocking(AnimationManager* animation_manager | ||||
|     DolphinStats stats = dolphin_stats(dolphin); | ||||
|     furi_record_close("dolphin"); | ||||
|     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) { | ||||
|         osTimerStop(animation_manager->idle_animation_timer); | ||||
|         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; | ||||
|     } | ||||
| 
 | ||||
| @ -199,7 +248,7 @@ static void animation_manager_replace_current_animation( | ||||
| 
 | ||||
|     const BubbleAnimation* animation = animation_storage_get_bubble_animation(storage_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); | ||||
|     animation_manager->current_animation = storage_animation; | ||||
| 
 | ||||
| @ -209,9 +258,11 @@ static void animation_manager_replace_current_animation( | ||||
| } | ||||
| 
 | ||||
| AnimationManager* animation_manager_alloc(void) { | ||||
|     animation_storage_initialize_internal_animations(); | ||||
|     AnimationManager* animation_manager = furi_alloc(sizeof(AnimationManager)); | ||||
|     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); | ||||
| 
 | ||||
|     animation_manager->idle_animation_timer = | ||||
| @ -251,6 +302,8 @@ void animation_manager_free(AnimationManager* animation_manager) { | ||||
|     furi_record_close("storage"); | ||||
| 
 | ||||
|     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); | ||||
|     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) { | ||||
|     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* | ||||
| @ -267,40 +352,25 @@ static StorageAnimation* | ||||
|     StorageAnimationList_init(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"); | ||||
|     DolphinStats stats = dolphin_stats(dolphin); | ||||
|     furi_record_close("dolphin"); | ||||
|     uint32_t whole_weight = 0; | ||||
| 
 | ||||
|     StorageAnimationList_it_t it; | ||||
|     for(StorageAnimationList_it(it, animation_list); !StorageAnimationList_end_p(it);) { | ||||
|         StorageAnimation* storage_animation = *StorageAnimationList_ref(it); | ||||
|         const StorageAnimationMeta* meta = animation_storage_get_meta(storage_animation); | ||||
|         bool skip_animation = false; | ||||
|         if(battery_is_well && !string_cmp_str(meta->name, BAD_BATTERY_ANIMATION_NAME)) { | ||||
|             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; | ||||
|         } | ||||
|         const StorageAnimationManifestInfo* manifest_info = | ||||
|             animation_storage_get_meta(storage_animation); | ||||
|         bool valid = animation_manager_is_valid_idle_animation(manifest_info, &stats); | ||||
| 
 | ||||
|         if(skip_animation) { | ||||
|         if(valid) { | ||||
|             whole_weight += manifest_info->weight; | ||||
|             StorageAnimationList_next(it); | ||||
|         } else { | ||||
|             animation_storage_free_storage_animation(&storage_animation); | ||||
|             /* remove and increase iterator */ | ||||
|             StorageAnimationList_remove(animation_list, it); | ||||
|         } else { | ||||
|             whole_weight += meta->weight; | ||||
|             StorageAnimationList_next(it); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -325,11 +395,10 @@ static StorageAnimation* | ||||
|         } | ||||
| 
 | ||||
|     StorageAnimationList_clear(animation_list); | ||||
|     furi_record_close("dolphin"); | ||||
| 
 | ||||
|     /* cache animation, if failed - choose reliable animation */ | ||||
|     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); | ||||
|         animation_storage_free_storage_animation(&selected); | ||||
|         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); | ||||
|     } | ||||
| 
 | ||||
|     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 */ | ||||
|     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); | ||||
|     animation_storage_free_storage_animation(&animation_manager->current_animation); | ||||
| @ -394,7 +469,15 @@ void animation_manager_load_and_continue_animation(AnimationManager* animation_m | ||||
|             StorageAnimation* restore_animation = animation_storage_find_animation( | ||||
|                 string_get_cstr(animation_manager->freezed_animation_name)); | ||||
|             if(restore_animation) { | ||||
|                 animation_manager_replace_current_animation(animation_manager, restore_animation); | ||||
|                 Dolphin* dolphin = furi_record_open("dolphin"); | ||||
|                 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) { | ||||
| @ -407,6 +490,7 @@ void animation_manager_load_and_continue_animation(AnimationManager* animation_m | ||||
|                         osTimerStart( | ||||
|                             animation_manager->idle_animation_timer, animation->duration * 1000); | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 FURI_LOG_E( | ||||
|                     TAG, | ||||
| @ -423,12 +507,47 @@ void animation_manager_load_and_continue_animation(AnimationManager* animation_m | ||||
|     if(!animation_manager->current_animation) { | ||||
|         animation_manager_start_new_idle(animation_manager); | ||||
|     } | ||||
|     FURI_LOG_D( | ||||
|     FURI_LOG_I( | ||||
|         TAG, | ||||
|         "Load & Continue with \'%s\'", | ||||
|         string_get_cstr(animation_storage_get_meta(animation_manager->current_animation)->name)); | ||||
|         "Load animation \'%s\'", | ||||
|         animation_storage_get_meta(animation_manager->current_animation)->name); | ||||
| 
 | ||||
|     bubble_animation_unfreeze(animation_manager->animation_view); | ||||
|     string_reset(animation_manager->freezed_animation_name); | ||||
|     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 | ||||
| 
 | ||||
| #include "dolphin/dolphin.h" | ||||
| #include <gui/view.h> | ||||
| #include <gui/icon_i.h> | ||||
| #include <stdint.h> | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| typedef struct AnimationManager AnimationManager; | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint8_t x; | ||||
|     uint8_t y; | ||||
|     const char* str; | ||||
|     Align horizontal; | ||||
|     Align vertical; | ||||
|     const char* text; | ||||
|     Align align_h; | ||||
|     Align align_v; | ||||
| } Bubble; | ||||
| 
 | ||||
| typedef struct FrameBubble { | ||||
|     Bubble bubble; | ||||
|     uint8_t starts_at_frame; | ||||
|     uint8_t ends_at_frame; | ||||
|     struct FrameBubble* next_bubble; | ||||
|     uint8_t start_frame; | ||||
|     uint8_t end_frame; | ||||
|     const struct FrameBubble* next_bubble; | ||||
| } FrameBubble; | ||||
| 
 | ||||
| typedef struct { | ||||
|     FrameBubble** frame_bubbles; | ||||
|     uint8_t frame_bubbles_count; | ||||
|     const Icon** icons; | ||||
|     const FrameBubble* const* frame_bubble_sequences; | ||||
|     uint8_t frame_bubble_sequences_count; | ||||
|     const Icon icon_animation; | ||||
|     const uint8_t* frame_order; | ||||
|     uint8_t passive_frames; | ||||
|     uint8_t active_frames; | ||||
|     uint8_t active_cycles; | ||||
|     uint8_t frame_rate; | ||||
|     uint16_t duration; | ||||
|     uint16_t active_cooldown; | ||||
| } 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 <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_DIR "/ext/dolphin/animations" | ||||
| #define ANIMATION_DIR "/ext/dolphin" | ||||
| #define ANIMATION_MANIFEST_FILE ANIMATION_DIR "/manifest.txt" | ||||
| #define TAG "AnimationStorage" | ||||
| #define DEBUG_PB 0 | ||||
| 
 | ||||
| static void animation_storage_free_bubbles(BubbleAnimation* animation); | ||||
| static void animation_storage_free_frames(BubbleAnimation* animation); | ||||
| static void animation_storage_free_animation(BubbleAnimation** storage_animation); | ||||
| 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) { | ||||
|     furi_assert(sizeof(StorageAnimationList_t) == sizeof(void*)); | ||||
|     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); | ||||
|     /* Forbid skipping fields */ | ||||
|     flipper_file_set_strict_mode(file, true); | ||||
|     string_t header; | ||||
|     string_init(header); | ||||
|     string_t read_string; | ||||
|     string_init(read_string); | ||||
| 
 | ||||
|     do { | ||||
|         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(!flipper_file_open_existing(file, ANIMATION_MANIFEST_FILE)) break; | ||||
|         if(!flipper_file_read_header(file, header, &u32value)) break; | ||||
|         if(string_cmp_str(header, "Flipper Animation Manifest")) break; | ||||
|         if(!flipper_file_read_header(file, read_string, &u32value)) break; | ||||
|         if(string_cmp_str(read_string, "Flipper Animation Manifest")) break; | ||||
|         do { | ||||
|             storage_animation = furi_alloc(sizeof(StorageAnimation)); | ||||
|             storage_animation->external = true; | ||||
|             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; | ||||
|             storage_animation->meta.min_butthurt = u32value; | ||||
|             storage_animation->manifest_info.min_butthurt = u32value; | ||||
|             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; | ||||
|             storage_animation->meta.min_level = u32value; | ||||
|             storage_animation->manifest_info.min_level = u32value; | ||||
|             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; | ||||
|             storage_animation->meta.weight = u32value; | ||||
|             storage_animation->manifest_info.weight = u32value; | ||||
| 
 | ||||
|             StorageAnimationList_push_back(*animation_list, storage_animation); | ||||
|         } while(1); | ||||
| @ -71,13 +127,13 @@ void animation_storage_fill_animation_list(StorageAnimationList_t* animation_lis | ||||
|         animation_storage_free_storage_animation(&storage_animation); | ||||
|     } while(0); | ||||
| 
 | ||||
|     string_clear(header); | ||||
|     string_clear(read_string); | ||||
|     flipper_file_close(file); | ||||
|     flipper_file_free(file); | ||||
| 
 | ||||
|     // add hard-coded animations
 | ||||
|     for(int i = 0; i < COUNT_OF(StorageAnimationInternal); ++i) { | ||||
|         StorageAnimationList_push_back(*animation_list, &StorageAnimationInternal[i]); | ||||
|     for(int i = 0; i < dolphin_internal_size; ++i) { | ||||
|         StorageAnimationList_push_back(*animation_list, (StorageAnimation*)&dolphin_internal[i]); | ||||
|     } | ||||
| 
 | ||||
|     furi_record_close("storage"); | ||||
| @ -88,41 +144,45 @@ StorageAnimation* animation_storage_find_animation(const char* name) { | ||||
|     furi_assert(strlen(name)); | ||||
|     StorageAnimation* storage_animation = NULL; | ||||
| 
 | ||||
|     /* look through internal animations */ | ||||
|     for(int i = 0; i < COUNT_OF(StorageAnimationInternal); ++i) { | ||||
|         if(!string_cmp_str(StorageAnimationInternal[i].meta.name, name)) { | ||||
|             storage_animation = &StorageAnimationInternal[i]; | ||||
|     for(int i = 0; i < dolphin_blocking_size; ++i) { | ||||
|         if(!strcmp(dolphin_blocking[i].manifest_info.name, name)) { | ||||
|             storage_animation = (StorageAnimation*)&dolphin_blocking[i]; | ||||
|             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 */ | ||||
|     if(!storage_animation) { | ||||
|         BubbleAnimation* animation = animation_storage_load_animation(name); | ||||
| 
 | ||||
|         if(animation != NULL) { | ||||
|         storage_animation = furi_alloc(sizeof(StorageAnimation)); | ||||
|             storage_animation->animation = animation; | ||||
|         storage_animation->external = true; | ||||
|             /* meta data takes part in random animation selection, so it
 | ||||
|              * doesn't need here as we exactly know which animation we need, | ||||
|              * that's why we can ignore reading manifest.txt file | ||||
|              * filling meta data by zeroes */ | ||||
|             storage_animation->meta.min_butthurt = 0; | ||||
|             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); | ||||
| 
 | ||||
|         bool result = false; | ||||
|         result = | ||||
|             animation_storage_load_single_manifest_info(&storage_animation->manifest_info, name); | ||||
|         if(result) { | ||||
|             storage_animation->animation = animation_storage_load_animation(name); | ||||
|             result = !!storage_animation->animation; | ||||
|         } | ||||
|         if(!result) { | ||||
|             animation_storage_free_storage_animation(&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); | ||||
|     return &storage_animation->meta; | ||||
|     return &storage_animation->manifest_info; | ||||
| } | ||||
| 
 | ||||
| const BubbleAnimation* | ||||
| @ -138,7 +198,7 @@ void animation_storage_cache_animation(StorageAnimation* storage_animation) { | ||||
|     if(storage_animation->external) { | ||||
|         if(!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) { | ||||
|         animation_storage_free_bubbles(*animation); | ||||
|         animation_storage_free_frames(*animation); | ||||
|         if((*animation)->frame_order) { | ||||
|             free((void*)(*animation)->frame_order); | ||||
|         } | ||||
|         free(*animation); | ||||
|         *animation = NULL; | ||||
|     } | ||||
| @ -161,7 +224,9 @@ void animation_storage_free_storage_animation(StorageAnimation** storage_animati | ||||
|     if((*storage_animation)->external) { | ||||
|         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); | ||||
|     } | ||||
| 
 | ||||
| @ -188,41 +253,15 @@ static bool animation_storage_cast_align(string_t align_str, Align* align) { | ||||
| 
 | ||||
| static void animation_storage_free_frames(BubbleAnimation* animation) { | ||||
|     furi_assert(animation); | ||||
|     furi_assert(animation->icons); | ||||
| 
 | ||||
|     const Icon** icons = animation->icons; | ||||
|     uint16_t frames = animation->active_frames + animation->passive_frames; | ||||
|     furi_assert(frames > 0); | ||||
|     const Icon* icon = &animation->icon_animation; | ||||
|     for(int i = 0; i < icon->frame_count; ++i) { | ||||
|         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); | ||||
| } | ||||
| 
 | ||||
| static bool animation_storage_load_frames( | ||||
| @ -230,24 +269,37 @@ static bool animation_storage_load_frames( | ||||
|     const char* name, | ||||
|     BubbleAnimation* animation, | ||||
|     uint32_t* frame_order, | ||||
|     uint32_t width, | ||||
|     uint32_t height) { | ||||
|     furi_assert(!animation->icons); | ||||
|     uint16_t frame_order_size = animation->passive_frames + animation->active_frames; | ||||
|     uint8_t width, | ||||
|     uint8_t height) { | ||||
|     uint16_t frame_order_count = 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; | ||||
|     animation->icons = furi_alloc(sizeof(const Icon*) * frame_order_size); | ||||
|     File* file = storage_file_alloc(storage); | ||||
|     FileInfo file_info; | ||||
|     string_t filename; | ||||
|     string_init(filename); | ||||
|     size_t max_filesize = ROUND_UP_TO(width, 8) * height + 1; | ||||
| 
 | ||||
|     for(int i = 0; i < frame_order_size; ++i) { | ||||
|         if(animation->icons[i]) continue; | ||||
| 
 | ||||
|     for(int i = 0; i < icon->frame_count; ++i) { | ||||
|         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(file_info.size > max_filesize) { | ||||
| @ -265,24 +317,12 @@ static bool animation_storage_load_frames( | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         Icon* icon = animation_storage_alloc_icon(file_info.size); | ||||
|         if(storage_file_read(file, (void*)icon->frames[0], file_info.size) != file_info.size) { | ||||
|         icon->frames[i] = furi_alloc(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)); | ||||
|             animation_storage_free_icon(icon); | ||||
|             break; | ||||
|         } | ||||
|         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; | ||||
|     } | ||||
| 
 | ||||
| @ -295,11 +335,10 @@ static bool animation_storage_load_frames( | ||||
|             height, | ||||
|             file_info.size); | ||||
|         animation_storage_free_frames(animation); | ||||
|         animation->icons = NULL; | ||||
|     } else { | ||||
|         for(int i = 0; i < frame_order_size; ++i) { | ||||
|             furi_check(animation->icons[i]); | ||||
|             furi_check(animation->icons[i]->frames[0]); | ||||
|         furi_check(animation->icon_animation.frames); | ||||
|         for(int i = 0; i < animation->icon_animation.frame_count; ++i) { | ||||
|             furi_check(animation->icon_animation.frames[i]); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -314,72 +353,73 @@ static bool animation_storage_load_bubbles(BubbleAnimation* animation, FlipperFi | ||||
|     string_t str; | ||||
|     string_init(str); | ||||
|     bool success = false; | ||||
|     furi_assert(!animation->frame_bubbles); | ||||
|     furi_assert(!animation->frame_bubble_sequences); | ||||
| 
 | ||||
|     do { | ||||
|         if(!flipper_file_read_uint32(ff, "Bubble slots", &u32value, 1)) break; | ||||
|         if(u32value > 20) break; | ||||
|         animation->frame_bubbles_count = u32value; | ||||
|         if(animation->frame_bubbles_count == 0) { | ||||
|             animation->frame_bubbles = NULL; | ||||
|         animation->frame_bubble_sequences_count = u32value; | ||||
|         if(animation->frame_bubble_sequences_count == 0) { | ||||
|             animation->frame_bubble_sequences = NULL; | ||||
|             success = true; | ||||
|             break; | ||||
|         } | ||||
|         animation->frame_bubbles = | ||||
|             furi_alloc(sizeof(FrameBubble*) * animation->frame_bubbles_count); | ||||
|         animation->frame_bubble_sequences = | ||||
|             furi_alloc(sizeof(FrameBubble*) * animation->frame_bubble_sequences_count); | ||||
| 
 | ||||
|         uint32_t current_slot = 0; | ||||
|         for(int i = 0; i < animation->frame_bubbles_count; ++i) { | ||||
|             animation->frame_bubbles[i] = furi_alloc(sizeof(FrameBubble)); | ||||
|         for(int i = 0; i < animation->frame_bubble_sequences_count; ++i) { | ||||
|             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; | ||||
|         for(;;) { | ||||
|             if(!flipper_file_read_uint32(ff, "Slot", ¤t_slot, 1)) break; | ||||
|             if((current_slot != 0) && (index == -1)) break; | ||||
| 
 | ||||
|             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; | ||||
|             } else if(current_slot == index + 1) { | ||||
|                 ++index; | ||||
|                 bubble = animation->frame_bubbles[index]; | ||||
|                 bubble = animation->frame_bubble_sequences[index]; | ||||
|             } else { | ||||
|                 /* slots have to start from 0, be ascending sorted, and
 | ||||
|                  * have exact number of slots as specified in "Bubble slots" */ | ||||
|                 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; | ||||
|             bubble->bubble.x = u32value; | ||||
|             FURI_CONST_ASSIGN(bubble->bubble.x, u32value); | ||||
|             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(string_size(str) > 100) break; | ||||
| 
 | ||||
|             string_replace_all_str(str, "\\n", "\n"); | ||||
| 
 | ||||
|             bubble->bubble.str = furi_alloc(string_size(str) + 1); | ||||
|             strcpy((char*)bubble->bubble.str, string_get_cstr(str)); | ||||
|             FURI_CONST_ASSIGN_PTR(bubble->bubble.text, furi_alloc(string_size(str) + 1)); | ||||
|             strcpy((char*)bubble->bubble.text, string_get_cstr(str)); | ||||
| 
 | ||||
|             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(!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; | ||||
|             bubble->starts_at_frame = u32value; | ||||
|             FURI_CONST_ASSIGN(bubble->start_frame, u32value); | ||||
|             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); | ||||
| 
 | ||||
|     if(!success) { | ||||
|         if(animation->frame_bubbles) { | ||||
|         if(animation->frame_bubble_sequences) { | ||||
|             FURI_LOG_E(TAG, "Failed to load animation bubbles"); | ||||
|             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); | ||||
|     string_t str; | ||||
|     string_init(str); | ||||
|     animation->frame_bubbles = NULL; | ||||
|     animation->frame_bubble_sequences = NULL; | ||||
| 
 | ||||
|     bool success = false; | ||||
|     do { | ||||
| @ -424,8 +464,18 @@ static BubbleAnimation* animation_storage_load_animation(const char* name) { | ||||
|         animation->active_frames = u32value; | ||||
| 
 | ||||
|         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); | ||||
|         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 */ | ||||
|         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; | ||||
|         animation->active_cycles = u32value; | ||||
|         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; | ||||
|         animation->duration = u32value; | ||||
|         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(animation->frame_order) { | ||||
|             free((void*)animation->frame_order); | ||||
|         } | ||||
|         free(animation); | ||||
|         animation = NULL; | ||||
|     } | ||||
| @ -460,10 +513,10 @@ static BubbleAnimation* animation_storage_load_animation(const char* name) { | ||||
| } | ||||
| 
 | ||||
| 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;) { | ||||
|         FrameBubble** bubble = &animation->frame_bubbles[i]; | ||||
|     for(int i = 0; i < animation->frame_bubble_sequences_count;) { | ||||
|         const FrameBubble* const* bubble = &animation->frame_bubble_sequences[i]; | ||||
| 
 | ||||
|         if((*bubble) == NULL) break; | ||||
| 
 | ||||
| @ -471,15 +524,15 @@ static void animation_storage_free_bubbles(BubbleAnimation* animation) { | ||||
|             bubble = &(*bubble)->next_bubble; | ||||
|         } | ||||
| 
 | ||||
|         if((*bubble)->bubble.str) { | ||||
|             free((void*)(*bubble)->bubble.str); | ||||
|         if((*bubble)->bubble.text) { | ||||
|             free((void*)(*bubble)->bubble.text); | ||||
|         } | ||||
|         if((*bubble) == animation->frame_bubbles[i]) { | ||||
|         if((*bubble) == animation->frame_bubble_sequences[i]) { | ||||
|             ++i; | ||||
|         } | ||||
|         free(*bubble); | ||||
|         *bubble = NULL; | ||||
|         free((void*)*bubble); | ||||
|         FURI_CONST_ASSIGN_PTR(*bubble, NULL); | ||||
|     } | ||||
|     free(animation->frame_bubbles); | ||||
|     animation->frame_bubbles = NULL; | ||||
|     free((void*)animation->frame_bubble_sequences); | ||||
|     animation->frame_bubble_sequences = NULL; | ||||
| } | ||||
|  | ||||
| @ -4,15 +4,6 @@ | ||||
| #include "views/bubble_animation_view.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.
 | ||||
|  * Contains all, including animation playing data (BubbleAnimation), | ||||
|  * data for random animation selection (StorageAnimationMeta) and | ||||
| @ -20,13 +11,13 @@ | ||||
| typedef struct StorageAnimation StorageAnimation; | ||||
| 
 | ||||
| typedef struct { | ||||
|     string_t name; | ||||
|     const char* name; | ||||
|     uint8_t min_butthurt; | ||||
|     uint8_t max_butthurt; | ||||
|     uint8_t min_level; | ||||
|     uint8_t max_level; | ||||
|     uint8_t weight; | ||||
| } StorageAnimationMeta; | ||||
| } StorageAnimationManifestInfo; | ||||
| 
 | ||||
| /** Container to return available animations list */ | ||||
| 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. | ||||
|  * @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 | ||||
|  | ||||
| @ -1,195 +1,9 @@ | ||||
| #pragma once | ||||
| #include "animation_storage.h" | ||||
| #include "assets_icons.h" | ||||
| #include "animation_manager.h" | ||||
| #include "gui/canvas.h" | ||||
| 
 | ||||
| struct StorageAnimation { | ||||
|     const BubbleAnimation* animation; | ||||
|     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_storage.h" | ||||
| #include "furi_hal_delay.h" | ||||
| #include "furi_hal_resources.h" | ||||
| #include "furi/check.h" | ||||
| #include "furi/memmgr.h" | ||||
| #include "gui/canvas.h" | ||||
| #include "gui/elements.h" | ||||
| #include "gui/view.h" | ||||
| #include "input/input.h" | ||||
| #include "bubble_animation_view.h" | ||||
| 
 | ||||
| #include <furi_hal.h> | ||||
| #include <furi.h> | ||||
| #include "portmacro.h" | ||||
| #include <gui/icon.h> | ||||
| #include <gui/canvas.h> | ||||
| #include <gui/elements.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/icon_i.h> | ||||
| #include <input/input.h> | ||||
| #include <stdint.h> | ||||
| #include <FreeRTOS.h> | ||||
| #include <timers.h> | ||||
| #include "bubble_animation_view.h" | ||||
| #include <gui/icon_i.h> | ||||
| #include <furi/dangerous_defines.h> | ||||
| 
 | ||||
| #define ACTIVE_SHIFT 2 | ||||
| 
 | ||||
| @ -43,7 +39,7 @@ struct BubbleAnimationView { | ||||
| static void bubble_animation_activate(BubbleAnimationView* view, bool force); | ||||
| 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); | ||||
|     uint8_t icon_index = 0; | ||||
|     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)); | ||||
| 
 | ||||
|     return icon_index; | ||||
|     return animation->frame_order[icon_index]; | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
| 
 | ||||
|     const Icon* icon = animation->icons[bubble_animation_get_icon_index(model)]; | ||||
|     furi_assert(icon); | ||||
|     uint8_t y_offset = canvas_height(canvas) - icon_get_height(icon); | ||||
|     canvas_draw_icon(canvas, 0, y_offset, icon); | ||||
|     uint8_t index = bubble_animation_get_frame_index(model); | ||||
|     uint8_t width = icon_get_width(&animation->icon_animation); | ||||
|     uint8_t height = icon_get_height(&animation->icon_animation); | ||||
|     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; | ||||
|     if(bubble) { | ||||
|         if((model->current_frame >= bubble->starts_at_frame) && | ||||
|            (model->current_frame <= bubble->ends_at_frame)) { | ||||
|         if((model->current_frame >= bubble->start_frame) && | ||||
|            (model->current_frame <= bubble->end_frame)) { | ||||
|             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) { | ||||
|     FrameBubble* bubble = NULL; | ||||
| static const FrameBubble* | ||||
|     bubble_animation_pick_bubble(BubbleAnimationViewModel* model, bool active) { | ||||
|     const FrameBubble* bubble = NULL; | ||||
| 
 | ||||
|     if((model->active_bubbles == 0) && (model->passive_bubbles == 0)) { | ||||
|         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); | ||||
|     const BubbleAnimation* animation = model->current; | ||||
| 
 | ||||
|     for(int i = 0; i < animation->frame_bubbles_count; ++i) { | ||||
|         if((animation->frame_bubbles[i]->starts_at_frame < animation->passive_frames) ^ active) { | ||||
|     for(int i = 0; i < animation->frame_bubble_sequences_count; ++i) { | ||||
|         if((animation->frame_bubble_sequences[i]->start_frame < animation->passive_frames) ^ | ||||
|            active) { | ||||
|             if(!index) { | ||||
|                 bubble = animation->frame_bubbles[i]; | ||||
|                 bubble = animation->frame_bubble_sequences[i]; | ||||
|             } | ||||
|             --index; | ||||
|         } | ||||
| @ -135,10 +135,6 @@ static bool bubble_animation_input_callback(InputEvent* event, void* 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; | ||||
| @ -190,7 +186,7 @@ static void bubble_animation_activate_right_now(BubbleAnimationView* view) { | ||||
|     if(model->current && (model->current->active_frames > 0) && (!model->freeze_frame)) { | ||||
|         model->current_frame = model->current->passive_frames; | ||||
|         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); | ||||
| 
 | ||||
| @ -222,7 +218,7 @@ static void bubble_animation_next_frame(BubbleAnimationViewModel* model) { | ||||
|         } | ||||
| 
 | ||||
|         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; | ||||
|             } | ||||
|         } | ||||
| @ -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->frames); | ||||
|     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; | ||||
|     icon_clone->frames[0] = furi_alloc(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; | ||||
| } | ||||
| @ -288,7 +289,7 @@ static void bubble_animation_enter(void* context) { | ||||
|     bubble_animation_activate(view, false); | ||||
| 
 | ||||
|     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); | ||||
| 
 | ||||
|     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_bubbles = 0; | ||||
|     model->passive_bubbles = 0; | ||||
|     for(int i = 0; i < new_animation->frame_bubbles_count; ++i) { | ||||
|         if(new_animation->frame_bubbles[i]->starts_at_frame < new_animation->passive_frames) { | ||||
|     for(int i = 0; i < new_animation->frame_bubble_sequences_count; ++i) { | ||||
|         if(new_animation->frame_bubble_sequences[i]->start_frame < new_animation->passive_frames) { | ||||
|             ++model->passive_bubbles; | ||||
|         } else { | ||||
|             ++model->active_bubbles; | ||||
| @ -367,7 +368,7 @@ void bubble_animation_view_set_animation( | ||||
|     model->active_cycle = 0; | ||||
|     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) { | ||||
| @ -376,12 +377,7 @@ void bubble_animation_freeze(BubbleAnimationView* view) { | ||||
|     BubbleAnimationViewModel* model = view_get_model(view->view); | ||||
|     furi_assert(model->current); | ||||
|     furi_assert(!model->freeze_frame); | ||||
|     /* always freeze first passive frame, because
 | ||||
|      * 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->freeze_frame = bubble_animation_clone_first_frame(&model->current->icon_animation); | ||||
|     model->current = NULL; | ||||
|     view_commit_model(view->view, false); | ||||
|     osTimerStop(view->timer); | ||||
| @ -395,8 +391,7 @@ void bubble_animation_unfreeze(BubbleAnimationView* view) { | ||||
|     furi_assert(model->freeze_frame); | ||||
|     bubble_animation_release_frame(&model->freeze_frame); | ||||
|     furi_assert(model->current); | ||||
|     furi_assert(model->current->icons); | ||||
|     frame_rate = model->current->frame_rate; | ||||
|     frame_rate = model->current->icon_animation.frame_rate; | ||||
|     view_commit_model(view->view, true); | ||||
| 
 | ||||
|     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 "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) { | ||||
|     furi_assert(canvas); | ||||
| @ -50,44 +49,27 @@ Desktop* desktop_alloc() { | ||||
|     view_dispatcher_set_navigation_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(); | ||||
|     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->debug_view = desktop_debug_alloc(); | ||||
|     desktop->first_start_view = desktop_first_start_alloc(); | ||||
|     desktop->hw_mismatch_popup = popup_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( | ||||
|         desktop->view_dispatcher, | ||||
|         DesktopViewMain, | ||||
|         view_composed_get_view(desktop->main_view_composed)); | ||||
|         desktop->view_dispatcher, DesktopViewMain, view_stack_get_view(desktop->main_view_stack)); | ||||
|     view_dispatcher_add_view( | ||||
|         desktop->view_dispatcher, | ||||
|         DesktopViewLockMenu, | ||||
|         desktop_lock_menu_get_view(desktop->lock_menu)); | ||||
|     view_dispatcher_add_view( | ||||
|         desktop->view_dispatcher, DesktopViewDebug, desktop_debug_get_view(desktop->debug_view)); | ||||
|     view_dispatcher_add_view( | ||||
|         desktop->view_dispatcher, | ||||
|         DesktopViewLocked, | ||||
|         view_composed_get_view(desktop->locked_view_composed)); | ||||
|     view_dispatcher_add_view( | ||||
|         desktop->view_dispatcher, | ||||
|         DesktopViewFirstStart, | ||||
| @ -123,8 +105,8 @@ void desktop_free(Desktop* desktop) { | ||||
|     scene_manager_free(desktop->scene_manager); | ||||
| 
 | ||||
|     animation_manager_free(desktop->animation_manager); | ||||
|     view_composed_free(desktop->main_view_composed); | ||||
|     view_composed_free(desktop->locked_view_composed); | ||||
|     view_stack_free(desktop->main_view_stack); | ||||
|     view_stack_free(desktop->locked_view_stack); | ||||
|     desktop_main_free(desktop->main_view); | ||||
|     desktop_lock_menu_free(desktop->lock_menu); | ||||
|     desktop_locked_free(desktop->locked_view); | ||||
| @ -163,15 +145,14 @@ int32_t desktop_srv(void* p) { | ||||
|         SAVE_DESKTOP_SETTINGS(&desktop->settings); | ||||
|     } | ||||
| 
 | ||||
|     scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); | ||||
| 
 | ||||
|     if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) { | ||||
|         furi_hal_usb_disable(); | ||||
|         scene_manager_set_scene_state( | ||||
|             desktop->scene_manager, DesktopSceneLocked, DesktopLockedWithPin); | ||||
|         scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); | ||||
|             desktop->scene_manager, DesktopSceneMain, DesktopMainSceneStateLockedWithPin); | ||||
|     } | ||||
| 
 | ||||
|     scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); | ||||
| 
 | ||||
|     if(desktop_is_first_start()) { | ||||
|         scene_manager_next_scene(desktop->scene_manager, DesktopSceneFirstStart); | ||||
|     } | ||||
|  | ||||
| @ -1,31 +1,21 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "cmsis_os2.h" | ||||
| #include "desktop.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_first_start.h" | ||||
| #include "views/desktop_lock_menu.h" | ||||
| #include "views/desktop_locked.h" | ||||
| #include "views/desktop_debug.h" | ||||
| 
 | ||||
| #include "scenes/desktop_scene.h" | ||||
| #include "desktop/desktop_settings/desktop_settings.h" | ||||
| #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 | ||||
| 
 | ||||
| @ -54,12 +44,11 @@ struct Desktop { | ||||
|     DesktopDebugView* debug_view; | ||||
|     CodeInput* code_input; | ||||
| 
 | ||||
|     View* dolphin_view; | ||||
|     DesktopMainView* main_view; | ||||
|     DesktopLockedView* locked_view; | ||||
| 
 | ||||
|     ViewComposed* main_view_composed; | ||||
|     ViewComposed* locked_view_composed; | ||||
|     ViewStack* main_view_stack; | ||||
|     ViewStack* locked_view_stack; | ||||
| 
 | ||||
|     DesktopSettings settings; | ||||
|     PinCode pincode_buffer; | ||||
| @ -69,8 +58,6 @@ struct Desktop { | ||||
|     AnimationManager* animation_manager; | ||||
|     osSemaphoreId_t unload_animation_semaphore; | ||||
|     FuriPubSubSubscription* app_start_stop_subscription; | ||||
| 
 | ||||
|     char* text_buffer; | ||||
| }; | ||||
| 
 | ||||
| Desktop* desktop_alloc(); | ||||
|  | ||||
| @ -1,4 +1,6 @@ | ||||
| #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) { | ||||
|     furi_assert(context); | ||||
|  | ||||
| @ -1,8 +1,6 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <furi.h> | ||||
| #include <gui/gui.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/view_dispatcher.h> | ||||
| #include <gui/scene_manager.h> | ||||
| #include <gui/modules/submenu.h> | ||||
| @ -10,8 +8,6 @@ | ||||
| 
 | ||||
| #include "desktop_settings.h" | ||||
| 
 | ||||
| #include "scenes/desktop_settings_scene.h" | ||||
| 
 | ||||
| typedef enum { | ||||
|     CodeEventsSetPin, | ||||
|     CodeEventsChangePin, | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| #include "../desktop_settings_app.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) { | ||||
|     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) { | ||||
|     DesktopSettingsApp* app = context; | ||||
|     Submenu* submenu = app->submenu; | ||||
|     submenu_clean(submenu); | ||||
|     submenu_reset(submenu); | ||||
| 
 | ||||
|     for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { | ||||
|         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) { | ||||
|     DesktopSettingsApp* app = context; | ||||
|     SAVE_DESKTOP_SETTINGS(&app->settings); | ||||
|     submenu_clean(app->submenu); | ||||
|     submenu_reset(app->submenu); | ||||
| } | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| #include "../desktop_settings_app.h" | ||||
| #include "desktop/desktop_settings/desktop_settings.h" | ||||
| #include "desktop_settings_scene.h" | ||||
| 
 | ||||
| #define SCENE_EXIT_EVENT (0U) | ||||
| 
 | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| #include "../desktop_settings_app.h" | ||||
| #include "applications.h" | ||||
| #include "desktop_settings_scene.h" | ||||
| 
 | ||||
| static void desktop_settings_scene_pincode_menu_submenu_callback(void* context, uint32_t index) { | ||||
|     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) { | ||||
|     DesktopSettingsApp* app = context; | ||||
|     Submenu* submenu = app->submenu; | ||||
|     submenu_clean(submenu); | ||||
|     submenu_reset(submenu); | ||||
| 
 | ||||
|     if(!app->settings.pincode.length) { | ||||
|         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) { | ||||
|     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 "applications.h" | ||||
| #include "desktop_settings_scene.h" | ||||
| 
 | ||||
| enum DesktopSettingsStartSubmenuIndex { | ||||
|     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) { | ||||
|     DesktopSettingsApp* app = context; | ||||
|     submenu_clean(app->submenu); | ||||
|     submenu_reset(app->submenu); | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| ADD_SCENE(desktop, main, Main) | ||||
| ADD_SCENE(desktop, lock_menu, LockMenu) | ||||
| ADD_SCENE(desktop, locked, Locked) | ||||
| ADD_SCENE(desktop, debug, Debug) | ||||
| ADD_SCENE(desktop, first_start, FirstStart) | ||||
| ADD_SCENE(desktop, hw_mismatch, HwMismatch) | ||||
|  | ||||
| @ -1,8 +1,11 @@ | ||||
| #include "../desktop_i.h" | ||||
| #include "../views/desktop_debug.h" | ||||
| 
 | ||||
| #include <dolphin/dolphin.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) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||
| @ -31,13 +34,13 @@ bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) { | ||||
|             break; | ||||
| 
 | ||||
|         case DesktopDebugEventDeed: | ||||
|             dolphin_deed(dolphin, DolphinDeedIButtonEmulate); | ||||
|             dolphin_deed(dolphin, DolphinDeedIbuttonEmulate); | ||||
|             desktop_debug_get_dolphin_data(desktop->debug_view); | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
|         case DesktopDebugEventWrongDeed: | ||||
|             dolphin_deed(dolphin, DolphinDeedWrong); | ||||
|             dolphin_deed(dolphin, DolphinDeedIbuttonRead); | ||||
|             desktop_debug_get_dolphin_data(desktop->debug_view); | ||||
|             consumed = true; | ||||
|             break; | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| #include <furi_hal.h> | ||||
| 
 | ||||
| #include "../desktop_i.h" | ||||
| 
 | ||||
| #define DesktopFaultEventExit 0x00FF00FF | ||||
|  | ||||
| @ -1,3 +1,6 @@ | ||||
| #include <power/power_service/power.h> | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| #include "../desktop_i.h" | ||||
| #include "../views/desktop_first_start.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 "desktop_scene.h" | ||||
| #include "../desktop_i.h" | ||||
| 
 | ||||
| #define HW_MISMATCH_BACK_EVENT (0UL) | ||||
| 
 | ||||
| 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) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     furi_assert(desktop); | ||||
|     furi_assert(!desktop->text_buffer); | ||||
|     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( | ||||
|         desktop->text_buffer, | ||||
|         text_buffer, | ||||
|         256, | ||||
|         "HW target: %d\nFW target: %d", | ||||
|         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_header( | ||||
|         popup, "!!!! HW Mismatch !!!!", 60, 14 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); | ||||
|     popup_set_text( | ||||
|         popup, desktop->text_buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); | ||||
|     popup_set_text(popup, text_buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); | ||||
|     popup_set_callback(popup, desktop_scene_hw_mismatch_callback); | ||||
|     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) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     furi_assert(desktop); | ||||
|     furi_assert(desktop->text_buffer); | ||||
|     Popup* popup = desktop->hw_mismatch_popup; | ||||
|     popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); | ||||
|     popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); | ||||
|     popup_set_callback(popup, NULL); | ||||
|     popup_set_context(popup, NULL); | ||||
|     free(desktop->text_buffer); | ||||
|     desktop->text_buffer = NULL; | ||||
|     char* text_buffer = | ||||
|         (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 <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) { | ||||
|     Desktop* desktop = (Desktop*)context; | ||||
|     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_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); | ||||
| } | ||||
| 
 | ||||
| @ -26,24 +32,25 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { | ||||
|         switch(event.event) { | ||||
|         case DesktopLockMenuEventLock: | ||||
|             scene_manager_set_scene_state( | ||||
|                 desktop->scene_manager, DesktopSceneLocked, DesktopLockedNoPin); | ||||
|             scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); | ||||
|                 desktop->scene_manager, DesktopSceneMain, DesktopMainSceneStateLockedNoPin); | ||||
|             scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); | ||||
|             scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case DesktopLockMenuEventPinLock: | ||||
|             if(desktop->settings.pincode.length > 0) { | ||||
|                 furi_hal_rtc_set_flag(FuriHalRtcFlagLock); | ||||
|                 furi_hal_usb_disable(); | ||||
|                 scene_manager_set_scene_state( | ||||
|                     desktop->scene_manager, DesktopSceneLocked, DesktopLockedWithPin); | ||||
|                 scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked); | ||||
|                     desktop->scene_manager, DesktopSceneMain, DesktopMainSceneStateLockedWithPin); | ||||
|                 scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain); | ||||
|             } else { | ||||
|                 scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 1); | ||||
|                 scene_manager_next_scene(desktop->scene_manager, DesktopScenePinSetup); | ||||
|             } | ||||
| 
 | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case DesktopLockMenuEventExit: | ||||
|             scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); | ||||
|             scene_manager_search_and_switch_to_previous_scene( | ||||
|                 desktop->scene_manager, DesktopSceneMain); | ||||
|             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) { | ||||
|     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 "../views/desktop_main.h" | ||||
| #include "applications.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 <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <applications.h> | ||||
| #include <assets_icons.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) | ||||
| 
 | ||||
| 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); | ||||
|     animation_manager_set_interact_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); | ||||
|     Loader* loader = furi_record_open("loader"); | ||||
|     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); | ||||
|     view_port_enabled_set(desktop->lock_viewport, false); | ||||
| 
 | ||||
|     if(scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneMain) == | ||||
|        DesktopMainEventUnlocked) { | ||||
|         desktop_main_unlocked(desktop->main_view); | ||||
|     DesktopMainSceneState state = | ||||
|         scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneMain); | ||||
|     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); | ||||
| @ -120,22 +131,22 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { | ||||
| 
 | ||||
|         case DesktopMainEventOpenArchive: | ||||
| #ifdef APP_ARCHIVE | ||||
|             animation_manager_unload_and_stall_animation(desktop->animation_manager); | ||||
|             desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE); | ||||
|             animation_manager_load_and_continue_animation(desktop->animation_manager); | ||||
| #endif | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
|         case DesktopMainEventOpenFavorite: | ||||
|             LOAD_DESKTOP_SETTINGS(&desktop->settings); | ||||
|             animation_manager_unload_and_stall_animation(desktop->animation_manager); | ||||
|             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 { | ||||
|                 FURI_LOG_E("DesktopSrv", "Can't find favorite application"); | ||||
|             } | ||||
|             animation_manager_load_and_continue_animation(desktop->animation_manager); | ||||
|             consumed = true; | ||||
|             break; | ||||
| 
 | ||||
| @ -160,6 +171,18 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { | ||||
|             animation_manager_load_and_continue_animation(desktop->animation_manager); | ||||
|             consumed = true; | ||||
|             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: | ||||
|             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 | ||||
|      * 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); | ||||
| 
 | ||||
|     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_context(desktop->animation_manager, desktop); | ||||
|     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_hal.h> | ||||
| 
 | ||||
| #include "../desktop_i.h" | ||||
| #include "desktop_debug.h" | ||||
| 
 | ||||
| #include "dolphin/helpers/dolphin_state.h" | ||||
| #include "dolphin/dolphin.h" | ||||
| 
 | ||||
|  | ||||
| @ -1,12 +1,7 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/gui_i.h> | ||||
| #include <stdint.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" | ||||
| 
 | ||||
| typedef struct DesktopDebugView DesktopDebugView; | ||||
|  | ||||
| @ -6,6 +6,7 @@ typedef enum { | ||||
|     DesktopMainEventOpenFavorite, | ||||
|     DesktopMainEventOpenMenu, | ||||
|     DesktopMainEventOpenDebug, | ||||
|     DesktopMainEventUpdate, | ||||
|     DesktopMainEventUnlocked, | ||||
|     DesktopMainEventRightShort, | ||||
|     DesktopMainEventCheckAnimation, | ||||
| @ -14,8 +15,6 @@ typedef enum { | ||||
|     DesktopMainEventBeforeAppStarted, | ||||
|     DesktopMainEventAfterAppFinished, | ||||
|     DesktopLockedEventUnlock, | ||||
|     DesktopLockedEventUpdate, | ||||
|     DesktopLockedEventInputReset, | ||||
|     DesktopLockedEventCheckAnimation, | ||||
|     DesktopLockedEventMax, | ||||
|     DesktopDebugEventDeed, | ||||
|  | ||||
| @ -1,4 +1,6 @@ | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| #include <gui/elements.h> | ||||
| #include "../desktop_i.h" | ||||
| #include "desktop_first_start.h" | ||||
| 
 | ||||
|  | ||||
| @ -1,10 +1,7 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/gui_i.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/canvas.h> | ||||
| #include <gui/elements.h> | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #include "desktop_events.h" | ||||
| 
 | ||||
| typedef struct DesktopFirstStartView DesktopFirstStartView; | ||||
|  | ||||
| @ -1,7 +1,11 @@ | ||||
| #include <furi.h> | ||||
| #include <gui/elements.h> | ||||
| 
 | ||||
| #include "../desktop_i.h" | ||||
| #include "desktop_lock_menu.h" | ||||
| 
 | ||||
| #define LOCK_MENU_ITEMS_NB 3 | ||||
| 
 | ||||
| void desktop_lock_menu_set_callback( | ||||
|     DesktopLockMenuView* lock_menu, | ||||
|     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( | ||||
|         lock_menu->view, (DesktopLockMenuViewModel * model) { | ||||
|             model->idx = 0; | ||||
|             model->idx = idx; | ||||
|             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) { | ||||
|     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; | ||||
|     canvas_clear(canvas); | ||||
| @ -58,12 +63,13 @@ void desktop_lock_menu_render(Canvas* canvas, void* model) { | ||||
|     canvas_draw_icon(canvas, 116, 0 + STATUS_BAR_Y_SHIFT, &I_DoorRight_70x55); | ||||
|     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]; | ||||
| 
 | ||||
|         if(i == 1 && !m->pin_set) str = "Set PIN"; | ||||
|         if(m->hint_timeout && m->idx == 2 && m->idx == i) str = "Not implemented"; | ||||
| 
 | ||||
|         if(str != NULL) | ||||
|             canvas_draw_str_aligned( | ||||
|                 canvas, 64, 9 + (i * 17) + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter, str); | ||||
| 
 | ||||
| @ -88,9 +94,9 @@ bool desktop_lock_menu_input(InputEvent* event, void* context) { | ||||
|         lock_menu->view, (DesktopLockMenuViewModel * model) { | ||||
|             model->hint_timeout = 0; // clear hint timeout
 | ||||
|             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) { | ||||
|                 model->idx = CLAMP(model->idx + 1, 2, 0); | ||||
|                 model->idx = CLAMP(model->idx + 1, LOCK_MENU_ITEMS_NB - 1, 0); | ||||
|             } | ||||
|             idx = model->idx; | ||||
|             return true; | ||||
|  | ||||
| @ -1,10 +1,6 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/gui_i.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/canvas.h> | ||||
| #include <gui/elements.h> | ||||
| #include <furi.h> | ||||
| #include "desktop_events.h" | ||||
| 
 | ||||
| #define HINT_TIMEOUT 2 | ||||
| @ -32,6 +28,6 @@ void desktop_lock_menu_set_callback( | ||||
| 
 | ||||
| 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_reset_idx(DesktopLockMenuView* lock_menu); | ||||
| void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx); | ||||
| DesktopLockMenuView* desktop_lock_menu_alloc(); | ||||
| 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 <gui/gui_i.h> | ||||
| #include <gui/elements.h> | ||||
| #include "../desktop_i.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( | ||||
|     DesktopLockedView* locked_view, | ||||
| @ -12,98 +48,69 @@ void desktop_locked_set_callback( | ||||
|     locked_view->context = context; | ||||
| } | ||||
| 
 | ||||
| void locked_view_timer_callback(void* context) { | ||||
|     DesktopLockedView* locked_view = context; | ||||
|     locked_view->callback(DesktopLockedEventUpdate, locked_view->context); | ||||
| void locked_view_timer_callback(TimerHandle_t timer) { | ||||
|     DesktopLockedView* locked_view = pvTimerGetTimerID(timer); | ||||
|     locked_view->callback(DesktopMainEventUpdate, locked_view->context); | ||||
| } | ||||
| 
 | ||||
| void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view) { | ||||
|     with_view_model( | ||||
|         locked_view->view, (DesktopLockedViewModel * model) { | ||||
|             model->hint_expire_at = osKernelGetTickCount() + osKernelGetTickFreq(); | ||||
|             return true; | ||||
|         }); | ||||
| static void desktop_locked_update_hint_icon_timeout(DesktopLockedView* locked_view) { | ||||
|     DesktopLockedViewModel* model = view_get_model(locked_view->view); | ||||
|     model->hint_icon_expire_at = osKernelGetTickCount() + osKernelGetTickFreq(); | ||||
|     view_commit_model(locked_view->view, true); | ||||
| } | ||||
| 
 | ||||
| void desktop_locked_reset_door_pos(DesktopLockedView* locked_view) { | ||||
|     with_view_model( | ||||
|         locked_view->view, (DesktopLockedViewModel * model) { | ||||
| static void desktop_locked_reset_door_pos(DesktopLockedView* locked_view) { | ||||
|     DesktopLockedViewModel* model = view_get_model(locked_view->view); | ||||
|     model->animation_seq_end = false; | ||||
|     model->door_left_x = DOOR_L_POS; | ||||
|     model->door_right_x = DOOR_R_POS; | ||||
|             return true; | ||||
|         }); | ||||
|     view_commit_model(locked_view->view, true); | ||||
| } | ||||
| 
 | ||||
| void desktop_locked_manage_redraw(DesktopLockedView* locked_view) { | ||||
|     bool animation_seq_end; | ||||
| void desktop_locked_update(DesktopLockedView* locked_view) { | ||||
|     bool stop_timer = false; | ||||
| 
 | ||||
|     with_view_model( | ||||
|         locked_view->view, (DesktopLockedViewModel * model) { | ||||
|             model->animation_seq_end = !model->door_left_x; | ||||
|             animation_seq_end = model->animation_seq_end; | ||||
| 
 | ||||
|             if(!model->animation_seq_end) { | ||||
|     DesktopLockedViewModel* model = view_get_model(locked_view->view); | ||||
|     if(model->locked) { | ||||
|         if(model->door_left_x != DOOR_L_POS_MAX) { | ||||
|             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->hint_expire_at = !model->hint_expire_at; | ||||
|             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); | ||||
| 
 | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     if(animation_seq_end) { | ||||
|         osTimerStop(locked_view->timer); | ||||
|     if(stop_timer) { | ||||
|         xTimerStop(locked_view->timer, portMAX_DELAY); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void desktop_locked_reset_counter(DesktopLockedView* locked_view) { | ||||
|     locked_view->lock_count = 0; | ||||
|     locked_view->lock_lastpress = 0; | ||||
| 
 | ||||
|     with_view_model( | ||||
|         locked_view->view, (DesktopLockedViewModel * model) { | ||||
|             model->hint_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) { | ||||
| void desktop_locked_draw(Canvas* canvas, void* model) { | ||||
|     DesktopLockedViewModel* m = model; | ||||
|     uint32_t now = osKernelGetTickCount(); | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
| 
 | ||||
|     if(m->locked) { | ||||
|         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); | ||||
|     } | ||||
| 
 | ||||
|     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) { | ||||
|             canvas_set_font(canvas, FontPrimary); | ||||
|             elements_multiline_text_framed(canvas, 42, 30 + STATUS_BAR_Y_SHIFT, "Locked"); | ||||
| 
 | ||||
|         } else if(!m->pin_lock) { | ||||
|         } else if((now < m->hint_icon_expire_at) && !m->pin_locked) { | ||||
|             canvas_set_font(canvas, FontSecondary); | ||||
|             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:"); | ||||
|         } | ||||
|     } 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(context); | ||||
|     DesktopLockedView* locked_view = context; | ||||
| 
 | ||||
|     uint32_t press_time = 0; | ||||
|     bool locked = false; | ||||
|     bool locked_with_pin = false; | ||||
|     uint32_t press_time = xTaskGetTickCount(); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         locked_view->view, (DesktopLockedViewModel * model) { | ||||
|             locked_with_pin = model->pin_lock; | ||||
|             return false; | ||||
|         }); | ||||
| 
 | ||||
|     if(event->type == InputTypeShort) { | ||||
|         if(locked_with_pin) { | ||||
|             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); | ||||
|     { | ||||
|         DesktopLockedViewModel* model = view_get_model(locked_view->view); | ||||
|         bool changed = false; | ||||
|         locked = model->locked; | ||||
|         locked_with_pin = model->pin_locked; | ||||
|         if(!locked && model->unlocked_hint && event->type == InputTypePress) { | ||||
|             model->unlocked_hint = false; | ||||
|             changed = true; | ||||
|         } | ||||
|         view_commit_model(locked_view->view, changed); | ||||
|     } | ||||
| 
 | ||||
|             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); | ||||
|                 } | ||||
|             } | ||||
|     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; | ||||
|         } | ||||
|     } | ||||
|     // All events consumed
 | ||||
|     return true; | ||||
|         locked_view->pincode_input.length = 0; | ||||
|     } | ||||
| 
 | ||||
| void desktop_locked_enter(void* context) { | ||||
|     DesktopLockedView* locked_view = context; | ||||
|     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); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         locked_view->view, (DesktopLockedViewModel * model) { | ||||
|             if(model->animation) icon_animation_start(model->animation); | ||||
|             return false; | ||||
|         }); | ||||
|         if(match) { | ||||
|             desktop_locked_unlock(locked_view); | ||||
|         } | ||||
|     } else { | ||||
|         if(event->key == InputKeyBack) { | ||||
|             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; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| void desktop_locked_exit(void* context) { | ||||
|     DesktopLockedView* locked_view = context; | ||||
|     locked_view->lock_lastpress = press_time; | ||||
| 
 | ||||
|     with_view_model( | ||||
|         locked_view->view, (DesktopLockedViewModel * model) { | ||||
|             if(model->animation) icon_animation_stop(model->animation); | ||||
|             return false; | ||||
|         }); | ||||
|     return locked; | ||||
| } | ||||
| 
 | ||||
| DesktopLockedView* desktop_locked_alloc() { | ||||
|     DesktopLockedView* locked_view = furi_alloc(sizeof(DesktopLockedView)); | ||||
|     locked_view->view = view_alloc(); | ||||
|     locked_view->timer = | ||||
|         osTimerNew(locked_view_timer_callback, osTimerPeriodic, locked_view, NULL); | ||||
|         xTimerCreate("Locked view", 1000 / 16, pdTRUE, locked_view, locked_view_timer_callback); | ||||
| 
 | ||||
|     view_allocate_model(locked_view->view, ViewModelTypeLocking, sizeof(DesktopLockedViewModel)); | ||||
|     view_set_context(locked_view->view, locked_view); | ||||
|     view_set_draw_callback(locked_view->view, (ViewDrawCallback)desktop_locked_render); | ||||
|     view_set_draw_callback(locked_view->view, (ViewDrawCallback)desktop_locked_draw); | ||||
|     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; | ||||
| } | ||||
| @ -205,3 +199,49 @@ void desktop_locked_free(DesktopLockedView* locked_view) { | ||||
|     view_free(locked_view->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 | ||||
| 
 | ||||
| #include <gui/gui_i.h> | ||||
| #include <desktop/desktop_settings/desktop_settings.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/canvas.h> | ||||
| #include <gui/elements.h> | ||||
| #include <furi.h> | ||||
| #include "desktop_events.h" | ||||
| 
 | ||||
| #define UNLOCK_RST_TIMEOUT 300 | ||||
| #define UNLOCK_CNT 2 // 3 actually
 | ||||
| #define UNLOCK_CNT 3 | ||||
| 
 | ||||
| #define DOOR_L_POS -57 | ||||
| #define DOOR_L_POS_MAX 0 | ||||
| @ -24,40 +21,16 @@ typedef struct DesktopLockedView DesktopLockedView; | ||||
| 
 | ||||
| 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( | ||||
|     DesktopLockedView* locked_view, | ||||
|     DesktopLockedViewCallback callback, | ||||
|     void* context); | ||||
| 
 | ||||
| void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view); | ||||
| void desktop_locked_reset_counter(DesktopLockedView* locked_view); | ||||
| void desktop_locked_reset_door_pos(DesktopLockedView* locked_view); | ||||
| void desktop_locked_manage_redraw(DesktopLockedView* locked_view); | ||||
| void desktop_locked_update(DesktopLockedView* locked_view); | ||||
| 
 | ||||
| View* desktop_locked_get_view(DesktopLockedView* locked_view); | ||||
| DesktopLockedView* desktop_locked_alloc(); | ||||
| 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 "furi/record.h" | ||||
| #include "gui/canvas.h" | ||||
| #include "gui/view.h" | ||||
| #include "gui/view_composed.h" | ||||
| #include "input/input.h" | ||||
| #include <gui/gui_i.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/elements.h> | ||||
| #include <gui/canvas.h> | ||||
| #include <furi.h> | ||||
| #include <input/input.h> | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| #include "../desktop_i.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( | ||||
|     DesktopMainView* main_view, | ||||
| @ -19,59 +25,6 @@ void desktop_main_set_callback( | ||||
|     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) { | ||||
|     furi_assert(main_view); | ||||
|     return main_view->view; | ||||
| @ -82,58 +35,35 @@ bool desktop_main_input(InputEvent* event, void* context) { | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     DesktopMainView* main_view = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->key == InputKeyOk && event->type == InputTypeShort) { | ||||
|     if(event->type == InputTypeShort) { | ||||
|         if(event->key == InputKeyOk) { | ||||
|             main_view->callback(DesktopMainEventOpenMenu, main_view->context); | ||||
|     } else if(event->key == InputKeyDown && event->type == InputTypeLong) { | ||||
|         main_view->callback(DesktopMainEventOpenDebug, main_view->context); | ||||
|     } else if(event->key == InputKeyUp && event->type == InputTypeShort) { | ||||
|         } else if(event->key == InputKeyUp) { | ||||
|             main_view->callback(DesktopMainEventOpenLockMenu, main_view->context); | ||||
|     } else if(event->key == InputKeyDown && event->type == InputTypeShort) { | ||||
|         } else if(event->key == InputKeyDown) { | ||||
|             main_view->callback(DesktopMainEventOpenArchive, main_view->context); | ||||
|     } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { | ||||
|         } else if(event->key == InputKeyLeft) { | ||||
|             main_view->callback(DesktopMainEventOpenFavorite, main_view->context); | ||||
|     } else if(event->key == InputKeyRight && event->type == InputTypeShort) { | ||||
|         } else if(event->key == InputKeyRight) { | ||||
|             main_view->callback(DesktopMainEventRightShort, main_view->context); | ||||
|     } else if(event->key == InputKeyBack && event->type == InputTypeShort) { | ||||
|         consumed = true; | ||||
|         } | ||||
|     } else if(event->type == InputTypeLong) { | ||||
|         if(event->key == InputKeyDown) { | ||||
|             main_view->callback(DesktopMainEventOpenDebug, main_view->context); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     desktop_main_reset_hint(main_view); | ||||
| 
 | ||||
|     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; | ||||
|         }); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| DesktopMainView* desktop_main_alloc() { | ||||
|     DesktopMainView* main_view = furi_alloc(sizeof(DesktopMainView)); | ||||
| 
 | ||||
|     main_view->view = view_alloc(); | ||||
|     view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(DesktopMainViewModel)); | ||||
|     view_allocate_model(main_view->view, ViewModelTypeLockFree, 1); | ||||
|     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_enter_callback(main_view->view, desktop_main_enter); | ||||
|     view_set_exit_callback(main_view->view, desktop_main_exit); | ||||
| 
 | ||||
|     return main_view; | ||||
| } | ||||
| @ -143,11 +73,3 @@ void desktop_main_free(DesktopMainView* main_view) { | ||||
|     view_free(main_view->view); | ||||
|     free(main_view); | ||||
| } | ||||
| 
 | ||||
| void desktop_main_unlocked(DesktopMainView* main_view) { | ||||
|     with_view_model( | ||||
|         main_view->view, (DesktopMainViewModel * model) { | ||||
|             model->hint_expire_at = osKernelGetTickCount() + osKernelGetTickFreq(); | ||||
|             return true; | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @ -1,31 +1,12 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "gui/view_composed.h" | ||||
| #include <gui/gui_i.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/canvas.h> | ||||
| #include <gui/elements.h> | ||||
| #include <furi.h> | ||||
| #include "desktop_events.h" | ||||
| 
 | ||||
| typedef struct DesktopMainView DesktopMainView; | ||||
| 
 | ||||
| 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( | ||||
|     DesktopMainView* main_view, | ||||
|     DesktopMainViewCallback callback, | ||||
| @ -34,10 +15,3 @@ void desktop_main_set_callback( | ||||
| View* desktop_main_get_view(DesktopMainView* main_view); | ||||
| DesktopMainView* desktop_main_alloc(); | ||||
| 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 "desktop/desktop.h" | ||||
| #include "dolphin/helpers/dolphin_state.h" | ||||
| #include "dolphin_i.h" | ||||
| #include "furi/pubsub.h" | ||||
| #include "sys/_stdint.h" | ||||
| #include "portmacro.h" | ||||
| #include "projdefs.h" | ||||
| #include <furi_hal.h> | ||||
| #include <stdint.h> | ||||
| #include <furi.h> | ||||
| #define DOLPHIN_TIMEGATE 86400 // one day
 | ||||
| #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) { | ||||
|     furi_assert(dolphin); | ||||
|     DolphinEvent event; | ||||
| @ -39,12 +44,51 @@ void dolphin_flush(Dolphin* dolphin) { | ||||
|     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 = furi_alloc(sizeof(Dolphin)); | ||||
| 
 | ||||
|     dolphin->state = dolphin_state_alloc(); | ||||
|     dolphin->event_queue = osMessageQueueNew(8, sizeof(DolphinEvent), NULL); | ||||
|     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; | ||||
| } | ||||
| @ -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) { | ||||
|     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) { | ||||
|     Dolphin* dolphin = dolphin_alloc(); | ||||
|     furi_record_create("dolphin", dolphin); | ||||
| 
 | ||||
|     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; | ||||
|     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(dolphin_state_on_deed(dolphin->state, event.deed)) { | ||||
|                 dolphin_state_on_deed(dolphin->state, event.deed); | ||||
|                 DolphinPubsubEvent event = DolphinPubsubEventUpdate; | ||||
|                 furi_pubsub_publish(dolphin->pubsub, &event); | ||||
|                 } | ||||
|                 xTimerReset(dolphin->butthurt_timer, portMAX_DELAY); | ||||
|                 xTimerReset(dolphin->flush_timer, portMAX_DELAY); | ||||
|             } else if(event.type == DolphinEventTypeStats) { | ||||
|                 // TODO: correct icounter/butthurt changing, stub till then
 | ||||
|                 event.stats->icounter = 0; | ||||
|                 event.stats->butthurt = 0; | ||||
|                 event.stats->icounter = dolphin->state->data.icounter; | ||||
|                 event.stats->butthurt = dolphin->state->data.butthurt; | ||||
|                 event.stats->timestamp = dolphin->state->data.timestamp; | ||||
|                 event.stats->level = 1; | ||||
|                 event.stats->level_up_is_pending = 0; | ||||
|                 event.stats->level = dolphin_get_level(dolphin->state->data.icounter); | ||||
|                 event.stats->level_up_is_pending = | ||||
|                     !dolphin_state_xp_to_levelup(dolphin->state->data.icounter); | ||||
|             } else if(event.type == DolphinEventTypeFlush) { | ||||
|                 // TODO: correct icounter/butthurt changing, stub till then
 | ||||
|                 dolphin->state->data.butthurt = 0; | ||||
|                 dolphin->state->data.icounter = 0; | ||||
|                 FURI_LOG_I(TAG, "Flush stats"); | ||||
|                 dolphin_state_save(dolphin->state); | ||||
|             } 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_event_release(dolphin, &event); | ||||
|         } else { | ||||
|             dolphin_check_butthurt(dolphin->state); | ||||
|             dolphin_state_save(dolphin->state); | ||||
|             /* once per hour check rtc time is not changed */ | ||||
|             dolphin_update_clear_limits_timer_period(dolphin); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -5,6 +5,10 @@ | ||||
| #include "helpers/dolphin_deed.h" | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef struct Dolphin Dolphin; | ||||
| 
 | ||||
| typedef struct { | ||||
| @ -19,6 +23,13 @@ typedef enum { | ||||
|     DolphinPubsubEventUpdate, | ||||
| } 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.
 | ||||
|  * See dolphin_deed.h for available deeds. In futures it will become part of assets. | ||||
|  * Thread safe, async | ||||
| @ -38,3 +49,7 @@ void dolphin_flush(Dolphin* dolphin); | ||||
| void dolphin_upgrade_level(Dolphin* dolphin); | ||||
| 
 | ||||
| FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
|  | ||||
| @ -11,9 +11,8 @@ typedef enum { | ||||
|     DolphinEventTypeDeed, | ||||
|     DolphinEventTypeStats, | ||||
|     DolphinEventTypeFlush, | ||||
|     DolphinEventTypeAnimationStartNewIdle, | ||||
|     DolphinEventTypeAnimationCheckBlocking, | ||||
|     DolphinEventTypeAnimationInteract, | ||||
|     DolphinEventTypeIncreaseButthurt, | ||||
|     DolphinEventTypeClearLimits, | ||||
| } DolphinEventType; | ||||
| 
 | ||||
| typedef struct { | ||||
| @ -31,6 +30,9 @@ struct Dolphin { | ||||
|     // Queue
 | ||||
|     osMessageQueueId_t event_queue; | ||||
|     FuriPubSub* pubsub; | ||||
|     TimerHandle_t butthurt_timer; | ||||
|     TimerHandle_t flush_timer; | ||||
|     TimerHandle_t clear_limits_timer; | ||||
| }; | ||||
| 
 | ||||
| Dolphin* dolphin_alloc(); | ||||
|  | ||||
| @ -1,14 +1,65 @@ | ||||
| #include "dolphin_deed.h" | ||||
| #include <furi.h> | ||||
| 
 | ||||
| static const DolphinDeedWeight dolphin_deed_weights[DolphinDeedMax] = { | ||||
|     {1, -1, 60}, | ||||
|     {1, -1, 60}, | ||||
|     {1, -1, 60}, | ||||
|     {-1, 1, 60}, | ||||
| static const DolphinDeedWeight dolphin_deed_weights[] = { | ||||
|     {1, DolphinAppSubGhz}, // DolphinDeedSubGhzReceiverInfo
 | ||||
|     {3, DolphinAppSubGhz}, // DolphinDeedSubGhzSave
 | ||||
|     {1, DolphinAppSubGhz}, // DolphinDeedSubGhzRawRec
 | ||||
|     {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) { | ||||
|     furi_assert(deed < DolphinDeedMax); | ||||
|     return &dolphin_deed_weights[deed]; | ||||
| static uint8_t dolphin_deed_limits[] = { | ||||
|     15, // DolphinAppSubGhz
 | ||||
|     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> | ||||
| 
 | ||||
| /* Countable deed that affects icounter*/ | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef enum { | ||||
|     // iButton
 | ||||
|     DolphinDeedIButtonRead, | ||||
|     DolphinDeedIButtonWrite, | ||||
|     DolphinDeedIButtonEmulate, | ||||
|     // for debug
 | ||||
|     DolphinDeedWrong, | ||||
|     // Special value, do not use
 | ||||
|     DolphinDeedMax | ||||
|     DolphinAppSubGhz, | ||||
|     DolphinAppRfid, | ||||
|     DolphinAppNfc, | ||||
|     DolphinAppIr, | ||||
|     DolphinAppIbutton, | ||||
|     DolphinAppBadusb, | ||||
|     DolphinAppU2f, | ||||
|     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; | ||||
| 
 | ||||
| typedef struct { | ||||
|     int32_t icounter; // how many icounter get by Deed
 | ||||
|     int32_t butthurt; // how many icounter get by Deed
 | ||||
|     uint32_t limit_value; // how many deeds in limit interval
 | ||||
|     uint32_t limit_interval; // interval, in minutes
 | ||||
|     uint8_t icounter; | ||||
|     DolphinApp app; | ||||
| } 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/helpers/dolphin_deed.h" | ||||
| #include <stdint.h> | ||||
| #include <storage/storage.h> | ||||
| #include <furi.h> | ||||
| @ -10,9 +11,8 @@ | ||||
| #define DOLPHIN_STATE_PATH "/int/dolphin.state" | ||||
| #define DOLPHIN_STATE_HEADER_MAGIC 0xD0 | ||||
| #define DOLPHIN_STATE_HEADER_VERSION 0x01 | ||||
| #define DOLPHIN_LVL_THRESHOLD 20.0f | ||||
| #define LEVEL2_THRESHOLD 20 | ||||
| #define LEVEL3_THRESHOLD 100 | ||||
| #define LEVEL2_THRESHOLD 735 | ||||
| #define LEVEL3_THRESHOLD 2940 | ||||
| #define BUTTHURT_MAX 14 | ||||
| #define BUTTHURT_MIN 0 | ||||
| 
 | ||||
| @ -125,50 +125,68 @@ uint32_t dolphin_state_xp_to_levelup(uint32_t icounter) { | ||||
|     return threshold - icounter; | ||||
| } | ||||
| 
 | ||||
| bool dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) { | ||||
|     const DolphinDeedWeight* deed_weight = dolphin_deed_weight(deed); | ||||
|     int32_t icounter = dolphin_state->data.icounter + deed_weight->icounter; | ||||
|     bool level_up = false; | ||||
|     bool mood_changed = false; | ||||
| 
 | ||||
|     if(icounter <= 0) { | ||||
|         icounter = 0; | ||||
|         if(dolphin_state->data.icounter == 0) { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) { | ||||
|     DolphinApp app = dolphin_deed_get_app(deed); | ||||
|     int8_t weight_limit = | ||||
|         dolphin_deed_get_app_limit(app) - dolphin_state->data.icounter_daily_limit[app]; | ||||
|     uint8_t deed_weight = CLAMP(dolphin_deed_get_weight(deed), weight_limit, 0); | ||||
| 
 | ||||
|     uint8_t xp_to_levelup = dolphin_state_xp_to_levelup(dolphin_state->data.icounter); | ||||
|     if(xp_to_levelup) { | ||||
|         level_up = true; | ||||
|         dolphin_state->data.icounter += MIN(xp_to_levelup, deed_weight->icounter); | ||||
|         deed_weight = MIN(xp_to_levelup, deed_weight); | ||||
|         dolphin_state->data.icounter += deed_weight; | ||||
|         dolphin_state->data.icounter_daily_limit[app] += deed_weight; | ||||
|     } | ||||
| 
 | ||||
|     uint32_t new_butthurt = CLAMP( | ||||
|         ((int32_t)dolphin_state->data.butthurt) + deed_weight->butthurt, | ||||
|         BUTTHURT_MAX, | ||||
|         BUTTHURT_MIN); | ||||
|     /* decrease butthurt:
 | ||||
|      * 0 deeds accumulating --> 0 butthurt | ||||
|      * +1....+15 deeds accumulating --> -1 butthurt | ||||
|      * +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.timestamp = dolphin_state_timestamp(); | ||||
|     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) { | ||||
|     if(dolphin_state->data.butthurt < BUTTHURT_MAX) { | ||||
|         dolphin_state->data.butthurt++; | ||||
|         FURI_LOG_I("DolphinState", "Increasing butthurt"); | ||||
|         dolphin_state->data.timestamp = dolphin_state_timestamp(); | ||||
|         dolphin_state->dirty = true; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void dolphin_state_increase_level(DolphinState* dolphin_state) { | ||||
|     furi_assert(dolphin_state_is_levelup(dolphin_state->data.icounter)); | ||||
|     ++dolphin_state->data.icounter; | ||||
|     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 { | ||||
|     uint32_t limit_ibutton; | ||||
|     uint32_t limit_nfc; | ||||
|     uint32_t limit_ir; | ||||
|     uint32_t limit_rfid; | ||||
|     uint8_t icounter_daily_limit[DolphinAppMAX]; | ||||
|     uint8_t butthurt_daily_limit; | ||||
| 
 | ||||
|     uint32_t flags; | ||||
|     uint32_t icounter; | ||||
| @ -31,11 +29,11 @@ bool dolphin_state_save(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(); | ||||
| 
 | ||||
| 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); | ||||
| 
 | ||||
|  | ||||
| @ -95,5 +95,5 @@ bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) { | ||||
| 
 | ||||
| void gpio_scene_start_on_exit(void* 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, | ||||
|         GpioAppViewUsbUartCfg, | ||||
|         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); | ||||
| } | ||||
|  | ||||
| @ -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_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_serial_init(usb_uart, usb_uart->cfg.uart_ch); | ||||
|     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_hal.h> | ||||
| #include <stdint.h> | ||||
| #include <u8g2_glue.h> | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| 
 | ||||
| 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( | ||||
|     Canvas* canvas, | ||||
|     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); | ||||
| 
 | ||||
| /** 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.
 | ||||
|  * | ||||
|  * @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; | ||||
|     uint8_t x; | ||||
|     uint8_t x_used = 0; | ||||
| @ -140,6 +140,30 @@ void gui_redraw_status_bar(Gui* gui) { | ||||
|         } | ||||
|         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) { | ||||
| @ -171,11 +195,19 @@ void gui_redraw(Gui* gui) { | ||||
| 
 | ||||
|     canvas_reset(gui->canvas); | ||||
| 
 | ||||
|     if(gui->lockdown) { | ||||
|         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); | ||||
|             gui_redraw_status_bar(gui, false); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     canvas_commit(gui->canvas); | ||||
| @ -210,9 +242,15 @@ void gui_input(Gui* gui, InputEvent* input_event) { | ||||
| 
 | ||||
|     gui_lock(gui); | ||||
| 
 | ||||
|     ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]); | ||||
|     ViewPort* view_port = NULL; | ||||
| 
 | ||||
|     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) { | ||||
|         gui->ongoing_input_view_port = view_port; | ||||
| @ -366,10 +404,18 @@ void gui_set_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, vo | ||||
|     gui_unlock(gui); | ||||
| 
 | ||||
|     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 = furi_alloc(sizeof(Gui)); | ||||
|     // 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); | ||||
| 
 | ||||
| /** 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 | ||||
| } | ||||
| #endif | ||||
|  | ||||
| @ -49,6 +49,7 @@ struct Gui { | ||||
|     osMutexId_t mutex; | ||||
| 
 | ||||
|     // Layers and Canvas
 | ||||
|     bool lockdown; | ||||
|     ViewPortArray_t layers[GuiLayerMAX]; | ||||
|     Canvas* canvas; | ||||
|     GuiCanvasCommitCallback canvas_callback; | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
|  * GUI: internal Icon API | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| #include "icon.h" | ||||
| 
 | ||||
| struct Icon { | ||||
|  | ||||
| @ -241,7 +241,7 @@ View* button_menu_get_view(ButtonMenu* button_menu) { | ||||
|     return button_menu->view; | ||||
| } | ||||
| 
 | ||||
| void button_menu_clean(ButtonMenu* button_menu) { | ||||
| void button_menu_reset(ButtonMenu* button_menu) { | ||||
|     furi_assert(button_menu); | ||||
| 
 | ||||
|     with_view_model( | ||||
|  | ||||
| @ -39,7 +39,7 @@ View* button_menu_get_view(ButtonMenu* button_menu); | ||||
|  * | ||||
|  * @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
 | ||||
|  * | ||||
|  | ||||
| @ -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) { | ||||
|     furi_assert(button_panel); | ||||
| 
 | ||||
|     button_panel_clean(button_panel); | ||||
|     button_panel_reset(button_panel); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         button_panel->view, (ButtonPanelModel * model) { | ||||
| @ -125,7 +125,7 @@ void button_panel_free(ButtonPanel* button_panel) { | ||||
|     free(button_panel); | ||||
| } | ||||
| 
 | ||||
| void button_panel_clean(ButtonPanel* button_panel) { | ||||
| void button_panel_reset(ButtonPanel* button_panel) { | ||||
|     furi_assert(button_panel); | ||||
| 
 | ||||
|     with_view_model( | ||||
|  | ||||
| @ -39,7 +39,7 @@ void button_panel_free(ButtonPanel* button_panel); | ||||
|  * | ||||
|  * @param      button_panel  ButtonPanel instance | ||||
|  */ | ||||
| void button_panel_clean(ButtonPanel* button_panel); | ||||
| void button_panel_reset(ButtonPanel* button_panel); | ||||
| 
 | ||||
| /** 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); | ||||
|     TextElement clean_text_el = { | ||||
|         .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 | ||||
|  */ | ||||
| void dialog_ex_clean(DialogEx* dialog_ex); | ||||
| void dialog_ex_reset(DialogEx* dialog_ex); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
|  | ||||
| @ -77,7 +77,7 @@ static bool file_select_input_callback(InputEvent* event, void* context) { | ||||
|     FileSelect* file_select = (FileSelect*)context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == InputTypeShort) { | ||||
|     if((event->type == InputTypeShort) | (event->type == InputTypeRepeat)) { | ||||
|         if(!file_select->init_completed) { | ||||
|             if(!file_select_init_inner(file_select)) { | ||||
|                 file_select->callback(false, file_select->context); | ||||
|  | ||||
| @ -87,6 +87,14 @@ static bool menu_input_callback(InputEvent* event, void* context) { | ||||
|             consumed = true; | ||||
|             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; | ||||
| @ -138,7 +146,7 @@ Menu* menu_alloc() { | ||||
| 
 | ||||
| void menu_free(Menu* menu) { | ||||
|     furi_assert(menu); | ||||
|     menu_clean(menu); | ||||
|     menu_reset(menu); | ||||
|     view_free(menu->view); | ||||
|     free(menu); | ||||
| } | ||||
| @ -172,7 +180,7 @@ void menu_add_item( | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void menu_clean(Menu* menu) { | ||||
| void menu_reset(Menu* menu) { | ||||
|     furi_assert(menu); | ||||
|     with_view_model( | ||||
|         menu->view, (MenuModel * model) { | ||||
|  | ||||
| @ -59,7 +59,7 @@ void menu_add_item( | ||||
|  * | ||||
|  * @param      menu  Menu instance | ||||
|  */ | ||||
| void menu_clean(Menu* menu); | ||||
| void menu_reset(Menu* menu); | ||||
| 
 | ||||
| /** Set current menu item
 | ||||
|  * | ||||
|  | ||||
| @ -227,3 +227,19 @@ void popup_enable_timeout(Popup* popup) { | ||||
| void popup_disable_timeout(Popup* popup) { | ||||
|     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); | ||||
| 
 | ||||
| /** Reset popup instance state
 | ||||
|  * | ||||
|  * @param       popup Popup instance | ||||
|  */ | ||||
| void popup_reset(Popup* popup); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
|  | ||||
| @ -106,6 +106,14 @@ static bool submenu_view_input_callback(InputEvent* event, void* context) { | ||||
|         default: | ||||
|             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; | ||||
| @ -169,7 +177,7 @@ void submenu_add_item( | ||||
|         }); | ||||
| } | ||||
| 
 | ||||
| void submenu_clean(Submenu* submenu) { | ||||
| void submenu_reset(Submenu* submenu) { | ||||
|     furi_assert(submenu); | ||||
| 
 | ||||
|     with_view_model( | ||||
|  | ||||
| @ -57,7 +57,7 @@ void submenu_add_item( | ||||
|  * | ||||
|  * @param      submenu  Submenu instance | ||||
|  */ | ||||
| void submenu_clean(Submenu* submenu); | ||||
| void submenu_reset(Submenu* submenu); | ||||
| 
 | ||||
| /** Set submenu item selector
 | ||||
|  * | ||||
|  | ||||
| @ -164,7 +164,7 @@ View* text_box_get_view(TextBox* text_box) { | ||||
|     return text_box->view; | ||||
| } | ||||
| 
 | ||||
| void text_box_clean(TextBox* text_box) { | ||||
| void text_box_reset(TextBox* text_box) { | ||||
|     furi_assert(text_box); | ||||
| 
 | ||||
|     with_view_model( | ||||
|  | ||||
| @ -44,7 +44,7 @@ View* text_box_get_view(TextBox* text_box); | ||||
|  * | ||||
|  * @param      text_box  TextBox instance | ||||
|  */ | ||||
| void text_box_clean(TextBox* text_box); | ||||
| void text_box_reset(TextBox* text_box); | ||||
| 
 | ||||
| /** Set text for text_box
 | ||||
|  * | ||||
|  | ||||
| @ -4,6 +4,7 @@ | ||||
| 
 | ||||
| struct TextInput { | ||||
|     View* view; | ||||
|     osTimerId_t timer; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
| @ -23,6 +24,11 @@ typedef struct { | ||||
| 
 | ||||
|     uint8_t selected_row; | ||||
|     uint8_t selected_column; | ||||
| 
 | ||||
|     TextInputValidatorCallback validator_callback; | ||||
|     void* validator_callback_context; | ||||
|     string_t validator_text; | ||||
|     bool valadator_message_visible; | ||||
| } TextInputModel; | ||||
| 
 | ||||
| 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) { | ||||
| @ -295,7 +312,13 @@ static void text_input_handle_ok(TextInput* text_input) { | ||||
|             uint8_t text_length = strlen(model->text_buffer); | ||||
| 
 | ||||
|             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); | ||||
|                 } | ||||
|             } else if(selected == BACKSPACE_KEY) { | ||||
| @ -321,6 +344,16 @@ static bool text_input_view_input_callback(InputEvent* event, void* context) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     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) { | ||||
|         case InputKeyUp: | ||||
|             text_input_handle_up(text_input); | ||||
| @ -351,7 +384,11 @@ static bool text_input_view_input_callback(InputEvent* event, void* context) { | ||||
|        event->key == InputKeyBack) { | ||||
|         with_view_model( | ||||
|             text_input->view, (TextInputModel * model) { | ||||
|                 if(model->valadator_message_visible) { | ||||
|                     model->valadator_message_visible = false; | ||||
|                 } else { | ||||
|                     text_input_backspace_cb(model); | ||||
|                 } | ||||
|                 return true; | ||||
|             }); | ||||
| 
 | ||||
| @ -361,6 +398,17 @@ static bool text_input_view_input_callback(InputEvent* event, void* context) { | ||||
|     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 = furi_alloc(sizeof(TextInput)); | ||||
|     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_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; | ||||
| } | ||||
| 
 | ||||
| void text_input_free(TextInput* 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); | ||||
| 
 | ||||
|     free(text_input); | ||||
| } | ||||
| 
 | ||||
| void text_input_clean(TextInput* text_input) { | ||||
| void text_input_reset(TextInput* text_input) { | ||||
|     furi_assert(text_input); | ||||
|     with_view_model( | ||||
|         text_input->view, (TextInputModel * model) { | ||||
| @ -393,6 +463,10 @@ void text_input_clean(TextInput* text_input) { | ||||
|             model->text_buffer_size = 0; | ||||
|             model->callback = 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; | ||||
|         }); | ||||
| } | ||||
| @ -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) { | ||||
|     with_view_model( | ||||
|         text_input->view, (TextInputModel * model) { | ||||
|  | ||||
| @ -6,6 +6,8 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/view.h> | ||||
| #include "validators.h" | ||||
| #include <m-string.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| @ -14,6 +16,7 @@ extern "C" { | ||||
| /** Text input anonymous structure */ | ||||
| typedef struct TextInput TextInput; | ||||
| typedef void (*TextInputCallback)(void* context); | ||||
| typedef bool (*TextInputValidatorCallback)(const char* text, string_t error, void* context); | ||||
| 
 | ||||
| /** Allocate and initialize text input 
 | ||||
|  *  | ||||
| @ -33,7 +36,7 @@ void text_input_free(TextInput* text_input); | ||||
|  * | ||||
|  * @param      text_input  Text input instance | ||||
|  */ | ||||
| void text_input_clean(TextInput* text_input); | ||||
| void text_input_reset(TextInput* text_input); | ||||
| 
 | ||||
| /** Get text input view
 | ||||
|  * | ||||
| @ -63,6 +66,15 @@ void text_input_set_result_callback( | ||||
|     size_t text_buffer_size, | ||||
|     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
 | ||||
|  * | ||||
|  * @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, 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)) { | ||||
|                 canvas_draw_str(canvas, 115, item_text_y, ">"); | ||||
| @ -147,6 +153,27 @@ static bool variable_item_list_input_callback(InputEvent* event, void* context) | ||||
|         default: | ||||
|             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; | ||||
| @ -285,7 +312,7 @@ void variable_item_list_free(VariableItemList* 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); | ||||
| 
 | ||||
|     with_view_model( | ||||
|  | ||||
| @ -32,7 +32,7 @@ void variable_item_list_free(VariableItemList* variable_item_list); | ||||
|  * | ||||
|  * @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
 | ||||
|  * | ||||
|  | ||||
| @ -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