diff --git a/app/src/main/java/com/example/vdcscore/LoginActivity.java b/app/src/main/java/com/example/vdcscore/LoginActivity.java index 477ef2e..05aa24c 100644 --- a/app/src/main/java/com/example/vdcscore/LoginActivity.java +++ b/app/src/main/java/com/example/vdcscore/LoginActivity.java @@ -39,12 +39,18 @@ public class LoginActivity extends AppCompatActivity { checkRememberMe = findViewById(R.id.checkRememberMe); criarContaTextView = findViewById(R.id.txtRegister); txtForgotPassword = findViewById(R.id.txtForgotPassword); + TextView txtGuestMode = findViewById(R.id.txtGuestMode); mAuth = FirebaseAuth.getInstance(); btnLogin.setOnClickListener(v -> loginUser()); criarContaTextView.setOnClickListener(view -> criarConta()); txtForgotPassword.setOnClickListener(view -> recuperarPassword()); + txtGuestMode.setOnClickListener(v -> { + Intent intent = new Intent(LoginActivity.this, MainActivity.class); + startActivity(intent); + finish(); + }); // Carregar credenciais guardadas loadSavedCredentials(); diff --git a/app/src/main/java/com/example/vdcscore/MainActivity.java b/app/src/main/java/com/example/vdcscore/MainActivity.java index fb2a782..bb3c1ee 100644 --- a/app/src/main/java/com/example/vdcscore/MainActivity.java +++ b/app/src/main/java/com/example/vdcscore/MainActivity.java @@ -22,6 +22,12 @@ import androidx.appcompat.app.AppCompatActivity; import com.example.vdcscore.databinding.ActivityMainBinding; import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.database.ValueEventListener; +import androidx.annotation.NonNull; import java.io.InputStream; import android.app.AlertDialog; @@ -43,6 +49,14 @@ public class MainActivity extends AppCompatActivity { private ActivityMainBinding binding; private ActivityResultLauncher mGetContent; + private java.util.Set favoriteClubs = new java.util.HashSet<>(); + private DatabaseReference favoritesRef; + private ValueEventListener favoritesListener; + private DatabaseReference senioresRef; + private ValueEventListener senioresListener; + private DatabaseReference junioresRef; + private ValueEventListener junioresListener; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -87,6 +101,9 @@ public class MainActivity extends AppCompatActivity { // Carregar dados do utilizador no menu lateral loadUserDataInNavHeader(); + + // Inicializar o sistema de notificações para favoritos + setupNotificationSystem(); } private void loadUserDataInNavHeader() { @@ -212,4 +229,157 @@ public class MainActivity extends AppCompatActivity { // Atualizar dados do utilizador quando voltar à activity loadUserDataInNavHeader(); } + + private void setupNotificationSystem() { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + if (user == null) return; + + createNotificationChannel(); + + // Listen to favorites + favoritesRef = FirebaseDatabase.getInstance().getReference("users") + .child(user.getUid()) + .child("favorites"); + + favoritesListener = new ValueEventListener() { + @Override + public void onDataChange(@NonNull DataSnapshot snapshot) { + favoriteClubs.clear(); + for (DataSnapshot favSnap : snapshot.getChildren()) { + if (Boolean.TRUE.equals(favSnap.getValue(Boolean.class))) { + favoriteClubs.add(favSnap.getKey()); + } + } + } + + @Override + public void onCancelled(@NonNull DatabaseError error) {} + }; + favoritesRef.addValueEventListener(favoritesListener); + + // Listen to seniores matches + senioresRef = FirebaseDatabase.getInstance().getReference("jornadas").child("seniores"); + senioresListener = new ValueEventListener() { + @Override + public void onDataChange(@NonNull DataSnapshot snapshot) { + checkMatchesAndNotify(snapshot); + } + + @Override + public void onCancelled(@NonNull DatabaseError error) {} + }; + senioresRef.addValueEventListener(senioresListener); + + // Listen to juniores matches + junioresRef = FirebaseDatabase.getInstance().getReference("jornadas").child("juniores"); + junioresListener = new ValueEventListener() { + @Override + public void onDataChange(@NonNull DataSnapshot snapshot) { + checkMatchesAndNotify(snapshot); + } + + @Override + public void onCancelled(@NonNull DatabaseError error) {} + }; + junioresRef.addValueEventListener(junioresListener); + } + + private void checkMatchesAndNotify(DataSnapshot categorySnapshot) { + if (favoriteClubs.isEmpty()) return; + + android.content.SharedPreferences prefs = getSharedPreferences("NotifiedMatchesPrefs", android.content.Context.MODE_PRIVATE); + android.content.SharedPreferences.Editor editor = prefs.edit(); + + for (DataSnapshot jornadaSnap : categorySnapshot.getChildren()) { + for (DataSnapshot matchSnap : jornadaSnap.getChildren()) { + com.example.vdcscore.ui.gallery.Match match = matchSnap.getValue(com.example.vdcscore.ui.gallery.Match.class); + if (match != null) { + match.setId(matchSnap.getKey()); + + if (match.isPlayed() && match.getId() != null) { + boolean involvesFavorite = false; + String favoriteName = ""; + + String homeSanitized = com.example.vdcscore.utils.FirebaseErrorUtils.sanitizeKey(match.getHomeName()); + String awaySanitized = com.example.vdcscore.utils.FirebaseErrorUtils.sanitizeKey(match.getAwayName()); + + if (homeSanitized != null && favoriteClubs.contains(homeSanitized)) { + involvesFavorite = true; + favoriteName = match.getHomeName(); + } else if (awaySanitized != null && favoriteClubs.contains(awaySanitized)) { + involvesFavorite = true; + favoriteName = match.getAwayName(); + } + + if (involvesFavorite) { + String matchPrefKey = "notified_" + match.getId(); + if (!prefs.getBoolean(matchPrefKey, false)) { + // Trigger notification! + triggerLocalNotification(match, favoriteName); + // Mark as notified + editor.putBoolean(matchPrefKey, true); + } + } + } + } + } + } + editor.apply(); + } + + private void triggerLocalNotification(com.example.vdcscore.ui.gallery.Match match, String favoriteClub) { + String homeScoreStr = match.getHomeScore() != null ? String.valueOf(match.getHomeScore()) : "0"; + String awayScoreStr = match.getAwayScore() != null ? String.valueOf(match.getAwayScore()) : "0"; + + String title = "Fim de Jogo - " + favoriteClub; + String message = match.getHomeName() + " " + homeScoreStr + " - " + awayScoreStr + " " + match.getAwayName(); + + androidx.core.app.NotificationCompat.Builder builder = new androidx.core.app.NotificationCompat.Builder(this, "vdcscore_notifications") + .setSmallIcon(R.drawable.ic_soccer) + .setContentTitle(title) + .setContentText(message) + .setPriority(androidx.core.app.NotificationCompat.PRIORITY_DEFAULT) + .setAutoCancel(true); + + // Click action: open MainActivity + android.content.Intent intent = new android.content.Intent(this, MainActivity.class); + intent.setFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK | android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK); + android.app.PendingIntent pendingIntent = android.app.PendingIntent.getActivity( + this, 0, intent, android.app.PendingIntent.FLAG_UPDATE_CURRENT | android.app.PendingIntent.FLAG_IMMUTABLE); + builder.setContentIntent(pendingIntent); + + android.app.NotificationManager notificationManager = (android.app.NotificationManager) getSystemService(android.content.Context.NOTIFICATION_SERVICE); + if (notificationManager != null) { + int notificationId = match.getId().hashCode(); + notificationManager.notify(notificationId, builder.build()); + } + } + + private void createNotificationChannel() { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + CharSequence name = "Resultados dos Jogos"; + String description = "Notificações de resultados dos jogos dos seus clubes favoritos"; + int importance = android.app.NotificationManager.IMPORTANCE_DEFAULT; + android.app.NotificationChannel channel = new android.app.NotificationChannel("vdcscore_notifications", name, importance); + channel.setDescription(description); + android.app.NotificationManager notificationManager = getSystemService(android.app.NotificationManager.class); + if (notificationManager != null) { + notificationManager.createNotificationChannel(channel); + } + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (favoritesRef != null && favoritesListener != null) { + favoritesRef.removeEventListener(favoritesListener); + } + if (senioresRef != null && senioresListener != null) { + senioresRef.removeEventListener(senioresListener); + } + if (junioresRef != null && junioresListener != null) { + junioresRef.removeEventListener(junioresListener); + } + } } diff --git a/app/src/main/java/com/example/vdcscore/ui/clubs/ClubDetailFragment.java b/app/src/main/java/com/example/vdcscore/ui/clubs/ClubDetailFragment.java index 0487531..c631e6b 100644 --- a/app/src/main/java/com/example/vdcscore/ui/clubs/ClubDetailFragment.java +++ b/app/src/main/java/com/example/vdcscore/ui/clubs/ClubDetailFragment.java @@ -26,6 +26,11 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import android.widget.ImageView; import android.widget.TextView; +import android.content.Intent; +import android.app.AlertDialog; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; +import com.example.vdcscore.LoginActivity; public class ClubDetailFragment extends Fragment { @@ -33,6 +38,10 @@ public class ClubDetailFragment extends Fragment { private DatabaseReference mDatabase; private String clubId; private String escalao; + private String currentClubName; + private boolean isFavorite = false; + private ValueEventListener favoriteListener; + private DatabaseReference favoriteRef; @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -96,6 +105,8 @@ public class ClubDetailFragment extends Fragment { private void populateStats(Team team) { binding.textDetailClubName.setText(team.getNome()); + currentClubName = team.getNome(); + setupFavoriteButton(); binding.labelStats.setVisibility(View.VISIBLE); binding.containerStats.setVisibility(View.VISIBLE); binding.btnCompare.setVisibility(View.VISIBLE); @@ -131,6 +142,8 @@ public class ClubDetailFragment extends Fragment { private void populateClubInfo(Club club) { binding.textDetailClubName.setText(club.getName()); + currentClubName = club.getName(); + setupFavoriteButton(); // Bind new club info fields binding.textDetailPresident.setText(club.getPresident() != null && !club.getPresident().isEmpty() ? club.getPresident() : "---"); @@ -325,9 +338,82 @@ public class ClubDetailFragment extends Fragment { binding.textCompareG2.setTypeface(null, t2.getGolos_marcados() > t1.getGolos_marcados() ? android.graphics.Typeface.BOLD : android.graphics.Typeface.NORMAL); } + private void setupFavoriteButton() { + if (currentClubName == null || binding == null) return; + + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + + // Remove previous listener if any + if (favoriteRef != null && favoriteListener != null) { + favoriteRef.removeEventListener(favoriteListener); + } + + if (user != null) { + favoriteRef = FirebaseDatabase.getInstance().getReference("users") + .child(user.getUid()) + .child("favorites") + .child(com.example.vdcscore.utils.FirebaseErrorUtils.sanitizeKey(currentClubName)); + + favoriteListener = new ValueEventListener() { + @Override + public void onDataChange(@NonNull DataSnapshot snapshot) { + if (binding == null) return; + isFavorite = snapshot.exists() && Boolean.TRUE.equals(snapshot.getValue(Boolean.class)); + updateFavoriteIcon(); + } + + @Override + public void onCancelled(@NonNull DatabaseError error) {} + }; + favoriteRef.addValueEventListener(favoriteListener); + } else { + isFavorite = false; + updateFavoriteIcon(); + } + + binding.btnFavorite.setOnClickListener(v -> { + FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); + if (u == null) { + // Pedir para criar conta + showRegisterRequiredDialog(); + } else { + // Alternar favorito + if (favoriteRef != null) { + favoriteRef.setValue(!isFavorite); + } + } + }); + } + + private void updateFavoriteIcon() { + if (binding == null || getContext() == null) return; + if (isFavorite) { + binding.btnFavorite.setImageResource(R.drawable.ic_star); + } else { + binding.btnFavorite.setImageResource(R.drawable.ic_star_border); + } + } + + private void showRegisterRequiredDialog() { + if (getContext() == null) return; + new AlertDialog.Builder(requireContext()) + .setTitle("Iniciar Sessão Necessário") + .setMessage("Para adicionar clubes aos seus favoritos, precisa de ter uma conta com sessão iniciada. Deseja criar conta ou fazer login agora?") + .setPositiveButton("Criar Conta / Login", (dialog, which) -> { + Intent intent = new Intent(requireContext(), LoginActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + }) + .setNegativeButton("Cancelar", null) + .show(); + } + @Override public void onDestroyView() { super.onDestroyView(); + if (favoriteRef != null && favoriteListener != null) { + favoriteRef.removeEventListener(favoriteListener); + } binding = null; } } diff --git a/app/src/main/java/com/example/vdcscore/ui/cup/CupFragment.java b/app/src/main/java/com/example/vdcscore/ui/cup/CupFragment.java index 9bd7f0d..0122cd6 100644 --- a/app/src/main/java/com/example/vdcscore/ui/cup/CupFragment.java +++ b/app/src/main/java/com/example/vdcscore/ui/cup/CupFragment.java @@ -4,7 +4,6 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; @@ -13,8 +12,6 @@ import androidx.recyclerview.widget.LinearLayoutManager; import com.example.vdcscore.R; import com.example.vdcscore.databinding.FragmentCupBinding; import com.example.vdcscore.ui.gallery.Match; -import com.example.vdcscore.ui.gallery.Matchday; -import com.example.vdcscore.ui.gallery.MatchesAdapter; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; @@ -23,26 +20,38 @@ import com.google.firebase.database.ValueEventListener; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class CupFragment extends Fragment { private FragmentCupBinding binding; - private MatchesAdapter adapter; + private CupPhasesAdapter adapter; private DatabaseReference mDatabase; - private List phasesList = new ArrayList<>(); // Reusing Matchday as generalized 'Phase' - private int currentPhaseIndex = 0; + private List phasesList = new ArrayList<>(); private String currentCategory = "seniores"; private ValueEventListener currentListener; + // Estrutura fixa das fases da Taça + private static final String[] CUP_PHASES_ORDER = { + "1ª Eliminatoria 1º Mão", + "2ª Eliminatoria 2º Mão", + "Quartos 1º Mão", + "Quartos 2º Mão", + "Meia Final 1º Mão", + "Meia Final 2º Mão", + "Final" + }; + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentCupBinding.inflate(inflater, container, false); View root = binding.getRoot(); - // Initialize RecyclerView with reused MatchesAdapter - adapter = new MatchesAdapter(); + // Initialize RecyclerView + adapter = new CupPhasesAdapter(); binding.recyclerPhases.setLayoutManager(new LinearLayoutManager(getContext())); binding.recyclerPhases.setAdapter(adapter); @@ -58,21 +67,6 @@ public class CupFragment extends Fragment { } }); - // Setup Navigation Buttons - binding.btnPrevPhase.setOnClickListener(v -> { - if (currentPhaseIndex > 0) { - currentPhaseIndex--; - updateUI(); - } - }); - - binding.btnNextPhase.setOnClickListener(v -> { - if (currentPhaseIndex < phasesList.size() - 1) { - currentPhaseIndex++; - updateUI(); - } - }); - // Initial Fetch fetchPhases(); @@ -92,44 +86,89 @@ public class CupFragment extends Fragment { public void onDataChange(@NonNull DataSnapshot snapshot) { if (binding == null) return; - phasesList.clear(); + // Obter todas as fases do Firebase + List allPhases = new ArrayList<>(); for (DataSnapshot phaseSnap : snapshot.getChildren()) { String name = phaseSnap.getKey(); - List matches = new ArrayList<>(); - - for (DataSnapshot matchSnap : phaseSnap.getChildren()) { - Match match = matchSnap.getValue(Match.class); - if (match != null) { - match.setId(matchSnap.getKey()); - matches.add(match); - } - } - - if (!matches.isEmpty()) { - phasesList.add(new Matchday(name, matches)); + if (name != null) { + allPhases.add(name); } } - // Sort alphabetically/chronologically as they are phrases like "1ª Fase", "Oitavos", etc. - Collections.sort(phasesList, (p1, p2) -> p1.getName().compareTo(p2.getName())); + if (allPhases.isEmpty()) { + binding.textPhaseName.setText("Sem fases"); + adapter.setPhases(new ArrayList<>()); + return; + } + + // Tentar ordenar pela ordem fixa + phasesList.clear(); + + // Adicionar fases que estão na lista fixa (na ordem correta) + for (String fixedPhase : CUP_PHASES_ORDER) { + for (String firebasePhase : allPhases) { + if (firebasePhase.equals(fixedPhase)) { + List matches = getMatchesFromSnapshot(snapshot, firebasePhase); + if (!matches.isEmpty()) { + phasesList.add(new CupPhase(fixedPhase, matches)); + } + break; + } + } + } + + // Adicionar fases que não estão na lista fixa (ordenadas alfabeticamente) + List remainingPhases = new ArrayList<>(); + for (String firebasePhase : allPhases) { + boolean isFixedPhase = false; + for (String fixed : CUP_PHASES_ORDER) { + if (fixed.equals(firebasePhase)) { + isFixedPhase = true; + break; + } + } + if (!isFixedPhase) { + remainingPhases.add(firebasePhase); + } + } + Collections.sort(remainingPhases); + for (String remainingPhase : remainingPhases) { + List matches = getMatchesFromSnapshot(snapshot, remainingPhase); + if (!matches.isEmpty()) { + phasesList.add(new CupPhase(remainingPhase, matches)); + } + } if (phasesList.isEmpty()) { binding.textPhaseName.setText("Sem fases"); - adapter.setMatches(new ArrayList<>()); + adapter.setPhases(new ArrayList<>()); } else { - if (currentPhaseIndex >= phasesList.size()) { - currentPhaseIndex = phasesList.size() - 1; - } - // In case initial load or filter swap, bound index. - if (currentPhaseIndex < 0) currentPhaseIndex = 0; - updateUI(); + binding.textPhaseName.setText(phasesList.get(0).getName()); + adapter.setPhases(phasesList); } } + private List getMatchesFromSnapshot(DataSnapshot snapshot, String phaseName) { + List matches = new ArrayList<>(); + for (DataSnapshot phaseSnap : snapshot.getChildren()) { + if (phaseSnap.getKey() != null && phaseSnap.getKey().equals(phaseName)) { + for (DataSnapshot matchSnap : phaseSnap.getChildren()) { + Match match = matchSnap.getValue(Match.class); + if (match != null) { + match.setId(matchSnap.getKey()); + matches.add(match); + } + } + break; + } + } + return matches; + } + @Override public void onCancelled(@NonNull DatabaseError error) { if (getContext() != null) { - Toast.makeText(getContext(), "Erro ao carregar taça: " + error.getMessage(), Toast.LENGTH_SHORT).show(); + com.google.android.material.snackbar.Snackbar.make(binding.getRoot(), "Erro ao carregar taça: " + error.getMessage(), com.google.android.material.snackbar.Snackbar.LENGTH_SHORT).show(); } } }; @@ -137,22 +176,6 @@ public class CupFragment extends Fragment { mDatabase.addValueEventListener(currentListener); } - private void updateUI() { - if (phasesList.isEmpty()) return; - - Matchday currentPhase = phasesList.get(currentPhaseIndex); - binding.textPhaseName.setText(currentPhase.getName()); - adapter.setMatches(currentPhase.getMatches()); - - // Enable/Disable navigation buttons - binding.btnPrevPhase.setEnabled(currentPhaseIndex > 0); - binding.btnNextPhase.setEnabled(currentPhaseIndex < phasesList.size() - 1); - - // Visual feedback - binding.btnPrevPhase.setAlpha(currentPhaseIndex > 0 ? 1.0f : 0.3f); - binding.btnNextPhase.setAlpha(currentPhaseIndex < phasesList.size() - 1 ? 1.0f : 0.3f); - } - @Override public void onDestroyView() { super.onDestroyView(); diff --git a/app/src/main/java/com/example/vdcscore/ui/gallery/MatchesAdapter.java b/app/src/main/java/com/example/vdcscore/ui/gallery/MatchesAdapter.java index 188a2ff..90c4dc9 100644 --- a/app/src/main/java/com/example/vdcscore/ui/gallery/MatchesAdapter.java +++ b/app/src/main/java/com/example/vdcscore/ui/gallery/MatchesAdapter.java @@ -12,8 +12,6 @@ import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.example.vdcscore.R; -import android.content.Intent; -import android.net.Uri; import java.util.ArrayList; import java.util.List; @@ -90,17 +88,7 @@ public class MatchesAdapter extends RecyclerView.Adapter { - Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(match.getMatchReportUrl())); - holder.itemView.getContext().startActivity(browserIntent); - }); - } else { - holder.btnMatchReport.setVisibility(View.GONE); - holder.btnMatchReport.setOnClickListener(null); - } + // Botão de Ficha de Jogo removido } private boolean isValid(String text) { @@ -121,8 +109,6 @@ public class MatchesAdapter extends RecyclerView.Adapter + + + diff --git a/app/src/main/res/layout/fragment_club_detail.xml b/app/src/main/res/layout/fragment_club_detail.xml index 72b4b62..47ea6e0 100644 --- a/app/src/main/res/layout/fragment_club_detail.xml +++ b/app/src/main/res/layout/fragment_club_detail.xml @@ -36,6 +36,20 @@ tools:src="@mipmap/ic_launcher_round"/> + + + - - - diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml index be1ec20..d01ba57 100644 --- a/app/src/main/res/menu/activity_main_drawer.xml +++ b/app/src/main/res/menu/activity_main_drawer.xml @@ -23,7 +23,8 @@ + android:title="@string/menu_live_games" + android:visible="false" />