diff --git a/android/app/build.gradle b/android/app/build.gradle index 35efa81..43a6c8c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -78,5 +78,7 @@ dependencies { implementation 'com.google.firebase:firebase-analytics:18.0.0' implementation 'com.facebook.android:facebook-login:5.15.3' implementation 'com.android.support:multidex:1.0.3' + def billing_version = "3.0.0" + implementation "com.android.billingclient:billing:$billing_version" } sourceCompatibility = '1.8' \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7f52f63..a80fc66 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -50,12 +50,12 @@ android:value="2" /> + android:value="@string/facebook_app_id"/> + android:label="@string/app_name" /> diff --git a/asset/data/promotion_codes-month.csv b/asset/data/promotion_codes-month.csv new file mode 100644 index 0000000..86abb63 --- /dev/null +++ b/asset/data/promotion_codes-month.csv @@ -0,0 +1,200 @@ +Promotion code + - A7DXXDNMKXU0KUJAEW70PRF +2HLKJ93QP0TTGYFB4EL383L +VDZBBGVMUUZ9PF2HTPG7PJ8 +V7RF0U3H6T0CY5LXX6AH7A4 +D63MQ1JPW6BQ4P0ZQM9UNQV +ZDT7SSQB01FG4LCE67APADL +U6953FSGRD54HYSAP27BQSA +P0EHCGYHBM8PCVDY3AETGBY +RUQX8TPKLT83WZ102EQ18B6 +LZ7MD5N30VQ6RD13J7NXHLL +02FR52AH4WSANVMFGNQLBHX +NLT4166PUJYW6ARAX76VK6Z +TT6J3EBD56YZEK5T7R48ZB7 +PJ9APMP07V716P0MZQ1QQZ4 +1LAEA5MAVZCQ6Q6V746VWHA +UR80FUSC88S4ZM6D7F2LAHX +QGSTE9Z6T4ZTBR7D18ENZZE +42F6RJ8UC0JGTJ3J3SPAW51 +Y1Z3S9D375EW5QG0KDWHRN5 +5KSKAP7JMSZB8KFWQCNJ2EP +RFPKVGG2HUTLJDS293SK7T5 +6AH8L8V6ZDXRFDGWLD7XC1R +L34MAXPGWBPHHEDRJ6J56Y2 +ZHD36F0FSPQ0302UG9JG4SJ +A534UX0580PET9ZLRJGSJBN +H8DZKTCYBXX3Y03UGQFVFFL +2EN7JH4A3KCJB2H49WBT4NX +XKP4HLPL738ZWAHH0DFPV73 +QA980ELAHDVJKAAALCSX3EC +RRXA7TKYR8T0GRUS7QEBMU6 +3E9K4YZ4JG6UM9A03LQYX8Z +YTYYQE4B1HNCAPVEWHAHBYN +TAUZRSDGB8HT4JHWT04R0T0 +2APA0FT1DH0XM35PESM7YX0 +CVLTAJM0NF1GX46FEYCKXR1 +A8GH58V40TSC6L86ECWRNBT +QJ37ENK3TK3FTZA250DGS02 +TJJWKUMBQRPTT8VWZDF4W9X +CM6DDDUYVCGSGXZ36D5N6WW +AZMUZ9EPL0JBHYKGRK3MCK0 +12MV9LC4BE4KGEDEW5X2MEM +41F0GRVWJSLEAUAY0ALUN1V +PPPHG266FFF6QBGJSL8WBDF +MGP71PUFZ6NKLPP4VF8DS3T +LXA6SH75WZ8KY4BEUV0S85U +447FRJGYW2902PLLXZYTRAU +D9EMMELU70PXZ8AKUZ56WCZ +CZ4VPRSD6RFCGRDM1SVU2BH +1ZTL14ZD7GHVB6B0NQMTCX5 +SA5W6ABBE74VUJV077C9A7E +WB9EMATS4DHY3N2QL8WPXGB +XQ368K414VJVVAS4G0EBT7C +KGXV8WTQZ39WUUFYUJQXS1Y +KW9920H2TDVJG9M6WVH2MBZ +EZUSXBLAT69K7MBYUULPSUY +3ZW2R3HFQB0GT27253L530Q +WNWUYXYZZLVT5EK5NH4UMB7 +7WSV8KB1DH5XEBE9ZBTVSVF +0Q3USEXJDRN219V66275B4H +U6LKPXG16Z83A1RA320P0U6 +PAV3UQXD61NR528X1GND42R +6NNZ9MVWZESFPHV0VBQBMNN +BD2XLH4HKJWCGPY9C1AJGNW +RQTLUQ1ZZJZBET76071WT89 +RBKQRGP9E6QGUX3KAJXF20K +UGM5QUE0EXKEBE2A75ECNSM +N2P0H1LFS2YEKX64D2PWDN7 +P6VJLEAKABRGFVR4T5DH1X5 +TJ815RNL5A460XJG4THR9JL +YL0XY8R2TBTP5N5F9S3VWSV +DU32C5H5U4GLT0509DS9G2K +9DXV8W7QU9L63EY7XJVYTQ7 +UXNLD05CKKX20YKKNLVJ4BS +VYDMRWTW5FJ76XGLSML71QU +MBJJHEJPTRY2HQXLLPJ51XR +XM63E19C75AJUBHH98D7W6C +PTAU2W57H36U9U547UUND9G +F1MZVKQ0Q4186X2JAPKW4RP +MJRP26W2QM6XKUJDKFXX6AY +AFN2GK30M9XNAPCF6GA1PZY +GMYZDKLK428WS94GYHSW97U +WVJHNTJE2USGWYAUB2G2ZNK +DPYWSJETW05X990Z0U2MWBS +S8U1061Q6LRZNGWHTGJ04U0 +HTLC06BVKKBKS7SBVK26J45 +A91PFKAHJMMXFTAHRUB9ENK +KXPJ0AV2QURS3T9Z3HR0RN8 +V24WFCGMMQ1X38EUXXAVCGW +263NEVRL247Q3D82PU5SFDK +Z4QPPMN3J2T12VTGFVEZ2K7 +29F4W2RCAWRMK9P7KWRQ0WK +LHJEC87AM9TRC0C0JX3XLGX +PFSML2E11VJEB9GJ9SR1T36 +ZECQPLLUVX3QR61WMUX42HA +K8MJAX3THB3FCWZT9VB2GSV +HV4NHY2E3YJ836HH5KKU9Z1 +H6VN3YCXJYL5KR63QNGUX1C +MDLNPC7H171QQN2C4SU0MZ7 +48HAF1RBHFWC2H8ZUHU8MGG +48Z1MS05VFAB5ZKDEYB17TA +V5LGP8113UJJ1SEWC5B5GTE +RR5FP5CY1JB2WB9KY0YRKJR +AYGQQN8FFPGY557PG6VGTTK +PXT14ECUUR2MYP7XH9LYUQ6 +FED9VS405XMWYHW2P14NGJN +V5NBC31P2B1A9D536ERVBYH +AUHL02U2UDZY9N25Z0QE9C8 +JLSRMKPW82XY5EALCPT1P7G +JM2BQUT9ZM3DNMDLVPATZGP +C26KUVKFHL0VJ03H95G6F2Q +LBLDHBEA63LH8S97SBLLA2C +GYNB2CCAU6QLGW79N2UJ4UT +1KPWT2JNULPBE3B9H8FC8NL +EPDC7FRE705861ZMRJ71BV4 +9E0CDJTDCG5HD7C2LWPFDS7 +D72UZ0B4EUBZE8C9ZNX7XVP +W2S98ZA8BW5NJ7RKHBSEE7P +HMCV6KNUVY3Q4WGM3ZVRKNY +U0BQEC40RZ5CDLQH26PP4YT +MTLLSJJN4LQVRS7JFL2Y2K6 +Z0G8BJ8NS9U8HYBHN3N99AE +9J14SSRZAMZ31DG1ZXNVNNV +WPS91QFT25LT7YXVYPG75BP +G3A4HNEVT5CH077FWZZUK88 +QDKUWSRJR5K6Z97C4ZUGTWH +5YZTDJ1J85QXZH6TXX3TDDQ +ZM500MPBGWU0F2VHQQUKZ5T +TTF0R47WTKWPJT4LATUKWHQ +JS5A5F0RCZRVB971F2LE2HS +1D8A1XVA3SKFCFWRSXQYRFV +K3LE2X4GAL1MYPR9RY9EN6G +24X5VZDJD3YZUABYW6YSVSC +9C3R9XDFL24D7CTZMFH2CMN +VX4B1HLX8VFZS2MPNN094TZ +4Z7EKRK9YC3T130V0QWMKWL +8AXQF15N9VESKNQRVXMGG87 +79T0R5HWKK91LUKJ0PMNDCB +YG5ZGFAAWC9ZD8SM1HARLBL +BMD30152E4L9WUL0Q5V7AF3 +R40VFDKQH0SVB3DJK170ZAZ +D74L2A1KJED72TZ3DWVH8ZP +0WT613SN6Y4K3J2G77D0EUT +W7EH5EV6KZ5T8TA0DRSYCED +U8HDE2HVB09ZB9TN4KAEAPR +JV75U798X715X36PN2PE0D3 +UUD5YAN2J2UXG15LBHHCP7G +W1W9C458C72XM1ULP1BVXPR +LR0UF5R33VN4DZPJUGQA0CY +A9WFKNJAW5K0D1SJNVY2DAW +UZ8TNXA91J8GASZEG3GXZQM +05DZ47KFJKPW6FRTJB1408J +37XQMR4EJ7RLDDDD3PZWWXY +NVGJTTW0GZCYKC07BDVQHVR +7MK2P0CJRR4DNVLY0K564T4 +L4S2NBED58K86MK7C9Y42ZT +LMB4TR1HPB3C6F6HDWKJ4A2 +NRTFR3Q4K4EDHH8WM2ADAKA +QLPLCN13Z2HC8ELBJ2HWZWR +KCWQE1KNVC3KW7XZGRVAU5Q +69GSLNNEC0N3FU4CA09EEGE +LQLPJR7VXCNGQZHZXM3Q09E +ZABXFHVGNKMBSF1KZD1JYGD +MH29T4T2BQW3BCU6BEZVNWB +SG0FS4R1W2TSG6V7ZU00UVD +5R99FUV2X6DU0QQFMACMV37 +AMPHQR8S0Q5D85XFZXS9WM8 +AU1325QFCDMD92BVJ1UNKUB +R9X4QMJ11NA9THEXPSCFCES +5MPD73H81E0ND5PAF9QTTA1 +R7UGAY5BBNP7PFBUK9F0UB2 +PLRAMYCRMTM8ENT7LDXZG4R +U4MJ3WGBZ4P04G14C0VPF1T +CFNQEFSUEKN1FLN9LMF5T7W +JBNZREJAFVY82LMU09H59NH +FH641QN4A5R139B1XDVR95H +71WJ7TL1SGBR1X4HK01RB3A +53SQJPFBWWM2QPHD7B5Y1D9 +HP56RB9QH0N0KFQDXDFJN4J +YFNL1WXSLQLBGY8H6H7SQKZ +N0K4B0CRE8XGFQ6R2J258XE +6E4MMLDN7PKYKVBC5HY84UB +LH4B02LJE3V4QWNBC92P36T +46K1R5XQJ8DNFL2S9PSVE05 +KTNP7KXY8P3BH4GJCBCHAW6 +WFEX4UK60WTQDJSNQ2BSJRM +LMVKNTH5SVTV4B6LNNZW3H8 +CWGQBAF6AZ82NU8U47MKE7Q +WNYXQQX0WEEVQTF8JH690S1 +5WJA3TF43HKWSBUUNB9F1A5 +CL78PV3T9JTFDNF1Y9CA9YS +NZPF8XE8562BCPQ3E4BLMCT +HHZBNN0US3LG1MUR6A7U1L4 +40ZS9086PMMYHEZZCMD8E19 +LJZXQ3JX5LNSBLDA3B2HAZZ +2KG86G8HC09KQ6AG3NP658C +KEW6N6ANMUABGEP6YPEAHH2 +V2TGMTNW2PKSSCR9J7V0GU8 +X40UEV1PEN3FJY7WXVS3M57 +QXYMX3Q8J48KN0SSMKK6F7V diff --git a/asset/data/promotion_codes-yb.csv b/asset/data/promotion_codes-yb.csv new file mode 100644 index 0000000..02dd2e8 --- /dev/null +++ b/asset/data/promotion_codes-yb.csv @@ -0,0 +1,101 @@ +Promotion code +-- KJM6GH5NTDDW12VDWK3D7AU +-- 0CZG2AWS41G29VD2S1C3FBQ +9H499GCQKXKEPMWTE6NYJ4L +09KHJXX8Y4AE3A4DPZSCZB0 +HT7GJR81R2Q5XWGD81E0ZY1 +ZJNPW2ACQQQVJDPDNG1JYYR +Q8BTA7DTADKF0S7YR8F6EKM +ECZRD2BU74JL3T94ZD04C4Z +NMLGBZP92MN9VH15FH4PJJK +PSUPGVX1BFPDAM5BPCS3FQQ +40PJBGMGJR0UHLEWBNWF32U +W7N5D66YFKNNRXE6CJNXDT7 +A1Y1V52C3WLUXFFL4F42LVZ +FM7EZK3HH6TN5YE702Z9AM5 +D5YSSBDF982P88YBGUNLFWF +JCY6JHSXGP3XVT471GS32TB +RMAH0VTA38WGR0YECTE6H09 +DTLSFDX7ZAA4HPEJJCKEZF5 +P8ZR8821XMNTMFN66DYG825 +THHUYPZ17EV00G2NM84EJWR +E7ZHT83XN6KCXPQ3KRXCL0D +R8BDSFN2F4GLNZU2V5QMSFF +7VWXPULDVL9LGM7TZRKCBQP +BD4UA5YKAV29CA8GN014RDQ +4W8GX1EKCVEH17SYZ9MFX85 +SZ8DL9G6SCMCHMS7JA7HCDT +D383P1Y0RCN5M49G454XJ4G +7DC9FKU6PLYL7ARNL9FUMBX +80RFXJF8ZHC6S4X592BTDR1 +VXJ9T0CGE2RLPSCRHGVDLK8 +W7LGSSRMEGGLDDT568WBMUT +Q5RG5QTLG0N285KAD43QGNN +QUYKWTNZ9MBQ1EL49WS2FB6 +GEQSQ8W0SHDK52RJTHBS45E +DZNBW0L2M6MF8JCKVT15SM9 +KHL0A6MKBY4WF4KGYVSEXU9 +HWEVEH5QS74RD06XGSXUP4R +CF1HCXMYWNT0GEK757DR7QJ +38NSEXPC1XG573M6FVPU1DS +RZ1QJ6TA68N0SPUT0FM6TYR +SRHCQJ4ANJKLHH9WV127VTU +RYD0XV94L770S9W1RW22AZY +EM6TWEC71ATHZRU159H577W +BZPJDHBUA0F37ULKUYKX3MD +SL5PSDXWFKMWRSB1VTVK982 +H630F22CDLKGTE7128ZSR7V +FXP9FC311KSFDX87KA7NDNS +4ZQX57DTQ82T7B2A113RRA2 +GF0X3UK29CRPFUSMTKJY7VJ +AHSZLDVUCDMXXC840DDERCX +T9C5PVP0AMS48UW88L9Q1BQ +G6SKQQK9EAVJF1TM4J2SK4H +3Y00PYF2QKRQN4ZNZVWERGH +QCSEMNG7G0704UHE93KENLH +MUB9P4Y1F5VLPTSGRT6E1ED +KE9EC1JH63LBX79BHX1WQFH +PD16F8BZZRC94TFJRQ3L3PG +ANQDQ2KJTVQQTYXPAUELB1S +RAXT8YFK3LAALY0HLWX9KAQ +4CR33PYZZ7RGKWDTCFD9XVK +NWV7CJX6AG78NR7769D190Y +EC490NHL19NZV0GF76TLQXQ +9E42VMUFB13NU6N8QGL2TYT +ED99UQ1VFVJSHPGXC8ERAEA +XZU9XZK7RKL8GRTFS5LUDXD +677FF9GZVHEYWC13N5HGSGD +NJVAWAYGEZAL6JCVB0D43CD +CJ4JGJZQW5JVLN5BDGKGLT6 +VHB93V8U4ASWS78ZUYZCN19 +3JGM2U3EPZ8MW4TBZW7H53G +2FYZ7G4JRWEMZMDPQQSJUTV +5E3C44SU9R3YLBJDGR3GQM8 +S1X2HWGFDDL61A48EY777DU +5MHRAX98UTQQ372ANJ3WJB0 +X5C7UB37VQ3R4PFT38FDAA5 +PFG41S3LRSXPQ9ZFMKFTGDQ +TRXZU2HEVR1TPXXQEGNU611 +0C4ZVMKCHVJVKASB78X6CYL +ZVHV1LA2JZXD90CEP375W8R +R9TGDA50REYPP4FKCF9GK6H +DGDYA7XVR0SLX6CWWMSVVDB +65XLN0P4LBQ6J81VJ1JD0CH +RYV7SZLD8B6N92QA8V4PT5N +RDK07CND4QR8QRCWNZMEMJA +NKNJ09BPS7M4E4CJ4H3WUX7 +554X7A7Z0F4MPS8NX8H5GLG +5XQZ5L9Y8BTB2C4DRQKDW53 +9T9KK0DUQQL7Z3H19HCFM4C +ZBF62Y2PF1M5T2B69PEKECR +DVG93BR8BVT0DW3V10USX3H +4DUQ0CAMF0S08YBVWTG4Z6Y +0EC19Q18NTVE1S8NN7EWFSQ +L9617J33F5GU2LRK4HQ0AY3 +D8MHJSK6E86QCLFYFBMJ63K +AJ8ZQK2SYGK6YK4ZWAJPV5H +WL7NJ28NRYQ4J36F13HX65W +VP5MTQZB78ZGJ5SKUQYJ67Y +6DL9WDQ3U1A8A9Z30B05KZL +VXF8A7QMEQCLQAY9P7B5EZA +KCNPFPUHRFXPZWCACFQ8YW6 diff --git a/asset/data/promotion_codes_ya.csv b/asset/data/promotion_codes_ya.csv new file mode 100644 index 0000000..710c3fd --- /dev/null +++ b/asset/data/promotion_codes_ya.csv @@ -0,0 +1,101 @@ +Promotion code +DULPYTPABG7Y51GYL6A8Z72 +8A8PH0M87ESVL2YTMXXFVKJ +8X2976ZLFR61SP85NGVK0AA +8CDC60CU9HLBNJEQ9F2QPC7 +VSHTMHLJTBAV731JX52MNQ4 +C302XQN62ZU8F27BQ1RBGT9 +6FDAGQVCGZQSCA56XXA08DM +KK0KBEUGWW3521NA31GV3T4 +PAT2TF5A44RAMD883ECHYCG +CGRHCDVKA9Z8D7RXV97BRPY +E1FQ4V968KYEU6JDQ8TQEAT +NEH6BUEAT9UCHAX1QJD0CJJ +QHLBD3GN473MHT30QAAD0U8 +2XK0LYH77U6EUQ3NM8M2U1C +CG6C3H6BK4RZAFS9WF46YU0 +BY6Y07P0AYT5TH2VQ8X267N +9C5VW5X5DHHKJ8Q40ZM81EG +P5Y8K895CP4AVBYVDCG5D60 +T5S0M4LN7SAX27B4RKDGKQ3 +6G34MCK50FNLZ8ZUTWLF5AJ +JZD22RSD7WRKXLP8EDBWH4U +7V4WT93MFY03AB76NJH8SXJ +CY4VKZ74MYUEM3KTGL0LHLT +XP4U1930D44QAUUEM16PLCT +U5M7FPV2MHUA6VEJN2XWVKY +D6EC4QWWXDYUULAW8QT8AHF +LJ4NJAY7KCM25WT0A25BPN6 +K6SQLF2C9APJBZF04BRDNH2 +1C8L76C512HBULG3NQUA84K +H1VR3131G5G2ARSLGVMM4GF +7AHJ0EB97PA4XY1GLTVTTHN +AUWZGVK4ULY52210Y8ULD30 +D0JY0HW6MLHAPCKBQTMVFQ3 +TSE722BT94HNSY3GRV28UN1 +H8Q1EAYYS5JDX2KU4J3P344 +U3K5S83M1ULR0ETL8XFJ8VJ +XEG86F51SXXY5P2HZG49H58 +8HFS2KE099XZMHYEDEQN84Z +3GUH12E4V5RYV9J8ZMBETZ8 +DHKTFWBNJ7R1CXV2E41K3CK +49KK8J5Y1K2LPERB6G09P9M +VXYYBTMLPXAY4EGUENVLAZR +5AHZGKA02K4717MD2A94EPJ +7DGNRVD16Q2GP5N9XL3Y0EG +7LG8U55NRA6TKRT3VPY1RME +S8RY3KSDJPMW0Y26VEC4RVG +WS043ADBZ4HHZ57U9SZRXUP +K7YET0Q249XNFH7NX8GX575 +UEJCE8CMDSUKJNBTVN9NUBA +UDQA1SY0L9FRHZBKR8GHWEN +XM4K7S04G018P3NQHS8TV2Y +9LJT7CSYTMJALZ6L7TUGP3R +SRN1UNBW6BSAYUFFJ7MQ690 +7XHX75BCBMK5R1WKHDFJX2P +RULRS8H8LKFZY8VVMRRD8DM +N1Y3DSPBCC6CZTKP5NUMZ1D +H4RAWV8Z8ZCS80QQMQ39AN9 +URF18Q5GTQ0A5WJG0MUCJ5G +QPBHN1B2JNQB2THW2NWDMS5 +C6N0Z1D7SA7H9HLABSA2W5Q +31CQANBKBPPUZ8FLHCYJ2WB +GHGJV049LN066UYM239K4SV +4NDWC74YQJKFJZHSHP6N4YG +904EM94E8VEFLWWLT10W7C8 +HRLLGB3QHCW8PG75XWL9CV8 +SHEYF4F7B5F6LGLWGXYMBKV +WABU07E42Z3FK2V06XLDQDC +40KX6L2T1R839HWQHF8UWWU +Q8XAXGQEZ2V847WMYKCV91A +LK1MV8G85EV51XA43HVW5AA +U5HB1KMVS1AUD21YE4ZXDS5 +5Y5SKU4MP2QGTUPKG3LEL8K +FC8VS4S0SZ1VJUH4SNDKAQ4 +WUS35TBS78VWS4SSJQPG502 +2FZQLEF358H0LA2HGQAB57W +KQVC3CHDNPDR1ADVDNAZDCH +0AFT33YM8X7RTXT07KVVN21 +GP2M6QAM3G18MBC2ZYD73RL +GFKSN039H70CYT70JM81SKQ +2ECADR3TXBJ2CUWF6WKDF7L +U2V944D5TB4TZXG6JCD003P +PHG6QP4YT5TJZ75F58QADQ8 +GUVJVYANS8HPWQHMTNGHXGA +USAL3QBLYWPCC0JXDQGK263 +V5M18UT9L2Q3EZ095TUAPQX +W3UCUV9WW5HEG4UBX1M0U58 +YBSM2K271EJHJ23CSD6HS04 +HT8RFH4TMX9LJVA3FJDNRCG +9HDPRJHG15E5275PDKXAFB7 +R95YEGEU7U8DR6DQUYMBKTH +3LCXQ54B24GH4K5WU6G3DU9 +NHHTHNT2C42CARLX2RLFMUS +WB5EW890X513DLFEW0QZX5M +WDB6FSAFZ8JXLPKUF2JLWRB +346AWN92NCYTK12NNV1SYMK +V0TQG3F0XYBDPU946C13LMQ +SA4HAPXW1TPVAFCG5P9H6Q3 +SMJJE6RY9W6Q6HWHEL7YSHU +AHA16TP02C6NS83A8DSA9B2 +NSJVXY2H026YQB3ZJMZLY2E diff --git a/i18n/en.json b/i18n/en.json index 48fd81d..c324114 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -9,7 +9,7 @@ "Logout": "Logout", "Tests": "Tests", "Change Language": "Change Language", - "Password too short": "Password too short (at least 6 characters)", + "Password too short": "Password too short (at least 9 characters)", "Please type an email address": "Please type an email address", "Exception: Please accept our data policy":"Please accept our data policy", "Please accept our data protection policy. For more information please click on 'Privacy'":"Please accept our data protection policy. For more information please click on 'Privacy'", @@ -27,6 +27,8 @@ "Please log in": "Please log in", "Exception: Customer does not exist or the password is wrong": "The email does not exist or the password is wrong", "Exception: Customer exists": "The email address has been registered already", + "Exception: Please type an email address": "Please type an email address", + "Exception: Password too short": "Password too short (at least 9 characters)", "There is an error: during registration:": "There is an error: during registration:", "Please select an exercise": "Please select an exercise", "Cardio": "Cardio", @@ -162,7 +164,7 @@ "Edit My Custom Plan": "Edit My Custom Plan", "Suggested Training Plan": "Suggested Training Plan", "My Special Plan": "My Special Plan", - "Star's Exercise Plan":"Star's Exercise Plan", + "Star's Exercise Plan":"Celeb Exercise Plan", "My Trainee's Plan": "My Trainee's Plan", "Execute My Trainee's Training Plan": "Execute My Trainee's Training Plan", diff --git a/i18n/hu.json b/i18n/hu.json index 79963ee..8c82843 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -9,7 +9,7 @@ "Login": "Bejelentkezés", "Logout": "Kijelentkezés", "Change Language": "Nyelv", - "Password too short": "A jelszó min. 6 karakterből álljon", + "Password too short": "A jelszó min. 9 karakterből álljon", "Please type an email address": "Kérlek írj be egy email címet", "SignUp": "Regisztráció", "Exception: Please accept our data policy":"Kérlek fogadd el az adatvédelmi szabályzatunkat", @@ -29,6 +29,8 @@ "Customer does not exist or the password is wrong": "A felhasználó nem létezik vagy a jelszó rossz.", "The email does not exist or the password is wrong": "A felhasználó nem létezik vagy a jelszó rossz.", "Exception: Facebook login was not successful": "Facebook bejelentkezés sikertelen", + "Exception: Please type an email address": "Kérlek írj be egy email címet", + "Exception: Password too short": "A jelszó min. 9 karakterből álljon", "Exception: You have a previous Facebook login operation in progress":"Az előző bejelentkezés még folyamatban van.", "Exception: Facebook login cancelled":"Facebook bejelentkezés megszakítva ", "Exception: Facebook login failed":"Facebook bejelentkezés sikertelen", diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index cdc9f4f..b27c0d8 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; BB69292B2521AF45001FBA4C /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BB69292A2521AF45001FBA4C /* Launch Screen.storyboard */; }; BB81345024BB4BE10078D9A4 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB81344F24BB4BE10078D9A4 /* GoogleService-Info.plist */; }; + BB8D3BFA25A8CBFE00BF29FE /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB8D3BF925A8CBFE00BF29FE /* AuthenticationServices.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -48,6 +49,7 @@ BB43773E2540715900D74BFA /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; BB69292A2521AF45001FBA4C /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; BB81344F24BB4BE10078D9A4 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + BB8D3BF925A8CBFE00BF29FE /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; }; D5EDDC52125075FB9E21AD35 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; F39E6E227EB942E5663A6086 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -57,6 +59,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BB8D3BFA25A8CBFE00BF29FE /* AuthenticationServices.framework in Frameworks */, 42B6B159AF35AFB6DE777DFB /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -77,6 +80,7 @@ 3ADC50290ED054951FAC1F56 /* Frameworks */ = { isa = PBXGroup; children = ( + BB8D3BF925A8CBFE00BF29FE /* AuthenticationServices.framework */, 09BD889296C5C90D989820C8 /* Pods_Runner.framework */, ); name = Frameworks; diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements index 903def2..f166400 100644 --- a/ios/Runner/Runner.entitlements +++ b/ios/Runner/Runner.entitlements @@ -4,5 +4,7 @@ aps-environment development + com.apple.developer.authentication-services.autofill-credential-provider + diff --git a/lib/bloc/sales/sales_bloc.dart b/lib/bloc/sales/sales_bloc.dart index 8e26f1d..b37c91a 100644 --- a/lib/bloc/sales/sales_bloc.dart +++ b/lib/bloc/sales/sales_bloc.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'dart:math'; import 'package:aitrainer_app/model/cache.dart'; @@ -6,6 +7,7 @@ import 'package:aitrainer_app/model/product.dart'; import 'package:aitrainer_app/model/product_test.dart'; import 'package:aitrainer_app/service/logging.dart'; import 'package:aitrainer_app/service/product_test_service.dart'; +import 'package:aitrainer_app/util/platform_purchase.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flurry/flurry.dart'; @@ -26,20 +28,46 @@ class SalesBloc extends Bloc with Logging { try { if (event is SalesLoad) { yield SalesLoading(); - Flurry.logEvent("SalesPageOpen"); + //Flurry.logEvent("SalesPageOpen"); + await PlatformPurchaseApi().initPurchasePlatformState(); this.getProductSet(); yield SalesReady(); } else if (event is SalesPurchase) { final int productId = event.productId; trace("Requesting purchase for" + productId.toString()); - Flurry.logEvent("PurchaseRequest"); - //PlatformPurchaseApi().requestPurchase(null); + //Flurry.logEvent("PurchaseRequest"); + PlatformPurchaseApi().purchase(getSelectedProduct(productId)); } } on Exception catch (ex) { yield SalesError(message: ex.toString()); } } + Product getSelectedProduct(int productId) { + Product prod; + for (var product in this.product2Display) { + if (product.productId == productId) { + prod = product; + } + } + return prod; + } + + String getLocalizedPrice(String productId) { + String price = ""; + for (var product in PlatformPurchaseApi().getIAPItems()) { + if (Platform.isAndroid) { + print("PlatformProduct " + product.toString()); + if (productId == product.productId) { + price = product.localizedPrice; + break; + } + } + } + + return price; + } + void getProductSet() { int productId = 0; this.tests = Cache().productTests; @@ -51,7 +79,7 @@ class SalesBloc extends Bloc with Logging { trace("Previous ProductTest: " + tests[0].toJson().toString()); productId = tests[0].productId; for (var elem in Cache().products) { - Product product = elem as Product; + final Product product = elem as Product; if (product.productId == productId) { productSet = product.productSet; break; @@ -63,8 +91,11 @@ class SalesBloc extends Bloc with Logging { trace("ProductSet: " + productSet.toString()); for (var elem in Cache().products) { Product product = elem as Product; + if (product.productSet == productSet) { productId = product.productId; + final String platformProductId = Platform.isAndroid ? product.productIdAndroid : product.productIdIos; + product.localizedPrice = getLocalizedPrice(platformProductId); product2Display.add(product); } } @@ -76,7 +107,7 @@ class SalesBloc extends Bloc with Logging { productTest.productId = productId; productTest.customerId = Cache().userLoggedIn.customerId; productTest.dateView = DateTime.now(); - ProductTestApi().saveProductTest(productTest); - Cache().productTests.add(productTest); + //ProductTestApi().saveProductTest(productTest); + //Cache().productTests.add(productTest); } } diff --git a/lib/bloc/session/session_bloc.dart b/lib/bloc/session/session_bloc.dart index 281a1be..5f866bf 100644 --- a/lib/bloc/session/session_bloc.dart +++ b/lib/bloc/session/session_bloc.dart @@ -28,7 +28,6 @@ class SessionBloc extends Bloc with Logging { // ignore: close_sinks SettingsBloc settingsBloc = event.settingsBloc; await session.fetchSessionAndNavigate(); - await PlatformPurchaseApi().initPurchasePlatformState(); String lang = AppLanguage().appLocal.languageCode; log("Change lang to $lang"); settingsBloc.add(SettingsChangeLanguage(language: lang)); diff --git a/lib/model/cache.dart b/lib/model/cache.dart index f81821f..7f68fac 100644 --- a/lib/model/cache.dart +++ b/lib/model/cache.dart @@ -105,7 +105,7 @@ class Cache with Logging { LinkedHashMap _badges = LinkedHashMap(); List deviceLanguages; - String startPage; + String startPage = "home"; String testEnvironment; bool liveServer = true; bool hasHardware = false; diff --git a/lib/model/product.dart b/lib/model/product.dart index ab29866..5acba9a 100644 --- a/lib/model/product.dart +++ b/lib/model/product.dart @@ -12,6 +12,7 @@ class Product { String productIdAndroid; double priceIos; double priceAndroid; + String localizedPrice; Product.fromJson(Map json) { this.productId = json['productId']; diff --git a/lib/repository/workout_tree_repository.dart b/lib/repository/workout_tree_repository.dart index 314ac6c..28c2e0a 100644 --- a/lib/repository/workout_tree_repository.dart +++ b/lib/repository/workout_tree_repository.dart @@ -10,6 +10,7 @@ import 'package:aitrainer_app/service/exercise_tree_service.dart'; import 'package:aitrainer_app/service/exercisetype_service.dart'; import 'package:aitrainer_app/service/logging.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; class Antagonist { static String chest = "Chest"; @@ -47,6 +48,19 @@ class WorkoutTreeRepository with Logging { Antagonist.calf: Antagonist.calfNr }; + Future _buildImage(String imageUrl) async { + String assetImage = 'asset/menu/' + imageUrl.substring(7); + //print("Loading image " + assetImage); + return rootBundle.load(assetImage).then((value) { + return assetImage; + }).catchError((_) { + String imagePath = assetImage.substring(10); + String url = Cache.mediaUrl + 'images' + imagePath; + //print("Exception: " + assetImage + " will be loaded from the network " + url); + return url; + }); + } + Future createTree() async { isEnglish = AppLanguage().appLocal == Locale('en'); log("** Start creating tree on lang: " + AppLanguage().appLocal.languageCode); @@ -65,7 +79,7 @@ class WorkoutTreeRepository with Logging { //log(" -- TreeItem " + treeItem.toJson().toString() + " active " + treeItem.active.toString()); if (treeItem.active == true) { String treeName = isEnglish ? treeItem.name : treeItem.nameTranslation; - String assetImage = 'asset/menu/' + treeItem.imageUrl.substring(7); + //String assetImage = await _buildImage(treeItem.imageUrl); bool is1RM = treeItem.name == 'One Rep Max' ? true : false; if (is1RM == false && treeItem.parentId != 0) { @@ -86,7 +100,7 @@ class WorkoutTreeRepository with Logging { treeItem.treeId, treeItem.parentId, treeName, - assetImage, + treeItem.imageUrl, Colors.white, 30, false, @@ -104,11 +118,11 @@ class WorkoutTreeRepository with Logging { } }); - exerciseTypes.forEach((exerciseType) { + exerciseTypes.forEach((exerciseType) async { if (!(exerciseType.imageUrl.isEmpty || exerciseType.name.isEmpty || exerciseType.nameTranslation.isEmpty) && exerciseType.active == true) { String exerciseTypeName = isEnglish ? exerciseType.name : exerciseType.nameTranslation; - String assetImage = 'asset/menu/' + exerciseType.imageUrl.substring(7); + //String assetImage = await _buildImage(exerciseType.imageUrl); //'asset/menu/' + exerciseType.imageUrl.substring(7); if (exerciseType.parents.isNotEmpty) { exerciseType.parents.forEach((parentId) { bool is1RM = this.isParent1RM(parentId); @@ -117,8 +131,21 @@ class WorkoutTreeRepository with Logging { if (isEndurance) exerciseType.setAbility(ExerciseAbility.endurance); bool isRunning = this.isParentRunning(parentId); if (isRunning) exerciseType.setAbility(ExerciseAbility.running); - WorkoutMenuTree menuItem = WorkoutMenuTree(exerciseType.exerciseTypeId, parentId, exerciseTypeName, assetImage, Colors.white, - 24, true, exerciseType.exerciseTypeId, exerciseType, exerciseType.base, is1RM, isEndurance, isRunning, exerciseType.name); + WorkoutMenuTree menuItem = WorkoutMenuTree( + exerciseType.exerciseTypeId, + parentId, + exerciseTypeName, + exerciseType.imageUrl, + Colors.white, + 24, + true, + exerciseType.exerciseTypeId, + exerciseType, + exerciseType.base, + is1RM, + isEndurance, + isRunning, + exerciseType.name); this.tree[exerciseType.name] = menuItem; //log("WorkoutMenuTree item " + menuItem.toJson().toString()); /* log("ExerciseType in Menu item " + diff --git a/lib/service/exercise_tree_service.dart b/lib/service/exercise_tree_service.dart index 1b83ead..7bbf7eb 100644 --- a/lib/service/exercise_tree_service.dart +++ b/lib/service/exercise_tree_service.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/exercise_tree.dart'; import 'package:aitrainer_app/model/exercise_tree_parents.dart'; +import 'package:flutter/services.dart'; import 'api.dart'; class ExerciseTreeApi { @@ -15,10 +16,28 @@ class ExerciseTreeApi { exerciseTree = await getExerciseTreeParents(exerciseTree); + if (exerciseTree != null) { + exerciseTree.forEach((element) async { + element.imageUrl = await _buildImage(element.imageUrl); + }); + } Cache().setExerciseTree(exerciseTree); return exerciseTree; } + Future _buildImage(String imageUrl) async { + String assetImage = 'asset/menu/' + imageUrl.substring(7); + //print("Loading image " + assetImage); + return rootBundle.load(assetImage).then((value) { + return assetImage; + }).catchError((_) { + String imagePath = assetImage.substring(10); + String url = Cache.mediaUrl + 'images' + imagePath; + //print("Exception: " + assetImage + " will be loaded from the network " + url); + return url; + }); + } + Future> getExerciseTreeParents(List exerciseTree) async { List copyList = this._copyList(exerciseTree); diff --git a/lib/service/exercisetype_service.dart b/lib/service/exercisetype_service.dart index 6afe41b..3d72e18 100644 --- a/lib/service/exercisetype_service.dart +++ b/lib/service/exercisetype_service.dart @@ -3,6 +3,7 @@ import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/exercise_type.dart'; import 'package:aitrainer_app/service/api.dart'; import 'package:aitrainer_app/service/logging.dart'; +import 'package:flutter/services.dart'; class ExerciseTypeApi with Logging { final APIClient _client = new APIClient(); @@ -11,10 +12,31 @@ class ExerciseTypeApi with Logging { final body = await _client.get("exercise_type", ""); final Iterable json = jsonDecode(body); final List exerciseTypes = json.map((exerciseType) => ExerciseType.fromJson(exerciseType)).toList(); + if (exerciseTypes != null) { + exerciseTypes.forEach((element) async { + element.imageUrl = await _buildImage(element.imageUrl); + }); + } Cache().setExerciseTypes(exerciseTypes); return exerciseTypes; } + Future _buildImage(String imageUrl) async { + if (imageUrl.length > 8) { + String assetImage = 'asset/menu/' + imageUrl.substring(7); + return rootBundle.load(assetImage).then((value) { + return assetImage; + }).catchError((_) { + String imagePath = assetImage.substring(10); + String url = Cache.mediaUrl + 'images' + imagePath; + //print("Exception: " + assetImage + " will be loaded from the network " + url); + return url; + }); + } else { + return imageUrl; + } + } + Future saveExerciseType(ExerciseType exerciseType) async { String body = JsonEncoder().convert(exerciseType.toJson()); log(" ===== saving exerciseType id: " + exerciseType.exerciseTypeId.toString() + ":" + body); diff --git a/lib/service/firebase_api.dart b/lib/service/firebase_api.dart index 07669db..a4233dd 100644 --- a/lib/service/firebase_api.dart +++ b/lib/service/firebase_api.dart @@ -35,6 +35,12 @@ class FirebaseApi with Logging { } Future signInEmail(String email, String password) async { + if (email == null) { + throw Exception("Please type an email address"); + } + if (password == null) { + throw Exception("Password too short"); + } String rc = SIGN_IN_OK; try { userCredential = await FirebaseAuth.instance.signInWithEmailAndPassword(email: email, password: password); diff --git a/lib/util/platform_purchase.dart b/lib/util/platform_purchase.dart index 02cbf3d..57d8246 100644 --- a/lib/util/platform_purchase.dart +++ b/lib/util/platform_purchase.dart @@ -1,5 +1,8 @@ import 'dart:async'; +import 'dart:io'; +import 'package:aitrainer_app/model/cache.dart'; +import 'package:aitrainer_app/model/product.dart'; import 'package:aitrainer_app/service/logging.dart'; import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart'; @@ -11,10 +14,14 @@ class PlatformPurchaseApi with Logging { StreamSubscription _conectionSubscription; String _platformVersion = 'Unknown'; List _items = []; + List getIAPItems() => _items; List _purchases = []; - final List _productLists = List(); - /* Platform.isAndroid + final List _productList = List(); + + List getProductList() => _productList; + + /* Platform.isAndroid ? [ 'android.test.purchased', 'point_1000', @@ -22,7 +29,7 @@ class PlatformPurchaseApi with Logging { 'android.test.canceled', ] : ['com.cooni.point1000', 'com.cooni.point5000']; - */ + */ factory PlatformPurchaseApi() { return _singleton; @@ -30,27 +37,32 @@ class PlatformPurchaseApi with Logging { PlatformPurchaseApi._internal(); - void close() { + Future close() async { if (_conectionSubscription != null) { _conectionSubscription.cancel(); _conectionSubscription = null; } + await FlutterInappPurchase.instance.endConnection; } // Platform messages are asynchronous, so we initialize in an async method. Future initPurchasePlatformState() async { + if (_productList.length > 0) { + return; + } + log(" --- Init PurchasePlatform"); String platformVersion; // Platform messages may fail, so we use a try/catch PlatformException. try { platformVersion = await FlutterInappPurchase.instance.platformVersion; } on Exception { platformVersion = 'Failed to get platform version for InAppPurchase.'; - log(platformVersion); + log("PlatformVersion" + platformVersion); } // prepare var result = await FlutterInappPurchase.instance.initConnection; - log('result: $result'); + log(' FlutterInappPurchase init result: $result'); _platformVersion = platformVersion; @@ -73,15 +85,57 @@ class PlatformPurchaseApi with Logging { _purchaseErrorSubscription = FlutterInappPurchase.purchaseError.listen((purchaseError) { log('purchase-error: $purchaseError'); }); + + getSubscriptions(); + } + + void getSubscriptions() { + Cache().products.forEach((element) { + Product product = element as Product; + if (Platform.isAndroid) { + if (product.productIdAndroid != null && product.productIdAndroid.isNotEmpty) { + _productList.add(product.productIdAndroid); + } + } else { + if (product.productIdIos != null && product.productIdIos.isNotEmpty) { + _productList.add(product.productIdIos); + } + } + }); + + _productList.forEach((element) { + print(element); + }); + _getSubscription(); + } + + void purchase(Product product) { + if (product == null) { + throw Exception("No product to purchase"); + } + String productId = Platform.isAndroid ? product.productIdAndroid : product.productIdIos; + IAPItem selected; + for (var item in _items) { + if (item.productId == productId) { + selected = item; + log("Item to purchase: " + item.toString()); + } + } + + if (selected != null) { + requestPurchase(selected); + } else { + throw Exception("product " + productId + " not defined in the PlatformStore"); + } } void requestPurchase(IAPItem item) { //FlutterInappPurchase.instance.requestPurchase(item.productId); - FlutterInappPurchase.instance.requestPurchase("WT_monthly"); + FlutterInappPurchase.instance.requestPurchase(item.productId); } - Future _getProduct() async { - List items = await FlutterInappPurchase.instance.getProducts(_productLists); + Future _getSubscription() async { + List items = await FlutterInappPurchase.instance.getSubscriptions(_productList); for (var item in items) { log('${item.toString()}'); this._items.add(item); diff --git a/lib/view/sales_page.dart b/lib/view/sales_page.dart index ab73a98..c0cf855 100644 --- a/lib/view/sales_page.dart +++ b/lib/view/sales_page.dart @@ -1,4 +1,5 @@ import 'package:aitrainer_app/bloc/sales/sales_bloc.dart'; +import 'package:aitrainer_app/service/logging.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/app_bar_min.dart'; import 'package:aitrainer_app/widgets/sales_button.dart'; @@ -8,121 +9,122 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:modal_progress_hud/modal_progress_hud.dart'; // ignore: must_be_immutable -class SalesPage extends StatelessWidget with Trans { +class SalesPage extends StatelessWidget with Trans, Logging { @override Widget build(BuildContext context) { setContext(context); - return BlocProvider( - create: (context) => SalesBloc()..add(SalesLoad()), - child: BlocConsumer(listener: (context, state) { - if (state is SalesError) { - Scaffold.of(context).showSnackBar( - SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white)))); - } - }, builder: (context, state) { - final salesBloc = BlocProvider.of(context); - return ModalProgressHUD( - child: salesWidget(salesBloc), - inAsyncCall: state is SalesLoading, - opacity: 0.5, - color: Colors.black54, - progressIndicator: CircularProgressIndicator(), - ); - })); + return Scaffold( + appBar: AppBarMin( + back: true, + ), + body: BlocProvider( + create: (context) => SalesBloc()..add(SalesLoad()), + child: BlocConsumer(listener: (context, state) { + if (state is SalesError) { + log("Error: " + state.message); + Scaffold.of(context).showSnackBar( + SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white)))); + } + }, builder: (context, state) { + final salesBloc = BlocProvider.of(context); + return ModalProgressHUD( + child: salesWidget(salesBloc), + inAsyncCall: state is SalesLoading, + opacity: 0.5, + color: Colors.black54, + progressIndicator: CircularProgressIndicator(), + ); + }))); } Widget salesWidget(SalesBloc bloc) { final double mediaWidth = MediaQuery.of(context).size.width; final double imageWidth = (mediaWidth - 5) / 2; - return Scaffold( - appBar: AppBarMin( - back: true, + return Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('asset/image/WT_black_background.png'), + fit: BoxFit.cover, + alignment: Alignment.center, + ), ), - body: Container( - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage('asset/image/WT_black_background.png'), - fit: BoxFit.cover, - alignment: Alignment.center, - ), + child: CustomScrollView(scrollDirection: Axis.vertical, slivers: [ + SliverList( + delegate: SliverChildListDelegate([ + Divider(), + Container( + padding: EdgeInsets.only(left: 65, right: 65), + child: Text("Unleash Your Development Now!", + textAlign: TextAlign.center, + maxLines: 4, + softWrap: true, + style: GoogleFonts.archivoBlack( + fontSize: 30, + color: Colors.white, + shadows: [ + Shadow( + offset: Offset(5.0, 5.0), + blurRadius: 12.0, + color: Colors.black54, + ), + Shadow( + offset: Offset(-3.0, 3.0), + blurRadius: 12.0, + color: Colors.black54, + ), + ], + ))), + Divider(), + Container( + padding: EdgeInsets.only(left: 45, right: 45), + child: Text("Learn about your development, enjoy AI-driven predictions of all of your skills and bodyparts.", + textAlign: TextAlign.left, + maxLines: 4, + softWrap: true, + style: GoogleFonts.inter( + fontSize: 16, + color: Colors.white, + ))), + SizedBox( + height: 50, ), - child: CustomScrollView(scrollDirection: Axis.vertical, slivers: [ - SliverList( - delegate: SliverChildListDelegate([ - Divider(), - Container( - padding: EdgeInsets.only(left: 65, right: 65), - child: Text("Unleash Your Development Now!", - textAlign: TextAlign.center, - maxLines: 4, - softWrap: true, - style: GoogleFonts.archivoBlack( - fontSize: 30, - color: Colors.white, - shadows: [ - Shadow( - offset: Offset(5.0, 5.0), - blurRadius: 12.0, - color: Colors.black54, - ), - Shadow( - offset: Offset(-3.0, 3.0), - blurRadius: 12.0, - color: Colors.black54, - ), - ], - ))), - Divider(), - Container( - padding: EdgeInsets.only(left: 45, right: 45), - child: Text("Learn about your development, enjoy AI-driven predictions of all of your skills and bodyparts.", - textAlign: TextAlign.left, - maxLines: 4, - softWrap: true, - style: GoogleFonts.inter( - fontSize: 16, - color: Colors.white, - ))), - SizedBox( - height: 50, - ), - ])), - SliverGrid( - delegate: SliverChildListDelegate(getButtons(bloc)), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - mainAxisSpacing: 10.0, - crossAxisSpacing: 10.0, - childAspectRatio: 0.55, - ), - ), - SliverList( - delegate: SliverChildListDelegate([ - SizedBox( - height: 30, - ), - Container( - padding: EdgeInsets.only(left: 55, right: 55), - child: Text( - "Subscription Conditions", - style: GoogleFonts.inter(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white), - )), - Divider(), - Container( - padding: EdgeInsets.only(left: 55, right: 55), - child: Text( - "Payment will be charged to your account. Subscription automatically renews unless auto-renew is turned off at least 24 hourse before the end of the current period", - style: GoogleFonts.inter(fontSize: 12, color: Colors.white), - )), - Divider(), - Container( - padding: EdgeInsets.only(left: 55, right: 55), - child: Text( - "Account will be charged for renewal within 24 hours prior to the end of the current period", - style: GoogleFonts.inter(fontSize: 12, color: Colors.white), - )), - ])), - ]))); + ])), + SliverGrid( + delegate: SliverChildListDelegate(getButtons(bloc)), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + mainAxisSpacing: 10.0, + crossAxisSpacing: 10.0, + childAspectRatio: 0.55, + ), + ), + SliverList( + delegate: SliverChildListDelegate([ + SizedBox( + height: 30, + ), + Container( + padding: EdgeInsets.only(left: 55, right: 55), + child: Text( + "Subscription Conditions", + style: GoogleFonts.inter(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white), + )), + Divider(), + Container( + padding: EdgeInsets.only(left: 55, right: 55), + child: Text( + "Payment will be charged to your account. Subscription automatically renews unless auto-renew is turned off at least 24 hourse before the end of the current period", + style: GoogleFonts.inter(fontSize: 12, color: Colors.white), + )), + Divider(), + Container( + padding: EdgeInsets.only(left: 55, right: 55), + child: Text( + "Account will be charged for renewal within 24 hours prior to the end of the current period", + style: GoogleFonts.inter(fontSize: 12, color: Colors.white), + )), + ])), + ])); ; } @@ -141,7 +143,7 @@ class SalesPage extends StatelessWidget with Trans { } Widget button = SalesButton( title: title, - price: element.description + interval, + price: element.localizedPrice, desc1: "Development programs", desc2: "Suggestions based on your actual status", desc3: "Special customized training plans", diff --git a/lib/widgets/app_bar.dart b/lib/widgets/app_bar.dart index 23558a4..3f6a4fc 100644 --- a/lib/widgets/app_bar.dart +++ b/lib/widgets/app_bar.dart @@ -142,6 +142,9 @@ class _AppBarNav extends State with SingleTickerProviderStateMixin, C ExerciseRepository exerciseRepository = ExerciseRepository(); exerciseRepository.getBaseExerciseFinishedPercent(); percent = Cache().getPercentExercises(); + if (percent == null || percent == -1) { + percent = 0; + } } int sizeExerciseList = Cache().getExercises() == null ? 0 : Cache().getExercises().length; if (sizeExerciseList == 0) { diff --git a/lib/widgets/dialog_premium.dart b/lib/widgets/dialog_premium.dart index 951e3f4..7c0eddc 100644 --- a/lib/widgets/dialog_premium.dart +++ b/lib/widgets/dialog_premium.dart @@ -160,7 +160,7 @@ class _DialogPremiumState extends State with Trans { Align( alignment: Alignment.center, child: GestureDetector( - onTap: widget.onTap ?? widget.onTap, + onTap: () => Navigator.of(context).pushNamed("salesPage"), child: Stack( alignment: Alignment.center, children: [ diff --git a/lib/widgets/home.dart b/lib/widgets/home.dart index 0a46a95..4826c1c 100644 --- a/lib/widgets/home.dart +++ b/lib/widgets/home.dart @@ -2,12 +2,14 @@ import 'package:aitrainer_app/bloc/session/session_bloc.dart'; import 'package:aitrainer_app/bloc/settings/settings_bloc.dart'; import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/service/logging.dart'; +import 'package:aitrainer_app/util/platform_purchase.dart'; import 'package:aitrainer_app/view/login.dart'; import 'package:aitrainer_app/view/menu_page.dart'; import 'package:aitrainer_app/view/registration.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; +import 'package:flutter/semantics.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'loading.dart'; @@ -85,4 +87,10 @@ class _HomePageState extends State with Logging { }), ); } + + @override + void dispose() async { + super.dispose(); + await PlatformPurchaseApi().close(); + } } diff --git a/lib/widgets/image_button.dart b/lib/widgets/image_button.dart index f34dbdd..0ea40a7 100644 --- a/lib/widgets/image_button.dart +++ b/lib/widgets/image_button.dart @@ -53,6 +53,8 @@ class ImageButton extends StatelessWidget { top = height - (style.fontSize - 5) * text.length - 2 * left < 0 ? height - 2 * style.fontSize - 22 : height - style.fontSize - 37; //print("Top: " + top.toStringAsFixed(0) + " length: " + ((style.fontSize - 5) * text.length).toString()); } + final double width = MediaQuery.of(context).size.width; + print("Mediawidth: " + width.toStringAsFixed(0)); return Stack(alignment: AlignmentDirectional.bottomStart, children: [ FlatButton( child: image == null @@ -101,29 +103,12 @@ class ImageButton extends StatelessWidget { ], )), )), - /* Positioned( - top: top, - left: left, - child: Container( - height: width - 2 * left, - width: width - 2 * left, - child: InkWell( - onTap: onTap ?? onTap, - child: Text( - text, - maxLines: 2, - style: style, - ), - ), - color: Colors.transparent, - ), - ), */ Cache().hasPurchased ? Offstage() : Stack(alignment: Alignment.topCenter, children: [ Positioned( - top: 20, - left: 20, + top: 10, + left: (width / 2 - 30) / 2 - 75, child: this.isLocked ? GestureDetector( child: Image.asset( diff --git a/lib/widgets/menu_page_widget.dart b/lib/widgets/menu_page_widget.dart index bc12ebb..69c2685 100644 --- a/lib/widgets/menu_page_widget.dart +++ b/lib/widgets/menu_page_widget.dart @@ -8,12 +8,14 @@ import 'package:aitrainer_app/localization/app_localization.dart'; import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/service/logging.dart'; +import 'package:aitrainer_app/util/not_found_exception.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/dialog_common.dart'; import 'package:badges/badges.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/painting.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -331,30 +333,30 @@ class _MenuPageWidgetState extends State with Trans, Logging { return returnCode; } - dynamic _getButtonImage(WorkoutMenuTree workoutTree, double cWidth, double cHeight) { - dynamic image; - try { + Widget _getButtonImage(WorkoutMenuTree workoutTree, double cWidth, double cHeight) { + Widget image; + + if (workoutTree.imageName.startsWith('https')) { image = ClipRRect( - borderRadius: BorderRadius.circular(24), - child: Image.asset( - workoutTree.imageName, - height: 210, - errorBuilder: (context, error, stackTrace) { - String url = Cache.mediaUrl + 'images/' + workoutTree.imageName.substring(11); - Widget image = FadeInImage.assetNetwork( - placeholder: 'asset/image/dots.gif', - image: url, - height: 210, - ); - return image; - }, + borderRadius: BorderRadius.circular(24.0), + child: Container( + color: Colors.black87, + child: FadeInImage( + image: NetworkImage(workoutTree.imageName), + placeholder: AssetImage("asset/image/dots.gif"), + ), )); - } on Exception catch (_) { - String url = Cache.mediaUrl + '/images/' + workoutTree.imageName; - image = FadeInImage.assetNetwork( - placeholder: 'asset/image/dots.gif', - image: url, - height: 210, + } else { + image = Container( + //width: cWidth - 30, + //height: 210.0, + decoration: BoxDecoration( + image: DecorationImage( + fit: BoxFit.cover, + image: AssetImage(workoutTree.imageName), + ), + borderRadius: BorderRadius.all(Radius.circular(24.0)), + ), ); } @@ -365,6 +367,7 @@ class _MenuPageWidgetState extends State with Trans, Logging { String badgeKey = workoutMenuTree.nameEnglish; bool show = Cache().getBadges()[badgeKey] != null; int counter = Cache().getBadges()[badgeKey] != null ? Cache().getBadges()[badgeKey] : 0; + Widget buttonImage = _getButtonImage(workoutMenuTree, cWidth, cHeight); return Badge( padding: EdgeInsets.all(8), position: BadgePosition.topEnd(top: 3, end: 3), @@ -377,7 +380,11 @@ class _MenuPageWidgetState extends State with Trans, Logging { color: Colors.white, fontSize: 16, )), - child: _getButtonImage(workoutMenuTree, cWidth, cHeight), + child: buttonImage == null + ? Container( + color: Colors.red, + ) + : buttonImage, ); } diff --git a/pubspec.lock b/pubspec.lock index 67b11f2..25edc20 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -369,7 +369,21 @@ packages: name: flutter_facebook_auth url: "https://pub.dartlang.org" source: hosted - version: "1.0.2+2" + version: "2.0.0+1" + flutter_facebook_auth_platform_interface: + dependency: transitive + description: + name: flutter_facebook_auth_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + flutter_facebook_auth_web: + dependency: transitive + description: + name: flutter_facebook_auth_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.6" flutter_form_bloc: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index f4720b9..961a9e3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.1.3+44 +version: 1.1.4+45 environment: sdk: ">=2.7.0 <3.0.0" @@ -55,7 +55,7 @@ dependencies: #firebase_analytics: ^6.2.0 firebase_messaging: ^7.0.3 firebase_auth: ^0.18.3 - flutter_facebook_auth: ^1.0.2+2 + flutter_facebook_auth: ^2.0.0+1 flurry: ^0.0.7 animated_widgets: ^1.0.6