mirror of
https://github.com/pelgraine/Meck.git
synced 2026-03-28 17:42:44 +01:00
added extra phone screen to allow phone or sms inbox selection to enabling dialing of non-contact numbers
This commit is contained in:
@@ -1322,7 +1322,7 @@ void handleKeyboardInput() {
|
||||
}
|
||||
|
||||
// Q from inbox → go home; Q from inner views is handled by SMSScreen
|
||||
if ((key == 'q' || key == '\b') && smsScr->getSubView() == SMSScreen::INBOX) {
|
||||
if ((key == 'q' || key == '\b') && smsScr->getSubView() == SMSScreen::APP_MENU) {
|
||||
Serial.println("Nav: SMS -> Home");
|
||||
ui_task.gotoHomeScreen();
|
||||
return;
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
// =============================================================================
|
||||
// SMSScreen - SMS messaging & Voice Calls UI for T-Deck Pro (4G variant)
|
||||
// SMSScreen - SMS & Phone UI for T-Deck Pro (4G variant)
|
||||
//
|
||||
// Sub-views:
|
||||
// INBOX — list of conversations (names resolved via SMSContacts)
|
||||
// CONVERSATION — messages for a selected contact, scrollable
|
||||
// COMPOSE — text input for new SMS
|
||||
// CONTACTS — browsable contacts list, pick to compose or call
|
||||
// EDIT_CONTACT — add or edit a contact name for a phone number
|
||||
// DIALING — outgoing call in progress
|
||||
// INCOMING_CALL — incoming call ringing (answer/reject)
|
||||
// IN_CALL — active voice call (timer, DTMF, volume, hangup)
|
||||
// APP_MENU — landing screen: choose Phone or SMS Inbox
|
||||
// INBOX — list of conversations (names resolved via SMSContacts)
|
||||
// CONVERSATION — messages for a selected contact, scrollable
|
||||
// COMPOSE — text input for new SMS
|
||||
// CONTACTS — browsable contacts list, pick to compose or call
|
||||
// EDIT_CONTACT — add or edit a contact name for a phone number
|
||||
// PHONE_DIALER — enter arbitrary phone number and call
|
||||
//
|
||||
// Navigation mirrors ChannelScreen conventions:
|
||||
// W/S: scroll Enter: select/send C: compose new/reply
|
||||
// Q: back Sh+Del: cancel compose
|
||||
// D: contacts (from inbox)
|
||||
// A: add/edit contact (from conversation)
|
||||
// F: call (from conversation or contacts)
|
||||
// F: call (from conversation, contacts, or phone dialer)
|
||||
//
|
||||
// Guard: HAS_4G_MODEM
|
||||
// =============================================================================
|
||||
@@ -44,16 +43,15 @@ class UITask; // forward declaration
|
||||
|
||||
class SMSScreen : public UIScreen {
|
||||
public:
|
||||
enum SubView {
|
||||
INBOX, CONVERSATION, COMPOSE, CONTACTS, EDIT_CONTACT,
|
||||
// Voice call views
|
||||
DIALING, INCOMING_CALL, IN_CALL
|
||||
};
|
||||
enum SubView { APP_MENU, INBOX, CONVERSATION, COMPOSE, CONTACTS, EDIT_CONTACT, PHONE_DIALER };
|
||||
|
||||
private:
|
||||
UITask* _task;
|
||||
SubView _view;
|
||||
|
||||
// App menu state
|
||||
int _menuCursor; // 0 = Phone, 1 = SMS Inbox
|
||||
|
||||
// Inbox state
|
||||
SMSConversation _conversations[SMS_MAX_CONVERSATIONS];
|
||||
int _convCount;
|
||||
@@ -72,7 +70,7 @@ private:
|
||||
char _composePhone[SMS_PHONE_LEN];
|
||||
bool _composeNewConversation;
|
||||
|
||||
// Phone input state (for new conversation)
|
||||
// Phone input state (for new conversation and phone dialer)
|
||||
char _phoneInputBuf[SMS_PHONE_LEN];
|
||||
int _phoneInputPos;
|
||||
bool _enteringPhone;
|
||||
@@ -85,14 +83,8 @@ private:
|
||||
char _editPhone[SMS_PHONE_LEN];
|
||||
char _editNameBuf[SMS_CONTACT_NAME_LEN];
|
||||
int _editNamePos;
|
||||
bool _editIsNew;
|
||||
SubView _editReturnView;
|
||||
|
||||
// Voice call state
|
||||
char _callPhone[SMS_PHONE_LEN]; // Number for current/pending call
|
||||
unsigned long _callConnectedMillis; // millis() when call connected
|
||||
SubView _preCallView; // View to return to after call ends
|
||||
uint8_t _callVolume; // Current volume level 0-5
|
||||
bool _editIsNew; // true = adding new, false = editing existing
|
||||
SubView _editReturnView; // where to return after save/cancel
|
||||
|
||||
// Refresh debounce
|
||||
bool _needsRefresh;
|
||||
@@ -109,29 +101,20 @@ private:
|
||||
|
||||
void refreshConversation() {
|
||||
_msgCount = smsStore.loadMessages(_activePhone, _msgs, SMS_MSG_PAGE_SIZE);
|
||||
// Scroll to bottom (newest messages are at end now, chat-style)
|
||||
_msgScrollPos = (_msgCount > 3) ? _msgCount - 3 : 0;
|
||||
}
|
||||
|
||||
// Helper: initiate a call to a phone number
|
||||
void startCall(const char* phone) {
|
||||
strncpy(_callPhone, phone, SMS_PHONE_LEN - 1);
|
||||
_callPhone[SMS_PHONE_LEN - 1] = '\0';
|
||||
_callConnectedMillis = 0;
|
||||
_preCallView = _view;
|
||||
modemManager.dialCall(phone);
|
||||
_view = DIALING;
|
||||
}
|
||||
|
||||
public:
|
||||
SMSScreen(UITask* task)
|
||||
: _task(task), _view(INBOX)
|
||||
: _task(task), _view(APP_MENU)
|
||||
, _menuCursor(0)
|
||||
, _convCount(0), _inboxCursor(0), _inboxScrollTop(0)
|
||||
, _msgCount(0), _msgScrollPos(0)
|
||||
, _composePos(0), _composeNewConversation(false)
|
||||
, _phoneInputPos(0), _enteringPhone(false)
|
||||
, _contactsCursor(0), _contactsScrollTop(0)
|
||||
, _editNamePos(0), _editIsNew(false), _editReturnView(INBOX)
|
||||
, _callConnectedMillis(0), _preCallView(INBOX), _callVolume(3)
|
||||
, _needsRefresh(false), _lastRefresh(0)
|
||||
, _sdReady(false)
|
||||
{
|
||||
@@ -141,26 +124,51 @@ public:
|
||||
memset(_activePhone, 0, sizeof(_activePhone));
|
||||
memset(_editPhone, 0, sizeof(_editPhone));
|
||||
memset(_editNameBuf, 0, sizeof(_editNameBuf));
|
||||
memset(_callPhone, 0, sizeof(_callPhone));
|
||||
}
|
||||
|
||||
void setSDReady(bool ready) { _sdReady = ready; }
|
||||
|
||||
void activate() {
|
||||
_view = INBOX;
|
||||
_inboxCursor = 0;
|
||||
_inboxScrollTop = 0;
|
||||
_view = APP_MENU;
|
||||
_menuCursor = 0;
|
||||
if (_sdReady) refreshInbox();
|
||||
}
|
||||
|
||||
SubView getSubView() const { return _view; }
|
||||
bool isComposing() const { return _view == COMPOSE; }
|
||||
bool isEnteringPhone() const { return _enteringPhone; }
|
||||
bool isInCallView() const {
|
||||
return _view == DIALING || _view == INCOMING_CALL || _view == IN_CALL;
|
||||
bool isEnteringPhone() const { return _enteringPhone || _view == PHONE_DIALER; }
|
||||
bool isInCallView() const { return false; } // TODO: return true when DIALING/IN_CALL views are added
|
||||
|
||||
// Handle call events from modem (incoming, connected, ended, etc.)
|
||||
void onCallEvent(const CallEvent& evt) {
|
||||
// TODO: implement call UI state transitions (DIALING, IN_CALL, INCOMING_CALL views)
|
||||
// For now, just log the event
|
||||
switch (evt.type) {
|
||||
case CallEventType::INCOMING:
|
||||
Serial.printf("[SMSScreen] Incoming call from %s\n", evt.phone);
|
||||
break;
|
||||
case CallEventType::CONNECTED:
|
||||
Serial.printf("[SMSScreen] Call connected: %s\n", evt.phone);
|
||||
break;
|
||||
case CallEventType::ENDED:
|
||||
Serial.printf("[SMSScreen] Call ended (%lus)\n", (unsigned long)evt.duration);
|
||||
break;
|
||||
case CallEventType::MISSED:
|
||||
Serial.printf("[SMSScreen] Missed call from %s\n", evt.phone);
|
||||
break;
|
||||
case CallEventType::BUSY:
|
||||
Serial.printf("[SMSScreen] Busy: %s\n", evt.phone);
|
||||
break;
|
||||
case CallEventType::NO_ANSWER:
|
||||
Serial.printf("[SMSScreen] No answer: %s\n", evt.phone);
|
||||
break;
|
||||
case CallEventType::DIAL_FAILED:
|
||||
Serial.printf("[SMSScreen] Dial failed: %s\n", evt.phone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Called from main loop when an SMS arrives
|
||||
// Called from main loop when an SMS arrives (saves to store + refreshes)
|
||||
void onIncomingSMS(const char* phone, const char* body, uint32_t timestamp) {
|
||||
if (_sdReady) {
|
||||
smsStore.saveMessage(phone, body, false, timestamp);
|
||||
@@ -174,47 +182,6 @@ public:
|
||||
_needsRefresh = true;
|
||||
}
|
||||
|
||||
// Called from main loop when a call event arrives
|
||||
void onCallEvent(const CallEvent& evt) {
|
||||
switch (evt.type) {
|
||||
case CallEventType::INCOMING:
|
||||
// Incoming call — switch to incoming call view
|
||||
strncpy(_callPhone, evt.phone, SMS_PHONE_LEN - 1);
|
||||
_callPhone[SMS_PHONE_LEN - 1] = '\0';
|
||||
if (_view != INCOMING_CALL) {
|
||||
_preCallView = _view;
|
||||
_view = INCOMING_CALL;
|
||||
}
|
||||
break;
|
||||
|
||||
case CallEventType::CONNECTED:
|
||||
// Call connected — switch to in-call view
|
||||
_callConnectedMillis = millis();
|
||||
_view = IN_CALL;
|
||||
break;
|
||||
|
||||
case CallEventType::ENDED:
|
||||
case CallEventType::MISSED:
|
||||
case CallEventType::BUSY:
|
||||
case CallEventType::NO_ANSWER:
|
||||
case CallEventType::DIAL_FAILED:
|
||||
// Call ended — return to previous view
|
||||
_callPhone[0] = '\0';
|
||||
_callConnectedMillis = 0;
|
||||
// Return to pre-call view or inbox
|
||||
if (_preCallView == DIALING || _preCallView == INCOMING_CALL || _preCallView == IN_CALL) {
|
||||
_view = INBOX;
|
||||
if (_sdReady) refreshInbox();
|
||||
} else {
|
||||
_view = _preCallView;
|
||||
if (_view == INBOX && _sdReady) refreshInbox();
|
||||
if (_view == CONVERSATION) refreshConversation();
|
||||
}
|
||||
break;
|
||||
}
|
||||
_needsRefresh = true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Signal strength indicator (top-right corner)
|
||||
// =========================================================================
|
||||
@@ -223,6 +190,7 @@ public:
|
||||
ModemState ms = modemManager.getState();
|
||||
int bars = modemManager.getSignalBars();
|
||||
|
||||
// Draw signal bars (4 bars, increasing height)
|
||||
int barWidth = 3;
|
||||
int barGap = 2;
|
||||
int maxBarH = 10;
|
||||
@@ -242,9 +210,8 @@ public:
|
||||
x += barWidth + barGap;
|
||||
}
|
||||
|
||||
if (ms != ModemState::READY && ms != ModemState::SENDING_SMS &&
|
||||
ms != ModemState::DIALING && ms != ModemState::IN_CALL &&
|
||||
ms != ModemState::RINGING_IN) {
|
||||
// Show modem state text if not ready
|
||||
if (ms != ModemState::READY && ms != ModemState::SENDING_SMS) {
|
||||
display.setTextSize(0);
|
||||
display.setColor(DisplayDriver::YELLOW);
|
||||
const char* label = ModemManager::stateToString(ms);
|
||||
@@ -266,18 +233,139 @@ public:
|
||||
_lastRefresh = millis();
|
||||
|
||||
switch (_view) {
|
||||
case INBOX: return renderInbox(display);
|
||||
case CONVERSATION: return renderConversation(display);
|
||||
case COMPOSE: return renderCompose(display);
|
||||
case CONTACTS: return renderContacts(display);
|
||||
case EDIT_CONTACT: return renderEditContact(display);
|
||||
case DIALING: return renderDialing(display);
|
||||
case INCOMING_CALL: return renderIncomingCall(display);
|
||||
case IN_CALL: return renderInCall(display);
|
||||
case APP_MENU: return renderAppMenu(display);
|
||||
case INBOX: return renderInbox(display);
|
||||
case CONVERSATION: return renderConversation(display);
|
||||
case COMPOSE: return renderCompose(display);
|
||||
case CONTACTS: return renderContacts(display);
|
||||
case EDIT_CONTACT: return renderEditContact(display);
|
||||
case PHONE_DIALER: return renderPhoneDialer(display);
|
||||
}
|
||||
return 1000;
|
||||
}
|
||||
|
||||
// ---- App menu (landing screen) ----
|
||||
int renderAppMenu(DisplayDriver& display) {
|
||||
// Header
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.setCursor(0, 0);
|
||||
display.print("Phone & SMS");
|
||||
|
||||
// Signal strength at top-right
|
||||
renderSignalIndicator(display, display.width() - 2, 0);
|
||||
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawRect(0, 11, display.width(), 1);
|
||||
|
||||
// Menu items
|
||||
display.setTextSize(1);
|
||||
int y = 24;
|
||||
int lineHeight = 16;
|
||||
|
||||
// Item 0: Phone
|
||||
display.setCursor(4, y);
|
||||
display.setColor(_menuCursor == 0 ? DisplayDriver::GREEN : DisplayDriver::LIGHT);
|
||||
if (_menuCursor == 0) display.print("> ");
|
||||
else display.print(" ");
|
||||
display.print("Phone");
|
||||
|
||||
y += lineHeight;
|
||||
|
||||
// Item 1: SMS Inbox
|
||||
display.setCursor(4, y);
|
||||
display.setColor(_menuCursor == 1 ? DisplayDriver::GREEN : DisplayDriver::LIGHT);
|
||||
if (_menuCursor == 1) display.print("> ");
|
||||
else display.print(" ");
|
||||
display.print("SMS Inbox");
|
||||
|
||||
// Show conversation count hint
|
||||
if (_convCount > 0) {
|
||||
char countHint[12];
|
||||
snprintf(countHint, sizeof(countHint), " [%d]", _convCount);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.print(countHint);
|
||||
}
|
||||
|
||||
// Modem status if not ready
|
||||
ModemState ms = modemManager.getState();
|
||||
if (ms != ModemState::READY && ms != ModemState::SENDING_SMS) {
|
||||
display.setTextSize(0);
|
||||
display.setColor(DisplayDriver::YELLOW);
|
||||
display.setCursor(4, y + lineHeight + 8);
|
||||
char statBuf[40];
|
||||
snprintf(statBuf, sizeof(statBuf), "Modem: %s", ModemManager::stateToString(ms));
|
||||
display.print(statBuf);
|
||||
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);
|
||||
display.print("Q:Back");
|
||||
const char* rt = "Ent:Open";
|
||||
display.setCursor(display.width() - display.getTextWidth(rt) - 2, footerY);
|
||||
display.print(rt);
|
||||
|
||||
return 5000;
|
||||
}
|
||||
|
||||
// ---- Phone dialer ----
|
||||
int renderPhoneDialer(DisplayDriver& display) {
|
||||
// Header
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.setCursor(0, 0);
|
||||
display.print("Dial Number");
|
||||
|
||||
// Signal strength at top-right
|
||||
renderSignalIndicator(display, display.width() - 2, 0);
|
||||
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawRect(0, 11, display.width(), 1);
|
||||
|
||||
// Phone number input
|
||||
display.setTextSize(0);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.setCursor(4, 24);
|
||||
display.print("Enter phone number:");
|
||||
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.setCursor(4, 40);
|
||||
if (_phoneInputPos > 0) {
|
||||
display.print(_phoneInputBuf);
|
||||
}
|
||||
display.print("_");
|
||||
|
||||
// Hint if empty
|
||||
if (_phoneInputPos == 0) {
|
||||
display.setTextSize(0);
|
||||
display.setColor(DisplayDriver::DARK);
|
||||
display.setCursor(4, 58);
|
||||
display.print("digits, +, *, #");
|
||||
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);
|
||||
display.print("Q:Back");
|
||||
if (_phoneInputPos > 0) {
|
||||
const char* rt = "Ent:Call";
|
||||
display.setCursor(display.width() - display.getTextWidth(rt) - 2, footerY);
|
||||
display.print(rt);
|
||||
}
|
||||
|
||||
return 2000;
|
||||
}
|
||||
|
||||
// ---- Inbox ----
|
||||
int renderInbox(DisplayDriver& display) {
|
||||
ModemState ms = modemManager.getState();
|
||||
@@ -318,6 +406,7 @@ public:
|
||||
int visibleCount = (display.height() - 14 - 14) / (lineHeight * 2 + 2);
|
||||
if (visibleCount < 1) visibleCount = 1;
|
||||
|
||||
// Adjust scroll to keep cursor visible
|
||||
if (_inboxCursor < _inboxScrollTop) _inboxScrollTop = _inboxCursor;
|
||||
if (_inboxCursor >= _inboxScrollTop + visibleCount) {
|
||||
_inboxScrollTop = _inboxCursor - visibleCount + 1;
|
||||
@@ -330,6 +419,7 @@ public:
|
||||
|
||||
bool selected = (idx == _inboxCursor);
|
||||
|
||||
// Resolve contact name (shows name if saved, phone otherwise)
|
||||
char dispName[SMS_CONTACT_NAME_LEN];
|
||||
smsContacts.displayName(c.phone, dispName, sizeof(dispName));
|
||||
|
||||
@@ -377,6 +467,7 @@ public:
|
||||
|
||||
// ---- Conversation view ----
|
||||
int renderConversation(DisplayDriver& display) {
|
||||
// Header - show contact name if available, phone otherwise
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.setCursor(0, 0);
|
||||
@@ -384,6 +475,7 @@ public:
|
||||
smsContacts.displayName(_activePhone, convTitle, sizeof(convTitle));
|
||||
display.print(convTitle);
|
||||
|
||||
// Signal icon
|
||||
renderSignalIndicator(display, display.width() - 2, 0);
|
||||
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
@@ -401,6 +493,7 @@ public:
|
||||
int headerHeight = 14;
|
||||
int footerHeight = 14;
|
||||
|
||||
// Estimate chars per line
|
||||
uint16_t testWidth = display.getTextWidth("MMMMMMMMMM");
|
||||
int charsPerLine = (testWidth > 0) ? (display.width() * 10) / testWidth : 20;
|
||||
if (charsPerLine < 12) charsPerLine = 12;
|
||||
@@ -413,13 +506,15 @@ public:
|
||||
SMSMessage& msg = _msgs[i];
|
||||
if (!msg.valid) continue;
|
||||
|
||||
// Direction indicator
|
||||
display.setCursor(0, y);
|
||||
display.setColor(msg.isSent ? DisplayDriver::BLUE : DisplayDriver::YELLOW);
|
||||
|
||||
// Time formatting (epoch-aware)
|
||||
char timeStr[16];
|
||||
time_t now = time(nullptr);
|
||||
bool haveEpoch = (now > 1700000000);
|
||||
bool msgIsEpoch = (msg.timestamp > 1700000000);
|
||||
bool haveEpoch = (now > 1700000000); // system clock is set
|
||||
bool msgIsEpoch = (msg.timestamp > 1700000000); // msg has real timestamp
|
||||
|
||||
if (haveEpoch && msgIsEpoch) {
|
||||
uint32_t age = (uint32_t)(now - msg.timestamp);
|
||||
@@ -437,6 +532,7 @@ public:
|
||||
display.print(header);
|
||||
y += lineHeight;
|
||||
|
||||
// Message body with simple word wrap
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
int textLen = strlen(msg.body);
|
||||
int pos = 0;
|
||||
@@ -466,16 +562,13 @@ public:
|
||||
display.setTextSize(1);
|
||||
}
|
||||
|
||||
// Footer — now includes F:Call
|
||||
// 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);
|
||||
display.print("Q:Bk A:Ct");
|
||||
const char* mid = "F:Call";
|
||||
display.setCursor((display.width() - display.getTextWidth(mid)) / 2, footerY);
|
||||
display.print(mid);
|
||||
display.print("Q:Bk A:Add Contact");
|
||||
const char* rt = "C:Reply";
|
||||
display.setCursor(display.width() - display.getTextWidth(rt) - 2, footerY);
|
||||
display.print(rt);
|
||||
@@ -495,6 +588,7 @@ public:
|
||||
display.print(_phoneInputBuf);
|
||||
display.print("_");
|
||||
} else {
|
||||
// Show contact name if available
|
||||
char dispName[SMS_CONTACT_NAME_LEN];
|
||||
smsContacts.displayName(_composePhone, dispName, sizeof(dispName));
|
||||
char toLabel[40];
|
||||
@@ -506,6 +600,7 @@ public:
|
||||
display.drawRect(0, 11, display.width(), 1);
|
||||
|
||||
if (!_enteringPhone) {
|
||||
// Message body
|
||||
display.setCursor(0, 14);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.setTextSize(0);
|
||||
@@ -528,6 +623,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Cursor
|
||||
display.setCursor(x * (display.width() / charsPerLine), y);
|
||||
display.print("_");
|
||||
display.setTextSize(1);
|
||||
@@ -587,6 +683,7 @@ public:
|
||||
int visibleCount = (display.height() - 14 - 14) / (lineHeight * 2 + 2);
|
||||
if (visibleCount < 1) visibleCount = 1;
|
||||
|
||||
// Adjust scroll
|
||||
if (_contactsCursor >= cnt) _contactsCursor = cnt - 1;
|
||||
if (_contactsCursor < 0) _contactsCursor = 0;
|
||||
if (_contactsCursor < _contactsScrollTop) _contactsScrollTop = _contactsCursor;
|
||||
@@ -601,12 +698,14 @@ public:
|
||||
|
||||
bool selected = (idx == _contactsCursor);
|
||||
|
||||
// Name
|
||||
display.setCursor(0, y);
|
||||
display.setColor(selected ? DisplayDriver::GREEN : DisplayDriver::LIGHT);
|
||||
if (selected) display.print("> ");
|
||||
display.print(ct.name);
|
||||
y += lineHeight;
|
||||
|
||||
// Phone (dimmer)
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.setCursor(12, y);
|
||||
display.print(ct.phone);
|
||||
@@ -615,17 +714,14 @@ public:
|
||||
display.setTextSize(1);
|
||||
}
|
||||
|
||||
// Footer — now includes F:Call
|
||||
// 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);
|
||||
display.print("Q:Back");
|
||||
const char* mid = "F:Call";
|
||||
display.setCursor((display.width() - display.getTextWidth(mid)) / 2, footerY);
|
||||
display.print(mid);
|
||||
const char* rt = "Ent:SMS";
|
||||
const char* rt = "Ent:SMS F:Call";
|
||||
display.setCursor(display.width() - display.getTextWidth(rt) - 2, footerY);
|
||||
display.print(rt);
|
||||
|
||||
@@ -642,12 +738,14 @@ public:
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawRect(0, 11, display.width(), 1);
|
||||
|
||||
// Phone number (read-only)
|
||||
display.setTextSize(0);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.setCursor(0, 16);
|
||||
display.print("Phone: ");
|
||||
display.print(_editPhone);
|
||||
|
||||
// Name input
|
||||
display.setCursor(0, 30);
|
||||
display.setColor(DisplayDriver::YELLOW);
|
||||
display.print("Name: ");
|
||||
@@ -657,6 +755,7 @@ public:
|
||||
|
||||
display.setTextSize(1);
|
||||
|
||||
// Footer
|
||||
display.setTextSize(1);
|
||||
int footerY = display.height() - 12;
|
||||
display.drawRect(0, footerY - 2, display.width(), 1);
|
||||
@@ -670,214 +769,92 @@ public:
|
||||
return 2000;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// VOICE CALL RENDER VIEWS
|
||||
// =========================================================================
|
||||
|
||||
// ---- Dialing (outgoing call in progress) ----
|
||||
int renderDialing(DisplayDriver& display) {
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.setCursor(0, 0);
|
||||
display.print("Calling...");
|
||||
|
||||
renderSignalIndicator(display, display.width() - 2, 0);
|
||||
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawRect(0, 11, display.width(), 1);
|
||||
|
||||
// Contact name / phone number centred
|
||||
int centreY = display.height() / 2 - 20;
|
||||
|
||||
char dispName[SMS_CONTACT_NAME_LEN];
|
||||
smsContacts.displayName(_callPhone, dispName, sizeof(dispName));
|
||||
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
uint16_t nameW = display.getTextWidth(dispName);
|
||||
display.setCursor((display.width() - nameW) / 2, centreY);
|
||||
display.print(dispName);
|
||||
|
||||
// Show raw phone number below name (if name differs from phone)
|
||||
if (strcmp(dispName, _callPhone) != 0) {
|
||||
display.setTextSize(0);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
uint16_t phoneW = display.getTextWidth(_callPhone);
|
||||
display.setCursor((display.width() - phoneW) / 2, centreY + 16);
|
||||
display.print(_callPhone);
|
||||
}
|
||||
|
||||
// Animated dots indicator
|
||||
display.setTextSize(0);
|
||||
display.setColor(DisplayDriver::YELLOW);
|
||||
unsigned long elapsed = millis() / 500;
|
||||
int dots = (elapsed % 4);
|
||||
char dotStr[5] = " ";
|
||||
for (int i = 0; i < dots; i++) dotStr[i] = '.';
|
||||
dotStr[dots] = '\0';
|
||||
uint16_t dotW = display.getTextWidth("...");
|
||||
display.setCursor((display.width() - dotW) / 2, centreY + 32);
|
||||
display.print(dotStr);
|
||||
|
||||
// Footer
|
||||
display.setTextSize(1);
|
||||
int footerY = display.height() - 12;
|
||||
display.drawRect(0, footerY - 2, display.width(), 1);
|
||||
display.setColor(DisplayDriver::YELLOW);
|
||||
const char* hangup = "Ent:Hangup";
|
||||
display.setCursor((display.width() - display.getTextWidth(hangup)) / 2, footerY);
|
||||
display.print(hangup);
|
||||
|
||||
return 500; // Fast refresh for animated dots
|
||||
}
|
||||
|
||||
// ---- Incoming call ----
|
||||
int renderIncomingCall(DisplayDriver& display) {
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.setCursor(0, 0);
|
||||
display.print("Incoming Call");
|
||||
|
||||
renderSignalIndicator(display, display.width() - 2, 0);
|
||||
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawRect(0, 11, display.width(), 1);
|
||||
|
||||
int centreY = display.height() / 2 - 20;
|
||||
|
||||
char dispName[SMS_CONTACT_NAME_LEN];
|
||||
if (_callPhone[0]) {
|
||||
smsContacts.displayName(_callPhone, dispName, sizeof(dispName));
|
||||
} else {
|
||||
strncpy(dispName, "Unknown", sizeof(dispName));
|
||||
}
|
||||
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
uint16_t nameW = display.getTextWidth(dispName);
|
||||
display.setCursor((display.width() - nameW) / 2, centreY);
|
||||
display.print(dispName);
|
||||
|
||||
if (_callPhone[0] && strcmp(dispName, _callPhone) != 0) {
|
||||
display.setTextSize(0);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
uint16_t phoneW = display.getTextWidth(_callPhone);
|
||||
display.setCursor((display.width() - phoneW) / 2, centreY + 16);
|
||||
display.print(_callPhone);
|
||||
}
|
||||
|
||||
// Ringing indicator
|
||||
display.setTextSize(0);
|
||||
display.setColor(DisplayDriver::YELLOW);
|
||||
unsigned long elapsed = millis() / 300;
|
||||
const char* ring = (elapsed % 2 == 0) ? "RINGING" : "";
|
||||
uint16_t ringW = display.getTextWidth("RINGING");
|
||||
display.setCursor((display.width() - ringW) / 2, centreY + 36);
|
||||
display.print(ring);
|
||||
|
||||
// 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);
|
||||
display.print("Ent:Answer");
|
||||
const char* rt = "Q:Reject";
|
||||
display.setCursor(display.width() - display.getTextWidth(rt) - 2, footerY);
|
||||
display.print(rt);
|
||||
|
||||
return 500; // Fast refresh for flashing ring indicator
|
||||
}
|
||||
|
||||
// ---- In-call ----
|
||||
int renderInCall(DisplayDriver& display) {
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.setCursor(0, 0);
|
||||
display.print("In Call");
|
||||
|
||||
renderSignalIndicator(display, display.width() - 2, 0);
|
||||
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawRect(0, 11, display.width(), 1);
|
||||
|
||||
int centreY = 20;
|
||||
|
||||
// Contact name
|
||||
char dispName[SMS_CONTACT_NAME_LEN];
|
||||
smsContacts.displayName(_callPhone, dispName, sizeof(dispName));
|
||||
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
uint16_t nameW = display.getTextWidth(dispName);
|
||||
display.setCursor((display.width() - nameW) / 2, centreY);
|
||||
display.print(dispName);
|
||||
|
||||
// Phone number (if name differs)
|
||||
if (strcmp(dispName, _callPhone) != 0) {
|
||||
display.setTextSize(0);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
uint16_t phoneW = display.getTextWidth(_callPhone);
|
||||
display.setCursor((display.width() - phoneW) / 2, centreY + 16);
|
||||
display.print(_callPhone);
|
||||
}
|
||||
|
||||
// Call duration timer
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
uint32_t durSec = 0;
|
||||
if (_callConnectedMillis > 0) {
|
||||
durSec = (millis() - _callConnectedMillis) / 1000;
|
||||
}
|
||||
char timerStr[12];
|
||||
snprintf(timerStr, sizeof(timerStr), "%02d:%02d", (int)(durSec / 60), (int)(durSec % 60));
|
||||
uint16_t timerW = display.getTextWidth(timerStr);
|
||||
display.setCursor((display.width() - timerW) / 2, centreY + 40);
|
||||
display.print(timerStr);
|
||||
|
||||
// Volume indicator
|
||||
display.setTextSize(0);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
char volStr[16];
|
||||
snprintf(volStr, sizeof(volStr), "Vol: %d/5", _callVolume);
|
||||
display.setCursor(0, centreY + 60);
|
||||
display.print(volStr);
|
||||
|
||||
// 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);
|
||||
display.print("Ent:Hang");
|
||||
const char* mid = "W/S:Vol";
|
||||
display.setCursor((display.width() - display.getTextWidth(mid)) / 2, footerY);
|
||||
display.print(mid);
|
||||
const char* rt = "0-9:DTMF";
|
||||
display.setCursor(display.width() - display.getTextWidth(rt) - 2, footerY);
|
||||
display.print(rt);
|
||||
|
||||
return 1000; // 1s refresh for call timer
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// INPUT HANDLING
|
||||
// =========================================================================
|
||||
|
||||
bool handleInput(char c) override {
|
||||
switch (_view) {
|
||||
case INBOX: return handleInboxInput(c);
|
||||
case CONVERSATION: return handleConversationInput(c);
|
||||
case COMPOSE: return handleComposeInput(c);
|
||||
case CONTACTS: return handleContactsInput(c);
|
||||
case EDIT_CONTACT: return handleEditContactInput(c);
|
||||
case DIALING: return handleDialingInput(c);
|
||||
case INCOMING_CALL: return handleIncomingCallInput(c);
|
||||
case IN_CALL: return handleInCallInput(c);
|
||||
case APP_MENU: return handleAppMenuInput(c);
|
||||
case INBOX: return handleInboxInput(c);
|
||||
case CONVERSATION: return handleConversationInput(c);
|
||||
case COMPOSE: return handleComposeInput(c);
|
||||
case CONTACTS: return handleContactsInput(c);
|
||||
case EDIT_CONTACT: return handleEditContactInput(c);
|
||||
case PHONE_DIALER: return handlePhoneDialerInput(c);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- App menu input ----
|
||||
bool handleAppMenuInput(char c) {
|
||||
switch (c) {
|
||||
case 'w': case 'W':
|
||||
_menuCursor = 0;
|
||||
return true;
|
||||
|
||||
case 's': case 'S':
|
||||
_menuCursor = 1;
|
||||
return true;
|
||||
|
||||
case '\r': // Enter - select menu item
|
||||
if (_menuCursor == 0) {
|
||||
// Phone dialer
|
||||
_phoneInputBuf[0] = '\0';
|
||||
_phoneInputPos = 0;
|
||||
_view = PHONE_DIALER;
|
||||
} else {
|
||||
// SMS Inbox
|
||||
if (_sdReady) refreshInbox();
|
||||
_inboxCursor = 0;
|
||||
_inboxScrollTop = 0;
|
||||
_view = INBOX;
|
||||
}
|
||||
return true;
|
||||
|
||||
case 'q': case 'Q': // Back to home (handled by main.cpp)
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Phone dialer input ----
|
||||
bool handlePhoneDialerInput(char c) {
|
||||
switch (c) {
|
||||
case '\r': // Enter - place call
|
||||
if (_phoneInputPos > 0) {
|
||||
_phoneInputBuf[_phoneInputPos] = '\0';
|
||||
modemManager.dialCall(_phoneInputBuf);
|
||||
// TODO: transition to DIALING/IN_CALL view when implemented
|
||||
}
|
||||
return true;
|
||||
|
||||
case '\b': // Backspace
|
||||
if (_phoneInputPos > 0) {
|
||||
_phoneInputPos--;
|
||||
_phoneInputBuf[_phoneInputPos] = '\0';
|
||||
}
|
||||
return true;
|
||||
|
||||
case 'q': case 'Q': // Back to app menu
|
||||
_phoneInputBuf[0] = '\0';
|
||||
_phoneInputPos = 0;
|
||||
_view = APP_MENU;
|
||||
return true;
|
||||
|
||||
default:
|
||||
// Accept phone number characters
|
||||
if (_phoneInputPos < SMS_PHONE_LEN - 1 &&
|
||||
((c >= '0' && c <= '9') || c == '+' || c == '*' || c == '#')) {
|
||||
_phoneInputBuf[_phoneInputPos++] = c;
|
||||
_phoneInputBuf[_phoneInputPos] = '\0';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Inbox input ----
|
||||
bool handleInboxInput(char c) {
|
||||
switch (c) {
|
||||
@@ -913,8 +890,10 @@ public:
|
||||
_view = CONTACTS;
|
||||
return true;
|
||||
|
||||
case 'q': case 'Q': // Back to home (handled by main.cpp)
|
||||
return false;
|
||||
case 'q': case 'Q': // Back to app menu
|
||||
_view = APP_MENU;
|
||||
_menuCursor = 0;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
@@ -941,9 +920,10 @@ public:
|
||||
_view = COMPOSE;
|
||||
return true;
|
||||
|
||||
case 'f': case 'F': // Call this contact
|
||||
if (modemManager.isReady() && _activePhone[0]) {
|
||||
startCall(_activePhone);
|
||||
case 'f': case 'F': // Call this number
|
||||
if (_activePhone[0] != '\0') {
|
||||
modemManager.dialCall(_activePhone);
|
||||
// TODO: transition to DIALING/IN_CALL view when implemented
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -1025,7 +1005,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Phone number input ----
|
||||
// ---- Phone number input (for compose) ----
|
||||
bool handlePhoneInput(char c) {
|
||||
switch (c) {
|
||||
case '\r': // Done entering phone, move to body
|
||||
@@ -1089,9 +1069,10 @@ public:
|
||||
return true;
|
||||
|
||||
case 'f': case 'F': // Call selected contact
|
||||
if (cnt > 0 && _contactsCursor < cnt && modemManager.isReady()) {
|
||||
if (cnt > 0 && _contactsCursor < cnt) {
|
||||
const SMSContact& ct = smsContacts.get(_contactsCursor);
|
||||
startCall(ct.phone);
|
||||
modemManager.dialCall(ct.phone);
|
||||
// TODO: transition to DIALING/IN_CALL view when implemented
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -1146,70 +1127,6 @@ public:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// VOICE CALL INPUT HANDLERS
|
||||
// =========================================================================
|
||||
|
||||
// ---- Dialing input ----
|
||||
bool handleDialingInput(char c) {
|
||||
switch (c) {
|
||||
case '\r': // Enter — hangup / cancel dial
|
||||
case 'q': case 'Q':
|
||||
modemManager.hangupCall();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return true; // Absorb all keys during dialing
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Incoming call input ----
|
||||
bool handleIncomingCallInput(char c) {
|
||||
switch (c) {
|
||||
case '\r': // Enter — answer call
|
||||
modemManager.answerCall();
|
||||
return true;
|
||||
|
||||
case 'q': case 'Q': // Reject call
|
||||
modemManager.hangupCall();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return true; // Absorb all keys
|
||||
}
|
||||
}
|
||||
|
||||
// ---- In-call input ----
|
||||
bool handleInCallInput(char c) {
|
||||
switch (c) {
|
||||
case '\r': // Enter — hangup
|
||||
case 'q': case 'Q':
|
||||
modemManager.hangupCall();
|
||||
return true;
|
||||
|
||||
case 'w': case 'W': // Volume up
|
||||
if (_callVolume < 5) {
|
||||
_callVolume++;
|
||||
modemManager.setCallVolume(_callVolume);
|
||||
}
|
||||
return true;
|
||||
|
||||
case 's': case 'S': // Volume down
|
||||
if (_callVolume > 0) {
|
||||
_callVolume--;
|
||||
modemManager.setCallVolume(_callVolume);
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
// 0-9, *, # — send as DTMF
|
||||
if ((c >= '0' && c <= '9') || c == '*' || c == '#') {
|
||||
modemManager.sendDTMF(c);
|
||||
}
|
||||
return true; // Absorb all keys during call
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // SMS_SCREEN_H
|
||||
|
||||
Reference in New Issue
Block a user