noticias funcionais

This commit is contained in:
2026-04-28 17:11:58 +01:00
parent 8784cc4975
commit 85f1a3679f
12 changed files with 668 additions and 2 deletions

View File

@@ -70,7 +70,7 @@ public class MainActivity extends AppCompatActivity {
// menu should be considered as top level destinations.
mAppBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_home, R.id.nav_gallery, R.id.nav_definicoes,
R.id.nav_live_games, R.id.nav_clubs, R.id.nav_top_scorers)
R.id.nav_live_games, R.id.nav_clubs, R.id.nav_top_scorers, R.id.nav_news)
.setOpenableLayout(drawer)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);

View File

@@ -0,0 +1,62 @@
package com.example.vdcscore.models;
import com.google.firebase.database.PropertyName;
import java.io.Serializable;
public class News implements Serializable {
private int newsID;
private String title;
private String body;
private String insertDate;
private String photoURL;
public News() {
}
@PropertyName("newsID")
public int getNewsID() {
return newsID;
}
@PropertyName("newsID")
public void setNewsID(int newsID) {
this.newsID = newsID;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
@PropertyName("insertDate")
public String getInsertDate() {
return insertDate;
}
@PropertyName("insertDate")
public void setInsertDate(String insertDate) {
this.insertDate = insertDate;
}
@PropertyName("photoURL")
public String getPhotoURL() {
return photoURL;
}
@PropertyName("photoURL")
public void setPhotoURL(String photoURL) {
this.photoURL = photoURL;
}
}

View File

@@ -0,0 +1,117 @@
package com.example.vdcscore.ui.news;
import android.os.Build;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.example.vdcscore.R;
import com.example.vdcscore.models.News;
import java.util.ArrayList;
import java.util.List;
public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {
private List<News> newsList = new ArrayList<>();
private OnNewsClickListener listener;
public interface OnNewsClickListener {
void onNewsClick(News news, int position);
}
public void setOnNewsClickListener(OnNewsClickListener listener) {
this.listener = listener;
}
public void setNewsList(List<News> newsList) {
this.newsList = (newsList != null) ? newsList : new ArrayList<>();
notifyDataSetChanged();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_news, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
News news = newsList.get(position);
holder.textTitle.setText(news.getTitle());
// Tratar o corpo da notícia (remover HTML)
if (news.getBody() != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
holder.textBody.setText(Html.fromHtml(news.getBody(), Html.FROM_HTML_MODE_COMPACT).toString().trim());
} else {
holder.textBody.setText(Html.fromHtml(news.getBody()).toString().trim());
}
}
// Formatar data se necessário (assumindo que vem da API/Firebase como ISO ou similar)
String dateStr = news.getInsertDate();
if (dateStr != null && dateStr.length() >= 10) {
try {
// Tentar extrair "2026-04-20" do formato "2026-04-20T20:05:01.000Z"
java.text.SimpleDateFormat inputFormat = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", java.util.Locale.getDefault());
java.util.Date date = inputFormat.parse(dateStr);
if (date != null) {
java.text.SimpleDateFormat outputFormat = new java.text.SimpleDateFormat("dd MMM yyyy", new java.util.Locale("pt", "PT"));
dateStr = outputFormat.format(date);
}
} catch (Exception e) {
// Fallback para exibir apenas a parte da data "YYYY-MM-DD"
if(dateStr.contains("T")) {
dateStr = dateStr.split("T")[0];
}
}
}
holder.textDate.setText(dateStr);
Glide.with(holder.itemView.getContext())
.load(news.getPhotoURL())
.placeholder(R.drawable.ic_menu_gallery)
.error(R.drawable.ic_menu_gallery)
.into(holder.imgNews);
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
int pos = holder.getAdapterPosition();
if (pos != RecyclerView.NO_POSITION) {
listener.onNewsClick(news, pos);
}
}
});
}
@Override
public int getItemCount() {
return newsList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public final ImageView imgNews;
public final TextView textTitle;
public final TextView textBody;
public final TextView textDate;
public ViewHolder(View view) {
super(view);
imgNews = view.findViewById(R.id.img_news);
textTitle = view.findViewById(R.id.text_news_title);
textBody = view.findViewById(R.id.text_news_body);
textDate = view.findViewById(R.id.text_news_date);
}
}
}

View File

@@ -0,0 +1,120 @@
package com.example.vdcscore.ui.news;
import android.os.Build;
import android.os.Bundle;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.bumptech.glide.Glide;
import com.example.vdcscore.R;
import com.example.vdcscore.models.News;
public class NewsDetailFragment extends Fragment {
private ImageView imgNewsDetail;
private TextView textDate, textTitle, textBody;
private com.google.android.material.button.MaterialButton btnPrev, btnNext;
private java.util.List<News> newsList;
private int currentIndex;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_news_detail, container, false);
imgNewsDetail = root.findViewById(R.id.img_news_detail);
textDate = root.findViewById(R.id.text_news_detail_date);
textTitle = root.findViewById(R.id.text_news_detail_title);
textBody = root.findViewById(R.id.text_news_detail_body);
btnPrev = root.findViewById(R.id.btn_prev_news);
btnNext = root.findViewById(R.id.btn_next_news);
if (getArguments() != null) {
News[] newsArray = (News[]) getArguments().getSerializable("news_list");
if (newsArray != null) {
newsList = java.util.Arrays.asList(newsArray);
}
currentIndex = getArguments().getInt("news_index", 0);
if (newsList != null && currentIndex >= 0 && currentIndex < newsList.size()) {
updateUI();
}
}
btnPrev.setOnClickListener(v -> {
if (newsList != null && currentIndex > 0) {
currentIndex--;
updateUI();
}
});
btnNext.setOnClickListener(v -> {
if (newsList != null && currentIndex < newsList.size() - 1) {
currentIndex++;
updateUI();
}
});
return root;
}
private void updateUI() {
if (newsList == null || currentIndex < 0 || currentIndex >= newsList.size()) return;
News news = newsList.get(currentIndex);
populateUI(news);
// Update button states
btnPrev.setEnabled(currentIndex > 0);
btnNext.setEnabled(currentIndex < newsList.size() - 1);
// Reset scroll position if needed
View scroll = getView() != null ? getView().findViewById(R.id.scroll_detail) : null;
if (scroll != null) {
scroll.scrollTo(0, 0);
}
}
private void populateUI(News news) {
textTitle.setText(news.getTitle());
if (news.getBody() != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
textBody.setText(Html.fromHtml(news.getBody(), Html.FROM_HTML_MODE_COMPACT).toString().trim());
} else {
textBody.setText(Html.fromHtml(news.getBody()).toString().trim());
}
}
String dateStr = news.getInsertDate();
if (dateStr != null && dateStr.length() >= 10) {
try {
java.text.SimpleDateFormat inputFormat = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", java.util.Locale.getDefault());
java.util.Date date = inputFormat.parse(dateStr);
if (date != null) {
java.text.SimpleDateFormat outputFormat = new java.text.SimpleDateFormat("dd MMM yyyy", new java.util.Locale("pt", "PT"));
dateStr = outputFormat.format(date);
}
} catch (Exception e) {
if(dateStr.contains("T")) {
dateStr = dateStr.split("T")[0];
}
}
}
textDate.setText(dateStr);
Glide.with(this)
.load(news.getPhotoURL())
.placeholder(R.drawable.ic_menu_gallery)
.error(R.drawable.ic_menu_gallery)
.into(imgNewsDetail);
}
}

View File

@@ -0,0 +1,76 @@
package com.example.vdcscore.ui.news;
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;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.vdcscore.R;
import com.example.vdcscore.models.News;
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 java.util.ArrayList;
import java.util.List;
public class NewsFragment extends Fragment {
private RecyclerView recyclerView;
private NewsAdapter adapter;
private DatabaseReference databaseReference;
private List<News> newsList = new ArrayList<>();
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_news, container, false);
recyclerView = root.findViewById(R.id.recycler_news);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
adapter = new NewsAdapter();
adapter.setOnNewsClickListener((news, position) -> {
Bundle bundle = new Bundle();
bundle.putSerializable("news_list", newsList.toArray(new News[0]));
bundle.putInt("news_index", position);
androidx.navigation.Navigation.findNavController(root).navigate(R.id.action_nav_news_to_nav_news_detail, bundle);
});
recyclerView.setAdapter(adapter);
databaseReference = FirebaseDatabase.getInstance().getReference("noticias");
loadNews();
return root;
}
private void loadNews() {
databaseReference.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
newsList.clear();
for (DataSnapshot dataSnapshot : snapshot.getChildren()) {
News news = dataSnapshot.getValue(News.class);
if (news != null) {
newsList.add(news);
}
}
adapter.setNewsList(newsList);
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
if (getContext() != null) {
Toast.makeText(getContext(), "Erro ao carregar notícias", Toast.LENGTH_SHORT).show();
}
}
});
}
}

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5"
tools:context=".ui.news.NewsFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_news"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:paddingTop="8dp"
android:paddingBottom="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,123 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
tools:context=".ui.news.NewsDetailFragment">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="250dp"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="@+id/img_news_detail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.7"
tools:src="@drawable/ic_menu_gallery" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#44000000" /> <!-- Gradiente escuro para garantir que a barra é visível se necessário -->
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_detail"
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="24dp">
<TextView
android:id="@+id/text_news_detail_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:text="Data"
android:textColor="#888888"
android:textSize="14sp"
android:textStyle="bold"
android:textAllCaps="true"
android:letterSpacing="0.05" />
<TextView
android:id="@+id/text_news_detail_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Título Completo da Notícia"
android:textColor="#1F2937"
android:textSize="26sp"
android:textStyle="bold"
android:lineSpacingExtra="6dp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginVertical="20dp"
android:background="#EEEEEE" />
<TextView
android:id="@+id/text_news_detail_body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="100dp"
android:text="Corpo da notícia completo..."
android:textColor="#4B5563"
android:textSize="16sp"
android:lineSpacingExtra="8dp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="horizontal"
android:padding="16dp"
android:background="#99FFFFFF">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_prev_news"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Anterior"
app:icon="@android:drawable/ic_media_previous" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_next_news"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Próxima"
app:icon="@android:drawable/ic_media_next"
app:iconGravity="end" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,75 @@
<?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_marginVertical="10dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="20dp"
app:cardElevation="6dp"
app:strokeWidth="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/img_news"
android:layout_width="0dp"
android:layout_height="220dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_menu_gallery"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Optional semi-transparent overlay to make the image pop or just raw image -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp"
app:layout_constraintTop_toBottomOf="@id/img_news"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android:id="@+id/text_news_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="Data"
android:textColor="#888888"
android:textSize="12sp"
android:textStyle="bold"
android:textAllCaps="true"
android:letterSpacing="0.05" />
<TextView
android:id="@+id/text_news_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Título da Notícia"
android:textColor="#1F2937"
android:textSize="22sp"
android:textStyle="bold"
android:lineSpacingExtra="4dp"
android:maxLines="3"
android:ellipsize="end" />
<TextView
android:id="@+id/text_news_body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Resumo da notícia..."
android:textColor="#4B5563"
android:textSize="15sp"
android:lineSpacingExtra="6dp"
android:maxLines="4"
android:ellipsize="end" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -4,6 +4,10 @@
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_news"
android:icon="@android:drawable/ic_menu_info_details"
android:title="@string/menu_news" />
<item
android:id="@+id/nav_home"
android:icon="@drawable/ic_menu_camera"

View File

@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mobile_navigation"
app:startDestination="@+id/nav_home">
app:startDestination="@+id/nav_news">
<fragment
android:id="@+id/nav_home"
@@ -102,4 +102,27 @@
android:label="@string/menu_top_scorers"
tools:layout="@layout/fragment_top_scorers" />
<fragment
android:id="@+id/nav_news"
android:name="com.example.vdcscore.ui.news.NewsFragment"
android:label="@string/menu_news"
tools:layout="@layout/fragment_news">
<action
android:id="@+id/action_nav_news_to_nav_news_detail"
app:destination="@id/nav_news_detail" />
</fragment>
<fragment
android:id="@+id/nav_news_detail"
android:name="com.example.vdcscore.ui.news.NewsDetailFragment"
android:label="Notícia"
tools:layout="@layout/fragment_news_detail">
<argument
android:name="news_list"
app:argType="com.example.vdcscore.models.News[]" />
<argument
android:name="news_index"
app:argType="integer" />
</fragment>
</navigation>

View File

@@ -15,6 +15,7 @@
<string name="title_live_game_detail">Detalhes do Jogo</string>
<string name="menu_clubs">Clubes</string>
<string name="menu_top_scorers">Melhores Marcadores</string>
<string name="menu_news">Ínicio</string>
<!-- Profile Strings -->
<string name="change_photo_title">Alterar Foto de Perfil</string>