Add downloaded indicator to Posts view

This commit is contained in:
Ammar Githam 2020-11-06 21:46:31 +09:00
parent 450dbba6de
commit d0bfe73ae6
14 changed files with 449 additions and 365 deletions

View File

@ -1,5 +1,6 @@
package awais.instagrabber.adapters.viewholder;
import android.content.res.ColorStateList;
import android.net.Uri;
import android.view.View;
import android.view.ViewGroup;
@ -17,6 +18,7 @@ import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.asyncs.DownloadedCheckerAsyncTask;
import awais.instagrabber.databinding.ItemFeedGridBinding;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
@ -131,5 +133,29 @@ public class FeedGridItemViewHolder extends RecyclerView.ViewHolder {
.setImageRequest(requestBuilder)
.setOldController(binding.postImage.getController());
binding.postImage.setController(builder.build());
final DownloadedCheckerAsyncTask task = new DownloadedCheckerAsyncTask(result -> {
final List<Boolean> checkList = result.get(feedModel.getPostId());
if (checkList == null || checkList.isEmpty()) {
return;
}
switch (feedModel.getItemType()) {
case MEDIA_TYPE_IMAGE:
case MEDIA_TYPE_VIDEO:
binding.downloaded.setVisibility(checkList.get(0) ? View.VISIBLE : View.GONE);
binding.downloaded.setImageTintList(ColorStateList.valueOf(itemView.getResources().getColor(R.color.green_A400)));
break;
case MEDIA_TYPE_SLIDER:
binding.downloaded.setVisibility(checkList.get(0) ? View.VISIBLE : View.GONE);
boolean allDownloaded = checkList.size() == feedModel.getSliderItems().size();
if (allDownloaded) {
allDownloaded = checkList.stream().allMatch(downloaded -> downloaded);
}
binding.downloaded.setImageTintList(ColorStateList.valueOf(itemView.getResources().getColor(
allDownloaded ? R.color.green_A400 : R.color.yellow_400)));
break;
default:
}
});
task.execute(feedModel);
}
}

View File

@ -1,241 +1,241 @@
package awais.instagrabber.asyncs;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaMetadataRetriever;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.FileProvider;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.atomic.AtomicReference;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Constants.NOTIF_GROUP_NAME;
import static awais.instagrabber.utils.Utils.logCollector;
import static awaisomereport.LogCollector.LogFile;
public final class DownloadAsync extends AsyncTask<Void, Float, File> {
private static final String TAG = "DownloadAsync";
private static int lastNotifId = 1;
private final int currentNotifId;
private final AtomicReference<Context> context;
private final File outFile;
private final String url;
private final FetchListener<File> fetchListener;
private final Resources resources;
private final NotificationCompat.Builder downloadNotif;
private String shortCode, username;
private final NotificationManagerCompat notificationManager;
public DownloadAsync(@NonNull final Context context,
final String url,
final File outFile,
final FetchListener<File> fetchListener) {
this.context = new AtomicReference<>(context);
this.resources = context.getResources();
this.url = url;
this.outFile = outFile;
this.fetchListener = fetchListener;
this.shortCode = this.username = resources.getString(R.string.downloader_started);
this.currentNotifId = ++lastNotifId;
if (++lastNotifId + 1 == Integer.MAX_VALUE) lastNotifId = 1;
@StringRes final int titleRes = R.string.downloader_downloading_post;
downloadNotif = new NotificationCompat.Builder(context, Constants.DOWNLOAD_CHANNEL_ID)
.setCategory(NotificationCompat.CATEGORY_STATUS)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentText(shortCode == null ? username : shortCode)
.setOngoing(true)
.setProgress(100, 0, false)
.setAutoCancel(false)
.setOnlyAlertOnce(true)
.setContentTitle(resources.getString(titleRes));
notificationManager = NotificationManagerCompat.from(context.getApplicationContext());
notificationManager.notify(currentNotifId, downloadNotif.build());
}
public DownloadAsync setItems(final String shortCode, final String username) {
this.shortCode = shortCode;
this.username = username;
if (downloadNotif != null) downloadNotif.setContentText(this.shortCode == null ? this.username : this.shortCode);
return this;
}
@Nullable
@Override
protected File doInBackground(final Void... voids) {
try {
final URLConnection urlConnection = new URL(url).openConnection();
final long fileSize = Build.VERSION.SDK_INT >= 24 ? urlConnection.getContentLengthLong() :
urlConnection.getContentLength();
float totalRead = 0;
try (final BufferedInputStream bis = new BufferedInputStream(urlConnection.getInputStream());
final FileOutputStream fos = new FileOutputStream(outFile)) {
final byte[] buffer = new byte[0x2000];
int count;
boolean deletedIPTC = false;
while ((count = bis.read(buffer, 0, 0x2000)) != -1) {
totalRead = totalRead + count;
if (!deletedIPTC) {
int iptcStart = -1;
int fbmdStart = -1;
int fbmdBytesLen = -1;
for (int i = 0; i < buffer.length; ++i) {
if (buffer[i] == (byte) 0xFF && buffer[i + 1] == (byte) 0xED)
iptcStart = i;
else if (buffer[i] == (byte) 'F' && buffer[i + 1] == (byte) 'B'
&& buffer[i + 2] == (byte) 'M' && buffer[i + 3] == (byte) 'D') {
fbmdStart = i;
fbmdBytesLen = buffer[i - 10] << 24 | (buffer[i - 9] & 0xFF) << 16 |
(buffer[i - 8] & 0xFF) << 8 | (buffer[i - 7] & 0xFF) |
(buffer[i - 6] & 0xFF);
break;
}
}
if (iptcStart != -1 && fbmdStart != -1 && fbmdBytesLen != -1) {
final int fbmdDataLen = (iptcStart + (fbmdStart - iptcStart) + (fbmdBytesLen - iptcStart)) - 4;
fos.write(buffer, 0, iptcStart);
fos.write(buffer, fbmdDataLen + iptcStart, count - fbmdDataLen - iptcStart);
publishProgress(totalRead * 100f / fileSize);
deletedIPTC = true;
continue;
}
}
fos.write(buffer, 0, count);
publishProgress(totalRead * 100f / fileSize);
}
fos.flush();
}
return outFile;
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "doInBackground",
new Pair<>("context", context.get()),
new Pair<>("resources", resources),
new Pair<>("lastNotifId", lastNotifId),
new Pair<>("downloadNotif", downloadNotif),
new Pair<>("currentNotifId", currentNotifId),
new Pair<>("notificationManager", notificationManager));
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
return null;
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
}
@Override
protected void onProgressUpdate(@NonNull final Float... values) {
if (downloadNotif != null) {
downloadNotif.setProgress(100, values[0].intValue(), false);
notificationManager.notify(currentNotifId, downloadNotif.build());
}
}
@Override
protected void onPostExecute(final File result) {
if (result != null) {
final Context context = this.context.get();
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(result.getAbsoluteFile())));
MediaScannerConnection.scanFile(context, new String[]{result.getAbsolutePath()}, null, null);
if (notificationManager != null) {
final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", result);
final ContentResolver contentResolver = context.getContentResolver();
Bitmap bitmap = null;
if (Utils.isImage(uri, contentResolver)) {
try (final InputStream inputStream = contentResolver.openInputStream(uri)) {
bitmap = BitmapFactory.decodeStream(inputStream);
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_1");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
}
if (bitmap == null) {
final MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
try {
retriever.setDataSource(context, uri);
} catch (final Exception e) {
retriever.setDataSource(result.getAbsolutePath());
}
bitmap = retriever.getFrameAtTime();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
try {
retriever.close();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_2");
}
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_3");
}
}
final String downloadComplete = resources.getString(R.string.downloader_complete);
downloadNotif.setContentText(null).setContentTitle(downloadComplete).setProgress(0, 0, false)
.setWhen(System.currentTimeMillis()).setOngoing(false).setOnlyAlertOnce(false).setAutoCancel(true)
.setGroup(NOTIF_GROUP_NAME).setGroupSummary(true).setContentIntent(
PendingIntent.getActivity(context, 2020, new Intent(Intent.ACTION_VIEW, uri)
.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_FROM_BACKGROUND | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
.putExtra(Intent.EXTRA_STREAM, uri), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT));
if (bitmap != null)
downloadNotif.setStyle(new NotificationCompat.BigPictureStyle().setBigContentTitle(downloadComplete).bigPicture(bitmap))
.setLargeIcon(bitmap).setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL);
notificationManager.cancel(currentNotifId);
notificationManager.notify(currentNotifId + 1, downloadNotif.build());
}
}
if (fetchListener != null) fetchListener.onResult(result);
}
}
// package awais.instagrabber.asyncs;
//
// import android.app.PendingIntent;
// import android.content.ContentResolver;
// import android.content.Context;
// import android.content.Intent;
// import android.content.res.Resources;
// import android.graphics.Bitmap;
// import android.graphics.BitmapFactory;
// import android.media.MediaMetadataRetriever;
// import android.media.MediaScannerConnection;
// import android.net.Uri;
// import android.os.AsyncTask;
// import android.os.Build;
// import android.util.Log;
// import android.util.Pair;
//
// import androidx.annotation.NonNull;
// import androidx.annotation.Nullable;
// import androidx.annotation.StringRes;
// import androidx.core.app.NotificationCompat;
// import androidx.core.app.NotificationManagerCompat;
// import androidx.core.content.FileProvider;
//
// import java.io.BufferedInputStream;
// import java.io.File;
// import java.io.FileOutputStream;
// import java.io.InputStream;
// import java.net.URL;
// import java.net.URLConnection;
// import java.util.concurrent.atomic.AtomicReference;
//
// import awais.instagrabber.BuildConfig;
// import awais.instagrabber.R;
// import awais.instagrabber.interfaces.FetchListener;
// import awais.instagrabber.utils.Constants;
// import awais.instagrabber.utils.Utils;
//
// import static awais.instagrabber.utils.Constants.NOTIF_GROUP_NAME;
// import static awais.instagrabber.utils.Utils.logCollector;
// import static awaisomereport.LogCollector.LogFile;
//
// public final class DownloadAsync extends AsyncTask<Void, Float, File> {
// private static final String TAG = "DownloadAsync";
//
// private static int lastNotifId = 1;
// private final int currentNotifId;
// private final AtomicReference<Context> context;
// private final File outFile;
// private final String url;
// private final FetchListener<File> fetchListener;
// private final Resources resources;
// private final NotificationCompat.Builder downloadNotif;
// private String shortCode, username;
// private final NotificationManagerCompat notificationManager;
//
// public DownloadAsync(@NonNull final Context context,
// final String url,
// final File outFile,
// final FetchListener<File> fetchListener) {
// this.context = new AtomicReference<>(context);
// this.resources = context.getResources();
// this.url = url;
// this.outFile = outFile;
// this.fetchListener = fetchListener;
// this.shortCode = this.username = resources.getString(R.string.downloader_started);
// this.currentNotifId = ++lastNotifId;
// if (++lastNotifId + 1 == Integer.MAX_VALUE) lastNotifId = 1;
//
// @StringRes final int titleRes = R.string.downloader_downloading_post;
// downloadNotif = new NotificationCompat.Builder(context, Constants.DOWNLOAD_CHANNEL_ID)
// .setCategory(NotificationCompat.CATEGORY_STATUS)
// .setSmallIcon(R.mipmap.ic_launcher)
// .setContentText(shortCode == null ? username : shortCode)
// .setOngoing(true)
// .setProgress(100, 0, false)
// .setAutoCancel(false)
// .setOnlyAlertOnce(true)
// .setContentTitle(resources.getString(titleRes));
//
// notificationManager = NotificationManagerCompat.from(context.getApplicationContext());
// notificationManager.notify(currentNotifId, downloadNotif.build());
// }
//
// public DownloadAsync setItems(final String shortCode, final String username) {
// this.shortCode = shortCode;
// this.username = username;
// if (downloadNotif != null) downloadNotif.setContentText(this.shortCode == null ? this.username : this.shortCode);
// return this;
// }
//
// @Nullable
// @Override
// protected File doInBackground(final Void... voids) {
// try {
// final URLConnection urlConnection = new URL(url).openConnection();
// final long fileSize = Build.VERSION.SDK_INT >= 24 ? urlConnection.getContentLengthLong() :
// urlConnection.getContentLength();
// float totalRead = 0;
//
// try (final BufferedInputStream bis = new BufferedInputStream(urlConnection.getInputStream());
// final FileOutputStream fos = new FileOutputStream(outFile)) {
// final byte[] buffer = new byte[0x2000];
//
// int count;
// boolean deletedIPTC = false;
// while ((count = bis.read(buffer, 0, 0x2000)) != -1) {
// totalRead = totalRead + count;
//
// if (!deletedIPTC) {
// int iptcStart = -1;
// int fbmdStart = -1;
// int fbmdBytesLen = -1;
//
// for (int i = 0; i < buffer.length; ++i) {
// if (buffer[i] == (byte) 0xFF && buffer[i + 1] == (byte) 0xED)
// iptcStart = i;
// else if (buffer[i] == (byte) 'F' && buffer[i + 1] == (byte) 'B'
// && buffer[i + 2] == (byte) 'M' && buffer[i + 3] == (byte) 'D') {
// fbmdStart = i;
// fbmdBytesLen = buffer[i - 10] << 24 | (buffer[i - 9] & 0xFF) << 16 |
// (buffer[i - 8] & 0xFF) << 8 | (buffer[i - 7] & 0xFF) |
// (buffer[i - 6] & 0xFF);
// break;
// }
// }
//
// if (iptcStart != -1 && fbmdStart != -1 && fbmdBytesLen != -1) {
// final int fbmdDataLen = (iptcStart + (fbmdStart - iptcStart) + (fbmdBytesLen - iptcStart)) - 4;
//
// fos.write(buffer, 0, iptcStart);
// fos.write(buffer, fbmdDataLen + iptcStart, count - fbmdDataLen - iptcStart);
//
// publishProgress(totalRead * 100f / fileSize);
//
// deletedIPTC = true;
// continue;
// }
// }
//
// fos.write(buffer, 0, count);
// publishProgress(totalRead * 100f / fileSize);
// }
// fos.flush();
// }
//
// return outFile;
// } catch (final Exception e) {
// if (logCollector != null)
// logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "doInBackground",
// new Pair<>("context", context.get()),
// new Pair<>("resources", resources),
// new Pair<>("lastNotifId", lastNotifId),
// new Pair<>("downloadNotif", downloadNotif),
// new Pair<>("currentNotifId", currentNotifId),
// new Pair<>("notificationManager", notificationManager));
// if (BuildConfig.DEBUG) Log.e(TAG, "", e);
// }
// return null;
// }
//
// @Override
// protected void onPreExecute() {
// if (fetchListener != null) fetchListener.doBefore();
// }
//
// @Override
// protected void onProgressUpdate(@NonNull final Float... values) {
// if (downloadNotif != null) {
// downloadNotif.setProgress(100, values[0].intValue(), false);
// notificationManager.notify(currentNotifId, downloadNotif.build());
// }
// }
//
// @Override
// protected void onPostExecute(final File result) {
// if (result != null) {
// final Context context = this.context.get();
// context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(result.getAbsoluteFile())));
// MediaScannerConnection.scanFile(context, new String[]{result.getAbsolutePath()}, null, null);
//
// if (notificationManager != null) {
// final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", result);
//
// final ContentResolver contentResolver = context.getContentResolver();
// Bitmap bitmap = null;
// if (Utils.isImage(uri, contentResolver)) {
// try (final InputStream inputStream = contentResolver.openInputStream(uri)) {
// bitmap = BitmapFactory.decodeStream(inputStream);
// } catch (final Exception e) {
// if (logCollector != null)
// logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_1");
// if (BuildConfig.DEBUG) Log.e(TAG, "", e);
// }
// }
//
// if (bitmap == null) {
// final MediaMetadataRetriever retriever = new MediaMetadataRetriever();
// try {
// try {
// retriever.setDataSource(context, uri);
// } catch (final Exception e) {
// retriever.setDataSource(result.getAbsolutePath());
// }
// bitmap = retriever.getFrameAtTime();
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
// try {
// retriever.close();
// } catch (final Exception e) {
// if (logCollector != null)
// logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_2");
// }
// } catch (final Exception e) {
// if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
// if (logCollector != null)
// logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_3");
// }
// }
//
// final String downloadComplete = resources.getString(R.string.downloader_complete);
//
// downloadNotif.setContentText(null).setContentTitle(downloadComplete).setProgress(0, 0, false)
// .setWhen(System.currentTimeMillis()).setOngoing(false).setOnlyAlertOnce(false).setAutoCancel(true)
// .setGroup(NOTIF_GROUP_NAME).setGroupSummary(true).setContentIntent(
// PendingIntent.getActivity(context, 2020, new Intent(Intent.ACTION_VIEW, uri)
// .addFlags(
// Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_FROM_BACKGROUND | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
// .putExtra(Intent.EXTRA_STREAM, uri), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT));
//
// if (bitmap != null)
// downloadNotif.setStyle(new NotificationCompat.BigPictureStyle().setBigContentTitle(downloadComplete).bigPicture(bitmap))
// .setLargeIcon(bitmap).setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL);
//
// notificationManager.cancel(currentNotifId);
// notificationManager.notify(currentNotifId + 1, downloadNotif.build());
// }
// }
//
// if (fetchListener != null) fetchListener.onResult(result);
// }
// }

View File

@ -0,0 +1,42 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.utils.DownloadUtils;
public final class DownloadedCheckerAsyncTask extends AsyncTask<FeedModel, Void, Map<String, List<Boolean>>> {
private static final String TAG = "DownloadedCheckerAsyncTask";
private final OnCheckResultListener listener;
public DownloadedCheckerAsyncTask(final OnCheckResultListener listener) {
this.listener = listener;
}
@Override
protected Map<String, List<Boolean>> doInBackground(final FeedModel... feedModels) {
if (feedModels == null) {
return null;
}
final Map<String, List<Boolean>> map = new HashMap<>();
for (final FeedModel feedModel : feedModels) {
map.put(feedModel.getPostId(), DownloadUtils.checkDownloaded(feedModel));
}
return map;
}
@Override
protected void onPostExecute(final Map<String, List<Boolean>> result) {
if (listener == null) return;
listener.onResult(result);
}
public interface OnCheckResultListener {
void onResult(final Map<String, List<Boolean>> result);
}
}

View File

@ -1,13 +1,11 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
@ -26,9 +24,6 @@ import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector;
public final class PostFetcher extends AsyncTask<Void, Void, FeedModel> {
@ -78,19 +73,6 @@ public final class PostFetcher extends AsyncTask<Void, Void, FeedModel> {
owner.optBoolean("requested_by_viewer")
);
}
final String username = profileModel == null ? "" : profileModel.getUsername();
// to check if file exists
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" +
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/" + username) : ""));
File customDir = null;
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH +
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER)
? ("/" + username)
: ""));
if (!TextUtils.isEmpty(customPath)) customDir = new File(customPath);
}
final long timestamp = media.getLong("taken_at_timestamp");
final boolean isVideo = media.has("is_video") && media.optBoolean("is_video");
@ -138,7 +120,6 @@ public final class PostFetcher extends AsyncTask<Void, Void, FeedModel> {
? null
: media.getJSONObject("location").optString("id"))
.setCommentsCount(commentsCount);
// DownloadUtils.checkExistence(downloadDir, customDir, false, feedModelBuilder);
if (isSlider) {
final JSONArray children = media.getJSONObject("edge_sidecar_to_children").getJSONArray("edges");
final List<PostChild> postModels = new ArrayList<>();
@ -158,7 +139,6 @@ public final class PostFetcher extends AsyncTask<Void, Void, FeedModel> {
.setHeight(childNode.getJSONObject("dimensions").getInt("height"))
.setWidth(childNode.getJSONObject("dimensions").getInt("width"))
.build());
// DownloadUtils.checkExistence(downloadDir, customDir, true, postModels.get(i));
}
feedModelBuilder.setSliderItems(postModels);
}

View File

@ -14,8 +14,14 @@ import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import androidx.transition.ChangeBounds;
import androidx.transition.Transition;
import androidx.transition.TransitionManager;
import androidx.work.Data;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import awais.instagrabber.adapters.FeedAdapterV2;
@ -24,9 +30,11 @@ import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtBottom;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.FeedViewModel;
import awais.instagrabber.workers.DownloadWorker;
public class PostsRecyclerView extends RecyclerView {
private static final String TAG = "PostsRecyclerView";
@ -146,7 +154,7 @@ public class PostsRecyclerView extends RecyclerView {
initAdapter();
initLayoutManager();
initSelf();
initDownloadWorkerListener();
}
private void initTransition() {
@ -189,6 +197,51 @@ public class PostsRecyclerView extends RecyclerView {
dispatchFetchStatus();
}
private void initDownloadWorkerListener() {
WorkManager.getInstance(getContext())
.getWorkInfosByTagLiveData("download")
.observe(lifeCycleOwner, workInfoList -> {
for (final WorkInfo workInfo : workInfoList) {
if (workInfo == null) continue;
final Data progress = workInfo.getProgress();
final float progressPercent = progress.getFloat(DownloadWorker.PROGRESS, 0);
if (progressPercent != 100) continue;
final String url = progress.getString(DownloadWorker.URL);
final List<FeedModel> feedModels = feedViewModel.getList().getValue();
for (int i = 0; i < feedModels.size(); i++) {
final FeedModel feedModel = feedModels.get(i);
final List<String> displayUrls = getDisplayUrl(feedModel);
if (displayUrls.contains(url)) {
feedAdapter.notifyItemChanged(i);
break;
}
}
}
});
}
private List<String> getDisplayUrl(final FeedModel feedModel) {
List<String> urls = Collections.emptyList();
switch (feedModel.getItemType()) {
case MEDIA_TYPE_IMAGE:
case MEDIA_TYPE_VIDEO:
urls = Collections.singletonList(feedModel.getDisplayUrl());
break;
case MEDIA_TYPE_SLIDER:
final List<PostChild> sliderItems = feedModel.getSliderItems();
if (sliderItems != null) {
final ImmutableList.Builder<String> builder = ImmutableList.builder();
for (final PostChild child : sliderItems) {
builder.add(child.getDisplayUrl());
}
urls = builder.build();
}
break;
default:
}
return urls;
}
private void updateLayout() {
post(() -> {
TransitionManager.beginDelayedTransition(this, transition);

View File

@ -6,9 +6,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Animatable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.util.Pair;
@ -55,7 +53,6 @@ import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
@ -65,7 +62,6 @@ import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.adapters.StoriesAdapter;
import awais.instagrabber.asyncs.DownloadAsync;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.asyncs.QuizAction;
import awais.instagrabber.asyncs.RespondAction;
@ -97,8 +93,6 @@ import awaisomereport.LogCollector;
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_THRESHOLD;
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Constants.MARK_AS_SEEN;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
@ -359,7 +353,8 @@ public class StoryViewerFragment extends Fragment {
binding.btnBackward.setVisibility(currentFeedStoryIndex == 0 ? View.INVISIBLE : View.VISIBLE);
binding.btnForward.setVisibility(currentFeedStoryIndex == finalModels.size() - 1 ? View.INVISIBLE : View.VISIBLE);
binding.btnBackward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex - 1), context, true, false));
binding.btnForward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex + 1), context, false, currentFeedStoryIndex == finalModels.size() - 2));
binding.btnForward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex + 1), context, false,
currentFeedStoryIndex == finalModels.size() - 2));
}
binding.imageViewer.setTapListener(simpleOnGestureListener);
@ -517,7 +512,7 @@ public class StoryViewerFragment extends Fragment {
}
storiesViewModel.getList().setValue(Collections.emptyList());
if (currentStoryMediaId == null) return;
final ServiceCallback storyCallback = new ServiceCallback<List<StoryModel>>() {
final ServiceCallback<List<StoryModel>> storyCallback = new ServiceCallback<List<StoryModel>>() {
@Override
public void onSuccess(final List<StoryModel> storyModels) {
fetching = false;
@ -612,43 +607,13 @@ public class StoryViewerFragment extends Fragment {
}
private void downloadStory() {
int error = 0;
final Context context = getContext();
if (context == null) return;
if (currentStory != null) {
File dir = new File(Environment.getExternalStorageDirectory(), "Download");
if (settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = settingsHelper.getString(FOLDER_PATH);
if (!TextUtils.isEmpty(customPath)) dir = new File(customPath);
}
if (settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(currentStoryUsername))
dir = new File(dir, currentStoryUsername);
if (dir.exists() || dir.mkdirs()) {
final String storyUrl = currentStory.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO
? currentStory.getVideoUrl()
: currentStory.getStoryUrl();
final File saveFile = new File(
dir,
currentStory.getStoryMediaId()
+ "_" + currentStory.getTimestamp()
+ DownloadUtils.getFileExtensionFromUrl(storyUrl));
new DownloadAsync(context, storyUrl, saveFile, result -> {
final int toastRes = result != null && result.exists() ? R.string.downloader_complete
: R.string.downloader_error_download_file;
Toast.makeText(context, toastRes, Toast.LENGTH_SHORT).show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else error = 1;
} else error = 2;
if (error == 1)
Toast.makeText(context, R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show();
else if (error == 2)
if (currentStory == null) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
DownloadUtils.download(context, currentStory);
}
private void setupImage() {

View File

@ -26,7 +26,7 @@ public final class StoryModel implements Serializable {
private String[] mentions;
private int position;
private boolean isCurrentSlide = false;
private boolean canReply = false;
private final boolean canReply;
public StoryModel(final String storyMediaId, final String storyUrl, final MediaItemType itemType,
final long timestamp, final String username, final String userId, final boolean canReply) {

View File

@ -44,7 +44,7 @@ public class DeleteImageIntentService extends IntentService {
if (file.exists()) {
deleted = file.delete();
if (!deleted) {
Log.w(TAG, "onHandleIntent: file not delete!");
Log.w(TAG, "onHandleIntent: file not deleted!");
}
} else {
deleted = true;

View File

@ -6,8 +6,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.util.Log;
import android.util.Pair;
import android.webkit.MimeTypeMap;
import android.widget.Toast;
@ -26,21 +24,20 @@ import androidx.work.WorkRequest;
import com.google.gson.Gson;
import java.io.File;
import java.io.FilenameFilter;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Pattern;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.workers.DownloadWorker;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
@ -48,15 +45,6 @@ import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
public final class DownloadUtils {
public static final String WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE;
public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
private static int lastNotificationId = UUID.randomUUID().hashCode();
public synchronized static int getNextDownloadNotificationId(@NonNull final Context context) {
lastNotificationId = lastNotificationId + 1;
if (lastNotificationId == Integer.MAX_VALUE) {
lastNotificationId = UUID.randomUUID().hashCode();
}
return lastNotificationId;
}
@NonNull
private static File getDownloadDir() {
@ -73,6 +61,13 @@ public final class DownloadUtils {
@Nullable
private static File getDownloadDir(@NonNull final Context context, @Nullable final String username) {
return getDownloadDir(context, username, false);
}
@Nullable
private static File getDownloadDir(final Context context,
@Nullable final String username,
final boolean skipCreateDir) {
File dir = getDownloadDir();
if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(username)) {
@ -80,7 +75,7 @@ public final class DownloadUtils {
dir = new File(dir, finaleUsername);
}
if (!dir.exists() && !dir.mkdirs()) {
if (context != null && !skipCreateDir && !dir.exists() && !dir.mkdirs()) {
Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show();
return null;
}
@ -183,42 +178,29 @@ public final class DownloadUtils {
return "";
}
public static void checkExistence(final File downloadDir,
final File customDir,
final boolean isSlider,
@NonNull final BasePostModel model) {
boolean exists = false;
try {
final String displayUrl = model.getDisplayUrl();
int index = displayUrl.indexOf('?');
if (index < 0) {
return;
public static List<Boolean> checkDownloaded(@NonNull final FeedModel feedModel) {
final List<Boolean> checkList = new LinkedList<>();
final File downloadDir = getDownloadDir(null, "@" + feedModel.getProfileModel().getUsername(), true);
switch (feedModel.getItemType()) {
case MEDIA_TYPE_IMAGE:
case MEDIA_TYPE_VIDEO: {
final String url = feedModel.getDisplayUrl();
final File file = getDownloadSaveFile(downloadDir, feedModel.getPostId(), url);
checkList.add(file.exists());
break;
}
final String fileName = model.getPostId() + '_';
final String extension = displayUrl.substring(index - 4, index);
final String fileWithoutPrefix = fileName + '0' + extension;
exists = new File(downloadDir, fileWithoutPrefix).exists();
if (!exists) {
final String fileWithPrefix = fileName + "[\\d]+(|_slide_[\\d]+)(\\.mp4|\\\\" + extension + ")";
final FilenameFilter filenameFilter = (dir, name) -> Pattern.matches(fileWithPrefix, name);
File[] files = downloadDir.listFiles(filenameFilter);
if ((files == null || files.length < 1) && customDir != null)
files = customDir.listFiles(filenameFilter);
if (files != null && files.length >= 1) exists = true;
}
} catch (final Exception e) {
if (Utils.logCollector != null)
Utils.logCollector.appendException(e, LogCollector.LogFile.UTILS, "checkExistence",
new Pair<>("isSlider", isSlider),
new Pair<>("model", model));
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
case MEDIA_TYPE_SLIDER:
final List<PostChild> sliderItems = feedModel.getSliderItems();
for (int i = 0; i < sliderItems.size(); i++) {
final PostChild child = sliderItems.get(i);
final String url = child.getDisplayUrl();
final File file = getDownloadChildSaveFile(downloadDir, feedModel.getPostId(), i + 1, url);
checkList.add(file.exists());
}
break;
default:
}
model.setDownloaded(exists);
return checkList;
}
public static void showDownloadDialog(@NonNull Context context,
@ -253,6 +235,19 @@ public final class DownloadUtils {
DownloadUtils.download(context, feedModel);
}
public static void download(@NonNull final Context context,
@NonNull final StoryModel storyModel) {
final File downloadDir = getDownloadDir(context, "@" + storyModel.getUsername());
final String url = storyModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO
? storyModel.getVideoUrl()
: storyModel.getStoryUrl();
final File saveFile = new File(downloadDir,
storyModel.getStoryMediaId()
+ "_" + storyModel.getTimestamp()
+ DownloadUtils.getFileExtensionFromUrl(url));
download(context, url, saveFile.getAbsolutePath());
}
public static void download(@NonNull final Context context,
@NonNull final FeedModel feedModel) {
download(context, feedModel, -1);

View File

@ -244,7 +244,6 @@ public class ProfileService extends BaseService {
}
final FeedModel feedModel = builder.build();
feedModels.add(feedModel);
// DownloadUtils.checkExistence(downloadDir, customDir, isSlider, model);
}
return new PostsFetchResponse(feedModels, hasNextPage, endCursor);
}

View File

@ -59,8 +59,8 @@ import static awais.instagrabber.utils.Utils.logCollector;
public class DownloadWorker extends Worker {
private static final String TAG = "DownloadWorker";
private static final String PROGRESS = "PROGRESS";
private static final String URL = "URL";
public static final String PROGRESS = "PROGRESS";
public static final String URL = "URL";
private static final String DOWNLOAD_GROUP = "DOWNLOAD_GROUP";
public static final String KEY_DOWNLOAD_REQUEST_JSON = "download_request_json";
@ -167,6 +167,9 @@ public class DownloadWorker extends Worker {
} catch (final Exception e) {
Log.e(TAG, "Error while downloading: " + url, e);
}
setProgressAsync(new Data.Builder().putString(URL, url)
.putFloat(PROGRESS, 100)
.build());
updateDownloadProgress(notificationId, position, total, 100);
}

View File

@ -0,0 +1,10 @@
<!-- drawable/download_circle.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M12 2C17.5 2 22 6.5 22 12C22 17.5 17.5 22 12 22C6.5 22 2 17.5 2 12C2 6.5 6.5 2 12 2M8 17H16V15H8V17M16 10H13.5V6H10.5V10H8L12 14L16 10Z" />
</vector>

View File

@ -66,15 +66,15 @@
tools:text="Full name Full name Full name Full name Full name Full name Full name " />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/isDownloaded"
android:id="@+id/downloaded"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_margin="8dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/ic_cloud_download_24"
app:tint="@color/green_400"
app:srcCompat="@drawable/ic_download_circle_24"
app:tint="@color/green_A400"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageView
@ -94,7 +94,7 @@
android:layout_height="match_parent"
android:background="@color/black_a50"
android:visibility="gone"
tools:visibility="visible">
tools:visibility="gone">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="28dp"

View File

@ -77,9 +77,20 @@
<color name="brown_800">#4E342E</color>
<color name="brown_900">#3E2723</color>
<color name="green_200">#03dac6</color>
<color name="green_50">#E8F5E9</color>
<color name="green_100">#C8E6C9</color>
<color name="green_200">#A5D6A7</color>
<color name="green_300">#81C784</color>
<color name="green_400">#66BB6A</color>
<color name="green_500">#018786</color>
<color name="green_500">#4CAF50</color>
<color name="green_600">#43A047</color>
<color name="green_700">#388E3C</color>
<color name="green_800">#2E7D32</color>
<color name="green_900">#1B5E20</color>
<color name="green_A100">#B9F6CA</color>
<color name="green_A200">#69F0AE</color>
<color name="green_A400">#00E676</color>
<color name="green_A700">#00C853</color>
<color name="purple_200">#bb86fc</color>
<color name="purple_600">#4b01d0</color>