adicionar loja

This commit is contained in:
MeuNome
2026-05-28 11:44:00 +01:00
parent 9c9fae0e32
commit 03b36ec9fe
11 changed files with 1895 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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