refactor(contacts): share case-insensitive compare, truncation helpers
The contacts list carried its own lowercase-and-compare sort, its own lowercase substring search, and an inline linear middle-truncation loop for fitting an address to a column width. These duplicated logic that belongs in the shared text helpers. - A new allocation-free case-insensitive compare replaces the per-sort lowercasing; it lowercases ASCII per byte and leaves bytes >= 0x80 alone, so UTF-8 labels sort exactly as before. - Search now uses the shared case-insensitive contains helper. - Middle-truncation to a pixel width moves to a shared template that binary-searches the fit (about log2(n) width measurements instead of up to n) while producing the same result as the old linear shrink. Tests: the compare matches lowercase-then-compare across ASCII, UTF-8 bytes and length ties, and the fit helper matches the old linear loop across lengths and widths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -69,9 +69,9 @@ static std::string toLower(std::string s) {
|
||||
|
||||
static bool matchesSearch(const data::AddressBookEntry& e, const std::string& needleLower) {
|
||||
if (needleLower.empty()) return true;
|
||||
return toLower(e.label).find(needleLower) != std::string::npos
|
||||
|| toLower(e.address).find(needleLower) != std::string::npos
|
||||
|| toLower(e.notes).find(needleLower) != std::string::npos;
|
||||
return util::containsIgnoreCase(e.label, needleLower)
|
||||
|| util::containsIgnoreCase(e.address, needleLower)
|
||||
|| util::containsIgnoreCase(e.notes, needleLower);
|
||||
}
|
||||
|
||||
// Lazily-loaded, path-keyed cache of custom avatar image textures. Each entry is a frame SEQUENCE
|
||||
@@ -285,18 +285,9 @@ static bool isShieldedAddr(const std::string& a) {
|
||||
// trimming is safe.
|
||||
static std::string truncateAddressToWidth(const std::string& s, ImFont* f, float size, float maxW,
|
||||
int minFront, int minBack) {
|
||||
const std::string floor = util::truncateMiddle(s, minFront, minBack);
|
||||
auto w = [&](const std::string& t){ return f->CalcTextSizeA(size, FLT_MAX, 0, t.c_str()).x; };
|
||||
if (w(s) <= maxW) return s; // fits in full — no truncation needed at all
|
||||
if (maxW <= 0.0f) return floor; // no room to measure against — fall back to the floor
|
||||
const std::string ell = "\xE2\x80\xA6";
|
||||
size_t head = s.size() / 2, tail = s.size() - head;
|
||||
while (head + tail > static_cast<size_t>(minFront + minBack)) {
|
||||
std::string cand = s.substr(0, head) + ell + s.substr(s.size() - tail);
|
||||
if (w(cand) <= maxW) return cand;
|
||||
if (head >= tail) --head; else --tail;
|
||||
}
|
||||
return floor; // couldn't fit even at the configured floor — use the fixed-length result
|
||||
return util::truncateMiddleToFit(s, minFront, minBack, maxW, [&](const std::string& t) {
|
||||
return f->CalcTextSizeA(size, FLT_MAX, 0, t.c_str()).x;
|
||||
});
|
||||
}
|
||||
|
||||
// Accent colour for a contact's address type (Z = shielded/green, T = transparent/amber), tuned per
|
||||
@@ -1213,7 +1204,7 @@ void RenderContactsTab(App* app)
|
||||
case 2: ka = &entries[a].notes; kb = &entries[b].notes; break;
|
||||
default: ka = &entries[a].label; kb = &entries[b].label; break;
|
||||
}
|
||||
int cmp = toLower(*ka).compare(toLower(*kb));
|
||||
int cmp = util::compareIgnoreCase(*ka, *kb);
|
||||
return asc ? (cmp < 0) : (cmp > 0);
|
||||
});
|
||||
}
|
||||
@@ -1281,7 +1272,7 @@ void RenderContactsTab(App* app)
|
||||
{
|
||||
const auto& entries = book.entries();
|
||||
std::stable_sort(visibleRows.begin(), visibleRows.end(), [&](size_t a, size_t b) {
|
||||
return toLower(entries[a].label).compare(toLower(entries[b].label)) < 0;
|
||||
return util::compareIgnoreCase(entries[a].label, entries[b].label) < 0;
|
||||
});
|
||||
}
|
||||
// Frosted-glass list container so the contacts area blurs the backdrop (peers_tab pattern:
|
||||
|
||||
Reference in New Issue
Block a user