Explorar o código

upd hex viewer

MX %!s(int64=2) %!d(string=hai) anos
pai
achega
8d65f6503b
Modificáronse 29 ficheiros con 2061 adicións e 266 borrados
  1. 52 0
      base_pack/hex_viewer/.gitignore
  2. 2 2
      base_pack/hex_viewer/application.fam
  3. 62 0
      base_pack/hex_viewer/helpers/hex_viewer_custom_event.h
  4. 36 0
      base_pack/hex_viewer/helpers/hex_viewer_haptic.c
  5. 8 0
      base_pack/hex_viewer/helpers/hex_viewer_haptic.h
  6. 39 0
      base_pack/hex_viewer/helpers/hex_viewer_led.c
  7. 6 0
      base_pack/hex_viewer/helpers/hex_viewer_led.h
  8. 27 0
      base_pack/hex_viewer/helpers/hex_viewer_speaker.c
  9. 4 0
      base_pack/hex_viewer/helpers/hex_viewer_speaker.h
  10. 171 0
      base_pack/hex_viewer/helpers/hex_viewer_storage.c
  11. 22 0
      base_pack/hex_viewer/helpers/hex_viewer_storage.h
  12. 120 264
      base_pack/hex_viewer/hex_viewer.c
  13. 108 0
      base_pack/hex_viewer/hex_viewer.h
  14. 30 0
      base_pack/hex_viewer/scenes/hex_viewer_scene.c
  15. 29 0
      base_pack/hex_viewer/scenes/hex_viewer_scene.h
  16. 7 0
      base_pack/hex_viewer/scenes/hex_viewer_scene_config.h
  17. 91 0
      base_pack/hex_viewer/scenes/hex_viewer_scene_menu.c
  18. 94 0
      base_pack/hex_viewer/scenes/hex_viewer_scene_scene_1.c
  19. 54 0
      base_pack/hex_viewer/scenes/hex_viewer_scene_scene_2.c
  20. 141 0
      base_pack/hex_viewer/scenes/hex_viewer_scene_scene_3.c
  21. 46 0
      base_pack/hex_viewer/scenes/hex_viewer_scene_scene_4.c
  22. 147 0
      base_pack/hex_viewer/scenes/hex_viewer_scene_settings.c
  23. 66 0
      base_pack/hex_viewer/scenes/hex_viewer_scene_startscreen.c
  24. 136 0
      base_pack/hex_viewer/views/hex_viewer_scene_1.c
  25. 19 0
      base_pack/hex_viewer/views/hex_viewer_scene_1.h
  26. 254 0
      base_pack/hex_viewer/views/hex_viewer_scene_2.c
  27. 19 0
      base_pack/hex_viewer/views/hex_viewer_scene_2.h
  28. 252 0
      base_pack/hex_viewer/views/hex_viewer_startscreen.c
  29. 19 0
      base_pack/hex_viewer/views/hex_viewer_startscreen.h

+ 52 - 0
base_pack/hex_viewer/.gitignore

@@ -0,0 +1,52 @@
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf

+ 2 - 2
base_pack/hex_viewer/application.fam

@@ -10,9 +10,9 @@ App(
     stack_size=2 * 1024,
     order=20,
     fap_icon="icons/hex_10px.png",
-    fap_category="Tools",
     fap_icon_assets="icons",
+    fap_category="Tools",
     fap_author="@QtRoS",
-    fap_version="1.1",
+    fap_version="2.0",
     fap_description="App allows to view various files as HEX.",
 )

+ 62 - 0
base_pack/hex_viewer/helpers/hex_viewer_custom_event.h

@@ -0,0 +1,62 @@
+#pragma once
+
+typedef enum {
+    HexViewerCustomEventStartscreenUp,
+    HexViewerCustomEventStartscreenDown,
+    HexViewerCustomEventStartscreenLeft,
+    HexViewerCustomEventStartscreenRight,
+    HexViewerCustomEventStartscreenOk,
+    HexViewerCustomEventStartscreenBack,
+    HexViewerCustomEventScene1Up,
+    HexViewerCustomEventScene1Down,
+    HexViewerCustomEventScene1Left,
+    HexViewerCustomEventScene1Right,
+    HexViewerCustomEventScene1Ok,
+    HexViewerCustomEventScene1Back,
+    HexViewerCustomEventScene2Up,
+    HexViewerCustomEventScene2Down,
+    HexViewerCustomEventScene2Left,
+    HexViewerCustomEventScene2Right,
+    HexViewerCustomEventScene2Ok,
+    HexViewerCustomEventScene2Back,
+} HexViewerCustomEvent;
+
+enum HexViewerCustomEventType {
+    // Reserve first 100 events for button types and indexes, starting from 0
+    HexViewerCustomEventMenuVoid,
+    HexViewerCustomEventMenuSelected,
+    HexViewerCustomEventMenuPercentEntered,
+};
+
+#pragma pack(push, 1)
+typedef union {
+    uint32_t packed_value;
+    struct {
+        uint16_t type;
+        int16_t value;
+    } content;
+} HexViewerCustomEventMenu;
+#pragma pack(pop)
+
+static inline uint32_t hex_viewer_custom_menu_event_pack(uint16_t type, int16_t value) {
+    HexViewerCustomEventMenu event = {.content = {.type = type, .value = value}};
+    return event.packed_value;
+}
+static inline void
+    hex_viewer_custom_menu_event_unpack(uint32_t packed_value, uint16_t* type, int16_t* value) {
+    HexViewerCustomEventMenu event = {.packed_value = packed_value};
+    if(type) *type = event.content.type;
+    if(value) *value = event.content.value;
+}
+
+static inline uint16_t hex_viewer_custom_menu_event_get_type(uint32_t packed_value) {
+    uint16_t type;
+    hex_viewer_custom_menu_event_unpack(packed_value, &type, NULL);
+    return type;
+}
+
+static inline int16_t hex_viewer_custom_menu_event_get_value(uint32_t packed_value) {
+    int16_t value;
+    hex_viewer_custom_menu_event_unpack(packed_value, NULL, &value);
+    return value;
+}

+ 36 - 0
base_pack/hex_viewer/helpers/hex_viewer_haptic.c

@@ -0,0 +1,36 @@
+#include "hex_viewer_haptic.h"
+#include "../hex_viewer.h"
+
+
+void hex_viewer_play_happy_bump(void* context) {
+    HexViewer* app = context;
+    if (app->haptic != 1) {
+        return;
+    }
+    notification_message(app->notification, &sequence_set_vibro_on);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 20);
+    notification_message(app->notification, &sequence_reset_vibro);
+}
+
+void hex_viewer_play_bad_bump(void* context) {
+    HexViewer* app = context;
+    if (app->haptic != 1) {
+        return;
+    }
+    notification_message(app->notification, &sequence_set_vibro_on);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+    notification_message(app->notification, &sequence_reset_vibro);
+}
+
+void hex_viewer_play_long_bump(void* context) {
+    HexViewer* app = context;
+    if (app->haptic != 1) {
+        return;
+    }
+    for (int i = 0; i < 4; i++) {
+        notification_message(app->notification, &sequence_set_vibro_on);
+        furi_thread_flags_wait(0, FuriFlagWaitAny, 50);
+        notification_message(app->notification, &sequence_reset_vibro);
+        furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+    }
+}

+ 8 - 0
base_pack/hex_viewer/helpers/hex_viewer_haptic.h

@@ -0,0 +1,8 @@
+#include <notification/notification_messages.h>
+
+void hex_viewer_play_happy_bump(void* context);
+
+void hex_viewer_play_bad_bump(void* context);
+
+void hex_viewer_play_long_bump(void* context);
+

+ 39 - 0
base_pack/hex_viewer/helpers/hex_viewer_led.c

@@ -0,0 +1,39 @@
+#include "hex_viewer_led.h"
+#include "../hex_viewer.h"
+
+
+
+void hex_viewer_led_set_rgb(void* context, int red, int green, int blue) {
+    HexViewer* app = context;
+    if (app->led != 1) {
+        return;
+    }
+    NotificationMessage notification_led_message_1;
+    notification_led_message_1.type = NotificationMessageTypeLedRed;
+    NotificationMessage notification_led_message_2;
+    notification_led_message_2.type = NotificationMessageTypeLedGreen;
+    NotificationMessage notification_led_message_3;
+    notification_led_message_3.type = NotificationMessageTypeLedBlue;
+
+    notification_led_message_1.data.led.value = red;
+    notification_led_message_2.data.led.value = green;
+    notification_led_message_3.data.led.value = blue;
+    const NotificationSequence notification_sequence = {
+        &notification_led_message_1,
+        &notification_led_message_2,
+        &notification_led_message_3,
+        &message_do_not_reset,
+        NULL,
+    };
+    notification_message(app->notification, &notification_sequence);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 10); //Delay, prevent removal from RAM before LED value set    
+}
+
+void hex_viewer_led_reset(void* context) {
+    HexViewer* app = context;
+    notification_message(app->notification, &sequence_reset_red);
+    notification_message(app->notification, &sequence_reset_green);
+    notification_message(app->notification, &sequence_reset_blue);
+    
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 300); //Delay, prevent removal from RAM before LED value set    
+}

+ 6 - 0
base_pack/hex_viewer/helpers/hex_viewer_led.h

@@ -0,0 +1,6 @@
+
+
+void hex_viewer_led_set_rgb(void* context, int red, int green, int blue);
+
+void hex_viewer_led_reset(void* context);
+

+ 27 - 0
base_pack/hex_viewer/helpers/hex_viewer_speaker.c

@@ -0,0 +1,27 @@
+#include "hex_viewer_speaker.h"
+#include "../hex_viewer.h"
+
+#define NOTE_INPUT 587.33f
+
+void hex_viewer_play_input_sound(void* context) {
+    HexViewer* app = context;
+    if (app->speaker != 1) {
+        return;
+    }
+    float volume = 1.0f;
+    if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
+        furi_hal_speaker_start(NOTE_INPUT, volume);
+    }
+    
+}
+
+void hex_viewer_stop_all_sound(void* context) {
+    HexViewer* app = context;
+    if (app->speaker != 1) {
+        return;
+    }
+    if(furi_hal_speaker_is_mine()) {
+        furi_hal_speaker_stop();
+        furi_hal_speaker_release();
+    }
+}

+ 4 - 0
base_pack/hex_viewer/helpers/hex_viewer_speaker.h

@@ -0,0 +1,4 @@
+#define NOTE_INPUT 587.33f
+
+void hex_viewer_play_input_sound(void* context);
+void hex_viewer_stop_all_sound(void* context);

+ 171 - 0
base_pack/hex_viewer/helpers/hex_viewer_storage.c

@@ -0,0 +1,171 @@
+#include "hex_viewer_storage.h"
+
+static Storage* hex_viewer_open_storage() {
+    return furi_record_open(RECORD_STORAGE);
+}
+
+static void hex_viewer_close_storage() {
+    furi_record_close(RECORD_STORAGE);
+}
+
+static void hex_viewer_close_config_file(FlipperFormat* file) {
+    if(file == NULL) return;
+    flipper_format_file_close(file);
+    flipper_format_free(file);
+}
+
+void hex_viewer_save_settings(void* context) {
+    HexViewer* app = context;
+    if(app->save_settings == 0) {
+        return;
+    }
+
+    FURI_LOG_D(TAG, "Saving Settings");
+    Storage* storage = hex_viewer_open_storage();
+    FlipperFormat* fff_file = flipper_format_file_alloc(storage);
+
+    // Overwrite wont work, so delete first
+    if(storage_file_exists(storage, HEX_VIEWER_SETTINGS_SAVE_PATH)) {
+        storage_simply_remove(storage, HEX_VIEWER_SETTINGS_SAVE_PATH);
+    }
+
+    // Open File, create if not exists
+    if(!storage_common_stat(storage, HEX_VIEWER_SETTINGS_SAVE_PATH, NULL) == FSE_OK) {
+        FURI_LOG_D(
+            TAG, "Config file %s is not found. Will create new.", HEX_VIEWER_SETTINGS_SAVE_PATH);
+        if(storage_common_stat(storage, CONFIG_FILE_DIRECTORY_PATH, NULL) == FSE_NOT_EXIST) {
+            FURI_LOG_D(
+                TAG, "Directory %s doesn't exist. Will create new.", CONFIG_FILE_DIRECTORY_PATH);
+            if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) {
+                FURI_LOG_E(TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH);
+            }
+        }
+    }
+
+    if(!flipper_format_file_open_new(fff_file, HEX_VIEWER_SETTINGS_SAVE_PATH)) {
+        //totp_close_config_file(fff_file);
+        FURI_LOG_E(TAG, "Error creating new file %s", HEX_VIEWER_SETTINGS_SAVE_PATH);
+        hex_viewer_close_storage();
+        return;
+    }
+
+    // Store Settings
+    flipper_format_write_header_cstr(
+        fff_file, HEX_VIEWER_SETTINGS_HEADER, HEX_VIEWER_SETTINGS_FILE_VERSION);
+    flipper_format_write_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_HAPTIC, &app->haptic, 1);
+    flipper_format_write_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_SPEAKER, &app->speaker, 1);
+    flipper_format_write_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_LED, &app->led, 1);
+    flipper_format_write_uint32(
+        fff_file, HEX_VIEWER_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1);
+
+    if(!flipper_format_rewind(fff_file)) {
+        hex_viewer_close_config_file(fff_file);
+        FURI_LOG_E(TAG, "Rewind error");
+        hex_viewer_close_storage();
+        return;
+    }
+
+    hex_viewer_close_config_file(fff_file);
+    hex_viewer_close_storage();
+}
+
+void hex_viewer_read_settings(void* context) {
+    HexViewer* app = context;
+    Storage* storage = hex_viewer_open_storage();
+    FlipperFormat* fff_file = flipper_format_file_alloc(storage);
+
+    if(storage_common_stat(storage, HEX_VIEWER_SETTINGS_SAVE_PATH, NULL) != FSE_OK) {
+        hex_viewer_close_config_file(fff_file);
+        hex_viewer_close_storage();
+        return;
+    }
+    uint32_t file_version;
+    FuriString* temp_str = furi_string_alloc();
+
+    if(!flipper_format_file_open_existing(fff_file, HEX_VIEWER_SETTINGS_SAVE_PATH)) {
+        FURI_LOG_E(TAG, "Cannot open file %s", HEX_VIEWER_SETTINGS_SAVE_PATH);
+        hex_viewer_close_config_file(fff_file);
+        hex_viewer_close_storage();
+        return;
+    }
+
+    if(!flipper_format_read_header(fff_file, temp_str, &file_version)) {
+        FURI_LOG_E(TAG, "Missing Header Data");
+        hex_viewer_close_config_file(fff_file);
+        hex_viewer_close_storage();
+        return;
+    }
+
+    if(file_version < HEX_VIEWER_SETTINGS_FILE_VERSION) {
+        FURI_LOG_I(TAG, "old config version, will be removed.");
+        hex_viewer_close_config_file(fff_file);
+        hex_viewer_close_storage();
+        return;
+    }
+
+    flipper_format_read_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_HAPTIC, &app->haptic, 1);
+    flipper_format_read_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_SPEAKER, &app->speaker, 1);
+    flipper_format_read_uint32(fff_file, HEX_VIEWER_SETTINGS_KEY_LED, &app->led, 1);
+    flipper_format_read_uint32(
+        fff_file, HEX_VIEWER_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1);
+
+    flipper_format_rewind(fff_file);
+
+    hex_viewer_close_config_file(fff_file);
+    hex_viewer_close_storage();
+}
+
+bool hex_viewer_open_file(void* context, const char* file_path) {
+    HexViewer* hex_viewer = context;
+    furi_assert(hex_viewer);
+    furi_assert(file_path);
+
+    // TODO Separate function?
+    if(hex_viewer->model->stream) {
+        buffered_file_stream_close(hex_viewer->model->stream);
+        stream_free(hex_viewer->model->stream); // TODO Check
+        hex_viewer->model->file_offset = 0;
+    }
+
+    hex_viewer->model->stream = buffered_file_stream_alloc(hex_viewer->storage);
+    bool isOk = true;
+
+    do {
+        if(!buffered_file_stream_open(
+               hex_viewer->model->stream, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
+            FURI_LOG_E(TAG, "Unable to open stream: %s", file_path);
+            isOk = false;
+            break;
+        };
+
+        hex_viewer->model->file_size = stream_size(hex_viewer->model->stream);
+    } while(false);
+
+    return isOk;
+}
+
+bool hex_viewer_read_file(void* context) {
+    HexViewer* hex_viewer = context;
+    furi_assert(hex_viewer);
+    furi_assert(hex_viewer->model->stream);
+    furi_assert(hex_viewer->model->file_offset % HEX_VIEWER_BYTES_PER_LINE == 0);
+
+    memset(hex_viewer->model->file_bytes, 0x0, HEX_VIEWER_BUF_SIZE);
+    bool isOk = true;
+
+    do {
+        uint32_t offset = hex_viewer->model->file_offset;
+        if(!stream_seek(hex_viewer->model->stream, offset, true)) {
+            FURI_LOG_E(TAG, "Unable to seek stream");
+            isOk = false;
+            break;
+        }
+
+        hex_viewer->model->file_read_bytes = stream_read(
+            hex_viewer->model->stream,
+            (uint8_t*)hex_viewer->model->file_bytes,
+            HEX_VIEWER_BUF_SIZE);
+    } while(false);
+
+    return isOk;
+}

+ 22 - 0
base_pack/hex_viewer/helpers/hex_viewer_storage.h

@@ -0,0 +1,22 @@
+#include <stdlib.h>
+#include <string.h>
+#include <storage/storage.h>
+#include <flipper_format/flipper_format_i.h>
+#include "../hex_viewer.h"
+
+#define HEX_VIEWER_SETTINGS_FILE_VERSION 1
+#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/hex_viewer")
+#define HEX_VIEWER_SETTINGS_SAVE_PATH CONFIG_FILE_DIRECTORY_PATH "/hex_viewer.conf"
+#define HEX_VIEWER_SETTINGS_SAVE_PATH_TMP HEX_VIEWER_SETTINGS_SAVE_PATH ".tmp"
+#define HEX_VIEWER_SETTINGS_HEADER "HexViewer Config File"
+#define HEX_VIEWER_SETTINGS_KEY_HAPTIC "Haptic"
+#define HEX_VIEWER_SETTINGS_KEY_LED "Led"
+#define HEX_VIEWER_SETTINGS_KEY_SPEAKER "Speaker"
+#define HEX_VIEWER_SETTINGS_KEY_SAVE_SETTINGS "SaveSettings"
+
+void hex_viewer_save_settings(void* context);
+void hex_viewer_read_settings(void* context);
+
+
+bool hex_viewer_open_file(void* context, const char* file_path);
+bool hex_viewer_read_file(void* context);

+ 120 - 264
base_pack/hex_viewer/hex_viewer.c

@@ -1,294 +1,150 @@
-#include <furi.h>
-#include <furi_hal.h>
-
-#include <hex_viewer_icons.h>
-#include <gui/gui.h>
-#include <gui/elements.h>
-#include <dialogs/dialogs.h>
-
-#include <storage/storage.h>
-#include <stream/stream.h>
-#include <stream/buffered_file_stream.h>
-#include <toolbox/stream/file_stream.h>
-
-#define TAG "HexViewer"
-
-#define HEX_VIEWER_APP_PATH_FOLDER "/any"
-#define HEX_VIEWER_APP_EXTENSION "*"
-
-#define HEX_VIEWER_BYTES_PER_LINE 4u
-#define HEX_VIEWER_LINES_ON_SCREEN 4u
-#define HEX_VIEWER_BUF_SIZE (HEX_VIEWER_LINES_ON_SCREEN * HEX_VIEWER_BYTES_PER_LINE)
-
-typedef struct {
-    uint8_t file_bytes[HEX_VIEWER_LINES_ON_SCREEN][HEX_VIEWER_BYTES_PER_LINE];
-    uint32_t file_offset;
-    uint32_t file_read_bytes;
-    uint32_t file_size;
-    Stream* stream;
-    bool mode; // Print address or content
-} HexViewerModel;
-
-typedef struct {
-    HexViewerModel* model;
-    FuriMutex** mutex;
-
-    FuriMessageQueue* input_queue;
-
-    ViewPort* view_port;
-    Gui* gui;
-    Storage* storage;
-} HexViewer;
-
-static void render_callback(Canvas* canvas, void* ctx) {
-    HexViewer* hex_viewer = ctx;
-    furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
-
-    canvas_clear(canvas);
-    canvas_set_color(canvas, ColorBlack);
-
-    elements_button_left(canvas, hex_viewer->model->mode ? "Addr" : "Text");
-    elements_button_right(canvas, "Info");
-
-    int ROW_HEIGHT = 12;
-    int TOP_OFFSET = 10;
-    int LEFT_OFFSET = 3;
-
-    uint32_t line_count = hex_viewer->model->file_size / HEX_VIEWER_BYTES_PER_LINE;
-    if(hex_viewer->model->file_size % HEX_VIEWER_BYTES_PER_LINE != 0) line_count += 1;
-    uint32_t first_line_on_screen = hex_viewer->model->file_offset / HEX_VIEWER_BYTES_PER_LINE;
-    if(line_count > HEX_VIEWER_LINES_ON_SCREEN) {
-        uint8_t width = canvas_width(canvas);
-        elements_scrollbar_pos(
-            canvas,
-            width,
-            0,
-            ROW_HEIGHT * HEX_VIEWER_LINES_ON_SCREEN,
-            first_line_on_screen, // TODO
-            line_count - (HEX_VIEWER_LINES_ON_SCREEN - 1));
-    }
-
-    char temp_buf[32];
-    uint32_t row_iters = hex_viewer->model->file_read_bytes / HEX_VIEWER_BYTES_PER_LINE;
-    if(hex_viewer->model->file_read_bytes % HEX_VIEWER_BYTES_PER_LINE != 0) row_iters += 1;
-
-    for(uint32_t i = 0; i < row_iters; ++i) {
-        uint32_t bytes_left_per_row =
-            hex_viewer->model->file_read_bytes - i * HEX_VIEWER_BYTES_PER_LINE;
-        bytes_left_per_row = MIN(bytes_left_per_row, HEX_VIEWER_BYTES_PER_LINE);
-
-        if(hex_viewer->model->mode) {
-            memcpy(temp_buf, hex_viewer->model->file_bytes[i], bytes_left_per_row);
-            temp_buf[bytes_left_per_row] = '\0';
-            for(uint32_t j = 0; j < bytes_left_per_row; ++j)
-                if(!isprint((int)temp_buf[j])) temp_buf[j] = '.';
-
-            canvas_set_font(canvas, FontKeyboard);
-            canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
-        } else {
-            uint32_t addr = hex_viewer->model->file_offset + i * HEX_VIEWER_BYTES_PER_LINE;
-            snprintf(temp_buf, 32, "%04lX", addr);
-
-            canvas_set_font(canvas, FontKeyboard);
-            canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
-        }
-
-        char* p = temp_buf;
-        for(uint32_t j = 0; j < bytes_left_per_row; ++j)
-            p += snprintf(p, 32, "%02X ", hex_viewer->model->file_bytes[i][j]);
-
-        canvas_set_font(canvas, FontKeyboard);
-        canvas_draw_str(canvas, LEFT_OFFSET + 41, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
-    }
-
-    furi_mutex_release(hex_viewer->mutex);
+#include "hex_viewer.h"
+
+bool hex_viewer_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    HexViewer* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, event);
 }
 
-static void input_callback(InputEvent* input_event, void* ctx) {
-    HexViewer* hex_viewer = ctx;
-    if(input_event->type == InputTypeShort || input_event->type == InputTypeRepeat) {
-        furi_message_queue_put(hex_viewer->input_queue, input_event, 0);
-    }
+void hex_viewer_tick_event_callback(void* context) {
+    furi_assert(context);
+    HexViewer* app = context;
+    scene_manager_handle_tick_event(app->scene_manager);
 }
 
-static HexViewer* hex_viewer_alloc() {
-    HexViewer* instance = malloc(sizeof(HexViewer));
+//leave app if back button pressed
+bool hex_viewer_navigation_event_callback(void* context) {
+    furi_assert(context);
+    HexViewer* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
+}
 
-    instance->model = malloc(sizeof(HexViewerModel));
-    memset(instance->model, 0x0, sizeof(HexViewerModel));
+HexViewer* hex_viewer_app_alloc() {
+    HexViewer* app = malloc(sizeof(HexViewer));
 
-    instance->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+    app->model = malloc(sizeof(HexViewerModel));
+    memset(app->model, 0, sizeof(HexViewerModel));
 
-    instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
+    app->gui = furi_record_open(RECORD_GUI);
+    app->storage = furi_record_open(RECORD_STORAGE);
+    app->notification = furi_record_open(RECORD_NOTIFICATION);
 
-    instance->view_port = view_port_alloc();
-    view_port_draw_callback_set(instance->view_port, render_callback, instance);
-    view_port_input_callback_set(instance->view_port, input_callback, instance);
+    //Turn backlight on, believe me this makes testing your app easier
+    notification_message(app->notification, &sequence_display_backlight_on);
 
-    instance->gui = furi_record_open(RECORD_GUI);
-    gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen);
+    //Scene additions
+    app->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_enable_queue(app->view_dispatcher);
 
-    instance->storage = furi_record_open(RECORD_STORAGE);
+    app->scene_manager = scene_manager_alloc(&hex_viewer_scene_handlers, app);
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+    view_dispatcher_set_navigation_event_callback(
+        app->view_dispatcher, hex_viewer_navigation_event_callback);
+    view_dispatcher_set_tick_event_callback(
+        app->view_dispatcher, hex_viewer_tick_event_callback, 100);
+    view_dispatcher_set_custom_event_callback(
+        app->view_dispatcher, hex_viewer_custom_event_callback);
 
-    return instance;
-}
+    app->submenu = submenu_alloc();
+    app->text_input = text_input_alloc();
 
-static void hex_viewer_free(HexViewer* instance) {
-    furi_record_close(RECORD_STORAGE);
+    // Set defaults, in case no config loaded
+    app->haptic = 1;
+    app->speaker = 1;
+    app->led = 1;
+    app->save_settings = 1;
 
-    gui_remove_view_port(instance->gui, instance->view_port);
-    furi_record_close(RECORD_GUI);
-    view_port_free(instance->view_port);
+    // Used for File Browser
+    app->dialogs = furi_record_open(RECORD_DIALOGS);
+    app->file_path = furi_string_alloc();
 
-    furi_message_queue_free(instance->input_queue);
+    // Load configs
+    hex_viewer_read_settings(app);
 
-    furi_mutex_free(instance->mutex);
+    view_dispatcher_add_view(
+        app->view_dispatcher, HexViewerViewIdMenu, submenu_get_view(app->submenu));
 
-    if(instance->model->stream) buffered_file_stream_close(instance->model->stream);
+    app->hex_viewer_startscreen = hex_viewer_startscreen_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        HexViewerViewIdStartscreen,
+        hex_viewer_startscreen_get_view(app->hex_viewer_startscreen));
 
-    free(instance->model);
-    free(instance);
-}
+    view_dispatcher_add_view(
+        app->view_dispatcher, HexViewerViewIdScene1, text_input_get_view(app->text_input));
 
-static bool hex_viewer_open_file(HexViewer* hex_viewer, const char* file_path) {
-    furi_assert(hex_viewer);
-    furi_assert(file_path);
+    app->hex_viewer_scene_2 = hex_viewer_scene_2_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        HexViewerViewIdScene2,
+        hex_viewer_scene_2_get_view(app->hex_viewer_scene_2));
 
-    hex_viewer->model->stream = buffered_file_stream_alloc(hex_viewer->storage);
-    bool isOk = true;
+    app->button_menu = button_menu_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, HexViewerViewIdScene3, button_menu_get_view(app->button_menu));
 
-    do {
-        if(!buffered_file_stream_open(
-               hex_viewer->model->stream, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
-            FURI_LOG_E(TAG, "Unable to open stream: %s", file_path);
-            isOk = false;
-            break;
-        };
+    app->variable_item_list = variable_item_list_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        HexViewerViewIdSettings,
+        variable_item_list_get_view(app->variable_item_list));
 
-        hex_viewer->model->file_size = stream_size(hex_viewer->model->stream);
-    } while(false);
+    //End Scene Additions
 
-    return isOk;
+    return app;
 }
 
-static bool hex_viewer_read_file(HexViewer* hex_viewer) {
-    furi_assert(hex_viewer);
-    furi_assert(hex_viewer->model->stream);
-    furi_assert(hex_viewer->model->file_offset % HEX_VIEWER_BYTES_PER_LINE == 0);
-
-    memset(hex_viewer->model->file_bytes, 0x0, HEX_VIEWER_BUF_SIZE);
-    bool isOk = true;
-
-    do {
-        uint32_t offset = hex_viewer->model->file_offset;
-        if(!stream_seek(hex_viewer->model->stream, offset, true)) {
-            FURI_LOG_E(TAG, "Unable to seek stream");
-            isOk = false;
-            break;
-        }
-
-        hex_viewer->model->file_read_bytes = stream_read(
-            hex_viewer->model->stream,
-            (uint8_t*)hex_viewer->model->file_bytes,
-            HEX_VIEWER_BUF_SIZE);
-    } while(false);
-
-    return isOk;
+void hex_viewer_app_free(HexViewer* app) {
+    furi_assert(app);
+
+    if(app->model->stream) buffered_file_stream_close(app->model->stream);
+
+    // Scene manager
+    scene_manager_free(app->scene_manager);
+
+    // View Dispatcher
+    view_dispatcher_remove_view(app->view_dispatcher, HexViewerViewIdMenu);
+    view_dispatcher_remove_view(app->view_dispatcher, HexViewerViewIdScene1);
+    view_dispatcher_remove_view(app->view_dispatcher, HexViewerViewIdScene2);
+    view_dispatcher_remove_view(app->view_dispatcher, HexViewerViewIdSettings);
+
+    submenu_free(app->submenu);
+    text_input_free(app->text_input);
+
+    view_dispatcher_free(app->view_dispatcher);
+    furi_record_close(RECORD_STORAGE);
+    furi_record_close(RECORD_GUI);
+
+    app->storage = NULL;
+    app->gui = NULL;
+    app->notification = NULL;
+
+    // Close File Browser
+    furi_record_close(RECORD_DIALOGS);
+    furi_string_free(app->file_path);
+
+    free(app->model);
+
+    //Remove whatever is left
+    free(app);
 }
 
 int32_t hex_viewer_app(void* p) {
-    HexViewer* hex_viewer = hex_viewer_alloc();
-
-    FuriString* file_path;
-    file_path = furi_string_alloc();
-
-    do {
-        if(p && strlen(p)) {
-            furi_string_set(file_path, (const char*)p);
-        } else {
-            furi_string_set(file_path, HEX_VIEWER_APP_PATH_FOLDER);
-
-            DialogsFileBrowserOptions browser_options;
-            dialog_file_browser_set_basic_options(
-                &browser_options, HEX_VIEWER_APP_EXTENSION, &I_hex_10px);
-            browser_options.hide_ext = false;
-
-            DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
-            bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
-
-            furi_record_close(RECORD_DIALOGS);
-            if(!res) {
-                FURI_LOG_I(TAG, "No file selected");
-                break;
-            }
-        }
-
-        FURI_LOG_I(TAG, "File selected: %s", furi_string_get_cstr(file_path));
-
-        if(!hex_viewer_open_file(hex_viewer, furi_string_get_cstr(file_path))) break;
-        hex_viewer_read_file(hex_viewer);
-
-        InputEvent input;
-        while(1) {
-            if(furi_message_queue_get(hex_viewer->input_queue, &input, 100) == FuriStatusOk) {
-                if(input.key == InputKeyBack) {
-                    break;
-                } else if(input.key == InputKeyUp) {
-                    furi_check(
-                        furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
-                    if(hex_viewer->model->file_offset > 0) {
-                        hex_viewer->model->file_offset -= HEX_VIEWER_BYTES_PER_LINE;
-                        if(!hex_viewer_read_file(hex_viewer)) break;
-                    }
-                    furi_mutex_release(hex_viewer->mutex);
-                } else if(input.key == InputKeyDown) {
-                    furi_check(
-                        furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
-                    uint32_t last_byte_on_screen =
-                        hex_viewer->model->file_offset + hex_viewer->model->file_read_bytes;
-
-                    if(hex_viewer->model->file_size > last_byte_on_screen) {
-                        hex_viewer->model->file_offset += HEX_VIEWER_BYTES_PER_LINE;
-                        if(!hex_viewer_read_file(hex_viewer)) break;
-                    }
-                    furi_mutex_release(hex_viewer->mutex);
-                } else if(input.key == InputKeyLeft) {
-                    furi_check(
-                        furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
-                    hex_viewer->model->mode = !hex_viewer->model->mode;
-                    furi_mutex_release(hex_viewer->mutex);
-                } else if(input.key == InputKeyRight) {
-                    FuriString* buffer;
-                    buffer = furi_string_alloc();
-                    furi_string_printf(
-                        buffer,
-                        "File path: %s\nFile size: %lu (0x%lX)",
-                        furi_string_get_cstr(file_path),
-                        hex_viewer->model->file_size,
-                        hex_viewer->model->file_size);
-
-                    DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
-                    DialogMessage* message = dialog_message_alloc();
-                    dialog_message_set_header(
-                        message, "Hex Viewer v1.1", 16, 2, AlignLeft, AlignTop);
-                    dialog_message_set_icon(message, &I_hex_10px, 3, 2);
-                    dialog_message_set_text(
-                        message, furi_string_get_cstr(buffer), 3, 16, AlignLeft, AlignTop);
-                    dialog_message_set_buttons(message, NULL, NULL, "Back");
-                    dialog_message_show(dialogs, message);
-
-                    furi_string_free(buffer);
-                    dialog_message_free(message);
-                    furi_record_close(RECORD_DIALOGS);
-                }
-            }
-
-            view_port_update(hex_viewer->view_port);
-        }
-    } while(false);
-
-    furi_string_free(file_path);
-    hex_viewer_free(hex_viewer);
+    UNUSED(p);
+    HexViewer* app = hex_viewer_app_alloc();
+
+    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+    scene_manager_next_scene(app->scene_manager, HexViewerSceneStartscreen);
+
+    furi_hal_power_suppress_charge_enter();
+
+    view_dispatcher_run(app->view_dispatcher);
+
+    hex_viewer_save_settings(app);
+
+    furi_hal_power_suppress_charge_exit();
+    hex_viewer_app_free(app);
 
     return 0;
 }

+ 108 - 0
base_pack/hex_viewer/hex_viewer.h

@@ -0,0 +1,108 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <stdlib.h>
+#include <hex_viewer_icons.h>
+#include <dialogs/dialogs.h>
+#include <notification/notification_messages.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/text_input.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/variable_item_list.h>
+#include <gui/modules/button_menu.h>
+#include <gui/modules/dialog_ex.h>
+#include "scenes/hex_viewer_scene.h"
+#include "views/hex_viewer_startscreen.h"
+#include "views/hex_viewer_scene_1.h"
+#include "views/hex_viewer_scene_2.h"
+#include "helpers/hex_viewer_storage.h"
+
+#include <storage/storage.h>
+#include <stream/stream.h>
+#include <stream/buffered_file_stream.h>
+#include <toolbox/stream/file_stream.h>
+
+#define TAG "HexViewer"
+
+// #define SUBGHZ_APP_EXTENSION ".sub"
+// #define SUBGHZ_APP_FOLDER ANY_PATH("subghz")
+
+#define HEX_VIEWER_APP_PATH_FOLDER "/any" // TODO ANY_PATH
+#define HEX_VIEWER_APP_EXTENSION "*"
+#define HEX_VIEWER_PERCENT_INPUT 16
+
+#define HEX_VIEWER_BYTES_PER_LINE 4u
+#define HEX_VIEWER_LINES_ON_SCREEN 4u
+#define HEX_VIEWER_BUF_SIZE (HEX_VIEWER_LINES_ON_SCREEN * HEX_VIEWER_BYTES_PER_LINE)
+
+// typedef struct HexViewerModel HexViewerModel;
+// typedef struct HexViewer HexViewer;
+
+typedef struct {
+    uint8_t file_bytes[HEX_VIEWER_LINES_ON_SCREEN][HEX_VIEWER_BYTES_PER_LINE];
+    uint32_t file_offset;
+    uint32_t file_read_bytes;
+    uint32_t file_size;
+
+    Stream* stream;
+} HexViewerModel;
+
+// TODO Clean
+typedef struct {
+    HexViewerModel* model;
+
+    Gui* gui;
+    Storage* storage;
+    NotificationApp* notification;
+    ViewDispatcher* view_dispatcher;
+    Submenu* submenu;
+    TextInput* text_input;
+    SceneManager* scene_manager;
+    VariableItemList* variable_item_list;
+    HexViewerStartscreen* hex_viewer_startscreen;
+    HexViewerScene1* hex_viewer_scene_1;
+    HexViewerScene2* hex_viewer_scene_2;
+    DialogsApp* dialogs; // File Browser
+    FuriString* file_path; // File Browser
+    uint32_t haptic;
+    uint32_t speaker;
+    uint32_t led;
+    uint32_t save_settings;
+    ButtonMenu* button_menu; // Button Menu
+    char percent_buf[HEX_VIEWER_PERCENT_INPUT];
+} HexViewer;
+
+typedef enum {
+    HexViewerViewIdStartscreen,
+    HexViewerViewIdMenu,
+    HexViewerViewIdScene1,
+    HexViewerViewIdScene2,
+    HexViewerViewIdScene3,
+    HexViewerViewIdScene4,
+    HexViewerViewIdScene5,
+    HexViewerViewIdSettings,
+} HexViewerViewId;
+
+typedef enum {
+    HexViewerHapticOff,
+    HexViewerHapticOn,
+} HexViewerHapticState;
+
+typedef enum {
+    HexViewerSpeakerOff,
+    HexViewerSpeakerOn,
+} HexViewerSpeakerState;
+
+typedef enum {
+    HexViewerLedOff,
+    HexViewerLedOn,
+} HexViewerLedState;
+
+typedef enum {
+    HexViewerSettingsOff,
+    HexViewerSettingsOn,
+} HexViewerSettingsStoreState;

+ 30 - 0
base_pack/hex_viewer/scenes/hex_viewer_scene.c

@@ -0,0 +1,30 @@
+#include "hex_viewer_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const hex_viewer_on_enter_handlers[])(void*) = {
+#include "hex_viewer_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const hex_viewer_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "hex_viewer_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const hex_viewer_on_exit_handlers[])(void* context) = {
+#include "hex_viewer_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers hex_viewer_scene_handlers = {
+    .on_enter_handlers = hex_viewer_on_enter_handlers,
+    .on_event_handlers = hex_viewer_on_event_handlers,
+    .on_exit_handlers = hex_viewer_on_exit_handlers,
+    .scene_num = HexViewerSceneNum,
+};

+ 29 - 0
base_pack/hex_viewer/scenes/hex_viewer_scene.h

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

+ 7 - 0
base_pack/hex_viewer/scenes/hex_viewer_scene_config.h

@@ -0,0 +1,7 @@
+ADD_SCENE(hex_viewer, startscreen, Startscreen)
+ADD_SCENE(hex_viewer, menu, Menu)
+ADD_SCENE(hex_viewer, scene_1, Scene_1)
+ADD_SCENE(hex_viewer, scene_2, Scene_2)
+ADD_SCENE(hex_viewer, scene_3, Scene_3)
+ADD_SCENE(hex_viewer, scene_4, Scene_4)
+ADD_SCENE(hex_viewer, settings, Settings)

+ 91 - 0
base_pack/hex_viewer/scenes/hex_viewer_scene_menu.c

@@ -0,0 +1,91 @@
+#include "../hex_viewer.h"
+
+enum SubmenuIndex {
+    SubmenuIndexScene1 = 10,
+    SubmenuIndexScene2,
+    // SubmenuIndexScene3,
+    SubmenuIndexScene4,
+    // SubmenuIndexScene5,
+    // SubmenuIndexSettings,
+};
+
+void hex_viewer_scene_menu_submenu_callback(void* context, uint32_t index) {
+    HexViewer* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void hex_viewer_scene_menu_on_enter(void* context) {
+    HexViewer* app = context;
+
+    submenu_set_header(app->submenu, "Select action");
+    submenu_add_item(
+        app->submenu,
+        "Open file ...",
+        SubmenuIndexScene4,
+        hex_viewer_scene_menu_submenu_callback,
+        app);
+    // submenu_add_item(app->submenu, "Scene 2 (Inputs/Effects)", SubmenuIndexScene2, hex_viewer_scene_menu_submenu_callback, app);
+    // submenu_add_item(app->submenu, "Scene 3 (Buttonmenu)", SubmenuIndexScene3, hex_viewer_scene_menu_submenu_callback, app);
+    submenu_add_item(
+        app->submenu,
+        "Scroll to ...",
+        SubmenuIndexScene1,
+        hex_viewer_scene_menu_submenu_callback,
+        app);
+    submenu_add_item(
+        app->submenu,
+        "Show info ...",
+        SubmenuIndexScene2,
+        hex_viewer_scene_menu_submenu_callback,
+        app);
+    // submenu_add_item(app->submenu, "Settings", SubmenuIndexSettings, hex_viewer_scene_menu_submenu_callback, app);
+
+    submenu_set_selected_item(
+        app->submenu, scene_manager_get_scene_state(app->scene_manager, HexViewerSceneMenu));
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerViewIdMenu);
+}
+
+bool hex_viewer_scene_menu_on_event(void* context, SceneManagerEvent event) {
+    HexViewer* app = context;
+
+    if(event.type == SceneManagerEventTypeBack) {
+        //exit app
+        // scene_manager_stop(app->scene_manager);
+        // view_dispatcher_stop(app->view_dispatcher);
+        scene_manager_previous_scene(app->scene_manager);
+        return true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexScene1) {
+            scene_manager_set_scene_state(
+                app->scene_manager, HexViewerSceneMenu, SubmenuIndexScene1);
+            scene_manager_next_scene(app->scene_manager, HexViewerSceneScene_1);
+            return true;
+        } else if(event.event == SubmenuIndexScene2) {
+            scene_manager_set_scene_state(
+                app->scene_manager, HexViewerSceneMenu, SubmenuIndexScene2);
+            scene_manager_next_scene(app->scene_manager, HexViewerSceneScene_2);
+            return true;
+            // } else if (event.event == SubmenuIndexScene3) {
+            //     scene_manager_set_scene_state(
+            //         app->scene_manager, HexViewerSceneMenu, SubmenuIndexScene3);
+            //     scene_manager_next_scene(app->scene_manager, HexViewerSceneScene_3);
+        } else if(event.event == SubmenuIndexScene4) {
+            scene_manager_set_scene_state(
+                app->scene_manager, HexViewerSceneMenu, SubmenuIndexScene4);
+            scene_manager_next_scene(app->scene_manager, HexViewerSceneScene_4);
+            // } else if (event.event == SubmenuIndexSettings) {
+            //     scene_manager_set_scene_state(
+            //         app->scene_manager, HexViewerSceneMenu, SubmenuIndexSettings);
+            //     scene_manager_next_scene(app->scene_manager, HexViewerSceneSettings);
+            //     return true;
+        }
+    }
+
+    return false;
+}
+
+void hex_viewer_scene_menu_on_exit(void* context) {
+    HexViewer* app = context;
+    submenu_reset(app->submenu);
+}

+ 94 - 0
base_pack/hex_viewer/scenes/hex_viewer_scene_scene_1.c

@@ -0,0 +1,94 @@
+#include "../hex_viewer.h"
+#include "../helpers/hex_viewer_custom_event.h"
+#include "../views/hex_viewer_scene_1.h"
+
+void hex_viewer_scene_scene_1_callback(void* context) {
+    HexViewer* app = (HexViewer*)context;
+    view_dispatcher_send_custom_event(
+        app->view_dispatcher, HexViewerCustomEventMenuPercentEntered);
+}
+
+void hex_viewer_scene_scene_1_on_enter(void* context) {
+    furi_assert(context);
+    HexViewer* app = context;
+
+    TextInput* text_input = app->text_input;
+
+    text_input_set_header_text(text_input, "Scroll to percent (0..100)");
+    text_input_set_result_callback(
+        text_input,
+        hex_viewer_scene_scene_1_callback,
+        app,
+        app->percent_buf,
+        HEX_VIEWER_PERCENT_INPUT,
+        false);
+
+    // ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
+    //     IBUTTON_APP_FOLDER, IBUTTON_APP_FILENAME_EXTENSION, ibutton->key_name);
+    // text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerSceneScene_1);
+
+    // if(success) {
+    //     //
+    // }
+
+    // if(success) {
+    //     // Load page to do something with result
+    //     //scene_manager_next_scene(app->scene_manager, HexViewerViewIdMenu);
+    //     //scene_manager_previous_scene(app->scene_manager); // temp for showcase
+    //     scene_manager_search_and_switch_to_previous_scene(
+    //         app->scene_manager, HexViewerViewIdStartscreen);
+    // } else {
+    //     // This is basically if someone quites the browser
+    //     scene_manager_previous_scene(app->scene_manager);
+    // }
+}
+
+bool hex_viewer_scene_scene_1_on_event(void* context, SceneManagerEvent event) {
+    HexViewer* app = (HexViewer*)context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == HexViewerCustomEventMenuPercentEntered) {
+            int ipercent = atoi(app->percent_buf);
+            // float percent = atof(app->percent_buf);
+            ipercent = MIN(ipercent, 100);
+            ipercent = MAX(ipercent, 0);
+            float percent = ipercent / 100.0;
+
+            uint32_t line_count = app->model->file_size / HEX_VIEWER_BYTES_PER_LINE;
+            if(app->model->file_size % HEX_VIEWER_BYTES_PER_LINE != 0) line_count += 1;
+            uint32_t scrollable_lines = line_count - HEX_VIEWER_LINES_ON_SCREEN;
+            uint32_t target_line = (uint32_t)(percent * scrollable_lines);
+
+            // uint32_t first_line_on_screen = model->file_offset / HEX_VIEWER_BYTES_PER_LINE;
+            // if(line_count > HEX_VIEWER_LINES_ON_SCREEN) {
+            //     uint8_t width = canvas_width(canvas);
+            //     elements_scrollbar_pos(
+            //         canvas,
+            //         width,
+            //         0,
+            //         ROW_HEIGHT * HEX_VIEWER_LINES_ON_SCREEN,
+            //         first_line_on_screen, // TODO
+            //         line_count - (HEX_VIEWER_LINES_ON_SCREEN - 1));
+            // }
+
+            uint32_t new_file_offset = target_line * HEX_VIEWER_BYTES_PER_LINE;
+            if(app->model->file_size > new_file_offset) {
+                app->model->file_offset = new_file_offset;
+                if(!hex_viewer_read_file(app)) new_file_offset = new_file_offset; // TODO Do smth
+            }
+
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, HexViewerViewIdStartscreen);
+
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void hex_viewer_scene_scene_1_on_exit(void* context) {
+    UNUSED(context);
+}

+ 54 - 0
base_pack/hex_viewer/scenes/hex_viewer_scene_scene_2.c

@@ -0,0 +1,54 @@
+#include "../hex_viewer.h"
+#include "../helpers/hex_viewer_custom_event.h"
+#include "../helpers/hex_viewer_haptic.h"
+#include "../helpers/hex_viewer_led.h"
+#include "../views/hex_viewer_scene_2.h"
+
+// void hex_viewer_scene_2_callback(HexViewerCustomEvent event, void* context) {
+//     furi_assert(context);
+//     HexViewer* app = context;
+//     view_dispatcher_send_custom_event(app->view_dispatcher, event);
+// }
+
+void hex_viewer_scene_scene_2_on_enter(void* context) {
+    furi_assert(context);
+    HexViewer* app = context;
+
+    FuriString* buffer;
+    buffer = furi_string_alloc();
+    furi_string_printf(
+        buffer,
+        "File path: %s\nFile size: %lu (0x%lX)",
+        furi_string_get_cstr(app->file_path),
+        app->model->file_size,
+        app->model->file_size);
+
+    // DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
+    DialogMessage* message = dialog_message_alloc();
+    dialog_message_set_header(message, "Hex Viewer v2.0", 16, 2, AlignLeft, AlignTop);
+    dialog_message_set_icon(message, &I_hex_10px, 3, 2);
+    dialog_message_set_text(message, furi_string_get_cstr(buffer), 3, 16, AlignLeft, AlignTop);
+    dialog_message_set_buttons(message, NULL, NULL, "Back");
+    dialog_message_show(app->dialogs, message);
+
+    furi_string_free(buffer);
+    dialog_message_free(message);
+    // furi_record_close(RECORD_DIALOGS);
+
+    scene_manager_search_and_switch_to_previous_scene(
+        app->scene_manager, HexViewerViewIdStartscreen);
+}
+
+bool hex_viewer_scene_scene_2_on_event(void* context, SceneManagerEvent event) {
+    HexViewer* app = context;
+    UNUSED(app);
+    UNUSED(event);
+    bool consumed = true;
+
+    return consumed;
+}
+
+void hex_viewer_scene_scene_2_on_exit(void* context) {
+    HexViewer* app = context;
+    UNUSED(app);
+}

+ 141 - 0
base_pack/hex_viewer/scenes/hex_viewer_scene_scene_3.c

@@ -0,0 +1,141 @@
+#include "../hex_viewer.h"
+#include "../helpers/hex_viewer_custom_event.h"
+#include "../helpers/hex_viewer_haptic.h"
+#include "../helpers/hex_viewer_led.h"
+
+typedef enum {
+    ButtonIndexControl3 = -3,
+    ButtonIndexControl2 = -2,
+    ButtonIndexControl1 = -1,
+    ButtonIndexButton1 = 0,
+    ButtonIndexButton2 = 1,
+    ButtonIndexButton3 = 2,
+} ButtonIndex;
+
+static void hex_viewer_scene_3_callback(void* context, int32_t index, InputType type) {
+    HexViewer* app = context;
+
+    uint16_t custom_type;
+    if(type == InputTypePress) {
+        custom_type = HexViewerCustomEventMenuSelected;
+    } else if(type == InputTypeRelease) {
+        custom_type = HexViewerCustomEventMenuVoid;
+    } else if(type == InputTypeShort) {
+        //somehow ButtonMenuItemTypeCommon uses InputTypeShort
+        custom_type = HexViewerCustomEventMenuSelected;
+    } else {
+        furi_crash("Unexpected Input Type");
+    }
+    view_dispatcher_send_custom_event(app->view_dispatcher, hex_viewer_custom_menu_event_pack(custom_type, index));
+}
+
+void hex_viewer_scene_scene_3_on_enter(void* context) {
+    furi_assert(context);
+    HexViewer* app = context;
+    ButtonMenu* button_menu = app->button_menu;
+    SceneManager* scene_manager = app->scene_manager;
+
+    button_menu_add_item(
+        button_menu,
+        "Common",
+        ButtonIndexButton1,
+        hex_viewer_scene_3_callback,
+        ButtonMenuItemTypeCommon,
+        context);
+    button_menu_add_item(
+        button_menu,
+        "Button",
+        ButtonIndexButton2,
+        hex_viewer_scene_3_callback,
+        ButtonMenuItemTypeCommon,
+        context);
+    button_menu_add_item(
+        button_menu,
+        "Examples",
+        ButtonIndexButton1,
+        hex_viewer_scene_3_callback,
+        ButtonMenuItemTypeCommon,
+        context);
+
+    button_menu_add_item(
+        button_menu,
+        "Control",
+        ButtonIndexControl1,
+        hex_viewer_scene_3_callback,
+        ButtonMenuItemTypeControl,
+        context);
+    
+    button_menu_add_item(
+        button_menu,
+        "Button",
+        ButtonIndexControl2,
+        hex_viewer_scene_3_callback,
+        ButtonMenuItemTypeControl,
+        context);
+
+    button_menu_add_item(
+        button_menu,
+        "Examples",
+        ButtonIndexControl3,
+        hex_viewer_scene_3_callback,
+        ButtonMenuItemTypeControl,
+        context);
+
+    button_menu_set_header(button_menu, "Button Menu");
+    const int16_t button_index =
+        (signed)scene_manager_get_scene_state(app->scene_manager, HexViewerViewIdScene3);
+    button_menu_set_selected_item(button_menu, button_index);
+    scene_manager_set_scene_state(scene_manager, HexViewerSceneScene_3, ButtonIndexButton1);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerViewIdScene3);
+}
+
+bool hex_viewer_scene_scene_3_on_event(void* context, SceneManagerEvent event) {
+    HexViewer* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        const uint16_t custom_type = hex_viewer_custom_menu_event_get_type(event.event);
+        const int16_t button_index = hex_viewer_custom_menu_event_get_value(event.event);
+        if (custom_type == HexViewerCustomEventMenuSelected) {
+            switch(button_index) {
+                case ButtonIndexButton1:
+                    hex_viewer_play_happy_bump(app);
+                    hex_viewer_led_set_rgb(app, 255, 0, 0);
+                    break;
+                case ButtonIndexButton2:
+                    hex_viewer_play_happy_bump(app);
+                    hex_viewer_led_set_rgb(app, 0, 255, 0);
+                    break;
+                case ButtonIndexButton3:
+                    hex_viewer_play_happy_bump(app);
+                    hex_viewer_led_set_rgb(app, 0, 0, 255);
+                    break;
+                case ButtonIndexControl1:
+                    hex_viewer_play_bad_bump(app);
+                    hex_viewer_led_set_rgb(app, 255, 0, 255);
+                    break;
+                case ButtonIndexControl2:
+                    hex_viewer_play_bad_bump(app);
+                    hex_viewer_led_set_rgb(app, 255, 255, 0);
+                    break;
+                case ButtonIndexControl3:
+                    hex_viewer_play_bad_bump(app);
+                    hex_viewer_led_set_rgb(app, 0, 255, 255);
+                    break;
+            }
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void hex_viewer_scene_scene_3_on_exit(void* context) {
+    HexViewer* app = context;
+    button_menu_reset(app->button_menu);
+    notification_message(app->notification, &sequence_reset_red);
+    notification_message(app->notification, &sequence_reset_green);
+    notification_message(app->notification, &sequence_reset_blue);
+}
+

+ 46 - 0
base_pack/hex_viewer/scenes/hex_viewer_scene_scene_4.c

@@ -0,0 +1,46 @@
+#include "../hex_viewer.h"
+
+void hex_viewer_scene_scene_4_on_enter(void* context) {
+    furi_assert(context);
+    HexViewer* app = context;
+
+    FuriString* initial_path;
+    initial_path = furi_string_alloc();
+    furi_string_set(initial_path, HEX_VIEWER_APP_PATH_FOLDER);
+
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(&browser_options, HEX_VIEWER_APP_EXTENSION, &I_hex_10px);
+    browser_options.hide_ext = false;
+
+    bool success =
+        dialog_file_browser_show(app->dialogs, app->file_path, initial_path, &browser_options);
+    furi_string_free(initial_path);
+
+    if(success) {
+        success = hex_viewer_open_file(app, furi_string_get_cstr(app->file_path));
+        if(success) hex_viewer_read_file(app);
+    }
+
+    if(success) {
+        // Load page to do something with result
+        //scene_manager_next_scene(app->scene_manager, HexViewerViewIdMenu);
+        //scene_manager_previous_scene(app->scene_manager); // temp for showcase
+        scene_manager_search_and_switch_to_previous_scene(
+            app->scene_manager, HexViewerViewIdStartscreen);
+    } else {
+        // This is basically if someone quites the browser
+        scene_manager_previous_scene(app->scene_manager);
+    }
+}
+
+bool hex_viewer_scene_scene_4_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    bool consumed = true;
+
+    return consumed;
+}
+
+void hex_viewer_scene_scene_4_on_exit(void* context) {
+    UNUSED(context);
+}

+ 147 - 0
base_pack/hex_viewer/scenes/hex_viewer_scene_settings.c

@@ -0,0 +1,147 @@
+#include "../hex_viewer.h"
+#include <lib/toolbox/value_index.h>
+
+enum SettingsIndex {
+    SettingsIndexHaptic = 10,
+    SettingsIndexValue1,
+    SettingsIndexValue2,
+};
+
+const char* const haptic_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t haptic_value[2] = {
+    HexViewerHapticOff,
+    HexViewerHapticOn,
+};
+
+const char* const speaker_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t speaker_value[2] = {
+    HexViewerSpeakerOff,
+    HexViewerSpeakerOn,
+};
+
+const char* const led_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t led_value[2] = {
+    HexViewerLedOff,
+    HexViewerLedOn,
+};
+
+const char* const settings_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t settings_value[2] = {
+    HexViewerSettingsOff,
+    HexViewerSettingsOn,
+};
+
+
+static void hex_viewer_scene_settings_set_haptic(VariableItem* item) {
+    HexViewer* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+
+    variable_item_set_current_value_text(item, haptic_text[index]);
+    app->haptic = haptic_value[index];
+}
+
+static void hex_viewer_scene_settings_set_speaker(VariableItem* item) {
+    HexViewer* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, speaker_text[index]);
+    app->speaker = speaker_value[index];
+}
+
+static void hex_viewer_scene_settings_set_led(VariableItem* item) {
+    HexViewer* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, led_text[index]);
+    app->led = led_value[index];
+}
+
+static void hex_viewer_scene_settings_set_save_settings(VariableItem* item) {
+    HexViewer* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, settings_text[index]);
+    app->save_settings = settings_value[index];
+}
+
+void hex_viewer_scene_settings_submenu_callback(void* context, uint32_t index) {
+    HexViewer* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void hex_viewer_scene_settings_on_enter(void* context) {
+    HexViewer* app = context;
+    VariableItem* item;
+    uint8_t value_index;
+
+    // Vibro on/off
+    item = variable_item_list_add(
+        app->variable_item_list,
+        "Vibro/Haptic:",
+        2,
+        hex_viewer_scene_settings_set_haptic,
+        app);
+    value_index = value_index_uint32(app->haptic, haptic_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, haptic_text[value_index]);
+
+    // Sound on/off
+    item = variable_item_list_add(
+        app->variable_item_list,
+        "Sound:",
+        2,
+        hex_viewer_scene_settings_set_speaker,
+        app);
+    value_index = value_index_uint32(app->speaker, speaker_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, speaker_text[value_index]);
+
+    // LED Effects on/off
+    item = variable_item_list_add(
+        app->variable_item_list,
+        "LED FX:",
+        2,
+        hex_viewer_scene_settings_set_led,
+        app);
+    value_index = value_index_uint32(app->led, led_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, led_text[value_index]);
+
+    // Save Settings to File
+    item = variable_item_list_add(
+        app->variable_item_list,
+        "Save Settings",
+        2,
+        hex_viewer_scene_settings_set_save_settings,
+        app);
+    value_index = value_index_uint32(app->save_settings, settings_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, settings_text[value_index]);
+    
+    view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerViewIdSettings);
+}
+
+bool hex_viewer_scene_settings_on_event(void* context, SceneManagerEvent event) {
+    HexViewer* app = context;
+    UNUSED(app);
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        
+    }
+    return consumed;
+}
+
+void hex_viewer_scene_settings_on_exit(void* context) {
+    HexViewer* app = context;
+    variable_item_list_set_selected_item(app->variable_item_list, 0);
+    variable_item_list_reset(app->variable_item_list);
+}

+ 66 - 0
base_pack/hex_viewer/scenes/hex_viewer_scene_startscreen.c

@@ -0,0 +1,66 @@
+#include "../hex_viewer.h"
+#include "../helpers/hex_viewer_custom_event.h"
+#include "../views/hex_viewer_startscreen.h"
+
+void hex_viewer_scene_startscreen_callback(HexViewerCustomEvent event, void* context) {
+    furi_assert(context);
+    HexViewer* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void hex_viewer_scene_startscreen_on_enter(void* context) {
+    furi_assert(context);
+    HexViewer* app = context;
+    hex_viewer_startscreen_set_callback(
+        app->hex_viewer_startscreen, hex_viewer_scene_startscreen_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, HexViewerViewIdStartscreen);
+}
+
+bool hex_viewer_scene_startscreen_on_event(void* context, SceneManagerEvent event) {
+    HexViewer* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case HexViewerCustomEventStartscreenLeft:
+            //app->model->mode = !app->model->mode;
+            consumed = true;
+            break;
+        case HexViewerCustomEventStartscreenRight:
+            // TODO Dialog
+            consumed = true;
+            break;
+        case HexViewerCustomEventStartscreenUp:
+            consumed = true;
+            break;
+        case HexViewerCustomEventStartscreenDown:
+            consumed = true;
+            break;
+        case HexViewerCustomEventStartscreenOk:
+            if(!app->model->file_size)
+                scene_manager_next_scene(app->scene_manager, HexViewerSceneScene_4);
+            else
+                scene_manager_next_scene(app->scene_manager, HexViewerSceneMenu);
+            consumed = true;
+            break;
+        case HexViewerCustomEventStartscreenBack: // TODO Delete
+            notification_message(app->notification, &sequence_reset_red);
+            notification_message(app->notification, &sequence_reset_green);
+            notification_message(app->notification, &sequence_reset_blue);
+            if(!scene_manager_search_and_switch_to_previous_scene(
+                   app->scene_manager, HexViewerSceneStartscreen)) {
+                scene_manager_stop(app->scene_manager);
+                view_dispatcher_stop(app->view_dispatcher);
+            }
+            consumed = true;
+            break;
+        }
+    }
+
+    return consumed;
+}
+
+void hex_viewer_scene_startscreen_on_exit(void* context) {
+    HexViewer* app = context;
+    UNUSED(app);
+}

+ 136 - 0
base_pack/hex_viewer/views/hex_viewer_scene_1.c

@@ -0,0 +1,136 @@
+#include "../hex_viewer.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+#include <dolphin/dolphin.h>
+
+struct HexViewerScene1 {
+    View* view;
+    HexViewerScene1Callback callback;
+    void* context;
+};
+
+
+typedef struct {
+    int some_value;
+} HexViewerScene1Model;
+
+void hex_viewer_scene_1_set_callback(
+    HexViewerScene1* instance,
+    HexViewerScene1Callback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void hex_viewer_scene_1_draw(Canvas* canvas, HexViewerScene1Model* model) {
+    UNUSED(model);
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, "This is Scene 1"); 
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str_aligned(canvas, 0, 22, AlignLeft, AlignTop, "An empty scene to be"); 
+    canvas_draw_str_aligned(canvas, 0, 32, AlignLeft, AlignTop, "used as hex_viewer"); 
+}
+
+static void hex_viewer_scene_1_model_init(HexViewerScene1Model* const model) {
+    model->some_value = 1;
+}
+
+bool hex_viewer_scene_1_input(InputEvent* event, void* context) {
+    furi_assert(context); 
+    HexViewerScene1* instance = context;
+    if (event->type == InputTypeRelease) {
+        switch(event->key) {
+            case InputKeyBack:
+                with_view_model(
+                    instance->view,
+                    HexViewerScene1Model * model,
+                    {
+                        UNUSED(model);
+                        instance->callback(HexViewerCustomEventScene1Back, instance->context);
+                    },
+                    true);
+                break;
+            case InputKeyLeft:
+            case InputKeyRight:
+            case InputKeyUp:
+            case InputKeyDown:
+            case InputKeyOk:
+                with_view_model(
+                    instance->view,
+                    HexViewerScene1Model* model,
+                    {
+                        UNUSED(model);
+                    },
+                    true);
+                break;
+            case InputKeyMAX:
+                break;
+        }
+    }
+    return true;
+}
+
+void hex_viewer_scene_1_exit(void* context) {
+    furi_assert(context);
+}
+
+void hex_viewer_scene_1_enter(void* context) {
+    furi_assert(context);
+    HexViewerScene1* instance = (HexViewerScene1*)context;
+    with_view_model(
+        instance->view,
+        HexViewerScene1Model * model,
+        {
+            hex_viewer_scene_1_model_init(model);
+        },
+        true
+    );
+}
+
+HexViewerScene1* hex_viewer_scene_1_alloc() {
+    HexViewerScene1* instance = malloc(sizeof(HexViewerScene1));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(HexViewerScene1Model));
+    view_set_context(instance->view, instance); // furi_assert crashes in events without this
+    view_set_draw_callback(instance->view, (ViewDrawCallback)hex_viewer_scene_1_draw);
+    view_set_input_callback(instance->view, hex_viewer_scene_1_input);
+    view_set_enter_callback(instance->view, hex_viewer_scene_1_enter);
+    view_set_exit_callback(instance->view, hex_viewer_scene_1_exit);
+
+    with_view_model(
+        instance->view,
+        HexViewerScene1Model * model,
+        {
+            hex_viewer_scene_1_model_init(model);
+        },
+        true
+    );
+    
+    return instance;
+}
+
+void hex_viewer_scene_1_free(HexViewerScene1* instance) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        HexViewerScene1Model * model,
+        {
+            UNUSED(model);
+        },
+        true);
+    view_free(instance->view);
+    free(instance);
+}
+
+View* hex_viewer_scene_1_get_view(HexViewerScene1* instance) {
+    furi_assert(instance);
+    return instance->view;
+}
+

+ 19 - 0
base_pack/hex_viewer/views/hex_viewer_scene_1.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/hex_viewer_custom_event.h"
+
+typedef struct HexViewerScene1 HexViewerScene1;
+
+typedef void (*HexViewerScene1Callback)(HexViewerCustomEvent event, void* context);
+
+void hex_viewer_scene_1_set_callback(
+    HexViewerScene1* hex_viewer_scene_1,
+    HexViewerScene1Callback callback,
+    void* context);
+
+View* hex_viewer_scene_1_get_view(HexViewerScene1* hex_viewer_static);
+
+HexViewerScene1* hex_viewer_scene_1_alloc();
+
+void hex_viewer_scene_1_free(HexViewerScene1* hex_viewer_static);

+ 254 - 0
base_pack/hex_viewer/views/hex_viewer_scene_2.c

@@ -0,0 +1,254 @@
+#include "../hex_viewer.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+#include <dolphin/dolphin.h>
+#include "../helpers/hex_viewer_haptic.h"
+#include "../helpers/hex_viewer_speaker.h"
+#include "../helpers/hex_viewer_led.h"
+
+struct HexViewerScene2 {
+    View* view;
+    HexViewerScene2Callback callback;
+    void* context;
+};
+
+typedef struct {
+    int screen_text;
+} HexViewerScene2Model;
+
+char buttonText[11][14] = {
+    "",
+    "Press Up",
+    "Press Down",
+    "Press Left",
+    "Press Right",
+    "Press Ok",
+    "Release Up",
+    "Release Down",
+    "Release Left",
+    "Release Right",
+    "Release Ok",
+};
+
+void hex_viewer_scene_2_set_callback(
+    HexViewerScene2* instance,
+    HexViewerScene2Callback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void hex_viewer_scene_2_draw(Canvas* canvas, HexViewerScene2Model* model) {
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, "Scene 2: Input Examples"); 
+    canvas_set_font(canvas, FontSecondary);
+    char *strInput = malloc(15);
+    strcpy(strInput, buttonText[model->screen_text]);
+    canvas_draw_str_aligned(canvas, 0, 22, AlignLeft, AlignTop, strInput); 
+    free(strInput);
+}
+
+static void hex_viewer_scene_2_model_init(HexViewerScene2Model* const model) {
+    model->screen_text = 0;
+}
+
+bool hex_viewer_scene_2_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    HexViewerScene2* instance = context;
+    if (event->type == InputTypeRelease) {
+        switch(event->key) {
+            case InputKeyBack:
+                with_view_model(
+                    instance->view,
+                    HexViewerScene2Model * model,
+                    {
+                        UNUSED(model);
+                        hex_viewer_stop_all_sound(instance->context);
+                        instance->callback(HexViewerCustomEventScene2Back, instance->context);
+                        hex_viewer_play_long_bump(instance->context);
+                    },
+                    true);
+                break;
+            case InputKeyUp:
+                with_view_model(
+                    instance->view,
+                    HexViewerScene2Model * model,
+                    {
+                        model->screen_text = 6;
+                        hex_viewer_play_bad_bump(instance->context);
+                        hex_viewer_stop_all_sound(instance->context);
+                        hex_viewer_led_set_rgb(instance->context, 255, 0, 255);
+                    },
+                    true);
+                break;
+            case InputKeyDown:
+                with_view_model(
+                    instance->view,
+                    HexViewerScene2Model * model,
+                    {
+                        model->screen_text = 7;
+                        hex_viewer_play_bad_bump(instance->context);
+                        hex_viewer_stop_all_sound(instance->context);
+                        hex_viewer_led_set_rgb(instance->context, 255, 255, 0);
+                    },
+                    true);
+                break;
+            case InputKeyLeft:
+                with_view_model(
+                    instance->view,
+                    HexViewerScene2Model * model,
+                    {
+                        model->screen_text = 8;
+                        hex_viewer_play_bad_bump(instance->context);
+                        hex_viewer_stop_all_sound(instance->context);
+                        hex_viewer_led_set_rgb(instance->context, 0, 255, 255);
+                    },
+                    true);
+                break;
+            case InputKeyRight:
+                with_view_model(
+                    instance->view,
+                    HexViewerScene2Model * model,
+                    {
+                        model->screen_text = 9;
+                        hex_viewer_play_bad_bump(instance->context);
+                        hex_viewer_stop_all_sound(instance->context);
+                        hex_viewer_led_set_rgb(instance->context, 255, 0, 0);
+                    },
+                    true);
+                break;
+            case InputKeyOk:
+                with_view_model(
+                    instance->view,
+                    HexViewerScene2Model * model,
+                    {
+                        model->screen_text = 10;
+                        hex_viewer_play_bad_bump(instance->context);
+                        hex_viewer_stop_all_sound(instance->context);
+                        hex_viewer_led_set_rgb(instance->context, 255, 255, 255);
+                    },
+                    true);
+                break;
+            case InputKeyMAX:
+                break;
+        }
+    } else if (event->type == InputTypePress) {
+         switch(event->key) {
+            case InputKeyUp:
+                with_view_model(
+                    instance->view,
+                    HexViewerScene2Model * model,
+                    {
+                        model->screen_text = 1;
+                        hex_viewer_play_happy_bump(instance->context);
+                        hex_viewer_play_input_sound(instance->context);
+                    },
+                    true);
+                break;
+            case InputKeyDown:
+                with_view_model(
+                    instance->view,
+                    HexViewerScene2Model * model,
+                    {
+                        model->screen_text = 2;
+                        hex_viewer_play_happy_bump(instance->context);
+                        hex_viewer_play_input_sound(instance->context);
+                    },
+                    true);
+                break;
+            case InputKeyLeft:
+                with_view_model(
+                    instance->view,
+                    HexViewerScene2Model * model,
+                    {
+                        model->screen_text = 3;
+                        hex_viewer_play_happy_bump(instance->context);
+                        hex_viewer_play_input_sound(instance->context);
+                    },
+                    true);
+                break;
+            case InputKeyRight:
+                with_view_model(
+                    instance->view,
+                    HexViewerScene2Model * model,
+                    {
+                        model->screen_text = 4;
+                        hex_viewer_play_happy_bump(instance->context);
+                        hex_viewer_play_input_sound(instance->context);
+                    },
+                    true);
+                break;
+            case InputKeyOk:
+                with_view_model(
+                    instance->view,
+                    HexViewerScene2Model * model,
+                    {
+                        model->screen_text = 5;
+                        hex_viewer_play_happy_bump(instance->context);
+                        hex_viewer_play_input_sound(instance->context);
+                    },
+                    true);
+                break;
+            case InputKeyBack:
+            case InputKeyMAX:
+                break;
+        }
+    }
+    
+    return true;
+}
+
+void hex_viewer_scene_2_exit(void* context) {
+    furi_assert(context);
+    HexViewer* app = context;
+    hex_viewer_stop_all_sound(app);
+    //hex_viewer_led_reset(app);
+}
+
+void hex_viewer_scene_2_enter(void* context) {
+    furi_assert(context);
+    dolphin_deed(DolphinDeedPluginStart);
+}
+
+HexViewerScene2* hex_viewer_scene_2_alloc() {
+    HexViewerScene2* instance = malloc(sizeof(HexViewerScene2));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(HexViewerScene2Model));
+    view_set_context(instance->view, instance);
+    view_set_draw_callback(instance->view, (ViewDrawCallback)hex_viewer_scene_2_draw);
+    view_set_input_callback(instance->view, hex_viewer_scene_2_input);
+    //view_set_enter_callback(instance->view, hex_viewer_scene_2_enter);
+    view_set_exit_callback(instance->view, hex_viewer_scene_2_exit);
+
+    with_view_model(
+        instance->view,
+        HexViewerScene2Model * model,
+        {
+            hex_viewer_scene_2_model_init(model);
+        },
+        true);
+    
+    return instance;
+}
+
+void hex_viewer_scene_2_free(HexViewerScene2* instance) {
+    furi_assert(instance);
+
+
+    view_free(instance->view);
+    free(instance);
+}
+
+View* hex_viewer_scene_2_get_view(HexViewerScene2* instance) {
+    furi_assert(instance);
+
+
+    return instance->view;
+}
+

+ 19 - 0
base_pack/hex_viewer/views/hex_viewer_scene_2.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/hex_viewer_custom_event.h"
+
+typedef struct HexViewerScene2 HexViewerScene2;
+
+typedef void (*HexViewerScene2Callback)(HexViewerCustomEvent event, void* context);
+
+void hex_viewer_scene_2_set_callback(
+    HexViewerScene2* instance,
+    HexViewerScene2Callback callback,
+    void * context);
+
+HexViewerScene2* hex_viewer_scene_2_alloc();
+
+void hex_viewer_scene_2_free(HexViewerScene2* hex_viewer_static);
+
+View* hex_viewer_scene_2_get_view(HexViewerScene2* boilerpate_static);

+ 252 - 0
base_pack/hex_viewer/views/hex_viewer_startscreen.c

@@ -0,0 +1,252 @@
+#include "../hex_viewer.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+
+struct HexViewerStartscreen {
+    View* view;
+    HexViewerStartscreenCallback callback;
+    void* context;
+};
+
+typedef struct {
+    uint8_t file_bytes[HEX_VIEWER_LINES_ON_SCREEN][HEX_VIEWER_BYTES_PER_LINE];
+    uint32_t file_offset;
+    uint32_t file_read_bytes;
+    uint32_t file_size;
+    bool mode;
+    uint32_t dbg;
+} HexViewerStartscreenModel;
+
+void hex_viewer_startscreen_set_callback(
+    HexViewerStartscreen* instance,
+    HexViewerStartscreenCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void hex_viewer_startscreen_draw(Canvas* canvas, HexViewerStartscreenModel* model) {
+    canvas_clear(canvas);
+
+    if(!model->file_size) {
+        canvas_set_color(canvas, ColorBlack);
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignTop, "HexViewer v2.0");
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(canvas, 64, 22, AlignCenter, AlignTop, "Basic hex viewer");
+        canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "for your Flipper");
+        elements_button_center(canvas, "Open");
+    } else {
+        canvas_set_color(canvas, ColorBlack);
+
+        elements_button_left(canvas, model->mode ? "Addr" : "Text");
+        //elements_button_right(canvas, "Info");
+        elements_button_center(canvas, "Menu");
+
+        int ROW_HEIGHT = 12;
+        int TOP_OFFSET = 10;
+        int LEFT_OFFSET = 3;
+
+        uint32_t line_count = model->file_size / HEX_VIEWER_BYTES_PER_LINE;
+        if(model->file_size % HEX_VIEWER_BYTES_PER_LINE != 0) line_count += 1;
+        uint32_t first_line_on_screen = model->file_offset / HEX_VIEWER_BYTES_PER_LINE;
+        if(line_count > HEX_VIEWER_LINES_ON_SCREEN) {
+            uint8_t width = canvas_width(canvas);
+            elements_scrollbar_pos(
+                canvas,
+                width,
+                0,
+                ROW_HEIGHT * HEX_VIEWER_LINES_ON_SCREEN,
+                first_line_on_screen, // TODO
+                line_count - (HEX_VIEWER_LINES_ON_SCREEN - 1));
+        }
+
+        char temp_buf[32];
+        uint32_t row_iters = model->file_read_bytes / HEX_VIEWER_BYTES_PER_LINE;
+        if(model->file_read_bytes % HEX_VIEWER_BYTES_PER_LINE != 0) row_iters += 1;
+
+        for(uint32_t i = 0; i < row_iters; ++i) {
+            uint32_t bytes_left_per_row = model->file_read_bytes - i * HEX_VIEWER_BYTES_PER_LINE;
+            bytes_left_per_row = MIN(bytes_left_per_row, HEX_VIEWER_BYTES_PER_LINE);
+
+            if(model->mode) {
+                memcpy(temp_buf, model->file_bytes[i], bytes_left_per_row);
+                temp_buf[bytes_left_per_row] = '\0';
+                for(uint32_t j = 0; j < bytes_left_per_row; ++j)
+                    if(!isprint((int)temp_buf[j])) temp_buf[j] = '.';
+
+                canvas_set_font(canvas, FontKeyboard);
+                canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
+            } else {
+                uint32_t addr = model->file_offset + i * HEX_VIEWER_BYTES_PER_LINE;
+                snprintf(temp_buf, 32, "%04lX", addr);
+
+                canvas_set_font(canvas, FontKeyboard);
+                canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
+            }
+
+            char* p = temp_buf;
+            for(uint32_t j = 0; j < bytes_left_per_row; ++j)
+                p += snprintf(p, 32, "%02X ", model->file_bytes[i][j]);
+
+            canvas_set_font(canvas, FontKeyboard);
+            canvas_draw_str(canvas, LEFT_OFFSET + 41, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
+        }
+
+        // Poor man's debug
+        // snprintf(temp_buf, 32, "D %02lX", model->dbg);
+        // elements_button_right(canvas, temp_buf);
+    }
+}
+
+static void hex_viewer_startscreen_model_init(HexViewerStartscreenModel* const model) {
+    memset(model->file_bytes, 0, sizeof(model->file_bytes));
+    model->file_offset = 0;
+    model->file_read_bytes = 0;
+    model->file_size = 0;
+    model->mode = false;
+    model->dbg = 0;
+}
+
+static void
+    update_local_model_from_app(HexViewer* const app, HexViewerStartscreenModel* const model) {
+    memcpy(model->file_bytes, app->model->file_bytes, sizeof(model->file_bytes));
+    model->file_offset = app->model->file_offset;
+    model->file_read_bytes = app->model->file_read_bytes;
+    model->file_size = app->model->file_size;
+    //model->mode = app->model->mode;
+}
+
+bool hex_viewer_startscreen_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    HexViewerStartscreen* instance = context;
+    HexViewer* app = instance->context; // TO so good, but works
+    // TODO InputTypeShort?
+    if(event->type == InputTypeRelease || event->type == InputTypeRepeat) {
+        switch(event->key) {
+        case InputKeyBack:
+            with_view_model(
+                instance->view,
+                HexViewerStartscreenModel * model,
+                {
+                    instance->callback(HexViewerCustomEventStartscreenBack, instance->context);
+                    update_local_model_from_app(instance->context, model);
+                },
+                true);
+            break;
+        case InputKeyLeft:
+            with_view_model(
+                instance->view,
+                HexViewerStartscreenModel * model,
+                { model->mode = !model->mode; },
+                true);
+            break;
+        case InputKeyRight:
+            with_view_model(
+                instance->view,
+                HexViewerStartscreenModel * model,
+                {
+                    // instance->callback(HexViewerCustomEventStartscreenRight, instance->context);
+                    // update_local_model_from_app(instance->context, model);
+                    model->dbg = 0;
+                },
+                true);
+            break;
+        case InputKeyUp:
+            with_view_model(
+                instance->view,
+                HexViewerStartscreenModel * model,
+                {
+                    if(app->model->file_offset > 0) {
+                        app->model->file_offset -= HEX_VIEWER_BYTES_PER_LINE;
+                        if(!hex_viewer_read_file(app)) break; // TODO Do smth
+                    }
+
+                    update_local_model_from_app(instance->context, model);
+                },
+                true);
+            break;
+        case InputKeyDown:
+            with_view_model(
+                instance->view,
+                HexViewerStartscreenModel * model,
+                {
+                    uint32_t last_byte_on_screen =
+                        app->model->file_offset + app->model->file_read_bytes;
+                    if(app->model->file_size > last_byte_on_screen) {
+                        app->model->file_offset += HEX_VIEWER_BYTES_PER_LINE;
+                        if(!hex_viewer_read_file(app)) break; // TODO Do smth
+                    }
+
+                    update_local_model_from_app(instance->context, model);
+                },
+                true);
+            break;
+        case InputKeyOk:
+            with_view_model(
+                instance->view,
+                HexViewerStartscreenModel * model,
+                {
+                    instance->callback(HexViewerCustomEventStartscreenOk, instance->context);
+                    update_local_model_from_app(instance->context, model);
+                },
+                true);
+            break;
+        case InputKeyMAX:
+            break;
+        }
+    }
+
+    return true;
+}
+
+void hex_viewer_startscreen_exit(void* context) {
+    furi_assert(context);
+}
+
+void hex_viewer_startscreen_enter(void* context) {
+    furi_assert(context);
+    HexViewerStartscreen* instance = (HexViewerStartscreen*)context;
+    with_view_model(
+        instance->view,
+        HexViewerStartscreenModel * model,
+        { update_local_model_from_app(instance->context, model); },
+        true);
+}
+
+HexViewerStartscreen* hex_viewer_startscreen_alloc() {
+    HexViewerStartscreen* instance = malloc(sizeof(HexViewerStartscreen));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(HexViewerStartscreenModel));
+    view_set_context(instance->view, instance);
+    view_set_draw_callback(instance->view, (ViewDrawCallback)hex_viewer_startscreen_draw);
+    view_set_input_callback(instance->view, hex_viewer_startscreen_input);
+    view_set_enter_callback(instance->view, hex_viewer_startscreen_enter);
+    view_set_exit_callback(instance->view, hex_viewer_startscreen_exit);
+
+    with_view_model(
+        instance->view,
+        HexViewerStartscreenModel * model,
+        { hex_viewer_startscreen_model_init(model); },
+        true);
+
+    return instance;
+}
+
+void hex_viewer_startscreen_free(HexViewerStartscreen* instance) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view, HexViewerStartscreenModel * model, { UNUSED(model); }, true);
+    view_free(instance->view);
+    free(instance);
+}
+
+View* hex_viewer_startscreen_get_view(HexViewerStartscreen* instance) {
+    furi_assert(instance);
+    return instance->view;
+}

+ 19 - 0
base_pack/hex_viewer/views/hex_viewer_startscreen.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/hex_viewer_custom_event.h"
+
+typedef struct HexViewerStartscreen HexViewerStartscreen;
+
+typedef void (*HexViewerStartscreenCallback)(HexViewerCustomEvent event, void* context);
+
+void hex_viewer_startscreen_set_callback(
+    HexViewerStartscreen* hex_viewer_startscreen,
+    HexViewerStartscreenCallback callback,
+    void* context);
+
+View* hex_viewer_startscreen_get_view(HexViewerStartscreen* hex_viewer_static);
+
+HexViewerStartscreen* hex_viewer_startscreen_alloc();
+
+void hex_viewer_startscreen_free(HexViewerStartscreen* hex_viewer_static);