This commit is contained in:
MeuNome
2026-05-21 10:55:31 +01:00
parent 01b54cd8d0
commit d357a9b220
12 changed files with 568 additions and 358 deletions

View File

@@ -682,6 +682,18 @@
<option name="screenX" value="2076" /> <option name="screenX" value="2076" />
<option name="screenY" value="2152" /> <option name="screenY" value="2152" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="google" />
<option name="codename" value="comet" />
<option name="id" value="comet" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro Fold" />
<option name="screenDensity" value="390" />
<option name="screenX" value="2076" />
<option name="screenY" value="2152" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="35" /> <option name="api" value="35" />
<option name="brand" value="motorola" /> <option name="brand" value="motorola" />

View File

@@ -31,7 +31,6 @@
<activity android:name=".StatisticsActivity" /> <activity android:name=".StatisticsActivity" />
<activity android:name=".FindFriendsActivity" /> <activity android:name=".FindFriendsActivity" />
<activity android:name=".TrophiesActivity" /> <activity android:name=".TrophiesActivity" />
<activity android:name=".MyFriendsActivity" />
<activity android:name=".AvatarEditorActivity" /> <activity android:name=".AvatarEditorActivity" />
</application> </application>

View File

@@ -1,40 +1,60 @@
package com.fluxup.app; package com.fluxup.app;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.text.Editable;
import android.text.TextWatcher;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager; import com.google.android.material.tabs.TabLayout;
import androidx.recyclerview.widget.RecyclerView; import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FieldValue;
import com.google.firebase.firestore.FirebaseFirestore;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
public class FindFriendsActivity extends AppCompatActivity { public class FindFriendsActivity extends AppCompatActivity {
private EditText etSearch; private EditText etSearch;
private LinearLayout resultsContainer; private LinearLayout resultsContainer;
private com.google.android.material.tabs.TabLayout tabLayout; private TabLayout tabLayout;
private ImageButton btnClose; private ImageButton btnClose;
private String currentTab = "Sugestões";
private FirebaseFirestore db;
private String myUid;
private int activeTabPosition = 0; // 0: Sugestões, 1: Os meus amigos, 2: Pendentes
private List<Usuario> allFriends = new ArrayList<>();
private static class PendingRequest {
Usuario sender;
String requestId;
PendingRequest(Usuario sender, String requestId) {
this.sender = sender;
this.requestId = requestId;
}
}
private List<PendingRequest> allRequests = new ArrayList<>();
@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);
db = FirebaseFirestore.getInstance();
myUid = FirebaseAuth.getInstance().getUid();
initViews(); initViews();
setupListeners(); setupListeners();
loadSuggestions(); loadTabContent();
} }
private void initViews() { private void initViews() {
@@ -47,62 +67,152 @@ public class FindFriendsActivity extends AppCompatActivity {
private void setupListeners() { private void setupListeners() {
btnClose.setOnClickListener(v -> finish()); btnClose.setOnClickListener(v -> finish());
etSearch.addTextChangedListener(new android.text.TextWatcher() { tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override
@Override public void onTextChanged(CharSequence s, int start, int before, int count) { public void onTabSelected(TabLayout.Tab tab) {
if (s.length() > 0) { activeTabPosition = tab.getPosition();
performSearch(s.toString()); etSearch.setText(""); // Clear search when switching tabs
} else { updateSearchHint();
loadSuggestions(); loadTabContent();
}
} }
@Override public void afterTextChanged(android.text.Editable s) {}
@Override
public void onTabUnselected(TabLayout.Tab tab) {}
@Override
public void onTabReselected(TabLayout.Tab tab) {}
}); });
tabLayout.addOnTabSelectedListener(new com.google.android.material.tabs.TabLayout.OnTabSelectedListener() { etSearch.addTextChangedListener(new TextWatcher() {
@Override @Override
public void onTabSelected(com.google.android.material.tabs.TabLayout.Tab tab) { public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
currentTab = tab.getText().toString();
if (currentTab.equals("Sugestões")) loadSuggestions(); @Override
else resultsContainer.removeAllViews(); // Placeholder for others public void onTextChanged(CharSequence s, int start, int before, int count) {
performSearch(s.toString());
} }
@Override public void onTabUnselected(com.google.android.material.tabs.TabLayout.Tab tab) {}
@Override public void onTabReselected(com.google.android.material.tabs.TabLayout.Tab tab) {} @Override
public void afterTextChanged(Editable s) {}
}); });
} }
private void updateSearchHint() {
if (activeTabPosition == 0) {
etSearch.setHint("Procurar novas pessoas...");
} else if (activeTabPosition == 1) {
etSearch.setHint("Procurar amigos...");
} else {
etSearch.setHint("Procurar pedidos...");
}
}
private void loadTabContent() {
if (myUid == null) return;
resultsContainer.removeAllViews();
if (activeTabPosition == 0) {
loadSuggestionsTab("");
} else if (activeTabPosition == 1) {
loadFriendsTab();
} else {
loadRequestsTab();
}
}
private void performSearch(String query) { private void performSearch(String query) {
com.google.firebase.firestore.FirebaseFirestore.getInstance() if (activeTabPosition == 0) {
.collection("users") loadSuggestionsTab(query);
.orderBy("usuario") } else if (activeTabPosition == 1) {
.startAt(query) filterFriends(query);
.endAt(query + "\uf8ff") } else {
.limit(10) filterRequests(query);
}
}
// -------------------------------------------------------------------------
// TAB 0: SUGESTÕES (Discover)
// -------------------------------------------------------------------------
private void loadSuggestionsTab(String query) {
// Fetch all friend IDs + sent/received request IDs to exclude
db.collection("friendships")
.whereArrayContains("users", myUid)
.get() .get()
.addOnSuccessListener(snapshots -> { .addOnSuccessListener(friendSnapshots -> {
resultsContainer.removeAllViews(); List<String> excludedIds = new ArrayList<>();
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) { excludedIds.add(myUid);
Usuario u = doc.toObject(Usuario.class);
if (u != null) addFriendItem(u); for (DocumentSnapshot doc : friendSnapshots.getDocuments()) {
List<String> users = (List<String>) doc.get("users");
if (users != null) {
for (String uId : users) {
if (!uId.equals(myUid)) excludedIds.add(uId);
}
}
} }
// Fetch sent requests
db.collection("friend_requests")
.whereEqualTo("fromUserId", myUid)
.get()
.addOnSuccessListener(sentSnap -> {
for (DocumentSnapshot doc : sentSnap.getDocuments()) {
String toUser = doc.getString("toUserId");
if (toUser != null) excludedIds.add(toUser);
}
// Fetch received requests
db.collection("friend_requests")
.whereEqualTo("toUserId", myUid)
.get()
.addOnSuccessListener(recvSnap -> {
for (DocumentSnapshot doc : recvSnap.getDocuments()) {
String fromUser = doc.getString("fromUserId");
if (fromUser != null) excludedIds.add(fromUser);
}
// Now query users
queryDiscoverUsers(excludedIds, query);
});
});
}); });
} }
private void loadSuggestions() { private void queryDiscoverUsers(List<String> excludedIds, String query) {
com.google.firebase.firestore.FirebaseFirestore.getInstance() if (query.isEmpty()) {
.collection("users") db.collection("users")
.limit(10) .limit(50)
.get() .get()
.addOnSuccessListener(snapshots -> { .addOnSuccessListener(snapshots -> {
resultsContainer.removeAllViews(); resultsContainer.removeAllViews();
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) { for (DocumentSnapshot doc : snapshots.getDocuments()) {
Usuario u = doc.toObject(Usuario.class); Usuario u = doc.toObject(Usuario.class);
if (u != null) addFriendItem(u); if (u != null && !excludedIds.contains(u.id_usuario)) {
} addDiscoverItem(u);
}); }
}
});
} else {
db.collection("users")
.orderBy("usuario")
.startAt(query)
.endAt(query + "\uf8ff")
.limit(50)
.get()
.addOnSuccessListener(snapshots -> {
resultsContainer.removeAllViews();
for (DocumentSnapshot doc : snapshots.getDocuments()) {
Usuario u = doc.toObject(Usuario.class);
if (u != null && !excludedIds.contains(u.id_usuario)) {
addDiscoverItem(u);
}
}
});
}
} }
private void addFriendItem(Usuario user) { private void addDiscoverItem(Usuario user) {
View view = getLayoutInflater().inflate(R.layout.item_friend_action, resultsContainer, false); View view = getLayoutInflater().inflate(R.layout.item_friend_action, resultsContainer, false);
TextView tvName = view.findViewById(R.id.tvFriendName); TextView tvName = view.findViewById(R.id.tvFriendName);
TextView tvStats = view.findViewById(R.id.tvFriendStats); TextView tvStats = view.findViewById(R.id.tvFriendStats);
@@ -110,6 +220,7 @@ public class FindFriendsActivity extends AppCompatActivity {
tvName.setText(user.usuario); tvName.setText(user.usuario);
tvStats.setText("Nível " + user.level + "" + user.xp + " XP"); tvStats.setText("Nível " + user.level + "" + user.xp + " XP");
btnAction.setText("Adicionar");
AvatarView ivAvatar = view.findViewById(R.id.ivFriendAvatar); AvatarView ivAvatar = view.findViewById(R.id.ivFriendAvatar);
if (ivAvatar != null && user.avatar != null) { if (ivAvatar != null && user.avatar != null) {
@@ -121,7 +232,227 @@ public class FindFriendsActivity extends AppCompatActivity {
btnAction.setText("Pendente"); btnAction.setText("Pendente");
btnAction.setEnabled(false); btnAction.setEnabled(false);
btnAction.setAlpha(0.5f); btnAction.setAlpha(0.5f);
Toast.makeText(this, "Pedido enviado para " + user.usuario, Toast.LENGTH_SHORT).show();
Map<String, Object> req = new HashMap<>();
req.put("fromUserId", myUid);
req.put("toUserId", user.id_usuario);
req.put("status", "pending");
req.put("timestamp", FieldValue.serverTimestamp());
String reqId = myUid + "_" + user.id_usuario;
db.collection("friend_requests").document(reqId).set(req)
.addOnSuccessListener(aVoid -> {
Toast.makeText(this, "Pedido enviado para " + user.usuario, Toast.LENGTH_SHORT).show();
})
.addOnFailureListener(e -> {
btnAction.setText("Adicionar");
btnAction.setEnabled(true);
btnAction.setAlpha(1.0f);
Toast.makeText(this, "Erro ao enviar pedido.", Toast.LENGTH_SHORT).show();
});
});
resultsContainer.addView(view);
}
// -------------------------------------------------------------------------
// TAB 1: OS MEUS AMIGOS
// -------------------------------------------------------------------------
private void loadFriendsTab() {
db.collection("friendships")
.whereArrayContains("users", myUid)
.get()
.addOnSuccessListener(snapshots -> {
allFriends.clear();
if (snapshots.isEmpty()) {
resultsContainer.removeAllViews();
return;
}
List<String> friendIds = new ArrayList<>();
for (DocumentSnapshot doc : snapshots.getDocuments()) {
List<String> users = (List<String>) doc.get("users");
if (users != null) {
for (String uId : users) {
if (!uId.equals(myUid)) friendIds.add(uId);
}
}
}
if (friendIds.isEmpty()) {
resultsContainer.removeAllViews();
return;
}
final int[] remaining = {friendIds.size()};
for (String friendId : friendIds) {
db.collection("users").document(friendId).get().addOnSuccessListener(snapshot -> {
Usuario friend = snapshot.toObject(Usuario.class);
if (friend != null) {
allFriends.add(friend);
}
remaining[0]--;
if (remaining[0] == 0) {
filterFriends(etSearch.getText().toString());
}
}).addOnFailureListener(e -> {
remaining[0]--;
if (remaining[0] == 0) {
filterFriends(etSearch.getText().toString());
}
});
}
});
}
private void filterFriends(String query) {
resultsContainer.removeAllViews();
for (Usuario friend : allFriends) {
if (query.isEmpty() || friend.usuario.toLowerCase().contains(query.toLowerCase())) {
addFriendItem(friend);
}
}
}
private void addFriendItem(Usuario friend) {
View view = getLayoutInflater().inflate(R.layout.item_friend_action, resultsContainer, false);
TextView tvName = view.findViewById(R.id.tvFriendName);
TextView tvStats = view.findViewById(R.id.tvFriendStats);
Button btnAction = view.findViewById(R.id.btnFriendAction);
tvName.setText(friend.usuario);
tvStats.setText("Nível " + friend.level + "" + friend.xp + " XP • 🔥 " + friend.streak);
btnAction.setText("Ver perfil");
AvatarView ivAvatar = view.findViewById(R.id.ivFriendAvatar);
if (ivAvatar != null && friend.avatar != null) {
ivAvatar.setAvatarData(friend.avatar);
ivAvatar.setLeague(friend.league);
}
View.OnClickListener clickListener = v -> {
Toast.makeText(this, "Perfil de " + friend.usuario, Toast.LENGTH_SHORT).show();
};
btnAction.setOnClickListener(clickListener);
view.setOnClickListener(clickListener);
resultsContainer.addView(view);
}
// -------------------------------------------------------------------------
// TAB 2: PENDENTES (Requests)
// -------------------------------------------------------------------------
private void loadRequestsTab() {
db.collection("friend_requests")
.whereEqualTo("toUserId", myUid)
.whereEqualTo("status", "pending")
.get()
.addOnSuccessListener(snapshots -> {
allRequests.clear();
if (snapshots.isEmpty()) {
resultsContainer.removeAllViews();
return;
}
List<String> senderIds = new ArrayList<>();
Map<String, String> requestMap = new HashMap<>(); // senderId -> requestId
for (DocumentSnapshot doc : snapshots.getDocuments()) {
String fromUserId = doc.getString("fromUserId");
if (fromUserId != null) {
senderIds.add(fromUserId);
requestMap.put(fromUserId, doc.getId());
}
}
final int[] remaining = {senderIds.size()};
for (String senderId : senderIds) {
db.collection("users").document(senderId).get().addOnSuccessListener(userSnap -> {
Usuario sender = userSnap.toObject(Usuario.class);
if (sender != null) {
allRequests.add(new PendingRequest(sender, requestMap.get(senderId)));
}
remaining[0]--;
if (remaining[0] == 0) {
filterRequests(etSearch.getText().toString());
}
}).addOnFailureListener(e -> {
remaining[0]--;
if (remaining[0] == 0) {
filterRequests(etSearch.getText().toString());
}
});
}
});
}
private void filterRequests(String query) {
resultsContainer.removeAllViews();
for (PendingRequest req : allRequests) {
if (query.isEmpty() || req.sender.usuario.toLowerCase().contains(query.toLowerCase())) {
addRequestItem(req.sender, req.requestId);
}
}
}
private void addRequestItem(Usuario sender, String requestId) {
View view = getLayoutInflater().inflate(R.layout.item_friend_request, resultsContainer, false);
TextView tvName = view.findViewById(R.id.tvFriendName);
TextView tvStats = view.findViewById(R.id.tvFriendStats);
Button btnAccept = view.findViewById(R.id.btnAcceptFriend);
Button btnReject = view.findViewById(R.id.btnRejectFriend);
tvName.setText(sender.usuario);
tvStats.setText(sender.xp + " XP");
AvatarView ivAvatar = view.findViewById(R.id.ivFriendAvatar);
if (ivAvatar != null && sender.avatar != null) {
ivAvatar.setAvatarData(sender.avatar);
ivAvatar.setLeague(sender.league);
}
btnAccept.setOnClickListener(v -> {
btnAccept.setEnabled(false);
btnReject.setEnabled(false);
String friendshipId = myUid.compareTo(sender.id_usuario) < 0 ? myUid + "_" + sender.id_usuario : sender.id_usuario + "_" + myUid;
Map<String, Object> friendship = new HashMap<>();
List<String> users = new ArrayList<>();
users.add(myUid);
users.add(sender.id_usuario);
friendship.put("users", users);
friendship.put("timestamp", FieldValue.serverTimestamp());
db.collection("friendships").document(friendshipId).set(friendship)
.addOnSuccessListener(aVoid -> {
db.collection("friend_requests").document(requestId).delete()
.addOnSuccessListener(aVoid2 -> {
Toast.makeText(this, "Pedido de " + sender.usuario + " aceite!", Toast.LENGTH_SHORT).show();
loadTabContent();
});
})
.addOnFailureListener(e -> {
btnAccept.setEnabled(true);
btnReject.setEnabled(true);
Toast.makeText(this, "Erro ao aceitar pedido.", Toast.LENGTH_SHORT).show();
});
});
btnReject.setOnClickListener(v -> {
btnAccept.setEnabled(false);
btnReject.setEnabled(false);
db.collection("friend_requests").document(requestId).delete()
.addOnSuccessListener(aVoid -> {
Toast.makeText(this, "Pedido recusado.", Toast.LENGTH_SHORT).show();
loadTabContent();
})
.addOnFailureListener(e -> {
btnAccept.setEnabled(true);
btnReject.setEnabled(true);
Toast.makeText(this, "Erro ao recusar pedido.", Toast.LENGTH_SHORT).show();
});
}); });
resultsContainer.addView(view); resultsContainer.addView(view);

View File

@@ -42,12 +42,11 @@ public class InicioFragment extends Fragment {
private TextView tvTimer, tvGreeting, tvMotivationalSubtitle, tvFocusStatus, tvFocusTitle; private TextView tvTimer, tvGreeting, tvMotivationalSubtitle, tvFocusStatus, tvFocusTitle;
private AvatarView ivUserAvatar; private AvatarView ivUserAvatar;
private TextView tvTodayStreak, tvTodayXP, tvTodayTasksCount, tvNoTasksIncentive; private TextView tvTodayStreak, tvTodayXP, tvTodayTasksCount, tvNoTasksIncentive;
private TextView tvDailyRewardGoal;
private ProgressBar pbDailyTasksProgress; private ProgressBar pbDailyTasksProgress;
private androidx.recyclerview.widget.RecyclerView rvTasks; private androidx.recyclerview.widget.RecyclerView rvTasks;
private TasksAdapter tasksAdapter; private TasksAdapter tasksAdapter;
private LinearLayout miniRankingContainer, layoutTasksSection; private LinearLayout miniRankingContainer, layoutTasksSection;
private Button btnStartFocus, btnSecondaryFocus, btnAddTasks, btnClaimReward; private Button btnStartFocus, btnSecondaryFocus, btnAddTasks;
private androidx.cardview.widget.CardView btnStreakPage; private androidx.cardview.widget.CardView btnStreakPage;
private CountDownTimer countDownTimer; private CountDownTimer countDownTimer;
@@ -145,10 +144,15 @@ public class InicioFragment extends Fragment {
startActivity(new android.content.Intent(getActivity(), TrophiesActivity.class)); startActivity(new android.content.Intent(getActivity(), TrophiesActivity.class));
}); });
// Reward // Promo Card to Rewards Tab
tvDailyRewardGoal = view.findViewById(R.id.tvDailyRewardGoal); view.findViewById(R.id.cardRewardsPromo).setOnClickListener(v -> {
btnClaimReward = view.findViewById(R.id.btnClaimReward); if (getActivity() instanceof MainActivity) {
btnClaimReward.setOnClickListener(v -> claimDailyReward()); BottomNavigationView nav = ((MainActivity) getActivity()).findViewById(R.id.bottom_navigation);
if (nav != null) {
nav.setSelectedItemId(R.id.nav_rewards);
}
}
});
// Streak Page // Streak Page
btnStreakPage = view.findViewById(R.id.btnStreakPage); btnStreakPage = view.findViewById(R.id.btnStreakPage);
@@ -797,33 +801,27 @@ public class InicioFragment extends Fragment {
} }
} }
private void claimDailyReward() {
FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser();
if (currentUser != null) {
String today = new java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.US).format(new java.util.Date());
Map<String, Object> updates = new HashMap<>();
updates.put("last_reward_claim_date", today);
FirestoreManager.getInstance().updateUserStats(currentUser.getUid(), updates);
addXP(100);
btnClaimReward.setEnabled(false);
btnClaimReward.setText("Resgatado");
btnClaimReward.setAlpha(0.5f);
if (getContext() != null) {
Toast.makeText(getContext(), "Recompensa diária resgatada! +100 XP", Toast.LENGTH_LONG).show();
}
}
}
private void showDeleteConfirmDialog(Task task) { private void showDeleteConfirmDialog(Task task) {
new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext()) new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext())
.setTitle("Eliminar Tarefa") .setTitle("Apagar Tarefa")
.setMessage("Tens a certeza que queres eliminar esta tarefa?") .setMessage("Tens a certeza que queres apagar esta tarefa?")
.setNegativeButton("Cancelar", null) .setNegativeButton("Cancelar", null)
.setPositiveButton("Sim", (dialog, which) -> { .setPositiveButton("Apagar", (dialog, which) -> {
FirestoreManager.getInstance().deleteTask(task.id); com.google.firebase.firestore.FirebaseFirestore.getInstance()
refreshHomeStats(); .collection("tasks").document(task.id).delete()
.addOnSuccessListener(aVoid -> {
refreshHomeStats();
if (getContext() != null) {
Toast.makeText(getContext(), "Tarefa apagada.", Toast.LENGTH_SHORT).show();
}
})
.addOnFailureListener(e -> {
if (getContext() != null) {
Toast.makeText(getContext(), "Erro ao apagar tarefa: " + e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
}
});
}) })
.show(); .show();
} }
@@ -1150,6 +1148,7 @@ public class InicioFragment extends Fragment {
new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext()) new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext())
.setTitle("Editar Tarefa") .setTitle("Editar Tarefa")
.setView(dialogView) .setView(dialogView)
.setNeutralButton("Apagar", (dialog, which) -> showDeleteConfirmDialog(task))
.setNegativeButton("Cancelar", null) .setNegativeButton("Cancelar", null)
.setPositiveButton("Atualizar", (dialog, which) -> { .setPositiveButton("Atualizar", (dialog, which) -> {
String title = etTitle.getText().toString().trim(); String title = etTitle.getText().toString().trim();

View File

@@ -48,6 +48,8 @@ public class MainActivity extends AppCompatActivity {
} else if (itemId == R.id.nav_trophies) { } else if (itemId == R.id.nav_trophies) {
startActivity(new android.content.Intent(this, TrophiesActivity.class)); startActivity(new android.content.Intent(this, TrophiesActivity.class));
return true; return true;
} else if (itemId == R.id.nav_rewards) {
selectedFragment = new RewardsFragment();
} else if (itemId == R.id.nav_profile) { } else if (itemId == R.id.nav_profile) {
selectedFragment = new ProfileFragment(); selectedFragment = new ProfileFragment();
} else if (itemId == R.id.nav_search) { } else if (itemId == R.id.nav_search) {

View File

@@ -1,109 +0,0 @@
package com.fluxup.app;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.FirebaseFirestore;
import java.util.ArrayList;
import java.util.List;
public class MyFriendsActivity extends AppCompatActivity {
private EditText etSearch;
private LinearLayout friendsContainer, emptyState;
private TextView tvFriendsCount;
private FirebaseFirestore db;
private String myUid;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_friends);
db = FirebaseFirestore.getInstance();
myUid = FirebaseAuth.getInstance().getUid();
etSearch = findViewById(R.id.etSearchFriends);
friendsContainer = findViewById(R.id.friendsListContainer);
emptyState = findViewById(R.id.emptyStateContainer);
tvFriendsCount = findViewById(R.id.tvFriendsCount);
findViewById(R.id.btnBack).setOnClickListener(v -> finish());
findViewById(R.id.btnFindNewFriends).setOnClickListener(v -> startActivity(new Intent(this, FindFriendsActivity.class)));
findViewById(R.id.btnGoToFindFriends).setOnClickListener(v -> startActivity(new Intent(this, FindFriendsActivity.class)));
loadFriends();
}
private void loadFriends() {
if (myUid == null) return;
// Fetching users I follow
db.collection("follows")
.whereEqualTo("followerId", myUid)
.get()
.addOnSuccessListener(snapshots -> {
if (snapshots.isEmpty()) {
showEmptyState(true);
return;
}
showEmptyState(false);
tvFriendsCount.setText(snapshots.size() + " Amigos");
friendsContainer.removeAllViews();
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) {
String friendId = doc.getString("followingId");
if (friendId != null) {
fetchFriendDetails(friendId);
}
}
});
}
private void fetchFriendDetails(String friendId) {
db.collection("users").document(friendId).get().addOnSuccessListener(snapshot -> {
Usuario friend = snapshot.toObject(Usuario.class);
if (friend != null) {
addFriendItem(friend);
}
});
}
private void addFriendItem(Usuario friend) {
View view = getLayoutInflater().inflate(R.layout.item_ranking_user, friendsContainer, false);
TextView tvName = view.findViewById(R.id.tvRankingName);
TextView tvXP = view.findViewById(R.id.tvRankingXP);
TextView tvLabel = view.findViewById(R.id.tvRankingLabelTu);
tvName.setText(friend.usuario);
tvXP.setText(friend.xp + " XP");
tvLabel.setText("Nível " + friend.level);
tvLabel.setVisibility(View.VISIBLE);
tvLabel.setTextColor(getResources().getColor(R.color.primary_purple));
AvatarView ivAvatar = view.findViewById(R.id.ivRankingAvatar);
if (ivAvatar != null && friend.avatar != null) {
ivAvatar.setAvatarData(friend.avatar);
ivAvatar.setLeague(friend.league);
}
view.setOnClickListener(v -> {
// Open public profile (future)
Toast.makeText(this, "Perfil de " + friend.usuario, Toast.LENGTH_SHORT).show();
});
friendsContainer.addView(view);
}
private void showEmptyState(boolean show) {
emptyState.setVisibility(show ? View.VISIBLE : View.GONE);
friendsContainer.setVisibility(show ? View.GONE : View.VISIBLE);
}
}

View File

@@ -134,19 +134,19 @@ public class ProfileFragment extends Fragment {
// Add Friends (+) // Add Friends (+)
View btnAddFriends = view.findViewById(R.id.btnAddFriends); View btnAddFriends = view.findViewById(R.id.btnAddFriends);
if (btnAddFriends != null) { if (btnAddFriends != null) {
btnAddFriends.setOnClickListener(v -> startActivity(new Intent(getActivity(), MyFriendsActivity.class))); btnAddFriends.setOnClickListener(v -> startActivity(new Intent(getActivity(), FindFriendsActivity.class)));
} }
// View All Friends // View All Friends
View btnViewAllFriends = view.findViewById(R.id.btnViewAllFriends); View btnViewAllFriends = view.findViewById(R.id.btnViewAllFriends);
if (btnViewAllFriends != null) { if (btnViewAllFriends != null) {
btnViewAllFriends.setOnClickListener(v -> startActivity(new Intent(getActivity(), MyFriendsActivity.class))); btnViewAllFriends.setOnClickListener(v -> startActivity(new Intent(getActivity(), FindFriendsActivity.class)));
} }
// Find Friends (ADICIONAR AMIGOS) // Find Friends (ADICIONAR AMIGOS)
View btnFindFriends = view.findViewById(R.id.btnFindFriends); View btnFindFriends = view.findViewById(R.id.btnFindFriends);
if (btnFindFriends != null) { if (btnFindFriends != null) {
btnFindFriends.setOnClickListener(v -> startActivity(new Intent(getActivity(), MyFriendsActivity.class))); btnFindFriends.setOnClickListener(v -> startActivity(new Intent(getActivity(), FindFriendsActivity.class)));
} }
// View All Badges // View All Badges
@@ -408,21 +408,39 @@ public class ProfileFragment extends Fragment {
} }
private void startObservingFriends() { private void startObservingFriends() {
if (AuthManager.getInstance().getCurrentUser() == null) return;
String myUid = AuthManager.getInstance().getCurrentUser().getUid();
friendsListener = com.google.firebase.firestore.FirebaseFirestore.getInstance() friendsListener = com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("users") .collection("friendships")
.whereArrayContains("users", myUid)
.limit(3) .limit(3)
.addSnapshotListener((snapshots, e) -> { .addSnapshotListener((snapshots, e) -> {
if (e != null || snapshots == null) return; if (e != null || snapshots == null) return;
friendsListContainer.removeAllViews(); friendsListContainer.removeAllViews();
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) { for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) {
Usuario f = doc.toObject(Usuario.class); List<String> users = (List<String>) doc.get("users");
if (f != null && !f.id_usuario.equals(AuthManager.getInstance().getCurrentUser().getUid())) { if (users != null) {
addFriendItem(f); for (String uId : users) {
if (!uId.equals(myUid)) {
fetchAndAddProfileFriendItem(uId);
}
}
} }
} }
}); });
} }
private void fetchAndAddProfileFriendItem(String friendId) {
com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("users").document(friendId).get().addOnSuccessListener(snapshot -> {
if (getContext() == null || view == null) return;
Usuario friend = snapshot.toObject(Usuario.class);
if (friend != null) {
addFriendItem(friend);
}
});
}
private void addFriendItem(Usuario friend) { private void addFriendItem(Usuario friend) {
View v = getLayoutInflater().inflate(R.layout.item_ranking_user, friendsListContainer, false); View v = getLayoutInflater().inflate(R.layout.item_ranking_user, friendsListContainer, false);
((TextView) v.findViewById(R.id.tvRankingName)).setText(friend.usuario); ((TextView) v.findViewById(R.id.tvRankingName)).setText(friend.usuario);

View File

@@ -12,6 +12,8 @@ import androidx.appcompat.app.AppCompatActivity;
import android.widget.Button; import android.widget.Button;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import java.util.Calendar; import java.util.Calendar;
import java.util.ArrayList;
import java.util.List;
import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.ListenerRegistration; import com.google.firebase.firestore.ListenerRegistration;
import android.graphics.Color; import android.graphics.Color;
@@ -219,26 +221,94 @@ public class TrophiesActivity extends AppCompatActivity {
} }
private void observeRanking(String filter) { private void observeRanking(String filter) {
if (rankingListener != null) rankingListener.remove(); if (rankingListener != null) {
rankingListener.remove();
rankingListener = null;
}
com.google.firebase.firestore.Query query = com.google.firebase.firestore.FirebaseFirestore.getInstance() String myUid = mAuth.getUid();
.collection("users") android.util.Log.d("FLUXUP_DEBUG", "CURRENT_USER_ID: " + myUid);
.orderBy("xp_semanal", com.google.firebase.firestore.Query.Direction.DESCENDING) android.util.Log.d("FLUXUP_DEBUG", "LEAGUE_TAB_SELECTED: " + filter);
.limit(20);
rankingListener = query.addSnapshotListener((snapshots, e) -> { if ("friends".equals(filter)) {
if (e != null || snapshots == null) return; // Load accepted friends from "friendships"
rankingContainer.removeAllViews(); com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("friendships")
.whereArrayContains("users", myUid)
.get()
.addOnSuccessListener(snapshots -> {
List<String> friendIds = new ArrayList<>();
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) {
List<String> users = (List<String>) doc.get("users");
if (users != null) {
for (String uId : users) {
if (!uId.equals(myUid)) {
friendIds.add(uId);
}
}
}
}
int position = 1; android.util.Log.d("FLUXUP_DEBUG", "MY_ACCEPTED_FRIENDS_COUNT: " + friendIds.size());
String myUid = mAuth.getUid(); android.util.Log.d("FLUXUP_DEBUG", "LEAGUE_FRIENDS_LIST: " + friendIds.toString());
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) { rankingContainer.removeAllViews();
Usuario user = doc.toObject(Usuario.class); if (friendIds.isEmpty()) {
if (user == null) continue; return;
addRankingItem(position++, user, myUid); }
}
}); // Fetch details of all friends and sort by xp_semanal desc
List<Usuario> friendList = new ArrayList<>();
final int[] remaining = {friendIds.size()};
for (String friendId : friendIds) {
com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("users").document(friendId).get().addOnSuccessListener(userSnap -> {
Usuario friend = userSnap.toObject(Usuario.class);
if (friend != null) {
friendList.add(friend);
}
remaining[0]--;
if (remaining[0] == 0) {
// Sort by xp_semanal descending
friendList.sort((u1, u2) -> Integer.compare(u2.xp_semanal, u1.xp_semanal));
int pos = 1;
for (Usuario u : friendList) {
addRankingItem(pos++, u, myUid);
}
}
}).addOnFailureListener(e -> {
remaining[0]--;
if (remaining[0] == 0) {
friendList.sort((u1, u2) -> Integer.compare(u2.xp_semanal, u1.xp_semanal));
int pos = 1;
for (Usuario u : friendList) {
addRankingItem(pos++, u, myUid);
}
}
});
}
});
} else {
// Global ranking
com.google.firebase.firestore.Query query = com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("users")
.orderBy("xp_semanal", com.google.firebase.firestore.Query.Direction.DESCENDING)
.limit(20);
rankingListener = query.addSnapshotListener((snapshots, e) -> {
if (e != null || snapshots == null) return;
android.util.Log.d("FLUXUP_DEBUG", "GLOBAL_RANKING_COUNT: " + snapshots.size());
rankingContainer.removeAllViews();
int position = 1;
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) {
Usuario user = doc.toObject(Usuario.class);
if (user == null) continue;
addRankingItem(position++, user, myUid);
}
});
}
} }
private void addRankingItem(int pos, Usuario user, String myUid) { private void addRankingItem(int pos, Usuario user, String myUid) {

View File

@@ -40,6 +40,15 @@ public class Usuario {
public AvatarData avatar = new AvatarData(); // Avatar gamificado public AvatarData avatar = new AvatarData(); // Avatar gamificado
public java.util.List<String> dias_concluidos = new java.util.ArrayList<>(); public java.util.List<String> dias_concluidos = new java.util.ArrayList<>();
// Gamification & Rewards
public int login_streak = 0;
public String last_login_claim_date = ""; // YYYY-MM-DD
public java.util.List<String> claimed_missions_today = new java.util.ArrayList<>();
public String claimed_missions_date = ""; // YYYY-MM-DD
public java.util.List<String> claimed_streak_rewards = new java.util.ArrayList<>();
public String last_box_open_date = ""; // YYYY-MM-DD
public int coins = 0;
public Usuario() {} public Usuario() {}
public Usuario(String id_usuario, String usuario, String email, String palavra_passe, String numero) { public Usuario(String id_usuario, String usuario, String email, String palavra_passe, String numero) {

View File

@@ -1,122 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_light">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/card_background"
app:elevation="0dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="64dp"
android:paddingHorizontal="16dp">
<ImageButton
android:id="@+id/btnBack"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_back"
app:tint="@color/text_primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Os Meus Amigos"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold" />
</RelativeLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<EditText
android:id="@+id/etSearchFriends"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/edittext_bg"
android:hint="Pesquisar amigos..."
android:paddingHorizontal="16dp"
android:layout_marginBottom="20dp"
android:drawableStart="@drawable/ic_nav_search"
android:drawablePadding="12dp"
android:maxLines="1"
android:inputType="text"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="16dp">
<TextView
android:id="@+id/tvFriendsCount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="0 Amigos"
android:textStyle="bold"
android:textColor="@color/text_primary"/>
<TextView
android:id="@+id/btnFindNewFriends"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Encontrar Novos"
android:textColor="@color/primary_purple"
android:textStyle="bold"/>
</LinearLayout>
<LinearLayout
android:id="@+id/friendsListContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<LinearLayout
android:id="@+id/emptyStateContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="40dp"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ainda não tens amigos adicionados."
android:textAlignment="center"
android:textColor="@color/text_secondary"
android:layout_marginBottom="20dp"/>
<Button
android:id="@+id/btnGoToFindFriends"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Encontrar Amigos"
android:backgroundTint="@color/primary_purple"
android:textAllCaps="false"/>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -443,14 +443,17 @@
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<!-- 7. 🎁 RECOMPENSA DIÁRIA --> <!-- 7. 🎁 ACESSO ÀS RECOMPENSAS -->
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/cardRewardsPromo"
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:layout_marginBottom="32dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardCornerRadius="@dimen/radius_duo" app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp" app:cardElevation="2dp">
app:cardBackgroundColor="@color/primary_purple">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -471,36 +474,30 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:orientation="vertical" android:orientation="vertical"
android:layout_marginStart="16dp"> android:layout_marginStart="16dp"
android:layout_marginEnd="8dp">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Presente diário" android:text="Central de Recompensas"
android:textColor="@color/white" android:textColor="@color/text_primary"
android:textStyle="bold" android:textStyle="bold"
android:textSize="16sp"/> android:textSize="16sp"/>
<TextView <TextView
android:id="@+id/tvDailyRewardGoal"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Completa 3 tarefas hoje para ganhar +100 XP" android:text="Resgata prémios, missões e sequências diárias!"
android:textColor="@color/white" android:textColor="@color/text_secondary"
android:alpha="0.8"
android:textSize="12sp"/> android:textSize="12sp"/>
</LinearLayout> </LinearLayout>
<Button <ImageView
android:id="@+id/btnClaimReward" android:layout_width="24dp"
android:layout_width="wrap_content" android:layout_height="24dp"
android:layout_height="wrap_content" android:src="@drawable/ic_arrow_right"
android:text="Resgatar" app:tint="@color/primary_purple"/>
android:textSize="12sp"
android:textColor="@color/primary_purple"
android:backgroundTint="@color/white"
android:enabled="false"
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"/>
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>

View File

@@ -8,6 +8,10 @@
android:id="@+id/nav_trophies" android:id="@+id/nav_trophies"
android:icon="@drawable/ic_nav_trophy" android:icon="@drawable/ic_nav_trophy"
android:title="Troféus" /> android:title="Troféus" />
<item
android:id="@+id/nav_rewards"
android:icon="@drawable/ic_nav_rewards"
android:title="Recompensas" />
<item <item
android:id="@+id/nav_profile" android:id="@+id/nav_profile"
android:icon="@drawable/ic_nav_profile" android:icon="@drawable/ic_nav_profile"