main commit
Post-processing infrastructure * remove interfaces with one implementation * fix download resources with unknow length * marquee style for ProgressDrawable * "view details" option in mission context menu * notification for finished downloads * postprocessing infrastructure: sub-missions, circular file, layers for layers of abstractions for Java IO streams * Mp4 muxing (only DASH brand) * WebM muxing * Captions downloading * alert dialog for overwrite existing downloads finished or not. Misc changes * delete SQLiteDownloadDataSource.java * delete DownloadMissionSQLiteHelper.java * implement Localization from #114 Misc fixes (this branch) * restore old mission listeners variables. Prevents registered listeners get de-referenced on low-end devices * DownloadManagerService.checkForRunningMission() now return false if the mission has a error. * use Intent.FLAG_ACTIVITY_NEW_TASK when launching an activity from gigaget threads (apparently it is required in old versions of android) More changes * proper error handling "infrastructure" * queue instead of multiple downloads * move serialized pending downloads (.giga files) to app data * stop downloads when swicthing to mobile network (never works, see 2nd point) * save the thread count for next downloads * a lot of incoherences fixed * delete DownloadManagerTest.java (too many changes to keep this file updated)
This commit is contained in:
parent
2dc2a56671
commit
21e205a6be
48 changed files with 4379 additions and 1119 deletions
|
|
@ -1,5 +1,6 @@
|
|||
package us.shandian.giga.ui.adapter;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
|
|
@ -7,12 +8,20 @@ import android.content.Intent;
|
|||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.FileProvider;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.util.DiffUtil;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.RecyclerView.ViewHolder;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
|
@ -24,28 +33,28 @@ import android.widget.PopupMenu;
|
|||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.download.DeleteDownloadManager;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import us.shandian.giga.get.DownloadManager;
|
||||
import us.shandian.giga.get.DownloadMission;
|
||||
import us.shandian.giga.get.FinishedMission;
|
||||
import us.shandian.giga.service.DownloadManager;
|
||||
import us.shandian.giga.service.DownloadManagerService;
|
||||
import us.shandian.giga.ui.common.Deleter;
|
||||
import us.shandian.giga.ui.common.ProgressDrawable;
|
||||
import us.shandian.giga.util.Utility;
|
||||
|
||||
import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
|
||||
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
||||
|
||||
public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHolder> {
|
||||
private static final Map<Integer, String> ALGORITHMS = new HashMap<>();
|
||||
public class MissionAdapter extends RecyclerView.Adapter<ViewHolder> {
|
||||
private static final SparseArray<String> ALGORITHMS = new SparseArray<>();
|
||||
private static final String TAG = "MissionAdapter";
|
||||
|
||||
static {
|
||||
|
|
@ -53,109 +62,131 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
|||
ALGORITHMS.put(R.id.sha1, "SHA1");
|
||||
}
|
||||
|
||||
private Activity mContext;
|
||||
private Context mContext;
|
||||
private LayoutInflater mInflater;
|
||||
private DownloadManager mDownloadManager;
|
||||
private DeleteDownloadManager mDeleteDownloadManager;
|
||||
private List<DownloadMission> mItemList;
|
||||
private DownloadManagerService.DMBinder mBinder;
|
||||
private Deleter mDeleter;
|
||||
private int mLayout;
|
||||
private DownloadManager.MissionIterator mIterator;
|
||||
private Handler mHandler;
|
||||
private ArrayList<ViewHolderItem> mPendingDownloadsItems = new ArrayList<>();
|
||||
private MenuItem mClear;
|
||||
private View mEmptyMessage;
|
||||
|
||||
public MissionAdapter(Activity context, DownloadManagerService.DMBinder binder, DownloadManager downloadManager, DeleteDownloadManager deleteDownloadManager, boolean isLinear) {
|
||||
public MissionAdapter(Context context, DownloadManager downloadManager, MenuItem clearButton, View emptyMessage) {
|
||||
mContext = context;
|
||||
mDownloadManager = downloadManager;
|
||||
mDeleteDownloadManager = deleteDownloadManager;
|
||||
mBinder = binder;
|
||||
mDeleter = null;
|
||||
|
||||
mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mLayout = isLinear ? R.layout.mission_item_linear : R.layout.mission_item;
|
||||
mLayout = R.layout.mission_item;
|
||||
|
||||
mItemList = new ArrayList<>();
|
||||
updateItemList();
|
||||
mHandler = new Handler(Looper.myLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case DownloadManagerService.MESSAGE_PROGRESS:
|
||||
case DownloadManagerService.MESSAGE_ERROR:
|
||||
case DownloadManagerService.MESSAGE_FINISHED:
|
||||
onServiceMessage(msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mClear = clearButton;
|
||||
mEmptyMessage = emptyMessage;
|
||||
|
||||
mIterator = downloadManager.getIterator();
|
||||
|
||||
checkEmptyMessageVisibility();
|
||||
}
|
||||
|
||||
public void updateItemList() {
|
||||
mItemList.clear();
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
switch (viewType) {
|
||||
case DownloadManager.SPECIAL_PENDING:
|
||||
case DownloadManager.SPECIAL_FINISHED:
|
||||
return new ViewHolderHeader(mInflater.inflate(R.layout.missions_header, parent, false));
|
||||
}
|
||||
|
||||
for (int i = 0; i < mDownloadManager.getCount(); i++) {
|
||||
DownloadMission mission = mDownloadManager.getMission(i);
|
||||
if (!mDeleteDownloadManager.contains(mission)) {
|
||||
mItemList.add(mDownloadManager.getMission(i));
|
||||
return new ViewHolderItem(mInflater.inflate(mLayout, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRecycled(@NonNull ViewHolder view) {
|
||||
super.onViewRecycled(view);
|
||||
|
||||
if (view instanceof ViewHolderHeader) return;
|
||||
ViewHolderItem h = (ViewHolderItem) view;
|
||||
|
||||
if (h.item.mission instanceof DownloadMission) mPendingDownloadsItems.remove(h);
|
||||
|
||||
h.popupMenu.dismiss();
|
||||
h.item = null;
|
||||
h.lastTimeStamp = -1;
|
||||
h.lastDone = -1;
|
||||
h.lastCurrent = -1;
|
||||
h.state = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("SetTextI18n")
|
||||
public void onBindViewHolder(@NonNull ViewHolder view, @SuppressLint("RecyclerView") int pos) {
|
||||
DownloadManager.MissionItem item = mIterator.getItem(pos);
|
||||
|
||||
if (view instanceof ViewHolderHeader) {
|
||||
if (item.special == DownloadManager.SPECIAL_NOTHING) return;
|
||||
int str;
|
||||
if (item.special == DownloadManager.SPECIAL_PENDING) {
|
||||
str = R.string.missions_header_pending;
|
||||
} else {
|
||||
str = R.string.missions_header_finished;
|
||||
mClear.setVisible(true);
|
||||
}
|
||||
|
||||
((ViewHolderHeader) view).header.setText(str);
|
||||
return;
|
||||
}
|
||||
|
||||
ViewHolderItem h = (ViewHolderItem) view;
|
||||
h.item = item;
|
||||
|
||||
Utility.FileType type = Utility.getFileType(item.mission.kind, item.mission.name);
|
||||
|
||||
h.icon.setImageResource(Utility.getIconForFileType(type));
|
||||
h.name.setText(item.mission.name);
|
||||
h.size.setText(Utility.formatBytes(item.mission.length));
|
||||
|
||||
h.progress.setColors(Utility.getBackgroundForFileType(mContext, type), Utility.getForegroundForFileType(mContext, type));
|
||||
|
||||
if (h.item.mission instanceof DownloadMission) {
|
||||
DownloadMission mission = (DownloadMission) item.mission;
|
||||
h.progress.setMarquee(mission.done < 1);
|
||||
updateProgress(h);
|
||||
h.pause.setTitle(mission.unknownLength ? R.string.stop : R.string.pause);
|
||||
mPendingDownloadsItems.add(h);
|
||||
} else {
|
||||
h.progress.setMarquee(false);
|
||||
h.status.setText("100%");
|
||||
h.progress.setProgress(1f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MissionAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
final ViewHolder h = new ViewHolder(mInflater.inflate(mLayout, parent, false));
|
||||
|
||||
h.menu.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
buildPopup(h);
|
||||
}
|
||||
});
|
||||
|
||||
/*h.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
showDetail(h);
|
||||
}
|
||||
});*/
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewRecycled(MissionAdapter.ViewHolder h) {
|
||||
super.onViewRecycled(h);
|
||||
h.mission.removeListener(h.observer);
|
||||
h.mission = null;
|
||||
h.observer = null;
|
||||
h.progress = null;
|
||||
h.position = -1;
|
||||
h.lastTimeStamp = -1;
|
||||
h.lastDone = -1;
|
||||
h.colorId = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(MissionAdapter.ViewHolder h, int pos) {
|
||||
DownloadMission ms = mItemList.get(pos);
|
||||
h.mission = ms;
|
||||
h.position = pos;
|
||||
|
||||
Utility.FileType type = Utility.getFileType(ms.name);
|
||||
|
||||
h.icon.setImageResource(Utility.getIconForFileType(type));
|
||||
h.name.setText(ms.name);
|
||||
h.size.setText(Utility.formatBytes(ms.length));
|
||||
|
||||
h.progress = new ProgressDrawable(mContext, Utility.getBackgroundForFileType(type), Utility.getForegroundForFileType(type));
|
||||
ViewCompat.setBackground(h.bkg, h.progress);
|
||||
|
||||
h.observer = new MissionObserver(this, h);
|
||||
ms.addListener(h.observer);
|
||||
|
||||
updateProgress(h);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mItemList.size();
|
||||
return mIterator.getOldListSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
public int getItemViewType(int position) {
|
||||
return mIterator.getSpecialAtItem(position);
|
||||
}
|
||||
|
||||
private void updateProgress(ViewHolder h) {
|
||||
updateProgress(h, false);
|
||||
}
|
||||
private void updateProgress(ViewHolderItem h) {
|
||||
if (h == null || h.item == null || h.item.mission instanceof FinishedMission) return;
|
||||
|
||||
private void updateProgress(ViewHolder h, boolean finished) {
|
||||
if (h.mission == null) return;
|
||||
DownloadMission mission = (DownloadMission) h.item.mission;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
|
|
@ -164,130 +195,110 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
|||
}
|
||||
|
||||
if (h.lastDone == -1) {
|
||||
h.lastDone = h.mission.done;
|
||||
h.lastDone = mission.done;
|
||||
}
|
||||
if (h.lastCurrent != mission.current) {
|
||||
h.lastCurrent = mission.current;
|
||||
h.lastDone = 0;
|
||||
h.lastTimeStamp = now;
|
||||
}
|
||||
|
||||
long deltaTime = now - h.lastTimeStamp;
|
||||
long deltaDone = h.mission.done - h.lastDone;
|
||||
long deltaDone = mission.done - h.lastDone;
|
||||
boolean hasError = mission.errCode != DownloadMission.ERROR_NOTHING;
|
||||
|
||||
if (deltaTime == 0 || deltaTime > 1000 || finished) {
|
||||
if (h.mission.errCode > 0) {
|
||||
h.status.setText(R.string.msg_error);
|
||||
if (hasError || deltaTime == 0 || deltaTime > 1000) {
|
||||
// on error hide marquee or show if condition (mission.done < 1 || mission.unknownLength) is true
|
||||
h.progress.setMarquee(!hasError && (mission.done < 1 || mission.unknownLength));
|
||||
|
||||
float progress;
|
||||
if (mission.unknownLength) {
|
||||
progress = Float.NaN;
|
||||
h.progress.setProgress(0f);
|
||||
} else {
|
||||
float progress = (float) h.mission.done / h.mission.length;
|
||||
h.status.setText(String.format(Locale.US, "%.2f%%", progress * 100));
|
||||
progress = (float) mission.done / mission.length;
|
||||
if (mission.urls.length > 1 && mission.current < mission.urls.length) {
|
||||
progress = (progress / mission.urls.length) + ((float) mission.current / mission.urls.length);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
if (Float.isNaN(progress) || Float.isInfinite(progress)) h.progress.setProgress(1f);
|
||||
h.status.setText(R.string.msg_error);
|
||||
} else if (Float.isNaN(progress) || Float.isInfinite(progress)) {
|
||||
h.status.setText("--.-%");
|
||||
} else {
|
||||
h.status.setText(String.format("%.2f%%", progress * 100));
|
||||
h.progress.setProgress(progress);
|
||||
}
|
||||
}
|
||||
|
||||
long length = mission.offsets[mission.current < mission.offsets.length ? mission.current : (mission.offsets.length - 1)];
|
||||
length += mission.length;
|
||||
|
||||
int state = 0;
|
||||
if (!mission.isFinished()) {
|
||||
if (!mission.running) {
|
||||
state = mission.enqueued ? 1 : 2;
|
||||
} else if (mission.postprocessingRunning) {
|
||||
state = 3;
|
||||
}
|
||||
}
|
||||
|
||||
if (state != 0) {
|
||||
if (h.state != state) {
|
||||
String statusStr;
|
||||
h.state = state;
|
||||
|
||||
switch (state) {
|
||||
case 1:
|
||||
statusStr = mContext.getString(R.string.queued);
|
||||
break;
|
||||
case 2:
|
||||
statusStr = mContext.getString(R.string.paused);
|
||||
break;
|
||||
case 3:
|
||||
statusStr = mContext.getString(R.string.post_processing);
|
||||
break;
|
||||
default:
|
||||
statusStr = "?";
|
||||
break;
|
||||
}
|
||||
|
||||
h.size.setText(Utility.formatBytes(length).concat(" (").concat(statusStr).concat(")"));
|
||||
} else if (deltaTime > 1000 && deltaDone > 0) {
|
||||
h.lastTimeStamp = now;
|
||||
h.lastDone = mission.done;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (deltaTime > 1000 && deltaDone > 0) {
|
||||
float speed = (float) deltaDone / deltaTime;
|
||||
String speedStr = Utility.formatSpeed(speed * 1000);
|
||||
String sizeStr = Utility.formatBytes(h.mission.length);
|
||||
String sizeStr = Utility.formatBytes(length);
|
||||
|
||||
h.size.setText(sizeStr + " " + speedStr);
|
||||
h.size.setText(sizeStr.concat(" ").concat(speedStr));
|
||||
|
||||
h.lastTimeStamp = now;
|
||||
h.lastDone = h.mission.done;
|
||||
h.lastDone = mission.done;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean viewWithFileProvider(@NonNull File file) {
|
||||
if (!file.exists()) return true;
|
||||
|
||||
private void buildPopup(final ViewHolder h) {
|
||||
PopupMenu popup = new PopupMenu(mContext, h.menu);
|
||||
popup.inflate(R.menu.mission);
|
||||
String ext = Utility.getFileExt(file.getName());
|
||||
if (ext == null) return false;
|
||||
|
||||
Menu menu = popup.getMenu();
|
||||
MenuItem start = menu.findItem(R.id.start);
|
||||
MenuItem pause = menu.findItem(R.id.pause);
|
||||
MenuItem view = menu.findItem(R.id.view);
|
||||
MenuItem delete = menu.findItem(R.id.delete);
|
||||
MenuItem checksum = menu.findItem(R.id.checksum);
|
||||
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.substring(1));
|
||||
Log.v(TAG, "Mime: " + mimeType + " package: " + BuildConfig.APPLICATION_ID + ".provider");
|
||||
|
||||
// Set to false first
|
||||
start.setVisible(false);
|
||||
pause.setVisible(false);
|
||||
view.setVisible(false);
|
||||
delete.setVisible(false);
|
||||
checksum.setVisible(false);
|
||||
|
||||
if (!h.mission.finished) {
|
||||
if (!h.mission.running) {
|
||||
if (h.mission.errCode == -1) {
|
||||
start.setVisible(true);
|
||||
}
|
||||
|
||||
delete.setVisible(true);
|
||||
} else {
|
||||
pause.setVisible(true);
|
||||
}
|
||||
} else {
|
||||
view.setVisible(true);
|
||||
delete.setVisible(true);
|
||||
checksum.setVisible(true);
|
||||
}
|
||||
|
||||
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
switch (id) {
|
||||
case R.id.start:
|
||||
mDownloadManager.resumeMission(h.position);
|
||||
mBinder.onMissionAdded(mItemList.get(h.position));
|
||||
return true;
|
||||
case R.id.pause:
|
||||
mDownloadManager.pauseMission(h.position);
|
||||
mBinder.onMissionRemoved(mItemList.get(h.position));
|
||||
h.lastTimeStamp = -1;
|
||||
h.lastDone = -1;
|
||||
return true;
|
||||
case R.id.view:
|
||||
File f = new File(h.mission.location, h.mission.name);
|
||||
String ext = Utility.getFileExt(h.mission.name);
|
||||
|
||||
Log.d(TAG, "Viewing file: " + f.getAbsolutePath() + " ext: " + ext);
|
||||
|
||||
if (ext == null) {
|
||||
Log.w(TAG, "Can't view file because it has no extension: " +
|
||||
h.mission.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.substring(1));
|
||||
Log.v(TAG, "Mime: " + mime + " package: " + mContext.getApplicationContext().getPackageName() + ".provider");
|
||||
if (f.exists()) {
|
||||
viewFileWithFileProvider(f, mime);
|
||||
} else {
|
||||
Log.w(TAG, "File doesn't exist");
|
||||
}
|
||||
|
||||
return true;
|
||||
case R.id.delete:
|
||||
mDeleteDownloadManager.add(h.mission);
|
||||
updateItemList();
|
||||
notifyDataSetChanged();
|
||||
return true;
|
||||
case R.id.md5:
|
||||
case R.id.sha1:
|
||||
DownloadMission mission = mItemList.get(h.position);
|
||||
new ChecksumTask(mContext).execute(mission.location + "/" + mission.name, ALGORITHMS.get(id));
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
popup.show();
|
||||
}
|
||||
|
||||
private void viewFileWithFileProvider(File file, String mimetype) {
|
||||
String ourPackage = mContext.getApplicationContext().getPackageName();
|
||||
Uri uri = FileProvider.getUriForFile(mContext, ourPackage + ".provider", file);
|
||||
Uri uri = FileProvider.getUriForFile(mContext, BuildConfig.APPLICATION_ID + ".provider", file);
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(uri, mimetype);
|
||||
intent.setDataAndType(uri, mimeType);
|
||||
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
intent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION);
|
||||
|
|
@ -300,75 +311,338 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
|||
Toast noPlayerToast = Toast.makeText(mContext, R.string.toast_no_player, Toast.LENGTH_LONG);
|
||||
noPlayerToast.show();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public DownloadMission mission;
|
||||
public int position;
|
||||
public Handler getMessenger() {
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
public final TextView status;
|
||||
public final ImageView icon;
|
||||
public final TextView name;
|
||||
public final TextView size;
|
||||
public final View bkg;
|
||||
public final ImageView menu;
|
||||
public ProgressDrawable progress;
|
||||
public MissionObserver observer;
|
||||
private void onServiceMessage(@NonNull Message msg) {
|
||||
switch (msg.what) {
|
||||
case DownloadManagerService.MESSAGE_PROGRESS:
|
||||
case DownloadManagerService.MESSAGE_ERROR:
|
||||
case DownloadManagerService.MESSAGE_FINISHED:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
public long lastTimeStamp = -1;
|
||||
public long lastDone = -1;
|
||||
public int colorId;
|
||||
for (int i = 0; i < mPendingDownloadsItems.size(); i++) {
|
||||
ViewHolderItem h = mPendingDownloadsItems.get(i);
|
||||
if (h.item.mission != msg.obj) continue;
|
||||
|
||||
public ViewHolder(View v) {
|
||||
super(v);
|
||||
if (msg.what == DownloadManagerService.MESSAGE_FINISHED) {
|
||||
// DownloadManager should mark the download as finished
|
||||
applyChanges();
|
||||
|
||||
status = v.findViewById(R.id.item_status);
|
||||
icon = v.findViewById(R.id.item_icon);
|
||||
name = v.findViewById(R.id.item_name);
|
||||
size = v.findViewById(R.id.item_size);
|
||||
bkg = v.findViewById(R.id.item_bkg);
|
||||
menu = v.findViewById(R.id.item_more);
|
||||
mPendingDownloadsItems.remove(i);
|
||||
return;
|
||||
}
|
||||
|
||||
updateProgress(h);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static class MissionObserver implements DownloadMission.MissionListener {
|
||||
private final MissionAdapter mAdapter;
|
||||
private final ViewHolder mHolder;
|
||||
private void showError(@NonNull DownloadMission mission) {
|
||||
StringBuilder str = new StringBuilder();
|
||||
str.append(mContext.getString(R.string.label_code));
|
||||
str.append(": ");
|
||||
str.append(mission.errCode);
|
||||
str.append('\n');
|
||||
|
||||
public MissionObserver(MissionAdapter adapter, ViewHolder holder) {
|
||||
mAdapter = adapter;
|
||||
mHolder = holder;
|
||||
switch (mission.errCode) {
|
||||
case 416:
|
||||
str.append(mContext.getString(R.string.error_http_requested_range_not_satisfiable));
|
||||
break;
|
||||
case 404:
|
||||
str.append(mContext.getString(R.string.error_http_not_found));
|
||||
break;
|
||||
case DownloadMission.ERROR_NOTHING:
|
||||
str.append("¿?");
|
||||
break;
|
||||
case DownloadMission.ERROR_FILE_CREATION:
|
||||
str.append(mContext.getString(R.string.error_file_creation));
|
||||
break;
|
||||
case DownloadMission.ERROR_HTTP_NO_CONTENT:
|
||||
str.append(mContext.getString(R.string.error_http_no_content));
|
||||
break;
|
||||
case DownloadMission.ERROR_HTTP_UNSUPPORTED_RANGE:
|
||||
str.append(mContext.getString(R.string.error_http_unsupported_range));
|
||||
break;
|
||||
case DownloadMission.ERROR_PATH_CREATION:
|
||||
str.append(mContext.getString(R.string.error_path_creation));
|
||||
break;
|
||||
case DownloadMission.ERROR_PERMISSION_DENIED:
|
||||
str.append(mContext.getString(R.string.permission_denied));
|
||||
break;
|
||||
case DownloadMission.ERROR_SSL_EXCEPTION:
|
||||
str.append(mContext.getString(R.string.error_ssl_exception));
|
||||
break;
|
||||
case DownloadMission.ERROR_UNKNOWN_HOST:
|
||||
str.append(mContext.getString(R.string.error_unknown_host));
|
||||
break;
|
||||
case DownloadMission.ERROR_CONNECT_HOST:
|
||||
str.append(mContext.getString(R.string.error_connect_host));
|
||||
break;
|
||||
case DownloadMission.ERROR_POSTPROCESSING_FAILED:
|
||||
str.append(R.string.error_postprocessing_failed);
|
||||
case DownloadMission.ERROR_UNKNOWN_EXCEPTION:
|
||||
break;
|
||||
default:
|
||||
if (mission.errCode >= 100 && mission.errCode < 600) {
|
||||
str.append("HTTP");
|
||||
} else if (mission.errObject == null) {
|
||||
str.append("(not_decelerated_error_code)");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressUpdate(DownloadMission downloadMission, long done, long total) {
|
||||
mAdapter.updateProgress(mHolder);
|
||||
if (mission.errObject != null) {
|
||||
str.append("\n\n");
|
||||
str.append(mission.errObject.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish(DownloadMission downloadMission) {
|
||||
//mAdapter.mManager.deleteMission(mHolder.position);
|
||||
// TODO Notification
|
||||
//mAdapter.notifyDataSetChanged();
|
||||
if (mHolder.mission != null) {
|
||||
mHolder.size.setText(Utility.formatBytes(mHolder.mission.length));
|
||||
mAdapter.updateProgress(mHolder, true);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
|
||||
builder.setTitle(mission.name)
|
||||
.setMessage(str)
|
||||
.setNegativeButton(android.R.string.ok, (dialog, which) -> dialog.cancel())
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
public void clearFinishedDownloads() {
|
||||
mDownloadManager.forgetFinishedDownloads();
|
||||
applyChanges();
|
||||
mClear.setVisible(false);
|
||||
}
|
||||
|
||||
private boolean handlePopupItem(@NonNull ViewHolderItem h, @NonNull MenuItem option) {
|
||||
int id = option.getItemId();
|
||||
DownloadMission mission = h.item.mission instanceof DownloadMission ? (DownloadMission) h.item.mission : null;
|
||||
|
||||
if (mission != null) {
|
||||
switch (id) {
|
||||
case R.id.start:
|
||||
h.state = -1;
|
||||
h.size.setText(Utility.formatBytes(mission.length));
|
||||
mDownloadManager.resumeMission(mission);
|
||||
return true;
|
||||
case R.id.pause:
|
||||
h.state = -1;
|
||||
mDownloadManager.pauseMission(mission);
|
||||
notifyItemChanged(h.getAdapterPosition());
|
||||
h.lastTimeStamp = -1;
|
||||
h.lastDone = -1;
|
||||
return true;
|
||||
case R.id.error_message_view:
|
||||
showError(mission);
|
||||
return true;
|
||||
case R.id.queue:
|
||||
h.queue.setChecked(!h.queue.isChecked());
|
||||
mission.enqueued = h.queue.isChecked();
|
||||
updateProgress(h);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(DownloadMission downloadMission, int errCode) {
|
||||
mAdapter.updateProgress(mHolder);
|
||||
switch (id) {
|
||||
case R.id.open:
|
||||
return viewWithFileProvider(h.item.mission.getDownloadedFile());
|
||||
case R.id.delete:
|
||||
if (mDeleter == null) {
|
||||
mDownloadManager.deleteMission(h.item.mission);
|
||||
} else {
|
||||
mDeleter.append(h.item.mission);
|
||||
}
|
||||
applyChanges();
|
||||
return true;
|
||||
case R.id.md5:
|
||||
case R.id.sha1:
|
||||
new ChecksumTask(mContext).execute(h.item.mission.getDownloadedFile().getAbsolutePath(), ALGORITHMS.get(id));
|
||||
return true;
|
||||
case R.id.source:
|
||||
/*Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(h.item.mission.source));
|
||||
mContext.startActivity(intent);*/
|
||||
try {
|
||||
Intent intent = NavigationHelper.getIntentByLink(mContext, h.item.mission.source);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
mContext.startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Selected item has a invalid source", e);
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ChecksumTask extends AsyncTask<String, Void, String> {
|
||||
ProgressDialog prog;
|
||||
final WeakReference<Activity> weakReference;
|
||||
public void applyChanges() {
|
||||
mIterator.start();
|
||||
DiffUtil.calculateDiff(mIterator, true).dispatchUpdatesTo(this);
|
||||
mIterator.end();
|
||||
|
||||
ChecksumTask(@NonNull Activity activity) {
|
||||
weakReference = new WeakReference<>(activity);
|
||||
checkEmptyMessageVisibility();
|
||||
|
||||
if (mIterator.getOldListSize() > 0) {
|
||||
int lastItemType = mIterator.getSpecialAtItem(mIterator.getOldListSize() - 1);
|
||||
mClear.setVisible(lastItemType == DownloadManager.SPECIAL_FINISHED);
|
||||
}
|
||||
}
|
||||
|
||||
public void forceUpdate() {
|
||||
mIterator.start();
|
||||
mIterator.end();
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setLinear(boolean isLinear) {
|
||||
mLayout = isLinear ? R.layout.mission_item_linear : R.layout.mission_item;
|
||||
}
|
||||
|
||||
private void checkEmptyMessageVisibility() {
|
||||
int flag = mIterator.getOldListSize() > 0 ? View.GONE : View.VISIBLE;
|
||||
if (mEmptyMessage.getVisibility() != flag) mEmptyMessage.setVisibility(flag);
|
||||
}
|
||||
|
||||
|
||||
public void deleterDispose(Bundle bundle) {
|
||||
if (mDeleter != null) mDeleter.dispose(bundle);
|
||||
}
|
||||
|
||||
public void deleterLoad(Bundle bundle, View view) {
|
||||
if (mDeleter == null)
|
||||
mDeleter = new Deleter(bundle, view, mContext, this, mDownloadManager, mIterator, mHandler);
|
||||
}
|
||||
|
||||
public void deleterResume() {
|
||||
if (mDeleter != null) mDeleter.resume();
|
||||
}
|
||||
|
||||
|
||||
class ViewHolderItem extends RecyclerView.ViewHolder {
|
||||
DownloadManager.MissionItem item;
|
||||
|
||||
TextView status;
|
||||
ImageView icon;
|
||||
TextView name;
|
||||
TextView size;
|
||||
ProgressDrawable progress;
|
||||
|
||||
PopupMenu popupMenu;
|
||||
MenuItem start;
|
||||
MenuItem pause;
|
||||
MenuItem open;
|
||||
MenuItem queue;
|
||||
MenuItem showError;
|
||||
MenuItem delete;
|
||||
MenuItem source;
|
||||
MenuItem checksum;
|
||||
|
||||
long lastTimeStamp = -1;
|
||||
long lastDone = -1;
|
||||
int lastCurrent = -1;
|
||||
int state = 0;
|
||||
|
||||
ViewHolderItem(View view) {
|
||||
super(view);
|
||||
|
||||
progress = new ProgressDrawable();
|
||||
ViewCompat.setBackground(itemView.findViewById(R.id.item_bkg), progress);
|
||||
|
||||
status = itemView.findViewById(R.id.item_status);
|
||||
name = itemView.findViewById(R.id.item_name);
|
||||
icon = itemView.findViewById(R.id.item_icon);
|
||||
size = itemView.findViewById(R.id.item_size);
|
||||
|
||||
name.setSelected(true);
|
||||
|
||||
ImageView button = itemView.findViewById(R.id.item_more);
|
||||
popupMenu = buildPopup(button);
|
||||
button.setOnClickListener(v -> showPopupMenu());
|
||||
|
||||
Menu menu = popupMenu.getMenu();
|
||||
start = menu.findItem(R.id.start);
|
||||
pause = menu.findItem(R.id.pause);
|
||||
open = menu.findItem(R.id.open);
|
||||
queue = menu.findItem(R.id.queue);
|
||||
showError = menu.findItem(R.id.error_message_view);
|
||||
delete = menu.findItem(R.id.delete);
|
||||
source = menu.findItem(R.id.source);
|
||||
checksum = menu.findItem(R.id.checksum);
|
||||
|
||||
//h.itemView.setOnClickListener(v -> showDetail(h));
|
||||
}
|
||||
|
||||
private void showPopupMenu() {
|
||||
start.setVisible(false);
|
||||
pause.setVisible(false);
|
||||
open.setVisible(false);
|
||||
queue.setVisible(false);
|
||||
showError.setVisible(false);
|
||||
delete.setVisible(false);
|
||||
source.setVisible(false);
|
||||
checksum.setVisible(false);
|
||||
|
||||
DownloadMission mission = item.mission instanceof DownloadMission ? (DownloadMission) item.mission : null;
|
||||
|
||||
if (mission != null) {
|
||||
if (!mission.postprocessingRunning) {
|
||||
if (mission.running) {
|
||||
pause.setVisible(true);
|
||||
} else {
|
||||
if (mission.errCode != DownloadMission.ERROR_NOTHING) {
|
||||
showError.setVisible(true);
|
||||
}
|
||||
|
||||
queue.setChecked(mission.enqueued);
|
||||
|
||||
start.setVisible(mission.errCode != DownloadMission.ERROR_POSTPROCESSING_FAILED);
|
||||
delete.setVisible(true);
|
||||
queue.setVisible(true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
open.setVisible(true);
|
||||
delete.setVisible(true);
|
||||
checksum.setVisible(true);
|
||||
}
|
||||
|
||||
if (item.mission.source != null && !item.mission.source.isEmpty()) {
|
||||
source.setVisible(true);
|
||||
}
|
||||
|
||||
popupMenu.show();
|
||||
}
|
||||
|
||||
private PopupMenu buildPopup(final View button) {
|
||||
PopupMenu popup = new PopupMenu(mContext, button);
|
||||
popup.inflate(R.menu.mission);
|
||||
popup.setOnMenuItemClickListener(option -> handlePopupItem(this, option));
|
||||
|
||||
return popup;
|
||||
}
|
||||
}
|
||||
|
||||
class ViewHolderHeader extends RecyclerView.ViewHolder {
|
||||
TextView header;
|
||||
|
||||
ViewHolderHeader(View view) {
|
||||
super(view);
|
||||
header = itemView.findViewById(R.id.item_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class ChecksumTask extends AsyncTask<String, Void, String> {
|
||||
ProgressDialog progressDialog;
|
||||
WeakReference<Activity> weakReference;
|
||||
|
||||
ChecksumTask(@NonNull Context context) {
|
||||
weakReference = new WeakReference<>((Activity) context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -378,10 +652,10 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
|||
Activity activity = getActivity();
|
||||
if (activity != null) {
|
||||
// Create dialog
|
||||
prog = new ProgressDialog(activity);
|
||||
prog.setCancelable(false);
|
||||
prog.setMessage(activity.getString(R.string.msg_wait));
|
||||
prog.show();
|
||||
progressDialog = new ProgressDialog(activity);
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.setMessage(activity.getString(R.string.msg_wait));
|
||||
progressDialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -394,10 +668,10 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
|||
protected void onPostExecute(String result) {
|
||||
super.onPostExecute(result);
|
||||
|
||||
if (prog != null) {
|
||||
Utility.copyToClipboard(prog.getContext(), result);
|
||||
if (progressDialog != null) {
|
||||
Utility.copyToClipboard(progressDialog.getContext(), result);
|
||||
if (getActivity() != null) {
|
||||
prog.dismiss();
|
||||
progressDialog.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -413,4 +687,5 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
169
app/src/main/java/us/shandian/giga/ui/common/Deleter.java
Normal file
169
app/src/main/java/us/shandian/giga/ui/common/Deleter.java
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
package us.shandian.giga.ui.common;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.view.View;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import us.shandian.giga.get.Mission;
|
||||
import us.shandian.giga.service.DownloadManager;
|
||||
import us.shandian.giga.service.DownloadManager.MissionIterator;
|
||||
import us.shandian.giga.ui.adapter.MissionAdapter;
|
||||
|
||||
public class Deleter {
|
||||
private static final int TIMEOUT = 5000;// ms
|
||||
private static final int DELAY = 350;// ms
|
||||
private static final String BUNDLE_NAMES = "us.shandian.giga.ui.common.deleter.names";
|
||||
private static final String BUNDLE_LOCATIONS = "us.shandian.giga.ui.common.deleter.locations";
|
||||
|
||||
private Snackbar snackbar;
|
||||
private ArrayList<Mission> items;
|
||||
private boolean running = true;
|
||||
|
||||
private Context mContext;
|
||||
private MissionAdapter mAdapter;
|
||||
private DownloadManager mDownloadManager;
|
||||
private MissionIterator mIterator;
|
||||
private Handler mHandler;
|
||||
private View mView;
|
||||
|
||||
private final Runnable rShow;
|
||||
private final Runnable rNext;
|
||||
private final Runnable rCommit;
|
||||
|
||||
public Deleter(Bundle b, View v, Context c, MissionAdapter a, DownloadManager d, MissionIterator i, Handler h) {
|
||||
mView = v;
|
||||
mContext = c;
|
||||
mAdapter = a;
|
||||
mDownloadManager = d;
|
||||
mIterator = i;
|
||||
mHandler = h;
|
||||
|
||||
// use variables to know the reference of the lambdas
|
||||
rShow = this::show;
|
||||
rNext = this::next;
|
||||
rCommit = this::commit;
|
||||
|
||||
items = new ArrayList<>(2);
|
||||
|
||||
if (b != null) {
|
||||
String[] names = b.getStringArray(BUNDLE_NAMES);
|
||||
String[] locations = b.getStringArray(BUNDLE_LOCATIONS);
|
||||
|
||||
if (names == null || locations == null) return;
|
||||
if (names.length < 1 || locations.length < 1) return;
|
||||
if (names.length != locations.length) return;
|
||||
|
||||
items.ensureCapacity(names.length);
|
||||
|
||||
for (int j = 0; j < locations.length; j++) {
|
||||
Mission mission = mDownloadManager.getAnyMission(locations[j], names[j]);
|
||||
if (mission == null) continue;
|
||||
|
||||
items.add(mission);
|
||||
mIterator.hide(mission);
|
||||
}
|
||||
|
||||
if (items.size() > 0) resume();
|
||||
}
|
||||
}
|
||||
|
||||
public void append(Mission item) {
|
||||
mIterator.hide(item);
|
||||
items.add(0, item);
|
||||
|
||||
show();
|
||||
}
|
||||
|
||||
private void forget() {
|
||||
mIterator.unHide(items.remove(0));
|
||||
mAdapter.applyChanges();
|
||||
|
||||
show();
|
||||
}
|
||||
|
||||
private void show() {
|
||||
if (items.size() < 1) return;
|
||||
|
||||
pause();
|
||||
running = true;
|
||||
|
||||
mHandler.postDelayed(rNext, DELAY);
|
||||
}
|
||||
|
||||
private void next() {
|
||||
if (items.size() < 1) return;
|
||||
|
||||
String msg = mContext.getString(R.string.file_deleted).concat(":\n").concat(items.get(0).name);
|
||||
|
||||
snackbar = Snackbar.make(mView, msg, Snackbar.LENGTH_INDEFINITE);
|
||||
snackbar.setAction(R.string.undo, s -> forget());
|
||||
snackbar.setActionTextColor(Color.YELLOW);
|
||||
snackbar.show();
|
||||
|
||||
mHandler.postDelayed(rCommit, TIMEOUT);
|
||||
}
|
||||
|
||||
private void commit() {
|
||||
if (items.size() < 1) return;
|
||||
|
||||
while (items.size() > 0) {
|
||||
Mission mission = items.remove(0);
|
||||
if (mission.deleted) continue;
|
||||
|
||||
mIterator.unHide(mission);
|
||||
mDownloadManager.deleteMission(mission);
|
||||
break;
|
||||
}
|
||||
|
||||
if (items.size() < 1) {
|
||||
pause();
|
||||
return;
|
||||
}
|
||||
|
||||
show();
|
||||
}
|
||||
|
||||
private void pause() {
|
||||
running = false;
|
||||
mHandler.removeCallbacks(rNext);
|
||||
mHandler.removeCallbacks(rShow);
|
||||
mHandler.removeCallbacks(rCommit);
|
||||
if (snackbar != null) snackbar.dismiss();
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
if (running) return;
|
||||
mHandler.postDelayed(rShow, (int) (DELAY * 1.5f));// 150% of the delay
|
||||
}
|
||||
|
||||
public void dispose(Bundle bundle) {
|
||||
if (items.size() < 1) return;
|
||||
|
||||
pause();
|
||||
|
||||
if (bundle == null) {
|
||||
for (Mission mission : items) mDownloadManager.deleteMission(mission);
|
||||
items = null;
|
||||
return;
|
||||
}
|
||||
|
||||
String[] names = new String[items.size()];
|
||||
String[] locations = new String[items.size()];
|
||||
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
Mission mission = items.get(i);
|
||||
names[i] = mission.name;
|
||||
locations[i] = mission.location;
|
||||
}
|
||||
|
||||
bundle.putStringArray(BUNDLE_NAMES, names);
|
||||
bundle.putStringArray(BUNDLE_LOCATIONS, locations);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,36 @@
|
|||
package us.shandian.giga.ui.common;
|
||||
package us.shandian.giga.ui.common;// TODO: ¡git it!
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.ColorRes;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
||||
public class ProgressDrawable extends Drawable {
|
||||
private float mProgress;
|
||||
private final int mBackgroundColor;
|
||||
private final int mForegroundColor;
|
||||
private static final int MARQUEE_INTERVAL = 150;
|
||||
|
||||
public ProgressDrawable(Context context, @ColorRes int background, @ColorRes int foreground) {
|
||||
this(ContextCompat.getColor(context, background), ContextCompat.getColor(context, foreground));
|
||||
private float mProgress;
|
||||
private int mBackgroundColor, mForegroundColor;
|
||||
private Handler mMarqueeHandler;
|
||||
private float mMarqueeProgress;
|
||||
private Path mMarqueeLine;
|
||||
private int mMarqueeSize;
|
||||
private long mMarqueeNext;
|
||||
|
||||
public ProgressDrawable() {
|
||||
mMarqueeLine = null;// marquee disabled
|
||||
mMarqueeProgress = 0f;
|
||||
mMarqueeSize = 0;
|
||||
mMarqueeNext = 0;
|
||||
}
|
||||
|
||||
public ProgressDrawable(int background, int foreground) {
|
||||
public void setColors(@ColorInt int background, @ColorInt int foreground) {
|
||||
mBackgroundColor = background;
|
||||
mForegroundColor = foreground;
|
||||
}
|
||||
|
|
@ -29,10 +40,20 @@ public class ProgressDrawable extends Drawable {
|
|||
invalidateSelf();
|
||||
}
|
||||
|
||||
public void setMarquee(boolean marquee) {
|
||||
if (marquee == (mMarqueeLine != null)) {
|
||||
return;
|
||||
}
|
||||
mMarqueeLine = marquee ? new Path() : null;
|
||||
mMarqueeHandler = marquee ? new Handler(Looper.getMainLooper()) : null;
|
||||
mMarqueeSize = 0;
|
||||
mMarqueeNext = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
int width = canvas.getWidth();
|
||||
int height = canvas.getHeight();
|
||||
int width = getBounds().width();
|
||||
int height = getBounds().height();
|
||||
|
||||
Paint paint = new Paint();
|
||||
|
||||
|
|
@ -40,6 +61,42 @@ public class ProgressDrawable extends Drawable {
|
|||
canvas.drawRect(0, 0, width, height, paint);
|
||||
|
||||
paint.setColor(mForegroundColor);
|
||||
|
||||
if (mMarqueeLine != null) {
|
||||
if (mMarqueeSize < 1) setupMarquee(width, height);
|
||||
|
||||
int size = mMarqueeSize;
|
||||
Paint paint2 = new Paint();
|
||||
paint2.setColor(mForegroundColor);
|
||||
paint2.setStrokeWidth(size);
|
||||
paint2.setStyle(Paint.Style.STROKE);
|
||||
|
||||
size *= 2;
|
||||
|
||||
if (mMarqueeProgress >= size) {
|
||||
mMarqueeProgress = 1;
|
||||
} else {
|
||||
mMarqueeProgress++;
|
||||
}
|
||||
|
||||
// render marquee
|
||||
width += size * 2;
|
||||
Path marquee = new Path();
|
||||
for (float i = -size; i < width; i += size) {
|
||||
marquee.addPath(mMarqueeLine, i + mMarqueeProgress, 0);
|
||||
}
|
||||
marquee.close();
|
||||
|
||||
canvas.drawPath(marquee, paint2);// draw marquee
|
||||
|
||||
if (System.currentTimeMillis() >= mMarqueeNext) {
|
||||
// program next update
|
||||
mMarqueeNext = System.currentTimeMillis() + MARQUEE_INTERVAL;
|
||||
mMarqueeHandler.postDelayed(this::invalidateSelf, MARQUEE_INTERVAL);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
canvas.drawRect(0, 0, (int) (mProgress * width), height, paint);
|
||||
}
|
||||
|
||||
|
|
@ -58,4 +115,17 @@ public class ProgressDrawable extends Drawable {
|
|||
return PixelFormat.OPAQUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBoundsChange(Rect rect) {
|
||||
if (mMarqueeLine != null) setupMarquee(rect.width(), rect.height());
|
||||
}
|
||||
|
||||
private void setupMarquee(int width, int height) {
|
||||
mMarqueeSize = (int) ((width * 10f) / 100f);// the size is 10% of the width
|
||||
|
||||
mMarqueeLine.rewind();
|
||||
mMarqueeLine.moveTo(-mMarqueeSize, -mMarqueeSize);
|
||||
mMarqueeLine.lineTo(-mMarqueeSize * 4, height + mMarqueeSize);
|
||||
mMarqueeLine.close();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import android.content.SharedPreferences;
|
|||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
|
@ -23,39 +21,47 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.download.DeleteDownloadManager;
|
||||
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import us.shandian.giga.get.DownloadManager;
|
||||
import us.shandian.giga.service.DownloadManager;
|
||||
import us.shandian.giga.service.DownloadManagerService;
|
||||
import us.shandian.giga.service.DownloadManagerService.DMBinder;
|
||||
import us.shandian.giga.ui.adapter.MissionAdapter;
|
||||
|
||||
public abstract class MissionsFragment extends Fragment {
|
||||
private DownloadManager mDownloadManager;
|
||||
private DownloadManagerService.DMBinder mBinder;
|
||||
public class MissionsFragment extends Fragment {
|
||||
|
||||
private static final int SPAN_SIZE = 2;
|
||||
|
||||
private SharedPreferences mPrefs;
|
||||
private boolean mLinear;
|
||||
private MenuItem mSwitch;
|
||||
private MenuItem mClear;
|
||||
|
||||
private RecyclerView mList;
|
||||
private View mEmpty;
|
||||
private MissionAdapter mAdapter;
|
||||
private GridLayoutManager mGridManager;
|
||||
private LinearLayoutManager mLinearManager;
|
||||
private Context mActivity;
|
||||
private DeleteDownloadManager mDeleteDownloadManager;
|
||||
private Disposable mDeleteDisposable;
|
||||
|
||||
private DMBinder mBinder;
|
||||
private Bundle mBundle;
|
||||
private boolean mForceUpdate;
|
||||
|
||||
private final ServiceConnection mConnection = new ServiceConnection() {
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder binder) {
|
||||
mBinder = (DownloadManagerService.DMBinder) binder;
|
||||
mDownloadManager = setupDownloadManager(mBinder);
|
||||
if (mDeleteDownloadManager != null) {
|
||||
mDeleteDownloadManager.setDownloadManager(mDownloadManager);
|
||||
updateList();
|
||||
}
|
||||
mBinder.resetFinishedDownloadCount();
|
||||
|
||||
mAdapter = new MissionAdapter(mActivity, mBinder.getDownloadManager(), mClear, mEmpty);
|
||||
mAdapter.deleterLoad(mBundle, getView());
|
||||
|
||||
mBundle = null;
|
||||
|
||||
mBinder.addMissionEventListener(mAdapter.getMessenger());
|
||||
|
||||
updateList();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -66,14 +72,6 @@ public abstract class MissionsFragment extends Fragment {
|
|||
|
||||
};
|
||||
|
||||
public void setDeleteManager(@NonNull DeleteDownloadManager deleteDownloadManager) {
|
||||
mDeleteDownloadManager = deleteDownloadManager;
|
||||
if (mDownloadManager != null) {
|
||||
mDeleteDownloadManager.setDownloadManager(mDownloadManager);
|
||||
updateList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.missions, container, false);
|
||||
|
|
@ -81,24 +79,47 @@ public abstract class MissionsFragment extends Fragment {
|
|||
mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
mLinear = mPrefs.getBoolean("linear", false);
|
||||
|
||||
mActivity = getActivity();
|
||||
mBundle = savedInstanceState;
|
||||
|
||||
// Bind the service
|
||||
Intent i = new Intent();
|
||||
i.setClass(getActivity(), DownloadManagerService.class);
|
||||
getActivity().bindService(i, mConnection, Context.BIND_AUTO_CREATE);
|
||||
mActivity.bindService(new Intent(mActivity, DownloadManagerService.class), mConnection, Context.BIND_AUTO_CREATE);
|
||||
|
||||
// Views
|
||||
mEmpty = v.findViewById(R.id.list_empty_view);
|
||||
mList = v.findViewById(R.id.mission_recycler);
|
||||
|
||||
// Init
|
||||
mGridManager = new GridLayoutManager(getActivity(), 2);
|
||||
mGridManager = new GridLayoutManager(getActivity(), SPAN_SIZE);
|
||||
mGridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
|
||||
@Override
|
||||
public int getSpanSize(int position) {
|
||||
switch (mAdapter.getItemViewType(position)) {
|
||||
case DownloadManager.SPECIAL_PENDING:
|
||||
case DownloadManager.SPECIAL_FINISHED:
|
||||
return SPAN_SIZE;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mLinearManager = new LinearLayoutManager(getActivity());
|
||||
mList.setLayoutManager(mGridManager);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
if (menu != null) {
|
||||
mSwitch = menu.findItem(R.id.switch_mode);
|
||||
mClear = menu.findItem(R.id.clear_list);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Added in API level 23.
|
||||
*/
|
||||
|
|
@ -108,7 +129,7 @@ public abstract class MissionsFragment extends Fragment {
|
|||
|
||||
// Bug: in api< 23 this is never called
|
||||
// so mActivity=null
|
||||
// so app crashes with nullpointer exception
|
||||
// so app crashes with null-pointer exception
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
|
|
@ -119,71 +140,78 @@ public abstract class MissionsFragment extends Fragment {
|
|||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if (mDeleteDownloadManager != null) {
|
||||
mDeleteDisposable = mDeleteDownloadManager.getUndoObservable().subscribe(mission -> {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.updateItemList();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (mBinder == null || mAdapter == null) return;
|
||||
|
||||
mBinder.removeMissionEventListener(mAdapter.getMessenger());
|
||||
mActivity.unbindService(mConnection);
|
||||
mAdapter.deleterDispose(null);
|
||||
|
||||
mBinder = null;
|
||||
mAdapter = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (mAdapter != null) {
|
||||
mAdapter.deleterDispose(outState);
|
||||
mForceUpdate = true;
|
||||
mBinder.removeMissionEventListener(mAdapter.getMessenger());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
getActivity().unbindService(mConnection);
|
||||
if (mDeleteDisposable != null) {
|
||||
mDeleteDisposable.dispose();
|
||||
}
|
||||
}
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (mAdapter != null) {
|
||||
mAdapter.deleterResume();
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
mSwitch = menu.findItem(R.id.switch_mode);
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
if (mForceUpdate) {
|
||||
mForceUpdate = false;
|
||||
mAdapter.forceUpdate();
|
||||
}
|
||||
|
||||
mBinder.addMissionEventListener(mAdapter.getMessenger());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.switch_mode:
|
||||
mLinear = !mLinear;
|
||||
updateList();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyChange() {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
mLinear = !mLinear;
|
||||
updateList();
|
||||
return true;
|
||||
case R.id.clear_list:
|
||||
mAdapter.clearFinishedDownloads();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateList() {
|
||||
mAdapter = new MissionAdapter((Activity) mActivity, mBinder, mDownloadManager, mDeleteDownloadManager, mLinear);
|
||||
|
||||
if (mLinear) {
|
||||
mList.setLayoutManager(mLinearManager);
|
||||
} else {
|
||||
mList.setLayoutManager(mGridManager);
|
||||
}
|
||||
|
||||
mList.setAdapter(null);
|
||||
mAdapter.notifyDataSetChanged();
|
||||
mAdapter.setLinear(mLinear);
|
||||
mList.setAdapter(mAdapter);
|
||||
|
||||
if (mSwitch != null) {
|
||||
mSwitch.setIcon(mLinear ? R.drawable.grid : R.drawable.list);
|
||||
mSwitch.setTitle(mLinear ? R.string.grid : R.string.list);
|
||||
mPrefs.edit().putBoolean("linear", mLinear).apply();
|
||||
}
|
||||
|
||||
mPrefs.edit().putBoolean("linear", mLinear).apply();
|
||||
}
|
||||
|
||||
protected abstract DownloadManager setupDownloadManager(DownloadManagerService.DMBinder binder);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue