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

WIP Transition to new structure

Roman Shchekin 2 лет назад
Родитель
Сommit
473ce0a72c

+ 0 - 7
README.md

@@ -1,7 +0,0 @@
-# flipper-zero-hex-viewer
-
-Hex Viewer application for Flipper Zero!
-
-![Hex Viewer app!](https://habrastorage.org/r/w1560/getpro/habr/upload_files/46e/28a/d97/46e28ad973d144b123a4ce513c895d18.png)
-
-[Link to FAP](https://nightly.link/QtRoS/flipper-zero-hex-viewer/actions/artifacts/448677581.zip) (firmware version 0.73.1)

+ 3 - 1
application.fam

@@ -10,7 +10,9 @@ App(
     ],
     ],
     stack_size=2 * 1024,
     stack_size=2 * 1024,
     order=20,
     order=20,
+    fap_libs=["assets"],
     fap_icon="icons/hex_10px.png",
     fap_icon="icons/hex_10px.png",
+	fap_icon_assets="icons",
     fap_category="Misc",
     fap_category="Misc",
-    fap_icon_assets="icons",
+
 )
 )

+ 60 - 0
helpers/hex_viewer_custom_event.h

@@ -0,0 +1,60 @@
+#pragma once
+
+typedef enum {
+    BoilerplateCustomEventStartscreenUp,
+    BoilerplateCustomEventStartscreenDown,
+    BoilerplateCustomEventStartscreenLeft,
+    BoilerplateCustomEventStartscreenRight,
+    BoilerplateCustomEventStartscreenOk,
+    BoilerplateCustomEventStartscreenBack,
+    BoilerplateCustomEventScene1Up,
+    BoilerplateCustomEventScene1Down,
+    BoilerplateCustomEventScene1Left,
+    BoilerplateCustomEventScene1Right,
+    BoilerplateCustomEventScene1Ok,
+    BoilerplateCustomEventScene1Back,
+    BoilerplateCustomEventScene2Up,
+    BoilerplateCustomEventScene2Down,
+    BoilerplateCustomEventScene2Left,
+    BoilerplateCustomEventScene2Right,
+    BoilerplateCustomEventScene2Ok,
+    BoilerplateCustomEventScene2Back,
+} BoilerplateCustomEvent;
+
+enum BoilerplateCustomEventType {
+    // Reserve first 100 events for button types and indexes, starting from 0
+    BoilerplateCustomEventMenuVoid,
+    BoilerplateCustomEventMenuSelected,
+};
+
+#pragma pack(push, 1)
+typedef union {
+    uint32_t packed_value;
+    struct {
+        uint16_t type;
+        int16_t value;
+    } content;
+} BoilerplateCustomEventMenu;
+#pragma pack(pop)
+
+static inline uint32_t hex_viewer_custom_menu_event_pack(uint16_t type, int16_t value) {
+    BoilerplateCustomEventMenu 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) {
+    BoilerplateCustomEventMenu 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
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) {
+    Boilerplate* 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) {
+    Boilerplate* 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) {
+    Boilerplate* 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
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
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) {
+    Boilerplate* 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) {
+    Boilerplate* 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
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
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) {
+    Boilerplate* 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) {
+    Boilerplate* app = context;
+    if (app->speaker != 1) {
+        return;
+    }
+    if(furi_hal_speaker_is_mine()) {
+        furi_hal_speaker_stop();
+        furi_hal_speaker_release();
+    }
+}

+ 4 - 0
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);

+ 120 - 0
helpers/hex_viewer_storage.c

@@ -0,0 +1,120 @@
+#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) {
+    Boilerplate* 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, BOILERPLATE_SETTINGS_SAVE_PATH)) {
+        storage_simply_remove(storage, BOILERPLATE_SETTINGS_SAVE_PATH);
+    }
+
+    // Open File, create if not exists
+    if(!storage_common_stat(storage, BOILERPLATE_SETTINGS_SAVE_PATH, NULL) == FSE_OK) {
+        FURI_LOG_D(TAG, "Config file %s is not found. Will create new.", BOILERPLATE_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, BOILERPLATE_SETTINGS_SAVE_PATH)) {
+        //totp_close_config_file(fff_file);
+        FURI_LOG_E(TAG, "Error creating new file %s", BOILERPLATE_SETTINGS_SAVE_PATH);
+        hex_viewer_close_storage();
+        return;
+    }
+    
+    // Store Settings
+    flipper_format_write_header_cstr(
+        fff_file, BOILERPLATE_SETTINGS_HEADER, BOILERPLATE_SETTINGS_FILE_VERSION);
+    flipper_format_write_uint32(
+        fff_file, BOILERPLATE_SETTINGS_KEY_HAPTIC, &app->haptic, 1);
+    flipper_format_write_uint32(
+        fff_file, BOILERPLATE_SETTINGS_KEY_SPEAKER, &app->speaker, 1);
+    flipper_format_write_uint32(
+        fff_file, BOILERPLATE_SETTINGS_KEY_LED, &app->led, 1);
+    flipper_format_write_uint32(
+        fff_file, BOILERPLATE_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) {
+    Boilerplate* app = context;
+    Storage* storage = hex_viewer_open_storage();
+    FlipperFormat* fff_file = flipper_format_file_alloc(storage);
+
+    if(storage_common_stat(storage, BOILERPLATE_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, BOILERPLATE_SETTINGS_SAVE_PATH)) {
+        FURI_LOG_E(TAG, "Cannot open file %s", BOILERPLATE_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 < BOILERPLATE_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, BOILERPLATE_SETTINGS_KEY_HAPTIC, &app->haptic, 1);
+    flipper_format_read_uint32(fff_file, BOILERPLATE_SETTINGS_KEY_SPEAKER, &app->speaker, 1);
+    flipper_format_read_uint32(fff_file, BOILERPLATE_SETTINGS_KEY_LED, &app->led, 1);
+    flipper_format_read_uint32(fff_file, BOILERPLATE_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1);
+
+    flipper_format_rewind(fff_file);
+
+    hex_viewer_close_config_file(fff_file);
+    hex_viewer_close_storage();
+}

+ 18 - 0
helpers/hex_viewer_storage.h

@@ -0,0 +1,18 @@
+#include <stdlib.h>
+#include <string.h>
+#include <storage/storage.h>
+#include <flipper_format/flipper_format_i.h>
+#include "../hex_viewer.h"
+
+#define BOILERPLATE_SETTINGS_FILE_VERSION 1
+#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/hex_viewer")
+#define BOILERPLATE_SETTINGS_SAVE_PATH CONFIG_FILE_DIRECTORY_PATH "/hex_viewer.conf"
+#define BOILERPLATE_SETTINGS_SAVE_PATH_TMP BOILERPLATE_SETTINGS_SAVE_PATH ".tmp"
+#define BOILERPLATE_SETTINGS_HEADER "Boilerplate Config File"
+#define BOILERPLATE_SETTINGS_KEY_HAPTIC "Haptic"
+#define BOILERPLATE_SETTINGS_KEY_LED "Led"
+#define BOILERPLATE_SETTINGS_KEY_SPEAKER "Speaker"
+#define BOILERPLATE_SETTINGS_KEY_SAVE_SETTINGS "SaveSettings"
+
+void hex_viewer_save_settings(void* context);
+void hex_viewer_read_settings(void* context);

+ 98 - 266
hex_viewer.c

@@ -1,289 +1,121 @@
-#include <furi.h>
-#include <furi_hal.h>
+#include "hex_viewer.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);
-}
-
-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);
-    }
+bool hex_viewer_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, event);
 }
 }
 
 
-static HexViewer* hex_viewer_alloc() {
-    HexViewer* instance = malloc(sizeof(HexViewer));
-
-    instance->model = malloc(sizeof(HexViewerModel));
-    memset(instance->model, 0x0, sizeof(HexViewerModel));
-
-    instance->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
-
-    instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
-
-    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);
-
-    instance->gui = furi_record_open(RECORD_GUI);
-    gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen);
-
-    instance->storage = furi_record_open(RECORD_STORAGE);
-
-    return instance;
+void hex_viewer_tick_event_callback(void* context) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    scene_manager_handle_tick_event(app->scene_manager);
 }
 }
 
 
-static void hex_viewer_free(HexViewer* instance) {
-    furi_record_close(RECORD_STORAGE);
-
-    gui_remove_view_port(instance->gui, instance->view_port);
-    furi_record_close(RECORD_GUI);
-    view_port_free(instance->view_port);
-
-    furi_message_queue_free(instance->input_queue);
-
-    furi_mutex_free(instance->mutex);
-
-    if(instance->model->stream) buffered_file_stream_close(instance->model->stream);
-
-    free(instance->model);
-    free(instance);
+//leave app if back button pressed
+bool hex_viewer_navigation_event_callback(void* context) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
 }
 }
 
 
-static bool hex_viewer_open_file(HexViewer* hex_viewer, const char* file_path) {
-    furi_assert(hex_viewer);
-    furi_assert(file_path);
-
-    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;
+Boilerplate* hex_viewer_app_alloc() {
+    Boilerplate* app = malloc(sizeof(Boilerplate));
+    app->gui = furi_record_open(RECORD_GUI);
+    app->notification = furi_record_open(RECORD_NOTIFICATION);
+    
+    //Turn backlight on, believe me this makes testing your app easier
+    notification_message(app->notification, &sequence_display_backlight_on);
+
+    //Scene additions
+    app->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_enable_queue(app->view_dispatcher);
+
+    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);
+    app->submenu = submenu_alloc();
+
+    // Set defaults, in case no config loaded
+    app->haptic = 1;
+    app->speaker = 1;
+    app->led = 1;
+    app->save_settings = 1;
+
+    // Used for File Browser
+    app->dialogs = furi_record_open(RECORD_DIALOGS);
+    app->file_path = furi_string_alloc();
+
+    // Load configs
+    hex_viewer_read_settings(app);
+
+    view_dispatcher_add_view(app->view_dispatcher, BoilerplateViewIdMenu, submenu_get_view(app->submenu));
+    app->hex_viewer_startscreen = hex_viewer_startscreen_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, BoilerplateViewIdStartscreen, hex_viewer_startscreen_get_view(app->hex_viewer_startscreen));
+    app->hex_viewer_scene_1 = hex_viewer_scene_1_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, BoilerplateViewIdScene1, hex_viewer_scene_1_get_view(app->hex_viewer_scene_1));
+    app->hex_viewer_scene_2 = hex_viewer_scene_2_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, BoilerplateViewIdScene2, hex_viewer_scene_2_get_view(app->hex_viewer_scene_2));
+    app->button_menu = button_menu_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, BoilerplateViewIdScene3, button_menu_get_view(app->button_menu));
+    
+    app->variable_item_list = variable_item_list_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, BoilerplateViewIdSettings, variable_item_list_get_view(app->variable_item_list));
+
+    //End Scene Additions
+
+    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);
+void hex_viewer_app_free(Boilerplate* app) {
+    furi_assert(app);
+    
+    // Scene manager
+    scene_manager_free(app->scene_manager);
 
 
-    memset(hex_viewer->model->file_bytes, 0x0, HEX_VIEWER_BUF_SIZE);
-    bool isOk = true;
+    // View Dispatcher
+    view_dispatcher_remove_view(app->view_dispatcher, BoilerplateViewIdMenu);
+    view_dispatcher_remove_view(app->view_dispatcher, BoilerplateViewIdScene1);
+    view_dispatcher_remove_view(app->view_dispatcher, BoilerplateViewIdScene2);
+    view_dispatcher_remove_view(app->view_dispatcher, BoilerplateViewIdSettings);
+    submenu_free(app->submenu);
 
 
-    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;
-        }
+    view_dispatcher_free(app->view_dispatcher);
+    furi_record_close(RECORD_GUI);
+    
+    app->gui = NULL;
+    app->notification = NULL;
 
 
-        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);
+    // Close File Browser
+    furi_record_close(RECORD_DIALOGS);
+    furi_string_free(app->file_path);
 
 
-    return isOk;
+    //Remove whatever is left
+    free(app);
 }
 }
 
 
 int32_t hex_viewer_app(void* p) {
 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;
-            }
-        }
+    UNUSED(p);
+    Boilerplate* app = hex_viewer_app_alloc();
+    
+    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+    
+    scene_manager_next_scene(app->scene_manager, BoilerplateSceneStartscreen); //Start with start screen
+    //scene_manager_next_scene(app->scene_manager, BoilerplateSceneMenu); //if you want to directly start with Menu
 
 
-        FURI_LOG_I(TAG, "File selected: %s", furi_string_get_cstr(file_path));
+    furi_hal_power_suppress_charge_enter();
 
 
-        if(!hex_viewer_open_file(hex_viewer, furi_string_get_cstr(file_path))) break;
-        hex_viewer_read_file(hex_viewer);
+    view_dispatcher_run(app->view_dispatcher);
 
 
-        InputEvent input;
-        while(furi_message_queue_get(hex_viewer->input_queue, &input, FuriWaitForever) ==
-              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;
+    hex_viewer_save_settings(app);
+    
+    furi_hal_power_suppress_charge_exit();
+    hex_viewer_app_free(app);
 
 
-                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);
-            }
+    return 0;
+}
 
 
-            view_port_update(hex_viewer->view_port);
-        }
-    } while(false);
 
 
-    furi_string_free(file_path);
-    hex_viewer_free(hex_viewer);
 
 
-    return 0;
-}

+ 76 - 0
hex_viewer.h

@@ -0,0 +1,76 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <stdlib.h>
+#include <assets_icons.h>
+#include <dialogs/dialogs.h>
+#include <notification/notification_messages.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/submenu.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"
+
+#define TAG "Boilerplate"
+
+#define SUBGHZ_APP_EXTENSION ".sub"
+#define SUBGHZ_APP_FOLDER ANY_PATH("subghz")
+
+typedef struct {
+    Gui* gui;
+    NotificationApp* notification;
+    ViewDispatcher* view_dispatcher;
+    Submenu* submenu;
+    SceneManager* scene_manager;
+    VariableItemList* variable_item_list;
+    BoilerplateStartscreen* hex_viewer_startscreen;
+    BoilerplateScene1* hex_viewer_scene_1;
+    BoilerplateScene2* 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
+} Boilerplate;
+
+typedef enum {
+    BoilerplateViewIdStartscreen,
+    BoilerplateViewIdMenu,
+    BoilerplateViewIdScene1,
+    BoilerplateViewIdScene2,
+    BoilerplateViewIdScene3,
+    BoilerplateViewIdScene4,
+    BoilerplateViewIdScene5,
+    BoilerplateViewIdSettings,
+} BoilerplateViewId;
+
+typedef enum {
+    BoilerplateHapticOff,
+    BoilerplateHapticOn,
+} BoilerplateHapticState;
+
+typedef enum {
+    BoilerplateSpeakerOff,
+    BoilerplateSpeakerOn,
+} BoilerplateSpeakerState;
+
+typedef enum {
+    BoilerplateLedOff,
+    BoilerplateLedOn,
+} BoilerplateLedState;
+
+typedef enum {
+    BoilerplateSettingsOff,
+    BoilerplateSettingsOn,
+} BoilerplateSettingsStoreState;

+ 30 - 0
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 = BoilerplateSceneNum,
+};

+ 29 - 0
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) BoilerplateScene##id,
+typedef enum {
+#include "hex_viewer_scene_config.h"
+    BoilerplateSceneNum,
+} BoilerplateScene;
+#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
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)

+ 71 - 0
scenes/hex_viewer_scene_menu.c

@@ -0,0 +1,71 @@
+#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) {
+    Boilerplate* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void hex_viewer_scene_menu_on_enter(void* context) {
+    Boilerplate* app = context;
+
+    submenu_add_item(app->submenu, "Scene 1 (empty)", SubmenuIndexScene1, 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, "Scene 4 (File Browser)", SubmenuIndexScene4, 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, BoilerplateSceneMenu));
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, BoilerplateViewIdMenu);
+}
+
+bool hex_viewer_scene_menu_on_event(void* context, SceneManagerEvent event) {
+    Boilerplate* app = context;
+    UNUSED(app);
+    if(event.type == SceneManagerEventTypeBack) {
+        //exit app
+        scene_manager_stop(app->scene_manager);
+        view_dispatcher_stop(app->view_dispatcher);
+        return true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexScene1) {
+            scene_manager_set_scene_state(
+                app->scene_manager, BoilerplateSceneMenu, SubmenuIndexScene1);
+            scene_manager_next_scene(app->scene_manager, BoilerplateSceneScene_1);
+            return true;
+        } else if (event.event == SubmenuIndexScene2) {
+            scene_manager_set_scene_state(
+                app->scene_manager, BoilerplateSceneMenu, SubmenuIndexScene2);
+            scene_manager_next_scene(app->scene_manager, BoilerplateSceneScene_2);
+            return true;
+        } else if (event.event == SubmenuIndexScene3) {
+            scene_manager_set_scene_state(
+                app->scene_manager, BoilerplateSceneMenu, SubmenuIndexScene3);
+            scene_manager_next_scene(app->scene_manager, BoilerplateSceneScene_3);
+        } else if (event.event == SubmenuIndexScene4) {
+            scene_manager_set_scene_state(
+                app->scene_manager, BoilerplateSceneMenu, SubmenuIndexScene4);
+            scene_manager_next_scene(app->scene_manager, BoilerplateSceneScene_4);
+        } else if (event.event == SubmenuIndexSettings) {
+            scene_manager_set_scene_state(
+                app->scene_manager, BoilerplateSceneMenu, SubmenuIndexSettings);
+            scene_manager_next_scene(app->scene_manager, BoilerplateSceneSettings);
+            return true;
+        }
+    }
+    return false;
+}
+
+void hex_viewer_scene_menu_on_exit(void* context) {
+    Boilerplate* app = context;
+    submenu_reset(app->submenu);
+}

+ 50 - 0
scenes/hex_viewer_scene_scene_1.c

@@ -0,0 +1,50 @@
+#include "../hex_viewer.h"
+#include "../helpers/hex_viewer_custom_event.h"
+#include "../views/hex_viewer_scene_1.h"
+
+void hex_viewer_scene_1_callback(BoilerplateCustomEvent event, void* context) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void hex_viewer_scene_scene_1_on_enter(void* context) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    hex_viewer_scene_1_set_callback(app->hex_viewer_scene_1, hex_viewer_scene_1_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, BoilerplateViewIdScene1);
+}
+
+bool hex_viewer_scene_scene_1_on_event(void* context, SceneManagerEvent event) {
+    Boilerplate* app = context;
+    bool consumed = false;
+    
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+            case BoilerplateCustomEventScene1Left:
+            case BoilerplateCustomEventScene1Right:
+                break;
+            case BoilerplateCustomEventScene1Up:
+            case BoilerplateCustomEventScene1Down:
+                break;
+            case BoilerplateCustomEventScene1Back:
+                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, BoilerplateSceneMenu)) {
+                        scene_manager_stop(app->scene_manager);
+                        view_dispatcher_stop(app->view_dispatcher);
+                    }
+                consumed = true;
+                break;
+        }
+    }
+    
+    return consumed;
+}
+
+void hex_viewer_scene_scene_1_on_exit(void* context) {
+    Boilerplate* app = context;
+    UNUSED(app);
+}

+ 53 - 0
scenes/hex_viewer_scene_scene_2.c

@@ -0,0 +1,53 @@
+#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(BoilerplateCustomEvent event, void* context) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void hex_viewer_scene_scene_2_on_enter(void* context) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    hex_viewer_scene_2_set_callback(app->hex_viewer_scene_2, hex_viewer_scene_2_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, BoilerplateViewIdScene2);
+}
+
+bool hex_viewer_scene_scene_2_on_event(void* context, SceneManagerEvent event) {
+    Boilerplate* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+            case BoilerplateCustomEventScene2Left:
+            case BoilerplateCustomEventScene2Right:
+                break;
+            case BoilerplateCustomEventScene2Up:
+            case BoilerplateCustomEventScene2Down:
+                break;
+            case BoilerplateCustomEventScene2Back:
+                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, BoilerplateSceneMenu)) {
+                        scene_manager_stop(app->scene_manager);
+                        view_dispatcher_stop(app->view_dispatcher);
+                    }
+                consumed = true;
+                break;
+        }
+    }
+
+    return consumed;
+}
+
+void hex_viewer_scene_scene_2_on_exit(void* context) {
+    Boilerplate* app = context;
+    UNUSED(app);
+}
+

+ 141 - 0
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) {
+    Boilerplate* app = context;
+
+    uint16_t custom_type;
+    if(type == InputTypePress) {
+        custom_type = BoilerplateCustomEventMenuSelected;
+    } else if(type == InputTypeRelease) {
+        custom_type = BoilerplateCustomEventMenuVoid;
+    } else if(type == InputTypeShort) {
+        //somehow ButtonMenuItemTypeCommon uses InputTypeShort
+        custom_type = BoilerplateCustomEventMenuSelected;
+    } 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);
+    Boilerplate* 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, BoilerplateViewIdScene3);
+    button_menu_set_selected_item(button_menu, button_index);
+    scene_manager_set_scene_state(scene_manager, BoilerplateSceneScene_3, ButtonIndexButton1);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, BoilerplateViewIdScene3);
+}
+
+bool hex_viewer_scene_scene_3_on_event(void* context, SceneManagerEvent event) {
+    Boilerplate* 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 == BoilerplateCustomEventMenuSelected) {
+            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) {
+    Boilerplate* 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);
+}
+

+ 45 - 0
scenes/hex_viewer_scene_scene_4.c

@@ -0,0 +1,45 @@
+#include "../hex_viewer.h"
+
+void hex_viewer_scene_scene_4_on_enter(void* context) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    DialogsFileBrowserOptions browser_options;
+    
+    // This will filter the browser to only show one file type and also add an icon
+    dialog_file_browser_set_basic_options(&browser_options, SUBGHZ_APP_EXTENSION, &I_sub1_10px);
+    
+    //Get the Folder you want to browse
+    browser_options.base_path = SUBGHZ_APP_FOLDER;
+    FuriString* path;
+    path = furi_string_alloc();
+    furi_string_set(path, SUBGHZ_APP_FOLDER);
+    bool success = dialog_file_browser_show(
+        app->dialogs, app->file_path, path, &browser_options);
+    furi_string_free(path);
+
+    if(success) {
+        // Do something with the result in app->file_path
+    }
+
+    if(success) {
+        // Load page to do something with result
+        //scene_manager_next_scene(app->scene_manager, BoilerplateViewIdMenu);
+        scene_manager_previous_scene(app->scene_manager); // temp for showcase
+    } 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
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] = {
+    BoilerplateHapticOff,
+    BoilerplateHapticOn,
+};
+
+const char* const speaker_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t speaker_value[2] = {
+    BoilerplateSpeakerOff,
+    BoilerplateSpeakerOn,
+};
+
+const char* const led_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t led_value[2] = {
+    BoilerplateLedOff,
+    BoilerplateLedOn,
+};
+
+const char* const settings_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t settings_value[2] = {
+    BoilerplateSettingsOff,
+    BoilerplateSettingsOn,
+};
+
+
+static void hex_viewer_scene_settings_set_haptic(VariableItem* item) {
+    Boilerplate* 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) {
+    Boilerplate* 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) {
+    Boilerplate* 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) {
+    Boilerplate* 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) {
+    Boilerplate* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void hex_viewer_scene_settings_on_enter(void* context) {
+    Boilerplate* 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, BoilerplateViewIdSettings);
+}
+
+bool hex_viewer_scene_settings_on_event(void* context, SceneManagerEvent event) {
+    Boilerplate* app = context;
+    UNUSED(app);
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        
+    }
+    return consumed;
+}
+
+void hex_viewer_scene_settings_on_exit(void* context) {
+    Boilerplate* app = context;
+    variable_item_list_set_selected_item(app->variable_item_list, 0);
+    variable_item_list_reset(app->variable_item_list);
+}

+ 54 - 0
scenes/hex_viewer_scene_startscreen.c

@@ -0,0 +1,54 @@
+#include "../hex_viewer.h"
+#include "../helpers/hex_viewer_custom_event.h"
+#include "../views/hex_viewer_startscreen.h"
+
+void hex_viewer_scene_startscreen_callback(BoilerplateCustomEvent event, void* context) {
+    furi_assert(context);
+    Boilerplate* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void hex_viewer_scene_startscreen_on_enter(void* context) {
+    furi_assert(context);
+    Boilerplate* 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, BoilerplateViewIdStartscreen);
+}
+
+bool hex_viewer_scene_startscreen_on_event(void* context, SceneManagerEvent event) {
+    Boilerplate* app = context;
+    bool consumed = false;
+    
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+            case BoilerplateCustomEventStartscreenLeft:
+            case BoilerplateCustomEventStartscreenRight:
+                break;
+            case BoilerplateCustomEventStartscreenUp:
+            case BoilerplateCustomEventStartscreenDown:
+                break;
+            case BoilerplateCustomEventStartscreenOk:
+                scene_manager_next_scene(app->scene_manager, BoilerplateSceneMenu);
+                consumed = true;
+                break;
+            case BoilerplateCustomEventStartscreenBack:
+                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, BoilerplateSceneStartscreen)) {
+                        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) {
+    Boilerplate* app = context;
+    UNUSED(app);
+}

+ 136 - 0
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 BoilerplateScene1 {
+    View* view;
+    BoilerplateScene1Callback callback;
+    void* context;
+};
+
+
+typedef struct {
+    int some_value;
+} BoilerplateScene1Model;
+
+void hex_viewer_scene_1_set_callback(
+    BoilerplateScene1* instance,
+    BoilerplateScene1Callback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void hex_viewer_scene_1_draw(Canvas* canvas, BoilerplateScene1Model* 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(BoilerplateScene1Model* const model) {
+    model->some_value = 1;
+}
+
+bool hex_viewer_scene_1_input(InputEvent* event, void* context) {
+    furi_assert(context); 
+    BoilerplateScene1* instance = context;
+    if (event->type == InputTypeRelease) {
+        switch(event->key) {
+            case InputKeyBack:
+                with_view_model(
+                    instance->view,
+                    BoilerplateScene1Model * model,
+                    {
+                        UNUSED(model);
+                        instance->callback(BoilerplateCustomEventScene1Back, instance->context);
+                    },
+                    true);
+                break;
+            case InputKeyLeft:
+            case InputKeyRight:
+            case InputKeyUp:
+            case InputKeyDown:
+            case InputKeyOk:
+                with_view_model(
+                    instance->view,
+                    BoilerplateScene1Model* 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);
+    BoilerplateScene1* instance = (BoilerplateScene1*)context;
+    with_view_model(
+        instance->view,
+        BoilerplateScene1Model * model,
+        {
+            hex_viewer_scene_1_model_init(model);
+        },
+        true
+    );
+}
+
+BoilerplateScene1* hex_viewer_scene_1_alloc() {
+    BoilerplateScene1* instance = malloc(sizeof(BoilerplateScene1));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(BoilerplateScene1Model));
+    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,
+        BoilerplateScene1Model * model,
+        {
+            hex_viewer_scene_1_model_init(model);
+        },
+        true
+    );
+    
+    return instance;
+}
+
+void hex_viewer_scene_1_free(BoilerplateScene1* instance) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        BoilerplateScene1Model * model,
+        {
+            UNUSED(model);
+        },
+        true);
+    view_free(instance->view);
+    free(instance);
+}
+
+View* hex_viewer_scene_1_get_view(BoilerplateScene1* instance) {
+    furi_assert(instance);
+    return instance->view;
+}
+

+ 19 - 0
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 BoilerplateScene1 BoilerplateScene1;
+
+typedef void (*BoilerplateScene1Callback)(BoilerplateCustomEvent event, void* context);
+
+void hex_viewer_scene_1_set_callback(
+    BoilerplateScene1* hex_viewer_scene_1,
+    BoilerplateScene1Callback callback,
+    void* context);
+
+View* hex_viewer_scene_1_get_view(BoilerplateScene1* hex_viewer_static);
+
+BoilerplateScene1* hex_viewer_scene_1_alloc();
+
+void hex_viewer_scene_1_free(BoilerplateScene1* hex_viewer_static);

+ 254 - 0
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 BoilerplateScene2 {
+    View* view;
+    BoilerplateScene2Callback callback;
+    void* context;
+};
+
+typedef struct {
+    int screen_text;
+} BoilerplateScene2Model;
+
+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(
+    BoilerplateScene2* instance,
+    BoilerplateScene2Callback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void hex_viewer_scene_2_draw(Canvas* canvas, BoilerplateScene2Model* 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(BoilerplateScene2Model* const model) {
+    model->screen_text = 0;
+}
+
+bool hex_viewer_scene_2_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    BoilerplateScene2* instance = context;
+    if (event->type == InputTypeRelease) {
+        switch(event->key) {
+            case InputKeyBack:
+                with_view_model(
+                    instance->view,
+                    BoilerplateScene2Model * model,
+                    {
+                        UNUSED(model);
+                        hex_viewer_stop_all_sound(instance->context);
+                        instance->callback(BoilerplateCustomEventScene2Back, instance->context);
+                        hex_viewer_play_long_bump(instance->context);
+                    },
+                    true);
+                break;
+            case InputKeyUp:
+                with_view_model(
+                    instance->view,
+                    BoilerplateScene2Model * 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,
+                    BoilerplateScene2Model * 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,
+                    BoilerplateScene2Model * 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,
+                    BoilerplateScene2Model * 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,
+                    BoilerplateScene2Model * 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,
+                    BoilerplateScene2Model * 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,
+                    BoilerplateScene2Model * 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,
+                    BoilerplateScene2Model * 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,
+                    BoilerplateScene2Model * 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,
+                    BoilerplateScene2Model * 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);
+    Boilerplate* 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);
+}
+
+BoilerplateScene2* hex_viewer_scene_2_alloc() {
+    BoilerplateScene2* instance = malloc(sizeof(BoilerplateScene2));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(BoilerplateScene2Model));
+    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,
+        BoilerplateScene2Model * model,
+        {
+            hex_viewer_scene_2_model_init(model);
+        },
+        true);
+    
+    return instance;
+}
+
+void hex_viewer_scene_2_free(BoilerplateScene2* instance) {
+    furi_assert(instance);
+
+
+    view_free(instance->view);
+    free(instance);
+}
+
+View* hex_viewer_scene_2_get_view(BoilerplateScene2* instance) {
+    furi_assert(instance);
+
+
+    return instance->view;
+}
+

+ 19 - 0
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 BoilerplateScene2 BoilerplateScene2;
+
+typedef void (*BoilerplateScene2Callback)(BoilerplateCustomEvent event, void* context);
+
+void hex_viewer_scene_2_set_callback(
+    BoilerplateScene2* instance,
+    BoilerplateScene2Callback callback,
+    void * context);
+
+BoilerplateScene2* hex_viewer_scene_2_alloc();
+
+void hex_viewer_scene_2_free(BoilerplateScene2* hex_viewer_static);
+
+View* hex_viewer_scene_2_get_view(BoilerplateScene2* boilerpate_static);

+ 137 - 0
views/hex_viewer_startscreen.c

@@ -0,0 +1,137 @@
+#include "../hex_viewer.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+
+struct BoilerplateStartscreen {
+    View* view;
+    BoilerplateStartscreenCallback callback;
+    void* context;
+};
+
+
+typedef struct {
+    int some_value;
+} BoilerplateStartscreenModel;
+
+void hex_viewer_startscreen_set_callback(
+    BoilerplateStartscreen* instance,
+    BoilerplateStartscreenCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void hex_viewer_startscreen_draw(Canvas* canvas, BoilerplateStartscreenModel* model) {
+    UNUSED(model);
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignTop, "Start Screen"); 
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str_aligned(canvas, 64, 22, AlignCenter, AlignTop, "Explain your app"); 
+    canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "on this screen");
+    elements_button_center(canvas, "Start"); 
+}
+
+static void hex_viewer_startscreen_model_init(BoilerplateStartscreenModel* const model) {
+    model->some_value = 1;
+}
+
+bool hex_viewer_startscreen_input(InputEvent* event, void* context) {
+    furi_assert(context); 
+    BoilerplateStartscreen* instance = context;
+    if (event->type == InputTypeRelease) {
+        switch(event->key) {
+            case InputKeyBack:
+                with_view_model(
+                    instance->view,
+                    BoilerplateStartscreenModel * model,
+                    {
+                        UNUSED(model);
+                        instance->callback(BoilerplateCustomEventStartscreenBack, instance->context);
+                    },
+                    true);
+                break;
+            case InputKeyLeft:
+            case InputKeyRight:
+            case InputKeyUp:
+            case InputKeyDown:
+            case InputKeyOk:
+                with_view_model(
+                    instance->view,
+                    BoilerplateStartscreenModel* model,
+                    {
+                        UNUSED(model);
+                        instance->callback(BoilerplateCustomEventStartscreenOk, instance->context);
+                    },
+                    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);
+    BoilerplateStartscreen* instance = (BoilerplateStartscreen*)context;
+    with_view_model(
+        instance->view,
+        BoilerplateStartscreenModel * model,
+        {
+            hex_viewer_startscreen_model_init(model);
+        },
+        true
+    );
+}
+
+BoilerplateStartscreen* hex_viewer_startscreen_alloc() {
+    BoilerplateStartscreen* instance = malloc(sizeof(BoilerplateStartscreen));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(BoilerplateStartscreenModel));
+    view_set_context(instance->view, instance); // furi_assert crashes in events without this
+    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,
+        BoilerplateStartscreenModel * model,
+        {
+            hex_viewer_startscreen_model_init(model);
+        },
+        true
+    );
+    
+    return instance;
+}
+
+void hex_viewer_startscreen_free(BoilerplateStartscreen* instance) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view,
+        BoilerplateStartscreenModel * model,
+        {
+            UNUSED(model);
+        },
+        true);
+    view_free(instance->view);
+    free(instance);
+}
+
+View* hex_viewer_startscreen_get_view(BoilerplateStartscreen* instance) {
+    furi_assert(instance);
+    return instance->view;
+}
+

+ 19 - 0
views/hex_viewer_startscreen.h

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