/* * Copyright (C) 2026 OpenGD77 contributors * * Messages menu: reading received texts, browsing sent ones, and starting a new one. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation, * incorporation in a commercial product or incorporation into a product or project which allows commercial use. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include "user_interface/menuSystem.h" #include "user_interface/uiLocalisation.h" #include "user_interface/uiUtilities.h" #include "functions/sms.h" #include "functions/settings.h" #include "functions/sound.h" // Entries of the top level Messages menu. enum { MESSAGES_ITEM_NEW = 0, MESSAGES_ITEM_INBOX, MESSAGES_ITEM_OUTBOX, MESSAGES_ITEM_COUNT }; // Entries of the Inbox / Outbox menus. enum { FOLDER_ITEM_LIST = 0, FOLDER_ITEM_DELETE_ALL, FOLDER_ITEM_COUNT }; static int menuIndex = 0; // selection in the Messages menu static int folderIndex = 0; // selection in an Inbox/Outbox menu static int listIndex = 0; // selected message in a list static bool showingOutbox = false; // which folder the list/delete screens act on static bool viewingMessage = false;// a single message is open static menuStatus_t exitCode = MENU_STATUS_SUCCESS; static void updateMessagesScreen(void); static void updateFolderScreen(void); static void updateListScreen(void); static bool handleMessagesEvent(uiEvent_t *ev); static bool handleFolderEvent(uiEvent_t *ev); static bool handleListEvent(uiEvent_t *ev); // ---------------------------------------------------------------- Messages static void updateMessagesScreen(void) { char buf[SCREEN_LINE_BUFFER_SIZE]; displayClearBuf(); menuDisplayTitle(currentLanguage->messages); // New msg / Inbox (n) / Outbox (n) for (int i = 0; i < MESSAGES_ITEM_COUNT; i++) { switch (i) { case MESSAGES_ITEM_NEW: snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "New msg"); break; case MESSAGES_ITEM_INBOX: snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "Inbox (%u)", (unsigned int)smsInboxCount()); break; case MESSAGES_ITEM_OUTBOX: snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "Outbox (%u)", (unsigned int)smsOutboxCount()); break; } menuDisplayEntry((i - menuIndex), i, buf, (strlen(buf) - 1), THEME_ITEM_FG_MENU_ITEM, THEME_ITEM_FG_MENU_ITEM_SELECTED, THEME_ITEM_BG); } displayRender(); } menuStatus_t menuMessages(uiEvent_t *ev, bool isFirstRun) { if (isFirstRun) { menuIndex = 0; updateMessagesScreen(); return (MENU_STATUS_LIST_TYPE | MENU_STATUS_SUCCESS); } if (ev->hasEvent) { if (handleMessagesEvent(ev)) { updateMessagesScreen(); } } return exitCode; } static bool handleMessagesEvent(uiEvent_t *ev) { if (KEYCHECK_PRESS(ev->keys, KEY_DOWN)) { menuIndex = ((menuIndex + 1) % MESSAGES_ITEM_COUNT); return true; } if (KEYCHECK_PRESS(ev->keys, KEY_UP)) { menuIndex = ((menuIndex + MESSAGES_ITEM_COUNT - 1) % MESSAGES_ITEM_COUNT); return true; } if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN)) { switch (menuIndex) { case MESSAGES_ITEM_NEW: // Sending is not implemented yet; say so rather than opening a dead screen. uiNotificationShow(NOTIFICATION_TYPE_MESSAGE, NOTIFICATION_ID_MESSAGE, 2000, "Not yet", true); break; case MESSAGES_ITEM_INBOX: showingOutbox = false; folderIndex = 0; menuSystemPushNewMenu(MENU_MESSAGES_FOLDER); break; case MESSAGES_ITEM_OUTBOX: showingOutbox = true; folderIndex = 0; menuSystemPushNewMenu(MENU_MESSAGES_FOLDER); break; } return true; } if (KEYCHECK_SHORTUP(ev->keys, KEY_RED)) { menuSystemPopPreviousMenu(); return false; } return false; } // ------------------------------------------------------- Inbox / Outbox menu static void updateFolderScreen(void) { char buf[SCREEN_LINE_BUFFER_SIZE]; uint8_t count = (showingOutbox ? smsOutboxCount() : smsInboxCount()); displayClearBuf(); menuDisplayTitle(showingOutbox ? "Outbox" : "Inbox"); for (int i = 0; i < FOLDER_ITEM_COUNT; i++) { if (i == FOLDER_ITEM_LIST) { snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "%s (%u)", (showingOutbox ? "Outbox List" : "Inbox List"), (unsigned int)count); } else { snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "Delete All"); } menuDisplayEntry((i - folderIndex), i, buf, (strlen(buf) - 1), THEME_ITEM_FG_MENU_ITEM, THEME_ITEM_FG_MENU_ITEM_SELECTED, THEME_ITEM_BG); } displayRender(); } menuStatus_t menuMessagesFolder(uiEvent_t *ev, bool isFirstRun) { if (isFirstRun) { updateFolderScreen(); return (MENU_STATUS_LIST_TYPE | MENU_STATUS_SUCCESS); } if (ev->hasEvent) { if (handleFolderEvent(ev)) { updateFolderScreen(); } } return exitCode; } static bool handleFolderEvent(uiEvent_t *ev) { if (KEYCHECK_PRESS(ev->keys, KEY_DOWN)) { folderIndex = ((folderIndex + 1) % FOLDER_ITEM_COUNT); return true; } if (KEYCHECK_PRESS(ev->keys, KEY_UP)) { folderIndex = ((folderIndex + FOLDER_ITEM_COUNT - 1) % FOLDER_ITEM_COUNT); return true; } if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN)) { if (folderIndex == FOLDER_ITEM_LIST) { listIndex = 0; viewingMessage = false; menuSystemPushNewMenu(MENU_MESSAGES_LIST); } else { if (showingOutbox) { smsOutboxClear(); } else { smsInboxClear(); } soundSetMelody(MELODY_ACK_BEEP); } return true; } if (KEYCHECK_SHORTUP(ev->keys, KEY_RED)) { menuSystemPopPreviousMenu(); return false; } return false; } // ------------------------------------------------------------- message list static void updateListScreen(void) { char buf[SCREEN_LINE_BUFFER_SIZE]; uint8_t count = (showingOutbox ? smsOutboxCount() : smsInboxCount()); displayClearBuf(); if (count == 0) { menuDisplayTitle(showingOutbox ? "Outbox" : "Inbox"); displayPrintCentered(32, "Empty", FONT_SIZE_3); displayRender(); return; } const smsMessage_t *msg = (showingOutbox ? smsOutboxGet((uint8_t)listIndex) : smsInboxGet((uint8_t)listIndex)); if (msg == NULL) { displayRender(); return; } if (viewingMessage) { // Full message: correspondent on the title line, text wrapped below. snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "%u", (unsigned int)(showingOutbox ? msg->dstId : msg->srcId)); menuDisplayTitle(buf); // Wrap the text over the available lines rather than letting it run off screen. const int charsPerLine = 20; int len = (int)strlen(msg->text); for (int line = 0; (line < 4) && ((line * charsPerLine) < len); line++) { int n = len - (line * charsPerLine); if (n > charsPerLine) { n = charsPerLine; } memcpy(buf, &msg->text[line * charsPerLine], n); buf[n] = '\0'; displayPrintCore(0, (16 + (line * 12)), buf, FONT_SIZE_2, TEXT_ALIGN_LEFT, false); } } else { // List: one line per message, newest first. snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "%s %u/%u", (showingOutbox ? "Outbox" : "Inbox"), (unsigned int)(listIndex + 1), (unsigned int)count); menuDisplayTitle(buf); for (int i = 0; i < count; i++) { const smsMessage_t *m = (showingOutbox ? smsOutboxGet((uint8_t)i) : smsInboxGet((uint8_t)i)); if (m == NULL) { continue; } snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "%s%u %.10s", ((m->unread) ? "*" : " "), (unsigned int)(showingOutbox ? m->dstId : m->srcId), m->text); menuDisplayEntry((i - listIndex), i, buf, (strlen(buf) - 1), THEME_ITEM_FG_MENU_ITEM, THEME_ITEM_FG_MENU_ITEM_SELECTED, THEME_ITEM_BG); } } displayRender(); } menuStatus_t menuMessagesList(uiEvent_t *ev, bool isFirstRun) { if (isFirstRun) { updateListScreen(); return (MENU_STATUS_LIST_TYPE | MENU_STATUS_SUCCESS); } if (ev->hasEvent) { if (handleListEvent(ev)) { updateListScreen(); } } return exitCode; } static bool handleListEvent(uiEvent_t *ev) { uint8_t count = (showingOutbox ? smsOutboxCount() : smsInboxCount()); if (KEYCHECK_PRESS(ev->keys, KEY_DOWN) && (count > 0) && (viewingMessage == false)) { listIndex = ((listIndex + 1) % count); return true; } if (KEYCHECK_PRESS(ev->keys, KEY_UP) && (count > 0) && (viewingMessage == false)) { listIndex = ((listIndex + count - 1) % count); return true; } if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN) && (count > 0)) { if (viewingMessage == false) { viewingMessage = true; // Opening a message marks it read. if (showingOutbox == false) { smsMarkRead(); } } return true; } if (KEYCHECK_SHORTUP(ev->keys, KEY_RED)) { if (viewingMessage) { viewingMessage = false; return true; } menuSystemPopPreviousMenu(); return false; } return false; }