Add v7.0: Tabs UI and Real-Time Events

Major Features:
- Dual tab interface (Channels and Events) with visible focus
- Real-time event status calculation (Live, Upcoming, Finished)
- Smart caching system for events (24-hour cache)
- Argentina timezone support (America/Argentina/Buenos_Aires)

UI/TV Improvements:
- Focusable tabs with bg_tab_selector for D-pad navigation
- Visual feedback with highlighted borders on focused tabs
- Consistent design between tabs and content cards
- Enhanced TV navigation experience

Real-Time Event System:
- EventRepository: Centralized event management with 24h cache
- EventAdapter: Optimized RecyclerView for event listings
- EventItem: Structured data model for events
- Dynamic status calculation (remaining time, live duration, completion)
- Automatic link normalization to global2.php

Technical Implementation:
- activity_main.xml: Complete dual-tab layout
- item_event.xml: Dedicated event item layout with RecyclerView
- bg_tab_selector.xml: Tab states (selected, focused, pressed)
- MainActivity.java: Tab switching and event management
- Automatic URL processing for seamless PlayerActivity integration

Time Zone Features:
- Argentina local time (America/Argentina/Buenos_Aires)
- Real-time status updates without page refresh
- "En Xh Ym" for upcoming events
- "En vivo durante 2h" status for live events
- "Finalizado" status for completed events

Solutions:
- Fixed web page "En vivo" not updating issue
- Provides always-current event status in app
- Direct event-to-player navigation without manual intervention
- Improved TV navigation with clear visual feedback

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-14 21:44:04 +00:00
parent ab43ce7794
commit 96c4c360ee
7 changed files with 556 additions and 13 deletions

View File

@@ -2,31 +2,112 @@ 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.RecyclerView;
import java.util.List;
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 EventAdapter eventAdapter;
private EventRepository eventRepository;
private boolean eventsLoaded = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = findViewById(R.id.channel_grid);
recyclerView.setLayoutManager(new GridLayoutManager(this, getSpanCount()));
recyclerView.setHasFixedSize(true);
ChannelAdapter adapter = new ChannelAdapter(
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);
channelGrid.setLayoutManager(new GridLayoutManager(this, getSpanCount()));
channelGrid.setHasFixedSize(true);
ChannelAdapter channelAdapter = new ChannelAdapter(
ChannelRepository.getChannels(),
channel -> {
Intent intent = new Intent(MainActivity.this, PlayerActivity.class);
intent.putExtra(PlayerActivity.EXTRA_CHANNEL_NAME, channel.getName());
intent.putExtra(PlayerActivity.EXTRA_CHANNEL_URL, channel.getPageUrl());
startActivity(intent);
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();
tabChannels.setOnClickListener(v -> showChannels());
tabEvents.setOnClickListener(v -> showEvents());
showChannels();
channelGrid.post(channelGrid::requestFocus);
}
private void showChannels() {
channelGrid.setVisibility(View.VISIBLE);
eventsContainer.setVisibility(View.GONE);
tabChannels.setSelected(true);
tabEvents.setSelected(false);
}
private void showEvents() {
channelGrid.setVisibility(View.GONE);
eventsContainer.setVisibility(View.VISIBLE);
tabChannels.setSelected(false);
tabEvents.setSelected(true);
if (!eventsLoaded) {
loadEvents(false);
}
}
private void loadEvents(boolean forceRefresh) {
eventsProgress.setVisibility(View.VISIBLE);
eventsError.setVisibility(View.GONE);
eventsList.setVisibility(View.GONE);
eventRepository.loadEvents(this, forceRefresh, new EventRepository.Callback() {
@Override
public void onSuccess(List<EventItem> events) {
runOnUiThread(() -> {
eventsProgress.setVisibility(View.GONE);
eventAdapter.submitList(events);
eventsList.setVisibility(View.VISIBLE);
eventsLoaded = true;
});
recyclerView.setAdapter(adapter);
recyclerView.post(recyclerView::requestFocus);
}
@Override
public void onError(String message) {
runOnUiThread(() -> {
eventsProgress.setVisibility(View.GONE);
eventsError.setText("No se pudieron cargar los eventos: " + message);
eventsError.setVisibility(View.VISIBLE);
});
}
});
}
private void openPlayer(String name, String pageUrl) {
Intent intent = new Intent(MainActivity.this, PlayerActivity.class);
intent.putExtra(PlayerActivity.EXTRA_CHANNEL_NAME, name);
intent.putExtra(PlayerActivity.EXTRA_CHANNEL_URL, pageUrl);
startActivity(intent);
}
private int getSpanCount() {