Jelajahi Sumber

[FL-1824] Dolphin refactoring (#701)

* refactoring p1
* refactoring p2
* cleanups
* locked screen refresh rate fix
* better locked view logic
* seperate dolphin service and desktop app
* Desktop: Favorite app acess (Left key), Settings app
* Desktop settings version, submenu header
* remove unused icon anomation + naming fix

Co-authored-by: あく <alleteam@gmail.com>
its your bedtime 4 tahun lalu
induk
melakukan
1c4e6ec74d
44 mengubah file dengan 1964 tambahan dan 718 penghapusan
  1. 10 0
      applications/applications.c
  2. 7 0
      applications/applications.mk
  3. 128 0
      applications/desktop/desktop.c
  4. 3 0
      applications/desktop/desktop.h
  5. 59 0
      applications/desktop/desktop_i.h
  6. 49 0
      applications/desktop/desktop_settings/desktop_settings.c
  7. 15 0
      applications/desktop/desktop_settings/desktop_settings.h
  8. 65 0
      applications/desktop/desktop_settings/desktop_settings_app.c
  9. 25 0
      applications/desktop/desktop_settings/desktop_settings_app.h
  10. 30 0
      applications/desktop/desktop_settings/scenes/desktop_settings_scene.c
  11. 29 0
      applications/desktop/desktop_settings/scenes/desktop_settings_scene.h
  12. 2 0
      applications/desktop/desktop_settings/scenes/desktop_settings_scene_config.h
  13. 47 0
      applications/desktop/desktop_settings/scenes/desktop_settings_scene_favorite.c
  14. 53 0
      applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c
  15. 30 0
      applications/desktop/scenes/desktop_scene.c
  16. 29 0
      applications/desktop/scenes/desktop_scene.h
  17. 6 0
      applications/desktop/scenes/desktop_scene_config.h
  18. 62 0
      applications/desktop/scenes/desktop_scene_debug.c
  19. 43 0
      applications/desktop/scenes/desktop_scene_first_start.c
  20. 37 0
      applications/desktop/scenes/desktop_scene_hw_mismatch.c
  21. 42 0
      applications/desktop/scenes/desktop_scene_lock_menu.c
  22. 51 0
      applications/desktop/scenes/desktop_scene_locked.c
  23. 88 0
      applications/desktop/scenes/desktop_scene_main.c
  24. 166 0
      applications/desktop/views/desktop_debug.c
  25. 52 0
      applications/desktop/views/desktop_debug.h
  26. 107 0
      applications/desktop/views/desktop_first_start.c
  27. 35 0
      applications/desktop/views/desktop_first_start.h
  28. 66 0
      applications/desktop/views/desktop_hw_mismatch.c
  29. 38 0
      applications/desktop/views/desktop_hw_mismatch.h
  30. 111 0
      applications/desktop/views/desktop_lock_menu.c
  31. 39 0
      applications/desktop/views/desktop_lock_menu.h
  32. 171 0
      applications/desktop/views/desktop_locked.c
  33. 55 0
      applications/desktop/views/desktop_locked.h
  34. 116 0
      applications/desktop/views/desktop_main.c
  35. 43 0
      applications/desktop/views/desktop_main.h
  36. 30 413
      applications/dolphin/dolphin.c
  37. 20 1
      applications/dolphin/dolphin.h
  38. 4 40
      applications/dolphin/dolphin_i.h
  39. 0 197
      applications/dolphin/dolphin_views.c
  40. 0 67
      applications/dolphin/dolphin_views.h
  41. 0 0
      applications/dolphin/helpers/dolphin_deed.c
  42. 1 0
      applications/dolphin/helpers/dolphin_deed.h
  43. 0 0
      applications/dolphin/helpers/dolphin_state.c
  44. 0 0
      applications/dolphin/helpers/dolphin_state.h

+ 10 - 0
applications/applications.c

@@ -14,6 +14,7 @@ extern int32_t notification_srv(void* p);
 extern int32_t power_observer_srv(void* p);
 extern int32_t power_srv(void* p);
 extern int32_t storage_srv(void* p);
+extern int32_t desktop_srv(void* p);
 
 // Apps
 extern int32_t accessor_app(void* p);
@@ -52,6 +53,7 @@ extern void power_cli_init();
 extern int32_t notification_settings_app(void* p);
 extern int32_t storage_settings_app(void* p);
 extern int32_t bt_settings_app(void* p);
+extern int32_t desktop_settings_app(void* p);
 extern int32_t about_settings_app(void* p);
 extern int32_t power_settings_app(void* p);
 
@@ -73,6 +75,10 @@ const FlipperApplication FLIPPER_SERVICES[] = {
     {.app = dolphin_srv, .name = "Dolphin", .stack_size = 1024, .icon = NULL},
 #endif
 
+#ifdef SRV_DESKTOP
+    {.app = desktop_srv, .name = "Desktop", .stack_size = 1024, .icon = NULL},
+#endif
+
 #ifdef SRV_GUI
     {.app = gui_srv, .name = "Gui", .stack_size = 8192, .icon = NULL},
 #endif
@@ -282,6 +288,10 @@ const FlipperApplication FLIPPER_SETTINGS_APPS[] = {
     {.app = power_settings_app, .name = "Power", .stack_size = 1024, .icon = NULL},
 #endif
 
+#ifdef APP_DESKTOP
+    {.app = desktop_settings_app, .name = "Desktop", .stack_size = 1024, .icon = NULL},
+#endif
+
 #ifdef APP_ABOUT
     {.app = about_settings_app, .name = "About", .stack_size = 1024, .icon = NULL},
 #endif

+ 7 - 0
applications/applications.mk

@@ -25,6 +25,7 @@ SRV_POWER_OBSERVER = 1
 SRV_STORAGE = 1
 
 # Apps
+SRV_DESKTOP = 1
 APP_ARCHIVE = 1
 APP_GPIO_TEST = 1
 APP_IBUTTON = 1
@@ -95,6 +96,12 @@ ifeq ($(APP_UNIT_TESTS), 1)
 CFLAGS		+= -DAPP_UNIT_TESTS
 endif
 
+SRV_DESKTOP ?= 0
+ifeq ($(SRV_DESKTOP), 1)
+CFLAGS		+= -DSRV_DESKTOP
+SRV_DESKTOP = 1
+endif
+
 APP_ARCHIVE ?= 0
 ifeq ($(APP_NFC), 1)
 CFLAGS		+= -DAPP_ARCHIVE

+ 128 - 0
applications/desktop/desktop.c

@@ -0,0 +1,128 @@
+#include "desktop_i.h"
+#include "applications/dolphin/dolphin.h"
+
+static void desktop_lock_icon_callback(Canvas* canvas, void* context) {
+    furi_assert(canvas);
+    canvas_draw_icon(canvas, 0, 0, &I_Lock_8x8);
+}
+
+bool desktop_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    Desktop* desktop = (Desktop*)context;
+    return scene_manager_handle_custom_event(desktop->scene_manager, event);
+}
+
+bool desktop_back_event_callback(void* context) {
+    furi_assert(context);
+    Desktop* desktop = (Desktop*)context;
+    return scene_manager_handle_back_event(desktop->scene_manager);
+}
+
+Desktop* desktop_alloc() {
+    Desktop* desktop = furi_alloc(sizeof(Desktop));
+
+    desktop->menu_vm = furi_record_open("menu");
+    desktop->gui = furi_record_open("gui");
+    desktop->scene_thread = furi_thread_alloc();
+    desktop->view_dispatcher = view_dispatcher_alloc();
+    desktop->scene_manager = scene_manager_alloc(&desktop_scene_handlers, desktop);
+
+    view_dispatcher_enable_queue(desktop->view_dispatcher);
+    view_dispatcher_attach_to_gui(
+        desktop->view_dispatcher, desktop->gui, ViewDispatcherTypeWindow);
+
+    view_dispatcher_set_event_callback_context(desktop->view_dispatcher, desktop);
+    view_dispatcher_set_custom_event_callback(
+        desktop->view_dispatcher, desktop_custom_event_callback);
+    view_dispatcher_set_navigation_event_callback(
+        desktop->view_dispatcher, desktop_back_event_callback);
+
+    desktop->main_view = desktop_main_alloc();
+    desktop->lock_menu = desktop_lock_menu_alloc();
+    desktop->locked_view = desktop_locked_alloc();
+    desktop->debug_view = desktop_debug_alloc();
+    desktop->first_start_view = desktop_first_start_alloc();
+    desktop->hw_mismatch_view = desktop_hw_mismatch_alloc();
+
+    view_dispatcher_add_view(
+        desktop->view_dispatcher, DesktopViewMain, desktop_main_get_view(desktop->main_view));
+    view_dispatcher_add_view(
+        desktop->view_dispatcher,
+        DesktopViewLockMenu,
+        desktop_lock_menu_get_view(desktop->lock_menu));
+    view_dispatcher_add_view(
+        desktop->view_dispatcher, DesktopViewDebug, desktop_debug_get_view(desktop->debug_view));
+    view_dispatcher_add_view(
+        desktop->view_dispatcher,
+        DesktopViewLocked,
+        desktop_locked_get_view(desktop->locked_view));
+    view_dispatcher_add_view(
+        desktop->view_dispatcher,
+        DesktopViewFirstStart,
+        desktop_first_start_get_view(desktop->first_start_view));
+    view_dispatcher_add_view(
+        desktop->view_dispatcher,
+        DesktopViewHwMismatch,
+        desktop_hw_mismatch_get_view(desktop->hw_mismatch_view));
+
+    // Lock icon
+    desktop->lock_viewport = view_port_alloc();
+    view_port_set_width(desktop->lock_viewport, icon_get_width(&I_Lock_8x8));
+    view_port_draw_callback_set(desktop->lock_viewport, desktop_lock_icon_callback, desktop);
+    view_port_enabled_set(desktop->lock_viewport, false);
+    gui_add_view_port(desktop->gui, desktop->lock_viewport, GuiLayerStatusBarLeft);
+
+    return desktop;
+}
+
+void desktop_free(Desktop* desktop) {
+    furi_assert(desktop);
+
+    view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewMain);
+    view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLockMenu);
+    view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLocked);
+    view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewDebug);
+    view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewFirstStart);
+    view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewHwMismatch);
+
+    view_dispatcher_free(desktop->view_dispatcher);
+    scene_manager_free(desktop->scene_manager);
+
+    desktop_main_free(desktop->main_view);
+    desktop_lock_menu_free(desktop->lock_menu);
+    desktop_locked_free(desktop->locked_view);
+    desktop_debug_free(desktop->debug_view);
+    desktop_first_start_free(desktop->first_start_view);
+    desktop_hw_mismatch_free(desktop->hw_mismatch_view);
+
+    furi_record_close("gui");
+    desktop->gui = NULL;
+
+    furi_thread_free(desktop->scene_thread);
+
+    furi_record_close("menu");
+    desktop->menu_vm = NULL;
+
+    free(desktop);
+}
+
+int32_t desktop_srv(void* p) {
+    Desktop* desktop = desktop_alloc();
+    Dolphin* dolphin = furi_record_open("dolphin");
+
+    if(dolphin_load(dolphin)) {
+        scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
+    } else {
+        scene_manager_next_scene(desktop->scene_manager, DesktopSceneFirstStart);
+    }
+    furi_record_close("dolphin");
+
+    if(!furi_hal_version_do_i_belong_here()) {
+        scene_manager_next_scene(desktop->scene_manager, DesktopSceneHwMismatch);
+    }
+
+    view_dispatcher_run(desktop->view_dispatcher);
+    desktop_free(desktop);
+
+    return 0;
+}

+ 3 - 0
applications/desktop/desktop.h

@@ -0,0 +1,3 @@
+#pragma once
+
+typedef struct Desktop Desktop;

+ 59 - 0
applications/desktop/desktop_i.h

@@ -0,0 +1,59 @@
+#pragma once
+
+#include <furi.h>
+#include <furi-hal.h>
+#include <menu/menu.h>
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <assets_icons.h>
+
+#include "desktop.h"
+
+#include "views/desktop_main.h"
+#include "views/desktop_first_start.h"
+#include "views/desktop_hw_mismatch.h"
+#include "views/desktop_lock_menu.h"
+#include "views/desktop_locked.h"
+#include "views/desktop_debug.h"
+#include "scenes/desktop_scene.h"
+
+#include "desktop/desktop_settings/desktop_settings.h"
+
+#define HINT_TIMEOUT_L 2
+#define HINT_TIMEOUT_H 11
+
+typedef enum {
+    DesktopViewMain,
+    DesktopViewLockMenu,
+    DesktopViewLocked,
+    DesktopViewDebug,
+    DesktopViewFirstStart,
+    DesktopViewHwMismatch,
+    DesktopViewTotal,
+} DesktopViewEnum;
+
+struct Desktop {
+    // Menu
+    ValueMutex* menu_vm;
+    // Scene
+    FuriThread* scene_thread;
+    // GUI
+    Gui* gui;
+    ViewDispatcher* view_dispatcher;
+    SceneManager* scene_manager;
+
+    DesktopFirstStartView* first_start_view;
+    DesktopHwMismatchView* hw_mismatch_view;
+    DesktopMainView* main_view;
+    DesktopLockMenuView* lock_menu;
+    DesktopLockedView* locked_view;
+    DesktopDebugView* debug_view;
+    DesktopSettings settings;
+
+    ViewPort* lock_viewport;
+};
+
+Desktop* desktop_alloc();
+
+void desktop_free(Desktop* desktop);

+ 49 - 0
applications/desktop/desktop_settings/desktop_settings.c

@@ -0,0 +1,49 @@
+#include <furi.h>
+#include <file-worker.h>
+#include "desktop_settings.h"
+
+#define DESKTOP_SETTINGS_TAG "Desktop settings"
+#define DESKTOP_SETTINGS_PATH "/int/desktop.settings"
+
+bool desktop_settings_load(DesktopSettings* desktop_settings) {
+    furi_assert(desktop_settings);
+    bool file_loaded = false;
+    DesktopSettings settings = {};
+
+    FURI_LOG_I(DESKTOP_SETTINGS_TAG, "Loading settings from \"%s\"", DESKTOP_SETTINGS_PATH);
+    FileWorker* file_worker = file_worker_alloc(true);
+    if(file_worker_open(file_worker, DESKTOP_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
+        if(file_worker_read(file_worker, &settings, sizeof(settings))) {
+            file_loaded = true;
+        }
+    }
+    file_worker_free(file_worker);
+
+    if(file_loaded) {
+        if(settings.version != DESKTOP_SETTINGS_VER) {
+            FURI_LOG_E(DESKTOP_SETTINGS_TAG, "Settings version mismatch");
+        } else {
+            osKernelLock();
+            *desktop_settings = settings;
+            osKernelUnlock();
+        }
+    } else {
+        FURI_LOG_E(DESKTOP_SETTINGS_TAG, "Settings load failed");
+    }
+    return file_loaded;
+}
+
+bool desktop_settings_save(DesktopSettings* desktop_settings) {
+    furi_assert(desktop_settings);
+    bool result = false;
+
+    FileWorker* file_worker = file_worker_alloc(true);
+    if(file_worker_open(file_worker, DESKTOP_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
+        if(file_worker_write(file_worker, desktop_settings, sizeof(DesktopSettings))) {
+            FURI_LOG_I(DESKTOP_SETTINGS_TAG, "Settings saved to \"%s\"", DESKTOP_SETTINGS_PATH);
+            result = true;
+        }
+    }
+    file_worker_free(file_worker);
+    return result;
+}

+ 15 - 0
applications/desktop/desktop_settings/desktop_settings.h

@@ -0,0 +1,15 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#define DESKTOP_SETTINGS_VER (0)
+
+typedef struct {
+    uint8_t version;
+    uint16_t favorite;
+} DesktopSettings;
+
+bool desktop_settings_load(DesktopSettings* desktop_settings);
+
+bool desktop_settings_save(DesktopSettings* desktop_settings);

+ 65 - 0
applications/desktop/desktop_settings/desktop_settings_app.c

@@ -0,0 +1,65 @@
+#include "desktop_settings_app.h"
+
+static bool desktop_settings_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    DesktopSettingsApp* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+static bool desktop_settings_back_event_callback(void* context) {
+    furi_assert(context);
+    DesktopSettingsApp* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
+}
+
+DesktopSettingsApp* desktop_settings_app_alloc() {
+    DesktopSettingsApp* app = furi_alloc(sizeof(DesktopSettingsApp));
+
+    app->settings.version = DESKTOP_SETTINGS_VER;
+    desktop_settings_load(&app->settings);
+
+    app->gui = furi_record_open("gui");
+    app->view_dispatcher = view_dispatcher_alloc();
+    app->scene_manager = scene_manager_alloc(&desktop_settings_scene_handlers, app);
+    view_dispatcher_enable_queue(app->view_dispatcher);
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+
+    view_dispatcher_set_custom_event_callback(
+        app->view_dispatcher, desktop_settings_custom_event_callback);
+    view_dispatcher_set_navigation_event_callback(
+        app->view_dispatcher, desktop_settings_back_event_callback);
+
+    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+    app->submenu = submenu_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, DesktopSettingsAppViewMain, submenu_get_view(app->submenu));
+
+    view_dispatcher_add_view(
+        app->view_dispatcher, DesktopSettingsAppViewFavorite, submenu_get_view(app->submenu));
+
+    scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneStart);
+    return app;
+}
+
+void desktop_settings_app_free(DesktopSettingsApp* app) {
+    furi_assert(app);
+    // Variable item list
+    view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewMain);
+    view_dispatcher_remove_view(app->view_dispatcher, DesktopSettingsAppViewFavorite);
+    submenu_free(app->submenu);
+    // View dispatcher
+    view_dispatcher_free(app->view_dispatcher);
+    scene_manager_free(app->scene_manager);
+    // Records
+    furi_record_close("gui");
+    free(app);
+}
+
+extern int32_t desktop_settings_app(void* p) {
+    DesktopSettingsApp* app = desktop_settings_app_alloc();
+    view_dispatcher_run(app->view_dispatcher);
+    desktop_settings_save(&app->settings);
+    desktop_settings_app_free(app);
+    return 0;
+}

+ 25 - 0
applications/desktop/desktop_settings/desktop_settings_app.h

@@ -0,0 +1,25 @@
+#pragma once
+
+#include <furi.h>
+#include <gui/gui.h>
+#include <gui/view.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/submenu.h>
+
+#include "desktop_settings.h"
+#include "scenes/desktop_settings_scene.h"
+
+typedef enum {
+    DesktopSettingsAppViewMain,
+    DesktopSettingsAppViewFavorite,
+} DesktopSettingsAppView;
+
+typedef struct {
+    DesktopSettings settings;
+    Gui* gui;
+    SceneManager* scene_manager;
+    ViewDispatcher* view_dispatcher;
+    Submenu* submenu;
+
+} DesktopSettingsApp;

+ 30 - 0
applications/desktop/desktop_settings/scenes/desktop_settings_scene.c

@@ -0,0 +1,30 @@
+#include "desktop_settings_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const desktop_settings_on_enter_handlers[])(void*) = {
+#include "desktop_settings_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const desktop_settings_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "desktop_settings_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const desktop_settings_on_exit_handlers[])(void* context) = {
+#include "desktop_settings_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers desktop_settings_scene_handlers = {
+    .on_enter_handlers = desktop_settings_on_enter_handlers,
+    .on_event_handlers = desktop_settings_on_event_handlers,
+    .on_exit_handlers = desktop_settings_on_exit_handlers,
+    .scene_num = DesktopSettingsAppSceneNum,
+};

+ 29 - 0
applications/desktop/desktop_settings/scenes/desktop_settings_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) DesktopSettingsAppScene##id,
+typedef enum {
+#include "desktop_settings_scene_config.h"
+    DesktopSettingsAppSceneNum,
+} DesktopSettingsAppScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers desktop_settings_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "desktop_settings_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+    bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "desktop_settings_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "desktop_settings_scene_config.h"
+#undef ADD_SCENE

+ 2 - 0
applications/desktop/desktop_settings/scenes/desktop_settings_scene_config.h

@@ -0,0 +1,2 @@
+ADD_SCENE(desktop_settings, start, Start)
+ADD_SCENE(desktop_settings, favorite, Favorite)

+ 47 - 0
applications/desktop/desktop_settings/scenes/desktop_settings_scene_favorite.c

@@ -0,0 +1,47 @@
+#include "../desktop_settings_app.h"
+#include "applications.h"
+
+static void desktop_settings_scene_favorite_submenu_callback(void* context, uint32_t index) {
+    DesktopSettingsApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void desktop_settings_scene_favorite_on_enter(void* context) {
+    DesktopSettingsApp* app = context;
+    Submenu* submenu = app->submenu;
+    submenu_clean(submenu);
+
+    for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) {
+        submenu_add_item(
+            submenu,
+            FLIPPER_APPS[i].name,
+            i,
+            desktop_settings_scene_favorite_submenu_callback,
+            app);
+    }
+
+    submenu_set_header(app->submenu, "Quick access app:");
+    submenu_set_selected_item(app->submenu, app->settings.favorite);
+    view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewFavorite);
+}
+
+bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent event) {
+    DesktopSettingsApp* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        default:
+            app->settings.favorite = event.event;
+            scene_manager_previous_scene(app->scene_manager);
+            consumed = true;
+            break;
+        }
+    }
+    return consumed;
+}
+
+void desktop_settings_scene_favorite_on_exit(void* context) {
+    DesktopSettingsApp* app = context;
+    submenu_clean(app->submenu);
+}

+ 53 - 0
applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c

@@ -0,0 +1,53 @@
+#include "../desktop_settings_app.h"
+#include "applications.h"
+
+enum DesktopSettingsStartSubmenuIndex {
+    DesktopSettingsStartSubmenuIndexFavorite,
+    DesktopSettingsStartSubmenuIndexPinSetup,
+};
+
+static void desktop_settings_scene_start_submenu_callback(void* context, uint32_t index) {
+    DesktopSettingsApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void desktop_settings_scene_start_on_enter(void* context) {
+    DesktopSettingsApp* app = context;
+    Submenu* submenu = app->submenu;
+
+    submenu_add_item(
+        submenu,
+        "Favorite App",
+        DesktopSettingsStartSubmenuIndexFavorite,
+        desktop_settings_scene_start_submenu_callback,
+        app);
+
+    submenu_add_item(
+        submenu,
+        "PIN Setup",
+        DesktopSettingsStartSubmenuIndexPinSetup,
+        desktop_settings_scene_start_submenu_callback,
+        app);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMain);
+}
+
+bool desktop_settings_scene_start_on_event(void* context, SceneManagerEvent event) {
+    DesktopSettingsApp* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case DesktopSettingsStartSubmenuIndexFavorite:
+            scene_manager_next_scene(app->scene_manager, DesktopSettingsAppViewFavorite);
+            consumed = true;
+            break;
+        }
+    }
+    return consumed;
+}
+
+void desktop_settings_scene_start_on_exit(void* context) {
+    DesktopSettingsApp* app = context;
+    submenu_clean(app->submenu);
+}

+ 30 - 0
applications/desktop/scenes/desktop_scene.c

@@ -0,0 +1,30 @@
+#include "desktop_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const desktop_on_enter_handlers[])(void*) = {
+#include "desktop_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const desktop_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "desktop_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const desktop_on_exit_handlers[])(void* context) = {
+#include "desktop_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers desktop_scene_handlers = {
+    .on_enter_handlers = desktop_on_enter_handlers,
+    .on_event_handlers = desktop_on_event_handlers,
+    .on_exit_handlers = desktop_on_exit_handlers,
+    .scene_num = DesktopSceneNum,
+};

+ 29 - 0
applications/desktop/scenes/desktop_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) DesktopScene##id,
+typedef enum {
+#include "desktop_scene_config.h"
+    DesktopSceneNum,
+} DesktopScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers desktop_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "desktop_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+    bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "desktop_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "desktop_scene_config.h"
+#undef ADD_SCENE

+ 6 - 0
applications/desktop/scenes/desktop_scene_config.h

@@ -0,0 +1,6 @@
+ADD_SCENE(desktop, main, Main)
+ADD_SCENE(desktop, lock_menu, LockMenu)
+ADD_SCENE(desktop, locked, Locked)
+ADD_SCENE(desktop, debug, Debug)
+ADD_SCENE(desktop, first_start, FirstStart)
+ADD_SCENE(desktop, hw_mismatch, HwMismatch)

+ 62 - 0
applications/desktop/scenes/desktop_scene_debug.c

@@ -0,0 +1,62 @@
+#include "../desktop_i.h"
+#include "../views/desktop_debug.h"
+#include "applications/dolphin/dolphin.h"
+#include "applications/dolphin/helpers/dolphin_deed.h"
+
+void desktop_scene_debug_callback(DesktopDebugEvent event, void* context) {
+    Desktop* desktop = (Desktop*)context;
+    view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
+}
+
+const void desktop_scene_debug_on_enter(void* context) {
+    Desktop* desktop = (Desktop*)context;
+
+    desktop_debug_get_dolphin_data(desktop->debug_view);
+
+    desktop_debug_set_callback(desktop->debug_view, desktop_scene_debug_callback, desktop);
+    view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewDebug);
+}
+
+const bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) {
+    Desktop* desktop = (Desktop*)context;
+    Dolphin* dolphin = furi_record_open("dolphin");
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case DesktopDebugEventExit:
+            scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
+            dolphin_save(dolphin);
+            consumed = true;
+            break;
+
+        case DesktopDebugEventDeed:
+            dolphin_deed(dolphin, DolphinDeedIButtonEmulate);
+            desktop_debug_get_dolphin_data(desktop->debug_view);
+            consumed = true;
+            break;
+
+        case DesktopDebugEventWrongDeed:
+            dolphin_deed(dolphin, DolphinDeedWrong);
+            desktop_debug_get_dolphin_data(desktop->debug_view);
+            consumed = true;
+            break;
+
+        case DesktopDebugEventSaveState:
+            dolphin_save(dolphin);
+            consumed = true;
+            break;
+
+        default:
+            break;
+        }
+    }
+
+    furi_record_close("dolphin");
+    return consumed;
+}
+
+const void desktop_scene_debug_on_exit(void* context) {
+    Desktop* desktop = (Desktop*)context;
+    desktop_debug_reset_screen_idx(desktop->debug_view);
+}

+ 43 - 0
applications/desktop/scenes/desktop_scene_first_start.c

@@ -0,0 +1,43 @@
+#include "../desktop_i.h"
+#include "../views/desktop_first_start.h"
+#include "applications/dolphin/dolphin.h"
+
+void desktop_scene_first_start_callback(DesktopFirstStartEvent event, void* context) {
+    Desktop* desktop = (Desktop*)context;
+    view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
+}
+
+const void desktop_scene_first_start_on_enter(void* context) {
+    Desktop* desktop = (Desktop*)context;
+    DesktopFirstStartView* first_start_view = desktop->first_start_view;
+
+    desktop_first_start_set_callback(
+        first_start_view, desktop_scene_first_start_callback, desktop);
+
+    view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewFirstStart);
+}
+
+const bool desktop_scene_first_start_on_event(void* context, SceneManagerEvent event) {
+    Desktop* desktop = (Desktop*)context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case DesktopFirstStartCompleted:
+            scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
+            consumed = true;
+            break;
+
+        default:
+            break;
+        }
+    }
+    return consumed;
+}
+
+const void desktop_scene_first_start_on_exit(void* context) {
+    // Desktop* desktop = (Desktop*)context;
+    Dolphin* dolphin = furi_record_open("dolphin");
+    dolphin_save(dolphin);
+    furi_record_close("dolphin");
+}

+ 37 - 0
applications/desktop/scenes/desktop_scene_hw_mismatch.c

@@ -0,0 +1,37 @@
+#include "../desktop_i.h"
+#include "../views/desktop_hw_mismatch.h"
+
+void desktop_scene_hw_mismatch_callback(DesktopHwMismatchEvent event, void* context) {
+    Desktop* desktop = (Desktop*)context;
+    view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
+}
+
+const void desktop_scene_hw_mismatch_on_enter(void* context) {
+    Desktop* desktop = (Desktop*)context;
+
+    desktop_hw_mismatch_set_callback(
+        desktop->hw_mismatch_view, desktop_scene_hw_mismatch_callback, desktop);
+    view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewHwMismatch);
+}
+
+const bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event) {
+    Desktop* desktop = (Desktop*)context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case DesktopHwMismatchEventExit:
+            scene_manager_previous_scene(desktop->scene_manager);
+            consumed = true;
+            break;
+
+        default:
+            break;
+        }
+    }
+    return consumed;
+}
+
+const void desktop_scene_hw_mismatch_on_exit(void* context) {
+    // Desktop* desktop = (Desktop*)context;
+}

+ 42 - 0
applications/desktop/scenes/desktop_scene_lock_menu.c

@@ -0,0 +1,42 @@
+#include "../desktop_i.h"
+#include "../views/desktop_lock_menu.h"
+
+void desktop_scene_lock_menu_callback(DesktopLockMenuEvent event, void* context) {
+    Desktop* desktop = (Desktop*)context;
+    view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
+}
+
+const void desktop_scene_lock_menu_on_enter(void* context) {
+    Desktop* desktop = (Desktop*)context;
+
+    desktop_lock_menu_set_callback(desktop->lock_menu, desktop_scene_lock_menu_callback, desktop);
+    view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLockMenu);
+}
+
+const bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) {
+    Desktop* desktop = (Desktop*)context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case DesktopLockMenuEventLock:
+            scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked);
+            consumed = true;
+            break;
+
+        case DesktopLockMenuEventExit:
+            scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
+            consumed = true;
+            break;
+
+        default:
+            break;
+        }
+    }
+    return consumed;
+}
+
+const void desktop_scene_lock_menu_on_exit(void* context) {
+    Desktop* desktop = (Desktop*)context;
+    desktop_lock_menu_reset_idx(desktop->lock_menu);
+}

+ 51 - 0
applications/desktop/scenes/desktop_scene_locked.c

@@ -0,0 +1,51 @@
+#include "../desktop_i.h"
+#include "../views/desktop_locked.h"
+
+void desktop_scene_locked_callback(DesktopLockedEvent event, void* context) {
+    Desktop* desktop = (Desktop*)context;
+    view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
+}
+
+const void desktop_scene_locked_on_enter(void* context) {
+    Desktop* desktop = (Desktop*)context;
+    DesktopLockedView* locked_view = desktop->locked_view;
+
+    desktop_locked_set_callback(locked_view, desktop_scene_locked_callback, desktop);
+    desktop_locked_reset_door_pos(locked_view);
+    desktop_locked_update_hint_timeout(locked_view);
+
+    view_port_enabled_set(desktop->lock_viewport, true);
+    osTimerStart(locked_view->timer, 63);
+
+    view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLocked);
+}
+
+const bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) {
+    Desktop* desktop = (Desktop*)context;
+
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case DesktopLockedEventUnlock:
+            scene_manager_set_scene_state(
+                desktop->scene_manager, DesktopSceneMain, DesktopMainEventUnlocked);
+            scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
+            consumed = true;
+            break;
+        case DesktopLockedEventUpdate:
+            desktop_locked_manage_redraw(desktop->locked_view);
+            consumed = true;
+        default:
+            break;
+        }
+    }
+
+    return consumed;
+}
+
+const void desktop_scene_locked_on_exit(void* context) {
+    Desktop* desktop = (Desktop*)context;
+    DesktopLockedView* locked_view = desktop->locked_view;
+    desktop_locked_reset_counter(desktop->locked_view);
+    osTimerStop(locked_view->timer);
+}

+ 88 - 0
applications/desktop/scenes/desktop_scene_main.c

@@ -0,0 +1,88 @@
+#include "../desktop_i.h"
+#include "../views/desktop_main.h"
+#include "applications.h"
+#define MAIN_VIEW_DEFAULT (0UL)
+
+static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* flipper_app) {
+    furi_assert(desktop);
+    furi_assert(flipper_app);
+    furi_assert(flipper_app->app);
+    furi_assert(flipper_app->name);
+
+    if(furi_thread_get_state(desktop->scene_thread) != FuriThreadStateStopped) {
+        FURI_LOG_E("Desktop", "Thread is already running");
+        return;
+    }
+
+    furi_thread_set_name(desktop->scene_thread, flipper_app->name);
+    furi_thread_set_stack_size(desktop->scene_thread, flipper_app->stack_size);
+    furi_thread_set_callback(desktop->scene_thread, flipper_app->app);
+
+    furi_thread_start(desktop->scene_thread);
+}
+
+void desktop_scene_main_callback(DesktopMainEvent event, void* context) {
+    Desktop* desktop = (Desktop*)context;
+    view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
+}
+
+const void desktop_scene_main_on_enter(void* context) {
+    Desktop* desktop = (Desktop*)context;
+    DesktopMainView* main_view = desktop->main_view;
+
+    desktop_main_set_callback(main_view, desktop_scene_main_callback, desktop);
+    view_port_enabled_set(desktop->lock_viewport, false);
+
+    if(scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneMain) ==
+       DesktopMainEventUnlocked) {
+        desktop_main_unlocked(desktop->main_view);
+    }
+
+    view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain);
+}
+
+const bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
+    Desktop* desktop = (Desktop*)context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case DesktopMainEventOpenMenu:
+            with_value_mutex(
+                desktop->menu_vm, (Menu * menu) { menu_ok(menu); });
+            consumed = true;
+            break;
+
+        case DesktopMainEventOpenLockMenu:
+            scene_manager_next_scene(desktop->scene_manager, DesktopSceneLockMenu);
+            consumed = true;
+            break;
+
+        case DesktopMainEventOpenDebug:
+            scene_manager_next_scene(desktop->scene_manager, DesktopViewDebug);
+            consumed = true;
+            break;
+
+        case DesktopMainEventOpenArchive:
+            desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE);
+            consumed = true;
+            break;
+        case DesktopMainEventOpenFavorite:
+            desktop_settings_load(&desktop->settings);
+            desktop_switch_to_app(desktop, &FLIPPER_APPS[desktop->settings.favorite]);
+            consumed = true;
+            break;
+
+        default:
+            break;
+        }
+    }
+
+    return consumed;
+}
+
+const void desktop_scene_main_on_exit(void* context) {
+    Desktop* desktop = (Desktop*)context;
+    scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneMain, MAIN_VIEW_DEFAULT);
+    desktop_main_reset_hint(desktop->main_view);
+}

+ 166 - 0
applications/desktop/views/desktop_debug.c

@@ -0,0 +1,166 @@
+#include <furi.h>
+#include "../desktop_i.h"
+#include "desktop_debug.h"
+
+#include "applications/dolphin/helpers/dolphin_state.h"
+#include "applications/dolphin/dolphin.h"
+
+void desktop_debug_set_callback(
+    DesktopDebugView* debug_view,
+    DesktopDebugViewCallback callback,
+    void* context) {
+    furi_assert(debug_view);
+    furi_assert(callback);
+    debug_view->callback = callback;
+    debug_view->context = context;
+}
+
+void desktop_debug_render(Canvas* canvas, void* model) {
+    canvas_clear(canvas);
+    DesktopDebugViewModel* m = model;
+    const Version* ver;
+    char buffer[64];
+
+    static const char* headers[] = {"FW Version info:", "Boot Version info:", "Desktop info:"};
+
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 2, 13, headers[m->screen]);
+    canvas_set_font(canvas, FontSecondary);
+
+    if(m->screen != DesktopViewStatsMeta) {
+        // Hardware version
+        const char* my_name = furi_hal_version_get_name_ptr();
+        snprintf(
+            buffer,
+            sizeof(buffer),
+            "HW: %d.F%dB%dC%d %s",
+            furi_hal_version_get_hw_version(),
+            furi_hal_version_get_hw_target(),
+            furi_hal_version_get_hw_body(),
+            furi_hal_version_get_hw_connect(),
+            my_name ? my_name : "Unknown");
+        canvas_draw_str(canvas, 5, 23, buffer);
+
+        ver = m->screen == DesktopViewStatsBoot ? furi_hal_version_get_boot_version() :
+                                                  furi_hal_version_get_firmware_version();
+
+        if(!ver) {
+            canvas_draw_str(canvas, 5, 33, "No info");
+            return;
+        }
+
+        snprintf(
+            buffer,
+            sizeof(buffer),
+            "%s [%s]",
+            version_get_version(ver),
+            version_get_builddate(ver));
+        canvas_draw_str(canvas, 5, 33, buffer);
+
+        snprintf(
+            buffer,
+            sizeof(buffer),
+            "%s [%s]",
+            version_get_githash(ver),
+            version_get_gitbranchnum(ver));
+        canvas_draw_str(canvas, 5, 43, buffer);
+
+        snprintf(
+            buffer, sizeof(buffer), "[%s] %s", version_get_target(ver), version_get_gitbranch(ver));
+        canvas_draw_str(canvas, 5, 53, buffer);
+
+    } else {
+        char buffer[64];
+
+        canvas_set_font(canvas, FontSecondary);
+        snprintf(buffer, 64, "Icounter: %ld", m->icounter);
+        canvas_draw_str(canvas, 5, 30, buffer);
+        snprintf(buffer, 64, "Butthurt: %ld", m->butthurt);
+        canvas_draw_str(canvas, 5, 40, buffer);
+        canvas_draw_str(canvas, 0, 53, "[< >] icounter value   [ok] save");
+    }
+}
+
+View* desktop_debug_get_view(DesktopDebugView* debug_view) {
+    furi_assert(debug_view);
+    return debug_view->view;
+}
+
+bool desktop_debug_input(InputEvent* event, void* context) {
+    furi_assert(event);
+    furi_assert(context);
+
+    DesktopDebugView* debug_view = context;
+
+    if(event->type != InputTypeShort) return false;
+    DesktopViewStatsScreens current = 0;
+    with_view_model(
+        debug_view->view, (DesktopDebugViewModel * model) {
+            if(event->key == InputKeyDown) {
+                model->screen = (model->screen + 1) % DesktopViewStatsTotalCount;
+            } else if(event->key == InputKeyUp) {
+                model->screen = ((model->screen - 1) + DesktopViewStatsTotalCount) %
+                                DesktopViewStatsTotalCount;
+            }
+            current = model->screen;
+            return true;
+        });
+
+    if(current == DesktopViewStatsMeta) {
+        if(event->key == InputKeyLeft) {
+            debug_view->callback(DesktopDebugEventWrongDeed, debug_view->context);
+        } else if(event->key == InputKeyRight) {
+            debug_view->callback(DesktopDebugEventDeed, debug_view->context);
+        } else if(event->key == InputKeyOk) {
+            debug_view->callback(DesktopDebugEventSaveState, debug_view->context);
+        } else {
+            return false;
+        }
+    }
+
+    if(event->key == InputKeyBack) {
+        debug_view->callback(DesktopDebugEventExit, debug_view->context);
+    }
+
+    return true;
+}
+
+DesktopDebugView* desktop_debug_alloc() {
+    DesktopDebugView* debug_view = furi_alloc(sizeof(DesktopDebugView));
+    debug_view->view = view_alloc();
+    view_allocate_model(debug_view->view, ViewModelTypeLocking, sizeof(DesktopDebugViewModel));
+    view_set_context(debug_view->view, debug_view);
+    view_set_draw_callback(debug_view->view, (ViewDrawCallback)desktop_debug_render);
+    view_set_input_callback(debug_view->view, desktop_debug_input);
+
+    return debug_view;
+}
+
+void desktop_debug_free(DesktopDebugView* debug_view) {
+    furi_assert(debug_view);
+
+    view_free(debug_view->view);
+    free(debug_view);
+}
+
+void desktop_debug_get_dolphin_data(DesktopDebugView* debug_view) {
+    Dolphin* dolphin = furi_record_open("dolphin");
+    DolphinDeedWeight stats = dolphin_stats(dolphin);
+    with_view_model(
+        debug_view->view, (DesktopDebugViewModel * model) {
+            model->icounter = stats.icounter;
+            model->butthurt = stats.butthurt;
+            return true;
+        });
+
+    furi_record_close("dolphin");
+}
+
+void desktop_debug_reset_screen_idx(DesktopDebugView* debug_view) {
+    with_view_model(
+        debug_view->view, (DesktopDebugViewModel * model) {
+            model->screen = 0;
+            return true;
+        });
+}

+ 52 - 0
applications/desktop/views/desktop_debug.h

@@ -0,0 +1,52 @@
+#pragma once
+
+#include <gui/gui_i.h>
+#include <gui/view.h>
+#include <gui/canvas.h>
+#include <gui/elements.h>
+#include <furi.h>
+#include <storage/storage.h>
+
+typedef enum {
+    DesktopDebugEventDeed,
+    DesktopDebugEventWrongDeed,
+    DesktopDebugEventSaveState,
+    DesktopDebugEventExit,
+} DesktopDebugEvent;
+
+typedef struct DesktopDebugView DesktopDebugView;
+
+typedef void (*DesktopDebugViewCallback)(DesktopDebugEvent event, void* context);
+
+// Debug info
+typedef enum {
+    DesktopViewStatsFw,
+    DesktopViewStatsBoot,
+    DesktopViewStatsMeta,
+    DesktopViewStatsTotalCount,
+} DesktopViewStatsScreens;
+
+struct DesktopDebugView {
+    View* view;
+    DesktopDebugViewCallback callback;
+    void* context;
+};
+
+typedef struct {
+    uint32_t icounter;
+    uint32_t butthurt;
+    DesktopViewStatsScreens screen;
+} DesktopDebugViewModel;
+
+void desktop_debug_set_callback(
+    DesktopDebugView* debug_view,
+    DesktopDebugViewCallback callback,
+    void* context);
+
+View* desktop_debug_get_view(DesktopDebugView* debug_view);
+
+DesktopDebugView* desktop_debug_alloc();
+void desktop_debug_free(DesktopDebugView* debug_view);
+
+void desktop_debug_get_dolphin_data(DesktopDebugView* debug_view);
+void desktop_debug_reset_screen_idx(DesktopDebugView* debug_view);

+ 107 - 0
applications/desktop/views/desktop_first_start.c

@@ -0,0 +1,107 @@
+#include <furi.h>
+#include "../desktop_i.h"
+#include "desktop_first_start.h"
+
+void desktop_first_start_set_callback(
+    DesktopFirstStartView* first_start_view,
+    DesktopFirstStartViewCallback callback,
+    void* context) {
+    furi_assert(first_start_view);
+    furi_assert(callback);
+    first_start_view->callback = callback;
+    first_start_view->context = context;
+}
+
+void desktop_first_start_render(Canvas* canvas, void* model) {
+    DesktopFirstStartViewModel* m = model;
+
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontSecondary);
+    uint8_t width = canvas_width(canvas);
+    uint8_t height = canvas_height(canvas);
+    const char* my_name = furi_hal_version_get_name_ptr();
+    if(m->page == 0) {
+        canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart0_70x53);
+        elements_multiline_text_framed(canvas, 75, 20, "Hey m8,\npress > to\ncontinue");
+    } else if(m->page == 1) {
+        canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart1_59x53);
+        elements_multiline_text_framed(canvas, 64, 20, "First Of All,\n...      >");
+    } else if(m->page == 2) {
+        canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart2_59x51);
+        elements_multiline_text_framed(canvas, 64, 20, "Thank you\nfor your\nsupport! >");
+    } else if(m->page == 3) {
+        canvas_draw_icon(canvas, width - 57, height - 48, &I_DolphinFirstStart3_57x48);
+        elements_multiline_text_framed(canvas, 0, 20, "Kickstarter\ncampaign\nwas INSANE! >");
+    } else if(m->page == 4) {
+        canvas_draw_icon(canvas, width - 67, height - 50, &I_DolphinFirstStart4_67x53);
+        elements_multiline_text_framed(canvas, 0, 17, "Now\nallow me\nto introduce\nmyself >");
+    } else if(m->page == 5) {
+        char buf[64];
+        snprintf(
+            buf,
+            64,
+            "%s %s%s",
+            "I am",
+            my_name ? my_name : "Unknown",
+            ",\ncyberdesktop\nliving in your\npocket >");
+        canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart5_54x49);
+        elements_multiline_text_framed(canvas, 60, 17, buf);
+    } else if(m->page == 6) {
+        canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart6_58x54);
+        elements_multiline_text_framed(
+            canvas, 63, 17, "I can grow\nsmart'n'cool\nif you use me\noften >");
+    } else if(m->page == 7) {
+        canvas_draw_icon(canvas, width - 61, height - 48, &I_DolphinFirstStart7_61x51);
+        elements_multiline_text_framed(
+            canvas, 0, 17, "As long as\nyou read, write\nand emulate >");
+    } else if(m->page == 8) {
+        canvas_draw_icon(canvas, width - 56, height - 48, &I_DolphinFirstStart8_56x51);
+        elements_multiline_text_framed(
+            canvas, 0, 17, "You can check\nmy level and\nmood in the\nPassport menu");
+    }
+}
+
+View* desktop_first_start_get_view(DesktopFirstStartView* first_start_view) {
+    furi_assert(first_start_view);
+    return first_start_view->view;
+}
+
+bool desktop_first_start_input(InputEvent* event, void* context) {
+    furi_assert(event);
+    DesktopFirstStartView* first_start_view = context;
+
+    if(event->type == InputTypeShort) {
+        DesktopFirstStartViewModel* model = view_get_model(first_start_view->view);
+        if(event->key == InputKeyLeft) {
+            if(model->page > 0) model->page--;
+        } else if(event->key == InputKeyRight) {
+            uint32_t page = ++model->page;
+            if(page > 8) {
+                first_start_view->callback(DesktopFirstStartCompleted, first_start_view->context);
+            }
+        }
+        view_commit_model(first_start_view->view, true);
+    }
+
+    return true;
+}
+
+DesktopFirstStartView* desktop_first_start_alloc() {
+    DesktopFirstStartView* first_start_view = furi_alloc(sizeof(DesktopFirstStartView));
+    first_start_view->view = view_alloc();
+    view_allocate_model(
+        first_start_view->view, ViewModelTypeLocking, sizeof(DesktopFirstStartViewModel));
+    view_set_context(first_start_view->view, first_start_view);
+    view_set_draw_callback(first_start_view->view, (ViewDrawCallback)desktop_first_start_render);
+    view_set_input_callback(first_start_view->view, desktop_first_start_input);
+
+    return first_start_view;
+}
+
+void desktop_first_start_free(DesktopFirstStartView* first_start_view) {
+    furi_assert(first_start_view);
+
+    view_free(first_start_view->view);
+    free(first_start_view);
+}

+ 35 - 0
applications/desktop/views/desktop_first_start.h

@@ -0,0 +1,35 @@
+#pragma once
+
+#include <gui/gui_i.h>
+#include <gui/view.h>
+#include <gui/canvas.h>
+#include <gui/elements.h>
+#include <furi.h>
+
+typedef enum {
+    DesktopFirstStartCompleted,
+} DesktopFirstStartEvent;
+
+typedef struct DesktopFirstStartView DesktopFirstStartView;
+
+typedef void (*DesktopFirstStartViewCallback)(DesktopFirstStartEvent event, void* context);
+
+struct DesktopFirstStartView {
+    View* view;
+    DesktopFirstStartViewCallback callback;
+    void* context;
+};
+
+typedef struct {
+    uint8_t page;
+} DesktopFirstStartViewModel;
+
+void desktop_first_start_set_callback(
+    DesktopFirstStartView* main_view,
+    DesktopFirstStartViewCallback callback,
+    void* context);
+
+View* desktop_first_start_get_view(DesktopFirstStartView* main_view);
+
+DesktopFirstStartView* desktop_first_start_alloc();
+void desktop_first_start_free(DesktopFirstStartView* main_view);

+ 66 - 0
applications/desktop/views/desktop_hw_mismatch.c

@@ -0,0 +1,66 @@
+#include <furi.h>
+#include "../desktop_i.h"
+#include <furi-hal.h>
+#include <furi-hal-version.h>
+
+#include "desktop_hw_mismatch.h"
+
+void desktop_hw_mismatch_set_callback(
+    DesktopHwMismatchView* main_view,
+    DesktopHwMismatchViewCallback callback,
+    void* context) {
+    furi_assert(main_view);
+    furi_assert(callback);
+    main_view->callback = callback;
+    main_view->context = context;
+}
+
+void desktop_hw_mismatch_render(Canvas* canvas, void* model) {
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 2, 15, "!!!! HW Mismatch !!!!");
+
+    char buffer[64];
+    canvas_set_font(canvas, FontSecondary);
+    snprintf(buffer, 64, "HW target: F%d", furi_hal_version_get_hw_target());
+    canvas_draw_str(canvas, 5, 27, buffer);
+    canvas_draw_str(canvas, 5, 38, "FW target: " TARGET);
+}
+
+View* desktop_hw_mismatch_get_view(DesktopHwMismatchView* hw_mismatch_view) {
+    furi_assert(hw_mismatch_view);
+    return hw_mismatch_view->view;
+}
+
+bool desktop_hw_mismatch_input(InputEvent* event, void* context) {
+    furi_assert(event);
+    furi_assert(context);
+
+    DesktopHwMismatchView* hw_mismatch_view = context;
+
+    if(event->type == InputTypeShort) {
+        hw_mismatch_view->callback(DesktopHwMismatchEventExit, hw_mismatch_view->context);
+    }
+
+    return true;
+}
+
+DesktopHwMismatchView* desktop_hw_mismatch_alloc() {
+    DesktopHwMismatchView* hw_mismatch_view = furi_alloc(sizeof(DesktopHwMismatchView));
+    hw_mismatch_view->view = view_alloc();
+    view_allocate_model(
+        hw_mismatch_view->view, ViewModelTypeLocking, sizeof(DesktopHwMismatchViewModel));
+    view_set_context(hw_mismatch_view->view, hw_mismatch_view);
+    view_set_draw_callback(hw_mismatch_view->view, (ViewDrawCallback)desktop_hw_mismatch_render);
+    view_set_input_callback(hw_mismatch_view->view, desktop_hw_mismatch_input);
+
+    return hw_mismatch_view;
+}
+
+void desktop_hw_mismatch_free(DesktopHwMismatchView* hw_mismatch_view) {
+    furi_assert(hw_mismatch_view);
+
+    view_free(hw_mismatch_view->view);
+    free(hw_mismatch_view);
+}

+ 38 - 0
applications/desktop/views/desktop_hw_mismatch.h

@@ -0,0 +1,38 @@
+#pragma once
+
+#include <gui/gui_i.h>
+#include <gui/view.h>
+#include <gui/canvas.h>
+#include <gui/elements.h>
+#include <furi.h>
+
+typedef enum {
+    DesktopHwMismatchEventExit,
+} DesktopHwMismatchEvent;
+
+typedef struct DesktopHwMismatchView DesktopHwMismatchView;
+
+typedef void (*DesktopHwMismatchViewCallback)(DesktopHwMismatchEvent event, void* context);
+
+struct DesktopHwMismatchView {
+    View* view;
+    DesktopHwMismatchViewCallback callback;
+    void* context;
+};
+
+typedef struct {
+    IconAnimation* animation;
+    uint8_t scene_num;
+    uint8_t hint_timeout;
+    bool locked;
+} DesktopHwMismatchViewModel;
+
+void desktop_hw_mismatch_set_callback(
+    DesktopHwMismatchView* hw_mismatch_view,
+    DesktopHwMismatchViewCallback callback,
+    void* context);
+
+View* desktop_hw_mismatch_get_view(DesktopHwMismatchView* hw_mismatch_view);
+
+DesktopHwMismatchView* desktop_hw_mismatch_alloc();
+void desktop_hw_mismatch_free(DesktopHwMismatchView* hw_mismatch_view);

+ 111 - 0
applications/desktop/views/desktop_lock_menu.c

@@ -0,0 +1,111 @@
+#include <furi.h>
+#include "../desktop_i.h"
+#include "desktop_lock_menu.h"
+
+void desktop_lock_menu_set_callback(
+    DesktopLockMenuView* lock_menu,
+    DesktopLockMenuViewCallback callback,
+    void* context) {
+    furi_assert(lock_menu);
+    furi_assert(callback);
+    lock_menu->callback = callback;
+    lock_menu->context = context;
+}
+
+void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu) {
+    with_view_model(
+        lock_menu->view, (DesktopLockMenuViewModel * model) {
+            model->idx = 0;
+            return true;
+        });
+}
+
+static void lock_menu_callback(void* context, uint8_t index) {
+    furi_assert(context);
+    DesktopLockMenuView* lock_menu = context;
+    switch(index) {
+    case 0: // lock
+        lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context);
+    default: // wip message
+        with_view_model(
+            lock_menu->view, (DesktopLockMenuViewModel * model) {
+                model->hint_timeout = HINT_TIMEOUT_L;
+                return true;
+            });
+        break;
+    }
+}
+
+void desktop_lock_menu_render(Canvas* canvas, void* model) {
+    const char* Lockmenu_Items[3] = {"Lock", "Set PIN", "DUMB mode"};
+
+    DesktopLockMenuViewModel* m = model;
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_draw_icon(canvas, -57, 0, &I_DoorLeft_70x55);
+    canvas_draw_icon(canvas, 115, 0, &I_DoorRight_70x55);
+    canvas_set_font(canvas, FontSecondary);
+
+    for(uint8_t i = 0; i < 3; ++i) {
+        canvas_draw_str_aligned(
+            canvas,
+            64,
+            13 + (i * 17),
+            AlignCenter,
+            AlignCenter,
+            (m->hint_timeout && m->idx == i && m->idx) ? "Not implemented" : Lockmenu_Items[i]);
+        if(m->idx == i) elements_frame(canvas, 15, 5 + (i * 17), 98, 15);
+    }
+}
+
+View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu) {
+    furi_assert(lock_menu);
+    return lock_menu->view;
+}
+
+bool desktop_lock_menu_input(InputEvent* event, void* context) {
+    furi_assert(event);
+    furi_assert(context);
+
+    DesktopLockMenuView* lock_menu = context;
+    uint8_t idx;
+
+    if(event->type != InputTypeShort) return false;
+    with_view_model(
+        lock_menu->view, (DesktopLockMenuViewModel * model) {
+            model->hint_timeout = 0; // clear hint timeout
+            if(event->key == InputKeyUp) {
+                model->idx = CLAMP(model->idx - 1, 2, 0);
+            } else if(event->key == InputKeyDown) {
+                model->idx = CLAMP(model->idx + 1, 2, 0);
+            }
+            idx = model->idx;
+            return true;
+        });
+
+    if(event->key == InputKeyBack) {
+        lock_menu->callback(DesktopLockMenuEventExit, lock_menu->context);
+    } else if(event->key == InputKeyOk) {
+        lock_menu_callback(lock_menu, idx);
+    }
+
+    return true;
+}
+
+DesktopLockMenuView* desktop_lock_menu_alloc() {
+    DesktopLockMenuView* lock_menu = furi_alloc(sizeof(DesktopLockMenuView));
+    lock_menu->view = view_alloc();
+    view_allocate_model(lock_menu->view, ViewModelTypeLocking, sizeof(DesktopLockMenuViewModel));
+    view_set_context(lock_menu->view, lock_menu);
+    view_set_draw_callback(lock_menu->view, (ViewDrawCallback)desktop_lock_menu_render);
+    view_set_input_callback(lock_menu->view, desktop_lock_menu_input);
+
+    return lock_menu;
+}
+
+void desktop_lock_menu_free(DesktopLockMenuView* lock_menu_view) {
+    furi_assert(lock_menu_view);
+
+    view_free(lock_menu_view->view);
+    free(lock_menu_view);
+}

+ 39 - 0
applications/desktop/views/desktop_lock_menu.h

@@ -0,0 +1,39 @@
+#pragma once
+
+#include <gui/gui_i.h>
+#include <gui/view.h>
+#include <gui/canvas.h>
+#include <gui/elements.h>
+#include <furi.h>
+
+typedef enum {
+    DesktopLockMenuEventLock,
+    DesktopLockMenuEventUnlock,
+    DesktopLockMenuEventExit,
+} DesktopLockMenuEvent;
+
+typedef struct DesktopLockMenuView DesktopLockMenuView;
+
+typedef void (*DesktopLockMenuViewCallback)(DesktopLockMenuEvent event, void* context);
+
+struct DesktopLockMenuView {
+    View* view;
+    DesktopLockMenuViewCallback callback;
+    void* context;
+};
+
+typedef struct {
+    uint8_t idx;
+    uint8_t hint_timeout;
+    bool locked;
+} DesktopLockMenuViewModel;
+
+void desktop_lock_menu_set_callback(
+    DesktopLockMenuView* lock_menu,
+    DesktopLockMenuViewCallback callback,
+    void* context);
+
+View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu);
+void desktop_lock_menu_reset_idx(DesktopLockMenuView* lock_menu);
+DesktopLockMenuView* desktop_lock_menu_alloc();
+void desktop_lock_menu_free(DesktopLockMenuView* lock_menu);

+ 171 - 0
applications/desktop/views/desktop_locked.c

@@ -0,0 +1,171 @@
+#include <furi.h>
+#include "../desktop_i.h"
+#include "desktop_locked.h"
+
+static const Icon* idle_scenes[] = {&A_Wink_128x64, &A_WatchingTV_128x64};
+
+void desktop_locked_set_callback(
+    DesktopLockedView* locked_view,
+    DesktopLockedViewCallback callback,
+    void* context) {
+    furi_assert(locked_view);
+    furi_assert(callback);
+    locked_view->callback = callback;
+    locked_view->context = context;
+}
+
+void locked_view_timer_callback(void* context) {
+    DesktopLockedView* locked_view = context;
+    locked_view->callback(DesktopLockedEventUpdate, locked_view->context);
+}
+
+// temporary locked screen animation managment
+static void
+    desktop_scene_handler_set_scene(DesktopLockedView* locked_view, const Icon* icon_data) {
+    with_view_model(
+        locked_view->view, (DesktopLockedViewModel * model) {
+            if(model->animation) icon_animation_free(model->animation);
+            model->animation = icon_animation_alloc(icon_data);
+            icon_animation_start(model->animation);
+            return true;
+        });
+}
+
+void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view) {
+    with_view_model(
+        locked_view->view, (DesktopLockedViewModel * model) {
+            model->hint_timeout = HINT_TIMEOUT_H;
+            return true;
+        });
+}
+
+void desktop_locked_reset_door_pos(DesktopLockedView* locked_view) {
+    with_view_model(
+        locked_view->view, (DesktopLockedViewModel * model) {
+            model->animation_seq_end = false;
+            model->door_left_x = -57;
+            model->door_right_x = 115;
+            return true;
+        });
+}
+
+void desktop_locked_manage_redraw(DesktopLockedView* locked_view) {
+    bool animation_seq_end;
+
+    with_view_model(
+        locked_view->view, (DesktopLockedViewModel * model) {
+            model->animation_seq_end = !model->door_left_x;
+            animation_seq_end = model->animation_seq_end;
+
+            if(!model->animation_seq_end) {
+                model->door_left_x = CLAMP(model->door_left_x + 5, 0, -57);
+                model->door_right_x = CLAMP(model->door_right_x - 5, 115, 60);
+            }
+            return true;
+        });
+
+    if(animation_seq_end) {
+        osTimerStop(locked_view->timer);
+    }
+}
+
+void desktop_locked_reset_counter(DesktopLockedView* locked_view) {
+    locked_view->lock_count = 0;
+    locked_view->lock_lastpress = 0;
+
+    with_view_model(
+        locked_view->view, (DesktopLockedViewModel * model) {
+            model->hint_timeout = 0;
+            return true;
+        });
+}
+
+void desktop_locked_render(Canvas* canvas, void* model) {
+    DesktopLockedViewModel* m = model;
+
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+
+    if(!m->animation_seq_end) {
+        canvas_draw_icon(canvas, m->door_left_x, 0, &I_DoorLeft_70x55);
+        canvas_draw_icon(canvas, m->door_right_x, 0, &I_DoorRight_70x55);
+    }
+
+    if(m->animation && m->animation_seq_end) {
+        canvas_draw_icon_animation(canvas, 0, -3, m->animation);
+    }
+
+    if(m->hint_timeout) {
+        m->hint_timeout--;
+
+        if(!m->animation_seq_end) {
+            canvas_set_font(canvas, FontPrimary);
+            elements_multiline_text_framed(canvas, 42, 30, "Locked");
+        } else {
+            canvas_set_font(canvas, FontSecondary);
+            canvas_draw_icon(canvas, 13, 5, &I_LockPopup_100x49);
+            elements_multiline_text(canvas, 65, 20, "To unlock\npress:");
+        }
+    }
+}
+
+View* desktop_locked_get_view(DesktopLockedView* locked_view) {
+    furi_assert(locked_view);
+    return locked_view->view;
+}
+
+bool desktop_locked_input(InputEvent* event, void* context) {
+    furi_assert(event);
+    furi_assert(context);
+
+    DesktopLockedView* locked_view = context;
+    if(event->type == InputTypeShort) {
+        with_view_model(
+            locked_view->view, (DesktopLockedViewModel * model) {
+                model->hint_timeout = HINT_TIMEOUT_L;
+                return true;
+            });
+
+        if(event->key == InputKeyBack) {
+            uint32_t press_time = HAL_GetTick();
+
+            // check if pressed sequentially
+            if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) {
+                locked_view->lock_lastpress = press_time;
+                locked_view->lock_count = 0;
+            } else if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) {
+                locked_view->lock_lastpress = press_time;
+                locked_view->lock_count++;
+            }
+
+            if(locked_view->lock_count == UNLOCK_CNT) {
+                locked_view->lock_count = 0;
+                locked_view->callback(DesktopLockedEventUnlock, locked_view->context);
+            }
+        }
+    }
+    // All events consumed
+    return true;
+}
+
+DesktopLockedView* desktop_locked_alloc() {
+    DesktopLockedView* locked_view = furi_alloc(sizeof(DesktopLockedView));
+    locked_view->view = view_alloc();
+    locked_view->timer =
+        osTimerNew(locked_view_timer_callback, osTimerPeriodic, locked_view, NULL);
+
+    view_allocate_model(locked_view->view, ViewModelTypeLocking, sizeof(DesktopLockedViewModel));
+    view_set_context(locked_view->view, locked_view);
+    view_set_draw_callback(locked_view->view, (ViewDrawCallback)desktop_locked_render);
+    view_set_input_callback(locked_view->view, desktop_locked_input);
+
+    desktop_scene_handler_set_scene(locked_view, idle_scenes[random() % COUNT_OF(idle_scenes)]);
+    return locked_view;
+}
+
+void desktop_locked_free(DesktopLockedView* locked_view) {
+    furi_assert(locked_view);
+    osTimerDelete(locked_view->timer);
+    view_free(locked_view->view);
+    free(locked_view);
+}

+ 55 - 0
applications/desktop/views/desktop_locked.h

@@ -0,0 +1,55 @@
+#pragma once
+
+#include <gui/gui_i.h>
+#include <gui/view.h>
+#include <gui/canvas.h>
+#include <gui/elements.h>
+#include <furi.h>
+
+#define UNLOCK_RST_TIMEOUT 200
+#define UNLOCK_CNT 2 // 3 actually
+
+typedef enum {
+    DesktopLockedEventUnlock,
+    DesktopLockedEventUpdate,
+} DesktopLockedEvent;
+
+typedef struct DesktopLockedView DesktopLockedView;
+
+typedef void (*DesktopLockedViewCallback)(DesktopLockedEvent event, void* context);
+
+struct DesktopLockedView {
+    View* view;
+    DesktopLockedViewCallback callback;
+    void* context;
+
+    osTimerId_t timer;
+    uint8_t lock_count;
+    uint32_t lock_lastpress;
+};
+
+typedef struct {
+    IconAnimation* animation;
+    uint8_t scene_num;
+    int8_t door_left_x;
+    int8_t door_right_x;
+    uint8_t hint_timeout;
+    bool animation_seq_end;
+
+} DesktopLockedViewModel;
+
+void desktop_locked_set_callback(
+    DesktopLockedView* locked_view,
+    DesktopLockedViewCallback callback,
+    void* context);
+
+void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view);
+void desktop_locked_reset_counter(DesktopLockedView* locked_view);
+void desktop_locked_reset_door_pos(DesktopLockedView* locked_view);
+void desktop_locked_manage_redraw(DesktopLockedView* locked_view);
+
+View* desktop_locked_get_view(DesktopLockedView* locked_view);
+DesktopLockedView* desktop_locked_alloc();
+void desktop_locked_free(DesktopLockedView* locked_view);
+void desktop_main_unlocked(DesktopMainView* main_view);
+void desktop_main_reset_hint(DesktopMainView* main_view);

+ 116 - 0
applications/desktop/views/desktop_main.c

@@ -0,0 +1,116 @@
+#include <furi.h>
+#include "../desktop_i.h"
+#include "desktop_main.h"
+
+static const Icon* idle_scenes[] = {&A_Wink_128x64, &A_WatchingTV_128x64};
+
+void desktop_main_set_callback(
+    DesktopMainView* main_view,
+    DesktopMainViewCallback callback,
+    void* context) {
+    furi_assert(main_view);
+    furi_assert(callback);
+    main_view->callback = callback;
+    main_view->context = context;
+}
+
+void desktop_main_reset_hint(DesktopMainView* main_view) {
+    with_view_model(
+        main_view->view, (DesktopMainViewModel * model) {
+            model->hint_timeout = 0;
+            return true;
+        });
+}
+// temporary main screen animation managment
+void desktop_scene_handler_set_scene(DesktopMainView* main_view, const Icon* icon_data) {
+    with_view_model(
+        main_view->view, (DesktopMainViewModel * model) {
+            if(model->animation) icon_animation_free(model->animation);
+            model->animation = icon_animation_alloc(icon_data);
+            icon_animation_start(model->animation);
+            return true;
+        });
+}
+
+void desktop_scene_handler_switch_scene(DesktopMainView* main_view) {
+    with_view_model(
+        main_view->view, (DesktopMainViewModel * model) {
+            if(icon_animation_is_last_frame(model->animation)) {
+                if(model->animation) icon_animation_free(model->animation);
+                model->animation = icon_animation_alloc(idle_scenes[model->scene_num]);
+                icon_animation_start(model->animation);
+                model->scene_num = random() % COUNT_OF(idle_scenes);
+            }
+            return true;
+        });
+}
+
+void desktop_main_render(Canvas* canvas, void* model) {
+    canvas_clear(canvas);
+    DesktopMainViewModel* m = model;
+
+    if(m->animation) {
+        canvas_draw_icon_animation(canvas, 0, -3, m->animation);
+    }
+
+    if(m->unlocked && m->hint_timeout) {
+        m->hint_timeout = CLAMP(m->hint_timeout - 1, 2, 0);
+        canvas_set_font(canvas, FontPrimary);
+        elements_multiline_text_framed(canvas, 42, 30, "Unlocked");
+    }
+}
+
+View* desktop_main_get_view(DesktopMainView* main_view) {
+    furi_assert(main_view);
+    return main_view->view;
+}
+
+bool desktop_main_input(InputEvent* event, void* context) {
+    furi_assert(event);
+    furi_assert(context);
+
+    DesktopMainView* main_view = context;
+
+    if(event->key == InputKeyOk && event->type == InputTypeShort) {
+        main_view->callback(DesktopMainEventOpenMenu, main_view->context);
+    } else if(event->key == InputKeyDown && event->type == InputTypeLong) {
+        main_view->callback(DesktopMainEventOpenDebug, main_view->context);
+    } else if(event->key == InputKeyUp && event->type == InputTypeShort) {
+        main_view->callback(DesktopMainEventOpenLockMenu, main_view->context);
+    } else if(event->key == InputKeyDown && event->type == InputTypeShort) {
+        main_view->callback(DesktopMainEventOpenArchive, main_view->context);
+    } else if(event->key == InputKeyLeft && event->type == InputTypeShort) {
+        main_view->callback(DesktopMainEventOpenFavorite, main_view->context);
+    }
+    desktop_main_reset_hint(main_view);
+
+    return true;
+}
+
+DesktopMainView* desktop_main_alloc() {
+    DesktopMainView* main_view = furi_alloc(sizeof(DesktopMainView));
+    main_view->view = view_alloc();
+    view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(DesktopMainViewModel));
+    view_set_context(main_view->view, main_view);
+    view_set_draw_callback(main_view->view, (ViewDrawCallback)desktop_main_render);
+    view_set_input_callback(main_view->view, desktop_main_input);
+
+    desktop_scene_handler_set_scene(main_view, idle_scenes[random() % COUNT_OF(idle_scenes)]);
+
+    return main_view;
+}
+
+void desktop_main_free(DesktopMainView* main_view) {
+    furi_assert(main_view);
+    view_free(main_view->view);
+    free(main_view);
+}
+
+void desktop_main_unlocked(DesktopMainView* main_view) {
+    with_view_model(
+        main_view->view, (DesktopMainViewModel * model) {
+            model->unlocked = true;
+            model->hint_timeout = 2;
+            return true;
+        });
+}

+ 43 - 0
applications/desktop/views/desktop_main.h

@@ -0,0 +1,43 @@
+#pragma once
+
+#include <gui/gui_i.h>
+#include <gui/view.h>
+#include <gui/canvas.h>
+#include <gui/elements.h>
+#include <furi.h>
+
+typedef enum {
+    DesktopMainEventOpenMenu,
+    DesktopMainEventOpenLockMenu,
+    DesktopMainEventOpenDebug,
+    DesktopMainEventUnlocked,
+    DesktopMainEventOpenArchive,
+    DesktopMainEventOpenFavorite,
+} DesktopMainEvent;
+
+typedef struct DesktopMainView DesktopMainView;
+
+typedef void (*DesktopMainViewCallback)(DesktopMainEvent event, void* context);
+
+struct DesktopMainView {
+    View* view;
+    DesktopMainViewCallback callback;
+    void* context;
+};
+
+typedef struct {
+    IconAnimation* animation;
+    uint8_t scene_num;
+    uint8_t hint_timeout;
+    bool unlocked;
+} DesktopMainViewModel;
+
+void desktop_main_set_callback(
+    DesktopMainView* main_view,
+    DesktopMainViewCallback callback,
+    void* context);
+
+View* desktop_main_get_view(DesktopMainView* main_view);
+
+DesktopMainView* desktop_main_alloc();
+void desktop_main_free(DesktopMainView* main_view);

+ 30 - 413
applications/dolphin/dolphin.c

@@ -1,361 +1,39 @@
 #include "dolphin_i.h"
-#include <stdlib.h>
-#include "applications.h"
+#include <furi.h>
 
-const Icon* idle_scenes[] = {&A_Wink_128x64, &A_WatchingTV_128x64};
-
-static void dolphin_switch_to_app(Dolphin* dolphin, const FlipperApplication* flipper_app) {
+bool dolphin_load(Dolphin* dolphin) {
     furi_assert(dolphin);
-    furi_assert(flipper_app);
-    furi_assert(flipper_app->app);
-    furi_assert(flipper_app->name);
-
-    if(furi_thread_get_state(dolphin->scene_thread) != FuriThreadStateStopped) {
-        FURI_LOG_E("Dolphin", "Thread is already running");
-        return;
-    }
-
-    furi_thread_set_name(dolphin->scene_thread, flipper_app->name);
-    furi_thread_set_stack_size(dolphin->scene_thread, flipper_app->stack_size);
-    furi_thread_set_callback(dolphin->scene_thread, flipper_app->app);
-
-    furi_thread_start(dolphin->scene_thread);
-}
-
-// temporary main screen animation managment
-void dolphin_scene_handler_set_scene(Dolphin* dolphin, const Icon* icon_data) {
-    with_view_model(
-        dolphin->idle_view_main, (DolphinViewMainModel * model) {
-            if(model->animation) icon_animation_free(model->animation);
-            model->animation = icon_animation_alloc(icon_data);
-            icon_animation_start(model->animation);
-            return true;
-        });
+    return dolphin_state_load(dolphin->state);
 }
 
-void dolphin_scene_handler_switch_scene(Dolphin* dolphin) {
-    with_view_model(
-        dolphin->idle_view_main, (DolphinViewMainModel * model) {
-            if(icon_animation_is_last_frame(model->animation)) {
-                if(model->animation) icon_animation_free(model->animation);
-                model->animation = icon_animation_alloc(idle_scenes[model->scene_num]);
-                icon_animation_start(model->animation);
-                model->scene_num = random() % COUNT_OF(idle_scenes);
-            }
-            return true;
-        });
-}
-
-bool dolphin_view_first_start_input(InputEvent* event, void* context) {
-    furi_assert(event);
-    furi_assert(context);
-    Dolphin* dolphin = context;
-    if(event->type == InputTypeShort) {
-        DolphinViewFirstStartModel* model = view_get_model(dolphin->idle_view_first_start);
-        if(event->key == InputKeyLeft) {
-            if(model->page > 0) model->page--;
-        } else if(event->key == InputKeyRight) {
-            uint32_t page = ++model->page;
-            if(page > 8) {
-                dolphin_save(dolphin);
-                view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain);
-            }
-        }
-        view_commit_model(dolphin->idle_view_first_start, true);
-    }
-    // All evennts cosumed
-    return true;
-}
-
-void dolphin_lock_handler(InputEvent* event, Dolphin* dolphin) {
-    furi_assert(event);
+void dolphin_save(Dolphin* dolphin) {
     furi_assert(dolphin);
-
-    with_view_model(
-        dolphin->idle_view_main, (DolphinViewMainModel * model) {
-            model->hint_timeout = HINT_TIMEOUT_L;
-            return true;
-        });
-
-    if(event->key == InputKeyBack && event->type == InputTypeShort) {
-        uint32_t press_time = HAL_GetTick();
-
-        // check if pressed sequentially
-        if(press_time - dolphin->lock_lastpress > UNLOCK_RST_TIMEOUT) {
-            dolphin->lock_lastpress = press_time;
-            dolphin->lock_count = 0;
-        } else if(press_time - dolphin->lock_lastpress < UNLOCK_RST_TIMEOUT) {
-            dolphin->lock_lastpress = press_time;
-            dolphin->lock_count++;
-        }
-
-        if(dolphin->lock_count == 2) {
-            dolphin->locked = false;
-            dolphin->lock_count = 0;
-
-            with_view_model(
-                dolphin->view_lockmenu, (DolphinViewLockMenuModel * model) {
-                    model->locked = false;
-                    model->door_left_x = -57; // move doors to default pos
-                    model->door_right_x = 115;
-                    return true;
-                });
-
-            with_view_model(
-                dolphin->idle_view_main, (DolphinViewMainModel * model) {
-                    model->hint_timeout = HINT_TIMEOUT_L; // "unlocked" hint timeout
-                    model->locked = false;
-                    return true;
-                });
-
-            view_port_enabled_set(dolphin->lock_viewport, false);
-        }
-    }
-}
-
-bool dolphin_view_idle_main_input(InputEvent* event, void* context) {
-    furi_assert(event);
-    furi_assert(context);
-    Dolphin* dolphin = context;
-    // unlocked
-    if(!dolphin->locked) {
-        if(event->key == InputKeyOk && event->type == InputTypeShort) {
-            with_value_mutex(
-                dolphin->menu_vm, (Menu * menu) { menu_ok(menu); });
-        } else if(event->key == InputKeyUp && event->type == InputTypeShort) {
-            osTimerStart(dolphin->timeout_timer, 64);
-            view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewLockMenu);
-        } else if(event->key == InputKeyDown && event->type == InputTypeShort) {
-            dolphin_switch_to_app(dolphin, &FLIPPER_ARCHIVE);
-        } else if(event->key == InputKeyDown && event->type == InputTypeLong) {
-            view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewStats);
-        } else if(event->key == InputKeyBack && event->type == InputTypeShort) {
-            view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain);
-        }
-
-        with_view_model(
-            dolphin->idle_view_main, (DolphinViewMainModel * model) {
-                model->hint_timeout = 0; // clear hint timeout
-                return true;
-            });
-
-    } else {
-        // locked
-
-        dolphin_lock_handler(event, dolphin);
-        dolphin_scene_handler_switch_scene(dolphin);
-    }
-    // All events consumed
-    return true;
-}
-
-void lock_menu_refresh_handler(void* p) {
-    osMessageQueueId_t event_queue = p;
     DolphinEvent event;
-    event.type = DolphinEventTypeTick;
-    // Some tick events may lost and we don't care.
-    osMessageQueuePut(event_queue, &event, 0, 0);
-}
-
-static void lock_menu_callback(void* context, uint8_t index) {
-    furi_assert(context);
-    Dolphin* dolphin = context;
-    switch(index) {
-    // lock
-    case 0:
-        dolphin->locked = true;
-
-        with_view_model(
-            dolphin->view_lockmenu, (DolphinViewLockMenuModel * model) {
-                model->locked = true;
-                model->exit_timeout = HINT_TIMEOUT_H;
-                return true;
-            });
-
-        with_view_model(
-            dolphin->idle_view_main, (DolphinViewMainModel * model) {
-                model->locked = true;
-                return true;
-            });
-        break;
-
-    default:
-        // wip message
-        with_view_model(
-            dolphin->view_lockmenu, (DolphinViewLockMenuModel * model) {
-                model->hint_timeout = HINT_TIMEOUT_H;
-                return true;
-            });
-        break;
-    }
+    event.type = DolphinEventTypeSave;
+    furi_check(osMessageQueuePut(dolphin->event_queue, &event, 0, osWaitForever) == osOK);
 }
 
-static void lock_icon_callback(Canvas* canvas, void* context) {
-    furi_assert(context);
-    Dolphin* dolphin = context;
-    canvas_draw_icon_animation(canvas, 0, 0, dolphin->lock_icon);
+void dolphin_deed(Dolphin* dolphin, DolphinDeed deed) {
+    furi_assert(dolphin);
+    DolphinEvent event;
+    event.type = DolphinEventTypeDeed;
+    event.deed = deed;
+    furi_check(osMessageQueuePut(dolphin->event_queue, &event, 0, osWaitForever) == osOK);
 }
 
-bool dolphin_view_lockmenu_input(InputEvent* event, void* context) {
-    furi_assert(event);
-    furi_assert(context);
-    Dolphin* dolphin = context;
-
-    if(event->type != InputTypeShort) return false;
-
-    DolphinViewLockMenuModel* model = view_get_model(dolphin->view_lockmenu);
-
-    model->hint_timeout = 0; // clear hint timeout
-
-    if(event->key == InputKeyUp) {
-        model->idx = CLAMP(model->idx - 1, 2, 0);
-    } else if(event->key == InputKeyDown) {
-        model->idx = CLAMP(model->idx + 1, 2, 0);
-    } else if(event->key == InputKeyOk) {
-        lock_menu_callback(context, model->idx);
-    } else if(event->key == InputKeyBack) {
-        model->idx = 0;
-        view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain);
-
-        if(random() % 100 > 50)
-            dolphin_scene_handler_set_scene(
-                dolphin, idle_scenes[random() % COUNT_OF(idle_scenes)]);
-    }
-
-    view_commit_model(dolphin->view_lockmenu, true);
+DolphinDeedWeight dolphin_stats(Dolphin* dolphin) {
+    DolphinDeedWeight stats;
+    stats.butthurt = dolphin_state_get_butthurt(dolphin->state);
+    stats.icounter = dolphin_state_get_icounter(dolphin->state);
 
-    return true;
-}
-
-bool dolphin_view_idle_down_input(InputEvent* event, void* context) {
-    furi_assert(event);
-    furi_assert(context);
-    Dolphin* dolphin = context;
-    DolphinViewStatsScreens current;
-
-    if(event->type != InputTypeShort) return false;
-
-    DolphinViewStatsModel* model = view_get_model(dolphin->idle_view_dolphin_stats);
-
-    current = model->screen;
-
-    if(event->key == InputKeyDown) {
-        model->screen = (model->screen + 1) % DolphinViewStatsTotalCount;
-    } else if(event->key == InputKeyUp) {
-        model->screen =
-            ((model->screen - 1) + DolphinViewStatsTotalCount) % DolphinViewStatsTotalCount;
-    }
-
-    view_commit_model(dolphin->idle_view_dolphin_stats, true);
-
-    if(current == DolphinViewStatsMeta) {
-        if(event->key == InputKeyLeft) {
-            dolphin_deed(dolphin, DolphinDeedWrong);
-        } else if(event->key == InputKeyRight) {
-            dolphin_deed(dolphin, DolphinDeedIButtonRead);
-        } else if(event->key == InputKeyOk) {
-            dolphin_save(dolphin);
-        } else {
-            return false;
-        }
-    }
-
-    if(event->key == InputKeyBack) {
-        view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain);
-    }
-
-    return true;
+    return stats;
 }
 
 Dolphin* dolphin_alloc() {
     Dolphin* dolphin = furi_alloc(sizeof(Dolphin));
-    // Message queue
-    dolphin->event_queue = osMessageQueueNew(8, sizeof(DolphinEvent), NULL);
-    furi_check(dolphin->event_queue);
-    // State
-    dolphin->state = dolphin_state_alloc();
-    // Menu
-    dolphin->menu_vm = furi_record_open("menu");
-    // Scene thread
-    dolphin->scene_thread = furi_thread_alloc();
-    // GUI
-    dolphin->gui = furi_record_open("gui");
-    // Dispatcher
-    dolphin->idle_view_dispatcher = view_dispatcher_alloc();
-
-    // First start View
-    dolphin->idle_view_first_start = view_alloc();
-    view_allocate_model(
-        dolphin->idle_view_first_start, ViewModelTypeLockFree, sizeof(DolphinViewFirstStartModel));
-    view_set_context(dolphin->idle_view_first_start, dolphin);
-    view_set_draw_callback(dolphin->idle_view_first_start, dolphin_view_first_start_draw);
-    view_set_input_callback(dolphin->idle_view_first_start, dolphin_view_first_start_input);
-    view_dispatcher_add_view(
-        dolphin->idle_view_dispatcher, DolphinViewFirstStart, dolphin->idle_view_first_start);
-
-    // Main Idle View
-    dolphin->idle_view_main = view_alloc();
-    view_set_context(dolphin->idle_view_main, dolphin);
-    view_allocate_model(
-        dolphin->idle_view_main, ViewModelTypeLockFree, sizeof(DolphinViewMainModel));
-
-    view_set_draw_callback(dolphin->idle_view_main, dolphin_view_idle_main_draw);
-    view_set_input_callback(dolphin->idle_view_main, dolphin_view_idle_main_input);
-    view_dispatcher_add_view(
-        dolphin->idle_view_dispatcher, DolphinViewIdleMain, dolphin->idle_view_main);
-
-    // Lock Menu View
-    dolphin->view_lockmenu = view_alloc();
-    view_set_context(dolphin->view_lockmenu, dolphin);
-    view_allocate_model(
-        dolphin->view_lockmenu, ViewModelTypeLockFree, sizeof(DolphinViewLockMenuModel));
-    view_set_draw_callback(dolphin->view_lockmenu, dolphin_view_lockmenu_draw);
-    view_set_input_callback(dolphin->view_lockmenu, dolphin_view_lockmenu_input);
-    view_set_previous_callback(dolphin->view_lockmenu, dolphin_view_idle_back);
-    view_dispatcher_add_view(
-        dolphin->idle_view_dispatcher, DolphinViewLockMenu, dolphin->view_lockmenu);
 
-    // default doors xpos
-    with_view_model(
-        dolphin->view_lockmenu, (DolphinViewLockMenuModel * model) {
-            model->door_left_x = -57; // defaults
-            model->door_right_x = 115; // defaults
-            return true;
-        });
-
-    dolphin->timeout_timer =
-        osTimerNew(lock_menu_refresh_handler, osTimerPeriodic, dolphin->event_queue, NULL);
-
-    // Stats Idle View
-    dolphin->idle_view_dolphin_stats = view_alloc();
-    view_set_context(dolphin->idle_view_dolphin_stats, dolphin);
-    view_allocate_model(
-        dolphin->idle_view_dolphin_stats, ViewModelTypeLockFree, sizeof(DolphinViewStatsModel));
-    view_set_draw_callback(dolphin->idle_view_dolphin_stats, dolphin_view_idle_down_draw);
-    view_set_input_callback(dolphin->idle_view_dolphin_stats, dolphin_view_idle_down_input);
-    view_set_previous_callback(dolphin->idle_view_dolphin_stats, dolphin_view_idle_back);
-    view_dispatcher_add_view(
-        dolphin->idle_view_dispatcher, DolphinViewStats, dolphin->idle_view_dolphin_stats);
-    // HW Mismatch
-    dolphin->view_hw_mismatch = view_alloc();
-    view_set_draw_callback(dolphin->view_hw_mismatch, dolphin_view_hw_mismatch_draw);
-    view_set_previous_callback(dolphin->view_hw_mismatch, dolphin_view_idle_back);
-    view_dispatcher_add_view(
-        dolphin->idle_view_dispatcher, DolphinViewHwMismatch, dolphin->view_hw_mismatch);
-
-    // Lock icon
-    dolphin->lock_icon = icon_animation_alloc(&I_Lock_8x8);
-    dolphin->lock_viewport = view_port_alloc();
-    view_port_set_width(dolphin->lock_viewport, icon_animation_get_width(dolphin->lock_icon));
-    view_port_draw_callback_set(dolphin->lock_viewport, lock_icon_callback, dolphin);
-    view_port_enabled_set(dolphin->lock_viewport, false);
-
-    // Main screen animation
-    dolphin_scene_handler_set_scene(dolphin, idle_scenes[random() % COUNT_OF(idle_scenes)]);
-
-    view_dispatcher_attach_to_gui(
-        dolphin->idle_view_dispatcher, dolphin->gui, ViewDispatcherTypeWindow);
-    gui_add_view_port(dolphin->gui, dolphin->lock_viewport, GuiLayerStatusBarLeft);
+    dolphin->state = dolphin_state_alloc();
+    dolphin->event_queue = osMessageQueueNew(8, sizeof(DolphinEvent), NULL);
 
     return dolphin;
 }
@@ -363,94 +41,33 @@ Dolphin* dolphin_alloc() {
 void dolphin_free(Dolphin* dolphin) {
     furi_assert(dolphin);
 
-    gui_remove_view_port(dolphin->gui, dolphin->lock_viewport);
-    view_port_free(dolphin->lock_viewport);
-    icon_animation_free(dolphin->lock_icon);
-
-    osTimerDelete(dolphin->timeout_timer);
-
-    view_dispatcher_free(dolphin->idle_view_dispatcher);
-
-    furi_record_close("gui");
-    dolphin->gui = NULL;
-
-    furi_thread_free(dolphin->scene_thread);
-
-    furi_record_close("menu");
-    dolphin->menu_vm = NULL;
-
     dolphin_state_free(dolphin->state);
-
     osMessageQueueDelete(dolphin->event_queue);
 
     free(dolphin);
 }
 
-void dolphin_save(Dolphin* dolphin) {
-    furi_assert(dolphin);
-    DolphinEvent event;
-    event.type = DolphinEventTypeSave;
-    furi_check(osMessageQueuePut(dolphin->event_queue, &event, 0, osWaitForever) == osOK);
-}
-
-void dolphin_deed(Dolphin* dolphin, DolphinDeed deed) {
-    furi_assert(dolphin);
-    DolphinEvent event;
-    event.type = DolphinEventTypeDeed;
-    event.deed = deed;
-    furi_check(osMessageQueuePut(dolphin->event_queue, &event, 0, osWaitForever) == osOK);
-}
-
-int32_t dolphin_srv() {
+int32_t dolphin_srv(void* p) {
     Dolphin* dolphin = dolphin_alloc();
-
-    if(dolphin_state_load(dolphin->state)) {
-        view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain);
-    } else {
-        view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewFirstStart);
-    }
-
-    with_view_model(
-        dolphin->idle_view_dolphin_stats, (DolphinViewStatsModel * model) {
-            model->icounter = dolphin_state_get_icounter(dolphin->state);
-            model->butthurt = dolphin_state_get_butthurt(dolphin->state);
-            return true;
-        });
-
     furi_record_create("dolphin", dolphin);
 
-    if(!furi_hal_version_do_i_belong_here()) {
-        view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewHwMismatch);
-    }
-
     DolphinEvent event;
     while(1) {
         furi_check(osMessageQueueGet(dolphin->event_queue, &event, NULL, osWaitForever) == osOK);
-
-        DolphinViewLockMenuModel* lock_model = view_get_model(dolphin->view_lockmenu);
-
-        if(lock_model->locked && lock_model->exit_timeout == 0 &&
-           osTimerIsRunning(dolphin->timeout_timer)) {
-            osTimerStop(dolphin->timeout_timer);
-            osDelay(1); // smol enterprise delay
-            view_dispatcher_switch_to_view(dolphin->idle_view_dispatcher, DolphinViewIdleMain);
-        }
-
-        if(event.type == DolphinEventTypeTick) {
-            view_commit_model(dolphin->view_lockmenu, true);
-
-        } else if(event.type == DolphinEventTypeDeed) {
+        switch(event.type) {
+        case DolphinEventTypeDeed:
             dolphin_state_on_deed(dolphin->state, event.deed);
-            with_view_model(
-                dolphin->idle_view_dolphin_stats, (DolphinViewStatsModel * model) {
-                    model->icounter = dolphin_state_get_icounter(dolphin->state);
-                    model->butthurt = dolphin_state_get_butthurt(dolphin->state);
-                    return true;
-                });
-        } else if(event.type == DolphinEventTypeSave) {
+            break;
+
+        case DolphinEventTypeSave:
             dolphin_state_save(dolphin->state);
+            break;
+
+        default:
+            break;
         }
     }
+
     dolphin_free(dolphin);
     return 0;
 }

+ 20 - 1
applications/dolphin/dolphin.h

@@ -1,11 +1,30 @@
 #pragma once
 
-#include "dolphin_deed.h"
+#include "helpers/dolphin_deed.h"
 
 typedef struct Dolphin Dolphin;
 
+/* Load Dolphin state
+ * Thread safe
+ */
+
+bool dolphin_load(Dolphin* dolphin);
+
 /* Deed complete notification. Call it on deed completion.
  * See dolphin_deed.h for available deeds. In futures it will become part of assets.
  * Thread safe
  */
+
 void dolphin_deed(Dolphin* dolphin, DolphinDeed deed);
+
+/* Save Dolphin state (write to permanent memory)
+ * Thread safe
+ */
+
+void dolphin_save(Dolphin* dolphin);
+
+/* Retrieve dolphin's icounter and butthurt values
+ * Thread safe
+ */
+
+DolphinDeedWeight dolphin_stats(Dolphin* dolphin);

+ 4 - 40
applications/dolphin/dolphin_i.h

@@ -1,22 +1,10 @@
 #pragma once
 
-#include "dolphin.h"
-#include "dolphin_state.h"
-#include "dolphin_views.h"
-
 #include <furi.h>
 #include <furi-hal.h>
-#include <gui/gui.h>
-#include <gui/view_dispatcher.h>
-#include <gui/canvas.h>
-#include <menu/menu.h>
 
-#include <assets_icons.h>
-#include <stdint.h>
-
-#define UNLOCK_RST_TIMEOUT 500 // keypress counter reset timeout (ms)
-#define HINT_TIMEOUT_L 3 // low refresh rate timeout (app ticks)
-#define HINT_TIMEOUT_H 40 // high refresh rate timeout (app ticks)
+#include "dolphin.h"
+#include "helpers/dolphin_state.h"
 
 typedef enum {
     DolphinEventTypeDeed,
@@ -32,36 +20,12 @@ typedef struct {
 } DolphinEvent;
 
 struct Dolphin {
-    // Internal message queue
-    osMessageQueueId_t event_queue;
     // State
     DolphinState* state;
-    // Menu
-    ValueMutex* menu_vm;
-    // Scene
-    FuriThread* scene_thread;
-    // GUI
-    Gui* gui;
-    ViewDispatcher* idle_view_dispatcher;
-    View* idle_view_first_start;
-    View* idle_view_main;
-    View* idle_view_dolphin_stats;
-    View* view_hw_mismatch;
-    View* view_lockmenu;
-    ViewPort* lock_viewport;
-    IconAnimation* lock_icon;
-
-    bool locked;
-    uint8_t lock_count;
-    uint32_t lock_lastpress;
-    osTimerId_t timeout_timer;
+    // Queue
+    osMessageQueueId_t event_queue;
 };
 
 Dolphin* dolphin_alloc();
 
 void dolphin_free(Dolphin* dolphin);
-
-/* Save Dolphin state (write to permanent memory)
- * Thread safe
- */
-void dolphin_save(Dolphin* dolphin);

+ 0 - 197
applications/dolphin/dolphin_views.c

@@ -1,197 +0,0 @@
-#include "dolphin_views.h"
-#include <gui/view.h>
-#include <gui/gui.h>
-#include <gui/elements.h>
-#include <furi-hal.h>
-#include <furi-hal-version.h>
-
-static char* Lockmenu_Items[3] = {"Lock", "Set PIN", "DUMB mode"};
-
-void dolphin_view_first_start_draw(Canvas* canvas, void* model) {
-    DolphinViewFirstStartModel* m = model;
-    canvas_clear(canvas);
-    canvas_set_color(canvas, ColorBlack);
-    canvas_set_font(canvas, FontSecondary);
-    uint8_t width = canvas_width(canvas);
-    uint8_t height = canvas_height(canvas);
-    const char* my_name = furi_hal_version_get_name_ptr();
-    if(m->page == 0) {
-        canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart0_70x53);
-        elements_multiline_text_framed(canvas, 75, 20, "Hey m8,\npress > to\ncontinue");
-    } else if(m->page == 1) {
-        canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart1_59x53);
-        elements_multiline_text_framed(canvas, 64, 20, "First Of All,\n...      >");
-    } else if(m->page == 2) {
-        canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart2_59x51);
-        elements_multiline_text_framed(canvas, 64, 20, "Thank you\nfor your\nsupport! >");
-    } else if(m->page == 3) {
-        canvas_draw_icon(canvas, width - 57, height - 48, &I_DolphinFirstStart3_57x48);
-        elements_multiline_text_framed(canvas, 0, 20, "Kickstarter\ncampaign\nwas INSANE! >");
-    } else if(m->page == 4) {
-        canvas_draw_icon(canvas, width - 67, height - 50, &I_DolphinFirstStart4_67x53);
-        elements_multiline_text_framed(canvas, 0, 17, "Now\nallow me\nto introduce\nmyself >");
-    } else if(m->page == 5) {
-        char buf[64];
-        snprintf(
-            buf,
-            64,
-            "%s %s%s",
-            "I am",
-            my_name ? my_name : "Unknown",
-            ",\ncyberdolphin\nliving in your\npocket >");
-        canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart5_54x49);
-        elements_multiline_text_framed(canvas, 60, 17, buf);
-    } else if(m->page == 6) {
-        canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart6_58x54);
-        elements_multiline_text_framed(
-            canvas, 63, 17, "I can grow\nsmart'n'cool\nif you use me\noften >");
-    } else if(m->page == 7) {
-        canvas_draw_icon(canvas, width - 61, height - 48, &I_DolphinFirstStart7_61x51);
-        elements_multiline_text_framed(
-            canvas, 0, 17, "As long as\nyou read, write\nand emulate >");
-    } else if(m->page == 8) {
-        canvas_draw_icon(canvas, width - 56, height - 48, &I_DolphinFirstStart8_56x51);
-        elements_multiline_text_framed(
-            canvas, 0, 17, "You can check\nmy level and\nmood in the\nPassport menu");
-    }
-}
-
-void dolphin_view_idle_main_draw(Canvas* canvas, void* model) {
-    canvas_clear(canvas);
-    DolphinViewMainModel* m = model;
-    if(m->animation) {
-        canvas_draw_icon_animation(canvas, 0, -3, m->animation);
-    }
-
-    if(m->hint_timeout) {
-        m->hint_timeout--;
-        if(m->locked) {
-            canvas_draw_icon(canvas, 13, 5, &I_LockPopup_100x49);
-            elements_multiline_text(canvas, 65, 20, "To unlock\npress:");
-        } else {
-            canvas_set_font(canvas, FontPrimary);
-            elements_multiline_text_framed(canvas, 42, 30, "Unlocked");
-        }
-    }
-}
-
-void dolphin_view_lockmenu_draw(Canvas* canvas, void* model) {
-    DolphinViewLockMenuModel* m = model;
-    canvas_clear(canvas);
-    canvas_set_color(canvas, ColorBlack);
-    canvas_draw_icon(canvas, m->door_left_x, 0, &I_DoorLeft_70x55);
-    canvas_draw_icon(canvas, m->door_right_x, 0, &I_DoorRight_70x55);
-    canvas_set_font(canvas, FontSecondary);
-
-    if(m->locked) {
-        m->exit_timeout--;
-
-        m->door_left_x = CLAMP(m->door_left_x + 5, 0, -57);
-        m->door_right_x = CLAMP(m->door_right_x - 5, 115, 60);
-
-        if(m->door_left_x > -10) {
-            canvas_set_font(canvas, FontPrimary);
-            elements_multiline_text_framed(canvas, 42, 30, "Locked");
-        }
-
-    } else {
-        if(m->door_left_x == -57) {
-            for(uint8_t i = 0; i < 3; ++i) {
-                canvas_draw_str_aligned(
-                    canvas,
-                    64,
-                    13 + (i * 17),
-                    AlignCenter,
-                    AlignCenter,
-                    (m->hint_timeout && m->idx == i) ? "Not implemented" : Lockmenu_Items[i]);
-                if(m->idx == i) elements_frame(canvas, 15, 5 + (i * 17), 98, 15);
-            }
-        }
-
-        if(m->hint_timeout) {
-            m->hint_timeout--;
-        }
-    }
-}
-
-void dolphin_view_idle_down_draw(Canvas* canvas, void* model) {
-    DolphinViewStatsModel* m = model;
-    const Version* ver;
-    char buffer[64];
-
-    static const char* headers[] = {"FW Version info:", "Boot Version info:", "Dolphin info:"};
-
-    canvas_set_color(canvas, ColorBlack);
-    canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str(canvas, 2, 13, headers[m->screen]);
-    canvas_set_font(canvas, FontSecondary);
-
-    if(m->screen != DolphinViewStatsMeta) {
-        // Hardware version
-        const char* my_name = furi_hal_version_get_name_ptr();
-        snprintf(
-            buffer,
-            sizeof(buffer),
-            "HW: %d.F%dB%dC%d %s",
-            furi_hal_version_get_hw_version(),
-            furi_hal_version_get_hw_target(),
-            furi_hal_version_get_hw_body(),
-            furi_hal_version_get_hw_connect(),
-            my_name ? my_name : "Unknown");
-        canvas_draw_str(canvas, 5, 23, buffer);
-
-        ver = m->screen == DolphinViewStatsBoot ? furi_hal_version_get_boot_version() :
-                                                  furi_hal_version_get_firmware_version();
-
-        if(!ver) {
-            canvas_draw_str(canvas, 5, 33, "No info");
-            return;
-        }
-
-        snprintf(
-            buffer,
-            sizeof(buffer),
-            "%s [%s]",
-            version_get_version(ver),
-            version_get_builddate(ver));
-        canvas_draw_str(canvas, 5, 33, buffer);
-
-        snprintf(
-            buffer,
-            sizeof(buffer),
-            "%s [%s]",
-            version_get_githash(ver),
-            version_get_gitbranchnum(ver));
-        canvas_draw_str(canvas, 5, 43, buffer);
-
-        snprintf(
-            buffer, sizeof(buffer), "[%s] %s", version_get_target(ver), version_get_gitbranch(ver));
-        canvas_draw_str(canvas, 5, 53, buffer);
-
-    } else {
-        char buffer[64];
-        canvas_set_font(canvas, FontSecondary);
-        snprintf(buffer, 64, "Icounter: %ld", m->icounter);
-        canvas_draw_str(canvas, 5, 30, buffer);
-        snprintf(buffer, 64, "Butthurt: %ld", m->butthurt);
-        canvas_draw_str(canvas, 5, 40, buffer);
-        canvas_draw_str(canvas, 0, 53, "[< >] icounter value   [ok] save");
-    }
-}
-
-void dolphin_view_hw_mismatch_draw(Canvas* canvas, void* model) {
-    canvas_clear(canvas);
-    canvas_set_color(canvas, ColorBlack);
-    canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str(canvas, 2, 15, "!!!! HW Mismatch !!!!");
-
-    char buffer[64];
-    canvas_set_font(canvas, FontSecondary);
-    snprintf(buffer, 64, "HW target: F%d", furi_hal_version_get_hw_target());
-    canvas_draw_str(canvas, 5, 27, buffer);
-    canvas_draw_str(canvas, 5, 38, "FW target: " TARGET);
-}
-
-uint32_t dolphin_view_idle_back(void* context) {
-    return DolphinViewIdleMain;
-}

+ 0 - 67
applications/dolphin/dolphin_views.h

@@ -1,67 +0,0 @@
-#pragma once
-
-#include <stdint.h>
-#include <stdbool.h>
-#include <gui/canvas.h>
-#include <input/input.h>
-#include <furi.h>
-
-// Idle screen
-typedef enum {
-    DolphinViewIdleMain,
-    DolphinViewFirstStart,
-    DolphinViewStats,
-    DolphinViewHwMismatch,
-    DolphinViewLockMenu,
-} DolphinViewIdle;
-
-// Debug info
-typedef enum {
-    DolphinViewStatsFw,
-    DolphinViewStatsBoot,
-    DolphinViewStatsMeta,
-    DolphinViewStatsTotalCount,
-} DolphinViewStatsScreens;
-
-typedef struct {
-    uint32_t page;
-} DolphinViewFirstStartModel;
-
-void dolphin_view_first_start_draw(Canvas* canvas, void* model);
-bool dolphin_view_first_start_input(InputEvent* event, void* context);
-
-typedef struct {
-    uint32_t icounter;
-    uint32_t butthurt;
-    DolphinViewStatsScreens screen;
-} DolphinViewStatsModel;
-
-typedef struct {
-    uint8_t idx;
-    int8_t door_left_x;
-    int8_t door_right_x;
-    uint8_t exit_timeout;
-    uint8_t hint_timeout;
-
-    bool locked;
-} DolphinViewLockMenuModel;
-
-typedef struct {
-    IconAnimation* animation;
-    uint8_t scene_num;
-    uint8_t hint_timeout;
-    bool locked;
-} DolphinViewMainModel;
-
-void dolphin_view_idle_main_draw(Canvas* canvas, void* model);
-bool dolphin_view_idle_main_input(InputEvent* event, void* context);
-
-void dolphin_view_idle_up_draw(Canvas* canvas, void* model);
-
-void dolphin_view_lockmenu_draw(Canvas* canvas, void* model);
-
-void dolphin_view_idle_down_draw(Canvas* canvas, void* model);
-
-void dolphin_view_hw_mismatch_draw(Canvas* canvas, void* model);
-
-uint32_t dolphin_view_idle_back(void* context);

+ 0 - 0
applications/dolphin/dolphin_deed.c → applications/dolphin/helpers/dolphin_deed.c


+ 1 - 0
applications/dolphin/dolphin_deed.h → applications/dolphin/helpers/dolphin_deed.h

@@ -16,6 +16,7 @@ typedef enum {
 
 typedef struct {
     int32_t icounter; // how many icounter get by Deed
+    int32_t butthurt; // how many icounter get by Deed
     uint32_t limit_value; // how many deeds in limit interval
     uint32_t limit_interval; // interval, in minutes
 } DolphinDeedWeight;

+ 0 - 0
applications/dolphin/dolphin_state.c → applications/dolphin/helpers/dolphin_state.c


+ 0 - 0
applications/dolphin/dolphin_state.h → applications/dolphin/helpers/dolphin_state.h