1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-11-22 22:57:29 +00:00

Add downloaded indicator to Posts view

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

View File

@ -1,5 +1,6 @@
package awais.instagrabber.adapters.viewholder; 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);
} }
} }

View File

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

View File

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

View File

@ -1,13 +1,11 @@
package awais.instagrabber.asyncs; 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);
} }

View File

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

View File

@ -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) { 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)
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() {

View File

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

View File

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

View File

@ -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, public static List<Boolean> checkDownloaded(@NonNull final FeedModel feedModel) {
final File customDir, final List<Boolean> checkList = new LinkedList<>();
final boolean isSlider, final File downloadDir = getDownloadDir(null, "@" + feedModel.getProfileModel().getUsername(), true);
@NonNull final BasePostModel model) { switch (feedModel.getItemType()) {
boolean exists = false; case MEDIA_TYPE_IMAGE:
case MEDIA_TYPE_VIDEO: {
try { final String url = feedModel.getDisplayUrl();
final String displayUrl = model.getDisplayUrl(); final File file = getDownloadSaveFile(downloadDir, feedModel.getPostId(), url);
int index = displayUrl.indexOf('?'); checkList.add(file.exists());
if (index < 0) { break;
return;
} }
final String fileName = model.getPostId() + '_'; case MEDIA_TYPE_SLIDER:
final String extension = displayUrl.substring(index - 4, index); final List<PostChild> sliderItems = feedModel.getSliderItems();
for (int i = 0; i < sliderItems.size(); i++) {
final String fileWithoutPrefix = fileName + '0' + extension; final PostChild child = sliderItems.get(i);
exists = new File(downloadDir, fileWithoutPrefix).exists(); final String url = child.getDisplayUrl();
if (!exists) { final File file = getDownloadChildSaveFile(downloadDir, feedModel.getPostId(), i + 1, url);
final String fileWithPrefix = fileName + "[\\d]+(|_slide_[\\d]+)(\\.mp4|\\\\" + extension + ")"; checkList.add(file.exists());
final FilenameFilter filenameFilter = (dir, name) -> Pattern.matches(fileWithPrefix, name); }
break;
File[] files = downloadDir.listFiles(filenameFilter); default:
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);
} }
return checkList;
model.setDownloaded(exists);
} }
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);

View File

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

View File

@ -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"; public static final String PROGRESS = "PROGRESS";
private static final String URL = "URL"; 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);
} }

View File

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

View File

@ -66,15 +66,15 @@
tools:text="Full name Full name Full name Full name Full name Full name Full name " /> 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:srcCompat="@drawable/ic_download_circle_24"
app:tint="@color/green_400" 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"

View File

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