diff --git a/applications/app-loader/app-loader.c b/applications/app-loader/app-loader.c index bd0419d2..bc170e85 100644 --- a/applications/app-loader/app-loader.c +++ b/applications/app-loader/app-loader.c @@ -2,6 +2,7 @@ #include #include "menu/menu.h" #include "applications.h" +#include typedef struct { FuriApp* handler; @@ -87,7 +88,9 @@ void app_loader(void* p) { ctx->app = &FLIPPER_APPS[i]; menu_item_add( - menu, menu_item_alloc_function(FLIPPER_APPS[i].name, NULL, handle_menu, ctx)); + menu, + menu_item_alloc_function( + FLIPPER_APPS[i].name, assets_icons_get(A_Infrared_14), handle_menu, ctx)); } /* diff --git a/applications/applications.h b/applications/applications.h index 003d078d..f06407fe 100644 --- a/applications/applications.h +++ b/applications/applications.h @@ -31,6 +31,7 @@ void cc1101_workaround(void* p); void lf_rfid_workaround(void* p); void nfc_task(void* p); void irukagotchi_task(void* p); +void power_task(void* p); const FlipperStartupApp FLIPPER_STARTUP[] = { #ifdef APP_DISPLAY @@ -59,6 +60,10 @@ const FlipperStartupApp FLIPPER_STARTUP[] = { {.app = irukagotchi_task, .name = "irukagotchi_task", .libs = {1, FURI_LIB{"menu_task"}}}, #endif +#ifdef APP_POWER + {.app = power_task, .name = "power_task", .libs = {1, FURI_LIB{"gui_task"}}}, +#endif + #ifdef APP_CC1101 {.app = cc1101_workaround, .name = "cc1101 workaround", .libs = {1, FURI_LIB{"gui_task"}}}, #endif diff --git a/applications/applications.mk b/applications/applications.mk index beaf03d1..e911a526 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -10,6 +10,7 @@ APP_RELEASE ?= 0 ifeq ($(APP_RELEASE), 1) APP_MENU = 1 APP_NFC = 1 +APP_POWER = 1 BUILD_IRDA = 1 APP_IRUKAGOTCHI = 1 BUILD_EXAMPLE_BLINK = 1 @@ -34,6 +35,13 @@ CFLAGS += -DAPP_IRUKAGOTCHI C_SOURCES += $(wildcard $(APP_DIR)/irukagotchi/*.c) endif +APP_POWER ?= 0 +ifeq ($(APP_POWER), 1) +APP_GUI = 1 +CFLAGS += -DAPP_POWER +C_SOURCES += $(wildcard $(APP_DIR)/power/*.c) +endif + APP_MENU ?= 0 ifeq ($(APP_MENU), 1) CFLAGS += -DAPP_MENU diff --git a/applications/gui/canvas.c b/applications/gui/canvas.c index 77ef2075..d3394413 100644 --- a/applications/gui/canvas.c +++ b/applications/gui/canvas.c @@ -64,6 +64,12 @@ void canvas_api_free(CanvasApi* api) { free(api); } +void canvas_reset(CanvasApi* api) { + assert(api); + canvas_color_set(api, ColorBlack); + canvas_font_set(api, FontSecondary); +} + void canvas_commit(CanvasApi* api) { furi_assert(api); Canvas* canvas = (Canvas*)api; @@ -144,23 +150,33 @@ void canvas_icon_draw(CanvasApi* api, uint8_t x, uint8_t y, Icon* icon) { void canvas_dot_draw(CanvasApi* api, uint8_t x, uint8_t y) { furi_assert(api); Canvas* canvas = (Canvas*)api; + x += canvas->offset_x; + y += canvas->offset_y; u8g2_DrawPixel(&canvas->fb, x, y); } void canvas_box_draw(CanvasApi* api, uint8_t x, uint8_t y, uint8_t width, uint8_t height) { furi_assert(api); Canvas* canvas = (Canvas*)api; + x += canvas->offset_x; + y += canvas->offset_y; u8g2_DrawBox(&canvas->fb, x, y, width, height); } void canvas_draw_frame(CanvasApi* api, uint8_t x, uint8_t y, uint8_t width, uint8_t height) { furi_assert(api); Canvas* canvas = (Canvas*)api; + x += canvas->offset_x; + y += canvas->offset_y; u8g2_DrawFrame(&canvas->fb, x, y, width, height); } void canvas_draw_line(CanvasApi* api, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) { furi_assert(api); Canvas* canvas = (Canvas*)api; + x1 += canvas->offset_x; + y1 += canvas->offset_y; + x2 += canvas->offset_x; + y2 += canvas->offset_y; u8g2_DrawLine(&canvas->fb, x1, y1, x2, y2); } diff --git a/applications/gui/canvas_i.h b/applications/gui/canvas_i.h index 7337db00..1104f75e 100644 --- a/applications/gui/canvas_i.h +++ b/applications/gui/canvas_i.h @@ -4,6 +4,8 @@ CanvasApi* canvas_api_init(); void canvas_api_free(CanvasApi* api); +void canvas_reset(CanvasApi* api); + void canvas_commit(CanvasApi* api); void canvas_frame_set( diff --git a/applications/gui/gui.c b/applications/gui/gui.c index 78b14932..2bd6feec 100644 --- a/applications/gui/gui.c +++ b/applications/gui/gui.c @@ -41,7 +41,7 @@ void gui_update(Gui* gui) { } bool gui_redraw_fs(Gui* gui) { - canvas_frame_set(gui->canvas_api, 0, 0, 128, 64); + canvas_frame_set(gui->canvas_api, 0, 0, GUI_DISPLAY_WIDTH, GUI_DISPLAY_HEIGHT); Widget* widget = gui_widget_find_enabled(gui->layers[GuiLayerFullscreen]); if(widget) { widget_draw(widget, gui->canvas_api); @@ -51,18 +51,48 @@ bool gui_redraw_fs(Gui* gui) { } } -bool gui_redraw_status_bar(Gui* gui) { - canvas_frame_set(gui->canvas_api, 0, 0, 128, 64); - Widget* widget = gui_widget_find_enabled(gui->layers[GuiLayerStatusBar]); - if(widget) { - widget_draw(widget, gui->canvas_api); - return true; +void gui_redraw_status_bar(Gui* gui) { + WidgetArray_it_t it; + uint8_t x; + uint8_t x_used = 0; + uint8_t width; + Widget* widget; + // Right side + x = 128; + WidgetArray_it(it, gui->layers[GuiLayerStatusBarRight]); + while(!WidgetArray_end_p(it) && x_used < GUI_STATUS_BAR_WIDTH) { + // Render widget; + widget = *WidgetArray_ref(it); + if(widget_is_enabled(widget)) { + width = widget_get_width(widget); + if(!width) width = 8; + x_used += width; + x -= width; + canvas_frame_set(gui->canvas_api, x, GUI_STATUS_BAR_Y, width, GUI_STATUS_BAR_HEIGHT); + widget_draw(widget, gui->canvas_api); + } + WidgetArray_next(it); + } + // Left side + x = 0; + WidgetArray_it(it, gui->layers[GuiLayerStatusBarLeft]); + while(!WidgetArray_end_p(it) && x_used < GUI_STATUS_BAR_WIDTH) { + // Render widget; + widget = *WidgetArray_ref(it); + if(widget_is_enabled(widget)) { + width = widget_get_width(widget); + if(!width) width = 8; + x_used += width; + canvas_frame_set(gui->canvas_api, x, GUI_STATUS_BAR_Y, width, GUI_STATUS_BAR_HEIGHT); + widget_draw(widget, gui->canvas_api); + x += width; + } + WidgetArray_next(it); } - return false; } bool gui_redraw_normal(Gui* gui) { - canvas_frame_set(gui->canvas_api, 0, 9, 128, 55); + canvas_frame_set(gui->canvas_api, GUI_MAIN_X, GUI_MAIN_Y, GUI_MAIN_WIDTH, GUI_MAIN_HEIGHT); Widget* widget = gui_widget_find_enabled(gui->layers[GuiLayerMain]); if(widget) { widget_draw(widget, gui->canvas_api); @@ -72,7 +102,7 @@ bool gui_redraw_normal(Gui* gui) { } bool gui_redraw_none(Gui* gui) { - canvas_frame_set(gui->canvas_api, 0, 9, 118, 44); + canvas_frame_set(gui->canvas_api, GUI_MAIN_X, GUI_MAIN_Y, GUI_MAIN_WIDTH, GUI_MAIN_HEIGHT); Widget* widget = gui_widget_find_enabled(gui->layers[GuiLayerNone]); if(widget) { widget_draw(widget, gui->canvas_api); @@ -86,6 +116,8 @@ void gui_redraw(Gui* gui) { furi_assert(gui); gui_lock(gui); + canvas_reset(gui->canvas_api); + if(!gui_redraw_fs(gui)) { if(!gui_redraw_normal(gui)) { gui_redraw_none(gui); diff --git a/applications/gui/gui.h b/applications/gui/gui.h index 69cd5c37..c7fe1ced 100644 --- a/applications/gui/gui.h +++ b/applications/gui/gui.h @@ -3,10 +3,24 @@ #include "widget.h" #include "canvas.h" +#define GUI_DISPLAY_WIDTH 128 +#define GUI_DISPLAY_HEIGHT 64 + +#define GUI_STATUS_BAR_X 0 +#define GUI_STATUS_BAR_Y 0 +#define GUI_STATUS_BAR_WIDTH GUI_DISPLAY_WIDTH +#define GUI_STATUS_BAR_HEIGHT 8 + +#define GUI_MAIN_X 0 +#define GUI_MAIN_Y 9 +#define GUI_MAIN_WIDTH GUI_DISPLAY_WIDTH +#define GUI_MAIN_HEIGHT (GUI_DISPLAY_HEIGHT - GUI_MAIN_Y) + typedef enum { GuiLayerNone, /* Special layer for internal use only */ - GuiLayerStatusBar, /* Status bar widget layer */ + GuiLayerStatusBarLeft, /* Status bar left-side widget layer, auto-layout */ + GuiLayerStatusBarRight, /* Status bar right-side widget layer, auto-layout */ GuiLayerMain, /* Main widget layer, status bar is shown */ GuiLayerFullscreen, /* Fullscreen widget layer */ diff --git a/applications/gui/gui_event.c b/applications/gui/gui_event.c index bc532a26..24707e8d 100644 --- a/applications/gui/gui_event.c +++ b/applications/gui/gui_event.c @@ -6,9 +6,20 @@ struct GuiEvent { PubSub* input_event_record; + osTimerId_t timer; osMessageQueueId_t mqueue; }; +void gui_event_timer_callback(void* arg) { + assert(arg); + GuiEvent* gui_event = arg; + + GuiMessage message; + message.type = GuiMessageTypeRedraw; + + osMessageQueuePut(gui_event->mqueue, &message, 0, osWaitForever); +} + void gui_event_input_events_callback(const void* value, void* ctx) { furi_assert(value); furi_assert(ctx); @@ -29,6 +40,10 @@ GuiEvent* gui_event_alloc() { gui_event->mqueue = osMessageQueueNew(GUI_EVENT_MQUEUE_SIZE, sizeof(GuiMessage), NULL); furi_check(gui_event->mqueue); + gui_event->timer = osTimerNew(gui_event_timer_callback, osTimerPeriodic, gui_event, NULL); + assert(gui_event->timer); + osTimerStart(gui_event->timer, 1000 / 10); + // Input gui_event->input_event_record = furi_open("input_events"); furi_check(gui_event->input_event_record != NULL); diff --git a/applications/gui/widget.c b/applications/gui/widget.c index 2b231efe..2df0853b 100644 --- a/applications/gui/widget.c +++ b/applications/gui/widget.c @@ -10,15 +10,6 @@ // TODO add mutex to widget ops -struct Widget { - Gui* gui; - bool is_enabled; - WidgetDrawCallback draw_callback; - void* draw_callback_context; - WidgetInputCallback input_callback; - void* input_callback_context; -}; - Widget* widget_alloc(WidgetDrawCallback callback, void* callback_context) { Widget* widget = furi_alloc(sizeof(Widget)); widget->is_enabled = true; @@ -31,6 +22,26 @@ void widget_free(Widget* widget) { free(widget); } +void widget_set_width(Widget* widget, uint8_t width) { + assert(widget); + widget->width = width; +} + +uint8_t widget_get_width(Widget* widget) { + assert(widget); + return widget->width; +} + +void widget_set_height(Widget* widget, uint8_t height) { + assert(widget); + widget->height = height; +} + +uint8_t widget_get_height(Widget* widget) { + assert(widget); + return widget->height; +} + void widget_enabled_set(Widget* widget, bool enabled) { furi_assert(widget); if(widget->is_enabled != enabled) { @@ -80,7 +91,9 @@ void widget_draw(Widget* widget, CanvasApi* canvas_api) { void widget_input(Widget* widget, InputEvent* event) { furi_assert(widget); furi_assert(event); - furi_check(widget->gui); - if(widget->input_callback) widget->input_callback(event, widget->input_callback_context); + + if(widget->input_callback) { + widget->input_callback(event, widget->input_callback_context); + } } diff --git a/applications/gui/widget.h b/applications/gui/widget.h index 83f7d497..d93b21a4 100644 --- a/applications/gui/widget.h +++ b/applications/gui/widget.h @@ -8,14 +8,51 @@ typedef struct Widget Widget; typedef void (*WidgetDrawCallback)(CanvasApi* api, void* context); typedef void (*WidgetInputCallback)(InputEvent* event, void* context); +/* + * Widget allocator + * always returns widget or stops system if not enough memory. + */ Widget* widget_alloc(); + +/* + * Widget deallocator + * Ensure that widget was unregistered in GUI system before use. + */ void widget_free(Widget* widget); +/* + * Set widget width. + * Will be used to limit canvas drawing area and autolayout feature. + * @param width - wanted width, 0 - auto. + */ +void widget_set_width(Widget* widget, uint8_t width); +uint8_t widget_get_width(Widget* widget); + +/* + * Set widget height. + * Will be used to limit canvas drawing area and autolayout feature. + * @param height - wanted height, 0 - auto. + */ +void widget_set_height(Widget* widget, uint8_t height); +uint8_t widget_get_height(Widget* widget); + +/* + * Enable or disable widget rendering. + * @param enabled. + */ void widget_enabled_set(Widget* widget, bool enabled); bool widget_is_enabled(Widget* widget); +/* + * Widget event callbacks + * @param callback - appropriate callback function + * @param context - context to pass to callback + */ void widget_draw_callback_set(Widget* widget, WidgetDrawCallback callback, void* context); void widget_input_callback_set(Widget* widget, WidgetInputCallback callback, void* context); -// emit update signal +/* + * Emit update signal to GUI system. + * Rendering will happen later after GUI system process signal. + */ void widget_update(Widget* widget); diff --git a/applications/gui/widget_i.h b/applications/gui/widget_i.h index b3d481e9..e48a08f7 100644 --- a/applications/gui/widget_i.h +++ b/applications/gui/widget_i.h @@ -2,8 +2,31 @@ #include "gui_i.h" +struct Widget { + Gui* gui; + bool is_enabled; + uint8_t width; + uint8_t height; + WidgetDrawCallback draw_callback; + void* draw_callback_context; + WidgetInputCallback input_callback; + void* input_callback_context; +}; + +/* + * Set GUI referenec. + * @param gui - gui instance pointer. + */ void widget_gui_set(Widget* widget, Gui* gui); +/* + * Process draw call. Calls draw callback. + * @param canvas_api - canvas to draw at. + */ void widget_draw(Widget* widget, CanvasApi* canvas_api); +/* + * Process input. Calls input callback. + * @param event - pointer to input event. + */ void widget_input(Widget* widget, InputEvent* event); diff --git a/applications/irukagotchi/irukagotchi.c b/applications/irukagotchi/irukagotchi.c index 7698a3f6..16d6e734 100644 --- a/applications/irukagotchi/irukagotchi.c +++ b/applications/irukagotchi/irukagotchi.c @@ -21,8 +21,9 @@ void irukagotchi_draw_callback(CanvasApi* canvas, void* context) { canvas->clear(canvas); canvas->set_color(canvas, ColorBlack); canvas->set_font(canvas, FontPrimary); - canvas->draw_icon(canvas, 10, 20, irukagotchi->icon); - canvas->draw_str(canvas, 30, 32, "Irukagotchi"); + canvas->draw_icon(canvas, 0, 0, irukagotchi->icon); + canvas->draw_str(canvas, 80, 30, "111001"); + canvas->draw_str(canvas, 80, 42, "011010"); } void irukagotchi_input_callback(InputEvent* event, void* context) { @@ -37,7 +38,7 @@ void irukagotchi_input_callback(InputEvent* event, void* context) { Irukagotchi* irukagotchi_alloc() { Irukagotchi* irukagotchi = furi_alloc(sizeof(Irukagotchi)); - irukagotchi->icon = assets_icons_get(A_Tamagotchi_14); + irukagotchi->icon = assets_icons_get(I_Flipper_young_80x60); icon_start_animation(irukagotchi->icon); irukagotchi->widget = widget_alloc(); diff --git a/applications/menu/menu.c b/applications/menu/menu.c index cd110c42..26284f7c 100644 --- a/applications/menu/menu.c +++ b/applications/menu/menu.c @@ -9,12 +9,14 @@ #include "menu_event.h" #include "menu_item.h" +#include struct Menu { MenuEvent* event; // GUI Widget* widget; + Icon* icon; // State MenuItem* root; @@ -56,7 +58,8 @@ void menu_build_main(Menu* menu) { // Root point menu->root = menu_item_alloc_menu(NULL, NULL); - menu->settings = menu_item_alloc_menu("Setting", NULL); + Icon* icon = assets_icons_get(A_Bluetooth_14); + menu->settings = menu_item_alloc_menu("Setting", icon); menu_item_add(menu, menu->settings); } @@ -103,16 +106,18 @@ void menu_widget_callback(CanvasApi* canvas, void* context) { canvas->set_font(canvas, FontPrimary); shift_position = (1 + position + items_count - 1) % (MenuItemArray_size(*items)); item = *MenuItemArray_get(*items, shift_position); - canvas->draw_icon(canvas, 4, 24, menu_item_get_icon(item)); - canvas->draw_str(canvas, 22, 35, menu_item_get_label(item)); + canvas->draw_icon(canvas, 4, 25, menu_item_get_icon(item)); + canvas->draw_str(canvas, 22, 36, menu_item_get_label(item)); // Third line canvas->set_font(canvas, FontSecondary); shift_position = (2 + position + items_count - 1) % (MenuItemArray_size(*items)); item = *MenuItemArray_get(*items, shift_position); - canvas->draw_icon(canvas, 4, 46, menu_item_get_icon(item)); - canvas->draw_str(canvas, 22, 57, menu_item_get_label(item)); + canvas->draw_icon(canvas, 4, 47, menu_item_get_icon(item)); + canvas->draw_str(canvas, 22, 58, menu_item_get_label(item)); // Frame and scrollbar - elements_frame(canvas, 0, 20, 128 - 5, 22); + // elements_frame(canvas, 0, 0, 128 - 5, 21); + elements_frame(canvas, 0, 21, 128 - 5, 21); + // elements_frame(canvas, 0, 42, 128 - 5, 21); elements_scrollbar(canvas, position, items_count); } else { canvas->draw_str(canvas, 2, 32, "Empty"); @@ -122,9 +127,33 @@ void menu_widget_callback(CanvasApi* canvas, void* context) { release_mutex((ValueMutex*)context, menu); } +void menu_set_icon(Menu* menu, Icon* icon) { + assert(menu); + + if(menu->icon) { + icon_stop_animation(menu->icon); + } + + menu->icon = icon; + + if(menu->icon) { + icon_start_animation(menu->icon); + } +} + void menu_update(Menu* menu) { furi_assert(menu); + if(menu->current) { + size_t position = menu_item_get_position(menu->current); + MenuItemArray_t* items = menu_item_get_subitems(menu->current); + size_t items_count = MenuItemArray_size(*items); + if(items_count) { + MenuItem* item = *MenuItemArray_get(*items, position); + menu_set_icon(menu, menu_item_get_icon(item)); + } + } + menu_event_activity_notify(menu->event); widget_update(menu->widget); } diff --git a/applications/power/power.c b/applications/power/power.c new file mode 100644 index 00000000..36804a16 --- /dev/null +++ b/applications/power/power.c @@ -0,0 +1,117 @@ +#include "power.h" + +#include +#include +#include +#include + +#define BATTERY_MIN_VOLTAGE 3.2f +#define BATTERY_MAX_VOLTAGE 4.0f +#define BATTERY_INIT 0xFFAACCEE + +extern ADC_HandleTypeDef hadc1; + +struct Power { + Icon* usb_icon; + Widget* usb_widget; + + Icon* battery_icon; + Widget* battery_widget; + + uint32_t charge; +}; + +void power_draw_usb_callback(CanvasApi* canvas, void* context) { + assert(context); + Power* power = context; + canvas->draw_icon(canvas, 0, 0, power->usb_icon); +} + +void power_draw_battery_callback(CanvasApi* canvas, void* context) { + assert(context); + Power* power = context; + + canvas->draw_icon(canvas, 0, 0, power->battery_icon); + + if(power->charge != BATTERY_INIT) { + float charge = ((float)power->charge / 1000 * 2 - BATTERY_MIN_VOLTAGE) / + (BATTERY_MAX_VOLTAGE - BATTERY_MIN_VOLTAGE); + if(charge > 1) { + charge = 1; + } + canvas->draw_box(canvas, 2, 2, charge * 14, 4); + } +} + +void power_input_events_callback(const void* value, void* ctx) { + assert(ctx); + Power* power = ctx; + InputEvent* event = value; + + if(event->input != InputCharging) return; + + widget_enabled_set(power->usb_widget, event->state); + widget_update(power->usb_widget); +} + +Power* power_alloc() { + Power* power = furi_alloc(sizeof(Power)); + + power->usb_icon = assets_icons_get(I_USBConnected_15x8); + power->usb_widget = widget_alloc(); + widget_set_width(power->usb_widget, icon_get_width(power->usb_icon)); + + ValueManager* input_state_manager = furi_open("input_state"); + InputState input_state; + read_mutex_block(input_state_manager, &input_state, sizeof(input_state)); + widget_enabled_set(power->usb_widget, input_state.charging); + + widget_draw_callback_set(power->usb_widget, power_draw_usb_callback, power); + + power->battery_icon = assets_icons_get(I_Battery_19x8); + power->battery_widget = widget_alloc(); + widget_set_width(power->battery_widget, icon_get_width(power->battery_icon)); + widget_draw_callback_set(power->battery_widget, power_draw_battery_callback, power); + + PubSub* input_event_record = furi_open("input_events"); + assert(input_event_record); + subscribe_pubsub(input_event_record, power_input_events_callback, power); + + power->charge = BATTERY_INIT; + + return power; +} + +void power_free(Power* power) { + assert(power); + free(power); +} + +void power_task(void* p) { + (void)p; + Power* power = power_alloc(); + + FuriRecordSubscriber* gui_record = furi_open_deprecated("gui", false, false, NULL, NULL, NULL); + assert(gui_record); + GuiApi* gui = furi_take(gui_record); + assert(gui); + gui->add_widget(gui, power->usb_widget, GuiLayerStatusBarLeft); + gui->add_widget(gui, power->battery_widget, GuiLayerStatusBarRight); + furi_commit(gui_record); + + if(!furi_create("power", power)) { + printf("[power_task] unable to create power record\n"); + furiac_exit(NULL); + } + + furiac_ready(); + + while(1) { + HAL_ADC_Start(&hadc1); + if(HAL_ADC_PollForConversion(&hadc1, 1000) != HAL_TIMEOUT) { + power->charge = HAL_ADC_GetValue(&hadc1); + widget_update(power->battery_widget); + } + osDelay(1000); + } +} diff --git a/applications/power/power.h b/applications/power/power.h new file mode 100644 index 00000000..f43b9c3f --- /dev/null +++ b/applications/power/power.h @@ -0,0 +1,3 @@ +#pragma once + +typedef struct Power Power; diff --git a/assets/icons/IrukaGotchi/Flipper_idle_76x52.png b/assets/icons/IrukaGotchi/Flipper_idle_76x52.png new file mode 100644 index 00000000..90a0c556 --- /dev/null +++ b/assets/icons/IrukaGotchi/Flipper_idle_76x52.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:526c2637560aab102f259c029eeee93cc13e5cd8bdb935acb624f1318502f286 +size 652 diff --git a/assets/icons/IrukaGotchi/Flipper_young_80x60.png b/assets/icons/IrukaGotchi/Flipper_young_80x60.png new file mode 100644 index 00000000..bc84e7a8 --- /dev/null +++ b/assets/icons/IrukaGotchi/Flipper_young_80x60.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7d2c5da9b957635189c2ee475d7065ae714fd6fb5d6f0c83ca5fa0bcd1497f8 +size 2653