Compare commits
17 Commits
d0216e424b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2fae2a058 | ||
|
|
03b36ec9fe | ||
|
|
9c9fae0e32 | ||
|
|
d357a9b220 | ||
|
|
01b54cd8d0 | ||
|
|
82b4b72d48 | ||
|
|
03074a881e | ||
|
|
2aa02bd434 | ||
|
|
d1a9183fbc | ||
|
|
851cd64f10 | ||
|
|
64f317c073 | ||
|
|
2c7d1a854f | ||
|
|
84dbdac0f2 | ||
|
|
ba523f16ad | ||
|
|
e38e791790 | ||
|
|
84e4a7f8b0 | ||
|
|
0fe31e3f65 |
367
.idea/caches/deviceStreaming.xml
generated
367
.idea/caches/deviceStreaming.xml
generated
@@ -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
1
.idea/misc.xml
generated
@@ -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">
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
26
app/src/main/java/com/fluxup/app/AvatarData.java
Normal file
26
app/src/main/java/com/fluxup/app/AvatarData.java
Normal 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() {}
|
||||
}
|
||||
253
app/src/main/java/com/fluxup/app/AvatarEditorActivity.java
Normal file
253
app/src/main/java/com/fluxup/app/AvatarEditorActivity.java
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
370
app/src/main/java/com/fluxup/app/AvatarView.java
Normal file
370
app/src/main/java/com/fluxup/app/AvatarView.java
Normal 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));
|
||||
}
|
||||
}
|
||||
40
app/src/main/java/com/fluxup/app/CosmeticItem.java
Normal file
40
app/src/main/java/com/fluxup/app/CosmeticItem.java
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
40
app/src/main/java/com/fluxup/app/DailyProgress.java
Normal file
40
app/src/main/java/com/fluxup/app/DailyProgress.java
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<>());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
50
app/src/main/java/com/fluxup/app/LeagueHelper.java
Normal file
50
app/src/main/java/com/fluxup/app/LeagueHelper.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
52
app/src/main/java/com/fluxup/app/NotificationHelper.java
Normal file
52
app/src/main/java/com/fluxup/app/NotificationHelper.java
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
966
app/src/main/java/com/fluxup/app/RewardsFragment.java
Normal file
966
app/src/main/java/com/fluxup/app/RewardsFragment.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
120
app/src/main/java/com/fluxup/app/StatisticsActivity.java
Normal file
120
app/src/main/java/com/fluxup/app/StatisticsActivity.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
85
app/src/main/java/com/fluxup/app/TasksAdapter.java
Normal file
85
app/src/main/java/com/fluxup/app/TasksAdapter.java
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
42
app/src/main/java/com/fluxup/app/UnlockManager.java
Normal file
42
app/src/main/java/com/fluxup/app/UnlockManager.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
9
app/src/main/res/drawable/boost_default_bg.xml
Normal file
9
app/src/main/res/drawable/boost_default_bg.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/boost_selected_bg.xml
Normal file
9
app/src/main/res/drawable/boost_selected_bg.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/button_secondary.xml
Normal file
11
app/src/main/res/drawable/button_secondary.xml
Normal 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>
|
||||
10
app/src/main/res/drawable/card_gradient_gold.xml
Normal file
10
app/src/main/res/drawable/card_gradient_gold.xml
Normal 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>
|
||||
10
app/src/main/res/drawable/card_gradient_purple.xml
Normal file
10
app/src/main/res/drawable/card_gradient_purple.xml
Normal 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>
|
||||
7
app/src/main/res/drawable/chart_fill_gradient.xml
Normal file
7
app/src/main/res/drawable/chart_fill_gradient.xml
Normal 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>
|
||||
5
app/src/main/res/drawable/circle_bg_light.xml
Normal file
5
app/src/main/res/drawable/circle_bg_light.xml
Normal 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>
|
||||
8
app/src/main/res/drawable/edittext_bg.xml
Normal file
8
app/src/main/res/drawable/edittext_bg.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_arrow_right.xml
Normal file
9
app/src/main/res/drawable/ic_arrow_right.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_nav_rewards.xml
Normal file
9
app/src/main/res/drawable/ic_nav_rewards.xml
Normal 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>
|
||||
10
app/src/main/res/drawable/ic_rocket.xml
Normal file
10
app/src/main/res/drawable/ic_rocket.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_trend_down.xml
Normal file
9
app/src/main/res/drawable/ic_trend_down.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_trend_up.xml
Normal file
9
app/src/main/res/drawable/ic_trend_up.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_trophy_diamond.xml
Normal file
9
app/src/main/res/drawable/ic_trophy_diamond.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_trophy_emerald.xml
Normal file
9
app/src/main/res/drawable/ic_trophy_emerald.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_trophy_legend.xml
Normal file
9
app/src/main/res/drawable/ic_trophy_legend.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_trophy_master.xml
Normal file
9
app/src/main/res/drawable/ic_trophy_master.xml
Normal 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>
|
||||
9
app/src/main/res/drawable/ic_trophy_platinum.xml
Normal file
9
app/src/main/res/drawable/ic_trophy_platinum.xml
Normal 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>
|
||||
6
app/src/main/res/drawable/task_selected_bg.xml
Normal file
6
app/src/main/res/drawable/task_selected_bg.xml
Normal 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>
|
||||
20
app/src/main/res/drawable/xp_progress_bar.xml
Normal file
20
app/src/main/res/drawable/xp_progress_bar.xml
Normal 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>
|
||||
201
app/src/main/res/layout/activity_avatar_editor.xml
Normal file
201
app/src/main/res/layout/activity_avatar_editor.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
133
app/src/main/res/layout/activity_statistics.xml
Normal file
133
app/src/main/res/layout/activity_statistics.xml
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
89
app/src/main/res/layout/dialog_add_task.xml
Normal file
89
app/src/main/res/layout/dialog_add_task.xml
Normal 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>
|
||||
|
||||
33
app/src/main/res/layout/dialog_duration_picker.xml
Normal file
33
app/src/main/res/layout/dialog_duration_picker.xml
Normal 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>
|
||||
45
app/src/main/res/layout/dialog_edit_profile.xml
Normal file
45
app/src/main/res/layout/dialog_edit_profile.xml
Normal 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>
|
||||
42
app/src/main/res/layout/dialog_level_up.xml
Normal file
42
app/src/main/res/layout/dialog_level_up.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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=">"
|
||||
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>
|
||||
|
||||
|
||||
487
app/src/main/res/layout/fragment_rewards.xml
Normal file
487
app/src/main/res/layout/fragment_rewards.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
61
app/src/main/res/layout/item_challenge.xml
Normal file
61
app/src/main/res/layout/item_challenge.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
104
app/src/main/res/layout/item_division_card.xml
Normal file
104
app/src/main/res/layout/item_division_card.xml
Normal 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>
|
||||
67
app/src/main/res/layout/item_featured_user.xml
Normal file
67
app/src/main/res/layout/item_featured_user.xml
Normal 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>
|
||||
63
app/src/main/res/layout/item_friend_action.xml
Normal file
63
app/src/main/res/layout/item_friend_action.xml
Normal 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>
|
||||
82
app/src/main/res/layout/item_friend_request.xml
Normal file
82
app/src/main/res/layout/item_friend_request.xml
Normal 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>
|
||||
@@ -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
|
||||
|
||||
25
app/src/main/res/layout/item_mini_calendar_day.xml
Normal file
25
app/src/main/res/layout/item_mini_calendar_day.xml
Normal 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>
|
||||
95
app/src/main/res/layout/item_mission.xml
Normal file
95
app/src/main/res/layout/item_mission.xml
Normal 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>
|
||||
45
app/src/main/res/layout/item_profile_stat_modern.xml
Normal file
45
app/src/main/res/layout/item_profile_stat_modern.xml
Normal 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>
|
||||
109
app/src/main/res/layout/item_ranking_user.xml
Normal file
109
app/src/main/res/layout/item_ranking_user.xml
Normal 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>
|
||||
58
app/src/main/res/layout/item_sequence_day.xml
Normal file
58
app/src/main/res/layout/item_sequence_day.xml
Normal 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>
|
||||
50
app/src/main/res/layout/item_settings_clickable.xml
Normal file
50
app/src/main/res/layout/item_settings_clickable.xml
Normal 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>
|
||||
32
app/src/main/res/layout/item_settings_switch.xml
Normal file
32
app/src/main/res/layout/item_settings_switch.xml
Normal 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>
|
||||
35
app/src/main/res/layout/item_stat_card.xml
Normal file
35
app/src/main/res/layout/item_stat_card.xml
Normal 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>
|
||||
44
app/src/main/res/layout/item_stat_small.xml
Normal file
44
app/src/main/res/layout/item_stat_small.xml
Normal 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>
|
||||
77
app/src/main/res/layout/item_store.xml
Normal file
77
app/src/main/res/layout/item_store.xml
Normal 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>
|
||||
68
app/src/main/res/layout/item_streak_reward.xml
Normal file
68
app/src/main/res/layout/item_streak_reward.xml
Normal 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>
|
||||
54
app/src/main/res/layout/item_task_home.xml
Normal file
54
app/src/main/res/layout/item_task_home.xml
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -10,13 +10,6 @@ buildscript {
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
5145
build_output.txt
5145
build_output.txt
File diff suppressed because it is too large
Load Diff
287
build_stacktrace.txt
Normal file
287
build_stacktrace.txt
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -1,2 +1,11 @@
|
||||
rootProject.name = "Fluxup"
|
||||
include ':app'
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user