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 podcastLibs; private static List 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, 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); 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 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 found = new ArrayList(); 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>() { boolean hasMore; List work() throws Exception { String lib = ensureLibrary(); List rows = new ArrayList(); if (!more) { App.flushPending(HomeActivity.this); App.loadProgress(HomeActivity.this); List cont = new ArrayList(); 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) { 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 work() throws Exception { String lib = ensureLibrary(); List rows = new ArrayList(); 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) { 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 = new ArrayList(); Episode cur = DownloadService.current; List 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 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); } } }