Preparing migration to Room

This commit is contained in:
Ammar Githam 2020-12-07 19:47:03 +09:00
parent f94694fff3
commit 126a0f7f4b
30 changed files with 1701 additions and 850 deletions

View File

@ -108,7 +108,8 @@
<activity
android:name=".activities.Login"
android:label="@string/login"
android:parentActivityName=".activities.MainActivity">
android:parentActivityName=".activities.MainActivity"
android:theme="@style/AppTheme.Light.White">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.MainActivity" />

View File

@ -13,7 +13,6 @@ import java.text.SimpleDateFormat;
import java.util.UUID;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.LocaleUtils;
import awais.instagrabber.utils.SettingsHelper;
import awaisomereport.CrashReporter;
@ -21,7 +20,6 @@ import awaisomereport.LogCollector;
import static awais.instagrabber.utils.CookieUtils.NET_COOKIE_MANAGER;
import static awais.instagrabber.utils.Utils.clipboardManager;
import static awais.instagrabber.utils.Utils.dataBox;
import static awais.instagrabber.utils.Utils.datetimeParser;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
@ -58,11 +56,6 @@ public final class InstaGrabberApplication extends Application {
CookieHandler.setDefault(NET_COOKIE_MANAGER);
final Context appContext = getApplicationContext();
if (dataBox == null)
dataBox = DataBox.getInstance(appContext);
if (settingsHelper == null)
settingsHelper = new SettingsHelper(this);

View File

@ -26,9 +26,11 @@ import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.IntentModel;
import awais.instagrabber.models.enums.IntentModelType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.IntentUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
public final class DirectDownload extends AppCompatActivity {
private static final int NOTIFICATION_ID = 1900000000;
@ -88,6 +90,7 @@ public final class DirectDownload extends AppCompatActivity {
}
private synchronized void doDownload() {
CookieUtils.setupCookies(Utils.settingsHelper.getString(Constants.COOKIE));
notificationManager = NotificationManagerCompat.from(getApplicationContext());
final Intent intent = getIntent();
final String action = intent.getAction();

View File

@ -14,21 +14,21 @@ import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
import awais.instagrabber.databinding.PrefAccountSwitcherBinding;
import awais.instagrabber.db.entities.Account;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DataBox;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class AccountSwitcherAdapter extends ListAdapter<DataBox.CookieModel, AccountSwitcherAdapter.ViewHolder> {
public class AccountSwitcherAdapter extends ListAdapter<Account, AccountSwitcherAdapter.ViewHolder> {
private static final String TAG = "AccountSwitcherAdapter";
private static final DiffUtil.ItemCallback<DataBox.CookieModel> DIFF_CALLBACK = new DiffUtil.ItemCallback<DataBox.CookieModel>() {
private static final DiffUtil.ItemCallback<Account> DIFF_CALLBACK = new DiffUtil.ItemCallback<Account>() {
@Override
public boolean areItemsTheSame(@NonNull final DataBox.CookieModel oldItem, @NonNull final DataBox.CookieModel newItem) {
public boolean areItemsTheSame(@NonNull final Account oldItem, @NonNull final Account newItem) {
return oldItem.getUid().equals(newItem.getUid());
}
@Override
public boolean areContentsTheSame(@NonNull final DataBox.CookieModel oldItem, @NonNull final DataBox.CookieModel newItem) {
public boolean areContentsTheSame(@NonNull final Account oldItem, @NonNull final Account newItem) {
return oldItem.getUid().equals(newItem.getUid());
}
};
@ -53,7 +53,7 @@ public class AccountSwitcherAdapter extends ListAdapter<DataBox.CookieModel, Acc
@Override
public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {
final DataBox.CookieModel model = getItem(position);
final Account model = getItem(position);
if (model == null) return;
final String cookie = settingsHelper.getString(Constants.COOKIE);
final boolean isCurrent = model.getCookie().equals(cookie);
@ -61,11 +61,11 @@ public class AccountSwitcherAdapter extends ListAdapter<DataBox.CookieModel, Acc
}
public interface OnAccountClickListener {
void onAccountClick(final DataBox.CookieModel model, final boolean isCurrent);
void onAccountClick(final Account model, final boolean isCurrent);
}
public interface OnAccountLongClickListener {
boolean onAccountLongClick(final DataBox.CookieModel model, final boolean isCurrent);
boolean onAccountLongClick(final Account model, final boolean isCurrent);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
@ -78,7 +78,7 @@ public class AccountSwitcherAdapter extends ListAdapter<DataBox.CookieModel, Acc
}
@SuppressLint("SetTextI18n")
public void bind(final DataBox.CookieModel model,
public void bind(final Account model,
final boolean isCurrent,
final OnAccountClickListener clickListener,
final OnAccountLongClickListener longClickListener) {

View File

@ -20,8 +20,8 @@ import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.FavoriteViewHolder;
import awais.instagrabber.databinding.ItemFavSectionHeaderBinding;
import awais.instagrabber.databinding.ItemSuggestionBinding;
import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.DataBox;
public class FavoritesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@ -102,7 +102,7 @@ public class FavoritesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
return getItem(position).isHeader() ? 0 : 1;
}
public void submitList(@Nullable final List<DataBox.FavoriteModel> list) {
public void submitList(@Nullable final List<Favorite> list) {
if (list == null) {
differ.submitList(null);
return;
@ -110,7 +110,7 @@ public class FavoritesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
differ.submitList(sectionAndSort(list));
}
public void submitList(@Nullable final List<DataBox.FavoriteModel> list, @Nullable final Runnable commitCallback) {
public void submitList(@Nullable final List<Favorite> list, @Nullable final Runnable commitCallback) {
if (list == null) {
differ.submitList(null, commitCallback);
return;
@ -119,8 +119,8 @@ public class FavoritesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
}
@NonNull
private List<FavoriteModelOrHeader> sectionAndSort(@NonNull final List<DataBox.FavoriteModel> list) {
final List<DataBox.FavoriteModel> listCopy = new ArrayList<>(list);
private List<FavoriteModelOrHeader> sectionAndSort(@NonNull final List<Favorite> list) {
final List<Favorite> listCopy = new ArrayList<>(list);
Collections.sort(listCopy, (o1, o2) -> {
if (o1.getType() == o2.getType()) return 0;
// keep users at top
@ -133,7 +133,7 @@ public class FavoritesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
});
final List<FavoriteModelOrHeader> modelOrHeaders = new ArrayList<>();
for (int i = 0; i < listCopy.size(); i++) {
final DataBox.FavoriteModel model = listCopy.get(i);
final Favorite model = listCopy.get(i);
final FavoriteModelOrHeader prev = modelOrHeaders.isEmpty() ? null : modelOrHeaders.get(modelOrHeaders.size() - 1);
boolean prevWasSameType = prev != null && prev.model.getType() == model.getType();
if (prevWasSameType) {
@ -156,7 +156,7 @@ public class FavoritesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
private static class FavoriteModelOrHeader {
FavoriteType header;
DataBox.FavoriteModel model;
Favorite model;
boolean isHeader() {
return header != null;
@ -164,11 +164,11 @@ public class FavoritesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
}
public interface OnFavoriteClickListener {
void onClick(final DataBox.FavoriteModel model);
void onClick(final Favorite model);
}
public interface OnFavoriteLongClickListener {
boolean onLongClick(final DataBox.FavoriteModel model);
boolean onLongClick(final Favorite model);
}
public static class FavSectionViewHolder extends RecyclerView.ViewHolder {

View File

@ -7,9 +7,9 @@ import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.adapters.FavoritesAdapter;
import awais.instagrabber.databinding.ItemSuggestionBinding;
import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DataBox;
public class FavoriteViewHolder extends RecyclerView.ViewHolder {
private static final String TAG = "FavoriteViewHolder";
@ -22,7 +22,7 @@ public class FavoriteViewHolder extends RecyclerView.ViewHolder {
binding.isVerified.setVisibility(View.GONE);
}
public void bind(final DataBox.FavoriteModel model,
public void bind(final Favorite model,
final FavoritesAdapter.OnFavoriteClickListener clickListener,
final FavoritesAdapter.OnFavoriteLongClickListener longClickListener) {
// Log.d(TAG, "bind: " + model);

View File

@ -16,13 +16,11 @@ import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.NotificationModel;
import awais.instagrabber.models.enums.NotificationType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.LocaleUtils;
import awais.instagrabber.utils.NetworkUtils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class NotificationsFetcher extends AsyncTask<Void, Void, List<NotificationModel>> {
private static final String TAG = "NotificationsFetcher";
@ -37,8 +35,6 @@ public final class NotificationsFetcher extends AsyncTask<Void, Void, List<Notif
protected List<NotificationModel> doInBackground(final Void... voids) {
List<NotificationModel> result = new ArrayList<>();
final String url = "https://www.instagram.com/accounts/activity/?__a=1";
CookieUtils.setupCookies(settingsHelper.getString(Constants.COOKIE));
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(false);

View File

@ -17,11 +17,9 @@ import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.NetworkUtils;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
@ -39,7 +37,6 @@ public final class PostFetcher extends AsyncTask<Void, Void, FeedModel> {
@Override
protected FeedModel doInBackground(final Void... voids) {
CookieUtils.setupCookies(Utils.settingsHelper.getString(Constants.COOKIE)); // <- direct download
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL("https://www.instagram.com/p/" + shortCode + "/?__a=1").openConnection();

View File

@ -0,0 +1,182 @@
package awais.instagrabber.db.datasources;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.db.entities.Account;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.TextUtils;
import static awais.instagrabber.utils.DataBox.KEY_COOKIE;
import static awais.instagrabber.utils.DataBox.KEY_FULL_NAME;
import static awais.instagrabber.utils.DataBox.KEY_ID;
import static awais.instagrabber.utils.DataBox.KEY_PROFILE_PIC;
import static awais.instagrabber.utils.DataBox.KEY_UID;
import static awais.instagrabber.utils.DataBox.KEY_USERNAME;
import static awais.instagrabber.utils.DataBox.TABLE_COOKIES;
public class AccountDataSource {
private static final String TAG = AccountDataSource.class.getSimpleName();
private static AccountDataSource INSTANCE;
private final DataBox dataBox;
private AccountDataSource(@NonNull Context context) {
dataBox = DataBox.getInstance(context);
}
public static synchronized AccountDataSource getInstance(@NonNull Context context) {
if (INSTANCE == null) {
INSTANCE = new AccountDataSource(context);
}
return INSTANCE;
}
@Nullable
public final Account getAccount(final String uid) {
Account cookie = null;
try (final SQLiteDatabase db = dataBox.getReadableDatabase();
final Cursor cursor = db.query(TABLE_COOKIES,
new String[]{
KEY_ID,
KEY_UID,
KEY_USERNAME,
KEY_COOKIE,
KEY_FULL_NAME,
KEY_PROFILE_PIC
},
KEY_UID + "=?",
new String[]{uid},
null,
null,
null)) {
if (cursor != null && cursor.moveToFirst())
cookie = new Account(
cursor.getInt(cursor.getColumnIndex(KEY_ID)),
cursor.getString(cursor.getColumnIndex(KEY_UID)),
cursor.getString(cursor.getColumnIndex(KEY_USERNAME)),
cursor.getString(cursor.getColumnIndex(KEY_COOKIE)),
cursor.getString(cursor.getColumnIndex(KEY_FULL_NAME)),
cursor.getString(cursor.getColumnIndex(KEY_PROFILE_PIC))
);
}
return cookie;
}
@NonNull
public final List<Account> getAllAccounts() {
final List<Account> cookies = new ArrayList<>();
try (final SQLiteDatabase db = dataBox.getReadableDatabase();
final Cursor cursor = db.query(TABLE_COOKIES,
new String[]{
KEY_ID,
KEY_UID,
KEY_USERNAME,
KEY_COOKIE,
KEY_FULL_NAME,
KEY_PROFILE_PIC
},
null,
null,
null,
null,
null)) {
if (cursor != null && cursor.moveToFirst()) {
do {
cookies.add(new Account(
cursor.getInt(cursor.getColumnIndex(KEY_ID)),
cursor.getString(cursor.getColumnIndex(KEY_UID)),
cursor.getString(cursor.getColumnIndex(KEY_USERNAME)),
cursor.getString(cursor.getColumnIndex(KEY_COOKIE)),
cursor.getString(cursor.getColumnIndex(KEY_FULL_NAME)),
cursor.getString(cursor.getColumnIndex(KEY_PROFILE_PIC))
));
} while (cursor.moveToNext());
}
}
return cookies;
}
// public final void insertOrUpdateAccount(@NonNull final Account account) {
// insertOrUpdateAccount(
// account.getUid(),
// account.getUsername(),
// account.getCookie(),
// account.getFullName(),
// account.getProfilePic()
// );
// }
public final void insertOrUpdateAccount(final String uid,
final String username,
final String cookie,
final String fullName,
final String profilePicUrl) {
if (TextUtils.isEmpty(uid)) return;
try (final SQLiteDatabase db = dataBox.getWritableDatabase()) {
db.beginTransaction();
try {
final ContentValues values = new ContentValues();
values.put(KEY_USERNAME, username);
values.put(KEY_COOKIE, cookie);
values.put(KEY_UID, uid);
values.put(KEY_FULL_NAME, fullName);
values.put(KEY_PROFILE_PIC, profilePicUrl);
final int rows = db.update(TABLE_COOKIES, values, KEY_UID + "=?", new String[]{uid});
if (rows != 1) {
db.insert(TABLE_COOKIES, null, values);
}
db.setTransactionSuccessful();
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e(TAG, "Error", e);
} finally {
db.endTransaction();
}
}
}
public final synchronized void deleteAccount(@NonNull final Account account) {
final String cookieModelUid = account.getUid();
if (!TextUtils.isEmpty(cookieModelUid)) {
try (final SQLiteDatabase db = dataBox.getWritableDatabase()) {
db.beginTransaction();
try {
final int rowsDeleted = db.delete(TABLE_COOKIES, KEY_UID + "=? AND " + KEY_USERNAME + "=? AND " + KEY_COOKIE + "=?",
new String[]{cookieModelUid, account.getUsername(), account.getCookie()});
if (rowsDeleted > 0) db.setTransactionSuccessful();
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
} finally {
db.endTransaction();
}
}
}
}
public final synchronized void deleteAllAccounts() {
try (final SQLiteDatabase db = dataBox.getWritableDatabase()) {
db.beginTransaction();
try {
final int rowsDeleted = db.delete(TABLE_COOKIES, null, null);
if (rowsDeleted > 0) db.setTransactionSuccessful();
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
} finally {
db.endTransaction();
}
}
}
}

View File

@ -0,0 +1,180 @@
package awais.instagrabber.db.datasources;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.TextUtils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.DataBox.FAV_COL_DATE_ADDED;
import static awais.instagrabber.utils.DataBox.FAV_COL_DISPLAY_NAME;
import static awais.instagrabber.utils.DataBox.FAV_COL_ID;
import static awais.instagrabber.utils.DataBox.FAV_COL_PIC_URL;
import static awais.instagrabber.utils.DataBox.FAV_COL_QUERY;
import static awais.instagrabber.utils.DataBox.FAV_COL_TYPE;
import static awais.instagrabber.utils.DataBox.TABLE_FAVORITES;
import static awais.instagrabber.utils.Utils.logCollector;
public class FavoriteDataSource {
private static final String TAG = FavoriteDataSource.class.getSimpleName();
private static FavoriteDataSource INSTANCE;
private final DataBox dataBox;
private FavoriteDataSource(@NonNull Context context) {
dataBox = DataBox.getInstance(context);
}
public static synchronized FavoriteDataSource getInstance(@NonNull Context context) {
if (INSTANCE == null) {
INSTANCE = new FavoriteDataSource(context);
}
return INSTANCE;
}
@Nullable
public final Favorite getFavorite(@NonNull final String query, @NonNull final FavoriteType type) {
try (final SQLiteDatabase db = dataBox.getReadableDatabase();
final Cursor cursor = db.query(TABLE_FAVORITES,
new String[]{
FAV_COL_ID,
FAV_COL_QUERY,
FAV_COL_TYPE,
FAV_COL_DISPLAY_NAME,
FAV_COL_PIC_URL,
FAV_COL_DATE_ADDED
},
FAV_COL_QUERY + "=?" + " AND " + FAV_COL_TYPE + "=?",
new String[]{
query,
type.toString()
},
null,
null,
null)) {
if (cursor != null && cursor.moveToFirst()) {
FavoriteType favoriteType = null;
try {
favoriteType = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE)));
} catch (IllegalArgumentException ignored) {}
return new Favorite(
cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)),
cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)),
favoriteType,
cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)),
cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)),
new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED)))
);
}
}
return null;
}
@NonNull
public final List<Favorite> getAllFavorites() {
final List<Favorite> favorites = new ArrayList<>();
try (final SQLiteDatabase db = dataBox.getWritableDatabase()) {
try (final Cursor cursor = db.query(TABLE_FAVORITES,
new String[]{
FAV_COL_ID,
FAV_COL_QUERY,
FAV_COL_TYPE,
FAV_COL_DISPLAY_NAME,
FAV_COL_PIC_URL,
FAV_COL_DATE_ADDED
},
null,
null,
null,
null,
null)) {
if (cursor != null && cursor.moveToFirst()) {
db.beginTransaction();
Favorite tempFav;
do {
FavoriteType type = null;
try {
type = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE)));
} catch (IllegalArgumentException ignored) {}
tempFav = new Favorite(
cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)),
cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)),
type,
cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)),
cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)),
new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED)))
);
favorites.add(tempFav);
} while (cursor.moveToNext());
db.endTransaction();
}
} catch (final Exception e) {
Log.e(TAG, "", e);
}
}
return favorites;
}
public final synchronized Favorite insertOrUpdateFavorite(@NonNull final Favorite model) {
final String query = model.getQuery();
if (!TextUtils.isEmpty(query)) {
try (final SQLiteDatabase db = dataBox.getWritableDatabase()) {
db.beginTransaction();
try {
dataBox.insertOrUpdateFavorite(db, model);
db.setTransactionSuccessful();
} catch (Exception e) {
if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "insertOrUpdateFavorite");
}
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error adding/updating favorite", e);
}
} finally {
db.endTransaction();
}
}
return getFavorite(model.getQuery(), model.getType());
}
return null;
}
public final synchronized void deleteFavorite(@NonNull final String query, @NonNull final FavoriteType type) {
if (!TextUtils.isEmpty(query)) {
try (final SQLiteDatabase db = dataBox.getWritableDatabase()) {
db.beginTransaction();
try {
final int rowsDeleted = db.delete(TABLE_FAVORITES,
FAV_COL_QUERY + "=?" +
" AND " + FAV_COL_TYPE + "=?",
new String[]{query, type.toString()});
if (rowsDeleted > 0) db.setTransactionSuccessful();
} catch (final Exception e) {
if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "deleteFavorite");
}
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error", e);
}
} finally {
db.endTransaction();
}
}
}
}
}

View File

@ -0,0 +1,114 @@
package awais.instagrabber.db.entities;
import androidx.annotation.NonNull;
import androidx.core.util.ObjectsCompat;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import awais.instagrabber.utils.TextUtils;
@Entity(tableName = "cookies")
public class Account {
@PrimaryKey
@ColumnInfo(name = "id")
private final int id;
@ColumnInfo(name = "uid")
private final String uid;
@ColumnInfo(name = "username")
private final String username;
@ColumnInfo(name = "cookie")
private final String cookie;
@ColumnInfo(name = "full_name")
private final String fullName;
@ColumnInfo(name = "profile_pic")
private final String profilePic;
private boolean selected;
public Account(final int id,
final String uid,
final String username,
final String cookie,
final String fullName,
final String profilePic) {
this.id = id;
this.uid = uid;
this.username = username;
this.cookie = cookie;
this.fullName = fullName;
this.profilePic = profilePic;
}
public int getId() {
return id;
}
public String getUid() {
return uid;
}
public String getUsername() {
return username;
}
public String getCookie() {
return cookie;
}
public String getFullName() {
return fullName;
}
public String getProfilePic() {
return profilePic;
}
public boolean isSelected() {
return selected;
}
public void setSelected(final boolean selected) {
this.selected = selected;
}
public boolean isValid() {
return !TextUtils.isEmpty(uid)
&& !TextUtils.isEmpty(username)
&& !TextUtils.isEmpty(cookie);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Account that = (Account) o;
return ObjectsCompat.equals(uid, that.uid) &&
ObjectsCompat.equals(username, that.username) &&
ObjectsCompat.equals(cookie, that.cookie);
}
@Override
public int hashCode() {
return ObjectsCompat.hash(uid, username, cookie);
}
@NonNull
@Override
public String toString() {
return "Account{" +
"uid='" + uid + '\'' +
", username='" + username + '\'' +
", cookie='" + cookie + '\'' +
", fullName='" + fullName + '\'' +
", profilePic='" + profilePic + '\'' +
", selected=" + selected +
'}';
}
}

View File

@ -0,0 +1,103 @@
package awais.instagrabber.db.entities;
import androidx.annotation.NonNull;
import androidx.core.util.ObjectsCompat;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import java.util.Date;
import awais.instagrabber.models.enums.FavoriteType;
@Entity(tableName = "favorites")
public class Favorite {
@PrimaryKey
@ColumnInfo(name = "id")
private final int id;
@ColumnInfo(name = "query_text")
private final String query;
@ColumnInfo(name = "type")
private final FavoriteType type;
@ColumnInfo(name = "display_name")
private final String displayName;
@ColumnInfo(name = "pic_url")
private final String picUrl;
@ColumnInfo(name = "date_added")
private final Date dateAdded;
public Favorite(final int id,
final String query,
final FavoriteType type,
final String displayName,
final String picUrl,
final Date dateAdded) {
this.id = id;
this.query = query;
this.type = type;
this.displayName = displayName;
this.picUrl = picUrl;
this.dateAdded = dateAdded;
}
public int getId() {
return id;
}
public String getQuery() {
return query;
}
public FavoriteType getType() {
return type;
}
public String getDisplayName() {
return displayName;
}
public String getPicUrl() {
return picUrl;
}
public Date getDateAdded() {
return dateAdded;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Favorite that = (Favorite) o;
return id == that.id &&
ObjectsCompat.equals(query, that.query) &&
type == that.type &&
ObjectsCompat.equals(displayName, that.displayName) &&
ObjectsCompat.equals(picUrl, that.picUrl) &&
ObjectsCompat.equals(dateAdded, that.dateAdded);
}
@Override
public int hashCode() {
return ObjectsCompat.hash(id, query, type, displayName, picUrl, dateAdded);
}
@NonNull
@Override
public String toString() {
return "FavoriteModel{" +
"id=" + id +
", query='" + query + '\'' +
", type=" + type +
", displayName='" + displayName + '\'' +
", picUrl='" + picUrl + '\'' +
", dateAdded=" + dateAdded +
'}';
}
}

View File

@ -0,0 +1,131 @@
package awais.instagrabber.db.repositories;
import java.util.List;
import awais.instagrabber.db.datasources.AccountDataSource;
import awais.instagrabber.db.entities.Account;
import awais.instagrabber.utils.AppExecutors;
public class AccountRepository {
private static final String TAG = AccountRepository.class.getSimpleName();
private static AccountRepository instance;
private final AppExecutors appExecutors;
private final AccountDataSource accountDataSource;
// private List<Account> cachedAccounts;
private AccountRepository(final AppExecutors appExecutors, final AccountDataSource accountDataSource) {
this.appExecutors = appExecutors;
this.accountDataSource = accountDataSource;
}
public static AccountRepository getInstance(final AppExecutors appExecutors, final AccountDataSource accountDataSource) {
if (instance == null) {
instance = new AccountRepository(appExecutors, accountDataSource);
}
return instance;
}
public void getAccount(final String uid,
final RepositoryCallback<Account> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
final Account account = accountDataSource.getAccount(uid);
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
if (account == null) {
callback.onDataNotAvailable();
return;
}
callback.onSuccess(account);
});
});
}
public void getAllAccounts(final RepositoryCallback<List<Account>> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
final List<Account> accounts = accountDataSource.getAllAccounts();
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
if (accounts == null) {
callback.onDataNotAvailable();
return;
}
// cachedAccounts = accounts;
callback.onSuccess(accounts);
});
});
}
public void insertOrUpdateAccounts(final List<Account> accounts,
final RepositoryCallback<Void> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
for (final Account account : accounts) {
accountDataSource.insertOrUpdateAccount(account.getUid(),
account.getUsername(),
account.getCookie(),
account.getFullName(),
account.getProfilePic());
}
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
callback.onSuccess(null);
});
});
}
public void insertOrUpdateAccount(final String uid,
final String username,
final String cookie,
final String fullName,
final String profilePicUrl,
final RepositoryCallback<Account> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
accountDataSource.insertOrUpdateAccount(uid, username, cookie, fullName, profilePicUrl);
final Account updated = accountDataSource.getAccount(uid);
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
if (updated == null) {
callback.onDataNotAvailable();
return;
}
callback.onSuccess(updated);
});
});
}
public void deleteAccount(final Account account,
final RepositoryCallback<Void> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
accountDataSource.deleteAccount(account);
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
callback.onSuccess(null);
});
});
}
public void deleteAllAccounts(final RepositoryCallback<Void> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
accountDataSource.deleteAllAccounts();
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
callback.onSuccess(null);
});
});
}
}

View File

@ -0,0 +1,92 @@
package awais.instagrabber.db.repositories;
import java.util.List;
import awais.instagrabber.db.datasources.FavoriteDataSource;
import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.AppExecutors;
public class FavoriteRepository {
private static final String TAG = FavoriteRepository.class.getSimpleName();
private static FavoriteRepository instance;
private final AppExecutors appExecutors;
private final FavoriteDataSource favoriteDataSource;
private FavoriteRepository(final AppExecutors appExecutors, final FavoriteDataSource favoriteDataSource) {
this.appExecutors = appExecutors;
this.favoriteDataSource = favoriteDataSource;
}
public static FavoriteRepository getInstance(final AppExecutors appExecutors, final FavoriteDataSource favoriteDataSource) {
if (instance == null) {
instance = new FavoriteRepository(appExecutors, favoriteDataSource);
}
return instance;
}
public void getFavorite(final String query, final FavoriteType type, final RepositoryCallback<Favorite> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
final Favorite favorite = favoriteDataSource.getFavorite(query, type);
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
if (favorite == null) {
callback.onDataNotAvailable();
return;
}
callback.onSuccess(favorite);
});
});
}
public void getAllFavorites(final RepositoryCallback<List<Favorite>> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
final List<Favorite> favorites = favoriteDataSource.getAllFavorites();
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
if (favorites == null) {
callback.onDataNotAvailable();
return;
}
callback.onSuccess(favorites);
});
});
}
public void insertOrUpdateFavorite(final Favorite favorite,
final RepositoryCallback<Favorite> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
final Favorite updated = favoriteDataSource.insertOrUpdateFavorite(favorite);
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
if (updated == null) {
callback.onDataNotAvailable();
return;
}
callback.onSuccess(updated);
});
});
}
public void deleteFavorite(final String query,
final FavoriteType type,
final RepositoryCallback<Void> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
favoriteDataSource.deleteFavorite(query, type);
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
callback.onSuccess(null);
});
});
}
}

View File

@ -0,0 +1,11 @@
package awais.instagrabber.db.repositories;
import androidx.annotation.MainThread;
public interface RepositoryCallback<T> {
@MainThread
void onSuccess(T result);
@MainThread
void onDataNotAvailable();
}

View File

@ -21,9 +21,13 @@ import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.adapters.AccountSwitcherAdapter;
import awais.instagrabber.databinding.DialogAccountSwitcherBinding;
import awais.instagrabber.db.datasources.AccountDataSource;
import awais.instagrabber.db.entities.Account;
import awais.instagrabber.db.repositories.AccountRepository;
import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
@ -31,9 +35,20 @@ import static awais.instagrabber.utils.Utils.settingsHelper;
public class AccountSwitcherDialogFragment extends DialogFragment {
private final OnAddAccountClickListener onAddAccountClickListener;
private AccountRepository accountRepository;
private OnAddAccountClickListener onAddAccountClickListener;
private DialogAccountSwitcherBinding binding;
public AccountSwitcherDialogFragment() {
accountRepository = AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(getContext()));
}
public AccountSwitcherDialogFragment(final OnAddAccountClickListener onAddAccountClickListener) {
this.onAddAccountClickListener = onAddAccountClickListener;
accountRepository = AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(getContext()));
}
private final AccountSwitcherAdapter.OnAccountClickListener accountClickListener = (model, isCurrent) -> {
if (isCurrent) {
dismiss();
@ -59,8 +74,18 @@ public class AccountSwitcherDialogFragment extends DialogFragment {
new AlertDialog.Builder(context)
.setMessage(getString(R.string.quick_access_confirm_delete, model.getUsername()))
.setPositiveButton(R.string.yes, (dialog, which) -> {
Utils.dataBox.delUserCookie(model);
dismiss();
if (accountRepository == null) return;
accountRepository.deleteAccount(model, new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {
dismiss();
}
@Override
public void onDataNotAvailable() {
dismiss();
}
});
})
.setNegativeButton(R.string.cancel, null)
.show();
@ -68,10 +93,6 @@ public class AccountSwitcherDialogFragment extends DialogFragment {
return true;
};
public AccountSwitcherDialogFragment(final OnAddAccountClickListener onAddAccountClickListener) {
this.onAddAccountClickListener = onAddAccountClickListener;
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
@ -102,11 +123,19 @@ public class AccountSwitcherDialogFragment extends DialogFragment {
private void init() {
final AccountSwitcherAdapter adapter = new AccountSwitcherAdapter(accountClickListener, accountLongClickListener);
binding.accounts.setAdapter(adapter);
final List<DataBox.CookieModel> allUsers = Utils.dataBox.getAllCookies();
if (allUsers == null) return;
final String cookie = settingsHelper.getString(Constants.COOKIE);
sortUserList(cookie, allUsers);
adapter.submitList(allUsers);
if (accountRepository == null) return;
accountRepository.getAllAccounts(new RepositoryCallback<List<Account>>() {
@Override
public void onSuccess(final List<Account> accounts) {
if (accounts == null) return;
final String cookie = settingsHelper.getString(Constants.COOKIE);
sortUserList(cookie, accounts);
adapter.submitList(accounts);
}
@Override
public void onDataNotAvailable() {}
});
binding.addAccountBtn.setOnClickListener(v -> {
if (onAddAccountClickListener == null) return;
onAddAccountClickListener.onAddAccountClick(this);
@ -125,9 +154,9 @@ public class AccountSwitcherDialogFragment extends DialogFragment {
* @param cookie active cookie
* @param allUsers list of users
*/
private void sortUserList(final String cookie, final List<DataBox.CookieModel> allUsers) {
private void sortUserList(final String cookie, final List<Account> allUsers) {
boolean sortByName = true;
for (final DataBox.CookieModel user : allUsers) {
for (final Account user : allUsers) {
if (TextUtils.isEmpty(user.getFullName())) {
sortByName = false;
break;

View File

@ -4,6 +4,7 @@ import android.app.Dialog;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
@ -93,7 +94,7 @@ public class RestoreBackupDialogFragment extends DialogFragment {
return;
}
binding.btnRestore.setEnabled(false);
binding.btnRestore.setOnClickListener(v -> {
binding.btnRestore.setOnClickListener(v -> new Handler().post(() -> {
int flags = 0;
if (binding.cbFavorites.isChecked()) {
flags |= ExportImportUtils.FLAG_FAVORITES;
@ -122,7 +123,7 @@ public class RestoreBackupDialogFragment extends DialogFragment {
} catch (IncorrectPasswordException e) {
binding.passwordField.setError("Incorrect password");
}
});
}));
binding.etPassword.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {}

View File

@ -28,9 +28,12 @@ import awais.instagrabber.adapters.FavoritesAdapter;
import awais.instagrabber.asyncs.LocationFetcher;
import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.databinding.FragmentFavoritesBinding;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.db.datasources.FavoriteDataSource;
import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.db.repositories.FavoriteRepository;
import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.FavoritesViewModel;
public class FavoritesFragment extends Fragment {
@ -41,6 +44,13 @@ public class FavoritesFragment extends Fragment {
private RecyclerView root;
private FavoritesViewModel favoritesViewModel;
private FavoritesAdapter adapter;
private FavoriteRepository favoriteRepository;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
favoriteRepository = FavoriteRepository.getInstance(new AppExecutors(), FavoriteDataSource.getInstance(getContext()));
}
@NonNull
@Override
@ -68,9 +78,16 @@ public class FavoritesFragment extends Fragment {
if (favoritesViewModel == null || adapter == null) return;
// refresh list every time in onViewStateRestored since it is cheaper than implementing pull down to refresh
favoritesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList);
final List<DataBox.FavoriteModel> allFavorites = Utils.dataBox.getAllFavorites();
favoritesViewModel.getList().postValue(allFavorites);
fetchMissingInfo(allFavorites);
favoriteRepository.getAllFavorites(new RepositoryCallback<List<Favorite>>() {
@Override
public void onSuccess(final List<Favorite> favorites) {
favoritesViewModel.getList().postValue(favorites);
fetchMissingInfo(favorites);
}
@Override
public void onDataNotAvailable() {}
});
}
private void init() {
@ -114,30 +131,43 @@ public class FavoritesFragment extends Fragment {
if (context == null) return false;
new MaterialAlertDialogBuilder(context)
.setMessage(getString(R.string.quick_access_confirm_delete, model.getQuery()))
.setPositiveButton(R.string.yes, (d, which) -> {
Utils.dataBox.deleteFavorite(model.getQuery(), model.getType());
d.dismiss();
favoritesViewModel.getList().postValue(Utils.dataBox.getAllFavorites());
})
.setPositiveButton(R.string.yes, (d, which) -> favoriteRepository
.deleteFavorite(model.getQuery(), model.getType(), new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {
d.dismiss();
favoriteRepository.getAllFavorites(new RepositoryCallback<List<Favorite>>() {
@Override
public void onSuccess(final List<Favorite> result) {
favoritesViewModel.getList().postValue(result);
}
@Override
public void onDataNotAvailable() {}
});
}
@Override
public void onDataNotAvailable() {}
}))
.setNegativeButton(R.string.no, null)
.show();
return true;
});
binding.favoriteList.setAdapter(adapter);
// favoritesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList);
}
private void fetchMissingInfo(final List<DataBox.FavoriteModel> allFavorites) {
private void fetchMissingInfo(final List<Favorite> allFavorites) {
final Runnable runnable = () -> {
final List<DataBox.FavoriteModel> updatedList = new ArrayList<>(allFavorites);
final List<Favorite> updatedList = new ArrayList<>(allFavorites);
// cyclic barrier is to make the async calls synchronous
final CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> {
// Log.d(TAG, "fetchMissingInfo: barrier action");
favoritesViewModel.getList().postValue(new ArrayList<>(updatedList));
});
try {
for (final DataBox.FavoriteModel model : allFavorites) {
for (final Favorite model : allFavorites) {
cyclicBarrier.reset();
// if the model has missing pic or display name (for user and location), fetch those details
switch (model.getType()) {
@ -145,27 +175,37 @@ public class FavoritesFragment extends Fragment {
if (TextUtils.isEmpty(model.getDisplayName())
|| TextUtils.isEmpty(model.getPicUrl())) {
new LocationFetcher(model.getQuery(), result -> {
try {
if (result == null) return;
final int i = updatedList.indexOf(model);
updatedList.remove(i);
final DataBox.FavoriteModel updated = new DataBox.FavoriteModel(
model.getId(),
model.getQuery(),
model.getType(),
result.getName(),
result.getSdProfilePic(),
model.getDateAdded()
);
Utils.dataBox.addOrUpdateFavorite(updated);
updatedList.add(i, updated);
} finally {
try {
cyclicBarrier.await();
} catch (BrokenBarrierException | InterruptedException e) {
Log.e(TAG, "fetchMissingInfo: ", e);
if (result == null) return;
final int i = updatedList.indexOf(model);
updatedList.remove(i);
final Favorite updated = new Favorite(
model.getId(),
model.getQuery(),
model.getType(),
result.getName(),
result.getSdProfilePic(),
model.getDateAdded()
);
favoriteRepository.insertOrUpdateFavorite(updated, new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
updatedList.add(i, updated);
try {
cyclicBarrier.await();
} catch (BrokenBarrierException | InterruptedException e) {
Log.e(TAG, "fetchMissingInfo: ", e);
}
}
}
@Override
public void onDataNotAvailable() {
try {
cyclicBarrier.await();
} catch (BrokenBarrierException | InterruptedException e) {
Log.e(TAG, "fetchMissingInfo: ", e);
}
}
});
}).execute();
cyclicBarrier.await();
}
@ -174,27 +214,37 @@ public class FavoritesFragment extends Fragment {
if (TextUtils.isEmpty(model.getDisplayName())
|| TextUtils.isEmpty(model.getPicUrl())) {
new ProfileFetcher(model.getQuery(), result -> {
try {
if (result == null) return;
final int i = updatedList.indexOf(model);
updatedList.remove(i);
final DataBox.FavoriteModel updated = new DataBox.FavoriteModel(
model.getId(),
model.getQuery(),
model.getType(),
result.getName(),
result.getSdProfilePic(),
model.getDateAdded()
);
Utils.dataBox.addOrUpdateFavorite(updated);
updatedList.add(i, updated);
} finally {
try {
cyclicBarrier.await();
} catch (BrokenBarrierException | InterruptedException e) {
Log.e(TAG, "fetchMissingInfo: ", e);
if (result == null) return;
final int i = updatedList.indexOf(model);
updatedList.remove(i);
final Favorite updated = new Favorite(
model.getId(),
model.getQuery(),
model.getType(),
result.getName(),
result.getSdProfilePic(),
model.getDateAdded()
);
favoriteRepository.insertOrUpdateFavorite(updated, new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
try {
cyclicBarrier.await();
} catch (BrokenBarrierException | InterruptedException e) {
Log.e(TAG, "fetchMissingInfo: ", e);
}
}
}
@Override
public void onDataNotAvailable() {
try {
cyclicBarrier.await();
} catch (BrokenBarrierException | InterruptedException e) {
Log.e(TAG, "fetchMissingInfo: ", e);
}
}
});
updatedList.add(i, updated);
}).execute();
cyclicBarrier.await();
}

View File

@ -49,15 +49,19 @@ import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.databinding.FragmentHashtagBinding;
import awais.instagrabber.databinding.LayoutHashtagDetailsBinding;
import awais.instagrabber.db.datasources.FavoriteDataSource;
import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.db.repositories.FavoriteRepository;
import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
@ -454,40 +458,61 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
} else {
hashtagDetailsBinding.btnFollowTag.setVisibility(View.GONE);
}
final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG);
final boolean isFav = favorite != null;
hashtagDetailsBinding.favChip.setVisibility(View.VISIBLE);
hashtagDetailsBinding.favChip.setChipIconResource(isFav ? R.drawable.ic_star_check_24
: R.drawable.ic_outline_star_plus_24);
hashtagDetailsBinding.favChip.setText(isFav ? R.string.favorite_short : R.string.add_to_favorites);
hashtagDetailsBinding.favChip.setOnClickListener(v -> {
final DataBox.FavoriteModel fav = Utils.dataBox.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG);
final boolean isFavorite = fav != null;
final String message;
if (isFavorite) {
Utils.dataBox.deleteFavorite(hashtag.substring(1), FavoriteType.HASHTAG);
hashtagDetailsBinding.favChip.setText(R.string.add_to_favorites);
hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
message = getString(R.string.removed_from_favs);
} else {
Utils.dataBox.addOrUpdateFavorite(new DataBox.FavoriteModel(
-1,
hashtag.substring(1),
FavoriteType.HASHTAG,
hashtagModel.getName(),
null,
new Date()
));
hashtagDetailsBinding.favChip.setText(R.string.favorite_short);
final FavoriteRepository favoriteRepository = FavoriteRepository
.getInstance(new AppExecutors(), FavoriteDataSource.getInstance(getContext()));
favoriteRepository.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG, new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
message = getString(R.string.added_to_favs);
hashtagDetailsBinding.favChip.setText(R.string.favorite_short);
}
@Override
public void onDataNotAvailable() {
hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
hashtagDetailsBinding.favChip.setText(R.string.add_to_favorites);
}
final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG);
snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss())
.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
.setAnchorView(fragmentActivity.getBottomNavView())
.show();
});
hashtagDetailsBinding.favChip.setOnClickListener(
v -> favoriteRepository.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG, new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
favoriteRepository.deleteFavorite(hashtag.substring(1), FavoriteType.HASHTAG, new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {
hashtagDetailsBinding.favChip.setText(R.string.add_to_favorites);
hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
showSnackbar(getString(R.string.removed_from_favs));
}
@Override
public void onDataNotAvailable() {}
});
}
@Override
public void onDataNotAvailable() {
favoriteRepository.insertOrUpdateFavorite(new Favorite(
-1,
hashtag.substring(1),
FavoriteType.HASHTAG,
hashtagModel.getName(),
null,
new Date()
), new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
hashtagDetailsBinding.favChip.setText(R.string.favorite_short);
hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
showSnackbar(getString(R.string.added_to_favs));
}
@Override
public void onDataNotAvailable() {}
});
}
}));
hashtagDetailsBinding.mainHashtagImage.setImageURI(hashtagModel.getSdProfilePic());
final String postCount = String.valueOf(hashtagModel.getPostCount());
final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline, postCount));
@ -504,6 +529,14 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
});
}
private void showSnackbar(final String message) {
final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG);
snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss())
.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
.setAnchorView(fragmentActivity.getBottomNavView())
.show();
}
private void fetchStories() {
if (!isLoggedIn) return;
storiesFetching = true;

View File

@ -52,15 +52,19 @@ import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.databinding.FragmentLocationBinding;
import awais.instagrabber.databinding.LayoutLocationDetailsBinding;
import awais.instagrabber.db.datasources.FavoriteDataSource;
import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.db.repositories.FavoriteRepository;
import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.LocationModel;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
@ -443,39 +447,63 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
locationDetailsBinding.locationUrl.setVisibility(View.VISIBLE);
locationDetailsBinding.locationUrl.setText(TextUtils.getSpannableUrl(url));
}
final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(locationId, FavoriteType.LOCATION);
final boolean isFav = favorite != null;
final FavoriteDataSource dataSource = FavoriteDataSource.getInstance(getContext());
final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(new AppExecutors(), dataSource);
locationDetailsBinding.favChip.setVisibility(View.VISIBLE);
locationDetailsBinding.favChip.setChipIconResource(isFav ? R.drawable.ic_star_check_24
: R.drawable.ic_outline_star_plus_24);
locationDetailsBinding.favChip.setText(isFav ? R.string.favorite_short : R.string.add_to_favorites);
locationDetailsBinding.favChip.setOnClickListener(v -> {
final DataBox.FavoriteModel fav = Utils.dataBox.getFavorite(locationId, FavoriteType.LOCATION);
final boolean isFavorite = fav != null;
final String message;
if (isFavorite) {
Utils.dataBox.deleteFavorite(locationId, FavoriteType.LOCATION);
locationDetailsBinding.favChip.setText(R.string.add_to_favorites);
locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
message = getString(R.string.removed_from_favs);
} else {
Utils.dataBox.addOrUpdateFavorite(new DataBox.FavoriteModel(
-1,
locationId,
FavoriteType.LOCATION,
locationModel.getName(),
locationModel.getSdProfilePic(),
new Date()
));
locationDetailsBinding.favChip.setText(R.string.favorite_short);
favoriteRepository.getFavorite(locationId, FavoriteType.LOCATION, new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
message = getString(R.string.added_to_favs);
locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
locationDetailsBinding.favChip.setText(R.string.favorite_short);
}
final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG);
snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss())
.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
.setAnchorView(fragmentActivity.getBottomNavView())
.show();
@Override
public void onDataNotAvailable() {
locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
locationDetailsBinding.favChip.setText(R.string.add_to_favorites);
}
});
locationDetailsBinding.favChip.setOnClickListener(v -> {
favoriteRepository.getFavorite(locationId, FavoriteType.LOCATION, new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
favoriteRepository.deleteFavorite(locationId, FavoriteType.LOCATION, new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {
locationDetailsBinding.favChip.setText(R.string.add_to_favorites);
locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
showSnackbar(getString(R.string.removed_from_favs));
}
@Override
public void onDataNotAvailable() {}
});
}
@Override
public void onDataNotAvailable() {
favoriteRepository.insertOrUpdateFavorite(new Favorite(
-1,
locationId,
FavoriteType.LOCATION,
locationModel.getName(),
locationModel.getSdProfilePic(),
new Date()
), new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
locationDetailsBinding.favChip.setText(R.string.favorite_short);
locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
showSnackbar(getString(R.string.added_to_favs));
}
@Override
public void onDataNotAvailable() {}
});
}
});
});
locationDetailsBinding.mainLocationImage.setOnClickListener(v -> {
if (hasStories) {
@ -487,6 +515,14 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
});
}
private void showSnackbar(final String message) {
final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG);
snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss())
.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
.setAnchorView(fragmentActivity.getBottomNavView())
.show();
}
private void fetchStories() {
if (isLoggedIn) {
storiesFetching = true;

View File

@ -43,6 +43,8 @@ import awais.instagrabber.webservices.FriendshipService;
import awais.instagrabber.webservices.NewsService;
import awais.instagrabber.webservices.ServiceCallback;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class NotificationsViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "NotificationsViewer";
@ -189,10 +191,12 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
}
private void init() {
final Context context = getContext();
CookieUtils.setupCookies(settingsHelper.getString(Constants.COOKIE));
binding.swipeRefreshLayout.setOnRefreshListener(this);
notificationViewModel = new ViewModelProvider(this).get(NotificationViewModel.class);
final NotificationsAdapter adapter = new NotificationsAdapter(clickListener, mentionClickListener);
binding.rvComments.setLayoutManager(new LinearLayoutManager(getContext()));
binding.rvComments.setLayoutManager(new LinearLayoutManager(context));
binding.rvComments.setAdapter(adapter);
notificationViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList);
onRefresh();

View File

@ -63,6 +63,13 @@ import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.PrimaryActionModeCallback.CallbacksHelper;
import awais.instagrabber.databinding.FragmentProfileBinding;
import awais.instagrabber.databinding.LayoutProfileDetailsBinding;
import awais.instagrabber.db.datasources.AccountDataSource;
import awais.instagrabber.db.datasources.FavoriteDataSource;
import awais.instagrabber.db.entities.Account;
import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.db.repositories.AccountRepository;
import awais.instagrabber.db.repositories.FavoriteRepository;
import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.dialogs.ProfilePicDialogFragment;
import awais.instagrabber.fragments.PostViewV2Fragment;
@ -75,9 +82,9 @@ import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse;
import awais.instagrabber.repositories.responses.FriendshipRepoRestrictRootResponse;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
@ -283,6 +290,8 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
};
private LayoutProfileDetailsBinding profileDetailsBinding;
private AccountRepository accountRepository;
private FavoriteRepository favoriteRepository;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -290,6 +299,9 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
fragmentActivity = (MainActivity) requireActivity();
friendshipService = FriendshipService.getInstance();
storiesService = StoriesService.getInstance();
final AppExecutors appExecutors = new AppExecutors();
accountRepository = AccountRepository.getInstance(appExecutors, AccountDataSource.getInstance(getContext()));
favoriteRepository = FavoriteRepository.getInstance(appExecutors, FavoriteDataSource.getInstance(getContext()));
setHasOptionsMenu(true);
}
@ -498,19 +510,26 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
setUsernameDelayed();
fetchProfileDetails();
};
boolean found = false;
final DataBox.CookieModel cookieModel = Utils.dataBox.getCookie(uid);
if (cookieModel != null) {
final String username = cookieModel.getUsername();
if (!TextUtils.isEmpty(username)) {
found = true;
fetchListener.onResult("@" + username);
accountRepository.getAccount(uid, new RepositoryCallback<Account>() {
@Override
public void onSuccess(final Account account) {
boolean found = false;
if (account != null) {
final String username = account.getUsername();
if (!TextUtils.isEmpty(username)) {
found = true;
fetchListener.onResult("@" + username);
}
}
if (!found) {
// if not in database, fetch info from instagram
new UsernameFetcher(uid, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
}
if (!found) {
// if not in database, fetch info from instagram
new UsernameFetcher(uid, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public void onDataNotAvailable() {}
});
return;
}
fetchProfileDetails();
@ -556,9 +575,19 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
setupButtons(profileId, myId);
if (!profileId.equals(myId)) {
profileDetailsBinding.favCb.setVisibility(View.VISIBLE);
final boolean isFav = Utils.dataBox.getFavorite(username.substring(1), FavoriteType.USER) != null;
profileDetailsBinding.favCb.setChecked(isFav);
profileDetailsBinding.favCb.setButtonDrawable(isFav ? R.drawable.ic_star_check_24 : R.drawable.ic_outline_star_plus_24);
favoriteRepository.getFavorite(username.substring(1), FavoriteType.USER, new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
profileDetailsBinding.favCb.setChecked(true);
profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_star_check_24);
}
@Override
public void onDataNotAvailable() {
profileDetailsBinding.favCb.setChecked(false);
profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24);
}
});
} else {
profileDetailsBinding.favCb.setVisibility(View.GONE);
}
@ -842,41 +871,67 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
});
profileDetailsBinding.favCb.setOnCheckedChangeListener((buttonView, isChecked) -> {
// do not do anything if state matches the db, as listener is set before profile details are set
final Context context = getContext();
if (context == null) return;
final String finalUsername = username.startsWith("@") ? username.substring(1) : username;
final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(finalUsername, FavoriteType.USER);
if ((isChecked && favorite != null) || (!isChecked && favorite == null)) {
return;
}
buttonView.setVisibility(View.GONE);
profileDetailsBinding.favProgress.setVisibility(View.VISIBLE);
final String message;
if (isChecked) {
final DataBox.FavoriteModel model = new DataBox.FavoriteModel(
-1,
finalUsername,
FavoriteType.USER,
profileModel.getName(),
profileModel.getSdProfilePic(),
new Date()
);
Utils.dataBox.addOrUpdateFavorite(model);
profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_star_check_24);
message = getString(R.string.added_to_favs);
} else {
Utils.dataBox.deleteFavorite(finalUsername, FavoriteType.USER);
message = getString(R.string.removed_from_favs);
profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24);
}
final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG);
snackbar.setAction(R.string.ok, v -> snackbar.dismiss())
.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
.setAnchorView(fragmentActivity.getBottomNavView())
.show();
profileDetailsBinding.favProgress.setVisibility(View.GONE);
profileDetailsBinding.favCb.setVisibility(View.VISIBLE);
favoriteRepository.getFavorite(finalUsername, FavoriteType.USER, new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
if (isChecked) return; // already a fav
buttonView.setVisibility(View.GONE);
profileDetailsBinding.favProgress.setVisibility(View.VISIBLE);
favoriteRepository.deleteFavorite(finalUsername, FavoriteType.USER, new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {
profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24);
profileDetailsBinding.favProgress.setVisibility(View.GONE);
profileDetailsBinding.favCb.setVisibility(View.VISIBLE);
showSnackbar(getString(R.string.removed_from_favs));
}
@Override
public void onDataNotAvailable() {}
});
}
@Override
public void onDataNotAvailable() {
if (!isChecked) return; // not in fav already
buttonView.setVisibility(View.GONE);
profileDetailsBinding.favProgress.setVisibility(View.VISIBLE);
final Favorite model = new Favorite(
-1,
finalUsername,
FavoriteType.USER,
profileModel.getName(),
profileModel.getSdProfilePic(),
new Date()
);
favoriteRepository.insertOrUpdateFavorite(model, new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_star_check_24);
profileDetailsBinding.favProgress.setVisibility(View.GONE);
profileDetailsBinding.favCb.setVisibility(View.VISIBLE);
showSnackbar(getString(R.string.added_to_favs));
}
@Override
public void onDataNotAvailable() {}
});
}
});
});
}
private void showSnackbar(final String message) {
final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG);
snackbar.setAction(R.string.ok, v -> snackbar.dismiss())
.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
.setAnchorView(fragmentActivity.getBottomNavView())
.show();
}
private void showProfilePicDialog() {
if (profileModel != null) {
final FragmentManager fragmentManager = getParentFragmentManager();

View File

@ -8,6 +8,7 @@ import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
@ -26,14 +27,17 @@ import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.activities.Login;
import awais.instagrabber.databinding.PrefAccountSwitcherBinding;
import awais.instagrabber.db.datasources.AccountDataSource;
import awais.instagrabber.db.entities.Account;
import awais.instagrabber.db.repositories.AccountRepository;
import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.dialogs.AccountSwitcherDialogFragment;
import awais.instagrabber.repositories.responses.UserInfo;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.FlavorTown;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.ProfileService;
import awais.instagrabber.webservices.ServiceCallback;
@ -42,20 +46,23 @@ import static awais.instagrabber.utils.Utils.settingsHelper;
public class MorePreferencesFragment extends BasePreferencesFragment {
private static final String TAG = "MorePreferencesFragment";
private final AccountRepository accountRepository;
public MorePreferencesFragment() {
accountRepository = AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(getContext()));
}
@Override
void setupPreferenceScreen(final PreferenceScreen screen) {
final String cookie = settingsHelper.getString(Constants.COOKIE);
final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null;
// screen.addPreference(new MoreHeaderPreference(getContext()));
final Context context = getContext();
if (context == null) return;
final PreferenceCategory accountCategory = new PreferenceCategory(context);
accountCategory.setTitle(R.string.account);
accountCategory.setIconSpaceReserved(false);
screen.addPreference(accountCategory);
final List<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies();
if (isLoggedIn) {
accountCategory.setSummary(R.string.account_hint);
accountCategory.addPreference(getAccountSwitcherPreference(cookie));
@ -67,34 +74,59 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
settingsHelper.putString(Constants.COOKIE, "");
return true;
}));
} else {
if (allCookies != null && allCookies.size() > 0) {
accountCategory.addPreference(getAccountSwitcherPreference(null));
}
// Need to show something to trigger login activity
accountCategory.addPreference(getPreference(R.string.add_account, R.drawable.ic_add, preference -> {
startActivityForResult(new Intent(getContext(), Login.class), Constants.LOGIN_RESULT_CODE);
return true;
}));
}
accountRepository.getAllAccounts(new RepositoryCallback<List<Account>>() {
@Override
public void onSuccess(@NonNull final List<Account> accounts) {
if (!isLoggedIn) {
if (accounts.size() > 0) {
accountCategory.addPreference(getAccountSwitcherPreference(null));
}
// Need to show something to trigger login activity
accountCategory.addPreference(getPreference(R.string.add_account, R.drawable.ic_add, preference -> {
startActivityForResult(new Intent(getContext(), Login.class), Constants.LOGIN_RESULT_CODE);
return true;
}));
}
if (accounts.size() > 0) {
accountCategory
.addPreference(getPreference(R.string.remove_all_acc, null, R.drawable.ic_account_multiple_remove_24, preference -> {
if (getContext() == null) return false;
new AlertDialog.Builder(getContext())
.setTitle(R.string.logout)
.setMessage(R.string.remove_all_acc_warning)
.setPositiveButton(R.string.yes, (dialog, which) -> {
CookieUtils.removeAllAccounts(context, new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {
shouldRecreate();
Toast.makeText(context, R.string.logout_success, Toast.LENGTH_SHORT).show();
settingsHelper.putString(Constants.COOKIE, "");
}
if (allCookies != null && allCookies.size() > 0) {
accountCategory.addPreference(getPreference(R.string.remove_all_acc, null, R.drawable.ic_account_multiple_remove_24, preference -> {
if (getContext() == null) return false;
new AlertDialog.Builder(getContext())
.setTitle(R.string.logout)
.setMessage(R.string.remove_all_acc_warning)
.setPositiveButton(R.string.yes, (dialog, which) -> {
CookieUtils.setupCookies("REMOVE");
shouldRecreate();
Toast.makeText(context, R.string.logout_success, Toast.LENGTH_SHORT).show();
settingsHelper.putString(Constants.COOKIE, "");
})
.setNegativeButton(R.string.cancel, null)
.show();
return true;
}));
}
@Override
public void onDataNotAvailable() {}
});
})
.setNegativeButton(R.string.cancel, null)
.show();
return true;
}));
}
}
@Override
public void onDataNotAvailable() {
Log.d(TAG, "onDataNotAvailable");
if (!isLoggedIn) {
// Need to show something to trigger login activity
accountCategory.addPreference(getPreference(R.string.add_account, R.drawable.ic_add, preference -> {
startActivityForResult(new Intent(getContext(), Login.class), Constants.LOGIN_RESULT_CODE);
return true;
}));
}
}
});
// final PreferenceCategory generalCategory = new PreferenceCategory(context);
// generalCategory.setTitle(R.string.pref_category_general);
@ -163,11 +195,26 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
public void onSuccess(final UserInfo result) {
// Log.d(TAG, "adding userInfo: " + result);
if (result != null) {
Utils.dataBox.addOrUpdateUser(uid, result.getUsername(), cookie, result.getFullName(), result.getProfilePicUrl());
accountRepository.insertOrUpdateAccount(
uid,
result.getUsername(),
cookie,
result.getFullName(),
result.getProfilePicUrl(),
new RepositoryCallback<Account>() {
@Override
public void onSuccess(final Account result) {
final FragmentActivity activity = getActivity();
if (activity == null) return;
activity.recreate();
}
@Override
public void onDataNotAvailable() {
Log.e(TAG, "onDataNotAvailable: insert failed");
}
});
}
final FragmentActivity activity = getActivity();
if (activity == null) return;
activity.recreate();
}
@Override
@ -181,7 +228,7 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
private AccountSwitcherPreference getAccountSwitcherPreference(final String cookie) {
final Context context = getContext();
if (context == null) return null;
return new AccountSwitcherPreference(context, cookie, v -> showAccountSwitcherDialog());
return new AccountSwitcherPreference(context, cookie, accountRepository, v -> showAccountSwitcherDialog());
}
private void showAccountSwitcherDialog() {
@ -244,13 +291,16 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
public static class AccountSwitcherPreference extends Preference {
private final String cookie;
private final AccountRepository accountRepository;
private final View.OnClickListener onClickListener;
public AccountSwitcherPreference(final Context context,
final String cookie,
final AccountRepository accountRepository,
final View.OnClickListener onClickListener) {
super(context);
this.cookie = cookie;
this.accountRepository = accountRepository;
this.onClickListener = onClickListener;
setLayoutResource(R.layout.pref_account_switcher);
}
@ -263,11 +313,20 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
final PrefAccountSwitcherBinding binding = PrefAccountSwitcherBinding.bind(root);
final String uid = CookieUtils.getUserIdFromCookie(cookie);
if (uid == null) return;
final DataBox.CookieModel user = Utils.dataBox.getCookie(uid);
if (user == null) return;
binding.fullName.setText(user.getFullName());
binding.username.setText("@" + user.getUsername());
binding.profilePic.setImageURI(user.getProfilePic());
accountRepository.getAccount(uid, new RepositoryCallback<Account>() {
@Override
public void onSuccess(final Account account) {
binding.getRoot().post(() -> {
binding.fullName.setText(account.getFullName());
binding.username.setText("@" + account.getUsername());
binding.profilePic.setImageURI(account.getProfilePic());
binding.getRoot().requestLayout();
});
}
@Override
public void onDataNotAvailable() {}
});
}
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package awais.instagrabber.utils;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* Global executor pools for the whole application.
* <p>
* Grouping tasks like this avoids the effects of task starvation (e.g. disk reads don't wait behind
* webservice requests).
*/
public class AppExecutors {
// private static final int THREAD_COUNT = 3;
private final Executor diskIO;
// private final Executor networkIO;
private final Executor mainThread;
private final ListeningExecutorService tasksThread;
private AppExecutors(Executor diskIO,
// Executor networkIO,
Executor mainThread,
ListeningExecutorService tasksThread) {
this.diskIO = diskIO;
// this.networkIO = networkIO;
this.mainThread = mainThread;
this.tasksThread = tasksThread;
}
public AppExecutors() {
this(Executors.newSingleThreadExecutor(),
// Executors.newFixedThreadPool(THREAD_COUNT),
new MainThreadExecutor(),
MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)));
}
public Executor diskIO() {
return diskIO;
}
// public Executor networkIO() {
// return networkIO;
// }
public ListeningExecutorService tasksThread() {
return tasksThread;
}
public Executor mainThread() {
return mainThread;
}
private static class MainThreadExecutor implements Executor {
private final Handler mainThreadHandler = new Handler(Looper.getMainLooper());
@Override
public void execute(@NonNull Runnable command) {
mainThreadHandler.post(command);
}
}
}

View File

@ -1,5 +1,6 @@
package awais.instagrabber.utils;
import android.content.Context;
import android.util.Log;
import android.webkit.CookieManager;
@ -17,9 +18,13 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.db.datasources.AccountDataSource;
import awais.instagrabber.db.repositories.AccountRepository;
import awais.instagrabber.db.repositories.RepositoryCallback;
import awaisomereport.LogCollector;
public final class CookieUtils {
private static final String TAG = CookieUtils.class.getSimpleName();
public static final CookieManager COOKIE_MANAGER = CookieManager.getInstance();
public static final java.net.CookieManager NET_COOKIE_MANAGER = new java.net.CookieManager(null, CookiePolicy.ACCEPT_ALL);
@ -28,11 +33,7 @@ public final class CookieUtils {
if (cookieStore == null || TextUtils.isEmpty(cookieRaw)) {
return;
}
if (cookieRaw.equals("REMOVE")) {
cookieStore.removeAll();
Utils.dataBox.deleteAllUserCookies();
return;
} else if (cookieRaw.equals("LOGOUT")) {
if (cookieRaw.equals("LOGOUT")) {
cookieStore.removeAll();
return;
}
@ -53,7 +54,19 @@ public final class CookieUtils {
} catch (final URISyntaxException e) {
if (Utils.logCollector != null)
Utils.logCollector.appendException(e, LogCollector.LogFile.UTILS, "setupCookies");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
}
public static void removeAllAccounts(final Context context, final RepositoryCallback<Void> callback) {
final CookieStore cookieStore = NET_COOKIE_MANAGER.getCookieStore();
if (cookieStore == null) return;
cookieStore.removeAll();
try {
AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(context))
.deleteAllAccounts(callback);
} catch (Exception e) {
Log.e(TAG, "setupCookies", e);
}
}

View File

@ -10,17 +10,13 @@ import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.ObjectsCompat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.models.enums.FavoriteType;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
public final class DataBox extends SQLiteOpenHelper {
private static final String TAG = "DataBox";
@ -28,22 +24,22 @@ public final class DataBox extends SQLiteOpenHelper {
private static DataBox sInstance;
private final static int VERSION = 3;
private final static String TABLE_COOKIES = "cookies";
private final static String TABLE_FAVORITES = "favorites";
public final static String TABLE_COOKIES = "cookies";
public final static String TABLE_FAVORITES = "favorites";
private final static String KEY_ID = "id";
private final static String KEY_USERNAME = Constants.EXTRAS_USERNAME;
private final static String KEY_COOKIE = "cookie";
private final static String KEY_UID = "uid";
private final static String KEY_FULL_NAME = "full_name";
private final static String KEY_PROFILE_PIC = "profile_pic";
public final static String KEY_ID = "id";
public final static String KEY_USERNAME = Constants.EXTRAS_USERNAME;
public final static String KEY_COOKIE = "cookie";
public final static String KEY_UID = "uid";
public final static String KEY_FULL_NAME = "full_name";
public final static String KEY_PROFILE_PIC = "profile_pic";
private final static String FAV_COL_ID = "id";
private final static String FAV_COL_QUERY = "query_text";
private final static String FAV_COL_TYPE = "type";
private final static String FAV_COL_DISPLAY_NAME = "display_name";
private final static String FAV_COL_PIC_URL = "pic_url";
private final static String FAV_COL_DATE_ADDED = "date_added";
public final static String FAV_COL_ID = "id";
public final static String FAV_COL_QUERY = "query_text";
public final static String FAV_COL_TYPE = "type";
public final static String FAV_COL_DISPLAY_NAME = "display_name";
public final static String FAV_COL_PIC_URL = "pic_url";
public final static String FAV_COL_DATE_ADDED = "date_added";
public static synchronized DataBox getInstance(final Context context) {
if (sInstance == null) sInstance = new DataBox(context.getApplicationContext());
@ -84,7 +80,7 @@ public final class DataBox extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_FULL_NAME + " TEXT");
db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_PROFILE_PIC + " TEXT");
case 2:
final List<FavoriteModel> oldFavorites = backupOldFavorites(db);
final List<Favorite> oldFavorites = backupOldFavorites(db);
// recreate with new columns (as there will be no doubt about the `query_display` column being present or not in the future versions)
db.execSQL("DROP TABLE " + TABLE_FAVORITES);
db.execSQL("CREATE TABLE " + TABLE_FAVORITES + " ("
@ -95,19 +91,41 @@ public final class DataBox extends SQLiteOpenHelper {
+ FAV_COL_PIC_URL + " TEXT,"
+ FAV_COL_DATE_ADDED + " INTEGER)");
// add the old favorites back
for (final FavoriteModel oldFavorite : oldFavorites) {
addOrUpdateFavorite(db, oldFavorite);
for (final Favorite oldFavorite : oldFavorites) {
insertOrUpdateFavorite(db, oldFavorite);
}
}
Log.i(TAG, String.format("DB update from v%d to v%d completed!", oldVersion, newVersion));
}
public synchronized void insertOrUpdateFavorite(@NonNull final SQLiteDatabase db, @NonNull final Favorite model) {
final ContentValues values = new ContentValues();
values.put(FAV_COL_QUERY, model.getQuery());
values.put(FAV_COL_TYPE, model.getType().toString());
values.put(FAV_COL_DISPLAY_NAME, model.getDisplayName());
values.put(FAV_COL_PIC_URL, model.getPicUrl());
values.put(FAV_COL_DATE_ADDED, model.getDateAdded().getTime());
int rows;
if (model.getId() >= 1) {
rows = db.update(TABLE_FAVORITES, values, FAV_COL_ID + "=?", new String[]{String.valueOf(model.getId())});
} else {
rows = db.update(TABLE_FAVORITES,
values,
FAV_COL_QUERY + "=?" +
" AND " + FAV_COL_TYPE + "=?",
new String[]{model.getQuery(), model.getType().toString()});
}
if (rows != 1) {
db.insert(TABLE_FAVORITES, null, values);
}
}
@NonNull
private List<FavoriteModel> backupOldFavorites(@NonNull final SQLiteDatabase db) {
private List<Favorite> backupOldFavorites(@NonNull final SQLiteDatabase db) {
// check if old favorites table had the column query_display
final boolean queryDisplayExists = checkColumnExists(db, TABLE_FAVORITES, "query_display");
Log.d(TAG, "backupOldFavorites: queryDisplayExists: " + queryDisplayExists);
final List<FavoriteModel> oldModels = new ArrayList<>();
final List<Favorite> oldModels = new ArrayList<>();
final String sql = "SELECT "
+ "query_text,"
+ "date_added"
@ -122,7 +140,7 @@ public final class DataBox extends SQLiteOpenHelper {
if (favoriteTypeQueryPair == null) continue;
final FavoriteType type = favoriteTypeQueryPair.first;
final String query = favoriteTypeQueryPair.second;
oldModels.add(new FavoriteModel(
oldModels.add(new Favorite(
-1,
query,
type,
@ -162,434 +180,4 @@ public final class DataBox extends SQLiteOpenHelper {
}
return exists;
}
public final void addOrUpdateFavorite(@NonNull final FavoriteModel model) {
final String query = model.getQuery();
if (!TextUtils.isEmpty(query)) {
try (final SQLiteDatabase db = getWritableDatabase()) {
db.beginTransaction();
try {
addOrUpdateFavorite(db, model);
db.setTransactionSuccessful();
} catch (final Exception e) {
if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "addOrUpdateFavorite");
}
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error adding/updating favorite", e);
}
} finally {
db.endTransaction();
}
}
}
}
private void addOrUpdateFavorite(@NonNull final SQLiteDatabase db, @NonNull final FavoriteModel model) {
final ContentValues values = new ContentValues();
values.put(FAV_COL_QUERY, model.getQuery());
values.put(FAV_COL_TYPE, model.getType().toString());
values.put(FAV_COL_DISPLAY_NAME, model.getDisplayName());
values.put(FAV_COL_PIC_URL, model.getPicUrl());
values.put(FAV_COL_DATE_ADDED, model.getDateAdded().getTime());
int rows;
if (model.getId() >= 1) {
rows = db.update(TABLE_FAVORITES, values, FAV_COL_ID + "=?", new String[]{String.valueOf(model.getId())});
} else {
rows = db.update(TABLE_FAVORITES,
values,
FAV_COL_QUERY + "=?" +
" AND " + FAV_COL_TYPE + "=?",
new String[]{model.getQuery(), model.getType().toString()});
}
if (rows != 1) {
db.insertOrThrow(TABLE_FAVORITES, null, values);
}
}
public final synchronized void deleteFavorite(@NonNull final String query, @NonNull final FavoriteType type) {
if (!TextUtils.isEmpty(query)) {
try (final SQLiteDatabase db = getWritableDatabase()) {
db.beginTransaction();
try {
final int rowsDeleted = db.delete(TABLE_FAVORITES,
FAV_COL_QUERY + "=?" +
" AND " + FAV_COL_TYPE + "=?",
new String[]{query, type.toString()});
if (rowsDeleted > 0) db.setTransactionSuccessful();
} catch (final Exception e) {
if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "deleteFavorite");
}
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error", e);
}
} finally {
db.endTransaction();
}
}
}
}
@NonNull
public final List<FavoriteModel> getAllFavorites() {
final List<FavoriteModel> favorites = new ArrayList<>();
final SQLiteDatabase db = getWritableDatabase();
try (final Cursor cursor = db.rawQuery("SELECT "
+ FAV_COL_ID + ","
+ FAV_COL_QUERY + ","
+ FAV_COL_TYPE + ","
+ FAV_COL_DISPLAY_NAME + ","
+ FAV_COL_PIC_URL + ","
+ FAV_COL_DATE_ADDED
+ " FROM " + TABLE_FAVORITES,
null)) {
if (cursor != null && cursor.moveToFirst()) {
db.beginTransaction();
FavoriteModel tempFav;
do {
FavoriteType type = null;
try {
type = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE)));
} catch (IllegalArgumentException ignored) {}
tempFav = new FavoriteModel(
cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)),
cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)),
type,
cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)),
cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)),
new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED)))
);
favorites.add(tempFav);
} while (cursor.moveToNext());
db.endTransaction();
}
} catch (final Exception e) {
Log.e(TAG, "", e);
}
return favorites;
}
@Nullable
public final FavoriteModel getFavorite(@NonNull final String query, @NonNull final FavoriteType type) {
try (final SQLiteDatabase db = getReadableDatabase();
final Cursor cursor = db.rawQuery("SELECT "
+ FAV_COL_ID + ","
+ FAV_COL_QUERY + ","
+ FAV_COL_TYPE + ","
+ FAV_COL_DISPLAY_NAME + ","
+ FAV_COL_PIC_URL + ","
+ FAV_COL_DATE_ADDED
+ " FROM " + TABLE_FAVORITES
+ " WHERE " + FAV_COL_QUERY + "='" + query + "'"
+ " AND " + FAV_COL_TYPE + "='" + type.toString() + "'",
null)) {
if (cursor != null && cursor.moveToFirst()) {
FavoriteType favoriteType = null;
try {
favoriteType = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE)));
} catch (IllegalArgumentException ignored) {}
return new FavoriteModel(
cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)),
cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)),
favoriteType,
cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)),
cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)),
new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED)))
);
}
}
return null;
}
public final void addOrUpdateUser(@NonNull final DataBox.CookieModel cookieModel) {
addOrUpdateUser(
cookieModel.getUid(),
cookieModel.getUsername(),
cookieModel.getCookie(),
cookieModel.getFullName(),
cookieModel.getProfilePic()
);
}
public final void addOrUpdateUser(final String uid,
final String username,
final String cookie,
final String fullName,
final String profilePicUrl) {
if (TextUtils.isEmpty(uid)) return;
try (final SQLiteDatabase db = getWritableDatabase()) {
db.beginTransaction();
try {
final ContentValues values = new ContentValues();
values.put(KEY_USERNAME, username);
values.put(KEY_COOKIE, cookie);
values.put(KEY_UID, uid);
values.put(KEY_FULL_NAME, fullName);
values.put(KEY_PROFILE_PIC, profilePicUrl);
final int rows = db.update(TABLE_COOKIES, values, KEY_UID + "=?", new String[]{uid});
if (rows != 1)
db.insertOrThrow(TABLE_COOKIES, null, values);
db.setTransactionSuccessful();
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e(TAG, "Error", e);
} finally {
db.endTransaction();
}
}
}
public final synchronized void delUserCookie(@NonNull final CookieModel cookieModel) {
final String cookieModelUid = cookieModel.getUid();
if (!TextUtils.isEmpty(cookieModelUid)) {
try (final SQLiteDatabase db = getWritableDatabase()) {
db.beginTransaction();
try {
final int rowsDeleted = db.delete(TABLE_COOKIES, KEY_UID + "=? AND " + KEY_USERNAME + "=? AND " + KEY_COOKIE + "=?",
new String[]{cookieModelUid, cookieModel.getUsername(), cookieModel.getCookie()});
if (rowsDeleted > 0) db.setTransactionSuccessful();
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
} finally {
db.endTransaction();
}
}
}
}
public final synchronized void deleteAllUserCookies() {
try (final SQLiteDatabase db = getWritableDatabase()) {
db.beginTransaction();
try {
final int rowsDeleted = db.delete(TABLE_COOKIES, null, null);
if (rowsDeleted > 0) db.setTransactionSuccessful();
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
} finally {
db.endTransaction();
}
}
}
@Nullable
public final CookieModel getCookie(final String uid) {
CookieModel cookie = null;
try (final SQLiteDatabase db = getReadableDatabase();
final Cursor cursor = db.rawQuery(
"SELECT "
+ KEY_UID + ","
+ KEY_USERNAME + ","
+ KEY_COOKIE + ","
+ KEY_FULL_NAME + ","
+ KEY_PROFILE_PIC
+ " FROM " + TABLE_COOKIES
+ " WHERE " + KEY_UID + " = ?",
new String[]{uid})
) {
if (cursor != null && cursor.moveToFirst())
cookie = new CookieModel(
cursor.getString(cursor.getColumnIndex(KEY_UID)),
cursor.getString(cursor.getColumnIndex(KEY_USERNAME)),
cursor.getString(cursor.getColumnIndex(KEY_COOKIE)),
cursor.getString(cursor.getColumnIndex(KEY_FULL_NAME)),
cursor.getString(cursor.getColumnIndex(KEY_PROFILE_PIC))
);
}
return cookie;
}
@NonNull
public final List<CookieModel> getAllCookies() {
final List<CookieModel> cookies = new ArrayList<>();
try (final SQLiteDatabase db = getReadableDatabase();
final Cursor cursor = db.rawQuery(
"SELECT "
+ KEY_UID + ","
+ KEY_USERNAME + ","
+ KEY_COOKIE + ","
+ KEY_FULL_NAME + ","
+ KEY_PROFILE_PIC
+ " FROM " + TABLE_COOKIES, null)
) {
if (cursor != null && cursor.moveToFirst()) {
do {
cookies.add(new CookieModel(
cursor.getString(cursor.getColumnIndex(KEY_UID)),
cursor.getString(cursor.getColumnIndex(KEY_USERNAME)),
cursor.getString(cursor.getColumnIndex(KEY_COOKIE)),
cursor.getString(cursor.getColumnIndex(KEY_FULL_NAME)),
cursor.getString(cursor.getColumnIndex(KEY_PROFILE_PIC))
));
} while (cursor.moveToNext());
}
}
return cookies;
}
public static class CookieModel {
private final String uid;
private final String username;
private final String cookie;
private final String fullName;
private final String profilePic;
private boolean selected;
public CookieModel(final String uid,
final String username,
final String cookie,
final String fullName,
final String profilePic) {
this.uid = uid;
this.username = username;
this.cookie = cookie;
this.fullName = fullName;
this.profilePic = profilePic;
}
public String getUid() {
return uid;
}
public String getUsername() {
return username;
}
public String getCookie() {
return cookie;
}
public String getFullName() {
return fullName;
}
public String getProfilePic() {
return profilePic;
}
public boolean isSelected() {
return selected;
}
public void setSelected(final boolean selected) {
this.selected = selected;
}
public boolean isValid() {
return !TextUtils.isEmpty(uid)
&& !TextUtils.isEmpty(username)
&& !TextUtils.isEmpty(cookie);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final CookieModel that = (CookieModel) o;
return ObjectsCompat.equals(uid, that.uid) &&
ObjectsCompat.equals(username, that.username) &&
ObjectsCompat.equals(cookie, that.cookie);
}
@Override
public int hashCode() {
return ObjectsCompat.hash(uid, username, cookie);
}
@NonNull
@Override
public String toString() {
return "CookieModel{" +
"uid='" + uid + '\'' +
", username='" + username + '\'' +
", cookie='" + cookie + '\'' +
", fullName='" + fullName + '\'' +
", profilePic='" + profilePic + '\'' +
", selected=" + selected +
'}';
}
}
public static class FavoriteModel {
private final int id;
private final String query;
private final FavoriteType type;
private final String displayName;
private final String picUrl;
private final Date dateAdded;
public FavoriteModel(final int id,
final String query,
final FavoriteType type,
final String displayName,
final String picUrl,
final Date dateAdded) {
this.id = id;
this.query = query;
this.type = type;
this.displayName = displayName;
this.picUrl = picUrl;
this.dateAdded = dateAdded;
}
public int getId() {
return id;
}
public String getQuery() {
return query;
}
public FavoriteType getType() {
return type;
}
public String getDisplayName() {
return displayName;
}
public String getPicUrl() {
return picUrl;
}
public Date getDateAdded() {
return dateAdded;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final FavoriteModel that = (FavoriteModel) o;
return id == that.id &&
ObjectsCompat.equals(query, that.query) &&
type == that.type &&
ObjectsCompat.equals(displayName, that.displayName) &&
ObjectsCompat.equals(picUrl, that.picUrl) &&
ObjectsCompat.equals(dateAdded, that.dateAdded);
}
@Override
public int hashCode() {
return ObjectsCompat.hash(id, query, type, displayName, picUrl, dateAdded);
}
@NonNull
@Override
public String toString() {
return "FavoriteModel{" +
"id=" + id +
", query='" + query + '\'' +
", type=" + type +
", displayName='" + displayName + '\'' +
", picUrl='" + picUrl + '\'' +
", dateAdded=" + dateAdded +
'}';
}
}
}

View File

@ -2,6 +2,7 @@ package awais.instagrabber.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.util.Base64;
import android.util.Log;
import android.util.Pair;
@ -18,12 +19,21 @@ import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.db.datasources.AccountDataSource;
import awais.instagrabber.db.datasources.FavoriteDataSource;
import awais.instagrabber.db.entities.Account;
import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.db.repositories.AccountRepository;
import awais.instagrabber.db.repositories.FavoriteRepository;
import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.PasswordUtils.IncorrectPasswordException;
@ -47,37 +57,38 @@ public final class ExportImportUtils {
@NonNull final File filePath,
final FetchListener<Boolean> fetchListener,
@NonNull final Context context) {
final String exportString = getExportString(flags, context);
if (TextUtils.isEmpty(exportString)) return;
final boolean isPass = !TextUtils.isEmpty(password);
byte[] exportBytes = null;
if (isPass) {
final byte[] passwordBytes = password.getBytes();
final byte[] bytes = new byte[32];
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
try {
exportBytes = PasswordUtils.enc(exportString, bytes);
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
getExportString(flags, context, exportString -> {
if (TextUtils.isEmpty(exportString)) return;
final boolean isPass = !TextUtils.isEmpty(password);
byte[] exportBytes = null;
if (isPass) {
final byte[] passwordBytes = password.getBytes();
final byte[] bytes = new byte[32];
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
try {
exportBytes = PasswordUtils.enc(exportString, bytes);
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
} else {
exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING);
}
} else {
exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING);
}
if (exportBytes != null && exportBytes.length > 1) {
try (final FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(isPass ? 'A' : 'Z');
fos.write(exportBytes);
if (fetchListener != null) fetchListener.onResult(true);
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::notPass");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
} else if (fetchListener != null) fetchListener.onResult(false);
if (exportBytes != null && exportBytes.length > 1) {
try (final FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(isPass ? 'A' : 'Z');
fos.write(exportBytes);
if (fetchListener != null) fetchListener.onResult(true);
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::notPass");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
} else if (fetchListener != null) fetchListener.onResult(false);
});
}
public static void importData(@NonNull final Context context,
@ -99,7 +110,8 @@ public final class ExportImportUtils {
final byte[] passwordBytes = password.getBytes();
final byte[] bytes = new byte[32];
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
importJson(new String(PasswordUtils.dec(builder.toString(), bytes)),
importJson(context,
new String(PasswordUtils.dec(builder.toString(), bytes)),
flags,
fetchListener);
} catch (final IncorrectPasswordException e) {
@ -111,7 +123,8 @@ public final class ExportImportUtils {
if (BuildConfig.DEBUG) Log.e(TAG, "Error importing backup", e);
}
} else if (configType == 'Z') {
importJson(new String(Base64.decode(builder.toString(), Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP)),
importJson(context,
new String(Base64.decode(builder.toString(), Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP)),
flags,
fetchListener);
@ -129,7 +142,8 @@ public final class ExportImportUtils {
}
}
private static void importJson(@NonNull final String json,
private static void importJson(final Context context,
@NonNull final String json,
@ExportImportFlags final int flags,
final FetchListener<Boolean> fetchListener) {
try {
@ -138,10 +152,10 @@ public final class ExportImportUtils {
importSettings(jsonObject);
}
if ((flags & FLAG_COOKIES) == FLAG_COOKIES && jsonObject.has("cookies")) {
importAccounts(jsonObject);
importAccounts(context, jsonObject);
}
if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES && jsonObject.has("favs")) {
importFavorites(jsonObject);
importFavorites(context, jsonObject);
}
if (fetchListener != null) fetchListener.onResult(true);
} catch (final Exception e) {
@ -151,7 +165,7 @@ public final class ExportImportUtils {
}
}
private static void importFavorites(final JSONObject jsonObject) throws JSONException {
private static void importFavorites(final Context context, final JSONObject jsonObject) throws JSONException {
final JSONArray favs = jsonObject.getJSONArray("favs");
for (int i = 0; i < favs.length(); i++) {
final JSONObject favsObject = favs.getJSONObject(i);
@ -175,7 +189,7 @@ public final class ExportImportUtils {
if (query == null || favoriteType == null) {
continue;
}
final DataBox.FavoriteModel favoriteModel = new DataBox.FavoriteModel(
final Favorite favorite = new Favorite(
-1,
query,
favoriteType,
@ -184,41 +198,55 @@ public final class ExportImportUtils {
: favsObject.optString("pic_url"),
new Date(favsObject.getLong("d")));
// Log.d(TAG, "importJson: favoriteModel: " + favoriteModel);
Utils.dataBox.addOrUpdateFavorite(favoriteModel);
FavoriteRepository.getInstance(new AppExecutors(), FavoriteDataSource.getInstance(context))
.insertOrUpdateFavorite(favorite, null);
}
}
private static void importAccounts(final JSONObject jsonObject) throws JSONException {
final JSONArray cookies = jsonObject.getJSONArray("cookies");
for (int i = 0; i < cookies.length(); i++) {
final JSONObject cookieObject = cookies.getJSONObject(i);
final DataBox.CookieModel cookieModel = new DataBox.CookieModel(
cookieObject.optString("i"),
cookieObject.optString("u"),
cookieObject.optString("c"),
cookieObject.optString("full_name"),
cookieObject.optString("profile_pic")
);
if (!cookieModel.isValid()) continue;
// Log.d(TAG, "importJson: cookieModel: " + cookieModel);
Utils.dataBox.addOrUpdateUser(cookieModel);
}
}
private static void importSettings(final JSONObject jsonObject) throws JSONException {
final JSONObject objSettings = jsonObject.getJSONObject("settings");
final Iterator<String> keys = objSettings.keys();
while (keys.hasNext()) {
final String key = keys.next();
final Object val = objSettings.opt(key);
// Log.d(TAG, "importJson: key: " + key + ", val: " + val);
if (val instanceof String) {
settingsHelper.putString(key, (String) val);
} else if (val instanceof Integer) {
settingsHelper.putInteger(key, (int) val);
} else if (val instanceof Boolean) {
settingsHelper.putBoolean(key, (boolean) val);
private static void importAccounts(final Context context,
final JSONObject jsonObject) {
final List<Account> accounts = new ArrayList<>();
try {
final JSONArray cookies = jsonObject.getJSONArray("cookies");
for (int i = 0; i < cookies.length(); i++) {
final JSONObject cookieObject = cookies.getJSONObject(i);
final Account account = new Account(
-1,
cookieObject.optString("i"),
cookieObject.optString("u"),
cookieObject.optString("c"),
cookieObject.optString("full_name"),
cookieObject.optString("profile_pic")
);
if (!account.isValid()) continue;
accounts.add(account);
}
} catch (Exception e) {
Log.e(TAG, "importAccounts: Error parsing json", e);
return;
}
AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(context))
.insertOrUpdateAccounts(accounts, null);
}
private static void importSettings(final JSONObject jsonObject) {
try {
final JSONObject objSettings = jsonObject.getJSONObject("settings");
final Iterator<String> keys = objSettings.keys();
while (keys.hasNext()) {
final String key = keys.next();
final Object val = objSettings.opt(key);
// Log.d(TAG, "importJson: key: " + key + ", val: " + val);
if (val instanceof String) {
settingsHelper.putString(key, (String) val);
} else if (val instanceof Integer) {
settingsHelper.putInteger(key, (int) val);
} else if (val instanceof Boolean) {
settingsHelper.putBoolean(key, (boolean) val);
}
}
} catch (Exception e) {
Log.e(TAG, "importSettings error", e);
}
}
@ -234,28 +262,62 @@ public final class ExportImportUtils {
return false;
}
@Nullable
private static String getExportString(@ExportImportFlags final int flags,
@NonNull final Context context) {
String result = null;
try {
final JSONObject jsonObject = new JSONObject();
if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS) {
jsonObject.put("settings", getSettings(context));
//todo Need to improve logic
private static void getExportString(@ExportImportFlags final int flags,
@NonNull final Context context,
final OnExportStringCreatedCallback callback) {
final AppExecutors appExecutors = new AppExecutors();
final Handler innerHandler = new Handler();
appExecutors.tasksThread().execute(() -> {
final CountDownLatch responseWaiter = new CountDownLatch(3);
try {
final JSONObject jsonObject = new JSONObject();
innerHandler.post(() -> {
if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS) {
try {
jsonObject.put("settings", getSettings(context));
} catch (JSONException e) {
Log.e(TAG, "getExportString: ", e);
}
}
responseWaiter.countDown();
});
innerHandler.post(() -> {
if ((flags & FLAG_COOKIES) == FLAG_COOKIES) {
getCookies(context, array -> {
try {
jsonObject.put("cookies", array);
} catch (JSONException e) {
Log.e(TAG, "error getting accounts", e);
}
responseWaiter.countDown();
});
return;
}
responseWaiter.countDown();
});
innerHandler.post(() -> {
if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES) {
getFavorites(context, array -> {
try {
jsonObject.put("favs", array);
} catch (JSONException e) {
Log.e(TAG, "getExportString: ", e);
}
responseWaiter.countDown();
});
return;
}
responseWaiter.countDown();
});
responseWaiter.await();
callback.onCreated(jsonObject.toString());
} catch (final Exception e) {
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getExportString");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
if ((flags & FLAG_COOKIES) == FLAG_COOKIES) {
jsonObject.put("cookies", getCookies());
}
if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES) {
jsonObject.put("favs", getFavorites());
}
result = jsonObject.toString();
} catch (final Exception e) {
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getExportString");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
return result;
callback.onCreated(null);
});
}
@NonNull
@ -277,22 +339,35 @@ public final class ExportImportUtils {
return new JSONObject();
}
@NonNull
private static JSONArray getFavorites() {
if (Utils.dataBox == null) return new JSONArray();
private static void getFavorites(final Context context, final OnFavoritesJsonLoadedCallback callback) {
final FavoriteDataSource dataSource = FavoriteDataSource.getInstance(context);
final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(new AppExecutors(), dataSource);
try {
final List<DataBox.FavoriteModel> allFavorites = Utils.dataBox.getAllFavorites();
final JSONArray jsonArray = new JSONArray();
for (final DataBox.FavoriteModel favorite : allFavorites) {
final JSONObject jsonObject = new JSONObject();
jsonObject.put("q", favorite.getQuery());
jsonObject.put("type", favorite.getType().toString());
jsonObject.put("s", favorite.getDisplayName());
jsonObject.put("pic_url", favorite.getPicUrl());
jsonObject.put("d", favorite.getDateAdded().getTime());
jsonArray.put(jsonObject);
}
return jsonArray;
favoriteRepository.getAllFavorites(new RepositoryCallback<List<Favorite>>() {
@Override
public void onSuccess(final List<Favorite> favorites) {
final JSONArray jsonArray = new JSONArray();
try {
for (final Favorite favorite : favorites) {
final JSONObject jsonObject = new JSONObject();
jsonObject.put("q", favorite.getQuery());
jsonObject.put("type", favorite.getType().toString());
jsonObject.put("s", favorite.getDisplayName());
jsonObject.put("pic_url", favorite.getPicUrl());
jsonObject.put("d", favorite.getDateAdded().getTime());
jsonArray.put(jsonObject);
}
} catch (Exception e) {
Log.e(TAG, "onSuccess: Error creating json array", e);
}
callback.onFavoritesJsonLoaded(jsonArray);
}
@Override
public void onDataNotAvailable() {
callback.onFavoritesJsonLoaded(new JSONArray());
}
});
} catch (final Exception e) {
if (logCollector != null) {
logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites");
@ -301,30 +376,48 @@ public final class ExportImportUtils {
Log.e(TAG, "Error exporting favorites", e);
}
}
return new JSONArray();
}
@NonNull
private static JSONArray getCookies() {
if (Utils.dataBox == null) return new JSONArray();
try {
final List<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies();
final JSONArray jsonArray = new JSONArray();
for (final DataBox.CookieModel cookie : allCookies) {
final JSONObject jsonObject = new JSONObject();
jsonObject.put("i", cookie.getUid());
jsonObject.put("u", cookie.getUsername());
jsonObject.put("c", cookie.getCookie());
jsonObject.put("full_name", cookie.getFullName());
jsonObject.put("profile_pic", cookie.getProfilePic());
jsonArray.put(jsonObject);
private static void getCookies(final Context context, final OnAccountJsonLoadedCallback callback) {
final AccountRepository accountRepository = AccountRepository.getInstance(new AppExecutors(), AccountDataSource.getInstance(context));
accountRepository.getAllAccounts(new RepositoryCallback<List<Account>>() {
@Override
public void onSuccess(final List<Account> accounts) {
try {
final JSONArray jsonArray = new JSONArray();
for (final Account cookie : accounts) {
final JSONObject jsonObject = new JSONObject();
jsonObject.put("i", cookie.getUid());
jsonObject.put("u", cookie.getUsername());
jsonObject.put("c", cookie.getCookie());
jsonObject.put("full_name", cookie.getFullName());
jsonObject.put("profile_pic", cookie.getProfilePic());
jsonArray.put(jsonObject);
}
callback.onAccountsJsonLoaded(jsonArray);
return;
} catch (Exception e) {
Log.e(TAG, "Error exporting accounts", e);
}
callback.onAccountsJsonLoaded(new JSONArray());
}
return jsonArray;
} catch (final Exception e) {
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error exporting accounts", e);
@Override
public void onDataNotAvailable() {
callback.onAccountsJsonLoaded(new JSONArray());
}
}
return new JSONArray();
});
}
public interface OnExportStringCreatedCallback {
void onCreated(String exportString);
}
public interface OnAccountJsonLoadedCallback {
void onAccountsJsonLoaded(JSONArray array);
}
public interface OnFavoritesJsonLoadedCallback {
void onFavoritesJsonLoaded(JSONArray array);
}
}

View File

@ -47,7 +47,6 @@ public final class Utils {
public static LogCollector logCollector;
public static SettingsHelper settingsHelper;
public static DataBox dataBox;
public static boolean sessionVolumeFull = false;
public static final MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
public static ClipboardManager clipboardManager;

View File

@ -5,12 +5,12 @@ import androidx.lifecycle.ViewModel;
import java.util.List;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.db.entities.Favorite;
public class FavoritesViewModel extends ViewModel {
private MutableLiveData<List<DataBox.FavoriteModel>> list;
private MutableLiveData<List<Favorite>> list;
public MutableLiveData<List<DataBox.FavoriteModel>> getList() {
public MutableLiveData<List<Favorite>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}

View File

@ -22,7 +22,7 @@
android:id="@+id/cookies"
style="@style/Widget.AppCompat.Button.ButtonBar.AlertDialog"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/get_cookies" />
@ -30,14 +30,15 @@
android:id="@+id/refresh"
style="@style/Widget.AppCompat.Button.ButtonBar.AlertDialog"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/refresh" />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/desktop_mode"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="@string/desktop_2fa" />
</androidx.appcompat.widget.LinearLayoutCompat>