Footer fixes when the N was truncated.

Added new Contacts screen for phone contacts for Pro 4G and Max.

Added touch recognition to phone screen app menu.
This commit is contained in:
pelgraine
2026-07-26 15:23:34 +10:00
parent 0a2e736ba5
commit 213e7d6b9e
6 changed files with 293 additions and 43 deletions
@@ -415,7 +415,7 @@ public:
} else {
display.print("W/S:Nav Q:X");
const char* rt = "Ent:Open";
display.setCursor(display.width() - display.getTextWidth(rt) - 2, footerY);
display.setCursor(display.width() - display.getTextWidth(rt) - 6, footerY);
display.print(rt);
}
#endif
@@ -866,7 +866,7 @@ public:
display.setCursor(0, footerY);
display.print("Q:Bck");
const char* rtInbox = "Ent:Open";
display.setCursor(display.width() - display.getTextWidth(rtInbox) - 2, footerY);
display.setCursor(display.width() - display.getTextWidth(rtInbox) - 6, footerY);
display.print(rtInbox);
#endif
@@ -746,14 +746,14 @@ private:
// --- Footer helpers ---
void renderFooterRight(DisplayDriver& display, int footerY, const char* text) {
display.setCursor(display.width() - display.getTextWidth(text) - 2, footerY);
display.setCursor(display.width() - display.getTextWidth(text) - 6, footerY);
display.print(text);
}
void renderFooterMidRight(DisplayDriver& display, int footerY,
const char* left, const char* right, const char* mid) {
int leftEnd = display.getTextWidth(left) + 2;
int rightStart = display.width() - display.getTextWidth(right) - 2;
int rightStart = display.width() - display.getTextWidth(right) - 6;
int midX = leftEnd + (rightStart - leftEnd - display.getTextWidth(mid)) / 2;
display.setCursor(midX, footerY);
display.print(mid);
+286 -36
View File
@@ -48,7 +48,7 @@ class UITask; // forward declaration
class SMSScreen : public UIScreen {
public:
enum SubView { APP_MENU, INBOX, CONVERSATION, COMPOSE, CONTACTS, EDIT_CONTACT, PHONE_DIALER,
DIALING_OUT, INCOMING_CALL, IN_CALL, CALL_LOG };
DIALING_OUT, INCOMING_CALL, IN_CALL, CALL_LOG, CONTACT_PAGE };
private:
UITask* _task;
@@ -92,6 +92,18 @@ private:
// Contacts list state
int _contactsCursor;
int _contactsScrollTop;
SubView _contactsReturnView; // where Q returns to from the contacts list
// Contact page (view / add / edit a single contact)
char _cpName[SMS_CONTACT_NAME_LEN];
char _cpPhone[SMS_PHONE_LEN];
char _cpOrigPhone[SMS_PHONE_LEN]; // key before an edit, for rename
int _cpCursor; // 0 = Name, 1 = Number, 2 = Save
bool _cpIsNew;
bool _cpEditing; // true = typing into the selected field
char _cpEditBuf[SMS_CONTACT_NAME_LEN];
int _cpEditPos;
bool _cpNeedNumber; // set when Save is pressed with no number
// Edit contact state
char _editPhone[SMS_PHONE_LEN];
@@ -142,7 +154,9 @@ public:
, _msgCount(0), _msgScrollPos(0)
, _composePos(0), _composeNewConversation(false)
, _phoneInputPos(0), _enteringPhone(false)
, _contactsCursor(0), _contactsScrollTop(0)
, _contactsCursor(0), _contactsScrollTop(0), _contactsReturnView(INBOX)
, _cpCursor(0), _cpIsNew(false), _cpEditing(false), _cpEditPos(0)
, _cpNeedNumber(false)
, _editNamePos(0), _editIsNew(false), _editReturnView(INBOX)
, _callReturnView(APP_MENU), _callConnectTime(0), _callVolume(3), _callDotAnim(0)
, _callLogCount(0), _callLogCursor(0), _callLogScrollTop(0), _unseenMissed(0)
@@ -154,6 +168,10 @@ public:
memset(_composePhone, 0, sizeof(_composePhone));
memset(_phoneInputBuf, 0, sizeof(_phoneInputBuf));
memset(_activePhone, 0, sizeof(_activePhone));
memset(_cpName, 0, sizeof(_cpName));
memset(_cpPhone, 0, sizeof(_cpPhone));
memset(_cpOrigPhone, 0, sizeof(_cpOrigPhone));
memset(_cpEditBuf, 0, sizeof(_cpEditBuf));
memset(_editPhone, 0, sizeof(_editPhone));
memset(_editNameBuf, 0, sizeof(_editNameBuf));
memset(_callPhone, 0, sizeof(_callPhone));
@@ -359,6 +377,7 @@ public:
switch (_view) {
case APP_MENU: return renderAppMenu(display);
case CALL_LOG: return renderCallLog(display);
case CONTACT_PAGE: return renderContactPage(display);
case INBOX: return renderInbox(display);
case CONVERSATION: return renderConversation(display);
case COMPOSE: return renderCompose(display);
@@ -400,11 +419,37 @@ public:
y += lineHeight;
// Item 1: SMS Inbox
// Item 1: Call Log
display.setCursor(4, y);
display.setColor(_menuCursor == 1 ? DisplayDriver::GREEN : DisplayDriver::LIGHT);
if (_menuCursor == 1) display.print("> ");
else display.print(" ");
display.print("Call Log");
// Show unseen missed-call count (hidden when there are none)
if (_unseenMissed > 0) {
char countHint[12];
snprintf(countHint, sizeof(countHint), " [%d]", _unseenMissed);
display.setColor(DisplayDriver::LIGHT);
display.print(countHint);
}
y += lineHeight;
// Item 2: Contacts
display.setCursor(4, y);
display.setColor(_menuCursor == 2 ? DisplayDriver::GREEN : DisplayDriver::LIGHT);
if (_menuCursor == 2) display.print("> ");
else display.print(" ");
display.print("Contacts");
y += lineHeight;
// Item 3: SMS Inbox
display.setCursor(4, y);
display.setColor(_menuCursor == 3 ? DisplayDriver::GREEN : DisplayDriver::LIGHT);
if (_menuCursor == 3) display.print("> ");
else display.print(" ");
display.print("SMS Inbox");
// Show unread count hint (hidden when there are none)
@@ -417,23 +462,6 @@ public:
display.print(countHint);
}
y += lineHeight;
// Item 2: Call Log
display.setCursor(4, y);
display.setColor(_menuCursor == 2 ? DisplayDriver::GREEN : DisplayDriver::LIGHT);
if (_menuCursor == 2) display.print("> ");
else display.print(" ");
display.print("Call Log");
// Show unseen missed-call count (hidden when there are none)
if (_unseenMissed > 0) {
char countHint[12];
snprintf(countHint, sizeof(countHint), " [%d]", _unseenMissed);
display.setColor(DisplayDriver::LIGHT);
display.print(countHint);
}
// Modem status indicator
ModemState ms = modemManager.getState();
display.setTextSize(_prefs->smallTextSize());
@@ -1028,6 +1056,203 @@ public:
}
// ---- Contacts list ----
// ---- Contact page: view / add / edit one contact ----
void openContactPage(const char* name, const char* phone, bool isNew) {
strncpy(_cpName, name, SMS_CONTACT_NAME_LEN - 1);
_cpName[SMS_CONTACT_NAME_LEN - 1] = '\0';
strncpy(_cpPhone, phone, SMS_PHONE_LEN - 1);
_cpPhone[SMS_PHONE_LEN - 1] = '\0';
strncpy(_cpOrigPhone, phone, SMS_PHONE_LEN - 1);
_cpOrigPhone[SMS_PHONE_LEN - 1] = '\0';
_cpIsNew = isNew;
_cpCursor = 0;
_cpEditing = false;
_cpEditPos = 0;
_cpEditBuf[0] = '\0';
_cpNeedNumber = false;
_view = CONTACT_PAGE;
}
int renderContactPage(DisplayDriver& display) {
display.setTextSize(1);
display.setColor(DisplayDriver::GREEN);
display.setCursor(0, 0);
display.print(_cpIsNew ? "New Contact" : "Contact");
display.setColor(DisplayDriver::LIGHT);
display.drawRect(0, 11, display.width(), 1);
display.setTextSize(1);
int lineHeight = 16;
int y = 24;
char rowBuf[SMS_CONTACT_NAME_LEN + 16];
// Row 0: Name
display.setCursor(4, y);
display.setColor(_cpCursor == 0 ? DisplayDriver::GREEN : DisplayDriver::LIGHT);
display.print(_cpCursor == 0 ? "> " : " ");
if (_cpEditing && _cpCursor == 0) {
snprintf(rowBuf, sizeof(rowBuf), "Name: %s_", _cpEditBuf);
} else {
snprintf(rowBuf, sizeof(rowBuf), "Name: %s", _cpName);
}
display.print(rowBuf);
y += lineHeight;
// Row 1: Number
display.setCursor(4, y);
display.setColor(_cpCursor == 1 ? DisplayDriver::GREEN : DisplayDriver::LIGHT);
display.print(_cpCursor == 1 ? "> " : " ");
if (_cpEditing && _cpCursor == 1) {
snprintf(rowBuf, sizeof(rowBuf), "Number: %s_", _cpEditBuf);
} else {
snprintf(rowBuf, sizeof(rowBuf), "Number: %s", _cpPhone);
}
display.print(rowBuf);
y += lineHeight;
// Row 2: Save
display.setCursor(4, y);
display.setColor(_cpCursor == 2 ? DisplayDriver::GREEN : DisplayDriver::LIGHT);
display.print(_cpCursor == 2 ? "> " : " ");
display.print("Save");
y += lineHeight;
if (_cpNeedNumber) {
display.setTextSize(_prefs->smallTextSize());
display.setColor(DisplayDriver::YELLOW);
display.setCursor(4, y + 4);
display.print("A number is required");
display.setTextSize(1);
}
// Footer
display.setTextSize(1);
int footerY = display.height() - 12;
display.drawRect(0, footerY - 2, display.width(), 1);
display.setColor(DisplayDriver::YELLOW);
display.setCursor(0, footerY);
if (_cpEditing) {
display.print("Sh+Del:Cancel");
const char* rt = "Ent:Done";
display.setCursor(display.width() - display.getTextWidth(rt) - 6, footerY);
display.print(rt);
} else {
display.print("Q:Back");
if (!_cpIsNew) {
const char* mid = "F:Dial D:Del";
display.setCursor((display.width() - display.getTextWidth(mid)) / 2, footerY);
display.print(mid);
}
const char* rt = "Ent:Sel";
display.setCursor(display.width() - display.getTextWidth(rt) - 6, footerY);
display.print(rt);
}
return 5000;
}
bool handleContactPageInput(char c) {
// --- Field editing ---
if (_cpEditing) {
if (c == '\r') {
_cpEditBuf[_cpEditPos] = '\0';
if (_cpCursor == 0) {
strncpy(_cpName, _cpEditBuf, SMS_CONTACT_NAME_LEN - 1);
_cpName[SMS_CONTACT_NAME_LEN - 1] = '\0';
} else {
strncpy(_cpPhone, _cpEditBuf, SMS_PHONE_LEN - 1);
_cpPhone[SMS_PHONE_LEN - 1] = '\0';
}
_cpEditing = false;
return true;
}
if (c == 0x18) { // Shift+Backspace -- abandon this field edit
_cpEditing = false;
return true;
}
if (c == '\b') {
if (_cpEditPos > 0) {
_cpEditPos--;
_cpEditBuf[_cpEditPos] = '\0';
}
return true;
}
int limit = (_cpCursor == 0) ? SMS_CONTACT_NAME_LEN - 1 : SMS_PHONE_LEN - 1;
if (c >= 32 && c < 127 && _cpEditPos < limit) {
_cpEditBuf[_cpEditPos++] = c;
_cpEditBuf[_cpEditPos] = '\0';
}
return true; // consume all keys while editing
}
// --- Navigation ---
switch (c) {
case 'w': case 'W':
if (_cpCursor > 0) _cpCursor--;
_cpNeedNumber = false;
return true;
case 's': case 'S':
if (_cpCursor < 2) _cpCursor++;
_cpNeedNumber = false;
return true;
case '\r':
if (_cpCursor == 2) {
saveContactPage();
} else {
const char* src = (_cpCursor == 0) ? _cpName : _cpPhone;
strncpy(_cpEditBuf, src, sizeof(_cpEditBuf) - 1);
_cpEditBuf[sizeof(_cpEditBuf) - 1] = '\0';
_cpEditPos = strlen(_cpEditBuf);
_cpEditing = true;
_cpNeedNumber = false;
}
return true;
case 'f': case 'F': // Dial this contact
if (!_cpIsNew && _cpPhone[0]) startCall(_cpPhone);
return true;
case 'd': case 'D': // Delete this contact
if (!_cpIsNew && _cpOrigPhone[0]) {
smsContacts.remove(_cpOrigPhone);
Serial.printf("[SMSContacts] Deleted: %s\n", _cpOrigPhone);
returnToContacts();
}
return true;
case 'q':
case KEY_CANCEL: // Back without saving
returnToContacts();
return true;
default:
return false;
}
}
void saveContactPage() {
if (_cpPhone[0] == '\0') {
_cpNeedNumber = true; // number is the storage key, so it cannot be blank
return;
}
// Renaming the number is a remove-then-add, since contacts are keyed on it
if (!_cpIsNew && _cpOrigPhone[0] && strcmp(_cpOrigPhone, _cpPhone) != 0) {
smsContacts.remove(_cpOrigPhone);
}
smsContacts.set(_cpPhone, _cpName);
Serial.printf("[SMSContacts] Saved: %s = %s\n", _cpPhone, _cpName);
returnToContacts();
}
void returnToContacts() {
int cnt = smsContacts.count();
if (_contactsCursor >= cnt) _contactsCursor = (cnt > 0) ? cnt - 1 : 0;
_view = CONTACTS;
}
int renderContacts(DisplayDriver& display) {
display.setTextSize(1);
display.setColor(DisplayDriver::GREEN);
@@ -1045,9 +1270,7 @@ public:
display.setCursor(0, 25);
display.print("No contacts saved");
display.setCursor(0, 37);
display.print("Open a conversation");
display.setCursor(0, 49);
display.print("and press A to add");
display.print("Press A to add one");
display.setTextSize(1);
} else {
display.setTextSize(_prefs->smallTextSize());
@@ -1095,8 +1318,11 @@ public:
display.setColor(DisplayDriver::YELLOW);
display.setCursor(0, footerY);
display.print("Q:Back");
const char* rt = "Ent:SMS F:Call";
display.setCursor(display.width() - display.getTextWidth(rt) - 2, footerY);
const char* mid = "A:Add";
display.setCursor((display.width() - display.getTextWidth(mid)) / 2, footerY);
display.print(mid);
const char* rt = "Ent:Open";
display.setCursor(display.width() - display.getTextWidth(rt) - 6, footerY);
display.print(rt);
return 5000;
@@ -1330,6 +1556,7 @@ public:
switch (_view) {
case APP_MENU: return handleAppMenuInput(c);
case CALL_LOG: return handleCallLogInput(c);
case CONTACT_PAGE: return handleContactPageInput(c);
case INBOX: return handleInboxInput(c);
case CONVERSATION: return handleConversationInput(c);
case COMPOSE: return handleComposeInput(c);
@@ -1351,7 +1578,7 @@ public:
return true;
case 's': case 'S':
if (_menuCursor < 2) _menuCursor++;
if (_menuCursor < 3) _menuCursor++;
return true;
case '\r': // Enter - select menu item
@@ -1361,12 +1588,6 @@ public:
_phoneInputPos = 0;
_view = PHONE_DIALER;
} else if (_menuCursor == 1) {
// SMS Inbox
if (_sdReady) refreshInbox();
_inboxCursor = 0;
_inboxScrollTop = 0;
_view = INBOX;
} else {
// Call Log
if (_sdReady) {
smsStore.markMissedSeen();
@@ -1375,6 +1596,18 @@ public:
_callLogCursor = 0;
_callLogScrollTop = 0;
_view = CALL_LOG;
} else if (_menuCursor == 2) {
// Contacts
_contactsCursor = 0;
_contactsScrollTop = 0;
_contactsReturnView = APP_MENU;
_view = CONTACTS;
} else {
// SMS Inbox
if (_sdReady) refreshInbox();
_inboxCursor = 0;
_inboxScrollTop = 0;
_view = INBOX;
}
return true;
@@ -1557,6 +1790,7 @@ public:
case 'd': case 'D': // Open contacts list
_contactsCursor = 0;
_contactsScrollTop = 0;
_contactsReturnView = INBOX;
_view = CONTACTS;
return true;
@@ -1727,7 +1961,18 @@ public:
if (_contactsCursor < cnt - 1) _contactsCursor++;
return true;
case '\r': // Enter - compose to selected contact
case '\r': // Enter - open the contact page
if (cnt > 0 && _contactsCursor < cnt) {
const SMSContact& ct = smsContacts.get(_contactsCursor);
openContactPage(ct.name, ct.phone, false);
}
return true;
case 'a': case 'A': // Add a new contact
openContactPage("", "", true);
return true;
case 'm': case 'M': // Compose an SMS to the selected contact
if (cnt > 0 && _contactsCursor < cnt) {
const SMSContact& ct = smsContacts.get(_contactsCursor);
_composeNewConversation = true;
@@ -1747,9 +1992,14 @@ public:
return true;
case 'q':
case KEY_CANCEL: // Back to inbox
refreshInbox();
_view = INBOX;
case KEY_CANCEL: // Back to wherever the list was opened from
if (_contactsReturnView == INBOX) {
refreshInbox();
_view = INBOX;
} else {
_view = APP_MENU;
_menuCursor = 2;
}
return true;
default:
+1 -1
View File
@@ -39,7 +39,7 @@
#define BQ27220_I2C_ADDR 0x55
#ifndef BQ27220_DESIGN_CAPACITY_MAH
#define BQ27220_DESIGN_CAPACITY_MAH 1400
#define BQ27220_DESIGN_CAPACITY_MAH 2500
#endif
class TDeckProMaxBoard : public ESP32Board {
+2 -2
View File
@@ -239,8 +239,8 @@
#define BQ27220_I2C_ADDR 0x55
#define BQ27220_I2C_SDA I2C_SDA
#define BQ27220_I2C_SCL I2C_SCL
#define BQ27220_DESIGN_CAPACITY 1400 //
#define BQ27220_DESIGN_CAPACITY_MAH 1400 // Alias used by TDeckBoard.h
#define BQ27220_DESIGN_CAPACITY 2500 //
#define BQ27220_DESIGN_CAPACITY_MAH 2500 // Alias used by TDeckBoard.h
#define HAS_PPM 1
// This board uses the SY6970 charger @ 0x6A (newer revision -- confirmed by I2C