diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml
index e2f2258..f9eb024 100644
--- a/.idea/caches/deviceStreaming.xml
+++ b/.idea/caches/deviceStreaming.xml
@@ -184,6 +184,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -340,6 +352,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8855d45..c01ef9c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -32,8 +32,7 @@
-
-
+
diff --git a/app/src/main/java/com/fluxup/app/AvatarData.java b/app/src/main/java/com/fluxup/app/AvatarData.java
new file mode 100644
index 0000000..e4336a4
--- /dev/null
+++ b/app/src/main/java/com/fluxup/app/AvatarData.java
@@ -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() {}
+}
diff --git a/app/src/main/java/com/fluxup/app/AvatarEditorActivity.java b/app/src/main/java/com/fluxup/app/AvatarEditorActivity.java
new file mode 100644
index 0000000..8c83d81
--- /dev/null
+++ b/app/src/main/java/com/fluxup/app/AvatarEditorActivity.java
@@ -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();
+ });
+ }
+}
diff --git a/app/src/main/java/com/fluxup/app/AvatarView.java b/app/src/main/java/com/fluxup/app/AvatarView.java
new file mode 100644
index 0000000..9236194
--- /dev/null
+++ b/app/src/main/java/com/fluxup/app/AvatarView.java
@@ -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 ("Platina".equalsIgnoreCase(league) || "Platinum".equalsIgnoreCase(league)) {
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeWidth(Math.min(w, h) * 0.06f);
+ paint.setColor(Color.parseColor("#06B6D4")); // Platina Ciano
+ 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("#6366F1")); // Diamante Indigo/Violeta
+ 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));
+ }
+}
diff --git a/app/src/main/java/com/fluxup/app/CosmeticItem.java b/app/src/main/java/com/fluxup/app/CosmeticItem.java
new file mode 100644
index 0000000..8d1993f
--- /dev/null
+++ b/app/src/main/java/com/fluxup/app/CosmeticItem.java
@@ -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
+ }
+ }
+}
diff --git a/app/src/main/java/com/fluxup/app/FindFriendsActivity.java b/app/src/main/java/com/fluxup/app/FindFriendsActivity.java
index 1662b4a..6522b60 100644
--- a/app/src/main/java/com/fluxup/app/FindFriendsActivity.java
+++ b/app/src/main/java/com/fluxup/app/FindFriendsActivity.java
@@ -111,6 +111,12 @@ public class FindFriendsActivity extends AppCompatActivity {
tvName.setText(user.usuario);
tvStats.setText("Nível " + user.level + " • " + user.xp + " XP");
+ AvatarView ivAvatar = view.findViewById(R.id.ivFriendAvatar);
+ if (ivAvatar != null && user.avatar != null) {
+ ivAvatar.setAvatarData(user.avatar);
+ ivAvatar.setLeague(user.league);
+ }
+
btnAction.setOnClickListener(v -> {
btnAction.setText("Pendente");
btnAction.setEnabled(false);
diff --git a/app/src/main/java/com/fluxup/app/InicioFragment.java b/app/src/main/java/com/fluxup/app/InicioFragment.java
index c5a9eed..bfe92a4 100644
--- a/app/src/main/java/com/fluxup/app/InicioFragment.java
+++ b/app/src/main/java/com/fluxup/app/InicioFragment.java
@@ -40,6 +40,7 @@ public class InicioFragment extends Fragment {
private FocusState currentFocusState = FocusState.NOT_STARTED;
private TextView tvTimer, tvGreeting, tvMotivationalSubtitle, tvFocusStatus, tvFocusTitle;
+ private AvatarView ivUserAvatar;
private TextView tvTodayStreak, tvTodayXP, tvTodayTasksCount, tvNoTasksIncentive;
private TextView tvDailyRewardGoal;
private ProgressBar pbDailyTasksProgress;
@@ -90,6 +91,7 @@ public class InicioFragment extends Fragment {
// Header
tvGreeting = view.findViewById(R.id.tvGreeting);
tvMotivationalSubtitle = view.findViewById(R.id.tvMotivationalSubtitle);
+ ivUserAvatar = view.findViewById(R.id.ivUserAvatar);
view.findViewById(R.id.cardProfileAvatar).setOnClickListener(v -> {
if (getActivity() instanceof MainActivity) {
BottomNavigationView nav = ((MainActivity) getActivity()).findViewById(R.id.bottom_navigation);
@@ -193,6 +195,10 @@ public class InicioFragment extends Fragment {
if (tvGreeting != null) {
tvGreeting.setText("Olá, " + user.usuario + "!");
}
+ if (ivUserAvatar != null && user.avatar != null) {
+ ivUserAvatar.setAvatarData(user.avatar);
+ ivUserAvatar.setLeague(user.league);
+ }
refreshHomeStats();
@@ -535,23 +541,28 @@ public class InicioFragment extends Fragment {
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) {
Usuario u = doc.toObject(Usuario.class);
if (u != null) {
- addMiniRankingItem(rank++, u.usuario, u.xp_hoje);
+ addMiniRankingItem(rank++, u);
}
}
});
}
- private void addMiniRankingItem(int rank, String name, int xp) {
+ private void addMiniRankingItem(int rank, Usuario u) {
if (getContext() == null || miniRankingContainer == null) return;
View item = getLayoutInflater().inflate(R.layout.item_ranking_user, miniRankingContainer, false);
TextView tvRank = item.findViewById(R.id.tvRankPosition);
TextView tvName = item.findViewById(R.id.tvRankingName);
TextView tvXP = item.findViewById(R.id.tvRankingXP);
+ AvatarView ivAvatar = item.findViewById(R.id.ivRankingAvatar);
tvRank.setText("#" + rank);
- tvName.setText(name);
- tvXP.setText(xp + " XP");
+ tvName.setText(u.usuario);
+ tvXP.setText(u.xp_hoje + " XP");
+ if (ivAvatar != null && u.avatar != null) {
+ ivAvatar.setAvatarData(u.avatar);
+ ivAvatar.setLeague(u.league);
+ }
// Remove elevation for mini ranking to keep it clean
if (item instanceof androidx.cardview.widget.CardView) {
diff --git a/app/src/main/java/com/fluxup/app/MyFriendsActivity.java b/app/src/main/java/com/fluxup/app/MyFriendsActivity.java
index dd5c303..938cb3a 100644
--- a/app/src/main/java/com/fluxup/app/MyFriendsActivity.java
+++ b/app/src/main/java/com/fluxup/app/MyFriendsActivity.java
@@ -88,6 +88,12 @@ public class MyFriendsActivity extends AppCompatActivity {
tvLabel.setVisibility(View.VISIBLE);
tvLabel.setTextColor(getResources().getColor(R.color.primary_purple));
+ AvatarView ivAvatar = view.findViewById(R.id.ivRankingAvatar);
+ if (ivAvatar != null && friend.avatar != null) {
+ ivAvatar.setAvatarData(friend.avatar);
+ ivAvatar.setLeague(friend.league);
+ }
+
view.setOnClickListener(v -> {
// Open public profile (future)
Toast.makeText(this, "Perfil de " + friend.usuario, Toast.LENGTH_SHORT).show();
diff --git a/app/src/main/java/com/fluxup/app/ProfileFragment.java b/app/src/main/java/com/fluxup/app/ProfileFragment.java
index 9a5be47..29a5dc9 100644
--- a/app/src/main/java/com/fluxup/app/ProfileFragment.java
+++ b/app/src/main/java/com/fluxup/app/ProfileFragment.java
@@ -43,7 +43,8 @@ import java.util.Locale;
public class ProfileFragment extends Fragment {
private TextView tvProfileName, tvProfileHandle;
- private ImageView ivProfileAvatar, ivLeagueBadgeSmall, ivLeagueIcon;
+ private AvatarView ivProfileAvatar;
+ private ImageView ivLeagueBadgeSmall, ivLeagueIcon;
private TextView tvUserLevel, tvXpFraction, tvLeagueName, tvLeagueMotivation, tvConsistencyPhrase;
private ProgressBar pbXpLevel;
private RecyclerView rvMiniCalendar;
@@ -124,6 +125,12 @@ public class ProfileFragment extends Fragment {
});
}
+ // 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) {
@@ -182,6 +189,10 @@ public class ProfileFragment extends Fragment {
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) {
@@ -201,10 +212,10 @@ public class ProfileFragment extends Fragment {
int progressPercent = (int) ((progressInLevel * 100.0f) / levelXpSpan);
// Animate Progress Bar
- ObjectAnimator.ofInt(pbXpLevel, "progress", pbXpLevel.getProgress(), progressPercent)
- .setDuration(1000)
- .setInterpolator(new DecelerateInterpolator())
- .start();
+ ObjectAnimator animator = ObjectAnimator.ofInt(pbXpLevel, "progress", pbXpLevel.getProgress(), progressPercent);
+ animator.setDuration(1000);
+ animator.setInterpolator(new DecelerateInterpolator());
+ animator.start();
}
private int calculateLevel(int totalXp) {
@@ -422,6 +433,12 @@ public class ProfileFragment extends Fragment {
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);
diff --git a/app/src/main/java/com/fluxup/app/SearchFragment.java b/app/src/main/java/com/fluxup/app/SearchFragment.java
index 2b08c09..8427019 100644
--- a/app/src/main/java/com/fluxup/app/SearchFragment.java
+++ b/app/src/main/java/com/fluxup/app/SearchFragment.java
@@ -310,7 +310,10 @@ public class SearchFragment extends Fragment {
Usuario user = items.get(position);
holder.name.setText(user.usuario);
holder.stats.setText("🔥 " + user.streak + " dias • " + user.league);
- // holder.avatar setup if needed
+ 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());
}
@@ -318,7 +321,7 @@ public class SearchFragment extends Fragment {
public int getItemCount() { return items.size(); }
class ViewHolder extends RecyclerView.ViewHolder {
- ImageView avatar;
+ AvatarView avatar;
TextView name, stats;
View btnAdd;
ViewHolder(View v) {
diff --git a/app/src/main/java/com/fluxup/app/TrophiesActivity.java b/app/src/main/java/com/fluxup/app/TrophiesActivity.java
index bff931f..db36d0a 100644
--- a/app/src/main/java/com/fluxup/app/TrophiesActivity.java
+++ b/app/src/main/java/com/fluxup/app/TrophiesActivity.java
@@ -136,6 +136,11 @@ public class TrophiesActivity extends AppCompatActivity {
}
// 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) {
@@ -243,10 +248,15 @@ public class TrophiesActivity extends AppCompatActivity {
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);
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 (user.id_usuario.equals(myUid)) {
tvTu.setVisibility(View.VISIBLE);
diff --git a/app/src/main/java/com/fluxup/app/UnlockManager.java b/app/src/main/java/com/fluxup/app/UnlockManager.java
new file mode 100644
index 0000000..4057fb1
--- /dev/null
+++ b/app/src/main/java/com/fluxup/app/UnlockManager.java
@@ -0,0 +1,42 @@
+package com.fluxup.app;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class UnlockManager {
+ private static List items;
+
+ public static List 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);
+ }
+}
diff --git a/app/src/main/java/com/fluxup/app/Usuario.java b/app/src/main/java/com/fluxup/app/Usuario.java
index fa265b2..9add5a2 100644
--- a/app/src/main/java/com/fluxup/app/Usuario.java
+++ b/app/src/main/java/com/fluxup/app/Usuario.java
@@ -37,6 +37,7 @@ public class Usuario {
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 dias_concluidos = new java.util.ArrayList<>();
public Usuario() {}
diff --git a/app/src/main/res/drawable/chart_fill_gradient.xml b/app/src/main/res/drawable/chart_fill_gradient.xml
new file mode 100644
index 0000000..695896b
--- /dev/null
+++ b/app/src/main/res/drawable/chart_fill_gradient.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/xp_progress_bar.xml b/app/src/main/res/drawable/xp_progress_bar.xml
new file mode 100644
index 0000000..0c40a41
--- /dev/null
+++ b/app/src/main/res/drawable/xp_progress_bar.xml
@@ -0,0 +1,20 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_avatar_editor.xml b/app/src/main/res/layout/activity_avatar_editor.xml
new file mode 100644
index 0000000..c42ed2b
--- /dev/null
+++ b/app/src/main/res/layout/activity_avatar_editor.xml
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_trophies.xml b/app/src/main/res/layout/activity_trophies.xml
index 2e3fb6c..95b462b 100644
--- a/app/src/main/res/layout/activity_trophies.xml
+++ b/app/src/main/res/layout/activity_trophies.xml
@@ -197,12 +197,10 @@
android:layout_height="48dp"
app:cardCornerRadius="24dp"
app:cardElevation="0dp">
-
+ android:layout_height="match_parent" />
-
+ android:layout_height="match_parent" />
diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml
index 64c8751..5ba2ddf 100644
--- a/app/src/main/res/layout/fragment_profile.xml
+++ b/app/src/main/res/layout/fragment_profile.xml
@@ -50,12 +50,10 @@
android:layout_centerInParent="true"
app:cardCornerRadius="55dp"
app:cardElevation="8dp">
-
+ android:layout_height="match_parent" />
-
+ android:layout_height="match_parent" />
-
+ android:layout_height="match_parent" />
-
+ android:layout_height="match_parent" />
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_profile_stat_modern.xml b/app/src/main/res/layout/item_profile_stat_modern.xml
new file mode 100644
index 0000000..8ea0670
--- /dev/null
+++ b/app/src/main/res/layout/item_profile_stat_modern.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_ranking_user.xml b/app/src/main/res/layout/item_ranking_user.xml
index 4626714..057e572 100644
--- a/app/src/main/res/layout/item_ranking_user.xml
+++ b/app/src/main/res/layout/item_ranking_user.xml
@@ -31,12 +31,10 @@
android:layout_marginStart="8dp"
app:cardCornerRadius="20dp"
app:cardElevation="0dp">
-
+ android:layout_height="match_parent" />