mirror of
https://github.com/pelgraine/Meck.git
synced 2026-08-09 18:22:45 +02:00
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:
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user