totp_app_settings.c 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. #include "totp_app_settings.h"
  2. #include <math.h>
  3. #include <totp_icons.h>
  4. #include "../../../services/fonts/font_provider.h"
  5. #include "../../canvas_extensions.h"
  6. #include "../../ui_controls.h"
  7. #include "../../common_dialogs.h"
  8. #include "../../scene_director.h"
  9. #include "../../constants.h"
  10. #include "../../../services/config/config.h"
  11. #include "../../../services/convert/convert.h"
  12. #include "../../../services/kb_layouts/kb_layout_provider.h"
  13. #include <roll_value.h>
  14. #include "../../../config/app/config.h"
  15. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  16. #include "../../../workers/bt_type_code/bt_type_code.h"
  17. #endif
  18. #include <assets_icons.h>
  19. #define FONT_TEST_STR_LENGTH (7)
  20. static const char* YES_NO_LIST[] = {"NO", "YES"};
  21. static const char* AUTOMATION_LIST[] = {
  22. "None",
  23. "USB"
  24. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  25. ,
  26. "Bluetooth",
  27. "BT and USB"
  28. #endif
  29. };
  30. static const char* FONT_TEST_STR = "0123BCD";
  31. typedef enum {
  32. HoursInput,
  33. MinutesInput,
  34. FontSelect,
  35. SoundSwitch,
  36. VibroSwitch,
  37. AutomationSwitch,
  38. BadKeyboardLayoutSelect,
  39. AutomationDelaySelect,
  40. BadBtProfileSelect,
  41. ConfirmButton
  42. } Control;
  43. typedef struct {
  44. int8_t tz_offset_hours;
  45. uint8_t tz_offset_minutes;
  46. bool notification_sound;
  47. bool notification_vibro;
  48. AutomationMethod automation_method;
  49. uint16_t y_offset;
  50. AutomationKeyboardLayout automation_kb_layout;
  51. uint8_t automation_kb_layout_count;
  52. char automation_kb_layout_name[TOTP_KB_LAYOUT_NAME_MAX_LENGTH + 1];
  53. Control selected_control;
  54. uint8_t active_font_index;
  55. FontInfo* active_font;
  56. uint8_t total_fonts_count;
  57. uint16_t automation_initial_delay;
  58. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  59. uint8_t badbt_profile_index;
  60. #endif
  61. char automation_initial_delay_formatted[10];
  62. } SceneState;
  63. static void two_digit_to_str(int8_t num, char* str) {
  64. char* s = str;
  65. if(num < 0) {
  66. *(s++) = '-';
  67. num = -num;
  68. }
  69. uint8_t d1 = (num / 10) % 10;
  70. uint8_t d2 = num % 10;
  71. *(s++) = CONVERT_DIGIT_TO_CHAR(d1);
  72. *(s++) = CONVERT_DIGIT_TO_CHAR(d2);
  73. *(s++) = '\0';
  74. }
  75. static void update_formatted_automation_initial_delay(SceneState* scene_state) {
  76. snprintf(
  77. &scene_state->automation_initial_delay_formatted[0],
  78. sizeof(scene_state->automation_initial_delay_formatted),
  79. "%.1f sec.",
  80. (double)(scene_state->automation_initial_delay / 1000.0f));
  81. }
  82. static void update_formatted_automation_kb_layout_name(SceneState* scene_state) {
  83. totp_kb_layout_provider_get_layout_name(
  84. scene_state->automation_kb_layout,
  85. &scene_state->automation_kb_layout_name[0],
  86. sizeof(scene_state->automation_kb_layout_name));
  87. }
  88. void totp_scene_app_settings_activate(PluginState* plugin_state) {
  89. SceneState* scene_state = malloc(sizeof(SceneState));
  90. furi_check(scene_state != NULL);
  91. plugin_state->current_scene_state = scene_state;
  92. float off_int;
  93. float off_dec = modff(plugin_state->timezone_offset, &off_int);
  94. scene_state->tz_offset_hours = off_int;
  95. scene_state->tz_offset_minutes = 60.0f * off_dec;
  96. scene_state->notification_sound = plugin_state->notification_method & NotificationMethodSound;
  97. scene_state->notification_vibro = plugin_state->notification_method & NotificationMethodVibro;
  98. scene_state->automation_method =
  99. MIN(plugin_state->automation_method, COUNT_OF(AUTOMATION_LIST) - 1);
  100. scene_state->automation_kb_layout_count = totp_kb_layout_provider_get_layouts_count();
  101. scene_state->automation_kb_layout =
  102. MIN(plugin_state->automation_kb_layout, scene_state->automation_kb_layout_count - 1);
  103. scene_state->automation_initial_delay = plugin_state->automation_initial_delay;
  104. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  105. scene_state->badbt_profile_index = plugin_state->bt_type_code_worker_profile_index;
  106. #endif
  107. scene_state->total_fonts_count = totp_font_provider_get_fonts_count();
  108. scene_state->active_font_index = plugin_state->active_font_index;
  109. scene_state->active_font = totp_font_info_alloc();
  110. if(!totp_font_provider_get_font(scene_state->active_font_index, scene_state->active_font)) {
  111. scene_state->active_font_index = 0;
  112. totp_font_provider_get_font(scene_state->active_font_index, scene_state->active_font);
  113. }
  114. update_formatted_automation_initial_delay(scene_state);
  115. update_formatted_automation_kb_layout_name(scene_state);
  116. }
  117. void totp_scene_app_settings_render(Canvas* const canvas, const PluginState* plugin_state) {
  118. const SceneState* scene_state = plugin_state->current_scene_state;
  119. if(scene_state->selected_control < FontSelect) {
  120. canvas_set_font(canvas, FontPrimary);
  121. canvas_draw_str_aligned(
  122. canvas, 0, 0 - scene_state->y_offset, AlignLeft, AlignTop, "Timezone offset");
  123. canvas_set_font(canvas, FontSecondary);
  124. char tmp_str[4];
  125. two_digit_to_str(scene_state->tz_offset_hours, &tmp_str[0]);
  126. canvas_draw_str_aligned(
  127. canvas, 0, 17 - scene_state->y_offset, AlignLeft, AlignTop, "Hours:");
  128. ui_control_select_render(
  129. canvas,
  130. 36,
  131. 10 - scene_state->y_offset,
  132. SCREEN_WIDTH - 36 - UI_CONTROL_VSCROLL_WIDTH,
  133. &tmp_str[0],
  134. scene_state->selected_control == HoursInput);
  135. two_digit_to_str(scene_state->tz_offset_minutes, &tmp_str[0]);
  136. canvas_draw_str_aligned(
  137. canvas, 0, 35 - scene_state->y_offset, AlignLeft, AlignTop, "Minutes:");
  138. ui_control_select_render(
  139. canvas,
  140. 36,
  141. 28 - scene_state->y_offset,
  142. SCREEN_WIDTH - 36 - UI_CONTROL_VSCROLL_WIDTH,
  143. &tmp_str[0],
  144. scene_state->selected_control == MinutesInput);
  145. } else if(scene_state->selected_control < SoundSwitch) {
  146. canvas_set_font(canvas, FontPrimary);
  147. canvas_draw_str_aligned(
  148. canvas, 0, 64 - scene_state->y_offset, AlignLeft, AlignTop, "Font");
  149. canvas_set_font(canvas, FontSecondary);
  150. const FontInfo* const font = scene_state->active_font;
  151. ui_control_select_render(
  152. canvas,
  153. 0,
  154. 74 - scene_state->y_offset,
  155. SCREEN_WIDTH - UI_CONTROL_VSCROLL_WIDTH,
  156. font->name,
  157. scene_state->selected_control == FontSelect);
  158. uint8_t font_x_offset =
  159. SCREEN_WIDTH_CENTER -
  160. (((font->char_info[0].width + font->space_width) * FONT_TEST_STR_LENGTH) >> 1);
  161. uint8_t font_y_offset = 108 - scene_state->y_offset - (font->height >> 1);
  162. canvas_draw_str_ex(
  163. canvas, font_x_offset, font_y_offset, FONT_TEST_STR, FONT_TEST_STR_LENGTH, font);
  164. } else if(scene_state->selected_control < AutomationSwitch) {
  165. canvas_set_font(canvas, FontPrimary);
  166. canvas_draw_str_aligned(
  167. canvas, 0, 128 - scene_state->y_offset, AlignLeft, AlignTop, "Notifications");
  168. canvas_set_font(canvas, FontSecondary);
  169. canvas_draw_str_aligned(
  170. canvas, 0, 145 - scene_state->y_offset, AlignLeft, AlignTop, "Sound:");
  171. ui_control_select_render(
  172. canvas,
  173. 36,
  174. 138 - scene_state->y_offset,
  175. SCREEN_WIDTH - 36 - UI_CONTROL_VSCROLL_WIDTH,
  176. YES_NO_LIST[scene_state->notification_sound],
  177. scene_state->selected_control == SoundSwitch);
  178. canvas_draw_str_aligned(
  179. canvas, 0, 163 - scene_state->y_offset, AlignLeft, AlignTop, "Vibro:");
  180. ui_control_select_render(
  181. canvas,
  182. 36,
  183. 156 - scene_state->y_offset,
  184. SCREEN_WIDTH - 36 - UI_CONTROL_VSCROLL_WIDTH,
  185. YES_NO_LIST[scene_state->notification_vibro],
  186. scene_state->selected_control == VibroSwitch);
  187. } else {
  188. canvas_set_font(canvas, FontPrimary);
  189. canvas_draw_str_aligned(
  190. canvas, 0, 192 - scene_state->y_offset, AlignLeft, AlignTop, "Automation");
  191. canvas_set_font(canvas, FontSecondary);
  192. int group_offset = 0;
  193. if(scene_state->selected_control <= BadKeyboardLayoutSelect) {
  194. canvas_draw_str_aligned(
  195. canvas,
  196. 0,
  197. 209 - scene_state->y_offset - group_offset,
  198. AlignLeft,
  199. AlignTop,
  200. "Method:");
  201. ui_control_select_render(
  202. canvas,
  203. 36,
  204. 202 - scene_state->y_offset - group_offset,
  205. SCREEN_WIDTH - 36 - UI_CONTROL_VSCROLL_WIDTH,
  206. AUTOMATION_LIST[scene_state->automation_method],
  207. scene_state->selected_control == AutomationSwitch);
  208. } else {
  209. group_offset += 18;
  210. }
  211. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  212. if(scene_state->selected_control <= AutomationDelaySelect ||
  213. (scene_state->automation_method & AutomationMethodBadBt) == 0) {
  214. #endif
  215. canvas_draw_str_aligned(
  216. canvas,
  217. 0,
  218. 227 - scene_state->y_offset - group_offset,
  219. AlignLeft,
  220. AlignTop,
  221. "Layout:");
  222. ui_control_select_render(
  223. canvas,
  224. 36,
  225. 220 - scene_state->y_offset - group_offset,
  226. SCREEN_WIDTH - 36 - UI_CONTROL_VSCROLL_WIDTH,
  227. scene_state->automation_kb_layout_name,
  228. scene_state->selected_control == BadKeyboardLayoutSelect);
  229. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  230. } else if((scene_state->automation_method & AutomationMethodBadBt) != 0) {
  231. group_offset += 18;
  232. }
  233. #endif
  234. canvas_draw_str_aligned(
  235. canvas, 0, 245 - scene_state->y_offset - group_offset, AlignLeft, AlignTop, "Delay:");
  236. ui_control_select_render(
  237. canvas,
  238. 36,
  239. 238 - scene_state->y_offset - group_offset,
  240. SCREEN_WIDTH - 36 - UI_CONTROL_VSCROLL_WIDTH,
  241. &scene_state->automation_initial_delay_formatted[0],
  242. scene_state->selected_control == AutomationDelaySelect);
  243. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  244. if((scene_state->automation_method & AutomationMethodBadBt) != 0) {
  245. canvas_draw_str_aligned(
  246. canvas,
  247. 0,
  248. 263 - scene_state->y_offset - group_offset,
  249. AlignLeft,
  250. AlignTop,
  251. "BT Profile:");
  252. char profile_index_formatted[4];
  253. snprintf(profile_index_formatted, 4, "%d", scene_state->badbt_profile_index);
  254. ui_control_select_render(
  255. canvas,
  256. 42,
  257. 256 - scene_state->y_offset - group_offset,
  258. SCREEN_WIDTH - 42 - UI_CONTROL_VSCROLL_WIDTH,
  259. &profile_index_formatted[0],
  260. scene_state->selected_control == BadBtProfileSelect);
  261. }
  262. #endif
  263. ui_control_button_render(
  264. canvas,
  265. SCREEN_WIDTH_CENTER - 24,
  266. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  267. ((scene_state->automation_method & AutomationMethodBadBt) == 0 ? 260 : 278)
  268. #else
  269. 260
  270. #endif
  271. - scene_state->y_offset - group_offset,
  272. 48,
  273. 13,
  274. "Confirm",
  275. scene_state->selected_control == ConfirmButton);
  276. }
  277. ui_control_vscroll_render(
  278. canvas, SCREEN_WIDTH - 3, 0, SCREEN_HEIGHT, scene_state->selected_control, ConfirmButton);
  279. }
  280. bool totp_scene_app_settings_handle_event(
  281. const PluginEvent* const event,
  282. PluginState* plugin_state) {
  283. if(event->type != EventTypeKey) {
  284. return true;
  285. }
  286. SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
  287. if(event->input.type == InputTypePress || event->input.type == InputTypeRepeat) {
  288. switch(event->input.key) {
  289. case InputKeyUp:
  290. totp_roll_value_uint8_t(
  291. &scene_state->selected_control,
  292. -1,
  293. HoursInput,
  294. ConfirmButton,
  295. RollOverflowBehaviorStop);
  296. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  297. if((scene_state->automation_method & AutomationMethodBadBt) == 0 &&
  298. scene_state->selected_control == BadBtProfileSelect) {
  299. scene_state->selected_control--;
  300. }
  301. #endif
  302. if(scene_state->selected_control > VibroSwitch) {
  303. scene_state->y_offset = SCREEN_HEIGHT * 3;
  304. } else if(scene_state->selected_control > FontSelect) {
  305. scene_state->y_offset = SCREEN_HEIGHT * 2;
  306. } else if(scene_state->selected_control > MinutesInput) {
  307. scene_state->y_offset = SCREEN_HEIGHT;
  308. } else {
  309. scene_state->y_offset = 0;
  310. }
  311. break;
  312. case InputKeyDown:
  313. totp_roll_value_uint8_t(
  314. &scene_state->selected_control,
  315. 1,
  316. HoursInput,
  317. ConfirmButton,
  318. RollOverflowBehaviorStop);
  319. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  320. if((scene_state->automation_method & AutomationMethodBadBt) == 0 &&
  321. scene_state->selected_control == BadBtProfileSelect) {
  322. scene_state->selected_control++;
  323. }
  324. #endif
  325. if(scene_state->selected_control > VibroSwitch) {
  326. scene_state->y_offset = SCREEN_HEIGHT * 3;
  327. } else if(scene_state->selected_control > FontSelect) {
  328. scene_state->y_offset = SCREEN_HEIGHT * 2;
  329. } else if(scene_state->selected_control > MinutesInput) {
  330. scene_state->y_offset = SCREEN_HEIGHT;
  331. } else {
  332. scene_state->y_offset = 0;
  333. }
  334. break;
  335. case InputKeyRight:
  336. if(scene_state->selected_control == HoursInput) {
  337. totp_roll_value_int8_t(
  338. &scene_state->tz_offset_hours, 1, -12, 12, RollOverflowBehaviorStop);
  339. } else if(scene_state->selected_control == MinutesInput) {
  340. totp_roll_value_uint8_t(
  341. &scene_state->tz_offset_minutes, 15, 0, 45, RollOverflowBehaviorRoll);
  342. } else if(scene_state->selected_control == FontSelect) {
  343. totp_roll_value_uint8_t(
  344. &scene_state->active_font_index,
  345. 1,
  346. 0,
  347. scene_state->total_fonts_count - 1,
  348. RollOverflowBehaviorRoll);
  349. totp_font_provider_get_font(
  350. scene_state->active_font_index, scene_state->active_font);
  351. } else if(scene_state->selected_control == SoundSwitch) {
  352. scene_state->notification_sound = !scene_state->notification_sound;
  353. } else if(scene_state->selected_control == VibroSwitch) {
  354. scene_state->notification_vibro = !scene_state->notification_vibro;
  355. } else if(scene_state->selected_control == AutomationSwitch) {
  356. totp_roll_value_uint8_t(
  357. &scene_state->automation_method,
  358. 1,
  359. 0,
  360. COUNT_OF(AUTOMATION_LIST) - 1,
  361. RollOverflowBehaviorRoll);
  362. } else if(scene_state->selected_control == BadKeyboardLayoutSelect) {
  363. totp_roll_value_uint8_t(
  364. &scene_state->automation_kb_layout,
  365. 1,
  366. 0,
  367. scene_state->automation_kb_layout_count - 1,
  368. RollOverflowBehaviorRoll);
  369. update_formatted_automation_kb_layout_name(scene_state);
  370. } else if(scene_state->selected_control == AutomationDelaySelect) {
  371. totp_roll_value_uint16_t(
  372. &scene_state->automation_initial_delay,
  373. 500,
  374. 0,
  375. 60000,
  376. RollOverflowBehaviorStop);
  377. update_formatted_automation_initial_delay(scene_state);
  378. }
  379. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  380. else if(scene_state->selected_control == BadBtProfileSelect) {
  381. totp_roll_value_uint8_t(
  382. &scene_state->badbt_profile_index, 1, 0, UINT8_MAX, RollOverflowBehaviorRoll);
  383. }
  384. #endif
  385. break;
  386. case InputKeyLeft:
  387. if(scene_state->selected_control == HoursInput) {
  388. totp_roll_value_int8_t(
  389. &scene_state->tz_offset_hours, -1, -12, 12, RollOverflowBehaviorStop);
  390. } else if(scene_state->selected_control == MinutesInput) {
  391. totp_roll_value_uint8_t(
  392. &scene_state->tz_offset_minutes, -15, 0, 45, RollOverflowBehaviorRoll);
  393. } else if(scene_state->selected_control == FontSelect) {
  394. totp_roll_value_uint8_t(
  395. &scene_state->active_font_index,
  396. -1,
  397. 0,
  398. scene_state->total_fonts_count - 1,
  399. RollOverflowBehaviorRoll);
  400. totp_font_provider_get_font(
  401. scene_state->active_font_index, scene_state->active_font);
  402. } else if(scene_state->selected_control == SoundSwitch) {
  403. scene_state->notification_sound = !scene_state->notification_sound;
  404. } else if(scene_state->selected_control == VibroSwitch) {
  405. scene_state->notification_vibro = !scene_state->notification_vibro;
  406. } else if(scene_state->selected_control == AutomationSwitch) {
  407. totp_roll_value_uint8_t(
  408. &scene_state->automation_method,
  409. -1,
  410. 0,
  411. COUNT_OF(AUTOMATION_LIST) - 1,
  412. RollOverflowBehaviorRoll);
  413. } else if(scene_state->selected_control == BadKeyboardLayoutSelect) {
  414. totp_roll_value_uint8_t(
  415. &scene_state->automation_kb_layout,
  416. -1,
  417. 0,
  418. scene_state->automation_kb_layout_count - 1,
  419. RollOverflowBehaviorRoll);
  420. update_formatted_automation_kb_layout_name(scene_state);
  421. } else if(scene_state->selected_control == AutomationDelaySelect) {
  422. totp_roll_value_uint16_t(
  423. &scene_state->automation_initial_delay,
  424. -500,
  425. 0,
  426. 60000,
  427. RollOverflowBehaviorStop);
  428. update_formatted_automation_initial_delay(scene_state);
  429. }
  430. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  431. else if(scene_state->selected_control == BadBtProfileSelect) {
  432. totp_roll_value_uint8_t(
  433. &scene_state->badbt_profile_index, -1, 0, UINT8_MAX, RollOverflowBehaviorRoll);
  434. }
  435. #endif
  436. break;
  437. case InputKeyOk:
  438. if(scene_state->selected_control == ConfirmButton) {
  439. plugin_state->timezone_offset = (float)scene_state->tz_offset_hours +
  440. (float)scene_state->tz_offset_minutes / 60.0f;
  441. plugin_state->notification_method =
  442. (scene_state->notification_sound ? NotificationMethodSound :
  443. NotificationMethodNone) |
  444. (scene_state->notification_vibro ? NotificationMethodVibro :
  445. NotificationMethodNone);
  446. plugin_state->automation_method = scene_state->automation_method;
  447. plugin_state->active_font_index = scene_state->active_font_index;
  448. plugin_state->automation_kb_layout = scene_state->automation_kb_layout;
  449. plugin_state->automation_initial_delay = scene_state->automation_initial_delay;
  450. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  451. if(((scene_state->automation_method & AutomationMethodBadBt) == 0 ||
  452. plugin_state->bt_type_code_worker_profile_index !=
  453. scene_state->badbt_profile_index) &&
  454. plugin_state->bt_type_code_worker_context != NULL) {
  455. totp_bt_type_code_worker_free(plugin_state->bt_type_code_worker_context);
  456. plugin_state->bt_type_code_worker_context = NULL;
  457. }
  458. plugin_state->bt_type_code_worker_profile_index = scene_state->badbt_profile_index;
  459. #endif
  460. if(!totp_config_file_update_user_settings(plugin_state)) {
  461. totp_dialogs_config_updating_error(plugin_state);
  462. return false;
  463. }
  464. totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu);
  465. }
  466. break;
  467. case InputKeyBack: {
  468. totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu);
  469. break;
  470. }
  471. default:
  472. break;
  473. }
  474. }
  475. return true;
  476. }
  477. void totp_scene_app_settings_deactivate(PluginState* plugin_state) {
  478. if(plugin_state->current_scene_state == NULL) return;
  479. SceneState* scene_state = plugin_state->current_scene_state;
  480. totp_font_info_free(scene_state->active_font);
  481. free(scene_state);
  482. plugin_state->current_scene_state = NULL;
  483. }