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

+ 47 - 0
helpers/hex_viewer_storage.c

@@ -117,4 +117,51 @@ void hex_viewer_read_settings(void* context) {
 
     hex_viewer_close_config_file(fff_file);
     hex_viewer_close_storage();
+}
+
+
+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;
+}
+
+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;
 }

+ 5 - 1
helpers/hex_viewer_storage.h

@@ -15,4 +15,8 @@
 #define HEX_VIEWER_SETTINGS_KEY_SAVE_SETTINGS "SaveSettings"
 
 void hex_viewer_save_settings(void* context);
-void hex_viewer_read_settings(void* context);
+void hex_viewer_read_settings(void* context);
+
+
+bool hex_viewer_open_file(HexViewer* hex_viewer, const char* file_path);
+bool hex_viewer_read_file(HexViewer* hex_viewer);

+ 6 - 0
hex_viewer.c

@@ -1,5 +1,6 @@
 #include "hex_viewer.h"
 
+
 bool hex_viewer_custom_event_callback(void* context, uint32_t event) {
     furi_assert(context);
     HexViewer* app = context;
@@ -22,6 +23,7 @@ bool hex_viewer_navigation_event_callback(void* context) {
 HexViewer* hex_viewer_app_alloc() {
     HexViewer* app = malloc(sizeof(HexViewer));
     app->gui = furi_record_open(RECORD_GUI);
+    app->storage = furi_record_open(RECORD_STORAGE);
     app->notification = furi_record_open(RECORD_NOTIFICATION);
     
     //Turn backlight on, believe me this makes testing your app easier
@@ -71,6 +73,8 @@ HexViewer* hex_viewer_app_alloc() {
 
 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);
@@ -83,8 +87,10 @@ void hex_viewer_app_free(HexViewer* app) {
     submenu_free(app->submenu);
 
     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;
 

+ 30 - 2
hex_viewer.h

@@ -20,13 +20,41 @@
 #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 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_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;
+
+
+// TODO Clean
+typedef struct {
+    HexViewerModel* model;
+    FuriMutex** mutex; // TODO Don't need?
+
     Gui* gui;
+    Storage* storage;
     NotificationApp* notification;
     ViewDispatcher* view_dispatcher;
     Submenu* submenu;

+ 27 - 25
scenes/hex_viewer_scene_menu.c

@@ -2,11 +2,11 @@
 
 enum SubmenuIndex {
     SubmenuIndexScene1 = 10,
-    SubmenuIndexScene2,
-    SubmenuIndexScene3,
+    // SubmenuIndexScene2,
+    // SubmenuIndexScene3,
     SubmenuIndexScene4,
-    SubmenuIndexScene5,
-    SubmenuIndexSettings,
+    // SubmenuIndexScene5,
+    // SubmenuIndexSettings,
 };
 
 void hex_viewer_scene_menu_submenu_callback(void* context, uint32_t index) {
@@ -17,11 +17,11 @@ void hex_viewer_scene_menu_submenu_callback(void* context, uint32_t index) {
 void hex_viewer_scene_menu_on_enter(void* context) {
     HexViewer* 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_add_item(app->submenu, "Open ...", 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, "Go to ...", SubmenuIndexScene1, 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));
 
@@ -33,8 +33,10 @@ bool hex_viewer_scene_menu_on_event(void* context, SceneManagerEvent event) {
     UNUSED(app);
     if(event.type == SceneManagerEventTypeBack) {
         //exit app
-        scene_manager_stop(app->scene_manager);
-        view_dispatcher_stop(app->view_dispatcher);
+        // TODO Check Return to main view
+        // 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) {
@@ -42,24 +44,24 @@ bool hex_viewer_scene_menu_on_event(void* context, SceneManagerEvent event) {
                 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 == 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;
+        // } 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;

+ 26 - 12
scenes/hex_viewer_scene_scene_4.c

@@ -1,24 +1,38 @@
 #include "../hex_viewer.h"
 
+
 void hex_viewer_scene_scene_4_on_enter(void* context) {
     furi_assert(context);
     HexViewer* 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);
+    // 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
+    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) {

+ 27 - 2
scenes/hex_viewer_scene_startscreen.c

@@ -22,16 +22,41 @@ bool hex_viewer_scene_startscreen_on_event(void* context, SceneManagerEvent even
     if(event.type == SceneManagerEventTypeCustom) {
         switch(event.event) {
             case HexViewerCustomEventStartscreenLeft:
+                app->model->mode = !hex_viewer->model->mode;
+                consumed = true;
+                break;
             case HexViewerCustomEventStartscreenRight:
+                // TODO Dialog
+                consumed = true;
                 break;
             case HexViewerCustomEventStartscreenUp:
+                //furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
+                if(app->model->file_offset > 0) {
+                    app->model->file_offset -= HEX_VIEWER_BYTES_PER_LINE;
+                    if(!hex_viewer_read_file(app)) break;
+                }
+                consumed = true;
+                //furi_mutex_release(hex_viewer->mutex);
+                break;
             case HexViewerCustomEventStartscreenDown:
+                //furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
+                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
+                }
+                consumed = true;
+                //furi_mutex_release(hex_viewer->mutex);
                 break;
             case HexViewerCustomEventStartscreenOk:
-                scene_manager_next_scene(app->scene_manager, HexViewerSceneMenu);
+                if (!app->model->file_size) // TODO
+                    scene_manager_next_scene(app->scene_manager, HexViewerSceneScene_4);
+                else scene_manager_next_scene(app->scene_manager, HexViewerSceneMenu);
                 consumed = true;
                 break;
-            case HexViewerCustomEventStartscreenBack:
+            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);

+ 104 - 7
views/hex_viewer_startscreen.c

@@ -28,13 +28,74 @@ void hex_viewer_startscreen_set_callback(
 void hex_viewer_startscreen_draw(Canvas* canvas, HexViewerStartscreenModel* 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"); 
+
+    if (!app->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, app->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 = 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 first_line_on_screen = app->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 = app->model->file_read_bytes / HEX_VIEWER_BYTES_PER_LINE;
+        if(app->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 =
+                app->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(app->model->mode) {
+                memcpy(temp_buf, app->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 = app->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 ", app->model->file_bytes[i][j]);
+
+            canvas_set_font(canvas, FontKeyboard);
+            canvas_draw_str(canvas, LEFT_OFFSET + 41, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
+        }
+    }
+    
 }
 
 static void hex_viewer_startscreen_model_init(HexViewerStartscreenModel* const model) {
@@ -57,9 +118,45 @@ bool hex_viewer_startscreen_input(InputEvent* event, void* context) {
                     true);
                 break;
             case InputKeyLeft:
+                with_view_model(
+                        instance->view,
+                        HexViewerStartscreenModel * model,
+                        {
+                            UNUSED(model);
+                            instance->callback(HexViewerCustomEventStartscreenLeft, instance->context);
+                        },
+                        true);
+                    break;
             case InputKeyRight:
+                with_view_model(
+                    instance->view,
+                    HexViewerStartscreenModel * model,
+                    {
+                        UNUSED(model);
+                        instance->callback(HexViewerCustomEventStartscreenRight, instance->context);
+                    },
+                    true);
+                break;
             case InputKeyUp:
+                with_view_model(
+                    instance->view,
+                    HexViewerStartscreenModel * model,
+                    {
+                        UNUSED(model);
+                        instance->callback(HexViewerCustomEventStartscreenUp, instance->context);
+                    },
+                    true);
+                break;
             case InputKeyDown:
+                with_view_model(
+                    instance->view,
+                    HexViewerStartscreenModel * model,
+                    {
+                        UNUSED(model);
+                        instance->callback(HexViewerCustomEventStartscreenDown, instance->context);
+                    },
+                    true);
+                break;
             case InputKeyOk:
                 with_view_model(
                     instance->view,