[FL-2556] Update complete screen (#1332)
* Desktop: slideshow implementation * Updater: handling splashscreen installation; added format version field to slideshow binary * Desktop: added bidirectional slideshow navigation + instant cancel by "back" button; Updater: rebalanced update stages weights * Updater: fixed missing field init; fixed potential loop when baking slideshows * Assets: fixed "update complete" image to match original * Desktop: added check for slideshow file version * Scripts: slideshow.py cleanup * Desktop: removed first start intro sequence * Desktop: removed first start remnants
This commit is contained in:
		
							parent
							
								
									4b02a404ba
								
							
						
					
					
						commit
						eb31fed0e2
					
				
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							| @ -2,6 +2,7 @@ PROJECT_ROOT := $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST))))) | |||||||
| 
 | 
 | ||||||
| include			$(PROJECT_ROOT)/make/git.mk | include			$(PROJECT_ROOT)/make/git.mk | ||||||
| include			$(PROJECT_ROOT)/assets/copro.mk | include			$(PROJECT_ROOT)/assets/copro.mk | ||||||
|  | include			$(PROJECT_ROOT)/assets/splash.mk | ||||||
| 
 | 
 | ||||||
| PROJECT_SOURCE_DIRECTORIES := \
 | PROJECT_SOURCE_DIRECTORIES := \
 | ||||||
| 	$(PROJECT_ROOT)/applications \
 | 	$(PROJECT_ROOT)/applications \
 | ||||||
| @ -103,7 +104,8 @@ updater_package: firmware_all updater assets_manifest | |||||||
| 	--radio $(COPRO_STACK_BIN_PATH) \
 | 	--radio $(COPRO_STACK_BIN_PATH) \
 | ||||||
| 	--radiotype $(COPRO_STACK_TYPE) \
 | 	--radiotype $(COPRO_STACK_TYPE) \
 | ||||||
| 	$(COPRO_DISCLAIMER) \
 | 	$(COPRO_DISCLAIMER) \
 | ||||||
| 	--obdata $(PROJECT_ROOT)/scripts/$(COPRO_OB_DATA) | 	--obdata $(PROJECT_ROOT)/scripts/$(COPRO_OB_DATA) \
 | ||||||
|  | 	--splash $(UPDATER_SPLASH_DIR) | ||||||
| 
 | 
 | ||||||
| .PHONY: assets_manifest | .PHONY: assets_manifest | ||||||
| assets_manifest: | assets_manifest: | ||||||
|  | |||||||
| @ -158,11 +158,11 @@ Desktop* desktop_alloc() { | |||||||
| 
 | 
 | ||||||
|     desktop->lock_menu = desktop_lock_menu_alloc(); |     desktop->lock_menu = desktop_lock_menu_alloc(); | ||||||
|     desktop->debug_view = desktop_debug_alloc(); |     desktop->debug_view = desktop_debug_alloc(); | ||||||
|     desktop->first_start_view = desktop_first_start_alloc(); |  | ||||||
|     desktop->hw_mismatch_popup = popup_alloc(); |     desktop->hw_mismatch_popup = popup_alloc(); | ||||||
|     desktop->locked_view = desktop_view_locked_alloc(); |     desktop->locked_view = desktop_view_locked_alloc(); | ||||||
|     desktop->pin_input_view = desktop_view_pin_input_alloc(); |     desktop->pin_input_view = desktop_view_pin_input_alloc(); | ||||||
|     desktop->pin_timeout_view = desktop_view_pin_timeout_alloc(); |     desktop->pin_timeout_view = desktop_view_pin_timeout_alloc(); | ||||||
|  |     desktop->slideshow_view = desktop_view_slideshow_alloc(); | ||||||
| 
 | 
 | ||||||
|     desktop->main_view_stack = view_stack_alloc(); |     desktop->main_view_stack = view_stack_alloc(); | ||||||
|     desktop->main_view = desktop_main_alloc(); |     desktop->main_view = desktop_main_alloc(); | ||||||
| @ -193,10 +193,6 @@ Desktop* desktop_alloc() { | |||||||
|         desktop_lock_menu_get_view(desktop->lock_menu)); |         desktop_lock_menu_get_view(desktop->lock_menu)); | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         desktop->view_dispatcher, DesktopViewIdDebug, desktop_debug_get_view(desktop->debug_view)); |         desktop->view_dispatcher, DesktopViewIdDebug, desktop_debug_get_view(desktop->debug_view)); | ||||||
|     view_dispatcher_add_view( |  | ||||||
|         desktop->view_dispatcher, |  | ||||||
|         DesktopViewIdFirstStart, |  | ||||||
|         desktop_first_start_get_view(desktop->first_start_view)); |  | ||||||
|     view_dispatcher_add_view( |     view_dispatcher_add_view( | ||||||
|         desktop->view_dispatcher, |         desktop->view_dispatcher, | ||||||
|         DesktopViewIdHwMismatch, |         DesktopViewIdHwMismatch, | ||||||
| @ -209,6 +205,10 @@ Desktop* desktop_alloc() { | |||||||
|         desktop->view_dispatcher, |         desktop->view_dispatcher, | ||||||
|         DesktopViewIdPinInput, |         DesktopViewIdPinInput, | ||||||
|         desktop_view_pin_input_get_view(desktop->pin_input_view)); |         desktop_view_pin_input_get_view(desktop->pin_input_view)); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         desktop->view_dispatcher, | ||||||
|  |         DesktopViewIdSlideshow, | ||||||
|  |         desktop_view_slideshow_get_view(desktop->slideshow_view)); | ||||||
| 
 | 
 | ||||||
|     // Lock icon
 |     // Lock icon
 | ||||||
|     desktop->lock_viewport = view_port_alloc(); |     desktop->lock_viewport = view_port_alloc(); | ||||||
| @ -258,7 +258,6 @@ void desktop_free(Desktop* desktop) { | |||||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdLockMenu); |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdLockMenu); | ||||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdLocked); |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdLocked); | ||||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdDebug); |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdDebug); | ||||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdFirstStart); |  | ||||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdHwMismatch); |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdHwMismatch); | ||||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdPinInput); |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdPinInput); | ||||||
|     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdPinTimeout); |     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewIdPinTimeout); | ||||||
| @ -274,7 +273,6 @@ void desktop_free(Desktop* desktop) { | |||||||
|     desktop_lock_menu_free(desktop->lock_menu); |     desktop_lock_menu_free(desktop->lock_menu); | ||||||
|     desktop_view_locked_free(desktop->locked_view); |     desktop_view_locked_free(desktop->locked_view); | ||||||
|     desktop_debug_free(desktop->debug_view); |     desktop_debug_free(desktop->debug_view); | ||||||
|     desktop_first_start_free(desktop->first_start_view); |  | ||||||
|     popup_free(desktop->hw_mismatch_popup); |     popup_free(desktop->hw_mismatch_popup); | ||||||
|     desktop_view_pin_timeout_free(desktop->pin_timeout_view); |     desktop_view_pin_timeout_free(desktop->pin_timeout_view); | ||||||
| 
 | 
 | ||||||
| @ -290,9 +288,9 @@ void desktop_free(Desktop* desktop) { | |||||||
|     free(desktop); |     free(desktop); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool desktop_is_first_start() { | static bool desktop_check_file_flag(const char* flag_path) { | ||||||
|     Storage* storage = furi_record_open("storage"); |     Storage* storage = furi_record_open("storage"); | ||||||
|     bool exists = storage_common_stat(storage, "/int/first_start", NULL) == FSE_OK; |     bool exists = storage_common_stat(storage, flag_path, NULL) == FSE_OK; | ||||||
|     furi_record_close("storage"); |     furi_record_close("storage"); | ||||||
| 
 | 
 | ||||||
|     return exists; |     return exists; | ||||||
| @ -320,8 +318,8 @@ int32_t desktop_srv(void* p) { | |||||||
|         desktop_lock(desktop); |         desktop_lock(desktop); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(desktop_is_first_start()) { |     if(desktop_check_file_flag("/int/slideshow")) { | ||||||
|         scene_manager_next_scene(desktop->scene_manager, DesktopSceneFirstStart); |         scene_manager_next_scene(desktop->scene_manager, DesktopSceneSlideshow); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(!furi_hal_version_do_i_belong_here()) { |     if(!furi_hal_version_do_i_belong_here()) { | ||||||
|  | |||||||
| @ -6,9 +6,9 @@ | |||||||
| #include "views/desktop_view_pin_input.h" | #include "views/desktop_view_pin_input.h" | ||||||
| #include "views/desktop_view_locked.h" | #include "views/desktop_view_locked.h" | ||||||
| #include "views/desktop_view_main.h" | #include "views/desktop_view_main.h" | ||||||
| #include "views/desktop_view_first_start.h" |  | ||||||
| #include "views/desktop_view_lock_menu.h" | #include "views/desktop_view_lock_menu.h" | ||||||
| #include "views/desktop_view_debug.h" | #include "views/desktop_view_debug.h" | ||||||
|  | #include "views/desktop_view_slideshow.h" | ||||||
| #include "desktop/desktop_settings/desktop_settings.h" | #include "desktop/desktop_settings/desktop_settings.h" | ||||||
| 
 | 
 | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| @ -28,10 +28,10 @@ typedef enum { | |||||||
|     DesktopViewIdLockMenu, |     DesktopViewIdLockMenu, | ||||||
|     DesktopViewIdLocked, |     DesktopViewIdLocked, | ||||||
|     DesktopViewIdDebug, |     DesktopViewIdDebug, | ||||||
|     DesktopViewIdFirstStart, |  | ||||||
|     DesktopViewIdHwMismatch, |     DesktopViewIdHwMismatch, | ||||||
|     DesktopViewIdPinInput, |     DesktopViewIdPinInput, | ||||||
|     DesktopViewIdPinTimeout, |     DesktopViewIdPinTimeout, | ||||||
|  |     DesktopViewIdSlideshow, | ||||||
|     DesktopViewIdTotal, |     DesktopViewIdTotal, | ||||||
| } DesktopViewId; | } DesktopViewId; | ||||||
| 
 | 
 | ||||||
| @ -43,13 +43,13 @@ struct Desktop { | |||||||
|     ViewDispatcher* view_dispatcher; |     ViewDispatcher* view_dispatcher; | ||||||
|     SceneManager* scene_manager; |     SceneManager* scene_manager; | ||||||
| 
 | 
 | ||||||
|     DesktopFirstStartView* first_start_view; |  | ||||||
|     Popup* hw_mismatch_popup; |     Popup* hw_mismatch_popup; | ||||||
|     DesktopLockMenuView* lock_menu; |     DesktopLockMenuView* lock_menu; | ||||||
|     DesktopDebugView* debug_view; |     DesktopDebugView* debug_view; | ||||||
|     DesktopViewLocked* locked_view; |     DesktopViewLocked* locked_view; | ||||||
|     DesktopMainView* main_view; |     DesktopMainView* main_view; | ||||||
|     DesktopViewPinTimeout* pin_timeout_view; |     DesktopViewPinTimeout* pin_timeout_view; | ||||||
|  |     DesktopSlideshowView* slideshow_view; | ||||||
| 
 | 
 | ||||||
|     ViewStack* main_view_stack; |     ViewStack* main_view_stack; | ||||||
|     ViewStack* locked_view_stack; |     ViewStack* locked_view_stack; | ||||||
|  | |||||||
							
								
								
									
										115
									
								
								applications/desktop/helpers/slideshow.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								applications/desktop/helpers/slideshow.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,115 @@ | |||||||
|  | #include "slideshow.h" | ||||||
|  | 
 | ||||||
|  | #include <stddef.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | #include <gui/icon.h> | ||||||
|  | #include <gui/icon_i.h> | ||||||
|  | #include <furi/dangerous_defines.h> | ||||||
|  | 
 | ||||||
|  | #define SLIDESHOW_MAGIC 0x72676468 | ||||||
|  | #define SLIDESHOW_MAX_SUPPORTED_VERSION 1 | ||||||
|  | 
 | ||||||
|  | struct Slideshow { | ||||||
|  |     Icon icon; | ||||||
|  |     uint32_t current_frame; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #pragma pack(push, 1) | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint32_t magic; | ||||||
|  |     uint8_t version; | ||||||
|  |     uint8_t width; | ||||||
|  |     uint8_t height; | ||||||
|  |     uint8_t frame_count; | ||||||
|  | } SlideshowFileHeader; | ||||||
|  | _Static_assert(sizeof(SlideshowFileHeader) == 8, "Incorrect SlideshowFileHeader size"); | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint16_t size; | ||||||
|  | } SlideshowFrameHeader; | ||||||
|  | _Static_assert(sizeof(SlideshowFrameHeader) == 2, "Incorrect SlideshowFrameHeader size"); | ||||||
|  | 
 | ||||||
|  | #pragma pack(pop) | ||||||
|  | 
 | ||||||
|  | Slideshow* slideshow_alloc() { | ||||||
|  |     Slideshow* ret = malloc(sizeof(Slideshow)); | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void slideshow_free(Slideshow* slideshow) { | ||||||
|  |     Icon* icon = &slideshow->icon; | ||||||
|  |     if(icon) { | ||||||
|  |         for(int frame_idx = 0; frame_idx < icon->frame_count; ++frame_idx) { | ||||||
|  |             uint8_t* frame_data = (uint8_t*)icon->frames[frame_idx]; | ||||||
|  |             free(frame_data); | ||||||
|  |         } | ||||||
|  |         free((uint8_t**)icon->frames); | ||||||
|  |     } | ||||||
|  |     free(slideshow); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool slideshow_load(Slideshow* slideshow, const char* fspath) { | ||||||
|  |     Storage* storage = furi_record_open("storage"); | ||||||
|  |     File* slideshow_file = storage_file_alloc(storage); | ||||||
|  |     bool load_success = false; | ||||||
|  |     do { | ||||||
|  |         if(!storage_file_open(slideshow_file, fspath, FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         SlideshowFileHeader header; | ||||||
|  |         if((storage_file_read(slideshow_file, &header, sizeof(header)) != sizeof(header)) || | ||||||
|  |            (header.magic != SLIDESHOW_MAGIC) || | ||||||
|  |            (header.version > SLIDESHOW_MAX_SUPPORTED_VERSION)) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         Icon* icon = &slideshow->icon; | ||||||
|  |         FURI_CONST_ASSIGN(icon->frame_count, header.frame_count); | ||||||
|  |         FURI_CONST_ASSIGN(icon->width, header.width); | ||||||
|  |         FURI_CONST_ASSIGN(icon->height, header.height); | ||||||
|  |         icon->frames = malloc(header.frame_count * sizeof(uint8_t*)); | ||||||
|  |         for(int frame_idx = 0; frame_idx < header.frame_count; ++frame_idx) { | ||||||
|  |             SlideshowFrameHeader frame_header; | ||||||
|  |             if(storage_file_read(slideshow_file, &frame_header, sizeof(frame_header)) != | ||||||
|  |                sizeof(frame_header)) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             FURI_CONST_ASSIGN_PTR(icon->frames[frame_idx], malloc(frame_header.size)); | ||||||
|  |             uint8_t* frame_data = (uint8_t*)icon->frames[frame_idx]; | ||||||
|  |             if(storage_file_read(slideshow_file, frame_data, frame_header.size) != | ||||||
|  |                frame_header.size) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             load_success = (frame_idx + 1) == header.frame_count; | ||||||
|  |         } | ||||||
|  |     } while(false); | ||||||
|  |     storage_file_free(slideshow_file); | ||||||
|  |     furi_record_close("storage"); | ||||||
|  |     return load_success; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool slideshow_advance(Slideshow* slideshow) { | ||||||
|  |     uint8_t next_frame = slideshow->current_frame + 1; | ||||||
|  |     if(next_frame < slideshow->icon.frame_count) { | ||||||
|  |         slideshow->current_frame = next_frame; | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void slideshow_goback(Slideshow* slideshow) { | ||||||
|  |     if(slideshow->current_frame > 0) { | ||||||
|  |         slideshow->current_frame--; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y) { | ||||||
|  |     furi_assert(slideshow->current_frame < slideshow->icon.frame_count); | ||||||
|  |     canvas_draw_bitmap( | ||||||
|  |         canvas, | ||||||
|  |         x, | ||||||
|  |         y, | ||||||
|  |         slideshow->icon.width, | ||||||
|  |         slideshow->icon.height, | ||||||
|  |         slideshow->icon.frames[slideshow->current_frame]); | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								applications/desktop/helpers/slideshow.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								applications/desktop/helpers/slideshow.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/canvas.h> | ||||||
|  | 
 | ||||||
|  | typedef struct Slideshow Slideshow; | ||||||
|  | 
 | ||||||
|  | Slideshow* slideshow_alloc(); | ||||||
|  | 
 | ||||||
|  | void slideshow_free(Slideshow* slideshow); | ||||||
|  | bool slideshow_load(Slideshow* slideshow, const char* fspath); | ||||||
|  | void slideshow_goback(Slideshow* slideshow); | ||||||
|  | bool slideshow_advance(Slideshow* slideshow); | ||||||
|  | void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y); | ||||||
| @ -1,9 +1,9 @@ | |||||||
| ADD_SCENE(desktop, main, Main) | ADD_SCENE(desktop, main, Main) | ||||||
| ADD_SCENE(desktop, lock_menu, LockMenu) | ADD_SCENE(desktop, lock_menu, LockMenu) | ||||||
| ADD_SCENE(desktop, debug, Debug) | ADD_SCENE(desktop, debug, Debug) | ||||||
| ADD_SCENE(desktop, first_start, FirstStart) |  | ||||||
| ADD_SCENE(desktop, hw_mismatch, HwMismatch) | ADD_SCENE(desktop, hw_mismatch, HwMismatch) | ||||||
| ADD_SCENE(desktop, fault, Fault) | ADD_SCENE(desktop, fault, Fault) | ||||||
| ADD_SCENE(desktop, locked, Locked) | ADD_SCENE(desktop, locked, Locked) | ||||||
| ADD_SCENE(desktop, pin_input, PinInput) | ADD_SCENE(desktop, pin_input, PinInput) | ||||||
| ADD_SCENE(desktop, pin_timeout, PinTimeout) | ADD_SCENE(desktop, pin_timeout, PinTimeout) | ||||||
|  | ADD_SCENE(desktop, slideshow, Slideshow) | ||||||
|  | |||||||
| @ -1,54 +0,0 @@ | |||||||
| #include <power/power_service/power.h> |  | ||||||
| #include <storage/storage.h> |  | ||||||
| 
 |  | ||||||
| #include "../desktop_i.h" |  | ||||||
| #include "../views/desktop_view_first_start.h" |  | ||||||
| #include "../views/desktop_events.h" |  | ||||||
| 
 |  | ||||||
| void desktop_scene_first_start_callback(DesktopEvent event, void* context) { |  | ||||||
|     Desktop* desktop = (Desktop*)context; |  | ||||||
|     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_scene_first_start_on_enter(void* context) { |  | ||||||
|     Desktop* desktop = (Desktop*)context; |  | ||||||
|     DesktopFirstStartView* first_start_view = desktop->first_start_view; |  | ||||||
| 
 |  | ||||||
|     desktop_first_start_set_callback( |  | ||||||
|         first_start_view, desktop_scene_first_start_callback, desktop); |  | ||||||
| 
 |  | ||||||
|     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdFirstStart); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool desktop_scene_first_start_on_event(void* context, SceneManagerEvent event) { |  | ||||||
|     Desktop* desktop = (Desktop*)context; |  | ||||||
|     bool consumed = false; |  | ||||||
|     Storage* storage = NULL; |  | ||||||
|     Power* power = NULL; |  | ||||||
| 
 |  | ||||||
|     if(event.type == SceneManagerEventTypeCustom) { |  | ||||||
|         switch(event.event) { |  | ||||||
|         case DesktopFirstStartCompleted: |  | ||||||
|             storage = furi_record_open("storage"); |  | ||||||
|             storage_common_remove(storage, "/int/first_start"); |  | ||||||
|             furi_record_close("storage"); |  | ||||||
|             scene_manager_previous_scene(desktop->scene_manager); |  | ||||||
|             consumed = true; |  | ||||||
|             break; |  | ||||||
|         case DesktopFirstStartPoweroff: |  | ||||||
|             power = furi_record_open("power"); |  | ||||||
|             power_off(power); |  | ||||||
|             furi_record_close("power"); |  | ||||||
|             consumed = true; |  | ||||||
|             break; |  | ||||||
| 
 |  | ||||||
|         default: |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return consumed; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_scene_first_start_on_exit(void* context) { |  | ||||||
|     UNUSED(context); |  | ||||||
| } |  | ||||||
							
								
								
									
										45
									
								
								applications/desktop/scenes/desktop_scene_slideshow.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								applications/desktop/scenes/desktop_scene_slideshow.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | |||||||
|  | #include <storage/storage.h> | ||||||
|  | 
 | ||||||
|  | #include "../desktop_i.h" | ||||||
|  | #include "../views/desktop_view_slideshow.h" | ||||||
|  | #include "../views/desktop_events.h" | ||||||
|  | 
 | ||||||
|  | void desktop_scene_slideshow_callback(DesktopEvent event, void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     view_dispatcher_send_custom_event(desktop->view_dispatcher, event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_slideshow_on_enter(void* context) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     DesktopSlideshowView* slideshow_view = desktop->slideshow_view; | ||||||
|  | 
 | ||||||
|  |     desktop_view_slideshow_set_callback(slideshow_view, desktop_scene_slideshow_callback, desktop); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdSlideshow); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool desktop_scene_slideshow_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Desktop* desktop = (Desktop*)context; | ||||||
|  |     bool consumed = false; | ||||||
|  |     Storage* storage = NULL; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case DesktopSlideshowCompleted: | ||||||
|  |             storage = furi_record_open("storage"); | ||||||
|  |             storage_common_remove(storage, "/int/slideshow"); | ||||||
|  |             furi_record_close("storage"); | ||||||
|  |             scene_manager_previous_scene(desktop->scene_manager); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_scene_slideshow_on_exit(void* context) { | ||||||
|  |     UNUSED(context); | ||||||
|  | } | ||||||
| @ -26,9 +26,6 @@ typedef enum { | |||||||
|     DesktopDebugEventSaveState, |     DesktopDebugEventSaveState, | ||||||
|     DesktopDebugEventExit, |     DesktopDebugEventExit, | ||||||
| 
 | 
 | ||||||
|     DesktopFirstStartCompleted, |  | ||||||
|     DesktopFirstStartPoweroff, |  | ||||||
| 
 |  | ||||||
|     DesktopLockMenuEventLock, |     DesktopLockMenuEventLock, | ||||||
|     DesktopLockMenuEventPinLock, |     DesktopLockMenuEventPinLock, | ||||||
|     DesktopLockMenuEventExit, |     DesktopLockMenuEventExit, | ||||||
| @ -37,6 +34,8 @@ typedef enum { | |||||||
|     DesktopAnimationEventNewIdleAnimation, |     DesktopAnimationEventNewIdleAnimation, | ||||||
|     DesktopAnimationEventInteractAnimation, |     DesktopAnimationEventInteractAnimation, | ||||||
| 
 | 
 | ||||||
|  |     DesktopSlideshowCompleted, | ||||||
|  | 
 | ||||||
|     // Global events
 |     // Global events
 | ||||||
|     DesktopGlobalBeforeAppStarted, |     DesktopGlobalBeforeAppStarted, | ||||||
|     DesktopGlobalAfterAppFinished, |     DesktopGlobalAfterAppFinished, | ||||||
|  | |||||||
| @ -1,166 +0,0 @@ | |||||||
| #include <furi.h> |  | ||||||
| #include <furi_hal.h> |  | ||||||
| #include <gui/elements.h> |  | ||||||
| 
 |  | ||||||
| #include "../desktop_i.h" |  | ||||||
| #include "desktop_view_first_start.h" |  | ||||||
| 
 |  | ||||||
| #define DESKTOP_FIRST_START_POWEROFF_SHORT 5000 |  | ||||||
| #define DESKTOP_FIRST_START_POWEROFF_LONG (60 * 60 * 1000) |  | ||||||
| 
 |  | ||||||
| struct DesktopFirstStartView { |  | ||||||
|     View* view; |  | ||||||
|     DesktopFirstStartViewCallback callback; |  | ||||||
|     void* context; |  | ||||||
|     osTimerId_t timer; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     uint8_t page; |  | ||||||
| } DesktopFirstStartViewModel; |  | ||||||
| 
 |  | ||||||
| static void desktop_first_start_draw(Canvas* canvas, void* model) { |  | ||||||
|     DesktopFirstStartViewModel* m = model; |  | ||||||
| 
 |  | ||||||
|     canvas_clear(canvas); |  | ||||||
|     canvas_set_color(canvas, ColorBlack); |  | ||||||
|     canvas_set_font(canvas, FontSecondary); |  | ||||||
|     uint8_t width = canvas_width(canvas); |  | ||||||
|     uint8_t height = canvas_height(canvas); |  | ||||||
|     const char* my_name = furi_hal_version_get_name_ptr(); |  | ||||||
|     if(m->page == 0) { |  | ||||||
|         canvas_draw_icon(canvas, 0, height - 51, &I_DolphinFirstStart0_70x53); |  | ||||||
|         elements_multiline_text_framed( |  | ||||||
|             canvas, 75, 16 + STATUS_BAR_Y_SHIFT, "Hey m8,\npress > to\ncontinue"); |  | ||||||
|     } else if(m->page == 1) { |  | ||||||
|         canvas_draw_icon(canvas, 0, height - 51, &I_DolphinFirstStart1_59x53); |  | ||||||
|         elements_multiline_text_framed( |  | ||||||
|             canvas, 64, 16 + STATUS_BAR_Y_SHIFT, "First Of All,\n...      >"); |  | ||||||
|     } else if(m->page == 2) { |  | ||||||
|         canvas_draw_icon(canvas, 0, height - 51, &I_DolphinFirstStart2_59x51); |  | ||||||
|         elements_multiline_text_framed( |  | ||||||
|             canvas, 64, 16 + STATUS_BAR_Y_SHIFT, "Thank you\nfor your\nsupport! >"); |  | ||||||
|     } else if(m->page == 3) { |  | ||||||
|         canvas_draw_icon(canvas, width - 57, height - 45, &I_DolphinFirstStart3_57x48); |  | ||||||
|         elements_multiline_text_framed( |  | ||||||
|             canvas, 0, 16 + STATUS_BAR_Y_SHIFT, "Kickstarter\ncampaign\nwas INSANE! >"); |  | ||||||
|     } else if(m->page == 4) { |  | ||||||
|         canvas_draw_icon(canvas, width - 67, height - 51, &I_DolphinFirstStart4_67x53); |  | ||||||
|         elements_multiline_text_framed( |  | ||||||
|             canvas, 0, 13 + STATUS_BAR_Y_SHIFT, "Now\nallow me\nto introduce\nmyself >"); |  | ||||||
|     } else if(m->page == 5) { |  | ||||||
|         char buf[64]; |  | ||||||
|         snprintf( |  | ||||||
|             buf, |  | ||||||
|             64, |  | ||||||
|             "%s %s%s", |  | ||||||
|             "I am", |  | ||||||
|             my_name ? my_name : "Unknown", |  | ||||||
|             ",\ncyberdolphin\nliving in your\npocket >"); |  | ||||||
|         canvas_draw_icon(canvas, 0, height - 49, &I_DolphinFirstStart5_54x49); |  | ||||||
|         elements_multiline_text_framed(canvas, 60, 13 + STATUS_BAR_Y_SHIFT, buf); |  | ||||||
|     } else if(m->page == 6) { |  | ||||||
|         canvas_draw_icon(canvas, 0, height - 51, &I_DolphinFirstStart6_58x54); |  | ||||||
|         elements_multiline_text_framed( |  | ||||||
|             canvas, |  | ||||||
|             63, |  | ||||||
|             13 + STATUS_BAR_Y_SHIFT, |  | ||||||
|             "I can grow\nsmart'n'cool\nif you use me\noften >"); |  | ||||||
|     } else if(m->page == 7) { |  | ||||||
|         canvas_draw_icon(canvas, width - 61, height - 51, &I_DolphinFirstStart7_61x51); |  | ||||||
|         elements_multiline_text_framed( |  | ||||||
|             canvas, 0, 13 + STATUS_BAR_Y_SHIFT, "As long as\nyou read, write\nand emulate >"); |  | ||||||
|     } else if(m->page == 8) { |  | ||||||
|         canvas_draw_icon(canvas, width - 56, height - 51, &I_DolphinFirstStart8_56x51); |  | ||||||
|         elements_multiline_text_framed( |  | ||||||
|             canvas, |  | ||||||
|             0, |  | ||||||
|             13 + STATUS_BAR_Y_SHIFT, |  | ||||||
|             "You can check\nmy level and\nmood in the\nPassport menu"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool desktop_first_start_input(InputEvent* event, void* context) { |  | ||||||
|     furi_assert(event); |  | ||||||
|     DesktopFirstStartView* instance = context; |  | ||||||
| 
 |  | ||||||
|     if(event->type == InputTypeShort) { |  | ||||||
|         DesktopFirstStartViewModel* model = view_get_model(instance->view); |  | ||||||
|         if(event->key == InputKeyLeft) { |  | ||||||
|             if(model->page > 0) model->page--; |  | ||||||
|         } else if(event->key == InputKeyRight) { |  | ||||||
|             uint32_t page = ++model->page; |  | ||||||
|             if(page > 8) { |  | ||||||
|                 instance->callback(DesktopFirstStartCompleted, instance->context); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         view_commit_model(instance->view, true); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(event->key == InputKeyOk) { |  | ||||||
|         if(event->type == InputTypePress) { |  | ||||||
|             osTimerStart(instance->timer, DESKTOP_FIRST_START_POWEROFF_SHORT); |  | ||||||
|         } else if(event->type == InputTypeRelease) { |  | ||||||
|             osTimerStop(instance->timer); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void desktop_first_start_timer_callback(void* context) { |  | ||||||
|     DesktopFirstStartView* instance = context; |  | ||||||
|     instance->callback(DesktopFirstStartPoweroff, instance->context); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void desktop_first_start_enter(void* context) { |  | ||||||
|     DesktopFirstStartView* instance = context; |  | ||||||
| 
 |  | ||||||
|     furi_assert(instance->timer == NULL); |  | ||||||
|     instance->timer = osTimerNew(desktop_first_start_timer_callback, osTimerOnce, instance, NULL); |  | ||||||
| 
 |  | ||||||
|     osTimerStart(instance->timer, DESKTOP_FIRST_START_POWEROFF_LONG); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void desktop_first_start_exit(void* context) { |  | ||||||
|     DesktopFirstStartView* instance = context; |  | ||||||
| 
 |  | ||||||
|     osTimerStop(instance->timer); |  | ||||||
|     osTimerDelete(instance->timer); |  | ||||||
|     instance->timer = NULL; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| DesktopFirstStartView* desktop_first_start_alloc() { |  | ||||||
|     DesktopFirstStartView* instance = malloc(sizeof(DesktopFirstStartView)); |  | ||||||
|     instance->view = view_alloc(); |  | ||||||
|     view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(DesktopFirstStartViewModel)); |  | ||||||
|     view_set_context(instance->view, instance); |  | ||||||
|     view_set_draw_callback(instance->view, (ViewDrawCallback)desktop_first_start_draw); |  | ||||||
|     view_set_input_callback(instance->view, desktop_first_start_input); |  | ||||||
|     view_set_enter_callback(instance->view, desktop_first_start_enter); |  | ||||||
|     view_set_exit_callback(instance->view, desktop_first_start_exit); |  | ||||||
| 
 |  | ||||||
|     return instance; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_first_start_free(DesktopFirstStartView* instance) { |  | ||||||
|     furi_assert(instance); |  | ||||||
| 
 |  | ||||||
|     view_free(instance->view); |  | ||||||
|     free(instance); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| View* desktop_first_start_get_view(DesktopFirstStartView* instance) { |  | ||||||
|     furi_assert(instance); |  | ||||||
|     return instance->view; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void desktop_first_start_set_callback( |  | ||||||
|     DesktopFirstStartView* instance, |  | ||||||
|     DesktopFirstStartViewCallback callback, |  | ||||||
|     void* context) { |  | ||||||
|     furi_assert(instance); |  | ||||||
|     furi_assert(callback); |  | ||||||
|     instance->callback = callback; |  | ||||||
|     instance->context = context; |  | ||||||
| } |  | ||||||
| @ -1,20 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <gui/view.h> |  | ||||||
| 
 |  | ||||||
| #include "desktop_events.h" |  | ||||||
| 
 |  | ||||||
| typedef struct DesktopFirstStartView DesktopFirstStartView; |  | ||||||
| 
 |  | ||||||
| typedef void (*DesktopFirstStartViewCallback)(DesktopEvent event, void* context); |  | ||||||
| 
 |  | ||||||
| DesktopFirstStartView* desktop_first_start_alloc(); |  | ||||||
| 
 |  | ||||||
| void desktop_first_start_free(DesktopFirstStartView* main_view); |  | ||||||
| 
 |  | ||||||
| View* desktop_first_start_get_view(DesktopFirstStartView* main_view); |  | ||||||
| 
 |  | ||||||
| void desktop_first_start_set_callback( |  | ||||||
|     DesktopFirstStartView* main_view, |  | ||||||
|     DesktopFirstStartViewCallback callback, |  | ||||||
|     void* context); |  | ||||||
							
								
								
									
										108
									
								
								applications/desktop/views/desktop_view_slideshow.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								applications/desktop/views/desktop_view_slideshow.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | |||||||
|  | #include <furi.h> | ||||||
|  | #include <furi_hal.h> | ||||||
|  | #include <gui/elements.h> | ||||||
|  | 
 | ||||||
|  | #include "../desktop_i.h" | ||||||
|  | #include "desktop_view_slideshow.h" | ||||||
|  | #include "../helpers/slideshow.h" | ||||||
|  | 
 | ||||||
|  | struct DesktopSlideshowView { | ||||||
|  |     View* view; | ||||||
|  |     DesktopSlideshowViewCallback callback; | ||||||
|  |     void* context; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t page; | ||||||
|  |     Slideshow* slideshow; | ||||||
|  | } DesktopSlideshowViewModel; | ||||||
|  | 
 | ||||||
|  | static void desktop_view_slideshow_draw(Canvas* canvas, void* model) { | ||||||
|  |     DesktopSlideshowViewModel* m = model; | ||||||
|  | 
 | ||||||
|  |     canvas_clear(canvas); | ||||||
|  |     slideshow_draw(m->slideshow, canvas, 0, 0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool desktop_view_slideshow_input(InputEvent* event, void* context) { | ||||||
|  |     furi_assert(event); | ||||||
|  |     DesktopSlideshowView* instance = context; | ||||||
|  | 
 | ||||||
|  |     if(event->type == InputTypeShort) { | ||||||
|  |         DesktopSlideshowViewModel* model = view_get_model(instance->view); | ||||||
|  |         bool end_slideshow = false; | ||||||
|  |         switch(event->key) { | ||||||
|  |         case InputKeyLeft: | ||||||
|  |             slideshow_goback(model->slideshow); | ||||||
|  |             break; | ||||||
|  |         case InputKeyRight: | ||||||
|  |         case InputKeyOk: | ||||||
|  |             end_slideshow = !slideshow_advance(model->slideshow); | ||||||
|  |             break; | ||||||
|  |         case InputKeyBack: | ||||||
|  |             end_slideshow = true; | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         if(end_slideshow) { | ||||||
|  |             instance->callback(DesktopSlideshowCompleted, instance->context); | ||||||
|  |         } | ||||||
|  |         view_commit_model(instance->view, true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void desktop_view_slideshow_enter(void* context) { | ||||||
|  |     DesktopSlideshowView* instance = context; | ||||||
|  | 
 | ||||||
|  |     DesktopSlideshowViewModel* model = view_get_model(instance->view); | ||||||
|  |     model->slideshow = slideshow_alloc(); | ||||||
|  |     if(!slideshow_load(model->slideshow, "/int/slideshow")) { | ||||||
|  |         instance->callback(DesktopSlideshowCompleted, instance->context); | ||||||
|  |     } | ||||||
|  |     view_commit_model(instance->view, false); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void desktop_view_slideshow_exit(void* context) { | ||||||
|  |     DesktopSlideshowView* instance = context; | ||||||
|  | 
 | ||||||
|  |     DesktopSlideshowViewModel* model = view_get_model(instance->view); | ||||||
|  |     slideshow_free(model->slideshow); | ||||||
|  |     view_commit_model(instance->view, false); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | DesktopSlideshowView* desktop_view_slideshow_alloc() { | ||||||
|  |     DesktopSlideshowView* instance = malloc(sizeof(DesktopSlideshowView)); | ||||||
|  |     instance->view = view_alloc(); | ||||||
|  |     view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(DesktopSlideshowViewModel)); | ||||||
|  |     view_set_context(instance->view, instance); | ||||||
|  |     view_set_draw_callback(instance->view, (ViewDrawCallback)desktop_view_slideshow_draw); | ||||||
|  |     view_set_input_callback(instance->view, desktop_view_slideshow_input); | ||||||
|  |     view_set_enter_callback(instance->view, desktop_view_slideshow_enter); | ||||||
|  |     view_set_exit_callback(instance->view, desktop_view_slideshow_exit); | ||||||
|  | 
 | ||||||
|  |     return instance; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_view_slideshow_free(DesktopSlideshowView* instance) { | ||||||
|  |     furi_assert(instance); | ||||||
|  | 
 | ||||||
|  |     view_free(instance->view); | ||||||
|  |     free(instance); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | View* desktop_view_slideshow_get_view(DesktopSlideshowView* instance) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     return instance->view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void desktop_view_slideshow_set_callback( | ||||||
|  |     DesktopSlideshowView* instance, | ||||||
|  |     DesktopSlideshowViewCallback callback, | ||||||
|  |     void* context) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     furi_assert(callback); | ||||||
|  |     instance->callback = callback; | ||||||
|  |     instance->context = context; | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								applications/desktop/views/desktop_view_slideshow.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								applications/desktop/views/desktop_view_slideshow.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/view.h> | ||||||
|  | 
 | ||||||
|  | #include "desktop_events.h" | ||||||
|  | 
 | ||||||
|  | typedef struct DesktopSlideshowView DesktopSlideshowView; | ||||||
|  | 
 | ||||||
|  | typedef void (*DesktopSlideshowViewCallback)(DesktopEvent event, void* context); | ||||||
|  | 
 | ||||||
|  | DesktopSlideshowView* desktop_view_slideshow_alloc(); | ||||||
|  | 
 | ||||||
|  | void desktop_view_slideshow_free(DesktopSlideshowView* main_view); | ||||||
|  | 
 | ||||||
|  | View* desktop_view_slideshow_get_view(DesktopSlideshowView* main_view); | ||||||
|  | 
 | ||||||
|  | void desktop_view_slideshow_set_callback( | ||||||
|  |     DesktopSlideshowView* main_view, | ||||||
|  |     DesktopSlideshowViewCallback callback, | ||||||
|  |     void* context); | ||||||
| @ -24,6 +24,7 @@ static const char* update_task_stage_descr[] = { | |||||||
|     [UpdateTaskStageLfsBackup] = "Backing up LFS", |     [UpdateTaskStageLfsBackup] = "Backing up LFS", | ||||||
|     [UpdateTaskStageLfsRestore] = "Restoring LFS", |     [UpdateTaskStageLfsRestore] = "Restoring LFS", | ||||||
|     [UpdateTaskStageResourcesUpdate] = "Updating resources", |     [UpdateTaskStageResourcesUpdate] = "Updating resources", | ||||||
|  |     [UpdateTaskStageSplashscreenInstall] = "Installing splashscreen", | ||||||
|     [UpdateTaskStageCompleted] = "Restarting...", |     [UpdateTaskStageCompleted] = "Restarting...", | ||||||
|     [UpdateTaskStageError] = "Error", |     [UpdateTaskStageError] = "Error", | ||||||
|     [UpdateTaskStageOBError] = "OB, report", |     [UpdateTaskStageOBError] = "OB, report", | ||||||
| @ -41,13 +42,13 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = { | |||||||
|     [UpdateTaskStageProgress] = STAGE_DEF(UpdateTaskStageGroupMisc, 0), |     [UpdateTaskStageProgress] = STAGE_DEF(UpdateTaskStageGroupMisc, 0), | ||||||
| 
 | 
 | ||||||
|     [UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5), |     [UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5), | ||||||
|     [UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 30), |     [UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 15), | ||||||
| 
 | 
 | ||||||
|     [UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 30), |     [UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 10), | ||||||
|     [UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 50), |     [UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 50), | ||||||
|     [UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 100), |     [UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 90), | ||||||
|     [UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 5), |     [UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 15), | ||||||
|     [UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 70), |     [UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 60), | ||||||
| 
 | 
 | ||||||
|     [UpdateTaskStageOBValidation] = STAGE_DEF(UpdateTaskStageGroupOptionBytes, 10), |     [UpdateTaskStageOBValidation] = STAGE_DEF(UpdateTaskStageGroupOptionBytes, 10), | ||||||
| 
 | 
 | ||||||
| @ -58,6 +59,7 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = { | |||||||
|     [UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 30), |     [UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 30), | ||||||
| 
 | 
 | ||||||
|     [UpdateTaskStageResourcesUpdate] = STAGE_DEF(UpdateTaskStageGroupResources, 255), |     [UpdateTaskStageResourcesUpdate] = STAGE_DEF(UpdateTaskStageGroupResources, 255), | ||||||
|  |     [UpdateTaskStageSplashscreenInstall] = STAGE_DEF(UpdateTaskStageGroupSplashscreen, 5), | ||||||
| 
 | 
 | ||||||
|     [UpdateTaskStageCompleted] = STAGE_DEF(UpdateTaskStageGroupMisc, 1), |     [UpdateTaskStageCompleted] = STAGE_DEF(UpdateTaskStageGroupMisc, 1), | ||||||
|     [UpdateTaskStageError] = STAGE_DEF(UpdateTaskStageGroupMisc, 1), |     [UpdateTaskStageError] = STAGE_DEF(UpdateTaskStageGroupMisc, 1), | ||||||
| @ -79,6 +81,9 @@ static UpdateTaskStageGroup update_task_get_task_groups(UpdateTask* update_task) | |||||||
|     if(!string_empty_p(manifest->resource_bundle)) { |     if(!string_empty_p(manifest->resource_bundle)) { | ||||||
|         ret |= UpdateTaskStageGroupResources; |         ret |= UpdateTaskStageGroupResources; | ||||||
|     } |     } | ||||||
|  |     if(!string_empty_p(manifest->splash_file)) { | ||||||
|  |         ret |= UpdateTaskStageGroupSplashscreen; | ||||||
|  |     } | ||||||
|     return ret; |     return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -33,6 +33,7 @@ typedef enum { | |||||||
| 
 | 
 | ||||||
|     UpdateTaskStageLfsRestore, |     UpdateTaskStageLfsRestore, | ||||||
|     UpdateTaskStageResourcesUpdate, |     UpdateTaskStageResourcesUpdate, | ||||||
|  |     UpdateTaskStageSplashscreenInstall, | ||||||
| 
 | 
 | ||||||
|     UpdateTaskStageCompleted, |     UpdateTaskStageCompleted, | ||||||
|     UpdateTaskStageError, |     UpdateTaskStageError, | ||||||
| @ -52,6 +53,7 @@ typedef enum { | |||||||
|     UpdateTaskStageGroupRadio = 1 << 4, |     UpdateTaskStageGroupRadio = 1 << 4, | ||||||
|     UpdateTaskStageGroupPostUpdate = 1 << 5, |     UpdateTaskStageGroupPostUpdate = 1 << 5, | ||||||
|     UpdateTaskStageGroupResources = 1 << 6, |     UpdateTaskStageGroupResources = 1 << 6, | ||||||
|  |     UpdateTaskStageGroupSplashscreen = 1 << 7, | ||||||
| } UpdateTaskStageGroup; | } UpdateTaskStageGroup; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|  | |||||||
| @ -93,6 +93,19 @@ static bool update_task_post_update(UpdateTask* update_task) { | |||||||
|                 CHECK_RESULT(tar_archive_unpack_to(archive, EXT_PATH)); |                 CHECK_RESULT(tar_archive_unpack_to(archive, EXT_PATH)); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         if(update_task->state.groups & UpdateTaskStageGroupSplashscreen) { | ||||||
|  |             update_task_set_progress(update_task, UpdateTaskStageSplashscreenInstall, 0); | ||||||
|  |             string_t tmp_path; | ||||||
|  |             string_init_set(tmp_path, update_task->update_path); | ||||||
|  |             path_append(tmp_path, string_get_cstr(update_task->manifest->splash_file)); | ||||||
|  |             if(storage_common_copy( | ||||||
|  |                    update_task->storage, string_get_cstr(tmp_path), "/int/slideshow") != FSE_OK) { | ||||||
|  |                 // actually, not critical
 | ||||||
|  |             } | ||||||
|  |             string_clear(tmp_path); | ||||||
|  |             update_task_set_progress(update_task, UpdateTaskStageSplashscreenInstall, 100); | ||||||
|  |         } | ||||||
|         success = true; |         success = true; | ||||||
|     } while(false); |     } while(false); | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								assets/slideshow/update_default/frame_00.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/slideshow/update_default/frame_00.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.2 KiB | 
							
								
								
									
										3
									
								
								assets/splash.mk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								assets/splash.mk
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | ASSETS_DIR			?= $(PROJECT_ROOT)/assets | ||||||
|  | UPDATER_SPLASH		?= update_default | ||||||
|  | UPDATER_SPLASH_DIR	:= $(ASSETS_DIR)/slideshow/$(UPDATER_SPLASH) | ||||||
| @ -17,6 +17,7 @@ | |||||||
| #define MANIFEST_KEY_OB_REFERENCE "OB reference" | #define MANIFEST_KEY_OB_REFERENCE "OB reference" | ||||||
| #define MANIFEST_KEY_OB_MASK "OB mask" | #define MANIFEST_KEY_OB_MASK "OB mask" | ||||||
| #define MANIFEST_KEY_OB_WRITE_MASK "OB write mask" | #define MANIFEST_KEY_OB_WRITE_MASK "OB write mask" | ||||||
|  | #define MANIFEST_KEY_SPLASH_FILE "Splashscreen" | ||||||
| 
 | 
 | ||||||
| UpdateManifest* update_manifest_alloc() { | UpdateManifest* update_manifest_alloc() { | ||||||
|     UpdateManifest* update_manifest = malloc(sizeof(UpdateManifest)); |     UpdateManifest* update_manifest = malloc(sizeof(UpdateManifest)); | ||||||
| @ -25,6 +26,7 @@ UpdateManifest* update_manifest_alloc() { | |||||||
|     string_init(update_manifest->radio_image); |     string_init(update_manifest->radio_image); | ||||||
|     string_init(update_manifest->staged_loader_file); |     string_init(update_manifest->staged_loader_file); | ||||||
|     string_init(update_manifest->resource_bundle); |     string_init(update_manifest->resource_bundle); | ||||||
|  |     string_init(update_manifest->splash_file); | ||||||
|     update_manifest->target = 0; |     update_manifest->target = 0; | ||||||
|     update_manifest->manifest_version = 0; |     update_manifest->manifest_version = 0; | ||||||
|     memset(update_manifest->ob_reference.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); |     memset(update_manifest->ob_reference.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); | ||||||
| @ -41,6 +43,7 @@ void update_manifest_free(UpdateManifest* update_manifest) { | |||||||
|     string_clear(update_manifest->radio_image); |     string_clear(update_manifest->radio_image); | ||||||
|     string_clear(update_manifest->staged_loader_file); |     string_clear(update_manifest->staged_loader_file); | ||||||
|     string_clear(update_manifest->resource_bundle); |     string_clear(update_manifest->resource_bundle); | ||||||
|  |     string_clear(update_manifest->splash_file); | ||||||
|     free(update_manifest); |     free(update_manifest); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -107,6 +110,9 @@ static bool | |||||||
|             update_manifest->ob_write_mask.bytes, |             update_manifest->ob_write_mask.bytes, | ||||||
|             FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); |             FURI_HAL_FLASH_OB_RAW_SIZE_BYTES); | ||||||
| 
 | 
 | ||||||
|  |         flipper_format_read_string( | ||||||
|  |             flipper_file, MANIFEST_KEY_SPLASH_FILE, update_manifest->splash_file); | ||||||
|  | 
 | ||||||
|         update_manifest->valid = |         update_manifest->valid = | ||||||
|             (!string_empty_p(update_manifest->firmware_dfu_image) || |             (!string_empty_p(update_manifest->firmware_dfu_image) || | ||||||
|              !string_empty_p(update_manifest->radio_image) || |              !string_empty_p(update_manifest->radio_image) || | ||||||
|  | |||||||
| @ -42,6 +42,7 @@ typedef struct { | |||||||
|     FuriHalFlashRawOptionByteData ob_reference; |     FuriHalFlashRawOptionByteData ob_reference; | ||||||
|     FuriHalFlashRawOptionByteData ob_compare_mask; |     FuriHalFlashRawOptionByteData ob_compare_mask; | ||||||
|     FuriHalFlashRawOptionByteData ob_write_mask; |     FuriHalFlashRawOptionByteData ob_write_mask; | ||||||
|  |     string_t splash_file; | ||||||
|     bool valid; |     bool valid; | ||||||
| } UpdateManifest; | } UpdateManifest; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -38,9 +38,9 @@ class Main(App): | |||||||
|             return 1 |             return 1 | ||||||
| 
 | 
 | ||||||
|         with open(self.args.input, mode="rb") as file: |         with open(self.args.input, mode="rb") as file: | ||||||
|             bin = file.read() |             bindata = file.read() | ||||||
| 
 | 
 | ||||||
|         data = struct.pack("<II", self.args.address, len(bin)) + bin |         data = struct.pack("<II", self.args.address, len(bindata)) + bindata | ||||||
| 
 | 
 | ||||||
|         # Target prefix |         # Target prefix | ||||||
|         szTargetName = self.args.label.encode("ascii") |         szTargetName = self.args.label.encode("ascii") | ||||||
|  | |||||||
							
								
								
									
										61
									
								
								scripts/slideshow.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								scripts/slideshow.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | 
 | ||||||
|  | import os | ||||||
|  | import struct | ||||||
|  | 
 | ||||||
|  | from flipper.app import App | ||||||
|  | from flipper.assets.icon import file2image | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Main(App): | ||||||
|  |     MAGIC = 0x72676468 | ||||||
|  |     VERSION = 1 | ||||||
|  | 
 | ||||||
|  |     def init(self): | ||||||
|  |         self.parser.add_argument("-i", "--input", help="input folder", required=True) | ||||||
|  |         self.parser.add_argument("-o", "--output", help="output file", required=True) | ||||||
|  | 
 | ||||||
|  |         self.parser.set_defaults(func=self.pack) | ||||||
|  | 
 | ||||||
|  |     def pack(self): | ||||||
|  |         if not os.path.exists(self.args.input): | ||||||
|  |             self.logger.error(f'"{self.args.input}" does not exist') | ||||||
|  |             return 1 | ||||||
|  | 
 | ||||||
|  |         file_idx = 0 | ||||||
|  |         images = [] | ||||||
|  |         while True: | ||||||
|  |             frame_filename = os.path.join(self.args.input, f"frame_{file_idx:02}.png") | ||||||
|  |             if not os.path.exists(frame_filename): | ||||||
|  |                 break | ||||||
|  | 
 | ||||||
|  |             self.logger.debug(f"working on {frame_filename}") | ||||||
|  |             try: | ||||||
|  |                 images.append(file2image(frame_filename)) | ||||||
|  |                 self.logger.debug(f"Processed frame #{file_idx}") | ||||||
|  |                 file_idx += 1 | ||||||
|  |             except Exception as e: | ||||||
|  |                 self.logger.error(e) | ||||||
|  |                 break | ||||||
|  | 
 | ||||||
|  |         widths = set(img.width for img in images) | ||||||
|  |         heights = set(img.height for img in images) | ||||||
|  |         if len(widths) != 1 or len(heights) != 1: | ||||||
|  |             self.logger.error("All images must have same dimensions!") | ||||||
|  |             return 2 | ||||||
|  | 
 | ||||||
|  |         data = struct.pack( | ||||||
|  |             "<IBBBB", self.MAGIC, self.VERSION, widths.pop(), heights.pop(), len(images) | ||||||
|  |         ) | ||||||
|  |         for image in images: | ||||||
|  |             data += struct.pack("<H", len(image.data)) | ||||||
|  |             data += image.data | ||||||
|  | 
 | ||||||
|  |         with open(self.args.output, mode="wb") as file: | ||||||
|  |             file.write(data) | ||||||
|  | 
 | ||||||
|  |         return 0 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     Main()() | ||||||
| @ -11,6 +11,8 @@ import zlib | |||||||
| import tarfile | import tarfile | ||||||
| import math | import math | ||||||
| 
 | 
 | ||||||
|  | from slideshow import Main as SlideshowMain | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class Main(App): | class Main(App): | ||||||
|     UPDATE_MANIFEST_VERSION = 2 |     UPDATE_MANIFEST_VERSION = 2 | ||||||
| @ -31,6 +33,9 @@ class Main(App): | |||||||
|     FLASH_BASE = 0x8000000 |     FLASH_BASE = 0x8000000 | ||||||
|     MIN_LFS_PAGES = 6 |     MIN_LFS_PAGES = 6 | ||||||
| 
 | 
 | ||||||
|  |     # Post-update slideshow | ||||||
|  |     SPLASH_BIN_NAME = "splash.bin" | ||||||
|  | 
 | ||||||
|     def init(self): |     def init(self): | ||||||
|         self.subparsers = self.parser.add_subparsers(help="sub-command help") |         self.subparsers = self.parser.add_subparsers(help="sub-command help") | ||||||
| 
 | 
 | ||||||
| @ -63,6 +68,7 @@ class Main(App): | |||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         self.parser_generate.add_argument("--obdata", dest="obdata", required=False) |         self.parser_generate.add_argument("--obdata", dest="obdata", required=False) | ||||||
|  |         self.parser_generate.add_argument("--splash", dest="splash", required=False) | ||||||
|         self.parser_generate.add_argument( |         self.parser_generate.add_argument( | ||||||
|             "--I-understand-what-I-am-doing", dest="disclaimer", required=False |             "--I-understand-what-I-am-doing", dest="disclaimer", required=False | ||||||
|         ) |         ) | ||||||
| @ -124,6 +130,19 @@ class Main(App): | |||||||
|                 self.disclaimer() |                 self.disclaimer() | ||||||
|                 return 2 |                 return 2 | ||||||
| 
 | 
 | ||||||
|  |         if self.args.splash: | ||||||
|  |             splash_args = [ | ||||||
|  |                 "-i", | ||||||
|  |                 self.args.splash, | ||||||
|  |                 "-o", | ||||||
|  |                 join(self.args.directory, self.SPLASH_BIN_NAME), | ||||||
|  |             ] | ||||||
|  |             if splash_code := SlideshowMain(no_exit=True)(splash_args): | ||||||
|  |                 self.logger.error( | ||||||
|  |                     f"Failed to convert splash screen data: {splash_code}" | ||||||
|  |                 ) | ||||||
|  |                 return splash_code | ||||||
|  | 
 | ||||||
|         file = FlipperFormatFile() |         file = FlipperFormatFile() | ||||||
|         file.setHeader( |         file.setHeader( | ||||||
|             "Flipper firmware upgrade configuration", self.UPDATE_MANIFEST_VERSION |             "Flipper firmware upgrade configuration", self.UPDATE_MANIFEST_VERSION | ||||||
| @ -152,6 +171,7 @@ class Main(App): | |||||||
|         file.writeKey("OB reference", self.bytes2ffhex(obvalues.reference)) |         file.writeKey("OB reference", self.bytes2ffhex(obvalues.reference)) | ||||||
|         file.writeKey("OB mask", self.bytes2ffhex(obvalues.compare_mask)) |         file.writeKey("OB mask", self.bytes2ffhex(obvalues.compare_mask)) | ||||||
|         file.writeKey("OB write mask", self.bytes2ffhex(obvalues.write_mask)) |         file.writeKey("OB write mask", self.bytes2ffhex(obvalues.write_mask)) | ||||||
|  |         file.writeKey("Splashscreen", self.SPLASH_BIN_NAME if self.args.splash else "") | ||||||
|         file.save(join(self.args.directory, self.UPDATE_MANIFEST_NAME)) |         file.save(join(self.args.directory, self.UPDATE_MANIFEST_NAME)) | ||||||
| 
 | 
 | ||||||
|         return 0 |         return 0 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 hedger
						hedger