Przeglądaj źródła

[FL-1401] Add Universal TV remote (#539)

* Remove excess headers
* Add ButtonPanel
* Add Popup
* Move FileReader to standalone object
* Universal remote (part 1)
* Universal remote (part 2)
* Global rename tranciever/file_parser
* Compile assets
* syntax fix
* English: rename tranceiver to transceiver.

Co-authored-by: あく <alleteam@gmail.com>
Albert Kharisov 4 lat temu
rodzic
commit
c583cce5bd
51 zmienionych plików z 1388 dodań i 311 usunięć
  1. 36 0
      applications/gui/elements.c
  2. 16 1
      applications/gui/elements.h
  3. 409 0
      applications/gui/modules/button_panel.c
  4. 129 0
      applications/gui/modules/button_panel.h
  5. 77 0
      applications/irda/irda-app-brute-force.cpp
  6. 38 0
      applications/irda/irda-app-brute-force.hpp
  7. 2 1
      applications/irda/irda-app-event.hpp
  8. 58 0
      applications/irda/irda-app-file-parser.cpp
  9. 17 0
      applications/irda/irda-app-file-parser.hpp
  10. 43 125
      applications/irda/irda-app-remote-manager.cpp
  11. 0 12
      applications/irda/irda-app-remote-manager.hpp
  12. 9 9
      applications/irda/irda-app-transceiver.cpp
  13. 5 4
      applications/irda/irda-app-transceiver.hpp
  14. 17 3
      applications/irda/irda-app-view-manager.cpp
  15. 7 0
      applications/irda/irda-app-view-manager.hpp
  16. 2 4
      applications/irda/irda-app.cpp
  17. 6 4
      applications/irda/irda-app.hpp
  18. 0 1
      applications/irda/scene/irda-app-scene-edit-delete.cpp
  19. 0 1
      applications/irda/scene/irda-app-scene-edit-rename.cpp
  20. 0 2
      applications/irda/scene/irda-app-scene-learn-done-after.cpp
  21. 0 2
      applications/irda/scene/irda-app-scene-learn-done.cpp
  22. 6 9
      applications/irda/scene/irda-app-scene-learn-enter-name.cpp
  23. 4 6
      applications/irda/scene/irda-app-scene-learn-success.cpp
  24. 2 2
      applications/irda/scene/irda-app-scene-learn.cpp
  25. 1 1
      applications/irda/scene/irda-app-scene-remote.cpp
  26. 98 0
      applications/irda/scene/irda-app-scene-universal-common.cpp
  27. 61 0
      applications/irda/scene/irda-app-scene-universal-tv.cpp
  28. 1 1
      applications/irda/scene/irda-app-scene-universal.cpp
  29. 31 2
      applications/irda/scene/irda-app-scene.hpp
  30. 82 0
      applications/irda/view/irda-app-brut-view.c
  31. 18 0
      applications/irda/view/irda-app-brut-view.h
  32. 16 21
      assets/compiled/assets_icons.c
  33. 114 100
      assets/compiled/assets_icons.h
  34. BIN
      assets/icons/Irda/Back_15x10.png
  35. BIN
      assets/icons/Irda/Down_25x27.png
  36. BIN
      assets/icons/Irda/Down_hvr_25x27.png
  37. BIN
      assets/icons/Irda/Fill-marker_7x7.png
  38. BIN
      assets/icons/Irda/Mute_25x27.png
  39. BIN
      assets/icons/Irda/Mute_hvr_25x27.png
  40. BIN
      assets/icons/Irda/Power_25x27.png
  41. BIN
      assets/icons/Irda/Power_hvr_25x27.png
  42. BIN
      assets/icons/Irda/Up_25x27.png
  43. BIN
      assets/icons/Irda/Up_hvr_25x27.png
  44. BIN
      assets/icons/Irda/Vol_down_25x27.png
  45. BIN
      assets/icons/Irda/Vol_down_hvr_25x27.png
  46. BIN
      assets/icons/Irda/Vol_up_25x27.png
  47. BIN
      assets/icons/Irda/Vol_up_hvr_25x27.png
  48. 39 0
      lib/file_reader/file_reader.cpp
  49. 39 0
      lib/file_reader/file_reader.hpp
  50. 1 0
      lib/irda/samsung/irda_encoder_samsung.c
  51. 4 0
      lib/lib.mk

+ 36 - 0
applications/gui/elements.c

@@ -8,6 +8,42 @@
 #include <string.h>
 #include <stdint.h>
 
+void elements_progress_bar(
+    Canvas* canvas,
+    uint8_t x,
+    uint8_t y,
+    uint8_t width,
+    uint8_t progress,
+    uint8_t total) {
+    furi_assert(canvas);
+    furi_assert(total > 0);
+    uint8_t height = 9;
+    uint8_t marker_width = 7;
+    furi_assert(width > marker_width);
+
+    uint8_t progress_length = ((float)progress / total) * (width - marker_width - 2);
+
+    // rframe doesnt work if (radius * 2) > any rect side, so write manually
+    uint8_t x_max = x + width - 1;
+    uint8_t y_max = y + height - 1;
+    canvas_draw_line(canvas, x + 3, y, x_max - 3, y);
+    canvas_draw_line(canvas, x_max - 3, y, x_max, y + 3);
+    canvas_draw_line(canvas, x_max, y + 3, x_max, y_max - 3);
+    canvas_draw_line(canvas, x_max, y_max - 3, x_max - 3, y_max);
+    canvas_draw_line(canvas, x_max - 3, y_max, x + 3, y_max);
+    canvas_draw_line(canvas, x + 3, y_max, x, y_max - 3);
+    canvas_draw_line(canvas, x, y_max - 3, x, y + 3);
+    canvas_draw_line(canvas, x, y + 3, x + 3, y);
+
+    canvas_draw_rbox(canvas, x + 1, y + 1, marker_width + progress_length, height - 2, 3);
+    canvas_invert_color(canvas);
+    canvas_draw_dot(canvas, x + progress_length + 3, y + 2);
+    canvas_draw_dot(canvas, x + progress_length + 4, y + 2);
+    canvas_draw_dot(canvas, x + progress_length + 5, y + 3);
+    canvas_draw_dot(canvas, x + progress_length + 6, y + 4);
+    canvas_invert_color(canvas);
+}
+
 void elements_scrollbar_pos(
     Canvas* canvas,
     uint8_t x,

+ 16 - 1
applications/gui/elements.h

@@ -8,6 +8,22 @@
 extern "C" {
 #endif
 
+/*
+ * Draw progress bar.
+ * @param x - progress bar position on X axis
+ * @param y - progress bar position on Y axis
+ * @param width - progress bar width
+ * @param progress - progress in unnamed metric
+ * @param total - total amount in unnamed metric
+ */
+void elements_progress_bar(
+    Canvas* canvas,
+    uint8_t x,
+    uint8_t y,
+    uint8_t width,
+    uint8_t progress,
+    uint8_t total);
+
 /*
  * Draw scrollbar on canvas at specific position.
  * @param x - scrollbar position on X axis
@@ -16,7 +32,6 @@ extern "C" {
  * @param pos - current element 
  * @param total - total elements
  */
-
 void elements_scrollbar_pos(
     Canvas* canvas,
     uint8_t x,

+ 409 - 0
applications/gui/modules/button_panel.c

@@ -0,0 +1,409 @@
+#include "button_panel.h"
+#include "api-hal-resources.h"
+#include "gui/canvas.h"
+#include <m-array.h>
+#include <m-i-list.h>
+#include <m-list.h>
+#include <furi.h>
+#include <gui/elements.h>
+#include <stdint.h>
+
+typedef struct {
+    // uint16_t to support multi-screen, wide button panel
+    uint16_t x;
+    uint16_t y;
+    Font font;
+    const char* str;
+} LabelElement;
+
+LIST_DEF(LabelList, LabelElement, M_POD_OPLIST)
+#define M_OPL_LabelList_t() LIST_OPLIST(LabelList)
+
+typedef struct {
+    uint16_t x;
+    uint16_t y;
+    IconName name;
+    IconName name_selected;
+} IconElement;
+
+typedef struct ButtonItem {
+    uint32_t index;
+    ButtonItemCallback callback;
+    IconElement icon;
+    void* callback_context;
+} ButtonItem;
+
+ARRAY_DEF(ButtonArray, ButtonItem*, M_PTR_OPLIST);
+#define M_OPL_ButtonArray_t() ARRAY_OPLIST(ButtonArray, M_PTR_OPLIST)
+ARRAY_DEF(ButtonMatrix, ButtonArray_t);
+#define M_OPL_ButtonMatrix_t() ARRAY_OPLIST(ButtonMatrix, M_OPL_ButtonArray_t())
+
+struct ButtonPanel {
+    View* view;
+    ButtonPanelInputCallback input_callback;
+    void* input_context;
+};
+
+typedef struct {
+    ButtonMatrix_t button_matrix;
+    LabelList_t labels;
+    uint16_t reserve_x;
+    uint16_t reserve_y;
+    uint16_t selected_item_x;
+    uint16_t selected_item_y;
+    ButtonPanelDrawCallback draw_callback;
+    void* draw_context;
+} ButtonPanelModel;
+
+static ButtonItem** button_panel_get_item(ButtonPanelModel* model, size_t x, size_t y);
+static void button_panel_process_up(ButtonPanel* button_panel);
+static void button_panel_process_down(ButtonPanel* button_panel);
+static void button_panel_process_left(ButtonPanel* button_panel);
+static void button_panel_process_right(ButtonPanel* button_panel);
+static void button_panel_process_ok(ButtonPanel* button_panel);
+static void button_panel_view_draw_callback(Canvas* canvas, void* _model);
+static bool button_panel_view_input_callback(InputEvent* event, void* context);
+
+ButtonPanel* button_panel_alloc() {
+    ButtonPanel* button_panel = furi_alloc(sizeof(ButtonPanel));
+    button_panel->view = view_alloc();
+    view_set_orientation(button_panel->view, ViewOrientationVertical);
+    view_set_context(button_panel->view, button_panel);
+    view_allocate_model(button_panel->view, ViewModelTypeLocking, sizeof(ButtonPanelModel));
+    view_set_draw_callback(button_panel->view, button_panel_view_draw_callback);
+    view_set_input_callback(button_panel->view, button_panel_view_input_callback);
+    button_panel->input_callback = NULL;
+
+    with_view_model(
+        button_panel->view, (ButtonPanelModel * model) {
+            model->reserve_x = 0;
+            model->reserve_y = 0;
+            model->selected_item_x = 0;
+            model->selected_item_y = 0;
+            model->draw_callback = NULL;
+            ButtonMatrix_init(model->button_matrix);
+            LabelList_init(model->labels);
+            return true;
+        });
+
+    return button_panel;
+}
+
+void button_panel_reserve(ButtonPanel* button_panel, size_t reserve_x, size_t reserve_y) {
+    furi_check(reserve_x > 0);
+    furi_check(reserve_y > 0);
+
+    with_view_model(
+        button_panel->view, (ButtonPanelModel * model) {
+            model->reserve_x = reserve_x;
+            model->reserve_y = reserve_y;
+            ButtonMatrix_reserve(model->button_matrix, model->reserve_y);
+            for(size_t i = 0; i > model->reserve_y; ++i) {
+                ButtonArray_t* array = ButtonMatrix_get(model->button_matrix, i);
+                ButtonArray_init(*array);
+                ButtonArray_reserve(*array, reserve_x);
+                // TODO: do we need to clear allocated memory of ptr-s to ButtonItem ??
+            }
+            LabelList_init(model->labels);
+            return true;
+        });
+}
+
+void button_panel_free(ButtonPanel* button_panel) {
+    furi_assert(button_panel);
+
+    button_panel_clean(button_panel);
+
+    with_view_model(
+        button_panel->view, (ButtonPanelModel * model) {
+            LabelList_clear(model->labels);
+            ButtonMatrix_clear(model->button_matrix);
+            return true;
+        });
+
+    view_free(button_panel->view);
+    free(button_panel);
+}
+
+void button_panel_clean(ButtonPanel* button_panel) {
+    furi_assert(button_panel);
+
+    with_view_model(
+        button_panel->view, (ButtonPanelModel * model) {
+            for(size_t x = 0; x < model->reserve_x; ++x) {
+                for(size_t y = 0; y < model->reserve_y; ++y) {
+                    ButtonItem** button_item = button_panel_get_item(model, x, y);
+                    free(*button_item);
+                    *button_item = NULL;
+                }
+            }
+            return true;
+        });
+}
+
+static ButtonItem** button_panel_get_item(ButtonPanelModel* model, size_t x, size_t y) {
+    furi_assert(model);
+
+    furi_check(x < model->reserve_x);
+    furi_check(y < model->reserve_y);
+    ButtonArray_t* button_array = ButtonMatrix_get_at(model->button_matrix, x);
+    ButtonItem** button_item = ButtonArray_get_at(*button_array, y);
+    return button_item;
+}
+
+void button_panel_add_item(
+    ButtonPanel* button_panel,
+    uint32_t index,
+    uint16_t matrix_place_x,
+    uint16_t matrix_place_y,
+    uint16_t x,
+    uint16_t y,
+    IconName icon_name,
+    IconName icon_name_selected,
+    ButtonItemCallback callback,
+    void* callback_context) {
+    furi_assert(button_panel);
+
+    with_view_model(
+        button_panel->view, (ButtonPanelModel * model) {
+            ButtonItem** button_item_ptr =
+                button_panel_get_item(model, matrix_place_x, matrix_place_y);
+            furi_check(*button_item_ptr == NULL);
+            *button_item_ptr = furi_alloc(sizeof(ButtonItem));
+            ButtonItem* button_item = *button_item_ptr;
+            button_item->callback = callback;
+            button_item->callback_context = callback_context;
+            button_item->icon.x = x;
+            button_item->icon.y = y;
+            button_item->icon.name = icon_name;
+            button_item->icon.name_selected = icon_name_selected;
+            button_item->index = index;
+            return true;
+        });
+}
+
+View* button_panel_get_view(ButtonPanel* button_panel) {
+    furi_assert(button_panel);
+    return button_panel->view;
+}
+
+static void button_panel_view_draw_callback(Canvas* canvas, void* _model) {
+    furi_assert(canvas);
+    furi_assert(_model);
+
+    ButtonPanelModel* model = _model;
+
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+
+    for(size_t x = 0; x < model->reserve_x; ++x) {
+        for(size_t y = 0; y < model->reserve_y; ++y) {
+            ButtonItem* button_item = *button_panel_get_item(model, x, y);
+            IconName icon_name = button_item->icon.name;
+            if((model->selected_item_x == x) && (model->selected_item_y == y)) {
+                icon_name = button_item->icon.name_selected;
+            }
+            canvas_draw_icon_name(canvas, button_item->icon.x, button_item->icon.y, icon_name);
+        }
+    }
+
+    for
+        M_EACH(label, model->labels, LabelList_t) {
+            canvas_set_font(canvas, label->font);
+            canvas_draw_str(canvas, label->x, label->y, label->str);
+        }
+
+    if(model->draw_callback) model->draw_callback(canvas, model->draw_context);
+}
+
+static void button_panel_process_down(ButtonPanel* button_panel) {
+    with_view_model(
+        button_panel->view, (ButtonPanelModel * model) {
+            size_t new_selected_item_x = model->selected_item_x;
+            size_t new_selected_item_y = model->selected_item_y;
+            size_t i;
+
+            if(new_selected_item_y >= (model->reserve_y - 1)) return false;
+
+            ++new_selected_item_y;
+
+            for(i = 0; i < model->reserve_x; ++i) {
+                new_selected_item_x = (model->selected_item_x + i) % model->reserve_x;
+                if(*button_panel_get_item(model, new_selected_item_x, new_selected_item_y)) {
+                    break;
+                }
+            }
+            if(i == model->reserve_x) return false;
+
+            model->selected_item_x = new_selected_item_x;
+            model->selected_item_y = new_selected_item_y;
+
+            return true;
+        });
+}
+
+static void button_panel_process_up(ButtonPanel* button_panel) {
+    with_view_model(
+        button_panel->view, (ButtonPanelModel * model) {
+            size_t new_selected_item_x = model->selected_item_x;
+            size_t new_selected_item_y = model->selected_item_y;
+            size_t i;
+
+            if(new_selected_item_y <= 0) return false;
+
+            --new_selected_item_y;
+
+            for(i = 0; i < model->reserve_x; ++i) {
+                new_selected_item_x = (model->selected_item_x + i) % model->reserve_x;
+                if(*button_panel_get_item(model, new_selected_item_x, new_selected_item_y)) {
+                    break;
+                }
+            }
+            if(i == model->reserve_x) return false;
+
+            model->selected_item_x = new_selected_item_x;
+            model->selected_item_y = new_selected_item_y;
+            return true;
+        });
+}
+
+static void button_panel_process_left(ButtonPanel* button_panel) {
+    with_view_model(
+        button_panel->view, (ButtonPanelModel * model) {
+            size_t new_selected_item_x = model->selected_item_x;
+            size_t new_selected_item_y = model->selected_item_y;
+            size_t i;
+
+            if(new_selected_item_x <= 0) return false;
+
+            --new_selected_item_x;
+
+            for(i = 0; i < model->reserve_y; ++i) {
+                new_selected_item_y = (model->selected_item_y + i) % model->reserve_y;
+                if(*button_panel_get_item(model, new_selected_item_x, new_selected_item_y)) {
+                    break;
+                }
+            }
+            if(i == model->reserve_y) return false;
+
+            model->selected_item_x = new_selected_item_x;
+            model->selected_item_y = new_selected_item_y;
+            return true;
+        });
+}
+
+static void button_panel_process_right(ButtonPanel* button_panel) {
+    with_view_model(
+        button_panel->view, (ButtonPanelModel * model) {
+            size_t new_selected_item_x = model->selected_item_x;
+            size_t new_selected_item_y = model->selected_item_y;
+            size_t i;
+
+            if(new_selected_item_x >= (model->reserve_x - 1)) return false;
+
+            ++new_selected_item_x;
+
+            for(i = 0; i < model->reserve_y; ++i) {
+                new_selected_item_y = (model->selected_item_y + i) % model->reserve_y;
+                if(*button_panel_get_item(model, new_selected_item_x, new_selected_item_y)) {
+                    break;
+                }
+            }
+            if(i == model->reserve_y) return false;
+
+            model->selected_item_x = new_selected_item_x;
+            model->selected_item_y = new_selected_item_y;
+            return true;
+        });
+}
+
+void button_panel_process_ok(ButtonPanel* button_panel) {
+    ButtonItem* button_item = NULL;
+
+    with_view_model(
+        button_panel->view, (ButtonPanelModel * model) {
+            button_item =
+                *button_panel_get_item(model, model->selected_item_x, model->selected_item_y);
+            return true;
+        });
+
+    if(button_item && button_item->callback) {
+        button_item->callback(button_item->callback_context, button_item->index);
+    }
+}
+
+static bool button_panel_view_input_callback(InputEvent* event, void* context) {
+    ButtonPanel* button_panel = context;
+    furi_assert(button_panel);
+    bool consumed = false;
+
+    if(button_panel->input_callback) {
+        consumed = button_panel->input_callback(event, button_panel->input_context);
+    } else if(event->type == InputTypeShort) {
+        switch(event->key) {
+        case InputKeyUp:
+            consumed = true;
+            button_panel_process_up(button_panel);
+            break;
+        case InputKeyDown:
+            consumed = true;
+            button_panel_process_down(button_panel);
+            break;
+        case InputKeyLeft:
+            consumed = true;
+            button_panel_process_left(button_panel);
+            break;
+        case InputKeyRight:
+            consumed = true;
+            button_panel_process_right(button_panel);
+            break;
+        case InputKeyOk:
+            consumed = true;
+            button_panel_process_ok(button_panel);
+            break;
+        default:
+            break;
+        }
+    }
+
+    return consumed;
+}
+
+void button_panel_add_label(
+    ButtonPanel* button_panel,
+    uint16_t x,
+    uint16_t y,
+    Font font,
+    const char* label_str) {
+    furi_assert(button_panel);
+
+    with_view_model(
+        button_panel->view, (ButtonPanelModel * model) {
+            LabelElement* label = LabelList_push_raw(model->labels);
+            label->x = x;
+            label->y = y;
+            label->font = font;
+            label->str = label_str;
+            return true;
+        });
+}
+
+void button_panel_set_popup_draw_callback(
+    ButtonPanel* button_panel,
+    ButtonPanelDrawCallback callback,
+    void* context) {
+    with_view_model(
+        button_panel->view, (ButtonPanelModel * model) {
+            model->draw_callback = callback;
+            model->draw_context = context;
+            return true;
+        });
+}
+
+void button_panel_set_popup_input_callback(
+    ButtonPanel* button_panel,
+    ButtonPanelInputCallback callback,
+    void* context) {
+    button_panel->input_context = context;
+    button_panel->input_callback = callback;
+}

+ 129 - 0
applications/gui/modules/button_panel.h

@@ -0,0 +1,129 @@
+#pragma once
+#include <gui/view.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Button panel module descriptor */
+typedef struct ButtonPanel ButtonPanel;
+
+/** Callback type to call for handling selecting button_panel items */
+typedef void (*ButtonItemCallback)(void* context, uint32_t index);
+/** Callback type for additional drawings above main button_panel screen */
+typedef void (*ButtonPanelDrawCallback)(Canvas* canvas, void* _model);
+/** Callback type to intercept input events of button_panel */
+typedef bool (*ButtonPanelInputCallback)(InputEvent* event, void* context);
+
+/** Allocate new button_panel module.
+ *
+ * @return  just-created module
+ */
+ButtonPanel* button_panel_alloc(void);
+
+/** Free button_panel module.
+ *
+ * @param   button_panel - module to free
+ */
+void button_panel_free(ButtonPanel* button_panel);
+
+/** Free items from button_panel module. Preallocated matrix stays unchanged.
+ *
+ * @param   button_panel - module to clean
+ */
+void button_panel_clean(ButtonPanel* button_panel);
+
+/** Reserve space for adding items.
+ *
+ * One does not simply use button_panel_add_item() without this function.
+ * It should be allocated space for it first.
+ *
+ * @param   button_panel - module to modify
+ * @param   reserve_x - number of columns in button_panel
+ * @param   reserve_y - number of rows in button_panel
+ */
+void button_panel_reserve(ButtonPanel* button_panel, size_t reserve_x, size_t reserve_y);
+
+/** Add item to button_panel module.
+ *
+ * Have to set element in bounds of allocated size by X and by Y.
+ *
+ * @param   button_panel - module
+ * @param   index - value to pass to callback
+ * @param   matrix_place_x - coordinates by x-axis on virtual grid, it
+ *                           is only used for naviagation
+ * @param   matrix_place_y - coordinates by y-axis on virtual grid, it
+ *                           is only used for naviagation
+ * @param   x - x-coordinate to draw icon on
+ * @param   y - y-coordinate to draw icon on
+ * @param   icon_name - name of the icon to draw
+ * @param   icon_name_selected - name of the icon to draw when current
+ *                               element is selected
+ * @param   callback - function to call when specific element is selected
+ *                     (pressed Ok on selected item)
+ * @param   callback_context - context to pass to callback
+ */
+void button_panel_add_item(
+    ButtonPanel* button_panel,
+    uint32_t index,
+    uint16_t matrix_place_x,
+    uint16_t matrix_place_y,
+    uint16_t x,
+    uint16_t y,
+    IconName icon_name,
+    IconName icon_name_selected,
+    ButtonItemCallback callback,
+    void* callback_context);
+
+/** Get button_panel view.
+ *
+ * @param   button_panel - module to get view from
+ * @return  acquired view
+ */
+View* button_panel_get_view(ButtonPanel* button_panel);
+
+/** Add label to button_panel module.
+ *
+ * @param   x - x-coordinate to place label
+ * @param   y - y-coordinate to place label
+ * @param   font - font to write label with
+ * @param   label_str - string label to write
+ */
+void button_panel_add_label(
+    ButtonPanel* button_panel,
+    uint16_t x,
+    uint16_t y,
+    Font font,
+    const char* label_str);
+
+// TODO: [FL-1445] Have to replace callbacks above with additional popup-layer
+/** Set popup draw callback for button_panel module.
+ *
+ * Used to add popup drawings after main draw callback is done.
+ *
+ * @param   button_panel - module to modify
+ * @param   callback - callback function to set for draw event
+ * @param   context - context to pass to callback
+ */
+void button_panel_set_popup_draw_callback(
+    ButtonPanel* button_panel,
+    ButtonPanelDrawCallback callback,
+    void* context);
+
+/** Set popup input callback for button_panel module.
+ *
+ * Used to add popup input callback. It will intercept all input
+ * events for current view.
+ *
+ * @param   button_panel - module to modify
+ * @param   callback - function to overwrite main input callbacks
+ * @param   context - context to pass to callback
+ */
+void button_panel_set_popup_input_callback(
+    ButtonPanel* button_panel,
+    ButtonPanelInputCallback callback,
+    void* context);
+
+#ifdef __cplusplus
+}
+#endif

+ 77 - 0
applications/irda/irda-app-brute-force.cpp

@@ -0,0 +1,77 @@
+#include "irda-app-brute-force.hpp"
+
+void IrdaAppBruteForce::add_record(int index, const char* name) {
+    records[name].index = index;
+    records[name].amount = 0;
+}
+
+bool IrdaAppBruteForce::calculate_messages() {
+    bool fs_res = false;
+    fs_res = file_parser.get_fs_api().file.open(
+        &file, universal_db_filename, FSAM_READ, FSOM_OPEN_EXISTING);
+    if(!fs_res) {
+        file_parser.get_sd_api().show_error(file_parser.get_sd_api().context, "Can't open file");
+        return false;
+    }
+
+    file_parser.reset();
+    while(1) {
+        auto message = file_parser.read_message(&file);
+        if(!message) break;
+        auto element = records.find(message->name);
+        if(element != records.cend()) {
+            ++element->second.amount;
+        }
+    }
+
+    file_parser.get_fs_api().file.close(&file);
+
+    return true;
+}
+
+void IrdaAppBruteForce::stop_bruteforce() {
+    if(current_record.size()) {
+        file_parser.get_fs_api().file.close(&file);
+        current_record.clear();
+    }
+}
+
+// TODO: [FL-1418] replace with timer-chained consequence of messages.
+bool IrdaAppBruteForce::send_next_bruteforce(const IrdaAppSignalTransceiver& transceiver) {
+    furi_assert(current_record.size());
+
+    std::unique_ptr<IrdaAppFileParser::IrdaFileMessage> message;
+
+    do {
+        message = file_parser.read_message(&file);
+    } while(message && current_record.compare(message->name));
+
+    if(message) {
+        transceiver.send_message(&message->message);
+    }
+    return !!message;
+}
+
+bool IrdaAppBruteForce::start_bruteforce(int index, int& record_amount) {
+    file_parser.reset();
+    for(const auto& it : records) {
+        if(it.second.index == index) {
+            record_amount = it.second.amount;
+            current_record = it.first;
+            break;
+        }
+    }
+
+    if(record_amount) {
+        bool fs_res = file_parser.get_fs_api().file.open(
+            &file, universal_db_filename, FSAM_READ, FSOM_OPEN_EXISTING);
+        if(fs_res) {
+            return true;
+        } else {
+            file_parser.get_sd_api().show_error(
+                file_parser.get_sd_api().context, "Can't open file");
+        }
+    }
+
+    return false;
+}

+ 38 - 0
applications/irda/irda-app-brute-force.hpp

@@ -0,0 +1,38 @@
+#pragma once
+#include "furi/check.h"
+#include <unordered_map>
+#include "irda-app-file-parser.hpp"
+#include "irda-app-transceiver.hpp"
+
+
+class IrdaAppBruteForce {
+    const char* universal_db_filename;
+    IrdaAppFileParser file_parser;
+    File file;
+    std::string current_record;
+
+    typedef struct {
+        int index;
+        int amount;
+    } Record;
+
+    // 'key' is record name, because we have to search by both, index and name,
+    // but index search has place once per button press, and should not be
+    // noticed, but name search should occur during entering universal menu,
+    // and will go through container for every record in file, that's why
+    // more critical to have faster search by record name.
+    std::unordered_map<std::string, Record> records;
+
+public:
+    bool calculate_messages();
+    void stop_bruteforce();
+    bool send_next_bruteforce(const IrdaAppSignalTransceiver& receiver);
+    bool start_bruteforce(int index, int& record_amount);
+    void add_record(int index, const char* name);
+
+    IrdaAppBruteForce(const char* filename) : universal_db_filename (filename) {}
+    ~IrdaAppBruteForce() {
+        stop_bruteforce();
+    }
+};
+

+ 2 - 1
applications/irda/irda-app-event.hpp

@@ -1,5 +1,4 @@
 #pragma once
-#include <stdint.h>
 #include <irda.h>
 #include <gui/modules/dialog_ex.h>
 
@@ -14,6 +13,8 @@ public:
         IrdaMessageReceived,
         TextEditDone,
         PopupTimer,
+        ButtonPanelPressed,
+        ButtonPanelPopupBackPressed,
     };
 
     union {

+ 58 - 0
applications/irda/irda-app-file-parser.cpp

@@ -0,0 +1,58 @@
+#include "irda-app-file-parser.hpp"
+
+std::unique_ptr<IrdaAppFileParser::IrdaFileMessage> IrdaAppFileParser::read_message(File* file) {
+    while(1) {
+        auto str = getline(file);
+        if(str.empty()) return nullptr;
+
+        auto message = parse_message(str);
+        if(message) return message;
+    }
+}
+
+std::unique_ptr<IrdaAppFileParser::IrdaFileMessage>
+IrdaAppFileParser::parse_message(const std::string& str) const {
+    char protocol_name[32];
+    uint32_t address;
+    uint32_t command;
+    auto irda_file_message = std::make_unique<IrdaFileMessage>();
+
+    int parsed = std::sscanf(
+        str.c_str(),
+        "%31s %31s A:%lX C:%lX",
+        irda_file_message->name,
+        protocol_name,
+        &address,
+        &command);
+
+    if(parsed != 4) {
+        return nullptr;
+    }
+
+    IrdaProtocol protocol = irda_get_protocol_by_name(protocol_name);
+
+    if(!irda_is_protocol_valid((IrdaProtocol)protocol)) {
+        return nullptr;
+    }
+
+    int address_length = irda_get_protocol_address_length(protocol);
+    uint32_t address_mask = (1LU << (4 * address_length)) - 1;
+    if(address != (address & address_mask)) {
+        return nullptr;
+    }
+
+    int command_length = irda_get_protocol_command_length(protocol);
+    uint32_t command_mask = (1LU << (4 * command_length)) - 1;
+    if(command != (command & command_mask)) {
+        return nullptr;
+    }
+
+    irda_file_message->message = {
+        .protocol = protocol,
+        .address = address,
+        .command = command,
+        .repeat = false,
+    };
+
+    return irda_file_message;
+}

+ 17 - 0
applications/irda/irda-app-file-parser.hpp

@@ -0,0 +1,17 @@
+#pragma once
+#include "file_reader/file_reader.hpp"
+#include "irda.h"
+
+class IrdaAppFileParser : public FileReader {
+public:
+    typedef struct {
+        char name[32];
+        IrdaMessage message;
+    } IrdaFileMessage;
+
+    std::unique_ptr<IrdaAppFileParser::IrdaFileMessage> read_message(File* file);
+
+private:
+    std::unique_ptr<IrdaFileMessage> parse_message(const std::string& str) const;
+};
+

+ 43 - 125
applications/irda/irda-app-remote-manager.cpp

@@ -4,10 +4,10 @@
 #include "furi/check.h"
 #include "gui/modules/button_menu.h"
 #include "irda.h"
-#include "sys/_stdint.h"
 #include <cstdio>
 #include <string>
 #include <utility>
+#include "irda-app-file-parser.hpp"
 
 const char* IrdaAppRemoteManager::irda_directory = "irda";
 const char* IrdaAppRemoteManager::irda_extension = ".ir";
@@ -33,16 +33,6 @@ find_vacant_name(const std::vector<std::string>& strings, const std::string& nam
     }
 }
 
-IrdaAppRemoteManager::IrdaAppRemoteManager() {
-    sd_ex_api = static_cast<SdCard_Api*>(furi_record_open("sdcard-ex"));
-    fs_api = static_cast<FS_Api*>(furi_record_open("sdcard"));
-}
-
-IrdaAppRemoteManager::~IrdaAppRemoteManager() {
-    furi_record_close("sdcard");
-    furi_record_close("sdcard-ex");
-}
-
 bool IrdaAppRemoteManager::add_button(const char* button_name, const IrdaMessage* message) {
     remote->buttons.emplace_back(button_name, message);
     return store();
@@ -94,10 +84,12 @@ std::string IrdaAppRemoteManager::make_filename(const std::string& name) const {
 
 bool IrdaAppRemoteManager::delete_remote() {
     FS_Error fs_res;
+    IrdaAppFileParser file_parser;
 
-    fs_res = fs_api->common.remove(make_filename(remote->name).c_str());
+    fs_res = file_parser.get_fs_api().common.remove(make_filename(remote->name).c_str());
     if(fs_res != FSE_OK) {
-        show_file_error_message("Error deleting file");
+        file_parser.get_sd_api().show_error(
+            file_parser.get_sd_api().context, "Error deleting file");
         return false;
     }
     remote.reset();
@@ -147,11 +139,13 @@ bool IrdaAppRemoteManager::rename_remote(const char* str) {
     if(!result) return false;
 
     auto new_name = find_vacant_name(remote_list, str);
-    FS_Error fs_err = fs_api->common.rename(
+    IrdaAppFileParser file_parser;
+    FS_Error fs_err = file_parser.get_fs_api().common.rename(
         make_filename(remote->name).c_str(), make_filename(new_name).c_str());
     remote->name = new_name;
     if(fs_err != FSE_OK) {
-        show_file_error_message("Error renaming\nremote file");
+        file_parser.get_sd_api().show_error(
+            file_parser.get_sd_api().context, "Error renaming\nremote file");
     }
     return fs_err == FSE_OK;
 }
@@ -170,26 +164,25 @@ size_t IrdaAppRemoteManager::get_number_of_buttons() {
     return remote->buttons.size();
 }
 
-void IrdaAppRemoteManager::show_file_error_message(const char* error_text) const {
-    sd_ex_api->show_error(sd_ex_api->context, error_text);
-}
-
 bool IrdaAppRemoteManager::store(void) {
     File file;
     uint16_t write_count;
     std::string dirname(std::string("/") + irda_directory);
 
-    FS_Error fs_err = fs_api->common.mkdir(dirname.c_str());
+    IrdaAppFileParser file_parser;
+    FS_Error fs_err = file_parser.get_fs_api().common.mkdir(dirname.c_str());
     if((fs_err != FSE_OK) && (fs_err != FSE_EXIST)) {
-        show_file_error_message("Can't create directory");
+        file_parser.get_sd_api().show_error(
+            file_parser.get_sd_api().context, "Can't create directory");
         return false;
     }
 
-    std::string filename = dirname + "/" + remote->name + irda_extension;
-    bool res = fs_api->file.open(&file, filename.c_str(), FSAM_WRITE, FSOM_CREATE_ALWAYS);
+    bool res = file_parser.get_fs_api().file.open(
+        &file, make_filename(remote->name).c_str(), FSAM_WRITE, FSOM_CREATE_ALWAYS);
 
     if(!res) {
-        show_file_error_message("Cannot create\nnew remote file");
+        file_parser.get_sd_api().show_error(
+            file_parser.get_sd_api().context, "Cannot create\nnew remote file");
         return false;
     }
 
@@ -210,103 +203,21 @@ bool IrdaAppRemoteManager::store(void) {
             button.message.command);
 
         auto content_len = strlen(content);
-        write_count = fs_api->file.write(&file, content, content_len);
+        write_count = file_parser.get_fs_api().file.write(&file, content, content_len);
         if(file.error_id != FSE_OK || write_count != content_len) {
-            show_file_error_message("Cannot write\nto key file");
-            fs_api->file.close(&file);
+            file_parser.get_sd_api().show_error(
+                file_parser.get_sd_api().context, "Cannot write\nto key file");
+            file_parser.get_fs_api().file.close(&file);
             return false;
         }
     }
 
-    fs_api->file.close(&file);
-    sd_ex_api->check_error(sd_ex_api->context);
-
-    return true;
-}
-
-bool IrdaAppRemoteManager::parse_button(std::string& str) {
-    char button_name[32];
-    char protocol_name[32];
-    uint32_t address;
-    uint32_t command;
-
-    int parsed = std::sscanf(
-        str.c_str(), "%31s %31s A:%lX C:%lX", button_name, protocol_name, &address, &command);
-
-    if(parsed != 4) {
-        return false;
-    }
-
-    IrdaProtocol protocol = irda_get_protocol_by_name(protocol_name);
-
-    if(!irda_is_protocol_valid((IrdaProtocol)protocol)) {
-        return false;
-    }
-
-    int address_length = irda_get_protocol_address_length(protocol);
-    uint32_t address_mask = (1LU << (4 * address_length)) - 1;
-    if(address != (address & address_mask)) {
-        return false;
-    }
-
-    int command_length = irda_get_protocol_command_length(protocol);
-    uint32_t command_mask = (1LU << (4 * command_length)) - 1;
-    if(command != (command & command_mask)) {
-        return false;
-    }
-
-    IrdaMessage irda_message = {
-        .protocol = protocol,
-        .address = address,
-        .command = command,
-        .repeat = false,
-    };
-    remote->buttons.emplace_back(button_name, &irda_message);
+    file_parser.get_fs_api().file.close(&file);
+    file_parser.get_sd_api().check_error(file_parser.get_sd_api().context);
 
     return true;
 }
 
-std::string getline(
-    const FS_Api* fs_api,
-    File& file,
-    char file_buf[],
-    size_t file_buf_size,
-    size_t& file_buf_cnt) {
-    std::string str;
-    size_t newline_index = 0;
-    bool found_eol = false;
-
-    while(1) {
-        if(file_buf_cnt > 0) {
-            size_t end_index = 0;
-            char* endline_ptr = (char*)memchr(file_buf, '\n', file_buf_cnt);
-            newline_index = endline_ptr - file_buf;
-
-            if(endline_ptr == 0) {
-                end_index = file_buf_cnt;
-            } else if(newline_index < file_buf_cnt) {
-                end_index = newline_index + 1;
-                found_eol = true;
-            } else {
-                furi_assert(0);
-            }
-
-            str.append(file_buf, end_index);
-            memmove(file_buf, &file_buf[end_index], file_buf_cnt - end_index);
-            file_buf_cnt = file_buf_cnt - end_index;
-            if(found_eol) break;
-        }
-
-        file_buf_cnt +=
-            fs_api->file.read(&file, &file_buf[file_buf_cnt], file_buf_size - file_buf_cnt);
-        if(file_buf_cnt == 0) {
-            break; // end of reading
-        }
-    }
-
-    return str;
-}
-
 bool IrdaAppRemoteManager::get_remote_list(std::vector<std::string>& remote_names) const {
     bool fs_res = false;
     char name[128];
@@ -314,17 +225,19 @@ bool IrdaAppRemoteManager::get_remote_list(std::vector<std::string>& remote_name
     std::string dirname(std::string("/") + irda_directory);
     remote_names.clear();
 
-    fs_res = fs_api->dir.open(&dir, dirname.c_str());
+    IrdaAppFileParser file_parser;
+    fs_res = file_parser.get_fs_api().dir.open(&dir, dirname.c_str());
     if(!fs_res) {
         if(!check_fs()) {
-            show_file_error_message("Cannot open\napplication directory");
+            file_parser.get_sd_api().show_error(
+                file_parser.get_sd_api().context, "Cannot open\napplication directory");
             return false;
         } else {
             return true; // SD ok, but no files written yet
         }
     }
 
-    while(fs_api->dir.read(&dir, nullptr, name, sizeof(name)) && strlen(name)) {
+    while(file_parser.get_fs_api().dir.read(&dir, nullptr, name, sizeof(name)) && strlen(name)) {
         std::string filename(name);
         auto extension_index = filename.rfind(irda_extension);
         if((extension_index == std::string::npos) ||
@@ -333,36 +246,41 @@ bool IrdaAppRemoteManager::get_remote_list(std::vector<std::string>& remote_name
         }
         remote_names.push_back(filename.erase(extension_index));
     }
-    fs_api->dir.close(&dir);
+    file_parser.get_fs_api().dir.close(&dir);
 
     return true;
 }
 
 bool IrdaAppRemoteManager::load(const std::string& name) {
     bool fs_res = false;
+    IrdaAppFileParser file_parser;
     File file;
 
-    fs_res = fs_api->file.open(&file, make_filename(name).c_str(), FSAM_READ, FSOM_OPEN_EXISTING);
+    fs_res = file_parser.get_fs_api().file.open(
+        &file, make_filename(name).c_str(), FSAM_READ, FSOM_OPEN_EXISTING);
     if(!fs_res) {
-        show_file_error_message("Error opening file");
+        file_parser.get_sd_api().show_error(
+            file_parser.get_sd_api().context, "Error opening file");
         return false;
     }
 
     remote = std::make_unique<IrdaAppRemote>(name);
 
     while(1) {
-        auto str = getline(fs_api, file, file_buf, sizeof(file_buf), file_buf_cnt);
-        if(str.empty()) break;
-        parse_button(str);
+        auto message = file_parser.read_message(&file);
+        if(!message) break;
+        remote->buttons.emplace_back(message->name, &message->message);
     }
-    fs_api->file.close(&file);
+    file_parser.get_fs_api().file.close(&file);
 
     return true;
 }
 
 bool IrdaAppRemoteManager::check_fs() const {
-    // TODO: [FL-1431] Add return value to sd_ex_api->check_error() and replace get_fs_info().
-    auto fs_err = fs_api->common.get_fs_info(nullptr, nullptr);
-    if(fs_err != FSE_OK) show_file_error_message("SD card not found");
+    // TODO: [FL-1431] Add return value to file_parser.get_sd_api().check_error() and replace get_fs_info().
+    IrdaAppFileParser file_parser;
+    auto fs_err = file_parser.get_fs_api().common.get_fs_info(nullptr, nullptr);
+    if(fs_err != FSE_OK)
+        file_parser.get_sd_api().show_error(file_parser.get_sd_api().context, "SD card not found");
     return fs_err == FSE_OK;
 }

+ 0 - 12
applications/irda/irda-app-remote-manager.hpp

@@ -1,9 +1,6 @@
 #pragma once
-#include "sys/_stdint.h"
-#include <algorithm>
 #include <stdint.h>
 #include <string>
-#include <list>
 #include <vector>
 #include <memory>
 #include <irda.h>
@@ -38,14 +35,7 @@ class IrdaAppRemoteManager {
     static const char* irda_directory;
     static const char* irda_extension;
     std::unique_ptr<IrdaAppRemote> remote;
-    // TODO: make FS_Api and SdCard_Api unique_ptr
-    SdCard_Api* sd_ex_api;
-    FS_Api* fs_api;
-    void show_file_error_message(const char* error_text) const;
-    bool parse_button(std::string& str);
     std::string make_filename(const std::string& name) const;
-    char file_buf[48];
-    size_t file_buf_cnt = 0;
 
 public:
     bool add_remote_with_button(const char* button_name, const IrdaMessage* message);
@@ -63,8 +53,6 @@ public:
     const IrdaMessage* get_button_data(size_t button_index) const;
     bool delete_button(uint32_t index);
     bool delete_remote();
-    IrdaAppRemoteManager();
-    ~IrdaAppRemoteManager();
 
     bool store();
     bool load(const std::string& name);

+ 9 - 9
applications/irda/irda-app-receiver.cpp → applications/irda/irda-app-transceiver.cpp

@@ -2,10 +2,10 @@
 #include "irda.h"
 #include <api-hal-irda.h>
 
-void IrdaAppSignalReceiver::irda_rx_callback(void* ctx, bool level, uint32_t duration) {
+void IrdaAppSignalTransceiver::irda_rx_callback(void* ctx, bool level, uint32_t duration) {
     IrdaAppEvent event;
     const IrdaMessage* irda_message;
-    IrdaAppSignalReceiver* this_ = static_cast<IrdaAppSignalReceiver*>(ctx);
+    IrdaAppSignalTransceiver* this_ = static_cast<IrdaAppSignalTransceiver*>(ctx);
 
     irda_message = irda_decode(this_->decoder, level, duration);
     if(irda_message) {
@@ -17,30 +17,30 @@ void IrdaAppSignalReceiver::irda_rx_callback(void* ctx, bool level, uint32_t dur
     }
 }
 
-IrdaAppSignalReceiver::IrdaAppSignalReceiver(void)
+IrdaAppSignalTransceiver::IrdaAppSignalTransceiver(void)
     : decoder(irda_alloc_decoder()) {
 }
 
-IrdaAppSignalReceiver::~IrdaAppSignalReceiver() {
+IrdaAppSignalTransceiver::~IrdaAppSignalTransceiver() {
     api_hal_irda_rx_irq_deinit();
     irda_free_decoder(decoder);
 }
 
-void IrdaAppSignalReceiver::capture_once_start(osMessageQueueId_t queue) {
+void IrdaAppSignalTransceiver::capture_once_start(osMessageQueueId_t queue) {
     event_queue = queue;
     irda_reset_decoder(decoder);
     api_hal_irda_rx_irq_init();
-    api_hal_irda_rx_irq_set_callback(IrdaAppSignalReceiver::irda_rx_callback, this);
+    api_hal_irda_rx_irq_set_callback(IrdaAppSignalTransceiver::irda_rx_callback, this);
 }
 
-void IrdaAppSignalReceiver::capture_stop(void) {
+void IrdaAppSignalTransceiver::capture_stop(void) {
     api_hal_irda_rx_irq_deinit();
 }
 
-IrdaMessage* IrdaAppSignalReceiver::get_last_message(void) {
+IrdaMessage* IrdaAppSignalTransceiver::get_last_message(void) {
     return &message;
 }
 
-void IrdaAppSignalReceiver::send_message(const IrdaMessage* message) {
+void IrdaAppSignalTransceiver::send_message(const IrdaMessage* message) const {
     irda_send(message, 1);
 }

+ 5 - 4
applications/irda/irda-app-receiver.hpp → applications/irda/irda-app-transceiver.hpp

@@ -1,14 +1,15 @@
+#pragma once
 #include <furi.h>
 #include <irda.h>
 
-class IrdaAppSignalReceiver {
+class IrdaAppSignalTransceiver {
 public:
-    IrdaAppSignalReceiver(void);
-    ~IrdaAppSignalReceiver(void);
+    IrdaAppSignalTransceiver(void);
+    ~IrdaAppSignalTransceiver(void);
     void capture_once_start(osMessageQueueId_t event_queue);
     void capture_stop(void);
     IrdaMessage* get_last_message(void);
-    void send_message(const IrdaMessage* message);
+    void send_message(const IrdaMessage* message) const;
 
 private:
     osMessageQueueId_t event_queue;

+ 17 - 3
applications/irda/irda-app-view-manager.cpp

@@ -1,7 +1,5 @@
 #include "furi.h"
-#include "gui/modules/button_menu.h"
-#include "gui/modules/dialog_ex.h"
-#include "gui/modules/text_input.h"
+#include "gui/modules/button_panel.h"
 #include "irda-app.hpp"
 #include <callback-connector.h>
 
@@ -19,13 +17,17 @@ IrdaAppViewManager::IrdaAppViewManager() {
     popup = popup_alloc();
     dialog_ex = dialog_ex_alloc();
     text_input = text_input_alloc();
+    button_panel = button_panel_alloc();
+    popup_brut = popup_brut_alloc();
 
+    add_view(ViewType::ButtonPanel, button_panel_get_view(button_panel));
     add_view(ViewType::ButtonMenu, button_menu_get_view(button_menu));
     add_view(ViewType::Submenu, submenu_get_view(submenu));
     add_view(ViewType::Popup, popup_get_view(popup));
     add_view(ViewType::DialogEx, dialog_ex_get_view(dialog_ex));
     add_view(ViewType::TextInput, text_input_get_view(text_input));
 
+    view_set_previous_callback(button_panel_get_view(button_panel), callback);
     view_set_previous_callback(button_menu_get_view(button_menu), callback);
     view_set_previous_callback(submenu_get_view(submenu), callback);
     view_set_previous_callback(popup_get_view(popup), callback);
@@ -34,6 +36,8 @@ IrdaAppViewManager::IrdaAppViewManager() {
 }
 
 IrdaAppViewManager::~IrdaAppViewManager() {
+    view_dispatcher_remove_view(
+        view_dispatcher, static_cast<uint32_t>(IrdaAppViewManager::ViewType::ButtonPanel));
     view_dispatcher_remove_view(
         view_dispatcher, static_cast<uint32_t>(IrdaAppViewManager::ViewType::ButtonMenu));
     view_dispatcher_remove_view(
@@ -47,9 +51,11 @@ IrdaAppViewManager::~IrdaAppViewManager() {
 
     submenu_free(submenu);
     popup_free(popup);
+    button_panel_free(button_panel);
     button_menu_free(button_menu);
     dialog_ex_free(dialog_ex);
     text_input_free(text_input);
+    popup_brut_free(popup_brut);
 
     view_dispatcher_free(view_dispatcher);
     furi_record_close("gui");
@@ -80,6 +86,14 @@ ButtonMenu* IrdaAppViewManager::get_button_menu() {
     return button_menu;
 }
 
+ButtonPanel* IrdaAppViewManager::get_button_panel() {
+    return button_panel;
+}
+
+IrdaAppPopupBrut* IrdaAppViewManager::get_popup_brut() {
+    return popup_brut;
+}
+
 osMessageQueueId_t IrdaAppViewManager::get_event_queue() {
     return event_queue;
 }

+ 7 - 0
applications/irda/irda-app-view-manager.hpp

@@ -7,6 +7,8 @@
 #include <gui/modules/submenu.h>
 #include <gui/modules/popup.h>
 #include "irda-app.hpp"
+#include "view/irda-app-brut-view.h"
+#include "gui/modules/button_panel.h"
 
 class IrdaAppViewManager {
 public:
@@ -15,6 +17,7 @@ public:
         TextInput,
         Submenu,
         ButtonMenu,
+        ButtonPanel,
         Popup,
     };
 
@@ -31,6 +34,8 @@ public:
     Popup* get_popup();
     TextInput* get_text_input();
     ButtonMenu* get_button_menu();
+    ButtonPanel* get_button_panel();
+    IrdaAppPopupBrut* get_popup_brut();
 
     osMessageQueueId_t get_event_queue();
 
@@ -44,6 +49,8 @@ private:
     Submenu* submenu;
     Popup* popup;
     ButtonMenu* button_menu;
+    ButtonPanel* button_panel;
+    IrdaAppPopupBrut* popup_brut;
 
     osMessageQueueId_t event_queue;
 

+ 2 - 4
applications/irda/irda-app.cpp

@@ -1,9 +1,7 @@
 #include "irda-app.hpp"
-#include "sys/_stdint.h"
 #include <furi.h>
 #include <gui/gui.h>
 #include <input/input.h>
-#include <stdarg.h>
 #include <stdio.h>
 #include <callback-connector.h>
 
@@ -101,8 +99,8 @@ IrdaAppRemoteManager* IrdaApp::get_remote_manager() {
     return &remote_manager;
 }
 
-IrdaAppSignalReceiver* IrdaApp::get_receiver() {
-    return &receiver;
+IrdaAppSignalTransceiver* IrdaApp::get_transceiver() {
+    return &transceiver;
 }
 
 void IrdaApp::set_text_store(uint8_t index, const char* text...) {

+ 6 - 4
applications/irda/irda-app.hpp

@@ -1,13 +1,13 @@
 #pragma once
-#include "sys/_stdint.h"
 #include <map>
 #include <irda.h>
 #include <furi.h>
+#include "irda/scene/irda-app-scene.hpp"
 #include "irda-app-event.hpp"
 #include "scene/irda-app-scene.hpp"
 #include "irda-app-view-manager.hpp"
 #include "irda-app-remote-manager.hpp"
-#include "irda-app-receiver.hpp"
+#include "irda-app-transceiver.hpp"
 #include <forward_list>
 #include <stdint.h>
 #include <notification/notification-messages.h>
@@ -51,7 +51,7 @@ public:
     bool switch_to_previous_scene(uint8_t count = 1);
     Scene get_previous_scene();
     IrdaAppViewManager* get_view_manager();
-    IrdaAppSignalReceiver* get_receiver();
+    IrdaAppSignalTransceiver* get_transceiver();
     void set_text_store(uint8_t index, const char* text...);
     char* get_text_store(uint8_t index);
     uint8_t get_text_store_size();
@@ -103,7 +103,7 @@ private:
     uint32_t current_button;
 
     NotificationApp* notification;
-    IrdaAppSignalReceiver receiver;
+    IrdaAppSignalTransceiver transceiver;
     IrdaAppViewManager view_manager;
     IrdaAppRemoteManager remote_manager;
 
@@ -113,6 +113,8 @@ private:
     std::map<Scene, IrdaAppScene*> scenes = {
         {Scene::Start, new IrdaAppSceneStart()},
         {Scene::Universal, new IrdaAppSceneUniversal()},
+        {Scene::UniversalTV, new IrdaAppSceneUniversalTV()},
+//        {Scene::UniversalAudio, new IrdaAppSceneUniversalAudio()},
         {Scene::Learn, new IrdaAppSceneLearn()},
         {Scene::LearnSuccess, new IrdaAppSceneLearnSuccess()},
         {Scene::LearnEnterName, new IrdaAppSceneLearnEnterName()},

+ 0 - 1
applications/irda/scene/irda-app-scene-edit-delete.cpp

@@ -2,7 +2,6 @@
 #include "irda.h"
 #include "irda/scene/irda-app-scene.hpp"
 #include <string>
-#include <stdio.h>
 
 static void dialog_result_callback(DialogExResult result, void* context) {
     auto app = static_cast<IrdaApp*>(context);

+ 0 - 1
applications/irda/scene/irda-app-scene-edit-rename.cpp

@@ -1,5 +1,4 @@
 #include "../irda-app.hpp"
-#include <cstdio>
 
 void IrdaAppSceneEditRename::on_enter(IrdaApp* app) {
     IrdaAppViewManager* view_manager = app->get_view_manager();

+ 0 - 2
applications/irda/scene/irda-app-scene-learn-done-after.cpp

@@ -1,6 +1,4 @@
 #include "../irda-app.hpp"
-#include <string>
-#include <stdio.h>
 #include <gui/modules/popup.h>
 
 void IrdaAppSceneLearnDoneAfter::on_enter(IrdaApp* app) {

+ 0 - 2
applications/irda/scene/irda-app-scene-learn-done.cpp

@@ -1,6 +1,4 @@
 #include "../irda-app.hpp"
-#include <string>
-#include <stdio.h>
 
 void IrdaAppSceneLearnDone::on_enter(IrdaApp* app) {
     IrdaAppViewManager* view_manager = app->get_view_manager();

+ 6 - 9
applications/irda/scene/irda-app-scene-learn-enter-name.cpp

@@ -1,15 +1,12 @@
 #include "../irda-app.hpp"
 #include "gui/modules/text_input.h"
-#include <callback-connector.h>
-#include <string>
-#include <stdio.h>
 
 void IrdaAppSceneLearnEnterName::on_enter(IrdaApp* app) {
     IrdaAppViewManager* view_manager = app->get_view_manager();
     TextInput* text_input = view_manager->get_text_input();
 
-    auto receiver = app->get_receiver();
-    auto message = receiver->get_last_message();
+    auto transceiver = app->get_transceiver();
+    auto message = transceiver->get_last_message();
 
     app->set_text_store(
         0,
@@ -34,14 +31,14 @@ bool IrdaAppSceneLearnEnterName::on_event(IrdaApp* app, IrdaAppEvent* event) {
 
     if(event->type == IrdaAppEvent::Type::TextEditDone) {
         auto remote_manager = app->get_remote_manager();
-        auto receiver = app->get_receiver();
+        auto transceiver = app->get_transceiver();
         bool result = false;
         if(app->get_learn_new_remote()) {
             result = remote_manager->add_remote_with_button(
-                app->get_text_store(0), receiver->get_last_message());
+                app->get_text_store(0), transceiver->get_last_message());
         } else {
-            result =
-                remote_manager->add_button(app->get_text_store(0), receiver->get_last_message());
+            result = remote_manager->add_button(
+                app->get_text_store(0), transceiver->get_last_message());
         }
 
         if(!result) {

+ 4 - 6
applications/irda/scene/irda-app-scene-learn-success.cpp

@@ -1,7 +1,5 @@
 #include "../irda-app.hpp"
 #include "irda.h"
-#include <string>
-#include <stdio.h>
 
 static void dialog_result_callback(DialogExResult result, void* context) {
     auto app = static_cast<IrdaApp*>(context);
@@ -19,8 +17,8 @@ void IrdaAppSceneLearnSuccess::on_enter(IrdaApp* app) {
 
     app->notify_green_on();
 
-    auto receiver = app->get_receiver();
-    auto message = receiver->get_last_message();
+    auto transceiver = app->get_transceiver();
+    auto message = transceiver->get_last_message();
 
     app->set_text_store(0, "%s", irda_get_protocol_name(message->protocol));
     app->set_text_store(
@@ -52,8 +50,8 @@ bool IrdaAppSceneLearnSuccess::on_event(IrdaApp* app, IrdaAppEvent* event) {
             break;
         case DialogExResultCenter: {
             app->notify_space_blink();
-            auto receiver = app->get_receiver();
-            auto message = receiver->get_last_message();
+            auto transceiver = app->get_transceiver();
+            auto message = transceiver->get_last_message();
             irda_send(message, 1);
             break;
         }

+ 2 - 2
applications/irda/scene/irda-app-scene-learn.cpp

@@ -2,10 +2,10 @@
 
 void IrdaAppSceneLearn::on_enter(IrdaApp* app) {
     auto view_manager = app->get_view_manager();
-    auto receiver = app->get_receiver();
+    auto transceiver = app->get_transceiver();
     auto event_queue = view_manager->get_event_queue();
 
-    receiver->capture_once_start(event_queue);
+    transceiver->capture_once_start(event_queue);
 
     auto popup = view_manager->get_popup();
 

+ 1 - 1
applications/irda/scene/irda-app-scene-remote.cpp

@@ -65,7 +65,7 @@ bool IrdaAppSceneRemote::on_event(IrdaApp* app, IrdaAppEvent* event) {
             app->notify_click_and_blink();
             auto remote_manager = app->get_remote_manager();
             auto message = remote_manager->get_button_data(event->payload.menu_index);
-            app->get_receiver()->send_message(message);
+            app->get_transceiver()->send_message(message);
             break;
         }
     } else if(event->type == IrdaAppEvent::Type::Back) {

+ 98 - 0
applications/irda/scene/irda-app-scene-universal-common.cpp

@@ -0,0 +1,98 @@
+#include "../irda-app.hpp"
+#include "assets_icons.h"
+#include "gui/modules/button_menu.h"
+#include "gui/modules/button_panel.h"
+#include "../view/irda-app-brut-view.h"
+#include "gui/view.h"
+#include "irda/irda-app-view-manager.hpp"
+#include "irda/scene/irda-app-scene.hpp"
+
+void IrdaAppSceneUniversalCommon::irda_app_item_callback(void* context, uint32_t index) {
+    IrdaApp* app = static_cast<IrdaApp*>(context);
+    IrdaAppEvent event;
+
+    event.type = IrdaAppEvent::Type::ButtonPanelPressed;
+    event.payload.menu_index = index;
+
+    app->get_view_manager()->send_event(&event);
+}
+
+static bool irda_popup_brut_input_callback(InputEvent* event, void* context) {
+    furi_assert(context);
+    furi_assert(event);
+    auto app = static_cast<IrdaApp*>(context);
+    bool consumed = false;
+
+    if((event->type == InputTypeShort) && (event->key == InputKeyBack)) {
+        consumed = true;
+        IrdaAppEvent irda_event;
+
+        irda_event.type = IrdaAppEvent::Type::ButtonPanelPopupBackPressed;
+        app->get_view_manager()->send_event(&irda_event);
+    }
+
+    return consumed;
+}
+
+void IrdaAppSceneUniversalCommon::remove_popup(IrdaApp* app) {
+    auto button_panel = app->get_view_manager()->get_button_panel();
+    button_panel_set_popup_draw_callback(button_panel, NULL, NULL);
+    button_panel_set_popup_input_callback(button_panel, NULL, NULL);
+}
+
+void IrdaAppSceneUniversalCommon::show_popup(IrdaApp* app, int record_amount) {
+    auto button_panel = app->get_view_manager()->get_button_panel();
+    auto popup_brut = app->get_view_manager()->get_popup_brut();
+    popup_brut_set_progress_max(popup_brut, record_amount);
+    button_panel_set_popup_draw_callback(button_panel, popup_brut_draw_callback, popup_brut);
+    button_panel_set_popup_input_callback(button_panel, irda_popup_brut_input_callback, app);
+}
+
+void IrdaAppSceneUniversalCommon::progress_popup(IrdaApp* app) {
+    popup_brut_increase_progress(app->get_view_manager()->get_popup_brut());
+    auto button_panel = app->get_view_manager()->get_button_panel();
+    with_view_model_cpp(button_panel_get_view(button_panel), void*, model, { return true; });
+}
+
+bool IrdaAppSceneUniversalCommon::on_event(IrdaApp* app, IrdaAppEvent* event) {
+    bool consumed = false;
+
+    if(event->type == IrdaAppEvent::Type::Tick) {
+        if(brute_force_started) {
+            if(brute_force.send_next_bruteforce(*app->get_transceiver())) {
+                progress_popup(app);
+            } else {
+                brute_force.stop_bruteforce();
+                brute_force_started = false;
+                remove_popup(app);
+            }
+        }
+        consumed = true;
+    }
+
+    if(event->type == IrdaAppEvent::Type::ButtonPanelPopupBackPressed) {
+        consumed = true;
+        brute_force_started = false;
+        brute_force.stop_bruteforce();
+        remove_popup(app);
+    } else if(event->type == IrdaAppEvent::Type::ButtonPanelPressed) {
+        int record_amount = 0;
+        if(brute_force.start_bruteforce(event->payload.menu_index, record_amount)) {
+            if(record_amount > 0) {
+                brute_force_started = true;
+                show_popup(app, record_amount);
+            }
+        } else {
+            app->switch_to_previous_scene();
+        }
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void IrdaAppSceneUniversalCommon::on_exit(IrdaApp* app) {
+    IrdaAppViewManager* view_manager = app->get_view_manager();
+    ButtonPanel* button_panel = view_manager->get_button_panel();
+    button_panel_clean(button_panel);
+}

+ 61 - 0
applications/irda/scene/irda-app-scene-universal-tv.cpp

@@ -0,0 +1,61 @@
+#include "irda/scene/irda-app-scene.hpp"
+#include "irda/irda-app.hpp"
+
+void IrdaAppSceneUniversalTV::on_enter(IrdaApp* app) {
+    IrdaAppViewManager* view_manager = app->get_view_manager();
+    ButtonPanel* button_panel = view_manager->get_button_panel();
+    button_panel_reserve(button_panel, 2, 3);
+
+    int i = 0;
+    button_panel_add_item(
+        button_panel, i, 0, 0, 3, 19, I_Power_25x27, I_Power_hvr_25x27, irda_app_item_callback, app);
+    brute_force.add_record(i, "POWER");
+    ++i;
+    button_panel_add_item(
+        button_panel, i, 1, 0, 36, 19, I_Mute_25x27, I_Mute_hvr_25x27, irda_app_item_callback, app);
+    brute_force.add_record(i, "MUTE");
+    ++i;
+    button_panel_add_item(
+        button_panel,
+        i,
+        0,
+        1,
+        3,
+        66,
+        I_Vol_up_25x27,
+        I_Vol_up_hvr_25x27,
+        irda_app_item_callback,
+        app);
+    brute_force.add_record(i, "VOL+");
+    ++i;
+    button_panel_add_item(
+        button_panel, i, 1, 1, 36, 66, I_Up_25x27, I_Up_hvr_25x27, irda_app_item_callback, app);
+    brute_force.add_record(i, "CH+");
+    ++i;
+    button_panel_add_item(
+        button_panel,
+        i,
+        0,
+        2,
+        3,
+        98,
+        I_Vol_down_25x27,
+        I_Vol_down_hvr_25x27,
+        irda_app_item_callback,
+        app);
+    brute_force.add_record(i, "VOL-");
+    ++i;
+    button_panel_add_item(
+        button_panel, i, 1, 2, 36, 98, I_Down_25x27, I_Down_hvr_25x27, irda_app_item_callback, app);
+    brute_force.add_record(i, "CH-");
+
+    button_panel_add_label(button_panel, 6, 11, FontPrimary, "TV remote");
+    button_panel_add_label(button_panel, 9, 64, FontSecondary, "Vol");
+    button_panel_add_label(button_panel, 43, 64, FontSecondary, "Ch");
+
+    view_manager->switch_to(IrdaAppViewManager::ViewType::ButtonPanel);
+
+    if(!brute_force.calculate_messages()) {
+        app->switch_to_previous_scene();
+    }
+}

+ 1 - 1
applications/irda/scene/irda-app-scene-universal.cpp

@@ -37,7 +37,7 @@ bool IrdaAppSceneUniversal::on_event(IrdaApp* app, IrdaAppEvent* event) {
         submenu_item_selected = event->payload.menu_index;
         switch(event->payload.menu_index) {
         case SubmenuIndexUniversalTV:
-            //            app->switch_to_next_scene(IrdaApp::Scene::UniversalTV);
+            app->switch_to_next_scene(IrdaApp::Scene::UniversalTV);
             break;
         case SubmenuIndexUniversalAudio:
             //            app->switch_to_next_scene(IrdaApp::Scene::UniversalAudio);

+ 31 - 2
applications/irda/scene/irda-app-scene.hpp

@@ -1,10 +1,11 @@
 #pragma once
-#include "../irda-app.hpp"
+#include "../irda-app-event.hpp"
 #include <api-hal-irda.h>
 #include "irda.h"
-#include <gui/elements.h>
 #include <vector>
 #include <string>
+#include "../irda-app-brute-force.hpp"
+
 
 class IrdaApp;
 
@@ -137,3 +138,31 @@ public:
     void on_exit(IrdaApp* app) final;
 };
 
+class IrdaAppSceneUniversalCommon : public IrdaAppScene {
+    bool brute_force_started = false;
+protected:
+    bool on_event(IrdaApp* app, IrdaAppEvent* event) final;
+    void on_exit(IrdaApp* app) final;
+    IrdaAppBruteForce brute_force;
+    void remove_popup(IrdaApp* app);
+    void show_popup(IrdaApp* app, int record_amount);
+    void progress_popup(IrdaApp* app);
+    static void irda_app_item_callback(void* context, uint32_t index);
+    IrdaAppSceneUniversalCommon(const char* filename) : brute_force(filename) {}
+    ~IrdaAppSceneUniversalCommon() {}
+};
+
+class IrdaAppSceneUniversalTV : public IrdaAppSceneUniversalCommon {
+public:
+    void on_enter(IrdaApp* app) final;
+    IrdaAppSceneUniversalTV() : IrdaAppSceneUniversalCommon("/irda/universal/tv.ir") {}
+    ~IrdaAppSceneUniversalTV() {}
+};
+
+class IrdaAppSceneUniversalAudio : public IrdaAppSceneUniversalCommon {
+public:
+    void on_enter(IrdaApp* app) final;
+    IrdaAppSceneUniversalAudio() : IrdaAppSceneUniversalCommon("/irda/universal/audio.ir") {}
+    ~IrdaAppSceneUniversalAudio() {}
+};
+

+ 82 - 0
applications/irda/view/irda-app-brut-view.c

@@ -0,0 +1,82 @@
+#include "api-hal-resources.h"
+#include "assets_icons.h"
+#include "gui/canvas.h"
+#include "gui/view.h"
+#include "input/input.h"
+#include <gui/elements.h>
+#include <furi.h>
+#include "irda-app-brut-view.h"
+#include "gui/modules/button_panel.h"
+#include <stdint.h>
+
+struct IrdaAppPopupBrut {
+    uint16_t progress;
+    uint16_t progress_max;
+    char percents_string_storage[8];
+};
+
+void popup_brut_increase_progress(IrdaAppPopupBrut* popup_brut) {
+    furi_assert(popup_brut);
+
+    if(popup_brut->progress < popup_brut->progress_max)
+        ++popup_brut->progress;
+    else
+        furi_assert(0);
+}
+
+void popup_brut_draw_callback(Canvas* canvas, void* context) {
+    furi_assert(canvas);
+    furi_assert(context);
+    IrdaAppPopupBrut* popup_brut = (IrdaAppPopupBrut*)context;
+    uint8_t x = 0;
+    uint8_t width = 64;
+    uint8_t x_max = x + width - 1;
+    uint8_t y = 36;
+    uint8_t height = 59;
+    uint8_t y_max = y + height - 1;
+
+    canvas_invert_color(canvas);
+    canvas_draw_rbox(canvas, x + 1, y + 1, width - 2, height - 2, 3);
+    canvas_invert_color(canvas);
+    canvas_draw_rframe(canvas, x, y, width, height, 3);
+    canvas_draw_rframe(canvas, x + 1, y + 1, width - 2, height - 2, 3);
+    canvas_draw_line(canvas, x + 2, y + 1, x + 2, y + 3);
+    canvas_draw_line(canvas, x + 1, y + 2, x + 3, y + 2);
+    canvas_draw_line(canvas, x_max - 2, y + 1, x_max - 2, y + 3);
+    canvas_draw_line(canvas, x_max - 1, y + 2, x_max - 3, y + 2);
+    canvas_draw_line(canvas, x + 2, y_max - 1, x + 2, y_max - 3);
+    canvas_draw_line(canvas, x + 1, y_max - 2, x + 3, y_max - 2);
+    canvas_draw_line(canvas, x_max - 2, y_max - 1, x_max - 2, y_max - 3);
+    canvas_draw_line(canvas, x_max - 1, y_max - 2, x_max - 3, y_max - 2);
+
+    elements_progress_bar(
+        canvas, x + 4, y + 19, x_max - 8, popup_brut->progress, popup_brut->progress_max);
+
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str(canvas, x + 15, y + 12, "Sending ...");
+    canvas_draw_icon_name(canvas, x + 11, y_max - 14, I_Back_15x10);
+
+    uint8_t percent_value = 100 * popup_brut->progress / popup_brut->progress_max;
+    snprintf(
+        popup_brut->percents_string_storage,
+        sizeof(popup_brut->percents_string_storage),
+        "%d%%",
+        percent_value);
+    elements_multiline_text_aligned(
+        canvas, x + 32, y + 40, AlignCenter, AlignBottom, popup_brut->percents_string_storage);
+    canvas_draw_str(canvas, x + 30, y_max - 5, "= stop");
+}
+
+void popup_brut_set_progress_max(IrdaAppPopupBrut* popup_brut, uint16_t progress_max) {
+    furi_assert(popup_brut);
+    popup_brut->progress = 0;
+    popup_brut->progress_max = progress_max;
+}
+
+IrdaAppPopupBrut* popup_brut_alloc(void) {
+    return (IrdaAppPopupBrut*)furi_alloc(sizeof(IrdaAppPopupBrut));
+}
+
+void popup_brut_free(IrdaAppPopupBrut* popup_brut) {
+    free(popup_brut);
+}

+ 18 - 0
applications/irda/view/irda-app-brut-view.h

@@ -0,0 +1,18 @@
+#pragma once
+#include <gui/view.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct IrdaAppPopupBrut IrdaAppPopupBrut;
+
+void popup_brut_increase_progress(IrdaAppPopupBrut* popup_brut);
+IrdaAppPopupBrut* popup_brut_alloc();
+void popup_brut_free(IrdaAppPopupBrut* popup_brut);
+void popup_brut_draw_callback(Canvas* canvas, void* model);
+void popup_brut_set_progress_max(IrdaAppPopupBrut* popup_brut, uint16_t progress_max);
+
+#ifdef __cplusplus
+}
+#endif

Plik diff jest za duży
+ 16 - 21
assets/compiled/assets_icons.c


+ 114 - 100
assets/compiled/assets_icons.h

@@ -3,129 +3,143 @@
 #include <gui/icon.h>
 
 typedef enum {
+	I_Down_hvr_25x27,
+	I_Vol_down_hvr_25x27,
+	I_Down_25x27,
+	I_Fill_marker_7x7,
+	I_Vol_down_25x27,
+	I_Vol_up_25x27,
+	I_Up_hvr_25x27,
+	I_Vol_up_hvr_25x27,
+	I_IrdaLearnShort_128x31,
+	I_IrdaSend_128x64,
+	I_Mute_hvr_25x27,
+	I_Back_15x10,
+	I_Up_25x27,
+	I_IrdaArrowUp_4x8,
+	I_Mute_25x27,
+	I_Power_25x27,
+	I_IrdaSendShort_128x34,
+	I_IrdaArrowDown_4x8,
+	I_IrdaLearn_128x64,
+	I_Power_hvr_25x27,
+	A_Games_14,
+	A_Power_14,
+	A_GPIO_14,
+	A_Bluetooth_14,
+	A_Passport_14,
+	A_Sub1ghz_14,
+	A_U2F_14,
+	A_Infrared_14,
+	A_Settings_14,
+	A_125khz_14,
+	A_iButton_14,
+	A_FileManager_14,
+	A_Tamagotchi_14,
+	A_NFC_14,
+	A_Plugins_14,
 	I_SDQuestion_35x43,
 	I_SDError_43x35,
-	I_Health_16x16,
-	I_FaceCharging_29x14,
 	I_BatteryBody_52x28,
-	I_Voltage_16x16,
+	I_FaceCharging_29x14,
+	I_Health_16x16,
 	I_Temperature_16x16,
-	I_FaceNopower_29x14,
-	I_FaceNormal_29x14,
 	I_Battery_16x16,
 	I_FaceConfused_29x14,
-	I_PassportBottom_128x17,
-	I_DoorLeft_8x56,
-	I_DoorLocked_10x56,
-	I_DoorRight_8x56,
-	I_DoorLeft_70x55,
-	I_PassportLeft_6x47,
-	I_DoorRight_70x55,
-	I_LockPopup_100x49,
+	I_FaceNormal_29x14,
+	I_Voltage_16x16,
+	I_FaceNopower_29x14,
+	I_iButtonKey_49x44,
+	I_DolphinExcited_64x63,
+	I_DolphinWait_61x59,
+	I_iButtonDolphinVerySuccess_108x52,
+	I_DolphinMafia_115x62,
+	I_DolphinNice_96x59,
+	I_iButtonDolphinSuccess_109x60,
+	I_Background_128x11,
+	I_Lock_8x8,
+	I_Battery_26x8,
+	I_Battery_19x8,
+	I_USBConnected_15x8,
+	I_Background_128x8,
+	I_BadUsb_9x8,
+	I_PlaceholderL_11x13,
+	I_SDcardFail_11x8,
+	I_Bluetooth_5x8,
+	I_PlaceholderR_30x13,
+	I_SDcardMounted_11x8,
 	I_WalkR2_32x32,
-	I_WalkL2_32x32,
+	I_WalkRB2_32x32,
+	I_WalkR1_32x32,
+	I_PC_22x29,
 	I_WalkRB1_32x32,
-	I_Home_painting_17x20,
-	I_WalkLB2_32x32,
-	I_Sofa_40x13,
+	I_WalkL2_32x32,
 	I_WalkLB1_32x32,
-	I_PC_22x29,
-	I_WalkL1_32x32,
+	I_WalkLB2_32x32,
 	I_TV_20x20,
-	I_WalkR1_32x32,
-	I_WalkRB2_32x32,
 	I_TV_20x24,
-	I_dir_10px,
-	I_Nfc_10px,
+	I_Home_painting_17x20,
+	I_Sofa_40x13,
+	I_WalkL1_32x32,
+	I_passport_bad1_43x45,
+	I_passport_bad3_43x45,
+	I_passport_happy3_43x45,
+	I_passport_happy2_43x45,
+	I_passport_okay3_43x45,
+	I_passport_okay2_43x45,
+	I_passport_happy1_43x45,
+	I_passport_bad2_43x45,
+	I_passport_okay1_43x45,
+	A_Wink_128x64,
+	A_MDI_32x32,
+	A_FX_Sitting_40x27,
+	A_MDWR_32x32,
+	A_MDWL_32x32,
+	A_MDWRB_32x32,
+	A_MDWLB_32x32,
+	A_MDIB_32x32,
+	A_WatchingTV_128x64,
+	I_PassportBottom_128x17,
+	I_DoorLeft_70x55,
+	I_DoorLeft_8x56,
+	I_DoorRight_70x55,
+	I_DoorRight_8x56,
+	I_DoorLocked_10x56,
+	I_PassportLeft_6x47,
+	I_LockPopup_100x49,
 	I_sub1_10px,
 	I_ir_10px,
-	I_ibutt_10px,
 	I_unknown_10px,
+	I_ibutt_10px,
+	I_Nfc_10px,
 	I_ble_10px,
 	I_125_10px,
-	I_FX_SittingB_40x27,
-	I_BigGames_24x24,
-	I_BigProfile_24x24,
-	I_DolphinOkay_41x43,
-	I_DolphinFirstStart5_45x53,
-	I_DolphinFirstStart4_67x53,
+	I_dir_10px,
+	I_ButtonCenter_7x7,
+	I_ButtonLeft_4x7,
+	I_ButtonLeftSmall_3x5,
+	I_ButtonRightSmall_3x5,
+	I_ButtonRight_4x7,
 	I_DolphinFirstStart2_59x51,
-	I_DolphinFirstStart0_70x53,
+	I_BigBurger_24x24,
 	I_DolphinFirstStart6_58x54,
-	I_DolphinFirstStart1_59x53,
-	I_DolphinFirstStart8_56x51,
-	I_DolphinFirstStart7_61x51,
 	I_Flipper_young_80x60,
-	I_BigBurger_24x24,
 	I_FX_Bang_32x6,
+	I_DolphinFirstStart8_56x51,
+	I_DolphinFirstStart1_59x53,
+	I_DolphinOkay_41x43,
 	I_DolphinFirstStart3_57x48,
-	I_BadUsb_9x8,
-	I_PlaceholderR_30x13,
-	I_Background_128x8,
-	I_Lock_8x8,
-	I_Battery_26x8,
-	I_PlaceholderL_11x13,
-	I_Battery_19x8,
-	I_SDcardMounted_11x8,
-	I_SDcardFail_11x8,
-	I_USBConnected_15x8,
-	I_Bluetooth_5x8,
-	I_Background_128x11,
-	I_IrdaArrowUp_4x8,
-	I_IrdaLearnShort_128x31,
-	I_IrdaArrowDown_4x8,
-	I_IrdaLearn_128x64,
-	I_IrdaSend_128x64,
-	I_IrdaSendShort_128x34,
-	I_passport_happy1_43x45,
-	I_passport_bad3_43x45,
-	I_passport_okay2_43x45,
-	I_passport_bad2_43x45,
-	I_passport_okay3_43x45,
-	I_passport_bad1_43x45,
-	I_passport_happy3_43x45,
-	I_passport_happy2_43x45,
-	I_passport_okay1_43x45,
-	I_ButtonRightSmall_3x5,
-	I_ButtonLeft_4x7,
-	I_ButtonLeftSmall_3x5,
-	I_ButtonRight_4x7,
-	I_ButtonCenter_7x7,
-	A_Games_14,
-	A_Plugins_14,
-	A_Passport_14,
-	A_Sub1ghz_14,
-	A_NFC_14,
-	A_Tamagotchi_14,
-	A_FileManager_14,
-	A_125khz_14,
-	A_U2F_14,
-	A_Infrared_14,
-	A_Power_14,
-	A_Settings_14,
-	A_iButton_14,
-	A_Bluetooth_14,
-	A_GPIO_14,
-	I_DolphinMafia_115x62,
-	I_DolphinExcited_64x63,
-	I_iButtonDolphinSuccess_109x60,
-	I_iButtonDolphinVerySuccess_108x52,
-	I_iButtonKey_49x44,
-	I_DolphinNice_96x59,
-	I_DolphinWait_61x59,
-	A_Wink_128x64,
-	A_MDWL_32x32,
-	A_MDWR_32x32,
-	A_WatchingTV_128x64,
-	A_MDI_32x32,
-	A_MDWRB_32x32,
-	A_MDIB_32x32,
-	A_FX_Sitting_40x27,
-	A_MDWLB_32x32,
-	I_KeySave_24x11,
-	I_KeyBackspaceSelected_16x9,
+	I_DolphinFirstStart5_45x53,
+	I_DolphinFirstStart7_61x51,
+	I_FX_SittingB_40x27,
+	I_BigProfile_24x24,
+	I_DolphinFirstStart0_70x53,
+	I_BigGames_24x24,
+	I_DolphinFirstStart4_67x53,
 	I_KeySaveSelected_24x11,
 	I_KeyBackspace_16x9,
+	I_KeyBackspaceSelected_16x9,
+	I_KeySave_24x11,
 } IconName;
 
 Icon * assets_icons_get(IconName name);

BIN
assets/icons/Irda/Back_15x10.png


BIN
assets/icons/Irda/Down_25x27.png


BIN
assets/icons/Irda/Down_hvr_25x27.png


BIN
assets/icons/Irda/Fill-marker_7x7.png


BIN
assets/icons/Irda/Mute_25x27.png


BIN
assets/icons/Irda/Mute_hvr_25x27.png


BIN
assets/icons/Irda/Power_25x27.png


BIN
assets/icons/Irda/Power_hvr_25x27.png


BIN
assets/icons/Irda/Up_25x27.png


BIN
assets/icons/Irda/Up_hvr_25x27.png


BIN
assets/icons/Irda/Vol_down_25x27.png


BIN
assets/icons/Irda/Vol_down_hvr_25x27.png


BIN
assets/icons/Irda/Vol_up_25x27.png


BIN
assets/icons/Irda/Vol_up_hvr_25x27.png


+ 39 - 0
lib/file_reader/file_reader.cpp

@@ -0,0 +1,39 @@
+#include "file_reader/file_reader.hpp"
+
+std::string FileReader::getline(File* file) {
+    std::string str;
+    size_t newline_index = 0;
+    bool found_eol = false;
+
+    while(1) {
+        if(file_buf_cnt > 0) {
+            size_t end_index = 0;
+            char* endline_ptr = (char*)memchr(file_buf, '\n', file_buf_cnt);
+            newline_index = endline_ptr - file_buf;
+
+            if(endline_ptr == 0) {
+                end_index = file_buf_cnt;
+            } else if(newline_index < file_buf_cnt) {
+                end_index = newline_index + 1;
+                found_eol = true;
+            } else {
+                furi_assert(0);
+            }
+
+            str.append(file_buf, end_index);
+            memmove(file_buf, &file_buf[end_index], file_buf_cnt - end_index);
+            file_buf_cnt = file_buf_cnt - end_index;
+            if(found_eol) break;
+        }
+
+        file_buf_cnt +=
+            fs_api->file.read(file, &file_buf[file_buf_cnt], sizeof(file_buf) - file_buf_cnt);
+        if(file_buf_cnt == 0) {
+            break; // end of reading
+        }
+    }
+
+    return str;
+}
+
+

+ 39 - 0
lib/file_reader/file_reader.hpp

@@ -0,0 +1,39 @@
+#pragma once
+#include <string>
+#include <memory>
+#include "sd-card-api.h"
+#include "filesystem-api.h"
+
+class FileReader {
+private:
+    char file_buf[48];
+    size_t file_buf_cnt = 0;
+    SdCard_Api* sd_ex_api;
+    FS_Api* fs_api;
+
+public:
+    FileReader() {
+        sd_ex_api = static_cast<SdCard_Api*>(furi_record_open("sdcard-ex"));
+        fs_api = static_cast<FS_Api*>(furi_record_open("sdcard"));
+        reset();
+    }
+    ~FileReader() {
+        furi_record_close("sdcard");
+        furi_record_close("sdcard-ex");
+    }
+
+    std::string getline(File* file);
+
+    void reset(void) {
+        file_buf_cnt = 0;
+    }
+
+    SdCard_Api& get_sd_api() {
+        return *sd_ex_api;
+    }
+
+    FS_Api& get_fs_api() {
+        return *fs_api;
+    }
+};
+

+ 1 - 0
lib/irda/samsung/irda_encoder_samsung.c

@@ -30,6 +30,7 @@ void irda_encoder_samsung32_encode(uint32_t addr, uint32_t cmd, bool repeat) {
     uint8_t command = cmd & 0xFF;
     uint8_t command_inverse = (uint8_t) ~command;
 
+    irda_encode_space(&encoder_timings, 100);
     if (!repeat) {
         irda_encode_samsung32_preamble();
         irda_encode_byte(&encoder_timings, address);

+ 4 - 0
lib/lib.mk

@@ -87,6 +87,10 @@ C_SOURCES		+= $(wildcard $(LIB_DIR)/drivers/*.c)
 CFLAGS			+= -I$(LIB_DIR)/version
 C_SOURCES		+= $(LIB_DIR)/version/version.c
 
+#file reader
+CFLAGS			+= -I$(LIB_DIR)/file_reader
+CPP_SOURCES		+= $(wildcard $(LIB_DIR)/file_reader/*.cpp)
+
 #irda lib
 CFLAGS			+= -I$(LIB_DIR)/irda
 C_SOURCES		+= $(wildcard $(LIB_DIR)/irda/*.c)

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików