suica_drawings.h 35 KB

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