feat: Tagline V3 (#5350)

* New JSON

* Tagline V3

* Refresh the tagline on language change

* Fix Lint errors

* Revert `SmoothScaffold` changes

* Filter news with startDate/endDate
This commit is contained in:
Edouard Marquez
2024-06-10 15:19:24 +02:00
committed by GitHub
parent 39c9c434b9
commit 41abf730fe
20 changed files with 1675 additions and 513 deletions

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="311px" height="54px" viewBox="0 0 311 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Logo OFF</title>
<g id="Homepage-V1---Nutriscore-badge" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Artboard" transform="translate(-75, -439)" fill-rule="nonzero">
<g id="Group-2" transform="translate(43, 439)">
<g id="Group" transform="translate(32, 0)">
<g transform="translate(131.0444, 5.792)">
<path d="M16.073906,4.08156488 L81.6038623,4.09408466 C89.7651103,4.0951589 96.3808409,10.7108882 96.3819168,18.8721362 L96.3902674,23.1433468 C96.3913434,31.3061154 89.7749844,37.924219 81.6122159,37.9252951 C81.6109179,37.9252952 81.60962,37.9252952 81.6083221,37.9252951 L16.0783663,37.908882 C7.91711831,37.9078078 1.30138767,31.2920784 1.30031183,23.1308305 L1.29196121,18.8596198 C1.29088516,10.6968513 7.9072442,4.07874765 16.0700127,4.07767161 C16.0713107,4.07767144 16.0726086,4.07767143 16.0739065,4.07767161 Z" id="Rectangle" fill="#FF8714" transform="translate(48.8411, 21.0015) rotate(-5) translate(-48.8411, -21.0015)"></path>
<g transform="translate(7.6498, 7.8307)" fill="#FFFFFF">
<polygon id="Path" points="1.82838106 27.0769533 0 6.21556434 14.3032727 4.96426134 14.6346667 8.74479381 4.67760821 9.61576155 5.14232173 14.9100284 13.6976214 14.1645712 14.0290155 17.9451037 5.4737158 18.6905608 6.1745952 26.7004214 1.82838106 27.0807566"></polygon>
<path d="M28.027558,25.1296367 C26.4581976,25.2665574 24.9764471,25.12203 23.5823065,24.6998579 C22.188166,24.2776858 20.9501996,23.6159024 19.8607893,22.7259179 C18.775188,21.8321301 17.8952796,20.7595846 17.217255,19.5006749 C16.5430395,18.2417652 16.1354629,16.8497382 16.0021434,15.3169871 C15.8688239,13.7880393 16.021189,12.3427654 16.4554331,10.9887718 C16.8934792,9.63477832 17.5676947,8.42911555 18.4856943,7.36798018 C19.3998849,6.31064816 20.5083409,5.45109047 21.8072533,4.79311048 C23.1061656,4.13513048 24.5498249,3.73577846 26.1382309,3.59505441 C27.7266369,3.45433037 29.2198148,3.59505441 30.6139554,4.0210299 C32.0080959,4.44700539 33.2460623,5.09737868 34.3316635,5.98355984 C35.4172648,6.86593764 36.293364,7.93848306 36.9599613,9.19739277 C37.6265586,10.4563025 38.0265169,11.8521329 38.1598364,13.3810806 C38.2931558,14.9138317 38.1369816,16.3553023 37.6913137,17.7092959 C37.2456458,19.0632894 36.5638121,20.2765588 35.6496215,21.3453009 C34.735431,22.414043 33.626975,23.277404 32.3280626,23.935384 C31.0291502,24.593364 29.5931093,24.9927161 28.0237489,25.1296367 L28.027558,25.1296367 Z M27.6847365,21.2083802 C28.6370183,21.1247065 29.5054994,20.8812919 30.2901796,20.4743332 C31.0748598,20.0673745 31.7490753,19.5349051 32.3128261,18.8731217 C32.8765769,18.2113384 33.2917718,17.4468645 33.5546016,16.5797001 C33.8174313,15.7087324 33.9050413,14.7731077 33.8174313,13.7614159 C33.7298214,12.7535274 33.478419,11.8483295 33.0708424,11.0458221 C32.6632658,10.2433147 32.1223697,9.56251462 31.4519634,8.99961844 C30.781557,8.43672225 30.0235407,8.02976353 29.1817235,7.77113555 C28.3399064,7.51631093 27.4409524,7.42883382 26.4886706,7.51250757 C25.5363888,7.59618133 24.6679078,7.83579254 23.8832276,8.23514457 C23.0985474,8.63449659 22.4243319,9.16696595 21.860581,9.836356 C21.2968302,10.5057461 20.8854445,11.2702199 20.6188056,12.1297776 C20.3559758,12.9893353 20.2683659,13.92496 20.3559758,14.9328485 C20.4435857,15.9407369 20.6949881,16.8497382 21.1025647,17.6598523 C21.5139505,18.4699664 22.0548465,19.1507665 22.7214438,19.706056 C23.3918502,20.2575421 24.1498665,20.6683042 24.9916836,20.9345388 C25.8335007,21.2007735 26.7324547,21.292054 27.6847365,21.2083802 Z" id="Shape"></path>
<path d="M51.8688852,23.0453995 C50.2995248,23.1823202 48.8177743,23.0377928 47.4236337,22.6156207 C46.0294932,22.1934486 44.7915268,21.5316652 43.7021165,20.6416807 C42.6165152,19.7478928 41.7366068,18.6753474 41.0585822,17.4164377 C40.3843667,16.157528 39.97679,14.765501 39.8434706,13.2327499 C39.7101511,11.7038021 39.8625162,10.2585281 40.2967603,8.90453462 C40.7348064,7.5505411 41.4090219,6.34487833 42.3270215,5.28374296 C43.2412121,4.22641094 44.3496681,3.36685326 45.6485805,2.70887326 C46.9474928,2.05089326 48.391152,1.65154124 49.9795581,1.5108172 C51.5679641,1.3738965 53.061142,1.5108172 54.4552826,1.93679269 C55.8494231,2.36276818 57.0873895,3.01314147 58.1729907,3.89932262 C59.258592,4.78170042 60.1346912,5.85424585 60.8012885,7.11315555 C61.4678858,8.37206526 61.8678441,9.76789566 62.0011636,11.2968434 C62.134483,12.8295945 61.9783088,14.2710651 61.5326409,15.6250586 C61.086973,16.9828555 60.4051393,18.1923216 59.4909487,19.2610637 C58.5767582,20.3298058 57.4683022,21.1931668 56.1693898,21.8511468 C54.8704774,22.5091268 53.4344365,22.9084788 51.8650761,23.0453995 L51.8688852,23.0453995 Z M51.5260637,19.124143 C52.4783455,19.0404693 53.3468265,18.7970547 54.1315067,18.390096 C54.916187,17.9831372 55.5904025,17.4506679 56.1541533,16.7888845 C56.7179041,16.1271012 57.133099,15.3626273 57.3959288,14.4954629 C57.6587585,13.6244952 57.7463685,12.6888704 57.6587585,11.6771787 C57.5711486,10.6692902 57.3197462,9.76409231 56.9121696,8.96158491 C56.504593,8.15907751 55.9636969,7.4782774 55.2932905,6.91538122 C54.6228842,6.35248504 53.8648678,5.94552631 53.0230507,5.68689833 C52.1812336,5.43207371 51.2822796,5.3445966 50.3299978,5.42827036 C49.377716,5.51194412 48.5054259,5.75155533 47.7207457,6.15090735 C46.9360655,6.55025937 46.2618499,7.08272873 45.6980991,7.75211879 C45.1343483,8.42150884 44.7229625,9.18598271 44.4563236,10.0455404 C44.1934939,10.9050981 44.1058839,11.8407228 44.1934939,12.8486113 C44.2811038,13.8564997 44.5325062,14.765501 44.9400828,15.5756151 C45.3514685,16.3857292 45.8923646,17.0665293 46.5589619,17.6218187 C47.2293682,18.1733049 47.9873846,18.5840669 48.8292017,18.8503016 C49.6710188,19.1165363 50.5699728,19.2078168 51.5222546,19.124143 L51.5260637,19.124143 Z" id="Shape"></path>
<path d="M65.4674693,21.5202551 L63.6390883,0.658866217 L70.4536168,0.0617398612 C72.7162384,-0.136034473 74.7198393,0.141610265 76.4606104,0.890870725 C78.2013816,1.64013118 79.5993313,2.7621202 80.6544595,4.24923106 C81.7057786,5.73634192 82.3228572,7.48968746 82.4980771,9.50546433 C82.6732969,11.5212412 82.3723759,13.3582605 81.5953139,15.0165222 C80.818252,16.674784 79.6336134,18.0249741 78.0490165,19.0670927 C76.4644196,20.1092113 74.5408103,20.7291578 72.2781888,20.9269321 L65.4636602,21.5240585 L65.4674693,21.5202551 Z M69.4822894,17.3593874 L72.0610685,17.1349896 C73.4247361,17.0170857 74.5789016,16.6367504 75.5235651,16.0015905 C76.4644196,15.3626273 77.1691081,14.5220864 77.6338216,13.4761644 C78.0985351,12.4302425 78.2699459,11.2321864 78.1518629,9.88960294 C78.03378,8.52800272 77.6528673,7.37558688 77.016743,6.43235544 C76.3768096,5.49292735 75.5388017,4.79311048 74.5027191,4.33670817 C73.4628273,3.88030586 72.2629523,3.71295834 70.8992847,3.83466563 L68.3205056,4.05906343 L69.4860985,17.3593874 L69.4822894,17.3593874 Z" id="Shape"></path>
</g>
</g>
<g transform="translate(233.3129, 11.7714)" fill="#000000">
<path d="M10.2008427,0 C8.78003821,0 7.54969013,0.258627976 6.50218014,0.775883927 C5.45467016,1.29313988 4.65094432,2.04240034 4.08719349,3.01986195 C3.52344267,3.99732357 3.24156725,5.19537963 3.24156725,6.61403015 L3.24156725,7.18833639 L0,7.18833639 L0,11.1362164 L3.24156725,11.1362164 L3.24156725,25.5395126 L7.66777307,25.5395126 L7.66777307,11.1362164 L11.7549666,11.1362164 L11.7549666,7.18833639 L7.66777307,7.18833639 L7.66777307,6.61403015 C7.66777307,5.6707987 7.9382211,4.97858853 8.47911716,4.54120299 C9.02001323,4.10381744 9.7856478,3.88322299 10.7760209,3.88322299 C10.9321951,3.88322299 11.1074149,3.89082969 11.2978713,3.8984364 C11.4883277,3.90984646 11.6978297,3.93646993 11.9225682,3.98211016 L11.9225682,0.171150866 C11.6978297,0.125510635 11.4197634,0.0874771095 11.0959876,0.0532469362 C10.7684026,0.0190167629 10.4712907,0.00380335258 10.2008427,0.00380335258 L10.2008427,0 Z" id="Path"></path>
<path d="M24.8583642,7.60670517 C23.7080077,7.05521904 22.3786223,6.78137766 20.870208,6.78137766 C19.6322416,6.78137766 18.4818852,6.98295535 17.4229478,7.38991407 C16.3640105,7.79306945 15.4460108,8.35216228 14.6689489,9.05958586 C13.8918869,9.76700944 13.324327,10.6075504 12.9624599,11.5736019 L16.5773216,13.3269475 C16.9163339,12.5168334 17.4496117,11.85505 18.1809642,11.3377941 C18.9123166,10.8205381 19.7503246,10.5619101 20.6987972,10.5619101 C21.7120251,10.5619101 22.5233692,10.8281448 23.1328295,11.3530075 C23.7422899,11.8816735 24.0432109,12.5396535 24.0432109,13.3269475 L24.0432109,13.9202705 L18.6037773,14.810255 C17.1601181,15.0346528 15.9678613,15.4187914 15.0231977,15.9588674 C14.0785342,16.4989435 13.3738456,17.1683336 12.9129412,17.9670376 C12.4520368,18.7657417 12.2196801,19.6709396 12.2196801,20.6826314 C12.2196801,21.6943231 12.4787007,22.6679814 12.996742,23.4666854 C13.5147833,24.2653895 14.253754,24.8777293 15.2098449,25.3037047 C16.1659359,25.7296802 17.2858193,25.9464713 18.5733043,25.9464713 C19.5865321,25.9464713 20.5045318,25.813354 21.3273032,25.543316 C22.1500747,25.2732779 22.8928545,24.8701226 23.5556427,24.3300465 C23.8260907,24.109452 24.0774931,23.8660375 24.3174681,23.6112128 L24.3174681,25.5471193 L28.507508,25.5471193 L28.507508,13.3345542 C28.507508,12.0528243 28.1875413,10.9156219 27.5437988,9.92675025 C26.9000563,8.93787857 26.0049114,8.165798 24.8583642,7.61431187 L24.8583642,7.60670517 Z M23.4718419,20.3403296 C23.08712,21.0363431 22.5424148,21.5840259 21.8339172,21.9757712 C21.1254195,22.3713199 20.3064571,22.5652909 19.3846484,22.5652909 C18.6647233,22.5652909 18.0628812,22.3789266 17.5753129,22.0100014 C17.0915538,21.6372729 16.8477696,21.1390337 16.8477696,20.5076771 C16.8477696,19.8763206 17.0610808,19.3324412 17.4915121,18.9406959 C17.9181344,18.5489506 18.5733043,18.2713058 19.4494035,18.1153684 L24.0432109,17.3014509 L24.0432109,18.0811382 C24.0432109,18.8912523 23.8527546,19.6443161 23.4680327,20.3403296 L23.4718419,20.3403296 Z" id="Shape"></path>
<path d="M37.8893883,11.5545852 C38.6435955,11.0943795 39.5044583,10.862375 40.4757857,10.862375 C41.5118683,10.862375 42.429868,11.1362164 43.2297847,11.6877025 C44.0297014,12.2391886 44.5972613,12.9846457 44.9362737,13.9316805 L48.8215834,12.2125652 C48.4597163,11.132413 47.8693016,10.1891816 47.0465301,9.37906747 C46.2237587,8.56895337 45.2486221,7.93379349 44.1249296,7.47358783 C42.9974279,7.01338217 41.7823163,6.78137766 40.4757857,6.78137766 C38.628359,6.78137766 36.9790069,7.19594309 35.5277295,8.02887731 C34.076452,8.86181152 32.9299047,9.99521059 32.099515,11.4366812 C31.2653161,12.8743485 30.8501213,14.5059868 30.8501213,16.3277926 C30.8501213,18.1495985 31.2729344,19.7850401 32.1185606,21.2341175 C32.9641869,22.6831948 34.106925,23.8318073 35.5467751,24.6761516 C36.9866252,25.5204958 38.6321681,25.942668 40.4795948,25.942668 C41.8089802,25.942668 43.0355192,25.7106635 44.1630208,25.2504578 C45.2905225,24.7902521 46.2580408,24.1474856 47.0693849,23.3259614 C47.880729,22.5044372 48.4673346,21.5764192 48.8253925,20.5419073 L44.9400828,18.7885618 C44.5782157,19.7317932 44.0106558,20.484857 43.2335938,21.0477532 C42.4565319,21.6106494 41.5385322,21.8920975 40.4795948,21.8920975 C39.5120765,21.8920975 38.6474046,21.6562896 37.8931975,21.1846739 C37.1389903,20.7130582 36.5485756,20.0588815 36.1181442,19.2297507 C35.6915219,18.3968165 35.4744017,17.442175 35.4744017,16.3620228 C35.4744017,15.2818707 35.6877128,14.3538527 36.1181442,13.5133117 C36.5447664,12.6689675 37.1351811,12.0185942 37.8931975,11.5583885 L37.8893883,11.5545852 Z" id="Path"></path>
<path d="M60.5041766,21.8616707 C59.7842515,21.8616707 59.1900277,21.7475701 58.7291233,21.5231723 C58.2682189,21.2987745 57.9292066,20.9716862 57.7158955,20.5457107 C57.5025843,20.1197352 57.3959288,19.6024792 57.3959288,18.9939428 L57.3959288,11.132413 L61.6202508,11.132413 L61.6202508,7.18453303 L57.3959288,7.18453303 L57.3959288,3.00084519 L52.9354408,3.00084519 L52.9354408,4.4194957 C52.9354408,5.32089027 52.6916567,6.00549373 52.2078975,6.47710945 C51.7241384,6.94872517 51.0308772,7.18453303 50.1319232,7.18453303 L49.7929109,7.18453303 L49.7929109,11.132413 L52.9354408,11.132413 L52.9354408,19.2297507 C52.9354408,21.2987745 53.510619,22.8999859 54.6571663,24.0371883 C55.8075227,25.1743908 57.4149744,25.7410903 59.4871396,25.7410903 C59.8261519,25.7410903 60.1956373,25.7182702 60.6032139,25.6726299 C61.0069814,25.6269897 61.3688484,25.5813495 61.685006,25.5395126 L61.685006,21.7627835 C61.4831223,21.7856036 61.2736203,21.8084237 61.0603091,21.8312438 C60.846998,21.854064 60.6603508,21.865474 60.5041766,21.865474 L60.5041766,21.8616707 Z" id="Path"></path>
<path d="M72.9257404,15.0118327 L69.9850942,14.1332582 C69.5584719,14.0001409 69.1813683,13.8518101 68.8537834,13.6958727 C68.5261985,13.5399352 68.2747961,13.3383575 68.091958,13.0873362 C67.912929,12.8401183 67.8215099,12.5586702 67.8215099,12.242992 C67.8215099,11.6800958 68.0348211,11.2351035 68.4652524,10.9118186 C68.8918747,10.5847302 69.4784803,10.4211861 70.2212601,10.4211861 C71.1430689,10.4211861 71.9772677,10.6646006 72.7200475,11.1476264 C73.4628273,11.6306522 73.992296,12.2886322 74.3084536,13.1215664 L77.6871494,11.5355684 C77.1233986,10.0294408 76.178735,8.85800817 74.8493496,8.02887731 C73.5199642,7.19594309 71.9886951,6.78137766 70.2555422,6.78137766 C68.9261568,6.78137766 67.7491365,7.01718552 66.7244813,7.48880124 C65.6998261,7.96041696 64.8999094,8.61839696 64.3247312,9.46274123 C63.7495529,10.3070855 63.4638684,11.2883505 63.4638684,12.4141428 C63.4638684,13.6730525 63.8638268,14.7684181 64.6637435,15.7040428 C65.4636602,16.6358642 66.6292531,17.3166643 68.1605223,17.7464432 L71.1659236,18.5907874 C71.5696911,18.704888 71.9315582,18.8456121 72.2477157,19.0129596 C72.5638733,19.1803071 72.8152757,19.3856881 73.0095412,19.621496 C73.1999976,19.8573038 73.2952257,20.1577687 73.2952257,20.5152838 C73.2952257,21.1010001 73.0590598,21.5726159 72.5867281,21.9339344 C72.1143963,22.2952529 71.4820812,22.4740104 70.6935918,22.4740104 C69.6575092,22.4740104 68.7166548,22.1811523 67.8710286,21.595436 C67.0254023,21.0097197 66.3664233,20.1996056 65.8940916,19.1650937 L62.5496779,20.7510917 C63.1134287,22.3484998 64.1190383,23.6150162 65.5741249,24.5468376 C67.0254023,25.478659 68.7318913,25.9464713 70.6935918,25.9464713 C72.0915415,25.9464713 73.3066531,25.7106635 74.3427357,25.2390478 C75.3788183,24.767432 76.1901624,24.109452 76.776768,23.2651078 C77.3633736,22.4207635 77.6566764,21.4509086 77.6566764,20.3479363 C77.6566764,19.0433864 77.2452906,17.9404141 76.4225192,17.0428229 C75.5997477,16.1452317 74.4341548,15.468235 72.9257404,15.0194394 L72.9257404,15.0118327 Z" id="Path"></path>
</g>
<g transform="translate(46.5018, 18.5528)" fill="#000000">
<path d="M14.6613306,1.24749965 C13.1986258,0.414565432 11.5530828,0 9.72851089,0 C7.90393896,0 6.29267815,0.414565432 4.8299733,1.24749965 C3.36726845,2.08043386 2.19405727,3.21383293 1.31795801,4.65530356 C0.438049629,6.09297084 0,7.73601916 0,9.58064516 C0,11.4252712 0.438049629,13.0683195 1.31795801,14.5059868 C2.1978664,15.943654 3.37107758,17.0808565 4.84901894,17.9137907 C6.32315116,18.7467249 7.94964848,19.1612903 9.73232002,19.1612903 C11.5149915,19.1612903 13.1681528,18.7467249 14.6308576,17.9137907 C16.0935625,17.0808565 17.2667736,15.9474574 18.1428729,14.5059868 C19.0227813,13.0683195 19.4608309,11.4252712 19.4608309,9.58064516 C19.4608309,7.73601916 19.0265904,6.06634737 18.1619185,4.64009015 C17.2934375,3.21383293 16.1278446,2.08043386 14.6651397,1.251303 L14.6613306,1.24749965 Z M14.1699532,12.4293562 C13.7319036,13.2737005 13.1338706,13.9316805 12.3796634,14.4032962 C11.6254562,14.874912 10.7417387,15.1107198 9.72851089,15.1107198 C8.71528305,15.1107198 7.85822943,14.874912 7.09259486,14.4032962 C6.32696029,13.9316805 5.72511819,13.2737005 5.28325944,12.4293562 C4.84520981,11.585012 4.62428043,10.6379772 4.62428043,9.58064516 C4.62428043,8.52331314 4.84520981,7.5800817 5.28325944,6.74714749 C5.72130907,5.91421327 6.32315116,5.26383998 7.09259486,4.79222426 C7.85822943,4.32060854 8.73813781,4.08480068 9.72851089,4.08480068 C10.718884,4.08480068 11.6254562,4.32060854 12.3796634,4.79222426 C13.1338706,5.26383998 13.7319036,5.91801662 14.1699532,6.74714749 C14.6080028,7.5800817 14.8289322,8.52331314 14.8289322,9.58064516 C14.8289322,10.6379772 14.6080028,11.5888153 14.1699532,12.4293562 Z" id="Shape"></path>
<path d="M36.6399946,1.28172982 C35.2420449,0.42597549 33.6764936,0 31.9433408,0 C30.5263454,0 29.2731426,0.273841386 28.1951596,0.825327511 C27.3304877,1.26651641 26.6105627,1.85223271 26.0315753,2.5748697 L26.0315753,0.403155374 L21.8415354,0.403155374 L21.8415354,25.5014791 L26.3020234,25.5014791 L26.3020234,16.9933793 C26.8429194,17.5676856 27.4904711,18.035498 28.2446782,18.3854064 C29.3607525,18.9026623 30.5796732,19.1612903 31.9090586,19.1612903 C33.687921,19.1612903 35.2763271,18.7391182 36.6742768,17.8947739 C38.0722264,17.0504296 39.1730642,15.8980138 39.9844083,14.4375264 C40.7957524,12.977039 41.1995199,11.3568108 41.1995199,9.58064516 C41.1995199,7.8044795 40.7881341,6.16143119 39.9653627,4.72376391 C39.1425912,3.28609663 38.0341352,2.13748415 36.6361855,1.28172982 L36.6399946,1.28172982 Z M35.931497,12.448373 C35.4820199,13.2813072 34.8687505,13.9316805 34.0916885,14.4032962 C33.3146266,14.874912 32.4194817,15.1107198 31.4062538,15.1107198 C30.393026,15.1107198 29.5664454,14.874912 28.7893834,14.4032962 C28.0123215,13.9316805 27.4028611,13.2775039 26.9648115,12.448373 C26.5267619,11.6154388 26.3058325,10.6607973 26.3058325,9.58064516 C26.3058325,8.50049303 26.5267619,7.5800817 26.9648115,6.74714749 C27.4028611,5.91421327 28.0123215,5.26383998 28.7893834,4.79222426 C29.5664454,4.32060854 30.4387355,4.08480068 31.4062538,4.08480068 C32.3737721,4.08480068 33.3146266,4.32060854 34.0916885,4.79222426 C34.8687505,5.26383998 35.4820199,5.91801662 35.931497,6.74714749 C36.380974,7.5800817 36.6057125,8.52331314 36.6057125,9.58064516 C36.6057125,10.6379772 36.380974,11.6154388 35.931497,12.448373 Z" id="Shape"></path>
<path d="M58.5348578,2.66615016 C57.7692232,1.83321595 56.8397962,1.18284265 55.7465767,0.711226933 C54.6533572,0.239611213 53.3887269,0.00380335258 51.9450677,0.00380335258 C50.2119149,0.00380335258 48.657791,0.418368784 47.282696,1.251303 C45.9076011,2.08423722 44.8181907,3.21383293 44.0068466,4.64009015 C43.1955025,6.07015073 42.7917351,7.70559234 42.7917351,9.54641499 C42.7917351,11.3872376 43.1840752,12.9428088 43.9725645,14.4032962 C44.7610538,15.8637836 45.8695098,17.0238062 47.3017417,17.8795605 C48.7301644,18.7353148 50.394753,19.1612903 52.2840801,19.1612903 C53.5220464,19.1612903 54.6724028,18.9711227 55.7313402,18.5869841 C56.7902775,18.2066488 57.7006589,17.6779828 58.4662935,17.0009861 C59.2319281,16.3277926 59.7956789,15.5519087 60.1537369,14.6733343 L56.5731573,12.9199887 C56.1693898,13.6388224 55.6094481,14.2131286 54.9009504,14.6391041 C54.1924528,15.0650796 53.33159,15.2818707 52.3145531,15.2818707 C51.2975161,15.2818707 50.4061803,15.0460628 49.6291184,14.5744471 C48.8520564,14.1028314 48.2654508,13.4334413 47.8731107,12.5662769 C47.6369449,12.0452176 47.4883889,11.4785181 47.4274429,10.862375 L60.6603508,10.862375 C60.7517698,10.5923369 60.8127159,10.2956754 60.846998,9.96858712 C60.8812802,9.6414988 60.8965167,9.31060713 60.8965167,8.97210875 C60.8965167,7.73601916 60.6946329,6.58360332 60.2870563,5.51486125 C59.8832888,4.44611917 59.2966832,3.49528103 58.5310487,2.66615016 L58.5348578,2.66615016 Z M47.5188619,7.45457107 C47.5950445,7.0666291 47.7017,6.70911396 47.8426377,6.37441893 C48.2159322,5.49584449 48.7606374,4.82265108 49.4805624,4.35103536 C50.2004875,3.87941964 51.0232589,3.64361178 51.9450677,3.64361178 C52.8668765,3.64361178 53.7391666,3.87941964 54.4286187,4.35103536 C55.1142616,4.82265108 55.6094481,5.46541766 55.9141783,6.27553177 C56.0513069,6.64445697 56.1351077,7.04000563 56.1617716,7.45457107 L47.5150528,7.45457107 L47.5188619,7.45457107 Z" id="Shape"></path>
<path d="M76.3349092,0.878574447 C75.2873993,0.292858149 74.0875242,0 72.735284,0 C71.3830439,0 70.2860152,0.285251444 69.3070695,0.859557684 C68.5490532,1.30074658 67.9624476,1.90547965 67.5320163,2.66234681 L67.5320163,0.406958727 L63.3419763,0.406958727 L63.3419763,18.7581349 L67.8024643,18.7581349 L67.8024643,7.99845049 C67.8024643,7.18833639 67.9586385,6.49232286 68.2747961,5.90660656 C68.5909536,5.32089027 69.0290033,4.87209466 69.5927541,4.5564164 C70.1565049,4.24073813 70.7964383,4.08480068 71.5201725,4.08480068 C72.2439066,4.08480068 72.88384,4.24073813 73.4475908,4.5564164 C74.0113416,4.87209466 74.4493913,5.32089027 74.7655488,5.90660656 C75.0817064,6.49232286 75.2378806,7.18833639 75.2378806,7.99845049 L75.2378806,18.7581349 L79.6640864,18.7581349 L79.6640864,6.95252853 C79.6640864,5.57951824 79.3707836,4.37005212 78.784178,3.32793351 C78.1975724,2.28201155 77.3824192,1.4680941 76.3349092,0.8823778 L76.3349092,0.878574447 Z" id="Path"></path>
</g>
<g>
<g transform="translate(0.0038, 9.3562)">
<path d="M0,18.628821 C0,8.34075222 8.35341597,0 18.6571051,0 C28.9607942,0 37.3142101,8.34075222 37.3142101,18.628821 L0,18.628821 Z" id="Path" fill="#FF8C14"></path>
<ellipse id="Oval" fill="#8C3C00" cx="24.9764471" cy="6.59881673" rx="1.02465522" ry="1.02310185"></ellipse>
<ellipse id="Oval" fill="#8C3C00" cx="29.974022" cy="11.1780532" rx="1.02465522" ry="1.02310185"></ellipse>
<ellipse id="Oval" fill="#8C3C00" cx="24.3212772" cy="12.3000423" rx="1.02465522" ry="1.02310185"></ellipse>
</g>
<path d="M9.61423707,44.2710241 L6.3002964,52.1743908 L10.6617471,54 L14.0061607,46.028173 C15.4955295,46.4123116 17.0572716,46.6176926 18.6647233,46.6176926 C28.9531759,46.6176926 37.3218284,38.261727 37.3218284,27.9888717 L32.5908924,27.9888717 C32.5908924,35.6564305 26.3439238,41.8977321 18.6609142,41.8977321 C10.9779046,41.8977321 4.73093599,35.6602338 4.73093599,27.9888717 L0,27.9888717 C0,34.9832371 3.88530975,41.087618 9.61042795,44.2748274 L9.61423707,44.2710241 Z" id="Path" fill="#000000"></path>
<path d="M26.3782059,0 C22.7100164,0 19.5217769,2.04240034 17.8800431,5.04704888 C19.1446733,6.21848148 20.093146,7.72080575 20.5883326,9.42090435 C24.8583642,8.4624595 28.0618401,4.69714044 28.1380227,0.163544161 C27.5666536,0.0570502888 26.980048,0 26.3782059,0 L26.3782059,0 Z" id="Path" fill="#00641E"></path>
<path d="M11.2978713,2.45696577 C10.570328,2.45696577 9.86563947,2.54063953 9.1838057,2.68897028 C10.3532078,6.71672066 14.0709159,9.66051557 18.4780761,9.66051557 C19.2056194,9.66051557 19.9103079,9.57684181 20.5921417,9.42851106 C19.4227396,5.40076067 15.7050315,2.45696577 11.2978713,2.45696577 Z" id="Path" fill="#00961E"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="311px" height="54px" viewBox="0 0 311 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Logo OFF</title>
<g id="Homepage-V1---Nutriscore-badge" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Artboard" transform="translate(-75, -439)" fill-rule="nonzero">
<g id="Group-2" transform="translate(43, 439)">
<g id="Group" transform="translate(32, 0)">
<g transform="translate(131.0444, 5.792)">
<path d="M16.073906,4.08156488 L81.6038623,4.09408466 C89.7651103,4.0951589 96.3808409,10.7108882 96.3819168,18.8721362 L96.3902674,23.1433468 C96.3913434,31.3061154 89.7749844,37.924219 81.6122159,37.9252951 C81.6109179,37.9252952 81.60962,37.9252952 81.6083221,37.9252951 L16.0783663,37.908882 C7.91711831,37.9078078 1.30138767,31.2920784 1.30031183,23.1308305 L1.29196121,18.8596198 C1.29088516,10.6968513 7.9072442,4.07874765 16.0700127,4.07767161 C16.0713107,4.07767144 16.0726086,4.07767143 16.0739065,4.07767161 Z" id="Rectangle" fill="#FF8714" transform="translate(48.8411, 21.0015) rotate(-5) translate(-48.8411, -21.0015)"></path>
<g transform="translate(7.6498, 7.8307)" fill="#FFFFFF">
<polygon id="Path" points="1.82838106 27.0769533 0 6.21556434 14.3032727 4.96426134 14.6346667 8.74479381 4.67760821 9.61576155 5.14232173 14.9100284 13.6976214 14.1645712 14.0290155 17.9451037 5.4737158 18.6905608 6.1745952 26.7004214 1.82838106 27.0807566"></polygon>
<path d="M28.027558,25.1296367 C26.4581976,25.2665574 24.9764471,25.12203 23.5823065,24.6998579 C22.188166,24.2776858 20.9501996,23.6159024 19.8607893,22.7259179 C18.775188,21.8321301 17.8952796,20.7595846 17.217255,19.5006749 C16.5430395,18.2417652 16.1354629,16.8497382 16.0021434,15.3169871 C15.8688239,13.7880393 16.021189,12.3427654 16.4554331,10.9887718 C16.8934792,9.63477832 17.5676947,8.42911555 18.4856943,7.36798018 C19.3998849,6.31064816 20.5083409,5.45109047 21.8072533,4.79311048 C23.1061656,4.13513048 24.5498249,3.73577846 26.1382309,3.59505441 C27.7266369,3.45433037 29.2198148,3.59505441 30.6139554,4.0210299 C32.0080959,4.44700539 33.2460623,5.09737868 34.3316635,5.98355984 C35.4172648,6.86593764 36.293364,7.93848306 36.9599613,9.19739277 C37.6265586,10.4563025 38.0265169,11.8521329 38.1598364,13.3810806 C38.2931558,14.9138317 38.1369816,16.3553023 37.6913137,17.7092959 C37.2456458,19.0632894 36.5638121,20.2765588 35.6496215,21.3453009 C34.735431,22.414043 33.626975,23.277404 32.3280626,23.935384 C31.0291502,24.593364 29.5931093,24.9927161 28.0237489,25.1296367 L28.027558,25.1296367 Z M27.6847365,21.2083802 C28.6370183,21.1247065 29.5054994,20.8812919 30.2901796,20.4743332 C31.0748598,20.0673745 31.7490753,19.5349051 32.3128261,18.8731217 C32.8765769,18.2113384 33.2917718,17.4468645 33.5546016,16.5797001 C33.8174313,15.7087324 33.9050413,14.7731077 33.8174313,13.7614159 C33.7298214,12.7535274 33.478419,11.8483295 33.0708424,11.0458221 C32.6632658,10.2433147 32.1223697,9.56251462 31.4519634,8.99961844 C30.781557,8.43672225 30.0235407,8.02976353 29.1817235,7.77113555 C28.3399064,7.51631093 27.4409524,7.42883382 26.4886706,7.51250757 C25.5363888,7.59618133 24.6679078,7.83579254 23.8832276,8.23514457 C23.0985474,8.63449659 22.4243319,9.16696595 21.860581,9.836356 C21.2968302,10.5057461 20.8854445,11.2702199 20.6188056,12.1297776 C20.3559758,12.9893353 20.2683659,13.92496 20.3559758,14.9328485 C20.4435857,15.9407369 20.6949881,16.8497382 21.1025647,17.6598523 C21.5139505,18.4699664 22.0548465,19.1507665 22.7214438,19.706056 C23.3918502,20.2575421 24.1498665,20.6683042 24.9916836,20.9345388 C25.8335007,21.2007735 26.7324547,21.292054 27.6847365,21.2083802 Z" id="Shape"></path>
<path d="M51.8688852,23.0453995 C50.2995248,23.1823202 48.8177743,23.0377928 47.4236337,22.6156207 C46.0294932,22.1934486 44.7915268,21.5316652 43.7021165,20.6416807 C42.6165152,19.7478928 41.7366068,18.6753474 41.0585822,17.4164377 C40.3843667,16.157528 39.97679,14.765501 39.8434706,13.2327499 C39.7101511,11.7038021 39.8625162,10.2585281 40.2967603,8.90453462 C40.7348064,7.5505411 41.4090219,6.34487833 42.3270215,5.28374296 C43.2412121,4.22641094 44.3496681,3.36685326 45.6485805,2.70887326 C46.9474928,2.05089326 48.391152,1.65154124 49.9795581,1.5108172 C51.5679641,1.3738965 53.061142,1.5108172 54.4552826,1.93679269 C55.8494231,2.36276818 57.0873895,3.01314147 58.1729907,3.89932262 C59.258592,4.78170042 60.1346912,5.85424585 60.8012885,7.11315555 C61.4678858,8.37206526 61.8678441,9.76789566 62.0011636,11.2968434 C62.134483,12.8295945 61.9783088,14.2710651 61.5326409,15.6250586 C61.086973,16.9828555 60.4051393,18.1923216 59.4909487,19.2610637 C58.5767582,20.3298058 57.4683022,21.1931668 56.1693898,21.8511468 C54.8704774,22.5091268 53.4344365,22.9084788 51.8650761,23.0453995 L51.8688852,23.0453995 Z M51.5260637,19.124143 C52.4783455,19.0404693 53.3468265,18.7970547 54.1315067,18.390096 C54.916187,17.9831372 55.5904025,17.4506679 56.1541533,16.7888845 C56.7179041,16.1271012 57.133099,15.3626273 57.3959288,14.4954629 C57.6587585,13.6244952 57.7463685,12.6888704 57.6587585,11.6771787 C57.5711486,10.6692902 57.3197462,9.76409231 56.9121696,8.96158491 C56.504593,8.15907751 55.9636969,7.4782774 55.2932905,6.91538122 C54.6228842,6.35248504 53.8648678,5.94552631 53.0230507,5.68689833 C52.1812336,5.43207371 51.2822796,5.3445966 50.3299978,5.42827036 C49.377716,5.51194412 48.5054259,5.75155533 47.7207457,6.15090735 C46.9360655,6.55025937 46.2618499,7.08272873 45.6980991,7.75211879 C45.1343483,8.42150884 44.7229625,9.18598271 44.4563236,10.0455404 C44.1934939,10.9050981 44.1058839,11.8407228 44.1934939,12.8486113 C44.2811038,13.8564997 44.5325062,14.765501 44.9400828,15.5756151 C45.3514685,16.3857292 45.8923646,17.0665293 46.5589619,17.6218187 C47.2293682,18.1733049 47.9873846,18.5840669 48.8292017,18.8503016 C49.6710188,19.1165363 50.5699728,19.2078168 51.5222546,19.124143 L51.5260637,19.124143 Z" id="Shape"></path>
<path d="M65.4674693,21.5202551 L63.6390883,0.658866217 L70.4536168,0.0617398612 C72.7162384,-0.136034473 74.7198393,0.141610265 76.4606104,0.890870725 C78.2013816,1.64013118 79.5993313,2.7621202 80.6544595,4.24923106 C81.7057786,5.73634192 82.3228572,7.48968746 82.4980771,9.50546433 C82.6732969,11.5212412 82.3723759,13.3582605 81.5953139,15.0165222 C80.818252,16.674784 79.6336134,18.0249741 78.0490165,19.0670927 C76.4644196,20.1092113 74.5408103,20.7291578 72.2781888,20.9269321 L65.4636602,21.5240585 L65.4674693,21.5202551 Z M69.4822894,17.3593874 L72.0610685,17.1349896 C73.4247361,17.0170857 74.5789016,16.6367504 75.5235651,16.0015905 C76.4644196,15.3626273 77.1691081,14.5220864 77.6338216,13.4761644 C78.0985351,12.4302425 78.2699459,11.2321864 78.1518629,9.88960294 C78.03378,8.52800272 77.6528673,7.37558688 77.016743,6.43235544 C76.3768096,5.49292735 75.5388017,4.79311048 74.5027191,4.33670817 C73.4628273,3.88030586 72.2629523,3.71295834 70.8992847,3.83466563 L68.3205056,4.05906343 L69.4860985,17.3593874 L69.4822894,17.3593874 Z" id="Shape"></path>
</g>
</g>
<g transform="translate(233.3129, 11.7714)" fill="#FFFFFF">
<path d="M10.2008427,0 C8.78003821,0 7.54969013,0.258627976 6.50218014,0.775883927 C5.45467016,1.29313988 4.65094432,2.04240034 4.08719349,3.01986195 C3.52344267,3.99732357 3.24156725,5.19537963 3.24156725,6.61403015 L3.24156725,7.18833639 L0,7.18833639 L0,11.1362164 L3.24156725,11.1362164 L3.24156725,25.5395126 L7.66777307,25.5395126 L7.66777307,11.1362164 L11.7549666,11.1362164 L11.7549666,7.18833639 L7.66777307,7.18833639 L7.66777307,6.61403015 C7.66777307,5.6707987 7.9382211,4.97858853 8.47911716,4.54120299 C9.02001323,4.10381744 9.7856478,3.88322299 10.7760209,3.88322299 C10.9321951,3.88322299 11.1074149,3.89082969 11.2978713,3.8984364 C11.4883277,3.90984646 11.6978297,3.93646993 11.9225682,3.98211016 L11.9225682,0.171150866 C11.6978297,0.125510635 11.4197634,0.0874771095 11.0959876,0.0532469362 C10.7684026,0.0190167629 10.4712907,0.00380335258 10.2008427,0.00380335258 L10.2008427,0 Z" id="Path"></path>
<path d="M24.8583642,7.60670517 C23.7080077,7.05521904 22.3786223,6.78137766 20.870208,6.78137766 C19.6322416,6.78137766 18.4818852,6.98295535 17.4229478,7.38991407 C16.3640105,7.79306945 15.4460108,8.35216228 14.6689489,9.05958586 C13.8918869,9.76700944 13.324327,10.6075504 12.9624599,11.5736019 L16.5773216,13.3269475 C16.9163339,12.5168334 17.4496117,11.85505 18.1809642,11.3377941 C18.9123166,10.8205381 19.7503246,10.5619101 20.6987972,10.5619101 C21.7120251,10.5619101 22.5233692,10.8281448 23.1328295,11.3530075 C23.7422899,11.8816735 24.0432109,12.5396535 24.0432109,13.3269475 L24.0432109,13.9202705 L18.6037773,14.810255 C17.1601181,15.0346528 15.9678613,15.4187914 15.0231977,15.9588674 C14.0785342,16.4989435 13.3738456,17.1683336 12.9129412,17.9670376 C12.4520368,18.7657417 12.2196801,19.6709396 12.2196801,20.6826314 C12.2196801,21.6943231 12.4787007,22.6679814 12.996742,23.4666854 C13.5147833,24.2653895 14.253754,24.8777293 15.2098449,25.3037047 C16.1659359,25.7296802 17.2858193,25.9464713 18.5733043,25.9464713 C19.5865321,25.9464713 20.5045318,25.813354 21.3273032,25.543316 C22.1500747,25.2732779 22.8928545,24.8701226 23.5556427,24.3300465 C23.8260907,24.109452 24.0774931,23.8660375 24.3174681,23.6112128 L24.3174681,25.5471193 L28.507508,25.5471193 L28.507508,13.3345542 C28.507508,12.0528243 28.1875413,10.9156219 27.5437988,9.92675025 C26.9000563,8.93787857 26.0049114,8.165798 24.8583642,7.61431187 L24.8583642,7.60670517 Z M23.4718419,20.3403296 C23.08712,21.0363431 22.5424148,21.5840259 21.8339172,21.9757712 C21.1254195,22.3713199 20.3064571,22.5652909 19.3846484,22.5652909 C18.6647233,22.5652909 18.0628812,22.3789266 17.5753129,22.0100014 C17.0915538,21.6372729 16.8477696,21.1390337 16.8477696,20.5076771 C16.8477696,19.8763206 17.0610808,19.3324412 17.4915121,18.9406959 C17.9181344,18.5489506 18.5733043,18.2713058 19.4494035,18.1153684 L24.0432109,17.3014509 L24.0432109,18.0811382 C24.0432109,18.8912523 23.8527546,19.6443161 23.4680327,20.3403296 L23.4718419,20.3403296 Z" id="Shape"></path>
<path d="M37.8893883,11.5545852 C38.6435955,11.0943795 39.5044583,10.862375 40.4757857,10.862375 C41.5118683,10.862375 42.429868,11.1362164 43.2297847,11.6877025 C44.0297014,12.2391886 44.5972613,12.9846457 44.9362737,13.9316805 L48.8215834,12.2125652 C48.4597163,11.132413 47.8693016,10.1891816 47.0465301,9.37906747 C46.2237587,8.56895337 45.2486221,7.93379349 44.1249296,7.47358783 C42.9974279,7.01338217 41.7823163,6.78137766 40.4757857,6.78137766 C38.628359,6.78137766 36.9790069,7.19594309 35.5277295,8.02887731 C34.076452,8.86181152 32.9299047,9.99521059 32.099515,11.4366812 C31.2653161,12.8743485 30.8501213,14.5059868 30.8501213,16.3277926 C30.8501213,18.1495985 31.2729344,19.7850401 32.1185606,21.2341175 C32.9641869,22.6831948 34.106925,23.8318073 35.5467751,24.6761516 C36.9866252,25.5204958 38.6321681,25.942668 40.4795948,25.942668 C41.8089802,25.942668 43.0355192,25.7106635 44.1630208,25.2504578 C45.2905225,24.7902521 46.2580408,24.1474856 47.0693849,23.3259614 C47.880729,22.5044372 48.4673346,21.5764192 48.8253925,20.5419073 L44.9400828,18.7885618 C44.5782157,19.7317932 44.0106558,20.484857 43.2335938,21.0477532 C42.4565319,21.6106494 41.5385322,21.8920975 40.4795948,21.8920975 C39.5120765,21.8920975 38.6474046,21.6562896 37.8931975,21.1846739 C37.1389903,20.7130582 36.5485756,20.0588815 36.1181442,19.2297507 C35.6915219,18.3968165 35.4744017,17.442175 35.4744017,16.3620228 C35.4744017,15.2818707 35.6877128,14.3538527 36.1181442,13.5133117 C36.5447664,12.6689675 37.1351811,12.0185942 37.8931975,11.5583885 L37.8893883,11.5545852 Z" id="Path"></path>
<path d="M60.5041766,21.8616707 C59.7842515,21.8616707 59.1900277,21.7475701 58.7291233,21.5231723 C58.2682189,21.2987745 57.9292066,20.9716862 57.7158955,20.5457107 C57.5025843,20.1197352 57.3959288,19.6024792 57.3959288,18.9939428 L57.3959288,11.132413 L61.6202508,11.132413 L61.6202508,7.18453303 L57.3959288,7.18453303 L57.3959288,3.00084519 L52.9354408,3.00084519 L52.9354408,4.4194957 C52.9354408,5.32089027 52.6916567,6.00549373 52.2078975,6.47710945 C51.7241384,6.94872517 51.0308772,7.18453303 50.1319232,7.18453303 L49.7929109,7.18453303 L49.7929109,11.132413 L52.9354408,11.132413 L52.9354408,19.2297507 C52.9354408,21.2987745 53.510619,22.8999859 54.6571663,24.0371883 C55.8075227,25.1743908 57.4149744,25.7410903 59.4871396,25.7410903 C59.8261519,25.7410903 60.1956373,25.7182702 60.6032139,25.6726299 C61.0069814,25.6269897 61.3688484,25.5813495 61.685006,25.5395126 L61.685006,21.7627835 C61.4831223,21.7856036 61.2736203,21.8084237 61.0603091,21.8312438 C60.846998,21.854064 60.6603508,21.865474 60.5041766,21.865474 L60.5041766,21.8616707 Z" id="Path"></path>
<path d="M72.9257404,15.0118327 L69.9850942,14.1332582 C69.5584719,14.0001409 69.1813683,13.8518101 68.8537834,13.6958727 C68.5261985,13.5399352 68.2747961,13.3383575 68.091958,13.0873362 C67.912929,12.8401183 67.8215099,12.5586702 67.8215099,12.242992 C67.8215099,11.6800958 68.0348211,11.2351035 68.4652524,10.9118186 C68.8918747,10.5847302 69.4784803,10.4211861 70.2212601,10.4211861 C71.1430689,10.4211861 71.9772677,10.6646006 72.7200475,11.1476264 C73.4628273,11.6306522 73.992296,12.2886322 74.3084536,13.1215664 L77.6871494,11.5355684 C77.1233986,10.0294408 76.178735,8.85800817 74.8493496,8.02887731 C73.5199642,7.19594309 71.9886951,6.78137766 70.2555422,6.78137766 C68.9261568,6.78137766 67.7491365,7.01718552 66.7244813,7.48880124 C65.6998261,7.96041696 64.8999094,8.61839696 64.3247312,9.46274123 C63.7495529,10.3070855 63.4638684,11.2883505 63.4638684,12.4141428 C63.4638684,13.6730525 63.8638268,14.7684181 64.6637435,15.7040428 C65.4636602,16.6358642 66.6292531,17.3166643 68.1605223,17.7464432 L71.1659236,18.5907874 C71.5696911,18.704888 71.9315582,18.8456121 72.2477157,19.0129596 C72.5638733,19.1803071 72.8152757,19.3856881 73.0095412,19.621496 C73.1999976,19.8573038 73.2952257,20.1577687 73.2952257,20.5152838 C73.2952257,21.1010001 73.0590598,21.5726159 72.5867281,21.9339344 C72.1143963,22.2952529 71.4820812,22.4740104 70.6935918,22.4740104 C69.6575092,22.4740104 68.7166548,22.1811523 67.8710286,21.595436 C67.0254023,21.0097197 66.3664233,20.1996056 65.8940916,19.1650937 L62.5496779,20.7510917 C63.1134287,22.3484998 64.1190383,23.6150162 65.5741249,24.5468376 C67.0254023,25.478659 68.7318913,25.9464713 70.6935918,25.9464713 C72.0915415,25.9464713 73.3066531,25.7106635 74.3427357,25.2390478 C75.3788183,24.767432 76.1901624,24.109452 76.776768,23.2651078 C77.3633736,22.4207635 77.6566764,21.4509086 77.6566764,20.3479363 C77.6566764,19.0433864 77.2452906,17.9404141 76.4225192,17.0428229 C75.5997477,16.1452317 74.4341548,15.468235 72.9257404,15.0194394 L72.9257404,15.0118327 Z" id="Path"></path>
</g>
<g transform="translate(46.5018, 18.5528)" fill="#FFFFFF">
<path d="M14.6613306,1.24749965 C13.1986258,0.414565432 11.5530828,0 9.72851089,0 C7.90393896,0 6.29267815,0.414565432 4.8299733,1.24749965 C3.36726845,2.08043386 2.19405727,3.21383293 1.31795801,4.65530356 C0.438049629,6.09297084 0,7.73601916 0,9.58064516 C0,11.4252712 0.438049629,13.0683195 1.31795801,14.5059868 C2.1978664,15.943654 3.37107758,17.0808565 4.84901894,17.9137907 C6.32315116,18.7467249 7.94964848,19.1612903 9.73232002,19.1612903 C11.5149915,19.1612903 13.1681528,18.7467249 14.6308576,17.9137907 C16.0935625,17.0808565 17.2667736,15.9474574 18.1428729,14.5059868 C19.0227813,13.0683195 19.4608309,11.4252712 19.4608309,9.58064516 C19.4608309,7.73601916 19.0265904,6.06634737 18.1619185,4.64009015 C17.2934375,3.21383293 16.1278446,2.08043386 14.6651397,1.251303 L14.6613306,1.24749965 Z M14.1699532,12.4293562 C13.7319036,13.2737005 13.1338706,13.9316805 12.3796634,14.4032962 C11.6254562,14.874912 10.7417387,15.1107198 9.72851089,15.1107198 C8.71528305,15.1107198 7.85822943,14.874912 7.09259486,14.4032962 C6.32696029,13.9316805 5.72511819,13.2737005 5.28325944,12.4293562 C4.84520981,11.585012 4.62428043,10.6379772 4.62428043,9.58064516 C4.62428043,8.52331314 4.84520981,7.5800817 5.28325944,6.74714749 C5.72130907,5.91421327 6.32315116,5.26383998 7.09259486,4.79222426 C7.85822943,4.32060854 8.73813781,4.08480068 9.72851089,4.08480068 C10.718884,4.08480068 11.6254562,4.32060854 12.3796634,4.79222426 C13.1338706,5.26383998 13.7319036,5.91801662 14.1699532,6.74714749 C14.6080028,7.5800817 14.8289322,8.52331314 14.8289322,9.58064516 C14.8289322,10.6379772 14.6080028,11.5888153 14.1699532,12.4293562 Z" id="Shape"></path>
<path d="M36.6399946,1.28172982 C35.2420449,0.42597549 33.6764936,0 31.9433408,0 C30.5263454,0 29.2731426,0.273841386 28.1951596,0.825327511 C27.3304877,1.26651641 26.6105627,1.85223271 26.0315753,2.5748697 L26.0315753,0.403155374 L21.8415354,0.403155374 L21.8415354,25.5014791 L26.3020234,25.5014791 L26.3020234,16.9933793 C26.8429194,17.5676856 27.4904711,18.035498 28.2446782,18.3854064 C29.3607525,18.9026623 30.5796732,19.1612903 31.9090586,19.1612903 C33.687921,19.1612903 35.2763271,18.7391182 36.6742768,17.8947739 C38.0722264,17.0504296 39.1730642,15.8980138 39.9844083,14.4375264 C40.7957524,12.977039 41.1995199,11.3568108 41.1995199,9.58064516 C41.1995199,7.8044795 40.7881341,6.16143119 39.9653627,4.72376391 C39.1425912,3.28609663 38.0341352,2.13748415 36.6361855,1.28172982 L36.6399946,1.28172982 Z M35.931497,12.448373 C35.4820199,13.2813072 34.8687505,13.9316805 34.0916885,14.4032962 C33.3146266,14.874912 32.4194817,15.1107198 31.4062538,15.1107198 C30.393026,15.1107198 29.5664454,14.874912 28.7893834,14.4032962 C28.0123215,13.9316805 27.4028611,13.2775039 26.9648115,12.448373 C26.5267619,11.6154388 26.3058325,10.6607973 26.3058325,9.58064516 C26.3058325,8.50049303 26.5267619,7.5800817 26.9648115,6.74714749 C27.4028611,5.91421327 28.0123215,5.26383998 28.7893834,4.79222426 C29.5664454,4.32060854 30.4387355,4.08480068 31.4062538,4.08480068 C32.3737721,4.08480068 33.3146266,4.32060854 34.0916885,4.79222426 C34.8687505,5.26383998 35.4820199,5.91801662 35.931497,6.74714749 C36.380974,7.5800817 36.6057125,8.52331314 36.6057125,9.58064516 C36.6057125,10.6379772 36.380974,11.6154388 35.931497,12.448373 Z" id="Shape"></path>
<path d="M58.5348578,2.66615016 C57.7692232,1.83321595 56.8397962,1.18284265 55.7465767,0.711226933 C54.6533572,0.239611213 53.3887269,0.00380335258 51.9450677,0.00380335258 C50.2119149,0.00380335258 48.657791,0.418368784 47.282696,1.251303 C45.9076011,2.08423722 44.8181907,3.21383293 44.0068466,4.64009015 C43.1955025,6.07015073 42.7917351,7.70559234 42.7917351,9.54641499 C42.7917351,11.3872376 43.1840752,12.9428088 43.9725645,14.4032962 C44.7610538,15.8637836 45.8695098,17.0238062 47.3017417,17.8795605 C48.7301644,18.7353148 50.394753,19.1612903 52.2840801,19.1612903 C53.5220464,19.1612903 54.6724028,18.9711227 55.7313402,18.5869841 C56.7902775,18.2066488 57.7006589,17.6779828 58.4662935,17.0009861 C59.2319281,16.3277926 59.7956789,15.5519087 60.1537369,14.6733343 L56.5731573,12.9199887 C56.1693898,13.6388224 55.6094481,14.2131286 54.9009504,14.6391041 C54.1924528,15.0650796 53.33159,15.2818707 52.3145531,15.2818707 C51.2975161,15.2818707 50.4061803,15.0460628 49.6291184,14.5744471 C48.8520564,14.1028314 48.2654508,13.4334413 47.8731107,12.5662769 C47.6369449,12.0452176 47.4883889,11.4785181 47.4274429,10.862375 L60.6603508,10.862375 C60.7517698,10.5923369 60.8127159,10.2956754 60.846998,9.96858712 C60.8812802,9.6414988 60.8965167,9.31060713 60.8965167,8.97210875 C60.8965167,7.73601916 60.6946329,6.58360332 60.2870563,5.51486125 C59.8832888,4.44611917 59.2966832,3.49528103 58.5310487,2.66615016 L58.5348578,2.66615016 Z M47.5188619,7.45457107 C47.5950445,7.0666291 47.7017,6.70911396 47.8426377,6.37441893 C48.2159322,5.49584449 48.7606374,4.82265108 49.4805624,4.35103536 C50.2004875,3.87941964 51.0232589,3.64361178 51.9450677,3.64361178 C52.8668765,3.64361178 53.7391666,3.87941964 54.4286187,4.35103536 C55.1142616,4.82265108 55.6094481,5.46541766 55.9141783,6.27553177 C56.0513069,6.64445697 56.1351077,7.04000563 56.1617716,7.45457107 L47.5150528,7.45457107 L47.5188619,7.45457107 Z" id="Shape"></path>
<path d="M76.3349092,0.878574447 C75.2873993,0.292858149 74.0875242,0 72.735284,0 C71.3830439,0 70.2860152,0.285251444 69.3070695,0.859557684 C68.5490532,1.30074658 67.9624476,1.90547965 67.5320163,2.66234681 L67.5320163,0.406958727 L63.3419763,0.406958727 L63.3419763,18.7581349 L67.8024643,18.7581349 L67.8024643,7.99845049 C67.8024643,7.18833639 67.9586385,6.49232286 68.2747961,5.90660656 C68.5909536,5.32089027 69.0290033,4.87209466 69.5927541,4.5564164 C70.1565049,4.24073813 70.7964383,4.08480068 71.5201725,4.08480068 C72.2439066,4.08480068 72.88384,4.24073813 73.4475908,4.5564164 C74.0113416,4.87209466 74.4493913,5.32089027 74.7655488,5.90660656 C75.0817064,6.49232286 75.2378806,7.18833639 75.2378806,7.99845049 L75.2378806,18.7581349 L79.6640864,18.7581349 L79.6640864,6.95252853 C79.6640864,5.57951824 79.3707836,4.37005212 78.784178,3.32793351 C78.1975724,2.28201155 77.3824192,1.4680941 76.3349092,0.8823778 L76.3349092,0.878574447 Z" id="Path"></path>
</g>
<g>
<g transform="translate(0.0038, 9.3562)">
<path d="M0,18.628821 C0,8.34075222 8.35341597,0 18.6571051,0 C28.9607942,0 37.3142101,8.34075222 37.3142101,18.628821 L0,18.628821 Z" id="Path" fill="#FF8C14"></path>
<ellipse id="Oval" fill="#8C3C00" cx="24.9764471" cy="6.59881673" rx="1.02465522" ry="1.02310185"></ellipse>
<ellipse id="Oval" fill="#8C3C00" cx="29.974022" cy="11.1780532" rx="1.02465522" ry="1.02310185"></ellipse>
<ellipse id="Oval" fill="#8C3C00" cx="24.3212772" cy="12.3000423" rx="1.02465522" ry="1.02310185"></ellipse>
</g>
<path d="M9.61423707,44.2710241 L6.3002964,52.1743908 L10.6617471,54 L14.0061607,46.028173 C15.4955295,46.4123116 17.0572716,46.6176926 18.6647233,46.6176926 C28.9531759,46.6176926 37.3218284,38.261727 37.3218284,27.9888717 L32.5908924,27.9888717 C32.5908924,35.6564305 26.3439238,41.8977321 18.6609142,41.8977321 C10.9779046,41.8977321 4.73093599,35.6602338 4.73093599,27.9888717 L0,27.9888717 C0,34.9832371 3.88530975,41.087618 9.61042795,44.2748274 L9.61423707,44.2710241 Z" id="Path" fill="#FFFFFF"></path>
<path d="M26.3782059,0 C22.7100164,0 19.5217769,2.04240034 17.8800431,5.04704888 C19.1446733,6.21848148 20.093146,7.72080575 20.5883326,9.42090435 C24.8583642,8.4624595 28.0618401,4.69714044 28.1380227,0.163544161 C27.5666536,0.0570502888 26.980048,0 26.3782059,0 L26.3782059,0 Z" id="Path" fill="#00641E"></path>
<path d="M11.2978713,2.45696577 C10.570328,2.45696577 9.86563947,2.54063953 9.1838057,2.68897028 C10.3532078,6.71672066 14.0709159,9.66051557 18.4780761,9.66051557 C19.2056194,9.66051557 19.9103079,9.57684181 20.5921417,9.42851106 C19.4227396,5.40076067 15.7050315,2.45696577 11.2978713,2.45696577 Z" id="Path" fill="#00961E"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -108,6 +108,11 @@ class UserPreferences extends ChangeNotifier {
static const String _TAG_USER_KNOWLEDGE_PANEL_ORDER = static const String _TAG_USER_KNOWLEDGE_PANEL_ORDER =
'userKnowledgePanelOrder'; 'userKnowledgePanelOrder';
/// Tagline feed (news displayed / clicked)
static const String _TAG_TAGLINE_FEED_NEWS_DISPLAYED =
'taglineFeedNewsDisplayed';
static const String _TAG_TAGLINE_FEED_NEWS_CLICKED = 'taglineFeedNewsClicked';
Future<void> init(final ProductPreferences productPreferences) async { Future<void> init(final ProductPreferences productPreferences) async {
await _onMigrate(); await _onMigrate();
@ -372,4 +377,56 @@ class UserPreferences extends ChangeNotifier {
_TAG_USER_KNOWLEDGE_PANEL_ORDER, source); _TAG_USER_KNOWLEDGE_PANEL_ORDER, source);
notifyListeners(); notifyListeners();
} }
List<String> get taglineFeedDisplayedNews =>
_sharedPreferences.getStringList(_TAG_TAGLINE_FEED_NEWS_DISPLAYED) ??
<String>[];
List<String> get taglineFeedClickedNews =>
_sharedPreferences.getStringList(_TAG_TAGLINE_FEED_NEWS_CLICKED) ??
<String>[];
// This method voluntarily does not notify listeners (not needed)
Future<void> taglineFeedMarkNewsAsDisplayed(final String ids) async {
final List<String> displayedNews = taglineFeedDisplayedNews;
final List<String> clickedNews = taglineFeedClickedNews;
if (!displayedNews.contains(ids)) {
displayedNews.add(ids);
_sharedPreferences.setStringList(
_TAG_TAGLINE_FEED_NEWS_DISPLAYED,
displayedNews,
);
}
if (clickedNews.contains(ids)) {
clickedNews.remove(ids);
_sharedPreferences.setStringList(
_TAG_TAGLINE_FEED_NEWS_CLICKED,
clickedNews,
);
}
}
// This method voluntarily does not notify listeners (not needed)
Future<void> taglineFeedMarkNewsAsClicked(final String ids) async {
final List<String> displayedNews = taglineFeedDisplayedNews;
final List<String> clickedNews = taglineFeedClickedNews;
if (displayedNews.contains(ids)) {
displayedNews.remove(ids);
_sharedPreferences.setStringList(
_TAG_TAGLINE_FEED_NEWS_DISPLAYED,
displayedNews,
);
}
if (!clickedNews.contains(ids)) {
clickedNews.add(ids);
_sharedPreferences.setStringList(
_TAG_TAGLINE_FEED_NEWS_CLICKED,
clickedNews,
);
}
}
} }

View File

@ -1,88 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:smooth_app/helpers/collections_helper.dart';
import 'package:smooth_app/query/product_query.dart';
/// A tagline is the text displayed on the homepage
/// It may contain a link to an external resource
/// No cache is expected here
/// API URL: [https://world.openfoodfacts.org/files/tagline-off-ios-v2.json] or
/// [https://world.openfoodfacts.org/files/tagline-off-android-v2.json]
Future<TagLineItem?> fetchTagLine() {
final String locale = ProductQuery.getLanguage().code;
return http
.get(
Uri.https(
'world.openfoodfacts.org',
_tagLineUrl,
),
)
.then(
(http.Response value) => const Utf8Decoder().convert(value.bodyBytes))
.then((String value) =>
_TagLine.fromJSON(jsonDecode(value) as List<dynamic>))
.then((_TagLine tagLine) => tagLine[locale] ?? tagLine['en'])
.catchError((dynamic err) => null);
}
/// Based on the platform, the URL may differ
String get _tagLineUrl {
if (Platform.isIOS || Platform.isMacOS) {
return '/files/tagline-off-ios-v2.json';
} else {
return '/files/tagline-off-android-v2.json';
}
}
class _TagLine {
_TagLine.fromJSON(List<dynamic> json)
: _items = Map<String, TagLineItem>.fromEntries(
json.map(
(dynamic element) {
return MapEntry<String, TagLineItem>(
((element as Map<dynamic, dynamic>)['language'] as String)
.toLowerCase(),
TagLineItem._fromJSON(element['data'] as Map<dynamic, dynamic>),
);
},
),
);
/// Taglines by their locale
final Map<String, TagLineItem> _items;
/// Finds a tagline with its locale
TagLineItem? operator [](String key) {
final String locale = key.toLowerCase();
// Let's try with the full locale
if (_items.containsKey(locale)) {
return _items[locale];
}
// Let's try with the language only (eg => fr_FR to fr)
final String languageCode = locale.substring(0, 2);
if (_items.containsKey(languageCode)) {
return _items[languageCode];
} else {
// Finally let's try with a subset (eg => no fr_BE but fr_FR)
return _items.getValueByKeyStartWith(languageCode, ignoreCase: true);
}
}
}
class TagLineItem {
TagLineItem._fromJSON(Map<dynamic, dynamic> json)
: url = json['url'] as String,
message = json['message'] as String;
final String url;
final String message;
bool get hasLink => url.startsWith('http');
}

View File

@ -0,0 +1,415 @@
part of 'tagline_provider.dart';
/// Content from the JSON and converted to what's in "tagmodel.dart"
class _TagLineJSON {
_TagLineJSON.fromJson(Map<dynamic, dynamic> json)
: news = (json['news'] as Map<dynamic, dynamic>).map(
(dynamic id, dynamic value) => MapEntry<String, _TagLineItemNewsItem>(
id,
_TagLineItemNewsItem.fromJson(id, value),
),
),
taglineFeed = _TaglineJSONFeed.fromJson(json['tagline_feed']);
final _TagLineJSONNewsList news;
final _TaglineJSONFeed taglineFeed;
TagLine toTagLine(String locale) {
final Map<String, TagLineNewsItem> tagLineNews = news.map(
(String key, _TagLineItemNewsItem value) =>
MapEntry<String, TagLineNewsItem>(
key,
value.toTagLineItem(locale),
),
);
final _TagLineJSONFeedLocale localizedFeed = taglineFeed.loadNews(locale);
final Iterable<TagLineFeedItem> feed = localizedFeed.news
.map((_TagLineJSONFeedLocaleItem item) {
if (news[item.id] == null) {
// The asked ID doesn't exist in the news
return null;
}
return item.overrideNewsItem(news[item.id]!, locale);
})
.where((TagLineFeedItem? item) =>
item != null &&
(item.startDate == null ||
item.startDate!.isBefore(DateTime.now())) &&
(item.endDate == null || item.endDate!.isAfter(DateTime.now())))
.whereNotNull();
return TagLine(
news: TagLineNewsList(tagLineNews),
feed: TagLineFeed(
feed.toList(growable: false),
),
);
}
}
typedef _TagLineJSONNewsList = Map<String, _TagLineItemNewsItem>;
class _TagLineItemNewsItem {
const _TagLineItemNewsItem._({
required this.id,
required this.url,
required _TagLineItemNewsTranslations translations,
this.startDate,
this.endDate,
this.style,
}) : _translations = translations;
_TagLineItemNewsItem.fromJson(this.id, Map<dynamic, dynamic> json)
: assert((json['url'] as String).isNotEmpty),
url = json['url'],
assert((json['translations'] as Map<dynamic, dynamic>)
.containsKey('default')),
_translations = (json['translations'] as Map<dynamic, dynamic>)
.map((dynamic key, dynamic value) {
if (key == 'default') {
return MapEntry<String, _TagLineItemNewsTranslation>(
key, _TagLineItemNewsTranslationDefault.fromJson(value));
} else {
return MapEntry<String, _TagLineItemNewsTranslation>(
key,
_TagLineItemNewsTranslation.fromJson(value),
);
}
}),
startDate = DateTime.tryParse(json['start_date']),
endDate = DateTime.tryParse(json['end_date']),
style = json['style'] == null
? null
: _TagLineNewsStyle.fromJson(json['style']);
final String id;
final String url;
final _TagLineItemNewsTranslations _translations;
final DateTime? startDate;
final DateTime? endDate;
final _TagLineNewsStyle? style;
_TagLineItemNewsTranslation loadTranslation(String locale) {
_TagLineItemNewsTranslation? translation;
// Direct match
if (_translations.containsKey(locale)) {
translation = _translations[locale];
} else if (locale.contains('_')) {
final String languageCode = locale.split('_').first;
if (_translations.containsKey(languageCode)) {
translation = _translations[languageCode];
}
}
return _translations['default']!.merge(translation);
}
TagLineNewsItem toTagLineItem(String locale) {
final _TagLineItemNewsTranslation translation = loadTranslation(locale);
// We can assume the default translation has a non-null title and message
return TagLineNewsItem(
id: id,
title: translation.title!,
message: translation.message!,
url: translation.url ?? url,
buttonLabel: translation.buttonLabel,
startDate: startDate,
endDate: endDate,
style: style?.toTagLineStyle(),
image: translation.image?.toTagLineImage(),
);
}
_TagLineItemNewsItem copyWith({
String? url,
_TagLineItemNewsTranslations? translations,
DateTime? startDate,
DateTime? endDate,
_TagLineNewsStyle? style,
}) {
return _TagLineItemNewsItem._(
id: id,
// Still the same
url: url ?? this.url,
translations: translations ?? _translations,
startDate: startDate ?? this.startDate,
endDate: endDate ?? this.endDate,
style: style ?? this.style,
);
}
}
typedef _TagLineItemNewsTranslations = Map<String, _TagLineItemNewsTranslation>;
class _TagLineItemNewsTranslation {
_TagLineItemNewsTranslation._({
this.title,
this.message,
this.url,
this.buttonLabel,
this.image,
});
_TagLineItemNewsTranslation.fromJson(Map<dynamic, dynamic> json)
: assert(json['title'] == null || (json['title'] as String).isNotEmpty),
assert(
json['message'] == null || (json['message'] as String).isNotEmpty),
assert(json['url'] == null || (json['url'] as String).isNotEmpty),
assert(json['button_label'] == null ||
(json['button_label'] as String).isNotEmpty),
title = json['title'],
message = json['message'],
url = json['url'],
buttonLabel = json['button_label'],
image = json['image'] == null
? null
: _TagLineNewsImage.fromJson(json['image']);
final String? title;
final String? message;
final String? url;
final String? buttonLabel;
final _TagLineNewsImage? image;
_TagLineItemNewsTranslation copyWith({
String? title,
String? message,
String? url,
String? buttonLabel,
_TagLineNewsImage? image,
}) {
return _TagLineItemNewsTranslation._(
title: title ?? this.title,
message: message ?? this.message,
url: url ?? this.url,
buttonLabel: buttonLabel ?? this.buttonLabel,
image: image ?? this.image,
);
}
_TagLineItemNewsTranslation merge(_TagLineItemNewsTranslation? other) {
if (other == null) {
return this;
}
return copyWith(
title: other.title,
message: other.message,
url: other.url,
buttonLabel: other.buttonLabel,
image: other.image,
);
}
}
class _TagLineItemNewsTranslationDefault extends _TagLineItemNewsTranslation {
_TagLineItemNewsTranslationDefault.fromJson(Map<dynamic, dynamic> json)
: assert((json['title'] as String).isNotEmpty),
assert((json['message'] as String).isNotEmpty),
super.fromJson(json);
}
class _TagLineNewsImage {
_TagLineNewsImage.fromJson(Map<dynamic, dynamic> json)
: assert((json['url'] as String).isNotEmpty),
assert(json['width'] == null ||
((json['width'] as num) >= 0.0 && (json['width'] as num) <= 1.0)),
assert(json['alt'] == null || (json['alt'] as String).isNotEmpty),
url = json['url'],
width = json['width'],
alt = json['alt'];
final String url;
final double? width;
final String? alt;
TagLineImage toTagLineImage() {
return TagLineImage(
src: url,
width: width,
alt: alt,
);
}
}
class _TagLineNewsStyle {
_TagLineNewsStyle._({
this.titleBackground,
this.titleTextColor,
this.titleIndicatorColor,
this.messageBackground,
this.messageTextColor,
this.buttonBackground,
this.buttonTextColor,
this.contentBackgroundColor,
});
_TagLineNewsStyle.fromJson(Map<dynamic, dynamic> json)
: assert(json['title_background'] == null ||
(json['title_background'] as String).startsWith('#')),
assert(json['title_text_color'] == null ||
(json['title_text_color'] as String).startsWith('#')),
assert(json['title_indicator_color'] == null ||
(json['title_indicator_color'] as String).startsWith('#')),
assert(json['message_background'] == null ||
(json['message_background'] as String).startsWith('#')),
assert(json['message_text_color'] == null ||
(json['message_text_color'] as String).startsWith('#')),
assert(json['button_background'] == null ||
(json['button_background'] as String).startsWith('#')),
assert(json['button_text_color'] == null ||
(json['button_text_color'] as String).startsWith('#')),
assert(json['content_background_color'] == null ||
(json['content_background_color'] as String).startsWith('#')),
titleBackground = json['title_background'],
titleTextColor = json['title_text_color'],
titleIndicatorColor = json['title_indicator_color'],
messageBackground = json['message_background'],
messageTextColor = json['message_text_color'],
buttonBackground = json['button_background'],
buttonTextColor = json['button_text_color'],
contentBackgroundColor = json['content_background_color'];
final String? titleBackground;
final String? titleTextColor;
final String? titleIndicatorColor;
final String? messageBackground;
final String? messageTextColor;
final String? buttonBackground;
final String? buttonTextColor;
final String? contentBackgroundColor;
_TagLineNewsStyle copyWith({
String? titleBackground,
String? titleTextColor,
String? titleIndicatorColor,
String? messageBackground,
String? messageTextColor,
String? buttonBackground,
String? buttonTextColor,
String? contentBackgroundColor,
}) {
return _TagLineNewsStyle._(
titleBackground: titleBackground ?? this.titleBackground,
titleTextColor: titleTextColor ?? this.titleTextColor,
titleIndicatorColor: titleIndicatorColor ?? this.titleIndicatorColor,
messageBackground: messageBackground ?? this.messageBackground,
messageTextColor: messageTextColor ?? this.messageTextColor,
buttonBackground: buttonBackground ?? this.buttonBackground,
buttonTextColor: buttonTextColor ?? this.buttonTextColor,
contentBackgroundColor:
contentBackgroundColor ?? this.contentBackgroundColor,
);
}
TagLineStyle toTagLineStyle() => TagLineStyle.fromHexa(
titleBackground: titleBackground,
titleTextColor: titleTextColor,
titleIndicatorColor: titleIndicatorColor,
messageBackground: messageBackground,
messageTextColor: messageTextColor,
buttonBackground: buttonBackground,
buttonTextColor: buttonTextColor,
contentBackgroundColor: contentBackgroundColor,
);
}
class _TaglineJSONFeed {
_TaglineJSONFeed.fromJson(Map<dynamic, dynamic> json)
: assert(json.containsKey('default')),
_news = json.map(
(dynamic key, dynamic value) =>
MapEntry<String, _TagLineJSONFeedLocale>(
key,
_TagLineJSONFeedLocale.fromJson(value),
),
);
final _TagLineJSONFeedList _news;
_TagLineJSONFeedLocale loadNews(String locale) {
// Direct match
if (_news.containsKey(locale)) {
return _news[locale]!;
}
// Try by language
if (locale.contains('_')) {
final String languageCode = locale.split('_').first;
if (_news.containsKey(languageCode)) {
return _news[languageCode]!;
}
}
return _news['default']!;
}
}
typedef _TagLineJSONFeedList = Map<String, _TagLineJSONFeedLocale>;
class _TagLineJSONFeedLocale {
_TagLineJSONFeedLocale.fromJson(Map<dynamic, dynamic> json)
: assert(json['news'] is Iterable<dynamic>),
news = (json['news'] as Iterable<dynamic>)
.map((dynamic json) => _TagLineJSONFeedLocaleItem.fromJson(json));
final Iterable<_TagLineJSONFeedLocaleItem> news;
}
class _TagLineJSONFeedLocaleItem {
_TagLineJSONFeedLocaleItem.fromJson(Map<dynamic, dynamic> json)
: assert((json['id'] as String).isNotEmpty),
id = json['id'],
overrideContent = json['override'] != null
? _TagLineJSONFeedNewsItemOverride.fromJson(
json['override'] as Map<dynamic, dynamic>)
: null;
final String id;
final _TagLineJSONFeedNewsItemOverride? overrideContent;
TagLineFeedItem overrideNewsItem(
_TagLineItemNewsItem newsItem,
String locale,
) {
_TagLineItemNewsItem item = newsItem;
if (overrideContent != null) {
item = newsItem.copyWith(
url: overrideContent!.url ?? newsItem.url,
startDate: overrideContent!.startDate ?? newsItem.startDate,
endDate: overrideContent!.endDate ?? newsItem.endDate,
style: overrideContent!.style ?? newsItem.style,
);
}
final TagLineNewsItem tagLineItem = item.toTagLineItem(locale);
return TagLineFeedItem(
news: tagLineItem,
startDate: tagLineItem.startDate,
endDate: tagLineItem.endDate,
);
}
}
class _TagLineJSONFeedNewsItemOverride {
_TagLineJSONFeedNewsItemOverride.fromJson(Map<dynamic, dynamic> json)
: assert(json['url'] == null || (json['url'] as String).isNotEmpty),
url = json['url'],
startDate = json['start_date'] != null
? DateTime.tryParse(json['start_date'])
: null,
endDate = json['end_date'] != null
? DateTime.tryParse(json['end_date'])
: null,
style = json['style'] == null
? null
: _TagLineNewsStyle.fromJson(json['style']);
final String? url;
final DateTime? startDate;
final DateTime? endDate;
final _TagLineNewsStyle? style;
}

View File

@ -0,0 +1,164 @@
import 'dart:ui';
class TagLine {
const TagLine({
required this.news,
required this.feed,
});
final TagLineNewsList news;
final TagLineFeed feed;
@override
String toString() {
return 'TagLine{news: $news, feed: $feed}';
}
}
class TagLineNewsList {
const TagLineNewsList(Map<String, TagLineNewsItem> news) : _news = news;
final Map<String, TagLineNewsItem> _news;
TagLineNewsItem? operator [](String key) => _news[key];
@override
String toString() {
return 'TagLineNewsList{_news: $_news}';
}
}
class TagLineNewsItem {
const TagLineNewsItem({
required this.id,
required this.title,
required this.message,
required this.url,
this.buttonLabel,
this.startDate,
this.endDate,
this.image,
this.style,
});
final String id;
final String title;
final String message;
final String url;
final String? buttonLabel;
final DateTime? startDate;
final DateTime? endDate;
final TagLineImage? image;
final TagLineStyle? style;
@override
String toString() {
return 'TagLineNewsItem{id: $id, title: $title, message: $message, url: $url, buttonLabel: $buttonLabel, startDate: $startDate, endDate: $endDate, image: $image, style: $style}';
}
}
class TagLineStyle {
const TagLineStyle({
this.titleBackground,
this.titleTextColor,
this.titleIndicatorColor,
this.messageBackground,
this.messageTextColor,
this.buttonBackground,
this.buttonTextColor,
this.contentBackgroundColor,
});
TagLineStyle.fromHexa({
String? titleBackground,
String? titleTextColor,
String? titleIndicatorColor,
String? messageBackground,
String? messageTextColor,
String? buttonBackground,
String? buttonTextColor,
String? contentBackgroundColor,
}) : titleBackground = _parseColor(titleBackground),
titleTextColor = _parseColor(titleTextColor),
titleIndicatorColor = _parseColor(titleIndicatorColor),
messageBackground = _parseColor(messageBackground),
messageTextColor = _parseColor(messageTextColor),
buttonBackground = _parseColor(buttonBackground),
buttonTextColor = _parseColor(buttonTextColor),
contentBackgroundColor = _parseColor(contentBackgroundColor);
final Color? titleBackground;
final Color? titleTextColor;
final Color? titleIndicatorColor;
final Color? messageBackground;
final Color? messageTextColor;
final Color? buttonBackground;
final Color? buttonTextColor;
final Color? contentBackgroundColor;
static Color? _parseColor(String? hexa) {
if (hexa == null || hexa.length != 7) {
return null;
}
return Color(int.parse(hexa.substring(1), radix: 16));
}
@override
String toString() {
return 'TagLineStyle{titleBackground: $titleBackground, titleTextColor: $titleTextColor, titleIndicatorColor: $titleIndicatorColor, messageBackground: $messageBackground, messageTextColor: $messageTextColor, buttonBackground: $buttonBackground, buttonTextColor: $buttonTextColor, contentBackgroundColor: $contentBackgroundColor}';
}
}
class TagLineImage {
const TagLineImage({
required this.src,
this.width,
this.alt,
});
final String src;
final double? width;
final String? alt;
@override
String toString() {
return 'TagLineImage{src: $src, width: $width, alt: $alt}';
}
}
class TagLineFeed {
const TagLineFeed(this.news);
final List<TagLineFeedItem> news;
bool get isNotEmpty => news.isNotEmpty;
@override
String toString() {
return 'TagLineFeed{news: $news}';
}
}
class TagLineFeedItem {
const TagLineFeedItem({
required this.news,
DateTime? startDate,
DateTime? endDate,
}) : _startDate = startDate,
_endDate = endDate;
final TagLineNewsItem news;
final DateTime? _startDate;
final DateTime? _endDate;
String get id => news.id;
DateTime? get startDate => _startDate ?? news.startDate;
DateTime? get endDate => _endDate ?? news.endDate;
@override
String toString() {
return 'TagLineFeedItem{news: $news, _startDate: $_startDate, _endDate: $_endDate}';
}
}

View File

@ -0,0 +1,148 @@
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:smooth_app/data_models/tagline/tagline_model.dart';
import 'package:smooth_app/query/product_query.dart';
part 'tagline_json.dart';
/// The TagLine provides one one side a list of news and on the other a feed
/// containing the some of the news
///
/// The TagLine is fetched on the server and cached locally (1 day).
/// To be notified of changes, listen to this [ChangeNotifier] and more
/// particularly to the [state] property
class TagLineProvider extends ChangeNotifier {
TagLineProvider() : _state = const TagLineLoading() {
loadTagLine();
}
TagLineState _state;
bool get hasContent => _state is TagLineLoaded;
Future<void> loadTagLine({bool forceUpdate = false}) async {
_emit(const TagLineLoading());
final String locale = ProductQuery.getLocaleString();
if (locale.startsWith('-')) {
// ProductQuery not ready
return;
}
final File cacheFile = await _tagLineCacheFile;
String? jsonString;
// Try from the cache first
if (!forceUpdate && _isTagLineCacheValid(cacheFile)) {
jsonString = cacheFile.readAsStringSync();
}
if (jsonString == null || jsonString.isEmpty == true) {
jsonString = await _fetchTagLine();
}
if (jsonString?.isNotEmpty != true) {
_emit(const TagLineError('JSON file is empty'));
return;
}
final TagLine? tagLine = await Isolate.run(
() => _parseJSONAndGetLocalizedContent(jsonString!, locale));
if (tagLine == null) {
_emit(const TagLineError('Unable to parse the JSON file'));
} else {
_emit(TagLineLoaded(tagLine));
}
}
void _emit(TagLineState state) {
_state = state;
try {
notifyListeners();
} catch (_) {}
}
TagLineState get state => _state;
static Future<TagLine?> _parseJSONAndGetLocalizedContent(
String json,
String locale,
) async {
try {
final _TagLineJSON tagLineJSON =
_TagLineJSON.fromJson(jsonDecode(json) as Map<dynamic, dynamic>);
return tagLineJSON.toTagLine(locale);
} catch (_) {
return null;
}
}
/// API URL: [https://world.openfoodfacts.org/files/tagline-off-ios-v3.json]
/// or [https://world.openfoodfacts.org/files/tagline-off-android-v3.json]
Future<String?> _fetchTagLine() async {
try {
final http.Response response =
await http.get(Uri.https('world.openfoodfacts.org', _tagLineUrl));
final String json = const Utf8Decoder().convert(response.bodyBytes);
if (!json.startsWith('[') && !json.startsWith('{')) {
throw Exception('Invalid JSON');
}
await _saveTagLineToCache(json);
return json;
} catch (_) {
return null;
}
}
/// Based on the platform, the URL may differ
String get _tagLineUrl {
if (Platform.isIOS || Platform.isMacOS) {
return '/files/tagline-off-ios-v3.json';
} else {
return '/files/tagline-off-android-v3.json';
}
}
Future<File> get _tagLineCacheFile => getApplicationCacheDirectory()
.then((Directory dir) => File(join(dir.path, 'tagline.json')));
Future<File> _saveTagLineToCache(final String json) async {
final File file = await _tagLineCacheFile;
return file.writeAsString(json);
}
bool _isTagLineCacheValid(File file) =>
file.existsSync() &&
file.lengthSync() > 0 &&
file
.lastModifiedSync()
.isAfter(DateTime.now().add(const Duration(days: -1)));
}
sealed class TagLineState {
const TagLineState();
}
final class TagLineLoading extends TagLineState {
const TagLineLoading();
}
class TagLineLoaded extends TagLineState {
const TagLineLoaded(this.tagLineContent);
final TagLine tagLineContent;
}
class TagLineError extends TagLineState {
const TagLineError(this.exception);
final dynamic exception;
}

View File

@ -38,3 +38,59 @@ class _ListenerState<T> extends SingleChildState<Listener<T>> {
return child ?? const SizedBox.shrink(); return child ?? const SizedBox.shrink();
} }
} }
/// Same as [Consumer] but only rebuilds if [buildWhen] returns true
/// (And on the first build)
class ConsumerFilter<T> extends StatefulWidget {
const ConsumerFilter({
required this.builder,
required this.buildWhen,
this.child,
super.key,
});
final Widget Function(
BuildContext context,
T value,
Widget? child,
) builder;
final bool Function(T? previousValue, T currentValue) buildWhen;
final Widget? child;
@override
State<ConsumerFilter<T>> createState() => _ConsumerFilterState<T>();
}
class _ConsumerFilterState<T> extends State<ConsumerFilter<T>> {
T? oldValue;
Widget? oldWidget;
@override
Widget build(BuildContext context) {
return Consumer<T>(
builder: (BuildContext context, T value, Widget? child) {
if (widget.buildWhen(oldValue, value) || oldWidget == null) {
oldWidget = widget.builder(
context,
value,
child,
);
}
oldValue = value;
return widget.builder(
context,
value,
oldWidget,
);
},
child: widget.child,
);
}
}
extension ValueNotifierExtensions<T> on ValueNotifier<T> {
void emit(T value) => this.value = value;
}

View File

@ -1,4 +1,4 @@
import 'package:flutter/painting.dart'; import 'package:flutter/material.dart';
extension StringExtensions on String { extension StringExtensions on String {
/// Returns a list containing all positions of a [charCode] /// Returns a list containing all positions of a [charCode]
@ -93,3 +93,41 @@ class TextHelper {
return parts; return parts;
} }
} }
class FormattedText extends StatelessWidget {
const FormattedText({
required this.text,
this.textStyle,
this.textAlign,
});
final String text;
final TextStyle? textStyle;
final TextAlign? textAlign;
@override
Widget build(BuildContext context) {
final TextStyle defaultTextStyle = textStyle ?? const TextStyle();
return RichText(
text: TextSpan(
style: DefaultTextStyle.of(context).style,
children: TextHelper.getPartsBetweenSymbol(
text: text,
symbol: r'\*\*',
symbolLength: 2,
defaultStyle: defaultTextStyle,
highlightedStyle: const TextStyle(fontWeight: FontWeight.bold))
.map(
((String, TextStyle?) part) {
return TextSpan(
text: part.$1,
style: defaultTextStyle.merge(part.$2),
);
},
).toList(growable: false),
),
textAlign: textAlign ?? TextAlign.start,
);
}
}

View File

@ -79,6 +79,7 @@ class KnowledgePanelTitleCard extends StatelessWidget {
child: Semantics( child: Semantics(
value: _generateSemanticsValue(context), value: _generateSemanticsValue(context),
button: isClickable, button: isClickable,
container: true,
excludeSemantics: true, excludeSemantics: true,
child: Row( child: Row(
children: <Widget>[ children: <Widget>[

View File

@ -742,8 +742,6 @@
"@no_product_found": {}, "@no_product_found": {},
"no_location_found": "No location found", "no_location_found": "No location found",
"not_found": "not found:", "not_found": "not found:",
"searchPanelHeader": "Search or scan your first product",
"@Product query status": {},
"refreshing_product": "Refreshing product", "refreshing_product": "Refreshing product",
"@refreshing_product": { "@refreshing_product": {
"description": "Confirmation, that the product data of a cached product is queried again" "description": "Confirmation, that the product data of a cached product is queried again"
@ -752,10 +750,24 @@
"@product_refreshed": { "@product_refreshed": {
"description": "Confirmation, that the product data refresh is done" "description": "Confirmation, that the product data refresh is done"
}, },
"homepage_main_card_logo_description": "Welcome to OpenFoodFacts",
"@homepage_main_card_logo_description": {
"description": "Description for accessibility of the Open Food Facts logo on the homepage"
},
"homepage_main_card_subheading": "**Scan** a barcode or\n**search** for a product",
"@homepage_main_card_subheading": {
"description": "Text between asterisks (eg: **My Text**) means text in bold. Please keep it."
},
"homepage_main_card_search_field_hint": "Search for a product",
"homepage_main_card_search_field_tooltip": "Start search",
"@homepage_main_card_search_field_tooltip": {
"description": "Description for accessibility of the search field on the homepage"
},
"tagline_app_review": "Do you like the app?", "tagline_app_review": "Do you like the app?",
"tagline_app_review_button_positive": "I love it! 😍", "tagline_app_review_button_positive": "I love it! 😍",
"tagline_app_review_button_negative": "Not really…", "tagline_app_review_button_negative": "Not really…",
"tagline_app_review_button_later": "Ask me later", "tagline_app_review_button_later": "Ask me later",
"tagline_feed_news_button": "Know more",
"app_review_negative_modal_title": "You don't like our app?", "app_review_negative_modal_title": "You don't like our app?",
"app_review_negative_modal_text": "Could you take a few seconds to tell us why?", "app_review_negative_modal_text": "Could you take a few seconds to tell us why?",
"app_review_negative_modal_positive_button": "Yes, absolutely!", "app_review_negative_modal_positive_button": "Yes, absolutely!",

View File

@ -18,6 +18,7 @@ import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:smooth_app/data_models/continuous_scan_model.dart'; import 'package:smooth_app/data_models/continuous_scan_model.dart';
import 'package:smooth_app/data_models/preferences/user_preferences.dart'; import 'package:smooth_app/data_models/preferences/user_preferences.dart';
import 'package:smooth_app/data_models/product_preferences.dart'; import 'package:smooth_app/data_models/product_preferences.dart';
import 'package:smooth_app/data_models/tagline/tagline_provider.dart';
import 'package:smooth_app/data_models/user_management_provider.dart'; import 'package:smooth_app/data_models/user_management_provider.dart';
import 'package:smooth_app/database/dao_string.dart'; import 'package:smooth_app/database/dao_string.dart';
import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/database/local_database.dart';
@ -105,6 +106,7 @@ late TextContrastProvider _textContrastProvider;
final ContinuousScanModel _continuousScanModel = ContinuousScanModel(); final ContinuousScanModel _continuousScanModel = ContinuousScanModel();
final PermissionListener _permissionListener = final PermissionListener _permissionListener =
PermissionListener(permission: Permission.camera); PermissionListener(permission: Permission.camera);
final TagLineProvider _tagLineProvider = TagLineProvider();
bool _init1done = false; bool _init1done = false;
// Had to split init in 2 methods, for test/screenshots reasons. // Had to split init in 2 methods, for test/screenshots reasons.
@ -199,8 +201,12 @@ class _SmoothAppState extends State<SmoothApp> {
// The `create` constructor of [ChangeNotifierProvider] takes care of // The `create` constructor of [ChangeNotifierProvider] takes care of
// disposing the value. // disposing the value.
ChangeNotifierProvider<T> provide<T extends ChangeNotifier>(T value) => ChangeNotifierProvider<T> provide<T extends ChangeNotifier>(T value,
ChangeNotifierProvider<T>(create: (BuildContext context) => value); {bool? lazy}) =>
ChangeNotifierProvider<T>(
create: (BuildContext context) => value,
lazy: lazy,
);
if (!_screenshots) { if (!_screenshots) {
// ending FlutterNativeSplash.preserve() // ending FlutterNativeSplash.preserve()
@ -218,6 +224,7 @@ class _SmoothAppState extends State<SmoothApp> {
provide<UserManagementProvider>(_userManagementProvider), provide<UserManagementProvider>(_userManagementProvider),
provide<ContinuousScanModel>(_continuousScanModel), provide<ContinuousScanModel>(_continuousScanModel),
provide<PermissionListener>(_permissionListener), provide<PermissionListener>(_permissionListener),
provide<TagLineProvider>(_tagLineProvider, lazy: true),
], ],
child: AnimationsLoader( child: AnimationsLoader(
child: AppNavigator( child: AppNavigator(

View File

@ -264,27 +264,7 @@ class _GuidesFormattedText extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const TextStyle defaultTextStyle = TextStyle(); return FormattedText(text: text);
return RichText(
text: TextSpan(
style: DefaultTextStyle.of(context).style,
children: TextHelper.getPartsBetweenSymbol(
text: text,
symbol: r'\*\*',
symbolLength: 2,
defaultStyle: defaultTextStyle,
highlightedStyle: const TextStyle(fontWeight: FontWeight.bold))
.map(
((String, TextStyle?) part) {
return TextSpan(
text: part.$1,
style: defaultTextStyle.merge(part.$2),
);
},
).toList(growable: false),
),
);
} }
} }

View File

@ -5,6 +5,7 @@ import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:smooth_app/data_models/preferences/user_preferences.dart'; import 'package:smooth_app/data_models/preferences/user_preferences.dart';
import 'package:smooth_app/data_models/product_preferences.dart'; import 'package:smooth_app/data_models/product_preferences.dart';
import 'package:smooth_app/data_models/tagline/tagline_provider.dart';
import 'package:smooth_app/helpers/analytics_helper.dart'; import 'package:smooth_app/helpers/analytics_helper.dart';
import 'package:smooth_app/helpers/extension_on_text_helper.dart'; import 'package:smooth_app/helpers/extension_on_text_helper.dart';
import 'package:smooth_app/pages/carousel_manager.dart'; import 'package:smooth_app/pages/carousel_manager.dart';
@ -312,6 +313,7 @@ class _SmoothGoRouter {
// Must be set first to ensure the method is only called once // Must be set first to ensure the method is only called once
_appLanguageInitialized = true; _appLanguageInitialized = true;
ProductQuery.setLanguage(context, context.read<UserPreferences>()); ProductQuery.setLanguage(context, context.read<UserPreferences>());
context.read<TagLineProvider>().loadTagLine();
return context.read<ProductPreferences>().refresh(); return context.read<ProductPreferences>().refresh();
} }

View File

@ -5,6 +5,7 @@ import 'package:provider/provider.dart';
import 'package:smooth_app/background/background_task_language_refresh.dart'; import 'package:smooth_app/background/background_task_language_refresh.dart';
import 'package:smooth_app/data_models/preferences/user_preferences.dart'; import 'package:smooth_app/data_models/preferences/user_preferences.dart';
import 'package:smooth_app/data_models/product_preferences.dart'; import 'package:smooth_app/data_models/product_preferences.dart';
import 'package:smooth_app/data_models/tagline/tagline_provider.dart';
import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/widgets/language_selector.dart'; import 'package:smooth_app/generic_lib/widgets/language_selector.dart';
@ -55,6 +56,11 @@ class UserPreferencesLanguageSelector extends StatelessWidget {
await BackgroundTaskLanguageRefresh.addTask( await BackgroundTaskLanguageRefresh.addTask(
context.read<LocalDatabase>(), context.read<LocalDatabase>(),
); );
// Refresh the tagline
if (context.mounted) {
context.read<TagLineProvider>().loadTagLine();
}
// TODO(monsieurtanuki): make it a background task also? // TODO(monsieurtanuki): make it a background task also?
// no await // no await
productPreferences.refresh(); productPreferences.refresh();

View File

@ -8,6 +8,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:smooth_app/data_models/continuous_scan_model.dart'; import 'package:smooth_app/data_models/continuous_scan_model.dart';
import 'package:smooth_app/data_models/preferences/user_preferences.dart'; import 'package:smooth_app/data_models/preferences/user_preferences.dart';
import 'package:smooth_app/data_models/tagline/tagline_provider.dart';
import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart'; import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart';
import 'package:smooth_app/generic_lib/widgets/smooth_card.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_card.dart';
@ -60,84 +61,88 @@ class _ScanPageState extends State<ScanPage> {
Theme.of(context).brightness == Brightness.light && Platform.isIOS Theme.of(context).brightness == Brightness.light && Platform.isIOS
? Brightness.dark ? Brightness.dark
: null, : null,
body: Container( body: ChangeNotifierProvider<TagLineProvider>(
color: Colors.white, lazy: true,
child: SafeArea( create: (_) => TagLineProvider(),
child: Container( child: Container(
color: Theme.of(context).colorScheme.background, color: Colors.white,
child: Column( child: SafeArea(
children: <Widget>[ child: Container(
if (hasACamera) color: Theme.of(context).colorScheme.background,
child: Column(
children: <Widget>[
if (hasACamera)
Expanded(
flex: 100 - _carouselHeightPct,
child: Consumer<PermissionListener>(
builder: (
BuildContext context,
PermissionListener listener,
_,
) {
switch (listener.value.status) {
case DevicePermissionStatus.checking:
return EMPTY_WIDGET;
case DevicePermissionStatus.granted:
// TODO(m123): change
return const CameraScannerPage();
default:
return const _PermissionDeniedCard();
}
},
),
),
Expanded( Expanded(
flex: 100 - _carouselHeightPct, flex: _carouselHeightPct,
child: Consumer<PermissionListener>( child: Padding(
builder: ( padding: const EdgeInsetsDirectional.only(bottom: 10.0),
BuildContext context, child: SmoothProductCarousel(
PermissionListener listener, containSearchCard: true,
_, onPageChangedTo: (int page, String? barcode) async {
) { if (barcode == null) {
switch (listener.value.status) { // We only notify for new products
case DevicePermissionStatus.checking: return;
return EMPTY_WIDGET; }
case DevicePermissionStatus.granted:
// TODO(m123): change
return const CameraScannerPage();
default:
return const _PermissionDeniedCard();
}
},
),
),
Expanded(
flex: _carouselHeightPct,
child: Padding(
padding: const EdgeInsetsDirectional.only(bottom: 10.0),
child: SmoothProductCarousel(
containSearchCard: true,
onPageChangedTo: (int page, String? barcode) async {
if (barcode == null) {
// We only notify for new products
return;
}
// Both are Future methods, but it doesn't matter to wait here // Both are Future methods, but it doesn't matter to wait here
SmoothHapticFeedback.lightNotification(); SmoothHapticFeedback.lightNotification();
if (_userPreferences.playCameraSound) { if (_userPreferences.playCameraSound) {
await _initSoundManagerIfNecessary(); await _initSoundManagerIfNecessary();
await _musicPlayer!.stop(); await _musicPlayer!.stop();
await _musicPlayer!.play( await _musicPlayer!.play(
AssetSource('audio/beep.wav'), AssetSource('audio/beep.wav'),
volume: 0.5, volume: 0.5,
ctx: const AudioContext( ctx: const AudioContext(
android: AudioContextAndroid( android: AudioContextAndroid(
isSpeakerphoneOn: false, isSpeakerphoneOn: false,
stayAwake: false, stayAwake: false,
contentType: AndroidContentType.sonification, contentType: AndroidContentType.sonification,
usageType: AndroidUsageType.notification, usageType: AndroidUsageType.notification,
audioFocus: audioFocus:
AndroidAudioFocus.gainTransientMayDuck, AndroidAudioFocus.gainTransientMayDuck,
),
iOS: AudioContextIOS(
category: AVAudioSessionCategory.soloAmbient,
options: <AVAudioSessionOptions>[
AVAudioSessionOptions.mixWithOthers,
],
),
), ),
iOS: AudioContextIOS( );
category: AVAudioSessionCategory.soloAmbient, }
options: <AVAudioSessionOptions>[
AVAudioSessionOptions.mixWithOthers, SemanticsService.announce(
], appLocalizations.scan_announce_new_barcode(barcode),
), direction,
), assertiveness: Assertiveness.assertive,
); );
} },
),
SemanticsService.announce(
appLocalizations.scan_announce_new_barcode(barcode),
direction,
assertiveness: Assertiveness.assertive,
);
},
), ),
), ),
), ],
], ),
), ),
), ),
), ),

View File

@ -0,0 +1,435 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart';
import 'package:smooth_app/cards/category_cards/svg_cache.dart';
import 'package:smooth_app/data_models/preferences/user_preferences.dart';
import 'package:smooth_app/data_models/tagline/tagline_model.dart';
import 'package:smooth_app/data_models/tagline/tagline_provider.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/widgets/smooth_card.dart';
import 'package:smooth_app/helpers/launch_url_helper.dart';
import 'package:smooth_app/helpers/provider_helper.dart';
import 'package:smooth_app/helpers/strings_helper.dart';
import 'package:smooth_app/resources/app_icons.dart';
import 'package:smooth_app/themes/smooth_theme_colors.dart';
import 'package:smooth_app/themes/theme_provider.dart';
class ScanTagLine extends StatelessWidget {
const ScanTagLine({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<_ScanTagLineProvider>(
create: (BuildContext context) => _ScanTagLineProvider(context),
child: Consumer<_ScanTagLineProvider>(
builder: (
BuildContext context,
_ScanTagLineProvider scanTagLineProvider,
Widget? child,
) {
final _ScanTagLineState state = scanTagLineProvider.value;
return switch (state) {
_ScanTagLineStateLoading() => const _ScanTagLineLoading(),
_ScanTagLineStateNoContent() => EMPTY_WIDGET,
_ScanTagLineStateLoaded() => _ScanTagLineContent(
news: state.tagLine,
),
};
},
),
);
}
}
class _ScanTagLineLoading extends StatelessWidget {
const _ScanTagLineLoading();
@override
Widget build(BuildContext context) {
return Shimmer.fromColors(
baseColor: Theme.of(context)
.extension<SmoothColorsThemeExtension>()!
.primaryMedium,
highlightColor: Colors.white,
child: const SmoothCard(
child: SizedBox(
width: double.infinity,
height: 200.0,
),
),
);
}
}
class _ScanTagLineContent extends StatefulWidget {
const _ScanTagLineContent({
required this.news,
});
final Iterable<TagLineNewsItem> news;
@override
State<_ScanTagLineContent> createState() => _ScanTagLineContentState();
}
class _ScanTagLineContentState extends State<_ScanTagLineContent> {
Timer? _timer;
int _index = -1;
@override
void initState() {
super.initState();
_rotateNews();
}
void _rotateNews() {
_timer?.cancel();
_index++;
if (_index >= widget.news.length) {
_index = 0;
}
_timer = Timer(const Duration(minutes: 30), () => _rotateNews());
}
@override
Widget build(BuildContext context) {
final ThemeProvider themeProvider = context.watch<ThemeProvider>();
final SmoothColorsThemeExtension theme =
Theme.of(context).extension<SmoothColorsThemeExtension>()!;
final TagLineNewsItem currentNews = widget.news.elementAt(_index);
// Default values seem weird
const Radius radius = Radius.circular(16.0);
return Column(
children: <Widget>[
DecoratedBox(
decoration: BoxDecoration(
color: currentNews.style?.titleBackground ??
(themeProvider.isLightTheme
? theme.primarySemiDark
: theme.primaryBlack),
borderRadius: const BorderRadiusDirectional.only(
topStart: radius,
topEnd: radius,
),
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: VERY_SMALL_SPACE,
horizontal: MEDIUM_SPACE,
),
child: _TagLineContentTitle(
title: currentNews.title,
backgroundColor: currentNews.style?.titleBackground,
indicatorColor: currentNews.style?.titleIndicatorColor,
titleColor: currentNews.style?.titleTextColor,
),
),
),
Expanded(
child: DecoratedBox(
decoration: BoxDecoration(
color: currentNews.style?.contentBackgroundColor ??
(themeProvider.isLightTheme
? theme.primaryMedium
: theme.primaryDark),
borderRadius: const BorderRadiusDirectional.only(
bottomStart: radius,
bottomEnd: radius,
),
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: SMALL_SPACE,
horizontal: MEDIUM_SPACE,
),
child: Column(
children: <Widget>[
Expanded(
child: _TagLineContentBody(
message: currentNews.message,
textColor: currentNews.style?.messageTextColor,
image: currentNews.image,
),
),
const SizedBox(height: SMALL_SPACE),
Align(
alignment: AlignmentDirectional.bottomEnd,
child: _TagLineContentButton(
link: currentNews.url,
label: currentNews.buttonLabel,
backgroundColor: currentNews.style?.buttonBackground,
foregroundColor: currentNews.style?.buttonTextColor,
),
),
],
),
),
),
),
],
);
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
}
class _TagLineContentTitle extends StatelessWidget {
const _TagLineContentTitle({
required this.title,
this.backgroundColor,
this.indicatorColor,
this.titleColor,
});
final String title;
final Color? backgroundColor;
final Color? indicatorColor;
final Color? titleColor;
@override
Widget build(BuildContext context) {
final SmoothColorsThemeExtension theme =
Theme.of(context).extension<SmoothColorsThemeExtension>()!;
return ConstrainedBox(
constraints: const BoxConstraints(minHeight: 30.0),
child: Row(
children: <Widget>[
SizedBox.square(
dimension: 11.0,
child: DecoratedBox(
decoration: BoxDecoration(
color: indicatorColor ?? theme.secondaryLight,
borderRadius: const BorderRadius.all(ROUNDED_RADIUS),
),
),
),
const SizedBox(width: SMALL_SPACE),
Expanded(
child: Text(
title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
color: titleColor ?? Colors.white,
),
))
],
),
);
}
}
class _TagLineContentBody extends StatelessWidget {
const _TagLineContentBody({
required this.message,
this.textColor,
this.image,
});
final String message;
final Color? textColor;
final TagLineImage? image;
@override
Widget build(BuildContext context) {
final ThemeProvider themeProvider = context.watch<ThemeProvider>();
final SmoothColorsThemeExtension theme =
Theme.of(context).extension<SmoothColorsThemeExtension>()!;
final Widget text = FormattedText(
text: message,
textStyle: TextStyle(
color: textColor ??
(themeProvider.isLightTheme
? theme.primarySemiDark
: theme.primaryLight),
),
);
if (image == null) {
return text;
}
final int imageFlex = ((image!.width ?? 0.2) * 10).toInt();
return Row(
children: <Widget>[
Expanded(
flex: imageFlex,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.sizeOf(context).height * 0.06,
),
child: AspectRatio(
aspectRatio: 1.0,
child: _image(),
),
),
),
const SizedBox(width: MEDIUM_SPACE),
Expanded(
flex: 10 - imageFlex,
child: text,
),
],
);
}
Widget _image() {
if (image!.src.endsWith('svg')) {
return SvgCache(
image!.src,
semanticsLabel: image!.alt,
);
} else {
return Image.network(
semanticLabel: image!.alt,
image!.src,
);
}
}
}
class _TagLineContentButton extends StatelessWidget {
const _TagLineContentButton({
required this.link,
this.label,
this.backgroundColor,
this.foregroundColor,
});
final String link;
final String? label;
final Color? backgroundColor;
final Color? foregroundColor;
@override
Widget build(BuildContext context) {
final AppLocalizations localizations = AppLocalizations.of(context);
final SmoothColorsThemeExtension theme =
Theme.of(context).extension<SmoothColorsThemeExtension>()!;
return FilledButton(
style: FilledButton.styleFrom(
backgroundColor: backgroundColor ?? theme.primaryBlack,
foregroundColor: foregroundColor ?? Colors.white,
padding: const EdgeInsets.symmetric(
vertical: VERY_SMALL_SPACE,
horizontal: MEDIUM_SPACE,
),
minimumSize: const Size(0, 20.0),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(label ?? localizations.tagline_feed_news_button),
const SizedBox(width: MEDIUM_SPACE),
const Arrow.right(
size: 12.0,
),
],
),
onPressed: () => LaunchUrlHelper.launchURL(link),
);
}
}
/// Listen to [TagLineProvider] feed and provide a list of [TagLineNewsItem]
/// randomly sorted by unread, then displayed and clicked news.
class _ScanTagLineProvider extends ValueNotifier<_ScanTagLineState> {
_ScanTagLineProvider(BuildContext context)
: _tagLineProvider = context.read<TagLineProvider>(),
_userPreferences = context.read<UserPreferences>(),
super(const _ScanTagLineStateLoading()) {
_tagLineProvider.addListener(_onTagLineStateChanged);
// Refresh with the current state
_onTagLineStateChanged();
}
final TagLineProvider _tagLineProvider;
final UserPreferences _userPreferences;
void _onTagLineStateChanged() {
switch (_tagLineProvider.state) {
case TagLineLoading():
emit(const _ScanTagLineStateLoading());
case TagLineError():
emit(const _ScanTagLineStateNoContent());
case TagLineLoaded():
_onTagLineContentAvailable(
(_tagLineProvider.state as TagLineLoaded).tagLineContent);
}
}
Future<void> _onTagLineContentAvailable(TagLine tagLine) async {
if (!tagLine.feed.isNotEmpty) {
emit(const _ScanTagLineStateNoContent());
return;
}
final List<TagLineNewsItem> unreadNews = <TagLineNewsItem>[];
final List<TagLineNewsItem> displayedNews = <TagLineNewsItem>[];
final List<TagLineNewsItem> clickedNews = <TagLineNewsItem>[];
final List<String> taglineFeedAlreadyClickedNews =
_userPreferences.taglineFeedClickedNews;
final List<String> taglineFeedAlreadyDisplayedNews =
_userPreferences.taglineFeedDisplayedNews;
for (final TagLineFeedItem feedItem in tagLine.feed.news) {
if (taglineFeedAlreadyClickedNews.contains(feedItem.id)) {
clickedNews.add(feedItem.news);
} else if (taglineFeedAlreadyDisplayedNews.contains(feedItem.id)) {
displayedNews.add(feedItem.news);
} else {
unreadNews.add(feedItem.news);
}
}
emit(
_ScanTagLineStateLoaded(
<TagLineNewsItem>[
...unreadNews..shuffle(),
...displayedNews..shuffle(),
...clickedNews..shuffle(),
],
),
);
}
@override
void dispose() {
_tagLineProvider.removeListener(_onTagLineStateChanged);
super.dispose();
}
}
sealed class _ScanTagLineState {
const _ScanTagLineState();
}
class _ScanTagLineStateLoading extends _ScanTagLineState {
const _ScanTagLineStateLoading();
}
class _ScanTagLineStateNoContent extends _ScanTagLineState {
const _ScanTagLineStateNoContent();
}
class _ScanTagLineStateLoaded extends _ScanTagLineState {
const _ScanTagLineStateLoaded(this.tagLine);
final Iterable<TagLineNewsItem> tagLine;
}

View File

@ -14,8 +14,8 @@ class SmoothColorsThemeExtension
required this.green, required this.green,
required this.orange, required this.orange,
required this.red, required this.red,
required this.grayDark, required this.greyDark,
required this.grayLight, required this.greyLight,
}); });
SmoothColorsThemeExtension.defaultValues() SmoothColorsThemeExtension.defaultValues()
@ -30,8 +30,8 @@ class SmoothColorsThemeExtension
green = const Color(0xFF219653), green = const Color(0xFF219653),
orange = const Color(0xFFFB8229), orange = const Color(0xFFFB8229),
red = const Color(0xFFEB5757), red = const Color(0xFFEB5757),
grayDark = const Color(0xFF666666), greyDark = const Color(0xFF666666),
grayLight = const Color(0xFF8F8F8F); greyLight = const Color(0xFF8F8F8F);
final Color primaryBlack; final Color primaryBlack;
final Color primaryDark; final Color primaryDark;
@ -44,8 +44,8 @@ class SmoothColorsThemeExtension
final Color green; final Color green;
final Color orange; final Color orange;
final Color red; final Color red;
final Color grayDark; final Color greyDark;
final Color grayLight; final Color greyLight;
@override @override
ThemeExtension<SmoothColorsThemeExtension> copyWith({ ThemeExtension<SmoothColorsThemeExtension> copyWith({
@ -60,8 +60,8 @@ class SmoothColorsThemeExtension
Color? green, Color? green,
Color? orange, Color? orange,
Color? red, Color? red,
Color? grayDark, Color? greyDark,
Color? grayLight, Color? greyLight,
}) { }) {
return SmoothColorsThemeExtension( return SmoothColorsThemeExtension(
primaryBlack: primaryBlack ?? this.primaryBlack, primaryBlack: primaryBlack ?? this.primaryBlack,
@ -75,8 +75,8 @@ class SmoothColorsThemeExtension
green: green ?? this.green, green: green ?? this.green,
orange: orange ?? this.orange, orange: orange ?? this.orange,
red: red ?? this.red, red: red ?? this.red,
grayDark: grayDark ?? this.grayDark, greyDark: greyDark ?? this.greyDark,
grayLight: grayLight ?? this.grayLight, greyLight: greyLight ?? this.greyLight,
); );
} }
@ -145,14 +145,14 @@ class SmoothColorsThemeExtension
other.red, other.red,
t, t,
)!, )!,
grayDark: Color.lerp( greyDark: Color.lerp(
grayDark, greyDark,
other.grayDark, other.greyDark,
t, t,
)!, )!,
grayLight: Color.lerp( greyLight: Color.lerp(
grayLight, greyLight,
other.grayLight, other.greyLight,
t, t,
)!, )!,
); );

View File

@ -1,33 +1,26 @@
import 'dart:math';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:carousel_slider/carousel_slider.dart'; import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:scanner_shared/scanner_shared.dart' hide EMPTY_WIDGET; import 'package:scanner_shared/scanner_shared.dart' hide EMPTY_WIDGET;
import 'package:smooth_app/cards/product_cards/smooth_product_base_card.dart';
import 'package:smooth_app/cards/product_cards/smooth_product_card_error.dart'; import 'package:smooth_app/cards/product_cards/smooth_product_card_error.dart';
import 'package:smooth_app/cards/product_cards/smooth_product_card_loading.dart'; import 'package:smooth_app/cards/product_cards/smooth_product_card_loading.dart';
import 'package:smooth_app/cards/product_cards/smooth_product_card_not_found.dart'; import 'package:smooth_app/cards/product_cards/smooth_product_card_not_found.dart';
import 'package:smooth_app/cards/product_cards/smooth_product_card_thanks.dart'; import 'package:smooth_app/cards/product_cards/smooth_product_card_thanks.dart';
import 'package:smooth_app/data_models/continuous_scan_model.dart'; import 'package:smooth_app/data_models/continuous_scan_model.dart';
import 'package:smooth_app/data_models/preferences/user_preferences.dart'; import 'package:smooth_app/data_models/tagline/tagline_provider.dart';
import 'package:smooth_app/data_models/tagline.dart';
import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_card.dart';
import 'package:smooth_app/helpers/app_helper.dart'; import 'package:smooth_app/helpers/provider_helper.dart';
import 'package:smooth_app/helpers/camera_helper.dart'; import 'package:smooth_app/helpers/strings_helper.dart';
import 'package:smooth_app/helpers/launch_url_helper.dart';
import 'package:smooth_app/helpers/user_feedback_helper.dart';
import 'package:smooth_app/pages/carousel_manager.dart'; import 'package:smooth_app/pages/carousel_manager.dart';
import 'package:smooth_app/pages/navigator/app_navigator.dart'; import 'package:smooth_app/pages/navigator/app_navigator.dart';
import 'package:smooth_app/pages/preferences/user_preferences_widgets.dart';
import 'package:smooth_app/pages/scan/scan_product_card_loader.dart'; import 'package:smooth_app/pages/scan/scan_product_card_loader.dart';
import 'package:smooth_app/pages/scan/search_page.dart'; import 'package:smooth_app/pages/scan/scan_tagline.dart';
import 'package:smooth_app/pages/scan/search_product_helper.dart'; import 'package:smooth_app/resources/app_icons.dart';
import 'package:smooth_app/services/smooth_services.dart'; import 'package:smooth_app/themes/smooth_theme_colors.dart';
import 'package:smooth_app/themes/theme_provider.dart';
class SmoothProductCarousel extends StatefulWidget { class SmoothProductCarousel extends StatefulWidget {
const SmoothProductCarousel({ const SmoothProductCarousel({
@ -127,7 +120,7 @@ class _SmoothProductCarouselState extends State<SmoothProductCarousel> {
horizontal: HORIZONTAL_SPACE_BETWEEN_CARDS, horizontal: HORIZONTAL_SPACE_BETWEEN_CARDS,
), ),
child: widget.containSearchCard && itemIndex == 0 child: widget.containSearchCard && itemIndex == 0
? SearchCard(height: constraints.maxHeight) ? const _MainCard()
: _getWidget(itemIndex - _searchCardAdjustment), : _getWidget(itemIndex - _searchCardAdjustment),
), ),
); );
@ -204,6 +197,10 @@ class _SmoothProductCarouselState extends State<SmoothProductCarousel> {
double _computeViewPortFraction() { double _computeViewPortFraction() {
final double screenWidth = MediaQuery.sizeOf(context).width; final double screenWidth = MediaQuery.sizeOf(context).width;
if (barcodes.isEmpty) {
return 0.95;
}
return (screenWidth - return (screenWidth -
(SmoothBarcodeScannerVisor.CORNER_PADDING * 2) - (SmoothBarcodeScannerVisor.CORNER_PADDING * 2) -
(SmoothBarcodeScannerVisor.STROKE_WIDTH * 2) + (SmoothBarcodeScannerVisor.STROKE_WIDTH * 2) +
@ -212,335 +209,171 @@ class _SmoothProductCarouselState extends State<SmoothProductCarousel> {
} }
} }
class SearchCard extends StatelessWidget { class _MainCard extends StatelessWidget {
const SearchCard({required this.height}); const _MainCard();
final double height; @override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Expanded(
child: ConsumerFilter<TagLineProvider>(
buildWhen:
(TagLineProvider? previousValue, TagLineProvider currentValue) {
return previousValue?.hasContent != currentValue.hasContent;
},
builder: (BuildContext context, TagLineProvider tagLineManager, _) {
if (!tagLineManager.hasContent) {
return const _SearchCard(
expandedMode: true,
);
} else {
return const Column(
children: <Widget>[
Expanded(
flex: 6,
child: _SearchCard(
expandedMode: false,
),
),
SizedBox(height: MEDIUM_SPACE),
Expanded(
flex: 4,
child: ScanTagLine(),
),
],
);
}
},
),
),
],
);
}
}
static const double OPACITY = 0.85; class _SearchCard extends StatelessWidget {
const _SearchCard({
required this.expandedMode,
});
/// Expanded is when this card is the only one (no tagline, no app review…)
final bool expandedMode;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AppLocalizations localizations = AppLocalizations.of(context); final AppLocalizations localizations = AppLocalizations.of(context);
final ThemeProvider themeProvider = context.watch<ThemeProvider>();
return SmoothProductBaseCard( final Widget widget = SmoothCard(
backgroundColorOpacity: OPACITY, color: themeProvider.isLightTheme
? Colors.grey.withOpacity(0.1)
: Colors.black,
padding: const EdgeInsets.symmetric(
vertical: MEDIUM_SPACE,
horizontal: LARGE_SPACE,
),
margin: const EdgeInsets.symmetric( margin: const EdgeInsets.symmetric(
horizontal: 0.0,
vertical: VERY_SMALL_SPACE, vertical: VERY_SMALL_SPACE,
), ),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
SvgPicture.asset( SvgPicture.asset(
Theme.of(context).brightness == Brightness.light Theme.of(context).brightness == Brightness.light
? 'assets/app/release_icon_light_transparent_no_border.svg' ? 'assets/app/logo_text_black.svg'
: 'assets/app/release_icon_dark_transparent_no_border.svg', : 'assets/app/logo_text_white.svg',
width: height * 0.2, semanticsLabel: localizations.homepage_main_card_logo_description,
height: height * 0.2,
package: AppHelper.APP_PACKAGE,
), ),
Padding( FormattedText(
padding: const EdgeInsets.only(top: MEDIUM_SPACE), text: localizations.homepage_main_card_subheading,
child: AutoSizeText( textAlign: TextAlign.center,
localizations.welcomeToOpenFoodFacts, textStyle: const TextStyle(height: 1.3),
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 26.0,
fontWeight: FontWeight.bold,
height: 1.00,
),
maxLines: 1,
),
), ),
const Expanded(child: _SearchCardContent()), const _SearchBar(),
], ],
), ),
); );
}
}
class _SearchCardContent extends StatefulWidget { if (expandedMode) {
const _SearchCardContent({ return ConstrainedBox(
Key? key, constraints: BoxConstraints(
}) : super(key: key); maxHeight: MediaQuery.sizeOf(context).height * 0.4,
),
@override child: widget,
State<_SearchCardContent> createState() => _SearchCardContentState(); );
}
class _SearchCardContentState extends State<_SearchCardContent>
with AutomaticKeepAliveClientMixin {
late _SearchCardContentType _content;
@override
void didChangeDependencies() {
super.didChangeDependencies();
final UserPreferences preferences = context.read<UserPreferences>();
final int scans = preferences.numberOfScans;
if (CameraHelper.hasACamera && scans < 1) {
_content = _SearchCardContentType.DEFAULT;
} else if (!preferences.inAppReviewAlreadyAsked &&
Random().nextInt(10) == 0) {
_content = _SearchCardContentType.REVIEW_APP;
} else { } else {
_content = _SearchCardContentType.TAG_LINE; return widget;
} }
} }
@override
Widget build(BuildContext context) {
super.build(context);
final ThemeData themeData = Theme.of(context);
final bool darkMode = themeData.brightness == Brightness.dark;
return Padding(
padding: const EdgeInsets.symmetric(vertical: VERY_SMALL_SPACE),
child: DefaultTextStyle.merge(
style: const TextStyle(
fontSize: LARGE_SPACE,
height: 1.22,
),
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 5,
child: Column(
children: <Widget>[
Expanded(
child: switch (_content) {
_SearchCardContentType.DEFAULT =>
const _SearchCardContentDefault(),
_SearchCardContentType.TAG_LINE =>
const _SearchCardContentTagLine(),
_SearchCardContentType.REVIEW_APP =>
_SearchCardContentAppReview(
onHideReview: () {
setState(() => _content = _SearchCardContentType.DEFAULT);
},
),
},
),
if (_content != _SearchCardContentType.REVIEW_APP)
SearchField(
searchHelper: const SearchProductHelper(),
onFocus: () => _openSearchPage(context),
readOnly: true,
showClearButton: false,
backgroundColor: darkMode
? Colors.white10
: const Color.fromARGB(255, 240, 240, 240)
.withOpacity(SearchCard.OPACITY),
foregroundColor: themeData.colorScheme.onSurface
.withOpacity(SearchCard.OPACITY),
),
],
),
),
);
}
void _openSearchPage(BuildContext context) {
AppNavigator.of(context).push(AppRoutes.SEARCH);
}
@override
bool get wantKeepAlive => true;
} }
enum _SearchCardContentType { class _SearchBar extends StatelessWidget {
TAG_LINE, const _SearchBar();
REVIEW_APP,
DEFAULT,
}
class _SearchCardContentDefault extends StatelessWidget { static const double SEARCH_BAR_HEIGHT = 47.0;
const _SearchCardContentDefault({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AppLocalizations localizations = AppLocalizations.of(context); final AppLocalizations localizations = AppLocalizations.of(context);
final ThemeProvider themeProvider = context.watch<ThemeProvider>();
final SmoothColorsThemeExtension theme =
Theme.of(context).extension<SmoothColorsThemeExtension>()!;
return Center( return SizedBox(
child: Padding( height: SEARCH_BAR_HEIGHT,
padding: const EdgeInsets.symmetric( child: InkWell(
horizontal: 10.0, onTap: () => AppNavigator.of(context).push(AppRoutes.SEARCH),
), borderRadius: BorderRadius.circular(30.0),
child: AutoSizeText( child: Ink(
localizations.searchPanelHeader, decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30.0),
color: themeProvider.isLightTheme ? Colors.white : theme.greyDark,
border: Border.all(color: theme.primaryBlack),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsetsDirectional.only(
start: 20.0,
end: 10.0,
bottom: 3.0,
),
child: Text(
localizations.homepage_main_card_search_field_hint,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: themeProvider.isLightTheme
? Colors.black
: Colors.white,
),
),
),
),
AspectRatio(
aspectRatio: 1.0,
child: DecoratedBox(
decoration: BoxDecoration(
color: theme.primaryDark,
shape: BoxShape.circle,
),
child: const Padding(
padding: EdgeInsets.all(10.0),
child: Search(
size: 20.0,
color: Colors.white,
),
),
),
)
],
),
), ),
), ),
); );
} }
} }
class _SearchCardContentTagLine extends StatelessWidget {
const _SearchCardContentTagLine();
@override
Widget build(BuildContext context) {
return FutureBuilder<TagLineItem?>(
future: fetchTagLine(),
builder: (BuildContext context, AsyncSnapshot<TagLineItem?> data) {
if (data.data != null) {
final TagLineItem tagLine = data.data!;
return InkWell(
borderRadius: ANGULAR_BORDER_RADIUS,
onTap: tagLine.hasLink
? () async => LaunchUrlHelper.launchURL(tagLine.url)
: null,
child: Center(
child: AutoSizeText(
tagLine.message,
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
),
);
} else {
return const _SearchCardContentDefault();
}
},
);
}
}
class _SearchCardContentAppReview extends StatelessWidget {
const _SearchCardContentAppReview({
required this.onHideReview,
});
final VoidCallback onHideReview;
@override
Widget build(BuildContext context) {
final AppLocalizations localizations = AppLocalizations.of(context);
final UserPreferences preferences = context.read<UserPreferences>();
return Center(
child: OutlinedButtonTheme(
data: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
shape: const RoundedRectangleBorder(
borderRadius: ROUNDED_BORDER_RADIUS,
),
side: BorderSide(
color: Theme.of(context).colorScheme.primary,
),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Spacer(),
const UserPreferencesListItemDivider(
margin: EdgeInsetsDirectional.only(
top: MEDIUM_SPACE,
bottom: SMALL_SPACE,
),
),
AutoSizeText(
localizations.tagline_app_review,
style: const TextStyle(
fontSize: 16.0,
),
),
const SizedBox(height: SMALL_SPACE),
SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: () async {
if (await ApplicationStore.openAppReview()) {
await preferences.markInAppReviewAsShown();
onHideReview.call();
}
},
style: OutlinedButton.styleFrom(
padding: const EdgeInsetsDirectional.symmetric(
vertical: SMALL_SPACE,
),
),
child: Text(
localizations.tagline_app_review_button_positive,
style: const TextStyle(fontSize: 17.0),
textAlign: TextAlign.center,
),
),
),
const SizedBox(height: VERY_SMALL_SPACE),
IntrinsicHeight(
child: Row(
children: <Widget>[
Expanded(
child: OutlinedButton(
onPressed: () async {
preferences.markInAppReviewAsShown();
await _showNegativeDialog(context, localizations);
onHideReview();
},
child: Text(
localizations.tagline_app_review_button_negative,
textAlign: TextAlign.center,
),
),
),
const SizedBox(width: VERY_SMALL_SPACE),
Expanded(
child: OutlinedButton(
onPressed: () => onHideReview(),
child: Text(
localizations.tagline_app_review_button_later,
textAlign: TextAlign.center,
),
),
),
],
),
),
const Spacer(),
],
),
),
);
}
Future<void> _showNegativeDialog(
BuildContext context,
AppLocalizations localizations,
) {
return showDialog(
context: context,
builder: (BuildContext context) {
return SmoothAlertDialog(
title: localizations.app_review_negative_modal_title,
body: Padding(
padding: const EdgeInsetsDirectional.only(
start: SMALL_SPACE,
end: SMALL_SPACE,
bottom: MEDIUM_SPACE,
),
child: Text(
localizations.app_review_negative_modal_text,
textAlign: TextAlign.center,
),
),
positiveAction: SmoothActionButton(
text: localizations.app_review_negative_modal_positive_button,
onPressed: () {
final String formLink = UserFeedbackHelper.getFeedbackFormLink();
LaunchUrlHelper.launchURL(formLink);
Navigator.of(context).pop();
},
),
negativeAction: SmoothActionButton(
text: localizations.app_review_negative_modal_negative_button,
onPressed: () => Navigator.of(context).pop(),
),
actionsAxis: Axis.vertical,
);
},
);
}
}

View File

@ -167,6 +167,7 @@ class SmoothScaffoldState extends ScaffoldState {
statusBarBrightness: Brightness.light, statusBarBrightness: Brightness.light,
systemNavigationBarContrastEnforced: false, systemNavigationBarContrastEnforced: false,
); );
case Brightness.light: case Brightness.light:
default: default:
return const SystemUiOverlayStyle( return const SystemUiOverlayStyle(