Import miniabs 1.0 source

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xockoandClaude Opus 5.5 committed 2026-10-14 19:26:04 +02:00
commit 4fe9eed870
78 files changed
+5800

No files matched your search

+237
View File
@@ -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);
}
}