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"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> <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' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 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=".MainActivity" />
<activity android:name=".SettingsActivity" /> <activity android:name=".SettingsActivity" />
<activity android:name=".StreakActivity" /> <activity android:name=".StreakActivity" />
<activity android:name=".StatisticsActivity" />
<activity android:name=".FindFriendsActivity" /> <activity android:name=".FindFriendsActivity" />
<activity android:name=".TrophiesActivity" /> <activity android:name=".TrophiesActivity" />

View File

@@ -11,104 +11,108 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.button.MaterialButton;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class FindFriendsActivity extends AppCompatActivity { public class FindFriendsActivity extends AppCompatActivity {
private EditText etSearch;
private LinearLayout resultsContainer;
private com.google.android.material.tabs.TabLayout tabLayout;
private ImageButton btnClose; private ImageButton btnClose;
private RecyclerView rvSuggestions; private String currentTab = "Sugestões";
private View btnContacts, btnSearchByName, btnProfileLink;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_find_friends); setContentView(R.layout.activity_find_friends);
btnClose = findViewById(R.id.btnClose); initViews();
rvSuggestions = findViewById(R.id.rvSuggestions); setupListeners();
btnContacts = findViewById(R.id.btnContacts); loadSuggestions();
btnSearchByName = findViewById(R.id.btnSearchByName); }
btnProfileLink = findViewById(R.id.btnProfileLink);
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()); btnClose.setOnClickListener(v -> finish());
setupSuggestions(); 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) {
private void setupSuggestions() { if (s.length() > 0) {
rvSuggestions.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); performSearch(s.toString());
} else {
List<Suggestion> suggestions = new ArrayList<>(); loadSuggestions();
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;
} }
} }
@Override public void afterTextChanged(android.text.Editable s) {}
});
// --- Adapter --- tabLayout.addOnTabSelectedListener(new com.google.android.material.tabs.TabLayout.OnTabSelectedListener() {
private class SuggestionsAdapter extends RecyclerView.Adapter<SuggestionsAdapter.ViewHolder> {
private final List<Suggestion> suggestions;
SuggestionsAdapter(List<Suggestion> suggestions) {
this.suggestions = suggestions;
}
@NonNull
@Override @Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public void onTabSelected(com.google.android.material.tabs.TabLayout.Tab tab) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_friend_suggestion, parent, false); currentTab = tab.getText().toString();
return new ViewHolder(view); if (currentTab.equals("Sugestões")) loadSuggestions();
else resultsContainer.removeAllViews(); // Placeholder for others
} }
@Override public void onTabUnselected(com.google.android.material.tabs.TabLayout.Tab tab) {}
@Override @Override public void onTabReselected(com.google.android.material.tabs.TabLayout.Tab tab) {}
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 private void performSearch(String query) {
public int getItemCount() { com.google.firebase.firestore.FirebaseFirestore.getInstance()
return suggestions.size(); .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 { private void loadSuggestions() {
ImageView ivAvatar; com.google.firebase.firestore.FirebaseFirestore.getInstance()
TextView tvName, tvInfo; .collection("users")
MaterialButton btnFollow; .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) { private void addFriendItem(Usuario user) {
super(view); View view = getLayoutInflater().inflate(R.layout.item_friend_action, resultsContainer, false);
ivAvatar = view.findViewById(R.id.ivFriendAvatar); TextView tvName = view.findViewById(R.id.tvFriendName);
tvName = view.findViewById(R.id.tvFriendName); TextView tvStats = view.findViewById(R.id.tvFriendStats);
tvInfo = view.findViewById(R.id.tvFriendInfo); Button btnAction = view.findViewById(R.id.btnFriendAction);
btnFollow = view.findViewById(R.id.btnFollowBack);
} 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); 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). * 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.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Button;
import com.google.firebase.auth.FirebaseUser; import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.firestore.ListenerRegistration; import com.google.firebase.firestore.ListenerRegistration;
import java.util.HashMap; import java.util.HashMap;
@@ -31,76 +32,114 @@ import java.util.List;
public class InicioFragment extends Fragment { public class InicioFragment extends Fragment {
private TextView tvTimer, tvProgressText, tvGreeting; private TextView tvTimer, tvGreeting, tvMotivationalSubtitle;
private FrameLayout timerBlock; private TextView tvTodayStreak, tvTodayXP, tvTodayTasksCount, tvNoTasksIncentive;
private LinearLayout tasksContainer; private TextView tvDailyRewardGoal;
private ProgressBar pbDailyTasks; private ProgressBar pbDailyTasksProgress;
private LinearLayout tasksContainer, miniRankingContainer;
private Button btnStartFocus, btnAddTasks, btnClaimReward;
private CountDownTimer countDownTimer; private CountDownTimer countDownTimer;
private LinearLayout progressPathContainer; private LinearLayout progressPathContainer;
private List<View> dayNodes = new ArrayList<>(); private List<View> dayNodes = new ArrayList<>();
private int currentDayIndex = 0; private int currentDayIndex = 0;
private ListenerRegistration tasksListener, userListener; private ListenerRegistration tasksListener, userListener, rankingListener;
private List<Task> currentTasks = new ArrayList<>(); private List<Task> currentTasks = new ArrayList<>();
private Task selectedTaskForFocus = null;
private boolean isTimerRunning = false; private boolean isTimerRunning = false;
private long timeLeftInMillis = 25 * 60 * 1000; // 25 minutos 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 @Nullable
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_inicio, container, false); View view = inflater.inflate(R.layout.fragment_inicio, container, false);
tvTimer = view.findViewById(R.id.tvTimer); // Header
tvGreeting = view.findViewById(R.id.tvGreeting); 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); tasksContainer = view.findViewById(R.id.tasksContainer);
tvProgressText = view.findViewById(R.id.tvProgressText); tvNoTasksIncentive = view.findViewById(R.id.tvNoTasksIncentive);
pbDailyTasks = view.findViewById(R.id.pbDailyTasks); 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);
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(); initProgressPath();
startObservingTasks(); startObservingTasks();
startObservingUser(); startObservingUser();
startObservingRanking();
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);
});
}
updateCountDownText(); updateCountDownText();
return view; return view;
} }
private void setRandomMotivationalQuote() {
int index = (int) (Math.random() * motivationalQuotes.length);
if (tvMotivationalSubtitle != null) {
tvMotivationalSubtitle.setText(motivationalQuotes[index]);
}
}
private void startObservingTasks() { private void startObservingTasks() {
FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser();
if (currentUser != null) { if (currentUser != null) {
tasksListener = FirestoreManager.getInstance().observeTasks(currentUser.getUid(), tasks -> { tasksListener = FirestoreManager.getInstance().observeTasks(currentUser.getUid(), tasks -> {
currentTasks = tasks; currentTasks = tasks;
updateTasksUI(); updateTasksUI();
updateTodayCard();
}); });
} }
} }
@@ -112,6 +151,23 @@ public class InicioFragment extends Fragment {
if (tvGreeting != null) { if (tvGreeting != null) {
tvGreeting.setText("Olá, " + user.usuario + "!"); 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(); tasksContainer.removeAllViews();
for (Task task : currentTasks) { for (Task task : currentTasks) {
androidx.cardview.widget.CardView card = new androidx.cardview.widget.CardView(getContext()); View taskView = LayoutInflater.from(getContext()).inflate(R.layout.item_task_home, tasksContainer, false);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
params.setMargins(0, 0, 0, 16);
card.setLayoutParams(params);
card.setRadius(getResources().getDimension(R.dimen.radius_md));
card.setCardElevation(2f);
card.setContentPadding(16, 16, 16, 16);
LinearLayout layout = new LinearLayout(getContext()); TextView tvTitle = taskView.findViewById(R.id.tvTaskTitle);
layout.setOrientation(LinearLayout.HORIZONTAL); TextView tvDuration = taskView.findViewById(R.id.tvTaskDuration);
layout.setGravity(android.view.Gravity.CENTER_VERTICAL); CheckBox cb = taskView.findViewById(R.id.cbTask);
Button btnStart = taskView.findViewById(R.id.btnStartTaskFocus);
CheckBox cb = new CheckBox(getContext()); tvTitle.setText(task.title);
cb.setText(task.title); tvDuration.setText(task.duration + " min");
cb.setTextColor(getResources().getColor(R.color.text_primary));
cb.setTextSize(16);
cb.setChecked(task.completed); cb.setChecked(task.completed);
if (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) -> { cb.setOnCheckedChangeListener((buttonView, isChecked) -> {
task.completed = isChecked; if (isChecked && !task.completed) {
FirestoreManager.getInstance().updateTask(task); completeTask(task);
// The observer will trigger updateTasksUI again, so we don't need manual UI update here
if (isChecked) {
addXP(task.xpReward);
} }
}); });
layout.addView(cb); btnStart.setOnClickListener(v -> {
card.addView(layout); selectedTaskForFocus = task;
tasksContainer.addView(card); 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) { private void addXP(int amount) {
FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser();
if (currentUser != null) { 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() com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("users").document(currentUser.getUid()) .collection("users").document(currentUser.getUid()).get()
.update("xp", com.google.firebase.firestore.FieldValue.increment(amount)); .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 total = currentTasks.size();
int completed = 0; int completed = 0;
for (Task task : currentTasks) { for (Task task : currentTasks) {
if (task.completed) completed++; if (task.completed) completed++;
} }
if (tvProgressText != null) { if (tvTodayTasksCount != null) {
tvProgressText.setText(completed + " de " + total + " concluídos"); tvTodayTasksCount.setText(completed + "/" + (total == 0 ? 3 : total));
} }
if (pbDailyTasks != null && total > 0) {
int progress = (completed * 100) / total; if (pbDailyTasksProgress != null) {
pbDailyTasks.setProgress(progress); int progress = (total == 0) ? 0 : (completed * 100) / total;
updatePathProgress(progress); 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() { 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) { private void updatePathProgress(int progress) {
if (currentDayIndex >= dayNodes.size()) return; if (currentDayIndex >= dayNodes.size()) return;
View todayNode = dayNodes.get(currentDayIndex); View todayNode = dayNodes.get(currentDayIndex);
ProgressBar nodeProgress = todayNode.findViewById(R.id.nodeProgress); ProgressBar nodeProgress = todayNode.findViewById(R.id.nodeProgress);
View nodeCircle = todayNode.findViewById(R.id.nodeCircle); 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) { if (progress == 100) {
// Task completion animation
nodeCircle.getBackground().setTint(getResources().getColor(R.color.success_green)); nodeCircle.getBackground().setTint(getResources().getColor(R.color.success_green));
triggerSuccessAnimation(todayNode); triggerSuccessAnimation(todayNode);
} else { } else {
@@ -282,31 +482,47 @@ public class InicioFragment extends Fragment {
private void startTimer() { private void startTimer() {
if (selectedTaskForFocus == null) return;
countDownTimer = new CountDownTimer(timeLeftInMillis, 1000) { countDownTimer = new CountDownTimer(timeLeftInMillis, 1000) {
@Override @Override
public void onTick(long millisUntilFinished) { public void onTick(long millisUntilFinished) {
timeLeftInMillis = millisUntilFinished; timeLeftInMillis = millisUntilFinished;
updateCountDownText(); updateCountDownText();
} }
@Override @Override
public void onFinish() { public void onFinish() {
isTimerRunning = false; isTimerRunning = false;
if(getContext() != null) { btnStartFocus.setText("Começar Foco");
Toast.makeText(getContext(), "Foco concluído! +50 XP", Toast.LENGTH_LONG).show(); completeTask(selectedTaskForFocus);
addXP(50); 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(); }.start();
isTimerRunning = true; isTimerRunning = true;
btnStartFocus.setText("Pausar Foco");
} }
private void pauseTimer() { private void pauseTimerWithWarning() {
if (countDownTimer != null) { new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext())
countDownTimer.cancel(); .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; isTimerRunning = false;
btnStartFocus.setText("Começar Foco");
timeLeftInMillis = 25 * 60 * 1000;
updateCountDownText();
})
.show();
} }
@@ -321,85 +537,47 @@ public class InicioFragment extends Fragment {
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
if (tasksListener != null) { if (tasksListener != null) tasksListener.remove();
tasksListener.remove(); if (userListener != null) userListener.remove();
} if (rankingListener != null) rankingListener.remove();
if (userListener != null) { if (countDownTimer != null) countDownTimer.cancel();
userListener.remove();
}
pauseTimer(); // Parar o timer se a view for destruída
} }
private void showAddTaskDialog() { private void showAddTaskDialog() {
if (getContext() == null) return; View dialogView = getLayoutInflater().inflate(R.layout.dialog_add_task, null);
android.widget.EditText etTitle = dialogView.findViewById(R.id.etTaskTitle);
// Build input field android.widget.EditText etDuration = dialogView.findViewById(R.id.etTaskDuration);
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);
androidx.appcompat.app.AlertDialog dialog = new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext()) androidx.appcompat.app.AlertDialog dialog = new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext())
.setTitle("Novo Desafio") .setTitle("Nova Tarefa")
.setView(container) .setView(dialogView)
.setNegativeButton("Cancelar", null) .setNegativeButton("Cancelar", null)
.setPositiveButton("Adicionar", null) .setPositiveButton("Guardar", null)
.create(); .create();
dialog.setOnShowListener(d -> { dialog.setOnShowListener(d -> {
// Style buttons dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
android.widget.Button btnAdd = dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE); String title = etTitle.getText().toString().trim();
android.widget.Button btnCancel = dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEGATIVE); String durationStr = etDuration.getText().toString().trim();
if (btnAdd != null) btnAdd.setTextColor(getResources().getColor(R.color.primary_purple));
if (btnCancel != null) btnCancel.setTextColor(getResources().getColor(R.color.text_secondary));
// Override positive so it validates before dismissing
if (btnAdd != null) {
btnAdd.setOnClickListener(v -> {
String title = editText.getText().toString().trim();
if (title.isEmpty()) { if (title.isEmpty()) {
editText.setError("Escreve um desafio"); etTitle.setError("Obrigatório");
return; return;
} }
saveNewTask(title); int duration = 25;
if (!durationStr.isEmpty()) duration = Integer.parseInt(durationStr);
saveNewTask(title, duration);
dialog.dismiss(); dialog.dismiss();
}); });
}
editText.requestFocus();
if (dialog.getWindow() != null) {
dialog.getWindow().setSoftInputMode(
android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
}
}); });
dialog.show(); dialog.show();
} }
private void saveNewTask(String title) { private void saveNewTask(String title, int duration) {
com.google.firebase.auth.FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser();
if (currentUser == null) return; if (currentUser == null) return;
String uid = currentUser.getUid(); String uid = currentUser.getUid();
String taskId = com.google.firebase.firestore.FirebaseFirestore.getInstance() String taskId = com.google.firebase.firestore.FirebaseFirestore.getInstance().collection("tasks").document().getId();
.collection("tasks").document().getId(); Task task = new Task(taskId, title, 30, duration, uid);
Task task = new Task(taskId, title, 10, uid);
FirestoreManager.getInstance().addTask(task); FirestoreManager.getInstance().addTask(task);
} }
} }

View File

@@ -16,71 +16,153 @@ import com.google.firebase.firestore.ListenerRegistration;
public class ProfileFragment extends Fragment { public class ProfileFragment extends Fragment {
private TextView tvUsername, tvHandle, tvStreakValue, tvTotalXP, tvLeagueName, tvAchievementsCount; private TextView tvProfileName, tvProfileTitle, tvProfileBio;
private ListenerRegistration userListener; private GridLayout badgesGrid;
private LinearLayout friendsListContainer;
private ListenerRegistration userListener, friendsListener;
private View view;
@Nullable @Nullable
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_profile, container, false); View view = inflater.inflate(R.layout.fragment_profile, container, false);
ImageButton btnSettings = view.findViewById(R.id.btnSettings); initViews(view);
btnSettings.setOnClickListener(v -> { setupListeners(view);
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);
});
startObservingUser(); startObservingUser();
startObservingFriends();
return view; 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() { private void startObservingUser() {
FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser();
if (currentUser != null) { 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) { private void updateStats(View view, Usuario user) {
if (getContext() == null) return; setStat(view.findViewById(R.id.statXP), "", String.valueOf(user.xp), "Total XP");
tvUsername.setText(user.usuario); setStat(view.findViewById(R.id.statStreak), "🔥", String.valueOf(user.streak), "Ofensiva");
tvHandle.setText(user.handle); setStat(view.findViewById(R.id.statLevel), "", String.valueOf(user.level), "Nível");
tvStreakValue.setText(String.valueOf(user.streak)); setStat(view.findViewById(R.id.statTasks), "", String.valueOf(user.total_tasks_concluidas), "Tarefas");
tvTotalXP.setText(String.valueOf(user.xp)); setStat(view.findViewById(R.id.statFocusTime), "⏱️", user.tempo_foco_total + "m", "Foco");
tvLeagueName.setText(user.league); setStat(view.findViewById(R.id.statSessions), "🎯", String.valueOf(user.sessoes_foco_completas), "Sessões");
tvAchievementsCount.setText(String.valueOf(user.achievementsCount)); }
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 @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
if (userListener != null) { if (userListener != null) userListener.remove();
userListener.remove(); if (friendsListener != null) friendsListener.remove();
}
}
private void openFindFriends() {
Intent intent = new Intent(getActivity(), FindFriendsActivity.class);
startActivity(intent);
} }
} }

View File

@@ -15,155 +15,101 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.switchmaterial.SwitchMaterial; import com.google.android.material.switchmaterial.SwitchMaterial;
import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import java.util.Locale;
public class SettingsActivity extends AppCompatActivity { 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 SharedPreferences sharedPreferences;
private static final String PREFS_NAME = "FluxupSettings"; private static final String PREFS_NAME = "FluxupSettings";
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
sharedPreferences = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); 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); setContentView(R.layout.activity_settings);
initViews(); initSettings();
setupListeners(); findViewById(R.id.btnBack).setOnClickListener(v -> finish());
loadSettings();
} }
private void applyTheme(boolean isDarkMode) { private void initSettings() {
if (isDarkMode) { // --- CONTA ---
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); setupClickable(R.id.settingEmail, "Email", FirebaseAuth.getInstance().getCurrentUser().getEmail(), null);
} else { setupClickable(R.id.settingPassword, "Alterar Palavra-passe", "********", v -> resetPassword());
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); setupSwitch(R.id.settingPublicProfile, "Perfil Público", "public_profile", true);
}
}
private void initViews() { // --- APARÊNCIA ---
btnBack = findViewById(R.id.btnBack); setupSwitch(R.id.settingDarkMode, "Modo Escuro", "darkMode", false, (v, isChecked) -> {
switchDarkMode = findViewById(R.id.switchDarkMode); AppCompatDelegate.setDefaultNightMode(isChecked ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
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);
}); });
setupClickable(R.id.settingThemeColor, "Cor do Tema", "Roxo (Padrão)", null);
switchPrivacy.setOnCheckedChangeListener((buttonView, isChecked) -> saveSetting("privacy", isChecked)); // --- NOTIFICAÇÕES ---
switchNotifications.setOnCheckedChangeListener((buttonView, isChecked) -> saveSetting("notifications", isChecked)); setupSwitch(R.id.settingDailyReminders, "Lembretes Diários", "daily_reminders", true);
setupSwitch(R.id.settingAntiProcrastination, "Anti-Procrastinação", "anti_procrastination", true);
btnLogout.setOnClickListener(v -> { // --- FOCO ---
FirebaseAuth.getInstance().signOut(); setupClickable(R.id.settingFocusDuration, "Duração do Foco", sharedPreferences.getInt("focus_duration", 25) + " min", v -> showDurationDialog("focus_duration", "Duração do Foco", 25));
Intent intent = new Intent(SettingsActivity.this, LoginActivity.class); setupClickable(R.id.settingBreakDuration, "Duração da Pausa", sharedPreferences.getInt("break_duration", 5) + " min", v -> showDurationDialog("break_duration", "Duração da Pausa", 5));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
});
spinnerLanguage.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { // --- PRIVACIDADE ---
@Override setupSwitch(R.id.settingIncognito, "Modo Incógnito", "incognito", false);
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { setupClickable(R.id.settingExportData, "Exportar Dados", "JSON/CSV", null);
String selectedLang = "pt";
if (position == 1) selectedLang = "en";
else if (position == 2) selectedLang = "es";
String currentLang = sharedPreferences.getString("language", "pt"); findViewById(R.id.btnDeleteAccount).setOnClickListener(v -> {
if (!selectedLang.equals(currentLang)) { Toast.makeText(this, "Funcionalidade disponível em breve", Toast.LENGTH_SHORT).show();
saveSetting("language", selectedLang);
updateLocale(selectedLang);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {}
});
btnChangePassword.setOnClickListener(v -> {
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null && user.getEmail() != null) {
FirebaseAuth.getInstance().sendPasswordResetEmail(user.getEmail())
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
Toast.makeText(this, "Email de redefinição enviado!", Toast.LENGTH_SHORT).show();
}
});
}
}); });
} }
private void loadSettings() { private void setupClickable(int id, String title, String value, View.OnClickListener listener) {
switchDarkMode.setChecked(sharedPreferences.getBoolean("darkMode", false)); View view = findViewById(id);
switchPrivacy.setChecked(sharedPreferences.getBoolean("privacy", false)); if (view == null) return;
switchNotifications.setChecked(sharedPreferences.getBoolean("notifications", true)); ((TextView) view.findViewById(R.id.tvSettingTitle)).setText(title);
((TextView) view.findViewById(R.id.tvSettingValue)).setText(value);
String lang = sharedPreferences.getString("language", "pt"); if (listener != null) view.setOnClickListener(listener);
if (lang.equals("en")) spinnerLanguage.setSelection(1, false); else view.findViewById(R.id.ivChevron).setVisibility(View.GONE);
else if (lang.equals("es")) spinnerLanguage.setSelection(2, false);
else spinnerLanguage.setSelection(0, false);
} }
private void updateLocaleSilent(String langCode) { private void setupSwitch(int id, String title, String key, boolean defValue) {
Locale locale = new Locale(langCode); setupSwitch(id, title, key, defValue, null);
Locale.setDefault(locale);
Resources resources = getResources();
Configuration config = resources.getConfiguration();
config.setLocale(locale);
resources.updateConfiguration(config, resources.getDisplayMetrics());
} }
private void updateLocale(String langCode) { private void setupSwitch(int id, String title, String key, boolean defValue, android.widget.CompoundButton.OnCheckedChangeListener extraListener) {
updateLocaleSilent(langCode); View view = findViewById(id);
Intent intent = new Intent(this, SettingsActivity.class); if (view == null) return;
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); ((TextView) view.findViewById(R.id.tvSettingTitle)).setText(title);
startActivity(intent); SwitchMaterial sw = view.findViewById(R.id.switchSetting);
finish(); 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) { private void resetPassword() {
sharedPreferences.edit().putBoolean(key, value).apply(); 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) { private void showDurationDialog(String key, String title, int current) {
sharedPreferences.edit().putString(key, value).apply(); 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 String title;
public boolean completed; public boolean completed;
public int xpReward; public int xpReward;
public int duration; // em minutos
public String userId; public String userId;
public Long completedDate;
public Task() {} 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.id = id;
this.title = title; this.title = title;
this.completed = false; this.completed = false;
this.xpReward = xpReward; this.xpReward = xpReward;
this.duration = duration;
this.userId = userId; this.userId = userId;
} }
} }

View File

@@ -14,14 +14,16 @@ import com.google.firebase.firestore.ListenerRegistration;
public class TrophiesActivity extends AppCompatActivity { 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 ImageButton btnBack;
private LinearLayout trophyContainer, inactiveState; private Button btnEarnXpNow;
private ImageView trophy1, trophy2, trophy3;
private FirestoreManager firestoreManager; private FirestoreManager firestoreManager;
private FirebaseAuth mAuth; private FirebaseAuth mAuth;
private ListenerRegistration userListener; private ListenerRegistration rankingListener, userListener;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -34,104 +36,159 @@ public class TrophiesActivity extends AppCompatActivity {
initViews(); initViews();
setupListeners(); setupListeners();
observeUserData(); observeUserData();
observeRanking("global");
} }
private void initViews() { private void initViews() {
tvDivisionTitle = findViewById(R.id.tvDivisionTitle); tvLeagueName = findViewById(R.id.tvLeagueName);
tvTimeRemaining = findViewById(R.id.tvTimeRemaining); tvLeagueTimeRemaining = findViewById(R.id.tvLeagueTimeRemaining);
tvTrophyProgress = findViewById(R.id.tvTrophyProgress); tvLeagueObjective = findViewById(R.id.tvLeagueObjective);
tvMotivational = findViewById(R.id.tvMotivational); 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); btnBack = findViewById(R.id.btnBack);
trophyContainer = findViewById(R.id.trophyContainer); btnEarnXpNow = findViewById(R.id.btnEarnXpNow);
inactiveState = findViewById(R.id.inactiveState);
trophy1 = findViewById(R.id.trophy1); updateTimeRemaining();
trophy2 = findViewById(R.id.trophy2);
trophy3 = findViewById(R.id.trophy3);
} }
private void setupListeners() { private void setupListeners() {
btnBack.setOnClickListener(v -> finish()); 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() { private void observeUserData() {
String uid = mAuth.getUid(); String uid = mAuth.getUid();
if (uid != null) { 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; 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 private void observeRanking(String filter) {
int currentStreak = user.streak; if (rankingListener != null) rankingListener.remove();
int trophies = user.trophiesCount;
// Update trophy visuals com.google.firebase.firestore.Query query = com.google.firebase.firestore.FirebaseFirestore.getInstance()
updateTrophyIcons(trophies); .collection("users")
.orderBy("xp_semanal", com.google.firebase.firestore.Query.Direction.DESCENDING)
.limit(20);
// Progress message rankingListener = query.addSnapshotListener((snapshots, e) -> {
int daysToNext = 30 - (currentStreak % 30); if (e != null || snapshots == null) return;
if (daysToNext == 30 && currentStreak > 0) { rankingContainer.removeAllViews();
tvTrophyProgress.setText("Troféu conquistado! Mantém a ofensiva.");
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 { } else {
tvTrophyProgress.setText("Faltam " + daysToNext + " dias para o próximo troféu"); 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);
}
} }
// Handle inactive state @Override
if (currentStreak == 0) { protected void onDestroy() {
inactiveState.setVisibility(View.VISIBLE); super.onDestroy();
trophyContainer.setAlpha(0.5f); if (rankingListener != null) rankingListener.remove();
tvMotivational.setVisibility(View.GONE); if (userListener != null) userListener.remove();
} 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);
} }
} }

View File

@@ -11,13 +11,28 @@ public class Usuario {
public String updated_at; public String updated_at;
// Stats and Social // 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 streak = 0;
public int melhor_streak = 0;
public String league = "Bronze"; public String league = "Bronze";
public String handle = ""; public String handle = "";
public String bio = ""; public String bio = "";
public String titulo = "Iniciante";
public int achievementsCount = 0; public int achievementsCount = 0;
public int trophiesCount = 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() {} public Usuario() {}

View File

@@ -6,174 +6,70 @@
android:background="@color/background_light" android:background="@color/background_light"
android:orientation="vertical"> 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 <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="64dp" android:layout_height="wrap_content"
android:paddingHorizontal="16dp"> android:layout_marginBottom="16dp">
<ImageButton <ImageButton
android:id="@+id/btnClose" android:id="@+id/btnClose"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_close" android:src="@drawable/ic_back"
app:tint="@color/text_primary" /> app:tint="@color/text_primary" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" android:layout_centerInParent="true"
android:text="Encontre os seus amigos" android:text="Amigos"
android:textColor="@color/text_primary" android:textColor="@color/text_primary"
android:textSize="18sp" android:textSize="20sp"
android:textStyle="bold" /> android:textStyle="bold" />
</RelativeLayout> </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 <androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<LinearLayout <LinearLayout
android:id="@+id/friendsResultsContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="20dp"> android:padding="16dp">
<!-- Results will be loaded here -->
<!-- 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" />
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

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

View File

@@ -26,10 +26,11 @@
app:tint="@color/text_primary" /> app:tint="@color/text_primary" />
<TextView <TextView
android:id="@+id/tvToolbarTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" android:layout_centerInParent="true"
android:text="Troféus" android:text="Ligas"
android:textColor="@color/text_primary" android:textColor="@color/text_primary"
android:textSize="20sp" android:textSize="20sp"
android:textStyle="bold" /> android:textStyle="bold" />
@@ -44,134 +45,206 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical" android:orientation="vertical"
android:padding="24dp"> android:padding="20dp">
<!-- Division Info --> <!-- 1. 🥇 HEADER DA LIGA -->
<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 -->
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="32dp" android:layout_marginBottom="20dp"
android:layout_marginBottom="32dp" app:cardCornerRadius="16dp"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp"> app:cardElevation="2dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:padding="32dp"> android:padding="20dp"
<LinearLayout
android:id="@+id/trophyContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"> android:gravity="center">
<!-- Trophies will be added here dynamically or statically -->
<ImageView <ImageView
android:id="@+id/trophy1" android:id="@+id/ivLeagueBadge"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="8dp"
android:src="@drawable/ic_trophy_bronze" />
<ImageView
android:id="@+id/trophy2"
android:layout_width="80dp" android:layout_width="80dp"
android:layout_height="80dp" android:layout_height="80dp"
android:layout_margin="8dp" android:src="@drawable/ic_trophy_bronze"
android:src="@drawable/ic_trophy_silver" android:layout_marginBottom="12dp"/>
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>
<TextView <TextView
android:id="@+id/tvTrophyProgress" android:id="@+id/tvLeagueName"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:text="Divisão Bronze"
android:text="Faltam 15 dias para o próximo troféu"
android:textColor="@color/text_primary" android:textColor="@color/text_primary"
android:textSize="16sp" android:textSize="22sp"
android:textStyle="bold" /> 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> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<!-- Inactive State (Initially Hidden) --> <!-- 2. 🎯 OBJETIVO DA LIGA -->
<LinearLayout <TextView
android:id="@+id/inactiveState" 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_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:visibility="gone"> android:layout_marginBottom="24dp"/>
<ImageView <!-- 5. ⚡ PROGRESSO DO UTILIZADOR -->
android:layout_width="150dp" <androidx.cardview.widget.CardView
android:layout_height="150dp" android:layout_width="match_parent"
android:src="@drawable/ic_sleeping_char" /> 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 <TextView
android:id="@+id/tvUserRankingStatus"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:text="Faltam 120 XP para entrares no TOP 3"
android:text="Hora de voltar!"
android:textColor="@color/text_primary" android:textColor="@color/text_primary"
android:textSize="20sp" android:textStyle="bold"
android:textStyle="bold" />
<TextView
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:textColor="@color/text_secondary"
android:textSize="14sp"/> android:textSize="14sp"/>
</LinearLayout>
<!-- Active Message -->
<TextView <TextView
android:id="@+id/tvMotivational" android:id="@+id/tvXPToNextPosition"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Estás a progredir bem na Divisão Prata!" android:layout_marginTop="4dp"
android:text="Diferença para o próximo: 45 XP"
android:textColor="@color/text_secondary"
android:textSize="12sp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 6. 🎁 RECOMPENSAS DA SEMANA -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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:textAlignment="center"
android:textColor="@color/text_secondary" 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> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@@ -12,7 +12,7 @@
android:orientation="vertical" android:orientation="vertical"
android:padding="20dp"> android:padding="20dp">
<!-- Header --> <!-- 1. 👋 HEADER -->
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -27,28 +27,34 @@
android:id="@+id/tvGreeting" android:id="@+id/tvGreeting"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Olá, Jvitor!" android:text="Olá, Utilizador!"
android:textColor="@color/text_primary" android:textColor="@color/text_primary"
android:textSize="24sp" android:textSize="24sp"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/tvMotivationalSubtitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text="Pronto para ser produtivo?" android:text="Só precisas de começar."
android:textColor="@color/text_secondary" android:textColor="@color/text_secondary"
android:textSize="16sp" /> android:textSize="16sp" />
</LinearLayout> </LinearLayout>
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/cardProfileAvatar"
android:layout_width="56dp" android:layout_width="56dp"
android:layout_height="56dp" android:layout_height="56dp"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardCornerRadius="28dp" app:cardCornerRadius="28dp"
app:cardElevation="2dp"> app:cardElevation="2dp">
<ImageView <ImageView
android:id="@+id/ivUserAvatar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scaleType="centerCrop" android:scaleType="centerCrop"
@@ -57,60 +63,125 @@
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
</RelativeLayout> </RelativeLayout>
<!-- Progress Overview --> <!-- 2. 🔥 CARD "HOJE" -->
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="24dp" android:layout_marginBottom="24dp"
app:cardCornerRadius="@dimen/radius_duo" app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp" app:cardElevation="4dp">
app:contentPadding="20dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical"
android:padding="20dp">
<RelativeLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" 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 <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="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:textColor="@color/text_primary"
android:textSize="18sp" android:layout_marginBottom="8dp"/>
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>
<ProgressBar <ProgressBar
android:id="@+id/pbDailyTasks" android:id="@+id/pbDailyTasksProgress"
style="?android:attr/progressBarStyleHorizontal" style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="12dp" android:layout_height="12dp"
android:max="100" android:max="100"
android:progress="33" android:progress="0"
android:progressDrawable="@drawable/progress_bar_duo" /> android:progressDrawable="@drawable/progress_bar_duo" />
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<!-- Daily Challenges List --> <!-- 3. 🎯 TAREFAS DO DIA -->
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
android:text="Meus Desafios" android:text="Tarefas do Dia"
android:textColor="@color/text_primary" android:textColor="@color/text_primary"
android:textSize="20sp" android:textSize="20sp"
android:textStyle="bold" /> android:textStyle="bold" />
@@ -122,24 +193,21 @@
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:orientation="vertical" /> android:orientation="vertical" />
<Button <TextView
android:id="@+id/btnAddTasks" android:id="@+id/tvNoTasksIncentive"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="56dp" android:layout_height="wrap_content"
android:layout_marginBottom="32dp" android:text="Ainda não tens tarefas para hoje. Vamos começar?"
android:background="@drawable/button_primary" android:textAlignment="center"
android:text="+ Adicionar Desafio" android:layout_marginBottom="16dp"
android:textAllCaps="false" android:visibility="gone"
android:textColor="@color/white" android:textColor="@color/text_secondary"/>
android:textSize="16sp"
android:textStyle="bold"
app:backgroundTint="@null" />
<!-- Focus Mode Section --> <!-- 4. ⏱️ MODO FOCO -->
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="40dp" android:layout_marginBottom="24dp"
app:cardCornerRadius="@dimen/radius_duo" app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp" app:cardElevation="2dp"
app:contentPadding="24dp"> app:contentPadding="24dp">
@@ -207,43 +275,16 @@
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<!-- Daily Progress Path Section --> <!-- 5. 📊 PROGRESSO DIÁRIO -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true" android:layout_marginBottom="12dp"
android:text="Caminho de Progresso Diário" android:text="Progresso Semanal"
android:textColor="@color/text_primary" android:textColor="@color/text_primary"
android:textSize="19sp" android:textSize="20sp"
android:textStyle="bold" /> 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 <LinearLayout
android:id="@+id/progressPathContainer" android:id="@+id/progressPathContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -253,7 +294,123 @@
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:orientation="vertical" android:orientation="vertical"
android:paddingVertical="32dp" 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> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@@ -12,414 +12,182 @@
android:orientation="vertical" android:orientation="vertical"
android:padding="20dp"> android:padding="20dp">
<!-- Top Header --> <!-- 👤 HEADER -->
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="24dp"> 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 <androidx.cardview.widget.CardView
android:layout_width="100dp" android:id="@+id/cardAvatar"
android:layout_height="100dp" android:layout_width="80dp"
android:layout_marginBottom="16dp" android:layout_height="80dp"
app:cardCornerRadius="50dp" app:cardCornerRadius="40dp"
app:cardElevation="4dp"> app:cardElevation="2dp">
<ImageView <ImageView
android:id="@+id/ivAvatar" android:id="@+id/ivProfileAvatar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:src="@drawable/ic_nav_profile" android:src="@drawable/ic_nav_profile" />
app:tint="@color/primary_purple" />
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<TextView <LinearLayout
android:id="@+id/tvUsername"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="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:textColor="@color/text_primary"
android:textSize="26sp" android:textSize="22sp"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/tvHandle" android:id="@+id/tvProfileTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@jvitor_prod" android:text="Iniciante de Foco"
android:textColor="@color/text_secondary" android:textColor="@color/primary_purple"
android:textSize="14sp" android:textSize="14sp"
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" /> android:layout_marginTop="4dp" />
</LinearLayout> </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 <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Estatísticas" android:text="Estatísticas"
android:textColor="@color/text_primary" android:textStyle="bold"
android:textSize="20sp" 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" /> android:textStyle="bold" />
</RelativeLayout>
<GridLayout <GridLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="32dp" android:columnCount="3"
android:columnCount="2" android:layout_marginBottom="24dp">
android:orientation="horizontal">
<!-- Streak Card --> <include layout="@layout/item_stat_small" android:id="@+id/statXP" />
<androidx.cardview.widget.CardView <include layout="@layout/item_stat_small" android:id="@+id/statStreak" />
android:layout_width="0dp" <include layout="@layout/item_stat_small" android:id="@+id/statLevel" />
android:layout_height="wrap_content" <include layout="@layout/item_stat_small" android:id="@+id/statTasks" />
android:layout_columnWeight="1" <include layout="@layout/item_stat_small" android:id="@+id/statFocusTime" />
android:layout_margin="6dp" <include layout="@layout/item_stat_small" android:id="@+id/statSessions" />
app:cardCornerRadius="@dimen/radius_duo" </GridLayout>
app:cardElevation="2dp"
app:contentPadding="16dp">
<LinearLayout <!-- 🏆 CONQUISTAS (Badges) -->
<RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:layout_marginBottom="12dp">
<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" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Conquistas" android:text="Conquistas"
android:textColor="@color/text_secondary" android:textStyle="bold"
android:textSize="12sp" /> android:textSize="18sp" />
</LinearLayout> <TextView
</androidx.cardview.widget.CardView> android:id="@+id/btnViewAllBadges"
</GridLayout> 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 <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp"> android:layout_marginBottom="12dp">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Amigos" android:text="Amigos"
android:textColor="@color/text_primary" android:textStyle="bold"
android:textSize="20sp" android:textSize="18sp" />
android:textStyle="bold" />
<TextView <TextView
android:id="@+id/btnViewAllFriends" android:id="@+id/btnManageFriends"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_centerVertical="true" android:text="Gerir"
android:text="Ver Todos"
android:textColor="@color/primary_purple" android:textColor="@color/primary_purple"
android:textSize="14sp"
android:textStyle="bold" /> android:textStyle="bold" />
</RelativeLayout> </RelativeLayout>
<HorizontalScrollView <LinearLayout
android:id="@+id/friendsListContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="32dp" android:orientation="vertical"
android:scrollbars="none"> android:layout_marginBottom="24dp"/>
<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>
<!-- 🔘 BOTÕES DE ACÇÃO -->
<Button <Button
android:id="@+id/btnInviteFriends" android:id="@+id/btnLogout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="56dp" android:layout_height="56dp"
android:background="@drawable/button_primary" android:text="Terminar Sessão"
android:text="+ Encontrar Amigos" android:backgroundTint="@color/error_red"
android:textAllCaps="false"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" android:layout_marginBottom="40dp"/>
android:textStyle="bold"
app:backgroundTint="@null" />
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View File

@@ -34,5 +34,28 @@
<string name="confirm_password_hint">Confirmar palavra-passe</string> <string name="confirm_password_hint">Confirmar palavra-passe</string>
<string name="register_button">Registar</string> <string name="register_button">Registar</string>
<string name="already_have_account">Já tens conta? </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> </resources>

View File

@@ -50,4 +50,23 @@
<item name="android:layout_marginBottom">@dimen/spacing_md</item> <item name="android:layout_marginBottom">@dimen/spacing_md</item>
</style> </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> </resources>

View File

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

View File

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