New Max home UI on all T-Deck Pro builds; Call Log; lock screen notifications

Home UI
- Widen the tile-grid home screen (e1300190) from Max-only to all T-Deck Pro
  builds: guard flips from LilyGo_TDeck_Pro_Max to LilyGo_TDeck_Pro at five
  sites in UITask.cpp (HomeIcons include, tile-flag set/reset, page-dot
  offset, tile render block) and at the touch hit-test in main.cpp. Max
  defines both flags, so Max behaviour is unchanged -- verified by diffing
  both files preprocessed with the Max define set.

Call Log (HAS_4G_MODEM)
- New CALL_LOG subview; app menu is now Dial / SMS Inbox / Call Log.
- SMSStore gains a fixed-size CallLogRecord store in /sms/calllog.dat
  (timestamp, duration, type, seen, phone), capped at 32 entries with the
  oldest trimmed on append: appendCallLog, loadCallLog (newest first),
  deleteCallLogEntry (rewrite via tmp + rename) and markMissedSeen
  (in-place flag update). Same SD idioms as the SMS store.
- Logs incoming and outgoing calls with duration, plus missed, busy and
  no-answer attempts. Calls arriving with no caller ID are logged and shown
  as "Unknown" rather than dropped.
- List mirrors inbox conventions: contact name via SMSContacts, detail line
  with type, duration and local date/time. Enter dials, D deletes, Q backs
  out. Menu row carries an unseen-missed [n] badge matching SMS Inbox.

Lock screen (HAS_4G_MODEM)
- Show outstanding unread SMS and unseen missed calls at height()-12 with
  setTextSize(1). Counts derive from the inbox read flags and call log seen
  flags, so they persist across re-locks and reboots and clear only when the
  conversation or the Call Log is opened. State loads at boot in
  setSDReady().

Phone screen fixes
- An incoming call now drops the lock screen. Keyboard and touch input are
  both blocked while locked, which left the ringing screen unable to answer
  or reject. The device stays unlocked afterwards.
- gotoSMSScreen() no longer calls activate() mid-call, which was resetting
  the view to the app menu and wiping the ringing screen.
- Widen the app menu footer right margin so "Ent:Open" is not clipped.
This commit is contained in:
pelgraine
2026-07-25 16:40:01 +10:00
parent e130019061
commit 922f0e3c17
6 changed files with 456 additions and 25 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
#define FIRMWARE_VER_CODE 11
#ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "18 July 2026"
#define FIRMWARE_BUILD_DATE "25 July 2026"
#endif
#ifndef FIRMWARE_VERSION
+10 -3
View File
@@ -1312,8 +1312,8 @@ static void lastHeardToggleContact() {
// Home screen FIRST page: tile taps (virtual coordinate hit test)
if (ui_task.isOnHomeScreen() && ui_task.isHomeShowingTiles()) {
#if defined(LilyGo_TDeck_Pro_Max)
// MAX: 2-column tile grid, 6 paired rows + full-width Phone row.
#if defined(LilyGo_TDeck_Pro)
// T-Deck Pro: 2-column tile grid, 6 paired rows + full-width Phone row.
// Hit-tested in RAW PHYSICAL pixels (240x320) using the untranslated
// touch coords -- geometry must stay in sync with the render block in
// UITask.cpp.
@@ -1413,7 +1413,7 @@ static void lastHeardToggleContact() {
}
// Tap outside tiles — left half backward, right half forward
return (vx < 64) ? (char)KEY_PREV : (char)KEY_NEXT;
#endif // LilyGo_TDeck_Pro_Max
#endif // LilyGo_TDeck_Pro
}
// Home screen (non-tile pages): left half taps backward, right half forward
@@ -3358,6 +3358,13 @@ void loop() {
ui_task.showAlert(alertBuf, 3000);
ui_task.notify(UIEventType::contactMessage);
// An incoming call drops the lock screen so the call can be answered.
// Keyboard and touch input are both blocked while locked, which would
// otherwise leave the ringing screen unable to answer or reject.
// The device stays unlocked after the call ends.
if (ui_task.isLocked()) {
ui_task.unlockScreen();
}
if (!smsMode) {
ui_task.gotoSMSScreen();
}
+229 -12
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 };
DIALING_OUT, INCOMING_CALL, IN_CALL, CALL_LOG };
private:
UITask* _task;
@@ -56,7 +56,15 @@ private:
SubView _view;
// App menu state
int _menuCursor; // 0 = Phone, 1 = SMS Inbox
int _menuCursor; // 0 = Dial, 1 = SMS Inbox, 2 = Call Log
// Call log state
CallLogRecord _callLog[SMS_CALLLOG_MAX];
int _callLogCount;
int _callLogCursor;
int _callLogScrollTop;
int _unseenMissed; // Missed calls not yet viewed in the log (menu badge)
bool _callWasIncoming; // Direction of the active/last call, for log entries
// Inbox state
SMSConversation _conversations[SMS_MAX_CONVERSATIONS];
@@ -108,6 +116,14 @@ private:
bool _sdReady;
// Reload helpers
void refreshCallLog() {
_callLogCount = smsStore.loadCallLog(_callLog, SMS_CALLLOG_MAX);
_unseenMissed = 0;
for (int i = 0; i < _callLogCount; i++) {
if (_callLog[i].type == CALL_LOG_MISSED && _callLog[i].seen == 0) _unseenMissed++;
}
}
void refreshInbox() {
_convCount = smsStore.loadConversations(_conversations, SMS_MAX_CONVERSATIONS);
}
@@ -129,6 +145,8 @@ public:
, _contactsCursor(0), _contactsScrollTop(0)
, _editNamePos(0), _editIsNew(false), _editReturnView(INBOX)
, _callReturnView(APP_MENU), _callConnectTime(0), _callVolume(3), _callDotAnim(0)
, _callLogCount(0), _callLogCursor(0), _callLogScrollTop(0), _unseenMissed(0)
, _callWasIncoming(false)
, _needsRefresh(false), _lastRefresh(0)
, _sdReady(false)
{
@@ -141,12 +159,32 @@ public:
memset(_callPhone, 0, sizeof(_callPhone));
}
void setSDReady(bool ready) { _sdReady = ready; }
void setSDReady(bool ready) {
_sdReady = ready;
if (_sdReady) {
// Load unread/unseen state now so the lock screen shows the right
// counts before the phone screen has ever been opened
refreshInbox();
refreshCallLog();
}
}
// Live counts for the lock screen badge. Both clear only when the relevant
// conversation or the call log is actually opened.
int getUnreadSmsCount() const {
int n = 0;
for (int i = 0; i < _convCount; i++) n += _conversations[i].unreadCount;
return n;
}
int getUnseenMissedCount() const { return _unseenMissed; }
void activate() {
_view = APP_MENU;
_menuCursor = 0;
if (_sdReady) refreshInbox();
if (_sdReady) {
refreshInbox();
refreshCallLog();
}
}
SubView getSubView() const { return _view; }
@@ -164,6 +202,7 @@ public:
_callConnectTime = 0;
_callVolume = 3;
_callDotAnim = 0;
_callWasIncoming = false;
_view = DIALING_OUT;
modemManager.dialCall(phone);
}
@@ -177,6 +216,7 @@ public:
_callPhone[SMS_PHONE_LEN - 1] = '\0';
_callConnectTime = 0;
_callVolume = 3;
_callWasIncoming = true;
if (!isInCallView()) {
_callReturnView = _view;
}
@@ -193,6 +233,10 @@ public:
case CallEventType::ENDED:
Serial.printf("[SMSScreen] Call ended (%lus)\n", (unsigned long)evt.duration);
if (_sdReady) {
smsStore.appendCallLog(_callWasIncoming ? CALL_LOG_INCOMING : CALL_LOG_OUTGOING,
evt.phone, evt.duration, (uint32_t)time(nullptr));
}
if (_view == IN_CALL || _view == DIALING_OUT) {
// Remote hangup or network drop — return to previous view
// "Call Ended" alert is shown by main.cpp via showAlert()
@@ -200,28 +244,41 @@ public:
}
_callPhone[0] = '\0';
_callConnectTime = 0;
if (_view == CALL_LOG && _sdReady) refreshCallLog();
_needsRefresh = true;
break;
case CallEventType::MISSED:
Serial.printf("[SMSScreen] Missed call from %s\n", evt.phone);
if (_sdReady) {
smsStore.appendCallLog(CALL_LOG_MISSED, evt.phone, 0, (uint32_t)time(nullptr));
}
_view = _callReturnView;
_callPhone[0] = '\0';
_callConnectTime = 0;
if (_sdReady) refreshCallLog();
_needsRefresh = true;
break;
case CallEventType::BUSY:
Serial.printf("[SMSScreen] Busy: %s\n", evt.phone);
if (_sdReady) {
smsStore.appendCallLog(CALL_LOG_OUTGOING, evt.phone, 0, (uint32_t)time(nullptr));
}
_view = _callReturnView;
_callPhone[0] = '\0';
if (_view == CALL_LOG && _sdReady) refreshCallLog();
_needsRefresh = true;
break;
case CallEventType::NO_ANSWER:
Serial.printf("[SMSScreen] No answer: %s\n", evt.phone);
if (_sdReady) {
smsStore.appendCallLog(CALL_LOG_OUTGOING, evt.phone, 0, (uint32_t)time(nullptr));
}
_view = _callReturnView;
_callPhone[0] = '\0';
if (_view == CALL_LOG && _sdReady) refreshCallLog();
_needsRefresh = true;
break;
@@ -243,9 +300,9 @@ public:
smsStore.markConversationRead(_activePhone);
refreshConversation();
}
if (_view == INBOX || _view == APP_MENU) {
refreshInbox();
}
// Refresh regardless of the current view so the unread total stays
// correct for the lock screen badge
if (_sdReady) refreshInbox();
_needsRefresh = true;
}
@@ -301,6 +358,7 @@ public:
switch (_view) {
case APP_MENU: return renderAppMenu(display);
case CALL_LOG: return renderCallLog(display);
case INBOX: return renderInbox(display);
case CONVERSATION: return renderConversation(display);
case COMPOSE: return renderCompose(display);
@@ -338,7 +396,7 @@ public:
display.setColor(_menuCursor == 0 ? DisplayDriver::GREEN : DisplayDriver::LIGHT);
if (_menuCursor == 0) display.print("> ");
else display.print(" ");
display.print("Phone");
display.print("Dial");
y += lineHeight;
@@ -359,6 +417,23 @@ 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());
@@ -387,7 +462,7 @@ public:
display.setCursor(0, footerY);
display.print("Q:Back");
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);
if (ms != ModemState::READY && ms != ModemState::SENDING_SMS) {
@@ -533,6 +608,138 @@ public:
}
// ---- Inbox ----
// ---- Call log ----
int renderCallLog(DisplayDriver& display) {
// Header
display.setTextSize(1);
display.setColor(DisplayDriver::GREEN);
display.setCursor(0, 0);
display.print("Call Log");
// Signal strength at top-right
renderSignalIndicator(display, display.width() - 2, 0);
display.setColor(DisplayDriver::LIGHT);
display.drawRect(0, 11, display.width(), 1);
if (_callLogCount == 0) {
display.setTextSize(_prefs->smallTextSize());
display.setColor(DisplayDriver::LIGHT);
display.setCursor(0, 20);
display.print("No calls");
display.setTextSize(1);
} else {
display.setTextSize(_prefs->smallTextSize());
int lineHeight = _prefs->smallLineH() + 1;
int y = 14;
int visibleCount = (display.height() - 14 - 14) / (lineHeight * 2 + 2);
if (visibleCount < 1) visibleCount = 1;
// Adjust scroll to keep cursor visible
if (_callLogCursor < _callLogScrollTop) _callLogScrollTop = _callLogCursor;
if (_callLogCursor >= _callLogScrollTop + visibleCount) {
_callLogScrollTop = _callLogCursor - visibleCount + 1;
}
for (int vi = 0; vi < visibleCount && (_callLogScrollTop + vi) < _callLogCount; vi++) {
int idx = _callLogScrollTop + vi;
CallLogRecord& e = _callLog[idx];
bool selected = (idx == _callLogCursor);
// Resolve contact name (shows name if saved, phone otherwise)
char dispName[SMS_CONTACT_NAME_LEN];
if (e.phone[0]) {
smsContacts.displayName(e.phone, dispName, sizeof(dispName));
} else {
strncpy(dispName, "Unknown", sizeof(dispName) - 1);
dispName[sizeof(dispName) - 1] = '\0';
}
display.setCursor(0, y);
display.setColor(selected ? DisplayDriver::GREEN : DisplayDriver::LIGHT);
if (selected) display.print("> ");
display.print(dispName);
y += lineHeight;
// Detail line: type, duration for connected calls, local date/time
const char* typeStr = (e.type == CALL_LOG_MISSED) ? "Missed"
: (e.type == CALL_LOG_INCOMING) ? "In" : "Out";
int32_t local = (int32_t)e.timestamp + ((int32_t)_prefs->utc_offset_hours * 3600);
time_t lt = (time_t)local;
struct tm tmv;
gmtime_r(&lt, &tmv);
char detail[36];
if (e.type != CALL_LOG_MISSED && e.duration > 0) {
snprintf(detail, sizeof(detail), "%s %lu:%02lu %02d/%02d %02d:%02d",
typeStr, (unsigned long)(e.duration / 60), (unsigned long)(e.duration % 60),
tmv.tm_mday, tmv.tm_mon + 1, tmv.tm_hour, tmv.tm_min);
} else {
snprintf(detail, sizeof(detail), "%s %02d/%02d %02d:%02d",
typeStr, tmv.tm_mday, tmv.tm_mon + 1, tmv.tm_hour, tmv.tm_min);
}
display.setColor(DisplayDriver::LIGHT);
display.setCursor(12, y);
display.print(detail);
y += lineHeight + 2;
}
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* mid = "D:Del";
display.setCursor((display.width() - display.getTextWidth(mid)) / 2, footerY);
display.print(mid);
const char* rt = "Ent:Dial";
display.setCursor(display.width() - display.getTextWidth(rt) - 2, footerY);
display.print(rt);
return 5000;
}
bool handleCallLogInput(char c) {
switch (c) {
case 'w': case 'W':
if (_callLogCursor > 0) _callLogCursor--;
return true;
case 's': case 'S':
if (_callLogCursor < _callLogCount - 1) _callLogCursor++;
return true;
case '\r': // Enter - dial selected entry
if (_callLogCount > 0 && _callLogCursor < _callLogCount
&& _callLog[_callLogCursor].phone[0]) {
startCall(_callLog[_callLogCursor].phone);
}
return true;
case 'd': case 'D': // Delete selected entry
if (_callLogCount > 0 && _callLogCursor < _callLogCount && _sdReady) {
smsStore.deleteCallLogEntry(_callLogCursor);
refreshCallLog();
if (_callLogCursor >= _callLogCount && _callLogCursor > 0) _callLogCursor--;
}
return true;
case 'q':
case KEY_CANCEL: // Back to app menu
_view = APP_MENU;
_menuCursor = 2;
return true;
default:
return false;
}
}
int renderInbox(DisplayDriver& display) {
ModemState ms = modemManager.getState();
@@ -1122,6 +1329,7 @@ public:
bool handleInput(char c) override {
switch (_view) {
case APP_MENU: return handleAppMenuInput(c);
case CALL_LOG: return handleCallLogInput(c);
case INBOX: return handleInboxInput(c);
case CONVERSATION: return handleConversationInput(c);
case COMPOSE: return handleComposeInput(c);
@@ -1139,11 +1347,11 @@ public:
bool handleAppMenuInput(char c) {
switch (c) {
case 'w': case 'W':
_menuCursor = 0;
if (_menuCursor > 0) _menuCursor--;
return true;
case 's': case 'S':
_menuCursor = 1;
if (_menuCursor < 2) _menuCursor++;
return true;
case '\r': // Enter - select menu item
@@ -1152,12 +1360,21 @@ public:
_phoneInputBuf[0] = '\0';
_phoneInputPos = 0;
_view = PHONE_DIALER;
} else {
} else if (_menuCursor == 1) {
// SMS Inbox
if (_sdReady) refreshInbox();
_inboxCursor = 0;
_inboxScrollTop = 0;
_view = INBOX;
} else {
// Call Log
if (_sdReady) {
smsStore.markMissedSeen();
refreshCallLog();
}
_callLogCursor = 0;
_callLogScrollTop = 0;
_view = CALL_LOG;
}
return true;
@@ -271,4 +271,149 @@ void SMSStore::migrateExistingAsRead() {
digitalWrite(SDCARD_CS, HIGH);
}
bool SMSStore::appendCallLog(uint8_t type, const char* phone, uint32_t duration, uint32_t timestamp) {
if (!_ready) return false;
CallLogRecord rec;
memset(&rec, 0, sizeof(rec));
rec.timestamp = timestamp;
rec.duration = duration;
rec.type = type;
strncpy(rec.phone, phone, SMS_PHONE_LEN - 1);
File f = SD.open(SMS_CALLLOG_FILE, FILE_APPEND);
if (!f) {
// Try creating
f = SD.open(SMS_CALLLOG_FILE, FILE_WRITE);
if (!f) {
MESH_DEBUG_PRINTLN("[SMSStore] can't open %s", SMS_CALLLOG_FILE);
digitalWrite(SDCARD_CS, HIGH);
return false;
}
}
size_t written = f.write((uint8_t*)&rec, sizeof(rec));
f.close();
// Drop oldest records once the file exceeds the cap
trimCallLog();
// Release SD CS
digitalWrite(SDCARD_CS, HIGH);
return written == sizeof(rec);
}
void SMSStore::trimCallLog() {
File f = SD.open(SMS_CALLLOG_FILE, FILE_READ);
if (!f) return;
int numRecords = f.size() / sizeof(CallLogRecord);
if (numRecords <= SMS_CALLLOG_MAX) { f.close(); return; }
int startIdx = numRecords - SMS_CALLLOG_MAX;
File t = SD.open(SMS_CALLLOG_TMP, FILE_WRITE);
if (!t) { f.close(); return; }
CallLogRecord rec;
for (int i = startIdx; i < numRecords; i++) {
f.seek((size_t)i * sizeof(CallLogRecord));
if (f.read((uint8_t*)&rec, sizeof(CallLogRecord)) != sizeof(CallLogRecord)) continue;
t.write((uint8_t*)&rec, sizeof(CallLogRecord));
}
f.close();
t.close();
SD.remove(SMS_CALLLOG_FILE);
SD.rename(SMS_CALLLOG_TMP, SMS_CALLLOG_FILE);
}
int SMSStore::loadCallLog(CallLogRecord* out, int maxCount) {
if (!_ready) return 0;
File f = SD.open(SMS_CALLLOG_FILE, FILE_READ);
if (!f) return 0;
int numRecords = f.size() / sizeof(CallLogRecord);
// Newest first
CallLogRecord rec;
int outIdx = 0;
for (int i = numRecords - 1; i >= 0 && outIdx < maxCount; i--) {
f.seek((size_t)i * sizeof(CallLogRecord));
if (f.read((uint8_t*)&rec, sizeof(CallLogRecord)) != sizeof(CallLogRecord)) continue;
out[outIdx++] = rec;
}
f.close();
digitalWrite(SDCARD_CS, HIGH);
return outIdx;
}
bool SMSStore::deleteCallLogEntry(int newestFirstIdx) {
if (!_ready) return false;
File f = SD.open(SMS_CALLLOG_FILE, FILE_READ);
if (!f) return false;
int numRecords = f.size() / sizeof(CallLogRecord);
int fileIdx = numRecords - 1 - newestFirstIdx;
if (fileIdx < 0 || fileIdx >= numRecords) {
f.close();
digitalWrite(SDCARD_CS, HIGH);
return false;
}
File t = SD.open(SMS_CALLLOG_TMP, FILE_WRITE);
if (!t) {
f.close();
digitalWrite(SDCARD_CS, HIGH);
return false;
}
CallLogRecord rec;
for (int i = 0; i < numRecords; i++) {
if (i == fileIdx) continue;
f.seek((size_t)i * sizeof(CallLogRecord));
if (f.read((uint8_t*)&rec, sizeof(CallLogRecord)) != sizeof(CallLogRecord)) continue;
t.write((uint8_t*)&rec, sizeof(CallLogRecord));
}
f.close();
t.close();
SD.remove(SMS_CALLLOG_FILE);
bool ok = SD.rename(SMS_CALLLOG_TMP, SMS_CALLLOG_FILE);
digitalWrite(SDCARD_CS, HIGH);
return ok;
}
void SMSStore::markMissedSeen() {
if (!_ready) return;
// In-place flag update, same approach as markFileRead()
File f = SD.open(SMS_CALLLOG_FILE, "r+");
if (!f) return;
int numRecords = f.size() / sizeof(CallLogRecord);
CallLogRecord rec;
for (int i = 0; i < numRecords; i++) {
f.seek((size_t)i * sizeof(CallLogRecord));
if (f.read((uint8_t*)&rec, sizeof(CallLogRecord)) != sizeof(CallLogRecord)) continue;
if (rec.type == CALL_LOG_MISSED && rec.seen == 0) {
rec.seen = 1;
f.seek((size_t)i * sizeof(CallLogRecord));
f.write((uint8_t*)&rec, sizeof(CallLogRecord));
}
}
f.close();
digitalWrite(SDCARD_CS, HIGH);
}
#endif // HAS_4G_MODEM
@@ -24,6 +24,26 @@
#define SMS_DIR "/sms"
#define SMS_READ_MIGRATED "/sms/rdmig.dat" // one-time read-state migration marker
// Call log: fixed-size records appended to a single file, oldest first
#define SMS_CALLLOG_FILE "/sms/calllog.dat"
#define SMS_CALLLOG_TMP "/sms/calllog.tmp"
#define SMS_CALLLOG_MAX 32
// Call log entry types
#define CALL_LOG_MISSED 0
#define CALL_LOG_INCOMING 1
#define CALL_LOG_OUTGOING 2
// On-SD call log record (fixed size for random access)
struct CallLogRecord {
uint32_t timestamp; // epoch seconds when the event was logged
uint32_t duration; // call duration in seconds (0 = never connected)
uint8_t type; // CALL_LOG_MISSED / _INCOMING / _OUTGOING
uint8_t seen; // 1 = missed call has been viewed in the log
uint8_t reserved[2];
char phone[SMS_PHONE_LEN];
};
// Fixed-size on-disk record (256 bytes, easy alignment)
struct SMSRecord {
uint32_t timestamp; // epoch seconds
@@ -78,9 +98,22 @@ public:
// Mark all received messages in a conversation as read (persisted to SD)
void markConversationRead(const char* phone);
// --- Call log (fixed-size records in /sms/calllog.dat) ---
// Append one entry; trims oldest once past SMS_CALLLOG_MAX
bool appendCallLog(uint8_t type, const char* phone, uint32_t duration, uint32_t timestamp);
// Load entries newest-first; returns count loaded
int loadCallLog(CallLogRecord* out, int maxCount);
// Delete one entry by newest-first index (as returned by loadCallLog)
bool deleteCallLogEntry(int newestFirstIdx);
// Mark every missed-call entry as seen (called when the log is opened)
void markMissedSeen();
private:
bool _ready = false;
// Rewrite the call log keeping only the newest SMS_CALLLOG_MAX records
void trimCallLog();
// Convert phone number to safe filename
void phoneToFilename(const char* phone, char* out, size_t outLen);
+38 -9
View File
@@ -23,7 +23,7 @@
#if defined(LilyGo_TDeck_Pro_Max)
#include "DRV2605Haptic.h" // haptic motor for "Buzzer (vibrate)" channels
#endif
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(MECK_AUDIO_VARIANT) || defined(LilyGo_TDeck_Pro_Max)
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(MECK_AUDIO_VARIANT) || defined(LilyGo_TDeck_Pro)
#include "HomeIcons.h"
#endif
#if defined(WIFI_SSID) || defined(MECK_WIFI_COMPANION)
@@ -368,7 +368,7 @@ public:
int render(DisplayDriver& display) override {
char tmp[80];
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro_Max)
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro)
_task->setHomeShowingTiles(false); // Reset — only set true on FIRST page
#endif
@@ -453,7 +453,7 @@ public:
int y = 13; // Below header
#elif defined(LilyGo_T5S3_EPaper_Pro)
int y = 14; // Closer to header
#elif defined(LilyGo_TDeck_Pro_Max)
#elif defined(LilyGo_TDeck_Pro)
int y = 8; // Tighter under header; frees room for the MSG strip above the tile grid
#else
int y = 14;
@@ -468,11 +468,11 @@ public:
}
if (_page == HomePage::FIRST) {
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro_Max)
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro)
_task->setHomeShowingTiles(true);
#endif
#if defined(LilyGo_TDeck_Pro_Max)
// ----- MAX: Touch tile grid home screen (T-Watch / P4 style) -----
#if defined(LilyGo_TDeck_Pro)
// ----- T-Deck Pro: Touch tile grid home screen (T-Watch / P4 style) -----
// Rendered in RAW PHYSICAL pixels (240x320) via GxEPDDisplay raw
// helpers, bypassing the 128x128 virtual scaling so icons stay 1:1
// and the dithered grey borders read as continuous bands.
@@ -561,7 +561,7 @@ public:
}
display.setTextSize(1); // restore driver font state after raw text
}
#else // not LilyGo_TDeck_Pro_Max
#else // not LilyGo_TDeck_Pro
#if defined(LilyGo_T5S3_EPaper_Pro)
#if defined(BLE_PIN_CODE) || defined(WIFI_SSID) || defined(MECK_WIFI_COMPANION)
int y = 18; // Tighter spacing — connectivity info fills gap below dots
@@ -848,7 +848,7 @@ public:
display.setTextSize(1); // restore
#endif // LILYGO_TECHO_LITE
#endif
#endif // not LilyGo_TDeck_Pro_Max
#endif // not LilyGo_TDeck_Pro
} else if (_page == HomePage::RECENT) {
the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE);
display.setColor(DisplayDriver::GREEN);
@@ -1455,6 +1455,31 @@ public:
display.drawTextCentered(display.width() / 2, 108, infoBuf);
}
#ifdef HAS_4G_MODEM
// ---- Unread SMS / missed-call notification ----
// Sourced from the SMS screen's own unread and unseen state, so it
// persists across re-locks and reboots until the conversation or the
// call log is actually opened.
{
SMSScreen* smsScr = _task->getSMSScreen();
int sms = smsScr ? smsScr->getUnreadSmsCount() : 0;
int missed = smsScr ? smsScr->getUnseenMissedCount() : 0;
if (sms > 0 || missed > 0) {
char notifBuf[32];
if (sms > 0 && missed > 0) {
sprintf(notifBuf, "SMS: %d Missed: %d", sms, missed);
} else if (sms > 0) {
sprintf(notifBuf, "SMS: %d", sms);
} else {
sprintf(notifBuf, "Missed: %d", missed);
}
display.setTextSize(1);
display.setColor(DisplayDriver::GREEN);
display.drawTextCentered(display.width() / 2, display.height() - 12, notifBuf);
}
}
#endif
// ---- Unlock hint ----
#if defined(LilyGo_T5S3_EPaper_Pro)
display.setTextSize(_node_prefs->smallTextSize());
@@ -3206,7 +3231,11 @@ void UITask::gotoVoiceScreen() {
#ifdef HAS_4G_MODEM
void UITask::gotoSMSScreen() {
SMSScreen* smsScr = (SMSScreen*)sms_screen;
smsScr->activate();
// activate() resets the view to the app menu, which would wipe the ringing
// or in-call screen that onCallEvent() has just put up. Skip it mid-call.
if (!smsScr->isInCallView()) {
smsScr->activate();
}
setCurrScreen(sms_screen);
if (_display != NULL && !_display->isOn()) {
_display->turnOn();