suica_drawings.h 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943
  1. #include <toolbox/stream/stream.h>
  2. #include <toolbox/stream/file_stream.h>
  3. #include "metroflip_i.h"
  4. #include <flipper_application.h>
  5. #include "../metroflip/metroflip_api.h"
  6. #include "suica_assets.h"
  7. #include <lib/nfc/protocols/felica/felica.h>
  8. #include <lib/nfc/protocols/felica/felica_poller.h>
  9. #include <lib/nfc/protocols/felica/felica_poller_i.h>
  10. #include <lib/nfc/helpers/felica_crc.h>
  11. #include <lib/bit_lib/bit_lib.h>
  12. #define SUICA_STATION_LIST_PATH APP_ASSETS_PATH("suica/line_")
  13. #define SUICA_IC_TYPE_CODE 0x31
  14. #define SERVICE_CODE_HISTORY_IN_LE (0x090FU)
  15. #define SERVICE_CODE_TAPS_LOG_IN_LE (0x108FU)
  16. #define BLOCK_COUNT 1
  17. #define HISTORY_VIEW_PAGE_NUM 3
  18. #define TERMINAL_NULL 0x02
  19. #define TERMINAL_BUS 0x05
  20. #define TERMINAL_TICKET_VENDING_MACHINE 0x12
  21. #define TERMINAL_TURNSTILE 0x16
  22. #define TERMINAL_MOBILE_PHONE 0x1B
  23. #define TERMINAL_IN_CAR_SUPP_MACHINE 0x24
  24. #define TERMINAL_POS_AND_TAXI 0xC7
  25. #define TERMINAL_VENDING_MACHINE 0xC8
  26. #define PROCESSING_CODE_NEW_ISSUE 0x02
  27. #define ARROW_ANIMATION_FRAME_MS 350
  28. typedef enum {
  29. SuicaTrainRideEntry,
  30. SuicaTrainRideExit,
  31. } SuicaTrainRideType;
  32. typedef enum {
  33. SuicaRedrawScreen,
  34. } SuicaCustomEvent;
  35. static void suica_draw_train_page_1(
  36. Canvas* canvas,
  37. SuicaHistory history,
  38. SuicaHistoryViewModel* model,
  39. bool is_birthday) {
  40. // Entry logo
  41. switch(history.entry_line.type) {
  42. case SuicaKeikyu:
  43. canvas_draw_icon(canvas, 2, 11, &I_Suica_KeikyuLogo);
  44. break;
  45. case SuicaJR:
  46. canvas_draw_icon(canvas, 1, 12, &I_Suica_JRLogo);
  47. break;
  48. case SuicaTokyoMetro:
  49. canvas_draw_icon(canvas, 2, 12, &I_Suica_TokyoMetroLogo);
  50. break;
  51. case SuicaToei:
  52. canvas_draw_icon(canvas, 4, 11, &I_Suica_ToeiLogo);
  53. break;
  54. case SuicaTWR:
  55. canvas_draw_icon(canvas, 0, 12, &I_Suica_TWRLogo);
  56. break;
  57. case SuicaTokyoMonorail:
  58. canvas_draw_icon(canvas, 0, 11, &I_Suica_TokyoMonorailLogo);
  59. break;
  60. case SuicaRailwayTypeMax:
  61. canvas_draw_icon(canvas, 5, 11, &I_Suica_QuestionMarkSmall);
  62. break;
  63. default:
  64. break;
  65. }
  66. // Entry Text
  67. canvas_set_font(canvas, FontPrimary);
  68. canvas_draw_str(canvas, 26, 23, history.entry_line.long_name);
  69. canvas_set_font(canvas, FontSecondary);
  70. canvas_draw_str(canvas, 2, 34, furi_string_get_cstr(history.entry_station.name));
  71. if(!is_birthday) {
  72. // Exit logo
  73. switch(history.exit_line.type) {
  74. case SuicaKeikyu:
  75. canvas_draw_icon(canvas, 2, 39, &I_Suica_KeikyuLogo);
  76. break;
  77. case SuicaJR:
  78. canvas_draw_icon(canvas, 1, 40, &I_Suica_JRLogo);
  79. break;
  80. case SuicaTokyoMetro:
  81. canvas_draw_icon(canvas, 2, 40, &I_Suica_TokyoMetroLogo);
  82. break;
  83. case SuicaToei:
  84. canvas_draw_icon(canvas, 4, 39, &I_Suica_ToeiLogo);
  85. break;
  86. case SuicaTWR:
  87. canvas_draw_icon(canvas, 0, 40, &I_Suica_TWRLogo);
  88. break;
  89. case SuicaTokyoMonorail:
  90. canvas_draw_icon(canvas, 0, 39, &I_Suica_TokyoMonorailLogo);
  91. break;
  92. case SuicaRailwayTypeMax:
  93. canvas_draw_icon(canvas, 5, 39, &I_Suica_QuestionMarkSmall);
  94. break;
  95. default:
  96. break;
  97. }
  98. // Exit Text
  99. canvas_set_font(canvas, FontPrimary);
  100. canvas_draw_str(canvas, 26, 51, history.exit_line.long_name);
  101. canvas_set_font(canvas, FontSecondary);
  102. canvas_draw_str(canvas, 2, 62, furi_string_get_cstr(history.exit_station.name));
  103. } else {
  104. // Birthday
  105. canvas_draw_icon(canvas, 5, 42, &I_Suica_CrackingEgg);
  106. canvas_set_font(canvas, FontPrimary);
  107. canvas_draw_str(canvas, 28, 56, "Suica issued");
  108. }
  109. // Separator
  110. canvas_draw_icon(canvas, 0, 37, &I_Suica_DashLine);
  111. // Arrow
  112. uint8_t arrow_bits[4] = {0b1000, 0b0100, 0b0010, 0b0001};
  113. // Arrow
  114. if(model->animator_tick > 3) {
  115. // 4 steps of animation
  116. model->animator_tick = 0;
  117. }
  118. uint8_t current_arrow_bits = arrow_bits[model->animator_tick];
  119. canvas_draw_icon(
  120. canvas,
  121. 110,
  122. 19,
  123. (current_arrow_bits & 0b1000) ? &I_Suica_FilledArrowDown : &I_Suica_EmptyArrowDown);
  124. canvas_draw_icon(
  125. canvas,
  126. 110,
  127. 29,
  128. (current_arrow_bits & 0b0100) ? &I_Suica_FilledArrowDown : &I_Suica_EmptyArrowDown);
  129. canvas_draw_icon(
  130. canvas,
  131. 110,
  132. 39,
  133. (current_arrow_bits & 0b0010) ? &I_Suica_FilledArrowDown : &I_Suica_EmptyArrowDown);
  134. canvas_draw_icon(
  135. canvas,
  136. 110,
  137. 49,
  138. (current_arrow_bits & 0b0001) ? &I_Suica_FilledArrowDown : &I_Suica_EmptyArrowDown);
  139. }
  140. static void
  141. suica_draw_train_page_2(Canvas* canvas, SuicaHistory history, SuicaHistoryViewModel* model) {
  142. FuriString* buffer = furi_string_alloc();
  143. // Entry
  144. switch(history.entry_line.type) {
  145. case SuicaKeikyu:
  146. canvas_draw_disc(canvas, 24, 38, 24);
  147. canvas_set_color(canvas, ColorWhite);
  148. canvas_draw_disc(canvas, 24, 38, 21);
  149. canvas_set_color(canvas, ColorBlack);
  150. canvas_set_font(canvas, FontKeyboard);
  151. canvas_draw_icon(canvas, 16, 24, history.entry_line.logo_icon);
  152. canvas_set_font(canvas, FontBigNumbers);
  153. furi_string_printf(buffer, "%02d", history.entry_station.station_number);
  154. canvas_draw_str(canvas, 13, 54, furi_string_get_cstr(buffer));
  155. break;
  156. case SuicaTokyoMonorail:
  157. canvas_draw_rbox(canvas, 8, 22, 34, 34, 7);
  158. canvas_set_color(canvas, ColorWhite);
  159. canvas_draw_box(canvas, 12, 26, 26, 26);
  160. canvas_set_color(canvas, ColorBlack);
  161. canvas_set_font(canvas, FontPrimary);
  162. canvas_draw_str_aligned(
  163. canvas, 25, 35, AlignCenter, AlignBottom, history.entry_line.short_name);
  164. canvas_set_font(canvas, FontBigNumbers);
  165. furi_string_printf(buffer, "%02d", history.entry_station.station_number);
  166. canvas_draw_str(canvas, 14, 51, furi_string_get_cstr(buffer));
  167. break;
  168. case SuicaJR:
  169. if(!furi_string_equal_str(history.entry_station.jr_header, "0")) {
  170. canvas_draw_rbox(canvas, 4, 13, 42, 51, 10);
  171. canvas_set_color(canvas, ColorWhite);
  172. canvas_set_font(canvas, FontPrimary);
  173. canvas_draw_str_aligned(
  174. canvas,
  175. 25,
  176. 24,
  177. AlignCenter,
  178. AlignBottom,
  179. furi_string_get_cstr(history.entry_station.jr_header));
  180. canvas_draw_rbox(canvas, 8, 26, 34, 34, 7);
  181. canvas_set_color(canvas, ColorBlack);
  182. canvas_draw_frame(canvas, 12, 30, 26, 26);
  183. canvas_set_font(canvas, FontKeyboard);
  184. canvas_draw_str_aligned(
  185. canvas, 25, 39, AlignCenter, AlignBottom, history.entry_line.short_name);
  186. canvas_set_font(canvas, FontBigNumbers);
  187. furi_string_printf(buffer, "%02d", history.entry_station.station_number);
  188. canvas_draw_str(canvas, 14, 54, furi_string_get_cstr(buffer));
  189. } else {
  190. canvas_draw_rframe(canvas, 8, 22, 34, 34, 7);
  191. canvas_draw_frame(canvas, 12, 26, 26, 26);
  192. canvas_set_font(canvas, FontKeyboard);
  193. canvas_draw_str_aligned(
  194. canvas, 25, 35, AlignCenter, AlignBottom, history.entry_line.short_name);
  195. canvas_set_font(canvas, FontBigNumbers);
  196. furi_string_printf(buffer, "%02d", history.entry_station.station_number);
  197. canvas_draw_str(canvas, 14, 50, furi_string_get_cstr(buffer));
  198. }
  199. break;
  200. case SuicaTokyoMetro:
  201. case SuicaToei:
  202. canvas_draw_disc(canvas, 24, 38, 24);
  203. canvas_set_color(canvas, ColorWhite);
  204. canvas_draw_disc(canvas, 24, 38, 19);
  205. canvas_set_color(canvas, ColorBlack);
  206. canvas_set_font(canvas, FontBigNumbers);
  207. canvas_draw_icon(
  208. canvas,
  209. 17 + history.entry_line.logo_offset[0],
  210. 22 + history.entry_line.logo_offset[1],
  211. history.entry_line.logo_icon);
  212. furi_string_printf(buffer, "%02d", history.entry_station.station_number);
  213. canvas_draw_str(canvas, 13, 53, furi_string_get_cstr(buffer));
  214. break;
  215. case SuicaTWR:
  216. canvas_draw_circle(canvas, 24, 38, 24);
  217. canvas_draw_circle(canvas, 24, 38, 20);
  218. canvas_draw_disc(canvas, 24, 38, 18);
  219. // supplement the circle
  220. canvas_draw_line(canvas, 39, 49, 35, 53);
  221. canvas_draw_line(canvas, 13, 23, 9, 27);
  222. canvas_draw_line(canvas, 39, 27, 35, 23);
  223. canvas_draw_line(canvas, 13, 53, 9, 49);
  224. canvas_set_color(canvas, ColorWhite);
  225. canvas_draw_icon(canvas, 20, 23, history.entry_line.logo_icon);
  226. canvas_set_font(canvas, FontBigNumbers);
  227. furi_string_printf(buffer, "%02d", history.entry_station.station_number);
  228. canvas_draw_str(canvas, 13, 53, furi_string_get_cstr(buffer));
  229. canvas_set_color(canvas, ColorBlack);
  230. break;
  231. case SuicaRailwayTypeMax:
  232. canvas_draw_circle(canvas, 24, 38, 24);
  233. canvas_draw_circle(canvas, 24, 38, 19);
  234. canvas_draw_icon(canvas, 14, 22, &I_Suica_QuestionMarkBig);
  235. break;
  236. default:
  237. break;
  238. }
  239. // Exit
  240. switch(history.exit_line.type) {
  241. case SuicaKeikyu:
  242. canvas_draw_disc(canvas, 103, 38, 24);
  243. canvas_set_color(canvas, ColorWhite);
  244. canvas_draw_disc(canvas, 103, 38, 21);
  245. canvas_set_color(canvas, ColorBlack);
  246. canvas_draw_icon(canvas, 95, 24, history.exit_line.logo_icon);
  247. canvas_set_font(canvas, FontBigNumbers);
  248. furi_string_printf(buffer, "%02d", history.exit_station.station_number);
  249. canvas_draw_str(canvas, 92, 54, furi_string_get_cstr(buffer));
  250. break;
  251. case SuicaTokyoMonorail:
  252. canvas_draw_rbox(canvas, 85, 22, 34, 34, 7);
  253. canvas_set_color(canvas, ColorWhite);
  254. canvas_draw_box(canvas, 89, 26, 26, 26);
  255. canvas_set_color(canvas, ColorBlack);
  256. canvas_set_font(canvas, FontPrimary);
  257. canvas_draw_str_aligned(
  258. canvas, 102, 35, AlignCenter, AlignBottom, history.exit_line.short_name);
  259. canvas_set_font(canvas, FontBigNumbers);
  260. furi_string_printf(buffer, "%02d", history.exit_station.station_number);
  261. canvas_draw_str(canvas, 91, 51, furi_string_get_cstr(buffer));
  262. break;
  263. case SuicaJR:
  264. if(!furi_string_equal_str(history.exit_station.jr_header, "0")) {
  265. canvas_draw_rbox(canvas, 81, 13, 42, 51, 10);
  266. canvas_set_color(canvas, ColorWhite);
  267. canvas_set_font(canvas, FontPrimary);
  268. canvas_draw_str_aligned(
  269. canvas,
  270. 101,
  271. 24,
  272. AlignCenter,
  273. AlignBottom,
  274. furi_string_get_cstr(history.exit_station.jr_header));
  275. canvas_draw_rbox(canvas, 85, 26, 34, 34, 7);
  276. canvas_set_color(canvas, ColorBlack);
  277. canvas_draw_frame(canvas, 89, 30, 26, 26);
  278. canvas_set_font(canvas, FontKeyboard);
  279. canvas_draw_str_aligned(
  280. canvas, 102, 39, AlignCenter, AlignBottom, history.exit_line.short_name);
  281. canvas_set_font(canvas, FontBigNumbers);
  282. furi_string_printf(buffer, "%02d", history.exit_station.station_number);
  283. canvas_draw_str(canvas, 91, 54, furi_string_get_cstr(buffer));
  284. } else {
  285. canvas_draw_rframe(canvas, 85, 22, 34, 34, 7);
  286. canvas_draw_frame(canvas, 89, 26, 26, 26);
  287. canvas_set_font(canvas, FontKeyboard);
  288. canvas_draw_str_aligned(
  289. canvas, 102, 35, AlignCenter, AlignBottom, history.exit_line.short_name);
  290. canvas_set_font(canvas, FontBigNumbers);
  291. furi_string_printf(buffer, "%02d", history.exit_station.station_number);
  292. canvas_draw_str(canvas, 91, 50, furi_string_get_cstr(buffer));
  293. }
  294. break;
  295. case SuicaTokyoMetro:
  296. case SuicaToei:
  297. canvas_draw_disc(canvas, 103, 38, 24);
  298. canvas_set_color(canvas, ColorWhite);
  299. canvas_draw_disc(canvas, 103, 38, 19);
  300. canvas_set_color(canvas, ColorBlack);
  301. canvas_draw_icon(
  302. canvas,
  303. 96 + history.exit_line.logo_offset[0],
  304. 22 + history.exit_line.logo_offset[1],
  305. history.exit_line.logo_icon);
  306. canvas_set_font(canvas, FontBigNumbers);
  307. furi_string_printf(buffer, "%02d", history.exit_station.station_number);
  308. canvas_draw_str(canvas, 92, 53, furi_string_get_cstr(buffer));
  309. break;
  310. case SuicaTWR:
  311. canvas_draw_circle(canvas, 103, 38, 24);
  312. canvas_draw_circle(canvas, 103, 38, 20);
  313. canvas_draw_disc(canvas, 103, 38, 18);
  314. // supplement the circle
  315. canvas_draw_line(canvas, 118, 49, 114, 53);
  316. canvas_draw_line(canvas, 118, 27, 114, 23);
  317. canvas_draw_line(canvas, 92, 23, 88, 27);
  318. canvas_draw_line(canvas, 92, 53, 88, 49);
  319. canvas_set_color(canvas, ColorWhite);
  320. canvas_draw_icon(canvas, 99, 23, history.exit_line.logo_icon);
  321. canvas_set_font(canvas, FontBigNumbers);
  322. furi_string_printf(buffer, "%02d", history.exit_station.station_number);
  323. canvas_draw_str(canvas, 92, 53, furi_string_get_cstr(buffer));
  324. canvas_set_color(canvas, ColorBlack);
  325. break;
  326. case SuicaRailwayTypeMax:
  327. canvas_draw_circle(canvas, 103, 38, 24);
  328. canvas_draw_circle(canvas, 103, 38, 19);
  329. canvas_draw_icon(canvas, 93, 22, &I_Suica_QuestionMarkBig);
  330. default:
  331. break;
  332. }
  333. uint8_t arrow_bits[3] = {0b100, 0b010, 0b001};
  334. // Arrow
  335. if(model->animator_tick > 2) {
  336. // 4 steps of animation
  337. model->animator_tick = 0;
  338. }
  339. uint8_t current_arrow_bits = arrow_bits[model->animator_tick];
  340. canvas_draw_icon(
  341. canvas,
  342. 51,
  343. 32,
  344. (current_arrow_bits & 0b100) ? &I_Suica_FilledArrowRight : &I_Suica_EmptyArrowRight);
  345. canvas_draw_icon(
  346. canvas,
  347. 59,
  348. 32,
  349. (current_arrow_bits & 0b010) ? &I_Suica_FilledArrowRight : &I_Suica_EmptyArrowRight);
  350. canvas_draw_icon(
  351. canvas,
  352. 67,
  353. 32,
  354. (current_arrow_bits & 0b001) ? &I_Suica_FilledArrowRight : &I_Suica_EmptyArrowRight);
  355. furi_string_free(buffer);
  356. }
  357. static void
  358. suica_draw_birthday_page_2(Canvas* canvas, SuicaHistory history, SuicaHistoryViewModel* model) {
  359. UNUSED(history);
  360. canvas_draw_icon(canvas, 27, 14, &I_Suica_PenguinHappyBirthday);
  361. canvas_draw_icon(canvas, 14, 14, &I_Suica_PenguinTodaysVIP);
  362. canvas_draw_rframe(canvas, 12, 12, 13, 52, 2); // VIP frame
  363. uint8_t star_bits[4] = {0b11000000, 0b11110000, 0b11111111, 0b00000000};
  364. // Arrow
  365. if(model->animator_tick > 3) {
  366. // 4 steps of animation
  367. model->animator_tick = 0;
  368. }
  369. uint8_t current_star_bits = star_bits[model->animator_tick];
  370. canvas_draw_icon(
  371. canvas, 87, 30, (current_star_bits & 0b10000000) ? &I_Suica_BigStar : &I_Suica_Nothing);
  372. canvas_draw_icon(
  373. canvas, 90, 12, (current_star_bits & 0b01000000) ? &I_Suica_PlusStar : &I_Suica_Nothing);
  374. canvas_draw_icon(
  375. canvas, 99, 34, (current_star_bits & 0b00100000) ? &I_Suica_SmallStar : &I_Suica_Nothing);
  376. canvas_draw_icon(
  377. canvas, 103, 12, (current_star_bits & 0b00010000) ? &I_Suica_SmallStar : &I_Suica_Nothing);
  378. canvas_draw_icon(
  379. canvas, 106, 21, (current_star_bits & 0b00001000) ? &I_Suica_BigStar : &I_Suica_Nothing);
  380. canvas_draw_icon(
  381. canvas, 109, 43, (current_star_bits & 0b00000100) ? &I_Suica_PlusStar : &I_Suica_Nothing);
  382. canvas_draw_icon(
  383. canvas, 117, 28, (current_star_bits & 0b00000010) ? &I_Suica_BigStar : &I_Suica_Nothing);
  384. canvas_draw_icon(
  385. canvas, 115, 16, (current_star_bits & 0b00000100) ? &I_Suica_PlusStar : &I_Suica_Nothing);
  386. }
  387. static void suica_draw_vending_machine_page_1(
  388. Canvas* canvas,
  389. SuicaHistory history,
  390. SuicaHistoryViewModel* model) {
  391. FuriString* buffer = furi_string_alloc();
  392. canvas_draw_icon(canvas, 0, 10, &I_Suica_VendingPage2Full);
  393. furi_string_printf(buffer, "%d", history.balance_change);
  394. canvas_set_font(canvas, FontPrimary);
  395. canvas_draw_str_aligned(canvas, 98, 39, AlignRight, AlignBottom, furi_string_get_cstr(buffer));
  396. // Animate Bubbles and LCD Refresh
  397. if(model->animator_tick > 14) {
  398. // 14 steps of animation
  399. model->animator_tick = 0;
  400. }
  401. canvas_set_color(canvas, ColorWhite);
  402. canvas_draw_line(canvas, 88, 51 + model->animator_tick, 128, 51 + model->animator_tick);
  403. switch(model->animator_tick % 7) {
  404. case 0:
  405. canvas_draw_circle(canvas, 12, 48, 1);
  406. canvas_draw_circle(canvas, 23, 39, 2);
  407. break;
  408. case 1:
  409. canvas_draw_circle(canvas, 11, 46, 1);
  410. canvas_draw_circle(canvas, 23, 39, 2);
  411. canvas_set_color(canvas, ColorBlack);
  412. canvas_draw_line(canvas, 24, 37, 22, 37);
  413. canvas_draw_line(canvas, 25, 40, 25, 38);
  414. canvas_set_color(canvas, ColorWhite);
  415. break;
  416. case 2:
  417. canvas_draw_circle(canvas, 12, 44, 1);
  418. canvas_draw_circle(canvas, 24, 50, 1);
  419. break;
  420. case 3:
  421. canvas_draw_icon(canvas, 12, 41, &I_Suica_SmallStar);
  422. canvas_draw_circle(canvas, 25, 48, 1);
  423. break;
  424. case 4:
  425. canvas_draw_icon(canvas, 14, 39, &I_Suica_SmallStar);
  426. canvas_draw_circle(canvas, 26, 46, 1);
  427. break;
  428. case 5:
  429. canvas_draw_icon(canvas, 24, 43, &I_Suica_SmallStar);
  430. canvas_draw_circle(canvas, 16, 38, 2);
  431. break;
  432. case 6:
  433. canvas_draw_icon(canvas, 23, 41, &I_Suica_SmallStar);
  434. canvas_draw_circle(canvas, 16, 38, 2);
  435. canvas_set_color(canvas, ColorBlack);
  436. canvas_draw_line(canvas, 15, 36, 17, 36);
  437. canvas_draw_line(canvas, 18, 39, 18, 37);
  438. canvas_set_color(canvas, ColorWhite);
  439. break;
  440. default:
  441. break;
  442. }
  443. furi_string_free(buffer);
  444. canvas_set_color(canvas, ColorBlack);
  445. }
  446. static void suica_draw_vending_machine_page_2(
  447. Canvas* canvas,
  448. SuicaHistory history,
  449. SuicaHistoryViewModel* model) {
  450. FuriString* buffer = furi_string_alloc();
  451. if(model->animator_tick > 42) {
  452. // 6 steps of animation
  453. model->animator_tick = 0;
  454. }
  455. // Draw Thank You Banner
  456. canvas_draw_icon(
  457. canvas, 49 - model->animator_tick, -9 + model->animator_tick, &I_Suica_VendingThankYou);
  458. canvas_set_color(canvas, ColorWhite);
  459. canvas_draw_box(canvas, 50, 0, 128, 64);
  460. canvas_draw_box(canvas, 0, 0, 42, 64);
  461. // Clock Component
  462. canvas_set_color(canvas, ColorWhite); // Erase part of old frame to allow for new frame
  463. canvas_draw_line(canvas, 91, 9, 94, 6);
  464. canvas_draw_line(canvas, 57, 9, 93, 9);
  465. canvas_set_color(canvas, ColorBlack);
  466. furi_string_printf(buffer, "%02d:%02d", history.hour, history.minute);
  467. canvas_draw_line(canvas, 63, 21, 60, 18);
  468. canvas_set_font(canvas, FontKeyboard);
  469. canvas_draw_str(canvas, 63, 19, furi_string_get_cstr(buffer));
  470. canvas_draw_line(canvas, 91, 21, 94, 18);
  471. canvas_draw_line(canvas, 64, 21, 91, 21);
  472. canvas_draw_line(canvas, 94, 6, 94, 17);
  473. canvas_draw_line(canvas, 60, 12, 60, 17);
  474. canvas_draw_line(canvas, 60, 12, 57, 9);
  475. // Vending Machine
  476. canvas_draw_icon(canvas, 4, 12, &I_Suica_VendingMachine);
  477. // Machine Code
  478. canvas_set_font(canvas, FontPrimary);
  479. canvas_draw_str(canvas, 75, 35, "Machine");
  480. canvas_draw_icon(canvas, 119, 25, &I_Suica_ShopPin);
  481. furi_string_printf(
  482. buffer, "%01d:%03d:%03d", history.area_code, history.shop_code[0], history.shop_code[1]);
  483. canvas_set_font(canvas, FontKeyboard);
  484. canvas_draw_str(canvas, 75, 45, furi_string_get_cstr(buffer));
  485. // Animate Vending Machine Flap
  486. switch(model->animator_tick % 7) {
  487. case 0:
  488. canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlapHollow);
  489. canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlap1);
  490. break;
  491. case 1:
  492. canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlapHollow);
  493. canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlap2);
  494. break;
  495. case 2:
  496. canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlapHollow);
  497. canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlap3);
  498. break;
  499. case 3:
  500. canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlapHollow);
  501. canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlap3);
  502. canvas_draw_icon(canvas, 59, 45, &I_Suica_VendingCan1);
  503. break;
  504. case 4:
  505. canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlapHollow);
  506. canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlap2);
  507. canvas_draw_icon(canvas, 74, 48, &I_Suica_VendingCan2);
  508. break;
  509. case 5:
  510. canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlapHollow);
  511. canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlap1);
  512. canvas_draw_icon(canvas, 89, 51, &I_Suica_VendingCan3);
  513. break;
  514. case 6:
  515. canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlapHollow);
  516. canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlap1);
  517. canvas_draw_icon(canvas, 110, 54, &I_Suica_VendingCan4);
  518. break;
  519. default:
  520. break;
  521. }
  522. furi_string_free(buffer);
  523. }
  524. static void
  525. suica_draw_store_page_1(Canvas* canvas, SuicaHistory history, SuicaHistoryViewModel* model) {
  526. FuriString* buffer = furi_string_alloc();
  527. furi_string_printf(buffer, "%d", history.balance_change);
  528. canvas_draw_icon(canvas, 0, 15, &I_Suica_StoreP1Counter);
  529. canvas_set_font(canvas, FontPrimary);
  530. canvas_draw_str_aligned(canvas, 98, 39, AlignRight, AlignBottom, furi_string_get_cstr(buffer));
  531. canvas_draw_icon(canvas, 59, 27, &I_Suica_StoreReceiptDashLine);
  532. // Animate Taxi and LCD Refresh
  533. if(model->animator_tick > 11) {
  534. // 14 steps of animation
  535. model->animator_tick = 0;
  536. }
  537. switch(model->animator_tick % 6) {
  538. case 0:
  539. case 1:
  540. case 2:
  541. canvas_draw_icon(canvas, 41, 18, &I_Suica_StoreReceiptFrame1);
  542. break;
  543. case 3:
  544. case 4:
  545. case 5:
  546. canvas_draw_icon(canvas, 41, 18, &I_Suica_StoreReceiptFrame2);
  547. break;
  548. default:
  549. break;
  550. }
  551. switch(model->animator_tick % 6) {
  552. case 0:
  553. case 1:
  554. canvas_draw_icon(canvas, 0, 24, &I_Suica_StoreLightningVertical);
  555. break;
  556. case 2:
  557. case 3:
  558. canvas_draw_icon(canvas, 3, 31, &I_Suica_StoreLightningHorizontal);
  559. break;
  560. case 4:
  561. case 5:
  562. canvas_draw_icon(canvas, 0, 24, &I_Suica_StoreLightningVertical);
  563. canvas_draw_icon(canvas, 3, 31, &I_Suica_StoreLightningHorizontal);
  564. break;
  565. default:
  566. break;
  567. }
  568. furi_string_free(buffer);
  569. }
  570. static void
  571. suica_draw_store_page_2(Canvas* canvas, SuicaHistory history, SuicaHistoryViewModel* model) {
  572. FuriString* buffer = furi_string_alloc();
  573. // Clock Component
  574. canvas_set_color(canvas, ColorWhite); // Erase part of old frame to allow for new frame
  575. canvas_draw_line(canvas, 91, 9, 94, 6);
  576. canvas_draw_line(canvas, 57, 9, 93, 9);
  577. canvas_set_color(canvas, ColorBlack);
  578. furi_string_printf(buffer, "%02d:%02d", history.hour, history.minute);
  579. canvas_draw_line(canvas, 63, 21, 60, 18);
  580. canvas_set_font(canvas, FontKeyboard);
  581. canvas_draw_str(canvas, 63, 19, furi_string_get_cstr(buffer));
  582. canvas_draw_line(canvas, 91, 21, 94, 18);
  583. canvas_draw_line(canvas, 64, 21, 91, 21);
  584. canvas_draw_line(canvas, 94, 6, 94, 17);
  585. canvas_draw_line(canvas, 60, 12, 60, 17);
  586. canvas_draw_line(canvas, 60, 12, 57, 9);
  587. // Machine Code
  588. canvas_set_font(canvas, FontPrimary);
  589. canvas_draw_str(canvas, 75, 35, "Store");
  590. canvas_draw_icon(canvas, 104, 25, &I_Suica_ShopPin);
  591. furi_string_printf(
  592. buffer, "%01d:%03d:%03d", history.area_code, history.shop_code[0], history.shop_code[1]);
  593. canvas_set_font(canvas, FontKeyboard);
  594. canvas_draw_str(canvas, 75, 45, furi_string_get_cstr(buffer));
  595. // Store Frame
  596. canvas_draw_icon(canvas, 0, 13, &I_Suica_StoreFrame);
  597. // Sliding Door
  598. uint8_t door_position[7] = {20, 18, 14, 6, 2, 0, 0};
  599. if(model->animator_tick > 20) {
  600. // 14 steps of animation
  601. model->animator_tick = 0;
  602. }
  603. if(model->animator_tick < 7) {
  604. canvas_draw_icon(
  605. canvas, -1 - door_position[6 - model->animator_tick], 28, &I_Suica_StoreSlidingDoor);
  606. } else if(model->animator_tick < 14) {
  607. canvas_draw_icon(
  608. canvas, -1 - door_position[model->animator_tick - 7], 28, &I_Suica_StoreSlidingDoor);
  609. } else {
  610. canvas_draw_icon(canvas, -1, 28, &I_Suica_StoreSlidingDoor);
  611. }
  612. // Animate Neon and Fan
  613. switch(model->animator_tick % 4) {
  614. case 0:
  615. case 1:
  616. canvas_draw_icon(canvas, 37, 18, &I_Suica_StoreFan1);
  617. break;
  618. case 2:
  619. case 3:
  620. canvas_draw_icon(canvas, 37, 18, &I_Suica_StoreFan2);
  621. break;
  622. default:
  623. break;
  624. }
  625. furi_string_free(buffer);
  626. }
  627. static void
  628. suica_draw_balance_page(Canvas* canvas, SuicaHistory history, SuicaHistoryViewModel* model) {
  629. FuriString* buffer = furi_string_alloc();
  630. // Balance
  631. canvas_set_font(canvas, FontBigNumbers);
  632. canvas_draw_icon(canvas, 0, 48, &I_Suica_YenSign);
  633. canvas_draw_icon(canvas, 111, 48, &I_Suica_YenKanji);
  634. furi_string_printf(buffer, "%d", history.balance);
  635. canvas_draw_str_aligned(
  636. canvas, 109, 64, AlignRight, AlignBottom, furi_string_get_cstr(buffer));
  637. furi_string_printf(buffer, "%d", history.previous_balance);
  638. canvas_draw_str_aligned(
  639. canvas, 109, 26, AlignRight, AlignBottom, furi_string_get_cstr(buffer));
  640. furi_string_printf(buffer, "%d", history.balance_change);
  641. canvas_draw_str_aligned(
  642. canvas, 109, 43, AlignRight, AlignBottom, furi_string_get_cstr(buffer));
  643. // Separator
  644. canvas_draw_line(canvas, 26, 45, 128, 45);
  645. canvas_draw_line(canvas, 26, 46, 128, 46);
  646. if(history.balance_sign == SuicaBalanceAdd) {
  647. // Animate plus sign
  648. if(model->animator_tick > 2) {
  649. // 9 steps of animation
  650. model->animator_tick = 0;
  651. }
  652. switch(model->animator_tick) {
  653. case 0:
  654. canvas_draw_icon(canvas, 28, 28, &I_Suica_PlusSign1);
  655. break;
  656. case 1:
  657. canvas_draw_icon(canvas, 27, 27, &I_Suica_PlusSign2);
  658. break;
  659. case 2:
  660. canvas_draw_icon(canvas, 26, 26, &I_Suica_PlusSign3);
  661. break;
  662. default:
  663. break;
  664. }
  665. } else if(history.balance_sign == SuicaBalanceSub) {
  666. // Animate plus sign
  667. if(model->animator_tick > 12) {
  668. // 9 steps of animation
  669. model->animator_tick = 0;
  670. }
  671. switch(model->animator_tick) {
  672. case 0:
  673. case 1:
  674. case 2:
  675. case 3:
  676. canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign0);
  677. break;
  678. case 4:
  679. canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign1);
  680. break;
  681. case 5:
  682. canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign2);
  683. break;
  684. case 6:
  685. canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign3);
  686. break;
  687. case 7:
  688. canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign4);
  689. break;
  690. case 8:
  691. canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign5);
  692. break;
  693. case 9:
  694. canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign6);
  695. break;
  696. case 10:
  697. canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign7);
  698. break;
  699. case 11:
  700. canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign8);
  701. break;
  702. case 12:
  703. canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign9);
  704. break;
  705. default:
  706. break;
  707. }
  708. } else {
  709. canvas_draw_str(canvas, 30, 28, "=");
  710. }
  711. }
  712. static void suica_history_draw_callback(Canvas* canvas, void* model) {
  713. canvas_set_bitmap_mode(canvas, true);
  714. SuicaHistoryViewModel* my_model = (SuicaHistoryViewModel*)model;
  715. FuriString* buffer = furi_string_alloc();
  716. // catch the case where the page and entry are not initialized
  717. if(my_model->entry > my_model->size || my_model->entry < 1) {
  718. my_model->entry = 1;
  719. }
  720. // Get previous balance if we are not at the earliest entry
  721. if(my_model->entry < my_model->size) {
  722. my_model->history.previous_balance = my_model->travel_history[(my_model->entry * 16) + 10];
  723. my_model->history.previous_balance |= my_model->travel_history[(my_model->entry * 16) + 11]
  724. << 8;
  725. } else {
  726. my_model->history.previous_balance = 0;
  727. }
  728. // Calculate balance change
  729. if(my_model->history.previous_balance < my_model->history.balance) {
  730. my_model->history.balance_change =
  731. my_model->history.balance - my_model->history.previous_balance;
  732. my_model->history.balance_sign = SuicaBalanceAdd;
  733. } else if(my_model->history.previous_balance > my_model->history.balance) {
  734. my_model->history.balance_change =
  735. my_model->history.previous_balance - my_model->history.balance;
  736. my_model->history.balance_sign = SuicaBalanceSub;
  737. } else {
  738. my_model->history.balance_change = 0;
  739. my_model->history.balance_sign = SuicaBalanceEqual;
  740. }
  741. switch((uint8_t)my_model->page) {
  742. case 0:
  743. switch(my_model->history.history_type) {
  744. case SuicaHistoryTrain:
  745. suica_draw_train_page_1(canvas, my_model->history, my_model, false);
  746. break;
  747. case SuicaHistoryHappyBirthday:
  748. suica_draw_train_page_1(canvas, my_model->history, my_model, true);
  749. break;
  750. case SuicaHistoryVendingMachine:
  751. suica_draw_vending_machine_page_1(canvas, my_model->history, my_model);
  752. break;
  753. case SuicaHistoryPosAndTaxi:
  754. suica_draw_store_page_1(canvas, my_model->history, my_model);
  755. break;
  756. default:
  757. break;
  758. }
  759. break;
  760. case 1:
  761. switch(my_model->history.history_type) {
  762. case SuicaHistoryTrain:
  763. suica_draw_train_page_2(canvas, my_model->history, my_model);
  764. break;
  765. case SuicaHistoryHappyBirthday:
  766. suica_draw_birthday_page_2(canvas, my_model->history, my_model);
  767. break;
  768. case SuicaHistoryVendingMachine:
  769. suica_draw_vending_machine_page_2(canvas, my_model->history, my_model);
  770. break;
  771. case SuicaHistoryPosAndTaxi:
  772. suica_draw_store_page_2(canvas, my_model->history, my_model);
  773. break;
  774. default:
  775. break;
  776. }
  777. break;
  778. case 2:
  779. suica_draw_balance_page(canvas, my_model->history, my_model);
  780. break;
  781. default:
  782. break;
  783. }
  784. // The the banner last so it is on top
  785. // Main title
  786. canvas_set_font(canvas, FontPrimary);
  787. canvas_draw_str(canvas, 0, 8, "Suica");
  788. // Date
  789. furi_string_printf(
  790. buffer,
  791. "20%02d-%02d-%02d",
  792. my_model->history.year,
  793. my_model->history.month,
  794. my_model->history.day);
  795. canvas_set_font(canvas, FontPrimary);
  796. canvas_draw_str(canvas, 36, 8, furi_string_get_cstr(buffer));
  797. // Entry Num
  798. canvas_draw_box(canvas, 106, 0, 13, 9);
  799. furi_string_printf(buffer, "%02d", my_model->entry);
  800. canvas_set_font(canvas, FontKeyboard);
  801. canvas_set_color(canvas, ColorWhite);
  802. canvas_draw_str(canvas, 107, 8, furi_string_get_cstr(buffer));
  803. canvas_set_color(canvas, ColorBlack);
  804. switch(my_model->page) {
  805. case 0:
  806. canvas_draw_icon(canvas, 100, 0, &I_Suica_EntrySlider1);
  807. break;
  808. case 1:
  809. canvas_draw_icon(canvas, 100, 0, &I_Suica_EntrySlider2);
  810. break;
  811. case 2:
  812. canvas_draw_icon(canvas, 100, 0, &I_Suica_EntrySlider3);
  813. break;
  814. default:
  815. break;
  816. }
  817. canvas_set_color(canvas, ColorWhite);
  818. if (my_model->entry == 1) {
  819. canvas_draw_box(canvas, 99, 0, 6, 9);
  820. } else if (my_model->entry == my_model->size) {
  821. canvas_draw_box(canvas, 120, 0, 6, 9);
  822. }
  823. canvas_set_color(canvas, ColorBlack);
  824. // Frame
  825. canvas_draw_line(canvas, 0, 9, 26, 9);
  826. canvas_draw_line(canvas, 27, 9, 29, 7);
  827. canvas_draw_line(canvas, 29, 0, 29, 6);
  828. canvas_draw_line(canvas, 33, 0, 31, 7);
  829. canvas_draw_line(canvas, 35, 9, 33, 7);
  830. canvas_draw_line(canvas, 92, 9, 36, 9);
  831. canvas_draw_line(canvas, 93, 9, 96, 6);
  832. canvas_draw_line(canvas, 96, 0, 96, 6);
  833. furi_string_free(buffer);
  834. }
  835. static void suica_view_history_timer_callback(void* context) {
  836. Metroflip* app = (Metroflip*)context;
  837. view_dispatcher_send_custom_event(app->view_dispatcher, 0);
  838. }
  839. static void suica_view_history_enter_callback(void* context) {
  840. uint32_t period = furi_ms_to_ticks(ARROW_ANIMATION_FRAME_MS);
  841. Metroflip* app = (Metroflip*)context;
  842. furi_assert(app->suica_context->timer == NULL);
  843. app->suica_context->timer =
  844. furi_timer_alloc(suica_view_history_timer_callback, FuriTimerTypePeriodic, context);
  845. furi_timer_start(app->suica_context->timer, period);
  846. }
  847. static void suica_view_history_exit_callback(void* context) {
  848. Metroflip* app = (Metroflip*)context;
  849. furi_timer_stop(app->suica_context->timer);
  850. furi_timer_free(app->suica_context->timer);
  851. app->suica_context->timer = NULL;
  852. }
  853. static bool suica_view_history_custom_event_callback(uint32_t event, void* context) {
  854. Metroflip* app = (Metroflip*)context;
  855. switch(event) {
  856. case 0:
  857. // Redraw screen by passing true to last parameter of with_view_model.
  858. {
  859. bool redraw = true;
  860. with_view_model(
  861. app->suica_context->view_history,
  862. SuicaHistoryViewModel * model,
  863. { model->animator_tick++; },
  864. redraw);
  865. return true;
  866. }
  867. default:
  868. return false;
  869. }
  870. }
  871. static uint32_t suica_navigation_raw_callback(void* _context) {
  872. UNUSED(_context);
  873. return MetroflipViewWidget;
  874. }