diff --git a/base_pack/barcode_gen/CHANGELOG.md b/base_pack/barcode_gen/CHANGELOG.md new file mode 100644 index 00000000..fcc65a4f --- /dev/null +++ b/base_pack/barcode_gen/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +## 1.5 + +- Fix the first save after a fresh install always failing with "A saving error has occurred": the barcodes folder was only created when the app exited; it is now created on startup and rechecked before every save +- Show the actual save error instead of a generic message: empty name or data, a file with this name already exists, SD card not accessible, rename failure +- Fix renaming a barcode to the name of another saved barcode silently overwriting it - it is now rejected with a clear message +- Fix "File Saved!" being reported when the file could not be fully written (e.g. SD card removed mid-save) +- Save and delete failures now return to the edit form with the entered data intact instead of dropping to the main menu + +## 1.4 + +- Add a custom keyboard (by thevan4): symbols can be entered for barcode data, while characters not allowed in file names stay hidden when naming a file +- Update for a newer firmware API (no functional changes) + +## 1.3 + +- Keep the display backlight on while a barcode is shown +- Version bump for catalog compatibility + +## 1.2 + +- Add an "Error Codes Info" screen to the main menu explaining every error the app can show +- Add an About screen +- Fix CODE-128 barcodes containing `#` producing an "Invalid Characters" error +- Fix the CODE-128 check digit encoding lookup for check digit values below 100 + +## 1.1 + +- Initial release in this pack: create, edit and display UPC-A, EAN-8, EAN-13, CODE-39, Codabar, CODE-128 (Set B) and CODE-128C barcodes; saved barcodes are stored in `/apps_data/barcodes` diff --git a/base_pack/barcode_gen/application.fam b/base_pack/barcode_gen/application.fam index 5608ba5a..8b00b152 100644 --- a/base_pack/barcode_gen/application.fam +++ b/base_pack/barcode_gen/application.fam @@ -11,6 +11,6 @@ App( fap_file_assets="barcode_encoding_files", fap_author="@Kingal1337", fap_weburl="https://github.com/Kingal1337/flipper-barcode-generator", - fap_version="1.4", + fap_version="1.5", fap_description="App allows you to display various barcodes on flipper screen", ) diff --git a/base_pack/barcode_gen/barcode_app.c b/base_pack/barcode_gen/barcode_app.c index 16c27046..7f8b5c00 100644 --- a/base_pack/barcode_gen/barcode_app.c +++ b/base_pack/barcode_gen/barcode_app.c @@ -95,17 +95,19 @@ bool get_file_name_from_path(FuriString* file_path, FuriString* file_name, bool } /** - * Creates the barcode folder + * Creates the barcode folder if it does not exist + * @returns true if the folder exists or was created */ -void init_folder() { +bool init_folder() { Storage* storage = furi_record_open(RECORD_STORAGE); - FURI_LOG_I(TAG, "Creating barcodes folder"); - if(storage_simply_mkdir(storage, DEFAULT_USER_BARCODES)) { - FURI_LOG_I(TAG, "Barcodes folder successfully created!"); - } else { - FURI_LOG_I(TAG, "Barcodes folder already exists."); + //simply_mkdir also returns true when the folder already exists, + //false is a real failure, e.g. a missing SD card + bool folder_ready = storage_simply_mkdir(storage, DEFAULT_USER_BARCODES); + if(!folder_ready) { + FURI_LOG_E(TAG, "Could not create the barcodes folder"); } furi_record_close(RECORD_STORAGE); + return folder_ready; } void select_barcode_item(BarcodeApp* app) { @@ -180,15 +182,7 @@ void edit_barcode_item(BarcodeApp* app) { reason = read_raw_data(file_path, raw_type, raw_data); if(reason != OKCode) { FURI_LOG_E(TAG, "Could not read data correctly"); - with_view_model( - app->message_view->view, - MessageViewModel * model, - { model->message = get_error_code_message(reason); }, - true); - - view_dispatcher_switch_to_view( - create_view_object->barcode_app->view_dispatcher, MessageErrorView); - + message_view_show(app->message_view, get_error_code_message(reason), MainMenuView); } else { BarcodeTypeObj* type_obj = get_type(raw_type); if(type_obj->type == UNKNOWN) { @@ -275,7 +269,6 @@ uint32_t exit_callback(void* context) { void free_app(BarcodeApp* app) { FURI_LOG_I(TAG, "Freeing Data"); - init_folder(); free_types(); view_dispatcher_remove_view(app->view_dispatcher, TextInputView); @@ -320,6 +313,7 @@ int32_t barcode_main(void* p) { UNUSED(p); BarcodeApp* app = malloc(sizeof(BarcodeApp)); init_types(); + init_folder(); app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); // Register view port in GUI diff --git a/base_pack/barcode_gen/barcode_app.h b/base_pack/barcode_gen/barcode_app.h index d5e7d6b2..a18e2a46 100644 --- a/base_pack/barcode_gen/barcode_app.h +++ b/base_pack/barcode_gen/barcode_app.h @@ -85,6 +85,8 @@ enum Views { void submenu_callback(void* context, uint32_t index); +bool init_folder(); + uint32_t main_menu_callback(void* context); uint32_t exit_callback(void* context); diff --git a/base_pack/barcode_gen/views/create_view.c b/base_pack/barcode_gen/views/create_view.c index d359a3d7..c4e130ba 100644 --- a/base_pack/barcode_gen/views/create_view.c +++ b/base_pack/barcode_gen/views/create_view.c @@ -353,20 +353,13 @@ void remove_barcode(CreateView* create_view_object) { true); furi_record_close(RECORD_STORAGE); - with_view_model( - create_view_object->barcode_app->message_view->view, - MessageViewModel * model, - { - if(success) { - model->message = "File Deleted"; - } else { - model->message = "Could not delete file"; - } - }, - true); - - view_dispatcher_switch_to_view( - create_view_object->barcode_app->view_dispatcher, MessageErrorView); + MessageView* message_view = create_view_object->barcode_app->message_view; + if(success) { + message_view_show(message_view, "File Deleted", MainMenuView); + } else { + //stay in the edit view so the user can retry + message_view_show(message_view, "Could not delete file", CreateBarcodeView); + } } void save_barcode(CreateView* create_view_object) { @@ -388,20 +381,26 @@ void save_barcode(CreateView* create_view_object) { }, true); + MessageView* message_view = create_view_object->barcode_app->message_view; + if(file_name == NULL || furi_string_empty(file_name)) { FURI_LOG_E(TAG, "File Name cannot be empty"); + message_view_show(message_view, "File name\ncannot be empty", CreateBarcodeView); return; } if(barcode_data == NULL || furi_string_empty(barcode_data)) { FURI_LOG_E(TAG, "Barcode Data cannot be empty"); + message_view_show(message_view, "Barcode data\ncannot be empty", CreateBarcodeView); return; } if(barcode_type == NULL) { FURI_LOG_E(TAG, "Type not defined"); + message_view_show(message_view, "Barcode type\nis not selected", CreateBarcodeView); return; } - bool success = false; + //NULL while the save is still going fine + const char* error_message = NULL; FuriString* full_file_path = furi_string_alloc_set(DEFAULT_USER_BARCODES); furi_string_push_back(full_file_path, '/'); @@ -410,76 +409,84 @@ void save_barcode(CreateView* create_view_object) { Storage* storage = furi_record_open(RECORD_STORAGE); - if(mode == EditMode) { - if(!furi_string_empty(file_path)) { - if(!furi_string_equal(file_path, full_file_path)) { - FS_Error error = storage_common_rename( - storage, - furi_string_get_cstr(file_path), - furi_string_get_cstr(full_file_path)); - if(error != FSE_OK) { - FURI_LOG_E(TAG, "Rename error: %s", storage_error_get_desc(error)); - } else { - FURI_LOG_I(TAG, "Rename Success"); - } - } - } + //ensure the barcodes folder exists before saving, + //it could have been removed while the app was running + if(!init_folder()) { + error_message = "Cannot access\nthe SD card"; } - FlipperFormat* ff = flipper_format_file_alloc(storage); - - FURI_LOG_I(TAG, "Saving Barcode to: %s", furi_string_get_cstr(full_file_path)); - - bool file_opened_status = false; - if(mode == NewMode) { - file_opened_status = - flipper_format_file_open_new(ff, furi_string_get_cstr(full_file_path)); - } else if(mode == EditMode) { - file_opened_status = - flipper_format_file_open_always(ff, furi_string_get_cstr(full_file_path)); + //overwriting the barcode's own file in edit mode is fine, but saving + //over any other existing file would destroy it, fail with a clear message instead + bool same_file = file_path != NULL && furi_string_equal(file_path, full_file_path); + if(error_message == NULL && !same_file && + storage_file_exists(storage, furi_string_get_cstr(full_file_path))) { + FURI_LOG_E(TAG, "File \"%s\" already exists", furi_string_get_cstr(full_file_path)); + error_message = "A file with this\nname already exists"; } - if(file_opened_status) { - // Filetype: Barcode - // Version: 1 - - // # Types - UPC-A, EAN-8, EAN-13, CODE-39 - // Type: CODE-39 - // Data: AB - flipper_format_write_string_cstr(ff, "Filetype", "Barcode"); - - flipper_format_write_string_cstr(ff, "Version", FILE_VERSION); - - flipper_format_write_comment_cstr( - ff, "Types - UPC-A, EAN-8, EAN-13, CODE-39, CODE-128, Codabar"); - - flipper_format_write_string_cstr(ff, "Type", barcode_type->name); - - flipper_format_write_string_cstr(ff, "Data", furi_string_get_cstr(barcode_data)); + //in edit mode a changed name moves the old file before saving + if(error_message == NULL && mode == EditMode && !same_file && !furi_string_empty(file_path)) { + FS_Error error = storage_common_rename( + storage, furi_string_get_cstr(file_path), furi_string_get_cstr(full_file_path)); + if(error != FSE_OK) { + FURI_LOG_E(TAG, "Rename error: %s", storage_error_get_desc(error)); + error_message = "Could not rename\nthe barcode file"; + } else { + FURI_LOG_I(TAG, "Rename Success"); + //keep the model path in sync so a failed save below + //does not leave it pointing at the old, now removed file + furi_string_set(file_path, full_file_path); + } + } - success = true; - } else { - FURI_LOG_E(TAG, "Save error"); - success = false; + FlipperFormat* ff = NULL; + if(error_message == NULL) { + FURI_LOG_I(TAG, "Saving Barcode to: %s", furi_string_get_cstr(full_file_path)); + ff = flipper_format_file_alloc(storage); + + //open_new fails on existing files as a backstop to the check above + bool file_saved = + mode == NewMode ? + flipper_format_file_open_new(ff, furi_string_get_cstr(full_file_path)) : + flipper_format_file_open_always(ff, furi_string_get_cstr(full_file_path)); + if(file_saved) { + // Filetype: Barcode + // Version: 1 + + // # Types - UPC-A, EAN-8, EAN-13, CODE-39, CODE-128, Codabar + // Type: CODE-39 + // Data: AB + file_saved = + flipper_format_write_string_cstr(ff, "Filetype", "Barcode") && + flipper_format_write_string_cstr(ff, "Version", FILE_VERSION) && + flipper_format_write_comment_cstr( + ff, "Types - UPC-A, EAN-8, EAN-13, CODE-39, CODE-128, Codabar") && + flipper_format_write_string_cstr(ff, "Type", barcode_type->name) && + flipper_format_write_string_cstr(ff, "Data", furi_string_get_cstr(barcode_data)); + if(!file_saved) { + //the file may be partially written, e.g. if the SD card was removed mid-save + FURI_LOG_E(TAG, "Could not write the barcode file"); + } + } else { + FURI_LOG_E(TAG, "Could not open file %s", furi_string_get_cstr(full_file_path)); + } + if(!file_saved) { + error_message = "A saving error\nhas occurred"; + } } + furi_string_free(full_file_path); - flipper_format_free(ff); + if(ff != NULL) { + flipper_format_free(ff); + } furi_record_close(RECORD_STORAGE); - with_view_model( - create_view_object->barcode_app->message_view->view, - MessageViewModel * model, - { - if(success) { - model->message = "File Saved!"; - } else { - model->message = "A saving error has occurred"; - } - }, - true); - - view_dispatcher_switch_to_view( - create_view_object->barcode_app->view_dispatcher, MessageErrorView); + if(error_message == NULL) { + message_view_show(message_view, "File Saved!", MainMenuView); + } else { + //go back to the create view so the entered data is not lost + message_view_show(message_view, error_message, CreateBarcodeView); + } } void create_view_free(CreateView* create_view_object) { diff --git a/base_pack/barcode_gen/views/message_view.c b/base_pack/barcode_gen/views/message_view.c index d68d0c0c..903b4bd5 100644 --- a/base_pack/barcode_gen/views/message_view.c +++ b/base_pack/barcode_gen/views/message_view.c @@ -8,7 +8,7 @@ static void app_draw_callback(Canvas* canvas, void* ctx) { canvas_clear(canvas); if(message_view_model->message != NULL) { - canvas_draw_str_aligned( + elements_multiline_text_aligned( canvas, 62, 30, AlignCenter, AlignCenter, message_view_model->message); } @@ -23,15 +23,18 @@ static bool app_input_callback(InputEvent* input_event, void* ctx) { MessageView* message_view_object = ctx; - if(input_event->key == InputKeyBack) { + bool dismissed = input_event->type == InputTypeShort && + (input_event->key == InputKeyBack || input_event->key == InputKeyOk); + + if(dismissed) { + uint32_t next_view; + with_view_model( + message_view_object->view, + MessageViewModel * model, + { next_view = model->next_view; }, + false); view_dispatcher_switch_to_view( - message_view_object->barcode_app->view_dispatcher, MainMenuView); - } - if(input_event->type == InputTypeShort) { - if(input_event->key == InputKeyOk) { - view_dispatcher_switch_to_view( - message_view_object->barcode_app->view_dispatcher, MainMenuView); - } + message_view_object->barcode_app->view_dispatcher, next_view); } return true; @@ -50,9 +53,32 @@ MessageView* message_view_allocate(BarcodeApp* barcode_app) { view_set_draw_callback(message_view_object->view, app_draw_callback); view_set_input_callback(message_view_object->view, app_input_callback); + //safe default in case a caller sets a message without a next_view + with_view_model( + message_view_object->view, + MessageViewModel * model, + { model->next_view = MainMenuView; }, + false); + return message_view_object; } +void message_view_show(MessageView* message_view_object, const char* message, uint32_t next_view) { + furi_assert(message_view_object); + + with_view_model( + message_view_object->view, + MessageViewModel * model, + { + model->message = message; + model->next_view = next_view; + }, + true); + + view_dispatcher_switch_to_view( + message_view_object->barcode_app->view_dispatcher, MessageErrorView); +} + void message_view_free(MessageView* message_view_object) { furi_assert(message_view_object); diff --git a/base_pack/barcode_gen/views/message_view.h b/base_pack/barcode_gen/views/message_view.h index 33acc3d0..c2742eb4 100644 --- a/base_pack/barcode_gen/views/message_view.h +++ b/base_pack/barcode_gen/views/message_view.h @@ -10,12 +10,22 @@ typedef struct { } MessageView; typedef struct { + //the message to display, the string is not copied + //so it must outlive the message view (e.g. a string literal) const char* message; + //the view to switch to when the message is dismissed + uint32_t next_view; } MessageViewModel; MessageView* message_view_allocate(BarcodeApp* barcode_app); -void message_view_free_model(MessageView* message_view_object); +/** + * Shows a message in the message view and switches to it + * @param message the message to display, the string is not copied + * so it must outlive the message view (e.g. a string literal) + * @param next_view the view to switch to when the message is dismissed +*/ +void message_view_show(MessageView* message_view_object, const char* message, uint32_t next_view); void message_view_free(MessageView* message_view_object);