diff --git a/app.c b/app.c index 0c8a54e5c7a..d583dcc581a 100644 --- a/app.c +++ b/app.c @@ -59,6 +59,9 @@ static void render_callback(Canvas *const canvas, void *ctx) { case ViewDirectSampling: render_view_direct_sampling(canvas,app); break; default: furi_crash(TAG "Invalid view selected"); break; } + + /* Draw the alert box if set. */ + ui_draw_alert_if_needed(canvas, app); } /* Here all we do is putting the events into the queue that will be handled @@ -106,6 +109,7 @@ static void app_switch_view(ProtoViewApp *app, ProtoViewCurrentView switchto) { * the main thing. */ app->current_subview[old] = 0; memset(app->view_privdata,0,PROTOVIEW_VIEW_PRIVDATA_LEN); + ui_dismiss_alert(app); } /* Allocate the application state and initialize a number of stuff. @@ -132,6 +136,7 @@ ProtoViewApp* protoview_app_alloc() { app->view_dispatcher = NULL; app->text_input = NULL; app->show_text_input = false; + app->alert_dismiss_time = 0; app->current_view = ViewRawPulses; for (int j = 0; j < ViewLast; j++) app->current_subview[j] = 0; app->direct_sampling_enabled = false; @@ -269,17 +274,27 @@ int32_t protoview_app_entry(void* p) { if (input.type == InputTypeShort && input.key == InputKeyBack) { - /* Exit the app. */ + if (app->current_view != ViewRawPulses) { + /* If this is not the main app view, go there. */ + app_switch_view(app,ViewRawPulses); + } else { + /* If we are in the main app view, warn the user + * they needs to long press to really quit. */ + ui_show_alert(app,"Long press to exit",1000); + } + } else if (input.type == InputTypeLong && + input.key == InputKeyBack) + { app->running = 0; } else if (input.type == InputTypeShort && input.key == InputKeyRight && - get_current_subview(app) == 0) + ui_get_current_subview(app) == 0) { /* Go to the next view. */ app_switch_view(app,ViewGoNext); } else if (input.type == InputTypeShort && input.key == InputKeyLeft && - get_current_subview(app) == 0) + ui_get_current_subview(app) == 0) { /* Go to the previous view. */ app_switch_view(app,ViewGoPrev); diff --git a/app.h b/app.h index 1082e0f833e..5f2e4379bd4 100644 --- a/app.h +++ b/app.h @@ -116,6 +116,7 @@ typedef struct ProtoViewMsgInfo { } ProtoViewMsgInfo; /* Our main application context. */ +#define ALERT_MAX_LEN 32 struct ProtoViewApp { /* GUI */ Gui *gui; @@ -125,6 +126,8 @@ struct ProtoViewApp { ProtoViewCurrentView current_view; /* Active left-right view ID. */ int current_subview[ViewLast]; /* Active up-down subview ID. */ FuriMessageQueue *event_queue; /* Keypress events go here. */ + + /* Input text state. */ ViewDispatcher *view_dispatcher; /* Used only when we want to show the text_input view for a moment. Otherwise it is set to null. */ @@ -134,6 +137,12 @@ struct ProtoViewApp { uint32_t text_input_buffer_len; void (*text_input_done_callback)(void*); + /* Alert state. */ + uint32_t alert_dismiss_time; /* Millisecond when the alert will be + no longer shown. Or zero if the alert + is currently not set at all. */ + char alert_text[ALERT_MAX_LEN]; /* Alert content. */ + /* Radio related. */ ProtoViewTxRx *txrx; /* Radio state. */ SubGhzSetting *setting; /* A list of valid frequencies. */ @@ -222,13 +231,16 @@ void view_exit_direct_sampling(ProtoViewApp *app); void view_exit_settings(ProtoViewApp *app); /* ui.c */ -int get_current_subview(ProtoViewApp *app); -void show_available_subviews(Canvas *canvas, ProtoViewApp *app, int last_subview); -bool process_subview_updown(ProtoViewApp *app, InputEvent input, int last_subview); -void canvas_draw_str_with_border(Canvas* canvas, uint8_t x, uint8_t y, const char* str, Color text_color, Color border_color); -void show_keyboard(ProtoViewApp *app, char *buffer, uint32_t buflen, +int ui_get_current_subview(ProtoViewApp *app); +void ui_show_available_subviews(Canvas *canvas, ProtoViewApp *app, int last_subview); +bool ui_process_subview_updown(ProtoViewApp *app, InputEvent input, int last_subview); +void ui_show_keyboard(ProtoViewApp *app, char *buffer, uint32_t buflen, void (*done_callback)(void*)); -void dismiss_keyboard(ProtoViewApp *app); +void ui_dismiss_keyboard(ProtoViewApp *app); +void ui_show_alert(ProtoViewApp *app, const char *text, uint32_t ttl); +void ui_dismiss_alert(ProtoViewApp *app); +void ui_draw_alert_if_needed(Canvas *canvas, ProtoViewApp *app); +void canvas_draw_str_with_border(Canvas* canvas, uint8_t x, uint8_t y, const char* str, Color text_color, Color border_color); /* crc.c */ uint8_t crc8(const uint8_t *data, size_t len, uint8_t init, uint8_t poly); diff --git a/ui.c b/ui.c index 325fc5efc0b..8badab5bf8a 100644 --- a/ui.c +++ b/ui.c @@ -10,17 +10,17 @@ /* Return the ID of the currently selected subview, of the current * view. */ -int get_current_subview(ProtoViewApp *app) { +int ui_get_current_subview(ProtoViewApp *app) { return app->current_subview[app->current_view]; } /* Called by view rendering callback that has subviews, to show small triangles * facing down/up if there are other subviews the user can access with up * and down. */ -void show_available_subviews(Canvas *canvas, ProtoViewApp *app, +void ui_show_available_subviews(Canvas *canvas, ProtoViewApp *app, int last_subview) { - int subview = get_current_subview(app); + int subview = ui_get_current_subview(app); if (subview != 0) canvas_draw_triangle(canvas,120,5,8,5,CanvasDirectionBottomToTop); if (subview != last_subview-1) @@ -30,8 +30,8 @@ void show_available_subviews(Canvas *canvas, ProtoViewApp *app, /* Handle up/down keys when we are in a subview. If the function catched * such keypress, it returns true, so that the actual view input callback * knows it can just return ASAP without doing anything. */ -bool process_subview_updown(ProtoViewApp *app, InputEvent input, int last_subview) { - int subview = get_current_subview(app); +bool ui_process_subview_updown(ProtoViewApp *app, InputEvent input, int last_subview) { + int subview = ui_get_current_subview(app); if (input.type == InputTypePress) { if (input.key == InputKeyUp) { if (subview != 0) @@ -62,7 +62,7 @@ bool process_subview_updown(ProtoViewApp *app, InputEvent input, int last_subvie * * Note: if the buffer is not a null-termined zero string, what it contains will * be used as initial input for the user. */ -void show_keyboard(ProtoViewApp *app, char *buffer, uint32_t buflen, +void ui_show_keyboard(ProtoViewApp *app, char *buffer, uint32_t buflen, void (*done_callback)(void*)) { app->show_text_input = true; @@ -71,10 +71,52 @@ void show_keyboard(ProtoViewApp *app, char *buffer, uint32_t buflen, app->text_input_done_callback = done_callback; } -void dismiss_keyboard(ProtoViewApp *app) { +void ui_dismiss_keyboard(ProtoViewApp *app) { view_dispatcher_stop(app->view_dispatcher); } +/* ================================= Alert ================================== */ + +/* Set an alert message to be shown over any currently active view, for + * the specified amount of time of 'ttl' milliseconds. */ +void ui_show_alert(ProtoViewApp *app, const char *text, uint32_t ttl) { + app->alert_dismiss_time = furi_get_tick() + furi_ms_to_ticks(ttl); + snprintf(app->alert_text,ALERT_MAX_LEN,"%s",text); +} + +/* Cancel the alert before its time has elapsed. */ +void ui_dismiss_alert(ProtoViewApp *app) { + app->alert_dismiss_time = 0; +} + +/* Show the alert if an alert is set. This is called after the currently + * active view displayed its stuff, so we overwrite the screen with the + * alert message. */ +void ui_draw_alert_if_needed(Canvas *canvas, ProtoViewApp *app) { + if (app->alert_dismiss_time == 0) { + /* No active alert. */ + return; + } else if (app->alert_dismiss_time < furi_get_tick()) { + /* Alert just expired. */ + ui_dismiss_alert(app); + return; + } + + /* Show the alert. A box with black border and a text inside. */ + canvas_set_font(canvas, FontPrimary); + uint8_t w = canvas_string_width(canvas, app->alert_text); + uint8_t h = 8; // Font height. + uint8_t text_x = 64-(w/2); + uint8_t text_y = 32+4; + uint8_t padding = 3; + canvas_set_color(canvas,ColorBlack); + canvas_draw_box(canvas,text_x-padding,text_y-padding-h,w+padding*2,h+padding*2); + canvas_set_color(canvas,ColorWhite); + canvas_draw_box(canvas,text_x-padding+1,text_y-padding-h+1,w+padding*2-2,h+padding*2-2); + canvas_set_color(canvas,ColorBlack); + canvas_draw_str(canvas,text_x,text_y,app->alert_text); +} + /* =========================== Canvas extensions ============================ */ void canvas_draw_str_with_border(Canvas* canvas, uint8_t x, uint8_t y, const char* str, Color text_color, Color border_color) diff --git a/view_info.c b/view_info.c index 396f8aac36b..6591d75a51f 100644 --- a/view_info.c +++ b/view_info.c @@ -96,7 +96,7 @@ void render_view_info(Canvas *const canvas, ProtoViewApp *app) { return; } - show_available_subviews(canvas,app,SubViewInfoLast); + ui_show_available_subviews(canvas,app,SubViewInfoLast); switch(app->current_subview[app->current_view]) { case SubViewInfoMain: render_subview_main(canvas,app); break; case SubViewInfoSave: render_subview_save(canvas,app); break; @@ -115,7 +115,7 @@ void text_input_done_callback(void* context) { furi_string_free(save_path); free(privdata->filename); - dismiss_keyboard(app); + ui_dismiss_keyboard(app); } /* Replace all the occurrences of character c1 with c2 in the specified @@ -253,9 +253,13 @@ void notify_signal_sent(ProtoViewApp *app) { /* Handle input for the info view. */ void process_input_info(ProtoViewApp *app, InputEvent input) { - if (process_subview_updown(app,input,SubViewInfoLast)) return; + /* If we don't have a decoded signal, we don't allow to go up/down + * in the subviews: they are only useful when a loaded signal. */ + if (app->signal_decoded && + ui_process_subview_updown(app,input,SubViewInfoLast)) return; + InfoViewPrivData *privdata = app->view_privdata; - int subview = get_current_subview(app); + int subview = ui_get_current_subview(app); /* Main subview. */ if (subview == SubViewInfoMain) { @@ -274,8 +278,8 @@ void process_input_info(ProtoViewApp *app, InputEvent input) { { privdata->filename = malloc(SAVE_FILENAME_LEN); set_signal_random_filename(app,privdata->filename,SAVE_FILENAME_LEN); - show_keyboard(app, privdata->filename, SAVE_FILENAME_LEN, - text_input_done_callback); + ui_show_keyboard(app, privdata->filename, SAVE_FILENAME_LEN, + text_input_done_callback); } else if (input.type == InputTypeShort && input.key == InputKeyOk) { SendSignalCtx send_state; send_signal_init(&send_state,app);