diff --git a/.idea/misc.xml b/.idea/misc.xml index 74dd639..b2c751a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/app/build.gradle b/app/build.gradle index a61d9f9..afdc6dd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -61,4 +61,5 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + implementation 'com.github.PhilJay:MPAndroidChart:3.1.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 79dae6b..7492b45 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,6 +27,7 @@ + diff --git a/app/src/main/java/com/fluxup/app/FindFriendsActivity.java b/app/src/main/java/com/fluxup/app/FindFriendsActivity.java index cf2dbac..a556f9f 100644 --- a/app/src/main/java/com/fluxup/app/FindFriendsActivity.java +++ b/app/src/main/java/com/fluxup/app/FindFriendsActivity.java @@ -11,104 +11,108 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.google.android.material.button.MaterialButton; import java.util.ArrayList; import java.util.List; public class FindFriendsActivity extends AppCompatActivity { + private EditText etSearch; + private LinearLayout resultsContainer; + private com.google.android.material.tabs.TabLayout tabLayout; private ImageButton btnClose; - private RecyclerView rvSuggestions; - private View btnContacts, btnSearchByName, btnProfileLink; + private String currentTab = "Sugestões"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_find_friends); - btnClose = findViewById(R.id.btnClose); - rvSuggestions = findViewById(R.id.rvSuggestions); - btnContacts = findViewById(R.id.btnContacts); - btnSearchByName = findViewById(R.id.btnSearchByName); - btnProfileLink = findViewById(R.id.btnProfileLink); + initViews(); + setupListeners(); + loadSuggestions(); + } + private void initViews() { + etSearch = findViewById(R.id.etSearchFriends); + resultsContainer = findViewById(R.id.friendsResultsContainer); + tabLayout = findViewById(R.id.tabLayoutFriends); + btnClose = findViewById(R.id.btnClose); + } + + private void setupListeners() { btnClose.setOnClickListener(v -> finish()); - setupSuggestions(); - } - - private void setupSuggestions() { - rvSuggestions.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); - - List suggestions = new ArrayList<>(); - suggestions.add(new Suggestion("Maria Silva", "Segue você", R.color.reward_yellow)); - suggestions.add(new Suggestion("João Pereira", "Amigo em comum", R.color.success_green)); - suggestions.add(new Suggestion("Ana Costa", "Segue você", R.color.streak_orange)); - suggestions.add(new Suggestion("Ricardo M.", "Novo no Fluxup", R.color.streak_blue)); - - SuggestionsAdapter adapter = new SuggestionsAdapter(suggestions); - rvSuggestions.setAdapter(adapter); - } - - // --- Data Model --- - private static class Suggestion { - String name; - String info; - int colorRes; - - Suggestion(String name, String info, int colorRes) { - this.name = name; - this.info = info; - this.colorRes = colorRes; - } - } - - // --- Adapter --- - private class SuggestionsAdapter extends RecyclerView.Adapter { - private final List suggestions; - - SuggestionsAdapter(List suggestions) { - this.suggestions = suggestions; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_friend_suggestion, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - Suggestion item = suggestions.get(position); - holder.tvName.setText(item.name); - holder.tvInfo.setText(item.info); - holder.ivAvatar.setColorFilter(getResources().getColor(item.colorRes)); - - holder.btnFollow.setOnClickListener(v -> { - holder.btnFollow.setText("Seguindo"); - holder.btnFollow.setEnabled(false); - holder.btnFollow.setAlpha(0.6f); - }); - } - - @Override - public int getItemCount() { - return suggestions.size(); - } - - class ViewHolder extends RecyclerView.ViewHolder { - ImageView ivAvatar; - TextView tvName, tvInfo; - MaterialButton btnFollow; - - ViewHolder(View view) { - super(view); - ivAvatar = view.findViewById(R.id.ivFriendAvatar); - tvName = view.findViewById(R.id.tvFriendName); - tvInfo = view.findViewById(R.id.tvFriendInfo); - btnFollow = view.findViewById(R.id.btnFollowBack); + etSearch.addTextChangedListener(new android.text.TextWatcher() { + @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + @Override public void onTextChanged(CharSequence s, int start, int before, int count) { + if (s.length() > 0) { + performSearch(s.toString()); + } else { + loadSuggestions(); + } } - } + @Override public void afterTextChanged(android.text.Editable s) {} + }); + + tabLayout.addOnTabSelectedListener(new com.google.android.material.tabs.TabLayout.OnTabSelectedListener() { + @Override + public void onTabSelected(com.google.android.material.tabs.TabLayout.Tab tab) { + currentTab = tab.getText().toString(); + if (currentTab.equals("Sugestões")) loadSuggestions(); + else resultsContainer.removeAllViews(); // Placeholder for others + } + @Override public void onTabUnselected(com.google.android.material.tabs.TabLayout.Tab tab) {} + @Override public void onTabReselected(com.google.android.material.tabs.TabLayout.Tab tab) {} + }); + } + + private void performSearch(String query) { + com.google.firebase.firestore.FirebaseFirestore.getInstance() + .collection("users") + .orderBy("usuario") + .startAt(query) + .endAt(query + "\uf8ff") + .limit(10) + .get() + .addOnSuccessListener(snapshots -> { + resultsContainer.removeAllViews(); + for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) { + Usuario u = doc.toObject(Usuario.class); + if (u != null) addFriendItem(u); + } + }); + } + + private void loadSuggestions() { + com.google.firebase.firestore.FirebaseFirestore.getInstance() + .collection("users") + .limit(10) + .get() + .addOnSuccessListener(snapshots -> { + resultsContainer.removeAllViews(); + for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) { + Usuario u = doc.toObject(Usuario.class); + if (u != null) addFriendItem(u); + } + }); + } + + private void addFriendItem(Usuario user) { + View view = getLayoutInflater().inflate(R.layout.item_friend_action, resultsContainer, false); + TextView tvName = view.findViewById(R.id.tvFriendName); + TextView tvStats = view.findViewById(R.id.tvFriendStats); + Button btnAction = view.findViewById(R.id.btnFriendAction); + + tvName.setText(user.usuario); + tvStats.setText("Nível " + user.level + " • " + user.xp + " XP"); + + btnAction.setOnClickListener(v -> { + btnAction.setText("Pendente"); + btnAction.setEnabled(false); + btnAction.setAlpha(0.5f); + Toast.makeText(this, "Pedido enviado para " + user.usuario, Toast.LENGTH_SHORT).show(); + }); + + resultsContainer.addView(view); } } diff --git a/app/src/main/java/com/fluxup/app/FirestoreManager.java b/app/src/main/java/com/fluxup/app/FirestoreManager.java index 6822133..0b66ef6 100644 --- a/app/src/main/java/com/fluxup/app/FirestoreManager.java +++ b/app/src/main/java/com/fluxup/app/FirestoreManager.java @@ -70,6 +70,13 @@ public class FirestoreManager { db.collection("tasks").document(task.id).set(task); } + /** + * Elimina uma tarefa. + */ + public void deleteTask(String taskId) { + db.collection("tasks").document(taskId).delete(); + } + /** * Atualiza campos específicos do perfil do utilizador (ex: XP, Streak). */ diff --git a/app/src/main/java/com/fluxup/app/InicioFragment.java b/app/src/main/java/com/fluxup/app/InicioFragment.java index 5bf49dd..3382cc3 100644 --- a/app/src/main/java/com/fluxup/app/InicioFragment.java +++ b/app/src/main/java/com/fluxup/app/InicioFragment.java @@ -11,6 +11,7 @@ import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; +import android.widget.Button; import com.google.firebase.auth.FirebaseUser; import com.google.firebase.firestore.ListenerRegistration; import java.util.HashMap; @@ -31,76 +32,114 @@ import java.util.List; public class InicioFragment extends Fragment { - private TextView tvTimer, tvProgressText, tvGreeting; - private FrameLayout timerBlock; - private LinearLayout tasksContainer; - private ProgressBar pbDailyTasks; + private TextView tvTimer, tvGreeting, tvMotivationalSubtitle; + private TextView tvTodayStreak, tvTodayXP, tvTodayTasksCount, tvNoTasksIncentive; + private TextView tvDailyRewardGoal; + private ProgressBar pbDailyTasksProgress; + private LinearLayout tasksContainer, miniRankingContainer; + private Button btnStartFocus, btnAddTasks, btnClaimReward; + private CountDownTimer countDownTimer; private LinearLayout progressPathContainer; private List dayNodes = new ArrayList<>(); private int currentDayIndex = 0; - private ListenerRegistration tasksListener, userListener; + private ListenerRegistration tasksListener, userListener, rankingListener; private List currentTasks = new ArrayList<>(); + private Task selectedTaskForFocus = null; private boolean isTimerRunning = false; private long timeLeftInMillis = 25 * 60 * 1000; // 25 minutos + private String[] motivationalQuotes = { + "Pronto para vencer hoje?", + "Só precisas de começar.", + "Um passo de cada vez.", + "A consistência é a chave.", + "Foca no progresso, não na perfeição.", + "Hoje é um ótimo dia para ser produtivo!" + }; + @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_inicio, container, false); - tvTimer = view.findViewById(R.id.tvTimer); + // Header tvGreeting = view.findViewById(R.id.tvGreeting); - timerBlock = view.findViewById(R.id.timerBlock); + tvMotivationalSubtitle = view.findViewById(R.id.tvMotivationalSubtitle); + view.findViewById(R.id.cardProfileAvatar).setOnClickListener(v -> { + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).findViewById(R.id.nav_profile).performClick(); + } + }); + + // Today Card + tvTodayStreak = view.findViewById(R.id.tvTodayStreak); + tvTodayXP = view.findViewById(R.id.tvTodayXP); + tvTodayTasksCount = view.findViewById(R.id.tvTodayTasksCount); + pbDailyTasksProgress = view.findViewById(R.id.pbDailyTasksProgress); + + // Tasks tasksContainer = view.findViewById(R.id.tasksContainer); - tvProgressText = view.findViewById(R.id.tvProgressText); - pbDailyTasks = view.findViewById(R.id.pbDailyTasks); - progressPathContainer = view.findViewById(R.id.progressPathContainer); + tvNoTasksIncentive = view.findViewById(R.id.tvNoTasksIncentive); + btnAddTasks = view.findViewById(R.id.btnAddTasks); + btnAddTasks.setOnClickListener(v -> showAddTaskDialog()); - progressPathContainer = view.findViewById(R.id.progressPathContainer); + // Focus Mode + tvTimer = view.findViewById(R.id.tvTimer); + btnStartFocus = view.findViewById(R.id.btnStartFocus); + btnStartFocus.setOnClickListener(v -> { + if (!isTimerRunning) { + if (selectedTaskForFocus == null) { + Toast.makeText(getContext(), "Seleciona uma tarefa primeiro!", Toast.LENGTH_SHORT).show(); + return; + } + startTimer(); + } else { + pauseTimerWithWarning(); + } + }); + // Progress + progressPathContainer = view.findViewById(R.id.progressPathContainer); + + // Mini Ranking + miniRankingContainer = view.findViewById(R.id.miniRankingContainer); + view.findViewById(R.id.btnViewFullRanking).setOnClickListener(v -> { + startActivity(new android.content.Intent(getActivity(), TrophiesActivity.class)); + }); + + // Reward + tvDailyRewardGoal = view.findViewById(R.id.tvDailyRewardGoal); + btnClaimReward = view.findViewById(R.id.btnClaimReward); + btnClaimReward.setOnClickListener(v -> claimDailyReward()); + + setRandomMotivationalQuote(); initProgressPath(); startObservingTasks(); startObservingUser(); - - - View btnStartFocus = view.findViewById(R.id.btnStartFocus); - if (btnStartFocus != null) { - btnStartFocus.setOnClickListener(v -> { - if (!isTimerRunning) { - startTimer(); - ((TextView)btnStartFocus).setText("Pausar Foco"); - } else { - pauseTimer(); - ((TextView)btnStartFocus).setText("Continuar Foco"); - } - }); - } - - view.findViewById(R.id.btnAddTasks).setOnClickListener(v -> showAddTaskDialog()); - - View btnStreak = view.findViewById(R.id.btnStreak); - if (btnStreak != null) { - btnStreak.setOnClickListener(v -> { - android.content.Intent intent = new android.content.Intent(getActivity(), StreakActivity.class); - startActivity(intent); - }); - } + startObservingRanking(); updateCountDownText(); - return view; } + private void setRandomMotivationalQuote() { + int index = (int) (Math.random() * motivationalQuotes.length); + if (tvMotivationalSubtitle != null) { + tvMotivationalSubtitle.setText(motivationalQuotes[index]); + } + } + private void startObservingTasks() { FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); if (currentUser != null) { tasksListener = FirestoreManager.getInstance().observeTasks(currentUser.getUid(), tasks -> { currentTasks = tasks; updateTasksUI(); + updateTodayCard(); }); } } @@ -112,6 +151,23 @@ public class InicioFragment extends Fragment { if (tvGreeting != null) { tvGreeting.setText("Olá, " + user.usuario + "!"); } + if (tvTodayStreak != null) { + tvTodayStreak.setText(user.streak + " dias"); + } + if (tvTodayXP != null) { + tvTodayXP.setText(String.valueOf(user.xp_hoje)); + } + + // Update Reward Button State + if (user.tasks_concluidas_hoje >= user.meta_diaria_tarefas) { + btnClaimReward.setEnabled(true); + btnClaimReward.setAlpha(1.0f); + } else { + btnClaimReward.setEnabled(false); + btnClaimReward.setAlpha(0.5f); + } + + checkDailyReset(user); }); } } @@ -121,72 +177,144 @@ public class InicioFragment extends Fragment { tasksContainer.removeAllViews(); for (Task task : currentTasks) { - androidx.cardview.widget.CardView card = new androidx.cardview.widget.CardView(getContext()); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ); - params.setMargins(0, 0, 0, 16); - card.setLayoutParams(params); - card.setRadius(getResources().getDimension(R.dimen.radius_md)); - card.setCardElevation(2f); - card.setContentPadding(16, 16, 16, 16); - - LinearLayout layout = new LinearLayout(getContext()); - layout.setOrientation(LinearLayout.HORIZONTAL); - layout.setGravity(android.view.Gravity.CENTER_VERTICAL); - - CheckBox cb = new CheckBox(getContext()); - cb.setText(task.title); - cb.setTextColor(getResources().getColor(R.color.text_primary)); - cb.setTextSize(16); + View taskView = LayoutInflater.from(getContext()).inflate(R.layout.item_task_home, tasksContainer, false); + + TextView tvTitle = taskView.findViewById(R.id.tvTaskTitle); + TextView tvDuration = taskView.findViewById(R.id.tvTaskDuration); + CheckBox cb = taskView.findViewById(R.id.cbTask); + Button btnStart = taskView.findViewById(R.id.btnStartTaskFocus); + + tvTitle.setText(task.title); + tvDuration.setText(task.duration + " min"); cb.setChecked(task.completed); if (task.completed) { - cb.setTextColor(getResources().getColor(R.color.success_green)); + tvTitle.setPaintFlags(tvTitle.getPaintFlags() | android.graphics.Paint.STRIKE_THRU_TEXT_FLAG); + tvTitle.setTextColor(getResources().getColor(R.color.text_secondary)); + btnStart.setEnabled(false); + btnStart.setAlpha(0.5f); } cb.setOnCheckedChangeListener((buttonView, isChecked) -> { - task.completed = isChecked; - FirestoreManager.getInstance().updateTask(task); - // The observer will trigger updateTasksUI again, so we don't need manual UI update here - - if (isChecked) { - addXP(task.xpReward); + if (isChecked && !task.completed) { + completeTask(task); } }); - layout.addView(cb); - card.addView(layout); - tasksContainer.addView(card); + btnStart.setOnClickListener(v -> { + selectedTaskForFocus = task; + timeLeftInMillis = task.duration * 60 * 1000; + updateCountDownText(); + Toast.makeText(getContext(), "Tarefa selecionada: " + task.title, Toast.LENGTH_SHORT).show(); + + // Highlight selection + for (int i = 0; i < tasksContainer.getChildCount(); i++) { + tasksContainer.getChildAt(i).setBackground(null); + } + taskView.setBackgroundResource(R.drawable.task_selected_bg); + }); + + taskView.setOnLongClickListener(v -> { + showDeleteConfirmDialog(task); + return true; + }); + + tasksContainer.addView(taskView); } - updateProgress(); + updateTodayCard(); } private void addXP(int amount) { FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); if (currentUser != null) { + Map updates = new HashMap<>(); + updates.put("xp", com.google.firebase.firestore.FieldValue.increment(amount)); + updates.put("xp_hoje", com.google.firebase.firestore.FieldValue.increment(amount)); + + // Check Level Up logic inside observe once com.google.firebase.firestore.FirebaseFirestore.getInstance() - .collection("users").document(currentUser.getUid()) - .update("xp", com.google.firebase.firestore.FieldValue.increment(amount)); + .collection("users").document(currentUser.getUid()).get() + .addOnSuccessListener(snapshot -> { + Usuario user = snapshot.toObject(Usuario.class); + if (user != null) { + int currentLevel = user.level; + int nextLevelThreshold = currentLevel * 500; + if (user.xp + amount >= nextLevelThreshold) { + updates.put("level", currentLevel + 1); + showLevelUpAnimation(currentLevel + 1); + } + FirestoreManager.getInstance().updateUserStats(currentUser.getUid(), updates); + } + }); } } - private void updateProgress() { + private void updateTodayCard() { int total = currentTasks.size(); int completed = 0; for (Task task : currentTasks) { if (task.completed) completed++; } - if (tvProgressText != null) { - tvProgressText.setText(completed + " de " + total + " concluídos"); + if (tvTodayTasksCount != null) { + tvTodayTasksCount.setText(completed + "/" + (total == 0 ? 3 : total)); } - if (pbDailyTasks != null && total > 0) { - int progress = (completed * 100) / total; - pbDailyTasks.setProgress(progress); - updatePathProgress(progress); + + if (pbDailyTasksProgress != null) { + int progress = (total == 0) ? 0 : (completed * 100) / total; + pbDailyTasksProgress.setProgress(progress); } + + if (total == 0) { + tvNoTasksIncentive.setVisibility(View.VISIBLE); + } else { + tvNoTasksIncentive.setVisibility(View.GONE); + } + } + + private void checkDailyReset(Usuario user) { + String today = new java.text.SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new java.util.Date()); + if (!today.equals(user.last_active_date)) { + Map updates = new HashMap<>(); + if (user.tasks_concluidas_hoje == 0 && !user.last_active_date.isEmpty()) { + updates.put("streak", 0); + } + updates.put("xp_hoje", 0); + updates.put("tasks_concluidas_hoje", 0); + updates.put("tempo_foco_hoje", 0); + updates.put("last_active_date", today); + FirestoreManager.getInstance().updateUserStats(user.id_usuario, updates); + } + } + + private void startObservingRanking() { + rankingListener = com.google.firebase.firestore.FirebaseFirestore.getInstance() + .collection("users") + .orderBy("xp_hoje", com.google.firebase.firestore.Query.Direction.DESCENDING) + .limit(3) + .addSnapshotListener((snapshots, e) -> { + if (e != null || snapshots == null) return; + miniRankingContainer.removeAllViews(); + int rank = 1; + for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) { + Usuario u = doc.toObject(Usuario.class); + if (u != null) { + addMiniRankingItem(rank++, u.usuario, u.xp_hoje); + } + } + }); + } + + private void addMiniRankingItem(int rank, String name, int xp) { + View item = getLayoutInflater().inflate(android.R.layout.simple_list_item_2, miniRankingContainer, false); + TextView tv1 = item.findViewById(android.R.id.text1); + TextView tv2 = item.findViewById(android.R.id.text2); + tv1.setText(rank + ". " + name); + tv1.setTextSize(14); + tv1.setTextColor(getResources().getColor(R.color.text_primary)); + tv2.setText(xp + " XP hoje"); + tv2.setTextSize(12); + miniRankingContainer.addView(item); } private void initProgressPath() { @@ -246,20 +374,92 @@ public class InicioFragment extends Fragment { } } + private void completeTask(Task task) { + task.completed = true; + FirestoreManager.getInstance().updateTask(task); + addXP(30); + updateUserTaskCount(); + boolean allDone = true; + for (Task t : currentTasks) { + if (!t.completed && !t.id.equals(task.id)) { + allDone = false; + break; + } + } + if (allDone && currentTasks.size() > 0) { + addXP(20); + Toast.makeText(getContext(), "Todas as tarefas concluídas! +20 XP Bónus", Toast.LENGTH_LONG).show(); + } + triggerVibration(); + showXpPopup("+30 XP"); + } + + private void updateUserTaskCount() { + FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); + if (currentUser != null) { + Map updates = new HashMap<>(); + updates.put("tasks_concluidas_hoje", com.google.firebase.firestore.FieldValue.increment(1)); + updates.put("total_tasks_concluidas", com.google.firebase.firestore.FieldValue.increment(1)); + FirestoreManager.getInstance().updateUserStats(currentUser.getUid(), updates); + } + } + + private void showLevelUpAnimation(int newLevel) { + if (getContext() == null) return; + View dialogView = getLayoutInflater().inflate(R.layout.dialog_level_up, null); + TextView tvNewLevel = dialogView.findViewById(R.id.tvNewLevel); + tvNewLevel.setText(String.valueOf(newLevel)); + + androidx.appcompat.app.AlertDialog dialog = new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext()) + .setView(dialogView) + .setPositiveButton("Incrível!", null) + .create(); + + dialog.show(); + triggerVibration(); + NotificationHelper.showNotification(getContext(), "🎉 SUBISTE DE NÍVEL!", "Chegaste ao nível " + newLevel + "! Continua assim."); + } + + private void showXpPopup(String text) { + Toast.makeText(getContext(), text, Toast.LENGTH_SHORT).show(); + } + + private void triggerVibration() { + android.os.Vibrator v = (android.os.Vibrator) getContext().getSystemService(android.content.Context.VIBRATOR_SERVICE); + if (v != null) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + v.vibrate(android.os.VibrationEffect.createOneShot(100, android.os.VibrationEffect.DEFAULT_AMPLITUDE)); + } else { + v.vibrate(100); + } + } + } + + private void claimDailyReward() { + addXP(100); + btnClaimReward.setEnabled(false); + btnClaimReward.setText("Resgatado"); + Toast.makeText(getContext(), "Recompensa diária resgatada! +100 XP", Toast.LENGTH_LONG).show(); + } + + private void showDeleteConfirmDialog(Task task) { + new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext()) + .setTitle("Eliminar Tarefa") + .setMessage("Tens a certeza que queres eliminar esta tarefa?") + .setNegativeButton("Cancelar", null) + .setPositiveButton("Eliminar", (dialog, which) -> { + FirestoreManager.getInstance().deleteTask(task.id); + }) + .show(); + } + private void updatePathProgress(int progress) { if (currentDayIndex >= dayNodes.size()) return; - View todayNode = dayNodes.get(currentDayIndex); ProgressBar nodeProgress = todayNode.findViewById(R.id.nodeProgress); View nodeCircle = todayNode.findViewById(R.id.nodeCircle); - TextView nodeDayInitial = todayNode.findViewById(R.id.nodeDayInitial); - - if (nodeProgress != null) { - nodeProgress.setProgress(progress); - } - + if (nodeProgress != null) nodeProgress.setProgress(progress); if (progress == 100) { - // Task completion animation nodeCircle.getBackground().setTint(getResources().getColor(R.color.success_green)); triggerSuccessAnimation(todayNode); } else { @@ -282,31 +482,47 @@ public class InicioFragment extends Fragment { private void startTimer() { + if (selectedTaskForFocus == null) return; countDownTimer = new CountDownTimer(timeLeftInMillis, 1000) { @Override public void onTick(long millisUntilFinished) { timeLeftInMillis = millisUntilFinished; updateCountDownText(); } - @Override public void onFinish() { isTimerRunning = false; - if(getContext() != null) { - Toast.makeText(getContext(), "Foco concluído! +50 XP", Toast.LENGTH_LONG).show(); - addXP(50); + btnStartFocus.setText("Começar Foco"); + completeTask(selectedTaskForFocus); + addXP(50); + FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); + if (currentUser != null) { + Map updates = new HashMap<>(); + updates.put("tempo_foco_total", com.google.firebase.firestore.FieldValue.increment(selectedTaskForFocus.duration)); + updates.put("tempo_foco_hoje", com.google.firebase.firestore.FieldValue.increment(selectedTaskForFocus.duration)); + updates.put("sessoes_foco_completas", com.google.firebase.firestore.FieldValue.increment(1)); + FirestoreManager.getInstance().updateUserStats(currentUser.getUid(), updates); } + selectedTaskForFocus = null; } }.start(); - isTimerRunning = true; + btnStartFocus.setText("Pausar Foco"); } - private void pauseTimer() { - if (countDownTimer != null) { - countDownTimer.cancel(); - } - isTimerRunning = false; + private void pauseTimerWithWarning() { + new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext()) + .setTitle("Sair do Foco?") + .setMessage("Se saíres agora, não ganharás o XP de foco.") + .setNegativeButton("Continuar Focado", null) + .setPositiveButton("Sair", (dialog, which) -> { + if (countDownTimer != null) countDownTimer.cancel(); + isTimerRunning = false; + btnStartFocus.setText("Começar Foco"); + timeLeftInMillis = 25 * 60 * 1000; + updateCountDownText(); + }) + .show(); } @@ -321,85 +537,47 @@ public class InicioFragment extends Fragment { @Override public void onDestroyView() { super.onDestroyView(); - if (tasksListener != null) { - tasksListener.remove(); - } - if (userListener != null) { - userListener.remove(); - } - pauseTimer(); // Parar o timer se a view for destruída + if (tasksListener != null) tasksListener.remove(); + if (userListener != null) userListener.remove(); + if (rankingListener != null) rankingListener.remove(); + if (countDownTimer != null) countDownTimer.cancel(); } private void showAddTaskDialog() { - if (getContext() == null) return; - - // Build input field - android.widget.EditText editText = new android.widget.EditText(getContext()); - editText.setHint("Escreve o teu desafio…"); - editText.setInputType(android.text.InputType.TYPE_CLASS_TEXT - | android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); - editText.setTextSize(16); - editText.setSingleLine(false); - editText.setMaxLines(3); - - // Wrap with padding - android.widget.FrameLayout container = new android.widget.FrameLayout(getContext()); - android.widget.FrameLayout.LayoutParams lp = new android.widget.FrameLayout.LayoutParams( - android.widget.FrameLayout.LayoutParams.MATCH_PARENT, - android.widget.FrameLayout.LayoutParams.WRAP_CONTENT); - int dp24 = (int) (24 * getResources().getDisplayMetrics().density); - lp.setMargins(dp24, dp24 / 2, dp24, 0); - editText.setLayoutParams(lp); - container.addView(editText); + View dialogView = getLayoutInflater().inflate(R.layout.dialog_add_task, null); + android.widget.EditText etTitle = dialogView.findViewById(R.id.etTaskTitle); + android.widget.EditText etDuration = dialogView.findViewById(R.id.etTaskDuration); androidx.appcompat.app.AlertDialog dialog = new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext()) - .setTitle("Novo Desafio") - .setView(container) + .setTitle("Nova Tarefa") + .setView(dialogView) .setNegativeButton("Cancelar", null) - .setPositiveButton("Adicionar", null) + .setPositiveButton("Guardar", null) .create(); - dialog.setOnShowListener(d -> { - // Style buttons - android.widget.Button btnAdd = dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE); - android.widget.Button btnCancel = dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEGATIVE); - if (btnAdd != null) btnAdd.setTextColor(getResources().getColor(R.color.primary_purple)); - if (btnCancel != null) btnCancel.setTextColor(getResources().getColor(R.color.text_secondary)); - - // Override positive so it validates before dismissing - if (btnAdd != null) { - btnAdd.setOnClickListener(v -> { - String title = editText.getText().toString().trim(); - if (title.isEmpty()) { - editText.setError("Escreve um desafio"); - return; - } - saveNewTask(title); - dialog.dismiss(); - }); - } - - - editText.requestFocus(); - if (dialog.getWindow() != null) { - dialog.getWindow().setSoftInputMode( - android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - } + dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> { + String title = etTitle.getText().toString().trim(); + String durationStr = etDuration.getText().toString().trim(); + if (title.isEmpty()) { + etTitle.setError("Obrigatório"); + return; + } + int duration = 25; + if (!durationStr.isEmpty()) duration = Integer.parseInt(durationStr); + saveNewTask(title, duration); + dialog.dismiss(); + }); }); - dialog.show(); } - private void saveNewTask(String title) { - com.google.firebase.auth.FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); + private void saveNewTask(String title, int duration) { + FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); if (currentUser == null) return; - String uid = currentUser.getUid(); - String taskId = com.google.firebase.firestore.FirebaseFirestore.getInstance() - .collection("tasks").document().getId(); - - Task task = new Task(taskId, title, 10, uid); + String taskId = com.google.firebase.firestore.FirebaseFirestore.getInstance().collection("tasks").document().getId(); + Task task = new Task(taskId, title, 30, duration, uid); FirestoreManager.getInstance().addTask(task); } } diff --git a/app/src/main/java/com/fluxup/app/ProfileFragment.java b/app/src/main/java/com/fluxup/app/ProfileFragment.java index c6f6679..3e8f325 100644 --- a/app/src/main/java/com/fluxup/app/ProfileFragment.java +++ b/app/src/main/java/com/fluxup/app/ProfileFragment.java @@ -16,71 +16,153 @@ import com.google.firebase.firestore.ListenerRegistration; public class ProfileFragment extends Fragment { - private TextView tvUsername, tvHandle, tvStreakValue, tvTotalXP, tvLeagueName, tvAchievementsCount; - private ListenerRegistration userListener; + private TextView tvProfileName, tvProfileTitle, tvProfileBio; + private GridLayout badgesGrid; + private LinearLayout friendsListContainer; + private ListenerRegistration userListener, friendsListener; + private View view; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_profile, container, false); - ImageButton btnSettings = view.findViewById(R.id.btnSettings); - btnSettings.setOnClickListener(v -> { - Intent intent = new Intent(getActivity(), SettingsActivity.class); - startActivity(intent); - }); - - View btnInviteCard = view.findViewById(R.id.btnInviteCard); - btnInviteCard.setOnClickListener(v -> openFindFriends()); - - View btnInviteFriends = view.findViewById(R.id.btnInviteFriends); - btnInviteFriends.setOnClickListener(v -> openFindFriends()); - - // Initialize UI components - tvUsername = view.findViewById(R.id.tvUsername); - tvHandle = view.findViewById(R.id.tvHandle); - tvStreakValue = view.findViewById(R.id.tvStreakValue); - tvTotalXP = view.findViewById(R.id.tvTotalXP); - tvLeagueName = view.findViewById(R.id.tvLeagueName); - tvAchievementsCount = view.findViewById(R.id.tvAchievementsCount); - View cardLeague = view.findViewById(R.id.cardLeague); - cardLeague.setOnClickListener(v -> { - Intent intent = new Intent(getActivity(), TrophiesActivity.class); - startActivity(intent); - }); - + initViews(view); + setupListeners(view); startObservingUser(); + startObservingFriends(); return view; } + private void initViews(View view) { + tvProfileName = view.findViewById(R.id.tvProfileName); + tvProfileTitle = view.findViewById(R.id.tvProfileTitle); + tvProfileBio = view.findViewById(R.id.tvProfileBio); + badgesGrid = view.findViewById(R.id.badgesGrid); + friendsListContainer = view.findViewById(R.id.friendsListContainer); + } + + private void setupListeners(View view) { + view.findViewById(R.id.btnEditProfile).setOnClickListener(v -> { + startActivity(new Intent(getActivity(), SettingsActivity.class)); + }); + + view.findViewById(R.id.btnManageFriends).setOnClickListener(v -> { + startActivity(new Intent(getActivity(), FindFriendsActivity.class)); + }); + + view.findViewById(R.id.btnViewStatsDetails).setOnClickListener(v -> { + startActivity(new Intent(getActivity(), StatisticsActivity.class)); + }); + + view.findViewById(R.id.btnLogout).setOnClickListener(v -> { + AuthManager.getInstance().signOut(); + Intent intent = new Intent(getActivity(), LoginActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + }); + } + private void startObservingUser() { FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); if (currentUser != null) { - userListener = FirestoreManager.getInstance().observeUser(currentUser.getUid(), this::updateUI); + userListener = FirestoreManager.getInstance().observeUser(currentUser.getUid(), user -> { + if (getContext() == null || view == null) return; + updateStats(view, user); + tvProfileName.setText(user.usuario); + tvProfileBio.setText(user.bio.isEmpty() ? "Sem bio definida." : user.bio); + tvProfileTitle.setText(determineTitle(user)); + renderBadges(user); + }); } } - private void updateUI(Usuario user) { - if (getContext() == null) return; - tvUsername.setText(user.usuario); - tvHandle.setText(user.handle); - tvStreakValue.setText(String.valueOf(user.streak)); - tvTotalXP.setText(String.valueOf(user.xp)); - tvLeagueName.setText(user.league); - tvAchievementsCount.setText(String.valueOf(user.achievementsCount)); + private void updateStats(View view, Usuario user) { + setStat(view.findViewById(R.id.statXP), "⚡", String.valueOf(user.xp), "Total XP"); + setStat(view.findViewById(R.id.statStreak), "🔥", String.valueOf(user.streak), "Ofensiva"); + setStat(view.findViewById(R.id.statLevel), "⭐", String.valueOf(user.level), "Nível"); + setStat(view.findViewById(R.id.statTasks), "✅", String.valueOf(user.total_tasks_concluidas), "Tarefas"); + setStat(view.findViewById(R.id.statFocusTime), "⏱️", user.tempo_foco_total + "m", "Foco"); + setStat(view.findViewById(R.id.statSessions), "🎯", String.valueOf(user.sessoes_foco_completas), "Sessões"); + } + + private void setStat(View statView, String icon, String value, String label) { + if (statView == null) return; + ((TextView) statView.findViewById(R.id.tvStatIcon)).setText(icon); + ((TextView) statView.findViewById(R.id.tvStatValue)).setText(value); + ((TextView) statView.findViewById(R.id.tvStatLabel)).setText(label); + } + + private String determineTitle(Usuario user) { + if (user.level > 50) return "Mestre do Foco"; + if (user.level > 20) return "Guerreiro Produtivo"; + if (user.level > 10) return "Focado Regular"; + return "Iniciante de Foco"; + } + + private void renderBadges(Usuario user) { + badgesGrid.removeAllViews(); + if (user.streak >= 7) addBadge("🔥", "Semana Ativa"); + if (user.total_tasks_concluidas >= 50) addBadge("🏆", "Executor"); + if (user.tempo_foco_total >= 600) addBadge("🧘", "Zen"); + if (user.level >= 5) addBadge("⭐", "Nível 5"); + } + + private void addBadge(String emoji, String name) { + LinearLayout badge = new LinearLayout(getContext()); + badge.setOrientation(LinearLayout.VERTICAL); + badge.setGravity(android.view.Gravity.CENTER); + badge.setPadding(8, 8, 8, 8); + + TextView tvEmoji = new TextView(getContext()); + tvEmoji.setText(emoji); + tvEmoji.setTextSize(24); + + TextView tvName = new TextView(getContext()); + tvName.setText(name); + tvName.setTextSize(8); + tvName.setGravity(android.view.Gravity.CENTER); + + badge.addView(tvEmoji); + badge.addView(tvName); + badgesGrid.addView(badge); + } + + private void startObservingFriends() { + friendsListener = com.google.firebase.firestore.FirebaseFirestore.getInstance() + .collection("users") + .limit(3) + .addSnapshotListener((snapshots, e) -> { + if (e != null || snapshots == null) return; + friendsListContainer.removeAllViews(); + for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) { + Usuario f = doc.toObject(Usuario.class); + if (f != null && !f.id_usuario.equals(AuthManager.getInstance().getCurrentUser().getUid())) { + addFriendItem(f); + } + } + }); + } + + private void addFriendItem(Usuario friend) { + View view = getLayoutInflater().inflate(R.layout.item_ranking_user, friendsListContainer, false); + ((TextView) view.findViewById(R.id.tvRankingName)).setText(friend.usuario); + ((TextView) view.findViewById(R.id.tvRankingXP)).setText(friend.xp + " XP"); + view.findViewById(R.id.ivRankingTrend).setVisibility(View.GONE); + friendsListContainer.addView(view); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + this.view = view; } @Override public void onDestroyView() { super.onDestroyView(); - if (userListener != null) { - userListener.remove(); - } - } - - private void openFindFriends() { - Intent intent = new Intent(getActivity(), FindFriendsActivity.class); - startActivity(intent); + if (userListener != null) userListener.remove(); + if (friendsListener != null) friendsListener.remove(); } } diff --git a/app/src/main/java/com/fluxup/app/SettingsActivity.java b/app/src/main/java/com/fluxup/app/SettingsActivity.java index 66d5189..4c2a49d 100644 --- a/app/src/main/java/com/fluxup/app/SettingsActivity.java +++ b/app/src/main/java/com/fluxup/app/SettingsActivity.java @@ -15,155 +15,101 @@ import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatDelegate; +import androidx.appcompat.app.AlertDialog; import com.google.android.material.switchmaterial.SwitchMaterial; import com.google.firebase.auth.FirebaseAuth; -import com.google.firebase.auth.FirebaseUser; -import java.util.Locale; public class SettingsActivity extends AppCompatActivity { - private ImageButton btnBack; - private SwitchMaterial switchDarkMode, switchPrivacy, switchNotifications; - private TextView tvEmail, btnChangePassword; - private Spinner spinnerLanguage; - private View btnLogout; private SharedPreferences sharedPreferences; private static final String PREFS_NAME = "FluxupSettings"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - sharedPreferences = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - - // Aplicar o idioma guardado antes de carregar o layout - String lang = sharedPreferences.getString("language", "pt"); - updateLocaleSilent(lang); - - // Aplicar tema guardado - applyTheme(sharedPreferences.getBoolean("darkMode", false)); - setContentView(R.layout.activity_settings); - initViews(); - setupListeners(); - loadSettings(); + initSettings(); + findViewById(R.id.btnBack).setOnClickListener(v -> finish()); } - private void applyTheme(boolean isDarkMode) { - if (isDarkMode) { - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); - } else { - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); + private void initSettings() { + // --- CONTA --- + setupClickable(R.id.settingEmail, "Email", FirebaseAuth.getInstance().getCurrentUser().getEmail(), null); + setupClickable(R.id.settingPassword, "Alterar Palavra-passe", "********", v -> resetPassword()); + setupSwitch(R.id.settingPublicProfile, "Perfil Público", "public_profile", true); + + // --- APARÊNCIA --- + setupSwitch(R.id.settingDarkMode, "Modo Escuro", "darkMode", false, (v, isChecked) -> { + AppCompatDelegate.setDefaultNightMode(isChecked ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO); + }); + setupClickable(R.id.settingThemeColor, "Cor do Tema", "Roxo (Padrão)", null); + + // --- NOTIFICAÇÕES --- + setupSwitch(R.id.settingDailyReminders, "Lembretes Diários", "daily_reminders", true); + setupSwitch(R.id.settingAntiProcrastination, "Anti-Procrastinação", "anti_procrastination", true); + + // --- FOCO --- + setupClickable(R.id.settingFocusDuration, "Duração do Foco", sharedPreferences.getInt("focus_duration", 25) + " min", v -> showDurationDialog("focus_duration", "Duração do Foco", 25)); + setupClickable(R.id.settingBreakDuration, "Duração da Pausa", sharedPreferences.getInt("break_duration", 5) + " min", v -> showDurationDialog("break_duration", "Duração da Pausa", 5)); + + // --- PRIVACIDADE --- + setupSwitch(R.id.settingIncognito, "Modo Incógnito", "incognito", false); + setupClickable(R.id.settingExportData, "Exportar Dados", "JSON/CSV", null); + + findViewById(R.id.btnDeleteAccount).setOnClickListener(v -> { + Toast.makeText(this, "Funcionalidade disponível em breve", Toast.LENGTH_SHORT).show(); + }); + } + + private void setupClickable(int id, String title, String value, View.OnClickListener listener) { + View view = findViewById(id); + if (view == null) return; + ((TextView) view.findViewById(R.id.tvSettingTitle)).setText(title); + ((TextView) view.findViewById(R.id.tvSettingValue)).setText(value); + if (listener != null) view.setOnClickListener(listener); + else view.findViewById(R.id.ivChevron).setVisibility(View.GONE); + } + + private void setupSwitch(int id, String title, String key, boolean defValue) { + setupSwitch(id, title, key, defValue, null); + } + + private void setupSwitch(int id, String title, String key, boolean defValue, android.widget.CompoundButton.OnCheckedChangeListener extraListener) { + View view = findViewById(id); + if (view == null) return; + ((TextView) view.findViewById(R.id.tvSettingTitle)).setText(title); + SwitchMaterial sw = view.findViewById(R.id.switchSetting); + sw.setChecked(sharedPreferences.getBoolean(key, defValue)); + sw.setOnCheckedChangeListener((v, isChecked) -> { + sharedPreferences.edit().putBoolean(key, isChecked).apply(); + if (extraListener != null) extraListener.onCheckedChanged(v, isChecked); + }); + } + + private void resetPassword() { + String email = FirebaseAuth.getInstance().getCurrentUser().getEmail(); + if (email != null) { + FirebaseAuth.getInstance().sendPasswordResetEmail(email) + .addOnSuccessListener(aVoid -> Toast.makeText(this, "Email enviado!", Toast.LENGTH_SHORT).show()); } } - private void initViews() { - btnBack = findViewById(R.id.btnBack); - switchDarkMode = findViewById(R.id.switchDarkMode); - switchPrivacy = findViewById(R.id.switchPrivacy); - switchNotifications = findViewById(R.id.switchNotifications); - tvEmail = findViewById(R.id.tvEmail); - btnChangePassword = findViewById(R.id.btnChangePassword); - spinnerLanguage = findViewById(R.id.spinnerLanguage); - btnLogout = findViewById(R.id.btnLogout); + private void showDurationDialog(String key, String title, int current) { + EditText input = new EditText(this); + input.setInputType(android.text.InputType.TYPE_CLASS_NUMBER); + input.setText(String.valueOf(sharedPreferences.getInt(key, current))); - // Configurar Spinner de Idiomas - String[] languages = {"Português", "English", "Español"}; - ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, languages); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinnerLanguage.setAdapter(adapter); - - // Mostrar email do utilizador - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - if (user != null) { - tvEmail.setText(user.getEmail()); - } - } - - private void setupListeners() { - btnBack.setOnClickListener(v -> finish()); - - switchDarkMode.setOnCheckedChangeListener((buttonView, isChecked) -> { - applyTheme(isChecked); - saveSetting("darkMode", isChecked); - }); - - switchPrivacy.setOnCheckedChangeListener((buttonView, isChecked) -> saveSetting("privacy", isChecked)); - switchNotifications.setOnCheckedChangeListener((buttonView, isChecked) -> saveSetting("notifications", isChecked)); - - btnLogout.setOnClickListener(v -> { - FirebaseAuth.getInstance().signOut(); - Intent intent = new Intent(SettingsActivity.this, LoginActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivity(intent); - finish(); - }); - - spinnerLanguage.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - String selectedLang = "pt"; - if (position == 1) selectedLang = "en"; - else if (position == 2) selectedLang = "es"; - - String currentLang = sharedPreferences.getString("language", "pt"); - if (!selectedLang.equals(currentLang)) { - saveSetting("language", selectedLang); - updateLocale(selectedLang); - } - } - - @Override - public void onNothingSelected(AdapterView parent) {} - }); - - btnChangePassword.setOnClickListener(v -> { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - if (user != null && user.getEmail() != null) { - FirebaseAuth.getInstance().sendPasswordResetEmail(user.getEmail()) - .addOnCompleteListener(task -> { - if (task.isSuccessful()) { - Toast.makeText(this, "Email de redefinição enviado!", Toast.LENGTH_SHORT).show(); - } - }); - } - }); - } - - private void loadSettings() { - switchDarkMode.setChecked(sharedPreferences.getBoolean("darkMode", false)); - switchPrivacy.setChecked(sharedPreferences.getBoolean("privacy", false)); - switchNotifications.setChecked(sharedPreferences.getBoolean("notifications", true)); - - String lang = sharedPreferences.getString("language", "pt"); - if (lang.equals("en")) spinnerLanguage.setSelection(1, false); - else if (lang.equals("es")) spinnerLanguage.setSelection(2, false); - else spinnerLanguage.setSelection(0, false); - } - - private void updateLocaleSilent(String langCode) { - Locale locale = new Locale(langCode); - Locale.setDefault(locale); - Resources resources = getResources(); - Configuration config = resources.getConfiguration(); - config.setLocale(locale); - resources.updateConfiguration(config, resources.getDisplayMetrics()); - } - - private void updateLocale(String langCode) { - updateLocaleSilent(langCode); - Intent intent = new Intent(this, SettingsActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - finish(); - } - - private void saveSetting(String key, boolean value) { - sharedPreferences.edit().putBoolean(key, value).apply(); - } - - private void saveSetting(String key, String value) { - sharedPreferences.edit().putString(key, value).apply(); + new AlertDialog.Builder(this) + .setTitle(title) + .setView(input) + .setPositiveButton("Guardar", (dialog, which) -> { + int val = Integer.parseInt(input.getText().toString()); + sharedPreferences.edit().putInt(key, val).apply(); + recreate(); + }) + .setNegativeButton("Cancelar", null) + .show(); } } diff --git a/app/src/main/java/com/fluxup/app/Task.java b/app/src/main/java/com/fluxup/app/Task.java index 476ea5a..8620e48 100644 --- a/app/src/main/java/com/fluxup/app/Task.java +++ b/app/src/main/java/com/fluxup/app/Task.java @@ -5,15 +5,18 @@ public class Task { public String title; public boolean completed; public int xpReward; + public int duration; // em minutos public String userId; + public Long completedDate; public Task() {} - public Task(String id, String title, int xpReward, String userId) { + public Task(String id, String title, int xpReward, int duration, String userId) { this.id = id; this.title = title; this.completed = false; this.xpReward = xpReward; + this.duration = duration; this.userId = userId; } } diff --git a/app/src/main/java/com/fluxup/app/TrophiesActivity.java b/app/src/main/java/com/fluxup/app/TrophiesActivity.java index 13ed051..396f1c5 100644 --- a/app/src/main/java/com/fluxup/app/TrophiesActivity.java +++ b/app/src/main/java/com/fluxup/app/TrophiesActivity.java @@ -14,14 +14,16 @@ import com.google.firebase.firestore.ListenerRegistration; public class TrophiesActivity extends AppCompatActivity { - private TextView tvDivisionTitle, tvTimeRemaining, tvTrophyProgress, tvMotivational; + private TextView tvLeagueName, tvLeagueTimeRemaining, tvLeagueObjective; + private TextView tvUserRankingStatus, tvXPToNextPosition, tvPastPerformance; + private ProgressBar pbWeeklyProgress; + private LinearLayout rankingContainer; private ImageButton btnBack; - private LinearLayout trophyContainer, inactiveState; - private ImageView trophy1, trophy2, trophy3; + private Button btnEarnXpNow; private FirestoreManager firestoreManager; private FirebaseAuth mAuth; - private ListenerRegistration userListener; + private ListenerRegistration rankingListener, userListener; @Override protected void onCreate(Bundle savedInstanceState) { @@ -34,107 +36,162 @@ public class TrophiesActivity extends AppCompatActivity { initViews(); setupListeners(); observeUserData(); + observeRanking("global"); } private void initViews() { - tvDivisionTitle = findViewById(R.id.tvDivisionTitle); - tvTimeRemaining = findViewById(R.id.tvTimeRemaining); - tvTrophyProgress = findViewById(R.id.tvTrophyProgress); - tvMotivational = findViewById(R.id.tvMotivational); + tvLeagueName = findViewById(R.id.tvLeagueName); + tvLeagueTimeRemaining = findViewById(R.id.tvLeagueTimeRemaining); + tvLeagueObjective = findViewById(R.id.tvLeagueObjective); + tvUserRankingStatus = findViewById(R.id.tvUserRankingStatus); + tvXPToNextPosition = findViewById(R.id.tvXPToNextPosition); + tvPastPerformance = findViewById(R.id.tvPastPerformance); + pbWeeklyProgress = findViewById(R.id.pbWeeklyProgress); + rankingContainer = findViewById(R.id.rankingContainer); btnBack = findViewById(R.id.btnBack); - trophyContainer = findViewById(R.id.trophyContainer); - inactiveState = findViewById(R.id.inactiveState); - trophy1 = findViewById(R.id.trophy1); - trophy2 = findViewById(R.id.trophy2); - trophy3 = findViewById(R.id.trophy3); + btnEarnXpNow = findViewById(R.id.btnEarnXpNow); + + updateTimeRemaining(); } private void setupListeners() { btnBack.setOnClickListener(v -> finish()); + btnEarnXpNow.setOnClickListener(v -> finish()); // Go back to Home + + findViewById(R.id.btnRankingGlobal).setOnClickListener(v -> observeRanking("global")); + findViewById(R.id.btnRankingAmigos).setOnClickListener(v -> observeRanking("friends")); + } + + private void updateTimeRemaining() { + // Simple logic for week end (Sunday 23:59) + Calendar now = Calendar.getInstance(); + int daysLeft = Calendar.SUNDAY - now.get(Calendar.DAY_OF_WEEK); + if (daysLeft < 0) daysLeft += 7; + int hoursLeft = 23 - now.get(Calendar.HOUR_OF_DAY); + tvLeagueTimeRemaining.setText(daysLeft + " dias " + hoursLeft + "h restantes"); + + int progress = (7 - daysLeft) * 100 / 7; + pbWeeklyProgress.setProgress(progress); } private void observeUserData() { String uid = mAuth.getUid(); if (uid != null) { - userListener = firestoreManager.observeUser(uid, this::updateUI); + userListener = firestoreManager.observeUser(uid, this::updateUserUI); } } - private void updateUI(Usuario user) { + private void updateUserUI(Usuario user) { if (user == null) return; + tvLeagueName.setText("Divisão " + user.league); + tvLeagueObjective.setText("Fica no TOP 3 para subir para " + getNextLeague(user.league)); + } - tvDivisionTitle.setText("Divisão " + user.league); - - // Trophy logic based on streak - int currentStreak = user.streak; - int trophies = user.trophiesCount; - - // Update trophy visuals - updateTrophyIcons(trophies); + private String getNextLeague(String current) { + switch (current) { + case "Bronze": return "Prata"; + case "Prata": return "Ouro"; + case "Ouro": return "Platina"; + default: return "Diamante"; + } + } - // Progress message - int daysToNext = 30 - (currentStreak % 30); - if (daysToNext == 30 && currentStreak > 0) { - tvTrophyProgress.setText("Troféu conquistado! Mantém a ofensiva."); + private void observeRanking(String filter) { + if (rankingListener != null) rankingListener.remove(); + + com.google.firebase.firestore.Query query = com.google.firebase.firestore.FirebaseFirestore.getInstance() + .collection("users") + .orderBy("xp_semanal", com.google.firebase.firestore.Query.Direction.DESCENDING) + .limit(20); + + rankingListener = query.addSnapshotListener((snapshots, e) -> { + if (e != null || snapshots == null) return; + rankingContainer.removeAllViews(); + + int position = 1; + String myUid = mAuth.getUid(); + Usuario me = null; + Usuario next = null; + int myPos = -1; + + for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) { + Usuario user = doc.toObject(Usuario.class); + if (user == null) continue; + + if (user.id_usuario.equals(myUid)) { + me = user; + myPos = position; + } else if (me == null) { + next = user; // The one above me + } + + addRankingItem(position++, user, myUid); + + // Add zone separators + if (position == 4) addZoneSeparator("🟢 ZONA DE PROMOÇÃO", R.color.success_green); + if (position == 11) addZoneSeparator("⚪ ZONA DE MANUTENÇÃO", R.color.text_secondary); + if (position == 16) addZoneSeparator("🔴 ZONA DE DESPROMOÇÃO", R.color.error_red); + } + + if (me != null) { + updateUserProgressCard(myPos, me, next); + } + }); + } + + private void addRankingItem(int pos, Usuario user, String myUid) { + View view = getLayoutInflater().inflate(R.layout.item_ranking_user, rankingContainer, false); + + TextView tvPos = view.findViewById(R.id.tvRankPosition); + TextView tvName = view.findViewById(R.id.tvRankingName); + TextView tvXP = view.findViewById(R.id.tvRankingXP); + TextView tvTu = view.findViewById(R.id.tvRankingLabelTu); + ImageView ivAvatar = view.findViewById(R.id.ivRankingAvatar); + androidx.cardview.widget.CardView card = view.findViewById(R.id.cardRankingUser); + + tvPos.setText("#" + pos); + tvName.setText(user.usuario); + tvXP.setText(user.xp_semanal + " XP"); + + if (user.id_usuario.equals(myUid)) { + tvTu.setVisibility(View.VISIBLE); + card.setCardBackgroundColor(getResources().getColor(R.color.background_light)); + card.setCardElevation(4f); + } + + rankingContainer.addView(view); + } + + private void addZoneSeparator(String text, int colorRes) { + TextView tv = new TextView(this); + tv.setText(text); + tv.setTextSize(10); + tv.setPadding(0, 24, 0, 8); + tv.setTextColor(getResources().getColor(colorRes)); + tv.setGravity(android.view.Gravity.CENTER); + tv.setAlpha(0.7f); + rankingContainer.addView(tv); + } + + private void updateUserProgressCard(int myPos, Usuario me, Usuario aboveMe) { + if (myPos <= 3) { + tvUserRankingStatus.setText("Estás na zona de promoção! 🎉"); + tvXPToNextPosition.setText("Mantém o foco para subir de liga."); } else { - tvTrophyProgress.setText("Faltam " + daysToNext + " dias para o próximo troféu"); - } - - // Handle inactive state - if (currentStreak == 0) { - inactiveState.setVisibility(View.VISIBLE); - trophyContainer.setAlpha(0.5f); - tvMotivational.setVisibility(View.GONE); - } else { - inactiveState.setVisibility(View.GONE); - trophyContainer.setAlpha(1.0f); - tvMotivational.setVisibility(View.VISIBLE); - tvMotivational.setText("Estás a progredir bem na Divisão " + user.league + "!"); - } - - // Simple scale animation for the active trophy - animateActiveTrophy(trophies); - } - - private void updateTrophyIcons(int count) { - // Reset alphas - trophy1.setAlpha(0.3f); - trophy2.setAlpha(0.3f); - trophy3.setAlpha(0.3f); - - if (count >= 1) trophy1.setAlpha(1.0f); - if (count >= 2) trophy2.setAlpha(1.0f); - if (count >= 3) trophy3.setAlpha(1.0f); - - // Highlight current progress (the next one) - if (count == 0) highlightTrophy(trophy1); - else if (count == 1) highlightTrophy(trophy2); - else if (count == 2) highlightTrophy(trophy3); - } - - private void highlightTrophy(ImageView trophy) { - trophy.setAlpha(0.6f); - trophy.setBackgroundResource(R.drawable.circle_bg); - trophy.setBackgroundTintList(android.content.res.ColorStateList.valueOf(getResources().getColor(R.color.primary_purple))); - trophy.setPadding(12, 12, 12, 12); - } - - private void animateActiveTrophy(int count) { - ImageView target = null; - if (count == 0) target = trophy1; - else if (count == 1) target = trophy2; - else if (count == 2) target = trophy3; - - if (target != null) { - ScaleAnimation scale = new ScaleAnimation(1f, 1.1f, 1f, 1.1f, - Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); - scale.setDuration(1000); - scale.setRepeatCount(Animation.INFINITE); - scale.setRepeatMode(Animation.REVERSE); - target.startAnimation(scale); + int needed = (aboveMe != null) ? (aboveMe.xp_semanal - me.xp_semanal + 1) : 0; + tvUserRankingStatus.setText("Faltam " + needed + " XP para subires de posição"); + tvXPToNextPosition.setText("Estás em #" + myPos); } } + @Override + protected void onDestroy() { + super.onDestroy(); + if (rankingListener != null) rankingListener.remove(); + if (userListener != null) userListener.remove(); + } +} + @Override protected void onDestroy() { super.onDestroy(); diff --git a/app/src/main/java/com/fluxup/app/Usuario.java b/app/src/main/java/com/fluxup/app/Usuario.java index 18f2664..46fc673 100644 --- a/app/src/main/java/com/fluxup/app/Usuario.java +++ b/app/src/main/java/com/fluxup/app/Usuario.java @@ -11,13 +11,28 @@ public class Usuario { public String updated_at; // Stats and Social - public int xp = 0; + public int level = 1; + public int xp = 0; // Total XP + public int xp_hoje = 0; + public int xp_semanal = 0; public int streak = 0; + public int melhor_streak = 0; public String league = "Bronze"; public String handle = ""; public String bio = ""; + public String titulo = "Iniciante"; public int achievementsCount = 0; public int trophiesCount = 0; + public int tasks_concluidas_hoje = 0; + public int total_tasks_concluidas = 0; + public int meta_diaria_tarefas = 3; + public int meta_diaria_foco = 60; // em minutos + public int tempo_foco_hoje = 0; // em minutos + public int tempo_foco_total = 0; // em minutos + public int sessoes_foco_completas = 0; + public int dias_ativos = 1; + public String last_active_date = ""; // YYYY-MM-DD + public String avatar_url = ""; public Usuario() {} diff --git a/app/src/main/res/layout/activity_find_friends.xml b/app/src/main/res/layout/activity_find_friends.xml index 1de8bf0..6d5508f 100644 --- a/app/src/main/res/layout/activity_find_friends.xml +++ b/app/src/main/res/layout/activity_find_friends.xml @@ -6,174 +6,70 @@ android:background="@color/background_light" android:orientation="vertical"> - - + + android:layout_height="wrap_content" + android:background="@color/card_background" + android:orientation="vertical" + android:padding="16dp"> - - - - + android:layout_marginBottom="16dp"> + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:padding="16dp"> + diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 0975ef7..9772d36 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -12,25 +12,23 @@ android:layout_height="64dp" android:paddingHorizontal="16dp" android:background="@color/card_background" - android:elevation="4dp"> + android:elevation="0dp"> - + app:tint="@color/text_primary" /> @@ -45,272 +43,78 @@ android:orientation="vertical" android:padding="20dp"> - + - android:textAllCaps="true" - android:textSize="12sp" - android:textStyle="bold" - android:layout_marginBottom="8dp" /> - - - - - - - - - - - + + + + + - + - android:textAllCaps="true" - android:textSize="12sp" - android:textStyle="bold" - android:layout_marginBottom="8dp" /> - - - - - - - - - - + + + + - + - android:textAllCaps="true" - android:textSize="12sp" - android:textStyle="bold" - android:layout_marginBottom="8dp" /> - - - - - - - - - - - + + + + - + - android:textAllCaps="true" - android:textSize="12sp" - android:textStyle="bold" - android:layout_marginBottom="8dp" /> - - - - - - - - - - - + + + + - + - android:textAllCaps="true" - android:textSize="12sp" - android:textStyle="bold" - android:layout_marginBottom="8dp" /> - - - - - - - - - - - + + + + - - +