Centralised syncing of inbox.

This commit is contained in:
Ammar Githam 2021-02-28 00:59:55 +09:00
parent ea7236dcc1
commit a7a595f8d4
63 changed files with 3559 additions and 2006 deletions

View File

@ -39,8 +39,8 @@ android {
buildTypes {
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
// minifyEnabled true
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
release {
@ -62,7 +62,7 @@ dependencies {
def nav_version = '2.3.2'
def exoplayer_version = '2.12.0'
implementation 'com.google.android.material:material:1.3.0-beta01'
implementation 'com.google.android.material:material:1.3.0-rc01'
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version"

View File

@ -38,6 +38,7 @@ import androidx.emoji.text.FontRequestEmojiCompatConfig;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
@ -75,6 +76,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 +104,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
private int firstFragmentGraphIndex;
private boolean isActivityCheckerServiceBound = false;
private boolean isBackStackEmpty = false;
private boolean isLoggedIn;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
@ -131,6 +134,7 @@ 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);
@ -142,6 +146,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)) {
@ -387,8 +392,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();

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

@ -349,9 +349,19 @@ 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;
if (thread.getDirectStory() != null) {
read = false;
} else {
final DirectItem item = thread.getFirstDirectItem();
if (item.getUserId() == thread.getViewerId()) {
// 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 = ResponseBodyUtils.isRead(item, lastSeenAtMap, Collections.singletonList(thread.getViewerId()));
}
}
binding.unread.setVisibility(read ? View.GONE : View.VISIBLE);
binding.threadTitle.setTypeface(binding.threadTitle.getTypeface(), read ? Typeface.NORMAL : Typeface.BOLD);
binding.subtitle.setTypeface(binding.subtitle.getTypeface(), read ? Typeface.NORMAL : Typeface.BOLD);

View File

@ -196,7 +196,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 = ResponseBodyUtils.isRead(item,
thread.getLastSeenAt(),
userIds
);
binding.deliveryStatus.setImageResource(R.drawable.ic_check_all_24);
ImageViewCompat.setImageTintList(
binding.deliveryStatus,
@ -358,7 +361,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

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

@ -37,7 +37,6 @@ 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 +57,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;
@ -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;
@ -235,7 +232,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));
}
@ -295,13 +292,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);
}
@ -332,7 +345,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
init();
binding.send.post(() -> initialSendX = binding.send.getX());
shouldRefresh = false;
setObservers();
}
@Override
@ -406,6 +418,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
wasKbShowing = true;
binding.emojiPicker.setAlpha(0);
}
removeObservers();
super.onPause();
}
@ -423,7 +436,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
}
binding.send.stopScale();
setupBackStackResultObserver();
attachPendingRequestsBadge(viewModel.getPendingRequestsCount().getValue());
setObservers();
// attachPendingRequestsBadge(viewModel.getPendingRequestsCount().getValue());
}
@Override
@ -463,42 +477,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();
@ -542,7 +552,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;
@ -556,7 +573,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;
@ -564,21 +582,34 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
hideInput();
}
});
viewModel.getThreadTitle().observe(getViewLifecycleOwner(), this::setTitle);
viewModel.getFetching().observe(getViewLifecycleOwner(), fetching -> {
if (fetching) {
setTitle(UPDATING_TITLE);
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(UPDATING_TITLE);
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);
@ -633,14 +664,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);
@ -669,9 +716,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;
@ -838,12 +891,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);
@ -958,7 +1014,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);
@ -1031,8 +1087,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) {
@ -1380,10 +1436,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();
@ -1410,7 +1462,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

@ -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,358 @@
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();
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();
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

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

@ -207,17 +207,40 @@ public class User implements Serializable {
// null);
// }
@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

@ -4,6 +4,7 @@ import androidx.annotation.NonNull;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.repositories.responses.Location;
@ -239,21 +240,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

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

@ -32,7 +32,6 @@ 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;
@ -1126,34 +1125,32 @@ public final class ResponseBodyUtils {
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;
});
final List<Long> userIdsToCheck) {
// Further check if directStory exists
if (read && directStory != null) {
read = false;
}
return read;
// 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 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

@ -5,8 +5,6 @@ import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.asyncs.UsernameFetcher;
@ -25,10 +23,10 @@ import static awais.instagrabber.utils.Utils.settingsHelper;
public class AppStateViewModel extends AndroidViewModel {
private static final String TAG = AppStateViewModel.class.getSimpleName();
private final MutableLiveData<User> currentUser = new MutableLiveData<>();
private final String cookie;
private final boolean isLoggedIn;
private User currentUser;
private AccountRepository accountRepository;
private String username;
@ -52,7 +50,7 @@ public class AppStateViewModel extends AndroidViewModel {
fetchUsername(usernameListener);
}
public LiveData<User> getCurrentUser() {
public User getCurrentUser() {
return currentUser;
}
@ -84,7 +82,7 @@ public class AppStateViewModel extends AndroidViewModel {
new ProfileFetcher(
username.trim().substring(1),
true,
currentUser::postValue
user -> this.currentUser = user
).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}

View File

@ -1,188 +1,56 @@
package awais.instagrabber.viewmodels;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import awais.instagrabber.managers.DirectMessagesManager;
import awais.instagrabber.managers.InboxManager;
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.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 awais.instagrabber.utils.Utils.settingsHelper;
public class DirectInboxViewModel extends ViewModel {
private static final String TAG = DirectInboxViewModel.class.getSimpleName();
private final DirectMessagesService service;
private final MutableLiveData<Boolean> fetchingInbox = new MutableLiveData<>(false);
private final MutableLiveData<List<DirectThread>> threads = new MutableLiveData<>();
private final MutableLiveData<Boolean> fetchingUnseenCount = new MutableLiveData<>(false);
private final MutableLiveData<Integer> unseenCount = new MutableLiveData<>(0);
private final MutableLiveData<Integer> pendingRequestsTotal = new MutableLiveData<>(0);
private Call<DirectInboxResponse> inboxRequest;
private Call<DirectBadgeCount> unseenCountRequest;
private long seqId;
private String cursor;
private boolean hasOlder = true;
private User viewer;
private final InboxManager inboxManager;
public DirectInboxViewModel() {
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);
fetchInbox();
fetchUnseenCount();
final DirectMessagesManager messagesManager = DirectMessagesManager.getInstance();
inboxManager = messagesManager.getInboxManager();
}
public LiveData<Resource<DirectInbox>> getInbox() {
return inboxManager.getInbox();
}
public LiveData<List<DirectThread>> getThreads() {
return threads;
return inboxManager.getThreads();
}
public void setThreads(final List<DirectThread> threads) {
this.threads.postValue(threads);
}
public void addThreads(final Collection<DirectThread> threads) {
if (threads == null) return;
List<DirectThread> list = getThreads().getValue();
list = list == null ? new LinkedList<>() : new LinkedList<>(list);
list.addAll(threads);
this.threads.postValue(list);
}
public LiveData<Integer> getUnseenCount() {
return unseenCount;
}
public LiveData<Boolean> getFetchingInbox() {
return fetchingInbox;
public LiveData<Resource<Integer>> getUnseenCount() {
return inboxManager.getUnseenCount();
}
public LiveData<Integer> getPendingRequestsTotal() {
return pendingRequestsTotal;
return inboxManager.getPendingRequestsTotal();
}
public User getViewer() {
return viewer;
return inboxManager.getViewer();
}
public void fetchInbox() {
if ((fetchingInbox.getValue() != null && fetchingInbox.getValue()) || !hasOlder) return;
stopCurrentInboxRequest();
fetchingInbox.postValue(true);
inboxRequest = 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());
fetchingInbox.postValue(false);
}
@Override
public void onFailure(@NonNull final Call<DirectInboxResponse> call, @NonNull final Throwable t) {
Log.e(TAG, "Failed fetching dm inbox", t);
fetchingInbox.postValue(false);
hasOlder = false;
}
});
}
private void parseInboxResponse(final DirectInboxResponse response) {
if (response == null) {
hasOlder = false;
return;
}
if (!response.getStatus().equals("ok")) {
Log.e(TAG, "DM inbox fetch response: status not ok");
hasOlder = false;
return;
}
seqId = response.getSeqId();
if (viewer == null) {
viewer = response.getViewer();
}
final DirectInbox inbox = response.getInbox();
final List<DirectThread> threads = inbox.getThreads();
if (!TextUtils.isEmpty(cursor)) {
addThreads(threads);
} else {
setThreads(threads);
}
cursor = inbox.getOldestCursor();
hasOlder = inbox.hasOlder();
pendingRequestsTotal.postValue(response.getPendingRequestsTotal());
// unseenCount.postValue(inbox.getUnseenCount());
}
private void stopCurrentInboxRequest() {
if (inboxRequest == null || inboxRequest.isCanceled() || inboxRequest.isExecuted()) return;
inboxRequest.cancel();
inboxRequest = null;
}
public void fetchUnseenCount() {
if ((fetchingUnseenCount.getValue() != null && fetchingUnseenCount.getValue())) return;
stopCurrentUnseenCountRequest();
fetchingUnseenCount.postValue(true);
unseenCountRequest = service.fetchUnseenCount();
unseenCountRequest.enqueue(new Callback<DirectBadgeCount>() {
@Override
public void onResponse(@NonNull final Call<DirectBadgeCount> call, @NonNull final Response<DirectBadgeCount> response) {
parseUnseenCountResponse(response.body());
fetchingUnseenCount.postValue(false);
}
@Override
public void onFailure(@NonNull final Call<DirectBadgeCount> call, @NonNull final Throwable t) {
Log.e(TAG, "Failed fetching unseen count", t);
fetchingUnseenCount.postValue(false);
}
});
}
private void parseUnseenCountResponse(final DirectBadgeCount directBadgeCount) {
if (directBadgeCount == null) return;
unseenCount.postValue(directBadgeCount.getBadgeCount());
}
private void stopCurrentUnseenCountRequest() {
if (unseenCountRequest == null || unseenCountRequest.isCanceled() || unseenCountRequest.isExecuted()) return;
unseenCountRequest.cancel();
unseenCountRequest = null;
inboxManager.fetchInbox();
}
public void refresh() {
cursor = null;
seqId = 0;
hasOlder = true;
fetchInbox();
fetchUnseenCount();
inboxManager.refresh();
}
public void onDestroy() {
stopCurrentInboxRequest();
// getThreads().postValue(Collections.emptyList());
inboxManager.onDestroy();
}
}

View File

@ -1,141 +1,48 @@
package awais.instagrabber.viewmodels;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import awais.instagrabber.managers.DirectMessagesManager;
import awais.instagrabber.managers.InboxManager;
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.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 awais.instagrabber.utils.Utils.settingsHelper;
public class DirectPendingInboxViewModel extends ViewModel {
private static final String TAG = DirectPendingInboxViewModel.class.getSimpleName();
private final DirectMessagesService service;
private final MutableLiveData<Boolean> fetchingInbox = new MutableLiveData<>(false);
private final MutableLiveData<List<DirectThread>> threads = new MutableLiveData<>();
private Call<DirectInboxResponse> inboxRequest;
private Call<DirectBadgeCount> unseenCountRequest;
private long seqId;
private String cursor;
private boolean hasOlder = true;
private User viewer;
private final InboxManager inboxManager;
public DirectPendingInboxViewModel() {
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);
fetchInbox();
inboxManager = DirectMessagesManager.getInstance().getPendingInboxManager();
inboxManager.fetchInbox();
}
public LiveData<List<DirectThread>> getThreads() {
return threads;
return inboxManager.getThreads();
}
public void setThreads(final List<DirectThread> threads) {
this.threads.postValue(threads);
}
public void addThreads(final Collection<DirectThread> threads) {
if (threads == null) return;
List<DirectThread> list = getThreads().getValue();
list = list == null ? new LinkedList<>() : new LinkedList<>(list);
list.addAll(threads);
this.threads.postValue(list);
}
public LiveData<Boolean> getFetchingInbox() {
return fetchingInbox;
public LiveData<Resource<DirectInbox>> getInbox() {
return inboxManager.getInbox();
}
public User getViewer() {
return viewer;
return inboxManager.getViewer();
}
public void fetchInbox() {
if ((fetchingInbox.getValue() != null && fetchingInbox.getValue()) || !hasOlder) return;
stopCurrentInboxRequest();
fetchingInbox.postValue(true);
inboxRequest = service.fetchPendingInbox(cursor, seqId);
inboxRequest.enqueue(new Callback<DirectInboxResponse>() {
@Override
public void onResponse(@NonNull final Call<DirectInboxResponse> call, @NonNull final Response<DirectInboxResponse> response) {
parseInboxResponse(response.body());
fetchingInbox.postValue(false);
}
@Override
public void onFailure(@NonNull final Call<DirectInboxResponse> call, @NonNull final Throwable t) {
Log.e(TAG, "Failed fetching pending inbox", t);
fetchingInbox.postValue(false);
hasOlder = false;
}
});
}
private void parseInboxResponse(final DirectInboxResponse response) {
if (response == null) {
hasOlder = false;
return;
}
if (!response.getStatus().equals("ok")) {
Log.e(TAG, "DM pending inbox fetch response: status not ok");
hasOlder = false;
return;
}
seqId = response.getSeqId();
if (viewer == null) {
viewer = response.getViewer();
}
final DirectInbox inbox = response.getInbox();
final List<DirectThread> threads = inbox.getThreads();
if (!TextUtils.isEmpty(cursor)) {
addThreads(threads);
} else {
setThreads(threads);
}
cursor = inbox.getOldestCursor();
hasOlder = inbox.hasOlder();
}
private void stopCurrentInboxRequest() {
if (inboxRequest == null || inboxRequest.isCanceled() || inboxRequest.isExecuted()) return;
inboxRequest.cancel();
inboxRequest = null;
inboxManager.fetchInbox();
}
public void refresh() {
cursor = null;
seqId = 0;
hasOlder = true;
fetchInbox();
inboxManager.refresh();
}
public void onDestroy() {
stopCurrentInboxRequest();
inboxManager.onDestroy();
}
}

View File

@ -1,49 +1,30 @@
package awais.instagrabber.viewmodels;
import android.app.Application;
import android.content.ContentResolver;
import android.content.res.Resources;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.util.Pair;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.google.common.collect.ImmutableList;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import awais.instagrabber.R;
import awais.instagrabber.dialogs.MultiOptionDialogFragment.Option;
import awais.instagrabber.managers.DirectMessagesManager;
import awais.instagrabber.managers.ThreadManager;
import awais.instagrabber.models.Resource;
import awais.instagrabber.repositories.responses.FriendshipChangeResponse;
import awais.instagrabber.repositories.responses.FriendshipRestrictResponse;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDetailsChangeResponse;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadParticipantRequestsResponse;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.webservices.DirectMessagesService;
import awais.instagrabber.webservices.FriendshipService;
import awais.instagrabber.webservices.ServiceCallback;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import static awais.instagrabber.utils.Utils.settingsHelper;
@ -58,573 +39,186 @@ public class DirectSettingsViewModel extends AndroidViewModel {
private static final String ACTION_RESTRICT = "restrict";
private static final String ACTION_UNRESTRICT = "unrestrict";
private final MutableLiveData<Pair<List<User>, List<User>>> users = new MutableLiveData<>(
new Pair<>(Collections.emptyList(), Collections.emptyList()));
private final MutableLiveData<String> title = new MutableLiveData<>("");
private final MutableLiveData<List<Long>> adminUserIds = new MutableLiveData<>(Collections.emptyList());
private final MutableLiveData<Boolean> muted = new MutableLiveData<>(false);
private final MutableLiveData<Boolean> mentionsMuted = new MutableLiveData<>(false);
private final MutableLiveData<Boolean> approvalRequiredToJoin = new MutableLiveData<>(false);
private final MutableLiveData<DirectThreadParticipantRequestsResponse> pendingRequests = new MutableLiveData<>(null);
private final MutableLiveData<Integer> inputMode = new MutableLiveData<>(null);
private final MutableLiveData<Boolean> isPending = new MutableLiveData<>(false);
private final DirectMessagesService directMessagesService;
private final long userId;
private final long viewerId;
private final Resources resources;
private final FriendshipService friendshipService;
private final String csrfToken;
private final ThreadManager threadManager;
private DirectThread thread;
private boolean viewerIsAdmin;
private User viewer;
public DirectSettingsViewModel(final Application application) {
public DirectSettingsViewModel(final Application application,
@NonNull final String threadId,
final boolean pending,
@NonNull final User currentUser) {
super(application);
final String cookie = settingsHelper.getString(Constants.COOKIE);
userId = CookieUtils.getUserIdFromCookie(cookie);
viewerId = CookieUtils.getUserIdFromCookie(cookie);
final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID);
csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
if (TextUtils.isEmpty(csrfToken) || userId <= 0 || TextUtils.isEmpty(deviceUuid)) {
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
if (TextUtils.isEmpty(csrfToken) || viewerId <= 0 || TextUtils.isEmpty(deviceUuid)) {
throw new IllegalArgumentException("User is not logged in!");
}
directMessagesService = DirectMessagesService.getInstance(csrfToken, userId, deviceUuid);
friendshipService = FriendshipService.getInstance(deviceUuid, csrfToken, userId);
final ContentResolver contentResolver = application.getContentResolver();
resources = getApplication().getResources();
final DirectMessagesManager messagesManager = DirectMessagesManager.getInstance();
threadManager = messagesManager.getThreadManager(threadId, pending, currentUser, contentResolver);
}
@NonNull
public DirectThread getThread() {
return thread;
public LiveData<DirectThread> getThread() {
return threadManager.getThread();
}
public void setThread(@NonNull final DirectThread thread) {
this.thread = thread;
inputMode.postValue(thread.getInputMode());
List<User> users = thread.getUsers();
if (viewer != null) {
final ImmutableList.Builder<User> builder = ImmutableList.<User>builder().add(viewer);
if (users != null) {
builder.addAll(users);
}
users = builder.build();
}
this.users.postValue(new Pair<>(users, thread.getLeftUsers()));
setTitle(thread.getThreadTitle());
final List<Long> adminUserIds = thread.getAdminUserIds();
this.adminUserIds.postValue(adminUserIds);
viewerIsAdmin = adminUserIds.contains(userId);
muted.postValue(thread.isMuted());
mentionsMuted.postValue(thread.isMentionsMuted());
approvalRequiredToJoin.postValue(thread.isApprovalRequiredForNewMembers());
isPending.postValue(thread.isPending());
if (thread.getInputMode() != 1 && thread.isGroup() && viewerIsAdmin) {
fetchPendingRequests();
}
}
// public void setThread(@NonNull final DirectThread thread) {
// this.thread = thread;
// inputMode.postValue(thread.getInputMode());
// List<User> users = thread.getUsers();
// final ImmutableList.Builder<User> builder = ImmutableList.<User>builder().add(currentUser);
// if (users != null) {
// builder.addAll(users);
// }
// users = builder.build();
// this.users.postValue(new Pair<>(users, thread.getLeftUsers()));
// // setTitle(thread.getThreadTitle());
// final List<Long> adminUserIds = thread.getAdminUserIds();
// this.adminUserIds.postValue(adminUserIds);
// viewerIsAdmin = adminUserIds.contains(viewerId);
// muted.postValue(thread.isMuted());
// mentionsMuted.postValue(thread.isMentionsMuted());
// approvalRequiredToJoin.postValue(thread.isApprovalRequiredForNewMembers());
// isPending.postValue(thread.isPending());
// if (thread.getInputMode() != 1 && thread.isGroup() && viewerIsAdmin) {
// fetchPendingRequests();
// }
// }
public LiveData<Integer> getInputMode() {
return inputMode;
return threadManager.getInputMode();
}
public boolean isGroup() {
if (thread != null) {
return thread.isGroup();
}
return false;
public LiveData<Boolean> isGroup() {
return threadManager.isGroup();
}
public LiveData<Pair<List<User>, List<User>>> getUsers() {
return users;
public LiveData<List<User>> getUsers() {
return threadManager.getUsersWithCurrent();
}
public LiveData<List<User>> getLeftUsers() {
return threadManager.getLeftUsers();
}
public LiveData<Pair<List<User>, List<User>>> getUsersAndLeftUsers() {
return threadManager.getUsersAndLeftUsers();
}
public LiveData<String> getTitle() {
return title;
return threadManager.getThreadTitle();
}
public void setTitle(final String title) {
if (title == null) {
this.title.postValue("");
return;
}
this.title.postValue(title.trim());
}
// public void setTitle(final String title) {
// if (title == null) {
// this.title.postValue("");
// return;
// }
// this.title.postValue(title.trim());
// }
public LiveData<List<Long>> getAdminUserIds() {
return adminUserIds;
return threadManager.getAdminUserIds();
}
public LiveData<Boolean> getMuted() {
return muted;
public LiveData<Boolean> isMuted() {
return threadManager.isMuted();
}
public LiveData<Boolean> getApprovalRequiredToJoin() {
return approvalRequiredToJoin;
return threadManager.isApprovalRequiredToJoin();
}
public LiveData<DirectThreadParticipantRequestsResponse> getPendingRequests() {
return pendingRequests;
return threadManager.getPendingRequests();
}
public LiveData<Boolean> isPending() {
return isPending;
return threadManager.isPending();
}
public boolean isViewerAdmin() {
return viewerIsAdmin;
public LiveData<Boolean> isViewerAdmin() {
return threadManager.isViewerAdmin();
}
public LiveData<Resource<Object>> updateTitle(final String newTitle) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
final Call<DirectThreadDetailsChangeResponse> addUsersRequest = directMessagesService
.updateTitle(thread.getThreadId(), newTitle.trim());
handleDetailsChangeRequest(data, addUsersRequest);
return data;
return threadManager.updateTitle(newTitle);
}
public LiveData<Resource<Object>> addMembers(final Set<User> users) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
final Call<DirectThreadDetailsChangeResponse> addUsersRequest = directMessagesService
.addUsers(thread.getThreadId(), users.stream().map(User::getPk).collect(Collectors.toList()));
handleDetailsChangeRequest(data, addUsersRequest);
return data;
return threadManager.addMembers(users);
}
public LiveData<Resource<Object>> removeMember(final User user) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
final Call<String> request = directMessagesService
.removeUsers(thread.getThreadId(), Collections.singleton(user.getPk()));
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
if (!response.isSuccessful()) {
handleSettingChangeResponseError(response, data);
return;
}
Pair<List<User>, List<User>> usersValue = users.getValue();
if (usersValue == null) {
usersValue = new Pair<>(Collections.emptyList(), Collections.emptyList());
}
List<User> activeUsers = usersValue.first;
if (activeUsers == null) {
activeUsers = Collections.emptyList();
}
final List<User> updatedActiveUsers = activeUsers.stream()
.filter(user1 -> user1.getPk() != user.getPk())
.collect(Collectors.toList());
List<User> leftUsers = usersValue.second;
if (leftUsers == null) {
leftUsers = Collections.emptyList();
}
final ImmutableList<User> updateLeftUsers = ImmutableList.<User>builder()
.addAll(leftUsers)
.add(user)
.build();
users.postValue(new Pair<>(updatedActiveUsers, updateLeftUsers));
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
Log.e(TAG, "onFailure: ", t);
data.postValue(Resource.error(t.getMessage(), null));
}
});
return data;
return threadManager.removeMember(user);
}
private LiveData<Resource<Object>> makeAdmin(final User user) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
if (isAdmin(user)) return data;
final Call<String> request = directMessagesService.addAdmins(thread.getThreadId(), Collections.singleton(user.getPk()));
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
if (!response.isSuccessful()) {
handleSettingChangeResponseError(response, data);
return;
}
final List<Long> currentAdmins = adminUserIds.getValue();
adminUserIds.postValue(ImmutableList.<Long>builder()
.addAll(currentAdmins != null ? currentAdmins : Collections.emptyList())
.add(user.getPk())
.build());
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
Log.e(TAG, "onFailure: ", t);
data.postValue(Resource.error(t.getMessage(), null));
}
});
return data;
return threadManager.makeAdmin(user);
}
private LiveData<Resource<Object>> removeAdmin(final User user) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
if (!isAdmin(user)) return data;
final Call<String> request = directMessagesService.removeAdmins(thread.getThreadId(), Collections.singleton(user.getPk()));
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
if (!response.isSuccessful()) {
handleSettingChangeResponseError(response, data);
return;
}
final List<Long> currentAdmins = adminUserIds.getValue();
if (currentAdmins == null) return;
adminUserIds.postValue(currentAdmins.stream()
.filter(userId1 -> userId1 != user.getPk())
.collect(Collectors.toList()));
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
Log.e(TAG, "onFailure: ", t);
data.postValue(Resource.error(t.getMessage(), null));
}
});
return data;
return threadManager.removeAdmin(user);
}
public LiveData<Resource<Object>> mute() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
data.postValue(Resource.loading(null));
if (thread.isMuted()) {
data.postValue(Resource.success(new Object()));
return data;
}
final Call<String> request = directMessagesService.mute(thread.getThreadId());
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
if (!response.isSuccessful()) {
handleSettingChangeResponseError(response, data);
return;
}
thread.setMuted(true);
muted.postValue(true);
data.postValue(Resource.success(new Object()));
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
Log.e(TAG, "onFailure: ", t);
data.postValue(Resource.error(t.getMessage(), null));
}
});
return data;
return threadManager.mute();
}
public LiveData<Resource<Object>> unmute() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
data.postValue(Resource.loading(null));
if (!thread.isMuted()) {
data.postValue(Resource.success(new Object()));
return data;
}
final Call<String> request = directMessagesService.unmute(thread.getThreadId());
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
if (!response.isSuccessful()) {
handleSettingChangeResponseError(response, data);
return;
}
thread.setMuted(false);
muted.postValue(false);
data.postValue(Resource.success(new Object()));
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
Log.e(TAG, "onFailure: ", t);
data.postValue(Resource.error(t.getMessage(), null));
}
});
return data;
return threadManager.unmute();
}
public LiveData<Resource<Object>> muteMentions() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
data.postValue(Resource.loading(null));
if (thread.isMentionsMuted()) {
data.postValue(Resource.success(new Object()));
return data;
}
final Call<String> request = directMessagesService.muteMentions(thread.getThreadId());
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
if (!response.isSuccessful()) {
handleSettingChangeResponseError(response, data);
return;
}
thread.setMentionsMuted(true);
mentionsMuted.postValue(true);
data.postValue(Resource.success(new Object()));
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
Log.e(TAG, "onFailure: ", t);
data.postValue(Resource.error(t.getMessage(), null));
}
});
return data;
return threadManager.muteMentions();
}
public LiveData<Resource<Object>> unmuteMentions() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
data.postValue(Resource.loading(null));
if (!thread.isMentionsMuted()) {
data.postValue(Resource.success(new Object()));
return data;
}
final Call<String> request = directMessagesService.unmuteMentions(thread.getThreadId());
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
if (!response.isSuccessful()) {
handleSettingChangeResponseError(response, data);
return;
}
thread.setMentionsMuted(false);
mentionsMuted.postValue(false);
data.postValue(Resource.success(new Object()));
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
Log.e(TAG, "onFailure: ", t);
data.postValue(Resource.error(t.getMessage(), null));
}
});
return data;
}
private void handleSettingChangeResponseError(@NonNull final Response<String> response,
final MutableLiveData<Resource<Object>> data) {
final ResponseBody errorBody = response.errorBody();
if (errorBody == null) {
handleErrorResponse(response, data);
return;
}
try {
final JSONObject json = new JSONObject(errorBody.string());
if (json.has("message")) {
data.postValue(Resource.error(json.getString("message"), null));
}
} catch (IOException | JSONException e) {
Log.e(TAG, "onResponse: ", e);
data.postValue(Resource.error(e.getMessage(), null));
}
return threadManager.unmuteMentions();
}
private LiveData<Resource<Object>> blockUser(final User user) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
friendshipService.block(user.getPk(), new ServiceCallback<FriendshipChangeResponse>() {
@Override
public void onSuccess(final FriendshipChangeResponse result) {
// refresh thread
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "onFailure: ", t);
data.postValue(Resource.error(t.getMessage(), null));
}
});
return data;
return threadManager.blockUser(user);
}
private LiveData<Resource<Object>> unblockUser(final User user) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
friendshipService.unblock(user.getPk(), new ServiceCallback<FriendshipChangeResponse>() {
@Override
public void onSuccess(final FriendshipChangeResponse result) {
// refresh thread
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "onFailure: ", t);
data.postValue(Resource.error(t.getMessage(), null));
}
});
return data;
return threadManager.unblockUser(user);
}
private LiveData<Resource<Object>> restrictUser(final User user) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
friendshipService.toggleRestrict(user.getPk(), true, new ServiceCallback<FriendshipRestrictResponse>() {
@Override
public void onSuccess(final FriendshipRestrictResponse result) {
// refresh thread
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "onFailure: ", t);
data.postValue(Resource.error(t.getMessage(), null));
}
});
return data;
return threadManager.restrictUser(user);
}
private LiveData<Resource<Object>> unRestrictUser(final User user) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
friendshipService.toggleRestrict(user.getPk(), false, new ServiceCallback<FriendshipRestrictResponse>() {
@Override
public void onSuccess(final FriendshipRestrictResponse result) {
// refresh thread
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "onFailure: ", t);
data.postValue(Resource.error(t.getMessage(), null));
}
});
return data;
return threadManager.unRestrictUser(user);
}
public LiveData<Resource<Object>> approveUsers(final List<User> users) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
data.postValue(Resource.loading(null));
final Call<DirectThreadDetailsChangeResponse> approveUsersRequest = directMessagesService
.approveParticipantRequests(thread.getThreadId(), users.stream().map(User::getPk).collect(Collectors.toList()));
handleDetailsChangeRequest(data, approveUsersRequest);
return data;
return threadManager.approveUsers(users);
}
public LiveData<Resource<Object>> denyUsers(final List<User> users) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
data.postValue(Resource.loading(null));
final Call<DirectThreadDetailsChangeResponse> approveUsersRequest = directMessagesService
.declineParticipantRequests(thread.getThreadId(), users.stream().map(User::getPk).collect(Collectors.toList()));
handleDetailsChangeRequest(data, approveUsersRequest, () -> {
final DirectThreadParticipantRequestsResponse pendingRequestsValue = pendingRequests.getValue();
if (pendingRequestsValue == null) return;
final List<User> pendingUsers = pendingRequestsValue.getUsers();
if (pendingUsers == null || pendingUsers.isEmpty()) return;
final List<User> filtered = pendingUsers.stream()
.filter(o -> !users.contains(o))
.collect(Collectors.toList());
try {
final DirectThreadParticipantRequestsResponse clone = (DirectThreadParticipantRequestsResponse) pendingRequestsValue.clone();
clone.setUsers(filtered);
pendingRequests.postValue(clone);
} catch (CloneNotSupportedException e) {
Log.e(TAG, "denyUsers: ", e);
}
});
return data;
return threadManager.denyUsers(users);
}
public LiveData<Resource<Object>> approvalRequired() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
data.postValue(Resource.loading(null));
if (thread.isApprovalRequiredForNewMembers()) {
data.postValue(Resource.success(new Object()));
return data;
}
final Call<DirectThreadDetailsChangeResponse> request = directMessagesService.approvalRequired(thread.getThreadId());
handleDetailsChangeRequest(data, request, () -> {
thread.setApprovalRequiredForNewMembers(true);
approvalRequiredToJoin.postValue(true);
});
return data;
return threadManager.approvalRequired();
}
public LiveData<Resource<Object>> approvalNotRequired() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
data.postValue(Resource.loading(null));
if (!thread.isApprovalRequiredForNewMembers()) {
data.postValue(Resource.success(new Object()));
return data;
}
final Call<DirectThreadDetailsChangeResponse> request = directMessagesService.approvalNotRequired(thread.getThreadId());
handleDetailsChangeRequest(data, request, () -> {
thread.setApprovalRequiredForNewMembers(false);
approvalRequiredToJoin.postValue(false);
});
return data;
return threadManager.approvalNotRequired();
}
public LiveData<Resource<Object>> leave() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
data.postValue(Resource.loading(null));
final Call<DirectThreadDetailsChangeResponse> request = directMessagesService.leave(thread.getThreadId());
handleDetailsChangeRequest(data, request);
return data;
return threadManager.leave();
}
public LiveData<Resource<Object>> end() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
data.postValue(Resource.loading(null));
final Call<DirectThreadDetailsChangeResponse> request = directMessagesService.end(thread.getThreadId());
handleDetailsChangeRequest(data, request, () -> {
thread.setInputMode(1);
inputMode.postValue(1);
});
return data;
}
private interface OnSuccessAction {
void onSuccess();
}
private void handleDetailsChangeRequest(final MutableLiveData<Resource<Object>> data,
final Call<DirectThreadDetailsChangeResponse> request) {
handleDetailsChangeRequest(data, request, null);
}
private void handleDetailsChangeRequest(final MutableLiveData<Resource<Object>> data,
final Call<DirectThreadDetailsChangeResponse> request,
@Nullable final OnSuccessAction action) {
request.enqueue(new Callback<DirectThreadDetailsChangeResponse>() {
@Override
public void onResponse(@NonNull final Call<DirectThreadDetailsChangeResponse> call,
@NonNull final Response<DirectThreadDetailsChangeResponse> response) {
if (!response.isSuccessful()) {
handleErrorResponse(response, data);
return;
}
final DirectThreadDetailsChangeResponse changeResponse = response.body();
if (changeResponse == null) {
data.postValue(Resource.error("Response is null", null));
return;
}
data.postValue(Resource.success(new Object()));
final DirectThread thread = changeResponse.getThread();
if (thread != null) {
setThread(thread);
}
if (action != null) {
action.onSuccess();
}
}
@Override
public void onFailure(@NonNull final Call<DirectThreadDetailsChangeResponse> call, @NonNull final Throwable t) {
Log.e(TAG, "onFailure: ", t);
data.postValue(Resource.error(t.getMessage(), null));
}
});
}
private void handleErrorResponse(@NonNull final Response<?> response,
final MutableLiveData<Resource<Object>> data) {
final ResponseBody errorBody = response.errorBody();
if (errorBody == null) {
data.postValue(Resource.error("Request failed!", null));
return;
}
try {
data.postValue(Resource.error(errorBody.string(), null));
} catch (IOException e) {
Log.e(TAG, "onResponse: ", e);
data.postValue(Resource.error(e.getMessage(), null));
}
return threadManager.end();
}
public ArrayList<Option<String>> createUserOptions(final User user) {
@ -632,10 +226,11 @@ public class DirectSettingsViewModel extends AndroidViewModel {
if (user == null || isSelf(user) || hasLeft(user)) {
return options;
}
if (viewerIsAdmin) {
final Boolean viewerIsAdmin = threadManager.isViewerAdmin().getValue();
if (viewerIsAdmin != null && viewerIsAdmin) {
options.add(new Option<>(getString(R.string.dms_action_kick), ACTION_KICK));
final boolean isAdmin = isAdmin(user);
final boolean isAdmin = threadManager.isAdmin(user);
options.add(new Option<>(
isAdmin ? getString(R.string.dms_action_remove_admin) : getString(R.string.dms_action_make_admin),
isAdmin ? ACTION_REMOVE_ADMIN : ACTION_MAKE_ADMIN
@ -649,8 +244,8 @@ public class DirectSettingsViewModel extends AndroidViewModel {
));
// options.add(new Option<>(getString(R.string.report), ACTION_REPORT));
if (!isGroup()) {
final Boolean isGroup = threadManager.isGroup().getValue();
if (isGroup != null && isGroup) {
final boolean restricted = user.getFriendshipStatus().isRestricted();
options.add(new Option<>(
restricted ? getString(R.string.unrestrict) : getString(R.string.restrict),
@ -661,18 +256,13 @@ public class DirectSettingsViewModel extends AndroidViewModel {
}
private boolean hasLeft(final User user) {
final Pair<List<User>, List<User>> users = this.users.getValue();
if (users == null || users.second == null) return false;
return users.second.contains(user);
}
private boolean isAdmin(final User user) {
final List<Long> adminUserIdsValue = adminUserIds.getValue();
return adminUserIdsValue != null && adminUserIdsValue.contains(user.getPk());
final List<User> leftUsers = getLeftUsers().getValue();
if (leftUsers == null) return false;
return leftUsers.contains(user);
}
private boolean isSelf(final User user) {
return user.getPk() == userId;
return user.getPk() == viewerId;
}
private String getString(@StringRes final int resId) {
@ -703,47 +293,7 @@ public class DirectSettingsViewModel extends AndroidViewModel {
}
}
public void setViewer(final User viewer) {
this.viewer = viewer;
}
private void fetchPendingRequests() {
final Call<DirectThreadParticipantRequestsResponse> request = directMessagesService.participantRequests(thread.getThreadId(), 5, null);
request.enqueue(new Callback<DirectThreadParticipantRequestsResponse>() {
@Override
public void onResponse(@NonNull final Call<DirectThreadParticipantRequestsResponse> call,
@NonNull final Response<DirectThreadParticipantRequestsResponse> response) {
if (!response.isSuccessful()) {
if (response.errorBody() != null) {
try {
final String string = response.errorBody().string();
final String msg = String.format(Locale.US,
"onResponse: url: %s, responseCode: %d, errorBody: %s",
call.request().url().toString(),
response.code(),
string);
Log.e(TAG, msg);
} catch (IOException e) {
Log.e(TAG, "onResponse: ", e);
}
return;
}
Log.e(TAG, "onResponse: request was not successful and response error body was null");
return;
}
final DirectThreadParticipantRequestsResponse body = response.body();
if (body == null) {
Log.e(TAG, "onResponse: response body was null");
return;
}
pendingRequests.postValue(body);
}
@Override
public void onFailure(@NonNull final Call<DirectThreadParticipantRequestsResponse> call, @NonNull final Throwable t) {
Log.e(TAG, "onFailure: ", t);
}
});
public LiveData<User> getInviter() {
return threadManager.getInviter();
}
}

View File

@ -0,0 +1,35 @@
package awais.instagrabber.viewmodels.factories;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.viewmodels.DirectSettingsViewModel;
public class DirectSettingsViewModelFactory implements ViewModelProvider.Factory {
private final Application application;
private final String threadId;
private final boolean pending;
private final User currentUser;
public DirectSettingsViewModelFactory(@NonNull final Application application,
@NonNull final String threadId,
final boolean pending,
@NonNull final User currentUser) {
this.application = application;
this.threadId = threadId;
this.pending = pending;
this.currentUser = currentUser;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull final Class<T> modelClass) {
//noinspection unchecked
return (T) new DirectSettingsViewModel(application, threadId, pending, currentUser);
}
}

View File

@ -0,0 +1,35 @@
package awais.instagrabber.viewmodels.factories;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.viewmodels.DirectThreadViewModel;
public class DirectThreadViewModelFactory implements ViewModelProvider.Factory {
private final Application application;
private final String threadId;
private final boolean pending;
private final User currentUser;
public DirectThreadViewModelFactory(@NonNull final Application application,
@NonNull final String threadId,
final boolean pending,
@NonNull final User currentUser) {
this.application = application;
this.threadId = threadId;
this.pending = pending;
this.currentUser = currentUser;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull final Class<T> modelClass) {
//noinspection unchecked
return (T) new DirectThreadViewModel(application, threadId, pending, currentUser);
}
}

View File

@ -20,4 +20,14 @@
android:paddingBottom="?attr/actionBarSize"
tools:listitem="@layout/layout_dm_inbox_item" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/empty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@string/no_pending_requests"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -428,4 +428,5 @@
<string name="accept_request_from_user">Accept request from %1s (%2s)?</string>
<string name="decline">Decline</string>
<string name="accept">Accept</string>
<string name="no_pending_requests">No pending requests</string>
</resources>

View File

@ -5,7 +5,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.1'
classpath 'com.android.tools.build:gradle:4.1.2'
def nav_version = "2.3.2"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}