Merge pull request #753 from ammargitham/dm-notifications-enhancements

DM Sync
This commit is contained in:
Austin Huang 2021-03-16 16:07:25 -04:00 committed by GitHub
commit 8a007cbeda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
124 changed files with 6779 additions and 2727 deletions

View File

@ -56,13 +56,13 @@ configurations.all {
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
def appcompat_version = "1.2.0"
def nav_version = '2.3.2'
def nav_version = '2.3.3'
def exoplayer_version = '2.12.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'com.google.android.material:material:1.4.0-alpha01'
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version"
@ -70,15 +70,16 @@ dependencies {
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
implementation "androidx.recyclerview:recyclerview:1.2.0-beta01"
implementation "androidx.recyclerview:recyclerview:1.2.0-beta02"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.work:work-runtime:2.4.0"
implementation "androidx.work:work-runtime:2.5.0"
implementation 'androidx.palette:palette:1.0.0'
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation 'com.google.guava:guava:27.0.1-android'
@ -89,10 +90,10 @@ dependencies {
annotationProcessor "androidx.room:room-compiler:$room_version"
// CameraX
def camerax_version = "1.0.0-rc01"
def camerax_version = "1.1.0-alpha02"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
implementation "androidx.camera:camera-view:1.0.0-alpha20"
implementation "androidx.camera:camera-view:1.0.0-alpha22"
// EmojiCompat
def emoji_compat_version = "1.1.0"

View File

@ -0,0 +1,161 @@
{
"formatVersion": 1,
"database": {
"version": 5,
"identityHash": "0b38e12b76bb081ec837191c5ef5b54e",
"entities": [
{
"tableName": "accounts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uid` TEXT, `username` TEXT, `cookie` TEXT, `full_name` TEXT, `profile_pic` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "username",
"columnName": "username",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "cookie",
"columnName": "cookie",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "fullName",
"columnName": "full_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "profilePic",
"columnName": "profile_pic",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "favorites",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query_text` TEXT, `type` TEXT, `display_name` TEXT, `pic_url` TEXT, `date_added` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "query",
"columnName": "query_text",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "displayName",
"columnName": "display_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "picUrl",
"columnName": "pic_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "dateAdded",
"columnName": "date_added",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "dm_last_notified",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `thread_id` TEXT, `last_notified_msg_ts` INTEGER, `last_notified_at` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "threadId",
"columnName": "thread_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastNotifiedMsgTs",
"columnName": "last_notified_msg_ts",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "lastNotifiedAt",
"columnName": "last_notified_at",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_dm_last_notified_thread_id",
"unique": true,
"columnNames": [
"thread_id"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_dm_last_notified_thread_id` ON `${TABLE_NAME}` (`thread_id`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0b38e12b76bb081ec837191c5ef5b54e')"
]
}
}

View File

@ -8,12 +8,12 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<!--<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />-->
<uses-feature
android:name="android.hardware.camera.any"
android:required="false" />
<application
android:name=".InstaGrabberApplication"
android:allowBackup="true"
@ -37,8 +37,8 @@
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<!--<action android:name="android.intent.action.SEARCH" />-->
<!--<action android:name="android.intent.action.WEB_SEARCH" />-->
<!-- <action android:name="android.intent.action.SEARCH" /> -->
<!-- <action android:name="android.intent.action.WEB_SEARCH" /> -->
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
@ -147,6 +147,19 @@
<service
android:name=".services.DeleteImageIntentService"
android:exported="false" />
<service
android:name=".services.DMSyncService"
android:exported="false" />
<receiver
android:name=".services.DMSyncAlarmReceiver"
android:enabled="true"
android:exported="false" />
<!--<receiver android:name=".services.BootCompletedReceiver" >-->
<!-- <intent-filter>-->
<!-- <action android:name="android.intent.action.BOOT_COMPLETED" />-->
<!-- </intent-filter>-->
<!--</receiver>-->
<uses-library
android:name="org.apache.http.legacy"
@ -156,4 +169,5 @@
android:name="fontProviderRequests"
android:value="Noto Color Emoji Compat" />
</application>
</manifest>

View File

@ -38,6 +38,8 @@ import androidx.emoji.text.FontRequestEmojiCompatConfig;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
@ -45,6 +47,7 @@ import androidx.navigation.ui.NavigationUI;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.behavior.HideBottomViewOnScrollBehavior;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.util.ArrayList;
@ -61,12 +64,15 @@ import awais.instagrabber.asyncs.SuggestionsFetcher;
import awais.instagrabber.customviews.emoji.EmojiVariantManager;
import awais.instagrabber.databinding.ActivityMainBinding;
import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.fragments.directmessages.DirectMessageInboxFragmentDirections;
import awais.instagrabber.fragments.main.FeedFragment;
import awais.instagrabber.fragments.settings.PreferenceKeys;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.IntentModel;
import awais.instagrabber.models.SuggestionModel;
import awais.instagrabber.models.enums.SuggestionType;
import awais.instagrabber.services.ActivityCheckerService;
import awais.instagrabber.services.DMSyncAlarmReceiver;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
@ -75,6 +81,7 @@ import awais.instagrabber.utils.IntentUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.utils.emoji.EmojiParser;
import awais.instagrabber.viewmodels.AppStateViewModel;
import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController;
import static awais.instagrabber.utils.Utils.settingsHelper;
@ -102,6 +109,8 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
private int firstFragmentGraphIndex;
private boolean isActivityCheckerServiceBound = false;
private boolean isBackStackEmpty = false;
private boolean isLoggedIn;
private HideBottomViewOnScrollBehavior<BottomNavigationView> behavior;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
@ -131,10 +140,18 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
binding = ActivityMainBinding.inflate(getLayoutInflater());
final String cookie = settingsHelper.getString(Constants.COOKIE);
CookieUtils.setupCookies(cookie);
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != 0;
setContentView(binding.getRoot());
final Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar);
createNotificationChannels();
try {
final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.bottomNavView.getLayoutParams();
//noinspection unchecked
behavior = (HideBottomViewOnScrollBehavior<BottomNavigationView>) layoutParams.getBehavior();
} catch (Exception e) {
Log.e(TAG, "onCreate: ", e);
}
if (savedInstanceState == null) {
setupBottomNavigationBar(true);
}
@ -142,6 +159,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
final boolean checkUpdates = settingsHelper.getBoolean(Constants.CHECK_UPDATES);
if (checkUpdates) FlavorTown.updateCheck(this);
FlavorTown.changelogCheck(this);
new ViewModelProvider(this).get(AppStateViewModel.class); // Just initiate the App state here
final Intent intent = getIntent();
handleIntent(intent);
if (!TextUtils.isEmpty(cookie) && settingsHelper.getBoolean(Constants.CHECK_ACTIVITY)) {
@ -154,6 +172,14 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
EmojiVariantManager.getInstance();
});
initEmojiCompat();
// initDmService();
}
private void initDmService() {
if (!isLoggedIn) return;
final boolean enabled = settingsHelper.getBoolean(PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH);
if (!enabled) return;
DMSyncAlarmReceiver.setAlarm(this);
}
@Override
@ -242,15 +268,22 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
}
private void createNotificationChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext());
notificationManager.createNotificationChannel(new NotificationChannel(Constants.DOWNLOAD_CHANNEL_ID,
Constants.DOWNLOAD_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT));
notificationManager.createNotificationChannel(new NotificationChannel(Constants.ACTIVITY_CHANNEL_ID,
Constants.ACTIVITY_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT));
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext());
notificationManager.createNotificationChannel(new NotificationChannel(Constants.DOWNLOAD_CHANNEL_ID,
Constants.DOWNLOAD_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT));
notificationManager.createNotificationChannel(new NotificationChannel(Constants.ACTIVITY_CHANNEL_ID,
Constants.ACTIVITY_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT));
notificationManager.createNotificationChannel(new NotificationChannel(Constants.DM_UNREAD_CHANNEL_ID,
Constants.DM_UNREAD_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT));
final NotificationChannel silentNotificationChannel = new NotificationChannel(Constants.SILENT_NOTIFICATIONS_CHANNEL_ID,
Constants.SILENT_NOTIFICATIONS_CHANNEL_NAME,
NotificationManager.IMPORTANCE_LOW);
silentNotificationChannel.setSound(null, null);
notificationManager.createNotificationChannel(silentNotificationChannel);
}
private void setupSuggestions() {
@ -387,8 +420,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
private void setupBottomNavigationBar(final boolean setDefaultFromSettings) {
int main_nav_ids = R.array.main_nav_ids;
final String cookie = settingsHelper.getString(Constants.COOKIE);
final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != 0;
if (!isLoggedIn) {
main_nav_ids = R.array.logged_out_main_nav_ids;
final int selectedItemId = binding.bottomNavView.getSelectedItemId();
@ -477,7 +508,11 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
final int destinationId = destination.getId();
@SuppressLint("RestrictedApi") final Deque<NavBackStackEntry> backStack = navController.getBackStack();
setupMenu(backStack.size(), destinationId);
binding.bottomNavView.setVisibility(SHOW_BOTTOM_VIEW_DESTINATIONS.contains(destinationId) ? View.VISIBLE : View.GONE);
final boolean contains = SHOW_BOTTOM_VIEW_DESTINATIONS.contains(destinationId);
binding.bottomNavView.setVisibility(contains ? View.VISIBLE : View.GONE);
if (contains && behavior != null) {
behavior.slideUp(binding.bottomNavView);
}
// explicitly hide keyboard when we navigate
final View view = getCurrentFocus();
@ -518,6 +553,10 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
showActivityView();
return;
}
if (Constants.ACTION_SHOW_DM_THREAD.equals(action)) {
showThread(intent);
return;
}
if (Intent.ACTION_SEND.equals(action) && type != null) {
if (type.equals("text/plain")) {
handleUrl(intent.getStringExtra(Intent.EXTRA_TEXT));
@ -531,6 +570,58 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
}
}
private void showThread(@NonNull final Intent intent) {
final String threadId = intent.getStringExtra(Constants.DM_THREAD_ACTION_EXTRA_THREAD_ID);
final String threadTitle = intent.getStringExtra(Constants.DM_THREAD_ACTION_EXTRA_THREAD_TITLE);
navigateToThread(threadId, threadTitle);
}
public void navigateToThread(final String threadId, final String threadTitle) {
if (threadId == null || threadTitle == null) return;
currentNavControllerLiveData.observe(this, new Observer<NavController>() {
@Override
public void onChanged(final NavController navController) {
if (navController == null) return;
if (navController.getGraph().getId() != R.id.direct_messages_nav_graph) return;
try {
final NavDestination currentDestination = navController.getCurrentDestination();
if (currentDestination != null && currentDestination.getId() == R.id.directMessagesInboxFragment) {
// if we are already on the inbox page, navigate to the thread
// need handler.post() to wait for the fragment manager to be ready to navigate
new Handler().post(() -> {
final DirectMessageInboxFragmentDirections.ActionInboxToThread action = DirectMessageInboxFragmentDirections
.actionInboxToThread(threadId, threadTitle);
navController.navigate(action);
});
return;
}
// add a destination change listener to navigate to thread once we are on the inbox page
navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
@Override
public void onDestinationChanged(@NonNull final NavController controller,
@NonNull final NavDestination destination,
@Nullable final Bundle arguments) {
if (destination.getId() == R.id.directMessagesInboxFragment) {
final DirectMessageInboxFragmentDirections.ActionInboxToThread action = DirectMessageInboxFragmentDirections
.actionInboxToThread(threadId, threadTitle);
controller.navigate(action);
controller.removeOnDestinationChangedListener(this);
}
}
});
// pop back stack until we reach the inbox page
navController.popBackStack(R.id.directMessagesInboxFragment, false);
} finally {
currentNavControllerLiveData.removeObserver(this);
}
}
});
final int selectedItemId = binding.bottomNavView.getSelectedItemId();
if (selectedItemId != R.navigation.direct_messages_nav_graph) {
setBottomNavSelectedItem(R.navigation.direct_messages_nav_graph);
}
}
private void handleUrl(final String url) {
if (url == null) return;
// Log.d(TAG, url);

View File

@ -258,7 +258,7 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
public void setThread(final DirectThread thread) {
if (thread == null) return;
this.thread = thread;
notifyDataSetChanged();
// notifyDataSetChanged();
}
public void submitList(@Nullable final List<DirectItem> list) {

View File

@ -36,7 +36,9 @@ public final class DirectMessageInboxAdapter extends ListAdapter<DirectThread, D
final DirectItem oldItemFirst = oldThread.getFirstDirectItem();
final DirectItem newItemFirst = newThread.getFirstDirectItem();
if (oldItemFirst == null || newItemFirst == null) return false;
return oldItemFirst.getItemId().equals(newItemFirst.getItemId());
final boolean idsEqual = oldItemFirst.getItemId().equals(newItemFirst.getItemId());
if (!idsEqual) return false;
return oldItemFirst.getTimestamp() == newItemFirst.getTimestamp();
}
};

View File

@ -43,14 +43,15 @@ public final class FeedAdapterV2 extends ListAdapter<Media, RecyclerView.ViewHol
private static final DiffUtil.ItemCallback<Media> DIFF_CALLBACK = new DiffUtil.ItemCallback<Media>() {
@Override
public boolean areItemsTheSame(@NonNull final Media oldItem, @NonNull final Media newItem) {
return oldItem.getPk().equals(newItem.getPk());
return Objects.equals(oldItem.getPk(), newItem.getPk());
}
@Override
public boolean areContentsTheSame(@NonNull final Media oldItem, @NonNull final Media newItem) {
final Caption oldItemCaption = oldItem.getCaption();
final Caption newItemCaption = newItem.getCaption();
return oldItem.getPk().equals(newItem.getPk()) && Objects.equals(getCaptionText(oldItemCaption), getCaptionText(newItemCaption));
return Objects.equals(oldItem.getPk(), newItem.getPk())
&& Objects.equals(getCaptionText(oldItemCaption), getCaptionText(newItemCaption));
}
private String getCaptionText(final Caption caption) {

View File

@ -0,0 +1,107 @@
package awais.instagrabber.adapters;
import android.net.Uri;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.imagepipeline.common.ResizeOptions;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import java.util.Objects;
import awais.instagrabber.databinding.ItemMediaBinding;
import awais.instagrabber.repositories.responses.giphy.GiphyGif;
import awais.instagrabber.utils.Utils;
public class GifItemsAdapter extends ListAdapter<GiphyGif, GifItemsAdapter.GifViewHolder> {
private static final DiffUtil.ItemCallback<GiphyGif> diffCallback = new DiffUtil.ItemCallback<GiphyGif>() {
@Override
public boolean areItemsTheSame(@NonNull final GiphyGif oldItem, @NonNull final GiphyGif newItem) {
return Objects.equals(oldItem.getId(), newItem.getId());
}
@Override
public boolean areContentsTheSame(@NonNull final GiphyGif oldItem, @NonNull final GiphyGif newItem) {
return Objects.equals(oldItem.getId(), newItem.getId());
}
};
private final OnItemClickListener onItemClickListener;
public GifItemsAdapter(final OnItemClickListener onItemClickListener) {
super(diffCallback);
this.onItemClickListener = onItemClickListener;
}
@NonNull
@Override
public GifViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final ItemMediaBinding binding = ItemMediaBinding.inflate(layoutInflater, parent, false);
return new GifViewHolder(binding, onItemClickListener);
}
@Override
public void onBindViewHolder(@NonNull final GifViewHolder holder, final int position) {
holder.bind(getItem(position));
}
public static class GifViewHolder extends RecyclerView.ViewHolder {
private static final String TAG = GifViewHolder.class.getSimpleName();
private static final int size = Utils.displayMetrics.widthPixels / 3;
private final ItemMediaBinding binding;
private final OnItemClickListener onItemClickListener;
public GifViewHolder(@NonNull final ItemMediaBinding binding,
final OnItemClickListener onItemClickListener) {
super(binding.getRoot());
this.binding = binding;
this.onItemClickListener = onItemClickListener;
binding.duration.setVisibility(View.GONE);
final GenericDraweeHierarchyBuilder builder = new GenericDraweeHierarchyBuilder(itemView.getResources());
builder.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER);
binding.item.setHierarchy(builder.build());
}
public void bind(final GiphyGif item) {
if (onItemClickListener != null) {
itemView.setOnClickListener(v -> onItemClickListener.onItemClick(item));
}
final BaseControllerListener<ImageInfo> controllerListener = new BaseControllerListener<ImageInfo>() {
@Override
public void onFailure(final String id, final Throwable throwable) {
Log.e(TAG, "onFailure: ", throwable);
}
};
final ImageRequest request = ImageRequestBuilder
.newBuilderWithSource(Uri.parse(item.getImages().getFixedHeight().getWebp()))
.setResizeOptions(ResizeOptions.forDimensions(size, size))
.build();
final PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setAutoPlayAnimations(true)
.setControllerListener(controllerListener);
binding.item.setController(builder.build());
}
}
public interface OnItemClickListener {
void onItemClick(GiphyGif giphyGif);
}
}

View File

@ -81,16 +81,18 @@ public class TopicClusterViewHolder extends RecyclerView.ViewHolder {
}
if (bitmap != null) {
Palette.from(bitmap).generate(p -> {
final Palette.Swatch swatch = p.getDominantSwatch();
final Resources resources = itemView.getResources();
int titleTextColor = resources.getColor(R.color.white);
if (swatch != null) {
backgroundColor.set(swatch.getRgb());
GradientDrawable gd = new GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
new int[]{Color.TRANSPARENT, backgroundColor.get()});
titleTextColor = swatch.getTitleTextColor();
binding.background.setBackground(gd);
if (p != null) {
final Palette.Swatch swatch = p.getDominantSwatch();
if (swatch != null) {
backgroundColor.set(swatch.getRgb());
GradientDrawable gd = new GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
new int[]{Color.TRANSPARENT, backgroundColor.get()});
titleTextColor = swatch.getTitleTextColor();
binding.background.setBackground(gd);
}
}
titleColor.set(titleTextColor);
binding.title.setTextColor(titleTextColor);
@ -127,8 +129,8 @@ public class TopicClusterViewHolder extends RecyclerView.ViewHolder {
// binding.title.setTransitionName("title-" + topicCluster.getId());
binding.cover.setTransitionName("cover-" + topicCluster.getId());
final String thumbUrl = ResponseBodyUtils.getThumbUrl(topicCluster.getCoverMedias() == null
? topicCluster.getCoverMedia()
: topicCluster.getCoverMedias().get(0));
? topicCluster.getCoverMedia()
: topicCluster.getCoverMedias().get(0));
if (thumbUrl == null) {
binding.cover.setImageURI((String) null);
} else {

View File

@ -12,26 +12,17 @@ import androidx.recyclerview.widget.RecyclerView;
import com.facebook.drawee.view.SimpleDraweeView;
import com.google.common.collect.ImmutableList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import awais.instagrabber.R;
import awais.instagrabber.adapters.DirectMessageInboxAdapter.OnItemClickListener;
import awais.instagrabber.databinding.LayoutDmInboxItemBinding;
import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemReelShare;
import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDirectStory;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadLastSeenAt;
import awais.instagrabber.repositories.responses.directmessages.RavenExpiringMediaActionSummary;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.DMUtils;
import awais.instagrabber.utils.TextUtils;
public final class DirectInboxItemViewHolder extends RecyclerView.ViewHolder {
@ -133,218 +124,17 @@ public final class DirectInboxItemViewHolder extends RecyclerView.ViewHolder {
if (directStory != null && !directStory.getItems().isEmpty()) {
final DirectItem item = directStory.getItems().get(0);
final MediaItemType mediaType = item.getVisualMedia().getMedia().getMediaType();
final String username = getUsername(thread.getUsers(), item.getUserId(), viewerId, resources);
final String subtitle = getMediaSpecificSubtitle(username, resources, mediaType);
final String username = DMUtils.getUsername(thread.getUsers(), item.getUserId(), viewerId, resources);
final String subtitle = DMUtils.getMediaSpecificSubtitle(username, resources, mediaType);
binding.subtitle.setText(subtitle);
return;
}
final DirectItem item = thread.getFirstDirectItem();
if (item == null) return;
final long senderId = item.getUserId();
final DirectItemType itemType = item.getItemType();
String subtitle = null;
final String username = getUsername(thread.getUsers(), senderId, viewerId, resources);
String message = "";
if (itemType == null) {
message = resources.getString(R.string.dms_inbox_raven_message_unknown);
} else {
switch (itemType) {
case TEXT:
message = item.getText();
break;
case LIKE:
message = item.getLike();
break;
case LINK:
message = item.getLink().getText();
break;
case PLACEHOLDER:
message = item.getPlaceholder().getMessage();
break;
case MEDIA_SHARE:
subtitle = resources.getString(R.string.dms_inbox_shared_post, username != null ? username : "", item.getMediaShare().getUser().getUsername());
break;
case ANIMATED_MEDIA:
subtitle = resources.getString(R.string.dms_inbox_shared_gif, username != null ? username : "");
break;
case PROFILE:
subtitle = resources.getString(R.string.dms_inbox_shared_profile, username != null ? username : "", item.getProfile().getUsername());
break;
case LOCATION:
subtitle = resources.getString(R.string.dms_inbox_shared_location, username != null ? username : "", item.getLocation().getName());
break;
case MEDIA: {
final MediaItemType mediaType = item.getMedia().getMediaType();
subtitle = getMediaSpecificSubtitle(username, resources, mediaType);
break;
}
case STORY_SHARE: {
final String reelType = item.getStoryShare().getReelType();
if (reelType == null) {
subtitle = item.getStoryShare().getTitle();
} else {
final int format = reelType.equals("highlight_reel")
? R.string.dms_inbox_shared_highlight
: R.string.dms_inbox_shared_story;
subtitle = resources.getString(format, username != null ? username : "",
item.getStoryShare().getMedia().getUser().getUsername());
}
break;
}
case VOICE_MEDIA:
subtitle = resources.getString(R.string.dms_inbox_shared_voice, username != null ? username : "");
break;
case ACTION_LOG:
subtitle = item.getActionLog().getDescription();
break;
case VIDEO_CALL_EVENT:
subtitle = item.getVideoCallEvent().getDescription();
break;
case CLIP:
subtitle = resources.getString(R.string.dms_inbox_shared_clip, username != null ? username : "",
item.getClip().getClip().getUser().getUsername());
break;
case FELIX_SHARE:
subtitle = resources.getString(R.string.dms_inbox_shared_igtv, username != null ? username : "",
item.getFelixShare().getVideo().getUser().getUsername());
break;
case RAVEN_MEDIA:
subtitle = getRavenMediaSubtitle(item, resources, username);
break;
case REEL_SHARE:
final DirectItemReelShare reelShare = item.getReelShare();
if (reelShare == null) {
subtitle = "";
break;
}
final String reelType = reelShare.getType();
switch (reelType) {
case "reply":
if (viewerId == item.getUserId()) {
subtitle = resources.getString(R.string.dms_inbox_replied_story_outgoing, reelShare.getText());
} else {
subtitle = resources.getString(R.string.dms_inbox_replied_story_incoming, username != null ? username : "", reelShare.getText());
}
break;
case "mention":
if (viewerId == item.getUserId()) {
// You mentioned the other person
final long mentionedUserId = item.getReelShare().getMentionedUserId();
final String otherUsername = getUsername(thread.getUsers(), mentionedUserId, viewerId, resources);
subtitle = resources.getString(R.string.dms_inbox_mentioned_story_outgoing, otherUsername);
} else {
// They mentioned you
subtitle = resources.getString(R.string.dms_inbox_mentioned_story_incoming, username != null ? username : "");
}
break;
case "reaction":
if (viewerId == item.getUserId()) {
subtitle = resources.getString(R.string.dms_inbox_reacted_story_outgoing, reelShare.getText());
} else {
subtitle = resources.getString(R.string.dms_inbox_reacted_story_incoming, username != null ? username : "", reelShare.getText());
}
break;
default:
subtitle = "";
break;
}
break;
default:
message = resources.getString(R.string.dms_inbox_raven_message_unknown);
}
}
if (subtitle == null) {
if (thread.getUsers().size() > 1
|| (thread.getUsers().size() == 1 && senderId == viewerId)) {
subtitle = String.format("%s: %s", username != null ? username : "", message);
} else {
subtitle = message;
}
}
final String subtitle = DMUtils.getMessageString(thread, resources, viewerId, item);
binding.subtitle.setText(subtitle != null ? subtitle : "");
}
private String getMediaSpecificSubtitle(final String username, final Resources resources, final MediaItemType mediaType) {
final String userSharedAnImage = resources.getString(R.string.dms_inbox_shared_image, username != null ? username : "");
final String userSharedAVideo = resources.getString(R.string.dms_inbox_shared_video, username != null ? username : "");
final String userSentAMessage = resources.getString(R.string.dms_inbox_shared_message, username != null ? username : "");
String subtitle;
switch (mediaType) {
case MEDIA_TYPE_IMAGE:
subtitle = userSharedAnImage;
break;
case MEDIA_TYPE_VIDEO:
subtitle = userSharedAVideo;
break;
default:
subtitle = userSentAMessage;
break;
}
return subtitle;
}
private String getRavenMediaSubtitle(final DirectItem item,
final Resources resources,
final String username) {
String subtitle = "";
final DirectItemVisualMedia visualMedia = item.getVisualMedia();
final RavenExpiringMediaActionSummary summary = visualMedia.getExpiringMediaActionSummary();
if (summary != null) {
final RavenExpiringMediaActionSummary.ActionType expiringMediaType = summary.getType();
int textRes = 0;
switch (expiringMediaType) {
case DELIVERED:
textRes = R.string.dms_inbox_raven_media_delivered;
break;
case SENT:
textRes = R.string.dms_inbox_raven_media_sent;
break;
case OPENED:
textRes = R.string.dms_inbox_raven_media_opened;
break;
case REPLAYED:
textRes = R.string.dms_inbox_raven_media_replayed;
break;
case SENDING:
textRes = R.string.dms_inbox_raven_media_sending;
break;
case BLOCKED:
textRes = R.string.dms_inbox_raven_media_blocked;
break;
case SUGGESTED:
textRes = R.string.dms_inbox_raven_media_suggested;
break;
case SCREENSHOT:
textRes = R.string.dms_inbox_raven_media_screenshot;
break;
case CANNOT_DELIVER:
textRes = R.string.dms_inbox_raven_media_cant_deliver;
break;
}
if (textRes > 0) {
subtitle += itemView.getContext().getString(textRes);
}
return subtitle;
}
final MediaItemType mediaType = visualMedia.getMedia().getMediaType();
subtitle = getMediaSpecificSubtitle(username, resources, mediaType);
return subtitle;
}
private String getUsername(final List<User> users,
final long userId,
final long viewerId,
final Resources resources) {
if (userId == viewerId) {
return resources.getString(R.string.you);
}
final Optional<User> senderOptional = users.stream()
.filter(Objects::nonNull)
.filter(user -> user.getPk() == userId)
.findFirst();
return senderOptional.map(User::getUsername).orElse(null);
}
private void setDateTime(@NonNull final DirectItem item) {
final long timestamp = item.getTimestamp() / 1000;
final String dateTimeString = TextUtils.getRelativeDateTimeString(itemView.getContext(), timestamp);
@ -352,9 +142,7 @@ public final class DirectInboxItemViewHolder extends RecyclerView.ViewHolder {
}
private void setReadState(@NonNull final DirectThread thread) {
final DirectItem item = thread.getItems().get(0);
final Map<Long, DirectThreadLastSeenAt> lastSeenAtMap = thread.getLastSeenAt();
final boolean read = ResponseBodyUtils.isRead(item, lastSeenAtMap, Collections.singletonList(thread.getViewerId()), thread.getDirectStory());
final boolean read = DMUtils.isRead(thread);
binding.unread.setVisibility(read ? View.GONE : View.VISIBLE);
binding.threadTitle.setTypeface(null, read ? Typeface.NORMAL : Typeface.BOLD);
binding.subtitle.setTypeface(null, read ? Typeface.NORMAL : Typeface.BOLD);

View File

@ -45,6 +45,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectItemEmojiR
import awais.instagrabber.repositories.responses.directmessages.DirectItemReactions;
import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.DMUtils;
import awais.instagrabber.utils.DeepLinkParser;
import awais.instagrabber.utils.ResponseBodyUtils;
@ -196,7 +197,10 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
if (item.isPending()) {
binding.deliveryStatus.setImageResource(R.drawable.ic_check_24);
} else {
final boolean read = ResponseBodyUtils.isRead(item, thread.getLastSeenAt(), userIds, null);
final boolean read = DMUtils.isRead(item,
thread.getLastSeenAt(),
userIds
);
binding.deliveryStatus.setImageResource(R.drawable.ic_check_all_24);
ImageViewCompat.setImageTintList(
binding.deliveryStatus,
@ -321,8 +325,8 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
final String repliedToUsername = user != null ? user.getUsername() : "";
if (item.getUserId() == currentUser.getPk()) {
return thread.isGroup()
? resources.getString(R.string.replied_you_group, repliedToUsername)
: resources.getString(R.string.replied_you);
? resources.getString(R.string.replied_you_group, repliedToUsername)
: resources.getString(R.string.replied_you);
}
if (repliedToUserId == currentUser.getPk()) {
return resources.getString(R.string.replied_to_you);
@ -361,7 +365,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
final DirectItemReactions reactions = item.getReactions();
final List<DirectItemEmojiReaction> emojis = reactions != null ? reactions.getEmojis() : null;
if (emojis == null || emojis.isEmpty()) {
binding.container.setPadding(messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall);
binding.container.setPadding(messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall, 0);
binding.reactionsWrapper.setVisibility(View.GONE);
return;
}

View File

@ -5,7 +5,6 @@ import android.util.Log;
import androidx.annotation.NonNull;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.Locale;
@ -13,15 +12,12 @@ import java.util.Locale;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.NetworkUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.DirectMessagesService;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class CreateThreadAction extends AsyncTask<Void, Void, Void> {
private static final String TAG = "CommentAction";

View File

@ -5,9 +5,6 @@ import android.util.Log;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.repositories.responses.FriendshipStatus;
import awais.instagrabber.repositories.responses.User;

View File

@ -20,14 +20,16 @@ import java.util.Date;
import java.util.List;
import awais.instagrabber.db.dao.AccountDao;
import awais.instagrabber.db.dao.DMLastNotifiedDao;
import awais.instagrabber.db.dao.FavoriteDao;
import awais.instagrabber.db.entities.Account;
import awais.instagrabber.db.entities.DMLastNotified;
import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.Utils;
@Database(entities = {Account.class, Favorite.class},
version = 4)
@Database(entities = {Account.class, Favorite.class, DMLastNotified.class},
version = 5)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
private static final String TAG = AppDatabase.class.getSimpleName();
@ -38,12 +40,14 @@ public abstract class AppDatabase extends RoomDatabase {
public abstract FavoriteDao favoriteDao();
public abstract DMLastNotifiedDao dmLastNotifiedDao();
public static AppDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "cookiebox.db")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
.build();
}
}
@ -140,6 +144,18 @@ public abstract class AppDatabase extends RoomDatabase {
}
};
static final Migration MIGRATION_4_5 = new Migration(4, 5) {
@Override
public void migrate(@NonNull final SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS `dm_last_notified` (" +
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
"`thread_id` TEXT, " +
"`last_notified_msg_ts` INTEGER, " +
"`last_notified_at` INTEGER)");
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_dm_last_notified_thread_id` ON `dm_last_notified` (`thread_id`)");
}
};
@NonNull
private static List<Favorite> backupOldFavorites(@NonNull final SupportSQLiteDatabase db) {
// check if old favorites table had the column query_display

View File

@ -2,6 +2,10 @@ package awais.instagrabber.db;
import androidx.room.TypeConverter;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Date;
import awais.instagrabber.models.enums.FavoriteType;
@ -30,4 +34,16 @@ public class Converters {
public static String favoriteTypeToString(FavoriteType favoriteType) {
return favoriteType == null ? null : favoriteType.toString();
}
@TypeConverter
public static LocalDateTime fromTimestampToLocalDateTime(Long value) {
if (value == null) return null;
return LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneOffset.systemDefault());
}
@TypeConverter
public static Long localDateTimeToTimestamp(LocalDateTime localDateTime) {
if (localDateTime == null) return null;
return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}
}

View File

@ -0,0 +1,34 @@
package awais.instagrabber.db.dao;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
import awais.instagrabber.db.entities.DMLastNotified;
@Dao
public interface DMLastNotifiedDao {
@Query("SELECT * FROM dm_last_notified")
List<DMLastNotified> getAllDMDmLastNotified();
@Query("SELECT * FROM dm_last_notified WHERE thread_id = :threadId")
DMLastNotified findDMLastNotifiedByThreadId(String threadId);
@Insert(onConflict = OnConflictStrategy.REPLACE)
List<Long> insertDMLastNotified(DMLastNotified... dmLastNotified);
@Update
void updateDMLastNotified(DMLastNotified... dmLastNotified);
@Delete
void deleteDMLastNotified(DMLastNotified... dmLastNotified);
@Query("DELETE from dm_last_notified")
void deleteAllDMLastNotified();
}

View File

@ -0,0 +1,70 @@
package awais.instagrabber.db.datasources;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.time.LocalDateTime;
import java.util.List;
import awais.instagrabber.db.AppDatabase;
import awais.instagrabber.db.dao.DMLastNotifiedDao;
import awais.instagrabber.db.entities.DMLastNotified;
public class DMLastNotifiedDataSource {
private static final String TAG = DMLastNotifiedDataSource.class.getSimpleName();
private static DMLastNotifiedDataSource INSTANCE;
private final DMLastNotifiedDao dmLastNotifiedDao;
private DMLastNotifiedDataSource(final DMLastNotifiedDao dmLastNotifiedDao) {
this.dmLastNotifiedDao = dmLastNotifiedDao;
}
public static DMLastNotifiedDataSource getInstance(@NonNull Context context) {
if (INSTANCE == null) {
synchronized (DMLastNotifiedDataSource.class) {
if (INSTANCE == null) {
final AppDatabase database = AppDatabase.getDatabase(context);
INSTANCE = new DMLastNotifiedDataSource(database.dmLastNotifiedDao());
}
}
}
return INSTANCE;
}
@Nullable
public final DMLastNotified getDMLastNotified(final String threadId) {
return dmLastNotifiedDao.findDMLastNotifiedByThreadId(threadId);
}
@NonNull
public final List<DMLastNotified> getAllDMDmLastNotified() {
return dmLastNotifiedDao.getAllDMDmLastNotified();
}
public final void insertOrUpdateDMLastNotified(final String threadId,
final LocalDateTime lastNotifiedMsgTs,
final LocalDateTime lastNotifiedAt) {
final DMLastNotified dmLastNotified = getDMLastNotified(threadId);
final DMLastNotified toUpdate = new DMLastNotified(dmLastNotified == null ? 0 : dmLastNotified.getId(),
threadId,
lastNotifiedMsgTs,
lastNotifiedAt);
if (dmLastNotified != null) {
dmLastNotifiedDao.updateDMLastNotified(toUpdate);
return;
}
dmLastNotifiedDao.insertDMLastNotified(toUpdate);
}
public final void deleteDMLastNotified(@NonNull final DMLastNotified dmLastNotified) {
dmLastNotifiedDao.deleteDMLastNotified(dmLastNotified);
}
public final void deleteAllDMLastNotified() {
dmLastNotifiedDao.deleteAllDMLastNotified();
}
}

View File

@ -0,0 +1,85 @@
package awais.instagrabber.db.entities;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import java.time.LocalDateTime;
import java.util.Objects;
@Entity(tableName = DMLastNotified.TABLE_NAME, indices = {@Index(value = DMLastNotified.COL_THREAD_ID, unique = true)})
public class DMLastNotified {
public final static String TABLE_NAME = "dm_last_notified";
public final static String COL_ID = "id";
public final static String COL_THREAD_ID = "thread_id";
public final static String COL_LAST_NOTIFIED_MSG_TS = "last_notified_msg_ts";
public final static String COL_LAST_NOTIFIED_AT = "last_notified_at";
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = COL_ID)
private final int id;
@ColumnInfo(name = COL_THREAD_ID)
private final String threadId;
@ColumnInfo(name = COL_LAST_NOTIFIED_MSG_TS)
private final LocalDateTime lastNotifiedMsgTs;
@ColumnInfo(name = COL_LAST_NOTIFIED_AT)
private final LocalDateTime lastNotifiedAt;
public DMLastNotified(final int id,
final String threadId,
final LocalDateTime lastNotifiedMsgTs,
final LocalDateTime lastNotifiedAt) {
this.id = id;
this.threadId = threadId;
this.lastNotifiedMsgTs = lastNotifiedMsgTs;
this.lastNotifiedAt = lastNotifiedAt;
}
public int getId() {
return id;
}
public String getThreadId() {
return threadId;
}
public LocalDateTime getLastNotifiedMsgTs() {
return lastNotifiedMsgTs;
}
public LocalDateTime getLastNotifiedAt() {
return lastNotifiedAt;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DMLastNotified that = (DMLastNotified) o;
return id == that.id &&
Objects.equals(threadId, that.threadId) &&
Objects.equals(lastNotifiedMsgTs, that.lastNotifiedMsgTs) &&
Objects.equals(lastNotifiedAt, that.lastNotifiedAt);
}
@Override
public int hashCode() {
return Objects.hash(id, threadId, lastNotifiedMsgTs, lastNotifiedAt);
}
@NonNull
@Override
public String toString() {
return "DMLastNotified{" +
"id=" + id +
", threadId='" + threadId + '\'' +
", lastNotifiedMsgTs='" + lastNotifiedMsgTs + '\'' +
", lastNotifiedAt='" + lastNotifiedAt + '\'' +
'}';
}
}

View File

@ -0,0 +1,126 @@
package awais.instagrabber.db.repositories;
import java.time.LocalDateTime;
import java.util.List;
import awais.instagrabber.db.datasources.DMLastNotifiedDataSource;
import awais.instagrabber.db.entities.DMLastNotified;
import awais.instagrabber.utils.AppExecutors;
public class DMLastNotifiedRepository {
private static final String TAG = DMLastNotifiedRepository.class.getSimpleName();
private static DMLastNotifiedRepository instance;
private final AppExecutors appExecutors;
private final DMLastNotifiedDataSource dmLastNotifiedDataSource;
private DMLastNotifiedRepository(final AppExecutors appExecutors, final DMLastNotifiedDataSource dmLastNotifiedDataSource) {
this.appExecutors = appExecutors;
this.dmLastNotifiedDataSource = dmLastNotifiedDataSource;
}
public static DMLastNotifiedRepository getInstance(final DMLastNotifiedDataSource dmLastNotifiedDataSource) {
if (instance == null) {
instance = new DMLastNotifiedRepository(AppExecutors.getInstance(), dmLastNotifiedDataSource);
}
return instance;
}
public void getDMLastNotified(final String threadId,
final RepositoryCallback<DMLastNotified> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
final DMLastNotified dmLastNotified = dmLastNotifiedDataSource.getDMLastNotified(threadId);
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
if (dmLastNotified == null) {
callback.onDataNotAvailable();
return;
}
callback.onSuccess(dmLastNotified);
});
});
}
public void getAllDMDmLastNotified(final RepositoryCallback<List<DMLastNotified>> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
final List<DMLastNotified> allDMDmLastNotified = dmLastNotifiedDataSource.getAllDMDmLastNotified();
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
if (allDMDmLastNotified == null) {
callback.onDataNotAvailable();
return;
}
// cachedAccounts = accounts;
callback.onSuccess(allDMDmLastNotified);
});
});
}
public void insertOrUpdateDMLastNotified(final List<DMLastNotified> dmLastNotifiedList,
final RepositoryCallback<Void> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
for (final DMLastNotified dmLastNotified : dmLastNotifiedList) {
dmLastNotifiedDataSource.insertOrUpdateDMLastNotified(dmLastNotified.getThreadId(),
dmLastNotified.getLastNotifiedMsgTs(),
dmLastNotified.getLastNotifiedAt());
}
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
callback.onSuccess(null);
});
});
}
public void insertOrUpdateDMLastNotified(final String threadId,
final LocalDateTime lastNotifiedMsgTs,
final LocalDateTime lastNotifiedAt,
final RepositoryCallback<DMLastNotified> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
dmLastNotifiedDataSource.insertOrUpdateDMLastNotified(threadId, lastNotifiedMsgTs, lastNotifiedAt);
final DMLastNotified updated = dmLastNotifiedDataSource.getDMLastNotified(threadId);
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
if (updated == null) {
callback.onDataNotAvailable();
return;
}
callback.onSuccess(updated);
});
});
}
public void deleteDMLastNotified(final DMLastNotified dmLastNotified,
final RepositoryCallback<Void> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
dmLastNotifiedDataSource.deleteDMLastNotified(dmLastNotified);
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
callback.onSuccess(null);
});
});
}
public void deleteAllDMLastNotified(final RepositoryCallback<Void> callback) {
// request on the I/O thread
appExecutors.diskIO().execute(() -> {
dmLastNotifiedDataSource.deleteAllDMLastNotified();
// notify on the main thread
appExecutors.mainThread().execute(() -> {
if (callback == null) return;
callback.onSuccess(null);
});
});
}
}

View File

@ -0,0 +1,150 @@
package awais.instagrabber.dialogs;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.text.Editable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.GridLayoutManager;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.android.material.snackbar.Snackbar;
import awais.instagrabber.R;
import awais.instagrabber.adapters.GifItemsAdapter;
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
import awais.instagrabber.databinding.LayoutGifPickerBinding;
import awais.instagrabber.repositories.responses.giphy.GiphyGif;
import awais.instagrabber.utils.Debouncer;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.viewmodels.GifPickerViewModel;
public class GifPickerBottomDialogFragment extends BottomSheetDialogFragment {
private static final String TAG = GifPickerBottomDialogFragment.class.getSimpleName();
private static final int INPUT_DEBOUNCE_INTERVAL = 500;
private static final String INPUT_KEY = "gif_search_input";
private LayoutGifPickerBinding binding;
private GifPickerViewModel viewModel;
private GifItemsAdapter gifItemsAdapter;
private OnSelectListener onSelectListener;
private Debouncer<String> inputDebouncer;
public static GifPickerBottomDialogFragment newInstance() {
final Bundle args = new Bundle();
final GifPickerBottomDialogFragment fragment = new GifPickerBottomDialogFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, R.style.ThemeOverlay_Rounded_BottomSheetDialog);
final Debouncer.Callback<String> callback = new Debouncer.Callback<String>() {
@Override
public void call(final String key) {
final Editable text = binding.input.getText();
if (TextUtils.isEmpty(text)) {
viewModel.search(null);
return;
}
viewModel.search(text.toString().trim());
}
@Override
public void onError(final Throwable t) {
Log.e(TAG, "onError: ", t);
}
};
inputDebouncer = new Debouncer<>(callback, INPUT_DEBOUNCE_INTERVAL);
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
binding = LayoutGifPickerBinding.inflate(inflater, container, false);
viewModel = new ViewModelProvider(this).get(GifPickerViewModel.class);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
init();
}
@Override
public void onStart() {
super.onStart();
final Dialog dialog = getDialog();
if (dialog == null) return;
final BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialog;
final View bottomSheetInternal = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
if (bottomSheetInternal == null) return;
bottomSheetInternal.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
bottomSheetInternal.requestLayout();
}
private void init() {
setupList();
setupInput();
setupObservers();
}
private void setupList() {
final Context context = getContext();
if (context == null) return;
binding.gifList.setLayoutManager(new GridLayoutManager(context, 3));
binding.gifList.setHasFixedSize(true);
gifItemsAdapter = new GifItemsAdapter(entry -> {
if (onSelectListener == null) return;
onSelectListener.onSelect(entry);
});
binding.gifList.setAdapter(gifItemsAdapter);
}
private void setupInput() {
binding.input.addTextChangedListener(new TextWatcherAdapter() {
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
inputDebouncer.call(INPUT_KEY);
}
});
}
private void setupObservers() {
viewModel.getImages().observe(getViewLifecycleOwner(), imagesResource -> {
if (imagesResource == null) return;
switch (imagesResource.status) {
case SUCCESS:
gifItemsAdapter.submitList(imagesResource.data);
break;
case ERROR:
final Context context = getContext();
if (context != null && imagesResource.message != null) {
Snackbar.make(context, binding.getRoot(), imagesResource.message, Snackbar.LENGTH_LONG);
}
break;
case LOADING:
break;
}
});
}
public void setOnSelectListener(final OnSelectListener onSelectListener) {
this.onSelectListener = onSelectListener;
}
public interface OnSelectListener {
void onSelect(GiphyGif giphyGif);
}
}

View File

@ -41,20 +41,26 @@ import static awais.instagrabber.utils.Utils.settingsHelper;
public class ProfilePicDialogFragment extends DialogFragment {
private static final String TAG = "ProfilePicDlgFragment";
private final long id;
private final String name;
private final String fallbackUrl;
private long id;
private String name;
private String fallbackUrl;
private boolean isLoggedIn;
private DialogProfilepicBinding binding;
private String url;
public ProfilePicDialogFragment(final long id, final String name, final String fallbackUrl) {
this.id = id;
this.name = name;
this.fallbackUrl = fallbackUrl;
public static ProfilePicDialogFragment getInstance(final long id, final String name, final String fallbackUrl) {
final Bundle args = new Bundle();
args.putLong("id", id);
args.putString("name", name);
args.putString("fallbackUrl", fallbackUrl);
final ProfilePicDialogFragment fragment = new ProfilePicDialogFragment();
fragment.setArguments(args);
return fragment;
}
public ProfilePicDialogFragment() {}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
@ -94,6 +100,14 @@ public class ProfilePicDialogFragment extends DialogFragment {
}
private void init() {
final Bundle arguments = getArguments();
if (arguments == null) {
dismiss();
return;
}
id = arguments.getLong("id");
name = arguments.getString("name");
fallbackUrl = arguments.getString("fallbackUrl");
binding.download.setOnClickListener(v -> {
final Context context = getContext();
if (context == null) return;
@ -127,11 +141,12 @@ public class ProfilePicDialogFragment extends DialogFragment {
@Override
public void onFailure(final Throwable t) {
final Context context = getContext();
try {
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
if (context == null) {
dismiss();
return;
}
catch(final Throwable e) {}
getDialog().dismiss();
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
dismiss();
}
});
} else setupPhoto(fallbackUrl);

View File

@ -416,6 +416,8 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
// binding.locationBiography.setCaptionIsExpandable(true);
// binding.locationBiography.setCaptionIsExpanded(true);
final Context context = getContext();
if (context == null) return;
if (TextUtils.isEmpty(biography)) {
locationDetailsBinding.locationBiography.setVisibility(View.GONE);
} else {
@ -432,13 +434,13 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
final String originalText = autoLinkItem.getOriginalText().trim();
navigateToProfile(originalText);
});
locationDetailsBinding.locationBiography.addOnEmailClickListener(autoLinkItem -> Utils.openEmailAddress(getContext(),
locationDetailsBinding.locationBiography.addOnEmailClickListener(autoLinkItem -> Utils.openEmailAddress(context,
autoLinkItem.getOriginalText()
.trim()));
locationDetailsBinding.locationBiography
.addOnURLClickListener(autoLinkItem -> Utils.openURL(getContext(), autoLinkItem.getOriginalText().trim()));
.addOnURLClickListener(autoLinkItem -> Utils.openURL(context, autoLinkItem.getOriginalText().trim()));
locationDetailsBinding.locationBiography.setOnLongClickListener(v -> {
Utils.copyText(getContext(), biography);
Utils.copyText(context, biography);
return true;
});
}
@ -465,7 +467,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
locationDetailsBinding.locationUrl.setVisibility(View.VISIBLE);
locationDetailsBinding.locationUrl.setText(TextUtils.getSpannableUrl(url));
}
final FavoriteDataSource dataSource = FavoriteDataSource.getInstance(getContext());
final FavoriteDataSource dataSource = FavoriteDataSource.getInstance(context);
final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(dataSource);
locationDetailsBinding.favChip.setVisibility(View.VISIBLE);
favoriteRepository.getFavorite(String.valueOf(locationId), FavoriteType.LOCATION, new RepositoryCallback<Favorite>() {

View File

@ -29,6 +29,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.badge.BadgeDrawable;
import com.google.android.material.badge.BadgeUtils;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.snackbar.Snackbar;
import java.util.List;
@ -172,6 +173,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
}
private void setupObservers() {
removeViewModelObservers();
threadsObserver = list -> {
if (inboxAdapter == null) return;
inboxAdapter.submitList(list, () -> {
@ -181,8 +183,28 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
});
};
viewModel.getThreads().observe(fragmentActivity, threadsObserver);
viewModel.getFetchingInbox().observe(getViewLifecycleOwner(), fetching -> binding.swipeRefreshLayout.setRefreshing(fetching));
viewModel.getUnseenCount().observe(getViewLifecycleOwner(), this::setBottomNavBarBadge);
viewModel.getInbox().observe(getViewLifecycleOwner(), inboxResource -> {
if (inboxResource == null) return;
switch (inboxResource.status) {
case SUCCESS:
binding.swipeRefreshLayout.setRefreshing(false);
break;
case ERROR:
if (inboxResource.message != null) {
Snackbar.make(binding.getRoot(), inboxResource.message, Snackbar.LENGTH_LONG).show();
}
binding.swipeRefreshLayout.setRefreshing(false);
break;
case LOADING:
binding.swipeRefreshLayout.setRefreshing(true);
break;
}
});
viewModel.getUnseenCount().observe(getViewLifecycleOwner(), unseenCountResource -> {
if (unseenCountResource == null) return;
final Integer unseenCount = unseenCountResource.data;
setBottomNavBarBadge(unseenCount == null ? 0 : unseenCount);
});
viewModel.getPendingRequestsTotal().observe(getViewLifecycleOwner(), this::attachPendingRequestsBadge);
}
@ -230,11 +252,10 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
inboxAdapter = new DirectMessageInboxAdapter(thread -> {
if (navigating) return;
navigating = true;
final Bundle bundle = new Bundle();
bundle.putString("threadId", thread.getThreadId());
bundle.putString("title", thread.getThreadTitle());
if (isAdded()) {
NavHostFragment.findNavController(this).navigate(R.id.action_inbox_to_thread, bundle);
final DirectMessageInboxFragmentDirections.ActionInboxToThread directions = DirectMessageInboxFragmentDirections
.actionInboxToThread(thread.getThreadId(), thread.getThreadTitle());
NavHostFragment.findNavController(this).navigate(directions);
}
navigating = false;
});

View File

@ -11,13 +11,11 @@ import android.widget.CompoundButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
@ -31,13 +29,13 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import awais.instagrabber.ProfileNavGraphDirections;
import awais.instagrabber.R;
import awais.instagrabber.UserSearchNavGraphDirections;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.DirectPendingUsersAdapter;
import awais.instagrabber.adapters.DirectPendingUsersAdapter.PendingUser;
import awais.instagrabber.adapters.DirectPendingUsersAdapter.PendingUserCallback;
@ -52,12 +50,11 @@ import awais.instagrabber.fragments.UserSearchFragment;
import awais.instagrabber.fragments.UserSearchFragmentDirections;
import awais.instagrabber.models.Resource;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadParticipantRequestsResponse;
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
import awais.instagrabber.viewmodels.DirectInboxViewModel;
import awais.instagrabber.viewmodels.DirectPendingInboxViewModel;
import awais.instagrabber.viewmodels.AppStateViewModel;
import awais.instagrabber.viewmodels.DirectSettingsViewModel;
import awais.instagrabber.viewmodels.factories.DirectSettingsViewModelFactory;
public class DirectMessageSettingsFragment extends Fragment implements ConfirmDialogFragmentCallback {
private static final String TAG = DirectMessageSettingsFragment.class.getSimpleName();
@ -77,33 +74,14 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
super.onCreate(savedInstanceState);
final Bundle arguments = getArguments();
if (arguments == null) return;
final NavController navController = NavHostFragment.findNavController(this);
final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
final DirectMessageSettingsFragmentArgs args = DirectMessageSettingsFragmentArgs.fromBundle(arguments);
final boolean pending = args.getPending();
final List<DirectThread> threads;
final User viewer;
if (pending) {
final DirectPendingInboxViewModel inboxViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectPendingInboxViewModel.class);
threads = inboxViewModel.getThreads().getValue();
viewer = inboxViewModel.getViewer();
} else {
final DirectInboxViewModel inboxViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
threads = inboxViewModel.getThreads().getValue();
viewer = inboxViewModel.getViewer();
}
final String threadId = args.getThreadId();
final Optional<DirectThread> first = threads != null ? threads.stream()
.filter(thread -> thread.getThreadId().equals(threadId))
.findFirst()
: Optional.empty();
if (!first.isPresent()) {
navController.navigateUp();
return;
}
viewModel = new ViewModelProvider(this).get(DirectSettingsViewModel.class);
viewModel.setViewer(viewer);
viewModel.setThread(first.get());
final MainActivity fragmentActivity = (MainActivity) requireActivity();
final AppStateViewModel appStateViewModel = new ViewModelProvider(fragmentActivity).get(AppStateViewModel.class);
viewModel = new ViewModelProvider(this, new DirectSettingsViewModelFactory(fragmentActivity.getApplication(),
args.getThreadId(),
args.getPending(),
appStateViewModel.getCurrentUser()))
.get(DirectSettingsViewModel.class);
}
@NonNull
@ -143,21 +121,23 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
binding.muteMessages.setVisibility(View.GONE);
}
});
viewModel.getUsers().observe(getViewLifecycleOwner(), users -> {
// Need to observe, so that getValue is correct
viewModel.getUsers().observe(getViewLifecycleOwner(), users -> {});
viewModel.getLeftUsers().observe(getViewLifecycleOwner(), users -> {});
viewModel.getUsersAndLeftUsers().observe(getViewLifecycleOwner(), usersPair -> {
if (usersAdapter == null) return;
usersAdapter.submitUsers(users.first, users.second);
usersAdapter.submitUsers(usersPair.first, usersPair.second);
});
viewModel.getTitle().observe(getViewLifecycleOwner(), title -> binding.titleEdit.setText(title));
viewModel.getAdminUserIds().observe(getViewLifecycleOwner(), adminUserIds -> {
if (usersAdapter == null) return;
usersAdapter.setAdminUserIds(adminUserIds);
});
viewModel.getMuted().observe(getViewLifecycleOwner(), muted -> binding.muteMessages.setChecked(muted));
viewModel.isMuted().observe(getViewLifecycleOwner(), muted -> binding.muteMessages.setChecked(muted));
viewModel.isPending().observe(getViewLifecycleOwner(), pending -> binding.muteMessages.setVisibility(pending ? View.GONE : View.VISIBLE));
if (viewModel.isViewerAdmin()) {
viewModel.getApprovalRequiredToJoin().observe(getViewLifecycleOwner(), required -> binding.approvalRequired.setChecked(required));
viewModel.getPendingRequests().observe(getViewLifecycleOwner(), this::setPendingRequests);
}
viewModel.isViewerAdmin().observe(getViewLifecycleOwner(), this::setApprovalRelatedUI);
viewModel.getApprovalRequiredToJoin().observe(getViewLifecycleOwner(), required -> binding.approvalRequired.setChecked(required));
viewModel.getPendingRequests().observe(getViewLifecycleOwner(), this::setPendingRequests);
final NavController navController = NavHostFragment.findNavController(this);
final NavBackStackEntry backStackEntry = navController.getCurrentBackStackEntry();
if (backStackEntry != null) {
@ -192,7 +172,11 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
private void addMembers(final Set<User> users) {
final Boolean approvalRequired = viewModel.getApprovalRequiredToJoin().getValue();
if (!viewModel.isViewerAdmin() && approvalRequired != null && approvalRequired) {
Boolean isViewerAdmin = viewModel.isViewerAdmin().getValue();
if (isViewerAdmin == null) {
isViewerAdmin = false;
}
if (!isViewerAdmin && approvalRequired != null && approvalRequired) {
approvalRequiredUsers = users;
final ConfirmDialogFragment confirmDialogFragment = ConfirmDialogFragment.newInstance(
APPROVAL_REQUIRED_REQUEST_CODE,
@ -226,13 +210,15 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
}
private void setupSettings() {
binding.groupSettings.setVisibility(viewModel.isGroup() ? View.VISIBLE : View.GONE);
Boolean isGroup = viewModel.isGroup().getValue();
if (isGroup == null) isGroup = false;
binding.groupSettings.setVisibility(isGroup ? View.VISIBLE : View.GONE);
binding.muteMessagesLabel.setOnClickListener(v -> binding.muteMessages.toggle());
binding.muteMessages.setOnCheckedChangeListener((buttonView, isChecked) -> {
final LiveData<Resource<Object>> resourceLiveData = isChecked ? viewModel.mute() : viewModel.unmute();
handleSwitchChangeResource(resourceLiveData, buttonView);
});
if (!viewModel.isGroup()) return;
if (!isGroup) return;
binding.titleEdit.addTextChangedListener(new TextWatcherAdapter() {
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
@ -256,14 +242,13 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
final NavDestination currentDestination = navController.getCurrentDestination();
if (currentDestination == null) return;
if (currentDestination.getId() != R.id.directMessagesSettingsFragment) return;
final Pair<List<User>, List<User>> users = viewModel.getUsers().getValue();
final List<User> users = viewModel.getUsers().getValue();
final long[] currentUserIds;
if (users != null && users.first != null) {
final List<User> currentMembers = users.first;
currentUserIds = currentMembers.stream()
.mapToLong(User::getPk)
.sorted()
.toArray();
if (users != null) {
currentUserIds = users.stream()
.mapToLong(User::getPk)
.sorted()
.toArray();
} else {
currentUserIds = new long[0];
}
@ -281,7 +266,6 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
final LiveData<Resource<Object>> resourceLiveData = isChecked ? viewModel.muteMentions() : viewModel.unmuteMentions();
handleSwitchChangeResource(resourceLiveData, buttonView);
});
setApprovalRelatedUI();
binding.leave.setOnClickListener(v -> {
final ConfirmDialogFragment confirmDialogFragment = ConfirmDialogFragment.newInstance(
LEAVE_THREAD_REQUEST_CODE,
@ -293,7 +277,9 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
);
confirmDialogFragment.show(getChildFragmentManager(), "leave_thread_confirmation_dialog");
});
if (viewModel.isViewerAdmin()) {
Boolean isViewerAdmin = viewModel.isViewerAdmin().getValue();
if (isViewerAdmin == null) isViewerAdmin = false;
if (isViewerAdmin) {
binding.end.setVisibility(View.VISIBLE);
binding.end.setOnClickListener(v -> {
final ConfirmDialogFragment confirmDialogFragment = ConfirmDialogFragment.newInstance(
@ -311,8 +297,8 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
}
}
private void setApprovalRelatedUI() {
if (!viewModel.isViewerAdmin()) {
private void setApprovalRelatedUI(final boolean isViewerAdmin) {
if (!isViewerAdmin) {
binding.pendingMembersGroup.setVisibility(View.GONE);
binding.approvalRequired.setVisibility(View.GONE);
binding.approvalRequiredLabel.setVisibility(View.GONE);
@ -352,7 +338,7 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
final Context context = getContext();
if (context == null) return;
binding.users.setLayoutManager(new LinearLayoutManager(context));
final User inviter = viewModel.getThread().getInviter();
final User inviter = viewModel.getInviter().getValue();
usersAdapter = new DirectUsersAdapter(
inviter != null ? inviter.getPk() : -1,
(position, user, selected) -> {

View File

@ -30,14 +30,12 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
@ -58,7 +56,6 @@ import com.google.common.collect.ImmutableList;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import awais.instagrabber.ProfileNavGraphDirections;
@ -83,6 +80,7 @@ import awais.instagrabber.customviews.helpers.SwipeAndRestoreItemTouchHelperCall
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding;
import awais.instagrabber.dialogs.DirectItemReactionDialogFragment;
import awais.instagrabber.dialogs.GifPickerBottomDialogFragment;
import awais.instagrabber.dialogs.MediaPickerBottomDialogFragment;
import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.fragments.UserSearchFragment;
@ -104,9 +102,8 @@ import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.AppStateViewModel;
import awais.instagrabber.viewmodels.DirectInboxViewModel;
import awais.instagrabber.viewmodels.DirectPendingInboxViewModel;
import awais.instagrabber.viewmodels.DirectThreadViewModel;
import awais.instagrabber.viewmodels.factories.DirectThreadViewModelFactory;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
@ -232,7 +229,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
@Override
public void onReaction(final DirectItem item, final Emoji emoji) {
if (item == null) return;
final LiveData<Resource<DirectItem>> resourceLiveData = viewModel.sendReaction(item, emoji);
final LiveData<Resource<Object>> resourceLiveData = viewModel.sendReaction(item, emoji);
if (resourceLiveData != null) {
resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData));
}
@ -292,13 +289,29 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
};
private final MutableLiveData<Integer> inputLength = new MutableLiveData<>(0);
private ItemTouchHelper itemTouchHelper;
private LiveData<Boolean> pendingLiveData;
private LiveData<DirectThread> threadLiveData;
private LiveData<Integer> inputModeLiveData;
private LiveData<String> threadTitleLiveData;
private LiveData<Resource<Object>> fetchingLiveData;
private LiveData<List<DirectItem>> itemsLiveData;
private LiveData<DirectItem> replyToItemLiveData;
private LiveData<Integer> pendingRequestsCountLiveData;
private LiveData<List<User>> usersLiveData;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity();
appStateViewModel = new ViewModelProvider(fragmentActivity).get(AppStateViewModel.class);
viewModel = new ViewModelProvider(this).get(DirectThreadViewModel.class);
final Bundle arguments = getArguments();
if (arguments == null) return;
final DirectMessageThreadFragmentArgs fragmentArgs = DirectMessageThreadFragmentArgs.fromBundle(arguments);
viewModel = new ViewModelProvider(this, new DirectThreadViewModelFactory(fragmentActivity.getApplication(),
fragmentArgs.getThreadId(),
fragmentArgs.getPending(),
appStateViewModel.getCurrentUser()))
.get(DirectThreadViewModel.class);
setHasOptionsMenu(true);
}
@ -329,7 +342,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
init();
binding.send.post(() -> initialSendX = binding.send.getX());
shouldRefresh = false;
setObservers();
}
@Override
@ -403,6 +415,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
wasKbShowing = true;
binding.emojiPicker.setAlpha(0);
}
removeObservers();
super.onPause();
}
@ -420,7 +433,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
}
binding.send.stopScale();
setupBackStackResultObserver();
attachPendingRequestsBadge(viewModel.getPendingRequestsCount().getValue());
setObservers();
// attachPendingRequestsBadge(viewModel.getPendingRequestsCount().getValue());
}
@Override
@ -460,42 +474,38 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (context == null) return;
if (getArguments() == null) return;
actionBar = fragmentActivity.getSupportActionBar();
final DirectMessageThreadFragmentArgs fragmentArgs = DirectMessageThreadFragmentArgs.fromBundle(getArguments());
viewModel.getThreadTitle().postValue(fragmentArgs.getTitle());
final String threadId = fragmentArgs.getThreadId();
viewModel.setThreadId(threadId);
setupList();
root.post(this::setupInput);
root.post(this::getInitialData);
// root.post(this::getInitialData);
}
private void getInitialData() {
final Bundle arguments = getArguments();
if (arguments == null) return;
final DirectMessageThreadFragmentArgs args = DirectMessageThreadFragmentArgs.fromBundle(arguments);
final boolean pending = args.getPending();
final NavController navController = NavHostFragment.findNavController(this);
final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
final List<DirectThread> threads;
if (!pending) {
final DirectInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
threads = threadListViewModel.getThreads().getValue();
} else {
final DirectPendingInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectPendingInboxViewModel.class);
threads = threadListViewModel.getThreads().getValue();
}
final Optional<DirectThread> first = threads != null
? threads.stream()
.filter(thread -> thread.getThreadId().equals(viewModel.getThreadId()))
.findFirst()
: Optional.empty();
if (first.isPresent()) {
final DirectThread thread = first.get();
viewModel.setThread(thread);
return;
}
viewModel.fetchChats();
}
// private void getInitialData() {
// final Bundle arguments = getArguments();
// if (arguments == null) return;
// final DirectMessageThreadFragmentArgs args = DirectMessageThreadFragmentArgs.fromBundle(arguments);
// final boolean pending = args.getPending();
// final NavController navController = NavHostFragment.findNavController(this);
// final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
// final List<DirectThread> threads;
// if (!pending) {
// final DirectInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
// threads = threadListViewModel.getThreads().getValue();
// } else {
// final DirectPendingInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectPendingInboxViewModel.class);
// threads = threadListViewModel.getThreads().getValue();
// }
// final Optional<DirectThread> first = threads != null
// ? threads.stream()
// .filter(thread -> thread.getThreadId().equals(viewModel.getThreadId()))
// .findFirst()
// : Optional.empty();
// if (first.isPresent()) {
// final DirectThread thread = first.get();
// viewModel.setThread(thread);
// return;
// }
// viewModel.fetchChats();
// }
private void setupList() {
final Context context = getContext();
@ -539,7 +549,14 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
}
private void setObservers() {
viewModel.isPending().observe(getViewLifecycleOwner(), isPending -> {
threadLiveData = viewModel.getThread();
if (threadLiveData == null) {
final NavController navController = NavHostFragment.findNavController(this);
navController.navigateUp();
return;
}
pendingLiveData = viewModel.isPending();
pendingLiveData.observe(getViewLifecycleOwner(), isPending -> {
if (isPending == null) {
hideInput();
return;
@ -553,7 +570,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (inputMode != null && inputMode == 1) return;
showInput();
});
viewModel.getInputMode().observe(getViewLifecycleOwner(), inputMode -> {
inputModeLiveData = viewModel.getInputMode();
inputModeLiveData.observe(getViewLifecycleOwner(), inputMode -> {
final Boolean isPending = viewModel.isPending().getValue();
if (isPending != null && isPending) return;
if (inputMode == null || inputMode == 0) return;
@ -561,21 +579,34 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
hideInput();
}
});
viewModel.getThreadTitle().observe(getViewLifecycleOwner(), this::setTitle);
viewModel.getFetching().observe(getViewLifecycleOwner(), fetching -> {
if (fetching) {
setTitle(getString(R.string.dms_thread_updating));
return;
threadTitleLiveData = viewModel.getThreadTitle();
threadTitleLiveData.observe(getViewLifecycleOwner(), this::setTitle);
fetchingLiveData = viewModel.isFetching();
fetchingLiveData.observe(getViewLifecycleOwner(), fetchingResource -> {
if (fetchingResource == null) return;
switch (fetchingResource.status) {
case SUCCESS:
case ERROR:
setTitle(viewModel.getThreadTitle().getValue());
if (fetchingResource.message != null) {
Snackbar.make(binding.getRoot(), fetchingResource.message, Snackbar.LENGTH_LONG).show();
}
break;
case LOADING:
setTitle(getString(R.string.dms_thread_updating));
break;
}
setTitle(viewModel.getThreadTitle().getValue());
});
final ItemsAdapterDataMerger itemsAdapterDataMerger = new ItemsAdapterDataMerger(appStateViewModel.getCurrentUser(), viewModel.getThread());
itemsAdapterDataMerger.observe(getViewLifecycleOwner(), userThreadPair -> {
viewModel.setCurrentUser(userThreadPair.first);
setupItemsAdapter(userThreadPair.first, userThreadPair.second);
});
viewModel.getItems().observe(getViewLifecycleOwner(), this::submitItemsToAdapter);
viewModel.getReplyToItem().observe(getViewLifecycleOwner(), item -> {
// final ItemsAdapterDataMerger itemsAdapterDataMerger = new ItemsAdapterDataMerger(appStateViewModel.getCurrentUser(), viewModel.getThread());
// itemsAdapterDataMerger.observe(getViewLifecycleOwner(), userThreadPair -> {
// viewModel.setCurrentUser(userThreadPair.first);
// setupItemsAdapter(userThreadPair.first, userThreadPair.second);
// });
threadLiveData.observe(getViewLifecycleOwner(), this::setupItemsAdapter);
itemsLiveData = viewModel.getItems();
itemsLiveData.observe(getViewLifecycleOwner(), this::submitItemsToAdapter);
replyToItemLiveData = viewModel.getReplyToItem();
replyToItemLiveData.observe(getViewLifecycleOwner(), item -> {
if (item == null) {
if (binding.input.length() == 0) {
showExtraInputOption(true);
@ -630,14 +661,30 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
}
prevLength = length;
});
viewModel.getPendingRequestsCount().observe(getViewLifecycleOwner(), this::attachPendingRequestsBadge);
viewModel.getUsers().observe(getViewLifecycleOwner(), users -> {
pendingRequestsCountLiveData = viewModel.getPendingRequestsCount();
pendingRequestsCountLiveData.observe(getViewLifecycleOwner(), this::attachPendingRequestsBadge);
usersLiveData = viewModel.getUsers();
usersLiveData.observe(getViewLifecycleOwner(), users -> {
if (users == null || users.isEmpty()) return;
final User user = users.get(0);
binding.acceptPendingRequestQuestion.setText(getString(R.string.accept_request_from_user, user.getUsername(), user.getFullName()));
});
}
private void removeObservers() {
pendingLiveData.removeObservers(getViewLifecycleOwner());
inputModeLiveData.removeObservers(getViewLifecycleOwner());
threadTitleLiveData.removeObservers(getViewLifecycleOwner());
fetchingLiveData.removeObservers(getViewLifecycleOwner());
threadLiveData.removeObservers(getViewLifecycleOwner());
itemsLiveData.removeObservers(getViewLifecycleOwner());
replyToItemLiveData.removeObservers(getViewLifecycleOwner());
inputLength.removeObservers(getViewLifecycleOwner());
pendingRequestsCountLiveData.removeObservers(getViewLifecycleOwner());
usersLiveData.removeObservers(getViewLifecycleOwner());
}
private void hidePendingOptions() {
binding.acceptPendingRequestQuestion.setVisibility(View.GONE);
binding.decline.setVisibility(View.GONE);
@ -666,9 +713,15 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
case SUCCESS:
resourceLiveData.removeObservers(getViewLifecycleOwner());
if (isDecline) {
removeObservers();
viewModel.removeThread();
final NavController navController = NavHostFragment.findNavController(this);
navController.navigateUp();
return;
}
removeObservers();
viewModel.moveFromPending();
setObservers();
break;
case LOADING:
break;
@ -684,6 +737,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private void hideInput() {
binding.emojiToggle.setVisibility(View.GONE);
binding.gif.setVisibility(View.GONE);
binding.camera.setVisibility(View.GONE);
binding.gallery.setVisibility(View.GONE);
binding.input.setVisibility(View.GONE);
@ -697,6 +751,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private void showInput() {
binding.emojiToggle.setVisibility(View.VISIBLE);
binding.gif.setVisibility(View.VISIBLE);
binding.camera.setVisibility(View.VISIBLE);
binding.gallery.setVisibility(View.VISIBLE);
binding.input.setVisibility(View.VISIBLE);
@ -735,16 +790,18 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
binding.send.setListenForRecord(true);
startIconAnimation();
}
binding.gallery.setVisibility(View.VISIBLE);
binding.gif.setVisibility(View.VISIBLE);
binding.camera.setVisibility(View.VISIBLE);
binding.gallery.setVisibility(View.VISIBLE);
return;
}
if (binding.send.isListenForRecord()) {
binding.send.setListenForRecord(false);
startIconAnimation();
}
binding.gallery.setVisibility(View.GONE);
binding.gif.setVisibility(View.GONE);
binding.camera.setVisibility(View.GONE);
binding.gallery.setVisibility(View.GONE);
}
private String getDirectItemPreviewText(final DirectItem item) {
@ -835,12 +892,15 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
});
}
private void setupItemsAdapter(final User currentUser, final DirectThread thread) {
private void setupItemsAdapter(final DirectThread thread) {
if (thread == null) return;
if (itemsAdapter != null) {
if (itemsAdapter.getThread() == thread) return;
itemsAdapter.setThread(thread);
return;
}
final User currentUser = appStateViewModel.getCurrentUser();
if (currentUser == null) return;
itemsAdapter = new DirectItemsAdapter(currentUser, thread, directItemCallback, directItemLongClickListener);
itemsAdapter.setHasStableIds(true);
itemsAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
@ -881,8 +941,9 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
public void onStart() {
isRecording = true;
binding.input.setHint(null);
binding.gallery.setVisibility(View.GONE);
binding.gif.setVisibility(View.GONE);
binding.camera.setVisibility(View.GONE);
binding.gallery.setVisibility(View.GONE);
if (PermissionUtils.hasAudioRecordPerms(context)) {
viewModel.startRecording();
return;
@ -902,8 +963,9 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
public void onFinish(final long recordTime) {
Log.d(TAG, "onFinish");
binding.input.setHint("Message");
binding.gallery.setVisibility(View.VISIBLE);
binding.gif.setVisibility(View.VISIBLE);
binding.camera.setVisibility(View.VISIBLE);
binding.gallery.setVisibility(View.VISIBLE);
viewModel.stopRecording(false);
isRecording = false;
}
@ -915,16 +977,18 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (PermissionUtils.hasAudioRecordPerms(context)) {
tooltip.show(binding.send);
}
binding.gallery.setVisibility(View.VISIBLE);
binding.gif.setVisibility(View.VISIBLE);
binding.camera.setVisibility(View.VISIBLE);
binding.gallery.setVisibility(View.VISIBLE);
viewModel.stopRecording(true);
isRecording = false;
}
});
binding.recordView.setOnBasketAnimationEndListener(() -> {
binding.input.setHint(R.string.dms_thread_message_hint);
binding.gallery.setVisibility(View.VISIBLE);
binding.gif.setVisibility(View.VISIBLE);
binding.camera.setVisibility(View.VISIBLE);
binding.gallery.setVisibility(View.VISIBLE);
});
binding.input.addTextChangedListener(new TextWatcherAdapter() {
// int prevLength = 0;
@ -955,7 +1019,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
binding.send.setOnRecordClickListener(v -> {
final Editable text = binding.input.getText();
if (TextUtils.isEmpty(text)) return;
final LiveData<Resource<DirectItem>> resourceLiveData = viewModel.sendText(text.toString());
final LiveData<Resource<Object>> resourceLiveData = viewModel.sendText(text.toString());
resourceLiveData.observe(getViewLifecycleOwner(), resource -> handleSentMessage(resourceLiveData));
binding.input.setText("");
viewModel.setReplyToItem(null);
@ -1001,6 +1065,16 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
mediaPicker.show(getChildFragmentManager(), "MediaPicker");
hideKeyboard(true);
});
binding.gif.setOnClickListener(v -> {
final GifPickerBottomDialogFragment gifPicker = GifPickerBottomDialogFragment.newInstance();
gifPicker.setOnSelectListener(giphyGif -> {
gifPicker.dismiss();
if (giphyGif == null) return;
handleSentMessage(viewModel.sendAnimatedMedia(giphyGif));
});
gifPicker.show(getChildFragmentManager(), "GifPicker");
hideKeyboard(true);
});
binding.camera.setOnClickListener(v -> {
final Intent intent = new Intent(context, CameraActivity.class);
startActivityForResult(intent, CAMERA_REQUEST_CODE);
@ -1028,8 +1102,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
navController.navigate(navDirections);
}
private void handleSentMessage(final LiveData<Resource<DirectItem>> resourceLiveData) {
final Resource<DirectItem> resource = resourceLiveData.getValue();
private void handleSentMessage(final LiveData<Resource<Object>> resourceLiveData) {
final Resource<Object> resource = resourceLiveData.getValue();
if (resource == null) return;
final Resource.Status status = resource.status;
switch (status) {
@ -1129,7 +1203,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private void setSendToMicIcon() {
final Context context = getContext();
if (context == null) return;
final Drawable sendToMicDrawable = ContextCompat.getDrawable(context, R.drawable.avd_send_to_mic_anim);
final Drawable sendToMicDrawable = Utils.getAnimatableDrawable(context, R.drawable.avd_send_to_mic_anim);
if (sendToMicDrawable instanceof Animatable) {
AnimatedVectorDrawableCompat.registerAnimationCallback(sendToMicDrawable, sendToMicAnimationCallback);
}
@ -1139,7 +1213,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private void setMicToSendIcon() {
final Context context = getContext();
if (context == null) return;
final Drawable micToSendDrawable = ContextCompat.getDrawable(context, R.drawable.avd_mic_to_send_anim);
final Drawable micToSendDrawable = Utils.getAnimatableDrawable(context, R.drawable.avd_mic_to_send_anim);
if (micToSendDrawable instanceof Animatable) {
AnimatedVectorDrawableCompat.registerAnimationCallback(micToSendDrawable, micToSendAnimationCallback);
}
@ -1350,6 +1424,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
ObjectAnimator.ofFloat(binding.inputBg, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.recordView, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.emojiToggle, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.gif, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.gallery, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.camera, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.send, TRANSLATION_Y, -height),
@ -1377,10 +1452,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
animatorSet.start();
}
private void showLongClickOptions(final View itemView) {
}
private void showReactionsDialog(final DirectItem item) {
final LiveData<List<User>> users = viewModel.getUsers();
final LiveData<List<User>> leftUsers = viewModel.getLeftUsers();
@ -1407,7 +1478,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
}
if (reaction == null) return;
if (reaction.getSenderId() == viewModel.getViewerId()) {
final LiveData<Resource<DirectItem>> resourceLiveData = viewModel.sendDeleteReaction(itemId);
final LiveData<Resource<Object>> resourceLiveData = viewModel.sendDeleteReaction(itemId);
if (resourceLiveData != null) {
resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData));
}

View File

@ -19,6 +19,9 @@ import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.Snackbar;
import java.util.Collections;
import java.util.List;
import awais.instagrabber.R;
@ -102,16 +105,41 @@ public class DirectPendingInboxFragment extends Fragment implements SwipeRefresh
}
private void setupObservers() {
removeViewModelObservers();
threadsObserver = list -> {
if (inboxAdapter == null) return;
inboxAdapter.submitList(list, () -> {
if (binding.swipeRefreshLayout.getVisibility() == View.GONE) {
binding.swipeRefreshLayout.setVisibility(View.VISIBLE);
binding.empty.setVisibility(View.GONE);
}
inboxAdapter.submitList(list == null ? Collections.emptyList() : list, () -> {
if (!scrollToTop) return;
binding.pendingInboxList.smoothScrollToPosition(0);
scrollToTop = false;
});
if (list == null || list.isEmpty()) {
binding.swipeRefreshLayout.setVisibility(View.GONE);
binding.empty.setVisibility(View.VISIBLE);
}
};
viewModel.getThreads().observe(fragmentActivity, threadsObserver);
viewModel.getFetchingInbox().observe(getViewLifecycleOwner(), fetching -> binding.swipeRefreshLayout.setRefreshing(fetching));
viewModel.getInbox().observe(getViewLifecycleOwner(), inboxResource -> {
if (inboxResource == null) return;
switch (inboxResource.status) {
case SUCCESS:
binding.swipeRefreshLayout.setRefreshing(false);
break;
case ERROR:
if (inboxResource.message != null) {
Snackbar.make(binding.getRoot(), inboxResource.message, Snackbar.LENGTH_LONG).show();
}
binding.swipeRefreshLayout.setRefreshing(false);
break;
case LOADING:
binding.swipeRefreshLayout.setRefreshing(true);
break;
}
});
}
private void removeViewModelObservers() {

View File

@ -375,8 +375,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
if (profileModel != null) {
restrictMenuItem.setVisible(!Objects.equals(profileModel.getPk(), CookieUtils.getUserIdFromCookie(cookie)));
restrictMenuItem.setTitle(profileModel.getFriendshipStatus().isRestricted() ? R.string.unrestrict : R.string.restrict);
}
else {
} else {
restrictMenuItem.setVisible(false);
}
}
@ -394,8 +393,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
if (profileModel != null) {
mutePostsMenuItem.setVisible(!Objects.equals(profileModel.getPk(), CookieUtils.getUserIdFromCookie(cookie)));
mutePostsMenuItem.setTitle(profileModel.getFriendshipStatus().isMuting() ? R.string.mute_posts : R.string.unmute_posts);
}
else {
} else {
mutePostsMenuItem.setVisible(false);
}
}
@ -403,8 +401,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
if (chainingMenuItem != null) {
if (profileModel != null) {
chainingMenuItem.setVisible(!Objects.equals(profileModel.getPk(), CookieUtils.getUserIdFromCookie(cookie)));
}
else {
} else {
chainingMenuItem.setVisible(false);
}
}
@ -524,7 +521,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override
public void onRefresh() {
profileDetailsBinding.countsBarrier.setVisibility(View.GONE);
profileDetailsBinding.countsBarrier.getRoot().setVisibility(View.GONE);
profileDetailsBinding.mainProfileImage.setVisibility(View.INVISIBLE);
fetchProfileDetails();
}
@ -641,11 +638,10 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
final Context context = getContext();
try {
if (t == null) Toast.makeText(context,
isLoggedIn ? R.string.error_loading_profile_loggedin : R.string.error_loading_profile,
Toast.LENGTH_LONG).show();
isLoggedIn ? R.string.error_loading_profile_loggedin : R.string.error_loading_profile,
Toast.LENGTH_LONG).show();
else Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
catch(final Throwable e) {}
} catch (final Throwable e) {}
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@ -742,7 +738,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
profileDetailsBinding.mainProfileImage.setImageURI(profileModel.getProfilePicUrl());
profileDetailsBinding.mainProfileImage.setVisibility(View.VISIBLE);
profileDetailsBinding.countsBarrier.setVisibility(View.VISIBLE);
profileDetailsBinding.countsBarrier.getRoot().setVisibility(View.VISIBLE);
final long followersCount = profileModel.getFollowerCount();
final long followingCount = profileModel.getFollowingCount();
@ -784,8 +780,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
final String biography = profileModel.getBiography();
if (TextUtils.isEmpty(biography)) {
profileDetailsBinding.mainBiography.setVisibility(View.GONE);
}
else {
} else {
profileDetailsBinding.mainBiography.setVisibility(View.VISIBLE);
profileDetailsBinding.mainBiography.setText(biography);
profileDetailsBinding.mainBiography.addOnHashtagListener(autoLinkItem -> {
@ -855,15 +850,14 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
String profileContext = profileModel.getProfileContext();
if (TextUtils.isEmpty(profileContext)) {
profileDetailsBinding.profileContext.setVisibility(View.GONE);
}
else {
} else {
profileDetailsBinding.profileContext.setVisibility(View.VISIBLE);
final List<UserProfileContextLink> userProfileContextLinks = profileModel.getProfileContextLinks();
for (int i = 0; i < userProfileContextLinks.size(); i++) {
final UserProfileContextLink link = userProfileContextLinks.get(i);
if (link.getUsername() != null)
profileContext = profileContext.substring(0, link.getStart() + i)
+ "@" + profileContext.substring(link.getStart() + i);
+ "@" + profileContext.substring(link.getStart() + i);
}
profileDetailsBinding.profileContext.setText(profileContext);
profileDetailsBinding.profileContext.addOnMentionClickListener(autoLinkItem -> {
@ -921,13 +915,13 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
profileDetailsBinding.btnTagged.setVisibility(View.VISIBLE);
profileDetailsBinding.btnSaved.setVisibility(View.VISIBLE);
profileDetailsBinding.btnLiked.setVisibility(View.VISIBLE);
// profileDetailsBinding.btnDM.setVisibility(View.GONE);
// profileDetailsBinding.btnDM.setVisibility(View.GONE);
profileDetailsBinding.btnSaved.setText(R.string.saved);
return;
}
profileDetailsBinding.btnSaved.setVisibility(View.GONE);
profileDetailsBinding.btnLiked.setVisibility(View.GONE);
// profileDetailsBinding.btnDM.setVisibility(View.VISIBLE);
// profileDetailsBinding.btnDM.setVisibility(View.VISIBLE);
profileDetailsBinding.btnFollow.setVisibility(View.VISIBLE);
final Context context = getContext();
if (context == null) return;
@ -1037,8 +1031,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}))
.setNegativeButton(R.string.cancel, null)
.show();
}
else if (profileModel.getFriendshipStatus().isFollowing() || profileModel.getFriendshipStatus().isOutgoingRequest()) {
} else if (profileModel.getFriendshipStatus().isFollowing() || profileModel.getFriendshipStatus().isOutgoingRequest()) {
friendshipService.unfollow(
profileModel.getPk(),
new ServiceCallback<FriendshipChangeResponse>() {
@ -1086,27 +1079,27 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
PostItemType.TAGGED);
NavHostFragment.findNavController(this).navigate(action);
});
// profileDetailsBinding.btnDM.setOnClickListener(v -> {
// profileDetailsBinding.btnDM.setEnabled(false);
// new CreateThreadAction(cookie, profileModel.getPk(), thread -> {
// if (thread == null) {
// Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
// profileDetailsBinding.btnDM.setEnabled(true);
// return;
// }
// if (isAdded()) {
// final Bundle bundle = new Bundle();
// bundle.putString("threadId", thread.getThreadId());
// bundle.putString("title", thread.getThreadTitle());
// if (isAdded()) {
// final NavDirections action = ProfileFragmentDirections
// .actionProfileFragmentToDMThreadFragment(thread.getThreadId(), profileModel.getUsername());
// NavHostFragment.findNavController(this).navigate(action);
// }
// }
// profileDetailsBinding.btnDM.setEnabled(true);
// }).execute();
// });
// profileDetailsBinding.btnDM.setOnClickListener(v -> {
// profileDetailsBinding.btnDM.setEnabled(false);
// new CreateThreadAction(cookie, profileModel.getPk(), thread -> {
// if (thread == null) {
// Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
// profileDetailsBinding.btnDM.setEnabled(true);
// return;
// }
// if (isAdded()) {
// final Bundle bundle = new Bundle();
// bundle.putString("threadId", thread.getThreadId());
// bundle.putString("title", thread.getThreadTitle());
// if (isAdded()) {
// final NavDirections action = ProfileFragmentDirections
// .actionProfileFragmentToDMThreadFragment(thread.getThreadId(), profileModel.getUsername());
// NavHostFragment.findNavController(this).navigate(action);
// }
// }
// profileDetailsBinding.btnDM.setEnabled(true);
// }).execute();
// });
profileDetailsBinding.mainProfileImage.setOnClickListener(v -> {
if (!hasStories) {
// show profile pic
@ -1149,7 +1142,9 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private void showProfilePicDialog() {
if (profileModel != null) {
final FragmentManager fragmentManager = getParentFragmentManager();
final ProfilePicDialogFragment fragment = new ProfilePicDialogFragment(profileModel.getPk(), username, profileModel.getProfilePicUrl());
final ProfilePicDialogFragment fragment = ProfilePicDialogFragment.getInstance(profileModel.getPk(),
username,
profileModel.getProfilePicUrl());
final FragmentTransaction ft = fragmentManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.add(fragment, "profilePicDialog")

View File

@ -0,0 +1,201 @@
package awais.instagrabber.fragments.settings;
import android.content.Context;
import android.content.Intent;
import android.text.Editable;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
import java.util.Objects;
import awais.instagrabber.R;
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
import awais.instagrabber.databinding.PrefAutoRefreshDmFreqBinding;
import awais.instagrabber.services.DMSyncAlarmReceiver;
import awais.instagrabber.services.DMSyncService;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Debouncer;
import awais.instagrabber.utils.TextUtils;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class DMPreferencesFragment extends BasePreferencesFragment {
private static final String TAG = DMPreferencesFragment.class.getSimpleName();
@Override
void setupPreferenceScreen(final PreferenceScreen screen) {
final Context context = getContext();
if (context == null) return;
screen.addPreference(getMarkDMSeenPreference(context));
// screen.addPreference(getAutoRefreshDMPreference(context));
// screen.addPreference(getAutoRefreshDMFreqPreference(context));
}
private Preference getMarkDMSeenPreference(@NonNull final Context context) {
return PreferenceHelper.getSwitchPreference(
context,
Constants.DM_MARK_AS_SEEN,
R.string.dm_mark_as_seen_setting,
R.string.dm_mark_as_seen_setting_summary,
false,
null
);
}
private Preference getAutoRefreshDMPreference(@NonNull final Context context) {
return PreferenceHelper.getSwitchPreference(
context,
PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH,
R.string.enable_dm_auto_refesh,
-1,
false,
(preference, newValue) -> {
if (!(newValue instanceof Boolean)) return false;
final boolean enabled = (Boolean) newValue;
if (enabled) {
DMSyncAlarmReceiver.setAlarm(context);
return true;
}
DMSyncAlarmReceiver.cancelAlarm(context);
try {
final Context applicationContext = context.getApplicationContext();
applicationContext.stopService(new Intent(applicationContext, DMSyncService.class));
} catch (Exception e) {
Log.e(TAG, "getAutoRefreshDMPreference: ", e);
}
return true;
}
);
}
private Preference getAutoRefreshDMFreqPreference(@NonNull final Context context) {
return new AutoRefreshDMFrePreference(context);
}
public static class AutoRefreshDMFrePreference extends Preference {
private static final String TAG = AutoRefreshDMFrePreference.class.getSimpleName();
private static final String DEBOUNCE_KEY = "dm_sync_service_update";
public static final int INTERVAL = 2000;
private final Debouncer.Callback<String> changeCallback;
private Debouncer<String> serviceUpdateDebouncer;
private PrefAutoRefreshDmFreqBinding binding;
public AutoRefreshDMFrePreference(final Context context) {
super(context);
setLayoutResource(R.layout.pref_auto_refresh_dm_freq);
// setKey(key);
setIconSpaceReserved(false);
changeCallback = new Debouncer.Callback<String>() {
@Override
public void call(final String key) {
DMSyncAlarmReceiver.setAlarm(context);
}
@Override
public void onError(final Throwable t) {
Log.e(TAG, "onError: ", t);
}
};
serviceUpdateDebouncer = new Debouncer<>(changeCallback, INTERVAL);
}
@Override
public void onDependencyChanged(final Preference dependency, final boolean disableDependent) {
// super.onDependencyChanged(dependency, disableDependent);
if (binding == null) return;
binding.startText.setEnabled(!disableDependent);
binding.freqNum.setEnabled(!disableDependent);
binding.freqUnit.setEnabled(!disableDependent);
if (disableDependent) {
serviceUpdateDebouncer.terminate();
return;
}
serviceUpdateDebouncer = new Debouncer<>(changeCallback, INTERVAL);
}
@Override
public void onBindViewHolder(final PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
setDependency(PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH);
binding = PrefAutoRefreshDmFreqBinding.bind(holder.itemView);
final Context context = getContext();
if (context == null) return;
setupUnitSpinner(context);
setupNumberEditText(context);
}
private void setupUnitSpinner(final Context context) {
final ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(context,
R.array.dm_auto_refresh_freq_unit_labels,
android.R.layout.simple_spinner_item);
final String[] values = context.getResources().getStringArray(R.array.dm_auto_refresh_freq_units);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
binding.freqUnit.setAdapter(adapter);
String unit = settingsHelper.getString(PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT);
if (TextUtils.isEmpty(unit)) {
unit = "secs";
}
int position = 0;
for (int i = 0; i < values.length; i++) {
if (Objects.equals(unit, values[i])) {
position = i;
break;
}
}
binding.freqUnit.setSelection(position);
binding.freqUnit.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(final AdapterView<?> parent, final View view, final int position, final long id) {
settingsHelper.putString(PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT, values[position]);
if (!isEnabled()) {
serviceUpdateDebouncer.terminate();
return;
}
serviceUpdateDebouncer.call(DEBOUNCE_KEY);
}
@Override
public void onNothingSelected(final AdapterView<?> parent) {}
});
}
private void setupNumberEditText(final Context context) {
int currentValue = settingsHelper.getInteger(PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER);
if (currentValue <= 0) {
currentValue = 30;
}
binding.freqNum.setText(String.valueOf(currentValue));
binding.freqNum.addTextChangedListener(new TextWatcherAdapter() {
@Override
public void afterTextChanged(final Editable s) {
if (TextUtils.isEmpty(s)) return;
try {
final int value = Integer.parseInt(s.toString());
if (value <= 0) return;
settingsHelper.putInteger(PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER, value);
if (!isEnabled()) {
serviceUpdateDebouncer.terminate();
return;
}
serviceUpdateDebouncer.call(DEBOUNCE_KEY);
} catch (Exception e) {
Log.e(TAG, "afterTextChanged: ", e);
}
}
});
}
}
}

View File

@ -0,0 +1,101 @@
package awais.instagrabber.fragments.settings;
import android.content.Context;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatButton;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreferenceCompat;
import com.google.android.material.switchmaterial.SwitchMaterial;
import awais.instagrabber.R;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DirectoryChooser;
import awais.instagrabber.utils.TextUtils;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class DownloadsPreferencesFragment extends BasePreferencesFragment {
@Override
void setupPreferenceScreen(final PreferenceScreen screen) {
final Context context = getContext();
if (context == null) return;
screen.addPreference(getDownloadUserFolderPreference(context));
screen.addPreference(getSaveToCustomFolderPreference(context));
}
private Preference getDownloadUserFolderPreference(@NonNull final Context context) {
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.DOWNLOAD_USER_FOLDER);
preference.setTitle(R.string.download_user_folder);
preference.setIconSpaceReserved(false);
return preference;
}
private Preference getSaveToCustomFolderPreference(@NonNull final Context context) {
return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser()
.setInitialDirectory(settingsHelper.getString(FOLDER_PATH))
.setInteractionListener(file -> {
settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath());
resultCallback.onResult(file.getAbsolutePath());
})
.show(getParentFragmentManager(), null));
}
public static class SaveToCustomFolderPreference extends Preference {
private AppCompatTextView customPathTextView;
private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener;
private final String key;
public SaveToCustomFolderPreference(final Context context, final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) {
super(context);
this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener;
key = Constants.FOLDER_SAVE_TO;
setLayoutResource(R.layout.pref_custom_folder);
setKey(key);
setTitle(R.string.save_to_folder);
setIconSpaceReserved(false);
}
@Override
public void onBindViewHolder(final PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo);
final View buttonContainer = holder.findViewById(R.id.button_container);
customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path);
cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> {
settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked);
buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE);
final String customPath = settingsHelper.getString(FOLDER_PATH);
customPathTextView.setText(customPath);
});
final boolean savedToEnabled = settingsHelper.getBoolean(key);
holder.itemView.setOnClickListener(v -> cbSaveTo.toggle());
cbSaveTo.setChecked(savedToEnabled);
buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE);
final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo);
btnSaveTo.setOnClickListener(v -> {
if (onSelectFolderButtonClickListener == null) return;
onSelectFolderButtonClickListener.onClick(result -> {
if (TextUtils.isEmpty(result)) return;
customPathTextView.setText(result);
});
});
}
public interface ResultCallback {
void onResult(String result);
}
public interface OnSelectFolderButtonClickListener {
void onClick(ResultCallback resultCallback);
}
}
}

View File

@ -0,0 +1,61 @@
package awais.instagrabber.fragments.settings;
import android.content.Context;
import android.content.res.TypedArray;
import androidx.annotation.NonNull;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreferenceCompat;
import awais.instagrabber.R;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.TextUtils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class GeneralPreferencesFragment extends BasePreferencesFragment {
@Override
void setupPreferenceScreen(final PreferenceScreen screen) {
final Context context = getContext();
if (context == null) return;
final String cookie = settingsHelper.getString(Constants.COOKIE);
final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0;
if (isLoggedIn) {
screen.addPreference(getDefaultTabPreference(context));
}
screen.addPreference(getUpdateCheckPreference(context));
}
private Preference getDefaultTabPreference(@NonNull final Context context) {
final ListPreference preference = new ListPreference(context);
preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance());
final TypedArray mainNavIds = getResources().obtainTypedArray(R.array.main_nav_ids);
final int length = mainNavIds.length();
final String[] values = new String[length];
for (int i = 0; i < length; i++) {
final int resourceId = mainNavIds.getResourceId(i, -1);
if (resourceId < 0) continue;
values[i] = getResources().getResourceEntryName(resourceId);
}
mainNavIds.recycle();
preference.setKey(Constants.DEFAULT_TAB);
preference.setTitle(R.string.pref_start_screen);
preference.setDialogTitle(R.string.pref_start_screen);
preference.setEntries(R.array.main_nav_ids_values);
preference.setEntryValues(values);
preference.setIconSpaceReserved(false);
return preference;
}
private Preference getUpdateCheckPreference(@NonNull final Context context) {
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.CHECK_UPDATES);
preference.setTitle(R.string.update_check);
preference.setIconSpaceReserved(false);
return preference;
}
}

View File

@ -0,0 +1,93 @@
package awais.instagrabber.fragments.settings;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import java.text.SimpleDateFormat;
import java.util.Date;
import awais.instagrabber.R;
import awais.instagrabber.dialogs.TimeSettingsDialog;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.LocaleUtils;
import awais.instagrabber.utils.UserAgentUtils;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class LocalePreferencesFragment extends BasePreferencesFragment {
@Override
void setupPreferenceScreen(final PreferenceScreen screen) {
final Context context = getContext();
if (context == null) return;
screen.addPreference(getLanguagePreference(context));
screen.addPreference(getPostTimeFormatPreference(context));
}
private Preference getLanguagePreference(@NonNull final Context context) {
final ListPreference preference = new ListPreference(context);
preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance());
final int length = getResources().getStringArray(R.array.languages).length;
final String[] values = new String[length];
for (int i = 0; i < length; i++) {
values[i] = String.valueOf(i);
}
preference.setKey(Constants.APP_LANGUAGE);
preference.setTitle(R.string.select_language);
preference.setDialogTitle(R.string.select_language);
preference.setEntries(R.array.languages);
preference.setIconSpaceReserved(false);
preference.setEntryValues(values);
preference.setOnPreferenceChangeListener((preference1, newValue) -> {
shouldRecreate();
final int appUaCode = settingsHelper.getInteger(Constants.APP_UA_CODE);
final String appUa = UserAgentUtils.generateAppUA(appUaCode, LocaleUtils.getCurrentLocale().getLanguage());
settingsHelper.putString(Constants.APP_UA, appUa);
return true;
});
return preference;
}
private Preference getPostTimeFormatPreference(@NonNull final Context context) {
final Preference preference = new Preference(context);
preference.setTitle(R.string.time_settings);
preference.setSummary(Utils.datetimeParser.format(new Date()));
preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(preference1 -> {
new TimeSettingsDialog(
settingsHelper.getBoolean(Constants.CUSTOM_DATE_TIME_FORMAT_ENABLED),
settingsHelper.getString(Constants.CUSTOM_DATE_TIME_FORMAT),
settingsHelper.getString(Constants.DATE_TIME_SELECTION),
settingsHelper.getBoolean(Constants.SWAP_DATE_TIME_FORMAT_ENABLED),
(isCustomFormat,
formatSelection,
spTimeFormatSelectedItemPosition,
spSeparatorSelectedItemPosition,
spDateFormatSelectedItemPosition,
selectedFormat,
currentFormat,
swapDateTime) -> {
if (isCustomFormat) {
settingsHelper.putString(Constants.CUSTOM_DATE_TIME_FORMAT, formatSelection);
} else {
final String formatSelectionUpdated = spTimeFormatSelectedItemPosition + ";"
+ spSeparatorSelectedItemPosition + ';'
+ spDateFormatSelectedItemPosition; // time;separator;date
settingsHelper.putString(Constants.DATE_TIME_FORMAT, selectedFormat);
settingsHelper.putString(Constants.DATE_TIME_SELECTION, formatSelectionUpdated);
}
settingsHelper.putBoolean(Constants.CUSTOM_DATE_TIME_FORMAT_ENABLED, isCustomFormat);
settingsHelper.putBoolean(Constants.SWAP_DATE_TIME_FORMAT_ENABLED, swapDateTime);
Utils.datetimeParser = (SimpleDateFormat) currentFormat.clone();
preference.setSummary(Utils.datetimeParser.format(new Date()));
}
).show(getParentFragmentManager(), null);
return true;
});
return preference;
}
}

View File

@ -14,6 +14,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.preference.Preference;
@ -132,43 +133,58 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
// generalCategory.setIconSpaceReserved(false);
// screen.addPreference(generalCategory);
screen.addPreference(getDivider(context));
final NavController navController = NavHostFragment.findNavController(this);
if (isLoggedIn) {
screen.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("notif", 0l);
NavHostFragment.findNavController(this).navigate(navDirections);
if (isSafeToNavigate(navController)) {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("notif", 0L);
navController.navigate(navDirections);
}
return true;
}));
screen.addPreference(getPreference(R.string.action_ayml, R.drawable.ic_suggested_users, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("ayml", 0l);
NavHostFragment.findNavController(this).navigate(navDirections);
if (isSafeToNavigate(navController)) {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("ayml", 0L);
navController.navigate(navDirections);
}
return true;
}));
screen.addPreference(getPreference(R.string.action_archive, R.drawable.ic_archive, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalStoryListViewerFragment("archive");
NavHostFragment.findNavController(this).navigate(navDirections);
if (isSafeToNavigate(navController)) {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalStoryListViewerFragment("archive");
navController.navigate(navDirections);
}
return true;
}));
}
screen.addPreference(getPreference(R.string.title_favorites, R.drawable.ic_star_24, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToFavoritesFragment();
NavHostFragment.findNavController(this).navigate(navDirections);
if (isSafeToNavigate(navController)) {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToFavoritesFragment();
navController.navigate(navDirections);
}
return true;
}));
screen.addPreference(getDivider(context));
screen.addPreference(getPreference(R.string.action_settings, R.drawable.ic_outline_settings_24, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToSettingsPreferencesFragment();
NavHostFragment.findNavController(this).navigate(navDirections);
if (isSafeToNavigate(navController)) {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToSettingsPreferencesFragment();
navController.navigate(navDirections);
}
return true;
}));
screen.addPreference(getPreference(R.string.backup_and_restore, R.drawable.ic_settings_backup_restore_24, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToBackupPreferencesFragment();
NavHostFragment.findNavController(this).navigate(navDirections);
if (isSafeToNavigate(navController)) {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToBackupPreferencesFragment();
navController.navigate(navDirections);
}
return true;
}));
screen.addPreference(getPreference(R.string.action_about, R.drawable.ic_outline_info_24, preference1 -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToAboutFragment();
NavHostFragment.findNavController(this).navigate(navDirections);
if (isSafeToNavigate(navController)) {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToAboutFragment();
navController.navigate(navDirections);
}
return true;
}));
@ -187,6 +203,11 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
screen.addPreference(reminderPreference);
}
private boolean isSafeToNavigate(final NavController navController) {
return navController.getCurrentDestination() != null
&& navController.getCurrentDestination().getId() == R.id.morePreferencesFragment;
}
@Override
public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
if (resultCode == Constants.LOGIN_RESULT_CODE) {

View File

@ -0,0 +1,43 @@
package awais.instagrabber.fragments.settings;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import awais.instagrabber.R;
import awais.instagrabber.utils.Constants;
public class NotificationsPreferencesFragment extends BasePreferencesFragment {
@Override
void setupPreferenceScreen(final PreferenceScreen screen) {
final Context context = getContext();
if (context == null) return;
screen.addPreference(getActivityNotificationsPreference(context));
screen.addPreference(getDMNotificationsPreference(context));
}
private Preference getActivityNotificationsPreference(@NonNull final Context context) {
return PreferenceHelper.getSwitchPreference(
context,
Constants.CHECK_ACTIVITY,
R.string.activity_setting,
-1,
false,
(preference, newValue) -> {
shouldRecreate();
return true;
});
}
private Preference getDMNotificationsPreference(@NonNull final Context context) {
return PreferenceHelper.getSwitchPreference(
context,
PreferenceKeys.PREF_ENABLE_DM_NOTIFICATIONS,
R.string.enable_dm_notifications,
-1,
false,
null);
}
}

View File

@ -0,0 +1,49 @@
package awais.instagrabber.fragments.settings;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreferenceCompat;
import awais.instagrabber.R;
import awais.instagrabber.utils.Constants;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class PostPreferencesFragment extends BasePreferencesFragment {
@Override
void setupPreferenceScreen(final PreferenceScreen screen) {
final Context context = getContext();
if (context == null) return;
// generalCategory.addPreference(getAutoPlayVideosPreference(context));
screen.addPreference(getAlwaysMuteVideosPreference(context));
screen.addPreference(getShowCaptionPreference(context));
}
private Preference getAutoPlayVideosPreference(@NonNull final Context context) {
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.AUTOPLAY_VIDEOS);
preference.setTitle(R.string.post_viewer_autoplay_video);
preference.setIconSpaceReserved(false);
return preference;
}
private Preference getAlwaysMuteVideosPreference(@NonNull final Context context) {
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.MUTED_VIDEOS);
preference.setTitle(R.string.post_viewer_muted_autoplay);
preference.setIconSpaceReserved(false);
return preference;
}
private Preference getShowCaptionPreference(@NonNull final Context context) {
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.SHOW_CAPTIONS);
preference.setDefaultValue(true);
preference.setTitle(R.string.post_viewer_show_captions);
preference.setIconSpaceReserved(false);
return preference;
}
}

View File

@ -0,0 +1,30 @@
package awais.instagrabber.fragments.settings;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.SwitchPreferenceCompat;
public final class PreferenceHelper {
public static SwitchPreferenceCompat getSwitchPreference(@NonNull final Context context,
@NonNull final String key,
@StringRes final int titleResId,
@StringRes final int summaryResId,
final boolean iconSpaceReserved,
final OnPreferenceChangeListener onPreferenceChangeListener) {
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(key);
preference.setTitle(titleResId);
preference.setIconSpaceReserved(iconSpaceReserved);
if (summaryResId != -1) {
preference.setSummary(summaryResId);
}
if (onPreferenceChangeListener != null) {
preference.setOnPreferenceChangeListener(onPreferenceChangeListener);
}
return preference;
}
}

View File

@ -0,0 +1,8 @@
package awais.instagrabber.fragments.settings;
public final class PreferenceKeys {
public static final String PREF_ENABLE_DM_NOTIFICATIONS = "enable_dm_notifications";
public static final String PREF_ENABLE_DM_AUTO_REFRESH = "enable_dm_auto_refresh";
public static final String PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT = "enable_dm_auto_refresh_freq_unit";
public static final String PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER = "enable_dm_auto_refresh_freq_number";
}

View File

@ -1,91 +1,55 @@
package awais.instagrabber.fragments.settings;
import android.content.Context;
import android.content.res.TypedArray;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatButton;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.annotation.StringRes;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreferenceCompat;
import com.google.android.material.switchmaterial.SwitchMaterial;
import com.google.common.collect.ImmutableList;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.dialogs.TimeSettingsDialog;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DirectoryChooser;
import awais.instagrabber.utils.LocaleUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.UserAgentUtils;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.fragments.settings.SettingsPreferencesFragmentDirections.actionSettingsToDm;
import static awais.instagrabber.fragments.settings.SettingsPreferencesFragmentDirections.actionSettingsToDownloads;
import static awais.instagrabber.fragments.settings.SettingsPreferencesFragmentDirections.actionSettingsToGeneral;
import static awais.instagrabber.fragments.settings.SettingsPreferencesFragmentDirections.actionSettingsToLocale;
import static awais.instagrabber.fragments.settings.SettingsPreferencesFragmentDirections.actionSettingsToNotifications;
import static awais.instagrabber.fragments.settings.SettingsPreferencesFragmentDirections.actionSettingsToPost;
import static awais.instagrabber.fragments.settings.SettingsPreferencesFragmentDirections.actionSettingsToStories;
import static awais.instagrabber.fragments.settings.SettingsPreferencesFragmentDirections.actionSettingsToTheme;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class SettingsPreferencesFragment extends BasePreferencesFragment {
private static final String TAG = "SettingsPrefsFrag";
private boolean isLoggedIn;
private static final String TAG = SettingsPreferencesFragment.class.getSimpleName();
private static final List<SettingScreen> screens = ImmutableList.of(
new SettingScreen(R.string.pref_category_general, actionSettingsToGeneral()),
new SettingScreen(R.string.pref_category_theme, actionSettingsToTheme()),
new SettingScreen(R.string.pref_category_locale, actionSettingsToLocale()),
new SettingScreen(R.string.pref_category_post, actionSettingsToPost()),
new SettingScreen(R.string.pref_category_stories, actionSettingsToStories(), true),
new SettingScreen(R.string.pref_category_dm, actionSettingsToDm(), true),
new SettingScreen(R.string.pref_category_notifications, actionSettingsToNotifications(), true),
new SettingScreen(R.string.pref_category_downloads, actionSettingsToDownloads())
);
@Override
void setupPreferenceScreen(final PreferenceScreen screen) {
final String cookie = settingsHelper.getString(Constants.COOKIE);
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0;
final Context context = getContext();
if (context == null) return;
final PreferenceCategory generalCategory = new PreferenceCategory(context);
screen.addPreference(generalCategory);
generalCategory.setTitle(R.string.pref_category_general);
generalCategory.setIconSpaceReserved(false);
generalCategory.addPreference(getThemePreference(context));
generalCategory.addPreference(getDefaultTabPreference());
generalCategory.addPreference(getUpdateCheckPreference());
// generalCategory.addPreference(getAutoPlayVideosPreference());
generalCategory.addPreference(getAlwaysMuteVideosPreference());
generalCategory.addPreference(getShowCaptionPreference());
// screen.addPreference(getDivider(context));
// final PreferenceCategory themeCategory = new PreferenceCategory(context);
// screen.addPreference(themeCategory);
// themeCategory.setTitle(R.string.pref_category_theme);
// themeCategory.setIconSpaceReserved(false);
// themeCategory.addPreference(getAmoledThemePreference());
final PreferenceCategory downloadsCategory = new PreferenceCategory(context);
screen.addPreference(downloadsCategory);
downloadsCategory.setTitle(R.string.pref_category_downloads);
downloadsCategory.setIconSpaceReserved(false);
downloadsCategory.addPreference(getDownloadUserFolderPreference());
downloadsCategory.addPreference(getSaveToCustomFolderPreference());
final PreferenceCategory localeCategory = new PreferenceCategory(context);
screen.addPreference(localeCategory);
localeCategory.setTitle(R.string.pref_category_locale);
localeCategory.setIconSpaceReserved(false);
localeCategory.addPreference(getLanguagePreference());
localeCategory.addPreference(getPostTimePreference());
if (isLoggedIn) {
final PreferenceCategory loggedInUsersPreferenceCategory = new PreferenceCategory(context);
screen.addPreference(loggedInUsersPreferenceCategory);
loggedInUsersPreferenceCategory.setIconSpaceReserved(false);
loggedInUsersPreferenceCategory.setTitle(R.string.login_settings);
loggedInUsersPreferenceCategory.addPreference(getStorySortPreference());
loggedInUsersPreferenceCategory.addPreference(getMarkStoriesSeenPreference());
loggedInUsersPreferenceCategory.addPreference(getMarkDMSeenPreference());
loggedInUsersPreferenceCategory.addPreference(getEnableActivityNotificationsPreference());
final String cookie = settingsHelper.getString(Constants.COOKIE);
final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0;
for (final SettingScreen settingScreen : screens) {
if (settingScreen.isLoginRequired() && !isLoggedIn) continue;
screen.addPreference(getNavPreference(context, settingScreen));
}
// else {
// final PreferenceCategory anonUsersPreferenceCategory = new PreferenceCategory(context);
@ -95,275 +59,43 @@ public class SettingsPreferencesFragment extends BasePreferencesFragment {
// }
}
private Preference getLanguagePreference() {
final Context context = getContext();
if (context == null) return null;
final ListPreference preference = new ListPreference(context);
preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance());
final int length = getResources().getStringArray(R.array.languages).length;
final String[] values = new String[length];
for (int i = 0; i < length; i++) {
values[i] = String.valueOf(i);
}
preference.setKey(Constants.APP_LANGUAGE);
preference.setTitle(R.string.select_language);
preference.setDialogTitle(R.string.select_language);
preference.setEntries(R.array.languages);
preference.setIconSpaceReserved(false);
preference.setEntryValues(values);
preference.setOnPreferenceChangeListener((preference1, newValue) -> {
shouldRecreate();
final int appUaCode = settingsHelper.getInteger(Constants.APP_UA_CODE);
final String appUa = UserAgentUtils.generateAppUA(appUaCode, LocaleUtils.getCurrentLocale().getLanguage());
settingsHelper.putString(Constants.APP_UA, appUa);
return true;
});
return preference;
}
private Preference getDefaultTabPreference() {
final Context context = getContext();
if (context == null) return null;
final ListPreference preference = new ListPreference(context);
preference.setEnabled(isLoggedIn);
preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance());
final TypedArray mainNavIds = getResources().obtainTypedArray(R.array.main_nav_ids);
final int length = mainNavIds.length();
final String[] values = new String[length];
for (int i = 0; i < length; i++) {
final int resourceId = mainNavIds.getResourceId(i, -1);
if (resourceId < 0) continue;
values[i] = getResources().getResourceEntryName(resourceId);
}
mainNavIds.recycle();
preference.setKey(Constants.DEFAULT_TAB);
preference.setTitle(R.string.pref_start_screen);
preference.setDialogTitle(R.string.pref_start_screen);
preference.setEntries(R.array.main_nav_ids_values);
preference.setEntryValues(values);
preference.setIconSpaceReserved(false);
return preference;
}
private Preference getUpdateCheckPreference() {
final Context context = getContext();
if (context == null) return null;
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.CHECK_UPDATES);
preference.setTitle(R.string.update_check);
preference.setIconSpaceReserved(false);
return preference;
}
private Preference getThemePreference(@NonNull final Context context) {
private Preference getNavPreference(@NonNull final Context context,
@NonNull final SettingScreen settingScreen) {
final Preference preference = new Preference(context);
preference.setTitle(R.string.pref_category_theme);
// preference.setIcon(R.drawable.ic_format_paint_24);
preference.setTitle(settingScreen.getTitleResId());
preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(preference1 -> {
final NavDirections navDirections = SettingsPreferencesFragmentDirections.actionSettingsPreferencesFragmentToThemePreferencesFragment();
NavHostFragment.findNavController(this).navigate(navDirections);
NavHostFragment.findNavController(this).navigate(settingScreen.getDirections());
return true;
});
return preference;
}
private Preference getDownloadUserFolderPreference() {
final Context context = getContext();
if (context == null) return null;
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.DOWNLOAD_USER_FOLDER);
preference.setTitle(R.string.download_user_folder);
preference.setIconSpaceReserved(false);
return preference;
}
private static class SettingScreen {
private final int titleResId;
private final NavDirections directions;
private final boolean loginRequired;
private Preference getSaveToCustomFolderPreference() {
final Context context = getContext();
if (context == null) return null;
return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser()
.setInitialDirectory(settingsHelper.getString(FOLDER_PATH))
.setInteractionListener(file -> {
settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath());
resultCallback.onResult(file.getAbsolutePath());
})
.show(getParentFragmentManager(), null));
}
private Preference getAutoPlayVideosPreference() {
final Context context = getContext();
if (context == null) return null;
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.AUTOPLAY_VIDEOS);
preference.setTitle(R.string.post_viewer_autoplay_video);
preference.setIconSpaceReserved(false);
return preference;
}
private Preference getAlwaysMuteVideosPreference() {
final Context context = getContext();
if (context == null) return null;
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.MUTED_VIDEOS);
preference.setTitle(R.string.post_viewer_muted_autoplay);
preference.setIconSpaceReserved(false);
return preference;
}
private Preference getShowCaptionPreference() {
final Context context = getContext();
if (context == null) return null;
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.SHOW_CAPTIONS);
preference.setDefaultValue(true);
preference.setTitle(R.string.post_viewer_show_captions);
preference.setIconSpaceReserved(false);
return preference;
}
private Preference getStorySortPreference() {
final Context context = getContext();
if (context == null) return null;
final ListPreference preference = new ListPreference(context);
preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance());
final int length = getResources().getStringArray(R.array.story_sorts).length;
final String[] values = new String[length];
for (int i = 0; i < length; i++) {
values[i] = String.valueOf(i);
}
preference.setKey(Constants.STORY_SORT);
preference.setTitle(R.string.story_sort_setting);
preference.setDialogTitle(R.string.story_sort_setting);
preference.setEntries(R.array.story_sorts);
preference.setIconSpaceReserved(false);
preference.setEntryValues(values);
return preference;
}
private Preference getMarkStoriesSeenPreference() {
final Context context = getContext();
if (context == null) return null;
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.MARK_AS_SEEN);
preference.setTitle(R.string.mark_as_seen_setting);
preference.setSummary(R.string.mark_as_seen_setting_summary);
preference.setIconSpaceReserved(false);
return preference;
}
private Preference getMarkDMSeenPreference() {
final Context context = getContext();
if (context == null) return null;
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.DM_MARK_AS_SEEN);
preference.setTitle(R.string.dm_mark_as_seen_setting);
preference.setSummary(R.string.dm_mark_as_seen_setting_summary);
preference.setIconSpaceReserved(false);
return preference;
}
private Preference getEnableActivityNotificationsPreference() {
final Context context = getContext();
if (context == null) return null;
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.CHECK_ACTIVITY);
preference.setTitle(R.string.activity_setting);
preference.setIconSpaceReserved(false);
preference.setOnPreferenceChangeListener((preference1, newValue) -> {
shouldRecreate();
return true;
});
return preference;
}
private Preference getPostTimePreference() {
final Context context = getContext();
if (context == null) return null;
final Preference preference = new Preference(context);
preference.setTitle(R.string.time_settings);
preference.setSummary(Utils.datetimeParser.format(new Date()));
preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(preference1 -> {
new TimeSettingsDialog(
settingsHelper.getBoolean(Constants.CUSTOM_DATE_TIME_FORMAT_ENABLED),
settingsHelper.getString(Constants.CUSTOM_DATE_TIME_FORMAT),
settingsHelper.getString(Constants.DATE_TIME_SELECTION),
settingsHelper.getBoolean(Constants.SWAP_DATE_TIME_FORMAT_ENABLED),
(isCustomFormat,
formatSelection,
spTimeFormatSelectedItemPosition,
spSeparatorSelectedItemPosition,
spDateFormatSelectedItemPosition,
selectedFormat,
currentFormat,
swapDateTime) -> {
if (isCustomFormat) {
settingsHelper.putString(Constants.CUSTOM_DATE_TIME_FORMAT, formatSelection);
} else {
final String formatSelectionUpdated = spTimeFormatSelectedItemPosition + ";"
+ spSeparatorSelectedItemPosition + ';'
+ spDateFormatSelectedItemPosition; // time;separator;date
settingsHelper.putString(Constants.DATE_TIME_FORMAT, selectedFormat);
settingsHelper.putString(Constants.DATE_TIME_SELECTION, formatSelectionUpdated);
}
settingsHelper.putBoolean(Constants.CUSTOM_DATE_TIME_FORMAT_ENABLED, isCustomFormat);
settingsHelper.putBoolean(Constants.SWAP_DATE_TIME_FORMAT_ENABLED, swapDateTime);
Utils.datetimeParser = (SimpleDateFormat) currentFormat.clone();
preference.setSummary(Utils.datetimeParser.format(new Date()));
}
).show(getParentFragmentManager(), null);
return true;
});
return preference;
}
public static class SaveToCustomFolderPreference extends Preference {
private AppCompatTextView customPathTextView;
private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener;
private final String key;
public SaveToCustomFolderPreference(final Context context, final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) {
super(context);
this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener;
key = Constants.FOLDER_SAVE_TO;
setLayoutResource(R.layout.pref_custom_folder);
setKey(key);
setTitle(R.string.save_to_folder);
setIconSpaceReserved(false);
public SettingScreen(@StringRes final int titleResId, final NavDirections directions) {
this(titleResId, directions, false);
}
@Override
public void onBindViewHolder(final PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo);
final View buttonContainer = holder.findViewById(R.id.button_container);
customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path);
cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> {
settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked);
buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE);
final String customPath = settingsHelper.getString(FOLDER_PATH);
customPathTextView.setText(customPath);
});
final boolean savedToEnabled = settingsHelper.getBoolean(key);
holder.itemView.setOnClickListener(v -> cbSaveTo.toggle());
cbSaveTo.setChecked(savedToEnabled);
buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE);
final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo);
btnSaveTo.setOnClickListener(v -> {
if (onSelectFolderButtonClickListener == null) return;
onSelectFolderButtonClickListener.onClick(result -> {
if (TextUtils.isEmpty(result)) return;
customPathTextView.setText(result);
});
});
public SettingScreen(@StringRes final int titleResId, final NavDirections directions, final boolean loginRequired) {
this.titleResId = titleResId;
this.directions = directions;
this.loginRequired = loginRequired;
}
public interface ResultCallback {
void onResult(String result);
public int getTitleResId() {
return titleResId;
}
public interface OnSelectFolderButtonClickListener {
void onClick(ResultCallback resultCallback);
public NavDirections getDirections() {
return directions;
}
public boolean isLoginRequired() {
return loginRequired;
}
}
}

View File

@ -0,0 +1,48 @@
package awais.instagrabber.fragments.settings;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreferenceCompat;
import awais.instagrabber.R;
import awais.instagrabber.utils.Constants;
public class StoriesPreferencesFragment extends BasePreferencesFragment {
@Override
void setupPreferenceScreen(final PreferenceScreen screen) {
final Context context = getContext();
if (context == null) return;
screen.addPreference(getStorySortPreference(context));
screen.addPreference(getMarkStoriesSeenPreference(context));
}
private Preference getStorySortPreference(@NonNull final Context context) {
final ListPreference preference = new ListPreference(context);
preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance());
final int length = getResources().getStringArray(R.array.story_sorts).length;
final String[] values = new String[length];
for (int i = 0; i < length; i++) {
values[i] = String.valueOf(i);
}
preference.setKey(Constants.STORY_SORT);
preference.setTitle(R.string.story_sort_setting);
preference.setDialogTitle(R.string.story_sort_setting);
preference.setEntries(R.array.story_sorts);
preference.setIconSpaceReserved(false);
preference.setEntryValues(values);
return preference;
}
private Preference getMarkStoriesSeenPreference(@NonNull final Context context) {
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.MARK_AS_SEEN);
preference.setTitle(R.string.mark_as_seen_setting);
preference.setSummary(R.string.mark_as_seen_setting_summary);
preference.setIconSpaceReserved(false);
return preference;
}
}

View File

@ -0,0 +1,81 @@
package awais.instagrabber.managers;
import android.content.ContentResolver;
import androidx.annotation.NonNull;
import com.google.common.collect.Iterables;
import java.util.List;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
public final class DirectMessagesManager {
private static final String TAG = DirectMessagesManager.class.getSimpleName();
private static final Object LOCK = new Object();
private static DirectMessagesManager instance;
private final InboxManager inboxManager;
private final InboxManager pendingInboxManager;
public static DirectMessagesManager getInstance() {
if (instance == null) {
synchronized (LOCK) {
if (instance == null) {
instance = new DirectMessagesManager();
}
}
}
return instance;
}
private DirectMessagesManager() {
inboxManager = InboxManager.getInstance(false);
pendingInboxManager = InboxManager.getInstance(true);
}
public void moveThreadFromPending(@NonNull final String threadId) {
final List<DirectThread> pendingThreads = pendingInboxManager.getThreads().getValue();
if (pendingThreads == null) return;
final int index = Iterables.indexOf(pendingThreads, t -> t.getThreadId().equals(threadId));
if (index < 0) return;
final DirectThread thread = pendingThreads.get(index);
final DirectItem threadFirstDirectItem = thread.getFirstDirectItem();
if (threadFirstDirectItem == null) return;
final List<DirectThread> threads = inboxManager.getThreads().getValue();
int insertIndex = 0;
for (final DirectThread tempThread : threads) {
final DirectItem firstDirectItem = tempThread.getFirstDirectItem();
if (firstDirectItem == null) continue;
final long timestamp = firstDirectItem.getTimestamp();
if (timestamp < threadFirstDirectItem.getTimestamp()) {
break;
}
insertIndex++;
}
thread.setPending(false);
inboxManager.addThread(thread, insertIndex);
pendingInboxManager.removeThread(threadId);
final Integer currentTotal = inboxManager.getPendingRequestsTotal().getValue();
if (currentTotal == null) return;
inboxManager.setPendingRequestsTotal(currentTotal - 1);
}
public InboxManager getInboxManager() {
return inboxManager;
}
public InboxManager getPendingInboxManager() {
return pendingInboxManager;
}
public ThreadManager getThreadManager(@NonNull final String threadId,
final boolean pending,
@NonNull final User currentUser,
@NonNull final ContentResolver contentResolver) {
return ThreadManager.getInstance(threadId, pending, currentUser, contentResolver);
}
}

View File

@ -0,0 +1,360 @@
package awais.instagrabber.managers;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import awais.instagrabber.models.Resource;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectBadgeCount;
import awais.instagrabber.repositories.responses.directmessages.DirectInbox;
import awais.instagrabber.repositories.responses.directmessages.DirectInboxResponse;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.webservices.DirectMessagesService;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import static androidx.lifecycle.Transformations.distinctUntilChanged;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class InboxManager {
private static final String TAG = InboxManager.class.getSimpleName();
private static final LoadingCache<String, Object> THREAD_LOCKS = CacheBuilder
.newBuilder()
.expireAfterAccess(1, TimeUnit.MINUTES) // max lock time ever expected
.build(CacheLoader.from(Object::new));
private static final Comparator<DirectThread> THREAD_COMPARATOR = (t1, t2) -> {
final DirectItem t1FirstDirectItem = t1.getFirstDirectItem();
final DirectItem t2FirstDirectItem = t2.getFirstDirectItem();
if (t1FirstDirectItem == null && t2FirstDirectItem == null) return 0;
if (t1FirstDirectItem == null) return 1;
if (t2FirstDirectItem == null) return -1;
return Long.compare(t2FirstDirectItem.getTimestamp(), t1FirstDirectItem.getTimestamp());
};
private final MutableLiveData<Resource<DirectInbox>> inbox = new MutableLiveData<>();
private final MutableLiveData<Resource<Integer>> unseenCount = new MutableLiveData<>();
private final MutableLiveData<Integer> pendingRequestsTotal = new MutableLiveData<>(0);
private final LiveData<List<DirectThread>> threads;
private final DirectMessagesService service;
private final boolean pending;
private Call<DirectInboxResponse> inboxRequest;
private Call<DirectBadgeCount> unseenCountRequest;
private long seqId;
private String cursor;
private boolean hasOlder = true;
private User viewer;
@NonNull
public static InboxManager getInstance(final boolean pending) {
return new InboxManager(pending);
}
private InboxManager(final boolean pending) {
this.pending = pending;
final String cookie = settingsHelper.getString(Constants.COOKIE);
final long userId = CookieUtils.getUserIdFromCookie(cookie);
final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID);
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
if (TextUtils.isEmpty(csrfToken) || userId <= 0 || TextUtils.isEmpty(deviceUuid)) {
throw new IllegalArgumentException("User is not logged in!");
}
service = DirectMessagesService.getInstance(csrfToken, userId, deviceUuid);
// Transformations
threads = distinctUntilChanged(Transformations.map(inbox, inboxResource -> {
if (inboxResource == null) {
return Collections.emptyList();
}
final DirectInbox inbox = inboxResource.data;
if (inbox == null) {
return Collections.emptyList();
}
return ImmutableList.sortedCopyOf(THREAD_COMPARATOR, inbox.getThreads());
}));
fetchInbox();
if (!pending) {
fetchUnseenCount();
}
}
public LiveData<Resource<DirectInbox>> getInbox() {
return distinctUntilChanged(inbox);
}
public LiveData<List<DirectThread>> getThreads() {
return threads;
}
public LiveData<Resource<Integer>> getUnseenCount() {
return distinctUntilChanged(unseenCount);
}
public LiveData<Integer> getPendingRequestsTotal() {
return distinctUntilChanged(pendingRequestsTotal);
}
public User getViewer() {
return viewer;
}
public void fetchInbox() {
final Resource<DirectInbox> inboxResource = inbox.getValue();
if ((inboxResource != null && inboxResource.status == Resource.Status.LOADING) || !hasOlder) return;
stopCurrentInboxRequest();
inbox.postValue(Resource.loading(getCurrentDirectInbox()));
inboxRequest = pending ? service.fetchPendingInbox(cursor, seqId) : service.fetchInbox(cursor, seqId);
inboxRequest.enqueue(new Callback<DirectInboxResponse>() {
@Override
public void onResponse(@NonNull final Call<DirectInboxResponse> call, @NonNull final Response<DirectInboxResponse> response) {
parseInboxResponse(response.body());
}
@Override
public void onFailure(@NonNull final Call<DirectInboxResponse> call, @NonNull final Throwable t) {
Log.e(TAG, "Failed fetching dm inbox", t);
inbox.postValue(Resource.error(t.getMessage(), getCurrentDirectInbox()));
hasOlder = false;
}
});
}
public void fetchUnseenCount() {
final Resource<Integer> unseenCountResource = unseenCount.getValue();
if ((unseenCountResource != null && unseenCountResource.status == Resource.Status.LOADING)) return;
stopCurrentUnseenCountRequest();
unseenCount.postValue(Resource.loading(getCurrentUnseenCount()));
unseenCountRequest = service.fetchUnseenCount();
unseenCountRequest.enqueue(new Callback<DirectBadgeCount>() {
@Override
public void onResponse(@NonNull final Call<DirectBadgeCount> call, @NonNull final Response<DirectBadgeCount> response) {
final DirectBadgeCount directBadgeCount = response.body();
if (directBadgeCount == null) {
Log.e(TAG, "onResponse: directBadgeCount Response is null");
unseenCount.postValue(Resource.error("Unseen count response is null", getCurrentUnseenCount()));
return;
}
unseenCount.postValue(Resource.success(directBadgeCount.getBadgeCount()));
}
@Override
public void onFailure(@NonNull final Call<DirectBadgeCount> call, @NonNull final Throwable t) {
Log.e(TAG, "Failed fetching unseen count", t);
unseenCount.postValue(Resource.error(t.getMessage(), getCurrentUnseenCount()));
}
});
}
public void refresh() {
cursor = null;
seqId = 0;
hasOlder = true;
fetchInbox();
if (!pending) {
fetchUnseenCount();
}
}
private DirectInbox getCurrentDirectInbox() {
final Resource<DirectInbox> inboxResource = inbox.getValue();
return inboxResource != null ? inboxResource.data : null;
}
private void parseInboxResponse(final DirectInboxResponse response) {
if (response == null) {
Log.e(TAG, "parseInboxResponse: Response is null");
inbox.postValue(Resource.error("Response is null", getCurrentDirectInbox()));
hasOlder = false;
return;
}
if (!response.getStatus().equals("ok")) {
final String msg = "DM inbox fetch response: status not ok";
Log.e(TAG, msg);
inbox.postValue(Resource.error(msg, getCurrentDirectInbox()));
hasOlder = false;
return;
}
seqId = response.getSeqId();
if (viewer == null) {
viewer = response.getViewer();
}
final DirectInbox inbox = response.getInbox();
if (!TextUtils.isEmpty(cursor)) {
final DirectInbox currentDirectInbox = getCurrentDirectInbox();
if (currentDirectInbox != null) {
List<DirectThread> threads = currentDirectInbox.getThreads();
threads = threads == null ? new LinkedList<>() : new LinkedList<>(threads);
threads.addAll(inbox.getThreads());
inbox.setThreads(threads);
}
}
this.inbox.postValue(Resource.success(inbox));
cursor = inbox.getOldestCursor();
hasOlder = inbox.hasOlder();
pendingRequestsTotal.postValue(response.getPendingRequestsTotal());
}
public void setThread(@NonNull final String threadId,
@NonNull final DirectThread thread) {
final DirectInbox inbox = getCurrentDirectInbox();
if (inbox == null) return;
final int index = getThreadIndex(threadId, inbox);
setThread(inbox, index, thread);
}
private void setThread(@NonNull final DirectInbox inbox,
final int index,
@NonNull final DirectThread thread) {
if (index < 0) return;
synchronized (this.inbox) {
final List<DirectThread> threadsCopy = new LinkedList<>(inbox.getThreads());
threadsCopy.set(index, thread);
try {
final DirectInbox clone = (DirectInbox) inbox.clone();
clone.setThreads(threadsCopy);
this.inbox.postValue(Resource.success(clone));
} catch (CloneNotSupportedException e) {
Log.e(TAG, "setThread: ", e);
}
}
}
public void addItemsToThread(@NonNull final String threadId,
final int insertIndex,
@NonNull final Collection<DirectItem> items) {
final DirectInbox inbox = getCurrentDirectInbox();
if (inbox == null) return;
synchronized (THREAD_LOCKS.getUnchecked(threadId)) {
final int index = getThreadIndex(threadId, inbox);
if (index < 0) return;
final List<DirectThread> threads = inbox.getThreads();
final DirectThread thread = threads.get(index);
List<DirectItem> list = thread.getItems();
list = list == null ? new LinkedList<>() : new LinkedList<>(list);
if (insertIndex >= 0) {
list.addAll(insertIndex, items);
} else {
list.addAll(items);
}
try {
final DirectThread threadClone = (DirectThread) thread.clone();
threadClone.setItems(list);
setThread(inbox, index, threadClone);
} catch (Exception e) {
Log.e(TAG, "addItemsToThread: ", e);
}
}
}
public void setItemsToThread(@NonNull final String threadId,
@NonNull final List<DirectItem> updatedItems) {
final DirectInbox inbox = getCurrentDirectInbox();
if (inbox == null) return;
synchronized (THREAD_LOCKS.getUnchecked(threadId)) {
final int index = getThreadIndex(threadId, inbox);
if (index < 0) return;
final List<DirectThread> threads = inbox.getThreads();
final DirectThread thread = threads.get(index);
thread.setItems(updatedItems);
setThread(inbox, index, thread);
}
}
private int getThreadIndex(@NonNull final String threadId,
@NonNull final DirectInbox inbox) {
final List<DirectThread> threads = inbox.getThreads();
if (threads == null || threads.isEmpty()) {
return -1;
}
return Iterables.indexOf(threads, t -> {
if (t == null) return false;
return t.getThreadId().equals(threadId);
});
}
private Integer getCurrentUnseenCount() {
final Resource<Integer> unseenCountResource = unseenCount.getValue();
return unseenCountResource != null ? unseenCountResource.data : null;
}
private void stopCurrentInboxRequest() {
if (inboxRequest == null || inboxRequest.isCanceled() || inboxRequest.isExecuted()) return;
inboxRequest.cancel();
inboxRequest = null;
}
private void stopCurrentUnseenCountRequest() {
if (unseenCountRequest == null || unseenCountRequest.isCanceled() || unseenCountRequest.isExecuted()) return;
unseenCountRequest.cancel();
unseenCountRequest = null;
}
public void onDestroy() {
stopCurrentInboxRequest();
stopCurrentUnseenCountRequest();
}
public void addThread(@NonNull final DirectThread thread, final int insertIndex) {
if (insertIndex < 0) return;
synchronized (this.inbox) {
final DirectInbox currentDirectInbox = getCurrentDirectInbox();
if (currentDirectInbox == null) return;
final List<DirectThread> threadsCopy = new LinkedList<>(currentDirectInbox.getThreads());
threadsCopy.add(insertIndex, thread);
try {
final DirectInbox clone = (DirectInbox) currentDirectInbox.clone();
clone.setThreads(threadsCopy);
this.inbox.setValue(Resource.success(clone));
} catch (CloneNotSupportedException e) {
Log.e(TAG, "setThread: ", e);
}
}
}
public void removeThread(@NonNull final String threadId) {
synchronized (this.inbox) {
final DirectInbox currentDirectInbox = getCurrentDirectInbox();
if (currentDirectInbox == null) return;
final List<DirectThread> threadsCopy = currentDirectInbox.getThreads()
.stream()
.filter(t -> !t.getThreadId().equals(threadId))
.collect(Collectors.toList());
try {
final DirectInbox clone = (DirectInbox) currentDirectInbox.clone();
clone.setThreads(threadsCopy);
this.inbox.postValue(Resource.success(clone));
} catch (CloneNotSupportedException e) {
Log.e(TAG, "setThread: ", e);
}
}
}
public void setPendingRequestsTotal(final int total) {
pendingRequestsTotal.postValue(total);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,8 @@ package awais.instagrabber.models;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Objects;
public class Resource<T> {
public final Status status;
public final T data;
@ -31,6 +33,21 @@ public class Resource<T> {
return new Resource<>(Status.LOADING, data, null);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Resource<?> resource = (Resource<?>) o;
return status == resource.status &&
Objects.equals(data, resource.data) &&
Objects.equals(message, resource.message);
}
@Override
public int hashCode() {
return Objects.hash(status, data, message);
}
public enum Status {
SUCCESS,
ERROR,

View File

@ -7,7 +7,8 @@ public enum BroadcastItemType {
IMAGE("configure_photo"),
LINK("link"),
VIDEO("configure_video"),
VOICE("share_voice");
VOICE("share_voice"),
ANIMATED_MEDIA("animated_media");
private final String value;

View File

@ -0,0 +1,14 @@
package awais.instagrabber.repositories;
import awais.instagrabber.repositories.responses.giphy.GiphyGifResponse;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
public interface GifRepository {
@GET("/api/v1/creatives/story_media_search_keyed_format/")
Call<GiphyGifResponse> searchGiphyGifs(@Query("request_surface") final String requestSurface,
@Query("q") final String query,
@Query("media_types") final String mediaTypes);
}

View File

@ -0,0 +1,27 @@
package awais.instagrabber.repositories.requests.directmessages;
import java.util.HashMap;
import java.util.Map;
import awais.instagrabber.models.enums.BroadcastItemType;
import awais.instagrabber.repositories.responses.giphy.GiphyGif;
public class AnimatedMediaBroadcastOptions extends BroadcastOptions {
private final GiphyGif giphyGif;
public AnimatedMediaBroadcastOptions(final String clientContext,
final ThreadIdOrUserIds threadIdOrUserIds,
final GiphyGif giphyGif) {
super(clientContext, threadIdOrUserIds, BroadcastItemType.ANIMATED_MEDIA);
this.giphyGif = giphyGif;
}
@Override
public Map<String, String> getFormMap() {
final Map<String, String> form = new HashMap<>();
form.put("is_sticker", String.valueOf(giphyGif.isSticker()));
form.put("id", giphyGif.getId());
return form;
}
}

View File

@ -1,5 +1,7 @@
package awais.instagrabber.repositories.responses;
import java.util.Objects;
public class AnimatedMediaFixedHeight {
private final int height;
private final int width;
@ -34,4 +36,21 @@ public class AnimatedMediaFixedHeight {
public String getWebp() {
return webp;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final AnimatedMediaFixedHeight that = (AnimatedMediaFixedHeight) o;
return height == that.height &&
width == that.width &&
Objects.equals(mp4, that.mp4) &&
Objects.equals(url, that.url) &&
Objects.equals(webp, that.webp);
}
@Override
public int hashCode() {
return Objects.hash(height, width, mp4, url, webp);
}
}

View File

@ -1,5 +1,7 @@
package awais.instagrabber.repositories.responses;
import java.util.Objects;
public class AnimatedMediaImages {
private final AnimatedMediaFixedHeight fixedHeight;
@ -10,4 +12,17 @@ public class AnimatedMediaImages {
public AnimatedMediaFixedHeight getFixedHeight() {
return fixedHeight;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final AnimatedMediaImages that = (AnimatedMediaImages) o;
return Objects.equals(fixedHeight, that.fixedHeight);
}
@Override
public int hashCode() {
return Objects.hash(fixedHeight);
}
}

View File

@ -2,6 +2,7 @@ package awais.instagrabber.repositories.responses;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
public class Audio implements Serializable {
private final String audioSrc;
@ -41,4 +42,21 @@ public class Audio implements Serializable {
public long getAudioSrcExpirationTimestampUs() {
return audioSrcExpirationTimestampUs;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Audio audio = (Audio) o;
return duration == audio.duration &&
waveformSamplingFrequencyHz == audio.waveformSamplingFrequencyHz &&
audioSrcExpirationTimestampUs == audio.audioSrcExpirationTimestampUs &&
Objects.equals(audioSrc, audio.audioSrc) &&
Objects.equals(waveformData, audio.waveformData);
}
@Override
public int hashCode() {
return Objects.hash(audioSrc, duration, waveformData, waveformSamplingFrequencyHz, audioSrcExpirationTimestampUs);
}
}

View File

@ -11,6 +11,7 @@ import com.google.gson.JsonParseException;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.Objects;
public class Caption implements Serializable {
private long mPk;
@ -42,6 +43,21 @@ public class Caption implements Serializable {
this.text = text;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Caption caption = (Caption) o;
return mPk == caption.mPk &&
userId == caption.userId &&
Objects.equals(text, caption.text);
}
@Override
public int hashCode() {
return Objects.hash(mPk, userId, text);
}
public static class CaptionDeserializer implements JsonDeserializer<Caption> {
private static final String TAG = CaptionDeserializer.class.getSimpleName();

View File

@ -1,6 +1,7 @@
package awais.instagrabber.repositories.responses;
import java.io.Serializable;
import java.util.Objects;
public class EndOfFeedDemarcator implements Serializable {
private final long id;
@ -18,4 +19,18 @@ public class EndOfFeedDemarcator implements Serializable {
public EndOfFeedGroupSet getGroupSet() {
return groupSet;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final EndOfFeedDemarcator that = (EndOfFeedDemarcator) o;
return id == that.id &&
Objects.equals(groupSet, that.groupSet);
}
@Override
public int hashCode() {
return Objects.hash(id, groupSet);
}
}

View File

@ -2,6 +2,7 @@ package awais.instagrabber.repositories.responses;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
public class EndOfFeedGroup implements Serializable {
private final String id;
@ -31,4 +32,20 @@ public class EndOfFeedGroup implements Serializable {
public List<Media> getFeedItems() {
return feedItems;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final EndOfFeedGroup that = (EndOfFeedGroup) o;
return Objects.equals(id, that.id) &&
Objects.equals(title, that.title) &&
Objects.equals(nextMaxId, that.nextMaxId) &&
Objects.equals(feedItems, that.feedItems);
}
@Override
public int hashCode() {
return Objects.hash(id, title, nextMaxId, feedItems);
}
}

View File

@ -2,6 +2,7 @@ package awais.instagrabber.repositories.responses;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
public class EndOfFeedGroupSet implements Serializable {
private final long id;
@ -48,4 +49,22 @@ public class EndOfFeedGroupSet implements Serializable {
public List<EndOfFeedGroup> getGroups() {
return groups;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final EndOfFeedGroupSet that = (EndOfFeedGroupSet) o;
return id == that.id &&
Objects.equals(activeGroupId, that.activeGroupId) &&
Objects.equals(connectedGroupId, that.connectedGroupId) &&
Objects.equals(nextMaxId, that.nextMaxId) &&
Objects.equals(paginationSource, that.paginationSource) &&
Objects.equals(groups, that.groups);
}
@Override
public int hashCode() {
return Objects.hash(id, activeGroupId, connectedGroupId, nextMaxId, paginationSource, groups);
}
}

View File

@ -3,6 +3,7 @@ package awais.instagrabber.repositories.responses;
import androidx.annotation.NonNull;
import java.io.Serializable;
import java.util.Objects;
public class FriendshipStatus implements Serializable {
private final boolean following;
@ -78,6 +79,29 @@ public class FriendshipStatus implements Serializable {
return isMutingReel;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final FriendshipStatus that = (FriendshipStatus) o;
return following == that.following &&
followedBy == that.followedBy &&
blocking == that.blocking &&
muting == that.muting &&
isPrivate == that.isPrivate &&
incomingRequest == that.incomingRequest &&
outgoingRequest == that.outgoingRequest &&
isBestie == that.isBestie &&
isRestricted == that.isRestricted &&
isMutingReel == that.isMutingReel;
}
@Override
public int hashCode() {
return Objects.hash(following, followedBy, blocking, muting, isPrivate, incomingRequest, outgoingRequest, isBestie, isRestricted,
isMutingReel);
}
@NonNull
@Override
public String toString() {

View File

@ -2,6 +2,7 @@ package awais.instagrabber.repositories.responses;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
public class ImageVersions2 implements Serializable {
private final List<MediaCandidate> candidates;
@ -13,4 +14,17 @@ public class ImageVersions2 implements Serializable {
public List<MediaCandidate> getCandidates() {
return candidates;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final ImageVersions2 that = (ImageVersions2) o;
return Objects.equals(candidates, that.candidates);
}
@Override
public int hashCode() {
return Objects.hash(candidates);
}
}

View File

@ -1,6 +1,7 @@
package awais.instagrabber.repositories.responses;
import java.io.Serializable;
import java.util.Objects;
public class Location implements Serializable {
private final long pk;
@ -54,4 +55,23 @@ public class Location implements Serializable {
public float getLat() {
return lat;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Location location = (Location) o;
return pk == location.pk &&
Float.compare(location.lng, lng) == 0 &&
Float.compare(location.lat, lat) == 0 &&
Objects.equals(shortName, location.shortName) &&
Objects.equals(name, location.name) &&
Objects.equals(address, location.address) &&
Objects.equals(city, location.city);
}
@Override
public int hashCode() {
return Objects.hash(pk, shortName, name, address, city, lng, lat);
}
}

View File

@ -6,6 +6,7 @@ import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Utils;
@ -272,4 +273,52 @@ public class Media implements Serializable {
final Caption caption1 = getCaption();
caption1.setText(caption);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Media media = (Media) o;
return takenAt == media.takenAt &&
canViewerReshare == media.canViewerReshare &&
commentLikesEnabled == media.commentLikesEnabled &&
commentsDisabled == media.commentsDisabled &&
nextMaxId == media.nextMaxId &&
commentCount == media.commentCount &&
originalWidth == media.originalWidth &&
originalHeight == media.originalHeight &&
likeCount == media.likeCount &&
hasLiked == media.hasLiked &&
isReelMedia == media.isReelMedia &&
hasAudio == media.hasAudio &&
Double.compare(media.videoDuration, videoDuration) == 0 &&
viewCount == media.viewCount &&
canViewerSave == media.canViewerSave &&
isSidecarChild == media.isSidecarChild &&
hasViewerSaved == media.hasViewerSaved &&
Objects.equals(pk, media.pk) &&
Objects.equals(id, media.id) &&
Objects.equals(code, media.code) &&
Objects.equals(user, media.user) &&
mediaType == media.mediaType &&
Objects.equals(imageVersions2, media.imageVersions2) &&
Objects.equals(videoVersions, media.videoVersions) &&
Objects.equals(caption, media.caption) &&
Objects.equals(audio, media.audio) &&
Objects.equals(title, media.title) &&
Objects.equals(location, media.location) &&
Objects.equals(usertags, media.usertags) &&
Objects.equals(carouselMedia, media.carouselMedia) &&
Objects.equals(injected, media.injected) &&
Objects.equals(endOfFeedDemarcator, media.endOfFeedDemarcator) &&
Objects.equals(dateString, media.dateString);
}
@Override
public int hashCode() {
return Objects.hash(pk, id, code, takenAt, user, mediaType, canViewerReshare, commentLikesEnabled, commentsDisabled, nextMaxId, commentCount,
imageVersions2, originalWidth, originalHeight, likeCount, hasLiked, isReelMedia, videoVersions, hasAudio, videoDuration,
viewCount, caption, canViewerSave, audio, title, location, usertags, carouselMedia, isSidecarChild, hasViewerSaved,
injected, endOfFeedDemarcator, dateString);
}
}

View File

@ -1,6 +1,7 @@
package awais.instagrabber.repositories.responses;
import java.io.Serializable;
import java.util.Objects;
public class MediaCandidate implements Serializable {
private final int width;
@ -24,4 +25,19 @@ public class MediaCandidate implements Serializable {
public String getUrl() {
return url;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MediaCandidate that = (MediaCandidate) o;
return width == that.width &&
height == that.height &&
Objects.equals(url, that.url);
}
@Override
public int hashCode() {
return Objects.hash(width, height, url);
}
}

View File

@ -194,17 +194,40 @@ public class User implements Serializable {
return profileContextLinksWithUserIds;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final User that = (User) o;
return pk == that.pk &&
Objects.equals(username, that.username);
final User user = (User) o;
return pk == user.pk &&
isPrivate == user.isPrivate &&
isVerified == user.isVerified &&
hasAnonymousProfilePicture == user.hasAnonymousProfilePicture &&
isUnpublished == user.isUnpublished &&
isFavorite == user.isFavorite &&
isDirectappInstalled == user.isDirectappInstalled &&
mediaCount == user.mediaCount &&
followerCount == user.followerCount &&
followingCount == user.followingCount &&
followingTagCount == user.followingTagCount &&
usertagsCount == user.usertagsCount &&
Objects.equals(username, user.username) &&
Objects.equals(fullName, user.fullName) &&
Objects.equals(profilePicUrl, user.profilePicUrl) &&
Objects.equals(profilePicId, user.profilePicId) &&
Objects.equals(friendshipStatus, user.friendshipStatus) &&
Objects.equals(reelAutoArchive, user.reelAutoArchive) &&
Objects.equals(allowedCommenterType, user.allowedCommenterType) &&
Objects.equals(biography, user.biography) &&
Objects.equals(externalUrl, user.externalUrl) &&
Objects.equals(publicEmail, user.publicEmail);
}
@Override
public int hashCode() {
return Objects.hash(pk, username);
return Objects.hash(pk, username, fullName, isPrivate, profilePicUrl, profilePicId, friendshipStatus, isVerified, hasAnonymousProfilePicture,
isUnpublished, isFavorite, isDirectappInstalled, reelAutoArchive, allowedCommenterType, mediaCount, followerCount,
followingCount, followingTagCount, biography, externalUrl, usertagsCount, publicEmail);
}
}

View File

@ -2,6 +2,7 @@ package awais.instagrabber.repositories.responses;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
public class UsertagIn implements Serializable {
private final User user;
@ -19,4 +20,18 @@ public class UsertagIn implements Serializable {
public List<String> getPosition() {
return position;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final UsertagIn usertagIn = (UsertagIn) o;
return Objects.equals(user, usertagIn.user) &&
Objects.equals(position, usertagIn.position);
}
@Override
public int hashCode() {
return Objects.hash(user, position);
}
}

View File

@ -2,6 +2,7 @@ package awais.instagrabber.repositories.responses;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
public class Usertags implements Serializable {
private final List<UsertagIn> in;
@ -13,4 +14,17 @@ public class Usertags implements Serializable {
public List<UsertagIn> getIn() {
return in;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Usertags usertags = (Usertags) o;
return Objects.equals(in, usertags.in);
}
@Override
public int hashCode() {
return Objects.hash(in);
}
}

View File

@ -1,6 +1,7 @@
package awais.instagrabber.repositories.responses;
import java.io.Serializable;
import java.util.Objects;
public class VideoVersion implements Serializable {
private final String id;
@ -36,4 +37,21 @@ public class VideoVersion implements Serializable {
public String getUrl() {
return url;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final VideoVersion that = (VideoVersion) o;
return width == that.width &&
height == that.height &&
Objects.equals(id, that.id) &&
Objects.equals(type, that.type) &&
Objects.equals(url, that.url);
}
@Override
public int hashCode() {
return Objects.hash(id, type, width, height, url);
}
}

View File

@ -1,9 +1,11 @@
package awais.instagrabber.repositories.responses.directmessages;
import androidx.annotation.NonNull;
import java.util.List;
public class DirectInbox {
private final List<DirectThread> threads;
public class DirectInbox implements Cloneable {
private List<DirectThread> threads;
private final boolean hasOlder;
private final int unseenCount;
private final String unseenCountTs;
@ -28,6 +30,10 @@ public class DirectInbox {
return threads;
}
public void setThreads(final List<DirectThread> threads) {
this.threads = threads;
}
public boolean hasOlder() {
return hasOlder;
}
@ -47,4 +53,10 @@ public class DirectInbox {
public boolean isBlendedInboxEnabled() {
return blendedInboxEnabled;
}
@NonNull
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

View File

@ -2,8 +2,12 @@ package awais.instagrabber.repositories.responses.directmessages;
import androidx.annotation.NonNull;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.repositories.responses.Location;
@ -40,6 +44,7 @@ public class DirectItem implements Cloneable {
private Date date;
private boolean isPending;
private boolean showForwardAttribution;
private LocalDateTime localDateTime;
public DirectItem(final String itemId,
final long userId,
@ -213,6 +218,13 @@ public class DirectItem implements Cloneable {
return date;
}
public LocalDateTime getLocalDateTime() {
if (localDateTime == null) {
localDateTime = Instant.ofEpochMilli(timestamp / 1000).atZone(ZoneId.systemDefault()).toLocalDateTime();;
}
return localDateTime;
}
public void setItemId(final String itemId) {
this.itemId = itemId;
}
@ -239,21 +251,47 @@ public class DirectItem implements Cloneable {
return super.clone();
}
// @Override
// public boolean equals(final Object o) {
// if (this == o) return true;
// if (o == null || getClass() != o.getClass()) return false;
// final DirectItem that = (DirectItem) o;
// return userId == that.userId &&
// timestamp == that.timestamp &&
// isPending == that.isPending &&
// Objects.equals(itemId, that.itemId) &&
// itemType == that.itemType &&
// Objects.equals(clientContext, that.clientContext);
// }
//
// @Override
// public int hashCode() {
// return Objects.hash(itemId, userId, timestamp, itemType, clientContext, isPending);
// }
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectItem that = (DirectItem) o;
return userId == that.userId &&
timestamp == that.timestamp &&
hideInThread == that.hideInThread &&
isPending == that.isPending &&
showForwardAttribution == that.showForwardAttribution &&
Objects.equals(itemId, that.itemId) &&
itemType == that.itemType &&
Objects.equals(text, that.text) &&
Objects.equals(like, that.like) &&
Objects.equals(link, that.link) &&
Objects.equals(clientContext, that.clientContext) &&
Objects.equals(reelShare, that.reelShare) &&
Objects.equals(storyShare, that.storyShare) &&
Objects.equals(mediaShare, that.mediaShare) &&
Objects.equals(profile, that.profile) &&
Objects.equals(placeholder, that.placeholder) &&
Objects.equals(media, that.media) &&
Objects.equals(previewMedias, that.previewMedias) &&
Objects.equals(actionLog, that.actionLog) &&
Objects.equals(videoCallEvent, that.videoCallEvent) &&
Objects.equals(clip, that.clip) &&
Objects.equals(felixShare, that.felixShare) &&
Objects.equals(visualMedia, that.visualMedia) &&
Objects.equals(animatedMedia, that.animatedMedia) &&
Objects.equals(reactions, that.reactions) &&
Objects.equals(repliedToMessage, that.repliedToMessage) &&
Objects.equals(voiceMedia, that.voiceMedia) &&
Objects.equals(location, that.location) &&
Objects.equals(date, that.date);
}
@Override
public int hashCode() {
return Objects
.hash(itemId, userId, timestamp, itemType, text, like, link, clientContext, reelShare, storyShare, mediaShare, profile, placeholder,
media, previewMedias, actionLog, videoCallEvent, clip, felixShare, visualMedia, animatedMedia, reactions, repliedToMessage,
voiceMedia, location, hideInThread, date, isPending, showForwardAttribution);
}
}

View File

@ -1,6 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.util.List;
import java.util.Objects;
public class DirectItemActionLog {
private final String description;
@ -27,6 +28,21 @@ public class DirectItemActionLog {
return textAttributes;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectItemActionLog that = (DirectItemActionLog) o;
return Objects.equals(description, that.description) &&
Objects.equals(bold, that.bold) &&
Objects.equals(textAttributes, that.textAttributes);
}
@Override
public int hashCode() {
return Objects.hash(description, bold, textAttributes);
}
public static class TextRange {
private final int start;
private final int end;
@ -55,5 +71,21 @@ public class DirectItemActionLog {
public String getIntent() {
return intent;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final TextRange textRange = (TextRange) o;
return start == textRange.start &&
end == textRange.end &&
Objects.equals(color, textRange.color) &&
Objects.equals(intent, textRange.intent);
}
@Override
public int hashCode() {
return Objects.hash(start, end, color, intent);
}
}
}

View File

@ -1,5 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
import awais.instagrabber.repositories.responses.AnimatedMediaImages;
public final class DirectItemAnimatedMedia {
@ -31,4 +33,20 @@ public final class DirectItemAnimatedMedia {
public boolean isSticker() {
return isSticker;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectItemAnimatedMedia that = (DirectItemAnimatedMedia) o;
return isRandom == that.isRandom &&
isSticker == that.isSticker &&
Objects.equals(id, that.id) &&
Objects.equals(images, that.images);
}
@Override
public int hashCode() {
return Objects.hash(id, images, isRandom, isSticker);
}
}

View File

@ -1,5 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
import awais.instagrabber.repositories.responses.Media;
public class DirectItemClip {
@ -12,4 +14,17 @@ public class DirectItemClip {
public Media getClip() {
return clip;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectItemClip that = (DirectItemClip) o;
return Objects.equals(clip, that.clip);
}
@Override
public int hashCode() {
return Objects.hash(clip);
}
}

View File

@ -1,5 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
import awais.instagrabber.repositories.responses.Media;
public class DirectItemFelixShare {
@ -12,4 +14,17 @@ public class DirectItemFelixShare {
public Media getVideo() {
return video;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectItemFelixShare that = (DirectItemFelixShare) o;
return Objects.equals(video, that.video);
}
@Override
public int hashCode() {
return Objects.hash(video);
}
}

View File

@ -1,5 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
public class DirectItemLink {
private final String text;
private final DirectItemLinkContext linkContext;
@ -31,4 +33,20 @@ public class DirectItemLink {
public String getMutationToken() {
return mutationToken;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectItemLink that = (DirectItemLink) o;
return Objects.equals(text, that.text) &&
Objects.equals(linkContext, that.linkContext) &&
Objects.equals(clientContext, that.clientContext) &&
Objects.equals(mutationToken, that.mutationToken);
}
@Override
public int hashCode() {
return Objects.hash(text, linkContext, clientContext, mutationToken);
}
}

View File

@ -1,5 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
public class DirectItemLinkContext {
private final String linkUrl;
private final String linkTitle;
@ -31,4 +33,20 @@ public class DirectItemLinkContext {
public String getLinkImageUrl() {
return linkImageUrl;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectItemLinkContext that = (DirectItemLinkContext) o;
return Objects.equals(linkUrl, that.linkUrl) &&
Objects.equals(linkTitle, that.linkTitle) &&
Objects.equals(linkSummary, that.linkSummary) &&
Objects.equals(linkImageUrl, that.linkImageUrl);
}
@Override
public int hashCode() {
return Objects.hash(linkUrl, linkTitle, linkSummary, linkImageUrl);
}
}

View File

@ -1,5 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
public class DirectItemPlaceholder {
private final boolean isLinked;
private final String title;
@ -24,4 +26,19 @@ public class DirectItemPlaceholder {
public String getMessage() {
return message;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectItemPlaceholder that = (DirectItemPlaceholder) o;
return isLinked == that.isLinked &&
Objects.equals(title, that.title) &&
Objects.equals(message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(isLinked, title, message);
}
}

View File

@ -1,5 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
import awais.instagrabber.repositories.responses.Media;
public class DirectItemReelShare {
@ -61,4 +63,24 @@ public class DirectItemReelShare {
public long getMentionedUserId() {
return mentionedUserId;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectItemReelShare that = (DirectItemReelShare) o;
return reelOwnerId == that.reelOwnerId &&
mentionedUserId == that.mentionedUserId &&
isReelPersisted == that.isReelPersisted &&
Objects.equals(text, that.text) &&
Objects.equals(type, that.type) &&
Objects.equals(reelType, that.reelType) &&
Objects.equals(media, that.media) &&
Objects.equals(reactionInfo, that.reactionInfo);
}
@Override
public int hashCode() {
return Objects.hash(text, type, reelOwnerId, mentionedUserId, isReelPersisted, reelType, media, reactionInfo);
}
}

View File

@ -1,5 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
public class DirectItemReelShareReactionInfo {
private final String emoji;
private final String intensity;
@ -16,4 +18,18 @@ public class DirectItemReelShareReactionInfo {
public String getIntensity() {
return intensity;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectItemReelShareReactionInfo that = (DirectItemReelShareReactionInfo) o;
return Objects.equals(emoji, that.emoji) &&
Objects.equals(intensity, that.intensity);
}
@Override
public int hashCode() {
return Objects.hash(emoji, intensity);
}
}

View File

@ -1,5 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
import awais.instagrabber.repositories.responses.Media;
public class DirectItemStoryShare {
@ -54,4 +56,23 @@ public class DirectItemStoryShare {
public String getMessage() {
return message;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectItemStoryShare that = (DirectItemStoryShare) o;
return isReelPersisted == that.isReelPersisted &&
Objects.equals(reelId, that.reelId) &&
Objects.equals(reelType, that.reelType) &&
Objects.equals(text, that.text) &&
Objects.equals(media, that.media) &&
Objects.equals(title, that.title) &&
Objects.equals(message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(reelId, reelType, text, isReelPersisted, media, title, message);
}
}

View File

@ -1,6 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.util.List;
import java.util.Objects;
public final class DirectItemVideoCallEvent {
private final String action;
@ -40,4 +41,21 @@ public final class DirectItemVideoCallEvent {
public List<DirectItemActionLog.TextRange> getTextAttributes() {
return textAttributes;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectItemVideoCallEvent that = (DirectItemVideoCallEvent) o;
return threadHasAudioOnlyCall == that.threadHasAudioOnlyCall &&
Objects.equals(action, that.action) &&
Objects.equals(encodedServerDataInfo, that.encodedServerDataInfo) &&
Objects.equals(description, that.description) &&
Objects.equals(textAttributes, that.textAttributes);
}
@Override
public int hashCode() {
return Objects.hash(action, encodedServerDataInfo, description, threadHasAudioOnlyCall, textAttributes);
}
}

View File

@ -1,6 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.util.List;
import java.util.Objects;
import awais.instagrabber.models.enums.RavenMediaViewMode;
import awais.instagrabber.repositories.responses.Media;
@ -64,4 +65,25 @@ public class DirectItemVisualMedia {
public Media getMedia() {
return media;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectItemVisualMedia media1 = (DirectItemVisualMedia) o;
return urlExpireAtSecs == media1.urlExpireAtSecs &&
playbackDurationSecs == media1.playbackDurationSecs &&
seenCount == media1.seenCount &&
replayExpiringAtUs == media1.replayExpiringAtUs &&
Objects.equals(seenUserIds, media1.seenUserIds) &&
viewMode == media1.viewMode &&
Objects.equals(expiringMediaActionSummary, media1.expiringMediaActionSummary) &&
Objects.equals(media, media1.media);
}
@Override
public int hashCode() {
return Objects
.hash(urlExpireAtSecs, playbackDurationSecs, seenUserIds, viewMode, seenCount, replayExpiringAtUs, expiringMediaActionSummary, media);
}
}

View File

@ -1,5 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
import awais.instagrabber.repositories.responses.Media;
public class DirectItemVoiceMedia {
@ -24,4 +26,19 @@ public class DirectItemVoiceMedia {
public String getViewMode() {
return viewMode;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectItemVoiceMedia that = (DirectItemVoiceMedia) o;
return seenCount == that.seenCount &&
Objects.equals(media, that.media) &&
Objects.equals(viewMode, that.viewMode);
}
@Override
public int hashCode() {
return Objects.hash(media, seenCount, viewMode);
}
}

View File

@ -1,5 +1,6 @@
package awais.instagrabber.repositories.responses.directmessages;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.Serializable;
@ -9,13 +10,13 @@ import java.util.Objects;
import awais.instagrabber.repositories.responses.User;
public class DirectThread implements Serializable {
public class DirectThread implements Serializable, Cloneable {
private final String threadId;
private final String threadV2Id;
private final List<User> users;
private final List<User> leftUsers;
private final List<Long> adminUserIds;
private final List<DirectItem> items;
private List<User> users;
private List<User> leftUsers;
private List<Long> adminUserIds;
private List<DirectItem> items;
private final long lastActivityAt;
private boolean muted;
private final boolean isPin;
@ -127,18 +128,34 @@ public class DirectThread implements Serializable {
return users;
}
public void setUsers(final List<User> users) {
this.users = users;
}
public List<User> getLeftUsers() {
return leftUsers;
}
public void setLeftUsers(final List<User> leftUsers) {
this.leftUsers = leftUsers;
}
public List<Long> getAdminUserIds() {
return adminUserIds;
}
public void setAdminUserIds(final List<Long> adminUserIds) {
this.adminUserIds = adminUserIds;
}
public List<DirectItem> getItems() {
return items;
}
public void setItems(final List<DirectItem> items) {
this.items = items;
}
public long getLastActivityAt() {
return lastActivityAt;
}
@ -284,17 +301,59 @@ public class DirectThread implements Serializable {
return firstItem;
}
@NonNull
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectThread that = (DirectThread) o;
return Objects.equals(threadId, that.threadId) &&
Objects.equals(threadV2Id, that.threadV2Id);
return lastActivityAt == that.lastActivityAt &&
muted == that.muted &&
isPin == that.isPin &&
named == that.named &&
canonical == that.canonical &&
pending == that.pending &&
archived == that.archived &&
valuedRequest == that.valuedRequest &&
viewerId == that.viewerId &&
folder == that.folder &&
vcMuted == that.vcMuted &&
isGroup == that.isGroup &&
mentionsMuted == that.mentionsMuted &&
hasOlder == that.hasOlder &&
hasNewer == that.hasNewer &&
isSpam == that.isSpam &&
approvalRequiredForNewMembers == that.approvalRequiredForNewMembers &&
inputMode == that.inputMode &&
Objects.equals(threadId, that.threadId) &&
Objects.equals(threadV2Id, that.threadV2Id) &&
Objects.equals(users, that.users) &&
Objects.equals(leftUsers, that.leftUsers) &&
Objects.equals(adminUserIds, that.adminUserIds) &&
Objects.equals(items, that.items) &&
Objects.equals(threadType, that.threadType) &&
Objects.equals(threadTitle, that.threadTitle) &&
Objects.equals(pendingScore, that.pendingScore) &&
Objects.equals(inviter, that.inviter) &&
Objects.equals(lastSeenAt, that.lastSeenAt) &&
Objects.equals(newestCursor, that.newestCursor) &&
Objects.equals(oldestCursor, that.oldestCursor) &&
Objects.equals(lastPermanentItem, that.lastPermanentItem) &&
Objects.equals(directStory, that.directStory) &&
Objects.equals(threadContextItems, that.threadContextItems);
}
@Override
public int hashCode() {
return Objects.hash(threadId, threadV2Id);
return Objects
.hash(threadId, threadV2Id, users, leftUsers, adminUserIds, items, lastActivityAt, muted, isPin, named, canonical, pending, archived,
valuedRequest, threadType, viewerId, threadTitle, pendingScore, folder, vcMuted, isGroup, mentionsMuted, inviter, hasOlder,
hasNewer, lastSeenAt, newestCursor, oldestCursor, isSpam, lastPermanentItem, directStory, approvalRequiredForNewMembers,
inputMode, threadContextItems);
}
}

View File

@ -1,6 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.util.List;
import java.util.Objects;
public class DirectThreadDirectStory {
private final List<DirectItem> items;
@ -18,4 +19,18 @@ public class DirectThreadDirectStory {
public int getUnseenCount() {
return unseenCount;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectThreadDirectStory that = (DirectThreadDirectStory) o;
return unseenCount == that.unseenCount &&
Objects.equals(items, that.items);
}
@Override
public int hashCode() {
return Objects.hash(items, unseenCount);
}
}

View File

@ -1,5 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
public class DirectThreadLastSeenAt {
private final String timestamp;
private final String itemId;
@ -16,4 +18,18 @@ public class DirectThreadLastSeenAt {
public String getItemId() {
return itemId;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final DirectThreadLastSeenAt that = (DirectThreadLastSeenAt) o;
return Objects.equals(timestamp, that.timestamp) &&
Objects.equals(itemId, that.itemId);
}
@Override
public int hashCode() {
return Objects.hash(timestamp, itemId);
}
}

View File

@ -13,7 +13,7 @@ public class DirectThreadParticipantRequestsResponse implements Serializable, Cl
private final Map<Long, String> requesterUsernames;
private final String cursor;
private final int totalThreadParticipants;
private final int totalParticipantRequests;
private int totalParticipantRequests;
private final String status;
public DirectThreadParticipantRequestsResponse(final List<User> users,
@ -54,6 +54,10 @@ public class DirectThreadParticipantRequestsResponse implements Serializable, Cl
return totalParticipantRequests;
}
public void setTotalParticipantRequests(final int totalParticipantRequests) {
this.totalParticipantRequests = totalParticipantRequests;
}
public String getStatus() {
return status;
}

View File

@ -2,6 +2,8 @@ package awais.instagrabber.repositories.responses.directmessages;
import com.google.gson.annotations.SerializedName;
import java.util.Objects;
public final class RavenExpiringMediaActionSummary {
private final ActionType type;
private final long timestamp;
@ -25,6 +27,21 @@ public final class RavenExpiringMediaActionSummary {
return type;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final RavenExpiringMediaActionSummary that = (RavenExpiringMediaActionSummary) o;
return timestamp == that.timestamp &&
count == that.count &&
type == that.type;
}
@Override
public int hashCode() {
return Objects.hash(type, timestamp, count);
}
// thanks to http://github.com/warifp/InstagramAutoPostImageUrl/blob/master/vendor/mgp25/instagram-php/src/Response/Model/ActionBadge.php
public enum ActionType {
@SerializedName("raven_delivered")

View File

@ -1,6 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.io.Serializable;
import java.util.Objects;
public class ThreadContext implements Serializable {
private final int type;
@ -18,4 +19,18 @@ public class ThreadContext implements Serializable {
public String getText() {
return text;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final ThreadContext that = (ThreadContext) o;
return type == that.type &&
Objects.equals(text, that.text);
}
@Override
public int hashCode() {
return Objects.hash(type, text);
}
}

View File

@ -0,0 +1,70 @@
package awais.instagrabber.repositories.responses.giphy;
import androidx.annotation.NonNull;
import java.util.Objects;
public class GiphyGif {
private final String type;
private final String id;
private final String title;
private final int isSticker;
private final GiphyGifImages images;
public GiphyGif(final String type, final String id, final String title, final int isSticker, final GiphyGifImages images) {
this.type = type;
this.id = id;
this.title = title;
this.isSticker = isSticker;
this.images = images;
}
public String getType() {
return type;
}
public String getId() {
return id;
}
public String getTitle() {
return title;
}
public boolean isSticker() {
return isSticker == 1;
}
public GiphyGifImages getImages() {
return images;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final GiphyGif giphyGif = (GiphyGif) o;
return isSticker == giphyGif.isSticker &&
Objects.equals(type, giphyGif.type) &&
Objects.equals(id, giphyGif.id) &&
Objects.equals(title, giphyGif.title) &&
Objects.equals(images, giphyGif.images);
}
@Override
public int hashCode() {
return Objects.hash(type, id, title, isSticker, images);
}
@NonNull
@Override
public String toString() {
return "GiphyGif{" +
"type='" + type + '\'' +
", id='" + id + '\'' +
", title='" + title + '\'' +
", isSticker=" + isSticker() +
", images=" + images +
'}';
}
}

View File

@ -0,0 +1,59 @@
package awais.instagrabber.repositories.responses.giphy;
import java.util.Objects;
public class GiphyGifImage {
private final int height;
private final int width;
private final long webpSize;
private final String webp;
public GiphyGifImage(final int height, final int width, final long webpSize, final String webp) {
this.height = height;
this.width = width;
this.webpSize = webpSize;
this.webp = webp;
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
public long getWebpSize() {
return webpSize;
}
public String getWebp() {
return webp;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final GiphyGifImage that = (GiphyGifImage) o;
return height == that.height &&
width == that.width &&
webpSize == that.webpSize &&
Objects.equals(webp, that.webp);
}
@Override
public int hashCode() {
return Objects.hash(height, width, webpSize, webp);
}
@Override
public String toString() {
return "GiphyGifImage{" +
"height=" + height +
", width=" + width +
", webpSize=" + webpSize +
", webp='" + webp + '\'' +
'}';
}
}

View File

@ -0,0 +1,40 @@
package awais.instagrabber.repositories.responses.giphy;
import androidx.annotation.NonNull;
import java.util.Objects;
import awais.instagrabber.repositories.responses.AnimatedMediaFixedHeight;
public class GiphyGifImages {
private final AnimatedMediaFixedHeight fixedHeight;
public GiphyGifImages(final AnimatedMediaFixedHeight fixedHeight) {
this.fixedHeight = fixedHeight;
}
public AnimatedMediaFixedHeight getFixedHeight() {
return fixedHeight;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final GiphyGifImages that = (GiphyGifImages) o;
return Objects.equals(fixedHeight, that.fixedHeight);
}
@Override
public int hashCode() {
return Objects.hash(fixedHeight);
}
@NonNull
@Override
public String toString() {
return "GiphyGifImages{" +
"fixedHeight=" + fixedHeight +
'}';
}
}

View File

@ -0,0 +1,46 @@
package awais.instagrabber.repositories.responses.giphy;
import androidx.annotation.NonNull;
import java.util.Objects;
public class GiphyGifResponse {
private final GiphyGifResults results;
private final String status;
public GiphyGifResponse(final GiphyGifResults results, final String status) {
this.results = results;
this.status = status;
}
public GiphyGifResults getResults() {
return results;
}
public String getStatus() {
return status;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final GiphyGifResponse that = (GiphyGifResponse) o;
return Objects.equals(results, that.results) &&
Objects.equals(status, that.status);
}
@Override
public int hashCode() {
return Objects.hash(results, status);
}
@NonNull
@Override
public String toString() {
return "GiphyGifResponse{" +
"results=" + results +
", status='" + status + '\'' +
'}';
}
}

View File

@ -0,0 +1,47 @@
package awais.instagrabber.repositories.responses.giphy;
import androidx.annotation.NonNull;
import java.util.List;
import java.util.Objects;
public class GiphyGifResults {
private final List<GiphyGif> giphyGifs;
private final List<GiphyGif> giphy;
public GiphyGifResults(final List<GiphyGif> giphyGifs, final List<GiphyGif> giphy) {
this.giphyGifs = giphyGifs;
this.giphy = giphy;
}
public List<GiphyGif> getGiphyGifs() {
return giphyGifs;
}
public List<GiphyGif> getGiphy() {
return giphy;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final GiphyGifResults that = (GiphyGifResults) o;
return Objects.equals(giphyGifs, that.giphyGifs) &&
Objects.equals(giphy, that.giphy);
}
@Override
public int hashCode() {
return Objects.hash(giphyGifs, giphy);
}
@NonNull
@Override
public String toString() {
return "GiphyGifResults{" +
"giphyGifs=" + giphyGifs +
", giphy=" + giphy +
'}';
}
}

View File

@ -126,6 +126,6 @@ public class ActivityCheckerService extends Service {
final Intent intent = new Intent(getApplicationContext(), MainActivity.class)
.setAction(Constants.ACTION_SHOW_ACTIVITY)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
return PendingIntent.getActivity(getApplicationContext(), 1738, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return PendingIntent.getActivity(getApplicationContext(), Constants.SHOW_ACTIVITY_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
}

View File

@ -0,0 +1,27 @@
package awais.instagrabber.services;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import java.util.Objects;
import awais.instagrabber.fragments.settings.PreferenceKeys;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.TextUtils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class BootCompletedReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
if (!Objects.equals(intent.getAction(), "android.intent.action.BOOT_COMPLETED")) return;
final boolean enabled = settingsHelper.getBoolean(PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH);
if (!enabled) return;
final String cookie = settingsHelper.getString(Constants.COOKIE);
final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != 0;
if (!isLoggedIn) return;
DMSyncAlarmReceiver.setAlarm(context);
}
}

View File

@ -0,0 +1,87 @@
package awais.instagrabber.services;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import awais.instagrabber.fragments.settings.PreferenceKeys;
import awais.instagrabber.utils.Constants;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class DMSyncAlarmReceiver extends BroadcastReceiver {
private static final String TAG = DMSyncAlarmReceiver.class.getSimpleName();
@Override
public void onReceive(final Context context, final Intent intent) {
final boolean enabled = settingsHelper.getBoolean(PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH);
if (!enabled) {
// If somehow the alarm was triggered even when auto refresh is disabled
cancelAlarm(context);
return;
}
try {
final Context applicationContext = context.getApplicationContext();
ContextCompat.startForegroundService(applicationContext, new Intent(applicationContext, DMSyncService.class));
} catch (Exception e) {
Log.e(TAG, "onReceive: ", e);
}
}
public static void setAlarm(@NonNull final Context context) {
Log.d(TAG, "setting DMSyncService Alarm");
final AlarmManager alarmManager = getAlarmManager(context);
if (alarmManager == null) return;
final PendingIntent pendingIntent = getPendingIntent(context);
alarmManager.setInexactRepeating(AlarmManager.RTC, System.currentTimeMillis(), getIntervalMillis(), pendingIntent);
}
public static void cancelAlarm(@NonNull final Context context) {
Log.d(TAG, "cancelling DMSyncService Alarm");
final AlarmManager alarmManager = getAlarmManager(context);
if (alarmManager == null) return;
final PendingIntent pendingIntent = getPendingIntent(context);
alarmManager.cancel(pendingIntent);
}
private static AlarmManager getAlarmManager(@NonNull final Context context) {
return (AlarmManager) context.getApplicationContext().getSystemService(Context.ALARM_SERVICE);
}
private static PendingIntent getPendingIntent(@NonNull final Context context) {
final Context applicationContext = context.getApplicationContext();
final Intent intent = new Intent(applicationContext, DMSyncAlarmReceiver.class);
return PendingIntent.getBroadcast(applicationContext,
Constants.DM_SYNC_SERVICE_REQUEST_CODE,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
private static long getIntervalMillis() {
int amount = settingsHelper.getInteger(PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER);
if (amount <= 0) {
amount = 30;
}
final String unit = settingsHelper.getString(PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT);
final TemporalUnit temporalUnit;
switch (unit) {
case "mins":
temporalUnit = ChronoUnit.MINUTES;
break;
default:
case "secs":
temporalUnit = ChronoUnit.SECONDS;
}
return Duration.of(amount, temporalUnit).toMillis();
}
}

View File

@ -0,0 +1,259 @@
package awais.instagrabber.services;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.lifecycle.LifecycleService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.db.datasources.DMLastNotifiedDataSource;
import awais.instagrabber.db.entities.DMLastNotified;
import awais.instagrabber.db.repositories.DMLastNotifiedRepository;
import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.fragments.settings.PreferenceKeys;
import awais.instagrabber.managers.DirectMessagesManager;
import awais.instagrabber.managers.InboxManager;
import awais.instagrabber.models.Resource;
import awais.instagrabber.repositories.responses.directmessages.DirectInbox;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadLastSeenAt;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DMUtils;
import awais.instagrabber.utils.DateUtils;
import awais.instagrabber.utils.TextUtils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class DMSyncService extends LifecycleService {
private static final String TAG = DMSyncService.class.getSimpleName();
private InboxManager inboxManager;
private DMLastNotifiedRepository dmLastNotifiedRepository;
private Map<String, DMLastNotified> dmLastNotifiedMap;
@Override
public void onCreate() {
super.onCreate();
startForeground(Constants.DM_CHECK_NOTIFICATION_ID, buildForegroundNotification());
Log.d(TAG, "onCreate: Service created");
final DirectMessagesManager directMessagesManager = DirectMessagesManager.getInstance();
inboxManager = directMessagesManager.getInboxManager();
dmLastNotifiedRepository = DMLastNotifiedRepository.getInstance(DMLastNotifiedDataSource.getInstance(getApplicationContext()));
}
private void parseUnread(@NonNull final DirectInbox directInbox) {
dmLastNotifiedRepository.getAllDMDmLastNotified(new RepositoryCallback<List<DMLastNotified>>() {
@Override
public void onSuccess(final List<DMLastNotified> result) {
dmLastNotifiedMap = result != null
? result.stream().collect(Collectors.toMap(DMLastNotified::getThreadId, Function.identity()))
: Collections.emptyMap();
parseUnreadActual(directInbox);
}
@Override
public void onDataNotAvailable() {
dmLastNotifiedMap = Collections.emptyMap();
parseUnreadActual(directInbox);
}
});
// Log.d(TAG, "inbox observer: " + directInbox);
}
private void parseUnreadActual(@NonNull final DirectInbox directInbox) {
final List<DirectThread> threads = directInbox.getThreads();
final ImmutableMap.Builder<String, List<DirectItem>> unreadMessagesMapBuilder = ImmutableMap.builder();
if (threads == null) {
stopSelf();
return;
}
for (final DirectThread thread : threads) {
if (thread.isMuted()) continue;
final boolean read = DMUtils.isRead(thread);
if (read) continue;
final List<DirectItem> unreadMessages = getUnreadMessages(thread);
if (unreadMessages.isEmpty()) continue;
unreadMessagesMapBuilder.put(thread.getThreadId(), unreadMessages);
}
final Map<String, List<DirectItem>> unreadMessagesMap = unreadMessagesMapBuilder.build();
if (unreadMessagesMap.isEmpty()) {
stopSelf();
return;
}
showNotification(directInbox, unreadMessagesMap);
final LocalDateTime now = LocalDateTime.now();
// Update db
final ImmutableList.Builder<DMLastNotified> lastNotifiedListBuilder = ImmutableList.builder();
for (final Map.Entry<String, List<DirectItem>> unreadMessagesEntry : unreadMessagesMap.entrySet()) {
final List<DirectItem> unreadItems = unreadMessagesEntry.getValue();
final DirectItem latestItem = unreadItems.get(unreadItems.size() - 1);
lastNotifiedListBuilder.add(new DMLastNotified(0,
unreadMessagesEntry.getKey(),
latestItem.getLocalDateTime(),
now));
}
dmLastNotifiedRepository.insertOrUpdateDMLastNotified(
lastNotifiedListBuilder.build(),
new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {
stopSelf();
}
@Override
public void onDataNotAvailable() {
stopSelf();
}
}
);
}
@NonNull
private List<DirectItem> getUnreadMessages(@NonNull final DirectThread thread) {
final List<DirectItem> items = thread.getItems();
if (items == null) return Collections.emptyList();
final DMLastNotified dmLastNotified = dmLastNotifiedMap.get(thread.getThreadId());
final long viewerId = thread.getViewerId();
final Map<Long, DirectThreadLastSeenAt> lastSeenAt = thread.getLastSeenAt();
final ImmutableList.Builder<DirectItem> unreadListBuilder = ImmutableList.builder();
int count = 0;
for (final DirectItem item : items) {
if (item == null) continue;
if (item.getUserId() == viewerId) break; // Reached a message from the viewer, it is assumed the viewer has read the next messages
final boolean read = DMUtils.isRead(item, lastSeenAt, Collections.singletonList(viewerId));
if (read) break;
if (dmLastNotified != null && dmLastNotified.getLastNotifiedMsgTs() != null) {
if (count == 0 && DateUtils.isBeforeOrEqual(item.getLocalDateTime(), dmLastNotified.getLastNotifiedMsgTs())) {
// The first unread item has been notified and hence all subsequent items can be ignored
// since the items are in desc timestamp order
break;
}
}
unreadListBuilder.add(item);
count++;
// Inbox style notification only allows 6 lines
if (count >= 6) break;
}
// Reversing, so that oldest messages are on top
return unreadListBuilder.build().reverse();
}
private void showNotification(final DirectInbox directInbox,
final Map<String, List<DirectItem>> unreadMessagesMap) {
final NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null) return;
for (final Map.Entry<String, List<DirectItem>> unreadMessagesEntry : unreadMessagesMap.entrySet()) {
final Optional<DirectThread> directThreadOptional = getThread(directInbox, unreadMessagesEntry.getKey());
if (!directThreadOptional.isPresent()) continue;
final DirectThread thread = directThreadOptional.get();
final DirectItem firstDirectItem = thread.getFirstDirectItem();
if (firstDirectItem == null) continue;
final List<DirectItem> unreadMessages = unreadMessagesEntry.getValue();
final NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
inboxStyle.setBigContentTitle(thread.getThreadTitle());
for (final DirectItem item : unreadMessages) {
inboxStyle.addLine(DMUtils.getMessageString(thread, getResources(), thread.getViewerId(), item));
}
final Notification notification = new NotificationCompat.Builder(this, Constants.DM_UNREAD_CHANNEL_ID)
.setStyle(inboxStyle)
.setSmallIcon(R.drawable.ic_round_mode_comment_24)
.setContentTitle(thread.getThreadTitle())
.setContentText(DMUtils.getMessageString(thread, getResources(), thread.getViewerId(), firstDirectItem))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setGroup(Constants.GROUP_KEY_DM)
.setAutoCancel(true)
.setContentIntent(getThreadPendingIntent(thread.getThreadId(), thread.getThreadTitle()))
.build();
notificationManager.notify(Constants.DM_UNREAD_PARENT_NOTIFICATION_ID, notification);
}
}
private Optional<DirectThread> getThread(@NonNull final DirectInbox directInbox, final String threadId) {
return directInbox.getThreads()
.stream()
.filter(thread -> Objects.equals(thread.getThreadId(), threadId))
.findFirst();
}
@NonNull
private PendingIntent getThreadPendingIntent(final String threadId, final String threadTitle) {
final Intent intent = new Intent(getApplicationContext(), MainActivity.class)
.setAction(Constants.ACTION_SHOW_DM_THREAD)
.putExtra(Constants.DM_THREAD_ACTION_EXTRA_THREAD_ID, threadId)
.putExtra(Constants.DM_THREAD_ACTION_EXTRA_THREAD_TITLE, threadTitle)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
return PendingIntent.getActivity(getApplicationContext(), Constants.SHOW_DM_THREAD, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
@Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
super.onStartCommand(intent, flags, startId);
final String cookie = settingsHelper.getString(Constants.COOKIE);
final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != 0;
if (!isLoggedIn) {
stopSelf();
return START_NOT_STICKY;
}
// Need to setup here if service was started by the boot completed receiver
CookieUtils.setupCookies(cookie);
final boolean notificationsEnabled = settingsHelper.getBoolean(PreferenceKeys.PREF_ENABLE_DM_NOTIFICATIONS);
inboxManager.getInbox().observe(this, inboxResource -> {
if (!notificationsEnabled || inboxResource == null || inboxResource.status != Resource.Status.SUCCESS) {
stopSelf();
return;
}
final DirectInbox directInbox = inboxResource.data;
if (directInbox == null) {
stopSelf();
return;
}
parseUnread(directInbox);
});
Log.d(TAG, "onStartCommand: refreshing inbox");
inboxManager.refresh();
return START_NOT_STICKY;
}
@Override
public IBinder onBind(@NonNull final Intent intent) {
super.onBind(intent);
return null;
}
private Notification buildForegroundNotification() {
final Resources resources = getResources();
return new NotificationCompat.Builder(this, Constants.SILENT_NOTIFICATIONS_CHANNEL_ID)
.setOngoing(true)
.setSound(null)
.setContentTitle(resources.getString(R.string.app_name))
.setContentText(resources.getString(R.string.checking_for_new_messages))
.setSmallIcon(R.mipmap.ic_launcher)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setGroup(Constants.GROUP_KEY_SILENT_NOTIFICATIONS)
.build();
}
}

View File

@ -7,10 +7,10 @@ import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;
import android.util.LruCache;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;

View File

@ -58,6 +58,8 @@ public final class Constants {
// Notification ids
public static final int ACTIVITY_NOTIFICATION_ID = 10;
public static final int DM_UNREAD_PARENT_NOTIFICATION_ID = 20;
public static final int DM_CHECK_NOTIFICATION_ID = 11;
// see https://github.com/dilame/instagram-private-api/blob/master/src/core/constants.ts
public static final String SUPPORTED_CAPABILITIES = "[ { \"name\": \"SUPPORTED_SDK_VERSIONS\", \"value\":" +
@ -74,12 +76,6 @@ public final class Constants {
public static final String FDROID_SHA1_FINGERPRINT = "C1661EB8FD09F618307E687786D5E5056F65084D";
public static final String SKIPPED_VERSION = "skipped_version";
public static final String DEFAULT_TAB = "default_tab";
public static final String ACTIVITY_CHANNEL_ID = "activity";
public static final String DOWNLOAD_CHANNEL_ID = "download";
public static final String ACTIVITY_CHANNEL_NAME = "Activity";
public static final String DOWNLOAD_CHANNEL_NAME = "Downloads";
public static final String NOTIF_GROUP_NAME = "awais.instagrabber.InstaNotif";
public static final String ACTION_SHOW_ACTIVITY = "show_activity";
public static final String PREF_DARK_THEME = "dark_theme";
public static final String PREF_LIGHT_THEME = "light_theme";
public static final String DEFAULT_HASH_TAG_PIC = "https://www.instagram.com/static/images/hashtag/search-hashtag-default-avatar.png/1d8417c9a4f5.png";
@ -94,4 +90,27 @@ public final class Constants {
public static final String PREF_SAVED_POSTS_LAYOUT = "saved_posts_layout";
public static final String PREF_EMOJI_VARIANTS = "emoji_variants";
public static final String PREF_REACTIONS = "reactions";
public static final String ACTIVITY_CHANNEL_ID = "activity";
public static final String ACTIVITY_CHANNEL_NAME = "Activity";
public static final String DOWNLOAD_CHANNEL_ID = "download";
public static final String DOWNLOAD_CHANNEL_NAME = "Downloads";
public static final String DM_UNREAD_CHANNEL_ID = "dmUnread";
public static final String DM_UNREAD_CHANNEL_NAME = "Messages";
public static final String SILENT_NOTIFICATIONS_CHANNEL_ID = "silentNotifications";
public static final String SILENT_NOTIFICATIONS_CHANNEL_NAME = "Silent notifications";
public static final String NOTIF_GROUP_NAME = "awais.instagrabber.InstaNotif";
public static final String GROUP_KEY_DM = "awais.instagrabber.MESSAGES";
public static final String GROUP_KEY_SILENT_NOTIFICATIONS = "awais.instagrabber.SILENT_NOTIFICATIONS";
public static final int SHOW_ACTIVITY_REQUEST_CODE = 1738;
public static final int SHOW_DM_THREAD = 2000;
public static final int DM_SYNC_SERVICE_REQUEST_CODE = 3000;
public static final String ACTION_SHOW_ACTIVITY = "show_activity";
public static final String ACTION_SHOW_DM_THREAD = "show_dm_thread";
public static final String DM_THREAD_ACTION_EXTRA_THREAD_ID = "thread_id";
public static final String DM_THREAD_ACTION_EXTRA_THREAD_TITLE = "thread_title";
}

View File

@ -0,0 +1,285 @@
package awais.instagrabber.utils;
import android.content.res.Resources;
import androidx.annotation.NonNull;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import awais.instagrabber.R;
import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemAnimatedMedia;
import awais.instagrabber.repositories.responses.directmessages.DirectItemReelShare;
import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadLastSeenAt;
import awais.instagrabber.repositories.responses.directmessages.RavenExpiringMediaActionSummary;
public final class DMUtils {
public static boolean isRead(final DirectItem item,
@NonNull final Map<Long, DirectThreadLastSeenAt> lastSeenAt,
final List<Long> userIdsToCheck) {
// Further check if directStory exists
// if (read && directStory != null) {
// read = false;
// }
return lastSeenAt.entrySet()
.stream()
.filter(entry -> userIdsToCheck.contains(entry.getKey()))
.anyMatch(entry -> {
final String userLastSeenTsString = entry.getValue().getTimestamp();
if (userLastSeenTsString == null) return false;
final long userTs = Long.parseLong(userLastSeenTsString);
final long itemTs = item.getTimestamp();
return userTs >= itemTs;
});
}
public static boolean isRead(@NonNull final DirectThread thread) {
final boolean read;
if (thread.getDirectStory() != null) {
return false;
}
final DirectItem item = thread.getFirstDirectItem();
final long viewerId = thread.getViewerId();
if (item != null && item.getUserId() == viewerId) {
// if last item was sent by user, then it is read (even though we have auto read unchecked?)
read = true;
} else {
final Map<Long, DirectThreadLastSeenAt> lastSeenAtMap = thread.getLastSeenAt();
read = isRead(item, lastSeenAtMap, Collections.singletonList(viewerId));
}
return read;
}
public static String getMessageString(@NonNull final DirectThread thread,
final Resources resources,
final long viewerId,
final DirectItem item) {
final long senderId = item.getUserId();
final DirectItemType itemType = item.getItemType();
String subtitle = null;
final String username = getUsername(thread.getUsers(), senderId, viewerId, resources);
String message = "";
if (itemType == null) {
message = resources.getString(R.string.dms_inbox_raven_message_unknown);
} else {
switch (itemType) {
case TEXT:
message = item.getText();
break;
case LIKE:
message = item.getLike();
break;
case LINK:
message = item.getLink().getText();
break;
case PLACEHOLDER:
message = item.getPlaceholder().getMessage();
break;
case MEDIA_SHARE:
final User mediaShareUser = item.getMediaShare().getUser();
subtitle = resources.getString(R.string.dms_inbox_shared_post,
username != null ? username : "",
mediaShareUser == null ? "" : mediaShareUser.getUsername());
break;
case ANIMATED_MEDIA:
final DirectItemAnimatedMedia animatedMedia = item.getAnimatedMedia();
subtitle = resources.getString(animatedMedia.isSticker() ? R.string.dms_inbox_shared_sticker
: R.string.dms_inbox_shared_gif,
username != null ? username : "");
break;
case PROFILE:
subtitle = resources
.getString(R.string.dms_inbox_shared_profile, username != null ? username : "", item.getProfile().getUsername());
break;
case LOCATION:
subtitle = resources
.getString(R.string.dms_inbox_shared_location, username != null ? username : "", item.getLocation().getName());
break;
case MEDIA: {
final MediaItemType mediaType = item.getMedia().getMediaType();
subtitle = getMediaSpecificSubtitle(username, resources, mediaType);
break;
}
case STORY_SHARE: {
final String reelType = item.getStoryShare().getReelType();
if (reelType == null) {
subtitle = item.getStoryShare().getTitle();
} else {
final int format = reelType.equals("highlight_reel")
? R.string.dms_inbox_shared_highlight
: R.string.dms_inbox_shared_story;
final User storyShareMediaUser = item.getStoryShare().getMedia().getUser();
subtitle = resources.getString(format,
username != null ? username : "",
storyShareMediaUser == null ? "" : storyShareMediaUser.getUsername());
}
break;
}
case VOICE_MEDIA:
subtitle = resources.getString(R.string.dms_inbox_shared_voice, username != null ? username : "");
break;
case ACTION_LOG:
subtitle = item.getActionLog().getDescription();
break;
case VIDEO_CALL_EVENT:
subtitle = item.getVideoCallEvent().getDescription();
break;
case CLIP:
final User clipUser = item.getClip().getClip().getUser();
subtitle = resources.getString(R.string.dms_inbox_shared_clip,
username != null ? username : "",
clipUser == null ? "" : clipUser.getUsername());
break;
case FELIX_SHARE:
final User felixShareVideoUser = item.getFelixShare().getVideo().getUser();
subtitle = resources.getString(R.string.dms_inbox_shared_igtv,
username != null ? username : "",
felixShareVideoUser == null ? "" : felixShareVideoUser.getUsername());
break;
case RAVEN_MEDIA:
subtitle = getRavenMediaSubtitle(item, resources, username);
break;
case REEL_SHARE:
final DirectItemReelShare reelShare = item.getReelShare();
if (reelShare == null) {
subtitle = "";
break;
}
final String reelType = reelShare.getType();
switch (reelType) {
case "reply":
if (viewerId == item.getUserId()) {
subtitle = resources.getString(R.string.dms_inbox_replied_story_outgoing, reelShare.getText());
} else {
subtitle = resources
.getString(R.string.dms_inbox_replied_story_incoming, username != null ? username : "", reelShare.getText());
}
break;
case "mention":
if (viewerId == item.getUserId()) {
// You mentioned the other person
final long mentionedUserId = item.getReelShare().getMentionedUserId();
final String otherUsername = getUsername(thread.getUsers(), mentionedUserId, viewerId, resources);
subtitle = resources.getString(R.string.dms_inbox_mentioned_story_outgoing, otherUsername);
} else {
// They mentioned you
subtitle = resources.getString(R.string.dms_inbox_mentioned_story_incoming, username != null ? username : "");
}
break;
case "reaction":
if (viewerId == item.getUserId()) {
subtitle = resources.getString(R.string.dms_inbox_reacted_story_outgoing, reelShare.getText());
} else {
subtitle = resources
.getString(R.string.dms_inbox_reacted_story_incoming, username != null ? username : "", reelShare.getText());
}
break;
default:
subtitle = "";
break;
}
break;
default:
message = resources.getString(R.string.dms_inbox_raven_message_unknown);
}
}
if (subtitle == null) {
if (thread.isGroup() || (!thread.isGroup() && senderId == viewerId)) {
subtitle = String.format("%s: %s", username != null ? username : "", message);
} else {
subtitle = message;
}
}
return subtitle;
}
public static String getUsername(final List<User> users,
final long userId,
final long viewerId,
final Resources resources) {
if (userId == viewerId) {
return resources.getString(R.string.you);
}
final Optional<User> senderOptional = users.stream()
.filter(Objects::nonNull)
.filter(user -> user.getPk() == userId)
.findFirst();
return senderOptional.map(User::getUsername).orElse(null);
}
public static String getMediaSpecificSubtitle(final String username, final Resources resources, final MediaItemType mediaType) {
final String userSharedAnImage = resources.getString(R.string.dms_inbox_shared_image, username != null ? username : "");
final String userSharedAVideo = resources.getString(R.string.dms_inbox_shared_video, username != null ? username : "");
final String userSentAMessage = resources.getString(R.string.dms_inbox_shared_message, username != null ? username : "");
String subtitle;
switch (mediaType) {
case MEDIA_TYPE_IMAGE:
subtitle = userSharedAnImage;
break;
case MEDIA_TYPE_VIDEO:
subtitle = userSharedAVideo;
break;
default:
subtitle = userSentAMessage;
break;
}
return subtitle;
}
private static String getRavenMediaSubtitle(final DirectItem item,
final Resources resources,
final String username) {
String subtitle = "";
final DirectItemVisualMedia visualMedia = item.getVisualMedia();
final RavenExpiringMediaActionSummary summary = visualMedia.getExpiringMediaActionSummary();
if (summary != null) {
final RavenExpiringMediaActionSummary.ActionType expiringMediaType = summary.getType();
int textRes = 0;
switch (expiringMediaType) {
case DELIVERED:
textRes = R.string.dms_inbox_raven_media_delivered;
break;
case SENT:
textRes = R.string.dms_inbox_raven_media_sent;
break;
case OPENED:
textRes = R.string.dms_inbox_raven_media_opened;
break;
case REPLAYED:
textRes = R.string.dms_inbox_raven_media_replayed;
break;
case SENDING:
textRes = R.string.dms_inbox_raven_media_sending;
break;
case BLOCKED:
textRes = R.string.dms_inbox_raven_media_blocked;
break;
case SUGGESTED:
textRes = R.string.dms_inbox_raven_media_suggested;
break;
case SCREENSHOT:
textRes = R.string.dms_inbox_raven_media_screenshot;
break;
case CANNOT_DELIVER:
textRes = R.string.dms_inbox_raven_media_cant_deliver;
break;
}
if (textRes > 0) {
subtitle += resources.getString(textRes);
}
return subtitle;
}
final MediaItemType mediaType = visualMedia.getMedia().getMediaType();
subtitle = getMediaSpecificSubtitle(username, resources, mediaType);
return subtitle;
}
}

View File

@ -2,6 +2,7 @@ package awais.instagrabber.utils;
import androidx.annotation.NonNull;
import java.time.LocalDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
@ -34,4 +35,8 @@ public final class DateUtils {
final Calendar calendar = Calendar.getInstance(Locale.getDefault());
return -(calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / (60 * 1000);
}
public static boolean isBeforeOrEqual(final LocalDateTime localDateTime, final LocalDateTime comparedTo) {
return localDateTime.isBefore(comparedTo) || localDateTime.isEqual(comparedTo);
}
}

View File

@ -8,13 +8,16 @@ import java.util.UUID;
import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.responses.AnimatedMediaImages;
import awais.instagrabber.repositories.responses.Audio;
import awais.instagrabber.repositories.responses.ImageVersions2;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.MediaCandidate;
import awais.instagrabber.repositories.responses.VideoVersion;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemAnimatedMedia;
import awais.instagrabber.repositories.responses.directmessages.DirectItemVoiceMedia;
import awais.instagrabber.repositories.responses.giphy.GiphyGif;
public final class DirectItemFactory {
@ -213,4 +216,45 @@ public final class DirectItemFactory {
0,
false);
}
public static DirectItem createAnimatedMedia(final long userId,
final String clientContext,
final GiphyGif giphyGif) {
final AnimatedMediaImages animatedImages = new AnimatedMediaImages(giphyGif.getImages().getFixedHeight());
final DirectItemAnimatedMedia animateMedia = new DirectItemAnimatedMedia(
giphyGif.getId(),
animatedImages,
false,
giphyGif.isSticker()
);
return new DirectItem(
UUID.randomUUID().toString(),
userId,
System.currentTimeMillis() * 1000,
DirectItemType.ANIMATED_MEDIA,
null,
null,
null,
clientContext,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
animateMedia,
null,
null,
null,
null,
0,
false
);
}
}

View File

@ -20,7 +20,6 @@ import androidx.appcompat.app.AppCompatActivity;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;
import java.util.concurrent.ThreadLocalRandom;
import javax.security.cert.CertificateException;
@ -106,11 +105,11 @@ public final class FlavorTown {
if (settingsHelper.getInteger(Constants.PREV_INSTALL_VERSION) < BuildConfig.VERSION_CODE) {
int appUaCode = settingsHelper.getInteger(Constants.APP_UA_CODE);
int browserUaCode = settingsHelper.getInteger(Constants.BROWSER_UA_CODE);
if (browserUaCode == -1) {
if (browserUaCode == -1 || browserUaCode >= UserAgentUtils.browsers.length) {
browserUaCode = ThreadLocalRandom.current().nextInt(0, UserAgentUtils.browsers.length);
settingsHelper.putInteger(Constants.BROWSER_UA_CODE, browserUaCode);
}
if (appUaCode == -1) {
if (appUaCode == -1 || appUaCode >= UserAgentUtils.devices.length) {
appUaCode = ThreadLocalRandom.current().nextInt(0, UserAgentUtils.devices.length);
settingsHelper.putInteger(Constants.APP_UA_CODE, appUaCode);
}

View File

@ -13,7 +13,6 @@ import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.models.StoryModel;
@ -31,9 +30,6 @@ import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.MediaCandidate;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.VideoVersion;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDirectStory;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadLastSeenAt;
import awaisomereport.LogCollector;
public final class ResponseBodyUtils {
@ -1125,36 +1121,15 @@ public final class ResponseBodyUtils {
return candidate.getUrl();
}
public static boolean isRead(final DirectItem item,
final Map<Long, DirectThreadLastSeenAt> lastSeenAt,
final List<Long> userIdsToCheck,
final DirectThreadDirectStory directStory) {
boolean read = lastSeenAt.entrySet()
.stream()
.filter(entry -> userIdsToCheck.contains(entry.getKey()))
.anyMatch(entry -> {
final String userLastSeenTsString = entry.getValue().getTimestamp();
if (userLastSeenTsString == null) return false;
final long userTs = Long.parseLong(userLastSeenTsString);
final long itemTs = item.getTimestamp();
return userTs >= itemTs;
});
// Further check if directStory exists
if (read && directStory != null) {
read = false;
}
return read;
}
public static StoryModel parseBroadcastItem(final JSONObject data) throws JSONException {
final StoryModel model = new StoryModel(data.getString("id"),
data.getString("cover_frame_url"),
data.getString("cover_frame_url"),
MediaItemType.MEDIA_TYPE_LIVE,
data.optLong("published_time", 0),
data.getJSONObject("broadcast_owner").getString("username"),
data.getJSONObject("broadcast_owner").getLong("pk"),
false);
data.getString("cover_frame_url"),
data.getString("cover_frame_url"),
MediaItemType.MEDIA_TYPE_LIVE,
data.optLong("published_time", 0),
data.getJSONObject("broadcast_owner").getString("username"),
data.getJSONObject("broadcast_owner").getLong("pk"),
false);
model.setVideoUrl(data.getString("dash_playback_url"));
return model;
}

View File

@ -8,6 +8,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.StringDef;
import androidx.appcompat.app.AppCompatDelegate;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_NOTIFICATIONS;
import static awais.instagrabber.utils.Constants.APP_LANGUAGE;
import static awais.instagrabber.utils.Constants.APP_THEME;
import static awais.instagrabber.utils.Constants.APP_UA;
@ -130,14 +134,14 @@ public final class SettingsHelper {
CUSTOM_DATE_TIME_FORMAT, DEVICE_UUID, SKIPPED_VERSION, DEFAULT_TAB, PREF_DARK_THEME, PREF_LIGHT_THEME,
PREF_POSTS_LAYOUT, PREF_PROFILE_POSTS_LAYOUT, PREF_TOPIC_POSTS_LAYOUT, PREF_HASHTAG_POSTS_LAYOUT,
PREF_LOCATION_POSTS_LAYOUT, PREF_LIKED_POSTS_LAYOUT, PREF_TAGGED_POSTS_LAYOUT, PREF_SAVED_POSTS_LAYOUT,
STORY_SORT, PREF_EMOJI_VARIANTS, PREF_REACTIONS})
STORY_SORT, PREF_EMOJI_VARIANTS, PREF_REACTIONS, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT})
public @interface StringSettings {}
@StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS,
SHOW_CAPTIONS, CUSTOM_DATE_TIME_FORMAT_ENABLED, MARK_AS_SEEN, DM_MARK_AS_SEEN, CHECK_ACTIVITY,
CHECK_UPDATES, SWAP_DATE_TIME_FORMAT_ENABLED})
CHECK_UPDATES, SWAP_DATE_TIME_FORMAT_ENABLED, PREF_ENABLE_DM_NOTIFICATIONS, PREF_ENABLE_DM_AUTO_REFRESH})
public @interface BooleanSettings {}
@StringDef({PREV_INSTALL_VERSION, BROWSER_UA_CODE, APP_UA_CODE})
@StringDef({PREV_INSTALL_VERSION, BROWSER_UA_CODE, APP_UA_CODE, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER})
public @interface IntegerSettings {}
}

View File

@ -10,6 +10,7 @@ import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.media.MediaScannerConnection;
import android.media.MediaScannerConnection.OnScanCompletedListener;
import android.net.Uri;
@ -28,10 +29,13 @@ import android.view.inputmethod.InputMethodManager;
import android.webkit.MimeTypeMap;
import android.widget.Toast;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
import com.google.android.exoplayer2.database.ExoDatabaseProvider;
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
@ -347,4 +351,15 @@ public final class Utils {
Log.e(TAG, "hideKeyboard: ", e);
}
}
public static Drawable getAnimatableDrawable(@NonNull final Context context,
@DrawableRes final int drawableResId) {
final Drawable drawable;
if (Build.VERSION.SDK_INT >= 24) {
drawable = ContextCompat.getDrawable(context, drawableResId);
} else {
drawable = AnimatedVectorDrawableCompat.create(context, drawableResId);
}
return drawable;
}
}

Some files were not shown because too many files have changed in this diff Show More