Compare commits

...

17 Commits

Author SHA1 Message Date
MeuNome
b2fae2a058 notificacoes 2026-06-08 11:47:28 +01:00
MeuNome
03b36ec9fe adicionar loja 2026-05-28 11:44:00 +01:00
MeuNome
9c9fae0e32 adicionar loja 2026-05-27 12:43:42 +01:00
MeuNome
d357a9b220 erro 2026-05-21 10:55:31 +01:00
MeuNome
01b54cd8d0 erro 2026-05-18 11:36:22 +01:00
MeuNome
82b4b72d48 erro 2026-05-14 11:43:11 +01:00
MeuNome
03074a881e calendario 2026-05-12 17:16:45 +01:00
MeuNome
2aa02bd434 tarefas 2026-05-11 17:21:38 +01:00
MeuNome
d1a9183fbc grafico 2026-05-11 15:00:18 +01:00
MeuNome
851cd64f10 gravar 2026-05-08 10:39:56 +01:00
MeuNome
64f317c073 melhorar o calendario 2026-05-06 12:42:58 +01:00
MeuNome
2c7d1a854f melhorar o streak 2026-05-05 17:14:37 +01:00
MeuNome
84dbdac0f2 melhorar o calendario 2026-04-30 18:04:23 +01:00
MeuNome
ba523f16ad amigos melhora 2026-04-30 10:38:13 +01:00
MeuNome
e38e791790 erro ao realizar a tarefa 2026-04-29 10:24:56 +01:00
MeuNome
84e4a7f8b0 erro ao realizar a tarefa 2026-04-28 17:14:36 +01:00
MeuNome
0fe31e3f65 mudar 2026-04-27 14:58:47 +01:00
93 changed files with 9899 additions and 6884 deletions

View File

@@ -10,7 +10,7 @@
<option name="id" value="A402SO" />
<option name="labId" value="google" />
<option name="manufacturer" value="Sony" />
<option name="name" value="Xperia 10" />
<option name="name" value="Xperia 10 VI" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2520" />
@@ -34,7 +34,7 @@
<option name="id" value="OP535DL1" />
<option name="labId" value="google" />
<option name="manufacturer" value="OnePlus" />
<option name="name" value="CPH2409" />
<option name="name" value="Nord CE 2 Lite 5G" />
<option name="screenDensity" value="401" />
<option name="screenX" value="1080" />
<option name="screenY" value="2412" />
@@ -46,7 +46,7 @@
<option name="id" value="OP5552L1" />
<option name="labId" value="google" />
<option name="manufacturer" value="OnePlus" />
<option name="name" value="CPH2415" />
<option name="name" value="10T 5G" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2412" />
@@ -58,7 +58,7 @@
<option name="id" value="OP573DL1" />
<option name="labId" value="google" />
<option name="manufacturer" value="OPPO" />
<option name="name" value="CPH2557" />
<option name="name" value="A79 5G" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
@@ -70,7 +70,7 @@
<option name="id" value="OP5759L1" />
<option name="labId" value="google" />
<option name="manufacturer" value="OPPO" />
<option name="name" value="CPH2579" />
<option name="name" value="A38" />
<option name="screenDensity" value="320" />
<option name="screenX" value="720" />
<option name="screenY" value="1612" />
@@ -94,7 +94,7 @@
<option name="id" value="RMX3231" />
<option name="labId" value="google" />
<option name="manufacturer" value="realme" />
<option name="name" value="C11" />
<option name="name" value="RMX3231" />
<option name="screenDensity" value="320" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
@@ -155,7 +155,7 @@
<option name="id" value="a05s" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A05s" />
<option name="name" value="Galaxy A05s" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
@@ -184,6 +184,18 @@
<option name="screenX" value="1080" />
<option name="screenY" value="2408" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="a13x" />
<option name="id" value="a13x" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy A13 5G" />
<option name="screenDensity" value="300" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
@@ -191,7 +203,7 @@
<option name="id" value="a14m" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-A145R" />
<option name="name" value="Galaxy A14" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2408" />
@@ -227,7 +239,7 @@
<option name="id" value="a15" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A15" />
<option name="name" value="Galaxy A15" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -239,7 +251,7 @@
<option name="id" value="a15x" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A15 5G" />
<option name="name" value="Galaxy A15 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -251,7 +263,7 @@
<option name="id" value="a15x" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A15 5G" />
<option name="name" value="Galaxy A15 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -275,7 +287,7 @@
<option name="id" value="a16" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-A165M" />
<option name="name" value="Galaxy A16" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -287,7 +299,7 @@
<option name="id" value="a16x" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A16 5G" />
<option name="name" value="Galaxy A16 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -299,7 +311,7 @@
<option name="id" value="a16x" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A16 5G" />
<option name="name" value="Galaxy A16 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -311,7 +323,7 @@
<option name="id" value="a16xeea" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A16 5G" />
<option name="name" value="Galaxy A16 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -335,11 +347,23 @@
<option name="id" value="a26x" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-A266B" />
<option name="name" value="Galaxy A26 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="a32" />
<option name="id" value="a32" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy A32" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="samsung" />
@@ -347,7 +371,7 @@
<option name="id" value="a34x" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-A346N" />
<option name="name" value="Galaxy A34 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -359,7 +383,7 @@
<option name="id" value="a35x" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A35" />
<option name="name" value="Galaxy A35 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -371,7 +395,7 @@
<option name="id" value="a35x" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A35" />
<option name="name" value="Galaxy A35 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -383,7 +407,7 @@
<option name="id" value="a35x" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A35" />
<option name="name" value="Galaxy A35 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -395,7 +419,7 @@
<option name="id" value="a36xq" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-A366E" />
<option name="name" value="Galaxy A36 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -407,7 +431,7 @@
<option name="id" value="a36xq" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-A366E" />
<option name="name" value="Galaxy A36 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -419,7 +443,7 @@
<option name="id" value="a56x" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-A566E" />
<option name="name" value="Galaxy A56 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -548,6 +572,7 @@
<option name="api" value="36" />
<option name="brand" value="google" />
<option name="codename" value="blazer" />
<option name="default" value="true" />
<option name="id" value="blazer" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
@@ -555,6 +580,11 @@
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2410" />
<option name="tags">
<list>
<option value="dda-default" />
</list>
</option>
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="32" />
@@ -575,11 +605,35 @@
<option name="id" value="c1q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Note 20 5G" />
<option name="name" value="Galaxy Note20 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="c1qksw" />
<option name="id" value="c1qksw" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Note20 5G" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="c2q" />
<option name="id" value="c2q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Note20 Ultra 5G" />
<option name="screenDensity" value="560" />
<option name="screenX" value="1440" />
<option name="screenY" value="3088" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
@@ -608,7 +662,6 @@
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="comet" />
<option name="default" value="true" />
<option name="id" value="comet" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
@@ -616,17 +669,11 @@
<option name="screenDensity" value="390" />
<option name="screenX" value="2076" />
<option name="screenY" value="2152" />
<option name="tags">
<list>
<option value="dda-default" />
</list>
</option>
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="35" />
<option name="brand" value="google" />
<option name="codename" value="comet" />
<option name="default" value="true" />
<option name="id" value="comet" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
@@ -634,11 +681,18 @@
<option name="screenDensity" value="390" />
<option name="screenX" value="2076" />
<option name="screenY" value="2152" />
<option name="tags">
<list>
<option value="dda-default" />
</list>
</option>
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="google" />
<option name="codename" value="comet" />
<option name="id" value="comet" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro Fold" />
<option name="screenDensity" value="390" />
<option name="screenX" value="2076" />
<option name="screenY" value="2152" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="35" />
@@ -647,7 +701,7 @@
<option name="id" value="cuscoi" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="edge 50 fusion" />
<option name="name" value="moto g96 5G" />
<option name="screenDensity" value="400" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
@@ -659,7 +713,7 @@
<option name="id" value="dm1q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="S23" />
<option name="name" value="Galaxy S23" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -671,7 +725,7 @@
<option name="id" value="dm1q-SM-S911U" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="S23" />
<option name="name" value="Galaxy S23" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -695,7 +749,7 @@
<option name="id" value="dm2q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="S23 Plus" />
<option name="name" value="Galaxy S23+" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -728,7 +782,6 @@
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="e1q" />
<option name="default" value="true" />
<option name="id" value="e1q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
@@ -736,17 +789,11 @@
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
<option name="tags">
<list>
<option value="dda-default" />
</list>
</option>
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="samsung" />
<option name="codename" value="e1q" />
<option name="default" value="true" />
<option name="id" value="e1q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
@@ -754,11 +801,18 @@
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
<option name="tags">
<list>
<option value="dda-default" />
</list>
</option>
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="samsung" />
<option name="codename" value="e1s" />
<option name="id" value="e1s" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S24" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
@@ -767,7 +821,7 @@
<option name="id" value="e2q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S24 +" />
<option name="name" value="Galaxy S24+" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -868,6 +922,18 @@
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="google" />
<option name="codename" value="felix" />
<option name="id" value="felix" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
@@ -916,10 +982,23 @@
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="35" />
<option name="brand" value="motorola" />
<option name="codename" value="fogos" />
<option name="id" value="fogos" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="moto g34 5G" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="google" />
<option name="codename" value="frankel" />
<option name="default" value="true" />
<option name="id" value="frankel" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
@@ -927,6 +1006,11 @@
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2424" />
<option name="tags">
<list>
<option value="dda-default" />
</list>
</option>
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
@@ -935,7 +1019,7 @@
<option name="id" value="g0q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-S906U1" />
<option name="name" value="Galaxy S22+" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -947,7 +1031,7 @@
<option name="id" value="g0q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-S906U1" />
<option name="name" value="Galaxy S22+" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -971,7 +1055,7 @@
<option name="id" value="gta9pwifi" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-X210" />
<option name="name" value="Galaxy Tab A9+" />
<option name="screenDensity" value="240" />
<option name="screenX" value="1200" />
<option name="screenY" value="1920" />
@@ -983,7 +1067,7 @@
<option name="id" value="gts7lwifi" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-T870" />
<option name="name" value="Galaxy Tab S7" />
<option name="screenDensity" value="340" />
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
@@ -995,7 +1079,7 @@
<option name="id" value="gts7xllite" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-T738U" />
<option name="name" value="Galaxy Tab S7 FE 5G" />
<option name="screenDensity" value="340" />
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
@@ -1045,7 +1129,7 @@
<option name="id" value="gts9wifi" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-X710" />
<option name="name" value="Galaxy Tab S9" />
<option name="screenDensity" value="340" />
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
@@ -1057,7 +1141,7 @@
<option name="id" value="guamna" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="Moto XT2093-3" />
<option name="name" value="moto g play (2021)" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
@@ -1069,7 +1153,7 @@
<option name="id" value="guamp" />
<option name="labId" value="google" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="Moto XT2083-1" />
<option name="name" value="moto g(9) play" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
@@ -1194,6 +1278,30 @@
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="samsung" />
<option name="codename" value="m1q" />
<option name="id" value="m1q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S26" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="samsung" />
<option name="codename" value="m2q" />
<option name="id" value="m2q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S26+" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="motorola" />
@@ -1222,6 +1330,7 @@
<option name="api" value="36" />
<option name="brand" value="google" />
<option name="codename" value="mustang" />
<option name="default" value="true" />
<option name="id" value="mustang" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
@@ -1229,6 +1338,11 @@
<option name="screenDensity" value="390" />
<option name="screenX" value="1080" />
<option name="screenY" value="2404" />
<option name="tags">
<list>
<option value="dda-default" />
</list>
</option>
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
@@ -1237,7 +1351,7 @@
<option name="id" value="o1q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S21" />
<option name="name" value="Galaxy S21 5G" />
<option name="screenDensity" value="421" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
@@ -1249,7 +1363,7 @@
<option name="id" value="o1q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S21" />
<option name="name" value="Galaxy S21 5G" />
<option name="screenDensity" value="421" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
@@ -1273,7 +1387,19 @@
<option name="id" value="p3q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S21 Ultra" />
<option name="name" value="Galaxy S21 Ultra 5G" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3200" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="35" />
<option name="brand" value="samsung" />
<option name="codename" value="p3s" />
<option name="id" value="p3s" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S21 Ultra 5G" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3200" />
@@ -1285,7 +1411,7 @@
<option name="id" value="pa2q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="S25+" />
<option name="name" value="Galaxy S25+" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -1306,6 +1432,7 @@
<option name="api" value="35" />
<option name="brand" value="samsung" />
<option name="codename" value="pa3q" />
<option name="default" value="true" />
<option name="id" value="pa3q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
@@ -1313,11 +1440,17 @@
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
<option name="tags">
<list>
<option value="dda-default" />
</list>
</option>
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="samsung" />
<option name="codename" value="pa3q" />
<option name="default" value="true" />
<option name="id" value="pa3q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
@@ -1325,6 +1458,11 @@
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
<option name="tags">
<list>
<option value="dda-default" />
</list>
</option>
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
@@ -1350,6 +1488,18 @@
<option name="screenX" value="1440" />
<option name="screenY" value="3120" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="samsung" />
<option name="codename" value="q4qksx" />
<option name="id" value="q4qksx" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold4" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1812" />
<option name="screenY" value="2176" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
@@ -1362,6 +1512,18 @@
<option name="screenX" value="1812" />
<option name="screenY" value="2176" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="samsung" />
<option name="codename" value="q5qksx" />
<option name="id" value="q5qksx" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold5" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1812" />
<option name="screenY" value="2176" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
@@ -1374,6 +1536,18 @@
<option name="screenX" value="1856" />
<option name="screenY" value="2160" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="samsung" />
<option name="codename" value="q7mq" />
<option name="id" value="q7mq" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z TriFold" />
<option name="screenDensity" value="320" />
<option name="screenX" value="2160" />
<option name="screenY" value="1584" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="samsung" />
@@ -1393,7 +1567,7 @@
<option name="id" value="r0qcsx" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="S22" />
<option name="name" value="Galaxy S22" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -1419,7 +1593,7 @@
<option name="id" value="r11q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-S711U" />
<option name="name" value="Galaxy S23 FE" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -1431,7 +1605,7 @@
<option name="id" value="r11s" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-S711N" />
<option name="name" value="Galaxy S23 FE" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
@@ -1488,6 +1662,7 @@
<option name="api" value="36" />
<option name="brand" value="google" />
<option name="codename" value="rango" />
<option name="default" value="true" />
<option name="id" value="rango" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
@@ -1495,6 +1670,11 @@
<option name="screenDensity" value="390" />
<option name="screenX" value="2076" />
<option name="screenY" value="2152" />
<option name="tags">
<list>
<option value="dda-default" />
</list>
</option>
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
@@ -1544,7 +1724,7 @@
<option name="id" value="t2q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S21 Plus" />
<option name="name" value="Galaxy S21+ 5G" />
<option name="screenDensity" value="394" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
@@ -1556,7 +1736,7 @@
<option name="id" value="t2q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S21 Plus" />
<option name="name" value="Galaxy S21+ 5G" />
<option name="screenDensity" value="394" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
@@ -1574,6 +1754,19 @@
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="google" />
<option name="codename" value="tangorpro" />
<option name="formFactor" value="Tablet" />
<option name="id" value="tangorpro" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Tablet" />
<option name="screenDensity" value="320" />
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="35" />
<option name="brand" value="google" />
@@ -1590,7 +1783,6 @@
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="tokay" />
<option name="default" value="true" />
<option name="id" value="tokay" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
@@ -1598,17 +1790,11 @@
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2424" />
<option name="tags">
<list>
<option value="dda-default" />
</list>
</option>
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="35" />
<option name="brand" value="google" />
<option name="codename" value="tokay" />
<option name="default" value="true" />
<option name="id" value="tokay" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
@@ -1616,17 +1802,11 @@
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2424" />
<option name="tags">
<list>
<option value="dda-default" />
</list>
</option>
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="google" />
<option name="codename" value="tokay" />
<option name="default" value="true" />
<option name="id" value="tokay" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
@@ -1634,11 +1814,6 @@
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2424" />
<option name="tags">
<list>
<option value="dda-default" />
</list>
</option>
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
@@ -1647,7 +1822,7 @@
<option name="id" value="xcover7" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-G556B" />
<option name="name" value="Galaxy XCover7" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2408" />
@@ -1659,11 +1834,23 @@
<option name="id" value="y2q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="S20 Plus 5G" />
<option name="name" value="Galaxy S20+ 5G" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3200" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="z3q" />
<option name="id" value="z3q" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S20 Ultra 5G" />
<option name="screenDensity" value="560" />
<option name="screenX" value="1440" />
<option name="screenY" value="3200" />
</PersistentDeviceSelectionData>
</list>
</option>
</component>

1
.idea/misc.xml generated
View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">

View File

@@ -61,4 +61,5 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation 'com.github.PhilJay:MPAndroidChart:3.1.0'
}

View File

@@ -3,6 +3,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:name=".FluxupApplication"
@@ -27,10 +28,10 @@
<activity android:name=".MainActivity" />
<activity android:name=".SettingsActivity" />
<activity android:name=".StreakActivity" />
<activity android:name=".StatisticsActivity" />
<activity android:name=".FindFriendsActivity" />
<activity android:name=".TrophiesActivity" />
<activity android:name=".AvatarEditorActivity" />
</application>

View File

@@ -0,0 +1,26 @@
package com.fluxup.app;
public class AvatarData {
public String skinColor = "#FFD1B3";
public String bodyFormat = "slim"; // slim, athletic, plus
public int heightOffset = 0; // -10, 0, 10
public String hairStyle = "short"; // short, spiky, long, bald, fade, curly
public String hairColor = "#4A4A4A";
public String eyesStyle = "normal"; // normal, happy, cool, cute
public String eyebrowsStyle = "normal"; // normal, thick, angry, sad
public String mouthStyle = "smile"; // smile, smirk, open, neutral
public String clothesStyle = "tshirt"; // tshirt, hoodie, outfit, sport
public String clothesColor = "#7C3AED";
public String beardStyle = "none"; // none, goatee, full
public String accessory = "none"; // none, glasses, headphones, chain, cap
// Novas propriedades de cosméticos desbloqueáveis
public String effect = "none"; // none, fire_aura, glow
public String frame = "none"; // none, gold, neon
public AvatarData() {}
}

View File

@@ -0,0 +1,253 @@
package com.fluxup.app;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.firestore.FirebaseFirestore;
public class AvatarEditorActivity extends AppCompatActivity {
private AvatarView previewAvatar;
private AvatarData currentData;
private Usuario currentUser;
// Novas listas expandidas com suporte a progressão
private String[] skinColors = {"#FFD1B3", "#FFDBAC", "#F1C27D", "#E0AC69", "#C68642", "#8D5524", "#3E2723"};
private String[] bodyFormats = {"slim", "athletic", "plus"};
private String[] hairStyles = {"short", "spiky", "long", "bald", "fade", "curly"};
private String[] hairColors = {"#4A4A4A", "#1A1A1A", "#C68642", "#E8B253", "#EF4444", "#3B82F6", "#10B981"};
private String[] eyeStyles = {"normal", "happy", "cool", "cute"};
private String[] eyebrowStyles = {"normal", "thick", "angry", "sad"};
private String[] mouthStyles = {"smile", "smirk", "open", "neutral"};
private String[] clothesStyles = {"tshirt", "hoodie", "outfit", "sport"};
private String[] clothesColors = {"#7C3AED", "#EF4444", "#3B82F6", "#10B981", "#F59E0B", "#1E293B", "#F8FAFC"};
private String[] beardStyles = {"none", "goatee", "full"};
private String[] accessories = {"none", "glasses", "headphones", "chain", "cap"};
private String[] frames = {"none", "gold", "neon"};
private String[] effects = {"none", "fire_aura", "glow"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_avatar_editor);
previewAvatar = findViewById(R.id.previewAvatar);
findViewById(R.id.btnBackEditor).setOnClickListener(v -> finish());
findViewById(R.id.btnSaveTop).setOnClickListener(v -> saveAvatar());
setupTabs();
loadCurrentAvatar();
}
private void loadCurrentAvatar() {
FirebaseUser fUser = AuthManager.getInstance().getCurrentUser();
if (fUser != null) {
FirestoreManager.getInstance().getUser(fUser.getUid(), user -> {
if (user != null) {
this.currentUser = user;
this.currentData = user.avatar != null ? user.avatar : new AvatarData();
previewAvatar.setAvatarData(this.currentData);
populateAllOptions();
}
});
}
}
private void setupTabs() {
com.google.android.material.tabs.TabLayout tabs = findViewById(R.id.tabsCategories);
tabs.addOnTabSelectedListener(new com.google.android.material.tabs.TabLayout.OnTabSelectedListener() {
@Override public void onTabSelected(com.google.android.material.tabs.TabLayout.Tab tab) {
findViewById(R.id.panelCorpo).setVisibility(android.view.View.GONE);
findViewById(R.id.panelCabelo).setVisibility(android.view.View.GONE);
findViewById(R.id.panelRosto).setVisibility(android.view.View.GONE);
findViewById(R.id.panelRoupa).setVisibility(android.view.View.GONE);
findViewById(R.id.panelAcessorios).setVisibility(android.view.View.GONE);
findViewById(R.id.panelMolduras).setVisibility(android.view.View.GONE);
findViewById(R.id.panelEfeitos).setVisibility(android.view.View.GONE);
int pos = tab.getPosition();
if (pos == 0) findViewById(R.id.panelCorpo).setVisibility(android.view.View.VISIBLE);
else if (pos == 1) findViewById(R.id.panelCabelo).setVisibility(android.view.View.VISIBLE);
else if (pos == 2) findViewById(R.id.panelRosto).setVisibility(android.view.View.VISIBLE);
else if (pos == 3) findViewById(R.id.panelRoupa).setVisibility(android.view.View.VISIBLE);
else if (pos == 4) findViewById(R.id.panelAcessorios).setVisibility(android.view.View.VISIBLE);
else if (pos == 5) findViewById(R.id.panelMolduras).setVisibility(android.view.View.VISIBLE);
else if (pos == 6) findViewById(R.id.panelEfeitos).setVisibility(android.view.View.VISIBLE);
}
@Override public void onTabUnselected(com.google.android.material.tabs.TabLayout.Tab tab) {}
@Override public void onTabReselected(com.google.android.material.tabs.TabLayout.Tab tab) {}
});
}
private void populateAllOptions() {
populateColorList(R.id.llSkinColors, skinColors, "skinColor");
populateTextList(R.id.llBodyFormats, bodyFormats, "bodyFormat");
populateTextList(R.id.llHairStyles, hairStyles, "hairStyle");
populateColorList(R.id.llHairColors, hairColors, "hairColor");
populateTextList(R.id.llEyes, eyeStyles, "eyesStyle");
populateTextList(R.id.llEyebrows, eyebrowStyles, "eyebrowsStyle");
populateTextList(R.id.llMouth, mouthStyles, "mouthStyle");
populateTextList(R.id.llBeard, beardStyles, "beardStyle");
populateTextList(R.id.llClothes, clothesStyles, "clothesStyle");
populateColorList(R.id.llClothesColors, clothesColors, "clothesColor");
populateTextList(R.id.llAccessories, accessories, "accessory");
populateTextList(R.id.llFrames, frames, "frame");
populateTextList(R.id.llEffects, effects, "effect");
}
private void populateColorList(int containerId, String[] colors, String property) {
android.widget.LinearLayout container = findViewById(containerId);
container.removeAllViews();
for (String color : colors) {
androidx.cardview.widget.CardView card = new androidx.cardview.widget.CardView(this);
android.widget.LinearLayout.LayoutParams params = new android.widget.LinearLayout.LayoutParams(120, 120);
params.setMargins(0, 0, 24, 0);
card.setLayoutParams(params);
card.setRadius(60f);
card.setCardElevation(4f);
card.setCardBackgroundColor(android.graphics.Color.parseColor(color));
// Checkmark no selecionado
String currentValue = getCurrentPropertyValue(property);
if (color.equalsIgnoreCase(currentValue)) {
android.widget.TextView check = new android.widget.TextView(this);
check.setText("");
check.setTextColor(android.graphics.Color.WHITE);
check.setGravity(android.view.Gravity.CENTER);
check.setTextSize(16f);
check.setTypeface(null, android.graphics.Typeface.BOLD);
card.addView(check);
}
card.setOnClickListener(v -> {
updateProperty(property, color);
populateAllOptions();
});
container.addView(card);
}
}
private void populateTextList(int containerId, String[] options, String property) {
android.widget.LinearLayout container = findViewById(containerId);
container.removeAllViews();
for (String opt : options) {
android.widget.TextView tv = new android.widget.TextView(this);
android.widget.LinearLayout.LayoutParams params = new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 120);
params.setMargins(0, 0, 24, 0);
tv.setLayoutParams(params);
boolean isLocked = UnlockManager.isItemLockedForUser(currentUser, property, opt);
CosmeticItem lockInfo = UnlockManager.findCosmetic(property, opt);
if (isLocked && lockInfo != null) {
tv.setText("🔒 " + opt.toUpperCase());
tv.setGravity(android.view.Gravity.CENTER);
tv.setPadding(40, 0, 40, 0);
tv.setTextColor(android.graphics.Color.parseColor("#94A3B8"));
tv.setTextSize(12f);
tv.setTypeface(null, android.graphics.Typeface.BOLD);
android.graphics.drawable.GradientDrawable gd = new android.graphics.drawable.GradientDrawable();
gd.setColor(android.graphics.Color.parseColor("#E2E8F0"));
gd.setCornerRadius(60f);
gd.setStroke(2, android.graphics.Color.parseColor("#CBD5E1"));
tv.setBackground(gd);
tv.setOnClickListener(v -> {
Toast.makeText(this, lockInfo.unlockRequirement, Toast.LENGTH_SHORT).show();
});
} else {
tv.setText(opt.toUpperCase());
tv.setGravity(android.view.Gravity.CENTER);
tv.setPadding(40, 0, 40, 0);
tv.setTextColor(android.graphics.Color.parseColor("#333333"));
tv.setTextSize(12f);
tv.setTypeface(null, android.graphics.Typeface.BOLD);
android.graphics.drawable.GradientDrawable gd = new android.graphics.drawable.GradientDrawable();
String currentValue = getCurrentPropertyValue(property);
if (opt.equals(currentValue)) {
gd.setColor(android.graphics.Color.parseColor("#7C3AED"));
tv.setTextColor(android.graphics.Color.WHITE);
} else {
gd.setColor(android.graphics.Color.parseColor("#E2E8F0"));
}
gd.setCornerRadius(60f);
tv.setBackground(gd);
tv.setOnClickListener(v -> {
updateProperty(property, opt);
populateAllOptions();
});
}
container.addView(tv);
}
}
private String getCurrentPropertyValue(String property) {
if (currentData == null) return "";
switch (property) {
case "skinColor": return currentData.skinColor;
case "bodyFormat": return currentData.bodyFormat;
case "hairStyle": return currentData.hairStyle;
case "hairColor": return currentData.hairColor;
case "eyesStyle": return currentData.eyesStyle;
case "eyebrowsStyle": return currentData.eyebrowsStyle;
case "mouthStyle": return currentData.mouthStyle;
case "clothesStyle": return currentData.clothesStyle;
case "clothesColor": return currentData.clothesColor;
case "beardStyle": return currentData.beardStyle;
case "accessory": return currentData.accessory;
case "frame": return currentData.frame;
case "effect": return currentData.effect;
}
return "";
}
private void updateProperty(String property, String value) {
if (currentData == null) return;
switch (property) {
case "skinColor": currentData.skinColor = value; break;
case "bodyFormat": currentData.bodyFormat = value; break;
case "hairStyle": currentData.hairStyle = value; break;
case "hairColor": currentData.hairColor = value; break;
case "eyesStyle": currentData.eyesStyle = value; break;
case "eyebrowsStyle": currentData.eyebrowsStyle = value; break;
case "mouthStyle": currentData.mouthStyle = value; break;
case "clothesStyle": currentData.clothesStyle = value; break;
case "clothesColor": currentData.clothesColor = value; break;
case "beardStyle": currentData.beardStyle = value; break;
case "accessory": currentData.accessory = value; break;
case "frame": currentData.frame = value; break;
case "effect": currentData.effect = value; break;
}
previewAvatar.setAvatarData(currentData);
}
private void saveAvatar() {
if (currentUser == null) return;
currentUser.avatar = currentData;
FirebaseFirestore.getInstance().collection("users").document(currentUser.id_usuario)
.update("avatar", currentData)
.addOnSuccessListener(aVoid -> {
Toast.makeText(this, "Avatar atualizado com sucesso!", Toast.LENGTH_SHORT).show();
finish();
})
.addOnFailureListener(e -> {
Toast.makeText(this, "Erro ao guardar", Toast.LENGTH_SHORT).show();
});
}
}

View File

@@ -0,0 +1,370 @@
package com.fluxup.app;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
public class AvatarView extends View {
private AvatarData avatarData = new AvatarData();
private Paint paint;
private Path path;
public AvatarView(Context context) {
super(context);
init();
}
public AvatarView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
path = new Path();
}
private String league = "Bronze";
public void setAvatarData(AvatarData data) {
if (data != null) {
this.avatarData = data;
invalidate(); // Redraw
}
}
public void setLeague(String league) {
if (league != null) {
this.league = league;
invalidate();
}
}
private int parseSafeColor(String colorStr, String defaultHex) {
try {
if (colorStr == null || colorStr.isEmpty()) return Color.parseColor(defaultHex);
return Color.parseColor(colorStr);
} catch (Exception e) {
return Color.parseColor(defaultHex);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int w = getWidth();
int h = getHeight();
if (w == 0 || h == 0) return;
// Fundo circular premium
paint.setColor(Color.parseColor("#F8FAFC"));
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
canvas.drawCircle(w/2f, h/2f, Math.min(w, h)/2f, paint);
canvas.save();
float scale = Math.min(w / 100f, h / 100f);
canvas.translate((w - 100f * scale) / 2f, (h - 100f * scale) / 2f);
canvas.scale(scale, scale);
// Translacao da altura
canvas.translate(0, avatarData.heightOffset);
// --- EFEITOS (Desenhados atrás do avatar) ---
if ("fire_aura".equals(avatarData.effect)) {
paint.setColor(Color.parseColor("#F97316")); // Laranja fogo
canvas.drawCircle(25, 75, 18, paint);
canvas.drawCircle(75, 75, 18, paint);
paint.setColor(Color.parseColor("#F59E0B")); // Amarelo fogo
canvas.drawCircle(35, 80, 12, paint);
canvas.drawCircle(65, 80, 12, paint);
canvas.drawCircle(50, 85, 14, paint);
} else if ("glow".equals(avatarData.effect)) {
paint.setColor(Color.parseColor("#E9D5FF")); // Roxo suave brilhante
canvas.drawCircle(50, 50, 42, paint);
paint.setColor(Color.parseColor("#F5F3FF")); // Fundo interior leve
canvas.drawCircle(50, 50, 36, paint);
}
// Cores
int skinCol = parseSafeColor(avatarData.skinColor, "#FFD1B3");
int clothesCol = parseSafeColor(avatarData.clothesColor, "#7C3AED");
int hairCol = parseSafeColor(avatarData.hairColor, "#4A4A4A");
// --- CORPO (Muito pequeno e simplificado) ---
float bodyW = 40f;
if ("athletic".equals(avatarData.bodyFormat)) bodyW = 48f;
else if ("plus".equals(avatarData.bodyFormat)) bodyW = 56f;
float bodyLeft = 50f - (bodyW / 2f);
float bodyRight = 50f + (bodyW / 2f);
// PESCOÇO
paint.setColor(darkenColor(skinCol, 0.8f)); // Sombra do pescoço
canvas.drawRoundRect(new RectF(43, 65, 57, 85), 6, 6, paint);
// ROUPA
paint.setColor(clothesCol);
paint.setShadowLayer(4f, 0f, 4f, Color.parseColor("#33000000")); // Sombra 3D
if ("hoodie".equals(avatarData.clothesStyle)) {
// Capuz atrás
canvas.drawRoundRect(new RectF(bodyLeft - 5, 72, bodyRight + 5, 110), 16, 16, paint);
// Corpo
canvas.drawRoundRect(new RectF(bodyLeft, 78, bodyRight, 110), 12, 12, paint);
// Cordões
paint.clearShadowLayer();
paint.setColor(Color.WHITE);
canvas.drawRoundRect(new RectF(45, 82, 47, 95), 1, 1, paint);
canvas.drawRoundRect(new RectF(53, 82, 55, 95), 1, 1, paint);
} else if ("sport".equals(avatarData.clothesStyle)) {
canvas.drawRoundRect(new RectF(bodyLeft, 78, bodyRight, 110), 8, 8, paint);
paint.clearShadowLayer();
paint.setColor(Color.WHITE);
canvas.drawRect(bodyLeft + 10, 78, bodyRight - 10, 110, paint);
} else if ("outfit".equals(avatarData.clothesStyle)) {
// Casaco aberto
canvas.drawRoundRect(new RectF(bodyLeft - 2, 78, bodyRight + 2, 110), 10, 10, paint);
paint.clearShadowLayer();
paint.setColor(Color.WHITE); // T-shirt interior
canvas.drawRect(42, 78, 58, 110, paint);
} else { // tshirt
canvas.drawRoundRect(new RectF(bodyLeft, 78, bodyRight, 110), 10, 10, paint);
paint.clearShadowLayer();
paint.setColor(skinCol);
canvas.drawArc(new RectF(40, 72, 60, 84), 0, 180, false, paint);
}
paint.clearShadowLayer();
// --- CABEÇA (Grande e expressiva) ---
paint.setColor(skinCol);
paint.setShadowLayer(6f, 0f, 6f, Color.parseColor("#22000000"));
// Orelhas
canvas.drawCircle(22, 45, 6, paint);
canvas.drawCircle(78, 45, 6, paint);
// Formato principal da cabeça
canvas.drawRoundRect(new RectF(20, 15, 80, 70), 28, 28, paint);
paint.clearShadowLayer();
// --- CABELO Atrás / Base ---
paint.setColor(hairCol);
if ("long".equals(avatarData.hairStyle)) {
canvas.drawRoundRect(new RectF(16, 30, 32, 85), 8, 8, paint);
canvas.drawRoundRect(new RectF(68, 30, 84, 85), 8, 8, paint);
} else if ("curly".equals(avatarData.hairStyle)) {
canvas.drawCircle(18, 35, 12, paint);
canvas.drawCircle(82, 35, 12, paint);
}
// --- BARBA ---
if (!"none".equals(avatarData.beardStyle)) {
paint.setColor(darkenColor(hairCol, 0.9f));
if ("goatee".equals(avatarData.beardStyle)) {
canvas.drawRoundRect(new RectF(44, 58, 56, 68), 6, 6, paint);
} else if ("full".equals(avatarData.beardStyle)) {
canvas.drawRoundRect(new RectF(25, 45, 75, 70), 15, 15, paint);
paint.setColor(skinCol);
canvas.drawRoundRect(new RectF(35, 45, 65, 58), 10, 10, paint); // Área da boca limpa
}
}
// --- OLHOS ---
paint.setColor(Color.parseColor("#1E293B"));
if ("happy".equals(avatarData.eyesStyle)) {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3f);
paint.setStrokeCap(Paint.Cap.ROUND);
canvas.drawArc(new RectF(32, 40, 42, 48), 180, 180, false, paint);
canvas.drawArc(new RectF(58, 40, 68, 48), 180, 180, false, paint);
paint.setStyle(Paint.Style.FILL);
} else if ("cool".equals(avatarData.eyesStyle)) {
canvas.drawRoundRect(new RectF(32, 42, 42, 46), 2, 2, paint);
canvas.drawRoundRect(new RectF(58, 42, 68, 46), 2, 2, paint);
} else if ("cute".equals(avatarData.eyesStyle)) {
canvas.drawCircle(37, 44, 5, paint);
canvas.drawCircle(63, 44, 5, paint);
paint.setColor(Color.WHITE);
canvas.drawCircle(38, 42, 1.5f, paint);
canvas.drawCircle(64, 42, 1.5f, paint);
} else { // normal
canvas.drawCircle(37, 44, 4.5f, paint);
canvas.drawCircle(63, 44, 4.5f, paint);
}
// --- SOBRANCELHAS ---
paint.setColor(darkenColor(hairCol, 0.8f));
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth("thick".equals(avatarData.eyebrowsStyle) ? 4f : 2.5f);
paint.setStrokeCap(Paint.Cap.ROUND);
if ("angry".equals(avatarData.eyebrowsStyle)) {
canvas.drawLine(30, 32, 44, 38, paint);
canvas.drawLine(70, 32, 56, 38, paint);
} else if ("sad".equals(avatarData.eyebrowsStyle)) {
canvas.drawLine(30, 38, 44, 32, paint);
canvas.drawLine(70, 38, 56, 32, paint);
} else { // normal / thick
canvas.drawLine(32, 35, 42, 35, paint);
canvas.drawLine(58, 35, 68, 35, paint);
}
paint.setStyle(Paint.Style.FILL);
// --- BOCA ---
paint.setColor(Color.parseColor("#BE123C")); // Vermelho suave
if ("smile".equals(avatarData.mouthStyle)) {
canvas.drawArc(new RectF(42, 53, 58, 62), 0, 180, false, paint);
} else if ("open".equals(avatarData.mouthStyle)) {
canvas.drawRoundRect(new RectF(44, 55, 56, 62), 4, 4, paint);
paint.setColor(Color.WHITE); // Dentes
canvas.drawRect(45, 55, 55, 57, paint);
} else if ("smirk".equals(avatarData.mouthStyle)) {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2f);
canvas.drawArc(new RectF(45, 54, 55, 60), 0, 130, false, paint);
canvas.drawLine(54, 55, 57, 53, paint); // sorrisinho de lado
paint.setStyle(Paint.Style.FILL);
} else { // neutral
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2.5f);
canvas.drawLine(46, 57, 54, 57, paint);
paint.setStyle(Paint.Style.FILL);
}
// --- CABELO Topo / Franja ---
paint.setColor(hairCol);
if ("short".equals(avatarData.hairStyle) || "long".equals(avatarData.hairStyle)) {
canvas.drawArc(new RectF(18, 8, 82, 40), 180, 180, false, paint);
canvas.drawRoundRect(new RectF(18, 20, 30, 45), 4, 4, paint); // patilhas
canvas.drawRoundRect(new RectF(70, 20, 82, 45), 4, 4, paint);
} else if ("spiky".equals(avatarData.hairStyle)) {
path.reset();
path.moveTo(18, 30);
path.lineTo(30, 5);
path.lineTo(40, 15);
path.lineTo(50, 0);
path.lineTo(60, 15);
path.lineTo(70, 5);
path.lineTo(82, 30);
path.close();
canvas.drawPath(path, paint);
} else if ("fade".equals(avatarData.hairStyle)) {
canvas.drawRoundRect(new RectF(25, 5, 75, 25), 8, 8, paint);
paint.setColor(darkenColor(skinCol, 0.9f));
canvas.drawRoundRect(new RectF(18, 20, 25, 40), 2, 2, paint);
canvas.drawRoundRect(new RectF(75, 20, 82, 40), 2, 2, paint);
} else if ("curly".equals(avatarData.hairStyle)) {
canvas.drawCircle(30, 15, 14, paint);
canvas.drawCircle(50, 10, 16, paint);
canvas.drawCircle(70, 15, 14, paint);
canvas.drawCircle(22, 25, 12, paint);
canvas.drawCircle(78, 25, 12, paint);
}
// --- ACESSÓRIOS ---
if ("glasses".equals(avatarData.accessory)) {
paint.setColor(Color.parseColor("#1E293B"));
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3f);
canvas.drawRoundRect(new RectF(28, 38, 46, 50), 6, 6, paint);
canvas.drawRoundRect(new RectF(54, 38, 72, 50), 6, 6, paint);
canvas.drawLine(46, 44, 54, 44, paint); // ponte
canvas.drawLine(20, 42, 28, 44, paint); // hastes
canvas.drawLine(80, 42, 72, 44, paint);
paint.setStyle(Paint.Style.FILL);
} else if ("headphones".equals(avatarData.accessory)) {
paint.setColor(Color.parseColor("#334155"));
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(4f);
canvas.drawArc(new RectF(18, 12, 82, 60), 180, 180, false, paint); // aro
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.parseColor("#E2E8F0"));
canvas.drawRoundRect(new RectF(12, 35, 22, 55), 4, 4, paint); // concha esq
canvas.drawRoundRect(new RectF(78, 35, 88, 55), 4, 4, paint); // concha dir
} else if ("chain".equals(avatarData.accessory)) {
paint.setColor(Color.parseColor("#FBBF24")); // Dourado
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2.5f);
canvas.drawArc(new RectF(40, 72, 60, 88), 0, 180, false, paint);
paint.setStyle(Paint.Style.FILL);
} else if ("cap".equals(avatarData.accessory)) {
paint.setColor(Color.parseColor("#0F172A")); // Boné escuro
canvas.drawArc(new RectF(18, 5, 82, 35), 180, 180, false, paint); // copa
canvas.drawRoundRect(new RectF(15, 25, 90, 32), 4, 4, paint); // aba
}
canvas.restore();
// --- MOLDURA (Desenhada no limite exterior do círculo) ---
String activeFrame = avatarData.frame;
if ("none".equals(activeFrame) || activeFrame == null) {
// Desenhar molduras automáticas por Liga
if ("Prata".equalsIgnoreCase(league) || "Silver".equalsIgnoreCase(league)) {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(Math.min(w, h) * 0.06f);
paint.setColor(Color.parseColor("#CBD5E1")); // Prata
canvas.drawCircle(w/2f, h/2f, Math.min(w, h)/2f - paint.getStrokeWidth()/2f, paint);
paint.setStyle(Paint.Style.FILL);
} else if ("Ouro".equalsIgnoreCase(league) || "Gold".equalsIgnoreCase(league)) {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(Math.min(w, h) * 0.06f);
paint.setColor(Color.parseColor("#F59E0B")); // Ouro
canvas.drawCircle(w/2f, h/2f, Math.min(w, h)/2f - paint.getStrokeWidth()/2f, paint);
paint.setStyle(Paint.Style.FILL);
} else if ("Esmeralda".equalsIgnoreCase(league) || "Platina".equalsIgnoreCase(league) || "Platinum".equalsIgnoreCase(league)) {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(Math.min(w, h) * 0.06f);
paint.setColor(Color.parseColor("#4CAF50")); // Esmeralda Verde
canvas.drawCircle(w/2f, h/2f, Math.min(w, h)/2f - paint.getStrokeWidth()/2f, paint);
paint.setStyle(Paint.Style.FILL);
} else if ("Diamante".equalsIgnoreCase(league) || "Diamond".equalsIgnoreCase(league)) {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(Math.min(w, h) * 0.06f);
paint.setColor(Color.parseColor("#1E88E5")); // Diamante Azul
canvas.drawCircle(w/2f, h/2f, Math.min(w, h)/2f - paint.getStrokeWidth()/2f, paint);
paint.setStyle(Paint.Style.FILL);
}
} else {
// Moldura manual equipada explícita
if ("gold".equals(activeFrame)) {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(Math.min(w, h) * 0.08f); // 8% da espessura
paint.setColor(Color.parseColor("#FBBF24")); // Dourado
canvas.drawCircle(w/2f, h/2f, Math.min(w, h)/2f - paint.getStrokeWidth()/2f, paint);
// Linha interior metálica
paint.setStrokeWidth(Math.min(w, h) * 0.02f);
paint.setColor(Color.parseColor("#D97706")); // Dourado Escuro
canvas.drawCircle(w/2f, h/2f, Math.min(w, h)/2f - Math.min(w, h)*0.07f, paint);
paint.setStyle(Paint.Style.FILL);
} else if ("neon".equals(activeFrame)) {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(Math.min(w, h) * 0.08f);
paint.setColor(Color.parseColor("#C084FC")); // Roxo Neon
canvas.drawCircle(w/2f, h/2f, Math.min(w, h)/2f - paint.getStrokeWidth()/2f, paint);
// Linha interior ciano
paint.setStrokeWidth(Math.min(w, h) * 0.02f);
paint.setColor(Color.parseColor("#22D3EE")); // Ciano Neon
canvas.drawCircle(w/2f, h/2f, Math.min(w, h)/2f - Math.min(w, h)*0.07f, paint);
paint.setStyle(Paint.Style.FILL);
}
}
}
private int darkenColor(int color, float factor) {
int a = Color.alpha(color);
int r = Math.round(Color.red(color) * factor);
int g = Math.round(Color.green(color) * factor);
int b = Math.round(Color.blue(color) * factor);
return Color.argb(a, Math.min(r, 255), Math.min(g, 255), Math.min(b, 255));
}
}

View File

@@ -0,0 +1,40 @@
package com.fluxup.app;
public class CosmeticItem {
public String id;
public String name;
public String category; // "corpo", "cabelo", "rosto", "roupa", "acessorios", "efeitos", "molduras"
public String property; // "clothesStyle", "accessory", "frame", "effect", etc.
public String value;
public String unlockRequirement;
public CosmeticItem(String id, String name, String category, String property, String value, String unlockRequirement) {
this.id = id;
this.name = name;
this.category = category;
this.property = property;
this.value = value;
this.unlockRequirement = unlockRequirement;
}
public boolean isUnlocked(Usuario user) {
if (user == null) return false;
switch (id) {
case "effect_fire":
return user.streak >= 7;
case "effect_glow":
return user.xp >= 5000;
case "clothes_outfit":
return user.xp >= 1000;
case "frame_gold":
return "Ouro".equalsIgnoreCase(user.league) || "Gold".equalsIgnoreCase(user.league) || user.xp >= 3000;
case "accessory_cap":
return user.total_tasks_concluidas >= 50;
case "accessory_headphones":
return user.tempo_foco_total >= 600; // 10 horas * 60 minutos
default:
return true; // Itens normais estão sempre desbloqueados
}
}
}

View File

@@ -0,0 +1,40 @@
package com.fluxup.app;
public class DailyProgress {
public String userId;
public String date;
public int completedTasks;
public int dailyGoal;
public int focusSessions;
public int xp;
public String status; // "complete", "partial", "empty"
public boolean isGoalReached;
public boolean isCompleted;
public Object createdAt;
public Object updatedAt;
public DailyProgress() {}
public DailyProgress(String date, int dailyGoal) {
this.date = date;
this.dailyGoal = dailyGoal;
this.completedTasks = 0;
this.focusSessions = 0;
this.xp = 0;
this.status = "empty";
this.isGoalReached = false;
this.isCompleted = false;
}
public void updateStatus() {
if (isCompleted || isGoalReached || (completedTasks >= dailyGoal && dailyGoal > 0)) {
status = "complete";
isCompleted = true;
isGoalReached = true;
} else if (completedTasks > 0) {
status = "partial";
} else {
status = "empty";
}
}
}

View File

@@ -1,114 +1,460 @@
package com.fluxup.app;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.tabs.TabLayout;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FieldValue;
import com.google.firebase.firestore.FirebaseFirestore;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FindFriendsActivity extends AppCompatActivity {
private EditText etSearch;
private LinearLayout resultsContainer;
private TabLayout tabLayout;
private ImageButton btnClose;
private RecyclerView rvSuggestions;
private View btnContacts, btnSearchByName, btnProfileLink;
private FirebaseFirestore db;
private String myUid;
private int activeTabPosition = 0; // 0: Sugestões, 1: Os meus amigos, 2: Pendentes
private List<Usuario> allFriends = new ArrayList<>();
private static class PendingRequest {
Usuario sender;
String requestId;
PendingRequest(Usuario sender, String requestId) {
this.sender = sender;
this.requestId = requestId;
}
}
private List<PendingRequest> allRequests = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_find_friends);
btnClose = findViewById(R.id.btnClose);
rvSuggestions = findViewById(R.id.rvSuggestions);
btnContacts = findViewById(R.id.btnContacts);
btnSearchByName = findViewById(R.id.btnSearchByName);
btnProfileLink = findViewById(R.id.btnProfileLink);
db = FirebaseFirestore.getInstance();
myUid = FirebaseAuth.getInstance().getUid();
initViews();
setupListeners();
loadTabContent();
}
private void initViews() {
etSearch = findViewById(R.id.etSearchFriends);
resultsContainer = findViewById(R.id.friendsResultsContainer);
tabLayout = findViewById(R.id.tabLayoutFriends);
btnClose = findViewById(R.id.btnClose);
}
private void setupListeners() {
btnClose.setOnClickListener(v -> finish());
setupSuggestions();
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
activeTabPosition = tab.getPosition();
etSearch.setText(""); // Clear search when switching tabs
updateSearchHint();
loadTabContent();
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {}
@Override
public void onTabReselected(TabLayout.Tab tab) {}
});
etSearch.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
performSearch(s.toString());
}
@Override
public void afterTextChanged(Editable s) {}
});
}
private void setupSuggestions() {
rvSuggestions.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
List<Suggestion> suggestions = new ArrayList<>();
suggestions.add(new Suggestion("Maria Silva", "Segue você", R.color.reward_yellow));
suggestions.add(new Suggestion("João Pereira", "Amigo em comum", R.color.success_green));
suggestions.add(new Suggestion("Ana Costa", "Segue você", R.color.streak_orange));
suggestions.add(new Suggestion("Ricardo M.", "Novo no Fluxup", R.color.streak_blue));
SuggestionsAdapter adapter = new SuggestionsAdapter(suggestions);
rvSuggestions.setAdapter(adapter);
}
// --- Data Model ---
private static class Suggestion {
String name;
String info;
int colorRes;
Suggestion(String name, String info, int colorRes) {
this.name = name;
this.info = info;
this.colorRes = colorRes;
private void updateSearchHint() {
if (activeTabPosition == 0) {
etSearch.setHint("Procurar novas pessoas...");
} else if (activeTabPosition == 1) {
etSearch.setHint("Procurar amigos...");
} else {
etSearch.setHint("Procurar pedidos...");
}
}
// --- Adapter ---
private class SuggestionsAdapter extends RecyclerView.Adapter<SuggestionsAdapter.ViewHolder> {
private final List<Suggestion> suggestions;
private void loadTabContent() {
if (myUid == null) return;
SuggestionsAdapter(List<Suggestion> suggestions) {
this.suggestions = suggestions;
resultsContainer.removeAllViews();
if (activeTabPosition == 0) {
loadSuggestionsTab("");
} else if (activeTabPosition == 1) {
loadFriendsTab();
} else {
loadRequestsTab();
}
}
private void performSearch(String query) {
if (activeTabPosition == 0) {
loadSuggestionsTab(query);
} else if (activeTabPosition == 1) {
filterFriends(query);
} else {
filterRequests(query);
}
}
// -------------------------------------------------------------------------
// TAB 0: SUGESTÕES (Discover)
// -------------------------------------------------------------------------
private void loadSuggestionsTab(String query) {
// Fetch all friend IDs + sent/received request IDs to exclude
db.collection("friendships")
.whereArrayContains("users", myUid)
.get()
.addOnSuccessListener(friendSnapshots -> {
List<String> excludedIds = new ArrayList<>();
excludedIds.add(myUid);
for (DocumentSnapshot doc : friendSnapshots.getDocuments()) {
List<String> users = (List<String>) doc.get("users");
if (users != null) {
for (String uId : users) {
if (!uId.equals(myUid)) excludedIds.add(uId);
}
}
}
// Fetch sent requests
db.collection("friend_requests")
.whereEqualTo("fromUserId", myUid)
.get()
.addOnSuccessListener(sentSnap -> {
for (DocumentSnapshot doc : sentSnap.getDocuments()) {
String toUser = doc.getString("toUserId");
if (toUser != null) excludedIds.add(toUser);
}
// Fetch received requests
db.collection("friend_requests")
.whereEqualTo("toUserId", myUid)
.get()
.addOnSuccessListener(recvSnap -> {
for (DocumentSnapshot doc : recvSnap.getDocuments()) {
String fromUser = doc.getString("fromUserId");
if (fromUser != null) excludedIds.add(fromUser);
}
// Now query users
queryDiscoverUsers(excludedIds, query);
});
});
});
}
private void queryDiscoverUsers(List<String> excludedIds, String query) {
if (query.isEmpty()) {
db.collection("users")
.limit(50)
.get()
.addOnSuccessListener(snapshots -> {
resultsContainer.removeAllViews();
for (DocumentSnapshot doc : snapshots.getDocuments()) {
Usuario u = doc.toObject(Usuario.class);
if (u != null && u.public_profile && !u.incognito && !excludedIds.contains(u.id_usuario)) {
addDiscoverItem(u);
}
}
});
} else {
db.collection("users")
.orderBy("usuario")
.startAt(query)
.endAt(query + "\uf8ff")
.limit(50)
.get()
.addOnSuccessListener(snapshots -> {
resultsContainer.removeAllViews();
for (DocumentSnapshot doc : snapshots.getDocuments()) {
Usuario u = doc.toObject(Usuario.class);
if (u != null && u.public_profile && !u.incognito && !excludedIds.contains(u.id_usuario)) {
addDiscoverItem(u);
}
}
});
}
}
private void addDiscoverItem(Usuario user) {
View view = getLayoutInflater().inflate(R.layout.item_friend_action, resultsContainer, false);
TextView tvName = view.findViewById(R.id.tvFriendName);
TextView tvStats = view.findViewById(R.id.tvFriendStats);
Button btnAction = view.findViewById(R.id.btnFriendAction);
tvName.setText(user.usuario);
tvStats.setText("Nível " + user.level + "" + user.xp + " XP");
btnAction.setText("Adicionar");
AvatarView ivAvatar = view.findViewById(R.id.ivFriendAvatar);
if (ivAvatar != null && user.avatar != null) {
ivAvatar.setAvatarData(user.avatar);
ivAvatar.setLeague(user.league);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_friend_suggestion, parent, false);
return new ViewHolder(view);
}
btnAction.setOnClickListener(v -> {
btnAction.setText("Pendente");
btnAction.setEnabled(false);
btnAction.setAlpha(0.5f);
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Suggestion item = suggestions.get(position);
holder.tvName.setText(item.name);
holder.tvInfo.setText(item.info);
holder.ivAvatar.setColorFilter(getResources().getColor(item.colorRes));
holder.btnFollow.setOnClickListener(v -> {
holder.btnFollow.setText("Seguindo");
holder.btnFollow.setEnabled(false);
holder.btnFollow.setAlpha(0.6f);
});
}
Map<String, Object> req = new HashMap<>();
req.put("fromUserId", myUid);
req.put("toUserId", user.id_usuario);
req.put("status", "pending");
req.put("timestamp", FieldValue.serverTimestamp());
@Override
public int getItemCount() {
return suggestions.size();
}
String reqId = myUid + "_" + user.id_usuario;
db.collection("friend_requests").document(reqId).set(req)
.addOnSuccessListener(aVoid -> {
Toast.makeText(this, "Pedido enviado para " + user.usuario, Toast.LENGTH_SHORT).show();
})
.addOnFailureListener(e -> {
btnAction.setText("Adicionar");
btnAction.setEnabled(true);
btnAction.setAlpha(1.0f);
Toast.makeText(this, "Erro ao enviar pedido.", Toast.LENGTH_SHORT).show();
});
});
class ViewHolder extends RecyclerView.ViewHolder {
ImageView ivAvatar;
TextView tvName, tvInfo;
MaterialButton btnFollow;
resultsContainer.addView(view);
}
ViewHolder(View view) {
super(view);
ivAvatar = view.findViewById(R.id.ivFriendAvatar);
tvName = view.findViewById(R.id.tvFriendName);
tvInfo = view.findViewById(R.id.tvFriendInfo);
btnFollow = view.findViewById(R.id.btnFollowBack);
// -------------------------------------------------------------------------
// TAB 1: OS MEUS AMIGOS
// -------------------------------------------------------------------------
private void loadFriendsTab() {
db.collection("friendships")
.whereArrayContains("users", myUid)
.get()
.addOnSuccessListener(snapshots -> {
allFriends.clear();
if (snapshots.isEmpty()) {
resultsContainer.removeAllViews();
return;
}
List<String> friendIds = new ArrayList<>();
for (DocumentSnapshot doc : snapshots.getDocuments()) {
List<String> users = (List<String>) doc.get("users");
if (users != null) {
for (String uId : users) {
if (!uId.equals(myUid)) friendIds.add(uId);
}
}
}
if (friendIds.isEmpty()) {
resultsContainer.removeAllViews();
return;
}
final int[] remaining = {friendIds.size()};
for (String friendId : friendIds) {
db.collection("users").document(friendId).get().addOnSuccessListener(snapshot -> {
Usuario friend = snapshot.toObject(Usuario.class);
if (friend != null) {
allFriends.add(friend);
}
remaining[0]--;
if (remaining[0] == 0) {
filterFriends(etSearch.getText().toString());
}
}).addOnFailureListener(e -> {
remaining[0]--;
if (remaining[0] == 0) {
filterFriends(etSearch.getText().toString());
}
});
}
});
}
private void filterFriends(String query) {
resultsContainer.removeAllViews();
for (Usuario friend : allFriends) {
if (query.isEmpty() || friend.usuario.toLowerCase().contains(query.toLowerCase())) {
addFriendItem(friend);
}
}
}
private void addFriendItem(Usuario friend) {
View view = getLayoutInflater().inflate(R.layout.item_friend_action, resultsContainer, false);
TextView tvName = view.findViewById(R.id.tvFriendName);
TextView tvStats = view.findViewById(R.id.tvFriendStats);
Button btnAction = view.findViewById(R.id.btnFriendAction);
tvName.setText(friend.usuario);
tvStats.setText("Nível " + friend.level + "" + friend.xp + " XP • 🔥 " + friend.streak);
btnAction.setText("Ver perfil");
AvatarView ivAvatar = view.findViewById(R.id.ivFriendAvatar);
if (ivAvatar != null && friend.avatar != null) {
ivAvatar.setAvatarData(friend.avatar);
ivAvatar.setLeague(friend.league);
}
View.OnClickListener clickListener = v -> {
Toast.makeText(this, "Perfil de " + friend.usuario, Toast.LENGTH_SHORT).show();
};
btnAction.setOnClickListener(clickListener);
view.setOnClickListener(clickListener);
resultsContainer.addView(view);
}
// -------------------------------------------------------------------------
// TAB 2: PENDENTES (Requests)
// -------------------------------------------------------------------------
private void loadRequestsTab() {
db.collection("friend_requests")
.whereEqualTo("toUserId", myUid)
.whereEqualTo("status", "pending")
.get()
.addOnSuccessListener(snapshots -> {
allRequests.clear();
if (snapshots.isEmpty()) {
resultsContainer.removeAllViews();
return;
}
List<String> senderIds = new ArrayList<>();
Map<String, String> requestMap = new HashMap<>(); // senderId -> requestId
for (DocumentSnapshot doc : snapshots.getDocuments()) {
String fromUserId = doc.getString("fromUserId");
if (fromUserId != null) {
senderIds.add(fromUserId);
requestMap.put(fromUserId, doc.getId());
}
}
final int[] remaining = {senderIds.size()};
for (String senderId : senderIds) {
db.collection("users").document(senderId).get().addOnSuccessListener(userSnap -> {
Usuario sender = userSnap.toObject(Usuario.class);
if (sender != null) {
allRequests.add(new PendingRequest(sender, requestMap.get(senderId)));
}
remaining[0]--;
if (remaining[0] == 0) {
filterRequests(etSearch.getText().toString());
}
}).addOnFailureListener(e -> {
remaining[0]--;
if (remaining[0] == 0) {
filterRequests(etSearch.getText().toString());
}
});
}
});
}
private void filterRequests(String query) {
resultsContainer.removeAllViews();
for (PendingRequest req : allRequests) {
if (query.isEmpty() || req.sender.usuario.toLowerCase().contains(query.toLowerCase())) {
addRequestItem(req.sender, req.requestId);
}
}
}
private void addRequestItem(Usuario sender, String requestId) {
View view = getLayoutInflater().inflate(R.layout.item_friend_request, resultsContainer, false);
TextView tvName = view.findViewById(R.id.tvFriendName);
TextView tvStats = view.findViewById(R.id.tvFriendStats);
Button btnAccept = view.findViewById(R.id.btnAcceptFriend);
Button btnReject = view.findViewById(R.id.btnRejectFriend);
tvName.setText(sender.usuario);
tvStats.setText(sender.xp + " XP");
AvatarView ivAvatar = view.findViewById(R.id.ivFriendAvatar);
if (ivAvatar != null && sender.avatar != null) {
ivAvatar.setAvatarData(sender.avatar);
ivAvatar.setLeague(sender.league);
}
btnAccept.setOnClickListener(v -> {
btnAccept.setEnabled(false);
btnReject.setEnabled(false);
String friendshipId = myUid.compareTo(sender.id_usuario) < 0 ? myUid + "_" + sender.id_usuario : sender.id_usuario + "_" + myUid;
Map<String, Object> friendship = new HashMap<>();
List<String> users = new ArrayList<>();
users.add(myUid);
users.add(sender.id_usuario);
friendship.put("users", users);
friendship.put("timestamp", FieldValue.serverTimestamp());
db.collection("friendships").document(friendshipId).set(friendship)
.addOnSuccessListener(aVoid -> {
db.collection("friend_requests").document(requestId).delete()
.addOnSuccessListener(aVoid2 -> {
Toast.makeText(this, "Pedido de " + sender.usuario + " aceite!", Toast.LENGTH_SHORT).show();
loadTabContent();
});
})
.addOnFailureListener(e -> {
btnAccept.setEnabled(true);
btnReject.setEnabled(true);
Toast.makeText(this, "Erro ao aceitar pedido.", Toast.LENGTH_SHORT).show();
});
});
btnReject.setOnClickListener(v -> {
btnAccept.setEnabled(false);
btnReject.setEnabled(false);
db.collection("friend_requests").document(requestId).delete()
.addOnSuccessListener(aVoid -> {
Toast.makeText(this, "Pedido recusado.", Toast.LENGTH_SHORT).show();
loadTabContent();
})
.addOnFailureListener(e -> {
btnAccept.setEnabled(true);
btnReject.setEnabled(true);
Toast.makeText(this, "Erro ao recusar pedido.", Toast.LENGTH_SHORT).show();
});
});
resultsContainer.addView(view);
}
}

View File

@@ -3,10 +3,14 @@ package com.fluxup.app;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.ListenerRegistration;
import com.google.firebase.firestore.QueryDocumentSnapshot;
import com.google.firebase.firestore.SetOptions;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import com.google.firebase.firestore.FieldValue;
import com.google.firebase.firestore.Query;
public class FirestoreManager {
@@ -24,6 +28,18 @@ public class FirestoreManager {
return instance;
}
/**
* Obtém os dados do utilizador uma única vez.
*/
public void getUser(String uid, Consumer<Usuario> callback) {
db.collection("users").document(uid).get().addOnSuccessListener(snapshot -> {
if (snapshot.exists()) {
Usuario user = snapshot.toObject(Usuario.class);
if (user != null) callback.accept(user);
}
});
}
/**
* Observa as mudanças no documento do utilizador em tempo real.
*/
@@ -59,8 +75,17 @@ public class FirestoreManager {
/**
* Atualiza o estado de uma tarefa.
*/
public void updateTask(Task task, Runnable onSuccess) {
db.collection("tasks").document(task.id).set(task)
.addOnSuccessListener(aVoid -> {
android.util.Log.d("FLUXUP_DEBUG", "TASK_UPDATE_SUCCESS");
if (onSuccess != null) onSuccess.run();
})
.addOnFailureListener(e -> android.util.Log.e("FLUXUP_DEBUG", "TASK_UPDATE_FAIL: " + e.getMessage()));
}
public void updateTask(Task task) {
db.collection("tasks").document(task.id).set(task);
updateTask(task, null);
}
/**
@@ -70,10 +95,370 @@ public class FirestoreManager {
db.collection("tasks").document(task.id).set(task);
}
/**
* Elimina uma tarefa.
*/
public void deleteTask(String taskId) {
db.collection("tasks").document(taskId).delete();
}
/**
* Atualiza campos específicos do perfil do utilizador (ex: XP, Streak).
*/
public void updateUserStats(String uid, Map<String, Object> updates, Runnable onSuccess) {
db.collection("users").document(uid).update(updates)
.addOnSuccessListener(aVoid -> {
if (onSuccess != null) onSuccess.run();
})
.addOnFailureListener(e -> android.util.Log.e("FLUXUP_DEBUG", "UPDATE_USER_STATS_FAIL: " + e.getMessage()));
}
public void updateUserStats(String uid, Map<String, Object> updates) {
db.collection("users").document(uid).update(updates);
updateUserStats(uid, updates, null);
}
/**
* Atualiza um campo específico do utilizador.
*/
public void updateUserField(String uid, String field, Object value) {
java.util.Map<String, Object> updates = new java.util.HashMap<>();
updates.put(field, value);
updateUserStats(uid, updates);
}
/**
* Regista um log de XP.
*/
public void addXpLog(String uid, int amount, String type, String taskId) {
Map<String, Object> log = new java.util.HashMap<>();
log.put("userId", uid);
log.put("amount", amount);
log.put("type", type);
if (taskId != null) log.put("taskId", taskId);
log.put("created_at", com.google.firebase.firestore.FieldValue.serverTimestamp());
db.collection("xp_logs").add(log)
.addOnSuccessListener(ref -> android.util.Log.d("FLUXUP_DEBUG", "XP_LOG_INSERT_SUCCESS"))
.addOnFailureListener(e -> android.util.Log.e("FLUXUP_DEBUG", "XP_LOG_INSERT_FAIL: " + e.getMessage()));
}
public void addXpLog(String uid, int amount, String type) {
addXpLog(uid, amount, type, null);
}
/**
* Adiciona moedas ao utilizador e regista a transação.
*/
public void addCoins(String uid, int amount, String reason) {
if (uid == null) return;
// Atualizar saldo
java.util.Map<String, Object> updates = new java.util.HashMap<>();
updates.put("coins", com.google.firebase.firestore.FieldValue.increment(amount));
updateUserStats(uid, updates);
// Registar transação
java.util.Map<String, Object> log = new java.util.HashMap<>();
log.put("userId", uid);
log.put("amount", amount);
log.put("reason", reason);
log.put("created_at", com.google.firebase.firestore.FieldValue.serverTimestamp());
db.collection("coin_logs").add(log)
.addOnSuccessListener(ref -> android.util.Log.d("FLUXUP_DEBUG", "COIN_LOG_INSERT_SUCCESS"))
.addOnFailureListener(e -> android.util.Log.e("FLUXUP_DEBUG", "COIN_LOG_INSERT_FAIL: " + e.getMessage()));
}
/**
* Calcula o XP total ganho hoje a partir dos logs.
*/
public void getTodayXp(String uid, Consumer<Integer> callback) {
if (uid == null) {
android.util.Log.e("FLUXUP_DEBUG", "getTodayXp: uid is null");
callback.accept(0);
return;
}
java.util.Calendar cal = java.util.Calendar.getInstance();
cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
cal.set(java.util.Calendar.MINUTE, 0);
cal.set(java.util.Calendar.SECOND, 0);
cal.set(java.util.Calendar.MILLISECOND, 0);
java.util.Date start = cal.getTime();
cal.add(java.util.Calendar.DAY_OF_MONTH, 1);
java.util.Date end = cal.getTime();
db.collection("xp_logs")
.whereEqualTo("userId", uid)
.whereGreaterThanOrEqualTo("created_at", start)
.whereLessThan("created_at", end)
.get()
.addOnSuccessListener(snapshots -> {
int total = 0;
if (snapshots != null) {
for (com.google.firebase.firestore.QueryDocumentSnapshot doc : snapshots) {
Long amount = doc.getLong("amount");
if (amount != null) total += amount.intValue();
}
}
android.util.Log.d("FLUXUP_DEBUG", "TODAY_XP_FROM_LOGS: " + total);
callback.accept(total);
})
.addOnFailureListener(e -> {
android.util.Log.e("FLUXUP_DEBUG", "XP_QUERY_FAIL: " + e.getMessage());
callback.accept(0);
});
}
/**
* Obtém os logs de XP de um determinado mês e ano.
*/
public void getMonthXpLogs(String uid, int year, int month, Consumer<List<Map<String, Object>>> callback) {
if (uid == null) {
callback.accept(new ArrayList<>());
return;
}
java.util.Calendar cal = java.util.Calendar.getInstance();
cal.set(year, month, 1, 0, 0, 0);
cal.set(java.util.Calendar.MILLISECOND, 0);
java.util.Date start = cal.getTime();
cal.add(java.util.Calendar.MONTH, 1);
java.util.Date end = cal.getTime();
db.collection("xp_logs")
.whereEqualTo("userId", uid)
.whereGreaterThanOrEqualTo("created_at", start)
.whereLessThan("created_at", end)
.get()
.addOnSuccessListener(snapshots -> {
List<Map<String, Object>> logs = new ArrayList<>();
if (snapshots != null) {
for (com.google.firebase.firestore.QueryDocumentSnapshot doc : snapshots) {
Map<String, Object> log = doc.getData();
logs.add(log);
}
}
callback.accept(logs);
})
.addOnFailureListener(e -> {
android.util.Log.e("FLUXUP_DEBUG", "MONTH_XP_QUERY_FAIL: " + e.getMessage());
callback.accept(new ArrayList<>());
});
}
/**
* Calculates daily progress (completed tasks, focus sessions, xp) for a date range
*/
public void getDailyProgress(String uid, java.util.Date startDate, java.util.Date endDate, int dailyGoal, Consumer<Map<String, DailyProgress>> callback) {
if (uid == null) {
callback.accept(new java.util.HashMap<>());
return;
}
db.collection("xp_logs")
.whereEqualTo("userId", uid)
.whereGreaterThanOrEqualTo("created_at", startDate)
.whereLessThanOrEqualTo("created_at", endDate)
.get()
.addOnSuccessListener(snapshots -> {
Map<String, DailyProgress> progressMap = new java.util.HashMap<>();
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.US);
if (snapshots != null) {
for (com.google.firebase.firestore.QueryDocumentSnapshot doc : snapshots) {
com.google.firebase.Timestamp ts = doc.getTimestamp("created_at");
if (ts != null) {
String dateStr = sdf.format(ts.toDate());
DailyProgress dp = progressMap.get(dateStr);
if (dp == null) {
dp = new DailyProgress(dateStr, dailyGoal);
progressMap.put(dateStr, dp);
}
String type = doc.getString("type");
Long amount = doc.getLong("amount");
if (amount != null) {
dp.xp += amount.intValue();
}
if ("focus_task".equals(type) || "task_manual_completion".equals(type)) {
dp.completedTasks++;
if ("focus_task".equals(type)) dp.focusSessions++;
} else if ("daily_goal_reached".equals(type)) {
dp.isGoalReached = true;
}
}
}
}
// Backup: Buscar tarefas concluídas para preencher lacunas nos logs
db.collection("tasks")
.whereEqualTo("userId", uid)
.whereEqualTo("completed", true)
.whereGreaterThanOrEqualTo("completedDate", startDate.getTime())
.whereLessThanOrEqualTo("completedDate", endDate.getTime())
.get()
.addOnSuccessListener(taskSnapshots -> {
if (taskSnapshots != null) {
Map<String, Integer> tasksPerDay = new java.util.HashMap<>();
for (com.google.firebase.firestore.QueryDocumentSnapshot doc : taskSnapshots) {
Long cDate = doc.getLong("completedDate");
if (cDate != null) {
String dateStr = sdf.format(new java.util.Date(cDate));
tasksPerDay.put(dateStr, tasksPerDay.getOrDefault(dateStr, 0) + 1);
}
}
for (Map.Entry<String, Integer> entry : tasksPerDay.entrySet()) {
String dateStr = entry.getKey();
int taskCount = entry.getValue();
DailyProgress dp = progressMap.get(dateStr);
if (dp == null) {
dp = new DailyProgress(dateStr, dailyGoal);
progressMap.put(dateStr, dp);
}
// Usar o maior valor para garantir que não perdemos progresso
dp.completedTasks = Math.max(dp.completedTasks, taskCount);
}
}
for (DailyProgress dp : progressMap.values()) {
dp.updateStatus();
}
callback.accept(progressMap);
})
.addOnFailureListener(e -> {
for (DailyProgress dp : progressMap.values()) {
dp.updateStatus();
}
callback.accept(progressMap);
});
})
.addOnFailureListener(e -> {
android.util.Log.e("FLUXUP_DEBUG", "DAILY_PROGRESS_QUERY_FAIL: " + e.getMessage());
callback.accept(new java.util.HashMap<>());
});
}
/**
* Recalculates the streak based on completed days in xp_logs.
*/
public void recalculateStreakFromCompletedDays(String uid, int dailyGoal, Consumer<Integer> callback) {
if (uid == null) {
callback.accept(0);
return;
}
// We'll query logs from the last 100 days to find the current consecutive streak
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_YEAR, -100);
java.util.Date startDate = cal.getTime();
java.util.Date endDate = new java.util.Date();
getDailyProgress(uid, startDate, endDate, dailyGoal, progressMap -> {
List<String> sortedDates = new java.util.ArrayList<>(progressMap.keySet());
java.util.Collections.sort(sortedDates, java.util.Collections.reverseOrder());
int streak = 0;
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.US);
Calendar checkCal = Calendar.getInstance();
// Check today
String todayStr = sdf.format(checkCal.getTime());
DailyProgress todayDp = progressMap.get(todayStr);
if (todayDp != null) todayDp.updateStatus();
boolean isTodayComplete = todayDp != null && "complete".equals(todayDp.status);
if (isTodayComplete) {
streak = 1;
} else {
// If today is not complete, check if yesterday was complete to continue the streak
checkCal.add(Calendar.DAY_OF_YEAR, -1);
}
// Go backwards from yesterday or today
while (true) {
String dateStr = sdf.format(checkCal.getTime());
DailyProgress dp = progressMap.get(dateStr);
if (dp != null) dp.updateStatus();
if (dp != null && "complete".equals(dp.status)) {
if (!dateStr.equals(todayStr)) streak++;
checkCal.add(Calendar.DAY_OF_YEAR, -1);
} else {
break; // Streak broken
}
}
// Update streak in DB
final int finalStreak = streak;
Map<String, Object> updates = new java.util.HashMap<>();
updates.put("streak", finalStreak);
updateUserStats(uid, updates, () -> callback.accept(finalStreak));
});
}
/**
* Guarda ou atualiza o progresso diário na coleção daily_progress.
*/
public void saveDailyProgress(DailyProgress dp) {
if (dp.userId == null || dp.date == null) return;
String docId = dp.userId + "_" + dp.date;
android.util.Log.d("FLUXUP_DEBUG", "SAVE_DAILY_PROGRESS_START");
android.util.Log.d("FLUXUP_DEBUG", "USER_ID: " + dp.userId);
android.util.Log.d("FLUXUP_DEBUG", "TODAY_DATE: " + dp.date);
android.util.Log.d("FLUXUP_DEBUG", "COMPLETED_TASKS_TODAY: " + dp.completedTasks);
android.util.Log.d("FLUXUP_DEBUG", "DAILY_GOAL: " + dp.dailyGoal);
Map<String, Object> data = new java.util.HashMap<>();
data.put("userId", dp.userId);
data.put("date", dp.date);
data.put("completedTasks", dp.completedTasks);
data.put("dailyGoal", dp.dailyGoal);
data.put("focusSessions", dp.focusSessions);
data.put("xp", dp.xp);
data.put("isCompleted", dp.isCompleted);
data.put("updatedAt", FieldValue.serverTimestamp());
// Se for a primeira vez, definimos o createdAt
db.collection("daily_progress").document(docId).get().addOnSuccessListener(snapshot -> {
if (!snapshot.exists()) {
data.put("createdAt", FieldValue.serverTimestamp());
}
db.collection("daily_progress").document(docId).set(data, SetOptions.merge())
.addOnSuccessListener(aVoid -> android.util.Log.d("FLUXUP_DEBUG", "DAILY_PROGRESS_SAVED"))
.addOnFailureListener(e -> android.util.Log.e("FLUXUP_DEBUG", "DAILY_PROGRESS_SAVE_FAIL: " + e.getMessage()));
});
}
/**
* Procura o progresso diário de um utilizador num intervalo de datas.
*/
public void getMonthlyDailyProgress(String userId, String startDate, String endDate, Consumer<List<DailyProgress>> callback) {
android.util.Log.d("FLUXUP_DEBUG", "LOAD_MONTHLY_PROGRESS_START");
android.util.Log.d("FLUXUP_DEBUG", "MONTH_START: " + startDate);
android.util.Log.d("FLUXUP_DEBUG", "MONTH_END: " + endDate);
db.collection("daily_progress")
.whereEqualTo("userId", userId)
.whereEqualTo("isCompleted", true)
.whereGreaterThanOrEqualTo("date", startDate)
.whereLessThanOrEqualTo("date", endDate)
.get()
.addOnSuccessListener(snapshots -> {
List<DailyProgress> results = new ArrayList<>();
if (snapshots != null) {
for (QueryDocumentSnapshot doc : snapshots) {
DailyProgress dp = doc.toObject(DailyProgress.class);
results.add(dp);
}
}
android.util.Log.d("FLUXUP_DEBUG", "COMPLETED_DAYS_FROM_FIREBASE: " + results.size());
android.util.Log.d("FLUXUP_DEBUG", "DIAS_CUMPRIDOS_COUNT: " + results.size());
callback.accept(results);
})
.addOnFailureListener(e -> {
android.util.Log.e("FLUXUP_DEBUG", "LOAD_MONTHLY_PROGRESS_FAIL: " + e.getMessage());
callback.accept(new ArrayList<>());
});
}
}

View File

@@ -22,10 +22,14 @@ public class FluxupApplication extends Application {
// Aplicar preferências de tema
SharedPreferences prefs = getSharedPreferences("FluxupSettings", Context.MODE_PRIVATE);
if (prefs.getBoolean("darkMode", false)) {
boolean darkMode = prefs.getBoolean("dark_mode_enabled", false);
Log.d(TAG, "APP_THEME_IS_DAYNIGHT | dark_mode_enabled=" + darkMode);
if (darkMode) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
Log.d(TAG, "DARK_MODE_APPLIED | mode=MODE_NIGHT_YES");
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
Log.d(TAG, "DARK_MODE_APPLIED | mode=MODE_NIGHT_NO");
}
// Inicializar Firebase

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
package com.fluxup.app;
import android.graphics.Color;
public class LeagueHelper {
public static class LeagueInfo {
public String name;
public int minXp;
public int maxXp;
public int iconRes;
public String colorHex;
public String rewards;
public LeagueInfo(String name, int minXp, int maxXp, int iconRes, String colorHex, String rewards) {
this.name = name;
this.minXp = minXp;
this.maxXp = maxXp;
this.iconRes = iconRes;
this.colorHex = colorHex;
this.rewards = rewards;
}
}
public static final LeagueInfo[] LEAGUES = {
new LeagueInfo("Bronze", 0, 499, R.drawable.ic_trophy_bronze, "#8D6E63", "Estás a começar a tua jornada!"),
new LeagueInfo("Prata", 500, 1499, R.drawable.ic_trophy_silver, "#B0BEC5", "A consistência está a dar frutos."),
new LeagueInfo("Ouro", 1500, 2999, R.drawable.ic_trophy_gold, "#FFD54F", "Um verdadeiro guerreiro do foco!"),
new LeagueInfo("Esmeralda", 3000, 4999, R.drawable.ic_trophy_emerald, "#4CAF50", "Elite da produtividade."),
new LeagueInfo("Diamante", 5000, Integer.MAX_VALUE, R.drawable.ic_trophy_diamond, "#1E88E5", "Inquebrável e imparável.")
};
public static LeagueInfo getCurrentLeague(int totalXp) {
for (int i = LEAGUES.length - 1; i >= 0; i--) {
if (totalXp >= LEAGUES[i].minXp) {
return LEAGUES[i];
}
}
return LEAGUES[0];
}
public static LeagueInfo getNextLeague(int totalXp) {
for (int i = 0; i < LEAGUES.length - 1; i++) {
if (totalXp < LEAGUES[i + 1].minXp) {
return LEAGUES[i + 1];
}
}
return null;
}
}

View File

@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
android.content.SharedPreferences prefs = getSharedPreferences("FluxupSettings", android.content.Context.MODE_PRIVATE);
// Tema
if (prefs.getBoolean("darkMode", false)) {
if (prefs.getBoolean("dark_mode_enabled", false)) {
androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode(androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES);
} else {
androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode(androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO);
@@ -48,6 +48,8 @@ public class MainActivity extends AppCompatActivity {
} else if (itemId == R.id.nav_trophies) {
startActivity(new android.content.Intent(this, TrophiesActivity.class));
return true;
} else if (itemId == R.id.nav_rewards) {
selectedFragment = new RewardsFragment();
} else if (itemId == R.id.nav_profile) {
selectedFragment = new ProfileFragment();
} else if (itemId == R.id.nav_search) {

View File

@@ -0,0 +1,52 @@
package com.fluxup.app;
import com.fluxup.app.R;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
public class NotificationHelper {
private static final String CHANNEL_ID = "fluxup_notifications";
private static final String CHANNEL_NAME = "Fluxup Reminders";
public static void showNotification(Context context, String title, String message) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
}
Intent intent = new Intent(context, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_nav_home)
.setContentTitle(title)
.setContentText(message)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent);
NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);
// Verificar permissão no Android 13+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
return;
}
}
notificationManagerCompat.notify((int) System.currentTimeMillis(), builder.build());
}
}

View File

@@ -1,86 +1,533 @@
package com.fluxup.app;
import android.animation.ObjectAnimator;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.FrameLayout;
import android.widget.GridLayout;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.cardview.widget.CardView;
import androidx.fragment.app.Fragment;
import android.widget.TextView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.formatter.IndexAxisValueFormatter;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.firestore.ListenerRegistration;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class ProfileFragment extends Fragment {
private TextView tvUsername, tvHandle, tvStreakValue, tvTotalXP, tvLeagueName, tvAchievementsCount;
private ListenerRegistration userListener;
private TextView tvProfileName, tvProfileHandle;
private AvatarView ivProfileAvatar;
private ImageView ivLeagueBadgeSmall, ivLeagueIcon;
private TextView tvUserLevel, tvXpFraction, tvLeagueName, tvLeagueMotivation, tvConsistencyPhrase;
private ProgressBar pbXpLevel;
private RecyclerView rvMiniCalendar;
private LineChart weeklyChart;
private GridLayout badgesGrid;
private LinearLayout friendsListContainer;
private View statTotalTasks, statBestStreak, statFocusSessions, statTotalFocus, statDaysCompleted, statDailyAvg;
private ListenerRegistration userListener, friendsListener;
private View view;
private Usuario currentUser;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_profile, container, false);
ImageButton btnSettings = view.findViewById(R.id.btnSettings);
btnSettings.setOnClickListener(v -> {
Intent intent = new Intent(getActivity(), SettingsActivity.class);
startActivity(intent);
});
View btnInviteCard = view.findViewById(R.id.btnInviteCard);
btnInviteCard.setOnClickListener(v -> openFindFriends());
View btnInviteFriends = view.findViewById(R.id.btnInviteFriends);
btnInviteFriends.setOnClickListener(v -> openFindFriends());
// Initialize UI components
tvUsername = view.findViewById(R.id.tvUsername);
tvHandle = view.findViewById(R.id.tvHandle);
tvStreakValue = view.findViewById(R.id.tvStreakValue);
tvTotalXP = view.findViewById(R.id.tvTotalXP);
tvLeagueName = view.findViewById(R.id.tvLeagueName);
tvAchievementsCount = view.findViewById(R.id.tvAchievementsCount);
View cardLeague = view.findViewById(R.id.cardLeague);
cardLeague.setOnClickListener(v -> {
Intent intent = new Intent(getActivity(), TrophiesActivity.class);
startActivity(intent);
});
initViews(view);
setupListeners(view);
startObservingUser();
startObservingFriends();
return view;
}
private void startObservingUser() {
FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser();
if (currentUser != null) {
userListener = FirestoreManager.getInstance().observeUser(currentUser.getUid(), this::updateUI);
private void initViews(View view) {
tvProfileName = view.findViewById(R.id.tvProfileName);
tvProfileHandle = view.findViewById(R.id.tvProfileHandle);
ivProfileAvatar = view.findViewById(R.id.ivProfileAvatar);
ivLeagueBadgeSmall = view.findViewById(R.id.ivLeagueBadgeSmall);
ivLeagueIcon = view.findViewById(R.id.ivLeagueIcon);
tvUserLevel = view.findViewById(R.id.tvUserLevel);
tvXpFraction = view.findViewById(R.id.tvXpFraction);
tvLeagueName = view.findViewById(R.id.tvLeagueName);
tvLeagueMotivation = view.findViewById(R.id.tvLeagueMotivation);
tvConsistencyPhrase = view.findViewById(R.id.tvConsistencyPhrase);
pbXpLevel = view.findViewById(R.id.pbXpLevel);
rvMiniCalendar = view.findViewById(R.id.rvMiniCalendar);
weeklyChart = view.findViewById(R.id.weeklyChart);
badgesGrid = view.findViewById(R.id.badgesGrid);
friendsListContainer = view.findViewById(R.id.friendsListContainer);
statTotalTasks = view.findViewById(R.id.statTotalTasks);
statBestStreak = view.findViewById(R.id.statBestStreak);
statFocusSessions = view.findViewById(R.id.statFocusSessions);
statTotalFocus = view.findViewById(R.id.statTotalFocus);
statDaysCompleted = view.findViewById(R.id.statDaysCompleted);
statDailyAvg = view.findViewById(R.id.statDailyAvg);
setupStatItems();
}
private void setupStatItems() {
setStatMeta(statTotalTasks, "🎯", "Tarefas");
setStatMeta(statBestStreak, "🔥", "Recorde");
setStatMeta(statFocusSessions, "🧠", "Sessões");
setStatMeta(statTotalFocus, "⏱️", "Total Foco");
setStatMeta(statDaysCompleted, "📅", "Dias");
setStatMeta(statDailyAvg, "📈", "Média/Dia");
}
private void setStatMeta(View statView, String icon, String label) {
if (statView == null) return;
((TextView) statView.findViewById(R.id.tvStatIcon)).setText(icon);
((TextView) statView.findViewById(R.id.tvStatLabel)).setText(label);
}
private void setupListeners(View view) {
// Edit Profile / Settings
View btnEditProfile = view.findViewById(R.id.btnEditProfile);
if (btnEditProfile != null) {
btnEditProfile.setOnClickListener(v -> {
startActivity(new Intent(getActivity(), SettingsActivity.class));
});
}
// Avatar Editor
View cardAvatar = view.findViewById(R.id.cardAvatar);
if (cardAvatar != null) {
cardAvatar.setOnClickListener(v -> startActivity(new Intent(getActivity(), AvatarEditorActivity.class)));
}
// Add Friends (+)
View btnAddFriends = view.findViewById(R.id.btnAddFriends);
if (btnAddFriends != null) {
btnAddFriends.setOnClickListener(v -> startActivity(new Intent(getActivity(), FindFriendsActivity.class)));
}
// View All Friends
View btnViewAllFriends = view.findViewById(R.id.btnViewAllFriends);
if (btnViewAllFriends != null) {
btnViewAllFriends.setOnClickListener(v -> startActivity(new Intent(getActivity(), FindFriendsActivity.class)));
}
// Find Friends (ADICIONAR AMIGOS)
View btnFindFriends = view.findViewById(R.id.btnFindFriends);
if (btnFindFriends != null) {
btnFindFriends.setOnClickListener(v -> startActivity(new Intent(getActivity(), FindFriendsActivity.class)));
}
// View All Badges
View btnViewAllBadges = view.findViewById(R.id.btnViewAllBadges);
if (btnViewAllBadges != null) {
btnViewAllBadges.setOnClickListener(v -> startActivity(new Intent(getActivity(), TrophiesActivity.class)));
}
// Logout
View btnLogout = view.findViewById(R.id.btnLogout);
if (btnLogout != null) {
btnLogout.setOnClickListener(v -> {
AuthManager.getInstance().logout();
Intent intent = new Intent(getActivity(), LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
});
}
}
private void updateUI(Usuario user) {
if (getContext() == null) return;
tvUsername.setText(user.usuario);
tvHandle.setText(user.handle);
tvStreakValue.setText(String.valueOf(user.streak));
tvTotalXP.setText(String.valueOf(user.xp));
tvLeagueName.setText(user.league);
tvAchievementsCount.setText(String.valueOf(user.achievementsCount));
private void startObservingUser() {
FirebaseUser fUser = AuthManager.getInstance().getCurrentUser();
if (fUser != null) {
userListener = FirestoreManager.getInstance().observeUser(fUser.getUid(), user -> {
if (getContext() == null || view == null || user == null) return;
this.currentUser = user;
updateBasicInfo(user);
updateXpBar(user);
updateLeagueCard(user);
updateRealStats(user);
updateMiniCalendar(user);
updateWeeklyChart(user);
renderBadges(user);
updateMotivation(user);
});
}
}
private void updateBasicInfo(Usuario user) {
tvProfileName.setText(user.usuario);
tvProfileHandle.setText(user.handle);
if (user.avatar != null && ivProfileAvatar != null) {
ivProfileAvatar.setAvatarData(user.avatar);
ivProfileAvatar.setLeague(user.league);
}
}
private void updateXpBar(Usuario user) {
LeagueHelper.LeagueInfo current = LeagueHelper.getCurrentLeague(user.xp);
LeagueHelper.LeagueInfo next = LeagueHelper.getNextLeague(user.xp);
int currentLevel = calculateLevel(user.xp);
tvUserLevel.setText("Nível " + currentLevel);
int levelStartXp = getLevelStartXp(currentLevel);
int levelEndXp = getLevelStartXp(currentLevel + 1);
int progressInLevel = user.xp - levelStartXp;
int levelXpSpan = levelEndXp - levelStartXp;
tvXpFraction.setText(user.xp + " / " + levelEndXp + " XP");
int progressPercent = (int) ((progressInLevel * 100.0f) / levelXpSpan);
// Animate Progress Bar
ObjectAnimator animator = ObjectAnimator.ofInt(pbXpLevel, "progress", pbXpLevel.getProgress(), progressPercent);
animator.setDuration(1000);
animator.setInterpolator(new DecelerateInterpolator());
animator.start();
}
private int calculateLevel(int totalXp) {
// Simple formula: Level = sqrt(xp/100)
return (int) Math.floor(Math.sqrt(totalXp / 50.0)) + 1;
}
private int getLevelStartXp(int level) {
if (level <= 1) return 0;
return (int) (Math.pow(level - 1, 2) * 50);
}
private void updateLeagueCard(Usuario user) {
LeagueHelper.LeagueInfo current = LeagueHelper.getCurrentLeague(user.xp);
LeagueHelper.LeagueInfo next = LeagueHelper.getNextLeague(user.xp);
tvLeagueName.setText("Liga " + current.name);
ivLeagueIcon.setImageResource(current.iconRes);
ivLeagueBadgeSmall.setImageResource(current.iconRes);
if (next != null) {
int remaining = next.minXp - user.xp;
tvLeagueMotivation.setText("Faltam " + remaining + " XP para " + next.name);
} else {
tvLeagueMotivation.setText("Estás no topo absoluto!");
}
}
private void updateRealStats(Usuario user) {
setStatValue(statTotalTasks, String.valueOf(user.total_tasks_concluidas));
setStatValue(statBestStreak, String.valueOf(user.melhor_streak));
setStatValue(statFocusSessions, String.valueOf(user.sessoes_foco_completas));
setStatValue(statTotalFocus, formatTime(user.tempo_foco_total));
setStatValue(statDaysCompleted, String.valueOf(user.dias_concluidos != null ? user.dias_concluidos.size() : 0));
int avg = user.dias_ativos > 0 ? user.tempo_foco_total / user.dias_ativos : 0;
setStatValue(statDailyAvg, avg + " min");
}
private void setStatValue(View statView, String value) {
if (statView == null) return;
((TextView) statView.findViewById(R.id.tvStatValue)).setText(value);
}
private String formatTime(int totalMinutes) {
if (totalMinutes < 60) return totalMinutes + "m";
int h = totalMinutes / 60;
int m = totalMinutes % 60;
if (m == 0) return h + "h";
return h + "h " + m + "m";
}
private void updateMotivation(Usuario user) {
if (user.streak >= 7) {
tvConsistencyPhrase.setText("Estás imparável com " + user.streak + " dias seguidos! 🔥");
} else if (user.streak > 0) {
tvConsistencyPhrase.setText("Continua assim! " + user.streak + " dias de foco.");
} else {
tvConsistencyPhrase.setText("Começa hoje a tua nova ofensiva! 🚀");
}
}
private void updateMiniCalendar(Usuario user) {
rvMiniCalendar.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
List<Date> last7Days = new ArrayList<>();
Calendar cal = Calendar.getInstance();
for (int i = 0; i < 7; i++) {
last7Days.add(cal.getTime());
cal.add(Calendar.DAY_OF_YEAR, -1);
}
Collections.reverse(last7Days);
MiniCalendarAdapter adapter = new MiniCalendarAdapter(last7Days, user.dias_concluidos);
rvMiniCalendar.setAdapter(adapter);
}
private void updateWeeklyChart(Usuario user) {
weeklyChart.getDescription().setEnabled(false);
weeklyChart.setDrawGridBackground(false);
weeklyChart.getLegend().setEnabled(false);
weeklyChart.setTouchEnabled(false);
XAxis xAxis = weeklyChart.getXAxis();
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
xAxis.setDrawGridLines(false);
xAxis.setTextColor(Color.GRAY);
xAxis.setTextSize(10f);
weeklyChart.getAxisLeft().setDrawGridLines(true);
weeklyChart.getAxisLeft().setGridColor(Color.parseColor("#F0F0F0"));
weeklyChart.getAxisLeft().setTextColor(Color.GRAY);
weeklyChart.getAxisRight().setEnabled(false);
// Fetch last 7 days of XP logs or use an approximation if logs aren't easily available
// For now, let's use a nice mockup using current data if logs are missing
List<Entry> entries = new ArrayList<>();
String[] days = new String[7];
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_YEAR, -6);
SimpleDateFormat sdf = new SimpleDateFormat("E", new Locale("pt", "PT"));
for (int i = 0; i < 7; i++) {
days[i] = sdf.format(cal.getTime()).substring(0, 1).toUpperCase();
// Mocking some data based on status if real logs aren't aggregated yet
// In a real app, we'd query DailyProgress for the last 7 days
float val = (float) (Math.random() * 50 + 20);
if (i == 6) val = user.xp_hoje;
entries.add(new Entry(i, val));
cal.add(Calendar.DAY_OF_YEAR, 1);
}
xAxis.setValueFormatter(new IndexAxisValueFormatter(days));
LineDataSet set = new LineDataSet(entries, "XP");
set.setColor(Color.parseColor("#7C3AED"));
set.setLineWidth(3f);
set.setCircleColor(Color.parseColor("#7C3AED"));
set.setCircleRadius(5f);
set.setDrawCircleHole(true);
set.setCircleHoleRadius(2f);
set.setMode(LineDataSet.Mode.CUBIC_BEZIER);
set.setDrawFilled(true);
set.setFillDrawable(getResources().getDrawable(R.drawable.chart_fill_gradient));
set.setDrawValues(false);
LineData data = new LineData(set);
weeklyChart.setData(data);
weeklyChart.animateY(1000);
weeklyChart.invalidate();
}
private String determineTitle(Usuario user) {
if (user.level > 50) return "Mestre do Foco";
if (user.level > 20) return "Guerreiro Produtivo";
if (user.level > 10) return "Focado Regular";
return "Iniciante de Foco";
}
private void renderBadges(Usuario user) {
badgesGrid.removeAllViews();
// 7 days streak
addBadge(user.streak >= 7 ? "🔥" : "🔒", "7 Dias", user.streak >= 7);
// 50 tasks
addBadge(user.total_tasks_concluidas >= 50 ? "🎯" : "🔒", "50 Tarefas", user.total_tasks_concluidas >= 50);
// 10 hours focus (600 min)
addBadge(user.tempo_foco_total >= 600 ? "⏱️" : "🔒", "10 Horas", user.tempo_foco_total >= 600);
// 1000 XP
addBadge(user.xp >= 1000 ? "" : "🔒", "1000 XP", user.xp >= 1000);
}
private void addBadge(String emoji, String name, boolean unlocked) {
LinearLayout badge = new LinearLayout(getContext());
badge.setOrientation(LinearLayout.VERTICAL);
badge.setGravity(android.view.Gravity.CENTER);
badge.setPadding(4, 12, 4, 12);
FrameLayout iconFrame = new FrameLayout(getContext());
View glow = new View(getContext());
int glowSize = (int) (48 * getResources().getDisplayMetrics().density);
glow.setLayoutParams(new FrameLayout.LayoutParams(glowSize, glowSize));
glow.setBackground(getResources().getDrawable(R.drawable.circle_bg));
glow.setBackgroundTintList(android.content.res.ColorStateList.valueOf(Color.parseColor(unlocked ? "#F3E8FF" : "#F3F4F6")));
TextView tvEmoji = new TextView(getContext());
tvEmoji.setText(emoji);
tvEmoji.setTextSize(24);
FrameLayout.LayoutParams emojiParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
emojiParams.gravity = android.view.Gravity.CENTER;
tvEmoji.setLayoutParams(emojiParams);
if (!unlocked) tvEmoji.setAlpha(0.3f);
iconFrame.addView(glow);
iconFrame.addView(tvEmoji);
TextView tvName = new TextView(getContext());
tvName.setText(name);
tvName.setTextSize(10);
tvName.setGravity(android.view.Gravity.CENTER);
tvName.setPadding(0, 8, 0, 0);
tvName.setTextColor(getResources().getColor(unlocked ? R.color.text_primary : R.color.text_secondary));
tvName.setAllCaps(true);
tvName.setLetterSpacing(0.05f);
badge.addView(iconFrame);
badge.addView(tvName);
badgesGrid.addView(badge);
}
private void startObservingFriends() {
if (AuthManager.getInstance().getCurrentUser() == null) return;
String myUid = AuthManager.getInstance().getCurrentUser().getUid();
friendsListener = com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("friendships")
.whereArrayContains("users", myUid)
.limit(3)
.addSnapshotListener((snapshots, e) -> {
if (e != null || snapshots == null) return;
friendsListContainer.removeAllViews();
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) {
List<String> users = (List<String>) doc.get("users");
if (users != null) {
for (String uId : users) {
if (!uId.equals(myUid)) {
fetchAndAddProfileFriendItem(uId);
}
}
}
}
});
}
private void fetchAndAddProfileFriendItem(String friendId) {
com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("users").document(friendId).get().addOnSuccessListener(snapshot -> {
if (getContext() == null || view == null) return;
Usuario friend = snapshot.toObject(Usuario.class);
if (friend != null) {
addFriendItem(friend);
}
});
}
private void addFriendItem(Usuario friend) {
View v = getLayoutInflater().inflate(R.layout.item_ranking_user, friendsListContainer, false);
((TextView) v.findViewById(R.id.tvRankingName)).setText(friend.usuario);
((TextView) v.findViewById(R.id.tvRankingXP)).setText(friend.xp + " XP");
// Populate Streak
TextView tvStreak = v.findViewById(R.id.tvRankingStreak);
if (tvStreak != null) {
tvStreak.setText("🔥 " + friend.streak);
}
AvatarView ivAvatar = v.findViewById(R.id.ivRankingAvatar);
if (ivAvatar != null && friend.avatar != null) {
ivAvatar.setAvatarData(friend.avatar);
ivAvatar.setLeague(friend.league);
}
// Gamify friend item
TextView tvRank = v.findViewById(R.id.tvRankPosition);
if (tvRank != null) tvRank.setText("#" + (friendsListContainer.getChildCount() + 1));
friendsListContainer.addView(v);
}
// --- MINI CALENDAR ADAPTER ---
private class MiniCalendarAdapter extends RecyclerView.Adapter<MiniCalendarAdapter.ViewHolder> {
private List<Date> dates;
private List<String> completedDates;
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
private SimpleDateFormat daySdf = new SimpleDateFormat("E", new Locale("pt", "PT"));
public MiniCalendarAdapter(List<Date> dates, List<String> completedDates) {
this.dates = dates;
this.completedDates = completedDates != null ? completedDates : new ArrayList<>();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_mini_calendar_day, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Date date = dates.get(position);
String dateStr = sdf.format(date);
String todayStr = sdf.format(new Date());
holder.tvDayName.setText(daySdf.format(date).substring(0, 1).toUpperCase());
if (completedDates.contains(dateStr)) {
holder.vDayIndicator.setBackgroundTintList(android.content.res.ColorStateList.valueOf(Color.parseColor("#22C55E")));
} else if (dateStr.equals(todayStr)) {
holder.vDayIndicator.setBackgroundTintList(android.content.res.ColorStateList.valueOf(Color.parseColor("#7C3AED")));
} else {
holder.vDayIndicator.setBackgroundTintList(android.content.res.ColorStateList.valueOf(Color.parseColor("#E5E7EB")));
}
}
@Override
public int getItemCount() {
return dates.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
TextView tvDayName;
View vDayIndicator;
public ViewHolder(@NonNull View itemView) {
super(itemView);
tvDayName = itemView.findViewById(R.id.tvDayName);
vDayIndicator = itemView.findViewById(R.id.vDayIndicator);
}
}
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
this.view = view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (userListener != null) {
userListener.remove();
}
}
private void openFindFriends() {
Intent intent = new Intent(getActivity(), FindFriendsActivity.class);
startActivity(intent);
if (userListener != null) userListener.remove();
if (friendsListener != null) friendsListener.remove();
}
}

View File

@@ -0,0 +1,966 @@
package com.fluxup.app;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.BounceInterpolator;
import android.view.animation.CycleInterpolator;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.firestore.FieldValue;
import com.google.firebase.firestore.ListenerRegistration;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
public class RewardsFragment extends Fragment {
// Views
private TextView tvStatsXP, tvStatsStreak, tvStatsLevel, tvStatsCoins;
private TextView tvDailyPresentGoal, tvDailyPresentProgressText;
private ProgressBar pbDailyPresent;
private Button btnClaimDailyPresent;
private LinearLayout llSequenceContainer;
private Button btnClaimSequence;
private android.widget.LinearLayout llMissionsContainer;
private android.widget.LinearLayout llStreakRewardsContainer;
private android.widget.LinearLayout llStoreContainer;
private int previousCoins = -1;
private TextView tvMysteryBoxIcon, tvMysteryBoxStatus;
private Button btnOpenMysteryBox;
// Listeners and Data
private ListenerRegistration userListener;
private Usuario currentUserData;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_rewards, container, false);
// Bind Views
tvStatsXP = view.findViewById(R.id.tvStatsXP);
tvStatsStreak = view.findViewById(R.id.tvStatsStreak);
tvStatsLevel = view.findViewById(R.id.tvStatsLevel);
tvStatsCoins = view.findViewById(R.id.tvStatsCoins);
tvDailyPresentGoal = view.findViewById(R.id.tvDailyPresentGoal);
tvDailyPresentProgressText = view.findViewById(R.id.tvDailyPresentProgressText);
pbDailyPresent = view.findViewById(R.id.pbDailyPresent);
btnClaimDailyPresent = view.findViewById(R.id.btnClaimDailyPresent);
llSequenceContainer = view.findViewById(R.id.llSequenceContainer);
btnClaimSequence = view.findViewById(R.id.btnClaimSequence);
llMissionsContainer = view.findViewById(R.id.llMissionsContainer);
llStreakRewardsContainer = view.findViewById(R.id.llStreakRewardsContainer);
llStoreContainer = view.findViewById(R.id.llStoreContainer);
tvMysteryBoxIcon = view.findViewById(R.id.tvMysteryBoxIcon);
tvMysteryBoxStatus = view.findViewById(R.id.tvMysteryBoxStatus);
btnOpenMysteryBox = view.findViewById(R.id.btnOpenMysteryBox);
// Initialize observers
startObservingUser();
return view;
}
private void startObservingUser() {
FirebaseUser user = AuthManager.getInstance().getCurrentUser();
if (user != null) {
userListener = FirestoreManager.getInstance().observeUser(user.getUid(), usuario -> {
if (usuario != null && isAdded()) {
currentUserData = usuario;
updateUI(usuario);
}
});
}
}
private void updateUI(Usuario user) {
String today = getTodayDateString();
// 1. Header Stats
tvStatsXP.setText(user.xp + " XP");
tvStatsStreak.setText(user.streak + " dias");
tvStatsLevel.setText("Nível " + user.level);
tvStatsCoins.setText(user.coins + " moedas");
if (previousCoins != -1 && user.coins > previousCoins) {
int diff = user.coins - previousCoins;
showFloatingText(tvStatsCoins, "+" + diff + " moedas");
pulseAnimation(tvStatsCoins);
}
previousCoins = user.coins;
// 2. Daily Present Card
int completedTasks = user.tasks_concluidas_hoje;
int dailyGoal = user.meta_diaria_tarefas > 0 ? user.meta_diaria_tarefas : 4;
tvDailyPresentGoal.setText("Completa " + dailyGoal + " tarefas hoje para ganhar +100 XP");
tvDailyPresentProgressText.setText(completedTasks + "/" + dailyGoal);
float progress = (float) completedTasks / dailyGoal;
pbDailyPresent.setProgress((int) (progress * 100));
boolean isDailyPresentClaimed = today.equals(user.last_reward_claim_date);
if (isDailyPresentClaimed) {
btnClaimDailyPresent.setEnabled(false);
btnClaimDailyPresent.setText("Resgatado");
btnClaimDailyPresent.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.border_color));
btnClaimDailyPresent.setTextColor(ContextCompat.getColor(getContext(), R.color.text_secondary));
} else if (completedTasks >= dailyGoal) {
btnClaimDailyPresent.setEnabled(true);
btnClaimDailyPresent.setText("Resgatar");
btnClaimDailyPresent.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.white));
btnClaimDailyPresent.setTextColor(ContextCompat.getColor(getContext(), R.color.primary_purple));
btnClaimDailyPresent.setOnClickListener(v -> claimDailyPresent());
} else {
btnClaimDailyPresent.setEnabled(false);
btnClaimDailyPresent.setText("Bloqueado");
btnClaimDailyPresent.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.border_color));
btnClaimDailyPresent.setTextColor(ContextCompat.getColor(getContext(), R.color.text_secondary));
}
// 3. 7-Day Sequence
setupSequenceCalendar(user);
// 4. Daily Missions
setupDailyMissions(user);
// 5. Streak Rewards
setupStreakRewards(user);
// 6. Loja
setupStore(user);
// 6. Mystery Box
setupMysteryBox(user);
}
// --- DAILY PRESENT ---
private void claimDailyPresent() {
if (currentUserData == null) return;
String today = getTodayDateString();
if (today.equals(currentUserData.last_reward_claim_date)) {
showToast("Já resgataste o presente de hoje!");
return;
}
int dailyGoal = currentUserData.meta_diaria_tarefas > 0 ? currentUserData.meta_diaria_tarefas : 4;
if (currentUserData.tasks_concluidas_hoje < dailyGoal) {
showToast("Completa as tuas tarefas primeiro!");
return;
}
Map<String, Object> updates = new HashMap<>();
updates.put("last_reward_claim_date", today);
updates.put("xp", FieldValue.increment(100));
updates.put("xp_hoje", FieldValue.increment(100));
updates.put("xp_semanal", FieldValue.increment(100));
FirestoreManager.getInstance().updateUserStats(currentUserData.id_usuario, updates, () -> {
FirestoreManager.getInstance().addXpLog(currentUserData.id_usuario, 100, "daily_present_claim");
FirestoreManager.getInstance().addCoins(currentUserData.id_usuario, 20, "daily_goal_reward");
showLevelUpCheck(100);
showRewardDialog("🎁 Presente Diário", "+100 XP e +20 moedas", "Excelente trabalho ao completares as tuas tarefas de hoje!");
});
}
// --- 7-DAY SEQUENCE ---
private void setupSequenceCalendar(Usuario user) {
if (getContext() == null) return;
llSequenceContainer.removeAllViews();
String today = getTodayDateString();
String lastClaim = user.last_login_claim_date;
int activeStreak = user.login_streak;
boolean claimedToday = today.equals(lastClaim);
boolean claimedYesterday = isYesterday(lastClaim);
int nextClaimDay;
if (claimedToday) {
nextClaimDay = activeStreak; // showing the day we claimed today
} else if (claimedYesterday) {
nextClaimDay = (activeStreak % 7) + 1; // advancing to the next day
} else {
nextClaimDay = 1; // streak broken, reset to 1
}
String[] rewards = {"+50 XP", "+75 XP", "+100 XP", "+125 XP 🎁", "+150 XP ⚡", "+175 XP 🏅", "+250 XP 👑"};
String[] icons = {"", "", "", "🎁", "", "🏅", "👑"};
for (int i = 1; i <= 7; i++) {
View node = LayoutInflater.from(getContext()).inflate(R.layout.item_sequence_day, llSequenceContainer, false);
TextView tvDayLabel = node.findViewById(R.id.tvDayLabel);
View flNodeBackground = node.findViewById(R.id.flNodeBackground);
TextView tvDayRewardIcon = node.findViewById(R.id.tvDayRewardIcon);
ImageView ivDayClaimedCheck = node.findViewById(R.id.ivDayClaimedCheck);
TextView tvDayRewardValue = node.findViewById(R.id.tvDayRewardValue);
tvDayLabel.setText("Dia " + i);
tvDayRewardValue.setText(rewards[i - 1]);
tvDayRewardIcon.setText(icons[i - 1]);
// Determine State
if (claimedToday) {
if (i <= activeStreak) {
// Claimed
flNodeBackground.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.success_green));
tvDayRewardIcon.setVisibility(View.GONE);
ivDayClaimedCheck.setVisibility(View.VISIBLE);
} else {
// Locked
flNodeBackground.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.border_color));
tvDayRewardIcon.setVisibility(View.VISIBLE);
ivDayClaimedCheck.setVisibility(View.GONE);
}
} else {
if (claimedYesterday) {
if (i < nextClaimDay) {
// Claimed previously
flNodeBackground.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.success_green));
tvDayRewardIcon.setVisibility(View.GONE);
ivDayClaimedCheck.setVisibility(View.VISIBLE);
} else if (i == nextClaimDay) {
// Current day to claim (Active)
flNodeBackground.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.primary_purple));
tvDayRewardIcon.setVisibility(View.VISIBLE);
tvDayRewardIcon.setTextColor(Color.WHITE);
ivDayClaimedCheck.setVisibility(View.GONE);
} else {
// Future lock
flNodeBackground.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.border_color));
tvDayRewardIcon.setVisibility(View.VISIBLE);
ivDayClaimedCheck.setVisibility(View.GONE);
}
} else {
// Streak broken, only Day 1 is active, others are locked
if (i == 1) {
flNodeBackground.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.primary_purple));
tvDayRewardIcon.setVisibility(View.VISIBLE);
tvDayRewardIcon.setTextColor(Color.WHITE);
ivDayClaimedCheck.setVisibility(View.GONE);
} else {
flNodeBackground.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.border_color));
tvDayRewardIcon.setVisibility(View.VISIBLE);
ivDayClaimedCheck.setVisibility(View.GONE);
}
}
}
llSequenceContainer.addView(node);
}
// Claim Button state
if (claimedToday) {
btnClaimSequence.setEnabled(false);
btnClaimSequence.setText("Recompensado Hoje (Dia " + activeStreak + ")");
btnClaimSequence.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.border_color));
btnClaimSequence.setTextColor(ContextCompat.getColor(getContext(), R.color.text_secondary));
} else {
btnClaimSequence.setEnabled(true);
btnClaimSequence.setText("Resgatar Dia " + nextClaimDay);
btnClaimSequence.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.primary_purple));
btnClaimSequence.setTextColor(Color.WHITE);
btnClaimSequence.setOnClickListener(v -> claimSequence(nextClaimDay));
}
}
private void claimSequence(int dayToClaim) {
if (currentUserData == null) return;
String today = getTodayDateString();
if (today.equals(currentUserData.last_login_claim_date)) {
showToast("Já resgataste a recompensa de login de hoje!");
return;
}
int[] xpRewards = {50, 75, 100, 125, 150, 175, 250};
int xpReward = xpRewards[dayToClaim - 1];
// Coins rewards to demonstrate preparation for currency
int coinsReward = dayToClaim * 5;
Map<String, Object> updates = new HashMap<>();
updates.put("login_streak", dayToClaim);
updates.put("last_login_claim_date", today);
updates.put("xp", FieldValue.increment(xpReward));
updates.put("xp_hoje", FieldValue.increment(xpReward));
updates.put("xp_semanal", FieldValue.increment(xpReward));
updates.put("coins", FieldValue.increment(coinsReward));
FirestoreManager.getInstance().updateUserStats(currentUserData.id_usuario, updates, () -> {
FirestoreManager.getInstance().addXpLog(currentUserData.id_usuario, xpReward, "daily_login_claim");
showLevelUpCheck(xpReward);
String rewardDesc = "+" + xpReward + " XP e +" + coinsReward + " 🪙";
if (dayToClaim == 4) rewardDesc += "\n🎁 Acessório Extra Desbloqueado!";
if (dayToClaim == 6) rewardDesc += "\n🏅 Emblema Diário Obtido!";
if (dayToClaim == 7) rewardDesc += "\n👑 Item Raro Desbloqueado!";
showRewardDialog("📅 Sequência Diária (Dia " + dayToClaim + ")", rewardDesc, "Volta amanhã para continuar a sequência!");
});
}
// --- DAILY MISSIONS ---
private void setupDailyMissions(Usuario user) {
if (getContext() == null) return;
llMissionsContainer.removeAllViews();
String today = getTodayDateString();
// Safe reset client-side if dates don't match
if (!today.equals(user.claimed_missions_date)) {
user.claimed_missions_date = today;
user.claimed_missions_today = new ArrayList<>();
Map<String, Object> updates = new HashMap<>();
updates.put("claimed_missions_date", today);
updates.put("claimed_missions_today", new ArrayList<>());
FirestoreManager.getInstance().updateUserStats(user.id_usuario, updates);
}
int dailyGoal = user.meta_diaria_tarefas > 0 ? user.meta_diaria_tarefas : 4;
List<DailyMission> missions = new ArrayList<>();
missions.add(new DailyMission("m_tasks", "Completar " + dailyGoal + " tarefas hoje", "", user.tasks_concluidas_hoje, dailyGoal, 50));
missions.add(new DailyMission("m_focus", "Fazer 1 sessão de foco (25 min)", "⏱️", user.tempo_foco_hoje >= 25 ? 1 : 0, 1, 50));
missions.add(new DailyMission("m_xp", "Ganhar 100 XP hoje", "", user.xp_hoje, 100, 50));
missions.add(new DailyMission("m_streak", "Manter a ofensiva diária", "🔥", user.streak >= 1 ? 1 : 0, 1, 50));
for (DailyMission mission : missions) {
View itemView = LayoutInflater.from(getContext()).inflate(R.layout.item_mission, llMissionsContainer, false);
TextView tvMissionIcon = itemView.findViewById(R.id.tvMissionIcon);
TextView tvMissionTitle = itemView.findViewById(R.id.tvMissionTitle);
ProgressBar pbMissionProgress = itemView.findViewById(R.id.pbMissionProgress);
TextView tvMissionProgressText = itemView.findViewById(R.id.tvMissionProgressText);
TextView tvMissionReward = itemView.findViewById(R.id.tvMissionReward);
Button btnClaimMission = itemView.findViewById(R.id.btnClaimMission);
tvMissionIcon.setText(mission.icon);
tvMissionTitle.setText(mission.title);
tvMissionReward.setText("+" + mission.xpReward + " XP");
tvMissionProgressText.setText(mission.progress + "/" + mission.max);
int progressPercent = (int) (((float) mission.progress / mission.max) * 100);
pbMissionProgress.setProgress(progressPercent);
boolean isClaimed = user.claimed_missions_today.contains(mission.id);
if (isClaimed) {
btnClaimMission.setEnabled(false);
btnClaimMission.setText("Resgatado");
btnClaimMission.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.border_color));
btnClaimMission.setTextColor(ContextCompat.getColor(getContext(), R.color.text_secondary));
} else if (mission.progress >= mission.max) {
btnClaimMission.setEnabled(true);
btnClaimMission.setText("Resgatar");
btnClaimMission.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.primary_purple));
btnClaimMission.setTextColor(Color.WHITE);
btnClaimMission.setOnClickListener(v -> claimMission(mission));
} else {
btnClaimMission.setEnabled(false);
btnClaimMission.setText("Resgatar");
btnClaimMission.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.border_color));
btnClaimMission.setTextColor(ContextCompat.getColor(getContext(), R.color.text_secondary));
}
llMissionsContainer.addView(itemView);
}
}
private void claimMission(DailyMission mission) {
if (currentUserData == null) return;
if (currentUserData.claimed_missions_today.contains(mission.id)) {
showToast("Já resgataste esta missão!");
return;
}
Map<String, Object> updates = new HashMap<>();
updates.put("claimed_missions_today", FieldValue.arrayUnion(mission.id));
updates.put("xp", FieldValue.increment(mission.xpReward));
updates.put("xp_hoje", FieldValue.increment(mission.xpReward));
updates.put("xp_semanal", FieldValue.increment(mission.xpReward));
FirestoreManager.getInstance().updateUserStats(currentUserData.id_usuario, updates, () -> {
FirestoreManager.getInstance().addXpLog(currentUserData.id_usuario, mission.xpReward, "daily_mission_" + mission.id);
showLevelUpCheck(mission.xpReward);
showRewardDialog("🎯 Missão Concluída", "+" + mission.xpReward + " XP", mission.title);
});
}
// --- STREAK REWARDS ---
private void setupStreakRewards(Usuario user) {
if (getContext() == null) return;
llStreakRewardsContainer.removeAllViews();
List<StreakReward> rewards = new ArrayList<>();
rewards.add(new StreakReward("streak_3", 3, "Ofensiva de 3 dias", "Recompensa: +100 XP", 100, null, "🔥"));
rewards.add(new StreakReward("streak_7", 7, "Ofensiva de 7 dias", "Recompensa: Aura de Fogo + 250 XP", 250, "effect_fire", ""));
rewards.add(new StreakReward("streak_14", 14, "Ofensiva de 14 dias", "Recompensa: Casaco Premium + 500 XP", 500, "clothes_outfit", "🧥"));
rewards.add(new StreakReward("streak_30", 30, "Ofensiva de 30 dias", "Recompensa: Moldura Dourada + 1000 XP", 1000, "frame_gold", "🏆"));
for (StreakReward reward : rewards) {
View itemView = LayoutInflater.from(getContext()).inflate(R.layout.item_streak_reward, llStreakRewardsContainer, false);
TextView tvStreakRewardIcon = itemView.findViewById(R.id.tvStreakRewardIcon);
TextView tvStreakTitle = itemView.findViewById(R.id.tvStreakTitle);
TextView tvStreakRewardDetail = itemView.findViewById(R.id.tvStreakRewardDetail);
Button btnClaimStreak = itemView.findViewById(R.id.btnClaimStreak);
tvStreakRewardIcon.setText(reward.icon);
tvStreakTitle.setText(reward.title);
tvStreakRewardDetail.setText(reward.rewardDetail);
boolean isClaimed = user.claimed_streak_rewards.contains(reward.id);
boolean isEligible = user.streak >= reward.requiredStreak;
if (isClaimed) {
btnClaimStreak.setEnabled(false);
btnClaimStreak.setText("Resgatado");
btnClaimStreak.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.border_color));
btnClaimStreak.setTextColor(ContextCompat.getColor(getContext(), R.color.text_secondary));
} else if (isEligible) {
btnClaimStreak.setEnabled(true);
btnClaimStreak.setText("Resgatar");
btnClaimStreak.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.primary_purple));
btnClaimStreak.setTextColor(Color.WHITE);
btnClaimStreak.setOnClickListener(v -> claimStreakReward(reward));
} else {
btnClaimStreak.setEnabled(false);
btnClaimStreak.setText(user.streak + "/" + reward.requiredStreak + " d");
btnClaimStreak.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.border_color));
btnClaimStreak.setTextColor(ContextCompat.getColor(getContext(), R.color.text_secondary));
}
llStreakRewardsContainer.addView(itemView);
}
}
private void claimStreakReward(StreakReward reward) {
if (currentUserData == null) return;
if (currentUserData.claimed_streak_rewards.contains(reward.id)) {
showToast("Já resgataste este prémio!");
return;
}
if (currentUserData.streak < reward.requiredStreak) {
showToast("Ainda não atingiste o streak necessário!");
return;
}
Map<String, Object> updates = new HashMap<>();
updates.put("claimed_streak_rewards", FieldValue.arrayUnion(reward.id));
updates.put("xp", FieldValue.increment(reward.xpReward));
updates.put("xp_hoje", FieldValue.increment(reward.xpReward));
updates.put("xp_semanal", FieldValue.increment(reward.xpReward));
// Prepare coins simulation
int bonusCoins = reward.requiredStreak * 3;
updates.put("coins", FieldValue.increment(bonusCoins));
FirestoreManager.getInstance().updateUserStats(currentUserData.id_usuario, updates, () -> {
FirestoreManager.getInstance().addXpLog(currentUserData.id_usuario, reward.xpReward, "streak_reward_" + reward.id);
showLevelUpCheck(reward.xpReward);
String rewardMsg = "+" + reward.xpReward + " XP e +" + bonusCoins + " 🪙";
if (reward.itemReward != null) {
rewardMsg += "\n✨ Desbloqueaste um Item Cosmético!";
}
showRewardDialog("🏆 Recompensa de Ofensiva", rewardMsg, reward.title);
});
}
// --- MYSTERY BOX ---
private void setupMysteryBox(Usuario user) {
if (getContext() == null) return;
String today = getTodayDateString();
boolean openedToday = today.equals(user.last_box_open_date);
if (openedToday) {
tvMysteryBoxIcon.setText("🔓");
tvMysteryBoxStatus.setText("Volta amanhã para abrir outra caixa!");
btnOpenMysteryBox.setEnabled(false);
btnOpenMysteryBox.setText("Concluído");
btnOpenMysteryBox.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), R.color.border_color));
btnOpenMysteryBox.setTextColor(ContextCompat.getColor(getContext(), R.color.text_secondary));
} else {
tvMysteryBoxIcon.setText("📦");
tvMysteryBoxStatus.setText("Abre uma caixa grátis hoje!");
btnOpenMysteryBox.setEnabled(true);
btnOpenMysteryBox.setText("Abrir");
btnOpenMysteryBox.setBackgroundTintList(ContextCompat.getColorStateList(getContext(), android.R.color.white));
btnOpenMysteryBox.setTextColor(Color.parseColor("#854D0E"));
btnOpenMysteryBox.setOnClickListener(v -> openMysteryBox());
}
}
private void openMysteryBox() {
if (currentUserData == null || getContext() == null) return;
String today = getTodayDateString();
if (today.equals(currentUserData.last_box_open_date)) {
showToast("Já abriste a caixa misteriosa hoje!");
return;
}
// Shaking animation
ObjectAnimator rotate = ObjectAnimator.ofFloat(tvMysteryBoxIcon, "rotation", 0f, 15f, -15f, 15f, -15f, 10f, -10f, 0f);
rotate.setDuration(1000);
rotate.setInterpolator(new CycleInterpolator(1.5f));
rotate.start();
btnOpenMysteryBox.setEnabled(false);
tvMysteryBoxIcon.postDelayed(() -> {
if (!isAdded()) return;
// Generate random reward
Random r = new Random();
int roll = r.nextInt(100);
int xpGained = 0;
int coinsGained = 0;
String prizeName = "";
if (roll < 40) { // 40% chance
xpGained = 50;
coinsGained = 5;
prizeName = "Caixa Comum: +50 XP e +5 Moedas 🪙";
} else if (roll < 70) { // 30% chance
xpGained = 100;
coinsGained = 10;
prizeName = "Caixa Incomum: +100 XP e +10 Moedas 🪙";
} else if (roll < 85) { // 15% chance
xpGained = 150;
coinsGained = 20;
prizeName = "Caixa Rara: +150 XP e +20 Moedas 🪙";
} else if (roll < 95) { // 10% chance
xpGained = 200;
coinsGained = 40;
prizeName = "Caixa Épica: +200 XP e +40 Moedas 🪙";
} else { // 5% chance
xpGained = 500;
coinsGained = 100;
prizeName = "🎁 Caixa Lendária: +500 XP e +100 Moedas 🪙!";
}
final int finalXp = xpGained;
final int finalCoins = coinsGained;
final String finalPrize = prizeName;
Map<String, Object> updates = new HashMap<>();
updates.put("last_box_open_date", today);
updates.put("xp", FieldValue.increment(finalXp));
updates.put("xp_hoje", FieldValue.increment(finalXp));
updates.put("xp_semanal", FieldValue.increment(finalXp));
updates.put("coins", FieldValue.increment(finalCoins));
FirestoreManager.getInstance().updateUserStats(currentUserData.id_usuario, updates, () -> {
FirestoreManager.getInstance().addXpLog(currentUserData.id_usuario, finalXp, "mystery_box_open");
showLevelUpCheck(finalXp);
tvMysteryBoxIcon.setText("🔓");
showRewardDialog("📦 Caixa Misteriosa", finalPrize, "Parabéns pela tua recompensa aleatória!");
});
}, 1200);
}
// --- HELPERS ---
private String getTodayDateString() {
return new SimpleDateFormat("yyyy-MM-dd", Locale.US).format(new Date());
}
private boolean isYesterday(String dateStr) {
if (dateStr == null || dateStr.isEmpty()) return false;
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
Date date = sdf.parse(dateStr);
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_YEAR, -1);
String yesterdayStr = sdf.format(cal.getTime());
return yesterdayStr.equals(dateStr);
} catch (Exception e) {
return false;
}
}
private void showToast(String message) {
if (getContext() != null) {
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
}
}
private void showRewardDialog(String title, String rewardText, String message) {
if (getContext() == null) return;
new MaterialAlertDialogBuilder(getContext())
.setTitle(title)
.setMessage(message + "\n\nGanhaste:\n" + rewardText)
.setPositiveButton("Incrível!", null)
.show();
}
private void showLevelUpCheck(int addedXp) {
if (currentUserData == null || !isAdded()) return;
int currentXp = currentUserData.xp;
int currentLevel = currentUserData.level;
int nextLevelThreshold = (currentLevel * (currentLevel + 1) / 2) * 100;
if (currentXp + addedXp >= nextLevelThreshold) {
// Trigger Level Up in database
Map<String, Object> updates = new HashMap<>();
updates.put("level", currentLevel + 1);
FirestoreManager.getInstance().addCoins(currentUserData.id_usuario, 50, "level_up");
FirestoreManager.getInstance().updateUserStats(currentUserData.id_usuario, updates, () -> {
if (getContext() != null) {
NotificationHelper.showNotification(getContext(), "🎉 SUBISTE DE NÍVEL!", "Chegaste ao nível " + (currentLevel + 1) + "! Continua assim.");
View dialogView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_level_up, null);
TextView tvNewLevel = dialogView.findViewById(R.id.tvNewLevel);
tvNewLevel.setText(String.valueOf(currentLevel + 1));
new MaterialAlertDialogBuilder(getContext())
.setView(dialogView)
.setPositiveButton("Incrível!", null)
.show();
}
});
}
}
private void setupStore(Usuario user) {
if (getContext() == null || llStoreContainer == null) return;
llStoreContainer.removeAllViews();
addStoreItem("mystery_box", "Caixa Misteriosa", "Ganha XP e moedas surpresa!", 60, "📦", user);
addStoreItem("boost_xp", "Boost de XP", "Dobra o XP ganho na próxima tarefa", 50, "🚀", user);
addStoreItem("streak_protect", "Proteção de Streak", "Salva a tua ofensiva se falhares um dia", 80, "🛡️", user);
addStoreItem("frame_avatar", "Moldura Épica", "Destaca o teu avatar no ranking", 100, "🖼️", user);
}
private void addStoreItem(String id, String title, String desc, int cost, String icon, Usuario user) {
View item = getLayoutInflater().inflate(R.layout.item_store, llStoreContainer, false);
android.widget.TextView tvStoreIcon = item.findViewById(R.id.tvStoreIcon);
android.widget.TextView tvStoreTitle = item.findViewById(R.id.tvStoreTitle);
android.widget.TextView tvStoreDesc = item.findViewById(R.id.tvStoreDesc);
android.widget.TextView tvStoreOwned = item.findViewById(R.id.tvStoreOwned);
android.widget.Button btnBuyStoreItem = item.findViewById(R.id.btnBuyStoreItem);
tvStoreIcon.setText(icon);
tvStoreTitle.setText(title);
tvStoreDesc.setText(desc);
btnBuyStoreItem.setText(cost + " 🪙");
boolean alreadyOwnedOneTime = false;
if (id.equals("frame_avatar")) {
if (user.unlockedItems != null && user.unlockedItems.contains(id)) {
tvStoreOwned.setVisibility(android.view.View.VISIBLE);
tvStoreOwned.setText("Possuído");
alreadyOwnedOneTime = true;
}
} else if (!id.equals("mystery_box")) {
int qty = (user.inventory != null && user.inventory.containsKey(id)) ? user.inventory.get(id) : 0;
if (qty > 0) {
tvStoreOwned.setVisibility(android.view.View.VISIBLE);
tvStoreOwned.setText("Possuis: " + qty);
}
}
Integer userCoins = user.coins;
if (userCoins == null) {
userCoins = 0;
}
if (alreadyOwnedOneTime) {
btnBuyStoreItem.setEnabled(false);
btnBuyStoreItem.setText("Comprado");
btnBuyStoreItem.setBackgroundTintList(androidx.core.content.ContextCompat.getColorStateList(getContext(), R.color.border_color));
} else if (userCoins < cost) {
btnBuyStoreItem.setEnabled(true);
btnBuyStoreItem.setOnClickListener(v -> {
showToast("Moedas insuficientes");
pulseAnimation(tvStatsCoins);
});
btnBuyStoreItem.setBackgroundTintList(androidx.core.content.ContextCompat.getColorStateList(getContext(), android.R.color.darker_gray));
} else {
btnBuyStoreItem.setOnClickListener(v -> buyStoreItem(id, title, cost, item));
}
llStoreContainer.addView(item);
}
private void buyStoreItem(String itemId, String itemName, int cost, View itemView) {
android.util.Log.d("FLUXUP_DEBUG", "SHOP_BUY_CLICKED");
if (getContext() == null) return;
if (itemId == null || itemName == null) {
android.util.Log.e("FLUXUP_DEBUG", "SHOP_ERROR: item null");
showToast("Erro: item inválido.");
return;
}
android.util.Log.d("FLUXUP_DEBUG", "SHOP_ITEM_ID: " + itemId);
android.util.Log.d("FLUXUP_DEBUG", "SHOP_PRICE: " + cost);
if (currentUserData == null || currentUserData.id_usuario == null) {
android.util.Log.e("FLUXUP_DEBUG", "SHOP_ERROR: userId null");
showToast("Erro: utilizador não encontrado.");
return;
}
android.util.Log.d("FLUXUP_DEBUG", "SHOP_USER_ID: " + currentUserData.id_usuario);
Integer currentCoins = currentUserData.coins;
if (currentCoins == null) {
currentCoins = 0;
}
android.util.Log.d("FLUXUP_DEBUG", "SHOP_COINS: " + currentCoins);
if (currentCoins < cost) {
android.util.Log.w("FLUXUP_DEBUG", "SHOP_ERROR: moedas insuficientes");
showToast("Moedas insuficientes");
return;
}
if (currentUserData.unlockedItems == null) {
currentUserData.unlockedItems = new java.util.ArrayList<>();
}
if (currentUserData.inventory == null) {
currentUserData.inventory = new java.util.HashMap<>();
}
try {
showGlowEffect(itemView);
java.util.Map<String, Object> updates = new java.util.HashMap<>();
if (itemId.equals("frame_avatar")) {
updates.put("unlockedItems", com.google.firebase.firestore.FieldValue.arrayUnion(itemId));
} else if (!itemId.equals("mystery_box")) {
int currentQty = currentUserData.inventory.containsKey(itemId) ? currentUserData.inventory.get(itemId) : 0;
updates.put("inventory." + itemId, currentQty + 1);
}
FirestoreManager.getInstance().updateUserStats(currentUserData.id_usuario, updates, () -> {
FirestoreManager.getInstance().addCoins(currentUserData.id_usuario, -cost, "store_purchase_" + itemId);
showConfetti();
if (itemId.equals("mystery_box")) {
openPaidMysteryBox();
} else {
showRewardDialog("🛍️ Compra Concluída", itemName, "Compraste " + itemName + " com sucesso!");
}
});
} catch (Exception e) {
android.util.Log.e("FLUXUP_DEBUG", "SHOP_ERROR: " + e.getMessage());
showToast("Erro ao processar compra.");
}
}
private void openPaidMysteryBox() {
if (getContext() == null || currentUserData == null) return;
int roll = new java.util.Random().nextInt(100);
int finalXp = 0;
int finalCoins = 0;
if (roll < 40) {
finalXp = 50;
finalCoins = 5;
} else if (roll < 75) {
finalXp = 100;
finalCoins = 15;
} else if (roll < 95) {
finalXp = 250;
finalCoins = 30;
} else {
finalXp = 500;
finalCoins = 100;
}
java.util.Map<String, Object> updates = new java.util.HashMap<>();
updates.put("xp", com.google.firebase.firestore.FieldValue.increment(finalXp));
updates.put("xp_hoje", com.google.firebase.firestore.FieldValue.increment(finalXp));
updates.put("xp_semanal", com.google.firebase.firestore.FieldValue.increment(finalXp));
int capturedXp = finalXp;
int capturedCoins = finalCoins;
FirestoreManager.getInstance().updateUserStats(currentUserData.id_usuario, updates, () -> {
FirestoreManager.getInstance().addXpLog(currentUserData.id_usuario, capturedXp, "paid_mystery_box");
if (capturedCoins > 0) {
FirestoreManager.getInstance().addCoins(currentUserData.id_usuario, capturedCoins, "paid_mystery_box_reward");
}
showLevelUpCheck(capturedXp);
showRewardDialog("🎁 Caixa Misteriosa (Paga)", "+" + capturedXp + " XP e +" + capturedCoins + " 🪙", "A caixa misteriosa revelou recompensas incríveis!");
});
}
// Animators
private void pulseAnimation(View view) {
if (view == null) return;
android.animation.ObjectAnimator scaleX = android.animation.ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.2f, 1f);
android.animation.ObjectAnimator scaleY = android.animation.ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.2f, 1f);
android.animation.AnimatorSet animatorSet = new android.animation.AnimatorSet();
animatorSet.setDuration(400);
animatorSet.playTogether(scaleX, scaleY);
animatorSet.start();
}
private void showGlowEffect(View view) {
if (view == null) return;
android.animation.ObjectAnimator alpha = android.animation.ObjectAnimator.ofFloat(view, "alpha", 1f, 0.5f, 1f);
alpha.setDuration(300);
pulseAnimation(view);
alpha.start();
}
private void showFloatingText(View anchor, String text) {
if (getContext() == null || anchor == null || anchor.getParent() == null) return;
android.view.ViewGroup parent = (android.view.ViewGroup) getView();
if (parent == null) return;
android.widget.TextView floatingText = new android.widget.TextView(getContext());
floatingText.setText(text);
floatingText.setTextColor(androidx.core.content.ContextCompat.getColor(getContext(), R.color.primary_purple));
floatingText.setTextSize(18f);
floatingText.setTypeface(null, android.graphics.Typeface.BOLD);
int[] location = new int[2];
anchor.getLocationInWindow(location);
int[] parentLocation = new int[2];
parent.getLocationInWindow(parentLocation);
float x = location[0] - parentLocation[0];
float y = location[1] - parentLocation[1] - 30;
floatingText.setX(x);
floatingText.setY(y);
parent.addView(floatingText);
floatingText.animate()
.translationYBy(-100f)
.alpha(0f)
.setDuration(1200)
.setInterpolator(new android.view.animation.DecelerateInterpolator())
.withEndAction(() -> parent.removeView(floatingText))
.start();
}
private void showConfetti() {
if (getContext() == null) return;
android.view.ViewGroup parent = (android.view.ViewGroup) getView();
if (parent == null) return;
String[] emojis = {"🎉", "", "🪙", "🌟"};
java.util.Random random = new java.util.Random();
int width = parent.getWidth();
if (width <= 0) width = 1000;
for (int i = 0; i < 20; i++) {
android.widget.TextView confetti = new android.widget.TextView(getContext());
confetti.setText(emojis[random.nextInt(emojis.length)]);
confetti.setTextSize(random.nextInt(16) + 16f);
float startX = random.nextInt(width);
confetti.setX(startX);
confetti.setY(-100f);
parent.addView(confetti);
confetti.animate()
.translationY(parent.getHeight() + 100f)
.translationX(startX + (random.nextBoolean() ? 150 : -150))
.rotation(random.nextInt(360))
.setDuration(1500 + random.nextInt(1000))
.setInterpolator(new android.view.animation.AccelerateInterpolator())
.withEndAction(() -> parent.removeView(confetti))
.start();
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (userListener != null) {
userListener.remove();
}
}
// Model structures
private static class DailyMission {
final String id;
final String title;
final String icon;
final int progress;
final int max;
final int xpReward;
DailyMission(String id, String title, String icon, int progress, int max, int xpReward) {
this.id = id;
this.title = title;
this.icon = icon;
this.progress = Math.min(progress, max);
this.max = max;
this.xpReward = xpReward;
}
}
private static class StreakReward {
final String id;
final int requiredStreak;
final String title;
final String rewardDetail;
final int xpReward;
final String itemReward;
final String icon;
StreakReward(String id, int requiredStreak, String title, String rewardDetail, int xpReward, String itemReward, String icon) {
this.id = id;
this.requiredStreak = requiredStreak;
this.title = title;
this.rewardDetail = rewardDetail;
this.xpReward = xpReward;
this.itemReward = itemReward;
this.icon = icon;
}
}
}

View File

@@ -1,58 +1,336 @@
package com.fluxup.app;
import android.graphics.Color;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.formatter.ValueFormatter;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class SearchFragment extends Fragment {
private RecyclerView recyclerView;
private EditText searchBar;
private ArrayAdapter<String> adapter;
private ArrayList<String> allItems;
private BarChart weeklyChart;
private TextView tvEmptyState;
private RecyclerView rvChallenges, rvFeaturedUsers;
private ChallengeAdapter challengeAdapter;
private FeaturedUserAdapter userAdapter;
private final FirestoreManager firestoreManager = FirestoreManager.getInstance();
private final String currentUserId = FirebaseAuth.getInstance().getUid();
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_search, container, false);
recyclerView = view.findViewById(R.id.recycler_view);
searchBar = view.findViewById(R.id.search_bar);
allItems = new ArrayList<>(Arrays.asList("Item 1", "Item 2", "Item 3", "Another Item", "More Items"));
adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, allItems);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(adapter);
searchBar.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
adapter.getFilter().filter(s);
}
@Override
public void afterTextChanged(Editable s) {
}
});
initViews(view);
setupCharts();
setupRecyclerViews();
loadData();
setupSearch();
return view;
}
private void initViews(View view) {
searchBar = view.findViewById(R.id.search_bar);
weeklyChart = view.findViewById(R.id.weeklyChart);
tvEmptyState = view.findViewById(R.id.tvEmptyState);
rvChallenges = view.findViewById(R.id.rvChallenges);
rvFeaturedUsers = view.findViewById(R.id.rvFeaturedUsers);
}
private void setupCharts() {
weeklyChart.getDescription().setEnabled(false);
weeklyChart.getLegend().setEnabled(false);
weeklyChart.setDrawGridBackground(false);
weeklyChart.setDrawBarShadow(false);
weeklyChart.setDrawValueAboveBar(true);
weeklyChart.setTouchEnabled(false);
XAxis xAxis = weeklyChart.getXAxis();
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
xAxis.setDrawGridLines(false);
xAxis.setGranularity(1f);
xAxis.setTextColor(Color.parseColor("#94A3B8"));
String[] days = {"SEG", "TER", "QUA", "QUI", "SEX", "SAB", "DOM"};
xAxis.setValueFormatter(new ValueFormatter() {
@Override
public String getFormattedValue(float value) {
int index = (int) value;
if (index >= 0 && index < days.length) return days[index];
return "";
}
});
weeklyChart.getAxisLeft().setDrawGridLines(true);
weeklyChart.getAxisLeft().setGridColor(Color.parseColor("#E2E8F0"));
weeklyChart.getAxisLeft().setTextColor(Color.parseColor("#94A3B8"));
weeklyChart.getAxisRight().setEnabled(false);
}
private void setupRecyclerViews() {
// Challenges
rvChallenges.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
challengeAdapter = new ChallengeAdapter(getMockChallenges());
rvChallenges.setAdapter(challengeAdapter);
// Featured Users
rvFeaturedUsers.setLayoutManager(new LinearLayoutManager(getContext()));
userAdapter = new FeaturedUserAdapter(new ArrayList<>());
rvFeaturedUsers.setAdapter(userAdapter);
}
private void setupSearch() {
searchBar.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
filterUsers(s.toString());
}
@Override
public void afterTextChanged(Editable s) {}
});
}
private void loadData() {
if (currentUserId == null) return;
// Load Weekly Progress
Calendar cal = Calendar.getInstance();
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
Date startDate = cal.getTime();
cal.add(Calendar.DAY_OF_WEEK, 6);
cal.set(Calendar.HOUR_OF_DAY, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
Date endDate = cal.getTime();
firestoreManager.getDailyProgress(currentUserId, startDate, endDate, 4, progressMap -> {
updateChart(progressMap);
});
// Load Featured Users (mocking top users from Firestore)
FirebaseFirestore.getInstance().collection("users")
.orderBy("xp", Query.Direction.DESCENDING)
.limit(10)
.get()
.addOnSuccessListener(snapshots -> {
List<Usuario> users = new ArrayList<>();
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots) {
Usuario u = doc.toObject(Usuario.class);
if (u != null && u.public_profile && !u.incognito) users.add(u);
}
userAdapter.updateList(users);
});
}
private void updateChart(Map<String, DailyProgress> progressMap) {
if (progressMap.isEmpty()) {
weeklyChart.setVisibility(View.GONE);
tvEmptyState.setVisibility(View.VISIBLE);
return;
}
weeklyChart.setVisibility(View.VISIBLE);
tvEmptyState.setVisibility(View.GONE);
List<BarEntry> entries = new ArrayList<>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
Calendar cal = Calendar.getInstance();
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
int todayIndex = Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - 2;
if (todayIndex < 0) todayIndex = 6; // Sunday fix
List<Integer> colors = new ArrayList<>();
for (int i = 0; i < 7; i++) {
String dateStr = sdf.format(cal.getTime());
DailyProgress dp = progressMap.get(dateStr);
int xp = (dp != null) ? dp.xp : 0;
entries.add(new BarEntry(i, xp));
if (i == todayIndex) {
colors.add(Color.parseColor("#7C3AED")); // Roxo principal destaque
} else {
colors.add(Color.parseColor("#DDD6FE")); // Roxo claro
}
cal.add(Calendar.DAY_OF_WEEK, 1);
}
BarDataSet dataSet = new BarDataSet(entries, "XP");
dataSet.setColors(colors);
dataSet.setDrawValues(true);
dataSet.setValueTextColor(Color.parseColor("#7C3AED"));
dataSet.setValueTextSize(10f);
BarData data = new BarData(dataSet);
data.setBarWidth(0.6f);
weeklyChart.setData(data);
weeklyChart.animateY(1000);
weeklyChart.invalidate();
}
private void filterUsers(String query) {
userAdapter.filter(query);
}
private List<Challenge> getMockChallenges() {
List<Challenge> list = new ArrayList<>();
list.add(new Challenge("🎯", "7 dias sem procrastinar", 500));
list.add(new Challenge("📚", "Ler 20 min por dia", 300));
list.add(new Challenge("⏱️", "30 min foco diário", 400));
return list;
}
// --- ADAPTERS ---
private static class Challenge {
String icon, name;
int xp;
Challenge(String icon, String name, int xp) {
this.icon = icon; this.name = name; this.xp = xp;
}
}
private class ChallengeAdapter extends RecyclerView.Adapter<ChallengeAdapter.ViewHolder> {
private final List<Challenge> items;
ChallengeAdapter(List<Challenge> items) { this.items = items; }
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_challenge, parent, false));
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Challenge item = items.get(position);
holder.icon.setText(item.icon);
holder.name.setText(item.name);
holder.xp.setText("+" + item.xp + " XP");
holder.btn.setOnClickListener(v -> Toast.makeText(getContext(), "Desafio iniciado!", Toast.LENGTH_SHORT).show());
}
@Override
public int getItemCount() { return items.size(); }
class ViewHolder extends RecyclerView.ViewHolder {
TextView icon, name, xp;
View btn;
ViewHolder(View v) {
super(v);
icon = v.findViewById(R.id.tvChallengeIcon);
name = v.findViewById(R.id.tvChallengeName);
xp = v.findViewById(R.id.tvChallengeXP);
btn = v.findViewById(R.id.btnStartChallenge);
}
}
}
private class FeaturedUserAdapter extends RecyclerView.Adapter<FeaturedUserAdapter.ViewHolder> {
private List<Usuario> items;
private List<Usuario> fullList;
FeaturedUserAdapter(List<Usuario> items) {
this.items = items;
this.fullList = new ArrayList<>(items);
}
void updateList(List<Usuario> newList) {
this.items = newList;
this.fullList = new ArrayList<>(newList);
notifyDataSetChanged();
}
void filter(String query) {
if (query.isEmpty()) {
items = new ArrayList<>(fullList);
} else {
items = new ArrayList<>();
for (Usuario u : fullList) {
if (u.usuario.toLowerCase().contains(query.toLowerCase())) {
items.add(u);
}
}
}
notifyDataSetChanged();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_featured_user, parent, false));
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Usuario user = items.get(position);
holder.name.setText(user.usuario);
holder.stats.setText("🔥 " + user.streak + " dias • " + user.league);
if (holder.avatar != null && user.avatar != null) {
holder.avatar.setAvatarData(user.avatar);
holder.avatar.setLeague(user.league);
}
holder.btnAdd.setOnClickListener(v -> Toast.makeText(getContext(), "Pedido enviado!", Toast.LENGTH_SHORT).show());
}
@Override
public int getItemCount() { return items.size(); }
class ViewHolder extends RecyclerView.ViewHolder {
AvatarView avatar;
TextView name, stats;
View btnAdd;
ViewHolder(View v) {
super(v);
avatar = v.findViewById(R.id.ivUserAvatar);
name = v.findViewById(R.id.tvUserName);
stats = v.findViewById(R.id.tvUserStats);
btnAdd = v.findViewById(R.id.btnAddFriend);
}
}
}
}

View File

@@ -6,164 +6,381 @@ import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageButton;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.switchmaterial.SwitchMaterial;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import java.util.Locale;
public class SettingsActivity extends AppCompatActivity {
private ImageButton btnBack;
private SwitchMaterial switchDarkMode, switchPrivacy, switchNotifications;
private TextView tvEmail, btnChangePassword;
private Spinner spinnerLanguage;
private View btnLogout;
private static final String TAG = "DARK_MODE";
private SharedPreferences sharedPreferences;
private static final String PREFS_NAME = "FluxupSettings";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPreferences = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
// Aplicar o idioma guardado antes de carregar o layout
String lang = sharedPreferences.getString("language", "pt");
updateLocaleSilent(lang);
// Aplicar tema guardado
applyTheme(sharedPreferences.getBoolean("darkMode", false));
// Aplicar o modo escuro antes de criar a activity
boolean isDarkMode = sharedPreferences.getBoolean("dark_mode_enabled", false);
Log.d(TAG, "APP_THEME_IS_DAYNIGHT | dark_mode_enabled=" + isDarkMode);
AppCompatDelegate.setDefaultNightMode(isDarkMode ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
Log.d(TAG, "DARK_MODE_APPLIED | mode=" + (isDarkMode ? "MODE_NIGHT_YES" : "MODE_NIGHT_NO"));
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
initViews();
setupListeners();
loadSettings();
initSettings();
findViewById(R.id.btnBack).setOnClickListener(v -> finish());
}
private void applyTheme(boolean isDarkMode) {
if (isDarkMode) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
}
private void initViews() {
btnBack = findViewById(R.id.btnBack);
switchDarkMode = findViewById(R.id.switchDarkMode);
switchPrivacy = findViewById(R.id.switchPrivacy);
switchNotifications = findViewById(R.id.switchNotifications);
tvEmail = findViewById(R.id.tvEmail);
btnChangePassword = findViewById(R.id.btnChangePassword);
spinnerLanguage = findViewById(R.id.spinnerLanguage);
btnLogout = findViewById(R.id.btnLogout);
// Configurar Spinner de Idiomas
String[] languages = {"Português", "English", "Español"};
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, languages);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerLanguage.setAdapter(adapter);
// Mostrar email do utilizador
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null) {
tvEmail.setText(user.getEmail());
}
}
private void setupListeners() {
btnBack.setOnClickListener(v -> finish());
switchDarkMode.setOnCheckedChangeListener((buttonView, isChecked) -> {
applyTheme(isChecked);
saveSetting("darkMode", isChecked);
});
switchPrivacy.setOnCheckedChangeListener((buttonView, isChecked) -> saveSetting("privacy", isChecked));
switchNotifications.setOnCheckedChangeListener((buttonView, isChecked) -> saveSetting("notifications", isChecked));
btnLogout.setOnClickListener(v -> {
FirebaseAuth.getInstance().signOut();
Intent intent = new Intent(SettingsActivity.this, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
});
spinnerLanguage.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String selectedLang = "pt";
if (position == 1) selectedLang = "en";
else if (position == 2) selectedLang = "es";
String currentLang = sharedPreferences.getString("language", "pt");
if (!selectedLang.equals(currentLang)) {
saveSetting("language", selectedLang);
updateLocale(selectedLang);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {}
});
btnChangePassword.setOnClickListener(v -> {
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null && user.getEmail() != null) {
FirebaseAuth.getInstance().sendPasswordResetEmail(user.getEmail())
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
Toast.makeText(this, "Email de redefinição enviado!", Toast.LENGTH_SHORT).show();
}
});
private void initSettings() {
// --- CONTA ---
setupClickable(R.id.settingEditProfile, "Editar Perfil", "Nome, Bio, Avatar", v -> showEditProfileDialog());
setupClickable(R.id.settingEmail, "Email", FirebaseAuth.getInstance().getCurrentUser().getEmail(), v -> showChangeEmailDialog());
setupClickable(R.id.settingPassword, "Alterar Palavra-passe", "********", v -> resetPassword());
setupSwitch(R.id.settingPublicProfile, "Perfil Público", "public_profile", true, (v, isChecked) -> {
String uid = FirebaseAuth.getInstance().getUid();
if (uid != null) {
java.util.Map<String, Object> updates = new java.util.HashMap<>();
updates.put("public_profile", isChecked);
FirestoreManager.getInstance().updateUserStats(uid, updates);
}
});
}
private void loadSettings() {
switchDarkMode.setChecked(sharedPreferences.getBoolean("darkMode", false));
switchPrivacy.setChecked(sharedPreferences.getBoolean("privacy", false));
switchNotifications.setChecked(sharedPreferences.getBoolean("notifications", true));
// --- APARÊNCIA ---
setupSwitch(R.id.settingDarkMode, "Modo Escuro", "dark_mode_enabled", false, (v, isChecked) -> {
Log.d(TAG, "DARK_MODE_SWITCH_CHANGED | isChecked=" + isChecked);
// Guardar valor (feito pelo setupSwitch genérico, mas confirmamos aqui)
sharedPreferences.edit().putBoolean("dark_mode_enabled", isChecked).apply();
boolean saved = sharedPreferences.getBoolean("dark_mode_enabled", false);
Log.d(TAG, "DARK_MODE_SAVED_VALUE | saved=" + saved);
// Aplicar o modo globalmente
AppCompatDelegate.setDefaultNightMode(
isChecked ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO
);
Log.d(TAG, "DARK_MODE_APPLIED | mode=" + (isChecked ? "MODE_NIGHT_YES" : "MODE_NIGHT_NO"));
// Recriar a Activity para aplicar o novo tema imediatamente
recreate();
});
String lang = sharedPreferences.getString("language", "pt");
if (lang.equals("en")) spinnerLanguage.setSelection(1, false);
else if (lang.equals("es")) spinnerLanguage.setSelection(2, false);
else spinnerLanguage.setSelection(0, false);
String themeColorValue = sharedPreferences.getString("theme_color", "purple");
setupClickable(R.id.settingThemeColor, "Cor Principal da App", getColorLabel(themeColorValue), v -> showThemeColorDialog());
// --- NOTIFICAÇÕES ---
setupSwitch(R.id.settingDailyReminders, "Lembretes Diários", "daily_reminders", true);
setupSwitch(R.id.settingAntiProcrastination, "Anti-Procrastinação", "anti_procrastination", true);
// --- FOCO ---
setupClickable(R.id.settingFocusDuration, "Duração do Foco", sharedPreferences.getInt("focus_duration", 25) + " min", v -> showDurationDialog("focus_duration", "Duração do Foco", 25));
setupClickable(R.id.settingBreakDuration, "Duração da Pausa", sharedPreferences.getInt("break_duration", 5) + " min", v -> showDurationDialog("break_duration", "Duração da Pausa", 5));
setupSwitch(R.id.settingVibration, "Vibração ao Terminar", "vibration_on_finish", true);
// --- PRIVACIDADE ---
setupSwitch(R.id.settingIncognito, "Modo Incógnito", "incognito", false, (v, isChecked) -> {
String uid = FirebaseAuth.getInstance().getUid();
if (uid != null) {
java.util.Map<String, Object> updates = new java.util.HashMap<>();
updates.put("incognito", isChecked);
FirestoreManager.getInstance().updateUserStats(uid, updates);
}
});
setupClickable(R.id.settingExportData, "Exportar Dados", "JSON/CSV", v -> exportUserData());
findViewById(R.id.btnDeleteAccount).setOnClickListener(v -> {
new AlertDialog.Builder(this)
.setTitle("Eliminar Conta")
.setMessage("Tens a certeza? Todos os teus dados serão apagados permanentemente.")
.setNegativeButton("Cancelar", null)
.setPositiveButton("Sim, Eliminar", (dialog, which) -> {
new AlertDialog.Builder(this)
.setTitle("Confirmação Final")
.setMessage("Esta ação é irreversível e apagará TODO o teu histórico e progresso. Tens a certeza absoluta?")
.setNegativeButton("Cancelar", null)
.setPositiveButton("Eliminar Definitivamente", (dialog2, which2) -> deleteAccount())
.show();
})
.show();
});
// Sync initial values from Firestore
String uid = FirebaseAuth.getInstance().getUid();
if (uid != null) {
FirestoreManager.getInstance().getUser(uid, user -> {
if (user != null) {
sharedPreferences.edit()
.putBoolean("public_profile", user.public_profile)
.putBoolean("incognito", user.incognito)
.putInt("focus_duration", user.meta_diaria_foco)
.apply();
updateClickableValue(R.id.settingFocusDuration, user.meta_diaria_foco + " min");
SwitchMaterial swPublic = findViewById(R.id.settingPublicProfile).findViewById(R.id.switchSetting);
if (swPublic != null) swPublic.setChecked(user.public_profile);
SwitchMaterial swIncognito = findViewById(R.id.settingIncognito).findViewById(R.id.switchSetting);
if (swIncognito != null) swIncognito.setChecked(user.incognito);
}
});
}
}
private void updateLocaleSilent(String langCode) {
Locale locale = new Locale(langCode);
Locale.setDefault(locale);
Resources resources = getResources();
Configuration config = resources.getConfiguration();
config.setLocale(locale);
resources.updateConfiguration(config, resources.getDisplayMetrics());
private void updateClickableValue(int id, String value) {
View view = findViewById(id);
if (view != null) {
TextView tvValue = view.findViewById(R.id.tvSettingValue);
if (tvValue != null) tvValue.setText(value);
}
}
private void updateLocale(String langCode) {
updateLocaleSilent(langCode);
Intent intent = new Intent(this, SettingsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
private String getColorLabel(String key) {
switch (key) {
case "blue": return "Azul";
case "green": return "Verde";
case "orange": return "Laranja";
case "red": return "Vermelho";
case "purple":
default:
return "Roxo (Padrão)";
}
}
private void saveSetting(String key, boolean value) {
sharedPreferences.edit().putBoolean(key, value).apply();
private void showThemeColorDialog() {
String[] colors = {"Roxo (Padrão)", "Azul", "Verde", "Laranja", "Vermelho"};
String[] keys = {"purple", "blue", "green", "orange", "red"};
String currentColor = sharedPreferences.getString("theme_color", "purple");
int checkedItem = 0;
for (int i = 0; i < keys.length; i++) {
if (keys[i].equals(currentColor)) {
checkedItem = i;
break;
}
}
new AlertDialog.Builder(this)
.setTitle("Escolher Cor Principal")
.setSingleChoiceItems(colors, checkedItem, (dialog, which) -> {
String selectedKey = keys[which];
sharedPreferences.edit().putString("theme_color", selectedKey).apply();
updateClickableValue(R.id.settingThemeColor, colors[which]);
Toast.makeText(this, "Cor do tema atualizada!", Toast.LENGTH_SHORT).show();
dialog.dismiss();
})
.setNegativeButton("Cancelar", null)
.show();
}
private void saveSetting(String key, String value) {
sharedPreferences.edit().putString(key, value).apply();
private void exportUserData() {
String uid = FirebaseAuth.getInstance().getUid();
if (uid == null) {
Toast.makeText(this, "Utilizador não autenticado", Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(this, "A exportar dados...", Toast.LENGTH_SHORT).show();
FirestoreManager.getInstance().getUser(uid, user -> {
if (user == null) {
Toast.makeText(this, "Erro ao obter dados do utilizador", Toast.LENGTH_SHORT).show();
return;
}
com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("tasks")
.whereEqualTo("userId", uid)
.get()
.addOnSuccessListener(queryDocumentSnapshots -> {
try {
org.json.JSONObject exportObj = new org.json.JSONObject();
org.json.JSONObject profileObj = new org.json.JSONObject();
profileObj.put("usuario", user.usuario);
profileObj.put("email", user.email);
profileObj.put("level", user.level);
profileObj.put("xp", user.xp);
profileObj.put("streak", user.streak);
profileObj.put("melhor_streak", user.melhor_streak);
profileObj.put("total_tasks_concluidas", user.total_tasks_concluidas);
profileObj.put("tempo_foco_total_minutos", user.tempo_foco_total);
exportObj.put("perfil", profileObj);
org.json.JSONArray tasksArray = new org.json.JSONArray();
for (com.google.firebase.firestore.DocumentSnapshot doc : queryDocumentSnapshots.getDocuments()) {
org.json.JSONObject taskObj = new org.json.JSONObject();
taskObj.put("id", doc.getId());
taskObj.put("title", doc.getString("title"));
taskObj.put("duration", doc.getLong("duration"));
taskObj.put("completed", doc.getBoolean("completed"));
Long completedDate = doc.getLong("completedDate");
taskObj.put("completedDate", completedDate != null ? completedDate : 0);
tasksArray.put(taskObj);
}
exportObj.put("tarefas", tasksArray);
String jsonString = exportObj.toString(4);
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, jsonString);
sendIntent.setType("text/plain");
Intent shareIntent = Intent.createChooser(sendIntent, "Exportar Dados Fluxup");
startActivity(shareIntent);
} catch (Exception e) {
Toast.makeText(SettingsActivity.this, "Erro ao exportar: " + e.getMessage(), Toast.LENGTH_LONG).show();
}
})
.addOnFailureListener(e -> {
Toast.makeText(SettingsActivity.this, "Erro ao obter tarefas: " + e.getMessage(), Toast.LENGTH_LONG).show();
});
});
}
private void setupClickable(int id, String title, String value, View.OnClickListener listener) {
View view = findViewById(id);
if (view == null) return;
((TextView) view.findViewById(R.id.tvSettingTitle)).setText(title);
((TextView) view.findViewById(R.id.tvSettingValue)).setText(value);
if (listener != null) view.setOnClickListener(listener);
else view.findViewById(R.id.ivChevron).setVisibility(View.GONE);
}
private void setupSwitch(int id, String title, String key, boolean defValue) {
setupSwitch(id, title, key, defValue, null);
}
private void setupSwitch(int id, String title, String key, boolean defValue, android.widget.CompoundButton.OnCheckedChangeListener extraListener) {
View view = findViewById(id);
if (view == null) return;
((TextView) view.findViewById(R.id.tvSettingTitle)).setText(title);
SwitchMaterial sw = view.findViewById(R.id.switchSetting);
sw.setChecked(sharedPreferences.getBoolean(key, defValue));
sw.setOnCheckedChangeListener((v, isChecked) -> {
sharedPreferences.edit().putBoolean(key, isChecked).apply();
if (extraListener != null) extraListener.onCheckedChanged(v, isChecked);
});
}
private void resetPassword() {
String email = FirebaseAuth.getInstance().getCurrentUser().getEmail();
if (email != null) {
FirebaseAuth.getInstance().sendPasswordResetEmail(email)
.addOnSuccessListener(aVoid -> Toast.makeText(this, "Email enviado!", Toast.LENGTH_SHORT).show());
}
}
private void showEditProfileDialog() {
View view = getLayoutInflater().inflate(R.layout.dialog_edit_profile, null);
EditText etName = view.findViewById(R.id.etEditName);
EditText etBio = view.findViewById(R.id.etEditBio);
String uid = FirebaseAuth.getInstance().getUid();
FirestoreManager.getInstance().getUser(uid, user -> {
etName.setText(user.usuario);
etBio.setText(user.bio);
});
new AlertDialog.Builder(this)
.setTitle("Editar Perfil")
.setView(view)
.setPositiveButton("Guardar", (dialog, which) -> {
String name = etName.getText().toString();
String bio = etBio.getText().toString();
java.util.Map<String, Object> updates = new java.util.HashMap<>();
updates.put("usuario", name);
updates.put("bio", bio);
FirestoreManager.getInstance().updateUserStats(uid, updates);
Toast.makeText(this, "Perfil atualizado!", Toast.LENGTH_SHORT).show();
})
.setNegativeButton("Cancelar", null)
.show();
}
private void showChangeEmailDialog() {
EditText input = new EditText(this);
input.setHint("Novo email");
new AlertDialog.Builder(this)
.setTitle("Alterar Email")
.setView(input)
.setPositiveButton("Confirmar", (dialog, which) -> {
String newEmail = input.getText().toString();
FirebaseAuth.getInstance().getCurrentUser().updateEmail(newEmail)
.addOnSuccessListener(aVoid -> Toast.makeText(this, "Email alterado!", Toast.LENGTH_SHORT).show())
.addOnFailureListener(e -> Toast.makeText(this, "Erro: " + e.getMessage(), Toast.LENGTH_LONG).show());
})
.setNegativeButton("Cancelar", null)
.show();
}
private void deleteAccount() {
String uid = FirebaseAuth.getInstance().getUid();
com.google.firebase.firestore.FirebaseFirestore.getInstance().collection("users").document(uid).delete()
.addOnSuccessListener(aVoid -> {
FirebaseAuth.getInstance().getCurrentUser().delete()
.addOnSuccessListener(aVoid2 -> {
Intent intent = new Intent(this, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
})
.addOnFailureListener(e -> {
Toast.makeText(this, "Erro ao eliminar autenticação: " + e.getMessage(), Toast.LENGTH_LONG).show();
});
})
.addOnFailureListener(e -> {
Toast.makeText(this, "Erro ao eliminar dados da conta: " + e.getMessage(), Toast.LENGTH_LONG).show();
});
}
private void showDurationDialog(String key, String title, int current) {
View dialogView = getLayoutInflater().inflate(R.layout.dialog_duration_picker, null);
TextView tvTitle = dialogView.findViewById(R.id.tvDurationTitle);
tvTitle.setText(title);
com.google.android.material.textfield.TextInputLayout til = dialogView.findViewById(R.id.tilDuration);
til.setHint("Minutos");
com.google.android.material.textfield.TextInputEditText etDuration = dialogView.findViewById(R.id.etDuration);
etDuration.setText(String.valueOf(sharedPreferences.getInt(key, current)));
new AlertDialog.Builder(this)
.setView(dialogView)
.setPositiveButton("Guardar", (dialog, which) -> {
try {
String valStr = etDuration.getText().toString();
if (valStr.isEmpty()) return;
int val = Integer.parseInt(valStr);
if (val <= 0) {
Toast.makeText(this, "A duração deve ser superior a zero", Toast.LENGTH_SHORT).show();
return;
}
sharedPreferences.edit().putInt(key, val).apply();
updateClickableValue(key.equals("focus_duration") ? R.id.settingFocusDuration : R.id.settingBreakDuration, val + " min");
String uid = FirebaseAuth.getInstance().getUid();
if (uid != null) {
java.util.Map<String, Object> updates = new java.util.HashMap<>();
if (key.equals("focus_duration")) updates.put("meta_diaria_foco", val);
FirestoreManager.getInstance().updateUserStats(uid, updates);
}
Toast.makeText(this, "Duração atualizada!", Toast.LENGTH_SHORT).show();
} catch (Exception e) {}
})
.setNegativeButton("Cancelar", null)
.show();
}
}

View File

@@ -0,0 +1,120 @@
package com.fluxup.app;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.formatter.ValueFormatter;
import java.util.ArrayList;
import java.util.List;
public class StatisticsActivity extends AppCompatActivity {
private LineChart xpChart;
private BarChart focusChart;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_statistics);
xpChart = findViewById(R.id.xpChart);
focusChart = findViewById(R.id.focusChart);
findViewById(R.id.btnBack).setOnClickListener(v -> finish());
setupXPChart();
setupFocusChart();
loadStats();
}
private void setupXPChart() {
List<Entry> entries = new ArrayList<>();
entries.add(new Entry(0, 100));
entries.add(new Entry(1, 150));
entries.add(new Entry(2, 130));
entries.add(new Entry(3, 200));
entries.add(new Entry(4, 250));
entries.add(new Entry(5, 220));
entries.add(new Entry(6, 300));
LineDataSet dataSet = new LineDataSet(entries, "XP Diário");
dataSet.setColor(Color.parseColor("#7C3AED"));
dataSet.setCircleColor(Color.parseColor("#7C3AED"));
dataSet.setLineWidth(3f);
dataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER);
dataSet.setDrawFilled(true);
dataSet.setFillColor(Color.parseColor("#DDD6FE"));
dataSet.setValueTextSize(10f);
LineData lineData = new LineData(dataSet);
xpChart.setData(lineData);
xpChart.getDescription().setEnabled(false);
xpChart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM);
xpChart.getXAxis().setDrawGridLines(false);
xpChart.getAxisRight().setEnabled(false);
xpChart.animateX(1000);
xpChart.invalidate();
}
private void setupFocusChart() {
List<BarEntry> entries = new ArrayList<>();
entries.add(new BarEntry(0, 45));
entries.add(new BarEntry(1, 60));
entries.add(new BarEntry(2, 30));
entries.add(new BarEntry(3, 90));
entries.add(new BarEntry(4, 120));
entries.add(new BarEntry(5, 45));
entries.add(new BarEntry(6, 75));
BarDataSet dataSet = new BarDataSet(entries, "Minutos de Foco");
dataSet.setColor(Color.parseColor("#3B82F6"));
dataSet.setValueTextSize(10f);
BarData barData = new BarData(dataSet);
focusChart.setData(barData);
focusChart.getDescription().setEnabled(false);
focusChart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM);
focusChart.getXAxis().setDrawGridLines(false);
focusChart.getAxisRight().setEnabled(false);
focusChart.animateY(1000);
focusChart.invalidate();
String[] days = {"Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom"};
focusChart.getXAxis().setValueFormatter(new ValueFormatter() {
@Override
public String getFormattedValue(float value) {
int index = (int) value;
if (index >= 0 && index < days.length) {
return days[index];
}
return "";
}
});
}
private void loadStats() {
updateStatCard(findViewById(R.id.statAvgFocus), "Média Foco", "62m");
updateStatCard(findViewById(R.id.statTasksPerDay), "Tarefas/Dia", "4.2");
updateStatCard(findViewById(R.id.statBestDay), "Melhor Dia", "Sex");
updateStatCard(findViewById(R.id.statTotalSessions), "Total Sessões", "28");
}
private void updateStatCard(View card, String label, String value) {
if (card == null) return;
TextView tvLabel = card.findViewById(R.id.tvStatLabel);
TextView tvValue = card.findViewById(R.id.tvStatValue);
if (tvLabel != null) tvLabel.setText(label);
if (tvValue != null) tvValue.setText(value);
}
}

View File

@@ -1,5 +1,7 @@
package com.fluxup.app;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -11,8 +13,15 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.tabs.TabLayout;
import com.google.firebase.auth.FirebaseUser;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
public class StreakActivity extends AppCompatActivity {
@@ -20,6 +29,16 @@ public class StreakActivity extends AppCompatActivity {
private TextView tvStreakCount;
private ImageButton btnClose;
private TabLayout tabLayout;
private TextView tvMonthName;
private ImageButton btnPrevMonth;
private ImageButton btnNextMonth;
private TextView tvDaysOfPractice;
private TextView tvFocusSessions;
private Calendar currentCalendar;
private String currentUserId;
private int currentDailyGoal = 4;
private Usuario currentUserObj;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -30,43 +49,207 @@ public class StreakActivity extends AppCompatActivity {
tvStreakCount = findViewById(R.id.tvStreakCount);
btnClose = findViewById(R.id.btnClose);
tabLayout = findViewById(R.id.tabLayout);
tvMonthName = findViewById(R.id.tvMonthName);
btnPrevMonth = findViewById(R.id.btnPrevMonth);
btnNextMonth = findViewById(R.id.btnNextMonth);
tvDaysOfPractice = findViewById(R.id.tvDaysOfPractice);
tvFocusSessions = findViewById(R.id.tvFocusSessions);
btnClose.setOnClickListener(v -> finish());
setupCalendar();
currentCalendar = Calendar.getInstance();
FirebaseUser user = AuthManager.getInstance().getCurrentUser();
if (user != null) {
currentUserId = user.getUid();
loadUserData();
}
btnPrevMonth.setOnClickListener(v -> {
currentCalendar.add(Calendar.MONTH, -1);
loadMonthData();
});
btnNextMonth.setOnClickListener(v -> {
currentCalendar.add(Calendar.MONTH, 1);
loadMonthData();
});
setupTabs();
}
private void setupTabs() {
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
// Future: toggle between personal and friends stats
}
public void onTabSelected(TabLayout.Tab tab) {}
@Override
public void onTabUnselected(TabLayout.Tab tab) {}
@Override
public void onTabReselected(TabLayout.Tab tab) {}
});
}
private void setupCalendar() {
private void loadUserData() {
FirestoreManager.getInstance().getUser(currentUserId, user -> {
if (user != null && !isDestroyed()) {
currentUserObj = user;
tvStreakCount.setText(user.streak + " dias de ofensiva!");
currentDailyGoal = user.meta_diaria_tarefas > 0 ? user.meta_diaria_tarefas : 4;
loadMonthData();
}
});
}
private void loadMonthData() {
int year = currentCalendar.get(Calendar.YEAR);
int month = currentCalendar.get(Calendar.MONTH);
String monthName = currentCalendar.getDisplayName(Calendar.MONTH, Calendar.LONG, new Locale("pt", "PT"));
if (monthName != null && monthName.length() > 0) {
tvMonthName.setText(monthName.substring(0, 1).toUpperCase() + monthName.substring(1) + " de " + year);
}
Calendar startCal = Calendar.getInstance();
startCal.set(year, month, 1, 0, 0, 0);
startCal.set(Calendar.MILLISECOND, 0);
java.util.Date startDate = startCal.getTime();
Calendar endCal = (Calendar) startCal.clone();
endCal.add(Calendar.MONTH, 1);
java.util.Date endDate = endCal.getTime();
android.util.Log.d("FLUXUP_DEBUG", "STREAK_VALUE_FROM_HOME_SOURCE: " + (currentUserObj != null ? currentUserObj.streak : "null"));
android.util.Log.d("FLUXUP_DEBUG", "OFFENSIVA_USER_ID: " + currentUserId);
android.util.Log.d("FLUXUP_DEBUG", "LOAD_MONTHLY_PROGRESS_START");
String startDateStr = String.format(Locale.US, "%04d-%02d-%02d", year, month + 1, 1);
Calendar endMonthCal = (Calendar) startCal.clone();
endMonthCal.set(Calendar.DAY_OF_MONTH, endMonthCal.getActualMaximum(Calendar.DAY_OF_MONTH));
String endDateStr = String.format(Locale.US, "%04d-%02d-%02d", year, month + 1, endMonthCal.getActualMaximum(Calendar.DAY_OF_MONTH));
android.util.Log.d("FLUXUP_DEBUG", "MONTH_START: " + startDateStr);
android.util.Log.d("FLUXUP_DEBUG", "MONTH_END: " + endDateStr);
FirestoreManager.getInstance().getMonthlyDailyProgress(currentUserId, startDateStr, endDateStr, progressList -> {
if (isDestroyed()) return;
android.util.Log.d("FLUXUP_DEBUG", "COMPLETED_DAYS_FROM_FIREBASE: " + progressList.size());
int focusSessionsCount = 0;
Map<Integer, DailyProgress> dailyProgressMap = new HashMap<>();
Set<String> completedDatesThisMonth = new java.util.HashSet<>();
for (DailyProgress dp : progressList) {
focusSessionsCount += dp.focusSessions;
completedDatesThisMonth.add(dp.date);
String[] parts = dp.date.split("-");
if (parts.length == 3) {
int d = Integer.parseInt(parts[2]);
dailyProgressMap.put(d, dp);
}
}
// MERGE com dias_concluidos do objeto user para retrocompatibilidade/consistência
if (currentUserObj != null && currentUserObj.dias_concluidos != null) {
String monthPrefix = String.format(Locale.US, "%04d-%02d-", year, month + 1);
for (String date : currentUserObj.dias_concluidos) {
if (date.startsWith(monthPrefix)) {
completedDatesThisMonth.add(date);
}
}
}
android.util.Log.d("FLUXUP_DEBUG", "COMPLETED_DAYS_COUNT: " + completedDatesThisMonth.size());
// Aplicar o fallback seguro para HOJE usando o tasks_concluidas_hoje do utilizador
Calendar todayCal = Calendar.getInstance();
if (todayCal.get(Calendar.YEAR) == year && todayCal.get(Calendar.MONTH) == month) {
int todayDay = todayCal.get(Calendar.DAY_OF_MONTH);
DailyProgress dpToday = dailyProgressMap.get(todayDay);
if (dpToday == null) {
dpToday = new DailyProgress(String.format(Locale.US, "%04d-%02d-%02d", year, month + 1, todayDay), currentDailyGoal);
dailyProgressMap.put(todayDay, dpToday);
}
int userObjectToday = (currentUserObj != null) ? currentUserObj.tasks_concluidas_hoje : 0;
dpToday.completedTasks = Math.max(dpToday.completedTasks, userObjectToday);
dpToday.updateStatus();
if (dpToday.isCompleted) {
completedDatesThisMonth.add(dpToday.date);
}
}
// Mostrar total de dias concluídos (Histórico Real do Mês)
int diasCumpridos = completedDatesThisMonth.size();
android.util.Log.d("FLUXUP_DEBUG", "DIAS_CUMPRIDOS_VALUE: " + diasCumpridos);
tvDaysOfPractice.setText(String.valueOf(diasCumpridos));
tvFocusSessions.setText(String.valueOf(focusSessionsCount));
updateCalendar(dailyProgressMap);
});
}
private void updateCalendar(Map<Integer, DailyProgress> dailyProgressMap) {
rvCalendar.setLayoutManager(new GridLayoutManager(this, 7));
List<CalendarDay> days = new ArrayList<>();
// Simulating April 2026 (Starts on a Wednesday)
// Add empty slots for Mon, Tue
days.add(new CalendarDay(0, false, false)); // Mon
days.add(new CalendarDay(0, false, false)); // Tue
Calendar calcCal = (Calendar) currentCalendar.clone();
calcCal.set(Calendar.DAY_OF_MONTH, 1);
int maxDays = calcCal.getActualMaximum(Calendar.DAY_OF_MONTH);
// Days 1 to 30
for (int i = 1; i <= 30; i++) {
boolean isActive = (i >= 1 && i <= 7); // 7-day streak for demo
boolean isCurrent = (i == 7);
days.add(new CalendarDay(i, isActive, isCurrent));
int startDayOfWeek = calcCal.get(Calendar.DAY_OF_WEEK);
int emptySlots = startDayOfWeek - 2;
if (emptySlots < 0) emptySlots = 6;
for (int i = 0; i < emptySlots; i++) {
days.add(new CalendarDay(0, 0, false, false, "empty"));
}
Calendar todayCal = Calendar.getInstance();
int todayYear = todayCal.get(Calendar.YEAR);
int todayMonth = todayCal.get(Calendar.MONTH);
int todayDay = todayCal.get(Calendar.DAY_OF_MONTH);
int calYear = currentCalendar.get(Calendar.YEAR);
int calMonth = currentCalendar.get(Calendar.MONTH);
boolean isCurrentMonth = (todayYear == calYear && todayMonth == calMonth);
for (int i = 1; i <= maxDays; i++) {
boolean isCurrent = (isCurrentMonth && i == todayDay);
boolean isFuture = false;
if (calYear > todayYear) {
isFuture = true;
} else if (calYear == todayYear) {
if (calMonth > todayMonth) {
isFuture = true;
} else if (calMonth == todayMonth) {
if (i > todayDay) isFuture = true;
}
}
// Regra: Histórico Persistente
DailyProgress dp = dailyProgressMap.get(i);
String dateKey = String.format(Locale.US, "%04d-%02d-%02d", calYear, calMonth + 1, i);
String status = "empty";
if (currentUserObj != null && currentUserObj.dias_concluidos != null && currentUserObj.dias_concluidos.contains(dateKey)) {
status = "complete";
} else if (dp != null) {
dp.updateStatus();
status = dp.status;
}
if (i == 11 && isCurrentMonth) {
android.util.Log.d("FLUXUP_DEBUG", "DAY_11_STATUS: " + status);
}
if (i == 12 && isCurrentMonth) {
android.util.Log.d("FLUXUP_DEBUG", "DAY_12_STATUS: " + status);
}
days.add(new CalendarDay(i, dp != null ? dp.completedTasks : 0, isCurrent, isFuture, status));
}
CalendarAdapter adapter = new CalendarAdapter(days);
@@ -77,13 +260,17 @@ public class StreakActivity extends AppCompatActivity {
private static class CalendarDay {
int dayNumber;
boolean isActive;
int completedTasks;
boolean isCurrent;
boolean isFuture;
String status;
CalendarDay(int dayNumber, boolean isActive, boolean isCurrent) {
CalendarDay(int dayNumber, int completedTasks, boolean isCurrent, boolean isFuture, String status) {
this.dayNumber = dayNumber;
this.isActive = isActive;
this.completedTasks = completedTasks;
this.isCurrent = isCurrent;
this.isFuture = isFuture;
this.status = status;
}
}
@@ -109,25 +296,43 @@ public class StreakActivity extends AppCompatActivity {
holder.tvDayNumber.setText("");
holder.dayBackground.setVisibility(View.GONE);
holder.streakConnector.setVisibility(View.GONE);
holder.dayIndicator.setVisibility(View.GONE);
if (holder.tvEmoji != null) holder.tvEmoji.setVisibility(View.GONE);
} else {
holder.tvDayNumber.setText(String.valueOf(day.dayNumber));
if (holder.tvEmoji != null) holder.tvEmoji.setVisibility(View.GONE);
if (day.isActive) {
if (day.isFuture) {
// DIAS FUTUROS: CINZENTO CLARO
holder.dayBackground.setVisibility(View.VISIBLE);
holder.tvDayNumber.setTextColor(getResources().getColor(R.color.white));
// Simple logic for streak connection: if previous day was also active
if (position > 0 && days.get(position-1).isActive && day.dayNumber > 1) {
holder.streakConnector.setVisibility(View.VISIBLE);
} else {
holder.streakConnector.setVisibility(View.GONE);
}
} else {
holder.dayBackground.setVisibility(View.GONE);
holder.dayBackground.setBackgroundTintList(ColorStateList.valueOf(Color.parseColor("#F5F5F5")));
holder.tvDayNumber.setTextColor(Color.parseColor("#BDBDBD"));
holder.streakConnector.setVisibility(View.GONE);
} else if ("complete".equals(day.status)) {
// META CUMPRIDA: VERDE
holder.dayBackground.setVisibility(View.VISIBLE);
holder.dayBackground.setBackgroundTintList(ColorStateList.valueOf(Color.parseColor("#4CAF50")));
holder.tvDayNumber.setTextColor(Color.WHITE);
holder.streakConnector.setVisibility(View.GONE);
if (holder.tvEmoji != null) {
holder.tvEmoji.setVisibility(View.VISIBLE);
holder.tvEmoji.setText("🔥");
}
} else if (day.isCurrent) {
// HOJE SEM META: ROXO
holder.dayBackground.setVisibility(View.VISIBLE);
holder.dayBackground.setBackgroundTintList(ColorStateList.valueOf(Color.parseColor("#A35AFF")));
holder.tvDayNumber.setTextColor(Color.WHITE);
holder.streakConnector.setVisibility(View.GONE);
} else {
// META NÃO CUMPRIDA (PASSADO): CINZENTO
holder.dayBackground.setVisibility(View.VISIBLE);
holder.dayBackground.setBackgroundTintList(ColorStateList.valueOf(Color.parseColor("#E0E0E0")));
holder.tvDayNumber.setTextColor(Color.BLACK);
holder.streakConnector.setVisibility(View.GONE);
holder.tvDayNumber.setTextColor(getResources().getColor(R.color.text_primary));
}
// Opcional: indicador adicional para hoje
if (day.isCurrent) {
holder.dayIndicator.setVisibility(View.VISIBLE);
} else {
@@ -143,6 +348,7 @@ public class StreakActivity extends AppCompatActivity {
class ViewHolder extends RecyclerView.ViewHolder {
TextView tvDayNumber;
TextView tvEmoji;
View dayBackground;
View streakConnector;
View dayIndicator;
@@ -150,6 +356,7 @@ public class StreakActivity extends AppCompatActivity {
ViewHolder(View view) {
super(view);
tvDayNumber = view.findViewById(R.id.tvDayNumber);
tvEmoji = view.findViewById(R.id.tvEmoji);
dayBackground = view.findViewById(R.id.dayBackground);
streakConnector = view.findViewById(R.id.streakConnector);
dayIndicator = view.findViewById(R.id.dayIndicator);

View File

@@ -5,15 +5,19 @@ public class Task {
public String title;
public boolean completed;
public int xpReward;
public int duration; // em minutos
public String userId;
public Long completedDate;
public boolean hasBoost = false;
public Task() {}
public Task(String id, String title, int xpReward, String userId) {
public Task(String id, String title, int xpReward, int duration, String userId) {
this.id = id;
this.title = title;
this.completed = false;
this.xpReward = xpReward;
this.duration = duration;
this.userId = userId;
}
}

View File

@@ -0,0 +1,85 @@
package com.fluxup.app;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class TasksAdapter extends RecyclerView.Adapter<TasksAdapter.TaskViewHolder> {
private List<Task> taskList;
private OnTaskClickListener listener;
public interface OnTaskClickListener {
void onTaskFocus(Task task);
void onTaskDelete(Task task);
void onTaskEdit(Task task);
}
public TasksAdapter(List<Task> taskList, OnTaskClickListener listener) {
this.taskList = taskList;
this.listener = listener;
}
public void setTasks(List<Task> tasks) {
this.taskList = tasks;
notifyDataSetChanged();
}
@NonNull
@Override
public TaskViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_task_home, parent, false);
return new TaskViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull TaskViewHolder holder, int position) {
Task task = taskList.get(position);
holder.bind(task, listener);
}
@Override
public int getItemCount() {
return taskList.size();
}
public static class TaskViewHolder extends RecyclerView.ViewHolder {
TextView tvTitle, tvDuration;
Button btnFocus;
public TaskViewHolder(@NonNull View itemView) {
super(itemView);
tvTitle = itemView.findViewById(R.id.tvTaskTitle);
tvDuration = itemView.findViewById(R.id.tvTaskDuration);
btnFocus = itemView.findViewById(R.id.btnStartTaskFocus);
}
public void bind(Task task, OnTaskClickListener listener) {
tvTitle.setText(task.title);
tvDuration.setText(task.duration + " min");
if (task.completed) {
tvTitle.setPaintFlags(tvTitle.getPaintFlags() | android.graphics.Paint.STRIKE_THRU_TEXT_FLAG);
tvTitle.setTextColor(ContextCompat.getColor(itemView.getContext(), R.color.text_secondary));
btnFocus.setEnabled(false);
btnFocus.setAlpha(0.5f);
btnFocus.setText("Concluído");
} else {
tvTitle.setPaintFlags(tvTitle.getPaintFlags() & (~android.graphics.Paint.STRIKE_THRU_TEXT_FLAG));
tvTitle.setTextColor(ContextCompat.getColor(itemView.getContext(), R.color.text_primary));
btnFocus.setEnabled(true);
btnFocus.setAlpha(1.0f);
btnFocus.setText("Focar");
}
btnFocus.setOnClickListener(v -> listener.onTaskFocus(task));
itemView.setOnClickListener(v -> listener.onTaskEdit(task));
}
}
}

View File

@@ -9,19 +9,32 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import android.widget.Button;
import android.widget.ProgressBar;
import java.util.Calendar;
import java.util.ArrayList;
import java.util.List;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.ListenerRegistration;
import android.graphics.Color;
public class TrophiesActivity extends AppCompatActivity {
private TextView tvDivisionTitle, tvTimeRemaining, tvTrophyProgress, tvMotivational;
private ImageButton btnBack;
private LinearLayout trophyContainer, inactiveState;
private ImageView trophy1, trophy2, trophy3;
private TextView tvHeaderTimeLeft, tvHeaderXP;
private ImageView ivHeaderLeagueIcon;
private ProgressBar pbHeaderProgress;
private TextView tvUserName, tvUserDivision;
private ProgressBar pbUserBottom;
private LinearLayout divisionsContainer, rankingContainer;
private ImageButton btnBack, btnHelp;
private Button btnEarnXpNow;
private com.google.android.material.tabs.TabLayout tabLayout;
private View tabDivisoes, tabRanking;
private FirestoreManager firestoreManager;
private FirebaseAuth mAuth;
private ListenerRegistration userListener;
private ListenerRegistration userListener, rankingListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -37,109 +50,307 @@ public class TrophiesActivity extends AppCompatActivity {
}
private void initViews() {
tvDivisionTitle = findViewById(R.id.tvDivisionTitle);
tvTimeRemaining = findViewById(R.id.tvTimeRemaining);
tvTrophyProgress = findViewById(R.id.tvTrophyProgress);
tvMotivational = findViewById(R.id.tvMotivational);
ivHeaderLeagueIcon = findViewById(R.id.ivHeaderLeagueIcon);
tvHeaderTimeLeft = findViewById(R.id.tvHeaderTimeLeft);
tvHeaderXP = findViewById(R.id.tvHeaderXP);
pbHeaderProgress = findViewById(R.id.pbHeaderProgress);
tvUserName = findViewById(R.id.tvUserName);
tvUserDivision = findViewById(R.id.tvUserDivision);
pbUserBottom = findViewById(R.id.pbUserBottom);
divisionsContainer = findViewById(R.id.divisionsContainer);
rankingContainer = findViewById(R.id.rankingContainer);
btnBack = findViewById(R.id.btnBack);
trophyContainer = findViewById(R.id.trophyContainer);
inactiveState = findViewById(R.id.inactiveState);
trophy1 = findViewById(R.id.trophy1);
trophy2 = findViewById(R.id.trophy2);
trophy3 = findViewById(R.id.trophy3);
btnHelp = findViewById(R.id.btnHelp);
btnEarnXpNow = findViewById(R.id.btnEarnXpNow);
tabLayout = findViewById(R.id.tabLayout);
tabDivisoes = findViewById(R.id.tabDivisoes);
tabRanking = findViewById(R.id.tabRanking);
updateTimeRemaining();
}
private void setupListeners() {
btnBack.setOnClickListener(v -> finish());
btnHelp.setOnClickListener(v -> {
// Show help dialog or info
});
btnEarnXpNow.setOnClickListener(v -> finish());
tabLayout.addOnTabSelectedListener(new com.google.android.material.tabs.TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(com.google.android.material.tabs.TabLayout.Tab tab) {
int position = tab.getPosition();
if (position == 0) {
tabDivisoes.setVisibility(View.VISIBLE);
tabRanking.setVisibility(View.GONE);
} else if (position == 1) {
tabDivisoes.setVisibility(View.GONE);
tabRanking.setVisibility(View.VISIBLE);
observeRanking("friends");
} else if (position == 2) {
tabDivisoes.setVisibility(View.GONE);
tabRanking.setVisibility(View.VISIBLE);
observeRanking("global");
}
}
@Override
public void onTabUnselected(com.google.android.material.tabs.TabLayout.Tab tab) {}
@Override
public void onTabReselected(com.google.android.material.tabs.TabLayout.Tab tab) {}
});
}
private void updateTimeRemaining() {
Calendar now = Calendar.getInstance();
int daysLeft = Calendar.SUNDAY - now.get(Calendar.DAY_OF_WEEK);
if (daysLeft < 0) daysLeft += 7;
tvHeaderTimeLeft.setText("Termina em " + daysLeft + " dias");
}
private void observeUserData() {
String uid = mAuth.getUid();
if (uid != null) {
userListener = firestoreManager.observeUser(uid, this::updateUI);
userListener = firestoreManager.observeUser(uid, user -> {
if (user != null) {
updateUI(user);
populateDivisionsPath(user.xp);
}
});
}
}
private void updateUI(Usuario user) {
if (user == null) return;
LeagueHelper.LeagueInfo current = LeagueHelper.getCurrentLeague(user.xp);
LeagueHelper.LeagueInfo next = LeagueHelper.getNextLeague(user.xp);
tvDivisionTitle.setText("Divisão " + user.league);
// Trophy logic based on streak
int currentStreak = user.streak;
int trophies = user.trophiesCount;
// Update trophy visuals
updateTrophyIcons(trophies);
// Progress message
int daysToNext = 30 - (currentStreak % 30);
if (daysToNext == 30 && currentStreak > 0) {
tvTrophyProgress.setText("Troféu conquistado! Mantém a ofensiva.");
// Header
ivHeaderLeagueIcon.setImageResource(current.iconRes);
if (next != null) {
int progress = (user.xp - current.minXp) * 100 / (next.minXp - current.minXp);
pbHeaderProgress.setProgress(progress);
tvHeaderXP.setText(user.xp + " / " + next.minXp + " XP");
} else {
tvTrophyProgress.setText("Faltam " + daysToNext + " dias para o próximo troféu");
pbHeaderProgress.setProgress(100);
tvHeaderXP.setText(user.xp + " XP (" + current.name + ")");
}
// Handle inactive state
if (currentStreak == 0) {
inactiveState.setVisibility(View.VISIBLE);
trophyContainer.setAlpha(0.5f);
tvMotivational.setVisibility(View.GONE);
// Bottom Card
AvatarView ivAvatar = findViewById(R.id.ivUserAvatar);
if (ivAvatar != null && user.avatar != null) {
ivAvatar.setAvatarData(user.avatar);
ivAvatar.setLeague(user.league);
}
tvUserName.setText(user.usuario);
tvUserDivision.setText(current.name + "" + user.xp + " XP");
if (next != null) {
int bottomProgress = (user.xp - current.minXp) * 100 / (next.minXp - current.minXp);
pbUserBottom.setProgress(bottomProgress);
} else {
inactiveState.setVisibility(View.GONE);
trophyContainer.setAlpha(1.0f);
tvMotivational.setVisibility(View.VISIBLE);
tvMotivational.setText("Estás a progredir bem na Divisão " + user.league + "!");
pbUserBottom.setProgress(100);
}
// Simple scale animation for the active trophy
animateActiveTrophy(trophies);
}
private void updateTrophyIcons(int count) {
// Reset alphas
trophy1.setAlpha(0.3f);
trophy2.setAlpha(0.3f);
trophy3.setAlpha(0.3f);
if (count >= 1) trophy1.setAlpha(1.0f);
if (count >= 2) trophy2.setAlpha(1.0f);
if (count >= 3) trophy3.setAlpha(1.0f);
// Highlight current progress (the next one)
if (count == 0) highlightTrophy(trophy1);
else if (count == 1) highlightTrophy(trophy2);
else if (count == 2) highlightTrophy(trophy3);
}
private void highlightTrophy(ImageView trophy) {
trophy.setAlpha(0.6f);
trophy.setBackgroundResource(R.drawable.circle_bg);
trophy.setBackgroundTintList(android.content.res.ColorStateList.valueOf(getResources().getColor(R.color.primary_purple)));
trophy.setPadding(12, 12, 12, 12);
}
private void animateActiveTrophy(int count) {
ImageView target = null;
if (count == 0) target = trophy1;
else if (count == 1) target = trophy2;
else if (count == 2) target = trophy3;
if (target != null) {
ScaleAnimation scale = new ScaleAnimation(1f, 1.1f, 1f, 1.1f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
scale.setDuration(1000);
scale.setRepeatCount(Animation.INFINITE);
scale.setRepeatMode(Animation.REVERSE);
target.startAnimation(scale);
// Update user league in Firestore if it changed
if (!current.name.equals(user.league)) {
firestoreManager.updateUserField(user.id_usuario, "league", current.name);
}
}
private void populateDivisionsPath(int totalXp) {
divisionsContainer.removeAllViews();
LeagueHelper.LeagueInfo current = LeagueHelper.getCurrentLeague(totalXp);
for (LeagueHelper.LeagueInfo league : LeagueHelper.LEAGUES) {
View view = getLayoutInflater().inflate(R.layout.item_division_card, divisionsContainer, false);
TextView tvStatus = view.findViewById(R.id.tvDivisionStatus);
ImageView ivIcon = view.findViewById(R.id.ivDivisionIcon);
TextView tvName = view.findViewById(R.id.tvDivisionName);
TextView tvXP = view.findViewById(R.id.tvDivisionXP);
TextView tvProgressText = view.findViewById(R.id.tvDivisionProgressText);
TextView tvPercentage = view.findViewById(R.id.tvDivisionPercentage);
TextView tvRewards = view.findViewById(R.id.tvRewardsList);
LinearLayout llBg = view.findViewById(R.id.llDivisionBg);
com.google.android.material.card.MaterialCardView card = view.findViewById(R.id.cardDivision);
ivIcon.setImageResource(league.iconRes);
tvName.setText(league.name);
tvXP.setText(league.minXp + (league.maxXp < 100000 ? " - " + league.maxXp : "+") + " XP");
tvRewards.setText(league.rewards);
if (league.name.equals(current.name)) {
// Current League
tvStatus.setVisibility(View.VISIBLE);
tvStatus.setText("Liga Atual");
tvProgressText.setVisibility(View.VISIBLE);
tvPercentage.setVisibility(View.VISIBLE);
LeagueHelper.LeagueInfo next = LeagueHelper.getNextLeague(totalXp);
if (next != null) {
int needed = next.minXp - totalXp;
tvProgressText.setText("Faltam " + needed + " XP para " + next.name);
int perc = (totalXp - league.minXp) * 100 / (next.minXp - league.minXp);
tvPercentage.setText(perc + "% até à próxima divisão");
} else {
tvProgressText.setText("Nível máximo atingido! 🔥");
tvPercentage.setVisibility(View.GONE);
}
card.setCardElevation(8f);
card.setStrokeWidth(4);
card.setStrokeColor(Color.parseColor(league.colorHex));
llBg.setBackgroundColor(Color.parseColor("#15" + league.colorHex.substring(1))); // ~8% opacity
} else if (totalXp > league.maxXp) {
// Completed
tvStatus.setVisibility(View.VISIBLE);
tvStatus.setText("Concluída ✅");
tvStatus.setTextColor(Color.GRAY);
card.setAlpha(0.6f);
} else {
// Locked
tvStatus.setVisibility(View.VISIBLE);
tvStatus.setText("Bloqueada 🔒");
tvStatus.setTextColor(Color.GRAY);
card.setAlpha(0.4f);
}
divisionsContainer.addView(view);
}
}
private void observeRanking(String filter) {
if (rankingListener != null) {
rankingListener.remove();
rankingListener = null;
}
String myUid = mAuth.getUid();
android.util.Log.d("FLUXUP_DEBUG", "CURRENT_USER_ID: " + myUid);
android.util.Log.d("FLUXUP_DEBUG", "LEAGUE_TAB_SELECTED: " + filter);
if ("friends".equals(filter)) {
// Load accepted friends from "friendships"
com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("friendships")
.whereArrayContains("users", myUid)
.get()
.addOnSuccessListener(snapshots -> {
List<String> friendIds = new ArrayList<>();
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) {
List<String> users = (List<String>) doc.get("users");
if (users != null) {
for (String uId : users) {
if (!uId.equals(myUid)) {
friendIds.add(uId);
}
}
}
}
android.util.Log.d("FLUXUP_DEBUG", "MY_ACCEPTED_FRIENDS_COUNT: " + friendIds.size());
android.util.Log.d("FLUXUP_DEBUG", "LEAGUE_FRIENDS_LIST: " + friendIds.toString());
rankingContainer.removeAllViews();
if (friendIds.isEmpty()) {
return;
}
// Fetch details of all friends and sort by xp_semanal desc
List<Usuario> friendList = new ArrayList<>();
final int[] remaining = {friendIds.size()};
for (String friendId : friendIds) {
com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("users").document(friendId).get().addOnSuccessListener(userSnap -> {
Usuario friend = userSnap.toObject(Usuario.class);
if (friend != null) {
friendList.add(friend);
}
remaining[0]--;
if (remaining[0] == 0) {
// Sort by xp_semanal descending
friendList.sort((u1, u2) -> Integer.compare(u2.xp_semanal, u1.xp_semanal));
int pos = 1;
for (Usuario u : friendList) {
addRankingItem(pos++, u, myUid);
}
}
}).addOnFailureListener(e -> {
remaining[0]--;
if (remaining[0] == 0) {
friendList.sort((u1, u2) -> Integer.compare(u2.xp_semanal, u1.xp_semanal));
int pos = 1;
for (Usuario u : friendList) {
addRankingItem(pos++, u, myUid);
}
}
});
}
});
} else {
// Global ranking
com.google.firebase.firestore.Query query = com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("users")
.orderBy("xp_semanal", com.google.firebase.firestore.Query.Direction.DESCENDING)
.limit(20);
rankingListener = query.addSnapshotListener((snapshots, e) -> {
if (e != null || snapshots == null) return;
android.util.Log.d("FLUXUP_DEBUG", "GLOBAL_RANKING_COUNT: " + snapshots.size());
rankingContainer.removeAllViews();
int position = 1;
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) {
Usuario user = doc.toObject(Usuario.class);
if (user == null) continue;
addRankingItem(position++, user, myUid);
}
});
}
}
private void addRankingItem(int pos, Usuario user, String myUid) {
View view = getLayoutInflater().inflate(R.layout.item_ranking_user, rankingContainer, false);
TextView tvPos = view.findViewById(R.id.tvRankPosition);
TextView tvName = view.findViewById(R.id.tvRankingName);
TextView tvXP = view.findViewById(R.id.tvRankingXP);
TextView tvTu = view.findViewById(R.id.tvRankingLabelTu);
androidx.cardview.widget.CardView card = view.findViewById(R.id.cardRankingUser);
AvatarView ivAvatar = view.findViewById(R.id.ivRankingAvatar);
TextView tvStreak = view.findViewById(R.id.tvRankingStreak);
ImageView ivTrophy = view.findViewById(R.id.ivRankingTrophy);
tvPos.setText("#" + pos);
tvName.setText(user.usuario);
tvXP.setText(user.xp_semanal + " XP");
if (ivAvatar != null && user.avatar != null) {
ivAvatar.setAvatarData(user.avatar);
ivAvatar.setLeague(user.league);
}
if (tvStreak != null) {
tvStreak.setText("🔥 " + user.streak);
}
if (ivTrophy != null) {
LeagueHelper.LeagueInfo userLeague = LeagueHelper.getCurrentLeague(user.xp);
ivTrophy.setImageResource(userLeague.iconRes);
ivTrophy.setVisibility(View.VISIBLE);
}
if (user.id_usuario.equals(myUid)) {
tvTu.setVisibility(View.VISIBLE);
card.setCardBackgroundColor(Color.parseColor("#F3E5F5"));
}
rankingContainer.addView(view);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (userListener != null) {
userListener.remove();
}
if (userListener != null) userListener.remove();
if (rankingListener != null) rankingListener.remove();
}
}

View File

@@ -0,0 +1,42 @@
package com.fluxup.app;
import java.util.ArrayList;
import java.util.List;
public class UnlockManager {
private static List<CosmeticItem> items;
public static List<CosmeticItem> getLockedCosmetics() {
if (items == null) {
items = new ArrayList<>();
// 🔥 7 dias ofensiva -> aura de fogo
items.add(new CosmeticItem("effect_fire", "Aura de Fogo", "efeitos", "effect", "fire_aura", "Desbloqueia com 7 dias de ofensiva"));
// ⚡ 5000 XP -> brilho especial
items.add(new CosmeticItem("effect_glow", "Brilho Roxo", "efeitos", "effect", "glow", "Desbloqueia com 5000 XP"));
// ⚡ 1000 XP -> roupa premium (outfit)
items.add(new CosmeticItem("clothes_outfit", "Casaco Premium", "roupa", "clothesStyle", "outfit", "Desbloqueia com 1000 XP"));
// 🏆 Liga Ouro -> moldura dourada
items.add(new CosmeticItem("frame_gold", "Moldura Dourada", "molduras", "frame", "gold", "Desbloqueia na Liga Ouro"));
// 🎯 50 tarefas concluídas -> boné de campeão
items.add(new CosmeticItem("accessory_cap", "Boné de Campeão", "acessorios", "accessory", "cap", "Desbloqueia com 50 tarefas concluídas"));
// ⏱ 10h de foco -> headphones de foco
items.add(new CosmeticItem("accessory_headphones", "Headphones de Foco", "acessorios", "accessory", "headphones", "Desbloqueia com 10h de foco"));
}
return items;
}
public static CosmeticItem findCosmetic(String property, String value) {
for (CosmeticItem item : getLockedCosmetics()) {
if (item.property.equals(property) && item.value.equals(value)) {
return item;
}
}
return null;
}
public static boolean isItemLockedForUser(Usuario user, String property, String value) {
CosmeticItem item = findCosmetic(property, value);
if (item == null) return false; // Se não estiver na lista de bloqueados, está livre
return !item.isUnlocked(user);
}
}

View File

@@ -11,13 +11,47 @@ public class Usuario {
public String updated_at;
// Stats and Social
public int xp = 0;
public int level = 1;
public int xp = 0; // Total XP
public int xp_hoje = 0;
public int xp_semanal = 0;
public int streak = 0;
public int melhor_streak = 0;
public String league = "Bronze";
public String handle = "";
public String bio = "";
public String titulo = "Iniciante";
public int achievementsCount = 0;
public int trophiesCount = 0;
public int followers = 0;
public int following = 0;
public int tasks_concluidas_hoje = 0;
public int total_tasks_concluidas = 0;
public int meta_diaria_tarefas = 4;
public int meta_diaria_foco = 60; // em minutos
public int tempo_foco_hoje = 0; // em minutos
public int tempo_foco_total = 0; // em minutos
public int sessoes_foco_completas = 0;
public int dias_ativos = 1;
public String last_active_date = ""; // YYYY-MM-DD
public String last_streak_completed_date = ""; // YYYY-MM-DD
public String last_reward_claim_date = ""; // YYYY-MM-DD
public String avatar_url = "";
public AvatarData avatar = new AvatarData(); // Avatar gamificado
public java.util.List<String> dias_concluidos = new java.util.ArrayList<>();
public boolean public_profile = true;
public boolean incognito = false;
// Gamification & Rewards
public int login_streak = 0;
public String last_login_claim_date = ""; // YYYY-MM-DD
public java.util.List<String> claimed_missions_today = new java.util.ArrayList<>();
public String claimed_missions_date = ""; // YYYY-MM-DD
public java.util.List<String> claimed_streak_rewards = new java.util.ArrayList<>();
public String last_box_open_date = ""; // YYYY-MM-DD
public int coins = 0;
public java.util.List<String> unlockedItems = new java.util.ArrayList<>();
public java.util.Map<String, Integer> inventory = new java.util.HashMap<>();
public Usuario() {}

View File

@@ -71,8 +71,7 @@ public class UsuariosService {
android.util.Log.d("FLUXUP_SERVICE", "A guardar utilizador no Firestore (Coleção: users, ID: " + uid + ")...");
getFirestore().collection("users").document(uid).set(usuario)
.addOnSuccessListener(aVoid -> {
android.util.Log.d("FLUXUP_SERVICE", "Utilizador guardado no Firestore com sucesso. A criar tarefas iniciais...");
createInitialTasks(uid);
android.util.Log.d("FLUXUP_SERVICE", "Utilizador guardado no Firestore com sucesso.");
usuario.palavra_passe = tempPass;
callback.onSuccess(usuario);
})
@@ -90,18 +89,6 @@ public class UsuariosService {
});
}
private static void createInitialTasks(String uid) {
String[] defaultTasks = {
"Completar o perfil",
"Iniciar primeira sessão de foco",
"Definir objetivo diário"
};
for (String title : defaultTasks) {
String taskId = getFirestore().collection("tasks").document().getId();
Task task = new Task(taskId, title, 50, uid);
getFirestore().collection("tasks").document(taskId).set(task);
}
}
public static void recuperarPalavraPasse(Context context, String email, ServiceCallback<Void> callback) {
FirebaseAuth.getInstance().sendPasswordResetEmail(email)

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFFF" />
<stroke
android:width="1dp"
android:color="#E5E7EB" />
<corners android:radius="12dp" />
</shape>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#F3E5F5" />
<stroke
android:width="2dp"
android:color="#7C3AED" />
<corners android:radius="12dp" />
</shape>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#20000000">
<item>
<shape android:shape="rectangle">
<solid android:color="#F5F5F5" />
<stroke android:width="1dp" android:color="@color/border_color" />
<corners android:radius="@dimen/radius_duo" />
</shape>
</item>
</ripple>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="135"
android:startColor="#FDE047"
android:endColor="#EAB308"
android:type="linear"/>
<corners android:radius="@dimen/radius_duo"/>
</shape>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="135"
android:startColor="#9D4EDD"
android:endColor="#7C3AED"
android:type="linear"/>
<corners android:radius="@dimen/radius_duo"/>
</shape>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#407C3AED"
android:endColor="#007C3AED"
android:angle="270" />
</shape>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/background_light" />
</shape>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF" />
<stroke
android:width="1dp"
android:color="#E2E8F0" />
<corners android:radius="8dp" />
</shape>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M8.59,16.59L13.17,12L8.59,7.41L10,6l6,6l-6,6L8.59,16.59z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M20,6h-3c0,-1.66 -1.34,-3 -3,-3h-4C8.34,3 7,4.34 7,6L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM14,5c0.55,0 1,0.45 1,1h-2c0,-0.55 0.45,-1 1,-1zM10,5c0.55,0 1,0.45 1,1L9,6c0,-0.55 0.45,-1 1,-1zM20,19L4,19v-4h16v4zM20,13L4,13v-5h3c0,1.66 1.34,3 3,3h4c1.66,0 3,-1.34 3,-3h3v5z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#7C3AED"
android:pathData="M12,2.5C12,2.5 6.5,5 6.5,12L6.5,14.5L4,17L4,20L7,20L7,17.5L9.5,17.5C9.5,17.5 10.5,19.5 12,19.5C13.5,19.5 14.5,17.5 14.5,17.5L17,17.5L17,20L20,20L20,17L17.5,14.5L17.5,12C17.5,5 12,2.5 12,2.5ZM12,5.5C12,5.5 15.5,7.5 15.5,12L8.5,12C8.5,7.5 12,5.5 12,5.5ZM10.5,13.5L13.5,13.5C13.5,14.33 12.83,15 12,15C11.17,15 10.5,14.33 10.5,13.5Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M7,10l5,5 5,-5z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M7,14l5,-5 5,5z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="120dp"
android:height="120dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#1E88E5"
android:pathData="M18,2H6A2,2 0,0 0,4 4V7C4,8.11 4.9,9 6,9H7V11C7,12.71 8.06,14.2 9.5,14.8V17H8V19H16V17H14.5V14.8C15.94,14.2 17,12.71 17,11V9H18A2,2 0,0 0,20 7V4A2,2 0,0 0,18 2M6,7V4H7V7H6M18,7H17V4H18V7Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="120dp"
android:height="120dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#4CAF50"
android:pathData="M18,2H6A2,2 0,0 0,4 4V7C4,8.11 4.9,9 6,9H7V11C7,12.71 8.06,14.2 9.5,14.8V17H8V19H16V17H14.5V14.8C15.94,14.2 17,12.71 17,11V9H18A2,2 0,0 0,20 7V4A2,2 0,0 0,18 2M6,7V4H7V7H6M18,7H17V4H18V7Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="120dp"
android:height="120dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#D81B60"
android:pathData="M18,2H6A2,2 0,0 0,4 4V7C4,8.11 4.9,9 6,9H7V11C7,12.71 8.06,14.2 9.5,14.8V17H8V19H16V17H14.5V14.8C15.94,14.2 17,12.71 17,11V9H18A2,2 0,0 0,20 7V4A2,2 0,0 0,18 2M6,7V4H7V7H6M18,7H17V4H18V7Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="120dp"
android:height="120dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#7C3AED"
android:pathData="M18,2H6A2,2 0,0 0,4 4V7C4,8.11 4.9,9 6,9H7V11C7,12.71 8.06,14.2 9.5,14.8V17H8V19H16V17H14.5V14.8C15.94,14.2 17,12.71 17,11V9H18A2,2 0,0 0,20 7V4A2,2 0,0 0,18 2M6,7V4H7V7H6M18,7H17V4H18V7Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="120dp"
android:height="120dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#4FC3F7"
android:pathData="M18,2H6A2,2 0,0 0,4 4V7C4,8.11 4.9,9 6,9H7V11C7,12.71 8.06,14.2 9.5,14.8V17H8V19H16V17H14.5V14.8C15.94,14.2 17,12.71 17,11V9H18A2,2 0,0 0,20 7V4A2,2 0,0 0,18 2M6,7V4H7V7H6M18,7H17V4H18V7Z" />
</vector>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#107C3AED" />
<stroke android:width="2dp" android:color="@color/primary_purple" />
<corners android:radius="@dimen/radius_md" />
</shape>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="12dp" />
<solid android:color="#E5E7EB" />
</shape>
</item>
<item android:id="@android:id/progress">
<scale android:scaleWidth="100%">
<shape>
<corners android:radius="12dp" />
<gradient
android:startColor="#A35AFF"
android:endColor="#7C3AED"
android:angle="0" />
</shape>
</scale>
</item>
</layer-list>

View File

@@ -0,0 +1,201 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/background_light">
<!-- HEADER -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="64dp"
android:paddingHorizontal="16dp"
android:background="@color/card_background"
android:elevation="4dp">
<ImageButton
android:id="@+id/btnBackEditor"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_back"
app:tint="@color/text_primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Avatar"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/text_primary"
android:layout_centerInParent="true"/>
<Button
android:id="@+id/btnSaveTop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:text="Guardar"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:textColor="@color/primary_purple"
android:textStyle="bold" />
</RelativeLayout>
<!-- AVATAR PREVIEW -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#F8FAFC">
<androidx.cardview.widget.CardView
android:layout_width="220dp"
android:layout_height="220dp"
android:layout_gravity="center"
app:cardCornerRadius="110dp"
app:cardElevation="12dp">
<com.fluxup.app.AvatarView
android:id="@+id/previewAvatar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.cardview.widget.CardView>
</FrameLayout>
<!-- EDITOR BOTTOM SHEET -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/card_background"
android:elevation="16dp">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabsCategories"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="scrollable"
app:tabIndicatorColor="@color/primary_purple"
app:tabSelectedTextColor="@color/primary_purple"
app:tabTextColor="@color/text_secondary">
<com.google.android.material.tabs.TabItem android:text="Corpo" />
<com.google.android.material.tabs.TabItem android:text="Cabelo" />
<com.google.android.material.tabs.TabItem android:text="Rosto" />
<com.google.android.material.tabs.TabItem android:text="Roupa" />
<com.google.android.material.tabs.TabItem android:text="Acessórios" />
<com.google.android.material.tabs.TabItem android:text="Molduras" />
<com.google.android.material.tabs.TabItem android:text="Efeitos" />
</com.google.android.material.tabs.TabLayout>
<!-- OPTIONS CONTAINER -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:padding="16dp">
<!-- CORPO -->
<ScrollView android:id="@+id/panelCorpo" android:layout_width="match_parent" android:layout_height="match_parent">
<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content">
<TextView android:text="Tom de Pele" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp"/>
<HorizontalScrollView android:scrollbars="none" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp">
<LinearLayout android:id="@+id/llSkinColors" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</HorizontalScrollView>
<TextView android:text="Formato do Corpo" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp"/>
<HorizontalScrollView android:scrollbars="none" android:layout_width="match_parent" android:layout_height="wrap_content">
<LinearLayout android:id="@+id/llBodyFormats" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</HorizontalScrollView>
</LinearLayout>
</ScrollView>
<!-- CABELO -->
<ScrollView android:id="@+id/panelCabelo" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone">
<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content">
<TextView android:text="Estilo de Cabelo" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp"/>
<HorizontalScrollView android:scrollbars="none" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp">
<LinearLayout android:id="@+id/llHairStyles" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</HorizontalScrollView>
<TextView android:text="Cor do Cabelo" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp"/>
<HorizontalScrollView android:scrollbars="none" android:layout_width="match_parent" android:layout_height="wrap_content">
<LinearLayout android:id="@+id/llHairColors" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</HorizontalScrollView>
</LinearLayout>
</ScrollView>
<!-- ROSTO -->
<ScrollView android:id="@+id/panelRosto" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone">
<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content">
<TextView android:text="Olhos" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp"/>
<HorizontalScrollView android:scrollbars="none" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp">
<LinearLayout android:id="@+id/llEyes" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</HorizontalScrollView>
<TextView android:text="Sobrancelhas" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp"/>
<HorizontalScrollView android:scrollbars="none" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp">
<LinearLayout android:id="@+id/llEyebrows" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</HorizontalScrollView>
<TextView android:text="Boca" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp"/>
<HorizontalScrollView android:scrollbars="none" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp">
<LinearLayout android:id="@+id/llMouth" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</HorizontalScrollView>
<TextView android:text="Barba" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp"/>
<HorizontalScrollView android:scrollbars="none" android:layout_width="match_parent" android:layout_height="wrap_content">
<LinearLayout android:id="@+id/llBeard" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</HorizontalScrollView>
</LinearLayout>
</ScrollView>
<!-- ROUPA -->
<ScrollView android:id="@+id/panelRoupa" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone">
<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content">
<TextView android:text="Estilo" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp"/>
<HorizontalScrollView android:scrollbars="none" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp">
<LinearLayout android:id="@+id/llClothes" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</HorizontalScrollView>
<TextView android:text="Cor" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp"/>
<HorizontalScrollView android:scrollbars="none" android:layout_width="match_parent" android:layout_height="wrap_content">
<LinearLayout android:id="@+id/llClothesColors" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</HorizontalScrollView>
</LinearLayout>
</ScrollView>
<!-- ACESSÓRIOS -->
<ScrollView android:id="@+id/panelAcessorios" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone">
<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content">
<TextView android:text="Cabeça / Rosto" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp"/>
<HorizontalScrollView android:scrollbars="none" android:layout_width="match_parent" android:layout_height="wrap_content">
<LinearLayout android:id="@+id/llAccessories" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</HorizontalScrollView>
</LinearLayout>
</ScrollView>
<!-- MOLDURAS -->
<ScrollView android:id="@+id/panelMolduras" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone">
<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content">
<TextView android:text="Molduras Especiais" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp"/>
<HorizontalScrollView android:scrollbars="none" android:layout_width="match_parent" android:layout_height="wrap_content">
<LinearLayout android:id="@+id/llFrames" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</HorizontalScrollView>
</LinearLayout>
</ScrollView>
<!-- EFEITOS -->
<ScrollView android:id="@+id/panelEfeitos" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone">
<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content">
<TextView android:text="Auras e Efeitos" android:textStyle="bold" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp"/>
<HorizontalScrollView android:scrollbars="none" android:layout_width="match_parent" android:layout_height="wrap_content">
<LinearLayout android:id="@+id/llEffects" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</HorizontalScrollView>
</LinearLayout>
</ScrollView>
</FrameLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -6,174 +6,70 @@
android:background="@color/background_light"
android:orientation="vertical">
<!-- Header -->
<RelativeLayout
<!-- Header with Search -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="64dp"
android:paddingHorizontal="16dp">
android:layout_height="wrap_content"
android:background="@color/card_background"
android:orientation="vertical"
android:padding="16dp">
<ImageButton
android:id="@+id/btnClose"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_close"
app:tint="@color/text_primary" />
<TextView
android:layout_width="wrap_content"
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Encontre os seus amigos"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
</RelativeLayout>
android:layout_marginBottom="16dp">
<ImageButton
android:id="@+id/btnClose"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_back"
app:tint="@color/text_primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Amigos"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold" />
</RelativeLayout>
<EditText
android:id="@+id/etSearchFriends"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/edit_text_bg"
android:drawableStart="@drawable/ic_search"
android:drawablePadding="12dp"
android:hint="Procurar por nome ou ID..."
android:paddingHorizontal="16dp"
android:textSize="14sp" />
</LinearLayout>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayoutFriends"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabIndicatorColor="@color/primary_purple"
app:tabSelectedTextColor="@color/primary_purple"
app:tabTextColor="@color/text_secondary">
<com.google.android.material.tabs.TabItem android:text="Sugestões" />
<com.google.android.material.tabs.TabItem android:text="Os meus amigos" />
<com.google.android.material.tabs.TabItem android:text="Pendentes" />
</com.google.android.material.tabs.TabLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/friendsResultsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<!-- Main Options Cards -->
<androidx.cardview.widget.CardView
android:id="@+id/btnContacts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:cardCornerRadius="20dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/card_background">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="20dp">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_contacts"
app:tint="@color/primary_purple" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Escolher nos contactos"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/btnSearchByName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:cardCornerRadius="20dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/card_background">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="20dp">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_search"
app:tint="@color/streak_blue" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Buscar por nome"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/btnProfileLink"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
app:cardCornerRadius="20dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/card_background">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="20dp">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_share"
app:tint="@color/success_green" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Link do seu perfil"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Suggestions Section -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sugestões de amigos"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/btnViewAll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:text="VER TODOS"
android:textColor="@color/primary_purple"
android:textSize="12sp"
android:textStyle="bold" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvSuggestions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
android:padding="16dp">
<!-- Results will be loaded here -->
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -12,25 +12,23 @@
android:layout_height="64dp"
android:paddingHorizontal="16dp"
android:background="@color/card_background"
android:elevation="4dp">
android:elevation="0dp">
<ImageButton
android:id="@+id/btnBack"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_back"
app:tint="@color/text_secondary" />
app:tint="@color/text_primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/settings"
android:textColor="@color/text_primary"
android:text="Configurações"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold" />
</RelativeLayout>
@@ -45,272 +43,81 @@
android:orientation="vertical"
android:padding="20dp">
<!-- Preferences Section -->
<!-- 👤 CONTA -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/preferences"
android:textColor="@color/text_secondary"
style="@style/SettingsSectionHeader"
android:text="Conta" />
android:textAllCaps="true"
android:textSize="12sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
android:backgroundTint="@color/card_background"
android:layout_marginBottom="24dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dark_mode"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switchDarkMode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true" />
</RelativeLayout>
<androidx.cardview.widget.CardView style="@style/SettingsCard">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
<include layout="@layout/item_settings_clickable" android:id="@+id/settingEditProfile" />
<include layout="@layout/item_settings_clickable" android:id="@+id/settingEmail" />
<include layout="@layout/item_settings_clickable" android:id="@+id/settingPassword" />
<include layout="@layout/item_settings_switch" android:id="@+id/settingPublicProfile" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Account Section -->
<!-- 🎨 APARÊNCIA -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/account"
android:textColor="@color/text_secondary"
style="@style/SettingsSectionHeader"
android:text="Aparência" />
android:textAllCaps="true"
android:textSize="12sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
android:backgroundTint="@color/card_background"
android:layout_marginBottom="24dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/tvEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="email@exemplo.com"
android:textColor="@color/text_primary"
android:textSize="15sp"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/border_color"
android:layout_marginVertical="12dp" />
<TextView
android:id="@+id/btnChangePassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/change_password"
android:textColor="@color/primary_purple"
android:textSize="14sp"
android:textStyle="bold"
android:clickable="true"
android:focusable="true" />
<androidx.cardview.widget.CardView style="@style/SettingsCard">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
<include layout="@layout/item_settings_switch" android:id="@+id/settingDarkMode" />
<include layout="@layout/item_settings_clickable" android:id="@+id/settingThemeColor" android:visibility="gone" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Privacy Section -->
<!-- 🔔 NOTIFICAÇÕES -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/privacy"
android:textColor="@color/text_secondary"
style="@style/SettingsSectionHeader"
android:text="Notificações" />
android:textAllCaps="true"
android:textSize="12sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
android:backgroundTint="@color/card_background"
android:layout_marginBottom="24dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/private_account"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switchPrivacy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true" />
</RelativeLayout>
<androidx.cardview.widget.CardView style="@style/SettingsCard">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
<include layout="@layout/item_settings_switch" android:id="@+id/settingDailyReminders" />
<include layout="@layout/item_settings_switch" android:id="@+id/settingAntiProcrastination" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Notifications Section -->
<!-- 🎯 PREFERÊNCIAS DE FOCO -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/notifications"
android:textColor="@color/text_secondary"
style="@style/SettingsSectionHeader"
android:text="Preferências de Foco" />
android:textAllCaps="true"
android:textSize="12sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
android:backgroundTint="@color/card_background"
android:layout_marginBottom="24dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/notifications"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switchNotifications"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true" />
</RelativeLayout>
<androidx.cardview.widget.CardView style="@style/SettingsCard">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
<include layout="@layout/item_settings_clickable" android:id="@+id/settingFocusDuration" />
<include layout="@layout/item_settings_clickable" android:id="@+id/settingBreakDuration" />
<include layout="@layout/item_settings_switch" android:id="@+id/settingVibration" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- App Section -->
<!-- 🛡️ PRIVACIDADE -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_section"
android:textColor="@color/text_secondary"
style="@style/SettingsSectionHeader"
android:text="Privacidade" />
android:textAllCaps="true"
android:textSize="12sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
android:backgroundTint="@color/card_background"
android:layout_marginBottom="40dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/language"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<Spinner
android:id="@+id/spinnerLanguage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:textAlignment="viewEnd" />
</RelativeLayout>
<androidx.cardview.widget.CardView style="@style/SettingsCard">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
<include layout="@layout/item_settings_switch" android:id="@+id/settingIncognito" />
<include layout="@layout/item_settings_clickable" android:id="@+id/settingExportData" android:visibility="gone" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Logout Button -->
<!-- Danger Zone -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btnLogout"
android:id="@+id/btnDeleteAccount"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="@string/logout"
android:textColor="@color/error_red"
android:textStyle="bold"
app:backgroundTint="#FEE2E2"
app:cornerRadius="16dp"
app:elevation="0dp"
app:icon="@drawable/ic_back"
app:iconGravity="textStart"
app:iconTint="@color/error_red"
android:layout_marginBottom="20dp"/>
android:layout_height="56dp"
android:layout_marginTop="24dp"
android:text="Eliminar Conta"
android:textColor="@color/white"
app:backgroundTint="@color/error_red"
app:cornerRadius="12dp"
android:layout_marginBottom="40dp"
android:textStyle="bold" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/background_light">
<!-- Header -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="64dp"
android:paddingHorizontal="16dp"
android:background="@color/card_background">
<ImageButton
android:id="@+id/btnBack"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_back"
app:tint="@color/text_primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Estatísticas"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold" />
</RelativeLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<!-- Selector -->
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/toggleGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="24dp"
app:singleSelection="true"
app:selectionRequired="true"
app:checkedButton="@+id/btnWeek">
<Button
android:id="@+id/btnWeek"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Semana" />
<Button
android:id="@+id/btnMonth"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Mês" />
</com.google.android.material.button.MaterialButtonToggleGroup>
<!-- XP Progress Chart -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Progresso de XP"
android:textStyle="bold"
android:textSize="18sp"
android:layout_marginBottom="12dp"/>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_marginBottom="24dp"
app:cardCornerRadius="16dp"
app:cardElevation="1dp">
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/xpChart"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp" />
</androidx.cardview.widget.CardView>
<!-- Focus Time Chart -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tempo de Foco (min)"
android:textStyle="bold"
android:textSize="18sp"
android:layout_marginBottom="12dp"/>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_marginBottom="24dp"
app:cardCornerRadius="16dp"
app:cardElevation="1dp">
<com.github.mikephil.charting.charts.BarChart
android:id="@+id/focusChart"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp" />
</androidx.cardview.widget.CardView>
<!-- Performance Grid -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Desempenho"
android:textStyle="bold"
android:textSize="18sp"
android:layout_marginBottom="12dp"/>
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="2"
android:layout_marginBottom="40dp">
<include layout="@layout/item_stat_card" android:id="@+id/statAvgFocus" />
<include layout="@layout/item_stat_card" android:id="@+id/statTasksPerDay" />
<include layout="@layout/item_stat_card" android:id="@+id/statBestDay" />
<include layout="@layout/item_stat_card" android:id="@+id/statTotalSessions" />
</GridLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View File

@@ -134,6 +134,7 @@
android:layout_alignParentEnd="true">
<ImageButton
android:id="@+id/btnPrevMonth"
android:layout_width="32dp"
android:layout_height="32dp"
android:background="?attr/selectableItemBackgroundBorderless"
@@ -141,6 +142,7 @@
app:tint="@color/text_secondary" />
<ImageButton
android:id="@+id/btnNextMonth"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="16dp"
@@ -198,13 +200,14 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Dias de prática"
android:text="Dias cumpridos"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
<TextView
android:id="@+id/tvDaysOfPractice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="12"
android:text="0"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold"
@@ -234,9 +237,10 @@
android:textColor="@color/text_secondary"
android:textSize="12sp" />
<TextView
android:id="@+id/tvFocusSessions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="24"
android:text="0"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold"

View File

@@ -1,179 +1,272 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_light">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/card_background"
app:elevation="0dp">
<RelativeLayout
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="64dp"
android:paddingHorizontal="16dp">
app:contentInsetStart="0dp">
<ImageButton
android:id="@+id/btnBack"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_back"
app:tint="@color/text_primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Troféus"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold" />
</RelativeLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="24dp">
<!-- Division Info -->
<TextView
android:id="@+id/tvDivisionTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Divisão Prata"
android:textColor="@color/primary_purple"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvTimeRemaining"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="3 dias restantes"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<!-- Trophy Progression Container -->
<androidx.cardview.widget.CardView
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginBottom="32dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp">
android:layout_height="match_parent"
android:paddingHorizontal="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="32dp">
<LinearLayout
android:id="@+id/trophyContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<!-- Trophies will be added here dynamically or statically -->
<ImageView
android:id="@+id/trophy1"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="8dp"
android:src="@drawable/ic_trophy_bronze" />
<ImageView
android:id="@+id/trophy2"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="8dp"
android:src="@drawable/ic_trophy_silver"
android:padding="4dp"
android:background="@drawable/circle_bg"
android:backgroundTint="#1A6200EE" />
<ImageView
android:id="@+id/trophy3"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="8dp"
android:alpha="0.3"
android:src="@drawable/ic_trophy_gold" />
</LinearLayout>
<TextView
android:id="@+id/tvTrophyProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Faltam 15 dias para o próximo troféu"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Inactive State (Initially Hidden) -->
<LinearLayout
android:id="@+id/inactiveState"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="150dp"
android:layout_height="150dp"
android:src="@drawable/ic_sleeping_char" />
<ImageButton
android:id="@+id/btnBack"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_back"
app:tint="@color/text_primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Hora de voltar!"
android:layout_centerInParent="true"
android:text="Ligas"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Completa tarefas para competir esta semana."
android:textAlignment="center"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
</LinearLayout>
<ImageButton
android:id="@+id/btnHelp"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_menu_help"
app:tint="@color/text_primary" />
</RelativeLayout>
</androidx.appcompat.widget.Toolbar>
<!-- Gamified Header Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp"
android:gravity="center">
<ImageView
android:id="@+id/ivHeaderLeagueIcon"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_trophy_bronze"
android:layout_marginBottom="12dp" />
<!-- Active Message -->
<TextView
android:id="@+id/tvMotivational"
android:id="@+id/tvHeaderTimeLeft"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Estás a progredir bem na Divisão Prata!"
android:textAlignment="center"
android:text="Termina em 3 dias"
android:textColor="@color/text_secondary"
android:textSize="16sp" />
android:textSize="14sp"
android:layout_marginBottom="8dp"/>
<ProgressBar
android:id="@+id/pbHeaderProgress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="12dp"
android:max="100"
android:progress="0"
android:progressTint="@color/primary_purple"
android:progressBackgroundTint="#E0E0E0" />
<TextView
android:id="@+id/tvHeaderXP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="6415 / 10000 XP"
android:textColor="@color/text_primary"
android:textStyle="bold"
android:layout_marginTop="8dp"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="fixed"
app:tabGravity="fill"
app:tabIndicatorColor="@color/primary_purple"
app:tabSelectedTextColor="@color/primary_purple"
app:tabTextColor="@color/text_secondary">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Divisões" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Amigos" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ranking" />
</com.google.android.material.tabs.TabLayout>
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="@+id/contentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/appBar"
android:layout_above="@id/bottomWrapper">
<!-- DIVISÕES TAB -->
<androidx.core.widget.NestedScrollView
android:id="@+id/tabDivisoes"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:padding="16dp">
<LinearLayout
android:id="@+id/divisionsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</androidx.core.widget.NestedScrollView>
<!-- RANKING / AMIGOS TAB -->
<androidx.core.widget.NestedScrollView
android:id="@+id/tabRanking"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:clipToPadding="false"
android:padding="16dp">
<LinearLayout
android:id="@+id/rankingContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</androidx.core.widget.NestedScrollView>
</FrameLayout>
<LinearLayout
android:id="@+id/bottomWrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical"
android:background="@color/card_background"
android:elevation="12dp">
<!-- Fixed User Card -->
<androidx.cardview.widget.CardView
android:id="@+id/cardUserProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="0dp"
app:cardBackgroundColor="@color/card_background">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<androidx.cardview.widget.CardView
android:id="@+id/cvAvatar"
android:layout_width="48dp"
android:layout_height="48dp"
app:cardCornerRadius="24dp"
app:cardElevation="0dp">
<com.fluxup.app.AvatarView
android:id="@+id/ivUserAvatar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/cvAvatar"
android:layout_toStartOf="@id/ivUserExpand"
android:layout_marginStart="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/tvUserName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Utilizador"
android:textColor="@color/text_primary"
android:textStyle="bold"
android:textSize="16sp"/>
<TextView
android:id="@+id/tvUserDivision"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Bronze • 0 XP"
android:textColor="@color/text_secondary"
android:textSize="14sp"/>
<ProgressBar
android:id="@+id/pbUserBottom"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="8dp"
android:layout_marginTop="4dp"
android:max="100"
android:progress="0"
android:progressTint="@color/primary_purple"
android:progressBackgroundTint="#E0E0E0" />
</LinearLayout>
<ImageView
android:id="@+id/ivUserExpand"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_arrow_right"
app:tint="@color/text_secondary" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
<Button
android:id="@+id/btnEarnXpNow"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="16dp"
android:text="Ganhar XP agora"
android:backgroundTint="@color/primary_purple"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
android:textAllCaps="false"
app:cornerRadius="12dp" />
</LinearLayout>
</RelativeLayout>

View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nome da Tarefa"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etTaskTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapSentences" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="Duração (minutos)"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etTaskDuration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:text="25" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Boost row: aligned to the right, only shown when boosts > 0 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="end"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/cardUseBoost"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/boost_default_bg"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:paddingEnd="12dp"
android:paddingBottom="8dp"
android:visibility="gone">
<ImageView
android:id="@+id/ivBoostRocketIcon"
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@drawable/ic_rocket"
app:tint="@color/text_secondary" />
<TextView
android:id="@+id/tvBoostLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:text="Usar Boost"
android:textColor="@color/text_secondary"
android:textSize="13sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvBoostCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text="x0"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:id="@+id/tvDurationTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Duração"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="16dp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilDuration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Duração (minutos)"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etDuration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nome"
android:textColor="@color/text_secondary"
android:textSize="12sp"
android:layout_marginBottom="4dp"/>
<EditText
android:id="@+id/etEditName"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/edittext_bg"
android:hint="O teu nome"
android:paddingHorizontal="12dp"
android:layout_marginBottom="16dp"
android:inputType="textPersonName"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Bio"
android:textColor="@color/text_secondary"
android:textSize="12sp"
android:layout_marginBottom="4dp"/>
<EditText
android:id="@+id/etEditBio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="80dp"
android:background="@drawable/edittext_bg"
android:hint="Uma frase sobre ti"
android:padding="12dp"
android:gravity="top"
android:inputType="textMultiLine"/>
</LinearLayout>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="32dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🔥"
android:textSize="64sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="NOVO NÍVEL!"
android:textColor="@color/primary_purple"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvNewLevel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="5"
android:textColor="@color/text_primary"
android:textSize="48sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="Estás a tornar-te uma lenda da produtividade!"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
</LinearLayout>

View File

@@ -12,7 +12,7 @@
android:orientation="vertical"
android:padding="20dp">
<!-- Header -->
<!-- 1. 👋 HEADER -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -27,119 +27,204 @@
android:id="@+id/tvGreeting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Olá, Jvitor!"
android:text="Olá, Utilizador!"
android:textColor="@color/text_primary"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvMotivationalSubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Pronto para ser produtivo?"
android:text="Só precisas de começar."
android:textColor="@color/text_secondary"
android:textSize="16sp" />
</LinearLayout>
<androidx.cardview.widget.CardView
android:id="@+id/cardProfileAvatar"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_alignParentEnd="true"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardCornerRadius="28dp"
app:cardElevation="2dp">
<ImageView
<com.fluxup.app.AvatarView
android:id="@+id/ivUserAvatar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/ic_nav_profile"
app:tint="@color/primary_purple" />
android:layout_height="match_parent" />
</androidx.cardview.widget.CardView>
</RelativeLayout>
<!-- Progress Overview -->
<!-- 2. 🔥 CARD "HOJE" -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp"
app:contentPadding="20dp">
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:padding="20dp">
<RelativeLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp">
android:orientation="horizontal"
android:weightSum="3">
<TextView
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Desafios Diários"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🔥 Streak"
android:textSize="12sp"
android:textColor="@color/text_secondary"/>
<TextView
android:id="@+id/tvTodayStreak"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0 dias"
android:textStyle="bold"
android:textSize="16sp"
android:textColor="@color/streak_orange"/>
</LinearLayout>
<TextView
android:id="@+id/tvProgressText"
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:text="1 de 3 concluídos"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
</RelativeLayout>
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="⚡ XP"
android:textSize="12sp"
android:textColor="@color/text_secondary"/>
<TextView
android:id="@+id/tvTodayXP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textStyle="bold"
android:textSize="16sp"
android:textColor="@color/primary_purple"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="✅ Tarefas"
android:textSize="12sp"
android:textColor="@color/text_secondary"/>
<TextView
android:id="@+id/tvTodayTasksCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0/4"
android:textStyle="bold"
android:textSize="16sp"
android:textColor="@color/success_green"/>
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/border_color"
android:layout_marginVertical="16dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Meta Diária"
android:textStyle="bold"
android:textSize="14sp"
android:textColor="@color/text_primary"
android:layout_marginBottom="8dp"/>
<ProgressBar
android:id="@+id/pbDailyTasks"
android:id="@+id/pbDailyTasksProgress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="12dp"
android:max="100"
android:progress="33"
android:progress="0"
android:progressDrawable="@drawable/progress_bar_duo" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Daily Challenges List -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:text="Meus Desafios"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/tasksContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="vertical" />
<Button
android:id="@+id/btnAddTasks"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginBottom="32dp"
android:layout_marginBottom="24dp"
android:background="@drawable/button_primary"
android:text="+ Adicionar Desafio"
android:text="+ Adicionar Tarefa"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
app:backgroundTint="@null" />
<!-- Focus Mode Section -->
<LinearLayout
android:id="@+id/layoutTasksSection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:text="Tarefas do Dia"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvTasks"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:nestedScrollingEnabled="false" />
</LinearLayout>
<TextView
android:id="@+id/tvNoTasksIncentive"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Ainda não tens tarefas para hoje. Vamos começar?"
android:textAlignment="center"
android:layout_marginBottom="16dp"
android:visibility="gone"
android:textColor="@color/text_secondary"/>
<!-- 4. ⏱️ MODO FOCO -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="40dp"
android:layout_marginBottom="24dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp"
app:contentPadding="24dp">
@@ -151,14 +236,44 @@
android:orientation="vertical">
<TextView
android:id="@+id/tvFocusTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="Modo Foco"
android:text="Seleciona uma tarefa"
android:textColor="@color/text_primary"
android:textSize="22sp"
android:textAlignment="center"
android:textStyle="bold" />
<TextView
android:id="@+id/tvFocusStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sessão de foco"
android:textColor="@color/text_secondary"
android:textSize="14sp"
android:visibility="visible" />
<TextView
android:id="@+id/tvFocusPauseCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pausas: 0"
android:textColor="@color/text_secondary"
android:textSize="12sp"
android:visibility="gone" />
<TextView
android:id="@+id/tvFocusPenaltyWarning"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="⚠ Muitas pausas: -5 XP"
android:textColor="@color/error_red"
android:textSize="12sp"
android:textStyle="bold"
android:visibility="gone" />
<FrameLayout
android:id="@+id/timerBlock"
android:layout_width="200dp"
@@ -184,6 +299,7 @@
android:textStyle="bold" />
<TextView
android:id="@+id/tvFocusXpReward"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+50 XP"
@@ -193,67 +309,198 @@
</LinearLayout>
</FrameLayout>
<Button
android:id="@+id/btnStartFocus"
android:layout_width="200dp"
android:layout_height="56dp"
android:background="@drawable/button_primary"
android:text="Começar Foco"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
app:backgroundTint="@null" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/btnSecondaryFocus"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:background="@drawable/button_secondary"
android:text="Cancelar"
android:textAllCaps="false"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold"
android:visibility="gone"
app:backgroundTint="@null" />
<Button
android:id="@+id/btnStartFocus"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:background="@drawable/button_primary"
android:text="Começar Foco"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
app:backgroundTint="@null" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Daily Progress Path Section -->
<!-- 5. 📊 PROGRESSO DIÁRIO -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
android:layout_marginBottom="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="Caminho de Progresso Diário"
android:text="Progresso Semanal"
android:textColor="@color/text_primary"
android:textSize="19sp"
android:textSize="20sp"
android:textStyle="bold" />
<androidx.cardview.widget.CardView
android:id="@+id/btnStreak"
android:layout_width="40dp"
android:layout_height="40dp"
android:id="@+id/btnStreakPage"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="@color/card_background"
app:cardCornerRadius="20dp"
app:cardCornerRadius="18dp"
app:cardElevation="2dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="🔥"
android:textSize="18sp" />
</androidx.cardview.widget.CardView>
</RelativeLayout>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:layout_marginBottom="24dp">
<LinearLayout
android:id="@+id/progressPathContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/card_duo"
android:elevation="2dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="24dp"
android:paddingVertical="16dp" />
</HorizontalScrollView>
<!-- 6. 👥 MINI RANKING -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Competição de hoje"
android:textStyle="bold"
android:textSize="16sp"
android:textColor="@color/text_primary"
android:layout_marginBottom="12dp"/>
<LinearLayout
android:id="@+id/miniRankingContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
<Button
android:id="@+id/btnViewFullRanking"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Ver Ranking Completo"
android:textColor="@color/primary_purple"
android:textSize="14sp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 7. 🎁 ACESSO ÀS RECOMPENSAS -->
<androidx.cardview.widget.CardView
android:id="@+id/cardRewardsPromo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="20dp"
android:gravity="center_vertical">
<TextView
android:layout_width="40dp"
android:layout_height="40dp"
android:text="🎁"
android:textSize="32sp"
android:gravity="center"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Central de Recompensas"
android:textColor="@color/text_primary"
android:textStyle="bold"
android:textSize="16sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Resgata prémios, missões e sequências diárias!"
android:textColor="@color/text_secondary"
android:textSize="12sp"/>
</LinearLayout>
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:src="@drawable/ic_flame" />
</androidx.cardview.widget.CardView>
</RelativeLayout>
android:src="@drawable/ic_arrow_right"
app:tint="@color/primary_purple"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<LinearLayout
android:id="@+id/progressPathContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/card_duo"
android:elevation="2dp"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingVertical="32dp"
android:layout_marginBottom="40dp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -10,417 +10,435 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
android:paddingBottom="40dp">
<!-- Top Header -->
<RelativeLayout
<!-- 👤 HEADER MODERNIZADO -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp">
android:layout_height="wrap_content">
<View
android:layout_width="match_parent"
android:layout_height="220dp"
android:background="@color/primary_purple" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="48dp"
android:paddingHorizontal="24dp"
android:gravity="center">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp">
<!-- GLOW EFFECT -->
<View
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_centerInParent="true"
android:background="@drawable/circle_bg"
android:backgroundTint="#40FFFFFF" />
<androidx.cardview.widget.CardView
android:id="@+id/cardAvatar"
android:layout_width="110dp"
android:layout_height="110dp"
android:layout_centerInParent="true"
app:cardCornerRadius="55dp"
app:cardElevation="8dp">
<com.fluxup.app.AvatarView
android:id="@+id/ivProfileAvatar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.cardview.widget.CardView>
<ImageView
android:id="@+id/ivLeagueBadgeSmall"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_alignBottom="@id/cardAvatar"
android:layout_alignEnd="@id/cardAvatar"
android:layout_marginEnd="-6dp"
android:layout_marginBottom="-6dp"
android:elevation="12dp"
android:src="@drawable/ic_trophy_bronze" />
</RelativeLayout>
<TextView
android:id="@+id/tvProfileName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Carregando..."
android:textColor="@color/white"
android:textSize="26sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvProfileHandle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@username"
android:textColor="#E0E0E0"
android:textSize="14sp" />
<!-- 🔥 NÍVEL E XP BAR -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
app:cardCornerRadius="20dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp">
<TextView
android:id="@+id/tvUserLevel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nível 7"
android:textStyle="bold"
android:textColor="@color/text_primary"
android:textSize="16sp"/>
<TextView
android:id="@+id/tvXpFraction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:text="1010 / 1500 XP"
android:textColor="@color/text_secondary"
android:textSize="13sp"/>
</RelativeLayout>
<ProgressBar
android:id="@+id/pbXpLevel"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="12dp"
android:progress="60"
android:progressDrawable="@drawable/xp_progress_bar" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
<ImageButton
android:id="@+id/btnSettings"
android:id="@+id/btnEditProfile"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentEnd="true"
android:layout_gravity="end|top"
android:layout_marginTop="40dp"
android:layout_marginEnd="16dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_settings"
app:tint="@color/primary_purple" />
</RelativeLayout>
app:tint="@color/white" />
</FrameLayout>
<!-- Profile Info -->
<!-- CONTENT -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:layout_marginBottom="32dp">
android:paddingHorizontal="20dp"
android:paddingTop="24dp">
<androidx.cardview.widget.CardView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginBottom="16dp"
app:cardCornerRadius="50dp"
app:cardElevation="4dp">
<ImageView
android:id="@+id/ivAvatar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/ic_nav_profile"
app:tint="@color/primary_purple" />
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/tvUsername"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Jvitor"
android:textColor="@color/text_primary"
android:textSize="26sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvHandle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@jvitor_prod"
android:textColor="@color/text_secondary"
android:textSize="14sp"
android:layout_marginTop="4dp"/>
</LinearLayout>
<!-- Stats Section -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Estatísticas"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold" />
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:columnCount="2"
android:orientation="horizontal">
<!-- Streak Card -->
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_margin="6dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp"
app:contentPadding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🔥"
android:textSize="24sp"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tvStreakValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="15"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ofensiva"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- XP Card -->
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_margin="6dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp"
app:contentPadding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="⚡"
android:textSize="24sp"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tvTotalXP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2450"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Total de XP"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- League Card -->
<!-- 🏆 LIGA ATUAL -->
<androidx.cardview.widget.CardView
android:id="@+id/cardLeague"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_margin="6dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp"
app:contentPadding="16dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground">
android:layout_marginBottom="24dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🏆"
android:textSize="24sp"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tvLeagueName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Prata"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Liga Atual"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Achievements Card -->
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_margin="6dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp"
app:contentPadding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🏅"
android:textSize="24sp"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tvAchievementsCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="8"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Conquistas"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</GridLayout>
<!-- Friends Section -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Amigos"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/btnViewAllFriends"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:text="Ver Todos"
android:textColor="@color/primary_purple"
android:textSize="14sp"
android:textStyle="bold" />
</RelativeLayout>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:scrollbars="none">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<!-- Invite Item -->
<LinearLayout
android:layout_width="80dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:gravity="center"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:id="@+id/btnInviteCard"
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical">
<ImageView
android:id="@+id/ivLeagueIcon"
android:layout_width="60dp"
android:layout_height="60dp"
app:cardCornerRadius="30dp"
app:cardElevation="0dp"
app:cardBackgroundColor="@color/border_color"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground">
android:src="@drawable/ic_trophy_bronze" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/tvLeagueName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="+"
android:textColor="@color/white"
android:textSize="32sp" />
</androidx.cardview.widget.CardView>
android:text="Liga Bronze"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="@color/text_primary"/>
<TextView
android:id="@+id/tvLeagueMotivation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Faltam 490 XP para Prata"
android:textColor="@color/text_secondary"
android:textSize="13sp"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Convidar"
android:text="&gt;"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
android:textSize="20sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Sample Friend 1 -->
<!-- 📅 MINI CALENDÁRIO -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CONSISTÊNCIA"
android:textStyle="bold"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="12dp"
android:letterSpacing="0.1" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="16dp"
app:cardElevation="1dp">
<LinearLayout
android:layout_width="80dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:gravity="center"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:layout_width="60dp"
android:layout_height="60dp"
app:cardCornerRadius="30dp"
app:cardElevation="2dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/ic_nav_profile"
app:tint="@color/reward_yellow" />
</androidx.cardview.widget.CardView>
android:orientation="vertical"
android:padding="16dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvMiniCalendar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:id="@+id/tvConsistencyPhrase"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Maria"
android:textColor="@color/text_primary"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="450 XP"
android:textColor="@color/text_secondary"
android:textSize="10sp" />
android:layout_marginTop="12dp"
android:text="Estás imparável este mês! 🔥"
android:textColor="@color/primary_purple"
android:textStyle="italic"
android:textSize="13sp"
android:gravity="center"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Sample Friend 2 -->
<LinearLayout
android:layout_width="80dp"
<!-- 📊 VISÃO GERAL (STATS REAIS) -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="VISÃO GERAL"
android:textStyle="bold"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="12dp"
android:letterSpacing="0.1" />
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="2"
android:rowCount="3"
android:layout_marginBottom="24dp"
android:alignmentMode="alignMargins"
android:columnOrderPreserved="false">
<include layout="@layout/item_profile_stat_modern"
android:id="@+id/statTotalTasks"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_columnWeight="1"
android:layout_margin="4dp"/>
<include layout="@layout/item_profile_stat_modern"
android:id="@+id/statBestStreak"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_margin="4dp"/>
<include layout="@layout/item_profile_stat_modern"
android:id="@+id/statFocusSessions"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_margin="4dp"/>
<include layout="@layout/item_profile_stat_modern"
android:id="@+id/statTotalFocus"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_margin="4dp"/>
<include layout="@layout/item_profile_stat_modern"
android:id="@+id/statDaysCompleted"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_margin="4dp"/>
<include layout="@layout/item_profile_stat_modern"
android:id="@+id/statDailyAvg"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_margin="4dp"/>
</GridLayout>
<!-- 📈 GRÁFICO SEMANAL -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="PROGRESSO SEMANAL"
android:textStyle="bold"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="12dp"
android:letterSpacing="0.1" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginBottom="24dp"
app:cardCornerRadius="16dp"
app:cardElevation="1dp">
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/weeklyChart"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"/>
</androidx.cardview.widget.CardView>
<!-- 🎖 CONQUISTAS -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CONQUISTAS"
android:textStyle="bold"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:letterSpacing="0.1" />
<TextView
android:id="@+id/btnViewAllBadges"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:text="VER TODAS"
android:textColor="@color/primary_purple"
android:textStyle="bold"
android:textSize="12sp"/>
</RelativeLayout>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="16dp"
app:cardElevation="1dp">
<GridLayout
android:id="@+id/badgesGrid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="4"
android:padding="16dp"
android:gravity="center">
</GridLayout>
</androidx.cardview.widget.CardView>
<!-- 👥 AMIGOS -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp">
<TextView
android:id="@+id/titleAmigos"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AMIGOS"
android:textStyle="bold"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:letterSpacing="0.1" />
<!-- BOTÃO "+" (btnAddFriends) -->
<TextView
android:id="@+id/btnAddFriends"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_toEndOf="@id/titleAmigos"
android:layout_marginStart="8dp"
android:layout_marginTop="-6dp"
android:text="+"
android:gravity="center"
android:orientation="vertical">
android:textColor="@color/primary_purple"
android:textStyle="bold"
android:textSize="22sp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"/>
<androidx.cardview.widget.CardView
android:layout_width="60dp"
android:layout_height="60dp"
app:cardCornerRadius="30dp"
app:cardElevation="2dp">
<TextView
android:id="@+id/btnViewAllFriends"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:text="VER TODOS"
android:textColor="@color/primary_purple"
android:textStyle="bold"
android:textSize="12sp"/>
</RelativeLayout>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/ic_nav_profile"
app:tint="@color/success_green" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:id="@+id/friendsListContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="João"
android:textColor="@color/text_primary"
android:textSize="12sp"
android:textStyle="bold" />
<!-- BOTÃO "ADICIONAR AMIGOS" (btnFindFriends) -->
<Button
android:id="@+id/btnFindFriends"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="ADICIONAR AMIGOS"
android:textColor="@color/primary_purple"
android:textStyle="bold"
app:rippleColor="#207C3AED" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="320 XP"
android:textColor="@color/text_secondary"
android:textSize="10sp" />
</LinearLayout>
</LinearLayout>
</HorizontalScrollView>
<Button
android:id="@+id/btnInviteFriends"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="@drawable/button_primary"
android:text="+ Encontrar Amigos"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
app:backgroundTint="@null" />
<!-- 🔘 LOGOUT -->
<Button
android:id="@+id/btnLogout"
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="Terminar Sessão"
android:backgroundTint="@color/background_light"
android:textColor="@color/error_red"
app:strokeColor="@color/error_red"
app:strokeWidth="1dp"
app:cornerRadius="12dp" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -0,0 +1,487 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_light"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<!-- 1. 👋 HEADER -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tvRewardsTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Recompensas"
android:textColor="@color/text_primary"
android:textSize="26sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvRewardsSubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Continua consistente para desbloquear prémios."
android:textColor="@color/text_secondary"
android:textSize="14sp" />
</LinearLayout>
</RelativeLayout>
<!-- Stats Chips Row -->
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:layout_marginBottom="24dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<!-- XP Chip -->
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
app:cardCornerRadius="20dp"
app:cardElevation="1dp"
app:cardBackgroundColor="@color/card_background">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingHorizontal="14dp"
android:paddingVertical="8dp"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="⚡"
android:textSize="14sp"/>
<TextView
android:id="@+id/tvStatsXP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:text="1200 XP"
android:textColor="@color/primary_purple"
android:textStyle="bold"
android:textSize="12sp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Streak Chip -->
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
app:cardCornerRadius="20dp"
app:cardElevation="1dp"
app:cardBackgroundColor="@color/card_background">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingHorizontal="14dp"
android:paddingVertical="8dp"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🔥"
android:textSize="14sp"/>
<TextView
android:id="@+id/tvStatsStreak"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:text="5 dias"
android:textColor="@color/streak_orange"
android:textStyle="bold"
android:textSize="12sp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Level Chip -->
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
app:cardCornerRadius="20dp"
app:cardElevation="1dp"
app:cardBackgroundColor="@color/card_background">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingHorizontal="14dp"
android:paddingVertical="8dp"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="⭐"
android:textSize="14sp"/>
<TextView
android:id="@+id/tvStatsLevel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:text="Nível 3"
android:textColor="@color/success_green"
android:textStyle="bold"
android:textSize="12sp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Coins Chip (Prep for Future Coins) -->
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="20dp"
app:cardElevation="1dp"
app:cardBackgroundColor="@color/card_background">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingHorizontal="14dp"
android:paddingVertical="8dp"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🪙"
android:textSize="14sp"/>
<TextView
android:id="@+id/tvStatsCoins"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:text="0 moedas"
android:textColor="@color/reward_yellow"
android:textStyle="bold"
android:textSize="12sp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</HorizontalScrollView>
<!-- 2. 🎁 PRESENTE DIÁRIO -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="3dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/card_gradient_purple"
android:padding="20dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:layout_width="52dp"
android:layout_height="52dp"
android:text="🎁"
android:textSize="40sp"
android:gravity="center"
android:background="@drawable/node_circle_bg"
android:backgroundTint="#40FFFFFF"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Presente diário"
android:textColor="@color/white"
android:textStyle="bold"
android:textSize="18sp"/>
<TextView
android:id="@+id/tvDailyPresentGoal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Completa 4 tarefas hoje"
android:textColor="@color/white"
android:alpha="0.9"
android:textSize="13sp"/>
</LinearLayout>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<ProgressBar
android:id="@+id/pbDailyPresent"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="12dp"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/tvDailyPresentProgressText"
android:layout_marginEnd="12dp"
android:max="100"
android:progress="0"
android:progressDrawable="@drawable/progress_bar_duo"
android:progressTint="@color/white"
android:progressBackgroundTint="#40FFFFFF"/>
<TextView
android:id="@+id/tvDailyPresentProgressText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:text="0/4"
android:textColor="@color/white"
android:textStyle="bold"
android:textSize="14sp"/>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:weightSum="2">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Recompensa: +100 XP"
android:textColor="@color/white"
android:textStyle="bold"
android:textSize="13sp"/>
<Button
android:id="@+id/btnClaimDailyPresent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Resgatar"
android:textSize="14sp"
android:textColor="@color/primary_purple"
android:backgroundTint="@color/white"
android:textStyle="bold"
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 3. 🗓️ SEQUÊNCIA DIÁRIA (7 DIAS) -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sequência de Acessos (7 dias)"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="4dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Resgata bónus adicionais todos os dias consecutivamente."
android:textColor="@color/text_secondary"
android:textSize="13sp"
android:layout_marginBottom="12dp"/>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:fillViewport="true">
<LinearLayout
android:id="@+id/llSequenceContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_gravity="center_horizontal"/>
</HorizontalScrollView>
<Button
android:id="@+id/btnClaimSequence"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:text="Resgatar Recompensa de Hoje"
android:textSize="14sp"
android:textColor="@color/white"
android:backgroundTint="@color/primary_purple"
android:textStyle="bold"
android:textAllCaps="false"
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 4. 🎯 MISSÕES DIÁRIAS -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Missões do Dia"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="12dp"/>
<LinearLayout
android:id="@+id/llMissionsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="16dp"/>
<!-- 5. 🔥 RECOMPENSAS DE STREAK -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Recompensas de Ofensiva"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="12dp"/>
<LinearLayout
android:id="@+id/llStreakRewardsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="16dp"/>
<!-- 6. 📦 CAIXA SURPRESA -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Caixa Misteriosa"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="12dp"/>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="3dp">
<LinearLayout
android:id="@+id/llMysteryBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/card_gradient_gold"
android:padding="24dp"
android:gravity="center">
<TextView
android:id="@+id/tvMysteryBoxIcon"
android:layout_width="80dp"
android:layout_height="80dp"
android:text="📦"
android:textSize="64sp"
android:gravity="center"
android:layout_marginBottom="12dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Caixa Misteriosa"
android:textColor="#854D0E"
android:textStyle="bold"
android:textSize="20sp"
android:layout_marginBottom="4dp"/>
<TextView
android:id="@+id/tvMysteryBoxStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Abre uma caixa grátis hoje!"
android:textColor="#A16207"
android:textSize="14sp"
android:textAlignment="center"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/btnOpenMysteryBox"
android:layout_width="160dp"
android:layout_height="48dp"
android:text="Abrir"
android:textSize="14sp"
android:textColor="@color/white"
android:backgroundTint="#854D0E"
android:textStyle="bold"
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 7. 🛒 LOJA -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Loja da App"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="12dp"
android:layout_marginTop="16dp"/>
<LinearLayout
android:id="@+id/llStoreContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="32dp"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -1,18 +1,151 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:background="@color/background_light"
android:fillViewport="true">
<EditText
android:id="@+id/search_bar"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Search..." />
android:orientation="vertical"
android:paddingHorizontal="20dp"
android:paddingTop="20dp"
android:paddingBottom="80dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/search_bar" />
<!-- 1. BARRA DE PESQUISA MODERNA -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginBottom="24dp"
app:cardCornerRadius="28dp"
app:cardElevation="2dp">
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="16dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_search"
app:tint="@color/text_secondary" />
<EditText
android:id="@+id/search_bar"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_weight="1"
android:background="@null"
android:hint="Pesquisar tarefas, desafios ou utilizadores..."
android:imeOptions="actionSearch"
android:inputType="text"
android:textColor="@color/text_primary"
android:textColorHint="@color/text_secondary"
android:textSize="15sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 2. CARD "TEU PROGRESSO" -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Teu progresso esta semana"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Continua consistente 🔥"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<com.github.mikephil.charting.charts.BarChart
android:id="@+id/weeklyChart"
android:layout_width="match_parent"
android:layout_height="200dp" />
<TextView
android:id="@+id/tvEmptyState"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="32dp"
android:text="Completa tarefas para veres teu progresso 📈"
android:textColor="@color/text_secondary"
android:visibility="gone" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 3. DESAFIOS POPULARES -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Desafios Populares"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/btnSeeAllChallenges"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:text="Ver todos"
android:textColor="@color/primary_purple"
android:textSize="14sp"
android:textStyle="bold" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvChallenges"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:clipToPadding="false"
android:orientation="horizontal" />
<!-- 4. UTILIZADORES EM DESTAQUE -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:text="Utilizadores em Destaque"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFeaturedUsers"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -34,6 +34,18 @@
android:textSize="16sp"
android:textStyle="bold" />
<!-- Emoji Overlay -->
<TextView
android:id="@+id/tvEmoji"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="🔥"
android:textSize="12sp"
android:visibility="gone" />
<!-- Indicator Dot (Optional) -->
<View
android:id="@+id/dayIndicator"

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/tvChallengeIcon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginBottom="12dp"
android:background="@drawable/circle_bg_light"
android:gravity="center"
android:text="🎯"
android:textSize="20sp" />
<TextView
android:id="@+id/tvChallengeName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:ellipsize="end"
android:maxLines="2"
android:text="Desafio de Foco"
android:textColor="@color/text_primary"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvChallengeXP"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:text="+500 XP"
android:textColor="@color/primary_purple"
android:textSize="12sp"
android:textStyle="bold" />
<Button
android:id="@+id/btnStartChallenge"
android:layout_width="match_parent"
android:layout_height="36dp"
android:background="@drawable/button_primary"
android:padding="0dp"
android:text="Iniciar"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="12sp"
android:textStyle="bold"
app:backgroundTint="@null" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -1,67 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
android:gravity="center_vertical"
android:orientation="horizontal">
<View
android:id="@+id/topConnector"
android:layout_width="4dp"
android:layout_height="20dp"
android:id="@+id/leftConnector"
android:layout_width="16dp"
android:layout_height="4dp"
android:background="@color/border_color" />
<FrameLayout
android:id="@+id/nodeContainer"
android:layout_width="80dp"
android:layout_height="80dp">
<!-- Progress Ring (Outer) -->
<ProgressBar
android:id="@+id/nodeProgress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:max="100"
android:progress="0"
android:progressDrawable="@drawable/node_progress_ring" />
<!-- Base Circle (Inner) -->
<View
android:id="@+id/nodeCircle"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="center"
android:background="@drawable/node_circle_bg" />
<!-- Icon or Initial (Optional) -->
<TextView
android:id="@+id/nodeDayInitial"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="S"
android:textColor="@color/text_secondary"
android:textSize="18sp"
android:textStyle="bold" />
</FrameLayout>
<TextView
android:id="@+id/nodeDayLabel"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="SEG"
android:textColor="@color/text_secondary"
android:textSize="12sp"
android:textStyle="bold" />
android:gravity="center"
android:orientation="vertical">
<FrameLayout
android:id="@+id/nodeContainer"
android:layout_width="48dp"
android:layout_height="48dp">
<ProgressBar
android:id="@+id/nodeProgress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:max="100"
android:progress="0"
android:visibility="gone"
android:progressDrawable="@drawable/node_progress_ring" />
<View
android:id="@+id/nodeCircle"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:background="@drawable/node_circle_bg" />
<TextView
android:id="@+id/nodeDayInitial"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="S"
android:textColor="@color/text_secondary"
android:textSize="16sp"
android:textStyle="bold" />
</FrameLayout>
<TextView
android:id="@+id/nodeDayLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="SEG"
android:visibility="gone"
android:textColor="@color/text_secondary"
android:textSize="10sp"
android:textStyle="bold" />
</LinearLayout>
<View
android:id="@+id/bottomConnector"
android:layout_width="4dp"
android:layout_height="20dp"
android:id="@+id/rightConnector"
android:layout_width="16dp"
android:layout_height="4dp"
android:background="@color/border_color" />
</LinearLayout>

View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardDivision"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/card_background"
app:strokeWidth="0dp">
<LinearLayout
android:id="@+id/llDivisionBg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp"
android:gravity="center">
<TextView
android:id="@+id/tvDivisionStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Liga Atual"
android:textColor="@color/primary_purple"
android:textStyle="bold"
android:textSize="12sp"
android:textAllCaps="true"
android:layout_marginBottom="8dp"
android:visibility="gone"/>
<ImageView
android:id="@+id/ivDivisionIcon"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/ic_trophy_bronze"
android:layout_marginBottom="12dp" />
<TextView
android:id="@+id/tvDivisionName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Bronze"
android:textColor="@color/text_primary"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvDivisionXP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="0 - 499 XP"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<TextView
android:id="@+id/tvDivisionProgressText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Faltam 3585 XP para Mestre"
android:textColor="@color/text_primary"
android:textSize="14sp"
android:visibility="gone"/>
<TextView
android:id="@+id/tvDivisionPercentage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="64% até à próxima divisão"
android:textColor="@color/text_secondary"
android:textSize="12sp"
android:visibility="gone"/>
<View
android:id="@+id/dividerRewards"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#E0E0E0"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Recompensas"
android:textColor="@color/text_primary"
android:textStyle="bold"
android:textSize="14sp"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tvRewardsList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="• Badge Bronze\n• Moldura Simples"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="12dp">
<androidx.cardview.widget.CardView
android:layout_width="48dp"
android:layout_height="48dp"
app:cardCornerRadius="24dp"
app:cardElevation="0dp">
<com.fluxup.app.AvatarView
android:id="@+id/ivUserAvatar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tvUserName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Username"
android:textColor="@color/text_primary"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvUserStats"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🔥 12 dias • Bronze"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
</LinearLayout>
<Button
android:id="@+id/btnAddFriend"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:paddingHorizontal="16dp"
android:text="Adicionar"
android:textAllCaps="false"
android:textColor="@color/primary_purple"
android:textSize="12sp"
android:textStyle="bold"
app:strokeColor="@color/primary_purple" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="12dp">
<androidx.cardview.widget.CardView
android:layout_width="50dp"
android:layout_height="50dp"
app:cardCornerRadius="25dp"
app:cardElevation="0dp">
<com.fluxup.app.AvatarView
android:id="@+id/ivFriendAvatar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/tvFriendName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nome do Amigo"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvFriendStats"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nível 12 • 450 XP"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
</LinearLayout>
<Button
android:id="@+id/btnFriendAction"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Adicionar"
android:textColor="@color/primary_purple"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/card_background">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="12dp">
<androidx.cardview.widget.CardView
android:layout_width="50dp"
android:layout_height="50dp"
app:cardCornerRadius="25dp"
app:cardElevation="0dp">
<com.fluxup.app.AvatarView
android:id="@+id/ivFriendAvatar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/tvFriendName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nome"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvFriendStats"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="450 XP"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btnAcceptFriend"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Aceitar"
android:textColor="@color/success_green"
android:textStyle="bold"
android:paddingHorizontal="8dp" />
<Button
android:id="@+id/btnRejectFriend"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Recusar"
android:textColor="@color/error_red"
android:textStyle="bold"
android:paddingHorizontal="8dp" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -22,13 +22,10 @@
app:cardCornerRadius="32dp"
app:cardElevation="0dp">
<ImageView
<com.fluxup.app.AvatarView
android:id="@+id/ivFriendAvatar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/ic_nav_profile"
app:tint="@color/primary_purple" />
android:layout_height="match_parent" />
</androidx.cardview.widget.CardView>
<TextView

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:paddingHorizontal="4dp">
<TextView
android:id="@+id/tvDayName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="S"
android:textSize="10sp"
android:textColor="@color/text_secondary"
android:layout_marginBottom="4dp"/>
<View
android:id="@+id/vDayIndicator"
android:layout_width="28dp"
android:layout_height="28dp"
android:background="@drawable/node_circle_bg"
android:backgroundTint="#E0E0E0" />
</LinearLayout>

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/card_background">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical">
<TextView
android:id="@+id/tvMissionIcon"
android:layout_width="40dp"
android:layout_height="40dp"
android:text="🎯"
android:textSize="24sp"
android:gravity="center"
android:background="@drawable/node_circle_bg"
android:backgroundTint="#F3E8FF"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="16dp"
android:layout_marginEnd="12dp">
<TextView
android:id="@+id/tvMissionTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Missão Diária"
android:textColor="@color/text_primary"
android:textStyle="bold"
android:textSize="14sp"/>
<ProgressBar
android:id="@+id/pbMissionProgress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="8dp"
android:layout_marginTop="8dp"
android:max="100"
android:progress="50"
android:progressDrawable="@drawable/progress_bar_duo"/>
<TextView
android:id="@+id/tvMissionProgressText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="2/4"
android:textColor="@color/text_secondary"
android:textSize="12sp"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="end">
<TextView
android:id="@+id/tvMissionReward"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+50 XP"
android:textColor="@color/primary_purple"
android:textStyle="bold"
android:textSize="12sp"
android:layout_marginBottom="4dp"/>
<Button
android:id="@+id/btnClaimMission"
android:layout_width="80dp"
android:layout_height="36dp"
android:text="Reclamar"
android:textSize="10sp"
android:textColor="@color/white"
android:backgroundTint="@color/primary_purple"
android:padding="0dp"
android:textAllCaps="false"
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="16dp"
app:cardElevation="1dp"
app:cardBackgroundColor="@color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp"
android:gravity="center">
<TextView
android:id="@+id/tvStatIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🎯"
android:textSize="20sp"
android:layout_marginBottom="4dp"/>
<TextView
android:id="@+id/tvStatValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="120"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="@color/text_primary"/>
<TextView
android:id="@+id/tvStatLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tarefas"
android:textSize="11sp"
android:textColor="@color/text_secondary"
android:textAllCaps="true"
android:letterSpacing="0.05"/>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardRankingUser"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="0dp"
app:cardBackgroundColor="@color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="12dp">
<TextView
android:id="@+id/tvRankPosition"
android:layout_width="32dp"
android:layout_height="wrap_content"
android:text="#1"
android:textStyle="bold"
android:textColor="@color/text_primary"
android:textSize="14sp" />
<androidx.cardview.widget.CardView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
app:cardCornerRadius="20dp"
app:cardElevation="0dp">
<com.fluxup.app.AvatarView
android:id="@+id/ivRankingAvatar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="12dp"
android:orientation="vertical">
<TextView
android:id="@+id/tvRankingName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nome do Utilizador"
android:textColor="@color/text_primary"
android:textSize="14sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/tvRankingStreak"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🔥 7"
android:textColor="@color/streak_orange"
android:textSize="11sp"
android:textStyle="bold"
android:layout_marginEnd="8dp"/>
<TextView
android:id="@+id/tvRankingLabelTu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tu"
android:textColor="@color/primary_purple"
android:textSize="10sp"
android:textStyle="bold"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/tvRankingXP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1250 XP"
android:textColor="@color/primary_purple"
android:textSize="14sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/ivRankingTrophy"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="6dp"
android:src="@drawable/ic_trophy_bronze"
android:visibility="gone" />
<ImageView
android:id="@+id/ivRankingTrend"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="8dp"
android:src="@drawable/ic_trend_up"
android:visibility="gone"
app:tint="@color/success_green" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:padding="8dp"
android:layout_marginHorizontal="4dp">
<TextView
android:id="@+id/tvDayLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Dia 1"
android:textColor="@color/text_secondary"
android:textSize="12sp"
android:textStyle="bold"
android:layout_marginBottom="6dp"/>
<FrameLayout
android:id="@+id/flNodeBackground"
android:layout_width="56dp"
android:layout_height="56dp"
android:background="@drawable/node_circle_bg"
android:backgroundTint="@color/border_color">
<TextView
android:id="@+id/tvDayRewardIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="⚡"
android:textSize="22sp"/>
<ImageView
android:id="@+id/ivDayClaimedCheck"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:src="@drawable/ic_back"
android:rotation="180"
android:visibility="gone"
app:tint="@color/white"/>
</FrameLayout>
<TextView
android:id="@+id/tvDayRewardValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="+50 XP"
android:textColor="@color/text_primary"
android:textSize="10sp"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end"/>
</LinearLayout>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="64dp"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingHorizontal="24dp"
android:paddingVertical="16dp">
<TextView
android:id="@+id/tvSettingTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/tvSettingValue"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:ellipsize="end"
android:singleLine="true"
android:text="Configuração"
android:textColor="@color/text_primary"
android:textSize="16sp" />
<TextView
android:id="@+id/tvSettingValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="@id/ivChevron"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="8dp"
app:layout_goneMarginEnd="0dp"
android:text="Valor"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<ImageView
android:id="@+id/ivChevron"
android:layout_width="20dp"
android:layout_height="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:src="@drawable/ic_back"
android:rotation="180"
android:alpha="0.3" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="64dp"
android:paddingHorizontal="24dp"
android:paddingVertical="16dp">
<TextView
android:id="@+id/tvSettingTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/switchSetting"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:ellipsize="end"
android:singleLine="true"
android:text="Configuração"
android:textColor="@color/text_primary"
android:textSize="16sp" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switchSetting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:useMaterialThemeColors="true" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_margin="6dp"
app:cardCornerRadius="16dp"
app:cardElevation="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/tvStatLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Média Foco"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
<TextView
android:id="@+id/tvStatValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="45m"
android:textColor="@color/primary_purple"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginTop="4dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_columnWeight="1"
app:cardCornerRadius="12dp"
app:cardElevation="0dp"
app:cardBackgroundColor="@color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="12dp">
<TextView
android:id="@+id/tvStatIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="⚡"
android:textSize="20sp"
android:layout_marginBottom="4dp"/>
<TextView
android:id="@+id/tvStatValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textColor="@color/text_primary"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvStatLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="XP"
android:textColor="@color/text_secondary"
android:textSize="10sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:cardCornerRadius="12dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical"
android:background="?attr/selectableItemBackground">
<TextView
android:id="@+id/tvStoreIcon"
android:layout_width="48dp"
android:layout_height="48dp"
android:text="📦"
android:textSize="32sp"
android:gravity="center"
android:background="@drawable/circle_bg_light"
android:layout_marginEnd="16dp"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tvStoreTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Item"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/tvStoreDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Descrição do item"
android:textColor="@color/text_secondary"
android:textSize="13sp"
android:layout_marginTop="4dp"/>
<TextView
android:id="@+id/tvStoreOwned"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Possuis: 0"
android:textColor="@color/primary_purple"
android:textSize="12sp"
android:textStyle="bold"
android:layout_marginTop="4dp"
android:visibility="gone"/>
</LinearLayout>
<Button
android:id="@+id/btnBuyStoreItem"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:text="60 🪙"
android:textSize="14sp"
android:textColor="@color/white"
android:backgroundTint="@color/primary_purple"
android:textStyle="bold"
app:cornerRadius="12dp"
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"/>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/card_background">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical">
<TextView
android:id="@+id/tvStreakRewardIcon"
android:layout_width="40dp"
android:layout_height="40dp"
android:text="🔥"
android:textSize="24sp"
android:gravity="center"
android:background="@drawable/node_circle_bg"
android:backgroundTint="#FFF7ED"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="16dp"
android:layout_marginEnd="12dp">
<TextView
android:id="@+id/tvStreakTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ofensiva de 3 dias"
android:textColor="@color/text_primary"
android:textStyle="bold"
android:textSize="14sp"/>
<TextView
android:id="@+id/tvStreakRewardDetail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Recompensa: +100 XP"
android:textColor="@color/text_secondary"
android:textSize="12sp"/>
</LinearLayout>
<Button
android:id="@+id/btnClaimStreak"
android:layout_width="90dp"
android:layout_height="36dp"
android:text="Resgatar"
android:textSize="10sp"
android:textColor="@color/white"
android:backgroundTint="@color/primary_purple"
android:padding="0dp"
android:textAllCaps="false"
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"/>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:cardCornerRadius="@dimen/radius_md"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="16dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:orientation="vertical">
<TextView
android:id="@+id/tvTaskTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nome da Tarefa"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvTaskDuration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="25 min"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
</LinearLayout>
<Button
android:id="@+id/btnStartTaskFocus"
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Focar"
android:textColor="@color/white"
android:backgroundTint="@color/primary_purple"
android:textSize="12sp"
app:cornerRadius="20dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -8,6 +8,10 @@
android:id="@+id/nav_trophies"
android:icon="@drawable/ic_nav_trophy"
android:title="Troféus" />
<item
android:id="@+id/nav_rewards"
android:icon="@drawable/ic_nav_rewards"
android:title="Recompensas" />
<item
android:id="@+id/nav_profile"
android:icon="@drawable/ic_nav_profile"

View File

@@ -11,7 +11,7 @@
<item name="colorOnSecondary">@color/background_light</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">@color/background_light</item>
<item name="android:windowLightStatusBar" tools:targetApi="m">false</item>
<item name="android:windowLightStatusBar" tools:targetApi="M">false</item>
<!-- Customize your theme here. -->
<item name="android:windowBackground">@color/background_light</item>
</style>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="nodeCircle" type="id">circle</item>
<item name="nodeDayInitial" type="id">dayInitial</item>
<item name="nodeDayLabel" type="id">nodeDayLabel</item>
<item name="nodeProgress" type="id">nodeProgress</item>
<item name="nodeCircle" type="id" />
<item name="nodeDayInitial" type="id" />
<item name="nodeDayLabel" type="id" />
<item name="nodeProgress" type="id" />
</resources>

View File

@@ -34,5 +34,28 @@
<string name="confirm_password_hint">Confirmar palavra-passe</string>
<string name="register_button">Registar</string>
<string name="already_have_account">Já tens conta? </string>
<!-- Statistics -->
<string name="statistics">Estatísticas</string>
<string name="view_details">Ver detalhes</string>
<string name="xp_progress">Progresso de XP</string>
<string name="focus_time_stats">Tempo de Foco (min)</string>
<string name="performance">Desempenho</string>
<string name="avg_focus">Média Foco</string>
<string name="tasks_per_day">Tarefas/Dia</string>
<string name="best_day">Melhor Dia</string>
<string name="total_sessions">Total Sessões</string>
<!-- Friends -->
<string name="friends">Amigos</string>
<string name="search_friends_hint">Procurar por nome ou ID...</string>
<string name="suggestions">Sugestões</string>
<string name="my_friends">Os meus amigos</string>
<string name="pending">Pendentes</string>
<string name="add_friend">Adicionar</string>
<!-- Gamification -->
<string name="level_up">NOVO NÍVEL!</string>
<string name="level_up_congrats">Estás a tornar-te uma lenda da produtividade!</string>
</resources>

View File

@@ -50,4 +50,23 @@
<item name="android:layout_marginBottom">@dimen/spacing_md</item>
</style>
<style name="SettingsSectionHeader">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textColor">@color/text_secondary</item>
<item name="android:textAllCaps">true</item>
<item name="android:textSize">12sp</item>
<item name="android:textStyle">bold</item>
<item name="android:layout_marginTop">24dp</item>
<item name="android:layout_marginBottom">8dp</item>
</style>
<style name="SettingsCard" parent="CardView">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="cardCornerRadius">16dp</item>
<item name="cardElevation">0dp</item>
<item name="cardBackgroundColor">@color/card_background</item>
<item name="android:layout_marginBottom">8dp</item>
</style>
</resources>

View File

@@ -9,11 +9,11 @@
<item name="colorSecondary">#FACC15</item>
<item name="colorSecondaryVariant">#FACC15</item>
<item name="colorOnSecondary">#11181C</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">#F9FAFB</item>
<item name="android:windowLightStatusBar" tools:targetApi="m">true</item>
<!-- Customize your theme here. -->
<item name="android:windowBackground">#F9FAFB</item>
<!-- Status bar color. Uses @color/background_light so values-night can override -->
<item name="android:statusBarColor">@color/background_light</item>
<item name="android:windowLightStatusBar" tools:targetApi="M">true</item>
<!-- Window background. Uses @color/background_light so values-night can override -->
<item name="android:windowBackground">@color/background_light</item>
</style>
</resources>

View File

@@ -10,13 +10,6 @@ buildscript {
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

File diff suppressed because it is too large Load Diff

287
build_stacktrace.txt Normal file
View File

@@ -0,0 +1,287 @@
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/230407/.gradle/wrapper/dists/gradle-9.3.1-bin/23ovyewtku6u96viwx3xl3oks/gradle-9.3.1/lib/native-platform-0.22-milestone-29.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
> Configure project :app
WARNING: The option setting 'android.usesSdkInManifest.disallowed=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.sdk.defaultTargetSdkToCompileSdkIfUnset=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.enableAppCompileTimeRClass=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.builtInKotlin=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.newDsl=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.r8.optimizedResourceShrinking=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.defaults.buildfeatures.resvalues=true' is deprecated.
The current default is 'false'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.enableJetifier=true' is deprecated.
The current default is 'false'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties
> Task :app:preBuild UP-TO-DATE
> Task :app:preDebugBuild UP-TO-DATE
> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
> Task :app:generateDebugBuildConfig UP-TO-DATE
> Task :app:javaPreCompileDebug UP-TO-DATE
> Task :app:checkDebugAarMetadata UP-TO-DATE
> Task :app:generateDebugResValues UP-TO-DATE
> Task :app:processDebugGoogleServices UP-TO-DATE
> Task :app:mapDebugSourceSetPaths UP-TO-DATE
> Task :app:processDebugNavigationResources UP-TO-DATE
> Task :app:compileDebugNavigationResources UP-TO-DATE
> Task :app:generateDebugResources UP-TO-DATE
> Task :app:packageDebugResources UP-TO-DATE
> Task :app:parseDebugLocalResources UP-TO-DATE
> Task :app:createDebugCompatibleScreenManifests UP-TO-DATE
> Task :app:extractDeepLinksDebug UP-TO-DATE
> Task :app:processDebugMainManifest UP-TO-DATE
> Task :app:processDebugManifest UP-TO-DATE
> Task :app:processDebugManifestForPackage UP-TO-DATE
> Task :app:generateDebugAssets UP-TO-DATE
> Task :app:mergeDebugAssets UP-TO-DATE
> Task :app:compressDebugAssets UP-TO-DATE
> Task :app:processDebugJavaRes NO-SOURCE
> Task :app:mergeDebugJavaResource UP-TO-DATE
> Task :app:desugarDebugFileDependencies
> Task :app:mergeDebugJniLibFolders
> Task :app:mergeDebugNativeLibs NO-SOURCE
> Task :app:stripDebugDebugSymbols NO-SOURCE
> Task :app:mergeDebugResources FAILED
/Users/230407/Desktop/FluxupP/app/src/main/res/values/ids.xml:3:4: <item> inner element must either be a resource reference or empty.
/Users/230407/Desktop/FluxupP/app/src/main/res/values/ids.xml:4:4: <item> inner element must either be a resource reference or empty.
/Users/230407/Desktop/FluxupP/app/src/main/res/values/ids.xml:5:4: <item> inner element must either be a resource reference or empty.
/Users/230407/.gradle/caches/9.3.1/transforms/3090efbf6d47378a0528b3ce679aa974/transformed/transition-1.4.1/res/values/values.xml:4:4: <item> inner element must either be a resource reference or empty.
> Task :app:checkDebugDuplicateClasses
> Task :app:validateSigningDebug
[Incubating] Problems report is available at: file:///Users/230407/Desktop/FluxupP/build/reports/problems/problems-report.html
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:mergeDebugResources'.
> A failure occurred while executing com.android.build.gradle.internal.res.ResourceCompilerRunnable
> Resource compilation failed (Failed to compile values resource file /Users/230407/Desktop/FluxupP/app/build/intermediates/incremental/debug/mergeDebugResources/merged.dir/values/values.xml. Cause: java.lang.IllegalStateException: Can not extract resource from com.android.aaptcompiler.ParsedResource@247bbe22.,Can not extract resource from com.android.aaptcompiler.ParsedResource@255775e2.,Can not extract resource from com.android.aaptcompiler.ParsedResource@87786c9.,Can not extract resource from com.android.aaptcompiler.ParsedResource@460c8a21.). Check logs for more details.
* Try:
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights from a Build Scan (powered by Develocity).
> Get more help at https://help.gradle.org.
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:mergeDebugResources'.
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:135)
at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:288)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:133)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:121)
at org.gradle.api.internal.tasks.execution.ProblemsTaskPathTrackingTaskExecuter.execute(ProblemsTaskPathTrackingTaskExecuter.java:41)
at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:74)
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
at org.gradle.execution.plan.DefaultNodeExecutor.executeLocalTaskNode(DefaultNodeExecutor.java:55)
at org.gradle.execution.plan.DefaultNodeExecutor.execute(DefaultNodeExecutor.java:34)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:355)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:343)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.lambda$execute$0(DefaultTaskExecutionGraph.java:339)
at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:84)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:339)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:328)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:459)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:376)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
Caused by: org.gradle.workers.internal.DefaultWorkerExecutor$WorkExecutionException: A failure occurred while executing com.android.build.gradle.internal.res.ResourceCompilerRunnable
at org.gradle.workers.internal.DefaultWorkerExecutor$WorkItemExecution.waitForCompletion(DefaultWorkerExecutor.java:289)
at org.gradle.internal.work.DefaultAsyncWorkTracker.lambda$waitForItemsAndGatherFailures$2(DefaultAsyncWorkTracker.java:130)
at org.gradle.internal.Factories$1.create(Factories.java:30)
at org.gradle.internal.work.DefaultWorkerLeaseService.lambda$withoutLocks$2(DefaultWorkerLeaseService.java:344)
at org.gradle.internal.work.ResourceLockStatistics$1.measure(ResourceLockStatistics.java:42)
at org.gradle.internal.work.DefaultWorkerLeaseService.withoutLocks(DefaultWorkerLeaseService.java:342)
at org.gradle.internal.work.DefaultWorkerLeaseService.withoutLocks(DefaultWorkerLeaseService.java:326)
at org.gradle.internal.work.DefaultWorkerLeaseService.withoutLock(DefaultWorkerLeaseService.java:331)
at org.gradle.internal.work.DefaultAsyncWorkTracker.waitForItemsAndGatherFailures(DefaultAsyncWorkTracker.java:126)
at org.gradle.internal.work.DefaultAsyncWorkTracker.waitForItemsAndGatherFailures(DefaultAsyncWorkTracker.java:92)
at org.gradle.internal.work.DefaultAsyncWorkTracker.waitForAll(DefaultAsyncWorkTracker.java:78)
at org.gradle.internal.work.DefaultAsyncWorkTracker.waitForCompletion(DefaultAsyncWorkTracker.java:66)
at org.gradle.api.internal.tasks.execution.TaskExecution$3.run(TaskExecution.java:267)
at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
at org.gradle.api.internal.tasks.execution.TaskExecution.executeAction(TaskExecution.java:244)
at org.gradle.api.internal.tasks.execution.TaskExecution.executeActions(TaskExecution.java:227)
at org.gradle.api.internal.tasks.execution.TaskExecution.executeWithPreviousOutputFiles(TaskExecution.java:210)
at org.gradle.api.internal.tasks.execution.TaskExecution.execute(TaskExecution.java:176)
at org.gradle.internal.execution.steps.ExecuteStep.executeInternal(ExecuteStep.java:110)
at org.gradle.internal.execution.steps.ExecuteStep.access$000(ExecuteStep.java:47)
at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:64)
at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:61)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:61)
at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:47)
at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:42)
at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:75)
at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:55)
at org.gradle.internal.execution.steps.PreCreateOutputParentsStep.execute(PreCreateOutputParentsStep.java:51)
at org.gradle.internal.execution.steps.PreCreateOutputParentsStep.execute(PreCreateOutputParentsStep.java:29)
at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.executeMutable(RemovePreviousOutputsStep.java:70)
at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.executeMutable(RemovePreviousOutputsStep.java:40)
at org.gradle.internal.execution.steps.MutableStep.execute(MutableStep.java:25)
at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:62)
at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:27)
at org.gradle.internal.execution.steps.CaptureOutputsAfterExecutionStep.execute(CaptureOutputsAfterExecutionStep.java:69)
at org.gradle.internal.execution.steps.CaptureOutputsAfterExecutionStep.execute(CaptureOutputsAfterExecutionStep.java:46)
at org.gradle.internal.execution.steps.ResolveInputChangesStep.executeMutable(ResolveInputChangesStep.java:39)
at org.gradle.internal.execution.steps.ResolveInputChangesStep.executeMutable(ResolveInputChangesStep.java:28)
at org.gradle.internal.execution.steps.MutableStep.execute(MutableStep.java:25)
at org.gradle.internal.execution.steps.BuildCacheStep.executeWithoutCache(BuildCacheStep.java:192)
at org.gradle.internal.execution.steps.BuildCacheStep.lambda$execute$1(BuildCacheStep.java:76)
at org.gradle.internal.Either$Right.fold(Either.java:176)
at org.gradle.internal.execution.caching.CachingState.fold(CachingState.java:62)
at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:74)
at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:49)
at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:46)
at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:35)
at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:75)
at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$2(SkipUpToDateStep.java:53)
at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:53)
at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:35)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:37)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:27)
at org.gradle.internal.execution.steps.ResolveIncrementalCachingStateStep.executeDelegate(ResolveIncrementalCachingStateStep.java:49)
at org.gradle.internal.execution.steps.ResolveIncrementalCachingStateStep.executeDelegate(ResolveIncrementalCachingStateStep.java:27)
at org.gradle.internal.execution.steps.AbstractResolveCachingStateStep.execute(AbstractResolveCachingStateStep.java:71)
at org.gradle.internal.execution.steps.AbstractResolveCachingStateStep.execute(AbstractResolveCachingStateStep.java:39)
at org.gradle.internal.execution.steps.ResolveChangesStep.executeMutable(ResolveChangesStep.java:63)
at org.gradle.internal.execution.steps.ResolveChangesStep.executeMutable(ResolveChangesStep.java:34)
at org.gradle.internal.execution.steps.MutableStep.execute(MutableStep.java:25)
at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:64)
at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:42)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.execute(AbstractCaptureStateBeforeExecutionStep.java:77)
at org.gradle.internal.execution.steps.AbstractCaptureStateBeforeExecutionStep.execute(AbstractCaptureStateBeforeExecutionStep.java:46)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.executeWithNonEmptySources(AbstractSkipEmptyWorkStep.java:133)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.execute(AbstractSkipEmptyWorkStep.java:59)
at org.gradle.internal.execution.steps.AbstractSkipEmptyWorkStep.execute(AbstractSkipEmptyWorkStep.java:36)
at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:36)
at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:23)
at org.gradle.internal.execution.steps.HandleStaleOutputsStep.executeMutable(HandleStaleOutputsStep.java:77)
at org.gradle.internal.execution.steps.HandleStaleOutputsStep.executeMutable(HandleStaleOutputsStep.java:43)
at org.gradle.internal.execution.steps.MutableStep.execute(MutableStep.java:25)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.lambda$execute$0(AssignMutableWorkspaceStep.java:35)
at org.gradle.api.internal.tasks.execution.TaskExecution$4.withWorkspace(TaskExecution.java:305)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.execute(AssignMutableWorkspaceStep.java:31)
at org.gradle.internal.execution.steps.AssignMutableWorkspaceStep.execute(AssignMutableWorkspaceStep.java:22)
at org.gradle.internal.execution.steps.ChoosePipelineStep.execute(ChoosePipelineStep.java:40)
at org.gradle.internal.execution.steps.ChoosePipelineStep.execute(ChoosePipelineStep.java:23)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.lambda$execute$2(ExecuteWorkBuildOperationFiringStep.java:67)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:67)
at org.gradle.internal.execution.steps.ExecuteWorkBuildOperationFiringStep.execute(ExecuteWorkBuildOperationFiringStep.java:39)
at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:46)
at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:34)
at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:44)
at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:31)
at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:68)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:132)
... 30 more
Caused by: com.android.aaptcompiler.ResourceCompilationException: Resource compilation failed (Failed to compile values resource file /Users/230407/Desktop/FluxupP/app/build/intermediates/incremental/debug/mergeDebugResources/merged.dir/values/values.xml. Cause: java.lang.IllegalStateException: Can not extract resource from com.android.aaptcompiler.ParsedResource@247bbe22.,Can not extract resource from com.android.aaptcompiler.ParsedResource@255775e2.,Can not extract resource from com.android.aaptcompiler.ParsedResource@87786c9.,Can not extract resource from com.android.aaptcompiler.ParsedResource@460c8a21.). Check logs for more details.
at com.android.aaptcompiler.ResourceCompiler.compileResource(ResourceCompiler.kt:120)
at com.android.build.gradle.internal.res.ResourceCompilerRunnable$Companion.compileSingleResource(ResourceCompilerRunnable.kt:33)
at com.android.build.gradle.internal.res.ResourceCompilerRunnable.run(ResourceCompilerRunnable.kt:13)
at com.android.build.gradle.internal.profile.ProfileAwareWorkAction.execute(ProfileAwareWorkAction.kt:66)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:68)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:64)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:61)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:102)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:61)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:58)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:176)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:30)
at org.gradle.internal.work.DefaultWorkerLeaseService.lambda$withLocksAcquired$0(DefaultWorkerLeaseService.java:269)
at org.gradle.internal.work.ResourceLockStatistics$1.measure(ResourceLockStatistics.java:42)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocksAcquired(DefaultWorkerLeaseService.java:267)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:259)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
... 2 more
Caused by: com.android.aaptcompiler.ResourceCompilationException: Failed to compile values resource file /Users/230407/Desktop/FluxupP/app/build/intermediates/incremental/debug/mergeDebugResources/merged.dir/values/values.xml
at com.android.aaptcompiler.ResourceCompiler.compileTable(ResourceCompiler.kt:179)
at com.android.aaptcompiler.ResourceCompiler.access$compileTable(ResourceCompiler.kt:1)
at com.android.aaptcompiler.ResourceCompiler$getCompileMethod$1.invoke(ResourceCompiler.kt:130)
at com.android.aaptcompiler.ResourceCompiler$getCompileMethod$1.invoke(ResourceCompiler.kt:130)
at com.android.aaptcompiler.ResourceCompiler.compileResource(ResourceCompiler.kt:116)
... 34 more
Caused by: java.lang.IllegalStateException: Can not extract resource from com.android.aaptcompiler.ParsedResource@247bbe22.,Can not extract resource from com.android.aaptcompiler.ParsedResource@255775e2.,Can not extract resource from com.android.aaptcompiler.ParsedResource@87786c9.,Can not extract resource from com.android.aaptcompiler.ParsedResource@460c8a21.
at com.android.aaptcompiler.TableExtractor.extractResourceValues(TableExtractor.kt:248)
at com.android.aaptcompiler.TableExtractor.extract(TableExtractor.kt:162)
at com.android.aaptcompiler.ResourceCompiler.compileTable(ResourceCompiler.kt:175)
... 38 more
Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/9.3.1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD FAILED in 935ms
24 actionable tasks: 5 executed, 19 up-to-date

View File

@@ -1,12 +1,4 @@
android.useAndroidX=true
android.enableJetifier=true
android.defaults.buildfeatures.resvalues=true
android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
android.enableAppCompileTimeRClass=false
android.usesSdkInManifest.disallowed=false
android.uniquePackageNames=false
android.dependency.useConstraints=true
android.r8.strictFullModeForKeepRules=false
android.r8.optimizedResourceShrinking=false
android.builtInKotlin=false
android.newDsl=false

View File

@@ -1,2 +1,11 @@
rootProject.name = "Fluxup"
include ':app'
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}