app atualizada para apresentação
This commit is contained in:
36
app/src/main/java/com/example/vdcscore/ui/cup/CupPhase.java
Normal file
36
app/src/main/java/com/example/vdcscore/ui/cup/CupPhase.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package com.example.vdcscore.ui.cup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CupPhase {
|
||||
private String name;
|
||||
private List<com.example.vdcscore.ui.gallery.Match> matches;
|
||||
|
||||
public CupPhase() {
|
||||
}
|
||||
|
||||
public CupPhase(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public CupPhase(String name, List<com.example.vdcscore.ui.gallery.Match> matches) {
|
||||
this.name = name;
|
||||
this.matches = matches;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public List<com.example.vdcscore.ui.gallery.Match> getMatches() {
|
||||
return matches;
|
||||
}
|
||||
|
||||
public void setMatches(List<com.example.vdcscore.ui.gallery.Match> matches) {
|
||||
this.matches = matches;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.example.vdcscore.ui.cup;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.example.vdcscore.databinding.ItemCupPhaseBinding;
|
||||
import com.example.vdcscore.ui.gallery.MatchesAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CupPhasesAdapter extends RecyclerView.Adapter<CupPhasesAdapter.PhaseViewHolder> {
|
||||
|
||||
private List<CupPhase> phasesList = new ArrayList<>();
|
||||
|
||||
public void setPhases(List<CupPhase> phases) {
|
||||
this.phasesList = phases;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PhaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
ItemCupPhaseBinding binding = ItemCupPhaseBinding.inflate(
|
||||
LayoutInflater.from(parent.getContext()), parent, false);
|
||||
return new PhaseViewHolder(binding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull PhaseViewHolder holder, int position) {
|
||||
CupPhase phase = phasesList.get(position);
|
||||
holder.binding.textPhaseName.setText(phase.getName());
|
||||
|
||||
MatchesAdapter matchesAdapter = new MatchesAdapter();
|
||||
matchesAdapter.setMatches(phase.getMatches() != null ? phase.getMatches() : new ArrayList<>());
|
||||
holder.binding.recyclerPhaseMatches.setAdapter(matchesAdapter);
|
||||
holder.binding.recyclerPhaseMatches.setLayoutManager(
|
||||
new androidx.recyclerview.widget.LinearLayoutManager(holder.binding.getRoot().getContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return phasesList.size();
|
||||
}
|
||||
|
||||
static class PhaseViewHolder extends RecyclerView.ViewHolder {
|
||||
ItemCupPhaseBinding binding;
|
||||
|
||||
PhaseViewHolder(ItemCupPhaseBinding binding) {
|
||||
super(binding.getRoot());
|
||||
this.binding = binding;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.example.vdcscore.utils;
|
||||
|
||||
import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException;
|
||||
import com.google.firebase.auth.FirebaseAuthInvalidUserException;
|
||||
import com.google.firebase.auth.FirebaseAuthUserCollisionException;
|
||||
import com.google.firebase.auth.FirebaseAuthWeakPasswordException;
|
||||
import com.google.firebase.FirebaseNetworkException;
|
||||
|
||||
public class FirebaseErrorUtils {
|
||||
|
||||
public static String getErrorMessagePt(Exception exception) {
|
||||
if (exception == null) {
|
||||
return "Ocorreu um erro desconhecido.";
|
||||
}
|
||||
|
||||
if (exception instanceof FirebaseAuthInvalidCredentialsException) {
|
||||
return "O e-mail ou a palavra-passe estão incorretos.";
|
||||
}
|
||||
|
||||
if (exception instanceof FirebaseAuthInvalidUserException) {
|
||||
String errorCode = ((FirebaseAuthInvalidUserException) exception).getErrorCode();
|
||||
if ("ERROR_USER_DISABLED".equals(errorCode)) {
|
||||
return "Esta conta de utilizador foi desativada.";
|
||||
}
|
||||
return "Não existe nenhuma conta registada com este e-mail.";
|
||||
}
|
||||
|
||||
if (exception instanceof FirebaseAuthUserCollisionException) {
|
||||
return "Já existe uma conta registada com este e-mail.";
|
||||
}
|
||||
|
||||
if (exception instanceof FirebaseAuthWeakPasswordException) {
|
||||
return "A palavra-passe introduzida é demasiado fraca. Introduza pelo menos 6 caracteres.";
|
||||
}
|
||||
|
||||
if (exception instanceof FirebaseNetworkException) {
|
||||
return "Sem ligação à internet. Por favor, verifique a sua rede.";
|
||||
}
|
||||
|
||||
// Fallbacks baseados na mensagem da exceção
|
||||
String message = exception.getMessage();
|
||||
if (message != null) {
|
||||
String lowerMessage = message.toLowerCase();
|
||||
if (lowerMessage.contains("badly formatted") || lowerMessage.contains("invalid email")) {
|
||||
return "O formato do e-mail introduzido é inválido.";
|
||||
}
|
||||
if (lowerMessage.contains("no user record") || lowerMessage.contains("user not found") || lowerMessage.contains("user-not-found")) {
|
||||
return "Não existe nenhuma conta registada com este e-mail.";
|
||||
}
|
||||
if (lowerMessage.contains("wrong password") || lowerMessage.contains("invalid password") || lowerMessage.contains("wrong-password")) {
|
||||
return "O e-mail ou a palavra-passe estão incorretos.";
|
||||
}
|
||||
if (lowerMessage.contains("email already in use") || lowerMessage.contains("already exists") || lowerMessage.contains("email-already-in-use")) {
|
||||
return "Já existe uma conta registada com este e-mail.";
|
||||
}
|
||||
if (lowerMessage.contains("network") || lowerMessage.contains("connection")) {
|
||||
return "Sem ligação à internet. Por favor, verifique a sua rede.";
|
||||
}
|
||||
if (lowerMessage.contains("recent login required")) {
|
||||
return "Por segurança, precisa de terminar sessão e voltar a entrar para realizar esta alteração.";
|
||||
}
|
||||
}
|
||||
|
||||
return "Erro: " + (message != null ? message : "desconhecido.");
|
||||
}
|
||||
|
||||
public static String sanitizeKey(String key) {
|
||||
if (key == null) return null;
|
||||
return key.replace(".", "_")
|
||||
.replace("#", "_")
|
||||
.replace("$", "_")
|
||||
.replace("[", "_")
|
||||
.replace("]", "_")
|
||||
.replace("/", "_");
|
||||
}
|
||||
}
|
||||
10
app/src/main/res/drawable/ic_arrow_forward.xml
Normal file
10
app/src/main/res/drawable/ic_arrow_forward.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_star.xml
Normal file
9
app/src/main/res/drawable/ic_star.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,17.27L18.18,21L16.54,13.97L22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27Z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_star_border.xml
Normal file
9
app/src/main/res/drawable/ic_star_border.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27L18.18,21L16.54,13.97L22,9.24M12,15.4L8.24,17.67L9.24,13.38L5.92,10.5L10.3,10.13L12,6.1L13.7,10.13L18.08,10.5L14.76,13.38L15.76,17.67L12,15.4Z"/>
|
||||
</vector>
|
||||
@@ -105,13 +105,13 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/font_ibm_plex_sans"
|
||||
android:text="Password"
|
||||
android:textColor="@color/text_1"
|
||||
android:textStyle="bold"
|
||||
android:textSize="13sp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginStart="4dp"/>
|
||||
android:fontFamily="@font/font_ibm_plex_sans"
|
||||
android:text="Palavra-passe"
|
||||
android:textColor="@color/text_1"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/editPassword2"
|
||||
@@ -131,13 +131,13 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/font_ibm_plex_sans"
|
||||
android:text="Confirmar Password"
|
||||
android:textColor="@color/text_1"
|
||||
android:textStyle="bold"
|
||||
android:textSize="13sp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginStart="4dp"/>
|
||||
android:fontFamily="@font/font_ibm_plex_sans"
|
||||
android:text="Confirmar Palavra-passe"
|
||||
android:textColor="@color/text_1"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/editConfirmPassword"
|
||||
@@ -187,13 +187,13 @@
|
||||
android:id="@+id/txtGoLogin"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/font_ibm_plex_sans"
|
||||
android:text="Sign In"
|
||||
android:textColor="@color/brand"
|
||||
android:textStyle="bold"
|
||||
android:textSize="13sp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"/>
|
||||
android:focusable="true"
|
||||
android:fontFamily="@font/font_ibm_plex_sans"
|
||||
android:text="Clique aqui para iniciar sessão"
|
||||
android:textColor="@color/brand"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -63,13 +63,13 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/font_bebas_neue"
|
||||
android:text="LOGIN"
|
||||
android:textSize="32sp"
|
||||
android:textColor="@color/text_1"
|
||||
android:letterSpacing="0.04"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="32dp" />
|
||||
android:layout_marginBottom="32dp"
|
||||
android:fontFamily="@font/font_bebas_neue"
|
||||
android:letterSpacing="0.04"
|
||||
android:text="Iniciar Sessão"
|
||||
android:textColor="@color/text_1"
|
||||
android:textSize="32sp" />
|
||||
|
||||
<!-- Email Label -->
|
||||
<TextView
|
||||
@@ -102,13 +102,13 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/font_ibm_plex_sans"
|
||||
android:text="Password"
|
||||
android:textColor="@color/text_1"
|
||||
android:textStyle="bold"
|
||||
android:textSize="13sp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginStart="4dp"/>
|
||||
android:fontFamily="@font/font_ibm_plex_sans"
|
||||
android:text="Palavra-passe"
|
||||
android:textColor="@color/text_1"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- Password Input -->
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
@@ -152,13 +152,13 @@
|
||||
android:id="@+id/txtForgotPassword"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/font_ibm_plex_sans"
|
||||
android:text="Esqueceu a senha?"
|
||||
android:textColor="@color/text_2"
|
||||
android:textStyle="bold"
|
||||
android:textSize="12sp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"/>
|
||||
android:focusable="true"
|
||||
android:fontFamily="@font/font_ibm_plex_sans"
|
||||
android:text="Esqueceu-se da palavra-passe?"
|
||||
android:textColor="@color/text_2"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Login Button -->
|
||||
|
||||
44
app/src/main/res/layout/item_cup_phase.xml
Normal file
44
app/src/main/res/layout/item_cup_phase.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView 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="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="@color/bg_surface">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Phase Header -->
|
||||
<TextView
|
||||
android:id="@+id/text_phase_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:fontFamily="@font/font_bebas_neue"
|
||||
android:textSize="20sp"
|
||||
android:letterSpacing="0.04"
|
||||
android:textColor="@color/text_1"
|
||||
android:text="1ª Eliminatoria 1º Mão" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/border" />
|
||||
|
||||
<!-- Matches Container -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_phase_matches"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:padding="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
63
memories/repo/vdcscore-overview.md
Normal file
63
memories/repo/vdcscore-overview.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# VdcScore - Visão Geral do Projeto
|
||||
|
||||
## O que é
|
||||
App Android (Java) chamada **VdcScore** que exibe dados de campeonatos locais de futebol (AFAVCD) em tempo real.
|
||||
|
||||
## Arquitetura
|
||||
- **Scraper Java** (projeto separado `scrapper/`): Aplicaçãp autónoma que faz scraping da API HTML/JSON da AFAVCD, processa os dados e escreve no Firebase Realtime Database.
|
||||
- **Firebase Realtime Database**: Centraliza todos os dados (Single Source of Truth).
|
||||
- **App Android VdcScore**: Cliente de leitura que consome os dados do Firebase em tempo real via ValueEventListener.
|
||||
|
||||
## Estrutura de Dados no Firebase
|
||||
```
|
||||
Senior/
|
||||
standings/ - Tabelas classificativas por clube
|
||||
journeys/ - Jornadas com jogos (homeTeam, awayTeam, scores, date, field, matchReportUrl)
|
||||
players/ - Plantéis de cada equipa
|
||||
melhores_marcadores/ - Top scorers (Seniores e Juniores)
|
||||
noticias/ - Notícias da AFAVCD
|
||||
live_matches/ - Jogos preparados para acompanhamento em direto
|
||||
Users/
|
||||
UID/ - Utilizadores (email, favoriteClub)
|
||||
```
|
||||
|
||||
## Componentes Principais da App Android
|
||||
- **models/**: `Club`, `Game`/`Match`, `Jornada`, `Player`, `TopScorer`, `News`
|
||||
- **ui/**: Fragments + ViewModels para cada secção
|
||||
- `home/` - Classificações
|
||||
- `gallery/` - Jornadas/Jogos (MatchesAdapter, Match.java)
|
||||
- `livegames/` - Jogos em direto
|
||||
- `clubs/` - Equipas/Plantéis
|
||||
- `top_scorers/` - Melhores Marcadores
|
||||
- `news/` - Notícias (ecrã principal por defeito)
|
||||
- `definicoes/` - Definições
|
||||
- **Autenticação**: LoginActivity, CriarContaActivity, RecuperarPasswordActivity, MainActivity
|
||||
|
||||
## Tecnologias
|
||||
- **Scraper**: JSoup, GSON, Firebase Admin SDK, Gradle
|
||||
- **Android**: ViewBinding, Glide, Firebase Auth, Firebase Realtime Database, Navigation Component, RecyclerView
|
||||
|
||||
## Estado Atual
|
||||
- Scraper de Standings/Jornadas: ✅ Funcional
|
||||
- Scraper de Melhores Marcadores: ✅ Funcional
|
||||
- Scraper de Notícias: ✅ Funcional
|
||||
- Scraper de Plantéis (PlayersScraper): 🔄 Em desenvolvimento
|
||||
- UI Jornadas: ✅ Cartões premium com Glide, Ficha de Jogo
|
||||
- UI Melhores Marcadores: ✅ Ecrã completo
|
||||
- UI Notícias: ✅ No ecrã principal (Ínicio)
|
||||
- UI Classificações: ✅ Funcional
|
||||
- Autenticação Firebase: ✅ Implementada
|
||||
- Live Matches: ✅ Preparação de jogos futuros
|
||||
|
||||
## Tarefas Pendentes
|
||||
- Completar PlayersScraper (plantéis completos)
|
||||
- Sistema Offline (Firebase cache local)
|
||||
- Push Notifications (FCM)
|
||||
- Testes finais de UI para campos opcionais null
|
||||
|
||||
## Convenções Importantes
|
||||
- Models Android devem bater certo com models Scraper (nomes de atributos)
|
||||
- Valores numéricos vêm como String da API - fazer parse para Integer
|
||||
- Campos opcionais podem vir vazios (matchReportUrl, data) - UI deve lidar com null
|
||||
- Chaves Firebase em minúsculas (standings, journeys, players)
|
||||
- Serviços: `service-account.json` nunca no version control
|
||||
Reference in New Issue
Block a user