Import miniabs 1.0 source
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
4fe9eed870
78 files changed
+5800
No files matched your search
@@ -0,0 +1,237 @@
|
||||
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;
|
||||
}
|
||||
toast(Util.describe(e));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ 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 ? Util.clock(dur - pos) + " left" : "");
|
||||
} 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");
|
||||
}
|
||||
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, "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");
|
||||
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");
|
||||
}
|
||||
|
||||
/** 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("You're not on Wi-Fi. Download over mobile data this time?")
|
||||
.setPositiveButton("Download", 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).");
|
||||
if (onChanged != null) onChanged.run();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", null)
|
||||
.show();
|
||||
} else {
|
||||
DownloadService.enqueue(this, e);
|
||||
toast("Downloading…");
|
||||
}
|
||||
if (onChanged != null) onChanged.run();
|
||||
return true;
|
||||
case M_CANCEL:
|
||||
DownloadService.cancel(e.id);
|
||||
toast("Download cancelled");
|
||||
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("Download deleted");
|
||||
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 ? "Marked as played" : "Marked as unplayed");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user