Browse Source

Add downloaded indicator to Posts view

renovate/org.robolectric-robolectric-4.x
Ammar Githam 5 years ago
parent
commit
d0bfe73ae6
  1. 26
      app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java
  2. 482
      app/src/main/java/awais/instagrabber/asyncs/DownloadAsync.java
  3. 42
      app/src/main/java/awais/instagrabber/asyncs/DownloadedCheckerAsyncTask.java
  4. 20
      app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java
  5. 55
      app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java
  6. 49
      app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java
  7. 2
      app/src/main/java/awais/instagrabber/models/StoryModel.java
  8. 2
      app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java
  9. 93
      app/src/main/java/awais/instagrabber/utils/DownloadUtils.java
  10. 1
      app/src/main/java/awais/instagrabber/webservices/ProfileService.java
  11. 7
      app/src/main/java/awais/instagrabber/workers/DownloadWorker.java
  12. 10
      app/src/main/res/drawable/ic_download_circle_24.xml
  13. 8
      app/src/main/res/layout/item_feed_grid.xml
  14. 15
      app/src/main/res/values/color.xml

26
app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java

@ -1,5 +1,6 @@
package awais.instagrabber.adapters.viewholder; package awais.instagrabber.adapters.viewholder;
import android.content.res.ColorStateList;
import android.net.Uri; import android.net.Uri;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -17,6 +18,7 @@ import java.util.List;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.FeedAdapterV2; import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.asyncs.DownloadedCheckerAsyncTask;
import awais.instagrabber.databinding.ItemFeedGridBinding; import awais.instagrabber.databinding.ItemFeedGridBinding;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild; import awais.instagrabber.models.PostChild;
@ -131,5 +133,29 @@ public class FeedGridItemViewHolder extends RecyclerView.ViewHolder {
.setImageRequest(requestBuilder) .setImageRequest(requestBuilder)
.setOldController(binding.postImage.getController()); .setOldController(binding.postImage.getController());
binding.postImage.setController(builder.build()); 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);
} }
} }

482
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<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);
// }
// }

42
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<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);
}
}

20
app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java

@ -1,13 +1,11 @@
package awais.instagrabber.asyncs; package awais.instagrabber.asyncs;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log; import android.util.Log;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
@ -26,9 +24,6 @@ import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector; 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; import static awais.instagrabber.utils.Utils.logCollector;
public final class PostFetcher extends AsyncTask<Void, Void, FeedModel> { 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") 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 long timestamp = media.getLong("taken_at_timestamp");
final boolean isVideo = media.has("is_video") && media.optBoolean("is_video"); 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 ? null
: media.getJSONObject("location").optString("id")) : media.getJSONObject("location").optString("id"))
.setCommentsCount(commentsCount); .setCommentsCount(commentsCount);
// DownloadUtils.checkExistence(downloadDir, customDir, false, feedModelBuilder);
if (isSlider) { if (isSlider) {
final JSONArray children = media.getJSONObject("edge_sidecar_to_children").getJSONArray("edges"); final JSONArray children = media.getJSONObject("edge_sidecar_to_children").getJSONArray("edges");
final List<PostChild> postModels = new ArrayList<>(); 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")) .setHeight(childNode.getJSONObject("dimensions").getInt("height"))
.setWidth(childNode.getJSONObject("dimensions").getInt("width")) .setWidth(childNode.getJSONObject("dimensions").getInt("width"))
.build()); .build());
// DownloadUtils.checkExistence(downloadDir, customDir, true, postModels.get(i));
} }
feedModelBuilder.setSliderItems(postModels); feedModelBuilder.setSliderItems(postModels);
} }

55
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.ChangeBounds;
import androidx.transition.Transition; import androidx.transition.Transition;
import androidx.transition.TransitionManager; 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.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import awais.instagrabber.adapters.FeedAdapterV2; import awais.instagrabber.adapters.FeedAdapterV2;
@ -24,9 +30,11 @@ import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtBottom; import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtBottom;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.FeedViewModel; import awais.instagrabber.viewmodels.FeedViewModel;
import awais.instagrabber.workers.DownloadWorker;
public class PostsRecyclerView extends RecyclerView { public class PostsRecyclerView extends RecyclerView {
private static final String TAG = "PostsRecyclerView"; private static final String TAG = "PostsRecyclerView";
@ -146,7 +154,7 @@ public class PostsRecyclerView extends RecyclerView {
initAdapter(); initAdapter();
initLayoutManager(); initLayoutManager();
initSelf(); initSelf();
initDownloadWorkerListener();
} }
private void initTransition() { private void initTransition() {
@ -189,6 +197,51 @@ public class PostsRecyclerView extends RecyclerView {
dispatchFetchStatus(); 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() { private void updateLayout() {
post(() -> { post(() -> {
TransitionManager.beginDelayedTransition(this, transition); TransitionManager.beginDelayedTransition(this, transition);

49
app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java

@ -6,9 +6,7 @@ import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.os.Handler; import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.util.Pair; 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.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.Collections; import java.util.Collections;
@ -65,7 +62,6 @@ import java.util.List;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.StoriesAdapter; import awais.instagrabber.adapters.StoriesAdapter;
import awais.instagrabber.asyncs.DownloadAsync;
import awais.instagrabber.asyncs.PostFetcher; import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.asyncs.QuizAction; import awais.instagrabber.asyncs.QuizAction;
import awais.instagrabber.asyncs.RespondAction; 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_THRESHOLD;
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_VELOCITY_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.Constants.MARK_AS_SEEN;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper; 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.btnBackward.setVisibility(currentFeedStoryIndex == 0 ? View.INVISIBLE : View.VISIBLE);
binding.btnForward.setVisibility(currentFeedStoryIndex == finalModels.size() - 1 ? 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.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); binding.imageViewer.setTapListener(simpleOnGestureListener);
@ -517,7 +512,7 @@ public class StoryViewerFragment extends Fragment {
} }
storiesViewModel.getList().setValue(Collections.emptyList()); storiesViewModel.getList().setValue(Collections.emptyList());
if (currentStoryMediaId == null) return; if (currentStoryMediaId == null) return;
final ServiceCallback storyCallback = new ServiceCallback<List<StoryModel>>() {
final ServiceCallback<List<StoryModel>> storyCallback = new ServiceCallback<List<StoryModel>>() {
@Override @Override
public void onSuccess(final List<StoryModel> storyModels) { public void onSuccess(final List<StoryModel> storyModels) {
fetching = false; fetching = false;
@ -612,43 +607,13 @@ public class StoryViewerFragment extends Fragment {
} }
private void downloadStory() { private void downloadStory() {
int error = 0;
final Context context = getContext(); final Context context = getContext();
if (context == null) return; 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(); Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
DownloadUtils.download(context, currentStory);
} }
private void setupImage() { private void setupImage() {

2
app/src/main/java/awais/instagrabber/models/StoryModel.java

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

2
app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java

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

93
app/src/main/java/awais/instagrabber/utils/DownloadUtils.java

@ -6,8 +6,6 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Environment; import android.os.Environment;
import android.util.Log;
import android.util.Pair;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.widget.Toast; import android.widget.Toast;
@ -26,21 +24,20 @@ import androidx.work.WorkRequest;
import com.google.gson.Gson; import com.google.gson.Gson;
import java.io.File; import java.io.File;
import java.io.FilenameFilter;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild; import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.workers.DownloadWorker; import awais.instagrabber.workers.DownloadWorker;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Constants.FOLDER_PATH; import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; 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 final class DownloadUtils {
public static final String WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE; public static final String WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE;
public static final String[] PERMS = new String[]{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 @NonNull
private static File getDownloadDir() { private static File getDownloadDir() {
@ -73,6 +61,13 @@ public final class DownloadUtils {
@Nullable @Nullable
private static File getDownloadDir(@NonNull final Context context, @Nullable final String username) { 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(); File dir = getDownloadDir();
if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(username)) { if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(username)) {
@ -80,7 +75,7 @@ public final class DownloadUtils {
dir = new File(dir, finaleUsername); 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(); Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show();
return null; return null;
} }
@ -183,42 +178,29 @@ public final class DownloadUtils {
return ""; 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;
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());
} }
} 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);
break;
default:
} }
model.setDownloaded(exists);
return checkList;
} }
public static void showDownloadDialog(@NonNull Context context, public static void showDownloadDialog(@NonNull Context context,
@ -253,6 +235,19 @@ public final class DownloadUtils {
DownloadUtils.download(context, feedModel); 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, public static void download(@NonNull final Context context,
@NonNull final FeedModel feedModel) { @NonNull final FeedModel feedModel) {
download(context, feedModel, -1); download(context, feedModel, -1);

1
app/src/main/java/awais/instagrabber/webservices/ProfileService.java

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

7
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 { public class DownloadWorker extends Worker {
private static final String TAG = "DownloadWorker"; 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"; private static final String DOWNLOAD_GROUP = "DOWNLOAD_GROUP";
public static final String KEY_DOWNLOAD_REQUEST_JSON = "download_request_json"; public static final String KEY_DOWNLOAD_REQUEST_JSON = "download_request_json";
@ -167,6 +167,9 @@ public class DownloadWorker extends Worker {
} catch (final Exception e) { } catch (final Exception e) {
Log.e(TAG, "Error while downloading: " + url, 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); updateDownloadProgress(notificationId, position, total, 100);
} }

10
app/src/main/res/drawable/ic_download_circle_24.xml

@ -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>

8
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:text="Full name Full name Full name Full name Full name Full name Full name " />
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/isDownloaded"
android:id="@+id/downloaded"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_margin="8dp" android:layout_margin="8dp"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="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" /> tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
@ -94,7 +94,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/black_a50" android:background="@color/black_a50"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible">
tools:visibility="gone">
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:layout_width="28dp" android:layout_width="28dp"

15
app/src/main/res/values/color.xml

@ -77,9 +77,20 @@
<color name="brown_800">#4E342E</color> <color name="brown_800">#4E342E</color>
<color name="brown_900">#3E2723</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_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_200">#bb86fc</color>
<color name="purple_600">#4b01d0</color> <color name="purple_600">#4b01d0</color>

Loading…
Cancel
Save