| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608 |
- /*
- * Suica Scene
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- #include "../../api/suica/suica_drawings.h"
- #include "../../metroflip_plugins.h"
- #include <lib/nfc/protocols/felica/felica.h>
- #include <lib/nfc/protocols/felica/felica_poller.h>
- // #include <lib/nfc/protocols/felica/felica_poller_i.h>
- #include <lib/bit_lib/bit_lib.h>
- #include <applications/services/locale/locale.h>
- #include <datetime.h>
- // Probably not needed after upstream include this in their suica_i.h
- #define TAG "Metroflip:Scene:Suica"
- const char* suica_service_names[] = {
- "Travel History",
- "Taps Log",
- };
- static void suica_model_initialize(SuicaHistoryViewModel* model, size_t initial_capacity) {
- model->travel_history =
- (uint8_t*)malloc(initial_capacity * FELICA_DATA_BLOCK_SIZE); // Each entry is 16 bytes
- model->size = 0;
- model->capacity = initial_capacity;
- model->entry = 1;
- model->page = 0;
- model->animator_tick = 0;
- model->history.entry_station.name = furi_string_alloc_set("Unknown");
- model->history.entry_station.jr_header = furi_string_alloc_set("0");
- model->history.exit_station.name = furi_string_alloc_set("Unknown");
- model->history.exit_station.jr_header = furi_string_alloc_set("0");
- model->history.entry_line = RailwaysList[SUICA_RAILWAY_NUM];
- model->history.exit_line = RailwaysList[SUICA_RAILWAY_NUM];
- }
- static void suica_model_initialize_after_load(SuicaHistoryViewModel* model) {
- model->entry = 1;
- model->page = 0;
- model->animator_tick = 0;
- model->history.entry_station.name = furi_string_alloc_set("Unknown");
- model->history.entry_station.jr_header = furi_string_alloc_set("0");
- model->history.exit_station.name = furi_string_alloc_set("Unknown");
- model->history.exit_station.jr_header = furi_string_alloc_set("0");
- model->history.entry_line = RailwaysList[SUICA_RAILWAY_NUM];
- model->history.exit_line = RailwaysList[SUICA_RAILWAY_NUM];
- }
- static void suica_add_entry(SuicaHistoryViewModel* model, const uint8_t* entry) {
- if(model->size <= 0) {
- suica_model_initialize(model, 3);
- }
- // Check if resizing is needed
- if(model->size == model->capacity) {
- size_t new_capacity = model->capacity * 2; // Double the capacity
- uint8_t* new_data =
- (uint8_t*)realloc(model->travel_history, new_capacity * FELICA_DATA_BLOCK_SIZE);
- model->travel_history = new_data;
- model->capacity = new_capacity;
- }
- // Copy the 16-byte entry to the next slot
- for(size_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i++) {
- model->travel_history[(model->size * FELICA_DATA_BLOCK_SIZE) + i] = entry[i];
- }
- model->size++;
- }
- static void suica_parse_train_code(
- uint8_t line_code,
- uint8_t station_code,
- SuicaTrainRideType ride_type,
- SuicaHistoryViewModel* model) {
- Storage* storage = furi_record_open(RECORD_STORAGE);
- Stream* stream = file_stream_alloc(storage);
- FuriString* line = furi_string_alloc();
- FuriString* line_code_str = furi_string_alloc();
- FuriString* line_and_station_code_str = furi_string_alloc();
- furi_string_printf(line_code_str, "0x%02X", line_code);
- furi_string_printf(line_and_station_code_str, "0x%02X,0x%02X", line_code, station_code);
- FuriString* line_candidate = furi_string_alloc_set(SUICA_RAILWAY_UNKNOWN_NAME);
- FuriString* station_candidate = furi_string_alloc_set(SUICA_RAILWAY_UNKNOWN_NAME);
- FuriString* station_num_candidate = furi_string_alloc_set("0");
- FuriString* station_JR_header_candidate = furi_string_alloc_set("0");
- FuriString* line_copy = furi_string_alloc();
- size_t line_comma_ind = 0;
- size_t station_comma_ind = 0;
- size_t station_num_comma_ind = 0;
- size_t station_JR_header_comma_ind = 0;
- bool station_found = false;
- FuriString* file_name = furi_string_alloc();
- furi_string_printf(file_name, "%s0x%02X.txt", SUICA_STATION_LIST_PATH, line_code);
- if(file_stream_open(stream, furi_string_get_cstr(file_name), FSAM_READ, FSOM_OPEN_EXISTING)) {
- while(stream_read_line(stream, line) && !station_found) {
- // file is in csv format: station_group_id,station_id,station_sub_id,station_name
- // search for the station
- furi_string_replace_all(line, "\r", "");
- furi_string_replace_all(line, "\n", "");
- furi_string_set(line_copy, line); // 0xD5,0x02,Keikyu Main,Shinagawa,1,0
- if(furi_string_start_with(line, line_code_str)) {
- // set line name here
- furi_string_right(line_copy, 10); // Keikyu Main,Shinagawa,1,0
- furi_string_set(line_candidate, line_copy);
- line_comma_ind = furi_string_search_char(line_candidate, ',', 0);
- furi_string_left(line_candidate, line_comma_ind); // Keikyu Main
- // we cut the line and station code in the line line copy
- // and we leave only the line name for the line candidate
- if(furi_string_start_with(line, line_and_station_code_str)) {
- furi_string_set(station_candidate, line_copy); // Keikyu Main,Shinagawa,1,0
- furi_string_right(station_candidate, line_comma_ind + 1);
- station_comma_ind =
- furi_string_search_char(station_candidate, ',', 0); // Shinagawa,1,0
- furi_string_left(station_candidate, station_comma_ind); // Shinagawa
- station_found = true;
- break;
- }
- }
- }
- } else {
- FURI_LOG_E(TAG, "Failed to open stations.txt");
- }
- furi_string_set(station_num_candidate, line_copy); // Keikyu Main,Shinagawa,1,0
- furi_string_right(station_num_candidate, line_comma_ind + station_comma_ind + 2); // 1,0
- station_num_comma_ind = furi_string_search_char(station_num_candidate, ',', 0);
- furi_string_left(station_num_candidate, station_num_comma_ind); // 1
- furi_string_set(station_JR_header_candidate, line_copy); // Keikyu Main,Shinagawa,1,0
- furi_string_right(
- station_JR_header_candidate,
- line_comma_ind + station_comma_ind + station_num_comma_ind + 3); // 0
- station_JR_header_comma_ind = furi_string_search_char(station_JR_header_candidate, ',', 0);
- furi_string_left(station_JR_header_candidate, station_JR_header_comma_ind); // 0
- switch(ride_type) {
- case SuicaTrainRideEntry:
- model->history.entry_station.name = furi_string_alloc_set("Unknown");
- model->history.entry_station.jr_header = furi_string_alloc_set("0");
- model->history.entry_line = RailwaysList[SUICA_RAILWAY_NUM];
- for(size_t i = 0; i < SUICA_RAILWAY_NUM; i++) {
- if(furi_string_equal_str(line_candidate, RailwaysList[i].long_name)) {
- model->history.entry_line = RailwaysList[i];
- furi_string_set(model->history.entry_station.name, station_candidate);
- model->history.entry_station.station_number =
- atoi(furi_string_get_cstr(station_num_candidate));
- furi_string_set(
- model->history.entry_station.jr_header, station_JR_header_candidate);
- break;
- }
- }
- break;
- case SuicaTrainRideExit:
- model->history.exit_station.name = furi_string_alloc_set("Unknown");
- model->history.exit_station.jr_header = furi_string_alloc_set("0");
- model->history.exit_line = RailwaysList[SUICA_RAILWAY_NUM];
- for(size_t i = 0; i < SUICA_RAILWAY_NUM; i++) {
- if(furi_string_equal_str(line_candidate, RailwaysList[i].long_name)) {
- model->history.exit_line = RailwaysList[i];
- furi_string_set(model->history.exit_station.name, station_candidate);
- model->history.exit_station.station_number =
- atoi(furi_string_get_cstr(station_num_candidate));
- furi_string_set(
- model->history.exit_station.jr_header, station_JR_header_candidate);
- break;
- }
- }
- break;
- default:
- UNUSED(model);
- break;
- }
- furi_string_free(line);
- furi_string_free(line_copy);
- furi_string_free(line_code_str);
- furi_string_free(line_and_station_code_str);
- furi_string_free(line_candidate);
- furi_string_free(station_candidate);
- furi_string_free(station_num_candidate);
- furi_string_free(station_JR_header_candidate);
- file_stream_close(stream);
- stream_free(stream);
- furi_record_close(RECORD_STORAGE);
- }
- static void suica_parse(SuicaHistoryViewModel* my_model) {
- uint8_t current_block[FELICA_DATA_BLOCK_SIZE];
- // Parse the current block/entry
- for(size_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i++) {
- current_block[i] = my_model->travel_history[((my_model->entry - 1) * 16) + i];
- }
- if(((uint8_t)current_block[4] + (uint8_t)current_block[5]) != 0) {
- my_model->history.year = ((uint8_t)current_block[4] & 0xFE) >> 1;
- my_model->history.month = (((uint8_t)current_block[4] & 0x01) << 3) |
- (((uint8_t)current_block[5] & 0xE0) >> 5);
- my_model->history.day = (uint8_t)current_block[5] & 0x1F;
- } else {
- my_model->history.year = 0;
- my_model->history.month = 0;
- my_model->history.day = 0;
- }
- my_model->history.balance = ((uint16_t)current_block[11] << 8) | (uint16_t)current_block[10];
- my_model->history.area_code = current_block[15];
- if((uint8_t)current_block[0] >= TERMINAL_TICKET_VENDING_MACHINE &&
- (uint8_t)current_block[0] <= TERMINAL_IN_CAR_SUPP_MACHINE) {
- // Train rides
- // Will be overwritton is is ticket sale (TERMINAL_TICKET_VENDING_MACHINE)
- my_model->history.history_type = SuicaHistoryTrain;
- uint8_t entry_line = current_block[6];
- uint8_t entry_station = current_block[7];
- uint8_t exit_line = current_block[8];
- uint8_t exit_station = current_block[9];
- suica_parse_train_code(entry_line, entry_station, SuicaTrainRideEntry, my_model);
- if((uint8_t)current_block[14] != 0x01) {
- suica_parse_train_code(exit_line, exit_station, SuicaTrainRideExit, my_model);
- }
- if(((uint8_t)current_block[4] + (uint8_t)current_block[5]) != 0) {
- my_model->history.year = ((uint8_t)current_block[4] & 0xFE) >> 1;
- my_model->history.month = (((uint8_t)current_block[4] & 0x01) << 3) |
- (((uint8_t)current_block[5] & 0xE0) >> 5);
- my_model->history.day = (uint8_t)current_block[5] & 0x1F;
- }
- }
- switch((uint8_t)current_block[0]) {
- case TERMINAL_BUS:
- // 6 & 7 bus line code
- // 8 & 9 bus stop code
- my_model->history.history_type = SuicaHistoryBus;
- break;
- case TERMINAL_POS_AND_TAXI:
- case TERMINAL_VENDING_MACHINE:
- // 6 & 7 are hour and minute
- my_model->history.history_type = ((uint8_t)current_block[0] == TERMINAL_POS_AND_TAXI) ?
- SuicaHistoryPosAndTaxi :
- SuicaHistoryVendingMachine;
- my_model->history.hour = ((uint8_t)current_block[6] & 0xF8) >> 3;
- my_model->history.minute = (((uint8_t)current_block[6] & 0x07) << 3) |
- (((uint8_t)current_block[7] & 0xE0) >> 5);
- my_model->history.shop_code = (uint8_t*)malloc(2);
- my_model->history.shop_code[0] = current_block[8];
- my_model->history.shop_code[1] = current_block[9];
- break;
- case TERMINAL_MOBILE_PHONE:
- break;
- case TERMINAL_TICKET_VENDING_MACHINE:
- my_model->history.history_type = SuicaHistoryHappyBirthday;
- break;
- default:
- if((uint8_t)current_block[0] <= TERMINAL_NULL) {
- my_model->history.history_type = SuicaHistoryNull;
- }
- break;
- }
- if((uint8_t)current_block[14] == 0x01) {
- my_model->history.history_type = SuicaHistoryHappyBirthday;
- }
- }
- static void suica_parse_detail_callback(GuiButtonType result, InputType type, void* context) {
- Metroflip* app = context;
- UNUSED(result);
- if(type == InputTypeShort) {
- SuicaHistoryViewModel* my_model = view_get_model(app->suica_context->view_history);
- suica_parse(my_model);
- FURI_LOG_I(TAG, "Draw Callback: We have %d entries", my_model->size);
- view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewCanvas);
- }
- }
- static NfcCommand suica_poller_callback(NfcGenericEvent event, void* context) {
- furi_assert(event.protocol == NfcProtocolFelica);
- NfcCommand command = NfcCommandContinue;
- MetroflipPollerEventType stage = MetroflipPollerEventTypeStart;
- Metroflip* app = context;
- FuriString* parsed_data = furi_string_alloc();
- SuicaHistoryViewModel* model = view_get_model(app->suica_context->view_history);
- Widget* widget = app->widget;
- const uint16_t service_code[2] = {SERVICE_CODE_HISTORY_IN_LE, SERVICE_CODE_TAPS_LOG_IN_LE};
- const FelicaPollerEvent* felica_event = event.event_data;
- FelicaPollerReadCommandResponse* rx_resp;
- rx_resp->SF1 = 0;
- rx_resp->SF2 = 0;
- uint8_t blocks[1] = {0x00};
- FelicaPoller* felica_poller = event.instance;
- const FelicaData* felica_data = nfc_poller_get_data(app->poller);
- FURI_LOG_I(TAG, "Poller set");
- if(felica_event->type == FelicaPollerEventTypeRequestAuthContext) {
- view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventCardDetected);
- command = NfcCommandContinue;
- if(stage == MetroflipPollerEventTypeStart) {
- nfc_device_set_data(
- app->nfc_device, NfcProtocolFelica, nfc_poller_get_data(app->poller));
- furi_string_printf(parsed_data, "\e#Suica\n");
- FelicaError error = FelicaErrorNone;
- int service_code_index = 0;
- // Authenticate with the card
- // Iterate through the two services
- while(service_code_index < 2 && error == FelicaErrorNone) {
- furi_string_cat_printf(
- parsed_data, "%s: \n", suica_service_names[service_code_index]);
- rx_resp->SF1 = 0;
- rx_resp->SF2 = 0;
- blocks[0] = 0; // firmware api requires this to be a list
- while((rx_resp->SF1 + rx_resp->SF2) == 0 &&
- blocks[0] < SUICA_MAX_HISTORY_ENTRIES && error == FelicaErrorNone) {
- uint8_t block_data[16] = {0};
- error = felica_poller_read_blocks(
- felica_poller, 1, blocks, service_code[service_code_index], &rx_resp);
- if(error != FelicaErrorNone) {
- view_dispatcher_send_custom_event(
- app->view_dispatcher, MetroflipCustomEventCardLost);
- command = NfcCommandStop;
- break;
- }
- furi_string_cat_printf(parsed_data, "Block %02X\n", blocks[0]);
- blocks[0]++;
- for(size_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i++) {
- furi_string_cat_printf(parsed_data, "%02X ", rx_resp->data[i]);
- block_data[i] = rx_resp->data[i];
- }
- furi_string_cat_printf(parsed_data, "\n");
- if(service_code_index == 0) {
- FURI_LOG_I(
- TAG,
- "Service code %d, adding entry %x",
- service_code_index,
- model->size);
- suica_add_entry(model, block_data);
- }
- }
- service_code_index++;
- }
- metroflip_app_blink_stop(app);
- if(model->size == 1) { // Have to let the poller run once before knowing we failed
- furi_string_printf(
- parsed_data,
- "\e#Suica\nSorry, no data found.\nPlease let the developers know and we will add support.");
- }
- if(model->size == 1 && felica_data->pmm.data[1] != SUICA_IC_TYPE_CODE) {
- furi_string_printf(parsed_data, "\e#Suica\nSorry, not a Suica.\n");
- }
- widget_add_text_scroll_element(
- widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
- widget_add_button_element(
- widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
- widget_add_button_element(
- widget, GuiButtonTypeLeft, "Del", metroflip_delete_widget_callback, app);
- if(model->size > 1) {
- widget_add_button_element(
- widget, GuiButtonTypeCenter, "Parse", suica_parse_detail_callback, app);
- }
- view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
- }
- }
- furi_string_free(parsed_data);
- command = NfcCommandStop;
- return command;
- }
- static bool suica_history_input_callback(InputEvent* event, void* context) {
- Metroflip* app = (Metroflip*)context;
- if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
- switch(event->key) {
- case InputKeyLeft: {
- bool redraw = true;
- with_view_model(
- app->suica_context->view_history,
- SuicaHistoryViewModel * model,
- {
- if(model->entry > 1) {
- model->entry--;
- }
- suica_parse(model);
- FURI_LOG_I(TAG, "Viewing entry %d", model->entry);
- },
- redraw);
- break;
- }
- case InputKeyRight: {
- bool redraw = true;
- with_view_model(
- app->suica_context->view_history,
- SuicaHistoryViewModel * model,
- {
- if(model->entry < model->size) {
- model->entry++;
- }
- suica_parse(model);
- FURI_LOG_I(TAG, "Viewing entry %d", model->entry);
- },
- redraw);
- break;
- }
- case InputKeyUp: {
- bool redraw = true;
- with_view_model(
- app->suica_context->view_history,
- SuicaHistoryViewModel * model,
- {
- if(model->page > 0) {
- model->page--;
- }
- },
- redraw);
- break;
- }
- case InputKeyDown: {
- bool redraw = true;
- with_view_model(
- app->suica_context->view_history,
- SuicaHistoryViewModel * model,
- {
- if(model->page < HISTORY_VIEW_PAGE_NUM - 1) {
- model->page++;
- }
- },
- redraw);
- break;
- }
- default:
- // Handle other keys or do nothing
- break;
- }
- }
- return false;
- }
- static void suica_on_enter(Metroflip* app) {
- // Gui* gui = furi_record_open(RECORD_GUI);
- dolphin_deed(DolphinDeedNfcRead);
- if(app->data_loaded == false) {
- app->suica_context = malloc(sizeof(SuicaContext));
- app->suica_context->view_history = view_alloc();
- view_set_context(app->suica_context->view_history, app);
- view_allocate_model(
- app->suica_context->view_history,
- ViewModelTypeLockFree,
- sizeof(SuicaHistoryViewModel));
- }
- view_set_input_callback(app->suica_context->view_history, suica_history_input_callback);
- view_set_previous_callback(app->suica_context->view_history, suica_navigation_raw_callback);
- view_set_enter_callback(app->suica_context->view_history, suica_view_history_enter_callback);
- view_set_exit_callback(app->suica_context->view_history, suica_view_history_exit_callback);
- view_set_custom_callback(
- app->suica_context->view_history, suica_view_history_custom_event_callback);
- view_set_draw_callback(app->suica_context->view_history, suica_history_draw_callback);
- view_dispatcher_add_view(
- app->view_dispatcher, MetroflipViewCanvas, app->suica_context->view_history);
- if(app->data_loaded == false) {
- popup_set_header(app->popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
- popup_set_icon(app->popup, 0, 3, &I_RFIDDolphinReceive_97x61);
- view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
- nfc_scanner_alloc(app->nfc);
- app->poller = nfc_poller_alloc(app->nfc, NfcProtocolFelica);
- nfc_poller_start(app->poller, suica_poller_callback, app);
- FURI_LOG_I(TAG, "Poller started");
- metroflip_app_blink_start(app);
- } else {
- SuicaHistoryViewModel* model = view_get_model(app->suica_context->view_history);
- suica_model_initialize_after_load(model);
- Widget* widget = app->widget;
- FuriString* parsed_data = furi_string_alloc();
- furi_string_printf(parsed_data, "\e#Suica\n");
- for(uint8_t i = 0; i < model->size; i++) {
- furi_string_cat_printf(parsed_data, "Block %02X\n", i);
- for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) {
- furi_string_cat_printf(parsed_data, "%02X ", model->travel_history[i * 16 + j]);
- }
- furi_string_cat_printf(parsed_data, "\n");
- }
- widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
- widget_add_button_element(
- widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
- widget_add_button_element(
- widget, GuiButtonTypeLeft, "Del", metroflip_delete_widget_callback, app);
- if(model->size > 1) {
- widget_add_button_element(
- widget, GuiButtonTypeCenter, "Parse", suica_parse_detail_callback, app);
- }
- view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
- furi_string_free(parsed_data);
- }
- }
- static bool suica_on_event(Metroflip* app, SceneManagerEvent event) {
- bool consumed = false;
- Popup* popup = app->popup;
- if(event.type == SceneManagerEventTypeCustom) {
- if(event.event == MetroflipCustomEventCardDetected) {
- popup_set_header(popup, "DON'T\nMOVE", 68, 30, AlignLeft, AlignTop);
- consumed = true;
- } else if(event.event == MetroflipCustomEventCardLost) {
- popup_set_header(popup, "Card \n lost", 68, 30, AlignLeft, AlignTop);
- // popup_set_timeout(popup, 2000);
- // popup_enable_timeout(popup);
- // view_dispatcher_switch_to_view(app->view_dispatcher, SuicaViewPopup);
- // popup_disable_timeout(popup);
- scene_manager_search_and_switch_to_previous_scene(
- app->scene_manager, MetroflipSceneStart);
- consumed = true;
- } else if(event.event == MetroflipCustomEventWrongCard) {
- popup_set_header(popup, "WRONG \n CARD", 68, 30, AlignLeft, AlignTop);
- scene_manager_search_and_switch_to_previous_scene(
- app->scene_manager, MetroflipSceneStart);
- consumed = true;
- } else if(event.event == MetroflipCustomEventPollerFail) {
- popup_set_header(popup, "Failed", 68, 30, AlignLeft, AlignTop);
- scene_manager_search_and_switch_to_previous_scene(
- app->scene_manager, MetroflipSceneStart);
- consumed = true;
- }
- } else if(event.type == SceneManagerEventTypeBack) {
- UNUSED(popup);
- scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
- consumed = true;
- }
- return consumed;
- }
- static void suica_on_exit(Metroflip* app) {
- widget_reset(app->widget);
- with_view_model(
- app->suica_context->view_history,
- SuicaHistoryViewModel * model,
- {
- if(model->travel_history) { // Check if memory was allocated
- free(model->travel_history);
- model->travel_history = NULL; // Set pointer to NULL to prevent dangling references
- }
- },
- false);
- view_free_model(app->suica_context->view_history);
- view_dispatcher_remove_view(app->view_dispatcher, MetroflipViewCanvas);
- view_free(app->suica_context->view_history);
- free(app->suica_context);
- if(app->poller && !app->data_loaded) {
- nfc_poller_stop(app->poller);
- nfc_poller_free(app->poller);
- }
- }
- /* Actual implementation of app<>plugin interface */
- static const MetroflipPlugin suica_plugin = {
- .card_name = "Suica",
- .plugin_on_enter = suica_on_enter,
- .plugin_on_event = suica_on_event,
- .plugin_on_exit = suica_on_exit,
- };
- /* Plugin descriptor to comply with basic plugin specification */
- static const FlipperAppPluginDescriptor suica_plugin_descriptor = {
- .appid = METROFLIP_SUPPORTED_CARD_PLUGIN_APP_ID,
- .ep_api_version = METROFLIP_SUPPORTED_CARD_PLUGIN_API_VERSION,
- .entry_point = &suica_plugin,
- };
- /* Plugin entry point - must return a pointer to const descriptor */
- const FlipperAppPluginDescriptor* suica_plugin_ep(void) {
- return &suica_plugin_descriptor;
- }
|