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
116static const char* const* s_titleKeys = nullptr;
117static const char* s_savedKey = nullptr;
118static const char* s_failedKey = nullptr;
119
123static const char* stepTitle(uint8_t step) {
124 if (!s_titleKeys || step >= STEP_COUNT) return "?";
125 return ui::tr(s_titleKeys[step]);
126}
127
131static void onStepSave(const char* text);
132
138static void pushCurrentStep() {
139 if (!s_wizard.active || s_wizard.currentStep >= STEP_COUNT) return;
140
141 const FieldRef& ref = k_steps[s_wizard.currentStep];
142 char* field = ref.get(&s_wizard.data);
143
144 // Auto-suggest formatted name from given + family when not yet set.
145 if (s_wizard.currentStep == STEP_FORMATTED && field[0] == '\0' &&
146 (s_wizard.data.given_name[0] || s_wizard.data.family_name[0])) {
147 if (s_wizard.data.given_name[0] && s_wizard.data.family_name[0]) {
148 snprintf(field, sizeof(vcard_data_t::formatted_name), "%s %s",
149 s_wizard.data.given_name, s_wizard.data.family_name);
150 } else if (s_wizard.data.given_name[0]) {
151 snprintf(field, sizeof(vcard_data_t::formatted_name), "%s",
152 s_wizard.data.given_name);
153 } else {
154 snprintf(field, sizeof(vcard_data_t::formatted_name), "%s",
155 s_wizard.data.family_name);
156 }
157 }
158
159 uint16_t maxLen = ref.maxLen;
161
162 s_t9Input.init(stepTitle(s_wizard.currentStep), field, maxLen);
163 s_t9Input.setOnSave(onStepSave);
165}
166
170static void wizardFinish() {
171 char buf[VCARD_MAX_LEN + 1];
172 size_t len = vcard_generate_from_struct(&s_wizard.data, buf, sizeof(buf));
173 if (len == 0) {
175 s_wizard.active = false;
176 return;
177 }
178
179 char err[64] = {};
180 bool ok = false;
181 switch (s_wizard.target) {
183 ok = vcard_store_set_own(buf, len, err, sizeof(err));
184 break;
186 ok = vcard_store_add(buf, len, err, sizeof(err));
187 break;
189 ok = vcard_store_update(s_wizard.editSlot, buf, len, err, sizeof(err));
190 break;
191 }
192 if (!ok) {
193 LOG_W(TAG, "save failed: %s", err[0] ? err : "(no detail)");
194 ui::showToastError(err[0] ? err
195 : ui::tr(s_failedKey ? s_failedKey : "core.failed"));
196 s_wizard.active = false;
197 return;
198 }
199
202 ui::IView* anchor = s_wizard.returnAnchor;
203 s_wizard.active = false;
204 // Refresh the caller's list before navigating back so it reflects the save.
205 if (done) done();
206 if (anchor) {
208 }
209}
210
215static void onStepSave(const char* text) {
216 if (!s_wizard.active || s_wizard.currentStep >= STEP_COUNT) return;
217
218 const FieldRef& ref = k_steps[s_wizard.currentStep];
219 char* field = ref.get(&s_wizard.data);
220 size_t cap = ref.maxLen;
221 if (text) {
222 size_t len = strlen(text);
223 if (len > cap) len = cap;
224 memcpy(field, text, len);
225 field[len] = '\0';
226 } else {
227 field[0] = '\0';
228 }
229
230 s_wizard.currentStep++;
231 if (s_wizard.currentStep >= STEP_COUNT) {
232 wizardFinish();
233 } else {
235 }
236}
237
238void VcardWizard::configure(const char* const* titleKeys, const char* savedKey,
239 const char* failedKey) {
240 s_titleKeys = titleKeys;
241 s_savedKey = savedKey;
242 s_failedKey = failedKey;
243}
244
245void VcardWizard::start(ui::IView* returnAnchor) {
246 memset(&s_wizard, 0, sizeof(s_wizard));
247 s_wizard.returnAnchor = returnAnchor;
248 s_wizard.active = true;
249 s_wizard.currentStep = 0;
251}
252
253void VcardWizard::edit(ui::IView* returnAnchor) {
254 memset(&s_wizard, 0, sizeof(s_wizard));
255 s_wizard.returnAnchor = returnAnchor;
256 s_wizard.active = true;
257 s_wizard.currentStep = 0;
258
259 char raw[VCARD_MAX_LEN + 1];
260 size_t got = vcard_store_get_own(raw, sizeof(raw));
261 if (got > 0) {
263 }
265}
266
268 memset(&s_wizard, 0, sizeof(s_wizard));
269 s_wizard.returnAnchor = returnAnchor;
270 s_wizard.active = true;
271 s_wizard.currentStep = 0;
273 s_wizard.onDone = onDone;
275}
276
277void VcardWizard::editReceived(ui::IView* returnAnchor, uint16_t slot, DoneCallback onDone) {
278 memset(&s_wizard, 0, sizeof(s_wizard));
279 s_wizard.returnAnchor = returnAnchor;
280 s_wizard.active = true;
281 s_wizard.currentStep = 0;
283 s_wizard.editSlot = slot;
284 s_wizard.onDone = onDone;
285
286 char raw[VCARD_MAX_LEN + 1];
287 if (vcard_store_get(slot, raw, sizeof(raw)) > 0) {
289 }
291}
292
293} // 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(const char *const *titleKeys, const char *savedKey, const char *failedKey)
Configures the wizard with i18n keys. Must be called before start() or edit() so step titles can be l...
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:31
static void edit(ui::IView *returnAnchor)
Starts the wizard prefilled with the currently stored own vCard. Falls back to start() when no vCard ...
static constexpr uint16_t MAX_TEXT_LEN
Definition T9InputView.h:22
static ViewStack & instance()
Returns singleton view-stack instance.
Definition ViewStack.cpp:53
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.
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 const char *const * s_titleKeys
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 const char * s_failedKey
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 const char * stepTitle(uint8_t step)
Returns the localized title for a wizard step via its i18n key.
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 const char * s_savedKey
static char * fThreema(vcard_data_t *d)
const char * tr(const char *key)
Look up a translation by string key.
Definition I18n.h:209
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....