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="screenY" value="2152" />
</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>
<option name="api" value="35" />
<option name="brand" value="motorola" />

View File

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

View File

@@ -1,40 +1,60 @@
package com.fluxup.app;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.tabs.TabLayout;
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.HashMap;
import java.util.List;
import java.util.Map;
public class FindFriendsActivity extends AppCompatActivity {
private EditText etSearch;
private LinearLayout resultsContainer;
private com.google.android.material.tabs.TabLayout tabLayout;
private TabLayout tabLayout;
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
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_find_friends);
db = FirebaseFirestore.getInstance();
myUid = FirebaseAuth.getInstance().getUid();
initViews();
setupListeners();
loadSuggestions();
loadTabContent();
}
private void initViews() {
@@ -47,62 +67,152 @@ public class FindFriendsActivity extends AppCompatActivity {
private void setupListeners() {
btnClose.setOnClickListener(v -> finish());
etSearch.addTextChangedListener(new android.text.TextWatcher() {
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.length() > 0) {
performSearch(s.toString());
} else {
loadSuggestions();
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
activeTabPosition = tab.getPosition();
etSearch.setText(""); // Clear search when switching tabs
updateSearchHint();
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
public void onTabSelected(com.google.android.material.tabs.TabLayout.Tab tab) {
currentTab = tab.getText().toString();
if (currentTab.equals("Sugestões")) loadSuggestions();
else resultsContainer.removeAllViews(); // Placeholder for others
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
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) {
com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("users")
if (activeTabPosition == 0) {
loadSuggestionsTab(query);
} else if (activeTabPosition == 1) {
filterFriends(query);
} else {
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()
.addOnSuccessListener(friendSnapshots -> {
List<String> excludedIds = new ArrayList<>();
excludedIds.add(myUid);
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 queryDiscoverUsers(List<String> excludedIds, String query) {
if (query.isEmpty()) {
db.collection("users")
.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);
}
}
});
} else {
db.collection("users")
.orderBy("usuario")
.startAt(query)
.endAt(query + "\uf8ff")
.limit(10)
.limit(50)
.get()
.addOnSuccessListener(snapshots -> {
resultsContainer.removeAllViews();
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) {
for (DocumentSnapshot doc : snapshots.getDocuments()) {
Usuario u = doc.toObject(Usuario.class);
if (u != null) addFriendItem(u);
if (u != null && !excludedIds.contains(u.id_usuario)) {
addDiscoverItem(u);
}
}
});
}
private void loadSuggestions() {
com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("users")
.limit(10)
.get()
.addOnSuccessListener(snapshots -> {
resultsContainer.removeAllViews();
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) {
Usuario u = doc.toObject(Usuario.class);
if (u != null) addFriendItem(u);
}
});
}
private void addFriendItem(Usuario user) {
private void addDiscoverItem(Usuario user) {
View view = getLayoutInflater().inflate(R.layout.item_friend_action, resultsContainer, false);
TextView tvName = view.findViewById(R.id.tvFriendName);
TextView tvStats = view.findViewById(R.id.tvFriendStats);
@@ -110,6 +220,7 @@ public class FindFriendsActivity extends AppCompatActivity {
tvName.setText(user.usuario);
tvStats.setText("Nível " + user.level + "" + user.xp + " XP");
btnAction.setText("Adicionar");
AvatarView ivAvatar = view.findViewById(R.id.ivFriendAvatar);
if (ivAvatar != null && user.avatar != null) {
@@ -121,7 +232,227 @@ public class FindFriendsActivity extends AppCompatActivity {
btnAction.setText("Pendente");
btnAction.setEnabled(false);
btnAction.setAlpha(0.5f);
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);

View File

@@ -42,12 +42,11 @@ public class InicioFragment extends Fragment {
private TextView tvTimer, tvGreeting, tvMotivationalSubtitle, tvFocusStatus, tvFocusTitle;
private AvatarView ivUserAvatar;
private TextView tvTodayStreak, tvTodayXP, tvTodayTasksCount, tvNoTasksIncentive;
private TextView tvDailyRewardGoal;
private ProgressBar pbDailyTasksProgress;
private androidx.recyclerview.widget.RecyclerView rvTasks;
private TasksAdapter tasksAdapter;
private LinearLayout miniRankingContainer, layoutTasksSection;
private Button btnStartFocus, btnSecondaryFocus, btnAddTasks, btnClaimReward;
private Button btnStartFocus, btnSecondaryFocus, btnAddTasks;
private androidx.cardview.widget.CardView btnStreakPage;
private CountDownTimer countDownTimer;
@@ -145,10 +144,15 @@ public class InicioFragment extends Fragment {
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());
// Promo Card to Rewards Tab
view.findViewById(R.id.cardRewardsPromo).setOnClickListener(v -> {
if (getActivity() instanceof MainActivity) {
BottomNavigationView nav = ((MainActivity) getActivity()).findViewById(R.id.bottom_navigation);
if (nav != null) {
nav.setSelectedItemId(R.id.nav_rewards);
}
}
});
// Streak Page
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) {
new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext())
.setTitle("Eliminar Tarefa")
.setMessage("Tens a certeza que queres eliminar esta tarefa?")
.setTitle("Apagar Tarefa")
.setMessage("Tens a certeza que queres apagar esta tarefa?")
.setNegativeButton("Cancelar", null)
.setPositiveButton("Sim", (dialog, which) -> {
FirestoreManager.getInstance().deleteTask(task.id);
.setPositiveButton("Apagar", (dialog, which) -> {
com.google.firebase.firestore.FirebaseFirestore.getInstance()
.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();
}
@@ -1150,6 +1148,7 @@ public class InicioFragment extends Fragment {
new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext())
.setTitle("Editar Tarefa")
.setView(dialogView)
.setNeutralButton("Apagar", (dialog, which) -> showDeleteConfirmDialog(task))
.setNegativeButton("Cancelar", null)
.setPositiveButton("Atualizar", (dialog, which) -> {
String title = etTitle.getText().toString().trim();

View File

@@ -48,6 +48,8 @@ public class MainActivity extends AppCompatActivity {
} else if (itemId == R.id.nav_trophies) {
startActivity(new android.content.Intent(this, TrophiesActivity.class));
return true;
} else if (itemId == R.id.nav_rewards) {
selectedFragment = new RewardsFragment();
} else if (itemId == R.id.nav_profile) {
selectedFragment = new ProfileFragment();
} 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 (+)
View btnAddFriends = view.findViewById(R.id.btnAddFriends);
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 btnViewAllFriends = view.findViewById(R.id.btnViewAllFriends);
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)
View btnFindFriends = view.findViewById(R.id.btnFindFriends);
if (btnFindFriends != null) {
btnFindFriends.setOnClickListener(v -> startActivity(new Intent(getActivity(), MyFriendsActivity.class)));
btnFindFriends.setOnClickListener(v -> startActivity(new Intent(getActivity(), FindFriendsActivity.class)));
}
// View All Badges
@@ -408,18 +408,36 @@ public class ProfileFragment extends Fragment {
}
private void startObservingFriends() {
if (AuthManager.getInstance().getCurrentUser() == null) return;
String myUid = AuthManager.getInstance().getCurrentUser().getUid();
friendsListener = com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("users")
.collection("friendships")
.whereArrayContains("users", myUid)
.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);
List<String> users = (List<String>) doc.get("users");
if (users != null) {
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);
}
});
}

View File

@@ -12,6 +12,8 @@ import androidx.appcompat.app.AppCompatActivity;
import android.widget.Button;
import android.widget.ProgressBar;
import java.util.Calendar;
import java.util.ArrayList;
import java.util.List;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.ListenerRegistration;
import android.graphics.Color;
@@ -219,8 +221,75 @@ public class TrophiesActivity extends AppCompatActivity {
}
private void observeRanking(String filter) {
if (rankingListener != null) rankingListener.remove();
if (rankingListener != null) {
rankingListener.remove();
rankingListener = null;
}
String myUid = mAuth.getUid();
android.util.Log.d("FLUXUP_DEBUG", "CURRENT_USER_ID: " + myUid);
android.util.Log.d("FLUXUP_DEBUG", "LEAGUE_TAB_SELECTED: " + filter);
if ("friends".equals(filter)) {
// Load accepted friends from "friendships"
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);
}
}
}
}
android.util.Log.d("FLUXUP_DEBUG", "MY_ACCEPTED_FRIENDS_COUNT: " + friendIds.size());
android.util.Log.d("FLUXUP_DEBUG", "LEAGUE_FRIENDS_LIST: " + friendIds.toString());
rankingContainer.removeAllViews();
if (friendIds.isEmpty()) {
return;
}
// 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)
@@ -228,11 +297,11 @@ public class TrophiesActivity extends AppCompatActivity {
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;
String myUid = mAuth.getUid();
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) {
Usuario user = doc.toObject(Usuario.class);
if (user == null) continue;
@@ -240,6 +309,7 @@ public class TrophiesActivity extends AppCompatActivity {
}
});
}
}
private void addRankingItem(int pos, Usuario user, String myUid) {
View view = getLayoutInflater().inflate(R.layout.item_ranking_user, rankingContainer, false);

View File

@@ -40,6 +40,15 @@ public class Usuario {
public AvatarData avatar = new AvatarData(); // Avatar gamificado
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(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>
</androidx.cardview.widget.CardView>
<!-- 7. 🎁 RECOMPENSA DIÁRIA -->
<!-- 7. 🎁 ACESSO ÀS RECOMPENSAS -->
<androidx.cardview.widget.CardView
android:id="@+id/cardRewardsPromo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardCornerRadius="@dimen/radius_duo"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/primary_purple">
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
@@ -471,36 +474,30 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="16dp">
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Presente diário"
android:textColor="@color/white"
android:text="Central de Recompensas"
android:textColor="@color/text_primary"
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:text="Resgata prémios, missões e sequências diárias!"
android:textColor="@color/text_secondary"
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"/>
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_arrow_right"
app:tint="@color/primary_purple"/>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

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