Просмотр исходного кода

[FL-2183] [FL-2209] Dolphin Deeds, Level up, assets generation, refactoring (#965)

* Desktop: cleanup headers
* Get loader pubsub via record
* [FL-2183] Dolphin refactoring 2022.01
* Restruct animations assets structure
* Rename assets
* Cleanup headers
* Update Recording animation
* Add BadBattery animation
* Provide loader's pubsub via record
* Fix load/unload animations
* Scripts: add flipper format support, initial dolphin packager rework. Assets: internal and external dolphin.
* Sync internal meta.txt and manifest.txt
* Reorder, rename dolphin assets
* Split essential generated assets
* Add ReadMe for dolphin assets
* Separate essential blocking animations
* Scripts: full dolphin validation before packaging
* Assets, Scripts: dolphin external resources packer
* Github: update codeowners
* Scripts: proper slots handling in dolphin animation meta
* Scripts: correct frames enumeration and fix compiled assets.
* [FL-2209] Add Dolphin Deeds points and many more
* Remove excess frame_rate
* Change dolphin assets directory
* Scripts: add internal resource support to dolphin compiler
* Scripts: add internal assets generation, renaming
* Scripts: correct assert, renaming
* Code cleanup, documentation, fixes
* Update Levelup animations
* Rename essential -> blocking
* Fix Unlocked hint
* Scripts: rewrite Templite compiller, replace regexps with token parser, split block types into code and variable blocks. Update dolphin templates.
* Documentation: add key combos description and use information
* Scripts: cleanup templit, more debug info and add dev comment

Co-authored-by: あく <alleteam@gmail.com>
Albert Kharisov 4 лет назад
Родитель
Сommit
84410c83b5
100 измененных файлов с 1945 добавлено и 1340 удалено
  1. 2 2
      .github/CODEOWNERS
  2. 2 0
      applications/bad_usb/bad_usb_script.c
  3. 185 66
      applications/desktop/animations/animation_manager.c
  4. 12 11
      applications/desktop/animations/animation_manager.h
  5. 180 144
      applications/desktop/animations/animation_storage.c
  6. 3 12
      applications/desktop/animations/animation_storage.h
  7. 1 187
      applications/desktop/animations/animation_storage_i.h
  8. 41 46
      applications/desktop/animations/views/bubble_animation_view.c
  9. 130 0
      applications/desktop/animations/views/one_shot_animation_view.c
  10. 17 0
      applications/desktop/animations/views/one_shot_animation_view.h
  11. 23 42
      applications/desktop/desktop.c
  12. 10 23
      applications/desktop/desktop_i.h
  13. 2 0
      applications/desktop/desktop_settings/desktop_settings_app.c
  14. 0 4
      applications/desktop/desktop_settings/desktop_settings_app.h
  15. 1 1
      applications/desktop/desktop_settings/scenes/desktop_settings_scene_favorite.c
  16. 1 1
      applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_input.c
  17. 1 0
      applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_menu.c
  18. 1 0
      applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c
  19. 0 1
      applications/desktop/scenes/desktop_scene_config.h
  20. 7 4
      applications/desktop/scenes/desktop_scene_debug.c
  21. 2 0
      applications/desktop/scenes/desktop_scene_fault.c
  22. 3 0
      applications/desktop/scenes/desktop_scene_first_start.c
  23. 15 9
      applications/desktop/scenes/desktop_scene_hw_mismatch.c
  24. 7 0
      applications/desktop/scenes/desktop_scene_i.h
  25. 9 8
      applications/desktop/scenes/desktop_scene_lock_menu.c
  26. 0 101
      applications/desktop/scenes/desktop_scene_locked.c
  27. 49 25
      applications/desktop/scenes/desktop_scene_main.c
  28. 3 1
      applications/desktop/views/desktop_debug.c
  29. 1 6
      applications/desktop/views/desktop_debug.h
  30. 1 2
      applications/desktop/views/desktop_events.h
  31. 2 0
      applications/desktop/views/desktop_first_start.c
  32. 1 4
      applications/desktop/views/desktop_first_start.h
  33. 2 0
      applications/desktop/views/desktop_lock_menu.c
  34. 0 4
      applications/desktop/views/desktop_lock_menu.h
  35. 156 128
      applications/desktop/views/desktop_locked.c
  36. 6 33
      applications/desktop/views/desktop_locked.h
  37. 14 93
      applications/desktop/views/desktop_main.c
  38. 0 26
      applications/desktop/views/desktop_main.h
  39. 94 28
      applications/dolphin/dolphin.c
  40. 15 0
      applications/dolphin/dolphin.h
  41. 5 3
      applications/dolphin/dolphin_i.h
  42. 59 8
      applications/dolphin/helpers/dolphin_deed.c
  43. 64 14
      applications/dolphin/helpers/dolphin_deed.h
  44. 44 26
      applications/dolphin/helpers/dolphin_state.c
  45. 4 6
      applications/dolphin/helpers/dolphin_state.h
  46. 17 0
      applications/gui/canvas.c
  47. 17 0
      applications/gui/canvas.h
  48. 1 0
      applications/gui/icon_i.h
  49. 0 166
      applications/gui/view_composed.c
  50. 0 12
      applications/gui/view_composed.h
  51. 165 0
      applications/gui/view_stack.c
  52. 53 0
      applications/gui/view_stack.h
  53. 3 1
      applications/ibutton/scene/ibutton_scene_add_value.cpp
  54. 3 1
      applications/ibutton/scene/ibutton_scene_emulate.cpp
  55. 3 1
      applications/ibutton/scene/ibutton_scene_read.cpp
  56. 3 1
      applications/ibutton/scene/ibutton_scene_read_success.cpp
  57. 3 1
      applications/ibutton/scene/ibutton_scene_save_success.cpp
  58. 2 0
      applications/irda/scene/irda_app_scene_learn_done.cpp
  59. 2 0
      applications/irda/scene/irda_app_scene_learn_success.cpp
  60. 2 0
      applications/irda/scene/irda_app_scene_remote.cpp
  61. 2 0
      applications/irda/scene/irda_app_scene_universal_common.cpp
  62. 2 0
      applications/lfrfid/scene/lfrfid_app_scene_emulate.cpp
  63. 3 0
      applications/lfrfid/scene/lfrfid_app_scene_read.cpp
  64. 2 0
      applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp
  65. 5 1
      applications/lfrfid/scene/lfrfid_app_scene_save_success.cpp
  66. 4 3
      applications/loader/loader.c
  67. 1 1
      applications/loader/loader.h
  68. 2 0
      applications/nfc/scenes/nfc_scene_emulate_mifare_ul.c
  69. 2 0
      applications/nfc/scenes/nfc_scene_emulate_uid.c
  70. 2 0
      applications/nfc/scenes/nfc_scene_read_card.c
  71. 2 0
      applications/nfc/scenes/nfc_scene_read_card_success.c
  72. 2 0
      applications/nfc/scenes/nfc_scene_read_emv_app.c
  73. 2 0
      applications/nfc/scenes/nfc_scene_read_emv_app_success.c
  74. 2 0
      applications/nfc/scenes/nfc_scene_read_emv_data.c
  75. 2 0
      applications/nfc/scenes/nfc_scene_read_emv_data_success.c
  76. 2 0
      applications/nfc/scenes/nfc_scene_read_mifare_ul.c
  77. 2 0
      applications/nfc/scenes/nfc_scene_read_mifare_ul_success.c
  78. 2 0
      applications/nfc/scenes/nfc_scene_save_success.c
  79. 2 0
      applications/nfc/scenes/nfc_scene_set_uid.c
  80. 2 0
      applications/subghz/scenes/subghz_scene_frequency_analyzer.c
  81. 3 0
      applications/subghz/scenes/subghz_scene_read_raw.c
  82. 2 0
      applications/subghz/scenes/subghz_scene_receiver_info.c
  83. 3 0
      applications/subghz/scenes/subghz_scene_save_success.c
  84. 2 0
      applications/subghz/scenes/subghz_scene_set_type.c
  85. 2 0
      applications/subghz/scenes/subghz_scene_transmitter.c
  86. 2 0
      applications/u2f/scenes/u2f_scene_main.c
  87. 9 5
      assets/Makefile
  88. 3 2
      assets/assets.mk
  89. 320 0
      assets/compiled/assets_dolphin_blocking.c
  90. 7 0
      assets/compiled/assets_dolphin_blocking.h
  91. 8 0
      assets/compiled/assets_dolphin_internal.c
  92. 7 0
      assets/compiled/assets_dolphin_internal.h
  93. 0 48
      assets/compiled/assets_icons.c
  94. 2 28
      assets/compiled/assets_icons.h
  95. 81 0
      assets/dolphin/ReadMe.md
  96. BIN
      assets/dolphin/animations/recording/frame_0.png
  97. BIN
      assets/dolphin/animations/recording/frame_10.png
  98. BIN
      assets/dolphin/animations/recording/frame_11.png
  99. BIN
      assets/dolphin/animations/recording/frame_3.png
  100. BIN
      assets/dolphin/animations/recording/frame_4.png

+ 2 - 2
.github/CODEOWNERS

@@ -1,4 +1,4 @@
-# Who owns all the code by default
+# Who owns all the fish by default
 
 * @skotopes @DrZlo13
 
@@ -8,7 +8,7 @@ applications/accessor/** @skotopes @DrZlo13
 applications/loader/** @skotopes @DrZlo13 @gornekich
 applications/bt/** @skotopes @DrZlo13
 applications/cli/** @skotopes @DrZlo13
-applications/dolphin/** @skotopes @DrZlo13 @itsyourbedtime
+applications/dolphin/** @skotopes @DrZlo13
 applications/gpio-tester/** @skotopes @DrZlo13
 applications/gui/** @skotopes @DrZlo13
 applications/gui-test/** @skotopes @DrZlo13

+ 2 - 0
applications/bad_usb/bad_usb_script.c

@@ -6,6 +6,7 @@
 #include <furi_hal_usb_hid.h>
 #include <storage/storage.h>
 #include "bad_usb_script.h"
+#include <dolphin/dolphin.h>
 
 #define TAG "BadUSB"
 #define WORKER_TAG TAG "Worker"
@@ -442,6 +443,7 @@ static int32_t bad_usb_worker(void* context) {
             if(flags & WorkerEvtEnd) {
                 break;
             } else if(flags & WorkerEvtToggle) { // Start executing script
+                DOLPHIN_DEED(DolphinDeedBadUsbPlayScript);
                 delay_val = 0;
                 bad_usb->buf_len = 0;
                 bad_usb->st.line_cur = 0;

+ 185 - 66
applications/desktop/animations/animation_manager.c

@@ -1,23 +1,30 @@
-#include "animation_manager.h"
-#include "furi_hal_delay.h"
-#include "portmacro.h"
-#include "views/bubble_animation_view.h"
-#include "animation_storage.h"
-
-#include <cmsis_os2.h>
-#include <dolphin/dolphin.h>
-#include <furi/check.h>
-#include <furi/pubsub.h>
-#include <furi/record.h>
+#include <gui/view_stack.h>
+#include <stdint.h>
+#include <furi.h>
 #include <m-string.h>
+#include <portmacro.h>
+#include <dolphin/dolphin.h>
 #include <power/power_service/power.h>
-#include <stdint.h>
 #include <storage/storage.h>
-#include <dolphin/dolphin_i.h>
-#include <storage/filesystem_api_defines.h>
+#include <assets_icons.h>
+
+#include "views/bubble_animation_view.h"
+#include "views/one_shot_animation_view.h"
+#include "animation_storage.h"
+#include "animation_manager.h"
 
 #define TAG "AnimationManager"
 
+#define HARDCODED_ANIMATION_NAME "L1_Tv_128x47"
+#define NO_SD_ANIMATION_NAME "L1_NoSd_128x49"
+#define BAD_BATTERY_ANIMATION_NAME "L1_BadBattery_128x47"
+
+#define NO_DB_ANIMATION_NAME "L0_NoDb_128x51"
+#define BAD_SD_ANIMATION_NAME "L0_SdBad_128x51"
+#define SD_OK_ANIMATION_NAME "L0_SdOk_128x51"
+#define URL_ANIMATION_NAME "L0_Url_128x51"
+#define NEW_MAIL_ANIMATION_NAME "L0_NewMail_128x51"
+
 typedef enum {
     AnimationManagerStateIdle,
     AnimationManagerStateBlocked,
@@ -29,10 +36,13 @@ struct AnimationManager {
     bool sd_show_url;
     bool sd_shown_no_db;
     bool sd_shown_sd_ok;
+    bool levelup_pending;
+    bool levelup_active;
     AnimationManagerState state;
     FuriPubSubSubscription* pubsub_subscription_storage;
     FuriPubSubSubscription* pubsub_subscription_dolphin;
     BubbleAnimationView* animation_view;
+    OneShotView* one_shot_view;
     osTimerId_t idle_animation_timer;
     StorageAnimation* current_animation;
     AnimationManagerInteractCallback interact_callback;
@@ -41,6 +51,7 @@ struct AnimationManager {
     void* context;
     string_t freezed_animation_name;
     int32_t freezed_animation_time_left;
+    ViewStack* view_stack;
 };
 
 static StorageAnimation*
@@ -50,6 +61,11 @@ static void animation_manager_replace_current_animation(
     StorageAnimation* storage_animation);
 static void animation_manager_start_new_idle(AnimationManager* animation_manager);
 static bool animation_manager_check_blocking(AnimationManager* animation_manager);
+static bool animation_manager_is_valid_idle_animation(
+    const StorageAnimationManifestInfo* info,
+    const DolphinStats* stats);
+static void animation_manager_switch_to_one_shot_view(AnimationManager* animation_manager);
+static void animation_manager_switch_to_animation_view(AnimationManager* animation_manager);
 
 void animation_manager_set_context(AnimationManager* animation_manager, void* context) {
     furi_assert(animation_manager);
@@ -101,12 +117,26 @@ static void animation_manager_interact_callback(void* context) {
     }
 }
 
-/* reaction to animation_manager->interact_callback() */
+/* reaction to animation_manager->check_blocking_callback() */
 void animation_manager_check_blocking_process(AnimationManager* animation_manager) {
     furi_assert(animation_manager);
 
     if(animation_manager->state == AnimationManagerStateIdle) {
-        animation_manager_check_blocking(animation_manager);
+        bool blocked = animation_manager_check_blocking(animation_manager);
+
+        if(!blocked) {
+            Dolphin* dolphin = furi_record_open("dolphin");
+            DolphinStats stats = dolphin_stats(dolphin);
+            furi_record_close("dolphin");
+
+            const StorageAnimationManifestInfo* manifest_info =
+                animation_storage_get_meta(animation_manager->current_animation);
+            bool valid = animation_manager_is_valid_idle_animation(manifest_info, &stats);
+
+            if(!valid) {
+                animation_manager_start_new_idle(animation_manager);
+            }
+        }
     }
 }
 
@@ -119,13 +149,24 @@ void animation_manager_new_idle_process(AnimationManager* animation_manager) {
     }
 }
 
-/* reaction to animation_manager->check_blocking_callback() */
+/* reaction to animation_manager->interact_callback() */
 void animation_manager_interact_process(AnimationManager* animation_manager) {
     furi_assert(animation_manager);
 
-    if(animation_manager->state == AnimationManagerStateBlocked) {
-        /* check if new blocking animation has to be displayed */
+    if(animation_manager->levelup_pending) {
+        animation_manager->levelup_pending = false;
+        animation_manager->levelup_active = true;
+        animation_manager_switch_to_one_shot_view(animation_manager);
+        Dolphin* dolphin = furi_record_open("dolphin");
+        dolphin_upgrade_level(dolphin);
+        furi_record_close("dolphin");
+    } else if(animation_manager->levelup_active) {
+        animation_manager->levelup_active = false;
+        animation_manager_start_new_idle(animation_manager);
+        animation_manager_switch_to_animation_view(animation_manager);
+    } else if(animation_manager->state == AnimationManagerStateBlocked) {
         bool blocked = animation_manager_check_blocking(animation_manager);
+
         if(!blocked) {
             animation_manager_start_new_idle(animation_manager);
         }
@@ -152,22 +193,26 @@ static bool animation_manager_check_blocking(AnimationManager* animation_manager
 
     if(sd_status == FSE_INTERNAL) {
         blocking_animation = animation_storage_find_animation(BAD_SD_ANIMATION_NAME);
+        furi_assert(blocking_animation);
     } else if(sd_status == FSE_NOT_READY) {
         animation_manager->sd_shown_sd_ok = false;
         animation_manager->sd_shown_no_db = false;
     } else if(sd_status == FSE_OK) {
         if(!animation_manager->sd_shown_sd_ok) {
             blocking_animation = animation_storage_find_animation(SD_OK_ANIMATION_NAME);
+            furi_assert(blocking_animation);
             animation_manager->sd_shown_sd_ok = true;
         } else if(!animation_manager->sd_shown_no_db) {
             bool db_exists = storage_common_stat(storage, "/ext/Manifest", NULL) == FSE_OK;
             if(!db_exists) {
                 blocking_animation = animation_storage_find_animation(NO_DB_ANIMATION_NAME);
+                furi_assert(blocking_animation);
                 animation_manager->sd_shown_no_db = true;
                 animation_manager->sd_show_url = true;
             }
         } else if(animation_manager->sd_show_url) {
             blocking_animation = animation_storage_find_animation(URL_ANIMATION_NAME);
+            furi_assert(blocking_animation);
             animation_manager->sd_show_url = false;
         }
     }
@@ -176,13 +221,17 @@ static bool animation_manager_check_blocking(AnimationManager* animation_manager
     DolphinStats stats = dolphin_stats(dolphin);
     furi_record_close("dolphin");
     if(!blocking_animation && stats.level_up_is_pending) {
-        blocking_animation = animation_storage_find_animation(LEVELUP_ANIMATION_NAME);
+        blocking_animation = animation_storage_find_animation(NEW_MAIL_ANIMATION_NAME);
+        furi_assert(blocking_animation);
+        if(blocking_animation) {
+            animation_manager->levelup_pending = true;
+        }
     }
 
     if(blocking_animation) {
         osTimerStop(animation_manager->idle_animation_timer);
         animation_manager_replace_current_animation(animation_manager, blocking_animation);
-        /* no starting timer because its blocking animation */
+        /* no timer starting because this is blocking animation */
         animation_manager->state = AnimationManagerStateBlocked;
     }
 
@@ -199,7 +248,7 @@ static void animation_manager_replace_current_animation(
 
     const BubbleAnimation* animation = animation_storage_get_bubble_animation(storage_animation);
     bubble_animation_view_set_animation(animation_manager->animation_view, animation);
-    const char* new_name = string_get_cstr(animation_storage_get_meta(storage_animation)->name);
+    const char* new_name = animation_storage_get_meta(storage_animation)->name;
     FURI_LOG_I(TAG, "Select \'%s\' animation", new_name);
     animation_manager->current_animation = storage_animation;
 
@@ -209,9 +258,11 @@ static void animation_manager_replace_current_animation(
 }
 
 AnimationManager* animation_manager_alloc(void) {
-    animation_storage_initialize_internal_animations();
     AnimationManager* animation_manager = furi_alloc(sizeof(AnimationManager));
     animation_manager->animation_view = bubble_animation_view_alloc();
+    animation_manager->view_stack = view_stack_alloc();
+    View* animation_view = bubble_animation_get_view(animation_manager->animation_view);
+    view_stack_add_view(animation_manager->view_stack, animation_view);
     string_init(animation_manager->freezed_animation_name);
 
     animation_manager->idle_animation_timer =
@@ -251,6 +302,8 @@ void animation_manager_free(AnimationManager* animation_manager) {
     furi_record_close("storage");
 
     string_clear(animation_manager->freezed_animation_name);
+    View* animation_view = bubble_animation_get_view(animation_manager->animation_view);
+    view_stack_remove_view(animation_manager->view_stack, animation_view);
     bubble_animation_view_free(animation_manager->animation_view);
     osTimerDelete(animation_manager->idle_animation_timer);
 }
@@ -258,7 +311,39 @@ void animation_manager_free(AnimationManager* animation_manager) {
 View* animation_manager_get_animation_view(AnimationManager* animation_manager) {
     furi_assert(animation_manager);
 
-    return bubble_animation_get_view(animation_manager->animation_view);
+    return view_stack_get_view(animation_manager->view_stack);
+}
+
+static bool animation_manager_is_valid_idle_animation(
+    const StorageAnimationManifestInfo* info,
+    const DolphinStats* stats) {
+    furi_assert(info);
+    furi_assert(info->name);
+
+    bool result = true;
+
+    if(!strcmp(info->name, BAD_BATTERY_ANIMATION_NAME)) {
+        Power* power = furi_record_open("power");
+        bool battery_is_well = power_is_battery_healthy(power);
+        furi_record_close("power");
+
+        result = !battery_is_well;
+    }
+    if(!strcmp(info->name, NO_SD_ANIMATION_NAME)) {
+        Storage* storage = furi_record_open("storage");
+        FS_Error sd_status = storage_sd_status(storage);
+        furi_record_close("storage");
+
+        result = (sd_status == FSE_NOT_READY);
+    }
+    if((stats->butthurt < info->min_butthurt) || (stats->butthurt > info->max_butthurt)) {
+        result = false;
+    }
+    if((stats->level < info->min_level) || (stats->level > info->max_level)) {
+        result = false;
+    }
+
+    return result;
 }
 
 static StorageAnimation*
@@ -267,40 +352,25 @@ static StorageAnimation*
     StorageAnimationList_init(animation_list);
     animation_storage_fill_animation_list(&animation_list);
 
-    Power* power = furi_record_open("power");
-    bool battery_is_well = power_is_battery_healthy(power);
-    furi_record_close("power");
-
-    Storage* storage = furi_record_open("storage");
-    FS_Error sd_status = storage_sd_status(storage);
-    furi_record_close("storage");
-
     Dolphin* dolphin = furi_record_open("dolphin");
     DolphinStats stats = dolphin_stats(dolphin);
+    furi_record_close("dolphin");
     uint32_t whole_weight = 0;
 
     StorageAnimationList_it_t it;
     for(StorageAnimationList_it(it, animation_list); !StorageAnimationList_end_p(it);) {
         StorageAnimation* storage_animation = *StorageAnimationList_ref(it);
-        const StorageAnimationMeta* meta = animation_storage_get_meta(storage_animation);
-        bool skip_animation = false;
-        if(battery_is_well && !string_cmp_str(meta->name, BAD_BATTERY_ANIMATION_NAME)) {
-            skip_animation = true;
-        } else if((sd_status != FSE_NOT_READY) && !string_cmp_str(meta->name, NO_SD_ANIMATION_NAME)) {
-            skip_animation = true;
-        } else if((stats.butthurt < meta->min_butthurt) || (stats.butthurt > meta->max_butthurt)) {
-            skip_animation = true;
-        } else if((stats.level < meta->min_level) || (stats.level > meta->max_level)) {
-            skip_animation = true;
-        }
+        const StorageAnimationManifestInfo* manifest_info =
+            animation_storage_get_meta(storage_animation);
+        bool valid = animation_manager_is_valid_idle_animation(manifest_info, &stats);
 
-        if(skip_animation) {
+        if(valid) {
+            whole_weight += manifest_info->weight;
+            StorageAnimationList_next(it);
+        } else {
             animation_storage_free_storage_animation(&storage_animation);
             /* remove and increase iterator */
             StorageAnimationList_remove(animation_list, it);
-        } else {
-            whole_weight += meta->weight;
-            StorageAnimationList_next(it);
         }
     }
 
@@ -325,11 +395,10 @@ static StorageAnimation*
         }
 
     StorageAnimationList_clear(animation_list);
-    furi_record_close("dolphin");
 
     /* cache animation, if failed - choose reliable animation */
     if(!animation_storage_get_bubble_animation(selected)) {
-        const char* name = string_get_cstr(animation_storage_get_meta(selected)->name);
+        const char* name = animation_storage_get_meta(selected)->name;
         FURI_LOG_E(TAG, "Can't upload animation described in manifest: \'%s\'", name);
         animation_storage_free_storage_animation(&selected);
         selected = animation_storage_find_animation(HARDCODED_ANIMATION_NAME);
@@ -362,9 +431,15 @@ void animation_manager_unload_and_stall_animation(AnimationManager* animation_ma
         furi_assert(0);
     }
 
-    StorageAnimationMeta* meta = animation_storage_get_meta(animation_manager->current_animation);
+    FURI_LOG_I(
+        TAG,
+        "Unload animation \'%s\'",
+        animation_storage_get_meta(animation_manager->current_animation)->name);
+
+    StorageAnimationManifestInfo* meta =
+        animation_storage_get_meta(animation_manager->current_animation);
     /* copy str, not move, because it can be internal animation */
-    string_set(animation_manager->freezed_animation_name, meta->name);
+    string_set_str(animation_manager->freezed_animation_name, meta->name);
 
     bubble_animation_freeze(animation_manager->animation_view);
     animation_storage_free_storage_animation(&animation_manager->current_animation);
@@ -394,18 +469,27 @@ void animation_manager_load_and_continue_animation(AnimationManager* animation_m
             StorageAnimation* restore_animation = animation_storage_find_animation(
                 string_get_cstr(animation_manager->freezed_animation_name));
             if(restore_animation) {
-                animation_manager_replace_current_animation(animation_manager, restore_animation);
-                animation_manager->state = AnimationManagerStateIdle;
-
-                if(animation_manager->freezed_animation_time_left) {
-                    osTimerStart(
-                        animation_manager->idle_animation_timer,
-                        animation_manager->freezed_animation_time_left);
-                } else {
-                    const BubbleAnimation* animation = animation_storage_get_bubble_animation(
-                        animation_manager->current_animation);
-                    osTimerStart(
-                        animation_manager->idle_animation_timer, animation->duration * 1000);
+                Dolphin* dolphin = furi_record_open("dolphin");
+                DolphinStats stats = dolphin_stats(dolphin);
+                furi_record_close("dolphin");
+                const StorageAnimationManifestInfo* manifest_info =
+                    animation_storage_get_meta(restore_animation);
+                bool valid = animation_manager_is_valid_idle_animation(manifest_info, &stats);
+                if(valid) {
+                    animation_manager_replace_current_animation(
+                        animation_manager, restore_animation);
+                    animation_manager->state = AnimationManagerStateIdle;
+
+                    if(animation_manager->freezed_animation_time_left) {
+                        osTimerStart(
+                            animation_manager->idle_animation_timer,
+                            animation_manager->freezed_animation_time_left);
+                    } else {
+                        const BubbleAnimation* animation = animation_storage_get_bubble_animation(
+                            animation_manager->current_animation);
+                        osTimerStart(
+                            animation_manager->idle_animation_timer, animation->duration * 1000);
+                    }
                 }
             } else {
                 FURI_LOG_E(
@@ -423,12 +507,47 @@ void animation_manager_load_and_continue_animation(AnimationManager* animation_m
     if(!animation_manager->current_animation) {
         animation_manager_start_new_idle(animation_manager);
     }
-    FURI_LOG_D(
+    FURI_LOG_I(
         TAG,
-        "Load & Continue with \'%s\'",
-        string_get_cstr(animation_storage_get_meta(animation_manager->current_animation)->name));
+        "Load animation \'%s\'",
+        animation_storage_get_meta(animation_manager->current_animation)->name);
 
     bubble_animation_unfreeze(animation_manager->animation_view);
     string_reset(animation_manager->freezed_animation_name);
     furi_assert(animation_manager->current_animation);
 }
+
+static void animation_manager_switch_to_one_shot_view(AnimationManager* animation_manager) {
+    furi_assert(animation_manager);
+    furi_assert(!animation_manager->one_shot_view);
+    Dolphin* dolphin = furi_record_open("dolphin");
+    DolphinStats stats = dolphin_stats(dolphin);
+    furi_record_close("dolphin");
+
+    animation_manager->one_shot_view = one_shot_view_alloc();
+    one_shot_view_set_interact_callback(
+        animation_manager->one_shot_view, animation_manager_interact_callback, animation_manager);
+    View* prev_view = bubble_animation_get_view(animation_manager->animation_view);
+    View* next_view = one_shot_view_get_view(animation_manager->one_shot_view);
+    view_stack_remove_view(animation_manager->view_stack, prev_view);
+    view_stack_add_view(animation_manager->view_stack, next_view);
+    if(stats.level == 1) {
+        one_shot_view_start_animation(animation_manager->one_shot_view, &A_Levelup1_128x64);
+    } else if(stats.level == 2) {
+        one_shot_view_start_animation(animation_manager->one_shot_view, &A_Levelup2_128x64);
+    } else {
+        furi_assert(0);
+    }
+}
+
+static void animation_manager_switch_to_animation_view(AnimationManager* animation_manager) {
+    furi_assert(animation_manager);
+    furi_assert(animation_manager->one_shot_view);
+
+    View* prev_view = one_shot_view_get_view(animation_manager->one_shot_view);
+    View* next_view = bubble_animation_get_view(animation_manager->animation_view);
+    view_stack_remove_view(animation_manager->view_stack, prev_view);
+    view_stack_add_view(animation_manager->view_stack, next_view);
+    one_shot_view_free(animation_manager->one_shot_view);
+    animation_manager->one_shot_view = NULL;
+}

+ 12 - 11
applications/desktop/animations/animation_manager.h

@@ -1,34 +1,35 @@
 #pragma once
 
-#include "dolphin/dolphin.h"
 #include <gui/view.h>
+#include <gui/icon_i.h>
 #include <stdint.h>
+#include <dolphin/dolphin.h>
 
 typedef struct AnimationManager AnimationManager;
 
 typedef struct {
     uint8_t x;
     uint8_t y;
-    const char* str;
-    Align horizontal;
-    Align vertical;
+    const char* text;
+    Align align_h;
+    Align align_v;
 } Bubble;
 
 typedef struct FrameBubble {
     Bubble bubble;
-    uint8_t starts_at_frame;
-    uint8_t ends_at_frame;
-    struct FrameBubble* next_bubble;
+    uint8_t start_frame;
+    uint8_t end_frame;
+    const struct FrameBubble* next_bubble;
 } FrameBubble;
 
 typedef struct {
-    FrameBubble** frame_bubbles;
-    uint8_t frame_bubbles_count;
-    const Icon** icons;
+    const FrameBubble* const* frame_bubble_sequences;
+    uint8_t frame_bubble_sequences_count;
+    const Icon icon_animation;
+    uint8_t frame_order[20];
     uint8_t passive_frames;
     uint8_t active_frames;
     uint8_t active_cycles;
-    uint8_t frame_rate;
     uint16_t duration;
     uint16_t active_cooldown;
 } BubbleAnimation;

+ 180 - 144
applications/desktop/animations/animation_storage.c

@@ -1,34 +1,86 @@
-#include "animation_manager.h"
-#include "file_worker.h"
-#include "flipper_file.h"
-#include "furi/common_defines.h"
-#include "furi/memmgr.h"
-#include "furi/record.h"
-#include "animation_storage.h"
-#include "gui/canvas.h"
-#include "m-string.h"
-#include "pb.h"
-#include "pb_decode.h"
-#include "storage/filesystem_api_defines.h"
-#include "storage/storage.h"
-#include "animation_storage_i.h"
+
 #include <stdint.h>
+#include <flipper_file.h>
+#include <furi.h>
+#include <furi/dangerous_defines.h>
+#include <storage/storage.h>
 #include <gui/icon_i.h>
+#include <m-string.h>
 
-// Read documentation before using it
-#include <furi/dangerous_defines.h>
+#include "animation_manager.h"
+#include "animation_storage.h"
+#include "animation_storage_i.h"
+#include <assets_dolphin_internal.h>
+#include <assets_dolphin_blocking.h>
 
 #define ANIMATION_META_FILE "meta.txt"
-#define ANIMATION_DIR "/ext/dolphin/animations"
+#define ANIMATION_DIR "/ext/dolphin"
 #define ANIMATION_MANIFEST_FILE ANIMATION_DIR "/manifest.txt"
 #define TAG "AnimationStorage"
-#define DEBUG_PB 0
 
 static void animation_storage_free_bubbles(BubbleAnimation* animation);
 static void animation_storage_free_frames(BubbleAnimation* animation);
 static void animation_storage_free_animation(BubbleAnimation** storage_animation);
 static BubbleAnimation* animation_storage_load_animation(const char* name);
 
+static bool animation_storage_load_single_manifest_info(
+    StorageAnimationManifestInfo* manifest_info,
+    const char* name) {
+    furi_assert(manifest_info);
+
+    bool result = false;
+    Storage* storage = furi_record_open("storage");
+    FlipperFile* file = flipper_file_alloc(storage);
+    flipper_file_set_strict_mode(file, true);
+    string_t read_string;
+    string_init(read_string);
+
+    do {
+        uint32_t u32value;
+        if(FSE_OK != storage_sd_status(storage)) break;
+        if(!flipper_file_open_existing(file, ANIMATION_MANIFEST_FILE)) break;
+
+        if(!flipper_file_read_header(file, read_string, &u32value)) break;
+        if(string_cmp_str(read_string, "Flipper Animation Manifest")) break;
+
+        manifest_info->name = NULL;
+
+        /* skip other animation names */
+        flipper_file_set_strict_mode(file, false);
+        while(flipper_file_read_string(file, "Name", read_string) &&
+              string_cmp_str(read_string, name))
+            ;
+        if(string_cmp_str(read_string, name)) break;
+        flipper_file_set_strict_mode(file, true);
+
+        manifest_info->name = furi_alloc(string_size(read_string) + 1);
+        strcpy((char*)manifest_info->name, string_get_cstr(read_string));
+
+        if(!flipper_file_read_uint32(file, "Min butthurt", &u32value, 1)) break;
+        manifest_info->min_butthurt = u32value;
+        if(!flipper_file_read_uint32(file, "Max butthurt", &u32value, 1)) break;
+        manifest_info->max_butthurt = u32value;
+        if(!flipper_file_read_uint32(file, "Min level", &u32value, 1)) break;
+        manifest_info->min_level = u32value;
+        if(!flipper_file_read_uint32(file, "Max level", &u32value, 1)) break;
+        manifest_info->max_level = u32value;
+        if(!flipper_file_read_uint32(file, "Weight", &u32value, 1)) break;
+        manifest_info->weight = u32value;
+        result = true;
+    } while(0);
+
+    if(!result && manifest_info->name) {
+        free((void*)manifest_info->name);
+    }
+    string_clear(read_string);
+    flipper_file_close(file);
+    flipper_file_free(file);
+
+    furi_record_close("storage");
+
+    return result;
+}
+
 void animation_storage_fill_animation_list(StorageAnimationList_t* animation_list) {
     furi_assert(sizeof(StorageAnimationList_t) == sizeof(void*));
     furi_assert(!StorageAnimationList_size(*animation_list));
@@ -37,8 +89,8 @@ void animation_storage_fill_animation_list(StorageAnimationList_t* animation_lis
     FlipperFile* file = flipper_file_alloc(storage);
     /* Forbid skipping fields */
     flipper_file_set_strict_mode(file, true);
-    string_t header;
-    string_init(header);
+    string_t read_string;
+    string_init(read_string);
 
     do {
         uint32_t u32value;
@@ -46,24 +98,28 @@ void animation_storage_fill_animation_list(StorageAnimationList_t* animation_lis
 
         if(FSE_OK != storage_sd_status(storage)) break;
         if(!flipper_file_open_existing(file, ANIMATION_MANIFEST_FILE)) break;
-        if(!flipper_file_read_header(file, header, &u32value)) break;
-        if(string_cmp_str(header, "Flipper Animation Manifest")) break;
+        if(!flipper_file_read_header(file, read_string, &u32value)) break;
+        if(string_cmp_str(read_string, "Flipper Animation Manifest")) break;
         do {
             storage_animation = furi_alloc(sizeof(StorageAnimation));
             storage_animation->external = true;
             storage_animation->animation = NULL;
+            storage_animation->manifest_info.name = NULL;
+
+            if(!flipper_file_read_string(file, "Name", read_string)) break;
+            storage_animation->manifest_info.name = furi_alloc(string_size(read_string) + 1);
+            strcpy((char*)storage_animation->manifest_info.name, string_get_cstr(read_string));
 
-            if(!flipper_file_read_string(file, "Name", storage_animation->meta.name)) break;
             if(!flipper_file_read_uint32(file, "Min butthurt", &u32value, 1)) break;
-            storage_animation->meta.min_butthurt = u32value;
+            storage_animation->manifest_info.min_butthurt = u32value;
             if(!flipper_file_read_uint32(file, "Max butthurt", &u32value, 1)) break;
-            storage_animation->meta.max_butthurt = u32value;
+            storage_animation->manifest_info.max_butthurt = u32value;
             if(!flipper_file_read_uint32(file, "Min level", &u32value, 1)) break;
-            storage_animation->meta.min_level = u32value;
+            storage_animation->manifest_info.min_level = u32value;
             if(!flipper_file_read_uint32(file, "Max level", &u32value, 1)) break;
-            storage_animation->meta.max_level = u32value;
+            storage_animation->manifest_info.max_level = u32value;
             if(!flipper_file_read_uint32(file, "Weight", &u32value, 1)) break;
-            storage_animation->meta.weight = u32value;
+            storage_animation->manifest_info.weight = u32value;
 
             StorageAnimationList_push_back(*animation_list, storage_animation);
         } while(1);
@@ -71,13 +127,13 @@ void animation_storage_fill_animation_list(StorageAnimationList_t* animation_lis
         animation_storage_free_storage_animation(&storage_animation);
     } while(0);
 
-    string_clear(header);
+    string_clear(read_string);
     flipper_file_close(file);
     flipper_file_free(file);
 
     // add hard-coded animations
-    for(int i = 0; i < COUNT_OF(StorageAnimationInternal); ++i) {
-        StorageAnimationList_push_back(*animation_list, &StorageAnimationInternal[i]);
+    for(int i = 0; i < dolphin_internal_size; ++i) {
+        StorageAnimationList_push_back(*animation_list, (StorageAnimation*)&dolphin_internal[i]);
     }
 
     furi_record_close("storage");
@@ -88,41 +144,45 @@ StorageAnimation* animation_storage_find_animation(const char* name) {
     furi_assert(strlen(name));
     StorageAnimation* storage_animation = NULL;
 
-    /* look through internal animations */
-    for(int i = 0; i < COUNT_OF(StorageAnimationInternal); ++i) {
-        if(!string_cmp_str(StorageAnimationInternal[i].meta.name, name)) {
-            storage_animation = &StorageAnimationInternal[i];
+    for(int i = 0; i < dolphin_blocking_size; ++i) {
+        if(!strcmp(dolphin_blocking[i].manifest_info.name, name)) {
+            storage_animation = (StorageAnimation*)&dolphin_blocking[i];
             break;
         }
     }
 
-    /* look through external animations */
     if(!storage_animation) {
-        BubbleAnimation* animation = animation_storage_load_animation(name);
+        for(int i = 0; i < dolphin_internal_size; ++i) {
+            if(!strcmp(dolphin_internal[i].manifest_info.name, name)) {
+                storage_animation = (StorageAnimation*)&dolphin_internal[i];
+                break;
+            }
+        }
+    }
 
-        if(animation != NULL) {
-            storage_animation = furi_alloc(sizeof(StorageAnimation));
-            storage_animation->animation = animation;
-            storage_animation->external = true;
-            /* meta data takes part in random animation selection, so it
-             * doesn't need here as we exactly know which animation we need,
-             * that's why we can ignore reading manifest.txt file
-             * filling meta data by zeroes */
-            storage_animation->meta.min_butthurt = 0;
-            storage_animation->meta.max_butthurt = 0;
-            storage_animation->meta.min_level = 0;
-            storage_animation->meta.max_level = 0;
-            storage_animation->meta.weight = 0;
-            string_init_set_str(storage_animation->meta.name, name);
+    /* look through external animations */
+    if(!storage_animation) {
+        storage_animation = furi_alloc(sizeof(StorageAnimation));
+        storage_animation->external = true;
+
+        bool result = false;
+        result =
+            animation_storage_load_single_manifest_info(&storage_animation->manifest_info, name);
+        if(result) {
+            storage_animation->animation = animation_storage_load_animation(name);
+            result = !!storage_animation->animation;
+        }
+        if(!result) {
+            animation_storage_free_storage_animation(&storage_animation);
         }
     }
 
     return storage_animation;
 }
 
-StorageAnimationMeta* animation_storage_get_meta(StorageAnimation* storage_animation) {
+StorageAnimationManifestInfo* animation_storage_get_meta(StorageAnimation* storage_animation) {
     furi_assert(storage_animation);
-    return &storage_animation->meta;
+    return &storage_animation->manifest_info;
 }
 
 const BubbleAnimation*
@@ -138,7 +198,7 @@ void animation_storage_cache_animation(StorageAnimation* storage_animation) {
     if(storage_animation->external) {
         if(!storage_animation->animation) {
             storage_animation->animation =
-                animation_storage_load_animation(string_get_cstr(storage_animation->meta.name));
+                animation_storage_load_animation(storage_animation->manifest_info.name);
         }
     }
 }
@@ -161,7 +221,9 @@ void animation_storage_free_storage_animation(StorageAnimation** storage_animati
     if((*storage_animation)->external) {
         animation_storage_free_animation((BubbleAnimation**)&(*storage_animation)->animation);
 
-        string_clear((*storage_animation)->meta.name);
+        if((*storage_animation)->manifest_info.name) {
+            free((void*)(*storage_animation)->manifest_info.name);
+        }
         free(*storage_animation);
     }
 
@@ -188,41 +250,15 @@ static bool animation_storage_cast_align(string_t align_str, Align* align) {
 
 static void animation_storage_free_frames(BubbleAnimation* animation) {
     furi_assert(animation);
-    furi_assert(animation->icons);
-
-    const Icon** icons = animation->icons;
-    uint16_t frames = animation->active_frames + animation->passive_frames;
-    furi_assert(frames > 0);
-
-    for(int i = 0; i < frames; ++i) {
-        if(!icons[i]) continue;
-
-        const Icon* icon = icons[i];
-        free((void*)icon->frames[0]);
-        free(icon->frames);
-        free((void*)icon);
-        for(int j = i; j < frames; ++j) {
-            if(icons[j] == icon) {
-                icons[j] = NULL;
-            }
+
+    const Icon* icon = &animation->icon_animation;
+    for(int i = 0; i < icon->frame_count; ++i) {
+        if(icon->frames[i]) {
+            free((void*)icon->frames[i]);
         }
     }
 
-    free(animation->icons);
-    animation->icons = NULL;
-}
-
-static Icon* animation_storage_alloc_icon(size_t frame_size) {
-    Icon* icon = furi_alloc(sizeof(Icon));
-    icon->frames = furi_alloc(sizeof(const uint8_t*));
-    icon->frames[0] = furi_alloc(frame_size);
-    return icon;
-}
-
-static void animation_storage_free_icon(Icon* icon) {
-    free((void*)icon->frames[0]);
     free(icon->frames);
-    free(icon);
 }
 
 static bool animation_storage_load_frames(
@@ -230,22 +266,25 @@ static bool animation_storage_load_frames(
     const char* name,
     BubbleAnimation* animation,
     uint32_t* frame_order,
-    uint32_t width,
-    uint32_t height) {
-    furi_assert(!animation->icons);
+    uint8_t width,
+    uint8_t height) {
     uint16_t frame_order_size = animation->passive_frames + animation->active_frames;
 
     bool frames_ok = false;
-    animation->icons = furi_alloc(sizeof(const Icon*) * frame_order_size);
     File* file = storage_file_alloc(storage);
     FileInfo file_info;
     string_t filename;
     string_init(filename);
     size_t max_filesize = ROUND_UP_TO(width, 8) * height + 1;
+    Icon* icon = (Icon*)&animation->icon_animation;
+    FURI_CONST_ASSIGN(icon->frame_count, frame_order_size);
+    FURI_CONST_ASSIGN(icon->frame_rate, 0);
+    FURI_CONST_ASSIGN(icon->height, height);
+    FURI_CONST_ASSIGN(icon->width, width);
 
-    for(int i = 0; i < frame_order_size; ++i) {
-        if(animation->icons[i]) continue;
+    icon->frames = furi_alloc(sizeof(const uint8_t*) * frame_order_size);
 
+    for(int i = 0; i < frame_order_size; ++i) {
         frames_ok = false;
         string_printf(filename, ANIMATION_DIR "/%s/frame_%d.bm", name, frame_order[i]);
 
@@ -265,24 +304,12 @@ static bool animation_storage_load_frames(
             break;
         }
 
-        Icon* icon = animation_storage_alloc_icon(file_info.size);
-        if(storage_file_read(file, (void*)icon->frames[0], file_info.size) != file_info.size) {
+        icon->frames[i] = furi_alloc(file_info.size);
+        if(storage_file_read(file, (void*)icon->frames[i], file_info.size) != file_info.size) {
             FURI_LOG_E(TAG, "Read failed: \'%s\'", string_get_cstr(filename));
-            animation_storage_free_icon(icon);
             break;
         }
         storage_file_close(file);
-        FURI_CONST_ASSIGN(icon->frame_count, 1);
-        FURI_CONST_ASSIGN(icon->frame_rate, 0);
-        FURI_CONST_ASSIGN(icon->height, height);
-        FURI_CONST_ASSIGN(icon->width, width);
-
-        /* Claim 1 allocation for 1 files blob and several links to it */
-        for(int j = i; j < frame_order_size; ++j) {
-            if(frame_order[i] == frame_order[j]) {
-                animation->icons[j] = icon;
-            }
-        }
         frames_ok = true;
     }
 
@@ -295,11 +322,10 @@ static bool animation_storage_load_frames(
             height,
             file_info.size);
         animation_storage_free_frames(animation);
-        animation->icons = NULL;
     } else {
-        for(int i = 0; i < frame_order_size; ++i) {
-            furi_check(animation->icons[i]);
-            furi_check(animation->icons[i]->frames[0]);
+        furi_check(animation->icon_animation.frames);
+        for(int i = 0; i < animation->icon_animation.frame_count; ++i) {
+            furi_check(animation->icon_animation.frames[i]);
         }
     }
 
@@ -314,72 +340,73 @@ static bool animation_storage_load_bubbles(BubbleAnimation* animation, FlipperFi
     string_t str;
     string_init(str);
     bool success = false;
-    furi_assert(!animation->frame_bubbles);
+    furi_assert(!animation->frame_bubble_sequences);
 
     do {
         if(!flipper_file_read_uint32(ff, "Bubble slots", &u32value, 1)) break;
         if(u32value > 20) break;
-        animation->frame_bubbles_count = u32value;
-        if(animation->frame_bubbles_count == 0) {
-            animation->frame_bubbles = NULL;
+        animation->frame_bubble_sequences_count = u32value;
+        if(animation->frame_bubble_sequences_count == 0) {
+            animation->frame_bubble_sequences = NULL;
             success = true;
             break;
         }
-        animation->frame_bubbles =
-            furi_alloc(sizeof(FrameBubble*) * animation->frame_bubbles_count);
+        animation->frame_bubble_sequences =
+            furi_alloc(sizeof(FrameBubble*) * animation->frame_bubble_sequences_count);
 
         uint32_t current_slot = 0;
-        for(int i = 0; i < animation->frame_bubbles_count; ++i) {
-            animation->frame_bubbles[i] = furi_alloc(sizeof(FrameBubble));
+        for(int i = 0; i < animation->frame_bubble_sequences_count; ++i) {
+            FURI_CONST_ASSIGN_PTR(
+                animation->frame_bubble_sequences[i], furi_alloc(sizeof(FrameBubble)));
         }
 
-        FrameBubble* bubble = animation->frame_bubbles[0];
+        const FrameBubble* bubble = animation->frame_bubble_sequences[0];
         int8_t index = -1;
         for(;;) {
             if(!flipper_file_read_uint32(ff, "Slot", &current_slot, 1)) break;
             if((current_slot != 0) && (index == -1)) break;
 
             if(current_slot == index) {
-                bubble->next_bubble = furi_alloc(sizeof(FrameBubble));
+                FURI_CONST_ASSIGN_PTR(bubble->next_bubble, furi_alloc(sizeof(FrameBubble)));
                 bubble = bubble->next_bubble;
             } else if(current_slot == index + 1) {
                 ++index;
-                bubble = animation->frame_bubbles[index];
+                bubble = animation->frame_bubble_sequences[index];
             } else {
                 /* slots have to start from 0, be ascending sorted, and
                  * have exact number of slots as specified in "Bubble slots" */
                 break;
             }
-            if(index >= animation->frame_bubbles_count) break;
+            if(index >= animation->frame_bubble_sequences_count) break;
 
             if(!flipper_file_read_uint32(ff, "X", &u32value, 1)) break;
-            bubble->bubble.x = u32value;
+            FURI_CONST_ASSIGN(bubble->bubble.x, u32value);
             if(!flipper_file_read_uint32(ff, "Y", &u32value, 1)) break;
-            bubble->bubble.y = u32value;
+            FURI_CONST_ASSIGN(bubble->bubble.y, u32value);
 
             if(!flipper_file_read_string(ff, "Text", str)) break;
             if(string_size(str) > 100) break;
 
             string_replace_all_str(str, "\\n", "\n");
 
-            bubble->bubble.str = furi_alloc(string_size(str) + 1);
-            strcpy((char*)bubble->bubble.str, string_get_cstr(str));
+            FURI_CONST_ASSIGN_PTR(bubble->bubble.text, furi_alloc(string_size(str) + 1));
+            strcpy((char*)bubble->bubble.text, string_get_cstr(str));
 
             if(!flipper_file_read_string(ff, "AlignH", str)) break;
-            if(!animation_storage_cast_align(str, &bubble->bubble.horizontal)) break;
+            if(!animation_storage_cast_align(str, (Align*)&bubble->bubble.align_h)) break;
             if(!flipper_file_read_string(ff, "AlignV", str)) break;
-            if(!animation_storage_cast_align(str, &bubble->bubble.vertical)) break;
+            if(!animation_storage_cast_align(str, (Align*)&bubble->bubble.align_v)) break;
 
             if(!flipper_file_read_uint32(ff, "StartFrame", &u32value, 1)) break;
-            bubble->starts_at_frame = u32value;
+            FURI_CONST_ASSIGN(bubble->start_frame, u32value);
             if(!flipper_file_read_uint32(ff, "EndFrame", &u32value, 1)) break;
-            bubble->ends_at_frame = u32value;
+            FURI_CONST_ASSIGN(bubble->end_frame, u32value);
         }
-        success = (index + 1) == animation->frame_bubbles_count;
+        success = (index + 1) == animation->frame_bubble_sequences_count;
     } while(0);
 
     if(!success) {
-        if(animation->frame_bubbles) {
+        if(animation->frame_bubble_sequences) {
             FURI_LOG_E(TAG, "Failed to load animation bubbles");
             animation_storage_free_bubbles(animation);
         }
@@ -402,7 +429,7 @@ static BubbleAnimation* animation_storage_load_animation(const char* name) {
     flipper_file_set_strict_mode(ff, true);
     string_t str;
     string_init(str);
-    animation->frame_bubbles = NULL;
+    animation->frame_bubble_sequences = NULL;
 
     bool success = false;
     do {
@@ -424,8 +451,17 @@ static BubbleAnimation* animation_storage_load_animation(const char* name) {
         animation->active_frames = u32value;
 
         uint8_t frames = animation->passive_frames + animation->active_frames;
+        uint32_t count = 0;
+        if(!flipper_file_get_value_count(ff, "Frames order", &count)) break;
+        if((count != frames) || (frames > COUNT_OF(animation->frame_order))) {
+            FURI_LOG_E(TAG, "Error loading animation: frames order");
+            break;
+        }
         u32array = furi_alloc(sizeof(uint32_t) * frames);
         if(!flipper_file_read_uint32(ff, "Frames order", u32array, frames)) break;
+        for(int i = 0; i < frames; ++i) {
+            animation->frame_order[i] = u32array[i];
+        }
 
         /* passive and active frames must be loaded up to this point */
         if(!animation_storage_load_frames(storage, name, animation, u32array, width, height))
@@ -434,7 +470,7 @@ static BubbleAnimation* animation_storage_load_animation(const char* name) {
         if(!flipper_file_read_uint32(ff, "Active cycles", &u32value, 1)) break;
         animation->active_cycles = u32value;
         if(!flipper_file_read_uint32(ff, "Frame rate", &u32value, 1)) break;
-        animation->frame_rate = u32value;
+        FURI_CONST_ASSIGN(animation->icon_animation.frame_rate, u32value);
         if(!flipper_file_read_uint32(ff, "Duration", &u32value, 1)) break;
         animation->duration = u32value;
         if(!flipper_file_read_uint32(ff, "Active cooldown", &u32value, 1)) break;
@@ -460,10 +496,10 @@ static BubbleAnimation* animation_storage_load_animation(const char* name) {
 }
 
 static void animation_storage_free_bubbles(BubbleAnimation* animation) {
-    if(!animation->frame_bubbles) return;
+    if(!animation->frame_bubble_sequences) return;
 
-    for(int i = 0; i < animation->frame_bubbles_count;) {
-        FrameBubble** bubble = &animation->frame_bubbles[i];
+    for(int i = 0; i < animation->frame_bubble_sequences_count;) {
+        const FrameBubble* const* bubble = &animation->frame_bubble_sequences[i];
 
         if((*bubble) == NULL) break;
 
@@ -471,15 +507,15 @@ static void animation_storage_free_bubbles(BubbleAnimation* animation) {
             bubble = &(*bubble)->next_bubble;
         }
 
-        if((*bubble)->bubble.str) {
-            free((void*)(*bubble)->bubble.str);
+        if((*bubble)->bubble.text) {
+            free((void*)(*bubble)->bubble.text);
         }
-        if((*bubble) == animation->frame_bubbles[i]) {
+        if((*bubble) == animation->frame_bubble_sequences[i]) {
             ++i;
         }
-        free(*bubble);
-        *bubble = NULL;
+        free((void*)*bubble);
+        FURI_CONST_ASSIGN_PTR(*bubble, NULL);
     }
-    free(animation->frame_bubbles);
-    animation->frame_bubbles = NULL;
+    free((void*)animation->frame_bubble_sequences);
+    animation->frame_bubble_sequences = NULL;
 }

+ 3 - 12
applications/desktop/animations/animation_storage.h

@@ -4,15 +4,6 @@
 #include "views/bubble_animation_view.h"
 #include <m-string.h>
 
-#define HARDCODED_ANIMATION_NAME "tv"
-#define NO_SD_ANIMATION_NAME "no_sd"
-#define BAD_BATTERY_ANIMATION_NAME "bad_battery"
-#define NO_DB_ANIMATION_NAME "no_db"
-#define BAD_SD_ANIMATION_NAME "bad_sd"
-#define SD_OK_ANIMATION_NAME "sd_ok"
-#define URL_ANIMATION_NAME "url"
-#define LEVELUP_ANIMATION_NAME "level"
-
 /** Main structure to handle animation data.
  * Contains all, including animation playing data (BubbleAnimation),
  * data for random animation selection (StorageAnimationMeta) and
@@ -20,13 +11,13 @@
 typedef struct StorageAnimation StorageAnimation;
 
 typedef struct {
-    string_t name;
+    const char* name;
     uint8_t min_butthurt;
     uint8_t max_butthurt;
     uint8_t min_level;
     uint8_t max_level;
     uint8_t weight;
-} StorageAnimationMeta;
+} StorageAnimationManifestInfo;
 
 /** Container to return available animations list */
 LIST_DEF(StorageAnimationList, StorageAnimation*, M_PTR_OPLIST)
@@ -81,7 +72,7 @@ StorageAnimation* animation_storage_find_animation(const char* name);
  * @storage_animation       item of whom we have to extract meta.
  * @return                  meta itself
  */
-StorageAnimationMeta* animation_storage_get_meta(StorageAnimation* storage_animation);
+StorageAnimationManifestInfo* animation_storage_get_meta(StorageAnimation* storage_animation);
 
 /**
  * Free storage_animation, which previously acquired

+ 1 - 187
applications/desktop/animations/animation_storage_i.h

@@ -1,195 +1,9 @@
 #pragma once
 #include "animation_storage.h"
-#include "assets_icons.h"
 #include "animation_manager.h"
-#include "gui/canvas.h"
 
 struct StorageAnimation {
     const BubbleAnimation* animation;
     bool external;
-    StorageAnimationMeta meta;
+    StorageAnimationManifestInfo manifest_info;
 };
-
-// Hard-coded, always available idle animation
-FrameBubble tv_bubble1 = {
-    .bubble =
-        {.x = 1,
-         .y = 23,
-         .str = "Take the red pill",
-         .horizontal = AlignRight,
-         .vertical = AlignBottom},
-    .starts_at_frame = 7,
-    .ends_at_frame = 9,
-    .next_bubble = NULL,
-};
-FrameBubble tv_bubble2 = {
-    .bubble =
-        {.x = 1,
-         .y = 23,
-         .str = "I can joke better",
-         .horizontal = AlignRight,
-         .vertical = AlignBottom},
-    .starts_at_frame = 7,
-    .ends_at_frame = 9,
-    .next_bubble = NULL,
-};
-FrameBubble* tv_bubbles[] = {&tv_bubble1, &tv_bubble2};
-const Icon* tv_icons[] = {
-    &I_tv1,
-    &I_tv2,
-    &I_tv3,
-    &I_tv4,
-    &I_tv5,
-    &I_tv6,
-    &I_tv7,
-    &I_tv8,
-};
-const BubbleAnimation tv_bubble_animation = {
-    .icons = tv_icons,
-    .frame_bubbles = tv_bubbles,
-    .frame_bubbles_count = COUNT_OF(tv_bubbles),
-    .passive_frames = 6,
-    .active_frames = 2,
-    .active_cycles = 2,
-    .frame_rate = 2,
-    .duration = 3600,
-    .active_cooldown = 5,
-};
-
-// System animation - no SD card
-const Icon* no_sd_icons[] = {
-    &I_no_sd1,
-    &I_no_sd2,
-    &I_no_sd1,
-    &I_no_sd2,
-    &I_no_sd1,
-    &I_no_sd3,
-    &I_no_sd4,
-    &I_no_sd5,
-    &I_no_sd4,
-    &I_no_sd6,
-};
-FrameBubble no_sd_bubble = {
-    .bubble =
-        {.x = 40,
-         .y = 18,
-         .str = "Need an\nSD card",
-         .horizontal = AlignRight,
-         .vertical = AlignBottom},
-    .starts_at_frame = 0,
-    .ends_at_frame = 9,
-    .next_bubble = NULL,
-};
-FrameBubble* no_sd_bubbles[] = {&no_sd_bubble};
-const BubbleAnimation no_sd_bubble_animation = {
-    .icons = no_sd_icons,
-    .frame_bubbles = no_sd_bubbles,
-    .frame_bubbles_count = COUNT_OF(no_sd_bubbles),
-    .passive_frames = 10,
-    .active_frames = 0,
-    .frame_rate = 2,
-    .duration = 3600,
-    .active_cooldown = 0,
-    .active_cycles = 0,
-};
-
-// BLOCKING ANIMATION - no_db, bad_sd, sd_ok, url
-const Icon* no_db_icons[] = {
-    &I_no_databases1,
-    &I_no_databases2,
-    &I_no_databases3,
-    &I_no_databases4,
-};
-const BubbleAnimation no_db_bubble_animation = {
-    .icons = no_db_icons,
-    .passive_frames = COUNT_OF(no_db_icons),
-    .frame_rate = 2,
-};
-
-const Icon* bad_sd_icons[] = {
-    &I_card_bad1,
-    &I_card_bad2,
-};
-const BubbleAnimation bad_sd_bubble_animation = {
-    .icons = bad_sd_icons,
-    .passive_frames = COUNT_OF(bad_sd_icons),
-    .frame_rate = 2,
-};
-
-const Icon* url_icons[] = {
-    &I_url1,
-    &I_url2,
-    &I_url3,
-    &I_url4,
-};
-const BubbleAnimation url_bubble_animation = {
-    .icons = url_icons,
-    .passive_frames = COUNT_OF(url_icons),
-    .frame_rate = 2,
-};
-
-const Icon* sd_ok_icons[] = {
-    &I_card_ok1,
-    &I_card_ok2,
-    &I_card_ok3,
-    &I_card_ok4,
-};
-const BubbleAnimation sd_ok_bubble_animation = {
-    .icons = sd_ok_icons,
-    .passive_frames = COUNT_OF(sd_ok_icons),
-    .frame_rate = 2,
-};
-
-static StorageAnimation StorageAnimationInternal[] = {
-    {.animation = &tv_bubble_animation,
-     .external = false,
-     .meta =
-         {
-             .min_butthurt = 0,
-             .max_butthurt = 11,
-             .min_level = 1,
-             .max_level = 3,
-             .weight = 3,
-         }},
-    {.animation = &no_sd_bubble_animation,
-     .external = false,
-     .meta =
-         {
-             .min_butthurt = 0,
-             .max_butthurt = 14,
-             .min_level = 1,
-             .max_level = 3,
-             .weight = 6,
-         }},
-    {
-        .animation = &no_db_bubble_animation,
-        .external = false,
-    },
-    {
-        .animation = &bad_sd_bubble_animation,
-        .external = false,
-    },
-    {
-        .animation = &sd_ok_bubble_animation,
-        .external = false,
-    },
-    {
-        .animation = &url_bubble_animation,
-        .external = false,
-    },
-};
-
-void animation_storage_initialize_internal_animations(void) {
-    /* not in constructor - no memory pool yet */
-    /* called in 1 thread - no need in double check */
-    static bool initialized = false;
-    if(!initialized) {
-        initialized = true;
-        string_init_set_str(StorageAnimationInternal[0].meta.name, HARDCODED_ANIMATION_NAME);
-        string_init_set_str(StorageAnimationInternal[1].meta.name, NO_SD_ANIMATION_NAME);
-        string_init_set_str(StorageAnimationInternal[2].meta.name, NO_DB_ANIMATION_NAME);
-        string_init_set_str(StorageAnimationInternal[3].meta.name, BAD_SD_ANIMATION_NAME);
-        string_init_set_str(StorageAnimationInternal[4].meta.name, SD_OK_ANIMATION_NAME);
-        string_init_set_str(StorageAnimationInternal[5].meta.name, URL_ANIMATION_NAME);
-    }
-}

+ 41 - 46
applications/desktop/animations/views/bubble_animation_view.c

@@ -1,23 +1,19 @@
 
-#include "cmsis_os2.h"
 #include "../animation_manager.h"
 #include "../animation_storage.h"
-#include "furi_hal_delay.h"
-#include "furi_hal_resources.h"
-#include "furi/check.h"
-#include "furi/memmgr.h"
-#include "gui/canvas.h"
-#include "gui/elements.h"
-#include "gui/view.h"
-#include "input/input.h"
+#include "bubble_animation_view.h"
+
+#include <furi_hal.h>
 #include <furi.h>
-#include "portmacro.h"
-#include <gui/icon.h>
+#include <gui/canvas.h>
+#include <gui/elements.h>
+#include <gui/view.h>
+#include <gui/icon_i.h>
+#include <input/input.h>
 #include <stdint.h>
 #include <FreeRTOS.h>
 #include <timers.h>
-#include "bubble_animation_view.h"
-#include <gui/icon_i.h>
+#include <furi/dangerous_defines.h>
 
 #define ACTIVE_SHIFT 2
 
@@ -43,7 +39,7 @@ struct BubbleAnimationView {
 static void bubble_animation_activate(BubbleAnimationView* view, bool force);
 static void bubble_animation_activate_right_now(BubbleAnimationView* view);
 
-static uint8_t bubble_animation_get_icon_index(BubbleAnimationViewModel* model) {
+static uint8_t bubble_animation_get_frame_index(BubbleAnimationViewModel* model) {
     furi_assert(model);
     uint8_t icon_index = 0;
     const BubbleAnimation* animation = model->current;
@@ -57,7 +53,7 @@ static uint8_t bubble_animation_get_icon_index(BubbleAnimationViewModel* model)
     }
     furi_assert(icon_index < (animation->passive_frames + animation->active_frames));
 
-    return icon_index;
+    return animation->frame_order[icon_index];
 }
 
 static void bubble_animation_draw_callback(Canvas* canvas, void* model_) {
@@ -79,23 +75,26 @@ static void bubble_animation_draw_callback(Canvas* canvas, void* model_) {
 
     furi_assert(model->current_frame < 255);
 
-    const Icon* icon = animation->icons[bubble_animation_get_icon_index(model)];
-    furi_assert(icon);
-    uint8_t y_offset = canvas_height(canvas) - icon_get_height(icon);
-    canvas_draw_icon(canvas, 0, y_offset, icon);
+    uint8_t index = bubble_animation_get_frame_index(model);
+    uint8_t width = icon_get_width(&animation->icon_animation);
+    uint8_t height = icon_get_height(&animation->icon_animation);
+    uint8_t y_offset = canvas_height(canvas) - height;
+    canvas_draw_bitmap(
+        canvas, 0, y_offset, width, height, animation->icon_animation.frames[index]);
 
     const FrameBubble* bubble = model->current_bubble;
     if(bubble) {
-        if((model->current_frame >= bubble->starts_at_frame) &&
-           (model->current_frame <= bubble->ends_at_frame)) {
+        if((model->current_frame >= bubble->start_frame) &&
+           (model->current_frame <= bubble->end_frame)) {
             const Bubble* b = &bubble->bubble;
-            elements_bubble_str(canvas, b->x, b->y, b->str, b->horizontal, b->vertical);
+            elements_bubble_str(canvas, b->x, b->y, b->text, b->align_h, b->align_v);
         }
     }
 }
 
-static FrameBubble* bubble_animation_pick_bubble(BubbleAnimationViewModel* model, bool active) {
-    FrameBubble* bubble = NULL;
+static const FrameBubble*
+    bubble_animation_pick_bubble(BubbleAnimationViewModel* model, bool active) {
+    const FrameBubble* bubble = NULL;
 
     if((model->active_bubbles == 0) && (model->passive_bubbles == 0)) {
         return NULL;
@@ -104,10 +103,11 @@ static FrameBubble* bubble_animation_pick_bubble(BubbleAnimationViewModel* model
     uint8_t index = random() % (active ? model->active_bubbles : model->passive_bubbles);
     const BubbleAnimation* animation = model->current;
 
-    for(int i = 0; i < animation->frame_bubbles_count; ++i) {
-        if((animation->frame_bubbles[i]->starts_at_frame < animation->passive_frames) ^ active) {
+    for(int i = 0; i < animation->frame_bubble_sequences_count; ++i) {
+        if((animation->frame_bubble_sequences[i]->start_frame < animation->passive_frames) ^
+           active) {
             if(!index) {
-                bubble = animation->frame_bubbles[i];
+                bubble = animation->frame_bubble_sequences[i];
             }
             --index;
         }
@@ -135,10 +135,6 @@ static bool bubble_animation_input_callback(InputEvent* event, void* context) {
                 animation_view->interact_callback(animation_view->interact_callback_context);
             }
         }
-    } else if(event->key == InputKeyBack) {
-        /* Prevent back button to fall down to common handler - leaving
-         * application, so consume */
-        consumed = true;
     }
 
     return consumed;
@@ -190,7 +186,7 @@ static void bubble_animation_activate_right_now(BubbleAnimationView* view) {
     if(model->current && (model->current->active_frames > 0) && (!model->freeze_frame)) {
         model->current_frame = model->current->passive_frames;
         model->current_bubble = bubble_animation_pick_bubble(model, true);
-        frame_rate = model->current->frame_rate;
+        frame_rate = model->current->icon_animation.frame_rate;
     }
     view_commit_model(view->view, true);
 
@@ -222,7 +218,7 @@ static void bubble_animation_next_frame(BubbleAnimationViewModel* model) {
         }
 
         if(model->current_bubble) {
-            if(model->current_frame > model->current_bubble->ends_at_frame) {
+            if(model->current_frame > model->current_bubble->end_frame) {
                 model->current_bubble = model->current_bubble->next_bubble;
             }
         }
@@ -251,7 +247,11 @@ static void bubble_animation_timer_callback(void* context) {
     }
 }
 
-static Icon* bubble_animation_clone_frame(const Icon* icon_orig) {
+/* always freeze first passive frame, because
+ * animation is always activated at unfreezing and played
+ * passive frame first, and 2 frames after - active
+ */
+static Icon* bubble_animation_clone_first_frame(const Icon* icon_orig) {
     furi_assert(icon_orig);
     furi_assert(icon_orig->frames);
     furi_assert(icon_orig->frames[0]);
@@ -268,6 +268,7 @@ static Icon* bubble_animation_clone_frame(const Icon* icon_orig) {
     size_t max_bitmap_size = ROUND_UP_TO(icon_orig->width, 8) * icon_orig->height + 1;
     icon_clone->frames[0] = furi_alloc(max_bitmap_size);
     memcpy((void*)icon_clone->frames[0], icon_orig->frames[0], max_bitmap_size);
+    FURI_CONST_ASSIGN(icon_clone->frame_count, 1);
 
     return icon_clone;
 }
@@ -288,7 +289,7 @@ static void bubble_animation_enter(void* context) {
     bubble_animation_activate(view, false);
 
     BubbleAnimationViewModel* model = view_get_model(view->view);
-    uint8_t frame_rate = model->current->frame_rate;
+    uint8_t frame_rate = model->current->icon_animation.frame_rate;
     view_commit_model(view->view, false);
 
     if(frame_rate) {
@@ -353,8 +354,8 @@ void bubble_animation_view_set_animation(
     model->active_ended_at = xTaskGetTickCount() - (model->current->active_cooldown * 1000);
     model->active_bubbles = 0;
     model->passive_bubbles = 0;
-    for(int i = 0; i < new_animation->frame_bubbles_count; ++i) {
-        if(new_animation->frame_bubbles[i]->starts_at_frame < new_animation->passive_frames) {
+    for(int i = 0; i < new_animation->frame_bubble_sequences_count; ++i) {
+        if(new_animation->frame_bubble_sequences[i]->start_frame < new_animation->passive_frames) {
             ++model->passive_bubbles;
         } else {
             ++model->active_bubbles;
@@ -367,7 +368,7 @@ void bubble_animation_view_set_animation(
     model->active_cycle = 0;
     view_commit_model(view->view, true);
 
-    osTimerStart(view->timer, 1000 / new_animation->frame_rate);
+    osTimerStart(view->timer, 1000 / new_animation->icon_animation.frame_rate);
 }
 
 void bubble_animation_freeze(BubbleAnimationView* view) {
@@ -376,12 +377,7 @@ void bubble_animation_freeze(BubbleAnimationView* view) {
     BubbleAnimationViewModel* model = view_get_model(view->view);
     furi_assert(model->current);
     furi_assert(!model->freeze_frame);
-    /* always freeze first passive frame, because
-     * animation is always activated at unfreezing and played
-     * passive frame first, and 2 frames after - active
-     */
-    uint8_t icon_index = 0;
-    model->freeze_frame = bubble_animation_clone_frame(model->current->icons[icon_index]);
+    model->freeze_frame = bubble_animation_clone_first_frame(&model->current->icon_animation);
     model->current = NULL;
     view_commit_model(view->view, false);
     osTimerStop(view->timer);
@@ -395,8 +391,7 @@ void bubble_animation_unfreeze(BubbleAnimationView* view) {
     furi_assert(model->freeze_frame);
     bubble_animation_release_frame(&model->freeze_frame);
     furi_assert(model->current);
-    furi_assert(model->current->icons);
-    frame_rate = model->current->frame_rate;
+    frame_rate = model->current->icon_animation.frame_rate;
     view_commit_model(view->view, true);
 
     osTimerStart(view->timer, 1000 / frame_rate);

+ 130 - 0
applications/desktop/animations/views/one_shot_animation_view.c

@@ -0,0 +1,130 @@
+
+#include "one_shot_animation_view.h"
+#include <furi.h>
+#include <portmacro.h>
+#include <gui/canvas.h>
+#include <gui/view.h>
+#include <gui/icon_i.h>
+#include <stdint.h>
+
+typedef void (*OneShotInteractCallback)(void*);
+
+struct OneShotView {
+    View* view;
+    TimerHandle_t update_timer;
+    OneShotInteractCallback interact_callback;
+    void* interact_callback_context;
+};
+
+typedef struct {
+    const Icon* icon;
+    uint32_t index;
+    bool block_input;
+} OneShotViewModel;
+
+static void one_shot_view_update_timer_callback(TimerHandle_t xTimer) {
+    OneShotView* view = (void*)pvTimerGetTimerID(xTimer);
+
+    OneShotViewModel* model = view_get_model(view->view);
+    if((model->index + 1) < model->icon->frame_count) {
+        ++model->index;
+    } else {
+        model->block_input = false;
+        model->index = model->icon->frame_count - 2;
+    }
+    view_commit_model(view->view, true);
+}
+
+static void one_shot_view_draw(Canvas* canvas, void* model_) {
+    furi_assert(canvas);
+    furi_assert(model_);
+
+    OneShotViewModel* model = model_;
+    furi_check(model->index < model->icon->frame_count);
+    uint8_t y_offset = canvas_height(canvas) - model->icon->height;
+    canvas_draw_bitmap(
+        canvas,
+        0,
+        y_offset,
+        model->icon->width,
+        model->icon->height,
+        model->icon->frames[model->index]);
+}
+
+static bool one_shot_view_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    furi_assert(event);
+
+    OneShotView* view = context;
+    bool consumed = false;
+
+    OneShotViewModel* model = view_get_model(view->view);
+    consumed = model->block_input;
+    view_commit_model(view->view, false);
+
+    if(!consumed) {
+        if(event->key == InputKeyRight) {
+            /* Right button reserved for animation activation, so consume */
+            consumed = true;
+            if(event->type == InputTypeShort) {
+                if(view->interact_callback) {
+                    view->interact_callback(view->interact_callback_context);
+                }
+            }
+        }
+    }
+
+    return consumed;
+}
+
+OneShotView* one_shot_view_alloc(void) {
+    OneShotView* view = furi_alloc(sizeof(OneShotView));
+    view->view = view_alloc();
+    view->update_timer =
+        xTimerCreate("Update timer", 1000, pdTRUE, view, one_shot_view_update_timer_callback);
+
+    view_allocate_model(view->view, ViewModelTypeLocking, sizeof(OneShotViewModel));
+    view_set_context(view->view, view);
+    view_set_draw_callback(view->view, one_shot_view_draw);
+    view_set_input_callback(view->view, one_shot_view_input);
+
+    return view;
+}
+
+void one_shot_view_free(OneShotView* view) {
+    furi_assert(view);
+
+    xTimerDelete(view->update_timer, portMAX_DELAY);
+    view_free(view->view);
+    view->view = NULL;
+    free(view);
+}
+
+void one_shot_view_set_interact_callback(
+    OneShotView* view,
+    OneShotInteractCallback callback,
+    void* context) {
+    furi_assert(view);
+
+    view->interact_callback_context = context;
+    view->interact_callback = callback;
+}
+
+void one_shot_view_start_animation(OneShotView* view, const Icon* icon) {
+    furi_assert(view);
+    furi_assert(icon);
+    furi_check(icon->frame_count >= 2);
+
+    OneShotViewModel* model = view_get_model(view->view);
+    model->index = 0;
+    model->icon = icon;
+    model->block_input = true;
+    view_commit_model(view->view, true);
+    xTimerChangePeriod(view->update_timer, 1000 / model->icon->frame_rate, portMAX_DELAY);
+}
+
+View* one_shot_view_get_view(OneShotView* view) {
+    furi_assert(view);
+
+    return view->view;
+}

+ 17 - 0
applications/desktop/animations/views/one_shot_animation_view.h

@@ -0,0 +1,17 @@
+#pragma once
+
+#include <furi.h>
+#include <gui/view.h>
+#include <stdint.h>
+
+typedef void (*OneShotInteractCallback)(void*);
+typedef struct OneShotView OneShotView;
+
+OneShotView* one_shot_view_alloc(void);
+void one_shot_view_free(OneShotView* view);
+void one_shot_view_set_interact_callback(
+    OneShotView* view,
+    OneShotInteractCallback callback,
+    void* context);
+void one_shot_view_start_animation(OneShotView* view, const Icon* icon);
+View* one_shot_view_get_view(OneShotView* view);

+ 23 - 42
applications/desktop/desktop.c

@@ -1,17 +1,16 @@
-#include "assets_icons.h"
-#include "cmsis_os2.h"
-#include "desktop/desktop.h"
+#include "animations/animation_manager.h"
+#include "desktop/scenes/desktop_scene.h"
+#include "desktop/scenes/desktop_scene_i.h"
+#include "desktop/views/desktop_locked.h"
 #include "desktop_i.h"
-#include "gui/view_composed.h"
-#include <dolphin/dolphin.h>
-#include <furi/pubsub.h>
-#include <furi/record.h>
-#include "portmacro.h"
-#include "storage/filesystem_api_defines.h"
-#include "storage/storage.h"
+
+#include <storage/storage.h>
+#include <assets_icons.h>
+#include <gui/view_stack.h>
+#include <furi.h>
+#include <furi_hal.h>
+#include <portmacro.h>
 #include <stdint.h>
-#include <power/power_service/power.h>
-#include "animations/animation_manager.h"
 
 static void desktop_lock_icon_callback(Canvas* canvas, void* context) {
     furi_assert(canvas);
@@ -50,44 +49,27 @@ Desktop* desktop_alloc() {
     view_dispatcher_set_navigation_event_callback(
         desktop->view_dispatcher, desktop_back_event_callback);
 
-    desktop->dolphin_view = animation_manager_get_animation_view(desktop->animation_manager);
-
-    desktop->main_view_composed = view_composed_alloc();
-    desktop->main_view = desktop_main_alloc();
-    view_composed_tie_views(
-        desktop->main_view_composed,
-        desktop->dolphin_view,
-        desktop_main_get_view(desktop->main_view));
-    view_composed_top_enable(desktop->main_view_composed, true);
-
-    desktop->locked_view_composed = view_composed_alloc();
     desktop->locked_view = desktop_locked_alloc();
-    view_composed_tie_views(
-        desktop->locked_view_composed,
-        desktop->dolphin_view,
-        desktop_locked_get_view(desktop->locked_view));
-    view_composed_top_enable(desktop->locked_view_composed, true);
-
     desktop->lock_menu = desktop_lock_menu_alloc();
     desktop->debug_view = desktop_debug_alloc();
     desktop->first_start_view = desktop_first_start_alloc();
     desktop->hw_mismatch_popup = popup_alloc();
     desktop->code_input = code_input_alloc();
+    desktop->main_view_stack = view_stack_alloc();
+    desktop->main_view = desktop_main_alloc();
+    View* dolphin_view = animation_manager_get_animation_view(desktop->animation_manager);
+    view_stack_add_view(desktop->main_view_stack, desktop_main_get_view(desktop->main_view));
+    view_stack_add_view(desktop->main_view_stack, dolphin_view);
+    view_stack_add_view(desktop->main_view_stack, desktop_locked_get_view(desktop->locked_view));
 
     view_dispatcher_add_view(
-        desktop->view_dispatcher,
-        DesktopViewMain,
-        view_composed_get_view(desktop->main_view_composed));
+        desktop->view_dispatcher, DesktopViewMain, view_stack_get_view(desktop->main_view_stack));
     view_dispatcher_add_view(
         desktop->view_dispatcher,
         DesktopViewLockMenu,
         desktop_lock_menu_get_view(desktop->lock_menu));
     view_dispatcher_add_view(
         desktop->view_dispatcher, DesktopViewDebug, desktop_debug_get_view(desktop->debug_view));
-    view_dispatcher_add_view(
-        desktop->view_dispatcher,
-        DesktopViewLocked,
-        view_composed_get_view(desktop->locked_view_composed));
     view_dispatcher_add_view(
         desktop->view_dispatcher,
         DesktopViewFirstStart,
@@ -123,8 +105,8 @@ void desktop_free(Desktop* desktop) {
     scene_manager_free(desktop->scene_manager);
 
     animation_manager_free(desktop->animation_manager);
-    view_composed_free(desktop->main_view_composed);
-    view_composed_free(desktop->locked_view_composed);
+    view_stack_free(desktop->main_view_stack);
+    view_stack_free(desktop->locked_view_stack);
     desktop_main_free(desktop->main_view);
     desktop_lock_menu_free(desktop->lock_menu);
     desktop_locked_free(desktop->locked_view);
@@ -163,15 +145,14 @@ int32_t desktop_srv(void* p) {
         SAVE_DESKTOP_SETTINGS(&desktop->settings);
     }
 
-    scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
-
     if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) {
         furi_hal_usb_disable();
         scene_manager_set_scene_state(
-            desktop->scene_manager, DesktopSceneLocked, DesktopLockedWithPin);
-        scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked);
+            desktop->scene_manager, DesktopSceneMain, DesktopMainSceneStateLockedWithPin);
     }
 
+    scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
+
     if(desktop_is_first_start()) {
         scene_manager_next_scene(desktop->scene_manager, DesktopSceneFirstStart);
     }

+ 10 - 23
applications/desktop/desktop_i.h

@@ -1,31 +1,21 @@
 #pragma once
 
-#include "cmsis_os2.h"
 #include "desktop.h"
-
 #include "animations/animation_manager.h"
-#include "gui/view_composed.h"
-#include <furi.h>
-#include <furi_hal.h>
-
-#include <gui/gui.h>
-#include <gui/view_dispatcher.h>
-#include <gui/modules/popup.h>
-#include <gui/modules/code_input.h>
-#include <gui/scene_manager.h>
-#include <assets_icons.h>
-#include <storage/storage.h>
-#include <power/power_service/power.h>
-
 #include "views/desktop_main.h"
 #include "views/desktop_first_start.h"
 #include "views/desktop_lock_menu.h"
 #include "views/desktop_locked.h"
 #include "views/desktop_debug.h"
-
-#include "scenes/desktop_scene.h"
 #include "desktop/desktop_settings/desktop_settings.h"
-#include <gui/icon.h>
+
+#include <furi.h>
+#include <gui/gui.h>
+#include <gui/view_stack.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/code_input.h>
+#include <gui/scene_manager.h>
 
 #define STATUS_BAR_Y_SHIFT 13
 
@@ -54,12 +44,11 @@ struct Desktop {
     DesktopDebugView* debug_view;
     CodeInput* code_input;
 
-    View* dolphin_view;
     DesktopMainView* main_view;
     DesktopLockedView* locked_view;
 
-    ViewComposed* main_view_composed;
-    ViewComposed* locked_view_composed;
+    ViewStack* main_view_stack;
+    ViewStack* locked_view_stack;
 
     DesktopSettings settings;
     PinCode pincode_buffer;
@@ -69,8 +58,6 @@ struct Desktop {
     AnimationManager* animation_manager;
     osSemaphoreId_t unload_animation_semaphore;
     FuriPubSubSubscription* app_start_stop_subscription;
-
-    char* text_buffer;
 };
 
 Desktop* desktop_alloc();

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

@@ -1,4 +1,6 @@
 #include "desktop_settings_app.h"
+#include <furi.h>
+#include "scenes/desktop_settings_scene.h"
 
 static bool desktop_settings_custom_event_callback(void* context, uint32_t event) {
     furi_assert(context);

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

@@ -1,8 +1,6 @@
 #pragma once
 
-#include <furi.h>
 #include <gui/gui.h>
-#include <gui/view.h>
 #include <gui/view_dispatcher.h>
 #include <gui/scene_manager.h>
 #include <gui/modules/submenu.h>
@@ -10,8 +8,6 @@
 
 #include "desktop_settings.h"
 
-#include "scenes/desktop_settings_scene.h"
-
 typedef enum {
     CodeEventsSetPin,
     CodeEventsChangePin,

+ 1 - 1
applications/desktop/desktop_settings/scenes/desktop_settings_scene_favorite.c

@@ -1,6 +1,6 @@
 #include "../desktop_settings_app.h"
 #include "applications.h"
-#include "desktop/desktop_settings/desktop_settings.h"
+#include "desktop_settings_scene.h"
 
 static void desktop_settings_scene_favorite_submenu_callback(void* context, uint32_t index) {
     DesktopSettingsApp* app = context;

+ 1 - 1
applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_input.c

@@ -1,5 +1,5 @@
 #include "../desktop_settings_app.h"
-#include "desktop/desktop_settings/desktop_settings.h"
+#include "desktop_settings_scene.h"
 
 #define SCENE_EXIT_EVENT (0U)
 

+ 1 - 0
applications/desktop/desktop_settings/scenes/desktop_settings_scene_pincode_menu.c

@@ -1,5 +1,6 @@
 #include "../desktop_settings_app.h"
 #include "applications.h"
+#include "desktop_settings_scene.h"
 
 static void desktop_settings_scene_pincode_menu_submenu_callback(void* context, uint32_t index) {
     DesktopSettingsApp* app = context;

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

@@ -1,5 +1,6 @@
 #include "../desktop_settings_app.h"
 #include "applications.h"
+#include "desktop_settings_scene.h"
 
 enum DesktopSettingsStartSubmenuIndex {
     DesktopSettingsStartSubmenuIndexFavorite,

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

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

+ 7 - 4
applications/desktop/scenes/desktop_scene_debug.c

@@ -1,8 +1,11 @@
-#include "../desktop_i.h"
-#include "../views/desktop_debug.h"
+
 #include <dolphin/dolphin.h>
 #include <dolphin/helpers/dolphin_deed.h>
 
+#include "../desktop_i.h"
+#include "../views/desktop_debug.h"
+#include "desktop_scene.h"
+
 void desktop_scene_debug_callback(DesktopEvent event, void* context) {
     Desktop* desktop = (Desktop*)context;
     view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
@@ -31,13 +34,13 @@ bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) {
             break;
 
         case DesktopDebugEventDeed:
-            dolphin_deed(dolphin, DolphinDeedIButtonEmulate);
+            dolphin_deed(dolphin, DolphinDeedIbuttonEmulate);
             desktop_debug_get_dolphin_data(desktop->debug_view);
             consumed = true;
             break;
 
         case DesktopDebugEventWrongDeed:
-            dolphin_deed(dolphin, DolphinDeedWrong);
+            dolphin_deed(dolphin, DolphinDeedIbuttonRead);
             desktop_debug_get_dolphin_data(desktop->debug_view);
             consumed = true;
             break;

+ 2 - 0
applications/desktop/scenes/desktop_scene_fault.c

@@ -1,3 +1,5 @@
+#include <furi_hal.h>
+
 #include "../desktop_i.h"
 
 #define DesktopFaultEventExit 0x00FF00FF

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

@@ -1,3 +1,6 @@
+#include <power/power_service/power.h>
+#include <storage/storage.h>
+
 #include "../desktop_i.h"
 #include "../views/desktop_first_start.h"
 #include "../views/desktop_events.h"

+ 15 - 9
applications/desktop/scenes/desktop_scene_hw_mismatch.c

@@ -1,6 +1,9 @@
-#include "../desktop_i.h"
+#include <gui/scene_manager.h>
 #include <furi_hal_version.h>
 
+#include "desktop_scene.h"
+#include "../desktop_i.h"
+
 #define HW_MISMATCH_BACK_EVENT (0UL)
 
 void desktop_scene_hw_mismatch_callback(void* context) {
@@ -11,11 +14,14 @@ void desktop_scene_hw_mismatch_callback(void* context) {
 void desktop_scene_hw_mismatch_on_enter(void* context) {
     Desktop* desktop = (Desktop*)context;
     furi_assert(desktop);
-    furi_assert(!desktop->text_buffer);
     Popup* popup = desktop->hw_mismatch_popup;
-    desktop->text_buffer = furi_alloc(256);
+
+    char* text_buffer = furi_alloc(256);
+    scene_manager_set_scene_state(
+        desktop->scene_manager, DesktopSceneHwMismatch, (uint32_t)text_buffer);
+
     snprintf(
-        desktop->text_buffer,
+        text_buffer,
         256,
         "HW target: %d\nFW target: %d",
         furi_hal_version_get_hw_target(),
@@ -23,8 +29,7 @@ void desktop_scene_hw_mismatch_on_enter(void* context) {
     popup_set_context(popup, desktop);
     popup_set_header(
         popup, "!!!! HW Mismatch !!!!", 60, 14 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
-    popup_set_text(
-        popup, desktop->text_buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
+    popup_set_text(popup, text_buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
     popup_set_callback(popup, desktop_scene_hw_mismatch_callback);
     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewHwMismatch);
 }
@@ -50,12 +55,13 @@ bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event)
 void desktop_scene_hw_mismatch_on_exit(void* context) {
     Desktop* desktop = (Desktop*)context;
     furi_assert(desktop);
-    furi_assert(desktop->text_buffer);
     Popup* popup = desktop->hw_mismatch_popup;
     popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
     popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
     popup_set_callback(popup, NULL);
     popup_set_context(popup, NULL);
-    free(desktop->text_buffer);
-    desktop->text_buffer = NULL;
+    char* text_buffer =
+        (char*)scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneHwMismatch);
+    free(text_buffer);
+    scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneHwMismatch, 0);
 }

+ 7 - 0
applications/desktop/scenes/desktop_scene_i.h

@@ -0,0 +1,7 @@
+#pragma once
+
+typedef enum {
+    DesktopMainSceneStateUnlocked,
+    DesktopMainSceneStateLockedWithPin,
+    DesktopMainSceneStateLockedNoPin,
+} DesktopMainSceneState;

+ 9 - 8
applications/desktop/scenes/desktop_scene_lock_menu.c

@@ -1,8 +1,11 @@
-#include "../desktop_i.h"
-#include "../views/desktop_lock_menu.h"
 #include <toolbox/saved_struct.h>
 #include <stdbool.h>
 
+#include "../desktop_i.h"
+#include "../views/desktop_lock_menu.h"
+#include "desktop_scene_i.h"
+#include "desktop_scene.h"
+
 void desktop_scene_lock_menu_callback(DesktopEvent event, void* context) {
     Desktop* desktop = (Desktop*)context;
     view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
@@ -26,17 +29,15 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) {
         switch(event.event) {
         case DesktopLockMenuEventLock:
             scene_manager_set_scene_state(
-                desktop->scene_manager, DesktopSceneLocked, DesktopLockedNoPin);
-            scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked);
+                desktop->scene_manager, DesktopSceneMain, DesktopMainSceneStateLockedNoPin);
+            scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
             consumed = true;
             break;
         case DesktopLockMenuEventPinLock:
             if(desktop->settings.pincode.length > 0) {
-                furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
-                furi_hal_usb_disable();
                 scene_manager_set_scene_state(
-                    desktop->scene_manager, DesktopSceneLocked, DesktopLockedWithPin);
-                scene_manager_next_scene(desktop->scene_manager, DesktopSceneLocked);
+                    desktop->scene_manager, DesktopSceneMain, DesktopMainSceneStateLockedWithPin);
+                scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
             } else {
                 scene_manager_next_scene(desktop->scene_manager, DesktopScenePinSetup);
             }

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

@@ -1,101 +0,0 @@
-#include "../desktop_i.h"
-#include "../views/desktop_locked.h"
-#include "desktop/views/desktop_main.h"
-
-void desktop_scene_locked_callback(DesktopEvent event, void* context) {
-    Desktop* desktop = (Desktop*)context;
-    view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
-}
-
-static void desktop_scene_locked_new_idle_animation_callback(void* context) {
-    furi_assert(context);
-    Desktop* desktop = context;
-    view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopLockedEventCheckAnimation);
-}
-
-void desktop_scene_locked_on_enter(void* context) {
-    Desktop* desktop = (Desktop*)context;
-    DesktopLockedView* locked_view = desktop->locked_view;
-
-    animation_manager_set_new_idle_callback(
-        desktop->animation_manager, desktop_scene_locked_new_idle_animation_callback);
-    desktop_locked_set_callback(locked_view, desktop_scene_locked_callback, desktop);
-    desktop_locked_reset_door_pos(locked_view);
-    desktop_locked_update_hint_timeout(locked_view);
-
-    uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopViewLocked);
-
-    desktop_locked_with_pin(desktop->locked_view, state == DesktopLockedWithPin);
-
-    view_port_enabled_set(desktop->lock_viewport, true);
-    osTimerStart(locked_view->timer, osKernelGetTickFreq() / 16);
-
-    view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLocked);
-}
-
-static bool desktop_scene_locked_check_pin(Desktop* desktop, DesktopEvent event) {
-    bool match = false;
-
-    size_t length = desktop->pincode_buffer.length;
-    length = code_input_push(desktop->pincode_buffer.data, length, event);
-    desktop->pincode_buffer.length = length;
-
-    match = code_input_compare(
-        desktop->pincode_buffer.data,
-        length,
-        desktop->settings.pincode.data,
-        desktop->settings.pincode.length);
-
-    if(match) {
-        desktop->pincode_buffer.length = 0;
-        furi_hal_usb_enable();
-        furi_hal_rtc_reset_flag(FuriHalRtcFlagLock);
-        desktop_main_unlocked(desktop->main_view);
-    }
-
-    return match;
-}
-
-bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) {
-    Desktop* desktop = (Desktop*)context;
-
-    bool consumed = false;
-    if(event.type == SceneManagerEventTypeCustom) {
-        switch(event.event) {
-        case DesktopLockedEventUnlock:
-            scene_manager_set_scene_state(
-                desktop->scene_manager, DesktopSceneMain, DesktopMainEventUnlocked);
-            scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
-            consumed = true;
-            break;
-        case DesktopLockedEventUpdate:
-            desktop_locked_manage_redraw(desktop->locked_view);
-            consumed = true;
-            break;
-        case DesktopLockedEventInputReset:
-            desktop->pincode_buffer.length = 0;
-            break;
-        case DesktopLockedEventCheckAnimation:
-            animation_manager_check_blocking_process(desktop->animation_manager);
-            consumed = true;
-            break;
-        default:
-            if(desktop_scene_locked_check_pin(desktop, event.event)) {
-                scene_manager_set_scene_state(
-                    desktop->scene_manager, DesktopSceneMain, DesktopMainEventUnlocked);
-                scene_manager_next_scene(desktop->scene_manager, DesktopSceneMain);
-                consumed = true;
-            }
-            break;
-        }
-    }
-
-    return consumed;
-}
-
-void desktop_scene_locked_on_exit(void* context) {
-    Desktop* desktop = (Desktop*)context;
-    animation_manager_set_new_idle_callback(desktop->animation_manager, NULL);
-    desktop_locked_reset_counter(desktop->locked_view);
-    osTimerStop(desktop->locked_view->timer);
-}

+ 49 - 25
applications/desktop/scenes/desktop_scene_main.c

@@ -1,17 +1,14 @@
-#include "../desktop_i.h"
-#include "../views/desktop_main.h"
-#include "applications.h"
-#include "assets_icons.h"
-#include "cmsis_os2.h"
-#include "desktop/desktop.h"
-#include "desktop/views/desktop_events.h"
-#include "dolphin/dolphin.h"
-#include "furi/pubsub.h"
-#include "furi/record.h"
-#include "furi/thread.h"
-#include "storage/storage_glue.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <applications.h>
+#include <assets_icons.h>
 #include <loader/loader.h>
-#include <m-list.h>
+
+#include "desktop/desktop_i.h"
+#include "desktop/views/desktop_main.h"
+#include "desktop_scene.h"
+#include "desktop_scene_i.h"
+
 #define MAIN_VIEW_DEFAULT (0UL)
 
 static void desktop_scene_main_app_started_callback(const void* message, void* context) {
@@ -81,17 +78,31 @@ void desktop_scene_main_on_enter(void* context) {
         desktop->animation_manager, desktop_scene_main_check_animation_callback);
     animation_manager_set_interact_callback(
         desktop->animation_manager, desktop_scene_main_interact_animation_callback);
+    desktop_locked_set_callback(desktop->locked_view, desktop_scene_main_callback, desktop);
 
     furi_assert(osSemaphoreGetCount(desktop->unload_animation_semaphore) == 0);
+    Loader* loader = furi_record_open("loader");
     desktop->app_start_stop_subscription = furi_pubsub_subscribe(
-        loader_get_pubsub(), desktop_scene_main_app_started_callback, desktop);
+        loader_get_pubsub(loader), desktop_scene_main_app_started_callback, desktop);
+    furi_record_close("loader");
 
     desktop_main_set_callback(main_view, desktop_scene_main_callback, desktop);
-    view_port_enabled_set(desktop->lock_viewport, false);
 
-    if(scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneMain) ==
-       DesktopMainEventUnlocked) {
-        desktop_main_unlocked(desktop->main_view);
+    DesktopMainSceneState state =
+        scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneMain);
+    if(state == DesktopMainSceneStateLockedNoPin) {
+        desktop_locked_lock(desktop->locked_view);
+        view_port_enabled_set(desktop->lock_viewport, true);
+    } else if(state == DesktopMainSceneStateLockedWithPin) {
+        LOAD_DESKTOP_SETTINGS(&desktop->settings);
+        furi_assert(desktop->settings.pincode.length > 0);
+        desktop_locked_lock_pincode(desktop->locked_view, desktop->settings.pincode);
+        view_port_enabled_set(desktop->lock_viewport, true);
+        furi_hal_rtc_set_flag(FuriHalRtcFlagLock);
+        furi_hal_usb_disable();
+    } else {
+        furi_assert(state == DesktopMainSceneStateUnlocked);
+        view_port_enabled_set(desktop->lock_viewport, false);
     }
 
     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain);
@@ -120,22 +131,22 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
 
         case DesktopMainEventOpenArchive:
 #ifdef APP_ARCHIVE
-            animation_manager_unload_and_stall_animation(desktop->animation_manager);
             desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE);
-            animation_manager_load_and_continue_animation(desktop->animation_manager);
 #endif
             consumed = true;
             break;
 
         case DesktopMainEventOpenFavorite:
             LOAD_DESKTOP_SETTINGS(&desktop->settings);
-            animation_manager_unload_and_stall_animation(desktop->animation_manager);
             if(desktop->settings.favorite < FLIPPER_APPS_COUNT) {
-                desktop_switch_to_app(desktop, &FLIPPER_APPS[desktop->settings.favorite]);
+                Loader* loader = furi_record_open("loader");
+                LoaderStatus status =
+                    loader_start(loader, FLIPPER_APPS[desktop->settings.favorite].name, NULL);
+                furi_check(status == LoaderStatusOk);
+                furi_record_close("loader");
             } else {
                 FURI_LOG_E("DesktopSrv", "Can't find favorite application");
             }
-            animation_manager_load_and_continue_animation(desktop->animation_manager);
             consumed = true;
             break;
 
@@ -160,6 +171,18 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
             animation_manager_load_and_continue_animation(desktop->animation_manager);
             consumed = true;
             break;
+        case DesktopMainEventUnlocked:
+            consumed = true;
+            furi_hal_rtc_reset_flag(FuriHalRtcFlagLock);
+            furi_hal_usb_enable();
+            view_port_enabled_set(desktop->lock_viewport, false);
+            scene_manager_set_scene_state(
+                desktop->scene_manager, DesktopSceneMain, DesktopMainSceneStateUnlocked);
+            break;
+        case DesktopMainEventUpdate:
+            desktop_locked_update(desktop->locked_view);
+            consumed = true;
+            break;
 
         default:
             break;
@@ -177,7 +200,9 @@ void desktop_scene_main_on_exit(void* context) {
      * is finished, that's why we can be sure there is no task waiting
      * for start/stop semaphore
      */
-    furi_pubsub_unsubscribe(loader_get_pubsub(), desktop->app_start_stop_subscription);
+    Loader* loader = furi_record_open("loader");
+    furi_pubsub_unsubscribe(loader_get_pubsub(loader), desktop->app_start_stop_subscription);
+    furi_record_close("loader");
     furi_assert(osSemaphoreGetCount(desktop->unload_animation_semaphore) == 0);
 
     animation_manager_set_new_idle_callback(desktop->animation_manager, NULL);
@@ -185,5 +210,4 @@ void desktop_scene_main_on_exit(void* context) {
     animation_manager_set_interact_callback(desktop->animation_manager, NULL);
     animation_manager_set_context(desktop->animation_manager, desktop);
     scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneMain, MAIN_VIEW_DEFAULT);
-    desktop_main_reset_hint(desktop->main_view);
 }

+ 3 - 1
applications/desktop/views/desktop_debug.c

@@ -1,7 +1,9 @@
+#include <toolbox/version.h>
 #include <furi.h>
+#include <furi_hal.h>
+
 #include "../desktop_i.h"
 #include "desktop_debug.h"
-
 #include "dolphin/helpers/dolphin_state.h"
 #include "dolphin/dolphin.h"
 

+ 1 - 6
applications/desktop/views/desktop_debug.h

@@ -1,12 +1,7 @@
 #pragma once
 
-#include <gui/gui_i.h>
+#include <stdint.h>
 #include <gui/view.h>
-#include <gui/canvas.h>
-#include <gui/elements.h>
-#include <furi.h>
-#include <storage/storage.h>
-#include <time.h>
 #include "desktop_events.h"
 
 typedef struct DesktopDebugView DesktopDebugView;

+ 1 - 2
applications/desktop/views/desktop_events.h

@@ -6,6 +6,7 @@ typedef enum {
     DesktopMainEventOpenFavorite,
     DesktopMainEventOpenMenu,
     DesktopMainEventOpenDebug,
+    DesktopMainEventUpdate,
     DesktopMainEventUnlocked,
     DesktopMainEventRightShort,
     DesktopMainEventCheckAnimation,
@@ -14,8 +15,6 @@ typedef enum {
     DesktopMainEventBeforeAppStarted,
     DesktopMainEventAfterAppFinished,
     DesktopLockedEventUnlock,
-    DesktopLockedEventUpdate,
-    DesktopLockedEventInputReset,
     DesktopLockedEventCheckAnimation,
     DesktopLockedEventMax,
     DesktopDebugEventDeed,

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

@@ -1,4 +1,6 @@
 #include <furi.h>
+#include <furi_hal.h>
+#include <gui/elements.h>
 #include "../desktop_i.h"
 #include "desktop_first_start.h"
 

+ 1 - 4
applications/desktop/views/desktop_first_start.h

@@ -1,10 +1,7 @@
 #pragma once
 
-#include <gui/gui_i.h>
 #include <gui/view.h>
-#include <gui/canvas.h>
-#include <gui/elements.h>
-#include <furi.h>
+
 #include "desktop_events.h"
 
 typedef struct DesktopFirstStartView DesktopFirstStartView;

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

@@ -1,4 +1,6 @@
 #include <furi.h>
+#include <gui/elements.h>
+
 #include "../desktop_i.h"
 #include "desktop_lock_menu.h"
 

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

@@ -1,10 +1,6 @@
 #pragma once
 
-#include <gui/gui_i.h>
 #include <gui/view.h>
-#include <gui/canvas.h>
-#include <gui/elements.h>
-#include <furi.h>
 #include "desktop_events.h"
 
 #define HINT_TIMEOUT 2

+ 156 - 128
applications/desktop/views/desktop_locked.c

@@ -1,6 +1,42 @@
+#include "desktop/desktop_settings/desktop_settings.h"
+#include "furi/check.h"
+#include "gui/view.h"
+#include "portmacro.h"
 #include <furi.h>
+#include <gui/gui_i.h>
+#include <gui/elements.h>
 #include "../desktop_i.h"
 #include "desktop_locked.h"
+#include <stdint.h>
+
+#define DOOR_MOVING_INTERVAL_MS (1000 / 16)
+#define UNLOCKED_HINT_TIMEOUT_MS (2000)
+
+struct DesktopLockedView {
+    View* view;
+    DesktopLockedViewCallback callback;
+    void* context;
+
+    TimerHandle_t timer;
+    uint8_t lock_count;
+    uint32_t lock_lastpress;
+
+    PinCode pincode;
+    PinCode pincode_input;
+};
+
+typedef struct {
+    uint32_t hint_icon_expire_at;
+    bool unlocked_hint;
+    bool locked;
+    bool pin_locked;
+
+    int8_t door_left_x;
+    int8_t door_right_x;
+    bool animation_seq_end;
+} DesktopLockedViewModel;
+
+static void desktop_locked_unlock(DesktopLockedView* locked_view);
 
 void desktop_locked_set_callback(
     DesktopLockedView* locked_view,
@@ -12,98 +48,69 @@ void desktop_locked_set_callback(
     locked_view->context = context;
 }
 
-void locked_view_timer_callback(void* context) {
-    DesktopLockedView* locked_view = context;
-    locked_view->callback(DesktopLockedEventUpdate, locked_view->context);
+void locked_view_timer_callback(TimerHandle_t timer) {
+    DesktopLockedView* locked_view = pvTimerGetTimerID(timer);
+    locked_view->callback(DesktopMainEventUpdate, locked_view->context);
 }
 
-void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view) {
-    with_view_model(
-        locked_view->view, (DesktopLockedViewModel * model) {
-            model->hint_expire_at = osKernelGetTickCount() + osKernelGetTickFreq();
-            return true;
-        });
+static void desktop_locked_update_hint_icon_timeout(DesktopLockedView* locked_view) {
+    DesktopLockedViewModel* model = view_get_model(locked_view->view);
+    model->hint_icon_expire_at = osKernelGetTickCount() + osKernelGetTickFreq();
+    view_commit_model(locked_view->view, true);
 }
 
-void desktop_locked_reset_door_pos(DesktopLockedView* locked_view) {
-    with_view_model(
-        locked_view->view, (DesktopLockedViewModel * model) {
-            model->animation_seq_end = false;
-            model->door_left_x = DOOR_L_POS;
-            model->door_right_x = DOOR_R_POS;
-            return true;
-        });
+static void desktop_locked_reset_door_pos(DesktopLockedView* locked_view) {
+    DesktopLockedViewModel* model = view_get_model(locked_view->view);
+    model->animation_seq_end = false;
+    model->door_left_x = DOOR_L_POS;
+    model->door_right_x = DOOR_R_POS;
+    view_commit_model(locked_view->view, 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;
+void desktop_locked_update(DesktopLockedView* locked_view) {
+    bool stop_timer = false;
 
-            if(!model->animation_seq_end) {
-                model->door_left_x = CLAMP(model->door_left_x + 5, DOOR_L_POS_MAX, DOOR_L_POS);
-                model->door_right_x = CLAMP(model->door_right_x - 5, DOOR_R_POS, DOOR_R_POS_MIN);
-            } else {
-                model->hint_expire_at = !model->hint_expire_at;
-            }
-
-            return true;
-        });
-
-    if(animation_seq_end) {
-        osTimerStop(locked_view->timer);
+    DesktopLockedViewModel* model = view_get_model(locked_view->view);
+    if(model->locked) {
+        if(model->door_left_x != DOOR_L_POS_MAX) {
+            model->door_left_x = CLAMP(model->door_left_x + 5, DOOR_L_POS_MAX, DOOR_L_POS);
+            model->door_right_x = CLAMP(model->door_right_x - 5, DOOR_R_POS, DOOR_R_POS_MIN);
+        } else {
+            model->animation_seq_end = true;
+        }
+        stop_timer = model->animation_seq_end;
+    } else {
+        model->unlocked_hint = false;
+        stop_timer = true;
     }
-}
-
-void desktop_locked_reset_counter(DesktopLockedView* locked_view) {
-    locked_view->lock_count = 0;
-    locked_view->lock_lastpress = 0;
+    view_commit_model(locked_view->view, true);
 
-    with_view_model(
-        locked_view->view, (DesktopLockedViewModel * model) {
-            model->hint_expire_at = 0;
-            return true;
-        });
-}
-
-void desktop_locked_with_pin(DesktopLockedView* locked_view, bool locked) {
-    with_view_model(
-        locked_view->view, (DesktopLockedViewModel * model) {
-            model->pin_lock = locked;
-            return true;
-        });
+    if(stop_timer) {
+        xTimerStop(locked_view->timer, portMAX_DELAY);
+    }
 }
 
-void desktop_locked_render(Canvas* canvas, void* model) {
+void desktop_locked_draw(Canvas* canvas, void* model) {
     DesktopLockedViewModel* m = model;
     uint32_t now = osKernelGetTickCount();
     canvas_set_color(canvas, ColorBlack);
 
-    if(!m->animation_seq_end) {
-        canvas_draw_icon(canvas, m->door_left_x, 0 + STATUS_BAR_Y_SHIFT, &I_DoorLeft_70x55);
-        canvas_draw_icon(canvas, m->door_right_x, 0 + STATUS_BAR_Y_SHIFT, &I_DoorRight_70x55);
-    }
-
-    if(m->animation && m->animation_seq_end) {
-        if(m->status_bar_background_black) {
-            canvas_draw_box(canvas, 0, 0, GUI_STATUS_BAR_WIDTH, GUI_STATUS_BAR_HEIGHT);
-        }
-        canvas_draw_icon_animation(canvas, 0, 0 + STATUS_BAR_Y_SHIFT, m->animation);
-    }
-
-    if(now < m->hint_expire_at) {
+    if(m->locked) {
         if(!m->animation_seq_end) {
+            canvas_draw_icon(canvas, m->door_left_x, 0 + STATUS_BAR_Y_SHIFT, &I_DoorLeft_70x55);
+            canvas_draw_icon(canvas, m->door_right_x, 0 + STATUS_BAR_Y_SHIFT, &I_DoorRight_70x55);
             canvas_set_font(canvas, FontPrimary);
             elements_multiline_text_framed(canvas, 42, 30 + STATUS_BAR_Y_SHIFT, "Locked");
-
-        } else if(!m->pin_lock) {
+        } else if((now < m->hint_icon_expire_at) && !m->pin_locked) {
             canvas_set_font(canvas, FontSecondary);
             canvas_draw_icon(canvas, 13, 2 + STATUS_BAR_Y_SHIFT, &I_LockPopup_100x49);
             elements_multiline_text(canvas, 65, 20 + STATUS_BAR_Y_SHIFT, "To unlock\npress:");
         }
+    } else {
+        if(m->unlocked_hint) {
+            canvas_set_font(canvas, FontPrimary);
+            elements_multiline_text_framed(canvas, 42, 30 + STATUS_BAR_Y_SHIFT, "Unlocked");
+        }
     }
 }
 
@@ -116,85 +123,72 @@ bool desktop_locked_input(InputEvent* event, void* context) {
     furi_assert(event);
     furi_assert(context);
     DesktopLockedView* locked_view = context;
-
-    uint32_t press_time = 0;
+    bool locked = false;
     bool locked_with_pin = false;
+    uint32_t press_time = xTaskGetTickCount();
+
+    {
+        DesktopLockedViewModel* model = view_get_model(locked_view->view);
+        bool changed = false;
+        locked = model->locked;
+        locked_with_pin = model->pin_locked;
+        if(!locked && model->unlocked_hint && event->type == InputTypePress) {
+            model->unlocked_hint = false;
+            changed = true;
+        }
+        view_commit_model(locked_view->view, changed);
+    }
 
-    with_view_model(
-        locked_view->view, (DesktopLockedViewModel * model) {
-            locked_with_pin = model->pin_lock;
-            return false;
-        });
-
-    if(event->type == InputTypeShort) {
-        if(locked_with_pin) {
-            press_time = osKernelGetTickCount();
+    if(!locked || (event->type != InputTypeShort)) {
+        return locked;
+    }
 
-            if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT * 3) {
-                locked_view->lock_lastpress = press_time;
-                locked_view->callback(DesktopLockedEventInputReset, locked_view->context);
-            }
+    if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) {
+        locked_view->lock_lastpress = press_time;
+        locked_view->lock_count = 0;
+        locked_view->pincode_input.length = 0;
+    }
 
-            locked_view->callback(event->key, locked_view->context);
-        } else {
-            desktop_locked_update_hint_timeout(locked_view);
-
-            if(event->key == InputKeyBack) {
-                press_time = osKernelGetTickCount();
-                // check if pressed sequentially
-                if(press_time - locked_view->lock_lastpress < UNLOCK_RST_TIMEOUT) {
-                    locked_view->lock_lastpress = press_time;
-                    locked_view->lock_count++;
-                }
-
-                if(locked_view->lock_count == UNLOCK_CNT) {
-                    locked_view->lock_count = 0;
-                    locked_view->callback(DesktopLockedEventUnlock, locked_view->context);
-                }
-            }
+    if(locked_with_pin) {
+        locked_view->pincode_input.length = code_input_push(
+            locked_view->pincode_input.data, locked_view->pincode_input.length, event->key);
+        bool match = code_input_compare(
+            locked_view->pincode_input.data,
+            locked_view->pincode_input.length,
+            locked_view->pincode.data,
+            locked_view->pincode.length);
+
+        if(match) {
+            desktop_locked_unlock(locked_view);
         }
-
-        if(press_time - locked_view->lock_lastpress > UNLOCK_RST_TIMEOUT) {
+    } else {
+        if(event->key == InputKeyBack) {
             locked_view->lock_lastpress = press_time;
+            locked_view->lock_count++;
+            if(locked_view->lock_count == UNLOCK_CNT) {
+                desktop_locked_unlock(locked_view);
+            }
+        } else {
+            desktop_locked_update_hint_icon_timeout(locked_view);
             locked_view->lock_count = 0;
         }
     }
-    // All events consumed
-    return true;
-}
 
-void desktop_locked_enter(void* context) {
-    DesktopLockedView* locked_view = context;
+    locked_view->lock_lastpress = press_time;
 
-    with_view_model(
-        locked_view->view, (DesktopLockedViewModel * model) {
-            if(model->animation) icon_animation_start(model->animation);
-            return false;
-        });
-}
-
-void desktop_locked_exit(void* context) {
-    DesktopLockedView* locked_view = context;
-
-    with_view_model(
-        locked_view->view, (DesktopLockedViewModel * model) {
-            if(model->animation) icon_animation_stop(model->animation);
-            return false;
-        });
+    return locked;
 }
 
 DesktopLockedView* desktop_locked_alloc() {
     DesktopLockedView* locked_view = furi_alloc(sizeof(DesktopLockedView));
     locked_view->view = view_alloc();
     locked_view->timer =
-        osTimerNew(locked_view_timer_callback, osTimerPeriodic, locked_view, NULL);
+        xTimerCreate("Locked view", 1000 / 16, pdTRUE, locked_view, locked_view_timer_callback);
 
     view_allocate_model(locked_view->view, ViewModelTypeLocking, sizeof(DesktopLockedViewModel));
     view_set_context(locked_view->view, locked_view);
-    view_set_draw_callback(locked_view->view, (ViewDrawCallback)desktop_locked_render);
+    view_set_draw_callback(locked_view->view, (ViewDrawCallback)desktop_locked_draw);
     view_set_input_callback(locked_view->view, desktop_locked_input);
-    view_set_enter_callback(locked_view->view, desktop_locked_enter);
-    view_set_exit_callback(locked_view->view, desktop_locked_exit);
 
     return locked_view;
 }
@@ -205,3 +199,37 @@ void desktop_locked_free(DesktopLockedView* locked_view) {
     view_free(locked_view->view);
     free(locked_view);
 }
+
+void desktop_locked_lock(DesktopLockedView* locked_view) {
+    locked_view->pincode.length = 0;
+    DesktopLockedViewModel* model = view_get_model(locked_view->view);
+    model->locked = true;
+    model->pin_locked = false;
+    view_commit_model(locked_view->view, true);
+    desktop_locked_reset_door_pos(locked_view);
+    xTimerChangePeriod(locked_view->timer, DOOR_MOVING_INTERVAL_MS, portMAX_DELAY);
+}
+
+void desktop_locked_lock_pincode(DesktopLockedView* locked_view, PinCode pincode) {
+    locked_view->pincode = pincode;
+    locked_view->pincode_input.length = 0;
+    DesktopLockedViewModel* model = view_get_model(locked_view->view);
+    model->locked = true;
+    model->pin_locked = true;
+    view_commit_model(locked_view->view, true);
+    desktop_locked_reset_door_pos(locked_view);
+    xTimerChangePeriod(locked_view->timer, DOOR_MOVING_INTERVAL_MS, portMAX_DELAY);
+}
+
+static void desktop_locked_unlock(DesktopLockedView* locked_view) {
+    furi_assert(locked_view);
+
+    locked_view->lock_count = 0;
+    DesktopLockedViewModel* model = view_get_model(locked_view->view);
+    model->locked = false;
+    model->pin_locked = false;
+    model->unlocked_hint = true;
+    view_commit_model(locked_view->view, true);
+    locked_view->callback(DesktopMainEventUnlocked, locked_view->context);
+    xTimerChangePeriod(locked_view->timer, UNLOCKED_HINT_TIMEOUT_MS, portMAX_DELAY);
+}

+ 6 - 33
applications/desktop/views/desktop_locked.h

@@ -1,14 +1,11 @@
 #pragma once
 
-#include <gui/gui_i.h>
+#include <desktop/desktop_settings/desktop_settings.h>
 #include <gui/view.h>
-#include <gui/canvas.h>
-#include <gui/elements.h>
-#include <furi.h>
 #include "desktop_events.h"
 
 #define UNLOCK_RST_TIMEOUT 300
-#define UNLOCK_CNT 2 // 3 actually
+#define UNLOCK_CNT 3
 
 #define DOOR_L_POS -57
 #define DOOR_L_POS_MAX 0
@@ -24,40 +21,16 @@ typedef struct DesktopLockedView DesktopLockedView;
 
 typedef void (*DesktopLockedViewCallback)(DesktopEvent event, void* context);
 
-struct DesktopLockedView {
-    View* view;
-    DesktopLockedViewCallback callback;
-    void* context;
-
-    osTimerId_t timer;
-    uint8_t lock_count;
-    uint32_t lock_lastpress;
-};
-
-typedef struct {
-    IconAnimation* animation;
-    uint32_t hint_expire_at;
-
-    bool status_bar_background_black;
-    uint8_t scene_num;
-    int8_t door_left_x;
-    int8_t door_right_x;
-    bool animation_seq_end;
-
-    bool pin_lock;
-} DesktopLockedViewModel;
-
 void desktop_locked_set_callback(
     DesktopLockedView* locked_view,
     DesktopLockedViewCallback callback,
     void* context);
 
-void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view);
-void desktop_locked_reset_counter(DesktopLockedView* locked_view);
-void desktop_locked_reset_door_pos(DesktopLockedView* locked_view);
-void desktop_locked_manage_redraw(DesktopLockedView* locked_view);
+void desktop_locked_update(DesktopLockedView* locked_view);
 
 View* desktop_locked_get_view(DesktopLockedView* locked_view);
 DesktopLockedView* desktop_locked_alloc();
 void desktop_locked_free(DesktopLockedView* locked_view);
-void desktop_locked_with_pin(DesktopLockedView* lock_menu, bool locked);
+
+void desktop_locked_lock_pincode(DesktopLockedView* locked_view, PinCode pincode);
+void desktop_locked_lock(DesktopLockedView* locked_view);

+ 14 - 93
applications/desktop/views/desktop_main.c

@@ -1,13 +1,19 @@
-#include "dolphin/dolphin.h"
-#include "furi/record.h"
-#include "gui/canvas.h"
-#include "gui/view.h"
-#include "gui/view_composed.h"
-#include "input/input.h"
+#include <gui/gui_i.h>
+#include <gui/view.h>
+#include <gui/elements.h>
+#include <gui/canvas.h>
 #include <furi.h>
+#include <input/input.h>
+#include <dolphin/dolphin.h>
+
 #include "../desktop_i.h"
 #include "desktop_main.h"
-//#include "../animations/views/bubble_animation_view.h"
+
+struct DesktopMainView {
+    View* view;
+    DesktopMainViewCallback callback;
+    void* context;
+};
 
 void desktop_main_set_callback(
     DesktopMainView* main_view,
@@ -19,59 +25,6 @@ void desktop_main_set_callback(
     main_view->context = context;
 }
 
-void desktop_main_reset_hint(DesktopMainView* main_view) {
-    with_view_model(
-        main_view->view, (DesktopMainViewModel * model) {
-            model->hint_expire_at = 0;
-            return true;
-        });
-}
-
-void desktop_main_switch_dolphin_animation(
-    DesktopMainView* main_view,
-    const Icon* icon,
-    bool status_bar_background_black) {
-    with_view_model(
-        main_view->view, (DesktopMainViewModel * model) {
-            if(model->animation) icon_animation_free(model->animation);
-            model->animation = icon_animation_alloc(icon);
-            view_tie_icon_animation(main_view->view, model->animation);
-            icon_animation_start(model->animation);
-            model->icon = NULL;
-            model->status_bar_background_black = status_bar_background_black;
-            return true;
-        });
-}
-
-void desktop_main_switch_dolphin_icon(DesktopMainView* main_view, const Icon* icon) {
-    with_view_model(
-        main_view->view, (DesktopMainViewModel * model) {
-            if(model->animation) icon_animation_free(model->animation);
-            model->animation = NULL;
-            model->icon = icon;
-            return true;
-        });
-}
-
-void desktop_main_render(Canvas* canvas, void* model) {
-    DesktopMainViewModel* m = model;
-    uint32_t now = osKernelGetTickCount();
-
-    if(m->status_bar_background_black) {
-        canvas_draw_box(canvas, 0, 0, GUI_STATUS_BAR_WIDTH, GUI_STATUS_BAR_HEIGHT);
-    }
-    if(m->icon) {
-        canvas_draw_icon(canvas, 0, 0 + STATUS_BAR_Y_SHIFT, m->icon);
-    } else if(m->animation) {
-        canvas_draw_icon_animation(canvas, 0, 0 + STATUS_BAR_Y_SHIFT, m->animation);
-    }
-
-    if(now < m->hint_expire_at) {
-        canvas_set_font(canvas, FontPrimary);
-        elements_multiline_text_framed(canvas, 42, 30 + STATUS_BAR_Y_SHIFT, "Unlocked");
-    }
-}
-
 View* desktop_main_get_view(DesktopMainView* main_view) {
     furi_assert(main_view);
     return main_view->view;
@@ -100,40 +53,16 @@ bool desktop_main_input(InputEvent* event, void* context) {
         consumed = true;
     }
 
-    desktop_main_reset_hint(main_view);
-
     return consumed;
 }
 
-void desktop_main_enter(void* context) {
-    DesktopMainView* main_view = context;
-
-    with_view_model(
-        main_view->view, (DesktopMainViewModel * model) {
-            if(model->animation) icon_animation_start(model->animation);
-            return false;
-        });
-}
-
-void desktop_main_exit(void* context) {
-    DesktopMainView* main_view = context;
-    with_view_model(
-        main_view->view, (DesktopMainViewModel * model) {
-            if(model->animation) icon_animation_stop(model->animation);
-            return false;
-        });
-}
-
 DesktopMainView* desktop_main_alloc() {
     DesktopMainView* main_view = furi_alloc(sizeof(DesktopMainView));
 
     main_view->view = view_alloc();
-    view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(DesktopMainViewModel));
+    view_allocate_model(main_view->view, ViewModelTypeLockFree, 1);
     view_set_context(main_view->view, main_view);
-    view_set_draw_callback(main_view->view, (ViewDrawCallback)desktop_main_render);
     view_set_input_callback(main_view->view, desktop_main_input);
-    view_set_enter_callback(main_view->view, desktop_main_enter);
-    view_set_exit_callback(main_view->view, desktop_main_exit);
 
     return main_view;
 }
@@ -143,11 +72,3 @@ void desktop_main_free(DesktopMainView* main_view) {
     view_free(main_view->view);
     free(main_view);
 }
-
-void desktop_main_unlocked(DesktopMainView* main_view) {
-    with_view_model(
-        main_view->view, (DesktopMainViewModel * model) {
-            model->hint_expire_at = osKernelGetTickCount() + osKernelGetTickFreq();
-            return true;
-        });
-}

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

@@ -1,31 +1,12 @@
 #pragma once
 
-#include "gui/view_composed.h"
-#include <gui/gui_i.h>
 #include <gui/view.h>
-#include <gui/canvas.h>
-#include <gui/elements.h>
-#include <furi.h>
 #include "desktop_events.h"
 
 typedef struct DesktopMainView DesktopMainView;
 
 typedef void (*DesktopMainViewCallback)(DesktopEvent event, void* context);
 
-struct DesktopMainView {
-    View* view;
-    DesktopMainViewCallback callback;
-    void* context;
-};
-
-typedef struct {
-    IconAnimation* animation;
-    const Icon* icon;
-    uint8_t scene_num;
-    bool status_bar_background_black;
-    uint32_t hint_expire_at;
-} DesktopMainViewModel;
-
 void desktop_main_set_callback(
     DesktopMainView* main_view,
     DesktopMainViewCallback callback,
@@ -34,10 +15,3 @@ void desktop_main_set_callback(
 View* desktop_main_get_view(DesktopMainView* main_view);
 DesktopMainView* desktop_main_alloc();
 void desktop_main_free(DesktopMainView* main_view);
-void desktop_main_switch_dolphin_animation(
-    DesktopMainView* main_view,
-    const Icon* icon,
-    bool status_bar_background_black);
-void desktop_main_unlocked(DesktopMainView* main_view);
-void desktop_main_reset_hint(DesktopMainView* main_view);
-void desktop_main_switch_dolphin_icon(DesktopMainView* main_view, const Icon* icon);

+ 94 - 28
applications/dolphin/dolphin.c

@@ -1,13 +1,18 @@
 #include "dolphin/dolphin.h"
-#include "desktop/desktop.h"
 #include "dolphin/helpers/dolphin_state.h"
 #include "dolphin_i.h"
-#include "furi/pubsub.h"
-#include "sys/_stdint.h"
+#include "portmacro.h"
+#include "projdefs.h"
+#include <furi_hal.h>
+#include <stdint.h>
 #include <furi.h>
-#define DOLPHIN_TIMEGATE 86400 // one day
 #define DOLPHIN_LOCK_EVENT_FLAG (0x1)
 
+#define TAG "Dolphin"
+#define HOURS_IN_TICKS(x) ((x)*60 * 60 * 1000)
+
+static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin);
+
 void dolphin_deed(Dolphin* dolphin, DolphinDeed deed) {
     furi_assert(dolphin);
     DolphinEvent event;
@@ -39,12 +44,51 @@ void dolphin_flush(Dolphin* dolphin) {
     dolphin_event_send_wait(dolphin, &event);
 }
 
+void dolphin_butthurt_timer_callback(TimerHandle_t xTimer) {
+    Dolphin* dolphin = pvTimerGetTimerID(xTimer);
+    furi_assert(dolphin);
+
+    DolphinEvent event;
+    event.type = DolphinEventTypeIncreaseButthurt;
+    dolphin_event_send_async(dolphin, &event);
+}
+
+void dolphin_flush_timer_callback(TimerHandle_t xTimer) {
+    Dolphin* dolphin = pvTimerGetTimerID(xTimer);
+    furi_assert(dolphin);
+
+    DolphinEvent event;
+    event.type = DolphinEventTypeFlush;
+    dolphin_event_send_async(dolphin, &event);
+}
+
+void dolphin_clear_limits_timer_callback(TimerHandle_t xTimer) {
+    Dolphin* dolphin = pvTimerGetTimerID(xTimer);
+    furi_assert(dolphin);
+
+    xTimerChangePeriod(dolphin->clear_limits_timer, HOURS_IN_TICKS(24), portMAX_DELAY);
+
+    DolphinEvent event;
+    event.type = DolphinEventTypeClearLimits;
+    dolphin_event_send_async(dolphin, &event);
+}
+
 Dolphin* dolphin_alloc() {
     Dolphin* dolphin = furi_alloc(sizeof(Dolphin));
 
     dolphin->state = dolphin_state_alloc();
     dolphin->event_queue = osMessageQueueNew(8, sizeof(DolphinEvent), NULL);
     dolphin->pubsub = furi_pubsub_alloc();
+    dolphin->butthurt_timer = xTimerCreate(
+        "Butthurt timer", HOURS_IN_TICKS(2 * 24), pdTRUE, dolphin, dolphin_butthurt_timer_callback);
+    dolphin->flush_timer =
+        xTimerCreate("Flush timer", 30 * 1000, pdFALSE, dolphin, dolphin_flush_timer_callback);
+    dolphin->clear_limits_timer = xTimerCreate(
+        "Clear limits timer",
+        HOURS_IN_TICKS(24),
+        pdTRUE,
+        dolphin,
+        dolphin_clear_limits_timer_callback);
 
     return dolphin;
 }
@@ -83,50 +127,72 @@ void dolphin_event_release(Dolphin* dolphin, DolphinEvent* event) {
     }
 }
 
-static void dolphin_check_butthurt(DolphinState* state) {
-    furi_assert(state);
-    float diff_time = difftime(state->data.timestamp, dolphin_state_timestamp());
-
-    if((fabs(diff_time)) > DOLPHIN_TIMEGATE) {
-        dolphin_state_butthurted(state);
-    }
-}
-
 FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin) {
     return dolphin->pubsub;
 }
 
+static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin) {
+    furi_assert(dolphin);
+    TickType_t now_ticks = xTaskGetTickCount();
+    TickType_t timer_expires_at = xTimerGetExpiryTime(dolphin->clear_limits_timer);
+
+    if((timer_expires_at - now_ticks) > HOURS_IN_TICKS(0.1)) {
+        FuriHalRtcDateTime date;
+        furi_hal_rtc_get_datetime(&date);
+        TickType_t now_time_in_ms = ((date.hour * 60 + date.minute) * 60 + date.second) * 1000;
+        TickType_t time_to_clear_limits = 0;
+
+        if(date.hour < 5) {
+            time_to_clear_limits = HOURS_IN_TICKS(5) - now_time_in_ms;
+        } else {
+            time_to_clear_limits = HOURS_IN_TICKS(24 + 5) - now_time_in_ms;
+        }
+
+        xTimerChangePeriod(dolphin->clear_limits_timer, time_to_clear_limits, portMAX_DELAY);
+    }
+}
+
 int32_t dolphin_srv(void* p) {
     Dolphin* dolphin = dolphin_alloc();
     furi_record_create("dolphin", dolphin);
 
     dolphin_state_load(dolphin->state);
+    xTimerReset(dolphin->butthurt_timer, portMAX_DELAY);
+    dolphin_update_clear_limits_timer_period(dolphin);
+    xTimerReset(dolphin->clear_limits_timer, portMAX_DELAY);
 
     DolphinEvent event;
     while(1) {
-        if(osMessageQueueGet(dolphin->event_queue, &event, NULL, 60000) == osOK) {
+        if(osMessageQueueGet(dolphin->event_queue, &event, NULL, HOURS_IN_TICKS(1)) == osOK) {
             if(event.type == DolphinEventTypeDeed) {
-                if(dolphin_state_on_deed(dolphin->state, event.deed)) {
-                    DolphinPubsubEvent event = DolphinPubsubEventUpdate;
-                    furi_pubsub_publish(dolphin->pubsub, &event);
-                }
+                dolphin_state_on_deed(dolphin->state, event.deed);
+                DolphinPubsubEvent event = DolphinPubsubEventUpdate;
+                furi_pubsub_publish(dolphin->pubsub, &event);
+                xTimerReset(dolphin->butthurt_timer, portMAX_DELAY);
+                xTimerReset(dolphin->flush_timer, portMAX_DELAY);
             } else if(event.type == DolphinEventTypeStats) {
-                // TODO: correct icounter/butthurt changing, stub till then
-                event.stats->icounter = 0;
-                event.stats->butthurt = 0;
+                event.stats->icounter = dolphin->state->data.icounter;
+                event.stats->butthurt = dolphin->state->data.butthurt;
                 event.stats->timestamp = dolphin->state->data.timestamp;
-                event.stats->level = 1;
-                event.stats->level_up_is_pending = 0;
+                event.stats->level = dolphin_get_level(dolphin->state->data.icounter);
+                event.stats->level_up_is_pending =
+                    !dolphin_state_xp_to_levelup(dolphin->state->data.icounter);
             } else if(event.type == DolphinEventTypeFlush) {
-                // TODO: correct icounter/butthurt changing, stub till then
-                dolphin->state->data.butthurt = 0;
-                dolphin->state->data.icounter = 0;
+                FURI_LOG_I(TAG, "Flush stats");
+                dolphin_state_save(dolphin->state);
+            } else if(event.type == DolphinEventTypeClearLimits) {
+                FURI_LOG_I(TAG, "Clear limits");
+                dolphin_state_clear_limits(dolphin->state);
+                dolphin_state_save(dolphin->state);
+            } else if(event.type == DolphinEventTypeIncreaseButthurt) {
+                FURI_LOG_I(TAG, "Increase butthurt");
+                dolphin_state_butthurted(dolphin->state);
                 dolphin_state_save(dolphin->state);
             }
             dolphin_event_release(dolphin, &event);
         } else {
-            dolphin_check_butthurt(dolphin->state);
-            dolphin_state_save(dolphin->state);
+            /* once per hour check rtc time is not changed */
+            dolphin_update_clear_limits_timer_period(dolphin);
         }
     }
 

+ 15 - 0
applications/dolphin/dolphin.h

@@ -5,6 +5,10 @@
 #include "helpers/dolphin_deed.h"
 #include <stdbool.h>
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 typedef struct Dolphin Dolphin;
 
 typedef struct {
@@ -19,6 +23,13 @@ typedef enum {
     DolphinPubsubEventUpdate,
 } DolphinPubsubEvent;
 
+#define DOLPHIN_DEED(deed)                                        \
+    do {                                                          \
+        Dolphin* dolphin = (Dolphin*)furi_record_open("dolphin"); \
+        dolphin_deed(dolphin, deed);                              \
+        furi_record_close("dolphin");                             \
+    } while(0)
+
 /** Deed complete notification. Call it on deed completion.
  * See dolphin_deed.h for available deeds. In futures it will become part of assets.
  * Thread safe, async
@@ -38,3 +49,7 @@ void dolphin_flush(Dolphin* dolphin);
 void dolphin_upgrade_level(Dolphin* dolphin);
 
 FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin);
+
+#ifdef __cplusplus
+}
+#endif

+ 5 - 3
applications/dolphin/dolphin_i.h

@@ -11,9 +11,8 @@ typedef enum {
     DolphinEventTypeDeed,
     DolphinEventTypeStats,
     DolphinEventTypeFlush,
-    DolphinEventTypeAnimationStartNewIdle,
-    DolphinEventTypeAnimationCheckBlocking,
-    DolphinEventTypeAnimationInteract,
+    DolphinEventTypeIncreaseButthurt,
+    DolphinEventTypeClearLimits,
 } DolphinEventType;
 
 typedef struct {
@@ -31,6 +30,9 @@ struct Dolphin {
     // Queue
     osMessageQueueId_t event_queue;
     FuriPubSub* pubsub;
+    TimerHandle_t butthurt_timer;
+    TimerHandle_t flush_timer;
+    TimerHandle_t clear_limits_timer;
 };
 
 Dolphin* dolphin_alloc();

+ 59 - 8
applications/dolphin/helpers/dolphin_deed.c

@@ -1,14 +1,65 @@
 #include "dolphin_deed.h"
 #include <furi.h>
 
-static const DolphinDeedWeight dolphin_deed_weights[DolphinDeedMax] = {
-    {1, -1, 60},
-    {1, -1, 60},
-    {1, -1, 60},
-    {-1, 1, 60},
+static const DolphinDeedWeight dolphin_deed_weights[] = {
+    {1, DolphinAppSubGhz}, // DolphinDeedSubGhzReceiverInfo
+    {3, DolphinAppSubGhz}, // DolphinDeedSubGhzSave
+    {1, DolphinAppSubGhz}, // DolphinDeedSubGhzRawRec
+    {2, DolphinAppSubGhz}, // DolphinDeedSubGhzAddManually
+    {2, DolphinAppSubGhz}, // DolphinDeedSubGhzSend
+    {1, DolphinAppSubGhz}, // DolphinDeedSubGhzFrequencyAnalyzer
+
+    {1, DolphinAppRfid}, // DolphinDeedRfidRead
+    {3, DolphinAppRfid}, // DolphinDeedRfidReadSuccess
+    {3, DolphinAppRfid}, // DolphinDeedRfidSave
+    {2, DolphinAppRfid}, // DolphinDeedRfidEmulate
+    {2, DolphinAppRfid}, // DolphinDeedRfidAdd
+
+    {1, DolphinAppNfc}, // DolphinDeedNfcRead
+    {3, DolphinAppNfc}, // DolphinDeedNfcReadSuccess
+    {3, DolphinAppNfc}, // DolphinDeedNfcSave
+    {2, DolphinAppNfc}, // DolphinDeedNfcEmulate
+    {2, DolphinAppNfc}, // DolphinDeedNfcAdd
+
+    {1, DolphinAppIr}, // DolphinDeedIrSend
+    {3, DolphinAppIr}, // DolphinDeedIrLearnSuccess
+    {3, DolphinAppIr}, // DolphinDeedIrSave
+    {2, DolphinAppIr}, // DolphinDeedIrBruteForce
+
+    {1, DolphinAppIbutton}, // DolphinDeedIbuttonRead
+    {3, DolphinAppIbutton}, // DolphinDeedIbuttonReadSuccess
+    {3, DolphinAppIbutton}, // DolphinDeedIbuttonSave
+    {2, DolphinAppIbutton}, // DolphinDeedIbuttonEmulate
+    {2, DolphinAppIbutton}, // DolphinDeedIbuttonAdd
+
+    {3, DolphinAppBadusb}, // DolphinDeedBadUsbPlayScript
+    {3, DolphinAppU2f}, // DolphinDeedU2fAuthorized
+};
+
+static uint8_t dolphin_deed_limits[] = {
+    15, // DolphinAppSubGhz
+    15, // DolphinAppRfid
+    15, // DolphinAppNfc
+    15, // DolphinAppIr
+    15, // DolphinAppIbutton
+    15, // DolphinAppBadusb
+    15, // DolphinAppU2f
 };
 
-const DolphinDeedWeight* dolphin_deed_weight(DolphinDeed deed) {
-    furi_assert(deed < DolphinDeedMax);
-    return &dolphin_deed_weights[deed];
+_Static_assert(COUNT_OF(dolphin_deed_weights) == DolphinDeedMAX, "dolphin_deed_weights size error");
+_Static_assert(COUNT_OF(dolphin_deed_limits) == DolphinAppMAX, "dolphin_deed_limits size error");
+
+uint8_t dolphin_deed_get_weight(DolphinDeed deed) {
+    furi_check(deed < DolphinDeedMAX);
+    return dolphin_deed_weights[deed].icounter;
+}
+
+DolphinApp dolphin_deed_get_app(DolphinDeed deed) {
+    furi_check(deed < DolphinDeedMAX);
+    return dolphin_deed_weights[deed].app;
+}
+
+uint8_t dolphin_deed_get_app_limit(DolphinApp app) {
+    furi_check(app < DolphinAppMAX);
+    return dolphin_deed_limits[app];
 }

+ 64 - 14
applications/dolphin/helpers/dolphin_deed.h

@@ -2,23 +2,73 @@
 
 #include <stdint.h>
 
-/* Countable deed that affects icounter*/
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    DolphinAppSubGhz,
+    DolphinAppRfid,
+    DolphinAppNfc,
+    DolphinAppIr,
+    DolphinAppIbutton,
+    DolphinAppBadusb,
+    DolphinAppU2f,
+    DolphinAppMAX,
+} DolphinApp;
+
 typedef enum {
-    // iButton
-    DolphinDeedIButtonRead,
-    DolphinDeedIButtonWrite,
-    DolphinDeedIButtonEmulate,
-    // for debug
-    DolphinDeedWrong,
-    // Special value, do not use
-    DolphinDeedMax
+    DolphinDeedSubGhzReceiverInfo,
+    DolphinDeedSubGhzSave,
+    DolphinDeedSubGhzRawRec,
+    DolphinDeedSubGhzAddManually,
+    DolphinDeedSubGhzSend,
+    DolphinDeedSubGhzFrequencyAnalyzer,
+
+    DolphinDeedRfidRead,
+    DolphinDeedRfidReadSuccess,
+    DolphinDeedRfidSave,
+    DolphinDeedRfidEmulate,
+    DolphinDeedRfidAdd,
+
+    DolphinDeedNfcRead,
+    DolphinDeedNfcReadSuccess,
+    DolphinDeedNfcSave,
+    DolphinDeedNfcEmulate,
+    DolphinDeedNfcAdd,
+
+    DolphinDeedIrSend,
+    DolphinDeedIrLearnSuccess,
+    DolphinDeedIrSave,
+    DolphinDeedIrBruteForce,
+
+    DolphinDeedIbuttonRead,
+    DolphinDeedIbuttonReadSuccess,
+    DolphinDeedIbuttonSave,
+    DolphinDeedIbuttonEmulate,
+    DolphinDeedIbuttonAdd,
+
+    DolphinDeedBadUsbPlayScript,
+
+    DolphinDeedU2fAuthorized,
+
+    DolphinDeedMAX
 } DolphinDeed;
 
 typedef struct {
-    int32_t icounter; // how many icounter get by Deed
-    int32_t butthurt; // how many icounter get by Deed
-    uint32_t limit_value; // how many deeds in limit interval
-    uint32_t limit_interval; // interval, in minutes
+    uint8_t icounter;
+    DolphinApp app;
 } DolphinDeedWeight;
 
-const DolphinDeedWeight* dolphin_deed_weight(DolphinDeed deed);
+typedef struct {
+    DolphinApp app;
+    uint8_t icounter_limit;
+} DolphinDeedLimits;
+
+DolphinApp dolphin_deed_get_app(DolphinDeed deed);
+uint8_t dolphin_deed_get_app_limit(DolphinApp app);
+uint8_t dolphin_deed_get_weight(DolphinDeed deed);
+
+#ifdef __cplusplus
+}
+#endif

+ 44 - 26
applications/dolphin/helpers/dolphin_state.c

@@ -1,4 +1,5 @@
 #include "dolphin_state.h"
+#include "dolphin/helpers/dolphin_deed.h"
 #include <stdint.h>
 #include <storage/storage.h>
 #include <furi.h>
@@ -10,9 +11,8 @@
 #define DOLPHIN_STATE_PATH "/int/dolphin.state"
 #define DOLPHIN_STATE_HEADER_MAGIC 0xD0
 #define DOLPHIN_STATE_HEADER_VERSION 0x01
-#define DOLPHIN_LVL_THRESHOLD 20.0f
-#define LEVEL2_THRESHOLD 20
-#define LEVEL3_THRESHOLD 100
+#define LEVEL2_THRESHOLD 735
+#define LEVEL3_THRESHOLD 2940
 #define BUTTHURT_MAX 14
 #define BUTTHURT_MIN 0
 
@@ -125,50 +125,68 @@ uint32_t dolphin_state_xp_to_levelup(uint32_t icounter) {
     return threshold - icounter;
 }
 
-bool dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) {
-    const DolphinDeedWeight* deed_weight = dolphin_deed_weight(deed);
-    int32_t icounter = dolphin_state->data.icounter + deed_weight->icounter;
-    bool level_up = false;
-    bool mood_changed = false;
-
-    if(icounter <= 0) {
-        icounter = 0;
-        if(dolphin_state->data.icounter == 0) {
-            return false;
-        }
-    }
+void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) {
+    DolphinApp app = dolphin_deed_get_app(deed);
+    int8_t weight_limit =
+        dolphin_deed_get_app_limit(app) - dolphin_state->data.icounter_daily_limit[app];
+    uint8_t deed_weight = CLAMP(dolphin_deed_get_weight(deed), weight_limit, 0);
 
     uint8_t xp_to_levelup = dolphin_state_xp_to_levelup(dolphin_state->data.icounter);
     if(xp_to_levelup) {
-        level_up = true;
-        dolphin_state->data.icounter += MIN(xp_to_levelup, deed_weight->icounter);
+        deed_weight = MIN(xp_to_levelup, deed_weight);
+        dolphin_state->data.icounter += deed_weight;
+        dolphin_state->data.icounter_daily_limit[app] += deed_weight;
     }
 
-    uint32_t new_butthurt = CLAMP(
-        ((int32_t)dolphin_state->data.butthurt) + deed_weight->butthurt,
-        BUTTHURT_MAX,
-        BUTTHURT_MIN);
+    /* decrease butthurt:
+     * 0 deeds accumulating --> 0 butthurt
+     * +1....+15 deeds accumulating --> -1 butthurt
+     * +16...+30 deeds accumulating --> -1 butthurt
+     * +31...+45 deeds accumulating --> -1 butthurt
+     * +46...... deeds accumulating --> -1 butthurt
+     * -4 butthurt per day is maximum
+     * */
+    uint8_t butthurt_icounter_level_old = dolphin_state->data.butthurt_daily_limit / 15 +
+                                          !!(dolphin_state->data.butthurt_daily_limit % 15);
+    dolphin_state->data.butthurt_daily_limit =
+        CLAMP(dolphin_state->data.butthurt_daily_limit + deed_weight, 46, 0);
+    uint8_t butthurt_icounter_level_new = dolphin_state->data.butthurt_daily_limit / 15 +
+                                          !!(dolphin_state->data.butthurt_daily_limit % 15);
+    int32_t new_butthurt = ((int32_t)dolphin_state->data.butthurt) -
+                           (butthurt_icounter_level_old != butthurt_icounter_level_new);
+    new_butthurt = CLAMP(new_butthurt, BUTTHURT_MAX, BUTTHURT_MIN);
 
-    if(!!dolphin_state->data.butthurt != !!new_butthurt) {
-        mood_changed = true;
-    }
     dolphin_state->data.butthurt = new_butthurt;
     dolphin_state->data.timestamp = dolphin_state_timestamp();
     dolphin_state->dirty = true;
 
-    return level_up || mood_changed;
+    FURI_LOG_D(
+        TAG,
+        "icounter %d, butthurt %d",
+        dolphin_state->data.icounter,
+        dolphin_state->data.butthurt);
 }
 
 void dolphin_state_butthurted(DolphinState* dolphin_state) {
     if(dolphin_state->data.butthurt < BUTTHURT_MAX) {
         dolphin_state->data.butthurt++;
-        FURI_LOG_I("DolphinState", "Increasing butthurt");
         dolphin_state->data.timestamp = dolphin_state_timestamp();
         dolphin_state->dirty = true;
     }
 }
 
 void dolphin_state_increase_level(DolphinState* dolphin_state) {
+    furi_assert(dolphin_state_is_levelup(dolphin_state->data.icounter));
     ++dolphin_state->data.icounter;
     dolphin_state->dirty = true;
 }
+
+void dolphin_state_clear_limits(DolphinState* dolphin_state) {
+    furi_assert(dolphin_state);
+
+    for(int i = 0; i < DolphinAppMAX; ++i) {
+        dolphin_state->data.icounter_daily_limit[i] = 0;
+    }
+    dolphin_state->data.butthurt_daily_limit = 0;
+    dolphin_state->dirty = true;
+}

+ 4 - 6
applications/dolphin/helpers/dolphin_state.h

@@ -7,10 +7,8 @@
 
 typedef struct DolphinState DolphinState;
 typedef struct {
-    uint32_t limit_ibutton;
-    uint32_t limit_nfc;
-    uint32_t limit_ir;
-    uint32_t limit_rfid;
+    uint8_t icounter_daily_limit[DolphinAppMAX];
+    uint8_t butthurt_daily_limit;
 
     uint32_t flags;
     uint32_t icounter;
@@ -31,11 +29,11 @@ bool dolphin_state_save(DolphinState* dolphin_state);
 
 bool dolphin_state_load(DolphinState* dolphin_state);
 
-void dolphin_state_clear(DolphinState* dolphin_state);
+void dolphin_state_clear_limits(DolphinState* dolphin_state);
 
 uint64_t dolphin_state_timestamp();
 
-bool dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed);
+void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed);
 
 void dolphin_state_butthurted(DolphinState* dolphin_state);
 

+ 17 - 0
applications/gui/canvas.c

@@ -4,6 +4,7 @@
 
 #include <furi.h>
 #include <furi_hal.h>
+#include <stdint.h>
 #include <u8g2_glue.h>
 
 const CanvasFontParameters canvas_font_params[FontTotalNumber] = {
@@ -202,6 +203,22 @@ uint8_t canvas_glyph_width(Canvas* canvas, char symbol) {
     return u8g2_GetGlyphWidth(&canvas->fb, symbol);
 }
 
+void canvas_draw_bitmap(
+    Canvas* canvas,
+    uint8_t x,
+    uint8_t y,
+    uint8_t width,
+    uint8_t height,
+    const uint8_t* compressed_bitmap_data) {
+    furi_assert(canvas);
+
+    x += canvas->offset_x;
+    y += canvas->offset_y;
+    uint8_t* bitmap_data = NULL;
+    furi_hal_compress_icon_decode(compressed_bitmap_data, &bitmap_data);
+    u8g2_DrawXBM(&canvas->fb, x, y, width, height, bitmap_data);
+}
+
 void canvas_draw_icon_animation(
     Canvas* canvas,
     uint8_t x,

+ 17 - 0
applications/gui/canvas.h

@@ -178,6 +178,23 @@ uint16_t canvas_string_width(Canvas* canvas, const char* str);
  */
 uint8_t canvas_glyph_width(Canvas* canvas, char symbol);
 
+/** Draw bitmap picture at position defined by x,y.
+ *
+ * @param      canvas                   Canvas instance
+ * @param      x                        x coordinate
+ * @param      y                        y coordinate
+ * @param      width                    width of bitmap
+ * @param      height                   height of bitmap
+ * @param      compressed_bitmap_data   compressed bitmap data
+ */
+void canvas_draw_bitmap(
+    Canvas* canvas,
+    uint8_t x,
+    uint8_t y,
+    uint8_t width,
+    uint8_t height,
+    const uint8_t* compressed_bitmap_data);
+
 /** Draw animation at position defined by x,y.
  *
  * @param      canvas          Canvas instance

+ 1 - 0
applications/gui/icon_i.h

@@ -3,6 +3,7 @@
  * GUI: internal Icon API
  */
 
+#pragma once
 #include "icon.h"
 
 struct Icon {

+ 0 - 166
applications/gui/view_composed.c

@@ -1,166 +0,0 @@
-#include "gui/view.h"
-#include "furi/memmgr.h"
-#include "view_composed.h"
-#include "view_i.h"
-
-typedef struct {
-    View* bottom;
-    View* top;
-    bool top_enabled;
-} ViewComposedModel;
-
-struct ViewComposed {
-    View* view;
-};
-
-static void view_composed_draw(Canvas* canvas, void* model);
-static bool view_composed_input(InputEvent* event, void* context);
-
-static void view_composed_update_callback(View* view_top_or_bottom, void* context) {
-    furi_assert(view_top_or_bottom);
-    furi_assert(context);
-
-    View* view_composed_view = context;
-    view_composed_view->update_callback(
-        view_composed_view, view_composed_view->update_callback_context);
-}
-
-static void view_composed_enter(void* context) {
-    furi_assert(context);
-
-    ViewComposed* view_composed = context;
-    ViewComposedModel* model = view_get_model(view_composed->view);
-
-    /* if more than 1 composed views hold same view it has to reassign update_callback_context */
-    if(model->bottom) {
-        view_set_update_callback_context(model->bottom, view_composed->view);
-        if(model->bottom->enter_callback) {
-            model->bottom->enter_callback(model->bottom->context);
-        }
-    }
-    if(model->top) {
-        view_set_update_callback_context(model->top, view_composed->view);
-        if(model->top->enter_callback) {
-            model->top->enter_callback(model->top->context);
-        }
-    }
-
-    view_commit_model(view_composed->view, false);
-}
-
-static void view_composed_exit(void* context) {
-    furi_assert(context);
-
-    ViewComposed* view_composed = context;
-    ViewComposedModel* model = view_get_model(view_composed->view);
-
-    if(model->bottom) {
-        if(model->bottom->exit_callback) {
-            model->bottom->exit_callback(model->bottom->context);
-        }
-    }
-    if(model->top) {
-        if(model->top->exit_callback) {
-            model->top->exit_callback(model->top->context);
-        }
-    }
-
-    view_commit_model(view_composed->view, false);
-}
-
-ViewComposed* view_composed_alloc(void) {
-    ViewComposed* view_composed = furi_alloc(sizeof(ViewComposed));
-    view_composed->view = view_alloc();
-
-    view_allocate_model(view_composed->view, ViewModelTypeLocking, sizeof(ViewComposedModel));
-    view_set_draw_callback(view_composed->view, view_composed_draw);
-    view_set_input_callback(view_composed->view, view_composed_input);
-    view_set_context(view_composed->view, view_composed);
-    view_set_enter_callback(view_composed->view, view_composed_enter);
-    view_set_exit_callback(view_composed->view, view_composed_exit);
-    return view_composed;
-}
-
-void view_composed_free(ViewComposed* view_composed) {
-    furi_assert(view_composed);
-
-    ViewComposedModel* view_composed_model = view_get_model(view_composed->view);
-    view_set_update_callback(view_composed_model->bottom, NULL);
-    view_set_update_callback_context(view_composed_model->bottom, NULL);
-    view_set_update_callback(view_composed_model->top, NULL);
-    view_set_update_callback_context(view_composed_model->top, NULL);
-    view_commit_model(view_composed->view, true);
-
-    view_free(view_composed->view);
-    free(view_composed);
-}
-
-static void view_composed_draw(Canvas* canvas, void* model) {
-    furi_assert(model);
-
-    ViewComposedModel* view_composed_model = model;
-
-    view_draw(view_composed_model->bottom, canvas);
-    if(view_composed_model->top_enabled && view_composed_model->top) {
-        view_draw(view_composed_model->top, canvas);
-    }
-}
-
-static bool view_composed_input(InputEvent* event, void* context) {
-    furi_assert(event);
-    furi_assert(context);
-
-    ViewComposed* view_composed = context;
-    ViewComposedModel* view_composed_model = view_get_model(view_composed->view);
-    bool consumed = false;
-
-    if(view_composed_model->top_enabled && view_composed_model->top) {
-        consumed = view_input(view_composed_model->top, event);
-    }
-    if(!consumed) {
-        consumed = view_input(view_composed_model->bottom, event);
-    }
-
-    view_commit_model(view_composed->view, false);
-
-    return consumed;
-}
-
-void view_composed_top_enable(ViewComposed* view_composed, bool enable) {
-    furi_assert(view_composed);
-
-    ViewComposedModel* view_composed_model = view_get_model(view_composed->view);
-    bool update = (view_composed_model->top_enabled != enable);
-    view_composed_model->top_enabled = enable;
-    view_commit_model(view_composed->view, update);
-}
-
-void view_composed_tie_views(ViewComposed* view_composed, View* view_bottom, View* view_top) {
-    furi_assert(view_composed);
-    furi_assert(view_bottom);
-
-    ViewComposedModel* view_composed_model = view_get_model(view_composed->view);
-
-    if(view_composed_model->bottom) {
-        view_set_update_callback(view_composed_model->bottom, NULL);
-        view_set_update_callback_context(view_composed_model->bottom, NULL);
-    }
-    if(view_composed_model->top) {
-        view_set_update_callback(view_composed_model->top, NULL);
-        view_set_update_callback_context(view_composed_model->top, NULL);
-    }
-
-    view_composed_model->bottom = view_bottom;
-    view_set_update_callback(view_bottom, view_composed_update_callback);
-    view_set_update_callback_context(view_bottom, view_composed->view);
-    view_composed_model->top = view_top;
-    view_set_update_callback(view_top, view_composed_update_callback);
-    view_set_update_callback_context(view_top, view_composed->view);
-
-    view_commit_model(view_composed->view, true);
-}
-
-View* view_composed_get_view(ViewComposed* view_composed) {
-    furi_assert(view_composed);
-    return view_composed->view;
-}

+ 0 - 12
applications/gui/view_composed.h

@@ -1,12 +0,0 @@
-#pragma once
-
-#include <stdbool.h>
-#include "view.h"
-
-typedef struct ViewComposed ViewComposed;
-
-ViewComposed* view_composed_alloc(void);
-void view_composed_free(ViewComposed* view_composed);
-void view_composed_top_enable(ViewComposed* view_composed, bool enable);
-void view_composed_tie_views(ViewComposed* view_composed, View* view_bottom, View* view_top);
-View* view_composed_get_view(ViewComposed* view_composed);

+ 165 - 0
applications/gui/view_stack.c

@@ -0,0 +1,165 @@
+#include "gui/view.h"
+#include "furi/memmgr.h"
+#include "view_stack.h"
+#include "view_i.h"
+
+#define MAX_VIEWS 3
+
+typedef struct {
+    View* views[MAX_VIEWS];
+} ViewStackModel;
+
+struct ViewStack {
+    View* view;
+};
+
+static void view_stack_draw(Canvas* canvas, void* model);
+static bool view_stack_input(InputEvent* event, void* context);
+
+static void view_stack_update_callback(View* view_top_or_bottom, void* context) {
+    furi_assert(view_top_or_bottom);
+    furi_assert(context);
+
+    View* view_stack_view = context;
+    if(view_stack_view->update_callback) {
+        view_stack_view->update_callback(
+            view_stack_view, view_stack_view->update_callback_context);
+    }
+}
+
+static void view_stack_enter(void* context) {
+    furi_assert(context);
+
+    ViewStack* view_stack = context;
+    ViewStackModel* model = view_get_model(view_stack->view);
+
+    /* if more than 1 composite views hold same view they have to reassign update_callback_context */
+    for(int i = 0; i < MAX_VIEWS; ++i) {
+        if(model->views[i]) {
+            view_set_update_callback_context(model->views[i], view_stack->view);
+            if(model->views[i]->enter_callback) {
+                model->views[i]->enter_callback(model->views[i]->context);
+            }
+        }
+    }
+
+    view_commit_model(view_stack->view, false);
+}
+
+static void view_stack_exit(void* context) {
+    furi_assert(context);
+
+    ViewStack* view_stack = context;
+    ViewStackModel* model = view_get_model(view_stack->view);
+
+    for(int i = 0; i < MAX_VIEWS; ++i) {
+        if(model->views[i] && model->views[i]->exit_callback) {
+            model->views[i]->exit_callback(model->views[i]->context);
+        }
+    }
+
+    view_commit_model(view_stack->view, false);
+}
+
+ViewStack* view_stack_alloc(void) {
+    ViewStack* view_stack = furi_alloc(sizeof(ViewStack));
+    view_stack->view = view_alloc();
+
+    view_allocate_model(view_stack->view, ViewModelTypeLocking, sizeof(ViewStackModel));
+    view_set_draw_callback(view_stack->view, view_stack_draw);
+    view_set_input_callback(view_stack->view, view_stack_input);
+    view_set_context(view_stack->view, view_stack);
+    view_set_enter_callback(view_stack->view, view_stack_enter);
+    view_set_exit_callback(view_stack->view, view_stack_exit);
+    return view_stack;
+}
+
+void view_stack_free(ViewStack* view_stack) {
+    furi_assert(view_stack);
+
+    ViewStackModel* model = view_get_model(view_stack->view);
+    for(int i = 0; i < MAX_VIEWS; ++i) {
+        if(model->views[i]) {
+            view_set_update_callback(model->views[i], NULL);
+            view_set_update_callback_context(model->views[i], NULL);
+        }
+    }
+    view_commit_model(view_stack->view, false);
+
+    view_free(view_stack->view);
+    free(view_stack);
+}
+
+static void view_stack_draw(Canvas* canvas, void* _model) {
+    furi_assert(_model);
+
+    ViewStackModel* model = _model;
+    for(int i = 0; i < MAX_VIEWS; ++i) {
+        if(model->views[i]) {
+            view_draw(model->views[i], canvas);
+        }
+    }
+}
+
+static bool view_stack_input(InputEvent* event, void* context) {
+    furi_assert(event);
+    furi_assert(context);
+
+    bool consumed = false;
+    ViewStack* view_stack = context;
+
+    ViewStackModel* model = view_get_model(view_stack->view);
+    for(int i = MAX_VIEWS - 1; !consumed && (i >= 0); --i) {
+        if(model->views[i]) {
+            consumed = view_input(model->views[i], event);
+        }
+    }
+    view_commit_model(view_stack->view, false);
+
+    return consumed;
+}
+
+void view_stack_add_view(ViewStack* view_stack, View* view) {
+    furi_assert(view_stack);
+    furi_assert(view);
+
+    bool result = false;
+    ViewStackModel* model = view_get_model(view_stack->view);
+    for(int i = 0; i < MAX_VIEWS; ++i) {
+        if(!model->views[i]) {
+            model->views[i] = view;
+            view_set_update_callback(model->views[i], view_stack_update_callback);
+            view_set_update_callback_context(model->views[i], view_stack->view);
+            result = true;
+            break;
+        }
+    }
+    view_commit_model(view_stack->view, result);
+    furi_assert(result);
+}
+
+void view_stack_remove_view(ViewStack* view_stack, View* view) {
+    furi_assert(view_stack);
+    furi_assert(view);
+
+    /* Removing view on-the-go is dangerous, but it is protected with
+     * Locking model, so system is consistent at any time. */
+    bool result = false;
+    ViewStackModel* model = view_get_model(view_stack->view);
+    for(int i = 0; i < MAX_VIEWS; ++i) {
+        if(model->views[i] == view) {
+            view_set_update_callback(model->views[i], NULL);
+            view_set_update_callback_context(model->views[i], NULL);
+            model->views[i] = NULL;
+            result = true;
+            break;
+        }
+    }
+    view_commit_model(view_stack->view, result);
+    furi_assert(result);
+}
+
+View* view_stack_get_view(ViewStack* view_stack) {
+    furi_assert(view_stack);
+    return view_stack->view;
+}

+ 53 - 0
applications/gui/view_stack.h

@@ -0,0 +1,53 @@
+/**
+ * @file view_stack.h
+ * GUI: ViewStack API
+ *
+ * ViewStack accumulates several Views in one stack.
+ * Draw callbacks are called sequenctially starting from
+ * first added. Input callbacks are called in reverse order.
+ * Consumed input is not passed on underlying layers.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include "view.h"
+
+/** ViewStack, anonymous type. */
+typedef struct ViewStack ViewStack;
+
+/** Allocate and init ViewStack
+ *
+ * @return      ViewStack instance
+ */
+ViewStack* view_stack_alloc(void);
+
+/** Free ViewStack instance
+ *
+ * @param       view_stack  instance
+ */
+void view_stack_free(ViewStack* view_stack);
+
+/** Get View of ViewStack.
+ * Should this View to any view manager such as
+ * ViewDispatcher or ViewHolder.
+ *
+ * @param       view_stack  instance
+ */
+View* view_stack_get_view(ViewStack* view_stack);
+
+/** Add View to ViewStack.
+ * Adds View on top of ViewStack.
+ *
+ * @param       view_stack  instance
+ * @view        view        view to add
+ */
+void view_stack_add_view(ViewStack* view_stack, View* view);
+
+/** Remove any View in ViewStack.
+ * If no View to remove found - ignore.
+ *
+ * @param       view_stack  instance
+ * @view        view        view to remove
+ */
+void view_stack_remove_view(ViewStack* view_stack, View* view);

+ 3 - 1
applications/ibutton/scene/ibutton_scene_add_value.cpp

@@ -3,6 +3,7 @@
 #include "../ibutton_view_manager.h"
 #include "../ibutton_event.h"
 #include <callback-connector.h>
+#include <dolphin/dolphin.h>
 
 void iButtonSceneAddValue::on_enter(iButtonApp* app) {
     iButtonAppViewManager* view_manager = app->get_view_manager();
@@ -20,6 +21,7 @@ bool iButtonSceneAddValue::on_event(iButtonApp* app, iButtonEvent* event) {
     bool consumed = false;
 
     if(event->type == iButtonEvent::Type::EventTypeByteEditResult) {
+        DOLPHIN_DEED(DolphinDeedIbuttonAdd);
         app->switch_to_next_scene(iButtonApp::Scene::SceneSaveName);
         consumed = true;
     }
@@ -42,4 +44,4 @@ void iButtonSceneAddValue::byte_input_callback(void* context) {
     event.type = iButtonEvent::Type::EventTypeByteEditResult;
     memcpy(app->get_key()->get_data(), this->new_key_data, app->get_key()->get_type_data_size());
     app->get_view_manager()->send_event(&event);
-}
+}

+ 3 - 1
applications/ibutton/scene/ibutton_scene_emulate.cpp

@@ -3,6 +3,7 @@
 #include "../ibutton_view_manager.h"
 #include "../ibutton_event.h"
 #include "../ibutton_key.h"
+#include <dolphin/dolphin.h>
 #include <callback-connector.h>
 
 void iButtonSceneEmulate::on_enter(iButtonApp* app) {
@@ -12,6 +13,7 @@ void iButtonSceneEmulate::on_enter(iButtonApp* app) {
     uint8_t* key_data = key->get_data();
     const char* key_name = key->get_name();
     uint8_t line_count = 2;
+    DOLPHIN_DEED(DolphinDeedIbuttonEmulate);
 
     // check that stored key has name
     if(strcmp(key_name, "") != 0) {
@@ -90,4 +92,4 @@ void iButtonSceneEmulate::on_exit(iButtonApp* app) {
     popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
     popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
     popup_set_icon(popup, 0, 0, NULL);
-}
+}

+ 3 - 1
applications/ibutton/scene/ibutton_scene_read.cpp

@@ -2,10 +2,12 @@
 #include "../ibutton_app.h"
 #include "../ibutton_view_manager.h"
 #include "../ibutton_event.h"
+#include <dolphin/dolphin.h>
 
 void iButtonSceneRead::on_enter(iButtonApp* app) {
     iButtonAppViewManager* view_manager = app->get_view_manager();
     Popup* popup = view_manager->get_popup();
+    DOLPHIN_DEED(DolphinDeedIbuttonRead);
 
     popup_set_header(popup, "iButton", 95, 26, AlignCenter, AlignBottom);
     popup_set_text(popup, "waiting\nfor key ...", 95, 30, AlignCenter, AlignTop);
@@ -50,4 +52,4 @@ void iButtonSceneRead::on_exit(iButtonApp* app) {
     popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
     popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
     popup_set_icon(popup, 0, 0, NULL);
-}
+}

+ 3 - 1
applications/ibutton/scene/ibutton_scene_read_success.cpp

@@ -2,12 +2,14 @@
 #include "../ibutton_app.h"
 #include "../ibutton_view_manager.h"
 #include "../ibutton_event.h"
+#include <dolphin/dolphin.h>
 #include <callback-connector.h>
 
 void iButtonSceneReadSuccess::on_enter(iButtonApp* app) {
     iButtonAppViewManager* view_manager = app->get_view_manager();
     DialogEx* dialog_ex = view_manager->get_dialog_ex();
     auto callback = cbc::obtain_connector(this, &iButtonSceneReadSuccess::dialog_ex_callback);
+    DOLPHIN_DEED(DolphinDeedIbuttonReadSuccess);
 
     iButtonKey* key = app->get_key();
     uint8_t* key_data = key->get_data();
@@ -87,4 +89,4 @@ void iButtonSceneReadSuccess::dialog_ex_callback(DialogExResult result, void* co
     event.payload.dialog_result = result;
 
     app->get_view_manager()->send_event(&event);
-}
+}

+ 3 - 1
applications/ibutton/scene/ibutton_scene_save_success.cpp

@@ -3,12 +3,14 @@
 #include "../ibutton_view_manager.h"
 #include "../ibutton_event.h"
 #include "../ibutton_key.h"
+#include <dolphin/dolphin.h>
 #include <callback-connector.h>
 
 void iButtonSceneSaveSuccess::on_enter(iButtonApp* app) {
     iButtonAppViewManager* view_manager = app->get_view_manager();
     Popup* popup = view_manager->get_popup();
     auto callback = cbc::obtain_connector(this, &iButtonSceneSaveSuccess::popup_callback);
+    DOLPHIN_DEED(DolphinDeedIbuttonSave);
 
     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
     popup_set_text(popup, "Saved!", 13, 22, AlignLeft, AlignBottom);
@@ -51,4 +53,4 @@ void iButtonSceneSaveSuccess::popup_callback(void* context) {
     iButtonEvent event;
     event.type = iButtonEvent::Type::EventTypeBack;
     app->get_view_manager()->send_event(&event);
-}
+}

+ 2 - 0
applications/irda/scene/irda_app_scene_learn_done.cpp

@@ -1,10 +1,12 @@
 #include "../irda_app.h"
+#include <dolphin/dolphin.h>
 
 void IrdaAppSceneLearnDone::on_enter(IrdaApp* app) {
     IrdaAppViewManager* view_manager = app->get_view_manager();
     Popup* popup = view_manager->get_popup();
 
     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
+    DOLPHIN_DEED(DolphinDeedIrSave);
 
     if(app->get_learn_new_remote()) {
         popup_set_text(popup, "New remote\ncreated!", 5, 7, AlignLeft, AlignTop);

+ 2 - 0
applications/irda/scene/irda_app_scene_learn_success.cpp

@@ -2,6 +2,7 @@
 #include <file_worker_cpp.h>
 #include "irda.h"
 #include <memory>
+#include <dolphin/dolphin.h>
 
 static void dialog_result_callback(DialogExResult result, void* context) {
     auto app = static_cast<IrdaApp*>(context);
@@ -17,6 +18,7 @@ void IrdaAppSceneLearnSuccess::on_enter(IrdaApp* app) {
     IrdaAppViewManager* view_manager = app->get_view_manager();
     DialogEx* dialog_ex = view_manager->get_dialog_ex();
 
+    DOLPHIN_DEED(DolphinDeedIrLearnSuccess);
     app->notify_green_on();
 
     auto signal = app->get_received_signal();

+ 2 - 0
applications/irda/scene/irda_app_scene_remote.cpp

@@ -2,6 +2,7 @@
 #include "gui/modules/button_menu.h"
 #include "input/input.h"
 #include "irda_worker.h"
+#include <dolphin/dolphin.h>
 
 typedef enum {
     ButtonIndexPlus = -2,
@@ -106,6 +107,7 @@ bool IrdaAppSceneRemote::on_event(IrdaApp* app, IrdaAppEvent* event) {
                         app->get_irda_worker(), &button_signal.get_message());
                 }
 
+                DOLPHIN_DEED(DolphinDeedIrSend);
                 irda_worker_tx_start(app->get_irda_worker());
             } else if(!pressed && button_pressed) {
                 button_pressed = false;

+ 2 - 0
applications/irda/scene/irda_app_scene_universal_common.cpp

@@ -1,5 +1,6 @@
 #include "../irda_app.h"
 #include "assets_icons.h"
+#include <dolphin/dolphin.h>
 #include "gui/modules/button_menu.h"
 #include "gui/modules/button_panel.h"
 #include "../view/irda_app_brut_view.h"
@@ -84,6 +85,7 @@ bool IrdaAppSceneUniversalCommon::on_event(IrdaApp* app, IrdaAppEvent* event) {
         if(event->type == IrdaAppEvent::Type::ButtonPanelPressed) {
             int record_amount = 0;
             if(brute_force.start_bruteforce(event->payload.menu_index, record_amount)) {
+                DOLPHIN_DEED(DolphinDeedIrBruteForce);
                 brute_force_started = true;
                 show_popup(app, record_amount);
             } else {

+ 2 - 0
applications/lfrfid/scene/lfrfid_app_scene_emulate.cpp

@@ -1,8 +1,10 @@
 #include "lfrfid_app_scene_emulate.h"
+#include <dolphin/dolphin.h>
 
 void LfRfidAppSceneEmulate::on_enter(LfRfidApp* app, bool need_restore) {
     string_init(data_string);
 
+    DOLPHIN_DEED(DolphinDeedRfidEmulate);
     const uint8_t* data = app->worker.key.get_data();
 
     for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) {

+ 3 - 0
applications/lfrfid/scene/lfrfid_app_scene_read.cpp

@@ -1,8 +1,10 @@
 #include "lfrfid_app_scene_read.h"
+#include <dolphin/dolphin.h>
 
 void LfRfidAppSceneRead::on_enter(LfRfidApp* app, bool need_restore) {
     auto popup = app->view_controller.get<PopupVM>();
 
+    DOLPHIN_DEED(DolphinDeedRfidRead);
     popup->set_header("Reading\nLF RFID", 89, 34, AlignCenter, AlignTop);
     popup->set_icon(0, 3, &I_RFIDDolphinReceive_97x61);
 
@@ -15,6 +17,7 @@ bool LfRfidAppSceneRead::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
 
     if(event->type == LfRfidApp::EventType::Tick) {
         if(app->worker.read()) {
+            DOLPHIN_DEED(DolphinDeedRfidReadSuccess);
             notification_message(app->notification, &sequence_success);
             app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::ReadSuccess);
         } else {

+ 2 - 0
applications/lfrfid/scene/lfrfid_app_scene_save_data.cpp

@@ -1,4 +1,5 @@
 #include "lfrfid_app_scene_save_data.h"
+#include <dolphin/dolphin.h>
 
 static void print_buffer(const uint8_t* buffer) {
     for(uint8_t i = 0; i < LFRFID_KEY_SIZE; i++) {
@@ -40,6 +41,7 @@ bool LfRfidAppSceneSaveData::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
 
     if(event->type == LfRfidApp::EventType::Next) {
         key.set_data(new_key_data, key.get_type_data_count());
+        DOLPHIN_DEED(DolphinDeedRfidAdd);
         app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveName);
     }
 

+ 5 - 1
applications/lfrfid/scene/lfrfid_app_scene_save_success.cpp

@@ -1,8 +1,12 @@
 #include "lfrfid_app_scene_save_success.h"
+#include <gui/scene_manager.h>
+#include <dolphin/dolphin.h>
+#include <stdint.h>
 
 void LfRfidAppSceneSaveSuccess::on_enter(LfRfidApp* app, bool need_restore) {
     auto popup = app->view_controller.get<PopupVM>();
 
+    DOLPHIN_DEED(DolphinDeedRfidSave);
     popup->set_icon(32, 5, &I_DolphinNice_96x59);
     popup->set_text("Saved!", 13, 22, AlignLeft, AlignBottom);
     popup->set_context(app);
@@ -43,4 +47,4 @@ void LfRfidAppSceneSaveSuccess::timeout_callback(void* context) {
     LfRfidApp::Event event;
     event.type = LfRfidApp::EventType::Back;
     app->view_controller.send_event(&event);
-}
+}

+ 4 - 3
applications/loader/loader.c

@@ -1,4 +1,4 @@
-#include <furi/pubsub.h>
+#include <furi.h>
 #include "loader/loader.h"
 #include "loader_i.h"
 
@@ -464,11 +464,12 @@ int32_t loader_srv(void* p) {
         }
     }
 
+    furi_record_destroy("loader");
     loader_free(loader_instance);
 
     return 0;
 }
 
-FuriPubSub* loader_get_pubsub() {
-    return loader_instance->pubsub;
+FuriPubSub* loader_get_pubsub(Loader* instance) {
+    return instance->pubsub;
 }

+ 1 - 1
applications/loader/loader.h

@@ -46,4 +46,4 @@ void loader_show_menu();
 void loader_update_menu();
 
 /** Show primary loader */
-FuriPubSub* loader_get_pubsub();
+FuriPubSub* loader_get_pubsub(Loader* instance);

+ 2 - 0
applications/nfc/scenes/nfc_scene_emulate_mifare_ul.c

@@ -1,4 +1,5 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 #define NFC_MF_UL_DATA_NOT_CHANGED (0UL)
 #define NFC_MF_UL_DATA_CHANGED (1UL)
@@ -11,6 +12,7 @@ void nfc_emulate_mifare_ul_worker_callback(void* context) {
 
 void nfc_scene_emulate_mifare_ul_on_enter(void* context) {
     Nfc* nfc = (Nfc*)context;
+    DOLPHIN_DEED(DolphinDeedNfcEmulate);
 
     // Setup view
     Popup* popup = nfc->popup;

+ 2 - 0
applications/nfc/scenes/nfc_scene_emulate_uid.c

@@ -1,7 +1,9 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 void nfc_scene_emulate_uid_on_enter(void* context) {
     Nfc* nfc = (Nfc*)context;
+    DOLPHIN_DEED(DolphinDeedNfcEmulate);
 
     // Setup view
     Popup* popup = nfc->popup;

+ 2 - 0
applications/nfc/scenes/nfc_scene_read_card.c

@@ -1,4 +1,5 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 #define NFC_READ_CARD_CUSTOM_EVENT (10UL)
 
@@ -9,6 +10,7 @@ void nfc_read_card_worker_callback(void* context) {
 
 void nfc_scene_read_card_on_enter(void* context) {
     Nfc* nfc = (Nfc*)context;
+    DOLPHIN_DEED(DolphinDeedNfcRead);
 
     // Setup view
     Popup* popup = nfc->popup;

+ 2 - 0
applications/nfc/scenes/nfc_scene_read_card_success.c

@@ -1,4 +1,5 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 void nfc_scene_read_card_success_widget_callback(
     GuiButtonType result,
@@ -17,6 +18,7 @@ void nfc_scene_read_card_success_on_enter(void* context) {
     string_t uid_str;
     string_init(data_str);
     string_init(uid_str);
+    DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
 
     // Send notification
     notification_message(nfc->notifications, &sequence_success);

+ 2 - 0
applications/nfc/scenes/nfc_scene_read_emv_app.c

@@ -1,4 +1,5 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 #define NFC_READ_EMV_APP_CUSTOM_EVENT (10UL)
 
@@ -9,6 +10,7 @@ void nfc_read_emv_app_worker_callback(void* context) {
 
 void nfc_scene_read_emv_app_on_enter(void* context) {
     Nfc* nfc = (Nfc*)context;
+    DOLPHIN_DEED(DolphinDeedNfcRead);
 
     // Setup view
     Popup* popup = nfc->popup;

+ 2 - 0
applications/nfc/scenes/nfc_scene_read_emv_app_success.c

@@ -1,5 +1,6 @@
 #include "../nfc_i.h"
 #include "../helpers/nfc_emv_parser.h"
+#include <dolphin/dolphin.h>
 
 #define NFC_SCENE_READ_SUCCESS_SHIFT "              "
 
@@ -11,6 +12,7 @@ void nfc_scene_read_emv_app_success_dialog_callback(DialogExResult result, void*
 
 void nfc_scene_read_emv_app_success_on_enter(void* context) {
     Nfc* nfc = (Nfc*)context;
+    DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
 
     // Setup view
     NfcDeviceCommonData* nfc_data = &nfc->dev->dev_data.nfc_data;

+ 2 - 0
applications/nfc/scenes/nfc_scene_read_emv_data.c

@@ -1,4 +1,5 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 #define NFC_READ_EMV_DATA_CUSTOM_EVENT (10UL)
 
@@ -9,6 +10,7 @@ void nfc_read_emv_data_worker_callback(void* context) {
 
 void nfc_scene_read_emv_data_on_enter(void* context) {
     Nfc* nfc = (Nfc*)context;
+    DOLPHIN_DEED(DolphinDeedNfcRead);
 
     // Setup view
     Popup* popup = nfc->popup;

+ 2 - 0
applications/nfc/scenes/nfc_scene_read_emv_data_success.c

@@ -1,5 +1,6 @@
 #include "../nfc_i.h"
 #include "../helpers/nfc_emv_parser.h"
+#include <dolphin/dolphin.h>
 
 void nfc_scene_read_emv_data_success_widget_callback(
     GuiButtonType result,
@@ -15,6 +16,7 @@ void nfc_scene_read_emv_data_success_on_enter(void* context) {
     Nfc* nfc = (Nfc*)context;
     NfcEmvData* emv_data = &nfc->dev->dev_data.emv_data;
     NfcDeviceCommonData* nfc_data = &nfc->dev->dev_data.nfc_data;
+    DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
 
     // Setup Custom Widget view
     // Add frame

+ 2 - 0
applications/nfc/scenes/nfc_scene_read_mifare_ul.c

@@ -1,4 +1,5 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 #define NFC_READ_MIFARE_UL_CUSTOM_EVENT (10UL)
 
@@ -9,6 +10,7 @@ void nfc_read_mifare_ul_worker_callback(void* context) {
 
 void nfc_scene_read_mifare_ul_on_enter(void* context) {
     Nfc* nfc = (Nfc*)context;
+    DOLPHIN_DEED(DolphinDeedNfcRead);
 
     // Setup view
     Popup* popup = nfc->popup;

+ 2 - 0
applications/nfc/scenes/nfc_scene_read_mifare_ul_success.c

@@ -1,4 +1,5 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 #define NFC_SCENE_READ_SUCCESS_SHIFT "              "
 #define NFC_SCENE_READ_MF_UL_CUSTOM_EVENT (0UL)
@@ -22,6 +23,7 @@ void nfc_scene_read_mifare_ul_success_text_box_callback(void* context) {
 
 void nfc_scene_read_mifare_ul_success_on_enter(void* context) {
     Nfc* nfc = (Nfc*)context;
+    DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
 
     // Send notification
     notification_message(nfc->notifications, &sequence_success);

+ 2 - 0
applications/nfc/scenes/nfc_scene_save_success.c

@@ -1,4 +1,5 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 #define SCENE_SAVE_SUCCESS_CUSTOM_EVENT (0UL)
 
@@ -9,6 +10,7 @@ void nfc_scene_save_success_popup_callback(void* context) {
 
 void nfc_scene_save_success_on_enter(void* context) {
     Nfc* nfc = (Nfc*)context;
+    DOLPHIN_DEED(DolphinDeedNfcSave);
 
     // Setup view
     Popup* popup = nfc->popup;

+ 2 - 0
applications/nfc/scenes/nfc_scene_set_uid.c

@@ -1,4 +1,5 @@
 #include "../nfc_i.h"
+#include <dolphin/dolphin.h>
 
 #define SCENE_SET_UID_CUSTOM_EVENT (0UL)
 
@@ -30,6 +31,7 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) {
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SCENE_SET_UID_CUSTOM_EVENT) {
+            DOLPHIN_DEED(DolphinDeedNfcAdd);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
             return true;
         }

+ 2 - 0
applications/subghz/scenes/subghz_scene_frequency_analyzer.c

@@ -1,5 +1,6 @@
 #include "../subghz_i.h"
 #include "../views/subghz_frequency_analyzer.h"
+#include <dolphin/dolphin.h>
 
 void subghz_scene_frequency_analyzer_callback(SubghzCustomEvent event, void* context) {
     furi_assert(context);
@@ -9,6 +10,7 @@ void subghz_scene_frequency_analyzer_callback(SubghzCustomEvent event, void* con
 
 void subghz_scene_frequency_analyzer_on_enter(void* context) {
     SubGhz* subghz = context;
+    DOLPHIN_DEED(DolphinDeedSubGhzFrequencyAnalyzer);
     subghz_frequency_analyzer_set_callback(
         subghz->subghz_frequency_analyzer, subghz_scene_frequency_analyzer_callback, subghz);
     view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewFrequencyAnalyzer);

+ 3 - 0
applications/subghz/scenes/subghz_scene_read_raw.c

@@ -1,5 +1,6 @@
 #include "../subghz_i.h"
 #include "../views/subghz_read_raw.h"
+#include <dolphin/dolphin.h>
 #include <lib/subghz/protocols/subghz_protocol_raw.h>
 #include <lib/subghz/subghz_parser.h>
 #include <lib/toolbox/path.h>
@@ -199,6 +200,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
                     if(!subghz_tx_start(subghz)) {
                         scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx);
                     } else {
+                        DOLPHIN_DEED(DolphinDeedSubGhzSend);
                         subghz->state_notifications = SubGhzNotificationStateTX;
                     }
                 }
@@ -240,6 +242,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) {
                        RAW_FILE_NAME,
                        subghz->txrx->frequency,
                        string_get_cstr(subghz->error_str))) {
+                    DOLPHIN_DEED(DolphinDeedSubGhzRawRec);
                     if((subghz->txrx->txrx_state == SubGhzTxRxStateIDLE) ||
                        (subghz->txrx->txrx_state == SubGhzTxRxStateSleep)) {
                         subghz_begin(subghz, subghz->txrx->preset);

+ 2 - 0
applications/subghz/scenes/subghz_scene_receiver_info.c

@@ -1,5 +1,6 @@
 #include "../subghz_i.h"
 #include "../helpers/subghz_custom_event.h"
+#include <dolphin/dolphin.h>
 
 void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, void* context) {
     furi_assert(context);
@@ -39,6 +40,7 @@ static bool subghz_scene_receiver_info_update_parser(void* context) {
 void subghz_scene_receiver_info_on_enter(void* context) {
     SubGhz* subghz = context;
 
+    DOLPHIN_DEED(DolphinDeedSubGhzReceiverInfo);
     if(subghz_scene_receiver_info_update_parser(subghz)) {
         string_t frequency_str;
         string_t modulation_str;

+ 3 - 0
applications/subghz/scenes/subghz_scene_save_success.c

@@ -1,5 +1,7 @@
 #include "../subghz_i.h"
 #include "../helpers/subghz_custom_event.h"
+#include "dolphin/helpers/dolphin_deed.h"
+#include <dolphin/dolphin.h>
 
 void subghz_scene_save_success_popup_callback(void* context) {
     SubGhz* subghz = context;
@@ -8,6 +10,7 @@ void subghz_scene_save_success_popup_callback(void* context) {
 
 void subghz_scene_save_success_on_enter(void* context) {
     SubGhz* subghz = context;
+    DOLPHIN_DEED(DolphinDeedSubGhzSave);
 
     // Setup view
     Popup* popup = subghz->popup;

+ 2 - 0
applications/subghz/scenes/subghz_scene_set_type.c

@@ -1,5 +1,6 @@
 #include "../subghz_i.h"
 #include "../lib/subghz/protocols/subghz_protocol_keeloq.h"
+#include <dolphin/dolphin.h>
 
 enum SubmenuIndex {
     SubmenuIndexPricenton,
@@ -192,6 +193,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
             subghz->txrx->frequency = subghz_frequencies[subghz_frequencies_433_92];
             subghz->txrx->preset = FuriHalSubGhzPresetOok650Async;
             subghz_file_name_clear(subghz);
+            DOLPHIN_DEED(DolphinDeedSubGhzAddManually);
             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
             return true;
         }

+ 2 - 0
applications/subghz/scenes/subghz_scene_transmitter.c

@@ -1,6 +1,7 @@
 #include "../subghz_i.h"
 #include "../views/subghz_transmitter.h"
 #include <lib/subghz/protocols/subghz_protocol_keeloq.h>
+#include <dolphin/dolphin.h>
 
 void subghz_scene_transmitter_callback(SubghzCustomEvent event, void* context) {
     furi_assert(context);
@@ -50,6 +51,7 @@ bool subghz_scene_transmitter_update_data_show(void* context) {
 
 void subghz_scene_transmitter_on_enter(void* context) {
     SubGhz* subghz = context;
+    DOLPHIN_DEED(DolphinDeedSubGhzSend);
     if(!subghz_scene_transmitter_update_data_show(subghz)) {
         view_dispatcher_send_custom_event(
             subghz->view_dispatcher, SubghzCustomEventViewTransmitterError);

+ 2 - 0
applications/u2f/scenes/u2f_scene_main.c

@@ -1,5 +1,6 @@
 #include "../u2f_app_i.h"
 #include "../views/u2f_view.h"
+#include <dolphin/dolphin.h>
 #include "furi_hal.h"
 #include "../u2f.h"
 
@@ -63,6 +64,7 @@ bool u2f_scene_main_on_event(void* context, SceneManagerEvent event) {
         } else if(event.event == U2fCustomEventWink) {
             notification_message(app->notifications, &sequence_blink_green_10);
         } else if(event.event == U2fCustomEventAuthSuccess) {
+            DOLPHIN_DEED(DolphinDeedU2fAuthorized);
             osTimerStart(app->timer, U2F_SUCCESS_TIMEOUT);
             app->event_cur = U2fCustomEventNone;
             u2f_view_set_state(app->u2f_view, U2fMsgSuccess);

+ 9 - 5
assets/Makefile

@@ -20,14 +20,18 @@ $(PROTOBUF) &: $(PROTOBUF_SOURCES) $(PROTOBUF_COMPILER)
 .PHONY: protobuf
 protobuf: $(PROTOBUF)
 
-$(DOLPHIN_OUTPUT_DIR): $(DOLPHIN_SOURCE_DIR)
-	@echo "\tDOLPHIN"
-	@$(ASSETS_COMPILLER) dolphin "$(DOLPHIN_SOURCE_DIR)" "$(DOLPHIN_OUTPUT_DIR)"
+$(DOLPHIN_EXTERNAL_OUTPUT_DIR): $(DOLPHIN_SOURCE_DIR)
+	@echo "\tDOLPHIN blocking"
+	@$(ASSETS_COMPILLER) dolphin -s dolphin_blocking "$(DOLPHIN_SOURCE_DIR)/blocking" "$(DOLPHIN_INTERNAL_OUTPUT_DIR)"
+	@echo "\tDOLPHIN internal"
+	@$(ASSETS_COMPILLER) dolphin -s dolphin_internal "$(DOLPHIN_SOURCE_DIR)/internal" "$(DOLPHIN_INTERNAL_OUTPUT_DIR)"
+	@echo "\tDOLPHIN external"
+	@$(ASSETS_COMPILLER) dolphin "$(DOLPHIN_SOURCE_DIR)/external" "$(DOLPHIN_EXTERNAL_OUTPUT_DIR)"
 
 .PHONY: dolphin
-dolphin: $(DOLPHIN_OUTPUT_DIR)
+dolphin: $(DOLPHIN_EXTERNAL_OUTPUT_DIR)
 
 clean:
 	@echo "\tCLEAN\t"
 	@$(RM) $(ASSETS_COMPILED_DIR)/*
-	@$(RM) -rf $(DOLPHIN_OUTPUT_DIR)
+	@$(RM) -rf $(DOLPHIN_EXTERNAL_OUTPUT_DIR)

+ 3 - 2
assets/assets.mk

@@ -6,8 +6,9 @@ ASSETS_SOURCE_DIR	:= $(ASSETS_DIR)/icons
 ASSETS_SOURCES		+= $(shell find $(ASSETS_SOURCE_DIR) -type f -iname '*.png' -or -iname 'frame_rate')
 ASSETS				+= $(ASSETS_COMPILED_DIR)/assets_icons.c
 
-DOLPHIN_SOURCE_DIR	:= $(ASSETS_DIR)/dolphin
-DOLPHIN_OUTPUT_DIR	:= $(ASSETS_DIR)/resources/dolphin
+DOLPHIN_SOURCE_DIR			:= $(ASSETS_DIR)/dolphin
+DOLPHIN_INTERNAL_OUTPUT_DIR	:= $(ASSETS_COMPILED_DIR)
+DOLPHIN_EXTERNAL_OUTPUT_DIR	:= $(ASSETS_DIR)/resources/dolphin
 
 PROTOBUF_SOURCE_DIR		:= $(ASSETS_DIR)/protobuf
 PROTOBUF_COMPILER		:= $(PROJECT_ROOT)/lib/nanopb/generator/nanopb_generator.py

+ 320 - 0
assets/compiled/assets_dolphin_blocking.c

@@ -0,0 +1,320 @@
+#include <assets_dolphin_blocking.h>
+#include <desktop/animations/animation_storage_i.h>
+#include <desktop/animations/animation_manager.h>
+#include <gui/icon_i.h>
+
+
+
+const uint8_t _A_L0_NoDb_128x51_0[] = {
+    0x1,0x0,0x43,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf8,0x7,0xfc,0x0,0x33,0xf0,0x75,0x60,0x1,0xe5,0x7f,0x7,0xde,0x4e,0x49,0x49,0xb9,0x3,0xff,0x11,0x0,0x88,0x0,0x4,0x16,0x34,0x0,0x10,0xc1,0x1,0xf7,0x20,0x7,0xbe,0x62,0x19,0x9c,0x9d,0xdd,0xf3,0x91,0x99,0x1c,0x9a,0x3b,0x7,0x27,0xa6,0xa2,0x25,0x52,0xc9,0x65,0x2a,0x5a,0x4b,0x4,0xa7,0x4a,0x1f,0x10,0x79,0xf2,0x1,0xe7,0x92,0x9e,0x48,0x41,0xef,0x88,0x7,0x9c,0x4a,0xb,0x22,0x7,0xc0,0xfc,0x62,0x77,0x7e,0xe6,0x62,0x43,0xc6,0x92,0x8f,0xd5,0x3f,0xe0,0xff,0x7,0xf8,0x3f,0xc1,0xf9,0x7,0x7,0xbd,0xc2,0x3c,0xaf,0x2a,0x7,0xff,0xf3,0xfc,0x7,0xb6,0x22,0x44,0xf,0x2d,0x7,0xff,0x1d,0xfb,0x7,0xa6,0x2,0x73,0x8,0x91,0x63,0x27,0x70,0x7e,0x85,0xff,0xf7,0xf7,0x7,0xa5,0x2,0x95,0x70,0x91,0x54,0xb1,0x50,0x4f,0x86,0xff,0xfb,0xef,0x7,0xb6,0x2,0x45,0x42,0x7,0x9f,0xfc,0x1e,0xe3,0xf1,0xf,0x9f,0x7e,0x3f,0xdf,0x1f,0xad,0x24,0xbe,0x38,0xc8,0x5c,0x1c,0x1e,0x3e,0xfe,0xf1,0xfe,0xc1,0xe3,0xff,0x7,0xe7,0x0,0x5a,0x22,0xf5,0x7,0xc6,0xfc,0x1f,0xa5,0xf7,0x7,0xc6,0xfc,0x1e,0xff,0xe6,0xd1,0x40,0x68,0x17,0xff,0xff,0x9c,0x1e,0xb8,0x8,0x8,0x10,0xa0,0x4b,0xf1,0xff,0x70,0xc1,0xeb,0xc0,0x2,0x1c,0x13,0xa0,0xdf,0x1c,0x0,0x3e,0xfe,0xf,0xf1,0x83,0x83,0xda,0x11,0x2,0x80,0x42,0x1,0xe5,0xff,0x87,0xdf,0x81,0xeb,0x18,0x81,0xc0,0x23,0x0,0xf3,0x8f,0xdf,0x1,0xeb,0xa8,0x99,0x59,0xe7,0x0,0xf3,0x9f,0xde,0x1,0xeb,0x48,0xa5,0x64,0x6f,0x0,0xf3,0xbf,0x83,0xda,0x11,0x4a,0xf8,0x87,0xd3,0xfe,0xf,0x88,0x88,0xfd,0x4,0x2,0xf,0x69,0x95,0x84,0xbe,0x80,0xf7,0x3f,0xb0,0x3f,0xc1,0xf0,0xbf,0x40,0x7c,0xe0,0x1,0x24,0xdf,0x1f,0x0,0x10,0xa7,0xee,0xf5,0x7,0x98,0x25,0x40,0x1e,0x0,0xf0,0x7,0x80,0x28
+};
+
+const uint8_t _A_L0_NoDb_128x51_1[] = {
+    0x1,0x0,0x45,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf8,0x7,0xfc,0x0,0x33,0xf0,0x75,0x60,0x1,0xe5,0x7f,0x7,0xde,0x4e,0x49,0x49,0xb9,0x3,0xff,0x11,0x0,0x88,0x0,0x4,0x16,0x34,0x0,0x10,0xc1,0x1,0xf7,0x20,0x7,0xbe,0x62,0x19,0x9c,0x9d,0xdd,0xf3,0x91,0x99,0x1c,0x9a,0x3b,0x7,0x27,0xa6,0xa2,0x25,0x52,0xc9,0x65,0x2a,0x5a,0x4b,0x4,0xa7,0x4a,0x1f,0x10,0x79,0xf2,0x1,0xe7,0x92,0x9e,0x48,0x41,0xef,0x88,0x7,0x9c,0x4a,0xb,0x22,0x7,0xc0,0xfc,0x62,0x77,0x7e,0xe6,0x62,0x43,0xc6,0x92,0x8f,0xd5,0x3f,0xe0,0xff,0x7,0xf8,0x3f,0xc1,0xf9,0x1f,0xfe,0x3,0xda,0xe1,0x1e,0x57,0x95,0x3,0xff,0xe7,0xf9,0x83,0xdb,0x11,0x22,0x7,0x96,0x83,0xff,0x3b,0xf7,0x3,0xd3,0x1,0x39,0x84,0x48,0xb1,0x93,0xb8,0x3f,0x43,0xff,0xed,0xef,0x83,0xd2,0x81,0x4a,0xb8,0x48,0xaa,0x58,0xa8,0x27,0xc3,0xff,0xf6,0xdf,0x83,0xdb,0x1,0x22,0xa1,0x3,0xcf,0xfc,0xf,0x71,0xf8,0x87,0xce,0xff,0x1f,0xbf,0x8f,0xd6,0x92,0x5f,0x1c,0x64,0x2e,0xe,0xf,0x1f,0x7d,0xf8,0xff,0x60,0xf1,0xff,0x83,0xf3,0x80,0x2d,0x11,0x7a,0x83,0xe0,0x9c,0x20,0xfc,0x2f,0xb8,0x3e,0x37,0xc0,0xf7,0xff,0x36,0x8a,0x2,0xbf,0x1f,0xee,0x7c,0x1e,0xb8,0x8,0x8,0x10,0xa0,0x4b,0xf1,0xfd,0xc3,0xc1,0xeb,0xc0,0x2,0x1c,0x11,0x7e,0x3e,0x78,0x1d,0xf8,0x1f,0x4a,0xf1,0x8f,0xc7,0x2f,0x80,0xf5,0x84,0x40,0xa0,0x10,0x80,0x79,0x7f,0xe7,0xf7,0x80,0x7a,0xc6,0x20,0x70,0x8,0xc0,0x3c,0xef,0xf7,0x0,0x7a,0xea,0x26,0x56,0x79,0xc0,0x3c,0xff,0xf6,0x0,0x7a,0xd2,0x29,0x59,0x1b,0xc0,0x3d,0x2c,0x23,0xf6,0xa5,0x7c,0x43,0xeb,0x63,0x7,0xbc,0x44,0x7e,0x84,0x1,0x7,0xb4,0xca,0xc2,0x5f,0x40,0x7b,0x9f,0xd8,0x1f,0xe0,0xf8,0x5f,0xa0,0x3e,0x70,0x0,0x92,0x6f,0x8f,0x80,0x8,0x53,0xf7,0x7a,0x83,0xcc,0x12,0xa0,0xf,0x0,0x78,0x3,0xc0,0x14
+};
+
+const uint8_t _A_L0_NoDb_128x51_2[] = {
+    0x1,0x0,0x43,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf8,0x7,0xfc,0x0,0x33,0xf0,0x75,0x60,0x1,0xe5,0x7f,0x7,0xde,0x4e,0x49,0x49,0xb9,0x3,0xff,0x11,0x0,0x88,0x0,0x4,0x16,0x34,0x0,0x10,0xc1,0x1,0xf7,0x20,0x7,0xbe,0x62,0x19,0x9c,0x9d,0xdd,0xf3,0x91,0x99,0x1c,0x9a,0x3b,0x7,0x27,0xa6,0xa2,0x25,0x52,0xc9,0x65,0x2a,0x5a,0x4b,0x4,0xa7,0x4a,0x1f,0x10,0x79,0xf2,0x1,0xe7,0x92,0x9e,0x48,0x41,0xef,0x88,0x7,0x9c,0x4a,0xb,0x22,0x7,0xc0,0xfc,0x62,0x77,0x7e,0xe6,0x62,0x43,0xc6,0x92,0x8f,0xd5,0x3f,0xe0,0xff,0x7,0xf8,0x3f,0xc1,0xfe,0xa,0x5b,0x84,0x79,0x5e,0x54,0x0,0x7c,0xe2,0x24,0x40,0xf2,0xd0,0x7f,0xe3,0xff,0xc0,0x7a,0x60,0x27,0x30,0x89,0x16,0x32,0x77,0x7,0xe8,0x7f,0xfc,0xff,0x30,0x7a,0x50,0x29,0x57,0x9,0x15,0x4b,0x15,0x4,0xf8,0x7f,0xe7,0x7e,0xe0,0x7b,0x60,0x24,0x54,0x20,0x79,0xfb,0x7b,0xe0,0xf6,0x1f,0x88,0x7d,0x22,0xdb,0xf1,0xfa,0xd2,0x4b,0xe3,0x8c,0x85,0xc1,0xc1,0xe3,0xee,0xdf,0x1f,0xef,0xe1,0x7f,0xff,0xdf,0x81,0xf7,0xc0,0xbf,0x80,0x8,0x1f,0x83,0xe1,0x7,0xea,0x78,0x43,0xfe,0xf,0x6f,0xf2,0xe8,0xa0,0x2b,0xf1,0xff,0x1b,0xd4,0xe0,0x30,0x10,0x21,0x40,0x97,0xe2,0xf,0x7e,0x0,0x10,0xe0,0x8b,0xf1,0xfe,0xe7,0xc1,0xf6,0x8f,0x1f,0xdc,0x3c,0x1e,0xd0,0x88,0x14,0x2,0x10,0xf,0x2f,0x3c,0xe,0xfc,0xf,0x58,0xc4,0xe,0x1,0x18,0x7,0x94,0x7e,0x39,0x7c,0x7,0xae,0xa2,0x65,0x67,0x9c,0x3,0xcb,0xff,0x3f,0xbc,0x3,0xd6,0x91,0x4a,0xc8,0xde,0x1,0xe7,0x7f,0xb8,0xf,0xda,0x95,0xf1,0xf,0xa7,0xfe,0xc0,0xf,0x78,0x88,0xfc,0xc0,0x3,0x61,0x7,0xb4,0xca,0xc2,0x5f,0x30,0x0,0xd8,0xcf,0xf8,0x40,0x20,0x7f,0x83,0xd5,0x7e,0x80,0xf9,0xc0,0x2,0x49,0xbe,0x3e,0x0,0x21,0x4f,0xdd,0xea,0xf,0x30,0x4a,0x80,0x3c,0x1,0xe0,0xf,0x0,0x50
+};
+
+const uint8_t _A_L0_NoDb_128x51_3[] = {
+    0x1,0x0,0x43,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf8,0x7,0xfc,0x0,0x33,0xf0,0x75,0x60,0x1,0xe5,0x7f,0x7,0xde,0x4e,0x49,0x49,0xb9,0x3,0xff,0x11,0x0,0x88,0x0,0x4,0x16,0x34,0x0,0x10,0xc1,0x1,0xf7,0x20,0x7,0xbe,0x62,0x19,0x9c,0x9d,0xdd,0xf3,0x91,0x99,0x1c,0x9a,0x3b,0x7,0x27,0xa6,0xa2,0x25,0x52,0xc9,0x65,0x2a,0x5a,0x4b,0x4,0xa7,0x4a,0x1f,0x10,0x79,0xf2,0x1,0xe7,0x92,0x9e,0x48,0x41,0xef,0x88,0x7,0x9c,0x4a,0xb,0x22,0x7,0xc0,0xfc,0x62,0x77,0x7e,0xe6,0x62,0x43,0xc6,0x92,0x8f,0xd5,0x3f,0xe0,0xff,0x7,0xf8,0x3f,0xc1,0xfe,0xa,0x5b,0x84,0x79,0x5e,0x54,0x0,0x7c,0xe2,0x24,0x40,0xf2,0xd0,0x7f,0xe0,0xe0,0xf5,0xc0,0x4e,0x61,0x12,0x2c,0x64,0xee,0xf,0xd0,0xff,0xfe,0x7f,0x80,0xf4,0xa0,0x52,0xae,0x12,0x2a,0x96,0x2a,0x9,0xf0,0xff,0xe3,0xbf,0x60,0xf6,0xc0,0x48,0xa8,0x40,0xf2,0xbf,0xfe,0xfe,0xe0,0xf6,0x1f,0x88,0x7c,0xf7,0xf1,0xdf,0x78,0xfd,0x69,0x25,0xf1,0xc6,0x42,0xe0,0xe0,0xf1,0xf7,0xfb,0x8f,0xf7,0xf0,0xef,0xff,0xfb,0xc0,0xfb,0xe0,0x77,0xef,0xe0,0x11,0x7,0xe6,0xfc,0x1f,0xdf,0xf0,0xff,0x83,0xdf,0xfc,0xba,0x28,0xd,0x3,0x7f,0xff,0x37,0xa9,0xc0,0x60,0x20,0x42,0x81,0x68,0x1,0xf1,0xc0,0x2,0x1c,0x13,0xa1,0x7f,0xff,0xf9,0xc1,0xf6,0xbf,0x1f,0xf7,0xc,0x1e,0xd0,0x88,0x14,0x2,0x10,0xf,0x2f,0xce,0x0,0x1e,0xd1,0x88,0x1c,0x2,0x30,0xf,0x28,0x3c,0x1c,0x1e,0xda,0x89,0x95,0x9e,0x70,0xf,0x2f,0xfc,0x3e,0xfc,0xf,0x5a,0x45,0x2b,0x23,0x78,0x7,0x9c,0x7e,0xf8,0x3f,0x6a,0x57,0xc4,0x3e,0x93,0xfb,0xc0,0x3d,0xe2,0x23,0xf3,0xff,0xdf,0xc1,0xef,0x32,0xb0,0x97,0xcc,0x0,0x20,0xf6,0x3f,0xb0,0x80,0x81,0xfe,0xf,0x55,0xfa,0x3,0xe7,0x0,0x9,0x26,0xf8,0xf8,0x0,0x85,0x3f,0x77,0xa8,0x3c,0xc1,0x2a,0x0,0xf0,0x7,0x80,0x3c,0x1,0x40
+};
+
+
+const uint8_t *_A_L0_NoDb_128x51[] = {
+
+    _A_L0_NoDb_128x51_0,
+
+    _A_L0_NoDb_128x51_1,
+
+    _A_L0_NoDb_128x51_2,
+
+    _A_L0_NoDb_128x51_3,
+
+};
+
+
+
+const BubbleAnimation BA_L0_NoDb_128x51 = {
+    .icon_animation = {
+        .width = 128,
+        .height = 51,
+        .frame_count = 4,
+        .frame_rate = 2,
+        .frames = _A_L0_NoDb_128x51
+    },
+    .frame_order = { 0, 1, 2, 3 },
+    .passive_frames = 4,
+    .active_frames = 0,
+    .active_cooldown = 0,
+    .active_cycles = 0,
+    .duration = 0,
+
+    .frame_bubble_sequences = NULL,
+    .frame_bubble_sequences_count = 0,
+
+};
+
+
+const uint8_t _A_L0_SdBad_128x51_0[] = {
+    0x1,0x0,0x1,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf8,0x7,0xfc,0x0,0x33,0xf0,0x75,0x60,0x1,0xe5,0x7f,0x7,0xde,0x4e,0x49,0x49,0xb9,0x3,0xfe,0x1,0x82,0x4,0xc,0x4a,0x1,0x70,0x8f,0x1,0x46,0x8,0xf,0x5e,0x30,0x24,0x60,0x50,0xc,0x44,0x88,0x1f,0x1a,0xaa,0x64,0xeb,0xaf,0x71,0x84,0x48,0xb1,0x93,0xb8,0x39,0x3d,0x72,0x55,0x2a,0x50,0x4,0x6e,0x12,0x2a,0x96,0x28,0x3e,0x20,0xf4,0xc1,0x3,0xcf,0x1,0x22,0xa1,0x3,0xf0,0x7e,0x21,0xf9,0xc6,0x52,0xea,0x57,0x22,0xf8,0xe3,0x21,0x63,0xf6,0x0,0x1d,0x1,0x3f,0xd3,0x5,0x7f,0x83,0xfc,0x1f,0xe0,0xf5,0xcf,0xfc,0xee,0x77,0xe0,0x5b,0x7e,0x28,0x10,0x18,0x25,0x2,0x7f,0xf9,0x93,0x87,0xde,0x11,0x0,0x7,0x8e,0x82,0xff,0xfc,0xc7,0x83,0xe2,0xb9,0x19,0x83,0xf4,0x1,0xf5,0x78,0xa9,0x69,0x60,0x9f,0x1,0x7d,0xd4,0xb7,0xa0,0xf1,0x27,0xd0,0x3c,0x70,0xa0,0xf1,0x37,0xd0,0xfc,0xc1,0xf6,0x0,0x30,0x7f,0xf0,0x1,0xff,0xff,0x83,0xfe,0x1,0xfb,0xf9,0xf0,0x83,0xf6,0x19,0xc6,0x7,0xb0,0xe8,0xe0,0x34,0xf,0xfc,0x1b,0x90,0xf,0x69,0x80,0xc,0xa0,0x5a,0xf,0xfc,0xd8,0x1e,0xf1,0x80,0x19,0x41,0x3a,0x5,0xd1,0xfe,0x3,0xec,0xff,0x51,0x8b,0x84,0x8a,0x4,0xf,0xcc,0x44,0x4a,0xc,0xf,0xd8,0x54,0x38,0x1f,0xb0,0x68,0xf0,0x7f,0xc7,0xfe,0x5f,0xf0,0x7e,0x1f,0xfc,0x1f,0xd3,0x85,0xf9,0x83,0xe8,0x14,0x70,0x1f,0x0,0x10,0xa7,0xe0,0xf5,0x5,0x18,0x25,0x40,0x1e,0x0,0xf0,0x7,0x80,0x28
+};
+
+const uint8_t _A_L0_SdBad_128x51_1[] = {
+    0x1,0x0,0x12,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf8,0x7,0xfc,0x0,0x33,0xf0,0x75,0x60,0x1,0xe5,0x7f,0x7,0xde,0x4e,0x49,0x49,0xb9,0x3,0xfe,0x1,0x82,0x4,0xc,0x4a,0x1,0x70,0x8f,0x1,0x46,0x8,0xf,0x5e,0x30,0x24,0x60,0x50,0xc,0x44,0x88,0x1f,0x1a,0xaa,0x64,0xeb,0xaf,0x71,0x84,0x48,0xb1,0x93,0xb8,0x39,0x3d,0x72,0x55,0x2a,0x50,0x4,0x6e,0x12,0x2a,0x96,0x28,0x3e,0x20,0xf4,0xc1,0x3,0xcf,0x1,0x22,0xa1,0x3,0xf0,0x7e,0x21,0xf9,0xc6,0x52,0xea,0x57,0x22,0xf8,0xe3,0x21,0x63,0xf6,0x0,0x1d,0x1,0x3f,0xd3,0x5,0x7f,0x83,0xfc,0x1f,0xe0,0xf5,0xcf,0xfc,0xee,0x77,0xe0,0x5b,0x7e,0x28,0x10,0x18,0x25,0x2,0x7f,0xf9,0x93,0x87,0xde,0x11,0x0,0x7,0x8e,0x82,0xff,0xfc,0xc7,0x83,0xe2,0xb9,0x19,0x83,0xf4,0x1,0xf5,0x78,0xa9,0x69,0x60,0x9f,0x1,0x7d,0xd4,0xb7,0xa0,0xf1,0x27,0xd0,0x3c,0x70,0xa0,0xf1,0x37,0xd0,0xfc,0xc1,0xf6,0x0,0x30,0x7f,0xf0,0x1,0xff,0xff,0x81,0xfc,0x1,0xfb,0xf8,0xe0,0x83,0xf2,0xff,0x4c,0xc3,0x3,0xd8,0x74,0x70,0x15,0xf8,0xe1,0xa1,0x0,0xf6,0x98,0x0,0xca,0x5,0xa0,0x9f,0xc5,0xa1,0x20,0xf6,0x8c,0x0,0xca,0x9,0xd0,0xbb,0xcf,0xb3,0x17,0xb0,0x7d,0x7c,0x3e,0xf3,0xff,0xc0,0x3d,0xee,0x12,0x28,0x10,0x3c,0x7d,0xee,0x1,0xbe,0x83,0xdb,0x11,0x12,0x83,0x2f,0xec,0x1e,0x30,0xa8,0x70,0x3f,0x60,0xd1,0xe0,0xff,0x83,0xf4,0x7f,0xc5,0xf4,0x7,0xd1,0xfd,0x1,0xfe,0xf,0x99,0xc2,0xfc,0xc1,0xf4,0xa,0x38,0xf,0x80,0x8,0x53,0xf0,0x7a,0x82,0x8c,0x12,0xa0,0xf,0x0,0x78,0x3,0xc0,0x14
+};
+
+
+const uint8_t *_A_L0_SdBad_128x51[] = {
+
+    _A_L0_SdBad_128x51_0,
+
+    _A_L0_SdBad_128x51_1,
+
+};
+
+
+
+const BubbleAnimation BA_L0_SdBad_128x51 = {
+    .icon_animation = {
+        .width = 128,
+        .height = 51,
+        .frame_count = 2,
+        .frame_rate = 2,
+        .frames = _A_L0_SdBad_128x51
+    },
+    .frame_order = { 0, 1 },
+    .passive_frames = 2,
+    .active_frames = 0,
+    .active_cooldown = 0,
+    .active_cycles = 0,
+    .duration = 0,
+
+    .frame_bubble_sequences = NULL,
+    .frame_bubble_sequences_count = 0,
+
+};
+
+
+const uint8_t _A_L0_SdOk_128x51_0[] = {
+    0x1,0x0,0x19,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf8,0x7,0xfc,0x0,0x33,0xf0,0x75,0x60,0x1,0xe5,0x7f,0x7,0xde,0x4e,0x49,0x49,0xb9,0x3,0xfc,0xe,0x54,0xec,0xe,0x0,0x8,0x35,0x48,0x20,0x3e,0x2a,0x20,0xf3,0xa8,0x3,0xeb,0xc3,0xdc,0x9c,0xc9,0x2a,0xb1,0xc8,0x19,0x3d,0xeb,0xf9,0x1c,0x94,0x90,0x1e,0x3a,0x48,0x20,0x3d,0xea,0x20,0xf5,0x83,0x83,0xf8,0xff,0x3,0xf1,0xce,0x4e,0x3b,0x15,0x41,0xfc,0xa7,0xfc,0x1f,0xe0,0xff,0x7,0xcc,0x1f,0xf8,0xf,0xdf,0xcf,0xcc,0x1e,0x8a,0xa1,0xb8,0x47,0x80,0xa5,0x40,0xff,0xff,0xbd,0xe0,0xf6,0xc4,0x48,0x80,0xa5,0xa0,0xbf,0xff,0xfb,0xe0,0xf1,0xb0,0x4b,0xa3,0x38,0x79,0xcc,0x22,0x45,0x8c,0x9d,0xc1,0xfa,0x1b,0xff,0xfe,0xfc,0x1e,0x31,0x9,0x4e,0x96,0x89,0x4a,0xb8,0x48,0xaa,0x58,0xa8,0x27,0xc0,0x1e,0x92,0x9,0x4e,0xf4,0x1e,0x38,0x9,0x15,0x8,0x1e,0x5d,0xf3,0x70,0x83,0xc6,0x81,0x29,0xc2,0x83,0xc4,0x7e,0x21,0xf3,0x7,0xa4,0xc3,0x9d,0x18,0xc3,0xd2,0x4b,0xe3,0x8c,0x85,0xc1,0xc1,0xea,0x0,0x90,0x78,0x9f,0x84,0x1f,0x7c,0x0,0x7f,0xf7,0xfe,0xe0,0xfe,0x1f,0xef,0x0,0xfe,0x80,0xa5,0x75,0x14,0x6,0x80,0xf,0x99,0x80,0xc,0xa0,0x4b,0xf5,0x0,0x24,0x61,0xaa,0x7d,0x6,0xfb,0x83,0xdb,0xe0,0xff,0x70,0x79,0x34,0x6,0x4,0xf,0x28,0x3f,0xf0,0x1e,0xf8,0x88,0x94,0x18,0x1e,0x40,0x1,0x7,0xc4,0x2a,0x1c,0xf,0xd8,0x34,0x78,0x3f,0xe0,0xfd,0x1f,0xf1,0x7d,0x41,0xf2,0x7f,0x50,0x7f,0x83,0xe2,0x70,0xbf,0x30,0x7d,0x2,0x8e,0x3,0xe0,0x2,0x14,0xfc,0x1e,0xa0,0xa3,0x4,0xa8,0x3,0xc0,0x1e,0x0,0xf0,0x5,0x0
+};
+
+const uint8_t _A_L0_SdOk_128x51_1[] = {
+    0x1,0x0,0x1e,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf8,0x7,0xfc,0x0,0x33,0xf0,0x75,0x60,0x1,0xe5,0x7f,0x7,0xde,0x4e,0x49,0x49,0xb9,0x3,0xfc,0xe,0x54,0xec,0xe,0x0,0x8,0x35,0x48,0x20,0x3e,0x2a,0x20,0xf3,0xa8,0x3,0xeb,0xc3,0xdc,0x9c,0xc9,0x2a,0xb1,0xc8,0x19,0x3d,0xeb,0xf9,0x1c,0x94,0x90,0x1e,0x3a,0x48,0x20,0x3d,0xea,0x20,0xf5,0x83,0x83,0xf8,0xff,0x3,0xf1,0xce,0x4e,0x3b,0x15,0x41,0xfc,0xa7,0xfc,0x1f,0xe0,0xff,0x7,0xcc,0x1f,0xf8,0xf,0xdf,0xcf,0xcc,0x1e,0x8a,0xa1,0xb8,0x47,0x80,0xa5,0x40,0xff,0xff,0xbd,0xe0,0xf6,0xc4,0x48,0x80,0xa5,0xa0,0xbf,0xff,0xfb,0xe0,0xf1,0xb0,0x4b,0xa3,0x38,0x79,0xcc,0x22,0x45,0x8c,0x9d,0xc1,0xfa,0x1b,0xff,0xfe,0xfc,0x1e,0x31,0x9,0x4e,0x96,0x89,0x4a,0xb8,0x48,0xaa,0x58,0xa8,0x27,0xc0,0x1e,0x92,0x9,0x4e,0xf4,0x1e,0x38,0x9,0x15,0x8,0x1e,0x5d,0xf3,0x70,0x83,0xc6,0x81,0x29,0xc2,0x83,0xc4,0x7e,0x21,0xf3,0x7,0xa4,0xc3,0x9d,0x18,0xc3,0xd2,0x4b,0xe3,0x8c,0x85,0xc1,0xc1,0xea,0x0,0x90,0x78,0x9f,0x84,0x1f,0x7c,0xe,0xff,0x8c,0x1f,0xd8,0x70,0x7f,0x63,0xc1,0xfb,0xbf,0xcf,0x9f,0xc8,0x14,0xae,0xa2,0x80,0xd0,0x11,0xe8,0x0,0x49,0x80,0xc,0xa0,0x4b,0xf5,0x0,0x24,0x61,0xaa,0x7d,0x6,0xfb,0x83,0xdb,0xe0,0xff,0x70,0x79,0x34,0x6,0x4,0xf,0x28,0x3f,0xf0,0x1e,0xf8,0x88,0x94,0x18,0x1e,0x40,0x1,0x7,0xc4,0x2a,0x1c,0xf,0xd8,0x34,0x78,0x3f,0xe0,0xfd,0x1f,0xf1,0x7d,0x41,0xf2,0x7f,0x50,0x7f,0x83,0xe2,0x70,0xbf,0x30,0x7d,0x2,0x8e,0x3,0xe0,0x2,0x14,0xfc,0x1e,0xa0,0xa3,0x4,0xa8,0x3,0xc0,0x1e,0x0,0xf0,0x5,0x0
+};
+
+const uint8_t _A_L0_SdOk_128x51_2[] = {
+    0x1,0x0,0x22,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf8,0x7,0xfc,0x0,0x33,0xf0,0x75,0x60,0x1,0xe5,0x7f,0x7,0xde,0x4e,0x49,0x49,0xb9,0x3,0xfc,0xe,0x54,0xec,0xe,0x0,0x8,0x35,0x48,0x20,0x3e,0x2a,0x20,0xf3,0xa8,0x3,0xeb,0xc3,0xdc,0x9c,0xc9,0x2a,0xb1,0xc8,0x19,0x3d,0xeb,0xf9,0x1c,0x94,0x90,0x1e,0x3a,0x48,0x20,0x3d,0xea,0x20,0xf5,0x83,0x83,0xf8,0xff,0x3,0xf1,0xce,0x4e,0x3b,0x15,0x41,0xfc,0xa7,0xfc,0x1f,0xe0,0xff,0x7,0xcc,0x1f,0xf8,0xf,0xdf,0xcf,0xcc,0x1e,0x8a,0xa1,0xb8,0x47,0x80,0xa5,0x40,0xff,0xff,0xbd,0xe0,0xf6,0xc4,0x48,0x80,0xa5,0xa0,0xbf,0xff,0xfb,0xe0,0xf1,0xb0,0x4b,0xa3,0x38,0x79,0xcc,0x22,0x45,0x8c,0x9d,0xc1,0xfa,0x1b,0xff,0xfe,0xfc,0x1e,0x31,0x9,0x4e,0x96,0x89,0x4a,0xb8,0x48,0xaa,0x58,0xa8,0x27,0xc0,0x1e,0x92,0x9,0x4e,0xf4,0x1e,0x38,0x9,0x15,0x8,0x1e,0x5d,0xf3,0x70,0x83,0xc6,0x81,0x29,0xc2,0x83,0xc4,0x7e,0x21,0xf3,0x7,0xa4,0xc3,0x9d,0x18,0xc3,0xd2,0x4b,0xe3,0x8c,0x85,0xc1,0xc1,0xe5,0x7d,0x3f,0xd8,0x3c,0x7e,0x77,0xc0,0x7d,0xf0,0x3b,0xf6,0x30,0x7f,0x41,0xef,0xc0,0xfd,0x87,0x93,0xc8,0x1f,0x5b,0xfc,0xf9,0xfc,0x81,0x4a,0xea,0x28,0xd,0x1,0x1e,0x80,0x4,0x98,0x0,0xca,0x4,0xbf,0x50,0x2,0x46,0x1a,0xa7,0xd0,0x6f,0xb8,0x3d,0xbe,0xf,0xf7,0x7,0x93,0x40,0x60,0x40,0xf2,0x83,0xff,0x1,0xef,0x88,0x89,0x41,0x81,0xe4,0x0,0x10,0x7c,0x42,0xa1,0xc0,0xfd,0x83,0x47,0x83,0xfe,0xf,0xd1,0xff,0x17,0xd4,0x1f,0x27,0xf5,0x7,0xf8,0x3e,0x27,0xb,0xf3,0x7,0xd0,0x28,0xe0,0x3e,0x0,0x21,0x4f,0xc1,0xea,0xa,0x30,0x4a,0x80,0x3c,0x1,0xe0,0xf,0x0,0x50
+};
+
+const uint8_t _A_L0_SdOk_128x51_3[] = {
+    0x1,0x0,0x26,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf8,0x7,0xfc,0x0,0x33,0xf0,0x75,0x60,0x1,0xe5,0x7f,0x7,0xde,0x4e,0x49,0x49,0xb9,0x3,0xfc,0xe,0x54,0xec,0xe,0x0,0x8,0x35,0x48,0x20,0x3e,0x2a,0x20,0xf3,0xa8,0x3,0xeb,0xc3,0xdc,0x9c,0xc9,0x2a,0xb1,0xc8,0x19,0x3d,0xeb,0xf9,0x1c,0x94,0x90,0x1e,0x3a,0x48,0x20,0x3d,0xea,0x20,0xf5,0x83,0x83,0xf8,0xff,0x3,0xf1,0xce,0x4e,0x3b,0x15,0x41,0xfc,0xa7,0xfc,0x1f,0xe0,0xff,0x7,0xcc,0x1f,0xf8,0xf,0xdf,0xcf,0xcc,0x1e,0x8a,0xa1,0xb8,0x47,0x80,0xa5,0x40,0xff,0xff,0xbd,0xe0,0xf6,0xc4,0x48,0x80,0xa5,0xa0,0xbf,0xff,0xfb,0xe0,0xf1,0xb0,0x4b,0xa3,0x38,0x79,0xcc,0x22,0x45,0x8c,0x9d,0xc1,0xfa,0x1b,0xff,0xfe,0xfc,0x1e,0x31,0x9,0x4e,0x96,0x89,0x4a,0xb8,0x48,0xaa,0x58,0xa8,0x27,0xc0,0x1e,0x92,0x9,0x4e,0xf4,0x1e,0x38,0x9,0x15,0x8,0x1e,0x5d,0xf3,0x70,0x83,0xc6,0x81,0x29,0xc2,0x83,0xc4,0x7e,0x21,0xf3,0x7,0x8d,0xcc,0x1e,0x33,0xe,0x74,0x63,0xf,0x49,0x2f,0x8e,0x32,0x17,0x7,0x7,0x95,0xc4,0xff,0x60,0xf1,0xf9,0xde,0x1,0xf7,0xc0,0xef,0xd8,0xef,0x80,0xfd,0x83,0xdf,0x81,0xfb,0xf,0x2f,0x90,0x3e,0xb7,0xf9,0xf3,0xf9,0x2,0x95,0xd4,0x50,0x1a,0x2,0x3d,0x0,0x9,0x30,0x1,0x94,0x9,0x7e,0xa0,0x4,0x8c,0x35,0x4f,0xa0,0xdf,0x70,0x7b,0x7c,0x1f,0xee,0xf,0x26,0x80,0xc0,0x81,0xe5,0x7,0xfe,0x3,0xdf,0x11,0x12,0x83,0x3,0xc8,0x0,0x20,0xf8,0x85,0x43,0x81,0xfb,0x6,0x8f,0x7,0xfc,0x1f,0xa3,0xfe,0x2f,0xa8,0x3e,0x4f,0xea,0xf,0xf0,0x7c,0x4e,0x17,0xe6,0xf,0xa0,0x51,0xc0,0x7c,0x0,0x42,0x9f,0x83,0xd4,0x14,0x60,0x95,0x0,0x78,0x3,0xc0,0x1e,0x0,0xa0
+};
+
+
+const uint8_t *_A_L0_SdOk_128x51[] = {
+
+    _A_L0_SdOk_128x51_0,
+
+    _A_L0_SdOk_128x51_1,
+
+    _A_L0_SdOk_128x51_2,
+
+    _A_L0_SdOk_128x51_3,
+
+};
+
+
+
+const BubbleAnimation BA_L0_SdOk_128x51 = {
+    .icon_animation = {
+        .width = 128,
+        .height = 51,
+        .frame_count = 4,
+        .frame_rate = 2,
+        .frames = _A_L0_SdOk_128x51
+    },
+    .frame_order = { 0, 1, 2, 3 },
+    .passive_frames = 4,
+    .active_frames = 0,
+    .active_cooldown = 0,
+    .active_cycles = 0,
+    .duration = 0,
+
+    .frame_bubble_sequences = NULL,
+    .frame_bubble_sequences_count = 0,
+
+};
+
+
+const uint8_t _A_L0_Url_128x51_0[] = {
+    0x1,0x0,0x33,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf8,0x7,0xfc,0x0,0x33,0xf0,0x75,0x60,0x1,0xe5,0x7f,0x7,0xde,0x4e,0x49,0x49,0xb9,0x3,0xfc,0x16,0xf0,0x38,0x80,0x3f,0x28,0x10,0x40,0x7f,0x58,0x27,0x10,0x32,0x7d,0xd0,0x32,0xd9,0x8,0x20,0x3f,0x32,0x80,0xff,0x7,0xee,0x2,0xc7,0x10,0x1f,0xe2,0x7f,0xc1,0xfe,0xf,0xf0,0x7f,0x83,0xd6,0xe,0xf,0x29,0xf0,0x20,0x60,0x30,0x1d,0x40,0x10,0x8e,0x63,0xff,0xfc,0xff,0x1,0xe3,0x47,0x7,0x9c,0x90,0x1e,0x7a,0xf,0xfe,0x3b,0xf7,0x7f,0xc0,0x6b,0xa9,0x1c,0x7d,0xcc,0xcf,0x49,0xce,0xe0,0xe6,0x60,0x9d,0xb,0xff,0xef,0xee,0xf,0x1d,0xe5,0x22,0x53,0x25,0xa4,0xeb,0x2a,0x52,0x2d,0x2c,0x13,0xe1,0xbf,0xfe,0xfb,0xc1,0xe3,0x8f,0x7,0x95,0xe7,0x48,0xf,0x1d,0xe8,0x3c,0xbf,0xe0,0xf2,0xcf,0x3,0xca,0x12,0xf,0x2c,0x28,0x3c,0x7b,0xf1,0xfe,0xf8,0x3c,0x7b,0xd7,0xe,0x3c,0xe6,0x63,0xa5,0xe7,0x72,0x63,0x30,0x30,0x78,0xfb,0xfb,0xc5,0xf1,0x0,0x89,0x64,0x40,0x3,0x42,0x1,0x90,0x3c,0x7f,0xe0,0xf2,0x3f,0x88,0x3d,0xf8,0x2,0xd1,0x0,0x8a,0x7e,0x81,0xe3,0xbf,0x7,0xe9,0x7c,0xc1,0xf9,0xbf,0x7,0xc7,0xe1,0xb4,0x30,0x1a,0x5,0xff,0xff,0xe7,0x7,0xbc,0x18,0x4,0x30,0x25,0xf8,0xff,0xb8,0x60,0xf7,0x81,0x80,0x85,0x7e,0x2d,0xf1,0xc0,0x3,0xef,0xe0,0xff,0x18,0x38,0x3d,0xf8,0x78,0x98,0x40,0x3c,0xbf,0xf0,0xfb,0xf0,0x3d,0xa4,0x74,0xa8,0xc0,0x3c,0xe3,0xf7,0xc0,0x7b,0xca,0xa7,0x0,0xf3,0x9f,0xde,0x1,0xef,0x1a,0xbc,0x3,0xce,0xfe,0xf,0x80,0xfa,0xff,0xc1,0xf0,0x3f,0x51,0x0,0x97,0xf4,0x1f,0x7,0xf5,0x7,0xf8,0x3e,0x60,0xeb,0xf2,0x7,0xdf,0xf9,0xbe,0x5e,0x0,0x79,0x4f,0xc1,0xed,0xfc,0x5,0x8,0x25,0x80,0x1e,0x0,0xf0,0x7,0x80,0x24
+};
+
+const uint8_t _A_L0_Url_128x51_1[] = {
+    0x1,0x0,0x33,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf8,0x7,0xfc,0x0,0x33,0xf0,0x75,0x60,0x1,0xe5,0x7f,0x7,0xde,0x4e,0x49,0x49,0xb9,0x3,0xfc,0x16,0xf0,0x38,0x80,0x3f,0x28,0x10,0x40,0x7f,0x58,0x27,0x10,0x32,0x7d,0xd0,0x32,0xd9,0x8,0x20,0x3f,0x32,0x80,0xff,0x7,0xee,0x2,0xc7,0x10,0x1f,0xe2,0x7f,0xc1,0xfe,0xf,0xf0,0x7f,0x83,0xd6,0x3f,0xfc,0x7,0x8c,0xf8,0x10,0x30,0x18,0xe,0xa0,0x8,0x47,0x31,0xff,0xf9,0xfe,0x60,0xf1,0xa3,0x83,0xce,0x48,0xf,0x3d,0x7,0xfe,0x77,0xee,0xbf,0xe0,0x35,0xd4,0x8e,0x3e,0xe6,0x67,0xa4,0xe7,0x70,0x73,0x30,0x4e,0x87,0xff,0xdb,0xdf,0x7,0x8e,0xf2,0x91,0x29,0x92,0xd2,0x75,0x95,0x29,0x16,0x96,0x9,0xf0,0xff,0xfd,0xb7,0xe0,0xf1,0xc7,0x83,0xca,0xf3,0xa4,0x7,0x8e,0xf4,0x1e,0x5f,0xe0,0x79,0x67,0x81,0xe5,0x9,0x7,0x96,0x14,0x1e,0x37,0xf8,0xfd,0xfc,0x1e,0x3d,0xeb,0x87,0x1e,0x73,0x31,0xd2,0xf3,0xb9,0x31,0x98,0x18,0x3c,0x7d,0xf7,0xe2,0xf8,0x80,0x44,0xb2,0x20,0x1,0xa1,0x0,0xc8,0x1e,0x3f,0xf0,0x79,0x1f,0xc4,0x1e,0xfc,0x1,0x68,0x80,0x5,0x3f,0x40,0xf1,0x27,0x8,0x3f,0xb,0xe6,0xf,0xcd,0xf0,0x3e,0x3f,0xd,0xa1,0x80,0xaf,0xc7,0xfb,0x9f,0x7,0xbc,0x18,0x4,0x30,0x25,0xf8,0xfe,0xe1,0xe0,0xf7,0x81,0x80,0x85,0x7e,0x5e,0x78,0x1d,0xf8,0x1f,0x4a,0xf1,0x8f,0xc7,0x2f,0x80,0xf6,0xe1,0xe2,0x61,0x0,0xf2,0xff,0xcf,0xef,0x0,0xf6,0x91,0xd2,0xa3,0x0,0xf3,0xbf,0xdc,0x1,0xef,0x2a,0x9c,0x3,0xcf,0xff,0x60,0x7,0xbc,0x6a,0xf0,0xf,0x4b,0x8,0x7f,0xac,0x63,0xfd,0x20,0x9,0x7f,0x41,0xf0,0x7f,0x50,0x7f,0x83,0xe6,0xe,0xbf,0x20,0x7d,0xff,0x9b,0xe5,0xe0,0x7,0x94,0xfc,0x1e,0xdf,0xc0,0x50,0x82,0x58,0x1,0xe0,0xf,0x0,0x78,0x2,0x40
+};
+
+const uint8_t _A_L0_Url_128x51_2[] = {
+    0x1,0x0,0x2e,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf8,0x7,0xfc,0x0,0x33,0xf0,0x75,0x60,0x1,0xe5,0x7f,0x7,0xde,0x4e,0x49,0x49,0xb9,0x3,0xfc,0x16,0xf0,0x38,0x80,0x3f,0x28,0x10,0x40,0x7f,0x58,0x27,0x10,0x32,0x7d,0xd0,0x32,0xd9,0x8,0x20,0x3f,0x32,0x80,0xff,0x7,0xee,0x2,0xc7,0x10,0x1f,0xe2,0x7f,0xc1,0xfe,0xf,0xf0,0x7f,0x83,0xe6,0x7c,0x8,0x18,0xc,0x7,0x50,0x4,0x23,0x98,0x83,0xd2,0x8e,0xf,0x39,0x20,0x3c,0xf4,0x1f,0xf8,0xff,0xf2,0xff,0x80,0xd7,0x52,0x38,0xfb,0x99,0x9e,0x93,0x9d,0xc1,0xcc,0xc1,0x3a,0x1f,0xff,0x3f,0xcc,0x1e,0x3b,0xca,0x44,0xa6,0x4b,0x49,0xd6,0x54,0xa4,0x5a,0x58,0x27,0xc3,0xff,0x3b,0xf7,0x3,0xc7,0x1e,0xf,0x2b,0xce,0x90,0x1e,0x3b,0xd0,0x79,0x7b,0x7b,0xe0,0xf1,0xcf,0x3,0xca,0x12,0xf,0x2c,0x28,0x3c,0xa2,0xdb,0xf0,0x78,0xf7,0xae,0x1c,0x79,0xcc,0xc7,0x4b,0xce,0xe4,0xc6,0x60,0x60,0xf1,0xf7,0x6f,0x8b,0xe2,0x1,0x12,0xc8,0x80,0x6,0x84,0x3,0x2f,0x85,0xff,0xff,0x7e,0x3f,0x98,0x3d,0xf8,0x17,0xf0,0x1,0x27,0xe8,0x1e,0x23,0xe1,0x7,0xea,0x78,0x43,0xfe,0xf,0x7f,0xc2,0xe8,0x60,0x2b,0xf1,0xff,0x4,0x4,0x1e,0xd0,0x60,0x10,0xc0,0x97,0xe2,0xf,0x98,0x18,0x8,0x57,0xe5,0xfd,0xcf,0x83,0xed,0x1e,0x3f,0xb8,0x78,0x3d,0xf8,0x78,0x98,0x40,0x3c,0xbc,0xf0,0x3b,0xf0,0x3d,0xa4,0x74,0xa8,0xc0,0x3c,0xa3,0xf1,0xcb,0xe0,0x3d,0xe5,0x53,0x80,0x79,0x7f,0xe7,0xf7,0x80,0x7b,0xc6,0xaf,0x0,0xf3,0xbf,0xdc,0x3,0xfb,0xff,0xb0,0xf,0xf0,0x0,0x36,0x12,0xfe,0x0,0x6,0xc6,0x7f,0xc2,0x1,0x3,0xfc,0x1e,0xd0,0x75,0xf9,0x3,0xef,0xfc,0xdf,0x2f,0x0,0x3c,0xa7,0xe0,0xf6,0xfe,0x2,0x84,0x12,0xc0,0xf,0x0,0x78,0x3,0xc0,0x12
+};
+
+const uint8_t _A_L0_Url_128x51_3[] = {
+    0x1,0x0,0x31,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf8,0x7,0xfc,0x0,0x33,0xf0,0x75,0x60,0x1,0xe5,0x7f,0x7,0xde,0x4e,0x49,0x49,0xb9,0x3,0xfc,0x16,0xf0,0x38,0x80,0x3f,0x28,0x10,0x40,0x7f,0x58,0x27,0x10,0x32,0x7d,0xd0,0x32,0xd9,0x8,0x20,0x3f,0x32,0x80,0xff,0x7,0xee,0x2,0xc7,0x10,0x1f,0xe2,0x7f,0xc1,0xfe,0xf,0xf0,0x7f,0x83,0xe6,0x7c,0x8,0x18,0xc,0x7,0x50,0x4,0x23,0x98,0x83,0xd2,0x8e,0xf,0x39,0x20,0x3c,0xf4,0x1f,0xf8,0x38,0x3c,0x70,0x1a,0xea,0x47,0x1f,0x73,0x33,0xd2,0x73,0xb8,0x39,0x98,0x27,0x43,0xff,0xf9,0xfe,0x3,0xc7,0x79,0x48,0x94,0xc9,0x69,0x3a,0xca,0x94,0x8b,0x4b,0x4,0xf8,0x7f,0xf1,0xdf,0xb0,0x78,0xe3,0xc1,0xe5,0x79,0xd2,0x3,0xc7,0x7a,0xf,0x1b,0xff,0xef,0xee,0xf,0x1c,0xf0,0x3c,0xa1,0x20,0xf2,0xc2,0x83,0xc7,0x7f,0x1d,0xf7,0x83,0xc7,0xbd,0x70,0xe3,0xce,0x66,0x3a,0x5e,0x77,0x26,0x33,0x3,0x7,0x8f,0xbf,0xdc,0x5f,0x10,0x8,0x96,0x44,0x0,0x34,0x20,0x19,0x7c,0x3b,0xff,0xfe,0xf1,0xfc,0xc1,0xef,0xc0,0xef,0xdf,0xc0,0x22,0x9f,0xa0,0x78,0xef,0xc1,0xfd,0xff,0xf,0xf8,0x3e,0x3f,0xb,0xa1,0x80,0xd0,0x37,0xff,0xf3,0x78,0x83,0xda,0xc,0x2,0x18,0x16,0x80,0x1f,0x50,0x30,0x10,0xaf,0xc6,0xff,0xff,0xf3,0x83,0xed,0x7e,0x3f,0xee,0x18,0x3d,0xf8,0x78,0x98,0x40,0x3c,0xbf,0x38,0x0,0x7b,0xc8,0xe9,0x51,0x80,0x79,0x41,0xe0,0xe0,0xf8,0x95,0x4e,0x1,0xe5,0xff,0x87,0xdf,0x81,0xef,0x1a,0xbc,0x3,0xce,0x3f,0x7c,0xf,0xec,0xfe,0xf0,0x3f,0xcf,0xfd,0xfc,0x1e,0xe5,0xf5,0x0,0x8,0x3d,0xcf,0xea,0x20,0x20,0x7f,0x83,0xda,0xe,0xbf,0x20,0x7d,0xff,0x9b,0xe5,0xe0,0x7,0x94,0xfc,0x1e,0xdf,0xc0,0x50,0x82,0x58,0x1,0xe0,0xf,0x0,0x78,0x2,0x40
+};
+
+
+const uint8_t *_A_L0_Url_128x51[] = {
+
+    _A_L0_Url_128x51_0,
+
+    _A_L0_Url_128x51_1,
+
+    _A_L0_Url_128x51_2,
+
+    _A_L0_Url_128x51_3,
+
+};
+
+
+
+const BubbleAnimation BA_L0_Url_128x51 = {
+    .icon_animation = {
+        .width = 128,
+        .height = 51,
+        .frame_count = 4,
+        .frame_rate = 2,
+        .frames = _A_L0_Url_128x51
+    },
+    .frame_order = { 0, 1, 2, 3 },
+    .passive_frames = 4,
+    .active_frames = 0,
+    .active_cooldown = 0,
+    .active_cycles = 0,
+    .duration = 0,
+
+    .frame_bubble_sequences = NULL,
+    .frame_bubble_sequences_count = 0,
+
+};
+
+
+const uint8_t _A_L0_NewMail_128x51_0[] = {
+    0x1,0x0,0x50,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf0,0x7,0x80,0x3c,0x1,0xe0,0x6,0xf8,0x40,0x40,0x63,0x30,0xfb,0x73,0x61,0x80,0xf7,0xfe,0x41,0xc0,0x63,0x70,0x9b,0x73,0x1,0xf1,0xfe,0x87,0x83,0xf7,0xff,0x1f,0x1,0x8e,0xc3,0xed,0xd9,0x83,0x3,0xdf,0x7e,0xf,0x39,0xb7,0x60,0x1e,0xf8,0xc,0xfc,0xfc,0x9e,0x53,0x7f,0xc1,0x14,0x81,0xeb,0xbf,0x7,0xa4,0xc2,0x6,0xa,0x2f,0xf8,0x0,0xc1,0xe9,0xf6,0x98,0x83,0xcb,0x80,0x1f,0x80,0x2,0xd0,0x11,0x24,0xf,0xc8,0x10,0x14,0xe1,0xf1,0xe0,0x7c,0x41,0xf,0xef,0xc,0xe3,0x6c,0x6c,0x20,0xf2,0x84,0x27,0xde,0x39,0xc7,0x7,0xa4,0x40,0x1e,0x7f,0xd3,0xc9,0x47,0x2c,0xfb,0x30,0x79,0xc8,0x9,0xe6,0xdf,0x3d,0xe0,0x3d,0x68,0x4,0x43,0x2,0x1e,0xb3,0xcd,0xb3,0x90,0x82,0x8b,0x0,0xa,0x29,0x0,0x3c,0xfd,0x93,0x6d,0xb0,0x3c,0xe0,0x2,0xa2,0x88,0x3,0xce,0x98,0xf,0x10,0x51,0x2,0x45,0x0,0x50,0x80,0x7b,0x5b,0xc1,0xe6,0x80,0x28,0x20,0x3c,0xd1,0xec,0x1c,0x10,0x20,0xc0,0x81,0xf6,0x80,0x28,0x8,0x3e,0xe6,0x7,0xe1,0x7,0xe5,0x21,0xa6,0x40,0xfb,0xc2,0x1f,0x84,0x9e,0x9f,0xef,0xf8,0x0,0x4e,0x2,0x6f,0x28,0xc,0x40,0xde,0x63,0x41,0x9,0x91,0xec,0x51,0xe5,0x2,0x84,0x23,0xcf,0x80,0x2d,0x33,0xd8,0xab,0xca,0x9,0x4,0x57,0x98,0x3d,0xc1,0x43,0xf8,0x81,0xb3,0xcf,0x81,0x8,0x16,0xc,0x20,0x1e,0x4e,0xf4,0x7,0x9c,0x62,0x7,0x0,0x8c,0x3,0xc9,0x1c,0x40,0x1,0xa0,0x83,0xcb,0x51,0x32,0xb3,0xce,0x1,0xe4,0xae,0x20,0x0,0xd0,0x81,0xe5,0x48,0xa5,0x64,0x6f,0x0,0xf2,0x67,0x10,0x0,0x68,0x83,0xf3,0xa5,0x7c,0x43,0xe6,0xee,0x20,0x0,0xa8,0xc4,0x1e,0x71,0x11,0xf9,0xa1,0x8e,0x1,0x50,0x7,0x9c,0xca,0xc2,0x5f,0x23,0xb2,0x40,0x2c,0x7,0xf8,0xc0,0x2d,0x48,0x0,0x33,0xf0,0x7c,0x0,0x23,0x80,0xbf,0x50,0x4,0xa,0x10,0x98,0x80,0x6,0x7,0x98,0x3a,0x80,0x1e,0x0,0xf0,0x7,0x80,0x14
+};
+
+const uint8_t _A_L0_NewMail_128x51_1[] = {
+    0x1,0x0,0x5d,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf0,0x7,0x80,0x3c,0x1,0xe0,0xa,0x98,0x7d,0xb9,0xb0,0xc0,0x7d,0xdc,0x26,0xdc,0xc0,0x7f,0x83,0xe3,0xe1,0x1,0x1,0x8e,0xc3,0xed,0xd9,0x83,0x3,0x13,0xfd,0xf,0x80,0x7f,0x20,0xe0,0xf2,0x9b,0x76,0x1,0xe9,0xc0,0x80,0xdc,0x2,0x46,0x4f,0x19,0xbf,0xe0,0x2a,0x40,0x1,0x9c,0x7f,0xb1,0xf0,0x3f,0xfc,0x7c,0x1e,0x73,0x8,0x18,0x38,0xb0,0x1c,0x7f,0xfd,0xfe,0x79,0xbf,0x7,0x9f,0xda,0x62,0xf,0x3b,0x87,0xf3,0xff,0xff,0xe3,0xe7,0xe7,0xe6,0x68,0xe7,0x3,0x84,0x0,0x3b,0xf0,0x7c,0x60,0x1,0xc6,0xf,0xae,0x0,0x3e,0x21,0x9c,0x6d,0x8d,0x84,0xa,0x34,0x0,0x7c,0x47,0x38,0xe0,0xf4,0x41,0xa4,0x3e,0x3c,0xf,0x2b,0x3e,0xcc,0x1e,0x70,0x40,0x79,0x9f,0xcf,0x78,0xf,0x58,0x42,0xc9,0xa0,0x1a,0xcf,0x36,0xce,0x42,0xf,0x28,0x80,0x3c,0xff,0xaa,0x92,0xf6,0x4d,0xb6,0xc0,0xf3,0xd0,0x13,0x8d,0xbe,0x74,0xc0,0x78,0x81,0x4a,0x81,0x40,0xf,0x10,0x11,0x1,0xe5,0x6f,0x5,0x16,0x0,0x14,0x52,0x0,0x79,0x80,0x14,0x12,0x22,0x42,0x88,0x3,0xef,0xfc,0x3f,0x18,0x78,0x3e,0xf0,0x82,0xe1,0x82,0x3,0xee,0x90,0x3,0x18,0x10,0x3e,0xe6,0xe,0x31,0x80,0x83,0xcc,0x6e,0x5f,0xf2,0x58,0x82,0x45,0x20,0x40,0xf3,0xa,0x8,0x4c,0xa0,0x40,0xf2,0x58,0x10,0xbc,0xf8,0x2,0xd3,0x66,0x8,0x24,0x5a,0x4,0x4f,0x30,0x7c,0x8,0xc4,0x44,0x20,0xf3,0x84,0x2,0x6,0x10,0xf,0x34,0x7b,0x70,0x23,0x10,0x38,0x4,0x60,0x1e,0x4a,0xf7,0xe0,0x6a,0x26,0x56,0x79,0xc0,0x3c,0x99,0xef,0xc0,0xa4,0x52,0xb2,0x37,0x80,0x79,0x48,0x1,0xe5,0x80,0x1f,0xa5,0x2b,0xe2,0x1f,0x33,0xf1,0x0,0x6,0x82,0xf,0x48,0x88,0xfc,0xcf,0xc4,0x0,0x1a,0x10,0x3c,0xe6,0x56,0x12,0xf9,0x9f,0x88,0x0,0x34,0x43,0xfc,0x9f,0x88,0x0,0x2d,0x31,0x7,0xc2,0xd8,0xe0,0x15,0x0,0x79,0x2f,0xd4,0x2c,0x90,0xb,0x1,0x8c,0x9b,0xc4,0xdf,0x20,0x32,0x7f,0xe7,0xe1,0x32,0x3,0x98,0x71,0x0,0x1e,0x0,0xf0,0x7,0x80,0x2c
+};
+
+const uint8_t _A_L0_NewMail_128x51_2[] = {
+    0x1,0x0,0x79,0x1,0x0,0x78,0x3,0xc0,0x1e,0x0,0xf0,0x1,0xfc,0x20,0x20,0xfd,0x84,0x40,0x81,0xfb,0xf6,0x86,0x83,0xf7,0xf3,0x13,0x3,0x24,0xc3,0xed,0xcd,0x86,0x1,0xcf,0x81,0xfe,0xb7,0x83,0xd2,0xe1,0x36,0xe6,0x3,0xd2,0x41,0xff,0xcf,0x83,0xf3,0xb1,0xff,0xbf,0xc0,0xc0,0xe7,0xb0,0xfb,0x76,0x60,0xc0,0x73,0xf2,0x7f,0xff,0xf0,0x40,0x7a,0x4d,0xbb,0x0,0xf4,0xfd,0x83,0xc6,0x36,0x82,0x39,0x84,0xdf,0xf1,0x7,0x7,0x16,0x3,0xf8,0xf,0x19,0x3f,0xf0,0x12,0x40,0xf1,0x98,0xd,0xcc,0x1c,0x40,0x2,0x87,0x9b,0xce,0x62,0xf,0x31,0xda,0x7f,0xe3,0xe8,0xd8,0x7,0xa6,0xfc,0x1f,0x25,0x33,0xc0,0x67,0xe7,0xe0,0xf8,0x82,0x83,0xcf,0x7e,0xf,0x28,0x67,0x1b,0x63,0x61,0x5,0x16,0x1c,0x9a,0x5b,0xf0,0x79,0xc7,0x38,0xe0,0xf3,0xe0,0x1c,0xd0,0x1e,0x96,0x7d,0x98,0x3c,0xa8,0x0,0xf8,0xde,0x3,0xd5,0x46,0x90,0xf8,0xf0,0xc,0x75,0x9e,0x6d,0x9c,0x84,0x1e,0x58,0x41,0xf9,0x4f,0xcf,0xe7,0xec,0x9b,0x6d,0x81,0xe7,0x8,0xf,0x9f,0xf9,0x3e,0x54,0xc0,0x78,0xb1,0x4a,0x81,0x10,0x7,0x9f,0xf4,0x8a,0x40,0xf2,0xb7,0x83,0xca,0x40,0x4f,0x36,0xf9,0x80,0x16,0x81,0xc0,0x1f,0x95,0xfe,0x80,0x9e,0x8a,0x6,0x0,0x8,0x52,0x0,0x7d,0x40,0x5,0x45,0x10,0x7,0xdc,0x8,0xd8,0x30,0x80,0x7d,0xff,0x20,0x1e,0x8,0x38,0x3c,0xff,0xc4,0x82,0x15,0x88,0x24,0x69,0x0,0x31,0x81,0x3,0xcb,0x0,0x62,0x38,0x10,0x3c,0xa6,0x0,0xa2,0x7,0x97,0x0,0x5a,0x70,0x40,0x79,0x24,0x8,0x1f,0x8b,0x2,0x17,0x9f,0x2,0x10,0x2c,0x18,0x40,0x3c,0xc0,0x62,0x2,0x10,0x79,0xc6,0x20,0x70,0x8,0xc0,0x3c,0xc2,0xc2,0x17,0x10,0x79,0xea,0x26,0x56,0x79,0xc0,0x3c,0xd1,0xed,0xc0,0xa4,0x52,0xb2,0x37,0x80,0x79,0x2b,0xdc,0x7e,0x34,0xaf,0x88,0x7c,0xd1,0x46,0x4,0x30,0x79,0x44,0x47,0xe6,0xef,0x40,0x7a,0x4c,0xac,0x25,0xf3,0x3f,0x10,0x0,0x68,0x27,0xf9,0x3f,0x10,0x0,0x68,0x40,0xf9,0x3f,0x10,0x0,0x68,0x80,0xf2,0x5f,0xb1,0xf8,0x80,0x2,0xd3,0x12,0x18,0xb7,0x89,0xbe,0x61,0x63,0x80,0x54,0x0,0x64,0xf,0x31,0xb3,0x40,0x2c,0x0,0x54,0x0,0x18,0x99,0x3f,0xf3,0xf0,0x7c,0x0,0x3c,0x1,0xc0
+};
+
+const uint8_t _A_L0_NewMail_128x51_3[] = {
+    0x1,0x0,0x77,0x1,0x0,0x1f,0xe2,0x6,0xf,0xd8,0x24,0x10,0x1f,0xbf,0xa8,0x58,0x3f,0x7f,0xb1,0x70,0x76,0x60,0x3f,0xd2,0xf0,0x7e,0x50,0x3f,0xf5,0xf0,0x7e,0x68,0x3f,0xfb,0xf0,0x7e,0x74,0x3f,0xf7,0xf8,0x8,0x1c,0xe6,0x1f,0x6e,0x6c,0x30,0xc,0x5f,0xcf,0xf8,0x0,0xa0,0xe0,0x82,0xb8,0x4d,0xb9,0x80,0xf3,0x3e,0x20,0x10,0xc1,0xff,0xb0,0xfb,0x76,0x60,0xc0,0xfc,0x9b,0x76,0x1,0xf9,0x30,0x9b,0xfe,0x8,0xc7,0x84,0x27,0x14,0xff,0xe8,0x61,0x20,0x78,0xcc,0x1b,0x6,0x1f,0x4f,0xe0,0x64,0x8d,0xe3,0x31,0x7,0xc7,0xfa,0x1e,0xed,0x90,0x21,0xff,0xe3,0xe0,0xf8,0x1f,0xa6,0xfc,0x1f,0x26,0x33,0xc0,0x67,0xe7,0xe0,0x41,0x86,0x71,0xb6,0x36,0x10,0x59,0x41,0x41,0xe7,0xbf,0x7,0x94,0x73,0x8e,0xf,0x3c,0x0,0x7d,0x1,0xe9,0x67,0xd9,0x83,0xcb,0x81,0x87,0x1f,0x96,0xfc,0x1e,0x7b,0xc0,0x7a,0x50,0x12,0x66,0x1f,0x4d,0x67,0x9b,0x67,0x21,0x7,0x90,0xbc,0xe0,0x10,0xf8,0xf0,0xc,0x7d,0x93,0x6d,0xb0,0x3c,0xcf,0xf1,0x4c,0x7,0x8a,0xd4,0xa8,0x18,0x83,0xf9,0xa7,0xcc,0x1e,0x56,0xf0,0x79,0x44,0x3,0xe7,0xfd,0x22,0x98,0x1,0x28,0x12,0x2,0x79,0xfc,0x5,0x24,0xf,0x6a,0x0,0x11,0xc1,0xed,0x80,0x1f,0x98,0x3e,0xa0,0x2,0xa2,0x88,0x3,0xee,0x4,0x6c,0x18,0x40,0x3c,0xff,0xc2,0x82,0xd,0x88,0x24,0x70,0x90,0x9,0x4,0x10,0x1e,0x58,0x2,0x91,0xc0,0x81,0xe5,0x48,0x1,0x8c,0x8,0x1e,0x5c,0x1,0x69,0xa7,0x4,0x12,0x33,0x0,0xd1,0x3,0xed,0x20,0x40,0xf4,0x84,0xb,0x6,0x10,0xf,0x38,0x40,0xd8,0x81,0xe7,0x18,0x81,0xc0,0x23,0x0,0xf3,0x68,0x11,0x3c,0xf8,0x1a,0x89,0x95,0x9e,0x70,0xf,0x31,0x30,0x88,0x84,0x1e,0x74,0x8a,0x56,0x46,0xf0,0xf,0x34,0x7b,0xf,0xc6,0x95,0xf1,0xf,0x9e,0x0,0xac,0x52,0x0,0x7a,0xc4,0x47,0xe6,0xcf,0x70,0x78,0xcc,0xac,0x25,0xf3,0x77,0xa1,0xfe,0x8f,0xc4,0x0,0x1a,0x8,0x3e,0x4f,0xc4,0x0,0x1a,0x10,0x3c,0x97,0xec,0x7e,0x20,0x0,0xd1,0x6,0x64,0xde,0x26,0xf9,0x9f,0x88,0x0,0x2d,0x31,0x0,0x8,0x3c,0xcb,0x44,0x26,0x38,0x5,0x40,0x8,0x60,0x4,0x23,0x24,0x2,0xc0,0xf,0x81,0xb2,0x7f,0xe7,0xe0,0xf8,0x0,0x70
+};
+
+
+const uint8_t *_A_L0_NewMail_128x51[] = {
+
+    _A_L0_NewMail_128x51_0,
+
+    _A_L0_NewMail_128x51_1,
+
+    _A_L0_NewMail_128x51_2,
+
+    _A_L0_NewMail_128x51_3,
+
+};
+
+
+
+const BubbleAnimation BA_L0_NewMail_128x51 = {
+    .icon_animation = {
+        .width = 128,
+        .height = 51,
+        .frame_count = 4,
+        .frame_rate = 2,
+        .frames = _A_L0_NewMail_128x51
+    },
+    .frame_order = { 0, 1, 2, 3, 2, 1 },
+    .passive_frames = 6,
+    .active_frames = 0,
+    .active_cooldown = 0,
+    .active_cycles = 0,
+    .duration = 0,
+
+    .frame_bubble_sequences = NULL,
+    .frame_bubble_sequences_count = 0,
+
+};
+
+
+const StorageAnimation dolphin_blocking[] = {
+
+    {
+        .animation = &BA_L0_NoDb_128x51,
+        .manifest_info = {
+            .name = "L0_NoDb_128x51",
+            .min_butthurt = 0,
+            .max_butthurt = 0,
+            .min_level = 0,
+            .max_level = 0,
+            .weight = 0,
+        }
+    },
+
+    {
+        .animation = &BA_L0_SdBad_128x51,
+        .manifest_info = {
+            .name = "L0_SdBad_128x51",
+            .min_butthurt = 0,
+            .max_butthurt = 0,
+            .min_level = 0,
+            .max_level = 0,
+            .weight = 0,
+        }
+    },
+
+    {
+        .animation = &BA_L0_SdOk_128x51,
+        .manifest_info = {
+            .name = "L0_SdOk_128x51",
+            .min_butthurt = 0,
+            .max_butthurt = 0,
+            .min_level = 0,
+            .max_level = 0,
+            .weight = 0,
+        }
+    },
+
+    {
+        .animation = &BA_L0_Url_128x51,
+        .manifest_info = {
+            .name = "L0_Url_128x51",
+            .min_butthurt = 0,
+            .max_butthurt = 0,
+            .min_level = 0,
+            .max_level = 0,
+            .weight = 0,
+        }
+    },
+
+    {
+        .animation = &BA_L0_NewMail_128x51,
+        .manifest_info = {
+            .name = "L0_NewMail_128x51",
+            .min_butthurt = 0,
+            .max_butthurt = 0,
+            .min_level = 0,
+            .max_level = 0,
+            .weight = 0,
+        }
+    },
+
+};
+
+const size_t dolphin_blocking_size = COUNT_OF(dolphin_blocking);

+ 7 - 0
assets/compiled/assets_dolphin_blocking.h

@@ -0,0 +1,7 @@
+#pragma once
+
+#include <stddef.h>
+#include <desktop/animations/animation_storage_i.h>
+
+extern const StorageAnimation dolphin_blocking[];
+extern const size_t dolphin_blocking_size;

Разница между файлами не показана из-за своего большого размера
+ 8 - 0
assets/compiled/assets_dolphin_internal.c


+ 7 - 0
assets/compiled/assets_dolphin_internal.h

@@ -0,0 +1,7 @@
+#pragma once
+
+#include <stddef.h>
+#include <desktop/animations/animation_storage_i.h>
+
+extern const StorageAnimation dolphin_internal[];
+extern const size_t dolphin_internal_size;

Разница между файлами не показана из-за своего большого размера
+ 0 - 48
assets/compiled/assets_icons.c


+ 2 - 28
assets/compiled/assets_icons.h

@@ -3,34 +3,8 @@
 
 extern const Icon I_Certification1_103x23;
 extern const Icon I_Certification2_119x30;
-extern const Icon I_card_bad1;
-extern const Icon I_card_bad2;
-extern const Icon I_card_ok1;
-extern const Icon I_card_ok2;
-extern const Icon I_card_ok3;
-extern const Icon I_card_ok4;
-extern const Icon I_no_databases1;
-extern const Icon I_no_databases2;
-extern const Icon I_no_databases3;
-extern const Icon I_no_databases4;
-extern const Icon I_no_sd1;
-extern const Icon I_no_sd2;
-extern const Icon I_no_sd3;
-extern const Icon I_no_sd4;
-extern const Icon I_no_sd5;
-extern const Icon I_no_sd6;
-extern const Icon I_tv1;
-extern const Icon I_tv2;
-extern const Icon I_tv3;
-extern const Icon I_tv4;
-extern const Icon I_tv5;
-extern const Icon I_tv6;
-extern const Icon I_tv7;
-extern const Icon I_tv8;
-extern const Icon I_url1;
-extern const Icon I_url2;
-extern const Icon I_url3;
-extern const Icon I_url4;
+extern const Icon A_Levelup1_128x64;
+extern const Icon A_Levelup2_128x64;
 extern const Icon I_125_10px;
 extern const Icon I_Nfc_10px;
 extern const Icon I_ble_10px;

+ 81 - 0
assets/dolphin/ReadMe.md

@@ -0,0 +1,81 @@
+# Dolphin assets
+
+Dolphin assets are split into 3 parts:
+
+- essential - Essential animations that are used for blocking system notifications. They are packed to `assets_dolphin_essential.[h,c]`.
+- internal  - Internal animations that are used for idle dolphin animation. Converted to `assets_dolphin_internal.[h,c]`.
+- external  - External animations that are used for idle dolphin animation. Packed to resource folder and placed on SD card.
+
+# Files
+
+- `manifest.txt` - contains animations enumeration that is used for random animation selection. Starting point for Dolphin.
+- `meta.txt`     - contains data that describes how animation is drawn.
+- `frame_X.bm`   - Flipper Compressed Bitmap.
+
+## File manifest.txt
+
+Flipper Format File with ordered keys.
+
+Header:
+
+```
+Filetype: Flipper Animation Manifest
+Version: 1
+```
+
+- `Name` - name of animation. Must be exact animation directory name.
+- `Min butthurt`, `Max butthurt` - range of dolphin's butthurt for this animation.
+- `Min level`, `Max level` - range of dolphin's level for this animation. If 0, this animation doesn't participate in random idle animation selection and can only be selected by exact name.
+- `Weight` - chance of this animation to be choosen at random animation selection.
+
+Some animations can be excluded from participation in random animation selection, such as `L1_NoSd_128x49`.
+
+## File meta.txt
+
+Flipper Format File with ordered keys.
+
+Header:
+
+```
+Filetype: Flipper Animation
+Version: 1
+```
+
+- `Width` - animation width in px (<= 128)
+- `Height` - animation height in px (<= 64)
+- `Passive frames` - number of bitmap frames for passive animation state
+- `Active frames` - number of bitmap frames for active animation state (can be 0)
+- `Frames order` - order of bitmap frames where first N frames are passive and following M are active. Each X number in order refers to bitmap frame, with name frame\_X.bm. This file must exist. Any X number can be repeated to refer same frame in animation.
+- `Active cycles` - cycles to repeat of N active frames for full active period. E.g. if frames for active cycles are 6 and 7, and active cycles is 3, so full active period plays 6 7 6 7 6 7. Full period of passive + active period are called *total period*.
+- `Frame rate` - number of frames to play for 1 second.
+- `Duration` - total amount of seconds to play 1 animation.
+- `Active cooldown` - amount of seconds (after passive mode) to pass before entering next active mode.
+
+- `Bubble slots` - amount of bubble sequences.
+- Any bubble sequence plays whole sequence during active mode. There can be many bubble sequences and bubbles inside it. Bubbles in 1 bubble sequence have to reside in 1 slot. Bubbles order in 1 bubble sequence is determined by occurance in file. As soon as frame index goes out of EndFrame index of bubble - next animation bubble is choosen. There can also be free of bubbles frames between 2 bubbles.
+
+- `Slot` - number to unite bubbles for same sequence.
+- `X`, `Y` - are coordinates of left top corner of bubble.
+- `Text` - text in bubble. New line is `\n`
+- `AlignH` - horizontal place of bubble corner (Left, Center, Right)
+- `AlignV` - vertical place of bubble corner (Top, Center, Bottom)
+- `StartFrame`, `EndFrame` - frame index range inside whole period to show bubble.
+
+### Understanding of frame indexes
+
+For example we have
+
+```
+Passive frames: 6
+Active frames: 2
+Frames order: 0 1 2 3 4 5 6 7
+Active cycles: 4
+```
+
+Then we have indexes
+
+```
+                        passive(6)            active (2 * 4)
+Real frames order:   0  1  2  3  4  5     6  7  6  7  6  7  6  7
+Frames indexes:      0  1  2  3  4  5     6  7  8  9  10 11 12 13
+```

BIN
assets/dolphin/animations/recording/frame_0.png


BIN
assets/dolphin/animations/recording/frame_10.png


BIN
assets/dolphin/animations/recording/frame_11.png


BIN
assets/dolphin/animations/recording/frame_3.png


BIN
assets/dolphin/animations/recording/frame_4.png


Некоторые файлы не были показаны из-за большого количества измененных файлов