Browse Source

[FL-1995] New dolphin animations (part 1) (#835)

* Desktop Animation (part 1): Ugly naked ohmygod architecture
* fix butthurt, fix locked scene
* Change SD icons, fixes
* Fix level update animation
* Fixes, correct butthurt
* Clean up code
* furi_assert(0) -> furi_crash("msg")
* Gui: rename none layer to desktop, update docs.

Co-authored-by: あく <alleteam@gmail.com>
Albert Kharisov 4 năm trước cách đây
mục cha
commit
9b8a139e2b
100 tập tin đã thay đổi với 1296 bổ sung399 xóa
  1. 38 1
      applications/desktop/desktop.c
  2. 2 0
      applications/desktop/desktop_i.h
  3. 353 16
      applications/desktop/helpers/desktop_animation.c
  4. 53 8
      applications/desktop/helpers/desktop_animation.h
  5. 330 0
      applications/desktop/helpers/desktop_animation_i.h
  6. 1 0
      applications/desktop/scenes/desktop_scene_config.h
  7. 1 0
      applications/desktop/scenes/desktop_scene_debug.c
  8. 79 0
      applications/desktop/scenes/desktop_scene_levelup.c
  9. 20 1
      applications/desktop/scenes/desktop_scene_locked.c
  10. 40 1
      applications/desktop/scenes/desktop_scene_main.c
  11. 22 13
      applications/desktop/views/desktop_debug.c
  12. 18 18
      applications/desktop/views/desktop_first_start.c
  13. 5 5
      applications/desktop/views/desktop_locked.c
  14. 2 2
      applications/desktop/views/desktop_locked.h
  15. 22 4
      applications/desktop/views/desktop_main.c
  16. 6 1
      applications/desktop/views/desktop_main.h
  17. 26 5
      applications/dolphin/dolphin.c
  18. 13 1
      applications/dolphin/dolphin.h
  19. 2 0
      applications/dolphin/dolphin_i.h
  20. 6 4
      applications/dolphin/helpers/dolphin_deed.c
  21. 56 41
      applications/dolphin/helpers/dolphin_state.c
  22. 21 7
      applications/dolphin/helpers/dolphin_state.h
  23. 10 10
      applications/gui/gui.c
  24. 5 3
      applications/gui/gui.h
  25. 4 4
      applications/gui/gui_i.h
  26. 1 0
      applications/gui/icon_animation.c
  27. 4 4
      applications/gui/view_dispatcher.c
  28. 3 3
      applications/gui/view_dispatcher.h
  29. 5 0
      applications/power/power_service/power.c
  30. 3 0
      applications/power/power_service/power.h
  31. 4 0
      applications/storage/storage-external-api.c
  32. 3 1
      applications/storage/storage-i.h
  33. 8 1
      applications/storage/storage.c
  34. 2 0
      applications/storage/storage.h
  35. 1 1
      applications/subghz/scenes/subghz_scene_need_saving.c
  36. 0 0
      assets/compiled/assets_icons.c
  37. 112 59
      assets/compiled/assets_icons.h
  38. 0 14
      assets/compiled/input.pb.c
  39. 0 78
      assets/compiled/input.pb.h
  40. 0 18
      assets/compiled/screen.pb.c
  41. 0 75
      assets/compiled/screen.pb.h
  42. BIN
      assets/icons/Animations/BadBattery_128x51/frame_01.png
  43. BIN
      assets/icons/Animations/BadBattery_128x51/frame_02.png
  44. 1 0
      assets/icons/Animations/BadBattery_128x51/frame_rate
  45. BIN
      assets/icons/Animations/BoxActive_128x51/frame_01.png
  46. BIN
      assets/icons/Animations/BoxActive_128x51/frame_02.png
  47. 1 0
      assets/icons/Animations/BoxActive_128x51/frame_rate
  48. BIN
      assets/icons/Animations/Box_128x51/frame_01.png
  49. BIN
      assets/icons/Animations/Box_128x51/frame_02.png
  50. BIN
      assets/icons/Animations/Box_128x51/frame_03.png
  51. BIN
      assets/icons/Animations/Box_128x51/frame_04.png
  52. 1 0
      assets/icons/Animations/Box_128x51/frame_rate
  53. BIN
      assets/icons/Animations/CardBad_128x51/frame_01.png
  54. BIN
      assets/icons/Animations/CardBad_128x51/frame_02.png
  55. 1 0
      assets/icons/Animations/CardBad_128x51/frame_rate
  56. 1 0
      assets/icons/Animations/CardNoDBUrl_128x51/frame_rate
  57. BIN
      assets/icons/Animations/CardNoDBUrl_128x51/url1.png
  58. BIN
      assets/icons/Animations/CardNoDBUrl_128x51/url2.png
  59. BIN
      assets/icons/Animations/CardNoDBUrl_128x51/url3.png
  60. BIN
      assets/icons/Animations/CardNoDBUrl_128x51/url4.png
  61. BIN
      assets/icons/Animations/CardNoDB_128x51/frame_01.png
  62. BIN
      assets/icons/Animations/CardNoDB_128x51/frame_02.png
  63. BIN
      assets/icons/Animations/CardNoDB_128x51/frame_03.png
  64. BIN
      assets/icons/Animations/CardNoDB_128x51/frame_04.png
  65. 1 0
      assets/icons/Animations/CardNoDB_128x51/frame_rate
  66. BIN
      assets/icons/Animations/CardOk_128x51/frame_01.png
  67. BIN
      assets/icons/Animations/CardOk_128x51/frame_02.png
  68. BIN
      assets/icons/Animations/CardOk_128x51/frame_03.png
  69. BIN
      assets/icons/Animations/CardOk_128x51/frame_04.png
  70. 1 0
      assets/icons/Animations/CardOk_128x51/frame_rate
  71. BIN
      assets/icons/Animations/CryActive_128x51/frame_01.png
  72. BIN
      assets/icons/Animations/CryActive_128x51/frame_02.png
  73. BIN
      assets/icons/Animations/CryActive_128x51/frame_03.png
  74. 1 0
      assets/icons/Animations/CryActive_128x51/frame_rate
  75. BIN
      assets/icons/Animations/Cry_128x51/frame_01.png
  76. BIN
      assets/icons/Animations/Cry_128x51/frame_02.png
  77. BIN
      assets/icons/Animations/Cry_128x51/frame_03.png
  78. BIN
      assets/icons/Animations/Cry_128x51/frame_04.png
  79. 1 0
      assets/icons/Animations/Cry_128x51/frame_rate
  80. BIN
      assets/icons/Animations/KnifeActive_128x51/frame_01.png
  81. BIN
      assets/icons/Animations/KnifeActive_128x51/frame_02.png
  82. 1 0
      assets/icons/Animations/KnifeActive_128x51/frame_rate
  83. BIN
      assets/icons/Animations/Knife_128x51/frame_01.png
  84. BIN
      assets/icons/Animations/Knife_128x51/frame_02.png
  85. 1 0
      assets/icons/Animations/Knife_128x51/frame_rate
  86. BIN
      assets/icons/Animations/LaptopActive_128x51/frame_01.png
  87. BIN
      assets/icons/Animations/LaptopActive_128x51/frame_02.png
  88. 1 0
      assets/icons/Animations/LaptopActive_128x51/frame_rate
  89. BIN
      assets/icons/Animations/Laptop_128x51/frame_01.png
  90. BIN
      assets/icons/Animations/Laptop_128x51/frame_02.png
  91. 1 0
      assets/icons/Animations/Laptop_128x51/frame_rate
  92. BIN
      assets/icons/Animations/LeavingActive_128x51/frame_01.png
  93. BIN
      assets/icons/Animations/LeavingActive_128x51/frame_02.png
  94. 1 0
      assets/icons/Animations/LeavingActive_128x51/frame_rate
  95. BIN
      assets/icons/Animations/Leaving_128x51/frame_01.png
  96. BIN
      assets/icons/Animations/Leaving_128x51/frame_02.png
  97. 1 0
      assets/icons/Animations/Leaving_128x51/frame_rate
  98. BIN
      assets/icons/Animations/Level1FurippaActive_128x51/frame_01.png
  99. BIN
      assets/icons/Animations/Level1FurippaActive_128x51/frame_02.png
  100. BIN
      assets/icons/Animations/Level1FurippaActive_128x51/frame_03.png

+ 38 - 1
applications/desktop/desktop.c

@@ -1,5 +1,17 @@
+#include "assets_icons.h"
+#include "cmsis_os2.h"
+#include "desktop/desktop.h"
 #include "desktop_i.h"
 #include "desktop_i.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 <furi-hal-lock.h>
 #include <furi-hal-lock.h>
+#include <stdint.h>
+#include <power/power_service/power.h>
+#include "helpers/desktop_animation.h"
 
 
 static void desktop_lock_icon_callback(Canvas* canvas, void* context) {
 static void desktop_lock_icon_callback(Canvas* canvas, void* context) {
     furi_assert(canvas);
     furi_assert(canvas);
@@ -25,10 +37,11 @@ Desktop* desktop_alloc() {
     desktop->scene_thread = furi_thread_alloc();
     desktop->scene_thread = furi_thread_alloc();
     desktop->view_dispatcher = view_dispatcher_alloc();
     desktop->view_dispatcher = view_dispatcher_alloc();
     desktop->scene_manager = scene_manager_alloc(&desktop_scene_handlers, desktop);
     desktop->scene_manager = scene_manager_alloc(&desktop_scene_handlers, desktop);
+    desktop->animation = desktop_animation_alloc();
 
 
     view_dispatcher_enable_queue(desktop->view_dispatcher);
     view_dispatcher_enable_queue(desktop->view_dispatcher);
     view_dispatcher_attach_to_gui(
     view_dispatcher_attach_to_gui(
-        desktop->view_dispatcher, desktop->gui, ViewDispatcherTypeWindow);
+        desktop->view_dispatcher, desktop->gui, ViewDispatcherTypeDesktop);
 
 
     view_dispatcher_set_event_callback_context(desktop->view_dispatcher, desktop);
     view_dispatcher_set_event_callback_context(desktop->view_dispatcher, desktop);
     view_dispatcher_set_custom_event_callback(
     view_dispatcher_set_custom_event_callback(
@@ -79,6 +92,7 @@ Desktop* desktop_alloc() {
 void desktop_free(Desktop* desktop) {
 void desktop_free(Desktop* desktop) {
     furi_assert(desktop);
     furi_assert(desktop);
 
 
+    desktop_animation_free(desktop->animation);
     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewMain);
     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewMain);
     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLockMenu);
     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLockMenu);
     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLocked);
     view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLocked);
@@ -116,8 +130,29 @@ static bool desktop_is_first_start() {
     return exists;
     return exists;
 }
 }
 
 
+static void desktop_dolphin_state_changed_callback(const void* message, void* context) {
+    Desktop* desktop = context;
+    view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventUpdateAnimation);
+}
+
+static void desktop_storage_state_changed_callback(const void* message, void* context) {
+    Desktop* desktop = context;
+    view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventUpdateAnimation);
+}
+
 int32_t desktop_srv(void* p) {
 int32_t desktop_srv(void* p) {
     Desktop* desktop = desktop_alloc();
     Desktop* desktop = desktop_alloc();
+
+    Dolphin* dolphin = furi_record_open("dolphin");
+    FuriPubSub* dolphin_pubsub = dolphin_get_pubsub(dolphin);
+    FuriPubSubSubscription* dolphin_subscription =
+        furi_pubsub_subscribe(dolphin_pubsub, desktop_dolphin_state_changed_callback, desktop);
+
+    Storage* storage = furi_record_open("storage");
+    FuriPubSub* storage_pubsub = storage_get_pubsub(storage);
+    FuriPubSubSubscription* storage_subscription =
+        furi_pubsub_subscribe(storage_pubsub, desktop_storage_state_changed_callback, desktop);
+
     bool loaded = LOAD_DESKTOP_SETTINGS(&desktop->settings);
     bool loaded = LOAD_DESKTOP_SETTINGS(&desktop->settings);
     if(!loaded) {
     if(!loaded) {
         furi_hal_lock_set(false);
         furi_hal_lock_set(false);
@@ -143,6 +178,8 @@ int32_t desktop_srv(void* p) {
     }
     }
 
 
     view_dispatcher_run(desktop->view_dispatcher);
     view_dispatcher_run(desktop->view_dispatcher);
+    furi_pubsub_unsubscribe(dolphin_pubsub, dolphin_subscription);
+    furi_pubsub_unsubscribe(storage_pubsub, storage_subscription);
     desktop_free(desktop);
     desktop_free(desktop);
 
 
     return 0;
     return 0;

+ 2 - 0
applications/desktop/desktop_i.h

@@ -23,6 +23,7 @@
 #include "scenes/desktop_scene.h"
 #include "scenes/desktop_scene.h"
 #include "helpers/desktop_animation.h"
 #include "helpers/desktop_animation.h"
 #include "desktop/desktop_settings/desktop_settings.h"
 #include "desktop/desktop_settings/desktop_settings.h"
+#include <gui/icon.h>
 
 
 typedef enum {
 typedef enum {
     DesktopViewMain,
     DesktopViewMain,
@@ -43,6 +44,7 @@ struct Desktop {
     ViewDispatcher* view_dispatcher;
     ViewDispatcher* view_dispatcher;
     SceneManager* scene_manager;
     SceneManager* scene_manager;
 
 
+    DesktopAnimation* animation;
     DesktopFirstStartView* first_start_view;
     DesktopFirstStartView* first_start_view;
     Popup* hw_mismatch_popup;
     Popup* hw_mismatch_popup;
     DesktopMainView* main_view;
     DesktopMainView* main_view;

+ 353 - 16
applications/desktop/helpers/desktop_animation.c

@@ -1,28 +1,365 @@
-#include "desktop_animation.h"
+#include "desktop/helpers/desktop_animation.h"
+#include "assets_icons.h"
+#include "desktop_animation_i.h"
+#include "cmsis_os2.h"
+#include "furi/common_defines.h"
+#include "furi/record.h"
+#include "storage/filesystem-api-defines.h"
+#include <power/power_service/power.h>
+#include <m-list.h>
+#include <storage/storage.h>
+#include <desktop/desktop.h>
+#include <dolphin/dolphin.h>
 
 
-#define TAG "DesktopAnimation"
+LIST_DEF(AnimationList, const PairedAnimation*, M_PTR_OPLIST)
+#define M_OPL_AnimationList_t() LIST_OPLIST(AnimationList)
 
 
-static const Icon* idle_scenes[] = {&A_Wink_128x64, &A_WatchingTV_128x64};
+#define PUSH_BACK_ANIMATIONS(listname, animations, butthurt)                          \
+    for(int i = 0; i < COUNT_OF(animations); ++i) {                                   \
+        if(!(animations)[i].basic->butthurt_level_mask ||                             \
+           ((animations)[i].basic->butthurt_level_mask & BUTTHURT_LEVEL(butthurt))) { \
+            AnimationList_push_back(animation_list, &(animations)[i]);                \
+        }                                                                             \
+    }
+
+#define IS_BLOCKING_ANIMATION(x) \
+    (((x) != DesktopAnimationStateBasic) && ((x) != DesktopAnimationStateActive))
+#define IS_ONESHOT_ANIMATION(x) ((x) == DesktopAnimationStateLevelUpIsPending)
+
+static void desktop_animation_timer_callback(void* context);
+
+struct DesktopAnimation {
+    bool sd_shown_error_db;
+    bool sd_shown_error_card_bad;
+    osTimerId_t timer;
+    const PairedAnimation* current;
+    const Icon* current_blocking_icon;
+    const Icon** current_one_shot_icons;
+    uint8_t one_shot_animation_counter;
+    uint8_t one_shot_animation_size;
+    DesktopAnimationState state;
+    TickType_t basic_started_at;
+    TickType_t active_finished_at;
+    AnimationChangedCallback animation_changed_callback;
+    void* animation_changed_callback_context;
+};
+
+DesktopAnimation* desktop_animation_alloc(void) {
+    DesktopAnimation* animation = furi_alloc(sizeof(DesktopAnimation));
+
+    animation->timer = osTimerNew(
+        desktop_animation_timer_callback, osTimerPeriodic /* osTimerOnce */, animation, NULL);
+    animation->active_finished_at = (TickType_t)(-30);
+    animation->basic_started_at = 0;
+    animation->animation_changed_callback = NULL;
+    animation->animation_changed_callback_context = NULL;
+    desktop_start_new_idle_animation(animation);
+
+    return animation;
+}
+
+void desktop_animation_free(DesktopAnimation* animation) {
+    furi_assert(animation);
+
+    osTimerDelete(animation->timer);
+    free(animation);
+}
 
 
-const Icon* desktop_get_icon() {
-    uint8_t new = 0;
+void desktop_animation_set_animation_changed_callback(
+    DesktopAnimation* animation,
+    AnimationChangedCallback callback,
+    void* context) {
+    furi_assert(animation);
 
 
-#if 0
-    // checking dolphin state here to choose appropriate animation
+    animation->animation_changed_callback = callback;
+    animation->animation_changed_callback_context = context;
+}
 
 
+void desktop_start_new_idle_animation(DesktopAnimation* animation) {
     Dolphin* dolphin = furi_record_open("dolphin");
     Dolphin* dolphin = furi_record_open("dolphin");
     DolphinStats stats = dolphin_stats(dolphin);
     DolphinStats stats = dolphin_stats(dolphin);
-    float timediff = fabs(difftime(stats.timestamp, dolphin_state_timestamp()));
+    furi_record_close("dolphin");
+
+    furi_assert((stats.level >= 1) && (stats.level <= 3));
+    uint8_t level = stats.level;
+
+    AnimationList_t animation_list;
+    AnimationList_init(animation_list);
+
+    PUSH_BACK_ANIMATIONS(animation_list, mad_animation, stats.butthurt);
+    PUSH_BACK_ANIMATIONS(animation_list, calm_animation, stats.butthurt);
+    switch(level) {
+    case 1:
+        PUSH_BACK_ANIMATIONS(animation_list, level_1_animation, stats.butthurt);
+        break;
+    case 2:
+        PUSH_BACK_ANIMATIONS(animation_list, level_2_animation, stats.butthurt);
+        break;
+    case 3:
+        PUSH_BACK_ANIMATIONS(animation_list, level_3_animation, stats.butthurt);
+        break;
+    default:
+        furi_crash("Dolphin level is out of bounds");
+    }
+
+    Power* power = furi_record_open("power");
+    PowerInfo info;
+    power_get_info(power, &info);
+
+    if(!power_is_battery_well(&info)) {
+        PUSH_BACK_ANIMATIONS(animation_list, check_battery_animation, stats.butthurt);
+    }
+
+    Storage* storage = furi_record_open("storage");
+    FS_Error sd_status = storage_sd_status(storage);
+    animation->current = NULL;
+
+    if(sd_status == FSE_NOT_READY) {
+        PUSH_BACK_ANIMATIONS(animation_list, no_sd_animation, stats.butthurt);
+        animation->sd_shown_error_card_bad = false;
+        animation->sd_shown_error_db = false;
+    }
+
+    uint32_t whole_weight = 0;
+    for
+        M_EACH(item, animation_list, AnimationList_t) {
+            whole_weight += (*item)->basic->weight;
+        }
+
+    uint32_t lucky_number = random() % whole_weight;
+    uint32_t weight = 0;
+
+    const PairedAnimation* selected = NULL;
+    for
+        M_EACH(item, animation_list, AnimationList_t) {
+            if(lucky_number < weight) {
+                break;
+            }
+            weight += (*item)->basic->weight;
+            selected = *item;
+        }
+    animation->basic_started_at = osKernelGetTickCount();
+    animation->current = selected;
+    osTimerStart(animation->timer, animation->current->basic->duration * 1000);
+    animation->state = DesktopAnimationStateBasic;
+    furi_assert(selected);
+    AnimationList_clear(animation_list);
+}
+
+static void desktop_animation_timer_callback(void* context) {
+    furi_assert(context);
+    DesktopAnimation* animation = context;
+    TickType_t now_ms = osKernelGetTickCount();
+    AnimationList_t animation_list;
+    AnimationList_init(animation_list);
+    bool new_basic_animation = false;
+
+    if(animation->state == DesktopAnimationStateActive) {
+        animation->state = DesktopAnimationStateBasic;
+        TickType_t basic_lasts_ms = now_ms - animation->basic_started_at;
+        animation->active_finished_at = now_ms;
+        TickType_t basic_duration_ms = animation->current->basic->duration * 1000;
+        if(basic_lasts_ms > basic_duration_ms) {
+            // if active animation finished, and basic duration came to an end
+            // select new idle animation
+            new_basic_animation = true;
+        } else {
+            // if active animation finished, but basic duration is not finished
+            // play current animation for the rest of time
+            furi_assert(basic_duration_ms != basic_lasts_ms);
+            osTimerStart(animation->timer, basic_duration_ms - basic_lasts_ms);
+        }
+    } else if(animation->state == DesktopAnimationStateBasic) {
+        // if basic animation finished
+        // select new idle animation
+        new_basic_animation = true;
+    }
+
+    if(new_basic_animation) {
+        animation->basic_started_at = now_ms;
+        desktop_start_new_idle_animation(animation);
+    }
+
+    // for oneshot generate events every time
+    if(animation->animation_changed_callback) {
+        animation->animation_changed_callback(animation->animation_changed_callback_context);
+    }
+}
+
+void desktop_animation_activate(DesktopAnimation* animation) {
+    furi_assert(animation);
+
+    if(animation->state != DesktopAnimationStateBasic) {
+        return;
+    }
+
+    if(animation->state == DesktopAnimationStateActive) {
+        return;
+    }
+
+    if(!animation->current->active) {
+        return;
+    }
+
+    TickType_t now = osKernelGetTickCount();
+    TickType_t time_since_last_active = now - animation->active_finished_at;
+
+    if(time_since_last_active > (animation->current->basic->active_cooldown * 1000)) {
+        animation->state = DesktopAnimationStateActive;
+        furi_assert(animation->current->active->duration > 0);
+        osTimerStart(animation->timer, animation->current->active->duration * 1000);
+        if(animation->animation_changed_callback) {
+            animation->animation_changed_callback(animation->animation_changed_callback_context);
+        }
+    }
+}
+
+static const Icon* desktop_animation_get_current_idle_animation(DesktopAnimation* animation) {
+    const Icon* active_icon = animation->current->active->icon;
+    const Icon* basic_icon = animation->current->basic->icon;
+    return (animation->state == DesktopAnimationStateActive && active_icon) ? active_icon :
+                                                                              basic_icon;
+}
+
+// Every time somebody starts 'desktop_animation_get_animation()'
+// 1) check if there is a new level
+// 2) check if there is SD card corruption
+// 3) check if the SD card is empty
+// 4) if all false - get idle animation
+
+const Icon* desktop_animation_get_animation(DesktopAnimation* animation) {
+    Dolphin* dolphin = furi_record_open("dolphin");
+    Storage* storage = furi_record_open("storage");
+    const Icon* icon = NULL;
+    furi_assert(animation);
+    FS_Error sd_status = storage_sd_status(storage);
+
+    if(IS_BLOCKING_ANIMATION(animation->state)) {
+        // don't give new animation till blocked animation
+        // is reseted
+        icon = animation->current_blocking_icon;
+    }
+
+    if(!icon) {
+        if(sd_status == FSE_INTERNAL) {
+            osTimerStop(animation->timer);
+            icon = &A_CardBad_128x51;
+            animation->current_blocking_icon = icon;
+            animation->state = DesktopAnimationStateSDCorrupted;
+            animation->sd_shown_error_card_bad = true;
+            animation->sd_shown_error_db = false;
+        } else if(sd_status == FSE_NOT_READY) {
+            animation->sd_shown_error_card_bad = false;
+            animation->sd_shown_error_db = false;
+        } else if(sd_status == FSE_OK) {
+            bool db_exists = storage_common_stat(storage, "/ext/manifest.txt", NULL) == FSE_OK;
+            if(db_exists && !animation->sd_shown_error_db) {
+                osTimerStop(animation->timer);
+                icon = &A_CardNoDB_128x51;
+                animation->current_blocking_icon = icon;
+                animation->state = DesktopAnimationStateSDEmpty;
+                animation->sd_shown_error_db = true;
+            }
+        }
+    }
+
+    DolphinStats stats = dolphin_stats(dolphin);
+    if(!icon && stats.level_up_is_pending) {
+        osTimerStop(animation->timer);
+        icon = &A_LevelUpPending_128x51;
+        animation->current_blocking_icon = icon;
+        animation->state = DesktopAnimationStateLevelUpIsPending;
+    }
+
+    if(!icon) {
+        icon = desktop_animation_get_current_idle_animation(animation);
+    }
+
+    furi_record_close("storage");
+    furi_record_close("dolphin");
+
+    return icon;
+}
+
+DesktopAnimationState desktop_animation_handle_right(DesktopAnimation* animation) {
+    furi_assert(animation);
+
+    bool reset_animation = false;
+    bool update_animation = false;
+
+    switch(animation->state) {
+    case DesktopAnimationStateActive:
+    case DesktopAnimationStateBasic:
+        /* nothing */
+        break;
+    case DesktopAnimationStateLevelUpIsPending:
+        /* do nothing, main scene should change itself */
+        break;
+    case DesktopAnimationStateSDCorrupted:
+        reset_animation = true;
+        break;
+    case DesktopAnimationStateSDEmpty:
+        animation->state = DesktopAnimationStateSDEmptyURL;
+        animation->current_blocking_icon = &A_CardNoDBUrl_128x51;
+        update_animation = true;
+        break;
+    case DesktopAnimationStateSDEmptyURL:
+        reset_animation = true;
+        break;
+    default:
+        furi_crash("Unhandled desktop animation state");
+    }
+
+    if(reset_animation) {
+        desktop_start_new_idle_animation(animation);
+        update_animation = true;
+    }
+
+    if(update_animation) {
+        if(animation->animation_changed_callback) {
+            animation->animation_changed_callback(animation->animation_changed_callback_context);
+        }
+    }
+
+    return animation->state;
+}
+
+#define LEVELUP_FRAME_RATE (0.2)
+
+void desktop_animation_start_oneshot_levelup(DesktopAnimation* animation) {
+    animation->one_shot_animation_counter = 0;
+    animation->state = DesktopAnimationStateLevelUpIsPending;
+
+    Dolphin* dolphin = furi_record_open("dolphin");
+    DolphinStats stats = dolphin_stats(dolphin);
+    furi_record_close("dolphin");
+    furi_assert(stats.level_up_is_pending);
+    if(stats.level == 1) {
+        animation->current_one_shot_icons = animation_level2up;
+        animation->one_shot_animation_size = COUNT_OF(animation_level2up);
+    } else if(stats.level == 2) {
+        animation->current_one_shot_icons = animation_level3up;
+        animation->one_shot_animation_size = COUNT_OF(animation_level3up);
+    } else {
+        furi_crash("Dolphin level is out of bounds");
+    }
+    osTimerStart(animation->timer, LEVELUP_FRAME_RATE * 1000);
+}
 
 
-    FURI_LOG_I(TAG, "background change");
-    FURI_LOG_I(TAG, "icounter: %d", stats.icounter);
-    FURI_LOG_I(TAG, "butthurt: %d", stats.butthurt);
-    FURI_LOG_I(TAG, "time since deeed: %.0f", timediff);
-#endif
+const Icon* desktop_animation_get_oneshot_frame(DesktopAnimation* animation) {
+    furi_assert(IS_ONESHOT_ANIMATION(animation->state));
+    furi_assert(animation->one_shot_animation_size > 0);
+    const Icon* icon = NULL;
 
 
-    if((random() % 100) > 50) { // temp rnd selection
-        new = random() % COUNT_OF(idle_scenes);
+    if(animation->one_shot_animation_counter < animation->one_shot_animation_size) {
+        icon = animation->current_one_shot_icons[animation->one_shot_animation_counter];
+        ++animation->one_shot_animation_counter;
+    } else {
+        animation->state = DesktopAnimationStateBasic;
+        animation->one_shot_animation_size = 0;
+        osTimerStop(animation->timer);
+        icon = NULL;
     }
     }
 
 
-    return idle_scenes[new];
+    return icon;
 }
 }

+ 53 - 8
applications/desktop/helpers/desktop_animation.h

@@ -1,10 +1,55 @@
 #pragma once
 #pragma once
 
 
-#include <furi.h>
-#include <math.h>
-#include <assets_icons.h>
-#include "dolphin/dolphin.h"
-#include "dolphin/helpers/dolphin_state.h"
-#include "time.h"
-
-const Icon* desktop_get_icon();
+#include <stdint.h>
+#include <gui/icon.h>
+
+typedef struct DesktopAnimation DesktopAnimation;
+
+typedef struct ActiveAnimation ActiveAnimation;
+typedef struct BasicAnimation BasicAnimation;
+
+typedef enum {
+    DesktopAnimationStateBasic,
+    DesktopAnimationStateActive,
+    DesktopAnimationStateLevelUpIsPending,
+    DesktopAnimationStateSDEmpty,
+    DesktopAnimationStateSDEmptyURL,
+    DesktopAnimationStateSDCorrupted,
+} DesktopAnimationState;
+
+struct BasicAnimation {
+    const Icon* icon;
+    uint16_t duration; // sec
+    uint16_t active_cooldown;
+    uint8_t weight;
+    uint16_t butthurt_level_mask;
+};
+
+struct ActiveAnimation {
+    const Icon* icon;
+    uint16_t duration; // sec
+};
+
+typedef struct {
+    const BasicAnimation* basic;
+    const ActiveAnimation* active;
+} PairedAnimation;
+
+typedef void (*AnimationChangedCallback)(void*);
+
+DesktopAnimation* desktop_animation_alloc(void);
+void desktop_animation_free(DesktopAnimation*);
+void desktop_animation_activate(DesktopAnimation* instance);
+void desktop_animation_set_animation_changed_callback(
+    DesktopAnimation* instance,
+    AnimationChangedCallback callback,
+    void* context);
+
+DesktopAnimationState desktop_animation_handle_right(DesktopAnimation* animation);
+
+void desktop_animation_start_oneshot_levelup(DesktopAnimation* animation);
+
+const Icon* desktop_animation_get_animation(DesktopAnimation* animation);
+const Icon* desktop_animation_get_oneshot_frame(DesktopAnimation* animation);
+
+void desktop_start_new_idle_animation(DesktopAnimation* animation);

+ 330 - 0
applications/desktop/helpers/desktop_animation_i.h

@@ -0,0 +1,330 @@
+#include <assets_icons.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <gui/icon.h>
+#include "desktop_animation.h"
+
+// Calm/Mad Basic Idle Animations
+
+#define COMMON_BASIC_DURATION (60 * 60)
+#define COMMON_ACTIVE_CYCLES 7
+#define COMMON_ACTIVE_COOLDOWN 30
+#define COMMON_WEIGHT 3
+
+#define BUTTHURT_LEVEL(x) (1UL << (x))
+#define BUTTHURT_LEVEL_0 0
+
+// frames * cycles / frame_rate
+#define COMMON_ACTIVE_DURATION(x) ((x)*COMMON_ACTIVE_CYCLES / 2)
+
+static const BasicAnimation animation_TV = {
+    .icon = &A_TV_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
+                           BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
+                           BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7)};
+
+static const ActiveAnimation animation_TV_active = {
+    .icon = &A_TVActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(2),
+};
+
+static const BasicAnimation animation_sleep = {
+    .icon = &A_Sleep_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
+                           BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
+                           BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) |
+                           BUTTHURT_LEVEL(9) | BUTTHURT_LEVEL(10)};
+
+static const ActiveAnimation animation_sleep_active = {
+    .icon = &A_SleepActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(2),
+};
+
+static const BasicAnimation animation_leaving = {
+    .icon = &A_Leaving_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(13) | BUTTHURT_LEVEL(14),
+};
+
+static const ActiveAnimation animation_leaving_active = {
+    .icon = &A_LeavingActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(2),
+};
+
+static const BasicAnimation animation_laptop = {
+    .icon = &A_Laptop_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
+                           BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5)};
+
+static const ActiveAnimation animation_laptop_active = {
+    .icon = &A_LaptopActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(2),
+};
+
+static const BasicAnimation animation_knife = {
+    .icon = &A_Knife_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(5) | BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) |
+                           BUTTHURT_LEVEL(8) | BUTTHURT_LEVEL(9) | BUTTHURT_LEVEL(10) |
+                           BUTTHURT_LEVEL(11) | BUTTHURT_LEVEL(12) | BUTTHURT_LEVEL(13)};
+
+static const ActiveAnimation animation_knife_active = {
+    .icon = &A_KnifeActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(2),
+};
+
+static const BasicAnimation animation_cry = {
+    .icon = &A_Cry_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
+                           BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) |
+                           BUTTHURT_LEVEL(9) | BUTTHURT_LEVEL(10) | BUTTHURT_LEVEL(11) |
+                           BUTTHURT_LEVEL(12) | BUTTHURT_LEVEL(13)};
+
+static const ActiveAnimation animation_cry_active = {
+    .icon = &A_CryActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(3),
+};
+
+static const BasicAnimation animation_box = {
+    .icon = &A_Box_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) | BUTTHURT_LEVEL(9) |
+                           BUTTHURT_LEVEL(10) | BUTTHURT_LEVEL(11) | BUTTHURT_LEVEL(12) |
+                           BUTTHURT_LEVEL(13)};
+
+static const ActiveAnimation animation_box_active = {
+    .icon = &A_BoxActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(2),
+};
+
+static const BasicAnimation animation_waves = {
+    .icon = &A_Waves_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2)};
+
+static const ActiveAnimation animation_waves_active = {
+    .icon = &A_WavesActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(2),
+};
+
+// Level Idle Animations
+
+static const BasicAnimation animation_level1furippa = {
+    .icon = &A_Level1Furippa_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
+                           BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
+                           BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7)};
+
+static const ActiveAnimation animation_level1furippa_active = {
+    .icon = &A_Level1FurippaActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(6),
+};
+
+static const BasicAnimation animation_level1read = {
+    .icon = &A_Level1Read_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2)};
+
+static const ActiveAnimation animation_level1read_active = {
+    .icon = &A_Level1ReadActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(2),
+};
+
+static const BasicAnimation animation_level1toys = {
+    .icon = &A_Level1Toys_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
+                           BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
+                           BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8)};
+
+static const ActiveAnimation animation_level1toys_active = {
+    .icon = &A_Level1ToysActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(2),
+};
+
+static const BasicAnimation animation_level2furippa = {
+    .icon = &A_Level2Furippa_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
+                           BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
+                           BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7)};
+
+static const ActiveAnimation animation_level2furippa_active = {
+    .icon = &A_Level2FurippaActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(6),
+};
+
+static const BasicAnimation animation_level2soldering = {
+    .icon = &A_Level2Soldering_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
+                           BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
+                           BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) |
+                           BUTTHURT_LEVEL(9)};
+
+static const ActiveAnimation animation_level2soldering_active = {
+    .icon = &A_Level2SolderingActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(2),
+};
+
+static const BasicAnimation animation_level2hack = {
+    .icon = &A_Level2Hack_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
+                           BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
+                           BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8)};
+
+static const ActiveAnimation animation_level2hack_active = {
+    .icon = &A_Level2HackActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(2),
+};
+
+static const BasicAnimation animation_level3furippa = {
+    .icon = &A_Level3Furippa_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
+                           BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
+                           BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7)};
+
+static const ActiveAnimation animation_level3furippa_active = {
+    .icon = &A_Level3FurippaActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(6),
+};
+
+static const BasicAnimation animation_level3hijack = {
+    .icon = &A_Level3Hijack_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
+                           BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
+                           BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) |
+                           BUTTHURT_LEVEL(9)};
+
+static const ActiveAnimation animation_level3hijack_active = {
+    .icon = &A_Level3HijackActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(2),
+};
+
+static const BasicAnimation animation_level3lab = {
+    .icon = &A_Level3Lab_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = COMMON_WEIGHT,
+    .active_cooldown = COMMON_ACTIVE_COOLDOWN,
+    .butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
+                           BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
+                           BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8)};
+
+static const ActiveAnimation animation_level3lab_active = {
+    .icon = &A_Level3LabActive_128x51,
+    .duration = COMMON_ACTIVE_DURATION(2),
+};
+
+// System Idle Animations
+
+static const BasicAnimation animation_bad_battery = {
+    .icon = &A_BadBattery_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = 7,
+};
+
+static const BasicAnimation animation_no_sd_card = {
+    .icon = &A_NoSdCard_128x51,
+    .duration = COMMON_BASIC_DURATION,
+    .weight = 7,
+};
+
+const Icon* animation_level2up[] = {
+    &I_LevelUp2_01,
+    &I_LevelUp2_02,
+    &I_LevelUp2_03,
+    &I_LevelUp2_04,
+    &I_LevelUp2_05,
+    &I_LevelUp2_06,
+    &I_LevelUp2_07};
+
+const Icon* animation_level3up[] = {
+    &I_LevelUp3_01,
+    &I_LevelUp3_02,
+    &I_LevelUp3_03,
+    &I_LevelUp3_04,
+    &I_LevelUp3_05,
+    &I_LevelUp3_06,
+    &I_LevelUp3_07};
+
+// Blocking Idle Animations & One shot Animations represented as naked Icon
+
+static const PairedAnimation calm_animation[] = {
+    {.basic = &animation_TV, .active = &animation_TV_active},
+    {.basic = &animation_waves, .active = &animation_waves_active},
+    {.basic = &animation_sleep, .active = &animation_sleep_active},
+    {.basic = &animation_laptop, .active = &animation_laptop_active},
+};
+
+static const PairedAnimation mad_animation[] = {
+    {.basic = &animation_cry, .active = &animation_cry_active},
+    {.basic = &animation_knife, .active = &animation_knife_active},
+    {.basic = &animation_box, .active = &animation_box_active},
+    {.basic = &animation_leaving, .active = &animation_leaving_active},
+};
+
+static const PairedAnimation level_1_animation[] = {
+    {.basic = &animation_level1furippa, .active = &animation_level1furippa_active},
+    {.basic = &animation_level1read, .active = &animation_level1read_active},
+    {.basic = &animation_level1toys, .active = &animation_level1toys_active},
+};
+
+static const PairedAnimation level_2_animation[] = {
+    {.basic = &animation_level2furippa, .active = &animation_level2furippa_active},
+    {.basic = &animation_level2soldering, .active = &animation_level2soldering_active},
+    {.basic = &animation_level2hack, .active = &animation_level2hack_active},
+};
+
+static const PairedAnimation level_3_animation[] = {
+    {.basic = &animation_level3furippa, .active = &animation_level3furippa_active},
+    {.basic = &animation_level3hijack, .active = &animation_level3hijack_active},
+    {.basic = &animation_level3lab, .active = &animation_level3lab_active},
+};
+
+static const PairedAnimation no_sd_animation[] = {
+    {.basic = &animation_no_sd_card, .active = NULL},
+};
+
+static const PairedAnimation check_battery_animation[] = {
+    {.basic = &animation_bad_battery, .active = NULL},
+};

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

@@ -5,3 +5,4 @@ ADD_SCENE(desktop, debug, Debug)
 ADD_SCENE(desktop, first_start, FirstStart)
 ADD_SCENE(desktop, first_start, FirstStart)
 ADD_SCENE(desktop, hw_mismatch, HwMismatch)
 ADD_SCENE(desktop, hw_mismatch, HwMismatch)
 ADD_SCENE(desktop, pinsetup, PinSetup)
 ADD_SCENE(desktop, pinsetup, PinSetup)
+ADD_SCENE(desktop, levelup, LevelUp)

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

@@ -59,4 +59,5 @@ bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) {
 void desktop_scene_debug_on_exit(void* context) {
 void desktop_scene_debug_on_exit(void* context) {
     Desktop* desktop = (Desktop*)context;
     Desktop* desktop = (Desktop*)context;
     desktop_debug_reset_screen_idx(desktop->debug_view);
     desktop_debug_reset_screen_idx(desktop->debug_view);
+    desktop_start_new_idle_animation(desktop->animation);
 }
 }

+ 79 - 0
applications/desktop/scenes/desktop_scene_levelup.c

@@ -0,0 +1,79 @@
+#include "../desktop_i.h"
+#include "../views/desktop_main.h"
+#include "applications.h"
+#include "assets_icons.h"
+#include "desktop/desktop.h"
+#include "desktop/helpers/desktop_animation.h"
+#include "dolphin/dolphin.h"
+#include "furi/pubsub.h"
+#include "furi/record.h"
+#include "storage/storage-glue.h"
+#include <loader/loader.h>
+#include <m-list.h>
+
+#define LEVELUP_SCENE_PLAYING 0
+#define LEVELUP_SCENE_STOPPED 1
+
+static void desktop_scene_levelup_callback(DesktopMainEvent event, void* context) {
+    Desktop* desktop = (Desktop*)context;
+    view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
+}
+
+static void desktop_scene_levelup_animation_changed_callback(void* context) {
+    furi_assert(context);
+    Desktop* desktop = context;
+    view_dispatcher_send_custom_event(
+        desktop->view_dispatcher, DesktopMainEventUpdateOneShotAnimation);
+}
+
+void desktop_scene_levelup_on_enter(void* context) {
+    Desktop* desktop = (Desktop*)context;
+    DesktopMainView* main_view = desktop->main_view;
+
+    desktop_main_set_callback(main_view, desktop_scene_levelup_callback, desktop);
+    desktop_animation_set_animation_changed_callback(
+        desktop->animation, desktop_scene_levelup_animation_changed_callback, desktop);
+
+    desktop_animation_start_oneshot_levelup(desktop->animation);
+    const Icon* icon = desktop_animation_get_oneshot_frame(desktop->animation);
+    desktop_main_switch_dolphin_icon(desktop->main_view, icon);
+    view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain);
+    scene_manager_set_scene_state(
+        desktop->scene_manager, DesktopSceneLevelUp, LEVELUP_SCENE_PLAYING);
+}
+
+bool desktop_scene_levelup_on_event(void* context, SceneManagerEvent event) {
+    Desktop* desktop = (Desktop*)context;
+    bool consumed = false;
+    DesktopMainEvent main_event = event.event;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(main_event == DesktopMainEventUpdateOneShotAnimation) {
+            const Icon* icon = desktop_animation_get_oneshot_frame(desktop->animation);
+            if(icon) {
+                desktop_main_switch_dolphin_icon(desktop->main_view, icon);
+            } else {
+                scene_manager_set_scene_state(
+                    desktop->scene_manager, DesktopSceneLevelUp, LEVELUP_SCENE_STOPPED);
+            }
+            consumed = true;
+        } else {
+            if(scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLevelUp) ==
+               LEVELUP_SCENE_STOPPED) {
+                scene_manager_previous_scene(desktop->scene_manager);
+            }
+        }
+    }
+
+    return consumed;
+}
+
+void desktop_scene_levelup_on_exit(void* context) {
+    Desktop* desktop = (Desktop*)context;
+
+    Dolphin* dolphin = furi_record_open("dolphin");
+    dolphin_upgrade_level(dolphin);
+    furi_record_close("dolphin");
+    desktop_animation_set_animation_changed_callback(desktop->animation, NULL, NULL);
+    desktop_start_new_idle_animation(desktop->animation);
+}

+ 20 - 1
applications/desktop/scenes/desktop_scene_locked.c

@@ -1,5 +1,7 @@
 #include "../desktop_i.h"
 #include "../desktop_i.h"
 #include "../views/desktop_locked.h"
 #include "../views/desktop_locked.h"
+#include "desktop/helpers/desktop_animation.h"
+#include "desktop/views/desktop_main.h"
 #include <furi-hal-lock.h>
 #include <furi-hal-lock.h>
 
 
 void desktop_scene_locked_callback(DesktopLockedEvent event, void* context) {
 void desktop_scene_locked_callback(DesktopLockedEvent event, void* context) {
@@ -7,6 +9,12 @@ void desktop_scene_locked_callback(DesktopLockedEvent event, void* context) {
     view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
     view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
 }
 }
 
 
+static void desktop_scene_locked_animation_changed_callback(void* context) {
+    furi_assert(context);
+    Desktop* desktop = context;
+    view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventUpdateAnimation);
+}
+
 void desktop_scene_locked_on_enter(void* context) {
 void desktop_scene_locked_on_enter(void* context) {
     Desktop* desktop = (Desktop*)context;
     Desktop* desktop = (Desktop*)context;
     DesktopLockedView* locked_view = desktop->locked_view;
     DesktopLockedView* locked_view = desktop->locked_view;
@@ -14,7 +22,11 @@ void desktop_scene_locked_on_enter(void* context) {
     desktop_locked_set_callback(locked_view, desktop_scene_locked_callback, desktop);
     desktop_locked_set_callback(locked_view, desktop_scene_locked_callback, desktop);
     desktop_locked_reset_door_pos(locked_view);
     desktop_locked_reset_door_pos(locked_view);
     desktop_locked_update_hint_timeout(locked_view);
     desktop_locked_update_hint_timeout(locked_view);
-    desktop_locked_set_dolphin_animation(locked_view);
+
+    desktop_animation_set_animation_changed_callback(
+        desktop->animation, desktop_scene_locked_animation_changed_callback, desktop);
+    const Icon* icon = desktop_animation_get_animation(desktop->animation);
+    desktop_locked_set_dolphin_animation(locked_view, icon);
 
 
     uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopViewLocked);
     uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopViewLocked);
 
 
@@ -68,6 +80,12 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) {
         case DesktopLockedEventInputReset:
         case DesktopLockedEventInputReset:
             desktop->pincode_buffer.length = 0;
             desktop->pincode_buffer.length = 0;
             break;
             break;
+        case DesktopMainEventUpdateAnimation: {
+            const Icon* icon = desktop_animation_get_animation(desktop->animation);
+            desktop_locked_set_dolphin_animation(desktop->locked_view, icon);
+            consumed = true;
+            break;
+        }
         default:
         default:
             if(desktop_scene_locked_check_pin(desktop, event.event)) {
             if(desktop_scene_locked_check_pin(desktop, event.event)) {
                 scene_manager_set_scene_state(
                 scene_manager_set_scene_state(
@@ -84,6 +102,7 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) {
 
 
 void desktop_scene_locked_on_exit(void* context) {
 void desktop_scene_locked_on_exit(void* context) {
     Desktop* desktop = (Desktop*)context;
     Desktop* desktop = (Desktop*)context;
+    desktop_animation_set_animation_changed_callback(desktop->animation, NULL, NULL);
     desktop_locked_reset_counter(desktop->locked_view);
     desktop_locked_reset_counter(desktop->locked_view);
     osTimerStop(desktop->locked_view->timer);
     osTimerStop(desktop->locked_view->timer);
 }
 }

+ 40 - 1
applications/desktop/scenes/desktop_scene_main.c

@@ -1,7 +1,13 @@
 #include "../desktop_i.h"
 #include "../desktop_i.h"
 #include "../views/desktop_main.h"
 #include "../views/desktop_main.h"
 #include "applications.h"
 #include "applications.h"
+#include "assets_icons.h"
+#include "dolphin/dolphin.h"
+#include "furi/pubsub.h"
+#include "furi/record.h"
+#include "storage/storage-glue.h"
 #include <loader/loader.h>
 #include <loader/loader.h>
+#include <m-list.h>
 #define MAIN_VIEW_DEFAULT (0UL)
 #define MAIN_VIEW_DEFAULT (0UL)
 
 
 static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* flipper_app) {
 static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* flipper_app) {
@@ -27,6 +33,12 @@ void desktop_scene_main_callback(DesktopMainEvent event, void* context) {
     view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
     view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
 }
 }
 
 
+static void desktop_scene_main_animation_changed_callback(void* context) {
+    furi_assert(context);
+    Desktop* desktop = context;
+    view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventUpdateAnimation);
+}
+
 void desktop_scene_main_on_enter(void* context) {
 void desktop_scene_main_on_enter(void* context) {
     Desktop* desktop = (Desktop*)context;
     Desktop* desktop = (Desktop*)context;
     DesktopMainView* main_view = desktop->main_view;
     DesktopMainView* main_view = desktop->main_view;
@@ -39,7 +51,11 @@ void desktop_scene_main_on_enter(void* context) {
         desktop_main_unlocked(desktop->main_view);
         desktop_main_unlocked(desktop->main_view);
     }
     }
 
 
-    desktop_main_switch_dolphin_animation(desktop->main_view);
+    desktop_animation_activate(desktop->animation);
+    desktop_animation_set_animation_changed_callback(
+        desktop->animation, desktop_scene_main_animation_changed_callback, desktop);
+    const Icon* icon = desktop_animation_get_animation(desktop->animation);
+    desktop_main_switch_dolphin_animation(desktop->main_view, icon);
     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain);
     view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain);
 }
 }
 
 
@@ -75,9 +91,30 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
             consumed = true;
             consumed = true;
             break;
             break;
 
 
+        case DesktopMainEventUpdateAnimation: {
+            const Icon* icon = desktop_animation_get_animation(desktop->animation);
+            desktop_main_switch_dolphin_animation(desktop->main_view, icon);
+            consumed = true;
+            break;
+        }
+
+        case DesktopMainEventRightShort: {
+            DesktopAnimationState state = desktop_animation_handle_right(desktop->animation);
+            if(state == DesktopAnimationStateLevelUpIsPending) {
+                scene_manager_next_scene(desktop->scene_manager, DesktopSceneLevelUp);
+            }
+            break;
+        }
+
         default:
         default:
             break;
             break;
         }
         }
+
+        if(event.event != DesktopMainEventUpdateAnimation) {
+            desktop_animation_activate(desktop->animation);
+        }
+    } else if(event.type != SceneManagerEventTypeTick) {
+        desktop_animation_activate(desktop->animation);
     }
     }
 
 
     return consumed;
     return consumed;
@@ -85,6 +122,8 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
 
 
 void desktop_scene_main_on_exit(void* context) {
 void desktop_scene_main_on_exit(void* context) {
     Desktop* desktop = (Desktop*)context;
     Desktop* desktop = (Desktop*)context;
+
+    desktop_animation_set_animation_changed_callback(desktop->animation, NULL, NULL);
     scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneMain, MAIN_VIEW_DEFAULT);
     scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneMain, MAIN_VIEW_DEFAULT);
     desktop_main_reset_hint(desktop->main_view);
     desktop_main_reset_hint(desktop->main_view);
 }
 }

+ 22 - 13
applications/desktop/views/desktop_debug.c

@@ -25,7 +25,7 @@ void desktop_debug_render(Canvas* canvas, void* model) {
 
 
     canvas_set_color(canvas, ColorBlack);
     canvas_set_color(canvas, ColorBlack);
     canvas_set_font(canvas, FontPrimary);
     canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str(canvas, 2, 13, headers[m->screen]);
+    canvas_draw_str(canvas, 2, 9, headers[m->screen]);
     canvas_set_font(canvas, FontSecondary);
     canvas_set_font(canvas, FontSecondary);
 
 
     if(m->screen != DesktopViewStatsMeta) {
     if(m->screen != DesktopViewStatsMeta) {
@@ -40,13 +40,13 @@ void desktop_debug_render(Canvas* canvas, void* model) {
             furi_hal_version_get_hw_body(),
             furi_hal_version_get_hw_body(),
             furi_hal_version_get_hw_connect(),
             furi_hal_version_get_hw_connect(),
             my_name ? my_name : "Unknown");
             my_name ? my_name : "Unknown");
-        canvas_draw_str(canvas, 5, 23, buffer);
+        canvas_draw_str(canvas, 5, 19, buffer);
 
 
         ver = m->screen == DesktopViewStatsBoot ? furi_hal_version_get_bootloader_version() :
         ver = m->screen == DesktopViewStatsBoot ? furi_hal_version_get_bootloader_version() :
                                                   furi_hal_version_get_firmware_version();
                                                   furi_hal_version_get_firmware_version();
 
 
         if(!ver) {
         if(!ver) {
-            canvas_draw_str(canvas, 5, 33, "No info");
+            canvas_draw_str(canvas, 5, 29, "No info");
             return;
             return;
         }
         }
 
 
@@ -56,7 +56,7 @@ void desktop_debug_render(Canvas* canvas, void* model) {
             "%s [%s]",
             "%s [%s]",
             version_get_version(ver),
             version_get_version(ver),
             version_get_builddate(ver));
             version_get_builddate(ver));
-        canvas_draw_str(canvas, 5, 32, buffer);
+        canvas_draw_str(canvas, 5, 28, buffer);
 
 
         snprintf(
         snprintf(
             buffer,
             buffer,
@@ -64,27 +64,36 @@ void desktop_debug_render(Canvas* canvas, void* model) {
             "%s [%s]",
             "%s [%s]",
             version_get_githash(ver),
             version_get_githash(ver),
             version_get_gitbranchnum(ver));
             version_get_gitbranchnum(ver));
-        canvas_draw_str(canvas, 5, 43, buffer);
+        canvas_draw_str(canvas, 5, 39, buffer);
 
 
         snprintf(
         snprintf(
             buffer, sizeof(buffer), "[%d] %s", version_get_target(ver), version_get_gitbranch(ver));
             buffer, sizeof(buffer), "[%d] %s", version_get_target(ver), version_get_gitbranch(ver));
-        canvas_draw_str(canvas, 5, 54, buffer);
+        canvas_draw_str(canvas, 5, 50, buffer);
 
 
     } else {
     } else {
         char buffer[64];
         char buffer[64];
-        uint32_t current_lvl = dolphin_state_get_level(m->icounter);
-        uint32_t remaining = dolphin_state_xp_to_levelup(m->icounter, current_lvl, true);
+        Dolphin* dolphin = furi_record_open("dolphin");
+        DolphinStats stats = dolphin_stats(dolphin);
+        furi_record_close("dolphin");
+
+        uint32_t current_lvl = stats.level;
+        uint32_t remaining = dolphin_state_xp_to_levelup(m->icounter);
 
 
         canvas_set_font(canvas, FontSecondary);
         canvas_set_font(canvas, FontSecondary);
         snprintf(buffer, 64, "Icounter: %ld  Butthurt %ld", m->icounter, m->butthurt);
         snprintf(buffer, 64, "Icounter: %ld  Butthurt %ld", m->icounter, m->butthurt);
-        canvas_draw_str(canvas, 5, 23, buffer);
+        canvas_draw_str(canvas, 5, 19, buffer);
 
 
-        snprintf(buffer, 64, "Level: %ld  To level up: %ld", current_lvl, remaining);
-        canvas_draw_str(canvas, 5, 33, buffer);
+        snprintf(
+            buffer,
+            64,
+            "Level: %ld  To level up: %ld",
+            current_lvl,
+            (remaining == (uint32_t)(-1) ? remaining : 0));
+        canvas_draw_str(canvas, 5, 29, buffer);
 
 
         snprintf(buffer, 64, "%s", asctime(localtime((const time_t*)&m->timestamp)));
         snprintf(buffer, 64, "%s", asctime(localtime((const time_t*)&m->timestamp)));
-        canvas_draw_str(canvas, 5, 43, buffer);
-        canvas_draw_str(canvas, 0, 53, "[< >] icounter value   [ok] save");
+        canvas_draw_str(canvas, 5, 39, buffer);
+        canvas_draw_str(canvas, 0, 49, "[< >] icounter value   [ok] save");
     }
     }
 }
 }
 
 

+ 18 - 18
applications/desktop/views/desktop_first_start.c

@@ -26,20 +26,20 @@ static void desktop_first_start_draw(Canvas* canvas, void* model) {
     uint8_t height = canvas_height(canvas);
     uint8_t height = canvas_height(canvas);
     const char* my_name = furi_hal_version_get_name_ptr();
     const char* my_name = furi_hal_version_get_name_ptr();
     if(m->page == 0) {
     if(m->page == 0) {
-        canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart0_70x53);
-        elements_multiline_text_framed(canvas, 75, 20, "Hey m8,\npress > to\ncontinue");
+        canvas_draw_icon(canvas, 0, height - 51, &I_DolphinFirstStart0_70x53);
+        elements_multiline_text_framed(canvas, 75, 16, "Hey m8,\npress > to\ncontinue");
     } else if(m->page == 1) {
     } else if(m->page == 1) {
-        canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart1_59x53);
-        elements_multiline_text_framed(canvas, 64, 20, "First Of All,\n...      >");
+        canvas_draw_icon(canvas, 0, height - 51, &I_DolphinFirstStart1_59x53);
+        elements_multiline_text_framed(canvas, 64, 16, "First Of All,\n...      >");
     } else if(m->page == 2) {
     } else if(m->page == 2) {
-        canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart2_59x51);
-        elements_multiline_text_framed(canvas, 64, 20, "Thank you\nfor your\nsupport! >");
+        canvas_draw_icon(canvas, 0, height - 51, &I_DolphinFirstStart2_59x51);
+        elements_multiline_text_framed(canvas, 64, 16, "Thank you\nfor your\nsupport! >");
     } else if(m->page == 3) {
     } else if(m->page == 3) {
-        canvas_draw_icon(canvas, width - 57, height - 48, &I_DolphinFirstStart3_57x48);
-        elements_multiline_text_framed(canvas, 0, 20, "Kickstarter\ncampaign\nwas INSANE! >");
+        canvas_draw_icon(canvas, width - 57, height - 45, &I_DolphinFirstStart3_57x48);
+        elements_multiline_text_framed(canvas, 0, 16, "Kickstarter\ncampaign\nwas INSANE! >");
     } else if(m->page == 4) {
     } else if(m->page == 4) {
-        canvas_draw_icon(canvas, width - 67, height - 50, &I_DolphinFirstStart4_67x53);
-        elements_multiline_text_framed(canvas, 0, 17, "Now\nallow me\nto introduce\nmyself >");
+        canvas_draw_icon(canvas, width - 67, height - 51, &I_DolphinFirstStart4_67x53);
+        elements_multiline_text_framed(canvas, 0, 13, "Now\nallow me\nto introduce\nmyself >");
     } else if(m->page == 5) {
     } else if(m->page == 5) {
         char buf[64];
         char buf[64];
         snprintf(
         snprintf(
@@ -49,20 +49,20 @@ static void desktop_first_start_draw(Canvas* canvas, void* model) {
             "I am",
             "I am",
             my_name ? my_name : "Unknown",
             my_name ? my_name : "Unknown",
             ",\ncyberdolphin\nliving in your\npocket >");
             ",\ncyberdolphin\nliving in your\npocket >");
-        canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart5_54x49);
-        elements_multiline_text_framed(canvas, 60, 17, buf);
+        canvas_draw_icon(canvas, 0, height - 49, &I_DolphinFirstStart5_54x49);
+        elements_multiline_text_framed(canvas, 60, 13, buf);
     } else if(m->page == 6) {
     } else if(m->page == 6) {
-        canvas_draw_icon(canvas, 0, height - 48, &I_DolphinFirstStart6_58x54);
+        canvas_draw_icon(canvas, 0, height - 51, &I_DolphinFirstStart6_58x54);
         elements_multiline_text_framed(
         elements_multiline_text_framed(
-            canvas, 63, 17, "I can grow\nsmart'n'cool\nif you use me\noften >");
+            canvas, 63, 13, "I can grow\nsmart'n'cool\nif you use me\noften >");
     } else if(m->page == 7) {
     } else if(m->page == 7) {
-        canvas_draw_icon(canvas, width - 61, height - 48, &I_DolphinFirstStart7_61x51);
+        canvas_draw_icon(canvas, width - 61, height - 51, &I_DolphinFirstStart7_61x51);
         elements_multiline_text_framed(
         elements_multiline_text_framed(
-            canvas, 0, 17, "As long as\nyou read, write\nand emulate >");
+            canvas, 0, 13, "As long as\nyou read, write\nand emulate >");
     } else if(m->page == 8) {
     } else if(m->page == 8) {
-        canvas_draw_icon(canvas, width - 56, height - 48, &I_DolphinFirstStart8_56x51);
+        canvas_draw_icon(canvas, width - 56, height - 51, &I_DolphinFirstStart8_56x51);
         elements_multiline_text_framed(
         elements_multiline_text_framed(
-            canvas, 0, 17, "You can check\nmy level and\nmood in the\nPassport menu");
+            canvas, 0, 13, "You can check\nmy level and\nmood in the\nPassport menu");
     }
     }
 }
 }
 
 

+ 5 - 5
applications/desktop/views/desktop_locked.c

@@ -17,13 +17,13 @@ void locked_view_timer_callback(void* context) {
     locked_view->callback(DesktopLockedEventUpdate, locked_view->context);
     locked_view->callback(DesktopLockedEventUpdate, locked_view->context);
 }
 }
 
 
-// temporary locked screen animation managment
-void desktop_locked_set_dolphin_animation(DesktopLockedView* locked_view) {
+void desktop_locked_set_dolphin_animation(DesktopLockedView* locked_view, const Icon* icon) {
     with_view_model(
     with_view_model(
         locked_view->view, (DesktopLockedViewModel * model) {
         locked_view->view, (DesktopLockedViewModel * model) {
             if(model->animation) icon_animation_free(model->animation);
             if(model->animation) icon_animation_free(model->animation);
-            model->animation = icon_animation_alloc(desktop_get_icon());
+            model->animation = icon_animation_alloc(icon);
             view_tie_icon_animation(locked_view->view, model->animation);
             view_tie_icon_animation(locked_view->view, model->animation);
+            icon_animation_start(model->animation);
             return true;
             return true;
         });
         });
 }
 }
@@ -100,7 +100,7 @@ void desktop_locked_render(Canvas* canvas, void* model) {
     }
     }
 
 
     if(m->animation && m->animation_seq_end) {
     if(m->animation && m->animation_seq_end) {
-        canvas_draw_icon_animation(canvas, 0, -3, m->animation);
+        canvas_draw_icon_animation(canvas, 0, 0, m->animation);
     }
     }
 
 
     if(now < m->hint_expire_at) {
     if(now < m->hint_expire_at) {
@@ -110,7 +110,7 @@ void desktop_locked_render(Canvas* canvas, void* model) {
 
 
         } else if(!m->pin_lock) {
         } else if(!m->pin_lock) {
             canvas_set_font(canvas, FontSecondary);
             canvas_set_font(canvas, FontSecondary);
-            canvas_draw_icon(canvas, 13, 5, &I_LockPopup_100x49);
+            canvas_draw_icon(canvas, 13, 2, &I_LockPopup_100x49);
             elements_multiline_text(canvas, 65, 20, "To unlock\npress:");
             elements_multiline_text(canvas, 65, 20, "To unlock\npress:");
         }
         }
     }
     }

+ 2 - 2
applications/desktop/views/desktop_locked.h

@@ -56,7 +56,7 @@ void desktop_locked_set_callback(
     DesktopLockedViewCallback callback,
     DesktopLockedViewCallback callback,
     void* context);
     void* context);
 
 
-void desktop_locked_set_dolphin_animation(DesktopLockedView* locked_view);
+void desktop_locked_set_dolphin_animation(DesktopLockedView* locked_view, const Icon* icon);
 void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view);
 void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view);
 void desktop_locked_reset_counter(DesktopLockedView* locked_view);
 void desktop_locked_reset_counter(DesktopLockedView* locked_view);
 void desktop_locked_reset_door_pos(DesktopLockedView* locked_view);
 void desktop_locked_reset_door_pos(DesktopLockedView* locked_view);
@@ -65,4 +65,4 @@ void desktop_locked_manage_redraw(DesktopLockedView* locked_view);
 View* desktop_locked_get_view(DesktopLockedView* locked_view);
 View* desktop_locked_get_view(DesktopLockedView* locked_view);
 DesktopLockedView* desktop_locked_alloc();
 DesktopLockedView* desktop_locked_alloc();
 void desktop_locked_free(DesktopLockedView* locked_view);
 void desktop_locked_free(DesktopLockedView* locked_view);
-void desktop_locked_with_pin(DesktopLockedView* lock_menu, bool locked);
+void desktop_locked_with_pin(DesktopLockedView* lock_menu, bool locked);

+ 22 - 4
applications/desktop/views/desktop_main.c

@@ -1,3 +1,5 @@
+#include "gui/canvas.h"
+#include "input/input.h"
 #include <furi.h>
 #include <furi.h>
 #include "../desktop_i.h"
 #include "../desktop_i.h"
 #include "desktop_main.h"
 #include "desktop_main.h"
@@ -20,12 +22,24 @@ void desktop_main_reset_hint(DesktopMainView* main_view) {
         });
         });
 }
 }
 
 
-void desktop_main_switch_dolphin_animation(DesktopMainView* main_view) {
+void desktop_main_switch_dolphin_animation(DesktopMainView* main_view, const Icon* icon) {
     with_view_model(
     with_view_model(
         main_view->view, (DesktopMainViewModel * model) {
         main_view->view, (DesktopMainViewModel * model) {
             if(model->animation) icon_animation_free(model->animation);
             if(model->animation) icon_animation_free(model->animation);
-            model->animation = icon_animation_alloc(desktop_get_icon());
+            model->animation = icon_animation_alloc(icon);
             view_tie_icon_animation(main_view->view, model->animation);
             view_tie_icon_animation(main_view->view, model->animation);
+            icon_animation_start(model->animation);
+            model->icon = NULL;
+            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;
             return true;
         });
         });
 }
 }
@@ -35,8 +49,10 @@ void desktop_main_render(Canvas* canvas, void* model) {
     DesktopMainViewModel* m = model;
     DesktopMainViewModel* m = model;
     uint32_t now = osKernelGetTickCount();
     uint32_t now = osKernelGetTickCount();
 
 
-    if(m->animation) {
-        canvas_draw_icon_animation(canvas, 0, -3, m->animation);
+    if(m->icon) {
+        canvas_draw_icon(canvas, 0, 0, m->icon);
+    } else if(m->animation) {
+        canvas_draw_icon_animation(canvas, 0, 0, m->animation);
     }
     }
 
 
     if(now < m->hint_expire_at) {
     if(now < m->hint_expire_at) {
@@ -66,6 +82,8 @@ bool desktop_main_input(InputEvent* event, void* context) {
         main_view->callback(DesktopMainEventOpenArchive, main_view->context);
         main_view->callback(DesktopMainEventOpenArchive, main_view->context);
     } else if(event->key == InputKeyLeft && event->type == InputTypeShort) {
     } else if(event->key == InputKeyLeft && event->type == InputTypeShort) {
         main_view->callback(DesktopMainEventOpenFavorite, main_view->context);
         main_view->callback(DesktopMainEventOpenFavorite, main_view->context);
+    } else if(event->key == InputKeyRight && event->type == InputTypeShort) {
+        main_view->callback(DesktopMainEventRightShort, main_view->context);
     }
     }
 
 
     desktop_main_reset_hint(main_view);
     desktop_main_reset_hint(main_view);

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

@@ -13,6 +13,9 @@ typedef enum {
     DesktopMainEventOpenMenu,
     DesktopMainEventOpenMenu,
     DesktopMainEventOpenDebug,
     DesktopMainEventOpenDebug,
     DesktopMainEventUnlocked,
     DesktopMainEventUnlocked,
+    DesktopMainEventRightShort,
+    DesktopMainEventUpdateAnimation,
+    DesktopMainEventUpdateOneShotAnimation,
 } DesktopMainEvent;
 } DesktopMainEvent;
 
 
 typedef struct DesktopMainView DesktopMainView;
 typedef struct DesktopMainView DesktopMainView;
@@ -27,6 +30,7 @@ struct DesktopMainView {
 
 
 typedef struct {
 typedef struct {
     IconAnimation* animation;
     IconAnimation* animation;
+    const Icon* icon;
     uint8_t scene_num;
     uint8_t scene_num;
     uint32_t hint_expire_at;
     uint32_t hint_expire_at;
 } DesktopMainViewModel;
 } DesktopMainViewModel;
@@ -39,6 +43,7 @@ void desktop_main_set_callback(
 View* desktop_main_get_view(DesktopMainView* main_view);
 View* desktop_main_get_view(DesktopMainView* main_view);
 DesktopMainView* desktop_main_alloc();
 DesktopMainView* desktop_main_alloc();
 void desktop_main_free(DesktopMainView* main_view);
 void desktop_main_free(DesktopMainView* main_view);
-void desktop_main_switch_dolphin_animation(DesktopMainView* main_view);
+void desktop_main_switch_dolphin_animation(DesktopMainView* main_view, const Icon* icon);
 void desktop_main_unlocked(DesktopMainView* main_view);
 void desktop_main_unlocked(DesktopMainView* main_view);
 void desktop_main_reset_hint(DesktopMainView* main_view);
 void desktop_main_reset_hint(DesktopMainView* main_view);
+void desktop_main_switch_dolphin_icon(DesktopMainView* main_view, const Icon* icon);

+ 26 - 5
applications/dolphin/dolphin.c

@@ -1,4 +1,9 @@
+#include "dolphin/dolphin.h"
+#include "desktop/desktop.h"
+#include "dolphin/helpers/dolphin_state.h"
 #include "dolphin_i.h"
 #include "dolphin_i.h"
+#include "furi/pubsub.h"
+#include "sys/_stdint.h"
 #include <furi.h>
 #include <furi.h>
 #define DOLPHIN_TIMEGATE 86400 // one day
 #define DOLPHIN_TIMEGATE 86400 // one day
 #define DOLPHIN_LOCK_EVENT_FLAG (0x1)
 #define DOLPHIN_LOCK_EVENT_FLAG (0x1)
@@ -39,6 +44,7 @@ Dolphin* dolphin_alloc() {
 
 
     dolphin->state = dolphin_state_alloc();
     dolphin->state = dolphin_state_alloc();
     dolphin->event_queue = osMessageQueueNew(8, sizeof(DolphinEvent), NULL);
     dolphin->event_queue = osMessageQueueNew(8, sizeof(DolphinEvent), NULL);
+    dolphin->pubsub = furi_pubsub_alloc();
 
 
     return dolphin;
     return dolphin;
 }
 }
@@ -79,7 +85,7 @@ void dolphin_event_release(Dolphin* dolphin, DolphinEvent* event) {
 
 
 static void dolphin_check_butthurt(DolphinState* state) {
 static void dolphin_check_butthurt(DolphinState* state) {
     furi_assert(state);
     furi_assert(state);
-    float diff_time = difftime(dolphin_state_get_timestamp(state), dolphin_state_timestamp());
+    float diff_time = difftime(state->data.timestamp, dolphin_state_timestamp());
 
 
     if((fabs(diff_time)) > DOLPHIN_TIMEGATE) {
     if((fabs(diff_time)) > DOLPHIN_TIMEGATE) {
         FURI_LOG_I("DolphinState", "Increasing butthurt");
         FURI_LOG_I("DolphinState", "Increasing butthurt");
@@ -87,6 +93,10 @@ static void dolphin_check_butthurt(DolphinState* state) {
     }
     }
 }
 }
 
 
+FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin) {
+    return dolphin->pubsub;
+}
+
 int32_t dolphin_srv(void* p) {
 int32_t dolphin_srv(void* p) {
     Dolphin* dolphin = dolphin_alloc();
     Dolphin* dolphin = dolphin_alloc();
     furi_record_create("dolphin", dolphin);
     furi_record_create("dolphin", dolphin);
@@ -97,11 +107,17 @@ int32_t dolphin_srv(void* p) {
     while(1) {
     while(1) {
         if(osMessageQueueGet(dolphin->event_queue, &event, NULL, 60000) == osOK) {
         if(osMessageQueueGet(dolphin->event_queue, &event, NULL, 60000) == osOK) {
             if(event.type == DolphinEventTypeDeed) {
             if(event.type == DolphinEventTypeDeed) {
-                dolphin_state_on_deed(dolphin->state, event.deed);
+                if(dolphin_state_on_deed(dolphin->state, event.deed)) {
+                    DolphinPubsubEvent event = DolphinPubsubEventUpdate;
+                    furi_pubsub_publish(dolphin->pubsub, &event);
+                }
             } else if(event.type == DolphinEventTypeStats) {
             } else if(event.type == DolphinEventTypeStats) {
-                event.stats->icounter = dolphin_state_get_icounter(dolphin->state);
-                event.stats->butthurt = dolphin_state_get_butthurt(dolphin->state);
-                event.stats->timestamp = dolphin_state_get_timestamp(dolphin->state);
+                event.stats->icounter = dolphin->state->data.icounter;
+                event.stats->butthurt = dolphin->state->data.butthurt;
+                event.stats->timestamp = dolphin->state->data.timestamp;
+                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) {
             } else if(event.type == DolphinEventTypeFlush) {
                 dolphin_state_save(dolphin->state);
                 dolphin_state_save(dolphin->state);
             }
             }
@@ -116,3 +132,8 @@ int32_t dolphin_srv(void* p) {
 
 
     return 0;
     return 0;
 }
 }
+
+void dolphin_upgrade_level(Dolphin* dolphin) {
+    dolphin_state_increase_level(dolphin->state);
+    dolphin_flush(dolphin);
+}

+ 13 - 1
applications/dolphin/dolphin.h

@@ -1,6 +1,8 @@
 #pragma once
 #pragma once
 
 
+#include "furi/pubsub.h"
 #include "helpers/dolphin_deed.h"
 #include "helpers/dolphin_deed.h"
+#include <stdbool.h>
 
 
 typedef struct Dolphin Dolphin;
 typedef struct Dolphin Dolphin;
 
 
@@ -8,8 +10,14 @@ typedef struct {
     uint32_t icounter;
     uint32_t icounter;
     uint32_t butthurt;
     uint32_t butthurt;
     uint64_t timestamp;
     uint64_t timestamp;
+    uint8_t level;
+    bool level_up_is_pending;
 } DolphinStats;
 } DolphinStats;
 
 
+typedef enum {
+    DolphinPubsubEventUpdate,
+} DolphinPubsubEvent;
+
 /** Deed complete notification. Call it on deed completion.
 /** Deed complete notification. Call it on deed completion.
  * See dolphin_deed.h for available deeds. In futures it will become part of assets.
  * See dolphin_deed.h for available deeds. In futures it will become part of assets.
  * Thread safe, async
  * Thread safe, async
@@ -24,4 +32,8 @@ DolphinStats dolphin_stats(Dolphin* dolphin);
 /** Flush dolphin queue and save state
 /** Flush dolphin queue and save state
  * Thread safe, blocking
  * Thread safe, blocking
  */
  */
-void dolphin_flush(Dolphin* dolphin);
+void dolphin_flush(Dolphin* dolphin);
+
+void dolphin_upgrade_level(Dolphin* dolphin);
+
+FuriPubSub* dolphin_get_pubsub(Dolphin* dolphin);

+ 2 - 0
applications/dolphin/dolphin_i.h

@@ -1,5 +1,6 @@
 #pragma once
 #pragma once
 
 
+#include "furi/pubsub.h"
 #include <furi.h>
 #include <furi.h>
 #include <furi-hal.h>
 #include <furi-hal.h>
 
 
@@ -26,6 +27,7 @@ struct Dolphin {
     DolphinState* state;
     DolphinState* state;
     // Queue
     // Queue
     osMessageQueueId_t event_queue;
     osMessageQueueId_t event_queue;
+    FuriPubSub* pubsub;
 };
 };
 
 
 Dolphin* dolphin_alloc();
 Dolphin* dolphin_alloc();

+ 6 - 4
applications/dolphin/helpers/dolphin_deed.c

@@ -1,12 +1,14 @@
 #include "dolphin_deed.h"
 #include "dolphin_deed.h"
+#include <furi.h>
 
 
 static const DolphinDeedWeight dolphin_deed_weights[DolphinDeedMax] = {
 static const DolphinDeedWeight dolphin_deed_weights[DolphinDeedMax] = {
-    {1, 2, 60},
-    {1, 2, 60},
-    {1, 2, 60},
-    {-1, 2, 60},
+    {1, -1, 60},
+    {1, -1, 60},
+    {1, -1, 60},
+    {-1, 1, 60},
 };
 };
 
 
 const DolphinDeedWeight* dolphin_deed_weight(DolphinDeed deed) {
 const DolphinDeedWeight* dolphin_deed_weight(DolphinDeed deed) {
+    furi_assert(deed < DolphinDeedMax);
     return &dolphin_deed_weights[deed];
     return &dolphin_deed_weights[deed];
 }
 }

+ 56 - 41
applications/dolphin/helpers/dolphin_state.c

@@ -1,4 +1,5 @@
 #include "dolphin_state.h"
 #include "dolphin_state.h"
+#include <stdint.h>
 #include <storage/storage.h>
 #include <storage/storage.h>
 #include <furi.h>
 #include <furi.h>
 #include <math.h>
 #include <math.h>
@@ -9,23 +10,8 @@
 #define DOLPHIN_STATE_HEADER_MAGIC 0xD0
 #define DOLPHIN_STATE_HEADER_MAGIC 0xD0
 #define DOLPHIN_STATE_HEADER_VERSION 0x01
 #define DOLPHIN_STATE_HEADER_VERSION 0x01
 #define DOLPHIN_LVL_THRESHOLD 20.0f
 #define DOLPHIN_LVL_THRESHOLD 20.0f
-
-typedef struct {
-    uint32_t limit_ibutton;
-    uint32_t limit_nfc;
-    uint32_t limit_ir;
-    uint32_t limit_rfid;
-
-    uint32_t flags;
-    uint32_t icounter;
-    uint32_t butthurt;
-    uint64_t timestamp;
-} DolphinStoreData;
-
-struct DolphinState {
-    DolphinStoreData data;
-    bool dirty;
-};
+#define LEVEL2_THRESHOLD 20
+#define LEVEL3_THRESHOLD 100
 
 
 DolphinState* dolphin_state_alloc() {
 DolphinState* dolphin_state_alloc() {
     return furi_alloc(sizeof(DolphinState));
     return furi_alloc(sizeof(DolphinState));
@@ -93,18 +79,62 @@ uint64_t dolphin_state_timestamp() {
     return mktime(&current);
     return mktime(&current);
 }
 }
 
 
-void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) {
+bool dolphin_state_is_levelup(uint32_t icounter) {
+    return (icounter == LEVEL2_THRESHOLD) || (icounter == LEVEL3_THRESHOLD);
+}
+
+uint8_t dolphin_get_level(uint32_t icounter) {
+    if(icounter <= LEVEL2_THRESHOLD) {
+        return 1;
+    } else if(icounter <= LEVEL3_THRESHOLD) {
+        return 2;
+    } else {
+        return 3;
+    }
+}
+
+uint32_t dolphin_state_xp_to_levelup(uint32_t icounter) {
+    uint32_t threshold = 0;
+    if(icounter <= LEVEL2_THRESHOLD) {
+        threshold = LEVEL2_THRESHOLD;
+    } else if(icounter <= LEVEL3_THRESHOLD) {
+        threshold = LEVEL3_THRESHOLD;
+    } else {
+        threshold = (uint32_t)-1;
+    }
+    return threshold - icounter;
+}
+
+bool dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) {
     const DolphinDeedWeight* deed_weight = dolphin_deed_weight(deed);
     const DolphinDeedWeight* deed_weight = dolphin_deed_weight(deed);
     int32_t icounter = dolphin_state->data.icounter + deed_weight->icounter;
     int32_t icounter = dolphin_state->data.icounter + deed_weight->icounter;
-    int32_t butthurt = dolphin_state->data.butthurt;
+    bool level_up = false;
+    bool mood_changed = false;
+
+    if(icounter <= 0) {
+        icounter = 0;
+        if(dolphin_state->data.icounter == 0) {
+            return false;
+        }
+    }
 
 
-    if(icounter >= 0) {
-        dolphin_state->data.icounter = icounter;
-        dolphin_state->data.butthurt = MAX(butthurt - deed_weight->icounter, 0);
-        dolphin_state->data.timestamp = dolphin_state_timestamp();
+    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);
     }
     }
 
 
+    uint32_t new_butthurt =
+        CLAMP(((int32_t)dolphin_state->data.butthurt) + deed_weight->butthurt, 14, 0);
+
+    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;
     dolphin_state->dirty = true;
+
+    return level_up || mood_changed;
 }
 }
 
 
 void dolphin_state_butthurted(DolphinState* dolphin_state) {
 void dolphin_state_butthurted(DolphinState* dolphin_state) {
@@ -113,22 +143,7 @@ void dolphin_state_butthurted(DolphinState* dolphin_state) {
     dolphin_state->dirty = true;
     dolphin_state->dirty = true;
 }
 }
 
 
-uint32_t dolphin_state_get_icounter(DolphinState* dolphin_state) {
-    return dolphin_state->data.icounter;
-}
-
-uint32_t dolphin_state_get_butthurt(DolphinState* dolphin_state) {
-    return dolphin_state->data.butthurt;
-}
-
-uint64_t dolphin_state_get_timestamp(DolphinState* dolphin_state) {
-    return dolphin_state->data.timestamp;
-}
-
-uint32_t dolphin_state_get_level(uint32_t icounter) {
-    return 0.5f + sqrtf(1.0f + 8.0f * ((float)icounter / DOLPHIN_LVL_THRESHOLD)) / 2.0f;
-}
-
-uint32_t dolphin_state_xp_to_levelup(uint32_t icounter, uint32_t level, bool remaining) {
-    return (DOLPHIN_LVL_THRESHOLD * level * (level + 1) / 2) - (remaining ? icounter : 0);
+void dolphin_state_increase_level(DolphinState* dolphin_state) {
+    ++dolphin_state->data.icounter;
+    dolphin_state->dirty = true;
 }
 }

+ 21 - 7
applications/dolphin/helpers/dolphin_state.h

@@ -7,6 +7,22 @@
 #include <time.h>
 #include <time.h>
 
 
 typedef struct DolphinState DolphinState;
 typedef struct DolphinState DolphinState;
+typedef struct {
+    uint32_t limit_ibutton;
+    uint32_t limit_nfc;
+    uint32_t limit_ir;
+    uint32_t limit_rfid;
+
+    uint32_t flags;
+    uint32_t icounter;
+    uint32_t butthurt;
+    uint64_t timestamp;
+} DolphinStoreData;
+
+struct DolphinState {
+    DolphinStoreData data;
+    bool dirty;
+};
 
 
 DolphinState* dolphin_state_alloc();
 DolphinState* dolphin_state_alloc();
 
 
@@ -20,16 +36,14 @@ void dolphin_state_clear(DolphinState* dolphin_state);
 
 
 uint64_t dolphin_state_timestamp();
 uint64_t dolphin_state_timestamp();
 
 
-void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed);
+bool dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed);
 
 
 void dolphin_state_butthurted(DolphinState* dolphin_state);
 void dolphin_state_butthurted(DolphinState* dolphin_state);
 
 
-uint32_t dolphin_state_get_icounter(DolphinState* dolphin_state);
+uint32_t dolphin_state_xp_to_levelup(uint32_t icounter);
 
 
-uint32_t dolphin_state_get_butthurt(DolphinState* dolphin_state);
+bool dolphin_state_is_levelup(uint32_t icounter);
 
 
-uint64_t dolphin_state_get_timestamp(DolphinState* dolphin_state);
+void dolphin_state_increase_level(DolphinState* dolphin_state);
 
 
-uint32_t dolphin_state_get_level(uint32_t icounter);
-
-uint32_t dolphin_state_xp_to_levelup(uint32_t icounter, uint32_t level, bool remaining);
+uint8_t dolphin_get_level(uint32_t icounter);

+ 10 - 10
applications/gui/gui.c

@@ -134,10 +134,10 @@ void gui_redraw_status_bar(Gui* gui) {
     }
     }
 }
 }
 
 
-bool gui_redraw_normal(Gui* gui) {
+bool gui_redraw_window(Gui* gui) {
     canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal);
     canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal);
-    canvas_frame_set(gui->canvas, GUI_MAIN_X, GUI_MAIN_Y, GUI_MAIN_WIDTH, GUI_MAIN_HEIGHT);
-    ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerMain]);
+    canvas_frame_set(gui->canvas, GUI_WINDOW_X, GUI_WINDOW_Y, GUI_WINDOW_WIDTH, GUI_WINDOW_HEIGHT);
+    ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerWindow]);
     if(view_port) {
     if(view_port) {
         view_port_draw(view_port, gui->canvas);
         view_port_draw(view_port, gui->canvas);
         return true;
         return true;
@@ -145,10 +145,10 @@ bool gui_redraw_normal(Gui* gui) {
     return false;
     return false;
 }
 }
 
 
-bool gui_redraw_none(Gui* gui) {
+bool gui_redraw_desktop(Gui* gui) {
     canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal);
     canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal);
-    canvas_frame_set(gui->canvas, GUI_MAIN_X, GUI_MAIN_Y, GUI_MAIN_WIDTH, GUI_MAIN_HEIGHT);
-    ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerNone]);
+    canvas_frame_set(gui->canvas, GUI_WINDOW_X, GUI_WINDOW_Y, GUI_WINDOW_WIDTH, GUI_WINDOW_HEIGHT);
+    ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]);
     if(view_port) {
     if(view_port) {
         view_port_draw(view_port, gui->canvas);
         view_port_draw(view_port, gui->canvas);
         return true;
         return true;
@@ -164,8 +164,8 @@ void gui_redraw(Gui* gui) {
     canvas_reset(gui->canvas);
     canvas_reset(gui->canvas);
 
 
     if(!gui_redraw_fs(gui)) {
     if(!gui_redraw_fs(gui)) {
-        if(!gui_redraw_normal(gui)) {
-            gui_redraw_none(gui);
+        if(!gui_redraw_window(gui)) {
+            gui_redraw_desktop(gui);
         }
         }
         gui_redraw_status_bar(gui);
         gui_redraw_status_bar(gui);
     }
     }
@@ -203,8 +203,8 @@ void gui_input(Gui* gui, InputEvent* input_event) {
     gui_lock(gui);
     gui_lock(gui);
 
 
     ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]);
     ViewPort* view_port = gui_view_port_find_enabled(gui->layers[GuiLayerFullscreen]);
-    if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerMain]);
-    if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerNone]);
+    if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerWindow]);
+    if(!view_port) view_port = gui_view_port_find_enabled(gui->layers[GuiLayerDesktop]);
 
 
     if(!(gui->ongoing_input & ~key_bit) && input_event->type == InputTypePress) {
     if(!(gui->ongoing_input & ~key_bit) && input_event->type == InputTypePress) {
         gui->ongoing_input_view_port = view_port;
         gui->ongoing_input_view_port = view_port;

+ 5 - 3
applications/gui/gui.h

@@ -14,12 +14,14 @@ extern "C" {
 
 
 /** Gui layers */
 /** Gui layers */
 typedef enum {
 typedef enum {
-    GuiLayerNone, /**< Special layer for internal use only */
+    GuiLayerDesktop, /**< Desktop layer for internal use. Like fullscreen but with status bar */
+
+    GuiLayerWindow, /**< Window layer, status bar is shown */
 
 
     GuiLayerStatusBarLeft, /**< Status bar left-side layer, auto-layout */
     GuiLayerStatusBarLeft, /**< Status bar left-side layer, auto-layout */
     GuiLayerStatusBarRight, /**< Status bar right-side layer, auto-layout */
     GuiLayerStatusBarRight, /**< Status bar right-side layer, auto-layout */
-    GuiLayerMain, /**< Main layer, status bar is shown */
-    GuiLayerFullscreen, /**< Fullscreen layer */
+
+    GuiLayerFullscreen, /**< Fullscreen layer, no status bar */
 
 
     GuiLayerMAX /**< Don't use or move, special value */
     GuiLayerMAX /**< Don't use or move, special value */
 } GuiLayer;
 } GuiLayer;

+ 4 - 4
applications/gui/gui_i.h

@@ -25,10 +25,10 @@
 #define GUI_STATUS_BAR_WIDTH GUI_DISPLAY_WIDTH
 #define GUI_STATUS_BAR_WIDTH GUI_DISPLAY_WIDTH
 #define GUI_STATUS_BAR_HEIGHT 13
 #define GUI_STATUS_BAR_HEIGHT 13
 
 
-#define GUI_MAIN_X 0
-#define GUI_MAIN_Y 9
-#define GUI_MAIN_WIDTH GUI_DISPLAY_WIDTH
-#define GUI_MAIN_HEIGHT (GUI_DISPLAY_HEIGHT - GUI_MAIN_Y)
+#define GUI_WINDOW_X 0
+#define GUI_WINDOW_Y GUI_STATUS_BAR_HEIGHT
+#define GUI_WINDOW_WIDTH GUI_DISPLAY_WIDTH
+#define GUI_WINDOW_HEIGHT (GUI_DISPLAY_HEIGHT - GUI_WINDOW_Y)
 
 
 #define GUI_THREAD_FLAG_DRAW (1 << 0)
 #define GUI_THREAD_FLAG_DRAW (1 << 0)
 #define GUI_THREAD_FLAG_INPUT (1 << 1)
 #define GUI_THREAD_FLAG_INPUT (1 << 1)

+ 1 - 0
applications/gui/icon_animation.c

@@ -62,6 +62,7 @@ void icon_animation_start(IconAnimation* instance) {
     furi_assert(instance);
     furi_assert(instance);
     if(!instance->animating) {
     if(!instance->animating) {
         instance->animating = true;
         instance->animating = true;
+        furi_assert(instance->icon->frame_rate);
         furi_check(
         furi_check(
             xTimerChangePeriod(
             xTimerChangePeriod(
                 instance->timer, (osKernelGetTickFreq() / instance->icon->frame_rate), 0) ==
                 instance->timer, (osKernelGetTickFreq() / instance->icon->frame_rate), 0) ==

+ 4 - 4
applications/gui/view_dispatcher.c

@@ -199,12 +199,12 @@ void view_dispatcher_attach_to_gui(
     furi_assert(view_dispatcher->gui == NULL);
     furi_assert(view_dispatcher->gui == NULL);
     furi_assert(gui);
     furi_assert(gui);
 
 
-    if(type == ViewDispatcherTypeNone) {
-        gui_add_view_port(gui, view_dispatcher->view_port, GuiLayerNone);
+    if(type == ViewDispatcherTypeDesktop) {
+        gui_add_view_port(gui, view_dispatcher->view_port, GuiLayerDesktop);
+    } else if(type == ViewDispatcherTypeWindow) {
+        gui_add_view_port(gui, view_dispatcher->view_port, GuiLayerWindow);
     } else if(type == ViewDispatcherTypeFullscreen) {
     } else if(type == ViewDispatcherTypeFullscreen) {
         gui_add_view_port(gui, view_dispatcher->view_port, GuiLayerFullscreen);
         gui_add_view_port(gui, view_dispatcher->view_port, GuiLayerFullscreen);
-    } else if(type == ViewDispatcherTypeWindow) {
-        gui_add_view_port(gui, view_dispatcher->view_port, GuiLayerMain);
     } else {
     } else {
         furi_check(NULL);
         furi_check(NULL);
     }
     }

+ 3 - 3
applications/gui/view_dispatcher.h

@@ -15,9 +15,9 @@ extern "C" {
 
 
 /** ViewDispatcher view_port placement */
 /** ViewDispatcher view_port placement */
 typedef enum {
 typedef enum {
-    ViewDispatcherTypeNone, /**< Special layer for internal use only */
-    ViewDispatcherTypeWindow, /**< Main view_port layer, status bar is shown */
-    ViewDispatcherTypeFullscreen /**< Fullscreen view_port layer */
+    ViewDispatcherTypeDesktop, /**< Desktop layer: fullscreen with status bar on top of it. For internal usage. */
+    ViewDispatcherTypeWindow, /**< Window layer: with status bar  */
+    ViewDispatcherTypeFullscreen /**< Fullscreen layer: without status bar */
 } ViewDispatcherType;
 } ViewDispatcherType;
 
 
 typedef struct ViewDispatcher ViewDispatcher;
 typedef struct ViewDispatcher ViewDispatcher;

+ 5 - 0
applications/power/power_service/power.c

@@ -7,6 +7,11 @@
 #include <gui/view.h>
 #include <gui/view.h>
 
 
 #define POWER_OFF_TIMEOUT 90
 #define POWER_OFF_TIMEOUT 90
+#define POWER_BATTERY_WELL_LEVEL 99
+
+bool power_is_battery_well(PowerInfo* info) {
+    return info->health > POWER_BATTERY_WELL_LEVEL;
+}
 
 
 void power_draw_battery_callback(Canvas* canvas, void* context) {
 void power_draw_battery_callback(Canvas* canvas, void* context) {
     furi_assert(context);
     furi_assert(context);

+ 3 - 0
applications/power/power_service/power.h

@@ -2,6 +2,7 @@
 
 
 #include <stdint.h>
 #include <stdint.h>
 #include <furi/pubsub.h>
 #include <furi/pubsub.h>
+#include <stdbool.h>
 
 
 typedef struct Power Power;
 typedef struct Power Power;
 
 
@@ -63,3 +64,5 @@ void power_get_info(Power* power, PowerInfo* info);
  * @param power - Power instance
  * @param power - Power instance
  */
  */
 FuriPubSub* power_get_pubsub(Power* power);
 FuriPubSub* power_get_pubsub(Power* power);
+
+bool power_is_battery_well(PowerInfo* info);

+ 4 - 0
applications/storage/storage-external-api.c

@@ -391,6 +391,10 @@ void storage_file_free(File* file) {
     free(file);
     free(file);
 }
 }
 
 
+FuriPubSub* storage_get_pubsub(Storage* storage) {
+    return storage->pubsub;
+}
+
 bool storage_simply_remove_recursive(Storage* storage, const char* path) {
 bool storage_simply_remove_recursive(Storage* storage, const char* path) {
     furi_assert(storage);
     furi_assert(storage);
     furi_assert(path);
     furi_assert(path);

+ 3 - 1
applications/storage/storage-i.h

@@ -19,9 +19,11 @@ typedef struct {
 struct Storage {
 struct Storage {
     osMessageQueueId_t message_queue;
     osMessageQueueId_t message_queue;
     StorageData storage[STORAGE_COUNT];
     StorageData storage[STORAGE_COUNT];
+    StorageStatus prev_ext_storage_status;
     StorageSDGui sd_gui;
     StorageSDGui sd_gui;
+    FuriPubSub* pubsub;
 };
 };
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }
-#endif
+#endif

+ 8 - 1
applications/storage/storage.c

@@ -2,6 +2,7 @@
 #include "storage-i.h"
 #include "storage-i.h"
 #include "storage-message.h"
 #include "storage-message.h"
 #include "storage-processing.h"
 #include "storage-processing.h"
+#include "storage/storage-glue.h"
 #include "storages/storage-int.h"
 #include "storages/storage-int.h"
 #include "storages/storage-ext.h"
 #include "storages/storage-ext.h"
 
 
@@ -31,6 +32,7 @@ static void storage_app_sd_icon_draw_callback(Canvas* canvas, void* context) {
 Storage* storage_app_alloc() {
 Storage* storage_app_alloc() {
     Storage* app = malloc(sizeof(Storage));
     Storage* app = malloc(sizeof(Storage));
     app->message_queue = osMessageQueueNew(8, sizeof(StorageMessage), NULL);
     app->message_queue = osMessageQueueNew(8, sizeof(StorageMessage), NULL);
+    app->pubsub = furi_pubsub_alloc();
 
 
     for(uint8_t i = 0; i < STORAGE_COUNT; i++) {
     for(uint8_t i = 0; i < STORAGE_COUNT; i++) {
         storage_data_init(&app->storage[i]);
         storage_data_init(&app->storage[i]);
@@ -61,6 +63,11 @@ void storage_tick(Storage* app) {
         }
         }
     }
     }
 
 
+    if(app->storage[ST_EXT].status != app->prev_ext_storage_status) {
+        app->prev_ext_storage_status = app->storage[ST_EXT].status;
+        furi_pubsub_publish(app->pubsub, &app->storage[ST_EXT].status);
+    }
+
     // storage not enabled but was enabled (sd card unmount)
     // storage not enabled but was enabled (sd card unmount)
     if(app->storage[ST_EXT].status == StorageStatusNotReady && app->sd_gui.enabled == true) {
     if(app->storage[ST_EXT].status == StorageStatusNotReady && app->sd_gui.enabled == true) {
         app->sd_gui.enabled = false;
         app->sd_gui.enabled = false;
@@ -93,4 +100,4 @@ int32_t storage_srv(void* p) {
     }
     }
 
 
     return 0;
     return 0;
-}
+}

+ 2 - 0
applications/storage/storage.h

@@ -18,6 +18,8 @@ File* storage_file_alloc(Storage* storage);
  */
  */
 void storage_file_free(File* file);
 void storage_file_free(File* file);
 
 
+FuriPubSub* storage_get_pubsub(Storage* storage);
+
 /******************* File Functions *******************/
 /******************* File Functions *******************/
 
 
 /** Opens an existing file or create a new one.
 /** Opens an existing file or create a new one.

+ 1 - 1
applications/subghz/scenes/subghz_scene_need_saving.c

@@ -16,7 +16,7 @@ void subghz_scene_need_saving_on_enter(void* context) {
     SubGhz* subghz = context;
     SubGhz* subghz = context;
 
 
     widget_add_string_multiline_element(
     widget_add_string_multiline_element(
-        subghz->widget, 64, 13, AlignCenter, AlignCenter, FontPrimary, "Exit to Gub-Ghz menu?");
+        subghz->widget, 64, 13, AlignCenter, AlignCenter, FontPrimary, "Exit to Sub-Ghz menu?");
     widget_add_string_multiline_element(
     widget_add_string_multiline_element(
         subghz->widget,
         subghz->widget,
         64,
         64,

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
assets/compiled/assets_icons.c


+ 112 - 59
assets/compiled/assets_icons.h

@@ -3,72 +3,125 @@
 
 
 extern const Icon I_Certification2_119x30;
 extern const Icon I_Certification2_119x30;
 extern const Icon I_Certification1_103x23;
 extern const Icon I_Certification1_103x23;
-extern const Icon A_WatchingTV_128x64;
-extern const Icon A_Wink_128x64;
-extern const Icon I_ble_10px;
-extern const Icon I_ibutt_10px;
-extern const Icon I_125_10px;
+extern const Icon A_BadBattery_128x51;
+extern const Icon A_BoxActive_128x51;
+extern const Icon A_Box_128x51;
+extern const Icon A_CardBad_128x51;
+extern const Icon A_CardNoDBUrl_128x51;
+extern const Icon A_CardNoDB_128x51;
+extern const Icon A_CardOk_128x51;
+extern const Icon A_CryActive_128x51;
+extern const Icon A_Cry_128x51;
+extern const Icon A_KnifeActive_128x51;
+extern const Icon A_Knife_128x51;
+extern const Icon A_LaptopActive_128x51;
+extern const Icon A_Laptop_128x51;
+extern const Icon A_LeavingActive_128x51;
+extern const Icon A_Leaving_128x51;
+extern const Icon A_Level1FurippaActive_128x51;
+extern const Icon A_Level1Furippa_128x51;
+extern const Icon A_Level1ReadActive_128x51;
+extern const Icon A_Level1Read_128x51;
+extern const Icon A_Level1ToysActive_128x51;
+extern const Icon A_Level1Toys_128x51;
+extern const Icon A_Level2FurippaActive_128x51;
+extern const Icon A_Level2Furippa_128x51;
+extern const Icon A_Level2HackActive_128x51;
+extern const Icon A_Level2Hack_128x51;
+extern const Icon A_Level2SolderingActive_128x51;
+extern const Icon A_Level2Soldering_128x51;
+extern const Icon A_Level3FurippaActive_128x51;
+extern const Icon A_Level3Furippa_128x51;
+extern const Icon A_Level3HijackActive_128x51;
+extern const Icon A_Level3Hijack_128x51;
+extern const Icon A_Level3LabActive_128x51;
+extern const Icon A_Level3Lab_128x51;
+extern const Icon I_LevelUp2_04;
+extern const Icon I_LevelUp2_03;
+extern const Icon I_LevelUp2_07;
+extern const Icon I_LevelUp2_05;
+extern const Icon I_LevelUp2_02;
+extern const Icon I_LevelUp2_06;
+extern const Icon I_LevelUp2_01;
+extern const Icon I_LevelUp3_07;
+extern const Icon I_LevelUp3_02;
+extern const Icon I_LevelUp3_04;
+extern const Icon I_LevelUp3_05;
+extern const Icon I_LevelUp3_01;
+extern const Icon I_LevelUp3_03;
+extern const Icon I_LevelUp3_06;
+extern const Icon A_LevelUpPending_128x51;
+extern const Icon A_NoSdCard_128x51;
+extern const Icon A_SleepActive_128x51;
+extern const Icon A_Sleep_128x51;
+extern const Icon A_TVActive_128x51;
+extern const Icon A_TV_128x51;
+extern const Icon A_WavesActive_128x51;
+extern const Icon A_Waves_128x51;
 extern const Icon I_sub1_10px;
 extern const Icon I_sub1_10px;
-extern const Icon I_dir_10px;
 extern const Icon I_ir_10px;
 extern const Icon I_ir_10px;
-extern const Icon I_Nfc_10px;
 extern const Icon I_unknown_10px;
 extern const Icon I_unknown_10px;
+extern const Icon I_ibutt_10px;
+extern const Icon I_Nfc_10px;
+extern const Icon I_ble_10px;
+extern const Icon I_125_10px;
+extern const Icon I_dir_10px;
 extern const Icon I_BLE_Pairing_128x64;
 extern const Icon I_BLE_Pairing_128x64;
-extern const Icon I_ButtonRightSmall_3x5;
-extern const Icon I_ButtonLeftSmall_3x5;
-extern const Icon I_ButtonCenter_7x7;
 extern const Icon I_ButtonDown_7x4;
 extern const Icon I_ButtonDown_7x4;
-extern const Icon I_ButtonRight_4x7;
-extern const Icon I_DFU_128x50;
+extern const Icon I_ButtonCenter_7x7;
+extern const Icon I_ButtonLeft_4x7;
 extern const Icon I_ButtonUp_7x4;
 extern const Icon I_ButtonUp_7x4;
+extern const Icon I_DFU_128x50;
+extern const Icon I_ButtonLeftSmall_3x5;
+extern const Icon I_ButtonRightSmall_3x5;
+extern const Icon I_ButtonRight_4x7;
 extern const Icon I_Warning_30x23;
 extern const Icon I_Warning_30x23;
-extern const Icon I_ButtonLeft_4x7;
-extern const Icon I_DolphinFirstStart7_61x51;
-extern const Icon I_DolphinOkay_41x43;
+extern const Icon I_DolphinFirstStart2_59x51;
 extern const Icon I_DolphinFirstStart5_54x49;
 extern const Icon I_DolphinFirstStart5_54x49;
+extern const Icon I_DolphinFirstStart6_58x54;
 extern const Icon I_Flipper_young_80x60;
 extern const Icon I_Flipper_young_80x60;
-extern const Icon I_DolphinFirstStart2_59x51;
 extern const Icon I_DolphinFirstStart8_56x51;
 extern const Icon I_DolphinFirstStart8_56x51;
+extern const Icon I_DolphinFirstStart1_59x53;
+extern const Icon I_DolphinOkay_41x43;
 extern const Icon I_DolphinFirstStart3_57x48;
 extern const Icon I_DolphinFirstStart3_57x48;
+extern const Icon I_DolphinFirstStart7_61x51;
 extern const Icon I_DolphinFirstStart0_70x53;
 extern const Icon I_DolphinFirstStart0_70x53;
 extern const Icon I_DolphinFirstStart4_67x53;
 extern const Icon I_DolphinFirstStart4_67x53;
-extern const Icon I_DolphinFirstStart6_58x54;
-extern const Icon I_DolphinFirstStart1_59x53;
-extern const Icon I_ArrowDownFilled_14x15;
 extern const Icon I_ArrowUpEmpty_14x15;
 extern const Icon I_ArrowUpEmpty_14x15;
 extern const Icon I_ArrowUpFilled_14x15;
 extern const Icon I_ArrowUpFilled_14x15;
+extern const Icon I_ArrowDownFilled_14x15;
 extern const Icon I_ArrowDownEmpty_14x15;
 extern const Icon I_ArrowDownEmpty_14x15;
-extern const Icon I_DoorLocked_10x56;
 extern const Icon I_PassportBottom_128x17;
 extern const Icon I_PassportBottom_128x17;
-extern const Icon I_PassportLeft_6x47;
 extern const Icon I_DoorLeft_70x55;
 extern const Icon I_DoorLeft_70x55;
-extern const Icon I_LockPopup_100x49;
 extern const Icon I_DoorRight_70x55;
 extern const Icon I_DoorRight_70x55;
-extern const Icon I_IrdaArrowDown_4x8;
-extern const Icon I_Power_25x27;
-extern const Icon I_Mute_25x27;
+extern const Icon I_DoorLocked_10x56;
+extern const Icon I_PassportLeft_6x47;
+extern const Icon I_LockPopup_100x49;
 extern const Icon I_Down_hvr_25x27;
 extern const Icon I_Down_hvr_25x27;
-extern const Icon I_Vol_up_25x27;
-extern const Icon I_IrdaLearnShort_128x31;
-extern const Icon I_Up_25x27;
 extern const Icon I_Vol_down_hvr_25x27;
 extern const Icon I_Vol_down_hvr_25x27;
-extern const Icon I_Vol_down_25x27;
-extern const Icon I_Vol_up_hvr_25x27;
+extern const Icon I_Down_25x27;
 extern const Icon I_Fill_marker_7x7;
 extern const Icon I_Fill_marker_7x7;
+extern const Icon I_Vol_down_25x27;
+extern const Icon I_Vol_up_25x27;
 extern const Icon I_Up_hvr_25x27;
 extern const Icon I_Up_hvr_25x27;
-extern const Icon I_IrdaArrowUp_4x8;
-extern const Icon I_Down_25x27;
+extern const Icon I_Vol_up_hvr_25x27;
+extern const Icon I_IrdaLearnShort_128x31;
+extern const Icon I_IrdaSend_128x64;
 extern const Icon I_DolphinReadingSuccess_59x63;
 extern const Icon I_DolphinReadingSuccess_59x63;
+extern const Icon I_Mute_hvr_25x27;
+extern const Icon I_Back_15x10;
+extern const Icon I_Up_25x27;
+extern const Icon I_IrdaArrowUp_4x8;
+extern const Icon I_Mute_25x27;
+extern const Icon I_Power_25x27;
 extern const Icon I_IrdaSendShort_128x34;
 extern const Icon I_IrdaSendShort_128x34;
+extern const Icon I_IrdaArrowDown_4x8;
 extern const Icon I_IrdaLearn_128x64;
 extern const Icon I_IrdaLearn_128x64;
-extern const Icon I_Mute_hvr_25x27;
-extern const Icon I_IrdaSend_128x64;
 extern const Icon I_Power_hvr_25x27;
 extern const Icon I_Power_hvr_25x27;
-extern const Icon I_Back_15x10;
 extern const Icon I_KeySaveSelected_24x11;
 extern const Icon I_KeySaveSelected_24x11;
-extern const Icon I_KeySave_24x11;
-extern const Icon I_KeyBackspaceSelected_16x9;
 extern const Icon I_KeyBackspace_16x9;
 extern const Icon I_KeyBackspace_16x9;
+extern const Icon I_KeyBackspaceSelected_16x9;
+extern const Icon I_KeySave_24x11;
 extern const Icon A_125khz_14;
 extern const Icon A_125khz_14;
 extern const Icon A_Bluetooth_14;
 extern const Icon A_Bluetooth_14;
 extern const Icon A_Debug_14;
 extern const Icon A_Debug_14;
@@ -85,46 +138,46 @@ extern const Icon A_Sub1ghz_14;
 extern const Icon A_Tamagotchi_14;
 extern const Icon A_Tamagotchi_14;
 extern const Icon A_U2F_14;
 extern const Icon A_U2F_14;
 extern const Icon A_iButton_14;
 extern const Icon A_iButton_14;
-extern const Icon I_Medium_chip_22x21;
 extern const Icon I_Detailed_chip_17x13;
 extern const Icon I_Detailed_chip_17x13;
-extern const Icon I_Health_16x16;
-extern const Icon I_Voltage_16x16;
+extern const Icon I_Medium_chip_22x21;
 extern const Icon I_BatteryBody_52x28;
 extern const Icon I_BatteryBody_52x28;
-extern const Icon I_FaceNormal_29x14;
 extern const Icon I_FaceCharging_29x14;
 extern const Icon I_FaceCharging_29x14;
+extern const Icon I_Health_16x16;
+extern const Icon I_Temperature_16x16;
 extern const Icon I_Battery_16x16;
 extern const Icon I_Battery_16x16;
 extern const Icon I_FaceConfused_29x14;
 extern const Icon I_FaceConfused_29x14;
-extern const Icon I_Temperature_16x16;
+extern const Icon I_FaceNormal_29x14;
+extern const Icon I_Voltage_16x16;
 extern const Icon I_FaceNopower_29x14;
 extern const Icon I_FaceNopower_29x14;
+extern const Icon I_RFIDDolphinSend_97x61;
 extern const Icon I_RFIDDolphinSuccess_108x57;
 extern const Icon I_RFIDDolphinSuccess_108x57;
-extern const Icon I_RFIDBigChip_37x36;
 extern const Icon I_RFIDDolphinReceive_97x61;
 extern const Icon I_RFIDDolphinReceive_97x61;
-extern const Icon I_RFIDDolphinSend_97x61;
-extern const Icon I_SDError_43x35;
+extern const Icon I_RFIDBigChip_37x36;
 extern const Icon I_SDQuestion_35x43;
 extern const Icon I_SDQuestion_35x43;
+extern const Icon I_SDError_43x35;
 extern const Icon I_Cry_dolph_55x52;
 extern const Icon I_Cry_dolph_55x52;
+extern const Icon I_Background_128x11;
+extern const Icon I_Lock_8x8;
+extern const Icon I_Battery_26x8;
 extern const Icon I_Battery_19x8;
 extern const Icon I_Battery_19x8;
+extern const Icon I_USBConnected_15x8;
+extern const Icon I_Background_128x8;
+extern const Icon I_BadUsb_9x8;
+extern const Icon I_BT_Pair_9x8;
+extern const Icon I_PlaceholderL_11x13;
 extern const Icon I_SDcardFail_11x8;
 extern const Icon I_SDcardFail_11x8;
 extern const Icon I_Bluetooth_5x8;
 extern const Icon I_Bluetooth_5x8;
 extern const Icon I_PlaceholderR_30x13;
 extern const Icon I_PlaceholderR_30x13;
-extern const Icon I_Battery_26x8;
-extern const Icon I_Lock_8x8;
 extern const Icon I_SDcardMounted_11x8;
 extern const Icon I_SDcardMounted_11x8;
-extern const Icon I_BadUsb_9x8;
-extern const Icon I_BT_Pair_9x8;
-extern const Icon I_PlaceholderL_11x13;
-extern const Icon I_Background_128x11;
-extern const Icon I_Background_128x8;
-extern const Icon I_USBConnected_15x8;
 extern const Icon I_Quest_7x8;
 extern const Icon I_Quest_7x8;
-extern const Icon I_MHz_25x11;
+extern const Icon I_Lock_7x8;
 extern const Icon I_Scanning_123x52;
 extern const Icon I_Scanning_123x52;
+extern const Icon I_MHz_25x11;
 extern const Icon I_Unlock_7x8;
 extern const Icon I_Unlock_7x8;
-extern const Icon I_Lock_7x8;
-extern const Icon I_DolphinNice_96x59;
-extern const Icon I_iButtonDolphinSuccess_109x60;
-extern const Icon I_DolphinExcited_64x63;
 extern const Icon I_iButtonKey_49x44;
 extern const Icon I_iButtonKey_49x44;
-extern const Icon I_iButtonDolphinVerySuccess_108x52;
+extern const Icon I_DolphinExcited_64x63;
 extern const Icon I_DolphinWait_61x59;
 extern const Icon I_DolphinWait_61x59;
+extern const Icon I_iButtonDolphinVerySuccess_108x52;
 extern const Icon I_DolphinMafia_115x62;
 extern const Icon I_DolphinMafia_115x62;
+extern const Icon I_DolphinNice_96x59;
+extern const Icon I_iButtonDolphinSuccess_109x60;

+ 0 - 14
assets/compiled/input.pb.c

@@ -1,14 +0,0 @@
-/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.5 */
-
-#include "input.pb.h"
-#if PB_PROTO_HEADER_VERSION != 40
-#error Regenerate this file with the current version of nanopb generator.
-#endif
-
-PB_BIND(PB_Input_SendEventRequest, PB_Input_SendEventRequest, AUTO)
-
-
-
-
-

+ 0 - 78
assets/compiled/input.pb.h

@@ -1,78 +0,0 @@
-/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.5 */
-
-#ifndef PB_PB_INPUT_INPUT_PB_H_INCLUDED
-#define PB_PB_INPUT_INPUT_PB_H_INCLUDED
-#include <pb.h>
-
-#if PB_PROTO_HEADER_VERSION != 40
-#error Regenerate this file with the current version of nanopb generator.
-#endif
-
-/* Enum definitions */
-typedef enum _PB_Input_Key { 
-    PB_Input_Key_UP = 0, 
-    PB_Input_Key_DOWN = 1, 
-    PB_Input_Key_RIGHT = 2, 
-    PB_Input_Key_LEFT = 3, 
-    PB_Input_Key_OK = 4, 
-    PB_Input_Key_BACK = 5 
-} PB_Input_Key;
-
-typedef enum _PB_Input_Type { 
-    PB_Input_Type_PRESS = 0, /* *< Press event, emitted after debounce */
-    PB_Input_Type_RELEASE = 1, /* *< Release event, emitted after debounce */
-    PB_Input_Type_SHORT = 2, /* *< Short event, emitted after InputTypeRelease done withing INPUT_LONG_PRESS interval */
-    PB_Input_Type_LONG = 3, /* *< Long event, emmited after INPUT_LONG_PRESS interval, asynchronouse to InputTypeRelease */
-    PB_Input_Type_REPEAT = 4 /* *< Repeat event, emmited with INPUT_REPEATE_PRESS period after InputTypeLong event */
-} PB_Input_Type;
-
-/* Struct definitions */
-typedef struct _PB_Input_SendEventRequest { 
-    PB_Input_Key key; 
-    PB_Input_Type type; 
-} PB_Input_SendEventRequest;
-
-
-/* Helper constants for enums */
-#define _PB_Input_Key_MIN PB_Input_Key_UP
-#define _PB_Input_Key_MAX PB_Input_Key_BACK
-#define _PB_Input_Key_ARRAYSIZE ((PB_Input_Key)(PB_Input_Key_BACK+1))
-
-#define _PB_Input_Type_MIN PB_Input_Type_PRESS
-#define _PB_Input_Type_MAX PB_Input_Type_REPEAT
-#define _PB_Input_Type_ARRAYSIZE ((PB_Input_Type)(PB_Input_Type_REPEAT+1))
-
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* Initializer values for message structs */
-#define PB_Input_SendEventRequest_init_default   {_PB_Input_Key_MIN, _PB_Input_Type_MIN}
-#define PB_Input_SendEventRequest_init_zero      {_PB_Input_Key_MIN, _PB_Input_Type_MIN}
-
-/* Field tags (for use in manual encoding/decoding) */
-#define PB_Input_SendEventRequest_key_tag        1
-#define PB_Input_SendEventRequest_type_tag       2
-
-/* Struct field encoding specification for nanopb */
-#define PB_Input_SendEventRequest_FIELDLIST(X, a) \
-X(a, STATIC,   SINGULAR, UENUM,    key,               1) \
-X(a, STATIC,   SINGULAR, UENUM,    type,              2)
-#define PB_Input_SendEventRequest_CALLBACK NULL
-#define PB_Input_SendEventRequest_DEFAULT NULL
-
-extern const pb_msgdesc_t PB_Input_SendEventRequest_msg;
-
-/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
-#define PB_Input_SendEventRequest_fields &PB_Input_SendEventRequest_msg
-
-/* Maximum encoded size of messages (where known) */
-#define PB_Input_SendEventRequest_size           4
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#endif

+ 0 - 18
assets/compiled/screen.pb.c

@@ -1,18 +0,0 @@
-/* Automatically generated nanopb constant definitions */
-/* Generated by nanopb-0.4.5 */
-
-#include "screen.pb.h"
-#if PB_PROTO_HEADER_VERSION != 40
-#error Regenerate this file with the current version of nanopb generator.
-#endif
-
-PB_BIND(PB_Screen_StartStreamRequest, PB_Screen_StartStreamRequest, AUTO)
-
-
-PB_BIND(PB_Screen_StopStreamRequest, PB_Screen_StopStreamRequest, AUTO)
-
-
-PB_BIND(PB_Screen_StreamFrame, PB_Screen_StreamFrame, AUTO)
-
-
-

+ 0 - 75
assets/compiled/screen.pb.h

@@ -1,75 +0,0 @@
-/* Automatically generated nanopb header */
-/* Generated by nanopb-0.4.5 */
-
-#ifndef PB_PB_SCREEN_SCREEN_PB_H_INCLUDED
-#define PB_PB_SCREEN_SCREEN_PB_H_INCLUDED
-#include <pb.h>
-
-#if PB_PROTO_HEADER_VERSION != 40
-#error Regenerate this file with the current version of nanopb generator.
-#endif
-
-/* Struct definitions */
-typedef struct _PB_Screen_StartStreamRequest { 
-    char dummy_field;
-} PB_Screen_StartStreamRequest;
-
-typedef struct _PB_Screen_StopStreamRequest { 
-    char dummy_field;
-} PB_Screen_StopStreamRequest;
-
-typedef struct _PB_Screen_StreamFrame { 
-    pb_bytes_array_t *data; 
-} PB_Screen_StreamFrame;
-
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* Initializer values for message structs */
-#define PB_Screen_StartStreamRequest_init_default {0}
-#define PB_Screen_StopStreamRequest_init_default {0}
-#define PB_Screen_StreamFrame_init_default       {NULL}
-#define PB_Screen_StartStreamRequest_init_zero   {0}
-#define PB_Screen_StopStreamRequest_init_zero    {0}
-#define PB_Screen_StreamFrame_init_zero          {NULL}
-
-/* Field tags (for use in manual encoding/decoding) */
-#define PB_Screen_StreamFrame_data_tag           1
-
-/* Struct field encoding specification for nanopb */
-#define PB_Screen_StartStreamRequest_FIELDLIST(X, a) \
-
-#define PB_Screen_StartStreamRequest_CALLBACK NULL
-#define PB_Screen_StartStreamRequest_DEFAULT NULL
-
-#define PB_Screen_StopStreamRequest_FIELDLIST(X, a) \
-
-#define PB_Screen_StopStreamRequest_CALLBACK NULL
-#define PB_Screen_StopStreamRequest_DEFAULT NULL
-
-#define PB_Screen_StreamFrame_FIELDLIST(X, a) \
-X(a, POINTER,  SINGULAR, BYTES,    data,              1)
-#define PB_Screen_StreamFrame_CALLBACK NULL
-#define PB_Screen_StreamFrame_DEFAULT NULL
-
-extern const pb_msgdesc_t PB_Screen_StartStreamRequest_msg;
-extern const pb_msgdesc_t PB_Screen_StopStreamRequest_msg;
-extern const pb_msgdesc_t PB_Screen_StreamFrame_msg;
-
-/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
-#define PB_Screen_StartStreamRequest_fields &PB_Screen_StartStreamRequest_msg
-#define PB_Screen_StopStreamRequest_fields &PB_Screen_StopStreamRequest_msg
-#define PB_Screen_StreamFrame_fields &PB_Screen_StreamFrame_msg
-
-/* Maximum encoded size of messages (where known) */
-/* PB_Screen_StreamFrame_size depends on runtime parameters */
-#define PB_Screen_StartStreamRequest_size        0
-#define PB_Screen_StopStreamRequest_size         0
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#endif

BIN
assets/icons/Animations/BadBattery_128x51/frame_01.png


BIN
assets/icons/Animations/BadBattery_128x51/frame_02.png


+ 1 - 0
assets/icons/Animations/BadBattery_128x51/frame_rate

@@ -0,0 +1 @@
+2

BIN
assets/icons/Animations/BoxActive_128x51/frame_01.png


BIN
assets/icons/Animations/BoxActive_128x51/frame_02.png


+ 1 - 0
assets/icons/Animations/BoxActive_128x51/frame_rate

@@ -0,0 +1 @@
+2

BIN
assets/icons/Animations/Box_128x51/frame_01.png


BIN
assets/icons/Animations/Box_128x51/frame_02.png


BIN
assets/icons/Animations/Box_128x51/frame_03.png


BIN
assets/icons/Animations/Box_128x51/frame_04.png


+ 1 - 0
assets/icons/Animations/Box_128x51/frame_rate

@@ -0,0 +1 @@
+2

BIN
assets/icons/Animations/CardBad_128x51/frame_01.png


BIN
assets/icons/Animations/CardBad_128x51/frame_02.png


+ 1 - 0
assets/icons/Animations/CardBad_128x51/frame_rate

@@ -0,0 +1 @@
+2

+ 1 - 0
assets/icons/Animations/CardNoDBUrl_128x51/frame_rate

@@ -0,0 +1 @@
+2

BIN
assets/icons/Animations/CardNoDBUrl_128x51/url1.png


BIN
assets/icons/Animations/CardNoDBUrl_128x51/url2.png


BIN
assets/icons/Animations/CardNoDBUrl_128x51/url3.png


BIN
assets/icons/Animations/CardNoDBUrl_128x51/url4.png


BIN
assets/icons/Animations/CardNoDB_128x51/frame_01.png


BIN
assets/icons/Animations/CardNoDB_128x51/frame_02.png


BIN
assets/icons/Animations/CardNoDB_128x51/frame_03.png


BIN
assets/icons/Animations/CardNoDB_128x51/frame_04.png


+ 1 - 0
assets/icons/Animations/CardNoDB_128x51/frame_rate

@@ -0,0 +1 @@
+2

BIN
assets/icons/Animations/CardOk_128x51/frame_01.png


BIN
assets/icons/Animations/CardOk_128x51/frame_02.png


BIN
assets/icons/Animations/CardOk_128x51/frame_03.png


BIN
assets/icons/Animations/CardOk_128x51/frame_04.png


+ 1 - 0
assets/icons/Animations/CardOk_128x51/frame_rate

@@ -0,0 +1 @@
+2

BIN
assets/icons/Animations/CryActive_128x51/frame_01.png


BIN
assets/icons/Animations/CryActive_128x51/frame_02.png


BIN
assets/icons/Animations/CryActive_128x51/frame_03.png


+ 1 - 0
assets/icons/Animations/CryActive_128x51/frame_rate

@@ -0,0 +1 @@
+2

BIN
assets/icons/Animations/Cry_128x51/frame_01.png


BIN
assets/icons/Animations/Cry_128x51/frame_02.png


BIN
assets/icons/Animations/Cry_128x51/frame_03.png


BIN
assets/icons/Animations/Cry_128x51/frame_04.png


+ 1 - 0
assets/icons/Animations/Cry_128x51/frame_rate

@@ -0,0 +1 @@
+2

BIN
assets/icons/Animations/KnifeActive_128x51/frame_01.png


BIN
assets/icons/Animations/KnifeActive_128x51/frame_02.png


+ 1 - 0
assets/icons/Animations/KnifeActive_128x51/frame_rate

@@ -0,0 +1 @@
+2

BIN
assets/icons/Animations/Knife_128x51/frame_01.png


BIN
assets/icons/Animations/Knife_128x51/frame_02.png


+ 1 - 0
assets/icons/Animations/Knife_128x51/frame_rate

@@ -0,0 +1 @@
+2

BIN
assets/icons/Animations/LaptopActive_128x51/frame_01.png


BIN
assets/icons/Animations/LaptopActive_128x51/frame_02.png


+ 1 - 0
assets/icons/Animations/LaptopActive_128x51/frame_rate

@@ -0,0 +1 @@
+2

BIN
assets/icons/Animations/Laptop_128x51/frame_01.png


BIN
assets/icons/Animations/Laptop_128x51/frame_02.png


+ 1 - 0
assets/icons/Animations/Laptop_128x51/frame_rate

@@ -0,0 +1 @@
+2

BIN
assets/icons/Animations/LeavingActive_128x51/frame_01.png


BIN
assets/icons/Animations/LeavingActive_128x51/frame_02.png


+ 1 - 0
assets/icons/Animations/LeavingActive_128x51/frame_rate

@@ -0,0 +1 @@
+2

BIN
assets/icons/Animations/Leaving_128x51/frame_01.png


BIN
assets/icons/Animations/Leaving_128x51/frame_02.png


+ 1 - 0
assets/icons/Animations/Leaving_128x51/frame_rate

@@ -0,0 +1 @@
+2

BIN
assets/icons/Animations/Level1FurippaActive_128x51/frame_01.png


BIN
assets/icons/Animations/Level1FurippaActive_128x51/frame_02.png


BIN
assets/icons/Animations/Level1FurippaActive_128x51/frame_03.png


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác