diff --git a/app/src/main/java/com/streamplayer/ChannelAdapter.java b/app/src/main/java/com/streamplayer/ChannelAdapter.java index 7b362ca..cc76548 100644 --- a/app/src/main/java/com/streamplayer/ChannelAdapter.java +++ b/app/src/main/java/com/streamplayer/ChannelAdapter.java @@ -9,6 +9,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; +import java.util.ArrayList; import java.util.List; public class ChannelAdapter extends RecyclerView.Adapter { @@ -17,11 +18,10 @@ public class ChannelAdapter extends RecyclerView.Adapter channels; + private final List channels = new ArrayList<>(); private final OnChannelClickListener listener; - public ChannelAdapter(List channels, OnChannelClickListener listener) { - this.channels = channels; + public ChannelAdapter(OnChannelClickListener listener) { this.listener = listener; } @@ -65,4 +65,12 @@ public class ChannelAdapter extends RecyclerView.Adapter newChannels) { + channels.clear(); + if (newChannels != null) { + channels.addAll(newChannels); + } + notifyDataSetChanged(); + } } diff --git a/app/src/main/java/com/streamplayer/MainActivity.java b/app/src/main/java/com/streamplayer/MainActivity.java index 8cf9da6..5472cfe 100644 --- a/app/src/main/java/com/streamplayer/MainActivity.java +++ b/app/src/main/java/com/streamplayer/MainActivity.java @@ -3,106 +3,147 @@ package com.streamplayer; import android.content.Intent; import android.os.Bundle; import android.view.View; -import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Locale; +import java.util.Map; public class MainActivity extends AppCompatActivity { - private RecyclerView channelGrid; - private RecyclerView eventsList; - private View eventsContainer; - private ProgressBar eventsProgress; - private TextView eventsError; - private Button tabChannels; - private Button tabEvents; + private RecyclerView sectionList; + private RecyclerView contentList; + private ProgressBar loadingIndicator; + private TextView messageView; + private TextView contentTitle; + private ChannelAdapter channelAdapter; private EventAdapter eventAdapter; private EventRepository eventRepository; - private boolean eventsLoaded = false; + private SectionAdapter sectionAdapter; + private GridLayoutManager channelLayoutManager; + private LinearLayoutManager eventLayoutManager; + private final List cachedEvents = new ArrayList<>(); + private List sections; + private SectionEntry currentSection; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - channelGrid = findViewById(R.id.channel_grid); - eventsList = findViewById(R.id.events_list); - eventsContainer = findViewById(R.id.events_container); - eventsProgress = findViewById(R.id.events_progress); - eventsError = findViewById(R.id.events_error); - tabChannels = findViewById(R.id.tab_channels); - tabEvents = findViewById(R.id.tab_events); + sectionList = findViewById(R.id.section_list); + contentList = findViewById(R.id.content_list); + loadingIndicator = findViewById(R.id.loading_indicator); + messageView = findViewById(R.id.message_view); + contentTitle = findViewById(R.id.content_title); - channelGrid.setLayoutManager(new GridLayoutManager(this, getSpanCount())); - channelGrid.setHasFixedSize(true); - ChannelAdapter channelAdapter = new ChannelAdapter( - ChannelRepository.getChannels(), + channelAdapter = new ChannelAdapter( channel -> openPlayer(channel.getName(), channel.getPageUrl())); - channelGrid.setAdapter(channelAdapter); - - eventsList.setLayoutManager(new GridLayoutManager(this, 1)); eventAdapter = new EventAdapter(event -> openPlayer(event.getTitle(), event.getPageUrl())); - eventsList.setAdapter(eventAdapter); - eventRepository = new EventRepository(); + channelLayoutManager = new GridLayoutManager(this, getSpanCount()); + eventLayoutManager = new LinearLayoutManager(this); - tabChannels.setOnClickListener(v -> showChannels()); - tabEvents.setOnClickListener(v -> showEvents()); + sections = buildSections(); + sectionList.setLayoutManager(new LinearLayoutManager(this)); + sectionAdapter = new SectionAdapter(getSectionTitles(), this::selectSection); + sectionList.setAdapter(sectionAdapter); - showChannels(); - channelGrid.post(channelGrid::requestFocus); + selectSection(0); } - private void showChannels() { - channelGrid.setVisibility(View.VISIBLE); - eventsContainer.setVisibility(View.GONE); - tabChannels.setSelected(true); - tabEvents.setSelected(false); + private void selectSection(int index) { + if (sections == null || sections.isEmpty()) { + return; + } + if (index < 0 || index >= sections.size()) { + index = 0; + } + sectionAdapter.setSelectedIndex(index); + currentSection = sections.get(index); + if (currentSection.type == SectionEntry.Type.EVENTS) { + showEvents(); + } else { + showChannels(currentSection); + } + } + + private void showChannels(SectionEntry section) { + contentTitle.setText(section.title); + contentList.setLayoutManager(channelLayoutManager); + contentList.setAdapter(channelAdapter); + loadingIndicator.setVisibility(View.GONE); + channelAdapter.submitList(section.channels); + if (section.channels.isEmpty()) { + messageView.setVisibility(View.VISIBLE); + messageView.setText(R.string.message_no_channels); + } else { + messageView.setVisibility(View.GONE); + contentList.post(() -> contentList.scrollToPosition(0)); + } } private void showEvents() { - channelGrid.setVisibility(View.GONE); - eventsContainer.setVisibility(View.VISIBLE); - tabChannels.setSelected(false); - tabEvents.setSelected(true); - if (!eventsLoaded) { + contentTitle.setText(currentSection != null ? currentSection.title : getString(R.string.section_events)); + contentList.setLayoutManager(eventLayoutManager); + contentList.setAdapter(eventAdapter); + if (cachedEvents.isEmpty()) { loadEvents(false); + } else { + displayEvents(); } } private void loadEvents(boolean forceRefresh) { - eventsProgress.setVisibility(View.VISIBLE); - eventsError.setVisibility(View.GONE); - eventsList.setVisibility(View.GONE); + loadingIndicator.setVisibility(View.VISIBLE); + messageView.setVisibility(View.GONE); eventRepository.loadEvents(this, forceRefresh, new EventRepository.Callback() { @Override public void onSuccess(List events) { runOnUiThread(() -> { - eventsProgress.setVisibility(View.GONE); - eventAdapter.submitList(events); - eventsList.setVisibility(View.VISIBLE); - eventsLoaded = true; + cachedEvents.clear(); + cachedEvents.addAll(events); + if (currentSection != null && currentSection.type == SectionEntry.Type.EVENTS) { + displayEvents(); + } else { + loadingIndicator.setVisibility(View.GONE); + } }); } @Override public void onError(String message) { runOnUiThread(() -> { - eventsProgress.setVisibility(View.GONE); - eventsError.setText("No se pudieron cargar los eventos: " + message); - eventsError.setVisibility(View.VISIBLE); + loadingIndicator.setVisibility(View.GONE); + messageView.setVisibility(View.VISIBLE); + messageView.setText(getString(R.string.message_events_error, message)); }); } }); } + private void displayEvents() { + loadingIndicator.setVisibility(View.GONE); + if (cachedEvents.isEmpty()) { + messageView.setVisibility(View.VISIBLE); + messageView.setText(R.string.message_no_events); + eventAdapter.submitList(new ArrayList<>()); + } else { + messageView.setVisibility(View.GONE); + eventAdapter.submitList(new ArrayList<>(cachedEvents)); + } + } + private void openPlayer(String name, String pageUrl) { Intent intent = new Intent(MainActivity.this, PlayerActivity.class); intent.putExtra(PlayerActivity.EXTRA_CHANNEL_NAME, name); @@ -113,4 +154,90 @@ public class MainActivity extends AppCompatActivity { private int getSpanCount() { return getResources().getInteger(R.integer.channel_grid_span); } + + private List buildSections() { + List list = new ArrayList<>(); + list.add(SectionEntry.events(getString(R.string.section_events))); + + Map> grouped = new HashMap<>(); + List allChannels = ChannelRepository.getChannels(); + for (StreamChannel channel : allChannels) { + String key = deriveGroupName(channel.getName()); + grouped.computeIfAbsent(key, k -> new ArrayList<>()).add(channel); + } + + List espnChannels = grouped.remove("ESPN"); + if (espnChannels != null && !espnChannels.isEmpty()) { + list.add(SectionEntry.channels("ESPN", espnChannels)); + } + + List remaining = new ArrayList<>(grouped.keySet()); + Collections.sort(remaining, String.CASE_INSENSITIVE_ORDER); + for (String key : remaining) { + List channels = grouped.get(key); + if (channels == null || channels.isEmpty()) { + continue; + } + list.add(SectionEntry.channels(key, channels)); + } + + list.add(SectionEntry.channels(getString(R.string.section_all_channels), allChannels)); + return list; + } + + private List getSectionTitles() { + List titles = new ArrayList<>(); + for (SectionEntry entry : sections) { + titles.add(entry.title); + } + return titles; + } + + private String deriveGroupName(String name) { + if (name == null) { + return getString(R.string.section_all_channels); + } + String upper = name.toUpperCase(Locale.US); + if (upper.startsWith("ESPN")) { + return "ESPN"; + } else if (upper.contains("FOX SPORTS")) { + return "Fox Sports"; + } else if (upper.contains("FOX")) { + return "Fox"; + } else if (upper.contains("TNT")) { + return "TNT"; + } else if (upper.contains("DAZN")) { + return "DAZN"; + } else if (upper.contains("TUDN")) { + return "TUDN"; + } else if (upper.contains("TYC")) { + return "TyC"; + } else if (upper.contains("GOL")) { + return "Gol"; + } + int spaceIndex = upper.indexOf(' '); + return spaceIndex > 0 ? upper.substring(0, spaceIndex) : upper; + } + + private static class SectionEntry { + enum Type { EVENTS, CHANNELS } + + final String title; + final Type type; + final List channels; + + private SectionEntry(String title, Type type, List channels) { + this.title = title; + this.type = type; + this.channels = channels == null ? new ArrayList<>() : new ArrayList<>(channels); + } + + static SectionEntry events(String title) { + return new SectionEntry(title, Type.EVENTS, null); + } + + static SectionEntry channels(String title, List channels) { + return new SectionEntry(title, Type.CHANNELS, channels); + } + } } diff --git a/app/src/main/java/com/streamplayer/SectionAdapter.java b/app/src/main/java/com/streamplayer/SectionAdapter.java new file mode 100644 index 0000000..3832c28 --- /dev/null +++ b/app/src/main/java/com/streamplayer/SectionAdapter.java @@ -0,0 +1,88 @@ +package com.streamplayer; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + +public class SectionAdapter extends RecyclerView.Adapter { + + public interface OnSectionSelectedListener { + void onSectionSelected(int position); + } + + private final List sections; + private final OnSectionSelectedListener listener; + private int selectedIndex = 0; + + public SectionAdapter(List sections, OnSectionSelectedListener listener) { + this.sections = sections; + this.listener = listener; + } + + @NonNull + @Override + public SectionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_section, parent, false); + return new SectionViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull SectionViewHolder holder, int position) { + holder.title.setText(sections.get(position)); + holder.itemView.setSelected(position == selectedIndex); + holder.itemView.setOnClickListener(v -> notifySelection(holder)); + holder.itemView.setOnFocusChangeListener((v, hasFocus) -> { + if (hasFocus) { + notifySelection(holder); + } + }); + } + + @Override + public int getItemCount() { + return sections.size(); + } + + public void setSelectedIndex(int index) { + if (index < 0 || index >= sections.size()) { + return; + } + if (selectedIndex == index) { + return; + } + int previous = selectedIndex; + selectedIndex = index; + notifyItemChanged(previous); + notifyItemChanged(selectedIndex); + } + + public int getSelectedIndex() { + return selectedIndex; + } + + private void notifySelection(SectionViewHolder holder) { + int position = holder.getBindingAdapterPosition(); + if (position == RecyclerView.NO_POSITION) { + return; + } + if (listener != null) { + listener.onSectionSelected(position); + } + } + + static class SectionViewHolder extends RecyclerView.ViewHolder { + final TextView title; + + SectionViewHolder(@NonNull View itemView) { + super(itemView); + title = itemView.findViewById(R.id.section_title); + } + } +} diff --git a/app/src/main/res/color/section_text_selector.xml b/app/src/main/res/color/section_text_selector.xml new file mode 100644 index 0000000..c20609b --- /dev/null +++ b/app/src/main/res/color/section_text_selector.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_section_indicator.xml b/app/src/main/res/drawable/bg_section_indicator.xml new file mode 100644 index 0000000..fdbfdeb --- /dev/null +++ b/app/src/main/res/drawable/bg_section_indicator.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index da17e9a..c03b9e5 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,111 +7,106 @@ android:background="@color/black" tools:context=".MainActivity"> - + + + + + + + + + - -