- 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>
249 lines
9.8 KiB
Java
249 lines
9.8 KiB
Java
package com.miniabs;
|
|
|
|
import android.app.Activity;
|
|
import android.app.AlertDialog;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.os.Handler;
|
|
import android.view.ContextMenu;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.widget.ImageButton;
|
|
import android.widget.ProgressBar;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
/** Shared bits: the mini player bar, a 1-second UI tick, sign-in errors and episode actions. */
|
|
abstract class BaseActivity extends Activity {
|
|
protected final Handler handler = new Handler();
|
|
private boolean ticking;
|
|
|
|
private View mini;
|
|
private TextView miniTitle, miniSub;
|
|
private ImageButton miniPlay;
|
|
private ProgressBar miniProgress;
|
|
|
|
private final Runnable ticker = new Runnable() {
|
|
public void run() {
|
|
onTick();
|
|
if (ticking) handler.postDelayed(this, 1000);
|
|
}
|
|
};
|
|
|
|
@Override
|
|
protected void onResume() {
|
|
super.onResume();
|
|
ticking = true;
|
|
handler.post(ticker);
|
|
}
|
|
|
|
@Override
|
|
protected void onPause() {
|
|
ticking = false;
|
|
handler.removeCallbacks(ticker);
|
|
super.onPause();
|
|
}
|
|
|
|
protected void onTick() {
|
|
updateMiniBar();
|
|
}
|
|
|
|
protected void toast(String s) {
|
|
Toast.makeText(this, s, s.length() > 60 ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show();
|
|
}
|
|
|
|
/** Shows an error; sends the user to the login screen if their sign-in expired. */
|
|
protected void showError(Exception e) {
|
|
if (e instanceof Api.AuthException) {
|
|
Prefs.get(this).logout();
|
|
App.clear();
|
|
HomeActivity.clearCache();
|
|
startActivity(new Intent(this, LoginActivity.class)
|
|
.putExtra("message", e.getMessage())
|
|
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
|
|
finish();
|
|
return;
|
|
}
|
|
if (offerTrust(null)) return;
|
|
toast(Util.describe(e));
|
|
}
|
|
|
|
/** If a request just failed on an unrecognised certificate, ask about it. retry runs after trusting. */
|
|
protected boolean offerTrust(final Runnable retry) {
|
|
return TrustPrompt.offer(this, new Runnable() {
|
|
public void run() {
|
|
if (retry != null) retry.run();
|
|
else toast("Certificado aceptado. Téntao de novo.");
|
|
}
|
|
});
|
|
}
|
|
|
|
// ------------------------------------------------------------ mini player
|
|
|
|
protected void setupMiniBar() {
|
|
mini = findViewById(R.id.mini_bar);
|
|
if (mini == null) return;
|
|
miniTitle = (TextView) findViewById(R.id.mini_title);
|
|
miniSub = (TextView) findViewById(R.id.mini_sub);
|
|
miniPlay = (ImageButton) findViewById(R.id.mini_play);
|
|
miniProgress = (ProgressBar) findViewById(R.id.mini_progress);
|
|
findViewById(R.id.mini_info).setOnClickListener(new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
startActivity(new Intent(BaseActivity.this, PlayerActivity.class));
|
|
}
|
|
});
|
|
miniPlay.setOnClickListener(new View.OnClickListener() {
|
|
public void onClick(View v) {
|
|
PlayerService ps = PlayerService.get();
|
|
if (ps != null && ps.episode() != null) {
|
|
PlayerService.send(BaseActivity.this, PlayerService.A_TOGGLE);
|
|
} else {
|
|
Episode last = Prefs.get(BaseActivity.this).lastEpisode();
|
|
if (last != null) PlayerService.play(BaseActivity.this, last);
|
|
}
|
|
handler.postDelayed(new Runnable() {
|
|
public void run() {
|
|
updateMiniBar();
|
|
}
|
|
}, 300);
|
|
}
|
|
});
|
|
}
|
|
|
|
protected void updateMiniBar() {
|
|
if (mini == null) return;
|
|
PlayerService ps = PlayerService.get();
|
|
Episode e = ps != null ? ps.episode() : null;
|
|
if (e == null) e = Prefs.get(this).lastEpisode();
|
|
if (e == null) {
|
|
mini.setVisibility(View.GONE);
|
|
return;
|
|
}
|
|
mini.setVisibility(View.VISIBLE);
|
|
miniTitle.setText(e.title);
|
|
boolean playing = ps != null && ps.episode() != null && ps.isPlaying();
|
|
miniPlay.setImageResource(playing ? R.drawable.ic_pause_small : R.drawable.ic_play_small);
|
|
int pos, dur;
|
|
String sub;
|
|
if (ps != null && ps.episode() != null) {
|
|
pos = ps.positionMs();
|
|
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 ? "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, "En pausa");
|
|
}
|
|
miniSub.setText(sub);
|
|
miniProgress.setProgress(dur > 0 ? (int) (1000L * pos / dur) : 0);
|
|
}
|
|
|
|
// ------------------------------------------------------------ episode actions
|
|
|
|
protected static final int M_PLAY = 1, M_DOWNLOAD = 2, M_DELETE = 3, M_PLAYED = 4, M_UNPLAYED = 5,
|
|
M_SHOW = 6, M_CANCEL = 7;
|
|
|
|
protected void episodeMenu(ContextMenu menu, Episode e, boolean offerShow) {
|
|
menu.setHeaderTitle(e.title);
|
|
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, "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. */
|
|
protected boolean episodeAction(int id, final Episode e, final Runnable onChanged) {
|
|
switch (id) {
|
|
case M_PLAY:
|
|
playEpisode(e);
|
|
return true;
|
|
case M_DOWNLOAD:
|
|
if (Prefs.get(this).wifiOnly() && !Util.onWifi(this)) {
|
|
new AlertDialog.Builder(this)
|
|
.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("Descargando. Desactivouse «só con wifi» (podes cambialo en Axustes).");
|
|
if (onChanged != null) onChanged.run();
|
|
}
|
|
})
|
|
.setNegativeButton("Cancelar", null)
|
|
.show();
|
|
} else {
|
|
DownloadService.enqueue(this, e);
|
|
toast("Descargando…");
|
|
}
|
|
if (onChanged != null) onChanged.run();
|
|
return true;
|
|
case M_CANCEL:
|
|
DownloadService.cancel(e.id);
|
|
toast("Descarga cancelada");
|
|
if (onChanged != null) onChanged.run();
|
|
return true;
|
|
case M_DELETE:
|
|
PlayerService ps = PlayerService.get();
|
|
if (ps != null && ps.episode() != null && ps.episode().id.equals(e.id) && ps.isLocal()) {
|
|
ps.stop();
|
|
}
|
|
Downloads.delete(this, e);
|
|
toast("Descarga eliminada");
|
|
if (onChanged != null) onChanged.run();
|
|
return true;
|
|
case M_PLAYED:
|
|
case M_UNPLAYED:
|
|
setPlayed(e, id == M_PLAYED, onChanged);
|
|
return true;
|
|
case M_SHOW:
|
|
startActivity(new Intent(this, PodcastActivity.class)
|
|
.putExtra("itemId", e.itemId).putExtra("title", e.show));
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected void playEpisode(Episode e) {
|
|
PlayerService.play(this, e);
|
|
startActivity(new Intent(this, PlayerActivity.class).putExtra("ep", e.toJson().toString()));
|
|
}
|
|
|
|
private void setPlayed(final Episode e, final boolean played, final Runnable onChanged) {
|
|
final double dur = e.duration;
|
|
final double[] before = App.progress(e.id);
|
|
App.setProgress(e.id, played ? dur : 0, dur, played);
|
|
if (onChanged != null) onChanged.run();
|
|
new Job<Void>() {
|
|
Void work() throws Exception {
|
|
Api.setProgress(BaseActivity.this, e.itemId, e.id, played ? dur : 0, dur, Boolean.valueOf(played));
|
|
App.forgetLocal(BaseActivity.this, e.id);
|
|
return null;
|
|
}
|
|
|
|
void done(Void r) {
|
|
toast(played ? "Marcado como escoitado" : "Marcado como non escoitado");
|
|
}
|
|
|
|
void failed(Exception ex) {
|
|
if (before != null) App.setProgress(e.id, before[0], before[1], before[2] > 0);
|
|
else App.setProgress(e.id, 0, dur, false);
|
|
if (onChanged != null) onChanged.run();
|
|
showError(ex);
|
|
}
|
|
}.start();
|
|
}
|
|
|
|
protected boolean onEpisodeContextItem(MenuItem item, Episode e, Runnable onChanged) {
|
|
return e != null && episodeAction(item.getItemId(), e, onChanged);
|
|
}
|
|
}
|