suica_drawings.h 36 KB

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