From d0bfe73ae6c18d2d6201e683d9a9e61643504066 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Fri, 6 Nov 2020 21:46:31 +0900 Subject: [PATCH] Add downloaded indicator to Posts view --- .../viewholder/FeedGridItemViewHolder.java | 26 + .../instagrabber/asyncs/DownloadAsync.java | 482 +++++++++--------- .../asyncs/DownloadedCheckerAsyncTask.java | 42 ++ .../instagrabber/asyncs/PostFetcher.java | 20 - .../customviews/PostsRecyclerView.java | 55 +- .../fragments/StoryViewerFragment.java | 49 +- .../awais/instagrabber/models/StoryModel.java | 2 +- .../services/DeleteImageIntentService.java | 2 +- .../instagrabber/utils/DownloadUtils.java | 95 ++-- .../webservices/ProfileService.java | 1 - .../instagrabber/workers/DownloadWorker.java | 7 +- .../res/drawable/ic_download_circle_24.xml | 10 + app/src/main/res/layout/item_feed_grid.xml | 8 +- app/src/main/res/values/color.xml | 15 +- 14 files changed, 449 insertions(+), 365 deletions(-) create mode 100644 app/src/main/java/awais/instagrabber/asyncs/DownloadedCheckerAsyncTask.java create mode 100644 app/src/main/res/drawable/ic_download_circle_24.xml diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java index db0545ac..53a372b8 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java @@ -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 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); } } diff --git a/app/src/main/java/awais/instagrabber/asyncs/DownloadAsync.java b/app/src/main/java/awais/instagrabber/asyncs/DownloadAsync.java index c470e478..41ea2fd9 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/DownloadAsync.java +++ b/app/src/main/java/awais/instagrabber/asyncs/DownloadAsync.java @@ -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 { - private static final String TAG = "DownloadAsync"; - - private static int lastNotifId = 1; - private final int currentNotifId; - private final AtomicReference context; - private final File outFile; - private final String url; - private final FetchListener 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 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); - } -} \ No newline at end of file +// 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 { +// private static final String TAG = "DownloadAsync"; +// +// private static int lastNotifId = 1; +// private final int currentNotifId; +// private final AtomicReference context; +// private final File outFile; +// private final String url; +// private final FetchListener 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 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); +// } +// } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/asyncs/DownloadedCheckerAsyncTask.java b/app/src/main/java/awais/instagrabber/asyncs/DownloadedCheckerAsyncTask.java new file mode 100644 index 00000000..49e1ae0e --- /dev/null +++ b/app/src/main/java/awais/instagrabber/asyncs/DownloadedCheckerAsyncTask.java @@ -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>> { + private static final String TAG = "DownloadedCheckerAsyncTask"; + + private final OnCheckResultListener listener; + + public DownloadedCheckerAsyncTask(final OnCheckResultListener listener) { + this.listener = listener; + } + + @Override + protected Map> doInBackground(final FeedModel... feedModels) { + if (feedModels == null) { + return null; + } + final Map> map = new HashMap<>(); + for (final FeedModel feedModel : feedModels) { + map.put(feedModel.getPostId(), DownloadUtils.checkDownloaded(feedModel)); + } + return map; + } + + @Override + protected void onPostExecute(final Map> result) { + if (listener == null) return; + listener.onResult(result); + } + + public interface OnCheckResultListener { + void onResult(final Map> result); + } +} diff --git a/app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java index b2fbfdc3..39ba0d9e 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java @@ -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 { @@ -78,19 +73,6 @@ public final class PostFetcher extends AsyncTask { 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 { ? 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 postModels = new ArrayList<>(); @@ -158,7 +139,6 @@ public final class PostFetcher extends AsyncTask { .setHeight(childNode.getJSONObject("dimensions").getInt("height")) .setWidth(childNode.getJSONObject("dimensions").getInt("width")) .build()); - // DownloadUtils.checkExistence(downloadDir, customDir, true, postModels.get(i)); } feedModelBuilder.setSliderItems(postModels); } diff --git a/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java b/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java index a3e909ce..280eb06c 100644 --- a/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java +++ b/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java @@ -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 feedModels = feedViewModel.getList().getValue(); + for (int i = 0; i < feedModels.size(); i++) { + final FeedModel feedModel = feedModels.get(i); + final List displayUrls = getDisplayUrl(feedModel); + if (displayUrls.contains(url)) { + feedAdapter.notifyItemChanged(i); + break; + } + } + } + }); + } + + private List getDisplayUrl(final FeedModel feedModel) { + List 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 sliderItems = feedModel.getSliderItems(); + if (sliderItems != null) { + final ImmutableList.Builder 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); diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java index aa9ae2bd..60897ec1 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java @@ -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>() { + final ServiceCallback> storyCallback = new ServiceCallback>() { @Override public void onSuccess(final List 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() { diff --git a/app/src/main/java/awais/instagrabber/models/StoryModel.java b/app/src/main/java/awais/instagrabber/models/StoryModel.java index 5cf43fbc..9d3959c8 100755 --- a/app/src/main/java/awais/instagrabber/models/StoryModel.java +++ b/app/src/main/java/awais/instagrabber/models/StoryModel.java @@ -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) { diff --git a/app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java b/app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java index fb0f3203..b9442527 100644 --- a/app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java +++ b/app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java @@ -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; diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index c718ed74..97919a65 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -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 checkDownloaded(@NonNull final FeedModel feedModel) { + final List 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 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); diff --git a/app/src/main/java/awais/instagrabber/webservices/ProfileService.java b/app/src/main/java/awais/instagrabber/webservices/ProfileService.java index a221f3fb..47766743 100644 --- a/app/src/main/java/awais/instagrabber/webservices/ProfileService.java +++ b/app/src/main/java/awais/instagrabber/webservices/ProfileService.java @@ -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); } diff --git a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java index b3b03829..79e4793e 100644 --- a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java +++ b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java @@ -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); } diff --git a/app/src/main/res/drawable/ic_download_circle_24.xml b/app/src/main/res/drawable/ic_download_circle_24.xml new file mode 100644 index 00000000..2a6f0257 --- /dev/null +++ b/app/src/main/res/drawable/ic_download_circle_24.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_feed_grid.xml b/app/src/main/res/layout/item_feed_grid.xml index d456d916..b7f58807 100644 --- a/app/src/main/res/layout/item_feed_grid.xml +++ b/app/src/main/res/layout/item_feed_grid.xml @@ -66,15 +66,15 @@ tools:text="Full name Full name Full name Full name Full name Full name Full name " /> + tools:visibility="gone"> #4E342E #3E2723 - #03dac6 + #E8F5E9 + #C8E6C9 + #A5D6A7 + #81C784 #66BB6A - #018786 + #4CAF50 + #43A047 + #388E3C + #2E7D32 + #1B5E20 + #B9F6CA + #69F0AE + #00E676 + #00C853 #bb86fc #4b01d0