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>
This commit is contained in:
1 parent
b7e43d7d61
commit
275bee3c11
51 files changed
+704
-372
No files matched your search
+20
-20
@@ -26,7 +26,7 @@ import android.os.Build;
|
||||
|
||||
/** Audiobookshelf REST client (API as of server 2.x, including the 2.26+ token system). */
|
||||
final class Api {
|
||||
static final String VERSION = "1.1";
|
||||
static final String VERSION = "1.2";
|
||||
|
||||
static class ApiException extends IOException {
|
||||
final int code;
|
||||
@@ -100,7 +100,7 @@ final class Api {
|
||||
if (peer.length == 0 || !(peer[0] instanceof java.security.cert.X509Certificate)
|
||||
|| !Trust.hostMatches((java.security.cert.X509Certificate) peer[0], u.getHost())) {
|
||||
ss.close();
|
||||
throw new SSLException("Certificate doesn't match " + u.getHost());
|
||||
throw new SSLException("O certificado non corresponde a " + u.getHost());
|
||||
}
|
||||
io = ss;
|
||||
}
|
||||
@@ -123,11 +123,11 @@ final class Api {
|
||||
int ch;
|
||||
while ((ch = in.read()) != -1 && ch != '\n' && line.length() < 200) line.append((char) ch);
|
||||
String[] parts = line.toString().trim().split(" ");
|
||||
if (parts.length < 2 || !parts[0].startsWith("HTTP/")) throw new IOException("Unexpected reply from server");
|
||||
if (parts.length < 2 || !parts[0].startsWith("HTTP/")) throw new IOException("Resposta inesperada do servidor");
|
||||
try {
|
||||
code = Integer.parseInt(parts[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException("Unexpected reply from server");
|
||||
throw new IOException("Resposta inesperada do servidor");
|
||||
}
|
||||
byte[] rest = new byte[1024];
|
||||
int total = 0, n;
|
||||
@@ -144,10 +144,10 @@ final class Api {
|
||||
refreshIf(c, used, 0);
|
||||
return rawPatch(c, path, body, false);
|
||||
}
|
||||
throw new AuthException("Your sign-in has expired. Please log in again.");
|
||||
throw new AuthException("A túa sesión caducou. Inicia sesión de novo.");
|
||||
}
|
||||
if (code >= 400) throw new ApiException(code, "");
|
||||
if (code >= 300) throw new ApiException(code, "The server redirected; check the address in Settings.");
|
||||
if (code >= 300) throw new ApiException(code, "O servidor redirixiu a petición; comproba o enderezo do servidor.");
|
||||
return "OK";
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ final class Api {
|
||||
static Object call(Context c, String method, String path, JSONObject body, int maxString, String[] deepSkip,
|
||||
boolean mayRefresh) throws IOException {
|
||||
Prefs p = Prefs.get(c);
|
||||
if (!p.loggedIn()) throw new AuthException("Please log in.");
|
||||
if (!p.loggedIn()) throw new AuthException("Inicia sesión.");
|
||||
if ("PATCH".equals(method)) return rawPatch(c, path, body, mayRefresh);
|
||||
if (mayRefresh) ensureFresh(c, 120);
|
||||
String used = p.token();
|
||||
@@ -232,7 +232,7 @@ final class Api {
|
||||
refreshIf(c, used, 0);
|
||||
return call(c, method, path, body, maxString, deepSkip, false);
|
||||
}
|
||||
throw new AuthException("Your sign-in has expired. Please log in again.");
|
||||
throw new AuthException("A túa sesión caducou. Inicia sesión de novo.");
|
||||
}
|
||||
if (code >= 400) throw new ApiException(code, errorText(conn));
|
||||
return readBody(conn, maxString, deepSkip);
|
||||
@@ -243,7 +243,7 @@ final class Api {
|
||||
|
||||
static JSONObject obj(Object o) throws IOException {
|
||||
if (o instanceof JSONObject) return (JSONObject) o;
|
||||
throw new IOException("Unexpected reply from server");
|
||||
throw new IOException("Resposta inesperada do servidor");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ sign-in
|
||||
@@ -253,13 +253,13 @@ final class Api {
|
||||
HttpURLConnection conn = open(c, server + "/status", "GET");
|
||||
try {
|
||||
int code = responseCode(conn);
|
||||
if (code == 404) throw new IOException("No Audiobookshelf server at this address.");
|
||||
if (code == 404) throw new IOException("Non hai ningún servidor de Audiobookshelf neste enderezo.");
|
||||
if (code >= 400) throw new ApiException(code, errorText(conn));
|
||||
Object o = readBody(conn, 200, null);
|
||||
if (!(o instanceof JSONObject)) throw new IOException("No Audiobookshelf server at this address.");
|
||||
if (!(o instanceof JSONObject)) throw new IOException("Non hai ningún servidor de Audiobookshelf neste enderezo.");
|
||||
JSONObject s = (JSONObject) o;
|
||||
if (s.has("isInit") && !s.optBoolean("isInit", true)) {
|
||||
throw new IOException("This server hasn't been set up yet. Finish setup in a browser first.");
|
||||
throw new IOException("Este servidor aínda non está configurado. Remata a configuración nun navegador.");
|
||||
}
|
||||
return Util.str(s, "serverVersion");
|
||||
} finally {
|
||||
@@ -280,19 +280,19 @@ final class Api {
|
||||
}
|
||||
writeBody(conn, body);
|
||||
int code = responseCode(conn);
|
||||
if (code == 401) throw new ApiException(401, "Wrong username or password.");
|
||||
if (code == 429) throw new ApiException(429, "Too many attempts. Wait a minute and try again.");
|
||||
if (code == 401) throw new ApiException(401, "Usuario ou contrasinal incorrectos.");
|
||||
if (code == 429) throw new ApiException(429, "Demasiados intentos. Agarda un minuto e téntao de novo.");
|
||||
if (code >= 400) throw new ApiException(code, errorText(conn));
|
||||
JSONObject o = obj(readBody(conn, 500, null));
|
||||
JSONObject u = o.optJSONObject("user");
|
||||
if (u == null) throw new IOException("Unexpected reply from server");
|
||||
if (u == null) throw new IOException("Resposta inesperada do servidor");
|
||||
String access = Util.str(u, "accessToken");
|
||||
String refresh = Util.str(u, "refreshToken");
|
||||
String legacy = Util.str(u, "token");
|
||||
Prefs p = Prefs.get(c);
|
||||
if (access.length() > 0) p.setAuth(user, Prefs.MODE_JWT, access, refresh);
|
||||
else if (legacy.length() > 0) p.setAuth(user, Prefs.MODE_LEGACY, legacy, "");
|
||||
else throw new IOException("The server didn't return a sign-in token.");
|
||||
else throw new IOException("O servidor non devolveu ningún token de sesión.");
|
||||
return Util.str(o, "userDefaultLibraryId");
|
||||
} finally {
|
||||
conn.disconnect();
|
||||
@@ -310,7 +310,7 @@ final class Api {
|
||||
return name;
|
||||
} catch (IOException e) {
|
||||
p.logout();
|
||||
if (e instanceof AuthException) throw new ApiException(401, "That API key wasn't accepted.");
|
||||
if (e instanceof AuthException) throw new ApiException(401, "Non se aceptou esa chave da API.");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -335,7 +335,7 @@ final class Api {
|
||||
private static void refreshIf(Context c, String stale, long minSeconds) throws IOException {
|
||||
synchronized (REFRESH_LOCK) {
|
||||
Prefs p = Prefs.get(c);
|
||||
if (!p.canRefresh()) throw new AuthException("Your sign-in has expired. Please log in again.");
|
||||
if (!p.canRefresh()) throw new AuthException("A túa sesión caducou. Inicia sesión de novo.");
|
||||
if (stale != null) {
|
||||
if (!stale.equals(p.token())) return;
|
||||
} else if (p.tokenSecondsLeft() >= minSeconds) {
|
||||
@@ -348,12 +348,12 @@ final class Api {
|
||||
writeBody(conn, null);
|
||||
int code = responseCode(conn);
|
||||
if (code == 401 || code == 403) {
|
||||
throw new AuthException("Your sign-in has expired. Please log in again.");
|
||||
throw new AuthException("A túa sesión caducou. Inicia sesión de novo.");
|
||||
}
|
||||
if (code >= 400) throw new ApiException(code, errorText(conn));
|
||||
JSONObject u = obj(readBody(conn, 500, null)).optJSONObject("user");
|
||||
String access = u == null ? "" : Util.str(u, "accessToken");
|
||||
if (access.length() == 0) throw new AuthException("Your sign-in has expired. Please log in again.");
|
||||
if (access.length() == 0) throw new AuthException("A túa sesión caducou. Inicia sesión de novo.");
|
||||
p.setTokens(access, Util.str(u, "refreshToken"));
|
||||
} finally {
|
||||
conn.disconnect();
|
||||
|
||||
@@ -73,7 +73,7 @@ abstract class BaseActivity extends Activity {
|
||||
return TrustPrompt.offer(this, new Runnable() {
|
||||
public void run() {
|
||||
if (retry != null) retry.run();
|
||||
else toast("Certificate trusted. Try again.");
|
||||
else toast("Certificado aceptado. Téntao de novo.");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -130,13 +130,13 @@ abstract class BaseActivity extends Activity {
|
||||
dur = ps.durationMs();
|
||||
int st = ps.state();
|
||||
if (st == PlayerService.LOADING || st == PlayerService.ERROR) sub = ps.status();
|
||||
else sub = Util.join(e.show, dur > 0 ? Util.clock(dur - pos) + " left" : "");
|
||||
else sub = Util.join(e.show, dur > 0 ? "quedan " + Util.clock(dur - pos) : "");
|
||||
} else {
|
||||
double[] p = App.progress(e.id);
|
||||
double cur = p != null ? p[0] : Math.max(0, App.localPosition(this, e.id));
|
||||
pos = (int) (cur * 1000);
|
||||
dur = (int) (e.duration * 1000);
|
||||
sub = Util.join(e.show, "Paused");
|
||||
sub = Util.join(e.show, "En pausa");
|
||||
}
|
||||
miniSub.setText(sub);
|
||||
miniProgress.setProgress(dur > 0 ? (int) (1000L * pos / dur) : 0);
|
||||
@@ -149,14 +149,14 @@ abstract class BaseActivity extends Activity {
|
||||
|
||||
protected void episodeMenu(ContextMenu menu, Episode e, boolean offerShow) {
|
||||
menu.setHeaderTitle(e.title);
|
||||
menu.add(0, M_PLAY, 0, "Play");
|
||||
if (DownloadService.busyWith(e.id)) menu.add(0, M_CANCEL, 0, "Cancel download");
|
||||
else if (Downloads.has(this, e.id)) menu.add(0, M_DELETE, 0, "Delete download");
|
||||
else menu.add(0, M_DOWNLOAD, 0, "Download");
|
||||
menu.add(0, M_PLAY, 0, "Reproducir");
|
||||
if (DownloadService.busyWith(e.id)) menu.add(0, M_CANCEL, 0, "Cancelar descarga");
|
||||
else if (Downloads.has(this, e.id)) menu.add(0, M_DELETE, 0, "Eliminar descarga");
|
||||
else menu.add(0, M_DOWNLOAD, 0, "Descargar");
|
||||
double[] p = App.progress(e.id);
|
||||
if (p != null && p[2] > 0) menu.add(0, M_UNPLAYED, 0, "Mark as unplayed");
|
||||
else menu.add(0, M_PLAYED, 0, "Mark as played");
|
||||
if (offerShow) menu.add(0, M_SHOW, 0, "Go to show");
|
||||
if (p != null && p[2] > 0) menu.add(0, M_UNPLAYED, 0, "Marcar como non escoitado");
|
||||
else menu.add(0, M_PLAYED, 0, "Marcar como escoitado");
|
||||
if (offerShow) menu.add(0, M_SHOW, 0, "Ir ao programa");
|
||||
}
|
||||
|
||||
/** Returns true if handled. onChanged runs on the UI thread after the list should refresh. */
|
||||
@@ -168,26 +168,26 @@ abstract class BaseActivity extends Activity {
|
||||
case M_DOWNLOAD:
|
||||
if (Prefs.get(this).wifiOnly() && !Util.onWifi(this)) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage("You're not on Wi-Fi. Download over mobile data this time?")
|
||||
.setPositiveButton("Download", new DialogInterface.OnClickListener() {
|
||||
.setMessage("Non estás nunha wifi. Descargar con datos móbiles esta vez?")
|
||||
.setPositiveButton("Descargar", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
Prefs.get(BaseActivity.this).sp.edit().putBoolean("wifi_only", false).commit();
|
||||
DownloadService.enqueue(BaseActivity.this, e);
|
||||
toast("Downloading. Wi-Fi-only is now off (change it in Settings).");
|
||||
toast("Descargando. Desactivouse «só con wifi» (podes cambialo en Axustes).");
|
||||
if (onChanged != null) onChanged.run();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", null)
|
||||
.setNegativeButton("Cancelar", null)
|
||||
.show();
|
||||
} else {
|
||||
DownloadService.enqueue(this, e);
|
||||
toast("Downloading…");
|
||||
toast("Descargando…");
|
||||
}
|
||||
if (onChanged != null) onChanged.run();
|
||||
return true;
|
||||
case M_CANCEL:
|
||||
DownloadService.cancel(e.id);
|
||||
toast("Download cancelled");
|
||||
toast("Descarga cancelada");
|
||||
if (onChanged != null) onChanged.run();
|
||||
return true;
|
||||
case M_DELETE:
|
||||
@@ -196,7 +196,7 @@ abstract class BaseActivity extends Activity {
|
||||
ps.stop();
|
||||
}
|
||||
Downloads.delete(this, e);
|
||||
toast("Download deleted");
|
||||
toast("Descarga eliminada");
|
||||
if (onChanged != null) onChanged.run();
|
||||
return true;
|
||||
case M_PLAYED:
|
||||
@@ -230,7 +230,7 @@ abstract class BaseActivity extends Activity {
|
||||
}
|
||||
|
||||
void done(Void r) {
|
||||
toast(played ? "Marked as played" : "Marked as unplayed");
|
||||
toast(played ? "Marcado como escoitado" : "Marcado como non escoitado");
|
||||
}
|
||||
|
||||
void failed(Exception ex) {
|
||||
|
||||
@@ -102,7 +102,7 @@ public class DownloadService extends IntentService {
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (!e.id.equals(cancelId)) {
|
||||
lastError = "Download failed: " + Util.describe(ex);
|
||||
lastError = "Fallou a descarga: " + Util.describe(ex);
|
||||
notifyDone(e.title, lastError);
|
||||
}
|
||||
} finally {
|
||||
@@ -116,14 +116,14 @@ public class DownloadService extends IntentService {
|
||||
|
||||
private void download(Episode e) throws IOException {
|
||||
if (Prefs.get(this).wifiOnly() && !Util.onWifi(this)) {
|
||||
throw new IOException("not on Wi-Fi (Settings: Download on Wi-Fi only)");
|
||||
throw new IOException("non hai wifi (Axustes: Descargar só con wifi)");
|
||||
}
|
||||
File dir = Downloads.dir(this);
|
||||
if (Downloads.audio(this, e) != null) {
|
||||
Downloads.saved(this, e);
|
||||
return;
|
||||
}
|
||||
if (e.ino.length() == 0) throw new IOException("this episode has no audio file");
|
||||
if (e.ino.length() == 0) throw new IOException("este episodio non ten ficheiro de audio");
|
||||
File part = new File(dir, e.id + ".part");
|
||||
Notification n = progressNotification(e, 0);
|
||||
startForeground(NOTIF_ID, n);
|
||||
@@ -158,8 +158,8 @@ public class DownloadService extends IntentService {
|
||||
if (code != 404 && code != 403) break;
|
||||
}
|
||||
if (code != 200 && code != 206) {
|
||||
if (code == 401) throw new Api.AuthException("sign-in expired; log in again");
|
||||
if (code == 403) throw new IOException("your account isn't allowed to download");
|
||||
if (code == 401) throw new Api.AuthException("a sesión caducou; inicia sesión de novo");
|
||||
if (code == 403) throw new IOException("a túa conta non ten permiso para descargar");
|
||||
throw new Api.ApiException(code, "");
|
||||
}
|
||||
try {
|
||||
@@ -194,7 +194,7 @@ public class DownloadService extends IntentService {
|
||||
}
|
||||
if (e.id.equals(cancelId)) return;
|
||||
File dest = new File(dir, e.id + e.ext);
|
||||
if (!part.renameTo(dest)) throw new IOException("couldn't save the file");
|
||||
if (!part.renameTo(dest)) throw new IOException("non se puido gardar o ficheiro");
|
||||
Downloads.saved(this, e);
|
||||
percent = 100;
|
||||
}
|
||||
@@ -216,7 +216,7 @@ public class DownloadService extends IntentService {
|
||||
synchronized (queue) {
|
||||
left = queue.size();
|
||||
}
|
||||
String text = "Downloading " + pct + "%" + (left > 0 ? " · " + left + " more queued" : "");
|
||||
String text = "Descargando " + pct + " %" + (left > 0 ? " · " + left + " máis na cola" : "");
|
||||
n.setLatestEventInfo(this, e.title, text, openApp());
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ final class Downloads {
|
||||
|
||||
static File dir(Context c) throws IOException {
|
||||
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
|
||||
throw new IOException("No SD card available. Insert or unmount it from USB to download.");
|
||||
throw new IOException("Non hai tarxeta SD dispoñible. Insírea ou desconéctaa do USB para descargar.");
|
||||
}
|
||||
File d = c.getExternalFilesDir(Environment.DIRECTORY_PODCASTS);
|
||||
if (d == null) throw new IOException("Can't use the SD card.");
|
||||
if (!d.exists() && !d.mkdirs()) throw new IOException("Can't create a folder on the SD card.");
|
||||
if (d == null) throw new IOException("Non se pode usar a tarxeta SD.");
|
||||
if (!d.exists() && !d.mkdirs()) throw new IOException("Non se pode crear un cartafol na tarxeta SD.");
|
||||
File nomedia = new File(d, ".nomedia");
|
||||
if (!nomedia.exists()) {
|
||||
try {
|
||||
|
||||
@@ -202,7 +202,7 @@ public class HomeActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
void done(Integer n) {
|
||||
if (n > 0) toast("Synced offline progress");
|
||||
if (n > 0) toast("Sincronizouse o progreso gardado sen conexión");
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
@@ -222,7 +222,7 @@ public class HomeActivity extends BaseActivity {
|
||||
found.add(new String[]{Util.str(l, "id"), Util.str(l, "name")});
|
||||
}
|
||||
}
|
||||
if (found.isEmpty()) throw new IOException("This server has no podcast libraries.");
|
||||
if (found.isEmpty()) throw new IOException("Este servidor non ten bibliotecas de podcasts.");
|
||||
podcastLibs = found;
|
||||
}
|
||||
String id = p.libraryId();
|
||||
@@ -273,10 +273,10 @@ public class HomeActivity extends BaseActivity {
|
||||
// older servers: just skip this section
|
||||
}
|
||||
if (!cont.isEmpty()) {
|
||||
rows.add(Rows.Row.header("CONTINUE LISTENING"));
|
||||
rows.add(Rows.Row.header("CONTINUAR ESCOITANDO"));
|
||||
rows.addAll(cont);
|
||||
}
|
||||
rows.add(Rows.Row.header("LATEST EPISODES"));
|
||||
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();
|
||||
@@ -301,8 +301,8 @@ public class HomeActivity extends BaseActivity {
|
||||
}
|
||||
latestPage = page;
|
||||
latestMore = hasMore;
|
||||
if (hasMore) latestCache.add(Rows.Row.more("Load more"));
|
||||
if (tab == TAB_LATEST) show(latestCache, "No episodes yet.");
|
||||
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) {
|
||||
@@ -315,12 +315,12 @@ public class HomeActivity extends BaseActivity {
|
||||
if (more) {
|
||||
toast(Util.describe(e));
|
||||
if (latestCache != null && !latestCache.isEmpty()) {
|
||||
latestCache.get(latestCache.size() - 1).text = "Load more";
|
||||
latestCache.get(latestCache.size() - 1).text = "Cargar máis";
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
return;
|
||||
}
|
||||
latestError = Util.describe(e) + "\n\nTap refresh to try again.";
|
||||
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;
|
||||
@@ -368,7 +368,7 @@ public class HomeActivity extends BaseActivity {
|
||||
if (isFinishing()) return;
|
||||
updateLibraryLabel();
|
||||
showsCache = rows;
|
||||
if (tab == TAB_SHOWS) show(rows, "No podcasts in this library.");
|
||||
if (tab == TAB_SHOWS) show(rows, "Non hai podcasts nesta biblioteca.");
|
||||
}
|
||||
|
||||
void failed(Exception e) {
|
||||
@@ -378,7 +378,7 @@ public class HomeActivity extends BaseActivity {
|
||||
showError(e);
|
||||
return;
|
||||
}
|
||||
showsError = Util.describe(e) + "\n\nTap refresh to try again.";
|
||||
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;
|
||||
@@ -391,7 +391,7 @@ public class HomeActivity extends BaseActivity {
|
||||
/** 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("Can't reach the server. Showing saved episodes.");
|
||||
toast("Non se pode acceder ao servidor. Amósanse os episodios gardados.");
|
||||
selectTab(TAB_SAVED);
|
||||
} else {
|
||||
show(null, null);
|
||||
@@ -405,17 +405,17 @@ public class HomeActivity extends BaseActivity {
|
||||
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("DOWNLOADING"));
|
||||
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("SAVED ON SD CARD (" + saved.size() + ")"));
|
||||
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, "Nothing saved yet.\n\nLong-press an episode and choose Download to listen offline.");
|
||||
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);
|
||||
}
|
||||
@@ -436,7 +436,7 @@ public class HomeActivity extends BaseActivity {
|
||||
break;
|
||||
case Rows.MORE:
|
||||
if (!loadingLatest && latestMore) {
|
||||
r.text = "Loading…";
|
||||
r.text = "Cargando…";
|
||||
adapter.notifyDataSetChanged();
|
||||
loadLatest(true);
|
||||
}
|
||||
@@ -469,12 +469,12 @@ public class HomeActivity extends BaseActivity {
|
||||
|
||||
private void chooseLibrary() {
|
||||
if (podcastLibs == null || podcastLibs.size() < 2) {
|
||||
toast(podcastLibs == null ? "Still loading libraries…" : "This is your only podcast library.");
|
||||
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("Podcast library")
|
||||
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);
|
||||
@@ -491,10 +491,10 @@ public class HomeActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, 1, 0, "Refresh").setIcon(android.R.drawable.ic_menu_rotate);
|
||||
menu.add(0, 2, 0, "Library").setIcon(android.R.drawable.ic_menu_agenda);
|
||||
menu.add(0, 3, 0, "Now playing").setIcon(android.R.drawable.ic_media_play);
|
||||
menu.add(0, 4, 0, "Settings").setIcon(android.R.drawable.ic_menu_preferences);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
+10
-10
@@ -88,13 +88,13 @@ final class Json {
|
||||
return JSONObject.NULL;
|
||||
default:
|
||||
if (c == '-' || (c >= '0' && c <= '9')) return number(c);
|
||||
throw new IOException("Unexpected reply from server");
|
||||
throw new IOException("Resposta inesperada do servidor");
|
||||
}
|
||||
}
|
||||
|
||||
private void expect(String rest) throws IOException {
|
||||
for (int i = 0; i < rest.length(); i++) {
|
||||
if (read() != rest.charAt(i)) throw new IOException("Unexpected reply from server");
|
||||
if (read() != rest.charAt(i)) throw new IOException("Resposta inesperada do servidor");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,10 +149,10 @@ final class Json {
|
||||
return o;
|
||||
}
|
||||
while (true) {
|
||||
if (c != '"') throw new IOException("Unexpected reply from server");
|
||||
if (c != '"') throw new IOException("Resposta inesperada do servidor");
|
||||
String key = string(512);
|
||||
c = skipWs();
|
||||
if (c != ':') throw new IOException("Unexpected reply from server");
|
||||
if (c != ':') throw new IOException("Resposta inesperada do servidor");
|
||||
c = skipWs();
|
||||
if (skipKey(key)) {
|
||||
skipValue(c);
|
||||
@@ -173,7 +173,7 @@ final class Json {
|
||||
depth--;
|
||||
return o;
|
||||
}
|
||||
throw new IOException("Unexpected reply from server");
|
||||
throw new IOException("Resposta inesperada do servidor");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ final class Json {
|
||||
continue;
|
||||
}
|
||||
if (c == ']') return a;
|
||||
throw new IOException("Unexpected reply from server");
|
||||
throw new IOException("Resposta inesperada do servidor");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ final class Json {
|
||||
int d = 1;
|
||||
while (d > 0) {
|
||||
int ch = read();
|
||||
if (ch == -1) throw new IOException("Reply was cut off");
|
||||
if (ch == -1) throw new IOException("A resposta chegou incompleta");
|
||||
if (ch == '"') string(0);
|
||||
else if (ch == '{' || ch == '[') d++;
|
||||
else if (ch == '}' || ch == ']') d--;
|
||||
@@ -217,7 +217,7 @@ final class Json {
|
||||
boolean cut = false;
|
||||
while (true) {
|
||||
int c = read();
|
||||
if (c == -1) throw new IOException("Reply was cut off");
|
||||
if (c == -1) throw new IOException("A resposta chegou incompleta");
|
||||
if (c == '"') break;
|
||||
if (c == '\\') {
|
||||
c = read();
|
||||
@@ -231,14 +231,14 @@ final class Json {
|
||||
int v = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int d = Character.digit(read(), 16);
|
||||
if (d < 0) throw new IOException("Unexpected reply from server");
|
||||
if (d < 0) throw new IOException("Resposta inesperada do servidor");
|
||||
v = v * 16 + d;
|
||||
}
|
||||
c = v;
|
||||
break;
|
||||
}
|
||||
case -1:
|
||||
throw new IOException("Reply was cut off");
|
||||
throw new IOException("A resposta chegou incompleta");
|
||||
default:
|
||||
break; // \" \\ \/
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class LoginActivity extends Activity {
|
||||
int vis = on ? View.GONE : View.VISIBLE;
|
||||
user.setVisibility(vis);
|
||||
userLabel.setVisibility(vis);
|
||||
passLabel.setText(on ? "API key" : "Password");
|
||||
passLabel.setText(on ? "Chave da API" : "Contrasinal");
|
||||
pass.setInputType(on ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||
: InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
pass.setText("");
|
||||
@@ -69,17 +69,17 @@ public class LoginActivity extends Activity {
|
||||
final String pw = pass.getText().toString();
|
||||
final boolean key = useKey.isChecked();
|
||||
if (srv.length() == 0) {
|
||||
status.setText("Enter your server address.");
|
||||
status.setText("Escribe o enderezo do servidor.");
|
||||
return;
|
||||
}
|
||||
if (key ? pw.trim().length() == 0 : u.length() == 0) {
|
||||
status.setText(key ? "Paste your API key." : "Enter your username.");
|
||||
status.setText(key ? "Pega a túa chave da API." : "Escribe o teu usuario.");
|
||||
return;
|
||||
}
|
||||
server.setText(srv);
|
||||
((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(pass.getWindowToken(), 0);
|
||||
connect.setEnabled(false);
|
||||
status.setText("Connecting…");
|
||||
status.setText("Conectando…");
|
||||
|
||||
final Prefs p = Prefs.get(this);
|
||||
p.logout();
|
||||
|
||||
@@ -167,9 +167,9 @@ public class PlayerActivity extends BaseActivity {
|
||||
Episode e = current();
|
||||
if (e == null) {
|
||||
show.setText("");
|
||||
title.setText("Nothing playing");
|
||||
title.setText("Non se está a reproducir nada");
|
||||
date.setText("");
|
||||
status.setText("Pick an episode from the list.");
|
||||
status.setText("Escolle un episodio da lista.");
|
||||
desc.setText("");
|
||||
cover.setVisibility(View.GONE);
|
||||
setControls(false);
|
||||
@@ -181,7 +181,7 @@ public class PlayerActivity extends BaseActivity {
|
||||
title.setText(e.title);
|
||||
date.setText(Util.join(Util.date(e.published), e.duration > 0 ? Util.duration(e.duration) : ""));
|
||||
desc.setText(Util.plainText(e.desc));
|
||||
Covers.load(this, cover, e.itemId, (int) (84 * getResources().getDisplayMetrics().density));
|
||||
Covers.load(this, cover, e.itemId, getResources().getDimensionPixelSize(R.dimen.cover_player));
|
||||
}
|
||||
|
||||
PlayerService ps = PlayerService.get();
|
||||
@@ -195,8 +195,8 @@ public class PlayerActivity extends BaseActivity {
|
||||
playing = ps.isPlaying() || (ps.state() == PlayerService.LOADING);
|
||||
st = ps.status();
|
||||
long sleep = ps.sleepLeftMs();
|
||||
if (sleep == -1) st = Util.join(st, "Sleep at end");
|
||||
else if (sleep > 0) st = Util.join(st, "Sleep in " + Util.clock(sleep));
|
||||
if (sleep == -1) st = Util.join(st, "Pausa ao final do episodio");
|
||||
else if (sleep > 0) st = Util.join(st, "Pausa en " + Util.clock(sleep));
|
||||
seek.setSecondaryProgress(ps.bufferedPercent() * 10);
|
||||
setControls(true);
|
||||
} else {
|
||||
@@ -204,7 +204,7 @@ public class PlayerActivity extends BaseActivity {
|
||||
double cur = pr != null ? pr[0] : Math.max(0, App.localPosition(this, e.id));
|
||||
p = (int) (cur * 1000);
|
||||
dur = (int) (e.duration * 1000);
|
||||
st = pending != null ? "Connecting…" : (Downloads.has(this, e.id) ? "Saved on SD card" : "");
|
||||
st = pending != null ? "Conectando…" : (Downloads.has(this, e.id) ? "Gardado na tarxeta SD" : "");
|
||||
playing = pending != null;
|
||||
seek.setSecondaryProgress(0);
|
||||
setControls(true);
|
||||
@@ -234,17 +234,17 @@ public class PlayerActivity extends BaseActivity {
|
||||
if (e == null) return false;
|
||||
PlayerService ps = PlayerService.get();
|
||||
boolean active = ps != null && ps.episode() != null && ps.episode().id.equals(e.id);
|
||||
if (active) menu.add(0, 1, 0, "Sleep timer").setIcon(android.R.drawable.ic_lock_idle_alarm);
|
||||
menu.add(0, 2, 0, "Mark as played").setIcon(android.R.drawable.ic_menu_agenda);
|
||||
if (active) menu.add(0, 1, 0, "Temporizador de pausa").setIcon(android.R.drawable.ic_lock_idle_alarm);
|
||||
menu.add(0, 2, 0, "Marcar como escoitado").setIcon(android.R.drawable.ic_menu_agenda);
|
||||
if (DownloadService.busyWith(e.id)) {
|
||||
menu.add(0, 3, 0, "Cancel download").setIcon(android.R.drawable.ic_menu_close_clear_cancel);
|
||||
menu.add(0, 3, 0, "Cancelar descarga").setIcon(android.R.drawable.ic_menu_close_clear_cancel);
|
||||
} else if (Downloads.has(this, e.id)) {
|
||||
menu.add(0, 3, 0, "Delete download").setIcon(android.R.drawable.ic_menu_delete);
|
||||
menu.add(0, 3, 0, "Eliminar descarga").setIcon(android.R.drawable.ic_menu_delete);
|
||||
} else {
|
||||
menu.add(0, 3, 0, "Download").setIcon(android.R.drawable.ic_menu_save);
|
||||
menu.add(0, 3, 0, "Descargar").setIcon(android.R.drawable.ic_menu_save);
|
||||
}
|
||||
menu.add(0, 4, 0, "Go to show").setIcon(android.R.drawable.ic_menu_view);
|
||||
if (active) menu.add(0, 5, 0, "Stop").setIcon(android.R.drawable.ic_menu_close_clear_cancel);
|
||||
menu.add(0, 4, 0, "Ir ao programa").setIcon(android.R.drawable.ic_menu_view);
|
||||
if (active) menu.add(0, 5, 0, "Deter").setIcon(android.R.drawable.ic_menu_close_clear_cancel);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ public class PlayerActivity extends BaseActivity {
|
||||
case 2:
|
||||
if (active) {
|
||||
ps.finishAndStop();
|
||||
toast("Marked as played");
|
||||
toast("Marcado como escoitado");
|
||||
} else {
|
||||
episodeAction(M_PLAYED, e, null);
|
||||
}
|
||||
@@ -285,15 +285,15 @@ public class PlayerActivity extends BaseActivity {
|
||||
|
||||
private void sleepDialog() {
|
||||
final int[] minutes = {0, 5, 10, 15, 30, 45, 60, -1};
|
||||
String[] labels = {"Off", "5 minutes", "10 minutes", "15 minutes", "30 minutes", "45 minutes", "1 hour",
|
||||
"End of episode"};
|
||||
new AlertDialog.Builder(this).setTitle("Sleep timer")
|
||||
String[] labels = {"Desactivado", "5 minutos", "10 minutos", "15 minutos", "30 minutos", "45 minutos", "1 hora",
|
||||
"Ao final do episodio"};
|
||||
new AlertDialog.Builder(this).setTitle("Temporizador de pausa")
|
||||
.setItems(labels, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int which) {
|
||||
PlayerService ps = PlayerService.get();
|
||||
if (ps == null) return;
|
||||
ps.setSleep(minutes[which]);
|
||||
toast(minutes[which] == 0 ? "Sleep timer off" : "Sleep timer set");
|
||||
toast(minutes[which] == 0 ? "Temporizador desactivado" : "Temporizador activado");
|
||||
refresh();
|
||||
}
|
||||
}).show();
|
||||
|
||||
@@ -256,7 +256,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
}
|
||||
lastPosMs = positionMs();
|
||||
state = PAUSED;
|
||||
status = "Paused";
|
||||
status = "En pausa";
|
||||
pausedAt = SystemClock.elapsedRealtime();
|
||||
syncNow(false);
|
||||
wifi.release();
|
||||
@@ -345,7 +345,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
markFinished();
|
||||
releasePlayer();
|
||||
state = ENDED;
|
||||
status = "Marked as played";
|
||||
status = "Marcado como escoitado";
|
||||
lastPosMs = dur;
|
||||
pausedAt = SystemClock.elapsedRealtime();
|
||||
wifi.release();
|
||||
@@ -364,7 +364,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
private void open(final Episode e, final double startSec, boolean playNow) {
|
||||
releasePlayer();
|
||||
state = LOADING;
|
||||
status = "Connecting…";
|
||||
status = "Conectando…";
|
||||
playOnPrepared = playNow;
|
||||
showNotification(true);
|
||||
final int gen = ++generation;
|
||||
@@ -407,9 +407,9 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
JSONObject t = tracks == null ? null : tracks.optJSONObject(0);
|
||||
String cu = t == null ? "" : Util.str(t, "contentUrl");
|
||||
if (cu.length() == 0) {
|
||||
s.error = "The server sent no audio for this episode.";
|
||||
s.error = "O servidor non enviou audio para este episodio.";
|
||||
} else if (cu.contains(".m3u8")) {
|
||||
s.error = "This episode needs transcoding, which Android 2.x can't play. Try downloading it.";
|
||||
s.error = "Este episodio precisa transcodificación, que Android 2.x non pode reproducir. Proba a descargalo.";
|
||||
} else {
|
||||
s.contentUrl = cu;
|
||||
s.url = streamUrl(c, cu);
|
||||
@@ -442,7 +442,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
local = s.local;
|
||||
resumeMs = (int) (s.start * 1000);
|
||||
lastPosMs = resumeMs;
|
||||
prepare(s.url, s.offline ? "Offline · progress will sync later" : null);
|
||||
prepare(s.url, s.offline ? "Sen conexión · o progreso sincronizarase despois" : null);
|
||||
}
|
||||
|
||||
private void prepare(String url, String note) {
|
||||
@@ -467,10 +467,10 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
wifi.acquire();
|
||||
}
|
||||
state = LOADING;
|
||||
status = note != null ? note : local ? "Opening saved file…" : "Buffering…";
|
||||
status = note != null ? note : local ? "Abrindo o ficheiro gardado…" : "Cargando…";
|
||||
mp.prepareAsync();
|
||||
} catch (Exception ex) {
|
||||
fail("Can't open the audio: " + Util.describe(ex));
|
||||
fail("Non se pode abrir o audio: " + Util.describe(ex));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,7 +490,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
start();
|
||||
} else {
|
||||
state = PAUSED;
|
||||
status = "Paused";
|
||||
status = "En pausa";
|
||||
showNotification(false);
|
||||
}
|
||||
}
|
||||
@@ -506,12 +506,12 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
mp.setVolume(1f, 1f);
|
||||
mp.start();
|
||||
} catch (Exception ex) {
|
||||
fail("Playback failed: " + Util.describe(ex));
|
||||
fail("Fallou a reprodución: " + Util.describe(ex));
|
||||
return;
|
||||
}
|
||||
if (!local) wifi.acquire();
|
||||
state = PLAYING;
|
||||
status = local ? (sessionId == null ? "Saved file · offline" : "Saved file") : "Streaming";
|
||||
status = local ? (sessionId == null ? "Ficheiro gardado · sen conexión" : "Ficheiro gardado") : "Reproducindo en liña";
|
||||
playingSince = SystemClock.elapsedRealtime();
|
||||
lastTick = playingSince;
|
||||
if (lastSync == 0) lastSync = playingSince;
|
||||
@@ -555,11 +555,11 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
// Old Android's streamer "completes" when the connection drops. Treat that as an error.
|
||||
if (!local && dur > 0 && pos < dur - 30000) {
|
||||
lastPosMs = pos;
|
||||
retryStream("Connection lost, reconnecting…");
|
||||
retryStream("Perdeuse a conexión, reconectando…");
|
||||
return;
|
||||
}
|
||||
state = ENDED;
|
||||
status = "Finished";
|
||||
status = "Rematado";
|
||||
lastPosMs = dur;
|
||||
pausedAt = SystemClock.elapsedRealtime();
|
||||
wifi.release();
|
||||
@@ -573,9 +573,9 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
if (p != mp) return true;
|
||||
if (prepared) lastPosMs = positionMs();
|
||||
if (!local && contentUrl != null) {
|
||||
retryStream("Connection lost, reconnecting…");
|
||||
retryStream("Perdeuse a conexión, reconectando…");
|
||||
} else {
|
||||
fail("Playback error (" + what + "/" + extra + ").");
|
||||
fail("Erro de reprodución (" + what + "/" + extra + ").");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -583,7 +583,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
/** Re-opens the stream at the last position with a fresh token. Gives up after three tries. */
|
||||
private void retryStream(final String msg) {
|
||||
if (retries >= 3 || contentUrl == null) {
|
||||
fail("Lost connection to the server. Press play to try again.");
|
||||
fail("Perdeuse a conexión co servidor. Preme reproducir para tentalo de novo.");
|
||||
retries = 0;
|
||||
return;
|
||||
}
|
||||
@@ -675,7 +675,7 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
if (sleepAt > 0 && now >= sleepAt) {
|
||||
sleepAt = 0;
|
||||
pause();
|
||||
status = "Sleep timer: paused";
|
||||
status = "Temporizador: en pausa";
|
||||
}
|
||||
} else if (state == PAUSED || state == ENDED || state == ERROR) {
|
||||
// Free the phone's memory if left paused for a long time.
|
||||
@@ -756,11 +756,11 @@ public class PlayerService extends Service implements MediaPlayer.OnPreparedList
|
||||
PendingIntent pi = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
String what;
|
||||
switch (state) {
|
||||
case PLAYING: what = "Playing"; break;
|
||||
case LOADING: what = "Loading…"; break;
|
||||
case ENDED: what = "Finished"; break;
|
||||
case ERROR: what = "Stopped: tap to retry"; break;
|
||||
default: what = "Paused"; break;
|
||||
case PLAYING: what = "Reproducindo"; break;
|
||||
case LOADING: what = "Cargando…"; break;
|
||||
case ENDED: what = "Rematado"; break;
|
||||
case ERROR: what = "Detido: toca para tentalo de novo"; break;
|
||||
default: what = "En pausa"; break;
|
||||
}
|
||||
n.setLatestEventInfo(this, ep.title, Util.join(what, ep.show), pi);
|
||||
if (foreground) {
|
||||
|
||||
@@ -45,7 +45,7 @@ public class PodcastActivity extends BaseActivity {
|
||||
title.setText(getIntent().getStringExtra("title"));
|
||||
subtitle.setText(getIntent().getStringExtra("author"));
|
||||
Covers.load(this, (ImageView) findViewById(R.id.cover), itemId,
|
||||
(int) (44 * getResources().getDisplayMetrics().density));
|
||||
getResources().getDimensionPixelSize(R.dimen.cover_podcast));
|
||||
|
||||
Prefs p = Prefs.get(this);
|
||||
oldestFirst = p.sp.getBoolean("oldest_first", false);
|
||||
@@ -104,7 +104,7 @@ public class PodcastActivity extends BaseActivity {
|
||||
loading.setVisibility(View.GONE);
|
||||
episodes = list;
|
||||
if (showTitle.length() > 0) title.setText(showTitle);
|
||||
String n = list.size() == 1 ? "1 episode" : list.size() + " episodes";
|
||||
String n = list.size() == 1 ? "1 episodio" : list.size() + " episodios";
|
||||
subtitle.setText(Util.join(author, n));
|
||||
render();
|
||||
}
|
||||
@@ -145,10 +145,10 @@ public class PodcastActivity extends BaseActivity {
|
||||
}
|
||||
rows.add(Rows.Row.episode(e));
|
||||
}
|
||||
if (hidden > 0) rows.add(0, Rows.Row.header(hidden + " PLAYED EPISODES HIDDEN"));
|
||||
if (hidden > 0) rows.add(0, Rows.Row.header(hidden + " EPISODIOS ESCOITADOS OCULTOS"));
|
||||
adapter.set(rows);
|
||||
boolean none = rows.isEmpty();
|
||||
empty.setText(none ? "No episodes." : "");
|
||||
empty.setText(none ? "Non hai episodios." : "");
|
||||
empty.setVisibility(none ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@@ -172,10 +172,10 @@ public class PodcastActivity extends BaseActivity {
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
menu.clear();
|
||||
menu.add(0, 1, 0, oldestFirst ? "Newest first" : "Oldest first").setIcon(android.R.drawable.ic_menu_sort_by_size);
|
||||
menu.add(0, 2, 0, hidePlayed ? "Show played" : "Hide played").setIcon(android.R.drawable.ic_menu_view);
|
||||
menu.add(0, 3, 0, "Refresh").setIcon(android.R.drawable.ic_menu_rotate);
|
||||
menu.add(0, 4, 0, "Now playing").setIcon(android.R.drawable.ic_media_play);
|
||||
menu.add(0, 1, 0, oldestFirst ? "Máis recentes primeiro" : "Máis antigos primeiro").setIcon(android.R.drawable.ic_menu_sort_by_size);
|
||||
menu.add(0, 2, 0, hidePlayed ? "Amosar escoitados" : "Ocultar escoitados").setIcon(android.R.drawable.ic_menu_view);
|
||||
menu.add(0, 3, 0, "Actualizar").setIcon(android.R.drawable.ic_menu_rotate);
|
||||
menu.add(0, 4, 0, "En reprodución").setIcon(android.R.drawable.ic_media_play);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ final class Rows extends BaseAdapter {
|
||||
Rows(Activity a) {
|
||||
act = a;
|
||||
inf = LayoutInflater.from(a);
|
||||
coverPx = (int) (40 * a.getResources().getDisplayMetrics().density);
|
||||
coverPx = a.getResources().getDimensionPixelSize(R.dimen.cover_row);
|
||||
}
|
||||
|
||||
void set(List<Row> list) {
|
||||
@@ -147,7 +147,7 @@ final class Rows extends BaseAdapter {
|
||||
|
||||
private void bindShow(View v, Show s) {
|
||||
((TextView) v.findViewById(R.id.title)).setText(s.title);
|
||||
String n = s.episodes == 1 ? "1 episode" : s.episodes + " episodes";
|
||||
String n = s.episodes == 1 ? "1 episodio" : s.episodes + " episodios";
|
||||
((TextView) v.findViewById(R.id.meta)).setText(Util.join(s.author, n));
|
||||
Covers.load(act, (ImageView) v.findViewById(R.id.cover), s.id, coverPx);
|
||||
}
|
||||
@@ -175,21 +175,21 @@ final class Rows extends BaseAdapter {
|
||||
|
||||
String m = Util.date(e.published);
|
||||
if (finished) {
|
||||
m = Util.join(m, "Played");
|
||||
m = Util.join(m, "Escoitado");
|
||||
} else if (started && dur > 0) {
|
||||
m = Util.join(m, Util.duration(Math.max(0, dur - cur)) + " left");
|
||||
m = Util.join(m, "quedan " + Util.duration(Math.max(0, dur - cur)));
|
||||
} else if (dur > 0) {
|
||||
m = Util.join(m, Util.duration(dur));
|
||||
}
|
||||
if (DownloadService.busyWith(e.id)) {
|
||||
Episode now = DownloadService.current;
|
||||
m = Util.join(m, now != null && now.id.equals(e.id) ? "Saving " + DownloadService.percent + "%" : "Queued");
|
||||
m = Util.join(m, now != null && now.id.equals(e.id) ? "Gardando " + DownloadService.percent + "%" : "Na cola");
|
||||
} else if (Downloads.has(act, e.id)) {
|
||||
m = Util.join(m, "Saved");
|
||||
m = Util.join(m, "Gardado");
|
||||
}
|
||||
PlayerService ps = PlayerService.get();
|
||||
if (ps != null && ps.episode() != null && ps.episode().id.equals(e.id) && ps.isPlaying()) {
|
||||
m = Util.join("Now playing", m);
|
||||
m = Util.join("En reprodución", m);
|
||||
}
|
||||
meta.setText(m);
|
||||
|
||||
|
||||
@@ -17,14 +17,14 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
|
||||
Preference.OnPreferenceChangeListener skipSummary = new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference pref, Object value) {
|
||||
pref.setSummary(value + " seconds");
|
||||
pref.setSummary(value + " segundos");
|
||||
return true;
|
||||
}
|
||||
};
|
||||
ListPreference back = (ListPreference) findPreference("skip_back");
|
||||
ListPreference fwd = (ListPreference) findPreference("skip_fwd");
|
||||
back.setSummary(p.skipBack() + " seconds");
|
||||
fwd.setSummary(p.skipFwd() + " seconds");
|
||||
back.setSummary(p.skipBack() + " segundos");
|
||||
fwd.setSummary(p.skipFwd() + " segundos");
|
||||
back.setOnPreferenceChangeListener(skipSummary);
|
||||
fwd.setOnPreferenceChangeListener(skipSummary);
|
||||
|
||||
@@ -35,42 +35,42 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
}
|
||||
});
|
||||
|
||||
String who = p.username().length() > 0 ? p.username() : "API key";
|
||||
String who = p.username().length() > 0 ? p.username() : "Chave da API";
|
||||
findPreference("account").setSummary(who + " @ " + p.server());
|
||||
|
||||
findPreference("delete_all").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference pref) {
|
||||
new AlertDialog.Builder(SettingsActivity.this)
|
||||
.setMessage("Delete every downloaded episode from the SD card?")
|
||||
.setPositiveButton("Delete", new DialogInterface.OnClickListener() {
|
||||
.setMessage("Eliminar todos os episodios descargados da tarxeta SD?")
|
||||
.setPositiveButton("Eliminar", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
PlayerService ps = PlayerService.get();
|
||||
if (ps != null && ps.isLocal()) ps.stop();
|
||||
int n = Downloads.deleteAll(SettingsActivity.this);
|
||||
Toast.makeText(SettingsActivity.this, n + " deleted", Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(SettingsActivity.this, n + " eliminados", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", null).show();
|
||||
.setNegativeButton("Cancelar", null).show();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
final Preference pins = findPreference("pins");
|
||||
int n = Trust.pinned(this).size();
|
||||
pins.setSummary(n == 0 ? "None" : n + " (tap to forget)");
|
||||
pins.setSummary(n == 0 ? "Ningún" : n + " (toca para esquecelos)");
|
||||
pins.setEnabled(n > 0);
|
||||
pins.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference pref) {
|
||||
new AlertDialog.Builder(SettingsActivity.this)
|
||||
.setMessage("Forget the certificates you chose to trust? You'll be asked again next time.")
|
||||
.setPositiveButton("Forget", new DialogInterface.OnClickListener() {
|
||||
.setMessage("Esquecer os certificados nos que decidiches confiar? Preguntarase de novo a próxima vez.")
|
||||
.setPositiveButton("Esquecer", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
Trust.forgetPins(SettingsActivity.this);
|
||||
pins.setSummary("None");
|
||||
pins.setSummary("Ningún");
|
||||
pins.setEnabled(false);
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", null).show();
|
||||
.setNegativeButton("Cancelar", null).show();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -78,8 +78,8 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
findPreference("logout").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference pref) {
|
||||
new AlertDialog.Builder(SettingsActivity.this)
|
||||
.setMessage("Log out of " + p.server() + "? Downloads stay on the SD card.")
|
||||
.setPositiveButton("Log out", new DialogInterface.OnClickListener() {
|
||||
.setMessage("Pechar sesión en " + p.server() + "? As descargas quedan na tarxeta SD.")
|
||||
.setPositiveButton("Pechar sesión", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
PlayerService ps = PlayerService.get();
|
||||
if (ps != null) ps.stop();
|
||||
@@ -89,7 +89,7 @@ public class SettingsActivity extends PreferenceActivity {
|
||||
finish(); // Home sees the logout on resume and opens the login screen
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", null).show();
|
||||
.setNegativeButton("Cancelar", null).show();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.security.MessageDigest;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -67,7 +66,7 @@ final class Trust {
|
||||
ctx.init(null, new TrustManager[]{new Manager(c.getApplicationContext())}, null);
|
||||
factory = ctx.getSocketFactory();
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Secure connections aren't available: " + e.getMessage());
|
||||
throw new IOException("As conexións seguras non están dispoñibles: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return factory;
|
||||
@@ -326,15 +325,15 @@ final class Trust {
|
||||
X509Certificate leaf = r.chain[0];
|
||||
X509Certificate top = top(r.chain);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("This phone doesn't recognise the certificate of ").append(host).append(".\n\n");
|
||||
sb.append("Issued to: ").append(commonName(leaf.getSubjectX500Principal())).append('\n');
|
||||
sb.append("Issued by: ").append(commonName(leaf.getIssuerX500Principal())).append('\n');
|
||||
sb.append("Valid until: ").append(DateFormat.getDateInstance().format(leaf.getNotAfter())).append("\n\n");
|
||||
sb.append("SHA-256 fingerprint:\n").append(fingerprint(leaf)).append("\n\n");
|
||||
sb.append("Only continue if this matches the fingerprint your computer's browser shows for the site.");
|
||||
sb.append("Este teléfono non recoñece o certificado de ").append(host).append(".\n\n");
|
||||
sb.append("Emitido para: ").append(commonName(leaf.getSubjectX500Principal())).append('\n');
|
||||
sb.append("Emitido por: ").append(commonName(leaf.getIssuerX500Principal())).append('\n');
|
||||
sb.append("Válido ata: ").append(Util.date(leaf.getNotAfter().getTime(), true)).append("\n\n");
|
||||
sb.append("Pegada dixital SHA-256:\n").append(fingerprint(leaf)).append("\n\n");
|
||||
sb.append("Continúa só se coincide coa pegada que amosa o navegador do teu ordenador para este sitio.");
|
||||
if (top != leaf) {
|
||||
sb.append("\n\nMiniABS will then trust certificates issued by \"")
|
||||
.append(commonName(top.getSubjectX500Principal())).append("\".");
|
||||
sb.append("\n\nMiniABS confiará desde agora nos certificados emitidos por «")
|
||||
.append(commonName(top.getSubjectX500Principal())).append("».");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -24,19 +24,19 @@ final class TrustPrompt {
|
||||
try {
|
||||
host = new URL(Prefs.get(a).server()).getHost();
|
||||
} catch (Exception e) {
|
||||
host = "the server";
|
||||
host = "o servidor";
|
||||
}
|
||||
showing = true;
|
||||
new AlertDialog.Builder(a)
|
||||
.setTitle("Check this certificate")
|
||||
.setTitle("Comproba este certificado")
|
||||
.setMessage(Trust.describe(r, host))
|
||||
.setPositiveButton("Trust", new DialogInterface.OnClickListener() {
|
||||
.setPositiveButton("Confiar", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface d, int w) {
|
||||
Trust.pin(a, r.chain);
|
||||
if (retry != null) retry.run();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", null)
|
||||
.setNegativeButton("Cancelar", null)
|
||||
.setOnCancelListener(null)
|
||||
.show()
|
||||
.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||
|
||||
+28
-19
@@ -5,10 +5,7 @@ import java.net.ConnectException;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
@@ -47,12 +44,24 @@ final class Util {
|
||||
return m + ":" + ss;
|
||||
}
|
||||
|
||||
/** Galician month abbreviations (Android 2.3 has no Galician date data of its own). */
|
||||
private static final String[] MONTHS = {"xan", "feb", "mar", "abr", "mai", "xuñ", "xul", "ago", "set", "out",
|
||||
"nov", "dec"};
|
||||
|
||||
/** "14 out", or "14 out 2025" for other years. */
|
||||
static String date(long ms) {
|
||||
if (ms <= 0) return "";
|
||||
Calendar now = Calendar.getInstance(), c = Calendar.getInstance();
|
||||
c.setTimeInMillis(ms);
|
||||
String pattern = c.get(Calendar.YEAR) == now.get(Calendar.YEAR) ? "d MMM" : "d MMM yyyy";
|
||||
return new SimpleDateFormat(pattern, Locale.getDefault()).format(new Date(ms));
|
||||
return date(ms, c.get(Calendar.YEAR) != now.get(Calendar.YEAR));
|
||||
}
|
||||
|
||||
static String date(long ms, boolean withYear) {
|
||||
if (ms <= 0) return "";
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.setTimeInMillis(ms);
|
||||
String d = c.get(Calendar.DAY_OF_MONTH) + " " + MONTHS[c.get(Calendar.MONTH)];
|
||||
return withYear ? d + " " + c.get(Calendar.YEAR) : d;
|
||||
}
|
||||
|
||||
static String plainText(String html) {
|
||||
@@ -87,28 +96,28 @@ final class Util {
|
||||
if (e instanceof Api.AuthException) return e.getMessage();
|
||||
if (e instanceof Api.ApiException) {
|
||||
Api.ApiException a = (Api.ApiException) e;
|
||||
if (a.code == 401) return "Not signed in. Please log in again.";
|
||||
if (a.code == 403) return "Your account isn't allowed to do that.";
|
||||
if (a.code == 404) return "Not found on the server.";
|
||||
if (a.code == 401) return "Non iniciaches sesión. Inicia sesión de novo.";
|
||||
if (a.code == 403) return "A túa conta non ten permiso para facer iso.";
|
||||
if (a.code == 404) return "Non se atopou no servidor.";
|
||||
String m = a.getMessage();
|
||||
return "Server error " + a.code + (m != null && m.length() > 0 ? ": " + m : "");
|
||||
return "Erro do servidor " + a.code + (m != null && m.length() > 0 ? ": " + m : "");
|
||||
}
|
||||
if (e instanceof UnknownHostException) return "Server not found. Check the address and your connection.";
|
||||
if (e instanceof SocketTimeoutException) return "The server took too long to answer.";
|
||||
if (e instanceof ConnectException) return "Can't connect to the server. Is it running and reachable?";
|
||||
if (e instanceof UnknownHostException) return "Non se atopou o servidor. Comproba o enderezo e a conexión.";
|
||||
if (e instanceof SocketTimeoutException) return "O servidor tardou demasiado en responder.";
|
||||
if (e instanceof ConnectException) return "Non se pode conectar co servidor. Está en marcha e accesible?";
|
||||
if (e instanceof SSLException) {
|
||||
Trust.Rejected r = Trust.recentRejection();
|
||||
if (r != null) {
|
||||
return r.expired() ? "The server's certificate has expired, or the phone's date is wrong."
|
||||
: "This phone doesn't recognise the server's certificate.";
|
||||
return r.expired() ? "O certificado do servidor caducou ou a data do teléfono é incorrecta."
|
||||
: "Este teléfono non recoñece o certificado do servidor.";
|
||||
}
|
||||
return "Couldn't set up a secure connection. Android 2.3 only speaks TLS 1.0, so the server needs "
|
||||
+ "a legacy HTTPS port (see README). On your home network, the http:// address works too.";
|
||||
return "Non se puido establecer unha conexión segura. Android 2.3 só admite TLS 1.0, así que o servidor "
|
||||
+ "precisa un porto HTTPS antigo (ve o README). Na rede da casa tamén funciona o enderezo http://.";
|
||||
}
|
||||
if (e instanceof SocketException) return "Connection dropped. Check your network.";
|
||||
if (e instanceof FileNotFoundException) return "Not found on the server.";
|
||||
if (e instanceof SocketException) return "Cortouse a conexión. Comproba a rede.";
|
||||
if (e instanceof FileNotFoundException) return "Non se atopou no servidor.";
|
||||
String m = e.getMessage();
|
||||
if (m != null && m.contains("not verified")) return "The server's certificate is for a different address.";
|
||||
if (m != null && m.contains("not verified")) return "O certificado do servidor é doutro enderezo.";
|
||||
return m == null || m.length() == 0 ? e.getClass().getSimpleName() : m;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user