suica_drawings.h 36 KB

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