Files
podlite/src/com/miniabs/HomeActivity.java
T
xockoandClaude Opus 5.5 275bee3c11 MiniABS 1.2: Galician interface, larger text and touch targets
- Translate the interface to Galician (tools/translate_gl.py), including
  Galician month abbreviations for dates, which Android 2.3 lacks
- Resize for the Galaxy Mini's 240x320 screen: larger text, rows, tabs and
  buttons; all sizes now live in res/values/dimens.xml
- Redraw play/pause/refresh icons for every density; slightly brighter
  secondary text colours

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:29:17 +02:00

521 lines
20 KiB
Java

package com.miniabs;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONObject;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
public class HomeActivity extends BaseActivity {
static final int TAB_LATEST = 0, TAB_SHOWS = 1, TAB_SAVED = 2;
private static final int PAGE = 20;
// Kept across screen changes so going back is instant on a slow phone.
private static List<String[]> podcastLibs;
private static List<Rows.Row> latestCache, showsCache;
private static int latestPage;
private static boolean latestMore;
private ListView list;
private TextView empty, library;
private ProgressBar loading;
private Rows adapter;
private TextView[] tabs;
private int tab;
private boolean loadingLatest, loadingShows;
private String latestError, showsError;
private String savedSignature = "";
private int ticks;
static void clearCache() {
podcastLibs = null;
latestCache = null;
showsCache = null;
latestPage = 0;
latestMore = false;
}
@Override
protected void onCreate(Bundle b) {
super.onCreate(b);
if (!Prefs.get(this).loggedIn()) {
startActivity(new Intent(this, LoginActivity.class));
finish();
return;
}
setContentView(R.layout.activity_home);
list = (ListView) findViewById(R.id.list);
empty = (TextView) findViewById(R.id.empty);
loading = (ProgressBar) findViewById(R.id.loading);
library = (TextView) findViewById(R.id.library);
adapter = new Rows(this);
list.setAdapter(adapter);
registerForContextMenu(list);
setupMiniBar();
tabs = new TextView[]{(TextView) findViewById(R.id.tab_latest), (TextView) findViewById(R.id.tab_shows),
(TextView) findViewById(R.id.tab_saved)};
for (int i = 0; i < tabs.length; i++) {
final int t = i;
tabs[i].setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
selectTab(t);
}
});
}
library.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
chooseLibrary();
}
});
findViewById(R.id.refresh).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
refresh();
}
});
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
onRowClick(adapter.getItem(pos));
}
});
updateLibraryLabel();
int start = getIntent().getIntExtra("tab", Prefs.get(this).sp.getInt("tab", TAB_LATEST));
selectTab(start);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent.hasExtra("tab")) selectTab(intent.getIntExtra("tab", TAB_LATEST));
}
@Override
protected void onResume() {
super.onResume();
if (adapter == null) return;
if (!Prefs.get(this).loggedIn()) { // logged out from Settings
startActivity(new Intent(this, LoginActivity.class));
finish();
return;
}
updateLibraryLabel();
if (tab == TAB_SAVED) showSaved();
else if ((tab == TAB_LATEST && latestCache == null && !loadingLatest)
|| (tab == TAB_SHOWS && showsCache == null && !loadingShows)) selectTab(tab); // signed in again
else adapter.notifyDataSetChanged();
}
@Override
protected void onTick() {
super.onTick();
if (adapter == null) return;
ticks++;
if (tab == TAB_SAVED) {
Episode cur = DownloadService.current;
String sig = (cur == null ? "" : cur.id) + "/" + DownloadService.queued().size()
+ "/" + DownloadService.lastError;
if (!sig.equals(savedSignature)) showSaved();
else if (cur != null) adapter.notifyDataSetChanged();
} else if (ticks % 3 == 0) {
adapter.notifyDataSetChanged();
}
}
private void updateLibraryLabel() {
String name = Prefs.get(this).libraryName();
library.setText(name.length() > 0 ? name : "");
}
// ------------------------------------------------------------------ tabs
private void selectTab(int t) {
if (t < 0 || t > 2) t = TAB_LATEST;
tab = t;
Prefs.get(this).sp.edit().putInt("tab", t).commit();
for (int i = 0; i < tabs.length; i++) {
tabs[i].setSelected(i == t);
tabs[i].setTextColor(getResources().getColor(i == t ? R.color.text : R.color.sub));
}
adapter.showPodcastName = t != TAB_SHOWS;
if (t == TAB_LATEST) {
if (latestCache != null) show(latestCache, null);
else if (!loadingLatest) loadLatest(false);
else show(null, null);
} else if (t == TAB_SHOWS) {
if (showsCache != null) show(showsCache, null);
else if (!loadingShows) loadShows();
else show(null, null);
} else {
showSaved();
}
list.setSelection(0);
}
/** rows == null means "still loading". */
private void show(List<Rows.Row> rows, String emptyText) {
boolean busy = rows == null && ((tab == TAB_LATEST && loadingLatest) || (tab == TAB_SHOWS && loadingShows));
String err = tab == TAB_LATEST ? latestError : tab == TAB_SHOWS ? showsError : null;
adapter.set(rows == null ? new ArrayList<Rows.Row>() : rows);
loading.setVisibility(busy ? View.VISIBLE : View.GONE);
String msg = null;
if (!busy && (rows == null || rows.isEmpty())) msg = err != null ? err : emptyText;
empty.setText(msg == null ? "" : msg);
empty.setVisibility(msg == null ? View.GONE : View.VISIBLE);
}
private final Runnable retryRefresh = new Runnable() {
public void run() {
refresh();
}
};
private void refresh() {
App.invalidate();
Covers.clearMemory();
latestCache = null;
showsCache = null;
podcastLibs = null;
latestError = null;
showsError = null;
if (tab == TAB_SHOWS) loadShows();
else if (tab == TAB_LATEST) loadLatest(false);
else {
showSaved();
new Job<Integer>() {
Integer work() {
return App.flushPending(HomeActivity.this);
}
void done(Integer n) {
if (n > 0) toast("Sincronizouse o progreso gardado sen conexión");
}
}.start();
}
}
// ------------------------------------------------------------------ data
/** Picks a podcast library (the saved one if it still exists). Runs in the background. */
private String ensureLibrary() throws Exception {
Prefs p = Prefs.get(this);
if (podcastLibs == null) {
JSONArray libs = Api.libraries(this);
List<String[]> found = new ArrayList<String[]>();
for (int i = 0; i < libs.length(); i++) {
JSONObject l = libs.optJSONObject(i);
if (l != null && "podcast".equals(Util.str(l, "mediaType"))) {
found.add(new String[]{Util.str(l, "id"), Util.str(l, "name")});
}
}
if (found.isEmpty()) throw new IOException("Este servidor non ten bibliotecas de podcasts.");
podcastLibs = found;
}
String id = p.libraryId();
for (String[] l : podcastLibs) {
if (l[0].equals(id)) {
if (!l[1].equals(p.libraryName())) p.setLibrary(l[0], l[1]);
return id;
}
}
String[] first = podcastLibs.get(0);
p.setLibrary(first[0], first[1]);
return first[0];
}
private void loadLatest(final boolean more) {
loadingLatest = true;
latestError = null;
if (!more && tab == TAB_LATEST) show(null, null);
final int page = more ? latestPage + 1 : 0;
new Job<List<Rows.Row>>() {
boolean hasMore;
List<Rows.Row> work() throws Exception {
String lib = ensureLibrary();
List<Rows.Row> rows = new ArrayList<Rows.Row>();
if (!more) {
App.flushPending(HomeActivity.this);
App.loadProgress(HomeActivity.this);
List<Rows.Row> cont = new ArrayList<Rows.Row>();
try {
JSONArray items = Api.itemsInProgress(HomeActivity.this).optJSONArray("libraryItems");
for (int i = 0; items != null && i < items.length(); i++) {
JSONObject it = items.optJSONObject(i);
if (it == null || !lib.equals(Util.str(it, "libraryId"))) continue;
JSONObject media = it.optJSONObject("media");
JSONObject md = media == null ? null : media.optJSONObject("metadata");
Episode e = Episode.fromApi(it.optJSONObject("recentEpisode"), Util.str(it, "id"),
Util.str(md, "title"), Util.str(md, "author"));
if (e == null) continue;
double[] p = App.progress(e.id);
if (p != null && p[2] > 0) continue;
cont.add(Rows.Row.episode(e));
if (cont.size() >= 5) break;
}
} catch (Api.AuthException ae) {
throw ae;
} catch (Exception ignored) {
// older servers: just skip this section
}
if (!cont.isEmpty()) {
rows.add(Rows.Row.header("CONTINUAR ESCOITANDO"));
rows.addAll(cont);
}
rows.add(Rows.Row.header("ÚLTIMOS EPISODIOS"));
}
JSONArray eps = Api.recentEpisodes(HomeActivity.this, lib, page, PAGE).optJSONArray("episodes");
int n = eps == null ? 0 : eps.length();
for (int i = 0; i < n; i++) {
Episode e = Episode.fromApi(eps.optJSONObject(i), null, null, null);
if (e != null) rows.add(Rows.Row.episode(e));
}
hasMore = n >= PAGE;
return rows;
}
void done(List<Rows.Row> rows) {
loadingLatest = false;
if (isFinishing()) return;
updateLibraryLabel();
if (more && latestCache != null) {
int last = latestCache.size() - 1;
if (last >= 0 && latestCache.get(last).type == Rows.MORE) latestCache.remove(last);
latestCache.addAll(rows);
} else {
latestCache = rows;
}
latestPage = page;
latestMore = hasMore;
if (hasMore) latestCache.add(Rows.Row.more("Cargar máis"));
if (tab == TAB_LATEST) show(latestCache, "Aínda non hai episodios.");
}
void failed(Exception e) {
loadingLatest = false;
if (isFinishing()) return;
if (e instanceof Api.AuthException) {
showError(e);
return;
}
if (more) {
toast(Util.describe(e));
if (latestCache != null && !latestCache.isEmpty()) {
latestCache.get(latestCache.size() - 1).text = "Cargar máis";
adapter.notifyDataSetChanged();
}
return;
}
latestError = Util.describe(e) + "\n\nToca o botón de actualizar (arriba á dereita) para tentalo de novo.";
if (offerTrust(retryRefresh)) {
if (tab == TAB_LATEST) show(null, null);
return;
}
if (tab == TAB_LATEST) offlineFallback(e);
}
}.start();
}
private void loadShows() {
loadingShows = true;
showsError = null;
if (tab == TAB_SHOWS) show(null, null);
new Job<List<Rows.Row>>() {
List<Rows.Row> work() throws Exception {
String lib = ensureLibrary();
List<Rows.Row> rows = new ArrayList<Rows.Row>();
for (int page = 0; page < 20; page++) {
JSONObject r = Api.libraryItems(HomeActivity.this, lib, page, 100);
JSONArray res = r.optJSONArray("results");
int n = res == null ? 0 : res.length();
for (int i = 0; i < n; i++) {
JSONObject it = res.optJSONObject(i);
if (it == null) continue;
JSONObject media = it.optJSONObject("media");
JSONObject md = media == null ? null : media.optJSONObject("metadata");
Rows.Show s = new Rows.Show();
s.id = Util.str(it, "id");
s.title = Util.str(md, "title");
s.author = Util.str(md, "author");
s.episodes = media == null ? 0 : media.optInt("numEpisodes", 0);
if (s.episodes == 0 && media != null && media.optJSONArray("episodes") != null) {
s.episodes = media.optJSONArray("episodes").length();
}
rows.add(Rows.Row.show(s));
}
int total = r.optInt("total", 0);
if (n < 100 || rows.size() >= total) break;
}
return rows;
}
void done(List<Rows.Row> rows) {
loadingShows = false;
if (isFinishing()) return;
updateLibraryLabel();
showsCache = rows;
if (tab == TAB_SHOWS) show(rows, "Non hai podcasts nesta biblioteca.");
}
void failed(Exception e) {
loadingShows = false;
if (isFinishing()) return;
if (e instanceof Api.AuthException) {
showError(e);
return;
}
showsError = Util.describe(e) + "\n\nToca o botón de actualizar (arriba á dereita) para tentalo de novo.";
if (offerTrust(retryRefresh)) {
if (tab == TAB_SHOWS) show(null, null);
return;
}
if (tab == TAB_SHOWS) offlineFallback(e);
}
}.start();
}
/** When the server can't be reached but episodes are saved, show those instead. */
private void offlineFallback(Exception e) {
if (!(e instanceof Api.ApiException) && !Downloads.list(this).isEmpty()) {
toast("Non se pode acceder ao servidor. Amósanse os episodios gardados.");
selectTab(TAB_SAVED);
} else {
show(null, null);
}
}
private void showSaved() {
List<Rows.Row> rows = new ArrayList<Rows.Row>();
Episode cur = DownloadService.current;
List<Episode> queued = DownloadService.queued();
savedSignature = (cur == null ? "" : cur.id) + "/" + queued.size() + "/" + DownloadService.lastError;
if (DownloadService.lastError != null) rows.add(Rows.Row.header(DownloadService.lastError));
if (cur != null || !queued.isEmpty()) {
rows.add(Rows.Row.header("DESCARGANDO"));
if (cur != null) rows.add(Rows.Row.episode(cur));
for (Episode q : queued) rows.add(Rows.Row.episode(q));
}
List<Episode> saved = Downloads.list(this);
if (!saved.isEmpty()) {
rows.add(Rows.Row.header("GARDADOS NA TARXETA SD (" + saved.size() + ")"));
for (Episode e : saved) rows.add(Rows.Row.episode(e));
}
if (rows.isEmpty()) {
show(rows, "Aínda non gardaches nada.\n\nMantén premido un episodio e escolle Descargar para escoitalo sen conexión.");
} else {
show(rows, null);
}
}
// ------------------------------------------------------------------ interaction
private void onRowClick(Rows.Row r) {
if (r == null) return;
switch (r.type) {
case Rows.EPISODE:
playEpisode(r.ep);
break;
case Rows.SHOW:
startActivity(new Intent(this, PodcastActivity.class)
.putExtra("itemId", r.show.id).putExtra("title", r.show.title)
.putExtra("author", r.show.author));
break;
case Rows.MORE:
if (!loadingLatest && latestMore) {
r.text = "Cargando…";
adapter.notifyDataSetChanged();
loadLatest(true);
}
break;
default:
break;
}
}
private Episode menuEpisode;
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo info) {
AdapterView.AdapterContextMenuInfo mi = (AdapterView.AdapterContextMenuInfo) info;
Rows.Row r = adapter.getItem(mi.position);
if (r.type != Rows.EPISODE) return;
menuEpisode = r.ep;
episodeMenu(menu, r.ep, true);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
return onEpisodeContextItem(item, menuEpisode, new Runnable() {
public void run() {
if (tab == TAB_SAVED) showSaved();
else adapter.notifyDataSetChanged();
}
}) || super.onContextItemSelected(item);
}
private void chooseLibrary() {
if (podcastLibs == null || podcastLibs.size() < 2) {
toast(podcastLibs == null ? "Aínda se están cargando as bibliotecas…" : "Esta é a túa única biblioteca de podcasts.");
return;
}
String[] names = new String[podcastLibs.size()];
for (int i = 0; i < names.length; i++) names[i] = podcastLibs.get(i)[1];
new AlertDialog.Builder(this).setTitle("Biblioteca de podcasts")
.setItems(names, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int which) {
String[] l = podcastLibs.get(which);
Prefs.get(HomeActivity.this).setLibrary(l[0], l[1]);
updateLibraryLabel();
latestCache = null;
showsCache = null;
selectTab(tab == TAB_SAVED ? TAB_LATEST : tab);
}
}).show();
}
// ------------------------------------------------------------------ menu key
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, 1, 0, "Actualizar").setIcon(android.R.drawable.ic_menu_rotate);
menu.add(0, 2, 0, "Biblioteca").setIcon(android.R.drawable.ic_menu_agenda);
menu.add(0, 3, 0, "En reprodución").setIcon(android.R.drawable.ic_media_play);
menu.add(0, 4, 0, "Axustes").setIcon(android.R.drawable.ic_menu_preferences);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case 1:
refresh();
return true;
case 2:
chooseLibrary();
return true;
case 3:
startActivity(new Intent(this, PlayerActivity.class));
return true;
case 4:
startActivity(new Intent(this, SettingsActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}