This commit is contained in:
MeuNome
2026-04-27 14:58:47 +01:00
parent d0216e424b
commit 0fe31e3f65
20 changed files with 1503 additions and 1468 deletions

1
.idea/misc.xml generated
View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">

View File

@@ -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'
}

View File

@@ -27,6 +27,7 @@
<activity android:name=".MainActivity" />
<activity android:name=".SettingsActivity" />
<activity android:name=".StreakActivity" />
<activity android:name=".StatisticsActivity" />
<activity android:name=".FindFriendsActivity" />
<activity android:name=".TrophiesActivity" />

View File

@@ -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<Suggestion> suggestions = new ArrayList<>();
suggestions.add(new Suggestion("Maria Silva", "Segue você", R.color.reward_yellow));
suggestions.add(new Suggestion("João Pereira", "Amigo em comum", R.color.success_green));
suggestions.add(new Suggestion("Ana Costa", "Segue você", R.color.streak_orange));
suggestions.add(new Suggestion("Ricardo M.", "Novo no Fluxup", R.color.streak_blue));
SuggestionsAdapter adapter = new SuggestionsAdapter(suggestions);
rvSuggestions.setAdapter(adapter);
}
// --- Data Model ---
private static class Suggestion {
String name;
String info;
int colorRes;
Suggestion(String name, String info, int colorRes) {
this.name = name;
this.info = info;
this.colorRes = colorRes;
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) {}
});
// --- Adapter ---
private class SuggestionsAdapter extends RecyclerView.Adapter<SuggestionsAdapter.ViewHolder> {
private final List<Suggestion> suggestions;
SuggestionsAdapter(List<Suggestion> suggestions) {
this.suggestions = suggestions;
}
@NonNull
tabLayout.addOnTabSelectedListener(new com.google.android.material.tabs.TabLayout.OnTabSelectedListener() {
@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);
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 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 void onTabUnselected(com.google.android.material.tabs.TabLayout.Tab tab) {}
@Override public void onTabReselected(com.google.android.material.tabs.TabLayout.Tab tab) {}
});
}
@Override
public int getItemCount() {
return suggestions.size();
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);
}
});
}
class ViewHolder extends RecyclerView.ViewHolder {
ImageView ivAvatar;
TextView tvName, tvInfo;
MaterialButton btnFollow;
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);
}
});
}
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);
}
}
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);
}
}

View File

@@ -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).
*/

View File

@@ -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<View> dayNodes = new ArrayList<>();
private int currentDayIndex = 0;
private ListenerRegistration tasksListener, userListener;
private ListenerRegistration tasksListener, userListener, rankingListener;
private List<Task> 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);
tvNoTasksIncentive = view.findViewById(R.id.tvNoTasksIncentive);
btnAddTasks = view.findViewById(R.id.btnAddTasks);
btnAddTasks.setOnClickListener(v -> showAddTaskDialog());
// 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);
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);
View taskView = LayoutInflater.from(getContext()).inflate(R.layout.item_task_home, tasksContainer, false);
LinearLayout layout = new LinearLayout(getContext());
layout.setOrientation(LinearLayout.HORIZONTAL);
layout.setGravity(android.view.Gravity.CENTER_VERTICAL);
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);
CheckBox cb = new CheckBox(getContext());
cb.setText(task.title);
cb.setTextColor(getResources().getColor(R.color.text_primary));
cb.setTextSize(16);
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);
}
updateProgress();
taskView.setBackgroundResource(R.drawable.task_selected_bg);
});
taskView.setOnLongClickListener(v -> {
showDeleteConfirmDialog(task);
return true;
});
tasksContainer.addView(taskView);
}
updateTodayCard();
}
private void addXP(int amount) {
FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser();
if (currentUser != null) {
Map<String, Object> 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<String, Object> 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<String, Object> 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();
btnStartFocus.setText("Começar Foco");
completeTask(selectedTaskForFocus);
addXP(50);
FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser();
if (currentUser != null) {
Map<String, Object> 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();
}
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();
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()) {
editText.setError("Escreve um desafio");
etTitle.setError("Obrigatório");
return;
}
saveNewTask(title);
int duration = 25;
if (!durationStr.isEmpty()) duration = Integer.parseInt(durationStr);
saveNewTask(title, duration);
dialog.dismiss();
});
}
editText.requestFocus();
if (dialog.getWindow() != null) {
dialog.getWindow().setSoftInputMode(
android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
}
});
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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
private void initViews() {
btnBack = findViewById(R.id.btnBack);
switchDarkMode = findViewById(R.id.switchDarkMode);
switchPrivacy = findViewById(R.id.switchPrivacy);
switchNotifications = findViewById(R.id.switchNotifications);
tvEmail = findViewById(R.id.tvEmail);
btnChangePassword = findViewById(R.id.btnChangePassword);
spinnerLanguage = findViewById(R.id.spinnerLanguage);
btnLogout = findViewById(R.id.btnLogout);
// Configurar Spinner de Idiomas
String[] languages = {"Português", "English", "Español"};
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, languages);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerLanguage.setAdapter(adapter);
// Mostrar email do utilizador
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null) {
tvEmail.setText(user.getEmail());
}
}
private void setupListeners() {
btnBack.setOnClickListener(v -> finish());
switchDarkMode.setOnCheckedChangeListener((buttonView, isChecked) -> {
applyTheme(isChecked);
saveSetting("darkMode", isChecked);
// --- 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);
switchPrivacy.setOnCheckedChangeListener((buttonView, isChecked) -> saveSetting("privacy", isChecked));
switchNotifications.setOnCheckedChangeListener((buttonView, isChecked) -> saveSetting("notifications", isChecked));
// --- NOTIFICAÇÕES ---
setupSwitch(R.id.settingDailyReminders, "Lembretes Diários", "daily_reminders", true);
setupSwitch(R.id.settingAntiProcrastination, "Anti-Procrastinação", "anti_procrastination", true);
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();
});
// --- 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));
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";
// --- PRIVACIDADE ---
setupSwitch(R.id.settingIncognito, "Modo Incógnito", "incognito", false);
setupClickable(R.id.settingExportData, "Exportar Dados", "JSON/CSV", null);
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();
}
});
}
findViewById(R.id.btnDeleteAccount).setOnClickListener(v -> {
Toast.makeText(this, "Funcionalidade disponível em breve", 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 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 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 setupSwitch(int id, String title, String key, boolean defValue) {
setupSwitch(id, title, key, defValue, null);
}
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 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 saveSetting(String key, boolean value) {
sharedPreferences.edit().putBoolean(key, value).apply();
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 saveSetting(String key, String value) {
sharedPreferences.edit().putString(key, value).apply();
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)));
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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
private String getNextLeague(String current) {
switch (current) {
case "Bronze": return "Prata";
case "Prata": return "Ouro";
case "Ouro": return "Platina";
default: return "Diamante";
}
}
// Trophy logic based on streak
int currentStreak = user.streak;
int trophies = user.trophiesCount;
private void observeRanking(String filter) {
if (rankingListener != null) rankingListener.remove();
// Update trophy visuals
updateTrophyIcons(trophies);
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);
// Progress message
int daysToNext = 30 - (currentStreak % 30);
if (daysToNext == 30 && currentStreak > 0) {
tvTrophyProgress.setText("Troféu conquistado! Mantém a ofensiva.");
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();

View File

@@ -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() {}

View File

@@ -6,174 +6,70 @@
android:background="@color/background_light"
android:orientation="vertical">
<!-- Header -->
<!-- Header with Search -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/card_background"
android:orientation="vertical"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="64dp"
android:paddingHorizontal="16dp">
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<ImageButton
android:id="@+id/btnClose"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_close"
android:src="@drawable/ic_back"
app:tint="@color/text_primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Encontre os seus amigos"
android:text="Amigos"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textSize="20sp"
android:textStyle="bold" />
</RelativeLayout>
<EditText
android:id="@+id/etSearchFriends"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/edit_text_bg"
android:drawableStart="@drawable/ic_search"
android:drawablePadding="12dp"
android:hint="Procurar por nome ou ID..."
android:paddingHorizontal="16dp"
android:textSize="14sp" />
</LinearLayout>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayoutFriends"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabIndicatorColor="@color/primary_purple"
app:tabSelectedTextColor="@color/primary_purple"
app:tabTextColor="@color/text_secondary">
<com.google.android.material.tabs.TabItem android:text="Sugestões" />
<com.google.android.material.tabs.TabItem android:text="Os meus amigos" />
<com.google.android.material.tabs.TabItem android:text="Pendentes" />
</com.google.android.material.tabs.TabLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/friendsResultsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<!-- Main Options Cards -->
<androidx.cardview.widget.CardView
android:id="@+id/btnContacts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:cardCornerRadius="20dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/card_background">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="20dp">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_contacts"
app:tint="@color/primary_purple" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Escolher nos contactos"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/btnSearchByName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:cardCornerRadius="20dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/card_background">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="20dp">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_search"
app:tint="@color/streak_blue" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Buscar por nome"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/btnProfileLink"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
app:cardCornerRadius="20dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/card_background">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="20dp">
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_share"
app:tint="@color/success_green" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Link do seu perfil"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Suggestions Section -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sugestões de amigos"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/btnViewAll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:text="VER TODOS"
android:textColor="@color/primary_purple"
android:textSize="12sp"
android:textStyle="bold" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvSuggestions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
android:padding="16dp">
<!-- Results will be loaded here -->
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -12,25 +12,23 @@
android:layout_height="64dp"
android:paddingHorizontal="16dp"
android:background="@color/card_background"
android:elevation="4dp">
android:elevation="0dp">
<ImageButton
android:id="@+id/btnBack"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_back"
app:tint="@color/text_secondary" />
app:tint="@color/text_primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/settings"
android:text="Configurações"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold" />
</RelativeLayout>
@@ -45,272 +43,78 @@
android:orientation="vertical"
android:padding="20dp">
<!-- Preferences Section -->
<!-- 👤 CONTA -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/preferences"
android:textColor="@color/text_secondary"
style="@style/SettingsSectionHeader"
android:text="Conta" />
android:textAllCaps="true"
android:textSize="12sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
android:backgroundTint="@color/card_background"
android:layout_marginBottom="24dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dark_mode"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switchDarkMode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true" />
</RelativeLayout>
<androidx.cardview.widget.CardView style="@style/SettingsCard">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
<include layout="@layout/item_settings_clickable" android:id="@+id/settingEmail" />
<include layout="@layout/item_settings_clickable" android:id="@+id/settingPassword" />
<include layout="@layout/item_settings_switch" android:id="@+id/settingPublicProfile" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Account Section -->
<!-- 🎨 APARÊNCIA -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/account"
android:textColor="@color/text_secondary"
style="@style/SettingsSectionHeader"
android:text="Aparência" />
android:textAllCaps="true"
android:textSize="12sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
android:backgroundTint="@color/card_background"
android:layout_marginBottom="24dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/tvEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="email@exemplo.com"
android:textColor="@color/text_primary"
android:textSize="15sp"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/border_color"
android:layout_marginVertical="12dp" />
<TextView
android:id="@+id/btnChangePassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/change_password"
android:textColor="@color/primary_purple"
android:textSize="14sp"
android:textStyle="bold"
android:clickable="true"
android:focusable="true" />
<androidx.cardview.widget.CardView style="@style/SettingsCard">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
<include layout="@layout/item_settings_switch" android:id="@+id/settingDarkMode" />
<include layout="@layout/item_settings_clickable" android:id="@+id/settingThemeColor" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Privacy Section -->
<!-- 🔔 NOTIFICAÇÕES -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/privacy"
android:textColor="@color/text_secondary"
style="@style/SettingsSectionHeader"
android:text="Notificações" />
android:textAllCaps="true"
android:textSize="12sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
android:backgroundTint="@color/card_background"
android:layout_marginBottom="24dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/private_account"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switchPrivacy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true" />
</RelativeLayout>
<androidx.cardview.widget.CardView style="@style/SettingsCard">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
<include layout="@layout/item_settings_switch" android:id="@+id/settingDailyReminders" />
<include layout="@layout/item_settings_switch" android:id="@+id/settingAntiProcrastination" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Notifications Section -->
<!-- 🎯 PREFERÊNCIAS DE FOCO -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/notifications"
android:textColor="@color/text_secondary"
style="@style/SettingsSectionHeader"
android:text="Preferências de Foco" />
android:textAllCaps="true"
android:textSize="12sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
android:backgroundTint="@color/card_background"
android:layout_marginBottom="24dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/notifications"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switchNotifications"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true" />
</RelativeLayout>
<androidx.cardview.widget.CardView style="@style/SettingsCard">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
<include layout="@layout/item_settings_clickable" android:id="@+id/settingFocusDuration" />
<include layout="@layout/item_settings_clickable" android:id="@+id/settingBreakDuration" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- App Section -->
<!-- 🛡️ PRIVACIDADE -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_section"
android:textColor="@color/text_secondary"
style="@style/SettingsSectionHeader"
android:text="Privacidade" />
android:textAllCaps="true"
android:textSize="12sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
android:backgroundTint="@color/card_background"
android:layout_marginBottom="40dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/language"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<Spinner
android:id="@+id/spinnerLanguage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:textAlignment="viewEnd" />
</RelativeLayout>
<androidx.cardview.widget.CardView style="@style/SettingsCard">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
<include layout="@layout/item_settings_switch" android:id="@+id/settingIncognito" />
<include layout="@layout/item_settings_clickable" android:id="@+id/settingExportData" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Logout Button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btnLogout"
<!-- Danger Zone -->
<Button
android:id="@+id/btnDeleteAccount"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="@string/logout"
android:layout_height="56dp"
android:layout_marginTop="24dp"
android:text="Eliminar Conta"
android:textColor="@color/error_red"
android:textStyle="bold"
app:backgroundTint="#FEE2E2"
app:cornerRadius="16dp"
app:elevation="0dp"
app:icon="@drawable/ic_back"
app:iconGravity="textStart"
app:iconTint="@color/error_red"
android:layout_marginBottom="20dp"/>
android:backgroundTint="#10EF4444"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_marginBottom="40dp"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -26,10 +26,11 @@
app:tint="@color/text_primary" />
<TextView
android:id="@+id/tvToolbarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Troféus"
android:text="Ligas"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold" />
@@ -44,134 +45,206 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="24dp">
android:padding="20dp">
<!-- Division Info -->
<TextView
android:id="@+id/tvDivisionTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Divisão Prata"
android:textColor="@color/primary_purple"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvTimeRemaining"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="3 dias restantes"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<!-- Trophy Progression Container -->
<!-- 1. 🥇 HEADER DA LIGA -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginBottom="32dp"
app:cardCornerRadius="@dimen/radius_duo"
android:layout_marginBottom="20dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="32dp">
<LinearLayout
android:id="@+id/trophyContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="20dp"
android:gravity="center">
<!-- Trophies will be added here dynamically or statically -->
<ImageView
android:id="@+id/trophy1"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="8dp"
android:src="@drawable/ic_trophy_bronze" />
<ImageView
android:id="@+id/trophy2"
android:id="@+id/ivLeagueBadge"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="8dp"
android:src="@drawable/ic_trophy_silver"
android:padding="4dp"
android:background="@drawable/circle_bg"
android:backgroundTint="#1A6200EE" />
<ImageView
android:id="@+id/trophy3"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="8dp"
android:alpha="0.3"
android:src="@drawable/ic_trophy_gold" />
</LinearLayout>
android:src="@drawable/ic_trophy_bronze"
android:layout_marginBottom="12dp"/>
<TextView
android:id="@+id/tvTrophyProgress"
android:id="@+id/tvLeagueName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Faltam 15 dias para o próximo troféu"
android:text="Divisão Bronze"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvLeagueTimeRemaining"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="2 dias 14h restantes"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<ProgressBar
android:id="@+id/pbWeeklyProgress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="8dp"
android:layout_marginTop="16dp"
android:max="100"
android:progress="60"
android:progressDrawable="@drawable/progress_bar_duo" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Inactive State (Initially Hidden) -->
<LinearLayout
android:id="@+id/inactiveState"
<!-- 2. 🎯 OBJETIVO DA LIGA -->
<TextView
android:id="@+id/tvLeagueObjective"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Fica no TOP 3 para subir para Prata"
android:textAlignment="center"
android:textColor="@color/primary_purple"
android:textStyle="bold"
android:textSize="16sp"
android:layout_marginBottom="24dp"/>
<!-- 8. 👥 FILTRO DE RANKING -->
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/toggleRankingFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="20dp"
app:singleSelection="true"
app:checkedButton="@+id/btnRankingGlobal">
<Button
android:id="@+id/btnRankingGlobal"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Global"
android:textSize="12sp"/>
<Button
android:id="@+id/btnRankingAmigos"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Amigos"
android:textSize="12sp"/>
</com.google.android.material.button.MaterialButtonToggleGroup>
<!-- 3. 📊 RANKING DA SEMANA -->
<LinearLayout
android:id="@+id/rankingContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
android:layout_marginBottom="24dp"/>
<ImageView
android:layout_width="150dp"
android:layout_height="150dp"
android:src="@drawable/ic_sleeping_char" />
<!-- 5. ⚡ PROGRESSO DO UTILIZADOR -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/tvUserRankingStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Hora de voltar!"
android:text="Faltam 120 XP para entrares no TOP 3"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold" />
android:textStyle="bold"
android:textSize="14sp"/>
<TextView
android:id="@+id/tvXPToNextPosition"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Completa tarefas para competir esta semana."
android:textAlignment="center"
android:layout_marginTop="4dp"
android:text="Diferença para o próximo: 45 XP"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
android:textSize="12sp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Active Message -->
<!-- 6. 🎁 RECOMPENSAS DA SEMANA -->
<TextView
android:id="@+id/tvMotivational"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Estás a progredir bem na Divisão Prata!"
android:text="Recompensas da semana"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="@color/text_primary"
android:layout_marginBottom="12dp"/>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="16dp"
app:cardElevation="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🥇 1º lugar: +300 XP + Badge Exclusiva"
android:padding="8dp"
android:textColor="@color/text_primary"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🥈 2º lugar: +200 XP"
android:padding="8dp"
android:textColor="@color/text_primary"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🥉 3º lugar: +100 XP"
android:padding="8dp"
android:textColor="@color/text_primary"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 7. 📈 HISTÓRICO DE PERFORMANCE -->
<TextView
android:id="@+id/tvPastPerformance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Semana passada: 2º lugar (subiste)"
android:textAlignment="center"
android:textColor="@color/text_secondary"
android:textSize="16sp" />
android:textSize="14sp"
android:layout_marginBottom="32dp"/>
<!-- 9. 🔘 BOTÃO DE ACÇÃO -->
<Button
android:id="@+id/btnEarnXpNow"
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="Ganhar XP agora"
android:background="@drawable/button_primary"
android:textColor="@color/white"
android:textStyle="bold"
android:textAllCaps="false"
app:backgroundTint="@null"
android:layout_marginBottom="40dp"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -12,7 +12,7 @@
android:orientation="vertical"
android:padding="20dp">
<!-- Header -->
<!-- 1. 👋 HEADER -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -27,28 +27,34 @@
android:id="@+id/tvGreeting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Olá, Jvitor!"
android:text="Olá, Utilizador!"
android:textColor="@color/text_primary"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvMotivationalSubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Pronto para ser produtivo?"
android:text="Só precisas de começar."
android:textColor="@color/text_secondary"
android:textSize="16sp" />
</LinearLayout>
<androidx.cardview.widget.CardView
android:id="@+id/cardProfileAvatar"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_alignParentEnd="true"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardCornerRadius="28dp"
app:cardElevation="2dp">
<ImageView
android:id="@+id/ivUserAvatar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
@@ -57,60 +63,125 @@
</androidx.cardview.widget.CardView>
</RelativeLayout>
<!-- Progress Overview -->
<!-- 2. 🔥 CARD "HOJE" -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp"
app:contentPadding="20dp">
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:padding="20dp">
<RelativeLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp">
android:orientation="horizontal"
android:weightSum="3">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🔥 Streak"
android:textSize="12sp"
android:textColor="@color/text_secondary"/>
<TextView
android:id="@+id/tvTodayStreak"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0 dias"
android:textStyle="bold"
android:textSize="16sp"
android:textColor="@color/streak_orange"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="⚡ XP"
android:textSize="12sp"
android:textColor="@color/text_secondary"/>
<TextView
android:id="@+id/tvTodayXP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textStyle="bold"
android:textSize="16sp"
android:textColor="@color/primary_purple"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="✅ Tarefas"
android:textSize="12sp"
android:textColor="@color/text_secondary"/>
<TextView
android:id="@+id/tvTodayTasksCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0/3"
android:textStyle="bold"
android:textSize="16sp"
android:textColor="@color/success_green"/>
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/border_color"
android:layout_marginVertical="16dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Desafios Diários"
android:text="Meta Diária"
android:textStyle="bold"
android:textSize="14sp"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvProgressText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:text="1 de 3 concluídos"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
</RelativeLayout>
android:layout_marginBottom="8dp"/>
<ProgressBar
android:id="@+id/pbDailyTasks"
android:id="@+id/pbDailyTasksProgress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="12dp"
android:max="100"
android:progress="33"
android:progress="0"
android:progressDrawable="@drawable/progress_bar_duo" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Daily Challenges List -->
<!-- 3. 🎯 TAREFAS DO DIA -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:text="Meus Desafios"
android:text="Tarefas do Dia"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold" />
@@ -122,24 +193,21 @@
android:layout_marginBottom="16dp"
android:orientation="vertical" />
<Button
android:id="@+id/btnAddTasks"
<TextView
android:id="@+id/tvNoTasksIncentive"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginBottom="32dp"
android:background="@drawable/button_primary"
android:text="+ Adicionar Desafio"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
app:backgroundTint="@null" />
android:layout_height="wrap_content"
android:text="Ainda não tens tarefas para hoje. Vamos começar?"
android:textAlignment="center"
android:layout_marginBottom="16dp"
android:visibility="gone"
android:textColor="@color/text_secondary"/>
<!-- Focus Mode Section -->
<!-- 4. ⏱️ MODO FOCO -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="40dp"
android:layout_marginBottom="24dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp"
app:contentPadding="24dp">
@@ -207,43 +275,16 @@
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Daily Progress Path Section -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<!-- 5. 📊 PROGRESSO DIÁRIO -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="Caminho de Progresso Diário"
android:layout_marginBottom="12dp"
android:text="Progresso Semanal"
android:textColor="@color/text_primary"
android:textSize="19sp"
android:textSize="20sp"
android:textStyle="bold" />
<androidx.cardview.widget.CardView
android:id="@+id/btnStreak"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="@color/card_background"
app:cardCornerRadius="20dp"
app:cardElevation="2dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:src="@drawable/ic_flame" />
</androidx.cardview.widget.CardView>
</RelativeLayout>
<LinearLayout
android:id="@+id/progressPathContainer"
android:layout_width="match_parent"
@@ -253,7 +294,123 @@
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingVertical="32dp"
android:layout_marginBottom="40dp" />
android:layout_marginBottom="24dp" />
<!-- 6. 👥 MINI RANKING -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Competição de hoje"
android:textStyle="bold"
android:textSize="16sp"
android:textColor="@color/text_primary"
android:layout_marginBottom="12dp"/>
<LinearLayout
android:id="@+id/miniRankingContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
<Button
android:id="@+id/btnViewFullRanking"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Ver Ranking Completo"
android:textColor="@color/primary_purple"
android:textSize="14sp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 7. 🎁 RECOMPENSA DIÁRIA -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/primary_purple">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="20dp"
android:gravity="center_vertical">
<TextView
android:layout_width="40dp"
android:layout_height="40dp"
android:text="🎁"
android:textSize="32sp"
android:gravity="center"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Presente diário"
android:textColor="@color/white"
android:textStyle="bold"
android:textSize="16sp"/>
<TextView
android:id="@+id/tvDailyRewardGoal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Completa 3 tarefas hoje para ganhar +100 XP"
android:textColor="@color/white"
android:alpha="0.8"
android:textSize="12sp"/>
</LinearLayout>
<Button
android:id="@+id/btnClaimReward"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Resgatar"
android:textSize="12sp"
android:textColor="@color/primary_purple"
android:backgroundTint="@color/white"
android:enabled="false"
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 8. BOTÃO ADICIONAR TAREFA -->
<Button
android:id="@+id/btnAddTasks"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginBottom="32dp"
android:background="@drawable/button_primary"
android:text="+ Adicionar Tarefa"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
app:backgroundTint="@null" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -12,414 +12,182 @@
android:orientation="vertical"
android:padding="20dp">
<!-- Top Header -->
<!-- 👤 HEADER -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp">
<ImageButton
android:id="@+id/btnSettings"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentEnd="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_settings"
app:tint="@color/primary_purple" />
</RelativeLayout>
<!-- Profile Info -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:layout_marginBottom="32dp">
<androidx.cardview.widget.CardView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginBottom="16dp"
app:cardCornerRadius="50dp"
app:cardElevation="4dp">
android:id="@+id/cardAvatar"
android:layout_width="80dp"
android:layout_height="80dp"
app:cardCornerRadius="40dp"
app:cardElevation="2dp">
<ImageView
android:id="@+id/ivAvatar"
android:id="@+id/ivProfileAvatar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/ic_nav_profile"
app:tint="@color/primary_purple" />
android:src="@drawable/ic_nav_profile" />
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/tvUsername"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Jvitor"
android:layout_toEndOf="@id/cardAvatar"
android:layout_marginStart="16dp"
android:layout_centerVertical="true"
android:orientation="vertical">
<TextView
android:id="@+id/tvProfileName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Carregando..."
android:textColor="@color/text_primary"
android:textSize="26sp"
android:textSize="22sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvHandle"
android:id="@+id/tvProfileTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@jvitor_prod"
android:textColor="@color/text_secondary"
android:text="Iniciante de Foco"
android:textColor="@color/primary_purple"
android:textSize="14sp"
android:layout_marginTop="4dp"/>
android:textStyle="bold" />
<TextView
android:id="@+id/tvProfileBio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sem bio definida."
android:textColor="@color/text_secondary"
android:textSize="12sp"
android:layout_marginTop="4dp" />
</LinearLayout>
<!-- Stats Section -->
<ImageButton
android:id="@+id/btnEditProfile"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_settings"
app:tint="@color/text_secondary" />
</RelativeLayout>
<!-- 📊 ESTATÍSTICAS PRINCIPAIS -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Estatísticas"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold"
android:textSize="18sp" />
<TextView
android:id="@+id/btnViewStatsDetails"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:text="Ver detalhes"
android:textColor="@color/primary_purple"
android:textStyle="bold" />
</RelativeLayout>
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:columnCount="2"
android:orientation="horizontal">
android:columnCount="3"
android:layout_marginBottom="24dp">
<!-- Streak Card -->
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_margin="6dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp"
app:contentPadding="16dp">
<include layout="@layout/item_stat_small" android:id="@+id/statXP" />
<include layout="@layout/item_stat_small" android:id="@+id/statStreak" />
<include layout="@layout/item_stat_small" android:id="@+id/statLevel" />
<include layout="@layout/item_stat_small" android:id="@+id/statTasks" />
<include layout="@layout/item_stat_small" android:id="@+id/statFocusTime" />
<include layout="@layout/item_stat_small" android:id="@+id/statSessions" />
</GridLayout>
<LinearLayout
<!-- 🏆 CONQUISTAS (Badges) -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🔥"
android:textSize="24sp"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tvStreakValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="15"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ofensiva"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- XP Card -->
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_margin="6dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp"
app:contentPadding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="⚡"
android:textSize="24sp"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tvTotalXP"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2450"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Total de XP"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- League Card -->
<androidx.cardview.widget.CardView
android:id="@+id/cardLeague"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_margin="6dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp"
app:contentPadding="16dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🏆"
android:textSize="24sp"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tvLeagueName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Prata"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Liga Atual"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Achievements Card -->
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_margin="6dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp"
app:contentPadding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🏅"
android:textSize="24sp"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tvAchievementsCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="8"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
android:layout_marginBottom="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Conquistas"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</GridLayout>
android:textStyle="bold"
android:textSize="18sp" />
<TextView
android:id="@+id/btnViewAllBadges"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:text="Ver todas"
android:textColor="@color/primary_purple"
android:textStyle="bold" />
</RelativeLayout>
<!-- Friends Section -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="16dp"
app:cardElevation="1dp">
<GridLayout
android:id="@+id/badgesGrid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="4"
android:padding="16dp"
android:gravity="center">
<!-- Badges will be added here -->
</GridLayout>
</androidx.cardview.widget.CardView>
<!-- 👥 AMIGOS -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
android:layout_marginBottom="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Amigos"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold" />
android:textStyle="bold"
android:textSize="18sp" />
<TextView
android:id="@+id/btnViewAllFriends"
android:id="@+id/btnManageFriends"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:text="Ver Todos"
android:text="Gerir"
android:textColor="@color/primary_purple"
android:textSize="14sp"
android:textStyle="bold" />
</RelativeLayout>
<HorizontalScrollView
<LinearLayout
android:id="@+id/friendsListContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:scrollbars="none">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<!-- Invite Item -->
<LinearLayout
android:layout_width="80dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:gravity="center"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:id="@+id/btnInviteCard"
android:layout_width="60dp"
android:layout_height="60dp"
app:cardCornerRadius="30dp"
app:cardElevation="0dp"
app:cardBackgroundColor="@color/border_color"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="+"
android:textColor="@color/white"
android:textSize="32sp" />
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Convidar"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
</LinearLayout>
<!-- Sample Friend 1 -->
<LinearLayout
android:layout_width="80dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:gravity="center"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:layout_width="60dp"
android:layout_height="60dp"
app:cardCornerRadius="30dp"
app:cardElevation="2dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/ic_nav_profile"
app:tint="@color/reward_yellow" />
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Maria"
android:textColor="@color/text_primary"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="450 XP"
android:textColor="@color/text_secondary"
android:textSize="10sp" />
</LinearLayout>
<!-- Sample Friend 2 -->
<LinearLayout
android:layout_width="80dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:gravity="center"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:layout_width="60dp"
android:layout_height="60dp"
app:cardCornerRadius="30dp"
app:cardElevation="2dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/ic_nav_profile"
app:tint="@color/success_green" />
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="João"
android:textColor="@color/text_primary"
android:textSize="12sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="320 XP"
android:textColor="@color/text_secondary"
android:textSize="10sp" />
</LinearLayout>
</LinearLayout>
</HorizontalScrollView>
android:orientation="vertical"
android:layout_marginBottom="24dp"/>
<!-- 🔘 BOTÕES DE ACÇÃO -->
<Button
android:id="@+id/btnInviteFriends"
android:id="@+id/btnLogout"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="@drawable/button_primary"
android:text="+ Encontrar Amigos"
android:textAllCaps="false"
android:text="Terminar Sessão"
android:backgroundTint="@color/error_red"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
app:backgroundTint="@null" />
android:layout_marginBottom="40dp"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -34,5 +34,28 @@
<string name="confirm_password_hint">Confirmar palavra-passe</string>
<string name="register_button">Registar</string>
<string name="already_have_account">Já tens conta? </string>
<!-- Statistics -->
<string name="statistics">Estatísticas</string>
<string name="view_details">Ver detalhes</string>
<string name="xp_progress">Progresso de XP</string>
<string name="focus_time_stats">Tempo de Foco (min)</string>
<string name="performance">Desempenho</string>
<string name="avg_focus">Média Foco</string>
<string name="tasks_per_day">Tarefas/Dia</string>
<string name="best_day">Melhor Dia</string>
<string name="total_sessions">Total Sessões</string>
<!-- Friends -->
<string name="friends">Amigos</string>
<string name="search_friends_hint">Procurar por nome ou ID...</string>
<string name="suggestions">Sugestões</string>
<string name="my_friends">Os meus amigos</string>
<string name="pending">Pendentes</string>
<string name="add_friend">Adicionar</string>
<!-- Gamification -->
<string name="level_up">NOVO NÍVEL!</string>
<string name="level_up_congrats">Estás a tornar-te uma lenda da produtividade!</string>
</resources>

View File

@@ -50,4 +50,23 @@
<item name="android:layout_marginBottom">@dimen/spacing_md</item>
</style>
<style name="SettingsSectionHeader">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textColor">@color/text_secondary</item>
<item name="android:textAllCaps">true</item>
<item name="android:textSize">12sp</item>
<item name="android:textStyle">bold</item>
<item name="android:layout_marginTop">24dp</item>
<item name="android:layout_marginBottom">8dp</item>
</style>
<style name="SettingsCard" parent="CardView">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="cardCornerRadius">16dp</item>
<item name="cardElevation">0dp</item>
<item name="cardBackgroundColor">@color/card_background</item>
<item name="android:layout_marginBottom">8dp</item>
</style>
</resources>

View File

@@ -10,13 +10,6 @@ buildscript {
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@@ -1,2 +1,11 @@
rootProject.name = "Fluxup"
include ':app'
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}