diff --git a/app/src/main/assets/car_images/m3_PBSB_W32P.png b/app/src/main/assets/car_images/m3_PBSB_W32P.png new file mode 100644 index 0000000..cc2dd9c Binary files /dev/null and b/app/src/main/assets/car_images/m3_PBSB_W32P.png differ diff --git a/app/src/main/assets/car_images/m3_PBSB_W38B.png b/app/src/main/assets/car_images/m3_PBSB_W38B.png new file mode 100644 index 0000000..2917148 Binary files /dev/null and b/app/src/main/assets/car_images/m3_PBSB_W38B.png differ diff --git a/app/src/main/assets/car_images/m3_PBSB_W39B.png b/app/src/main/assets/car_images/m3_PBSB_W39B.png new file mode 100644 index 0000000..b9c602f Binary files /dev/null and b/app/src/main/assets/car_images/m3_PBSB_W39B.png differ diff --git a/app/src/main/assets/car_images/m3_PMNG_W32P.png b/app/src/main/assets/car_images/m3_PMNG_W32P.png new file mode 100644 index 0000000..077c79b Binary files /dev/null and b/app/src/main/assets/car_images/m3_PMNG_W32P.png differ diff --git a/app/src/main/assets/car_images/m3_PMNG_W38B.png b/app/src/main/assets/car_images/m3_PMNG_W38B.png new file mode 100644 index 0000000..ec955d2 Binary files /dev/null and b/app/src/main/assets/car_images/m3_PMNG_W38B.png differ diff --git a/app/src/main/assets/car_images/m3_PMNG_W39B.png b/app/src/main/assets/car_images/m3_PMNG_W39B.png new file mode 100644 index 0000000..4d865bf Binary files /dev/null and b/app/src/main/assets/car_images/m3_PMNG_W39B.png differ diff --git a/app/src/main/assets/car_images/m3_PMSS_W32P.png b/app/src/main/assets/car_images/m3_PMSS_W32P.png new file mode 100644 index 0000000..7c05f77 Binary files /dev/null and b/app/src/main/assets/car_images/m3_PMSS_W32P.png differ diff --git a/app/src/main/assets/car_images/m3_PMSS_W38B.png b/app/src/main/assets/car_images/m3_PMSS_W38B.png new file mode 100644 index 0000000..907fa78 Binary files /dev/null and b/app/src/main/assets/car_images/m3_PMSS_W38B.png differ diff --git a/app/src/main/assets/car_images/m3_PMSS_W39B.png b/app/src/main/assets/car_images/m3_PMSS_W39B.png new file mode 100644 index 0000000..0bedcc4 Binary files /dev/null and b/app/src/main/assets/car_images/m3_PMSS_W39B.png differ diff --git a/app/src/main/assets/car_images/m3_PN00_W32P.png b/app/src/main/assets/car_images/m3_PN00_W32P.png new file mode 100644 index 0000000..8dde43e Binary files /dev/null and b/app/src/main/assets/car_images/m3_PN00_W32P.png differ diff --git a/app/src/main/assets/car_images/m3_PN00_W38B.png b/app/src/main/assets/car_images/m3_PN00_W38B.png new file mode 100644 index 0000000..8dde43e Binary files /dev/null and b/app/src/main/assets/car_images/m3_PN00_W38B.png differ diff --git a/app/src/main/assets/car_images/m3_PN00_W39B.png b/app/src/main/assets/car_images/m3_PN00_W39B.png new file mode 100644 index 0000000..8dde43e Binary files /dev/null and b/app/src/main/assets/car_images/m3_PN00_W39B.png differ diff --git a/app/src/main/assets/car_images/m3_PN01_W32P.png b/app/src/main/assets/car_images/m3_PN01_W32P.png new file mode 100644 index 0000000..8dde43e Binary files /dev/null and b/app/src/main/assets/car_images/m3_PN01_W32P.png differ diff --git a/app/src/main/assets/car_images/m3_PN01_W38B.png b/app/src/main/assets/car_images/m3_PN01_W38B.png new file mode 100644 index 0000000..8dde43e Binary files /dev/null and b/app/src/main/assets/car_images/m3_PN01_W38B.png differ diff --git a/app/src/main/assets/car_images/m3_PN01_W39B.png b/app/src/main/assets/car_images/m3_PN01_W39B.png new file mode 100644 index 0000000..8dde43e Binary files /dev/null and b/app/src/main/assets/car_images/m3_PN01_W39B.png differ diff --git a/app/src/main/assets/car_images/m3_PPMR_W32P.png b/app/src/main/assets/car_images/m3_PPMR_W32P.png new file mode 100644 index 0000000..965b95b Binary files /dev/null and b/app/src/main/assets/car_images/m3_PPMR_W32P.png differ diff --git a/app/src/main/assets/car_images/m3_PPMR_W38B.png b/app/src/main/assets/car_images/m3_PPMR_W38B.png new file mode 100644 index 0000000..6d2a51d Binary files /dev/null and b/app/src/main/assets/car_images/m3_PPMR_W38B.png differ diff --git a/app/src/main/assets/car_images/m3_PPMR_W39B.png b/app/src/main/assets/car_images/m3_PPMR_W39B.png new file mode 100644 index 0000000..073f71a Binary files /dev/null and b/app/src/main/assets/car_images/m3_PPMR_W39B.png differ diff --git a/app/src/main/assets/car_images/m3_PPSB_W32P.png b/app/src/main/assets/car_images/m3_PPSB_W32P.png new file mode 100644 index 0000000..926270e Binary files /dev/null and b/app/src/main/assets/car_images/m3_PPSB_W32P.png differ diff --git a/app/src/main/assets/car_images/m3_PPSB_W38B.png b/app/src/main/assets/car_images/m3_PPSB_W38B.png new file mode 100644 index 0000000..8f1942c Binary files /dev/null and b/app/src/main/assets/car_images/m3_PPSB_W38B.png differ diff --git a/app/src/main/assets/car_images/m3_PPSB_W39B.png b/app/src/main/assets/car_images/m3_PPSB_W39B.png new file mode 100644 index 0000000..f694777 Binary files /dev/null and b/app/src/main/assets/car_images/m3_PPSB_W39B.png differ diff --git a/app/src/main/assets/car_images/m3_PPSW_W32P.png b/app/src/main/assets/car_images/m3_PPSW_W32P.png new file mode 100644 index 0000000..0ec98c1 Binary files /dev/null and b/app/src/main/assets/car_images/m3_PPSW_W32P.png differ diff --git a/app/src/main/assets/car_images/m3_PPSW_W38B.png b/app/src/main/assets/car_images/m3_PPSW_W38B.png new file mode 100644 index 0000000..5bf4d54 Binary files /dev/null and b/app/src/main/assets/car_images/m3_PPSW_W38B.png differ diff --git a/app/src/main/assets/car_images/m3_PPSW_W39B.png b/app/src/main/assets/car_images/m3_PPSW_W39B.png new file mode 100644 index 0000000..972a0ba Binary files /dev/null and b/app/src/main/assets/car_images/m3_PPSW_W39B.png differ diff --git a/app/src/main/assets/car_images/m3_PR00_W32P.png b/app/src/main/assets/car_images/m3_PR00_W32P.png new file mode 100644 index 0000000..8dde43e Binary files /dev/null and b/app/src/main/assets/car_images/m3_PR00_W32P.png differ diff --git a/app/src/main/assets/car_images/m3_PR00_W38B.png b/app/src/main/assets/car_images/m3_PR00_W38B.png new file mode 100644 index 0000000..8dde43e Binary files /dev/null and b/app/src/main/assets/car_images/m3_PR00_W38B.png differ diff --git a/app/src/main/assets/car_images/m3_PR00_W39B.png b/app/src/main/assets/car_images/m3_PR00_W39B.png new file mode 100644 index 0000000..8dde43e Binary files /dev/null and b/app/src/main/assets/car_images/m3_PR00_W39B.png differ diff --git a/app/src/main/assets/car_images/m3_PR01_W32P.png b/app/src/main/assets/car_images/m3_PR01_W32P.png new file mode 100644 index 0000000..8dde43e Binary files /dev/null and b/app/src/main/assets/car_images/m3_PR01_W32P.png differ diff --git a/app/src/main/assets/car_images/m3_PR01_W38B.png b/app/src/main/assets/car_images/m3_PR01_W38B.png new file mode 100644 index 0000000..8dde43e Binary files /dev/null and b/app/src/main/assets/car_images/m3_PR01_W38B.png differ diff --git a/app/src/main/assets/car_images/m3_PR01_W39B.png b/app/src/main/assets/car_images/m3_PR01_W39B.png new file mode 100644 index 0000000..8dde43e Binary files /dev/null and b/app/src/main/assets/car_images/m3_PR01_W39B.png differ diff --git a/app/src/main/assets/car_images/my_PBSB_WY0S.png b/app/src/main/assets/car_images/my_PBSB_WY0S.png new file mode 100644 index 0000000..8a53205 Binary files /dev/null and b/app/src/main/assets/car_images/my_PBSB_WY0S.png differ diff --git a/app/src/main/assets/car_images/my_PBSB_WY18B.png b/app/src/main/assets/car_images/my_PBSB_WY18B.png new file mode 100644 index 0000000..f034286 Binary files /dev/null and b/app/src/main/assets/car_images/my_PBSB_WY18B.png differ diff --git a/app/src/main/assets/car_images/my_PBSB_WY19B.png b/app/src/main/assets/car_images/my_PBSB_WY19B.png new file mode 100644 index 0000000..ba10af2 Binary files /dev/null and b/app/src/main/assets/car_images/my_PBSB_WY19B.png differ diff --git a/app/src/main/assets/car_images/my_PBSB_WY1S.png b/app/src/main/assets/car_images/my_PBSB_WY1S.png new file mode 100644 index 0000000..8a53205 Binary files /dev/null and b/app/src/main/assets/car_images/my_PBSB_WY1S.png differ diff --git a/app/src/main/assets/car_images/my_PBSB_WY20P.png b/app/src/main/assets/car_images/my_PBSB_WY20P.png new file mode 100644 index 0000000..8f053fd Binary files /dev/null and b/app/src/main/assets/car_images/my_PBSB_WY20P.png differ diff --git a/app/src/main/assets/car_images/my_PMNG_WY0S.png b/app/src/main/assets/car_images/my_PMNG_WY0S.png new file mode 100644 index 0000000..ec647d4 Binary files /dev/null and b/app/src/main/assets/car_images/my_PMNG_WY0S.png differ diff --git a/app/src/main/assets/car_images/my_PMNG_WY18B.png b/app/src/main/assets/car_images/my_PMNG_WY18B.png new file mode 100644 index 0000000..59ab803 Binary files /dev/null and b/app/src/main/assets/car_images/my_PMNG_WY18B.png differ diff --git a/app/src/main/assets/car_images/my_PMNG_WY19B.png b/app/src/main/assets/car_images/my_PMNG_WY19B.png new file mode 100644 index 0000000..6e7a59b Binary files /dev/null and b/app/src/main/assets/car_images/my_PMNG_WY19B.png differ diff --git a/app/src/main/assets/car_images/my_PMNG_WY1S.png b/app/src/main/assets/car_images/my_PMNG_WY1S.png new file mode 100644 index 0000000..ec647d4 Binary files /dev/null and b/app/src/main/assets/car_images/my_PMNG_WY1S.png differ diff --git a/app/src/main/assets/car_images/my_PMNG_WY20P.png b/app/src/main/assets/car_images/my_PMNG_WY20P.png new file mode 100644 index 0000000..a6e8e6a Binary files /dev/null and b/app/src/main/assets/car_images/my_PMNG_WY20P.png differ diff --git a/app/src/main/assets/car_images/my_PMSS_WY0S.png b/app/src/main/assets/car_images/my_PMSS_WY0S.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PMSS_WY0S.png differ diff --git a/app/src/main/assets/car_images/my_PMSS_WY18B.png b/app/src/main/assets/car_images/my_PMSS_WY18B.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PMSS_WY18B.png differ diff --git a/app/src/main/assets/car_images/my_PMSS_WY19B.png b/app/src/main/assets/car_images/my_PMSS_WY19B.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PMSS_WY19B.png differ diff --git a/app/src/main/assets/car_images/my_PMSS_WY1S.png b/app/src/main/assets/car_images/my_PMSS_WY1S.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PMSS_WY1S.png differ diff --git a/app/src/main/assets/car_images/my_PMSS_WY20P.png b/app/src/main/assets/car_images/my_PMSS_WY20P.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PMSS_WY20P.png differ diff --git a/app/src/main/assets/car_images/my_PN00_WY0S.png b/app/src/main/assets/car_images/my_PN00_WY0S.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PN00_WY0S.png differ diff --git a/app/src/main/assets/car_images/my_PN00_WY18B.png b/app/src/main/assets/car_images/my_PN00_WY18B.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PN00_WY18B.png differ diff --git a/app/src/main/assets/car_images/my_PN00_WY19B.png b/app/src/main/assets/car_images/my_PN00_WY19B.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PN00_WY19B.png differ diff --git a/app/src/main/assets/car_images/my_PN00_WY1S.png b/app/src/main/assets/car_images/my_PN00_WY1S.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PN00_WY1S.png differ diff --git a/app/src/main/assets/car_images/my_PN00_WY20P.png b/app/src/main/assets/car_images/my_PN00_WY20P.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PN00_WY20P.png differ diff --git a/app/src/main/assets/car_images/my_PN01_WY0S.png b/app/src/main/assets/car_images/my_PN01_WY0S.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PN01_WY0S.png differ diff --git a/app/src/main/assets/car_images/my_PN01_WY18B.png b/app/src/main/assets/car_images/my_PN01_WY18B.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PN01_WY18B.png differ diff --git a/app/src/main/assets/car_images/my_PN01_WY19B.png b/app/src/main/assets/car_images/my_PN01_WY19B.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PN01_WY19B.png differ diff --git a/app/src/main/assets/car_images/my_PN01_WY1S.png b/app/src/main/assets/car_images/my_PN01_WY1S.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PN01_WY1S.png differ diff --git a/app/src/main/assets/car_images/my_PN01_WY20P.png b/app/src/main/assets/car_images/my_PN01_WY20P.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PN01_WY20P.png differ diff --git a/app/src/main/assets/car_images/my_PPMR_WY0S.png b/app/src/main/assets/car_images/my_PPMR_WY0S.png new file mode 100644 index 0000000..9baf3f0 Binary files /dev/null and b/app/src/main/assets/car_images/my_PPMR_WY0S.png differ diff --git a/app/src/main/assets/car_images/my_PPMR_WY18B.png b/app/src/main/assets/car_images/my_PPMR_WY18B.png new file mode 100644 index 0000000..6987981 Binary files /dev/null and b/app/src/main/assets/car_images/my_PPMR_WY18B.png differ diff --git a/app/src/main/assets/car_images/my_PPMR_WY19B.png b/app/src/main/assets/car_images/my_PPMR_WY19B.png new file mode 100644 index 0000000..ce2fcf7 Binary files /dev/null and b/app/src/main/assets/car_images/my_PPMR_WY19B.png differ diff --git a/app/src/main/assets/car_images/my_PPMR_WY1S.png b/app/src/main/assets/car_images/my_PPMR_WY1S.png new file mode 100644 index 0000000..9baf3f0 Binary files /dev/null and b/app/src/main/assets/car_images/my_PPMR_WY1S.png differ diff --git a/app/src/main/assets/car_images/my_PPMR_WY20P.png b/app/src/main/assets/car_images/my_PPMR_WY20P.png new file mode 100644 index 0000000..51b3b17 Binary files /dev/null and b/app/src/main/assets/car_images/my_PPMR_WY20P.png differ diff --git a/app/src/main/assets/car_images/my_PPSB_WY0S.png b/app/src/main/assets/car_images/my_PPSB_WY0S.png new file mode 100644 index 0000000..0ab9c9f Binary files /dev/null and b/app/src/main/assets/car_images/my_PPSB_WY0S.png differ diff --git a/app/src/main/assets/car_images/my_PPSB_WY18B.png b/app/src/main/assets/car_images/my_PPSB_WY18B.png new file mode 100644 index 0000000..cb618f9 Binary files /dev/null and b/app/src/main/assets/car_images/my_PPSB_WY18B.png differ diff --git a/app/src/main/assets/car_images/my_PPSB_WY19B.png b/app/src/main/assets/car_images/my_PPSB_WY19B.png new file mode 100644 index 0000000..6419ad1 Binary files /dev/null and b/app/src/main/assets/car_images/my_PPSB_WY19B.png differ diff --git a/app/src/main/assets/car_images/my_PPSB_WY1S.png b/app/src/main/assets/car_images/my_PPSB_WY1S.png new file mode 100644 index 0000000..0ab9c9f Binary files /dev/null and b/app/src/main/assets/car_images/my_PPSB_WY1S.png differ diff --git a/app/src/main/assets/car_images/my_PPSB_WY20P.png b/app/src/main/assets/car_images/my_PPSB_WY20P.png new file mode 100644 index 0000000..3b07a4d Binary files /dev/null and b/app/src/main/assets/car_images/my_PPSB_WY20P.png differ diff --git a/app/src/main/assets/car_images/my_PPSW_WY0S.png b/app/src/main/assets/car_images/my_PPSW_WY0S.png new file mode 100644 index 0000000..ccaad14 Binary files /dev/null and b/app/src/main/assets/car_images/my_PPSW_WY0S.png differ diff --git a/app/src/main/assets/car_images/my_PPSW_WY18B.png b/app/src/main/assets/car_images/my_PPSW_WY18B.png new file mode 100644 index 0000000..1955ffe Binary files /dev/null and b/app/src/main/assets/car_images/my_PPSW_WY18B.png differ diff --git a/app/src/main/assets/car_images/my_PPSW_WY19B.png b/app/src/main/assets/car_images/my_PPSW_WY19B.png new file mode 100644 index 0000000..2e6ce96 Binary files /dev/null and b/app/src/main/assets/car_images/my_PPSW_WY19B.png differ diff --git a/app/src/main/assets/car_images/my_PPSW_WY1S.png b/app/src/main/assets/car_images/my_PPSW_WY1S.png new file mode 100644 index 0000000..ccaad14 Binary files /dev/null and b/app/src/main/assets/car_images/my_PPSW_WY1S.png differ diff --git a/app/src/main/assets/car_images/my_PPSW_WY20P.png b/app/src/main/assets/car_images/my_PPSW_WY20P.png new file mode 100644 index 0000000..890191e Binary files /dev/null and b/app/src/main/assets/car_images/my_PPSW_WY20P.png differ diff --git a/app/src/main/assets/car_images/my_PR00_WY0S.png b/app/src/main/assets/car_images/my_PR00_WY0S.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PR00_WY0S.png differ diff --git a/app/src/main/assets/car_images/my_PR00_WY18B.png b/app/src/main/assets/car_images/my_PR00_WY18B.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PR00_WY18B.png differ diff --git a/app/src/main/assets/car_images/my_PR00_WY19B.png b/app/src/main/assets/car_images/my_PR00_WY19B.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PR00_WY19B.png differ diff --git a/app/src/main/assets/car_images/my_PR00_WY1S.png b/app/src/main/assets/car_images/my_PR00_WY1S.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PR00_WY1S.png differ diff --git a/app/src/main/assets/car_images/my_PR00_WY20P.png b/app/src/main/assets/car_images/my_PR00_WY20P.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PR00_WY20P.png differ diff --git a/app/src/main/assets/car_images/my_PR01_WY0S.png b/app/src/main/assets/car_images/my_PR01_WY0S.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PR01_WY0S.png differ diff --git a/app/src/main/assets/car_images/my_PR01_WY18B.png b/app/src/main/assets/car_images/my_PR01_WY18B.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PR01_WY18B.png differ diff --git a/app/src/main/assets/car_images/my_PR01_WY19B.png b/app/src/main/assets/car_images/my_PR01_WY19B.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PR01_WY19B.png differ diff --git a/app/src/main/assets/car_images/my_PR01_WY1S.png b/app/src/main/assets/car_images/my_PR01_WY1S.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PR01_WY1S.png differ diff --git a/app/src/main/assets/car_images/my_PR01_WY20P.png b/app/src/main/assets/car_images/my_PR01_WY20P.png new file mode 100644 index 0000000..6454734 Binary files /dev/null and b/app/src/main/assets/car_images/my_PR01_WY20P.png differ diff --git a/app/src/main/java/com/matedroid/data/api/models/CarModels.kt b/app/src/main/java/com/matedroid/data/api/models/CarModels.kt index 2523883..53f4e4a 100644 --- a/app/src/main/java/com/matedroid/data/api/models/CarModels.kt +++ b/app/src/main/java/com/matedroid/data/api/models/CarModels.kt @@ -17,12 +17,20 @@ data class CarsData( data class CarData( @Json(name = "car_id") val carId: Int, @Json(name = "name") val name: String? = null, - @Json(name = "car_details") val carDetails: CarDetails? = null + @Json(name = "car_details") val carDetails: CarDetails? = null, + @Json(name = "car_exterior") val carExterior: CarExterior? = null ) { val displayName: String? get() = name } +@JsonClass(generateAdapter = true) +data class CarExterior( + @Json(name = "exterior_color") val exteriorColor: String? = null, + @Json(name = "spoiler_type") val spoilerType: String? = null, + @Json(name = "wheel_type") val wheelType: String? = null +) + @JsonClass(generateAdapter = true) data class CarDetails( @Json(name = "model") val model: String? = null, diff --git a/app/src/main/java/com/matedroid/domain/model/CarImageResolver.kt b/app/src/main/java/com/matedroid/domain/model/CarImageResolver.kt new file mode 100644 index 0000000..847d238 --- /dev/null +++ b/app/src/main/java/com/matedroid/domain/model/CarImageResolver.kt @@ -0,0 +1,172 @@ +package com.matedroid.domain.model + +/** + * Resolves Tesla car image assets based on vehicle configuration. + * + * Maps TeslamateAPI values (e.g., "MidnightSilver", "Pinwheel18CapKit") + * to Tesla compositor codes (e.g., "PMNG", "W38B") to construct asset paths. + */ +object CarImageResolver { + + // Model code mappings (TeslamateAPI -> Compositor) + private val MODEL_CODES = mapOf( + "3" to "m3", + "Y" to "my", + "S" to "ms", + "X" to "mx" + ) + + // Color code mappings (TeslamateAPI -> Compositor) + // Keys are normalized (lowercase, no spaces) + private val COLOR_CODES = mapOf( + "black" to "PBSB", + "solidblack" to "PBSB", + "obsidianblack" to "PMBL", + "midnightsilver" to "PMNG", + "midnightsilvermetallic" to "PMNG", + "silver" to "PMSS", + "silvermetallic" to "PMSS", + "white" to "PPSW", + "pearlwhite" to "PPSW", + "pearlwhitemulticoat" to "PPSW", + "deepblue" to "PPSB", + "deepbluemetallic" to "PPSB", + "blue" to "PPSB", + "red" to "PPMR", + "redmulticoat" to "PPMR", + "quicksilver" to "PN00", + "stealthgrey" to "PN01", + "stealthgray" to "PN01", + "midnightcherryred" to "PR00", + "ultrared" to "PR01" + ) + + // Wheel code mappings per model + // Keys are patterns that match the start of the TeslamateAPI wheel type + private val WHEEL_PATTERNS_M3 = listOf( + "pinwheel18" to "W38B", + "aero18" to "W38B", + "aeroturbine19" to "W39B", + "stiletto19" to "W39B", + "sport19" to "W39B", + "performance20" to "W32P", + "19" to "W39B", // Default 19" to Sport + "20" to "W32P", // Default 20" to Performance + "18" to "W38B" // Default 18" to Aero + ) + + private val WHEEL_PATTERNS_MY = listOf( + "pinwheel18" to "WY18B", + "aero18" to "WY18B", + "gemini19" to "WY19B", + "aeroturbine19" to "WY19B", + "stiletto19" to "WY19B", + "sport19" to "WY19B", + "apollo19" to "WY9S", + "induction20" to "WY0S", + "performance20" to "WY20P", + "uberturbine21" to "WY1S", + "21" to "WY1S", // Default 21" to Uberturbine + "20" to "WY0S", // Default 20" to Induction + "19" to "WY19B", // Default 19" to Sport + "18" to "WY18B" // Default 18" to Aero + ) + + // Default wheels per model (most common configuration) + private val DEFAULT_WHEELS = mapOf( + "m3" to "W38B", // 18" Aero + "my" to "WY19B" // 19" Sport + ) + + // Default color + private const val DEFAULT_COLOR = "PPSW" // Pearl White + + /** + * Get the asset path for a car image based on its configuration. + * + * @param model The car model from TeslamateAPI (e.g., "3", "Y") + * @param exteriorColor The exterior color from TeslamateAPI (e.g., "MidnightSilver") + * @param wheelType The wheel type from TeslamateAPI (e.g., "Pinwheel18CapKit") + * @return The asset path (e.g., "car_images/m3_PMNG_W38B.png") + */ + fun getAssetPath( + model: String?, + exteriorColor: String?, + wheelType: String? + ): String { + val modelCode = mapModel(model) ?: return getDefaultAssetPath(model) + val colorCode = mapColor(exteriorColor) ?: DEFAULT_COLOR + val wheelCode = mapWheel(modelCode, wheelType) ?: DEFAULT_WHEELS[modelCode] ?: "W38B" + + return "car_images/${modelCode}_${colorCode}_${wheelCode}.png" + } + + /** + * Get the default asset path for a model when configuration is unavailable. + */ + fun getDefaultAssetPath(model: String?): String { + val modelCode = mapModel(model) ?: "m3" + val defaultWheel = DEFAULT_WHEELS[modelCode] ?: "W38B" + return "car_images/${modelCode}_${DEFAULT_COLOR}_${defaultWheel}.png" + } + + /** + * Check if a specific asset exists (for fallback logic). + * This should be called with actual asset checking from the AssetManager. + */ + fun getFallbackAssetPath( + model: String?, + exteriorColor: String?, + wheelType: String?, + assetExists: (String) -> Boolean + ): String { + // Try exact match first + val exactPath = getAssetPath(model, exteriorColor, wheelType) + if (assetExists(exactPath)) return exactPath + + // Try with default wheel + val modelCode = mapModel(model) ?: "m3" + val colorCode = mapColor(exteriorColor) ?: DEFAULT_COLOR + val defaultWheelPath = "car_images/${modelCode}_${colorCode}_${DEFAULT_WHEELS[modelCode]}.png" + if (assetExists(defaultWheelPath)) return defaultWheelPath + + // Try with default color + val wheelCode = mapWheel(modelCode, wheelType) ?: DEFAULT_WHEELS[modelCode] ?: "W38B" + val defaultColorPath = "car_images/${modelCode}_${DEFAULT_COLOR}_${wheelCode}.png" + if (assetExists(defaultColorPath)) return defaultColorPath + + // Fall back to complete default + return getDefaultAssetPath(model) + } + + private fun mapModel(model: String?): String? { + return model?.let { MODEL_CODES[it.uppercase()] ?: MODEL_CODES[it] } + } + + private fun mapColor(color: String?): String? { + if (color == null) return null + val normalized = color.lowercase().replace(" ", "").replace("-", "").replace("_", "") + return COLOR_CODES[normalized] + } + + private fun mapWheel(modelCode: String, wheelType: String?): String? { + if (wheelType == null) return null + + val normalized = wheelType.lowercase().replace(" ", "").replace("-", "").replace("_", "") + + val patterns = when (modelCode) { + "m3" -> WHEEL_PATTERNS_M3 + "my" -> WHEEL_PATTERNS_MY + else -> WHEEL_PATTERNS_M3 // Default to Model 3 patterns + } + + // Find the first pattern that matches the start of the wheel type + for ((pattern, code) in patterns) { + if (normalized.startsWith(pattern)) { + return code + } + } + + return null + } +} diff --git a/app/src/main/java/com/matedroid/ui/screens/dashboard/DashboardScreen.kt b/app/src/main/java/com/matedroid/ui/screens/dashboard/DashboardScreen.kt index 49c250b..53c60e2 100644 --- a/app/src/main/java/com/matedroid/ui/screens/dashboard/DashboardScreen.kt +++ b/app/src/main/java/com/matedroid/ui/screens/dashboard/DashboardScreen.kt @@ -1,7 +1,9 @@ package com.matedroid.ui.screens.dashboard +import android.graphics.BitmapFactory import android.graphics.Paint import androidx.compose.foundation.Canvas +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -52,6 +54,8 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -69,8 +73,10 @@ import org.osmdroid.util.GeoPoint import org.osmdroid.views.MapView import org.osmdroid.views.overlay.Marker import com.matedroid.data.api.models.BatteryDetails +import com.matedroid.data.api.models.CarExterior import com.matedroid.data.api.models.CarGeodata import com.matedroid.data.api.models.CarStatus +import com.matedroid.domain.model.CarImageResolver import com.matedroid.data.api.models.CarStatusDetails import com.matedroid.data.api.models.Units import com.matedroid.domain.model.UnitFormatter @@ -132,6 +138,8 @@ fun DashboardScreen( DashboardContent( status = uiState.carStatus!!, units = uiState.units, + carModel = uiState.selectedCarModel, + carExterior = uiState.selectedCarExterior, onNavigateToCharges = { uiState.selectedCarId?.let { onNavigateToCharges(it) } }, @@ -229,6 +237,8 @@ private fun ErrorContent(message: String) { private fun DashboardContent( status: CarStatus, units: Units? = null, + carModel: String? = null, + carExterior: CarExterior? = null, onNavigateToCharges: () -> Unit = {}, onNavigateToDrives: () -> Unit = {}, onNavigateToBattery: () -> Unit = {}, @@ -245,6 +255,12 @@ private fun DashboardContent( .padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { + // Car image + CarImageCard( + carModel = carModel, + carExterior = carExterior + ) + // Compact status indicators (right-aligned) StatusIndicatorsRow(status, units) @@ -271,6 +287,56 @@ private fun DashboardContent( } } +@Composable +private fun CarImageCard( + carModel: String?, + carExterior: CarExterior?, + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val assetPath = remember(carModel, carExterior) { + CarImageResolver.getAssetPath( + model = carModel, + exteriorColor = carExterior?.exteriorColor, + wheelType = carExterior?.wheelType + ) + } + + val bitmap = remember(assetPath) { + try { + context.assets.open(assetPath).use { inputStream -> + BitmapFactory.decodeStream(inputStream) + } + } catch (e: Exception) { + // Try fallback to default + try { + val fallbackPath = CarImageResolver.getDefaultAssetPath(carModel) + context.assets.open(fallbackPath).use { inputStream -> + BitmapFactory.decodeStream(inputStream) + } + } catch (e2: Exception) { + null + } + } + } + + if (bitmap != null) { + Box( + modifier = modifier + .fillMaxWidth() + .height(180.dp), + contentAlignment = Alignment.Center + ) { + Image( + bitmap = bitmap.asImageBitmap(), + contentDescription = "Car image", + modifier = Modifier.fillMaxWidth(), + contentScale = ContentScale.Fit + ) + } + } +} + @Composable private fun StatusIndicatorsRow(status: CarStatus, units: Units?) { Row( diff --git a/app/src/main/java/com/matedroid/ui/screens/dashboard/DashboardViewModel.kt b/app/src/main/java/com/matedroid/ui/screens/dashboard/DashboardViewModel.kt index ae506fd..0eabb35 100644 --- a/app/src/main/java/com/matedroid/ui/screens/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/com/matedroid/ui/screens/dashboard/DashboardViewModel.kt @@ -3,6 +3,7 @@ package com.matedroid.ui.screens.dashboard import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.matedroid.data.api.models.CarData +import com.matedroid.data.api.models.CarExterior import com.matedroid.data.api.models.CarStatus import com.matedroid.data.api.models.Units import com.matedroid.data.repository.ApiResult @@ -26,8 +27,17 @@ data class DashboardUiState( val units: Units? = null, val error: String? = null ) { + private val selectedCar: CarData? + get() = cars.find { it.carId == selectedCarId } + val selectedCarEfficiency: Double? - get() = cars.find { it.carId == selectedCarId }?.carDetails?.efficiency + get() = selectedCar?.carDetails?.efficiency + + val selectedCarModel: String? + get() = selectedCar?.carDetails?.model + + val selectedCarExterior: CarExterior? + get() = selectedCar?.carExterior } @HiltViewModel diff --git a/util/fetch_tesla_assets.py b/util/fetch_tesla_assets.py new file mode 100755 index 0000000..943ef9e --- /dev/null +++ b/util/fetch_tesla_assets.py @@ -0,0 +1,194 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.11" +# dependencies = ["httpx", "rich"] +# /// +""" +Tesla Car Image Asset Fetcher + +Downloads Tesla car 3D renders from the Tesla compositor service +for use in the MateDroid Android app. + +Usage: + ./fetch_tesla_assets.py [--output-dir PATH] [--dry-run] +""" + +import argparse +import asyncio +import sys +from pathlib import Path + +import httpx +from rich.console import Console +from rich.progress import Progress, TaskID + +console = Console() + +# Tesla Compositor Configuration +COMPOSITOR_BASE_URL = "https://static-assets.tesla.com/v1/compositor/" +IMAGE_VIEW = "STUD_3QTR" # 3/4 front view +IMAGE_SIZE = 800 +BACKGROUND_OPT = 1 # Transparent background + +# Model configurations +MODELS = { + "m3": { + "name": "Model 3", + "wheels": ["W38B", "W39B", "W32P"], # Aero 18", Sport 19", Performance 20" + }, + "my": { + "name": "Model Y", + "wheels": ["WY18B", "WY19B", "WY20P", "WY0S", "WY1S"], # Various Model Y wheels + }, +} + +# Color codes - common across models +COLORS = { + "PBSB": "Solid Black", + "PMNG": "Midnight Silver Metallic", + "PMSS": "Silver Metallic", + "PPSW": "Pearl White Multi-Coat", + "PPSB": "Deep Blue Metallic", + "PPMR": "Red Multi-Coat", + "PN00": "Quicksilver", + "PN01": "Stealth Grey", + "PR00": "Midnight Cherry Red", + "PR01": "Ultra Red", +} + + +def build_compositor_url(model: str, color: str, wheel: str) -> str: + """Build the Tesla compositor URL for a specific configuration.""" + options = f"{color},{wheel}" + return ( + f"{COMPOSITOR_BASE_URL}" + f"?model={model}" + f"&view={IMAGE_VIEW}" + f"&size={IMAGE_SIZE}" + f"&options={options}" + f"&bkba_opt={BACKGROUND_OPT}" + ) + + +def get_output_filename(model: str, color: str, wheel: str) -> str: + """Generate the output filename for an asset.""" + return f"{model}_{color}_{wheel}.png" + + +async def download_image( + client: httpx.AsyncClient, + url: str, + output_path: Path, + progress: Progress, + task_id: TaskID, + description: str, +) -> bool: + """Download a single image from the compositor.""" + try: + response = await client.get(url, follow_redirects=True) + response.raise_for_status() + + # Verify it's actually a PNG + content = response.content + if not content.startswith(b'\x89PNG'): + console.print(f"[yellow]Warning: {description} - Not a valid PNG[/yellow]") + return False + + output_path.write_bytes(content) + progress.update(task_id, advance=1) + return True + + except httpx.HTTPStatusError as e: + console.print(f"[red]Error: {description} - HTTP {e.response.status_code}[/red]") + progress.update(task_id, advance=1) + return False + except Exception as e: + console.print(f"[red]Error: {description} - {e}[/red]") + progress.update(task_id, advance=1) + return False + + +async def download_all_assets(output_dir: Path, dry_run: bool = False) -> tuple[int, int]: + """Download all asset combinations.""" + output_dir.mkdir(parents=True, exist_ok=True) + + # Build list of all downloads + downloads = [] + for model_code, model_config in MODELS.items(): + for color_code in COLORS: + for wheel_code in model_config["wheels"]: + filename = get_output_filename(model_code, color_code, wheel_code) + url = build_compositor_url(model_code, color_code, wheel_code) + output_path = output_dir / filename + description = f"{model_config['name']} {COLORS[color_code]} {wheel_code}" + downloads.append((url, output_path, description)) + + total = len(downloads) + console.print(f"\n[bold]Tesla Car Asset Fetcher[/bold]") + console.print(f"Total images to download: {total}") + console.print(f"Output directory: {output_dir}\n") + + if dry_run: + console.print("[yellow]Dry run mode - showing what would be downloaded:[/yellow]\n") + for url, output_path, description in downloads: + console.print(f" {description}") + console.print(f" -> {output_path.name}") + console.print(f" URL: {url}\n") + return total, 0 + + # Download with progress bar + success_count = 0 + + async with httpx.AsyncClient(timeout=30.0) as client: + with Progress() as progress: + task_id = progress.add_task("[cyan]Downloading...", total=total) + + # Process in batches to avoid overwhelming the server + batch_size = 5 + for i in range(0, len(downloads), batch_size): + batch = downloads[i:i + batch_size] + tasks = [ + download_image(client, url, output_path, progress, task_id, description) + for url, output_path, description in batch + ] + results = await asyncio.gather(*tasks) + success_count += sum(results) + + # Small delay between batches + if i + batch_size < len(downloads): + await asyncio.sleep(0.5) + + return total, success_count + + +def main(): + parser = argparse.ArgumentParser( + description="Download Tesla car images from compositor service" + ) + parser.add_argument( + "--output-dir", + type=Path, + default=Path(__file__).parent.parent / "app/src/main/assets/car_images", + help="Output directory for images", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Show what would be downloaded without actually downloading", + ) + args = parser.parse_args() + + total, success = asyncio.run(download_all_assets(args.output_dir, args.dry_run)) + + if not args.dry_run: + console.print(f"\n[bold]Download complete![/bold]") + console.print(f" Success: {success}/{total}") + if success < total: + console.print(f" [yellow]Failed: {total - success}[/yellow]") + sys.exit(1) + + sys.exit(0) + + +if __name__ == "__main__": + main()