From 03b36ec9fe0a00d0406ad088f4c1da03c33a1378 Mon Sep 17 00:00:00 2001 From: MeuNome Date: Thu, 28 May 2026 11:44:00 +0100 Subject: [PATCH] adicionar loja --- .../java/com/fluxup/app/RewardsFragment.java | 966 ++++++++++++++++++ .../main/res/drawable/card_gradient_gold.xml | 10 + .../res/drawable/card_gradient_purple.xml | 10 + app/src/main/res/drawable/ic_nav_rewards.xml | 9 + .../res/layout/dialog_duration_picker.xml | 33 + app/src/main/res/layout/fragment_rewards.xml | 487 +++++++++ .../main/res/layout/item_friend_request.xml | 82 ++ app/src/main/res/layout/item_mission.xml | 95 ++ app/src/main/res/layout/item_sequence_day.xml | 58 ++ app/src/main/res/layout/item_store.xml | 77 ++ .../main/res/layout/item_streak_reward.xml | 68 ++ 11 files changed, 1895 insertions(+) create mode 100644 app/src/main/java/com/fluxup/app/RewardsFragment.java create mode 100644 app/src/main/res/drawable/card_gradient_gold.xml create mode 100644 app/src/main/res/drawable/card_gradient_purple.xml create mode 100644 app/src/main/res/drawable/ic_nav_rewards.xml create mode 100644 app/src/main/res/layout/dialog_duration_picker.xml create mode 100644 app/src/main/res/layout/fragment_rewards.xml create mode 100644 app/src/main/res/layout/item_friend_request.xml create mode 100644 app/src/main/res/layout/item_mission.xml create mode 100644 app/src/main/res/layout/item_sequence_day.xml create mode 100644 app/src/main/res/layout/item_store.xml create mode 100644 app/src/main/res/layout/item_streak_reward.xml diff --git a/app/src/main/java/com/fluxup/app/RewardsFragment.java b/app/src/main/java/com/fluxup/app/RewardsFragment.java new file mode 100644 index 0000000..730553e --- /dev/null +++ b/app/src/main/java/com/fluxup/app/RewardsFragment.java @@ -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 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 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 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 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 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 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 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 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 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 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 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; + } + } +} diff --git a/app/src/main/res/drawable/card_gradient_gold.xml b/app/src/main/res/drawable/card_gradient_gold.xml new file mode 100644 index 0000000..63d52e3 --- /dev/null +++ b/app/src/main/res/drawable/card_gradient_gold.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/app/src/main/res/drawable/card_gradient_purple.xml b/app/src/main/res/drawable/card_gradient_purple.xml new file mode 100644 index 0000000..0825ab5 --- /dev/null +++ b/app/src/main/res/drawable/card_gradient_purple.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_nav_rewards.xml b/app/src/main/res/drawable/ic_nav_rewards.xml new file mode 100644 index 0000000..3a70a5a --- /dev/null +++ b/app/src/main/res/drawable/ic_nav_rewards.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/dialog_duration_picker.xml b/app/src/main/res/layout/dialog_duration_picker.xml new file mode 100644 index 0000000..5827ae9 --- /dev/null +++ b/app/src/main/res/layout/dialog_duration_picker.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_rewards.xml b/app/src/main/res/layout/fragment_rewards.xml new file mode 100644 index 0000000..e7fc883 --- /dev/null +++ b/app/src/main/res/layout/fragment_rewards.xml @@ -0,0 +1,487 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +