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:
148
app/src/main/java/com/streamplayer/EventRepository.java
Normal file
148
app/src/main/java/com/streamplayer/EventRepository.java
Normal file
@@ -0,0 +1,148 @@
|
||||
package com.streamplayer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class EventRepository {
|
||||
|
||||
private static final String PREFS_NAME = "events_cache";
|
||||
private static final String KEY_JSON = "json";
|
||||
private static final String KEY_TIMESTAMP = "timestamp";
|
||||
private static final long CACHE_DURATION = 24L * 60 * 60 * 1000; // 24 horas
|
||||
private static final String EVENTS_URL = "https://streamtpmedia.com/eventos.json";
|
||||
|
||||
public interface Callback {
|
||||
void onSuccess(List<EventItem> events);
|
||||
|
||||
void onError(String message);
|
||||
}
|
||||
|
||||
public void loadEvents(Context context, boolean forceRefresh, Callback callback) {
|
||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
long last = prefs.getLong(KEY_TIMESTAMP, 0);
|
||||
long now = System.currentTimeMillis();
|
||||
if (!forceRefresh && now - last < CACHE_DURATION) {
|
||||
String cachedJson = prefs.getString(KEY_JSON, null);
|
||||
if (cachedJson != null) {
|
||||
try {
|
||||
callback.onSuccess(parseEvents(cachedJson));
|
||||
return;
|
||||
} catch (JSONException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
String json = downloadJson();
|
||||
List<EventItem> events = parseEvents(json);
|
||||
prefs.edit().putString(KEY_JSON, json).putLong(KEY_TIMESTAMP, System.currentTimeMillis()).apply();
|
||||
callback.onSuccess(events);
|
||||
} catch (IOException | JSONException e) {
|
||||
String cachedJson = prefs.getString(KEY_JSON, null);
|
||||
if (cachedJson != null) {
|
||||
try {
|
||||
callback.onSuccess(parseEvents(cachedJson));
|
||||
return;
|
||||
} catch (JSONException ignored) {
|
||||
}
|
||||
}
|
||||
callback.onError(e.getMessage() != null ? e.getMessage() : "Error desconocido");
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private String downloadJson() throws IOException {
|
||||
URL url = new URL(EVENTS_URL);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setConnectTimeout(15000);
|
||||
connection.setReadTimeout(15000);
|
||||
connection.setRequestMethod("GET");
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
builder.append(line);
|
||||
}
|
||||
return builder.toString();
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private List<EventItem> parseEvents(String json) throws JSONException {
|
||||
JSONArray array = new JSONArray(json);
|
||||
List<EventItem> events = new ArrayList<>();
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
JSONObject obj = array.getJSONObject(i);
|
||||
String title = obj.optString("title");
|
||||
String time = obj.optString("time");
|
||||
String category = obj.optString("category");
|
||||
String status = obj.optString("status");
|
||||
String link = obj.optString("link");
|
||||
String normalized = normalizeLink(link);
|
||||
long startMillis = parseEventTime(time);
|
||||
events.add(new EventItem(title, time, category, status, normalized, extractChannelName(link), startMillis));
|
||||
}
|
||||
return Collections.unmodifiableList(events);
|
||||
}
|
||||
|
||||
private String normalizeLink(String link) {
|
||||
if (link == null) {
|
||||
return "";
|
||||
}
|
||||
return link.replace("global1.php", "global2.php");
|
||||
}
|
||||
|
||||
private String extractChannelName(String link) {
|
||||
if (link == null) {
|
||||
return "";
|
||||
}
|
||||
int idx = link.indexOf("stream=");
|
||||
if (idx == -1) {
|
||||
return "";
|
||||
}
|
||||
return link.substring(idx + 7).replace("_", " ").toUpperCase();
|
||||
}
|
||||
|
||||
private long parseEventTime(String time) {
|
||||
if (time == null || time.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
|
||||
LocalTime localTime = LocalTime.parse(time.trim(), formatter);
|
||||
ZoneId zone = ZoneId.of("America/Argentina/Buenos_Aires");
|
||||
LocalDate today = LocalDate.now(zone);
|
||||
ZonedDateTime start = ZonedDateTime.of(LocalDateTime.of(today, localTime), zone);
|
||||
ZonedDateTime now = ZonedDateTime.now(zone);
|
||||
if (start.isBefore(now.minusHours(12))) {
|
||||
start = start.plusDays(1);
|
||||
}
|
||||
return start.toInstant().toEpochMilli();
|
||||
} catch (DateTimeParseException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user