CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
VcardWizard.cpp
Go to the documentation of this file.
3
4#include "cdc_ui/I18n.h"
5#include "cdc_ui/ViewStack.h"
8#include "cdc_log.h"
9#include "esp_attr.h"
10
11#include <cstdio>
12#include <cstring>
13
14static const char* TAG = "VCARD_WIZ";
15
16namespace cdc::mod_vcard {
17
41
45struct FieldRef {
46 char* (*get)(vcard_data_t* d);
47 uint16_t maxLen;
48};
49
50static char* fGiven(vcard_data_t* d) { return d->given_name; }
51static char* fFamily(vcard_data_t* d) { return d->family_name; }
52static char* fFn(vcard_data_t* d) { return d->formatted_name; }
53static char* fOrg(vcard_data_t* d) { return d->organization; }
54static char* fTitle(vcard_data_t* d) { return d->title; }
55static char* fEmail(vcard_data_t* d) { return d->email; }
56static char* fCell(vcard_data_t* d) { return d->tel_cell; }
57static char* fHome(vcard_data_t* d) { return d->tel_home; }
58static char* fWork(vcard_data_t* d) { return d->tel_work; }
59static char* fUrl(vcard_data_t* d) { return d->url; }
60static char* fTele(vcard_data_t* d) { return d->impp_telegram; }
61static char* fSig(vcard_data_t* d) { return d->impp_signal; }
62static char* fMatrix(vcard_data_t* d) { return d->impp_matrix; }
63static char* fThreema(vcard_data_t* d) { return d->impp_threema; }
64static char* fSocial(vcard_data_t* d) { return d->social_profile; }
65static char* fNote(vcard_data_t* d) { return d->note; }
66
72static const FieldRef k_steps[STEP_COUNT] = {
73 { fGiven, sizeof(vcard_data_t::given_name) - 1 },
74 { fFamily, sizeof(vcard_data_t::family_name) - 1 },
75 { fFn, sizeof(vcard_data_t::formatted_name) - 1 },
76 { fOrg, sizeof(vcard_data_t::organization) - 1 },
77 { fTitle, sizeof(vcard_data_t::title) - 1 },
78 { fEmail, sizeof(vcard_data_t::email) - 1 },
79 { fCell, sizeof(vcard_data_t::tel_cell) - 1 },
80 { fHome, sizeof(vcard_data_t::tel_home) - 1 },
81 { fWork, sizeof(vcard_data_t::tel_work) - 1 },
82 { fUrl, sizeof(vcard_data_t::url) - 1 },
83 { fTele, sizeof(vcard_data_t::impp_telegram) - 1 },
84 { fSig, sizeof(vcard_data_t::impp_signal) - 1 },
85 { fMatrix, sizeof(vcard_data_t::impp_matrix) - 1 },
86 { fThreema, sizeof(vcard_data_t::impp_threema) - 1 },
88 { fNote, sizeof(vcard_data_t::note) - 1 },
89};
90
94enum class WizardTarget : uint8_t {
95 OWN = 0,
98};
99
112
113EXT_RAM_BSS_ATTR static WizardState s_wizard = {};
115
117static const uint16_t* s_titleOffsets = nullptr;
118static uint16_t s_savedOffset = 0;
119static uint16_t s_failedOffset = 0;
120
124static const char* stepTitle(uint8_t step) {
125 if (!s_resolver || !s_titleOffsets || step >= STEP_COUNT) return "?";
126 return s_resolver(s_titleOffsets[step]);
127}
128
132static void onStepSave(const char* text);
133
139static void pushCurrentStep() {
140 if (!s_wizard.active || s_wizard.currentStep >= STEP_COUNT) return;
141
142 const FieldRef& ref = k_steps[s_wizard.currentStep];
143 char* field = ref.get(&s_wizard.data);
144
145 // Auto-suggest formatted name from given + family when not yet set.
146 if (s_wizard.currentStep == STEP_FORMATTED && field[0] == '\0' &&
147 (s_wizard.data.given_name[0] || s_wizard.data.family_name[0])) {
148 if (s_wizard.data.given_name[0] && s_wizard.data.family_name[0]) {
149 snprintf(field, sizeof(vcard_data_t::formatted_name), "%s %s",
150 s_wizard.data.given_name, s_wizard.data.family_name);
151 } else if (s_wizard.data.given_name[0]) {
152 snprintf(field, sizeof(vcard_data_t::formatted_name), "%s",
153 s_wizard.data.given_name);
154 } else {
155 snprintf(field, sizeof(vcard_data_t::formatted_name), "%s",
156 s_wizard.data.family_name);
157 }
158 }
159
160 uint16_t maxLen = ref.maxLen;
162
163 s_t9Input.init(stepTitle(s_wizard.currentStep), field, maxLen);
164 s_t9Input.setOnSave(onStepSave);
166}
167
171static void wizardFinish() {
172 char buf[VCARD_MAX_LEN + 1];
173 size_t len = vcard_generate_from_struct(&s_wizard.data, buf, sizeof(buf));
174 if (len == 0) {
176 : ui::tr("core.failed"));
177 s_wizard.active = false;
178 return;
179 }
180
181 char err[64] = {};
182 bool ok = false;
183 switch (s_wizard.target) {
185 ok = vcard_store_set_own(buf, len, err, sizeof(err));
186 break;
188 ok = vcard_store_add(buf, len, err, sizeof(err));
189 break;
191 ok = vcard_store_update(s_wizard.editSlot, buf, len, err, sizeof(err));
192 break;
193 }
194 if (!ok) {
195 LOG_W(TAG, "save failed: %s", err[0] ? err : "(no detail)");
196 ui::showToastError(err[0] ? err
198 : ui::tr("core.failed")));
199 s_wizard.active = false;
200 return;
201 }
202
204 : "Saved");
206 ui::IView* anchor = s_wizard.returnAnchor;
207 s_wizard.active = false;
208 // Refresh the caller's list before navigating back so it reflects the save.
209 if (done) done();
210 if (anchor) {
212 }
213}
214
219static void onStepSave(const char* text) {
220 if (!s_wizard.active || s_wizard.currentStep >= STEP_COUNT) return;
221
222 const FieldRef& ref = k_steps[s_wizard.currentStep];
223 char* field = ref.get(&s_wizard.data);
224 size_t cap = ref.maxLen;
225 if (text) {
226 size_t len = strlen(text);
227 if (len > cap) len = cap;
228 memcpy(field, text, len);
229 field[len] = '\0';
230 } else {
231 field[0] = '\0';
232 }
233
234 s_wizard.currentStep++;
235 if (s_wizard.currentStep >= STEP_COUNT) {
236 wizardFinish();
237 } else {
239 }
240}
241
242void VcardWizard::configure(StringResolver resolver, const uint16_t* titleOffsets,
243 uint16_t savedOffset, uint16_t failedOffset) {
244 s_resolver = resolver;
245 s_titleOffsets = titleOffsets;
246 s_savedOffset = savedOffset;
247 s_failedOffset = failedOffset;
248}
249
250void VcardWizard::start(ui::IView* returnAnchor) {
251 memset(&s_wizard, 0, sizeof(s_wizard));
252 s_wizard.returnAnchor = returnAnchor;
253 s_wizard.active = true;
254 s_wizard.currentStep = 0;
256}
257
258void VcardWizard::edit(ui::IView* returnAnchor) {
259 memset(&s_wizard, 0, sizeof(s_wizard));
260 s_wizard.returnAnchor = returnAnchor;
261 s_wizard.active = true;
262 s_wizard.currentStep = 0;
263
264 char raw[VCARD_MAX_LEN + 1];
265 size_t got = vcard_store_get_own(raw, sizeof(raw));
266 if (got > 0) {
268 }
270}
271
273 memset(&s_wizard, 0, sizeof(s_wizard));
274 s_wizard.returnAnchor = returnAnchor;
275 s_wizard.active = true;
276 s_wizard.currentStep = 0;
278 s_wizard.onDone = onDone;
280}
281
282void VcardWizard::editReceived(ui::IView* returnAnchor, uint16_t slot, DoneCallback onDone) {
283 memset(&s_wizard, 0, sizeof(s_wizard));
284 s_wizard.returnAnchor = returnAnchor;
285 s_wizard.active = true;
286 s_wizard.currentStep = 0;
288 s_wizard.editSlot = slot;
289 s_wizard.onDone = onDone;
290
291 char raw[VCARD_MAX_LEN + 1];
292 if (vcard_store_get(slot, raw, sizeof(raw)) > 0) {
294 }
296}
297
298} // namespace cdc::mod_vcard
static const char * TAG
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
static void start(ui::IView *returnAnchor)
Starts the wizard with an empty struct.
static void editReceived(ui::IView *returnAnchor, uint16_t slot, DoneCallback onDone)
Starts the wizard prefilled with a stored contact for editing.
static void configure(StringResolver resolver, const uint16_t *titleOffsets, uint16_t savedOffset, uint16_t failedOffset)
Configures the wizard with i18n callbacks. Must be called before start() or edit() so step titles can...
static void startReceived(ui::IView *returnAnchor, DoneCallback onDone)
Starts the wizard to create a new stored contact (received list).
void(*)() DoneCallback
Callback fired after a successful save, before returning to the anchor view. Lets the caller refresh ...
Definition VcardWizard.h:40
static void edit(ui::IView *returnAnchor)
Starts the wizard prefilled with the currently stored own vCard. Falls back to start() when no vCard ...
const char *(*)(uint16_t offset) StringResolver
Resolves a localized step title.
Definition VcardWizard.h:23
static constexpr uint16_t MAX_TEXT_LEN
Definition T9InputView.h:22
static ViewStack & instance()
Returns singleton view-stack instance.
Definition ViewStack.cpp:34
void popToAnchor(IView *anchor)
Pops views until the specified anchor view is the current view.
void push(IView *view, void *context=nullptr)
static char * fCell(vcard_data_t *d)
static WizardState s_wizard
static char * fOrg(vcard_data_t *d)
static void onStepSave(const char *text)
Forward declaration of the single per-step save handler.
static VcardWizard::StringResolver s_resolver
WizardTarget
Save destination for the wizard's generated vCard.
@ RECEIVED_NEW
Add a new stored contact.
@ RECEIVED_EDIT
Overwrite the stored contact at editSlot.
@ OWN
Persist via vcard_store_set_own().
static char * fTele(vcard_data_t *d)
static void wizardFinish()
Persists wizard data and returns to the anchor view on success.
static char * fMatrix(vcard_data_t *d)
static char * fTitle(vcard_data_t *d)
static char * fGiven(vcard_data_t *d)
WizardStepId
Step indices into the wizard's title offset table. Order must match s_steps[] below.
static char * fWork(vcard_data_t *d)
static char * fSocial(vcard_data_t *d)
static uint16_t s_savedOffset
static char * fFn(vcard_data_t *d)
static char * fUrl(vcard_data_t *d)
static ui::T9InputView s_t9Input
static void pushCurrentStep()
Pushes the T9 input view for the current wizard step. Prefills the input with the current field value...
static char * fHome(vcard_data_t *d)
static uint16_t s_failedOffset
static const char * stepTitle(uint8_t step)
Returns the localized title for a wizard step via the configured resolver.
static const uint16_t * s_titleOffsets
static char * fEmail(vcard_data_t *d)
static char * fNote(vcard_data_t *d)
static char * fSig(vcard_data_t *d)
static char * fFamily(vcard_data_t *d)
static const FieldRef k_steps[STEP_COUNT]
Step descriptor table. Step order is fixed at compile time. The maximum input length for each step is...
static char * fThreema(vcard_data_t *d)
const char * tr(const char *key)
Look up a translation by string key.
Definition I18n.h:208
void showToastSuccess(const char *message, uint16_t durationMs=1500)
Shows a success toast message.
void showToastError(const char *message, uint16_t durationMs=1500)
Shows an error toast message.
Maps a step ID to the matching field inside vcard_data_t.
char *(* get)(vcard_data_t *d)
Holds the wizard's running state between callback firings.
VcardWizard::DoneCallback onDone
Structured representation of an own vCard for editor/wizard use.
Definition vcard_store.h:15
char tel_cell[32]
Definition vcard_store.h:23
char family_name[48]
Definition vcard_store.h:17
char tel_work[32]
Definition vcard_store.h:24
char formatted_name[96]
Definition vcard_store.h:18
char title[64]
Definition vcard_store.h:20
char social_profile[128]
Definition vcard_store.h:30
char tel_home[32]
Definition vcard_store.h:22
char note[128]
Definition vcard_store.h:31
char email[96]
Definition vcard_store.h:21
char organization[64]
Definition vcard_store.h:19
char given_name[48]
Definition vcard_store.h:16
char url[128]
Definition vcard_store.h:25
char impp_signal[64]
Definition vcard_store.h:27
char impp_threema[32]
Definition vcard_store.h:29
char impp_telegram[64]
Definition vcard_store.h:26
char impp_matrix[96]
Definition vcard_store.h:28
size_t vcard_store_get_own(char *out, size_t max_len)
Retrieves local own-vCard text.
bool vcard_store_set_own(const char *vcard, size_t len, char *err, size_t err_len)
Stores local own-vCard after validation and field filtering.
bool vcard_store_update(uint16_t slot, const char *vcard, size_t len, char *err, size_t err_len)
Overwrites the vCard stored at slot in place after validation.
size_t vcard_store_get(uint16_t slot, char *out, size_t max_len)
Retrieves raw vCard text from slot.
bool vcard_parse_to_struct(const char *raw, vcard_data_t *out)
Parses vCard 4.0 raw text into a structured vcard_data_t.
bool vcard_store_add(const char *vcard, size_t len, char *err, size_t err_len)
Adds peer vCard to first free slot after validation and duplicate check.
#define VCARD_MAX_LEN
Definition vcard_store.h:6
size_t vcard_generate_from_struct(const vcard_data_t *data, char *out_buf, size_t buf_len)
Generates vCard 4.0 text from a structured vcard_data_t. Empty fields are omitted....