1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-11-26 00:27:30 +00:00

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 { buildTypes {
debug { debug {
minifyEnabled true // minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' // proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
release { release {
@ -62,7 +62,7 @@ dependencies {
def nav_version = '2.3.2' def nav_version = '2.3.2'
def exoplayer_version = '2.12.0' 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-core:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-dash:$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.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavBackStackEntry; import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.NavDestination; import androidx.navigation.NavDestination;
@ -75,6 +76,7 @@ import awais.instagrabber.utils.IntentUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.utils.emoji.EmojiParser; import awais.instagrabber.utils.emoji.EmojiParser;
import awais.instagrabber.viewmodels.AppStateViewModel;
import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController; import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
@ -102,6 +104,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
private int firstFragmentGraphIndex; private int firstFragmentGraphIndex;
private boolean isActivityCheckerServiceBound = false; private boolean isActivityCheckerServiceBound = false;
private boolean isBackStackEmpty = false; private boolean isBackStackEmpty = false;
private boolean isLoggedIn;
private final ServiceConnection serviceConnection = new ServiceConnection() { private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override @Override
@ -131,6 +134,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
binding = ActivityMainBinding.inflate(getLayoutInflater()); binding = ActivityMainBinding.inflate(getLayoutInflater());
final String cookie = settingsHelper.getString(Constants.COOKIE); final String cookie = settingsHelper.getString(Constants.COOKIE);
CookieUtils.setupCookies(cookie); CookieUtils.setupCookies(cookie);
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != 0;
setContentView(binding.getRoot()); setContentView(binding.getRoot());
final Toolbar toolbar = binding.toolbar; final Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
@ -142,6 +146,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
final boolean checkUpdates = settingsHelper.getBoolean(Constants.CHECK_UPDATES); final boolean checkUpdates = settingsHelper.getBoolean(Constants.CHECK_UPDATES);
if (checkUpdates) FlavorTown.updateCheck(this); if (checkUpdates) FlavorTown.updateCheck(this);
FlavorTown.changelogCheck(this); FlavorTown.changelogCheck(this);
new ViewModelProvider(this).get(AppStateViewModel.class); // Just initiate the App state here
final Intent intent = getIntent(); final Intent intent = getIntent();
handleIntent(intent); handleIntent(intent);
if (!TextUtils.isEmpty(cookie) && settingsHelper.getBoolean(Constants.CHECK_ACTIVITY)) { 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) { private void setupBottomNavigationBar(final boolean setDefaultFromSettings) {
int main_nav_ids = R.array.main_nav_ids; 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) { if (!isLoggedIn) {
main_nav_ids = R.array.logged_out_main_nav_ids; main_nav_ids = R.array.logged_out_main_nav_ids;
final int selectedItemId = binding.bottomNavView.getSelectedItemId(); 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) { public void setThread(final DirectThread thread) {
if (thread == null) return; if (thread == null) return;
this.thread = thread; this.thread = thread;
notifyDataSetChanged(); // notifyDataSetChanged();
} }
public void submitList(@Nullable final List<DirectItem> list) { 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 oldItemFirst = oldThread.getFirstDirectItem();
final DirectItem newItemFirst = newThread.getFirstDirectItem(); final DirectItem newItemFirst = newThread.getFirstDirectItem();
if (oldItemFirst == null || newItemFirst == null) return false; 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) { private void setReadState(@NonNull final DirectThread thread) {
final DirectItem item = thread.getItems().get(0); 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(); final Map<Long, DirectThreadLastSeenAt> lastSeenAtMap = thread.getLastSeenAt();
final boolean read = ResponseBodyUtils.isRead(item, lastSeenAtMap, Collections.singletonList(thread.getViewerId()), thread.getDirectStory()); read = ResponseBodyUtils.isRead(item, lastSeenAtMap, Collections.singletonList(thread.getViewerId()));
}
}
binding.unread.setVisibility(read ? View.GONE : View.VISIBLE); binding.unread.setVisibility(read ? View.GONE : View.VISIBLE);
binding.threadTitle.setTypeface(binding.threadTitle.getTypeface(), read ? Typeface.NORMAL : Typeface.BOLD); binding.threadTitle.setTypeface(binding.threadTitle.getTypeface(), read ? Typeface.NORMAL : Typeface.BOLD);
binding.subtitle.setTypeface(binding.subtitle.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()) { if (item.isPending()) {
binding.deliveryStatus.setImageResource(R.drawable.ic_check_24); binding.deliveryStatus.setImageResource(R.drawable.ic_check_24);
} else { } 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); binding.deliveryStatus.setImageResource(R.drawable.ic_check_all_24);
ImageViewCompat.setImageTintList( ImageViewCompat.setImageTintList(
binding.deliveryStatus, binding.deliveryStatus,
@ -358,7 +361,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
final DirectItemReactions reactions = item.getReactions(); final DirectItemReactions reactions = item.getReactions();
final List<DirectItemEmojiReaction> emojis = reactions != null ? reactions.getEmojis() : null; final List<DirectItemEmojiReaction> emojis = reactions != null ? reactions.getEmojis() : null;
if (emojis == null || emojis.isEmpty()) { if (emojis == null || emojis.isEmpty()) {
binding.container.setPadding(messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall); binding.container.setPadding(messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall, 0);
binding.reactionsWrapper.setVisibility(View.GONE); binding.reactionsWrapper.setVisibility(View.GONE);
return; 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.BadgeDrawable;
import com.google.android.material.badge.BadgeUtils; import com.google.android.material.badge.BadgeUtils;
import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.snackbar.Snackbar;
import java.util.List; import java.util.List;
@ -172,6 +173,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
} }
private void setupObservers() { private void setupObservers() {
removeViewModelObservers();
threadsObserver = list -> { threadsObserver = list -> {
if (inboxAdapter == null) return; if (inboxAdapter == null) return;
inboxAdapter.submitList(list, () -> { inboxAdapter.submitList(list, () -> {
@ -181,8 +183,28 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
}); });
}; };
viewModel.getThreads().observe(fragmentActivity, threadsObserver); viewModel.getThreads().observe(fragmentActivity, threadsObserver);
viewModel.getFetchingInbox().observe(getViewLifecycleOwner(), fetching -> binding.swipeRefreshLayout.setRefreshing(fetching)); viewModel.getInbox().observe(getViewLifecycleOwner(), inboxResource -> {
viewModel.getUnseenCount().observe(getViewLifecycleOwner(), this::setBottomNavBarBadge); 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); viewModel.getPendingRequestsTotal().observe(getViewLifecycleOwner(), this::attachPendingRequestsBadge);
} }
@ -230,11 +252,10 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
inboxAdapter = new DirectMessageInboxAdapter(thread -> { inboxAdapter = new DirectMessageInboxAdapter(thread -> {
if (navigating) return; if (navigating) return;
navigating = true; navigating = true;
final Bundle bundle = new Bundle();
bundle.putString("threadId", thread.getThreadId());
bundle.putString("title", thread.getThreadTitle());
if (isAdded()) { 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; navigating = false;
}); });

View File

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

View File

@ -37,7 +37,6 @@ import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.navigation.NavBackStackEntry; import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
@ -58,7 +57,6 @@ import com.google.common.collect.ImmutableList;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import awais.instagrabber.ProfileNavGraphDirections; import awais.instagrabber.ProfileNavGraphDirections;
@ -104,9 +102,8 @@ import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.AppStateViewModel; import awais.instagrabber.viewmodels.AppStateViewModel;
import awais.instagrabber.viewmodels.DirectInboxViewModel;
import awais.instagrabber.viewmodels.DirectPendingInboxViewModel;
import awais.instagrabber.viewmodels.DirectThreadViewModel; 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_ADJUST_NOTHING;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
@ -235,7 +232,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
@Override @Override
public void onReaction(final DirectItem item, final Emoji emoji) { public void onReaction(final DirectItem item, final Emoji emoji) {
if (item == null) return; 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) { if (resourceLiveData != null) {
resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData)); 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 final MutableLiveData<Integer> inputLength = new MutableLiveData<>(0);
private ItemTouchHelper itemTouchHelper; 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 @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity(); fragmentActivity = (MainActivity) requireActivity();
appStateViewModel = new ViewModelProvider(fragmentActivity).get(AppStateViewModel.class); 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); setHasOptionsMenu(true);
} }
@ -332,7 +345,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
init(); init();
binding.send.post(() -> initialSendX = binding.send.getX()); binding.send.post(() -> initialSendX = binding.send.getX());
shouldRefresh = false; shouldRefresh = false;
setObservers();
} }
@Override @Override
@ -406,6 +418,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
wasKbShowing = true; wasKbShowing = true;
binding.emojiPicker.setAlpha(0); binding.emojiPicker.setAlpha(0);
} }
removeObservers();
super.onPause(); super.onPause();
} }
@ -423,7 +436,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
} }
binding.send.stopScale(); binding.send.stopScale();
setupBackStackResultObserver(); setupBackStackResultObserver();
attachPendingRequestsBadge(viewModel.getPendingRequestsCount().getValue()); setObservers();
// attachPendingRequestsBadge(viewModel.getPendingRequestsCount().getValue());
} }
@Override @Override
@ -463,42 +477,38 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (context == null) return; if (context == null) return;
if (getArguments() == null) return; if (getArguments() == null) return;
actionBar = fragmentActivity.getSupportActionBar(); actionBar = fragmentActivity.getSupportActionBar();
final DirectMessageThreadFragmentArgs fragmentArgs = DirectMessageThreadFragmentArgs.fromBundle(getArguments());
viewModel.getThreadTitle().postValue(fragmentArgs.getTitle());
final String threadId = fragmentArgs.getThreadId();
viewModel.setThreadId(threadId);
setupList(); setupList();
root.post(this::setupInput); root.post(this::setupInput);
root.post(this::getInitialData); // root.post(this::getInitialData);
} }
private void getInitialData() { // private void getInitialData() {
final Bundle arguments = getArguments(); // final Bundle arguments = getArguments();
if (arguments == null) return; // if (arguments == null) return;
final DirectMessageThreadFragmentArgs args = DirectMessageThreadFragmentArgs.fromBundle(arguments); // final DirectMessageThreadFragmentArgs args = DirectMessageThreadFragmentArgs.fromBundle(arguments);
final boolean pending = args.getPending(); // final boolean pending = args.getPending();
final NavController navController = NavHostFragment.findNavController(this); // final NavController navController = NavHostFragment.findNavController(this);
final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph); // final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
final List<DirectThread> threads; // final List<DirectThread> threads;
if (!pending) { // if (!pending) {
final DirectInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class); // final DirectInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
threads = threadListViewModel.getThreads().getValue(); // threads = threadListViewModel.getThreads().getValue();
} else { // } else {
final DirectPendingInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectPendingInboxViewModel.class); // final DirectPendingInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectPendingInboxViewModel.class);
threads = threadListViewModel.getThreads().getValue(); // threads = threadListViewModel.getThreads().getValue();
} // }
final Optional<DirectThread> first = threads != null // final Optional<DirectThread> first = threads != null
? threads.stream() // ? threads.stream()
.filter(thread -> thread.getThreadId().equals(viewModel.getThreadId())) // .filter(thread -> thread.getThreadId().equals(viewModel.getThreadId()))
.findFirst() // .findFirst()
: Optional.empty(); // : Optional.empty();
if (first.isPresent()) { // if (first.isPresent()) {
final DirectThread thread = first.get(); // final DirectThread thread = first.get();
viewModel.setThread(thread); // viewModel.setThread(thread);
return; // return;
} // }
viewModel.fetchChats(); // viewModel.fetchChats();
} // }
private void setupList() { private void setupList() {
final Context context = getContext(); final Context context = getContext();
@ -542,7 +552,14 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
} }
private void setObservers() { 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) { if (isPending == null) {
hideInput(); hideInput();
return; return;
@ -556,7 +573,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (inputMode != null && inputMode == 1) return; if (inputMode != null && inputMode == 1) return;
showInput(); showInput();
}); });
viewModel.getInputMode().observe(getViewLifecycleOwner(), inputMode -> { inputModeLiveData = viewModel.getInputMode();
inputModeLiveData.observe(getViewLifecycleOwner(), inputMode -> {
final Boolean isPending = viewModel.isPending().getValue(); final Boolean isPending = viewModel.isPending().getValue();
if (isPending != null && isPending) return; if (isPending != null && isPending) return;
if (inputMode == null || inputMode == 0) return; if (inputMode == null || inputMode == 0) return;
@ -564,21 +582,34 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
hideInput(); hideInput();
} }
}); });
viewModel.getThreadTitle().observe(getViewLifecycleOwner(), this::setTitle); threadTitleLiveData = viewModel.getThreadTitle();
viewModel.getFetching().observe(getViewLifecycleOwner(), fetching -> { threadTitleLiveData.observe(getViewLifecycleOwner(), this::setTitle);
if (fetching) { fetchingLiveData = viewModel.isFetching();
setTitle(UPDATING_TITLE); fetchingLiveData.observe(getViewLifecycleOwner(), fetchingResource -> {
return; if (fetchingResource == null) return;
} switch (fetchingResource.status) {
case SUCCESS:
case ERROR:
setTitle(viewModel.getThreadTitle().getValue()); 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;
}
}); });
final ItemsAdapterDataMerger itemsAdapterDataMerger = new ItemsAdapterDataMerger(appStateViewModel.getCurrentUser(), viewModel.getThread()); // final ItemsAdapterDataMerger itemsAdapterDataMerger = new ItemsAdapterDataMerger(appStateViewModel.getCurrentUser(), viewModel.getThread());
itemsAdapterDataMerger.observe(getViewLifecycleOwner(), userThreadPair -> { // itemsAdapterDataMerger.observe(getViewLifecycleOwner(), userThreadPair -> {
viewModel.setCurrentUser(userThreadPair.first); // viewModel.setCurrentUser(userThreadPair.first);
setupItemsAdapter(userThreadPair.first, userThreadPair.second); // setupItemsAdapter(userThreadPair.first, userThreadPair.second);
}); // });
viewModel.getItems().observe(getViewLifecycleOwner(), this::submitItemsToAdapter); threadLiveData.observe(getViewLifecycleOwner(), this::setupItemsAdapter);
viewModel.getReplyToItem().observe(getViewLifecycleOwner(), item -> { itemsLiveData = viewModel.getItems();
itemsLiveData.observe(getViewLifecycleOwner(), this::submitItemsToAdapter);
replyToItemLiveData = viewModel.getReplyToItem();
replyToItemLiveData.observe(getViewLifecycleOwner(), item -> {
if (item == null) { if (item == null) {
if (binding.input.length() == 0) { if (binding.input.length() == 0) {
showExtraInputOption(true); showExtraInputOption(true);
@ -633,14 +664,30 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
} }
prevLength = length; prevLength = length;
}); });
viewModel.getPendingRequestsCount().observe(getViewLifecycleOwner(), this::attachPendingRequestsBadge); pendingRequestsCountLiveData = viewModel.getPendingRequestsCount();
viewModel.getUsers().observe(getViewLifecycleOwner(), users -> { pendingRequestsCountLiveData.observe(getViewLifecycleOwner(), this::attachPendingRequestsBadge);
usersLiveData = viewModel.getUsers();
usersLiveData.observe(getViewLifecycleOwner(), users -> {
if (users == null || users.isEmpty()) return; if (users == null || users.isEmpty()) return;
final User user = users.get(0); final User user = users.get(0);
binding.acceptPendingRequestQuestion.setText(getString(R.string.accept_request_from_user, user.getUsername(), user.getFullName())); 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() { private void hidePendingOptions() {
binding.acceptPendingRequestQuestion.setVisibility(View.GONE); binding.acceptPendingRequestQuestion.setVisibility(View.GONE);
binding.decline.setVisibility(View.GONE); binding.decline.setVisibility(View.GONE);
@ -669,9 +716,15 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
case SUCCESS: case SUCCESS:
resourceLiveData.removeObservers(getViewLifecycleOwner()); resourceLiveData.removeObservers(getViewLifecycleOwner());
if (isDecline) { if (isDecline) {
removeObservers();
viewModel.removeThread();
final NavController navController = NavHostFragment.findNavController(this); final NavController navController = NavHostFragment.findNavController(this);
navController.navigateUp(); navController.navigateUp();
return;
} }
removeObservers();
viewModel.moveFromPending();
setObservers();
break; break;
case LOADING: case LOADING:
break; 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 != null) {
if (itemsAdapter.getThread() == thread) return; if (itemsAdapter.getThread() == thread) return;
itemsAdapter.setThread(thread); itemsAdapter.setThread(thread);
return; return;
} }
final User currentUser = appStateViewModel.getCurrentUser();
if (currentUser == null) return;
itemsAdapter = new DirectItemsAdapter(currentUser, thread, directItemCallback, directItemLongClickListener); itemsAdapter = new DirectItemsAdapter(currentUser, thread, directItemCallback, directItemLongClickListener);
itemsAdapter.setHasStableIds(true); itemsAdapter.setHasStableIds(true);
itemsAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY); itemsAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
@ -958,7 +1014,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
binding.send.setOnRecordClickListener(v -> { binding.send.setOnRecordClickListener(v -> {
final Editable text = binding.input.getText(); final Editable text = binding.input.getText();
if (TextUtils.isEmpty(text)) return; 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)); resourceLiveData.observe(getViewLifecycleOwner(), resource -> handleSentMessage(resourceLiveData));
binding.input.setText(""); binding.input.setText("");
viewModel.setReplyToItem(null); viewModel.setReplyToItem(null);
@ -1031,8 +1087,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
navController.navigate(navDirections); navController.navigate(navDirections);
} }
private void handleSentMessage(final LiveData<Resource<DirectItem>> resourceLiveData) { private void handleSentMessage(final LiveData<Resource<Object>> resourceLiveData) {
final Resource<DirectItem> resource = resourceLiveData.getValue(); final Resource<Object> resource = resourceLiveData.getValue();
if (resource == null) return; if (resource == null) return;
final Resource.Status status = resource.status; final Resource.Status status = resource.status;
switch (status) { switch (status) {
@ -1380,10 +1436,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
animatorSet.start(); animatorSet.start();
} }
private void showLongClickOptions(final View itemView) {
}
private void showReactionsDialog(final DirectItem item) { private void showReactionsDialog(final DirectItem item) {
final LiveData<List<User>> users = viewModel.getUsers(); final LiveData<List<User>> users = viewModel.getUsers();
final LiveData<List<User>> leftUsers = viewModel.getLeftUsers(); final LiveData<List<User>> leftUsers = viewModel.getLeftUsers();
@ -1410,7 +1462,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
} }
if (reaction == null) return; if (reaction == null) return;
if (reaction.getSenderId() == viewModel.getViewerId()) { if (reaction.getSenderId() == viewModel.getViewerId()) {
final LiveData<Resource<DirectItem>> resourceLiveData = viewModel.sendDeleteReaction(itemId); final LiveData<Resource<Object>> resourceLiveData = viewModel.sendDeleteReaction(itemId);
if (resourceLiveData != null) { if (resourceLiveData != null) {
resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData)); resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData));
} }

View File

@ -19,6 +19,9 @@ import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.Snackbar;
import java.util.Collections;
import java.util.List; import java.util.List;
import awais.instagrabber.R; import awais.instagrabber.R;
@ -102,16 +105,41 @@ public class DirectPendingInboxFragment extends Fragment implements SwipeRefresh
} }
private void setupObservers() { private void setupObservers() {
removeViewModelObservers();
threadsObserver = list -> { threadsObserver = list -> {
if (inboxAdapter == null) return; 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; if (!scrollToTop) return;
binding.pendingInboxList.smoothScrollToPosition(0); binding.pendingInboxList.smoothScrollToPosition(0);
scrollToTop = false; scrollToTop = false;
}); });
if (list == null || list.isEmpty()) {
binding.swipeRefreshLayout.setVisibility(View.GONE);
binding.empty.setVisibility(View.VISIBLE);
}
}; };
viewModel.getThreads().observe(fragmentActivity, threadsObserver); 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() { 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.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.Objects;
public class Resource<T> { public class Resource<T> {
public final Status status; public final Status status;
public final T data; public final T data;
@ -31,6 +33,21 @@ public class Resource<T> {
return new Resource<>(Status.LOADING, data, null); 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 { public enum Status {
SUCCESS, SUCCESS,
ERROR, ERROR,

View File

@ -1,5 +1,7 @@
package awais.instagrabber.repositories.responses; package awais.instagrabber.repositories.responses;
import java.util.Objects;
public class AnimatedMediaFixedHeight { public class AnimatedMediaFixedHeight {
private final int height; private final int height;
private final int width; private final int width;
@ -34,4 +36,21 @@ public class AnimatedMediaFixedHeight {
public String getWebp() { public String getWebp() {
return webp; 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; package awais.instagrabber.repositories.responses;
import java.util.Objects;
public class AnimatedMediaImages { public class AnimatedMediaImages {
private final AnimatedMediaFixedHeight fixedHeight; private final AnimatedMediaFixedHeight fixedHeight;
@ -10,4 +12,17 @@ public class AnimatedMediaImages {
public AnimatedMediaFixedHeight getFixedHeight() { public AnimatedMediaFixedHeight getFixedHeight() {
return fixedHeight; 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.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Objects;
public class Audio implements Serializable { public class Audio implements Serializable {
private final String audioSrc; private final String audioSrc;
@ -41,4 +42,21 @@ public class Audio implements Serializable {
public long getAudioSrcExpirationTimestampUs() { public long getAudioSrcExpirationTimestampUs() {
return audioSrcExpirationTimestampUs; 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.io.Serializable;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Objects;
public class Caption implements Serializable { public class Caption implements Serializable {
private long mPk; private long mPk;
@ -42,6 +43,21 @@ public class Caption implements Serializable {
this.text = text; 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> { public static class CaptionDeserializer implements JsonDeserializer<Caption> {
private static final String TAG = CaptionDeserializer.class.getSimpleName(); private static final String TAG = CaptionDeserializer.class.getSimpleName();

View File

@ -1,6 +1,7 @@
package awais.instagrabber.repositories.responses; package awais.instagrabber.repositories.responses;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects;
public class EndOfFeedDemarcator implements Serializable { public class EndOfFeedDemarcator implements Serializable {
private final long id; private final long id;
@ -18,4 +19,18 @@ public class EndOfFeedDemarcator implements Serializable {
public EndOfFeedGroupSet getGroupSet() { public EndOfFeedGroupSet getGroupSet() {
return groupSet; 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.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Objects;
public class EndOfFeedGroup implements Serializable { public class EndOfFeedGroup implements Serializable {
private final String id; private final String id;
@ -31,4 +32,20 @@ public class EndOfFeedGroup implements Serializable {
public List<Media> getFeedItems() { public List<Media> getFeedItems() {
return feedItems; 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.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Objects;
public class EndOfFeedGroupSet implements Serializable { public class EndOfFeedGroupSet implements Serializable {
private final long id; private final long id;
@ -48,4 +49,22 @@ public class EndOfFeedGroupSet implements Serializable {
public List<EndOfFeedGroup> getGroups() { public List<EndOfFeedGroup> getGroups() {
return groups; 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 androidx.annotation.NonNull;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects;
public class FriendshipStatus implements Serializable { public class FriendshipStatus implements Serializable {
private final boolean following; private final boolean following;
@ -78,6 +79,29 @@ public class FriendshipStatus implements Serializable {
return isMutingReel; 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 @NonNull
@Override @Override
public String toString() { public String toString() {

View File

@ -2,6 +2,7 @@ package awais.instagrabber.repositories.responses;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Objects;
public class ImageVersions2 implements Serializable { public class ImageVersions2 implements Serializable {
private final List<MediaCandidate> candidates; private final List<MediaCandidate> candidates;
@ -13,4 +14,17 @@ public class ImageVersions2 implements Serializable {
public List<MediaCandidate> getCandidates() { public List<MediaCandidate> getCandidates() {
return candidates; 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; package awais.instagrabber.repositories.responses;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects;
public class Location implements Serializable { public class Location implements Serializable {
private final long pk; private final long pk;
@ -54,4 +55,23 @@ public class Location implements Serializable {
public float getLat() { public float getLat() {
return lat; 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.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import awais.instagrabber.models.enums.MediaItemType; import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
@ -272,4 +273,52 @@ public class Media implements Serializable {
final Caption caption1 = getCaption(); final Caption caption1 = getCaption();
caption1.setText(caption); 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; package awais.instagrabber.repositories.responses;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects;
public class MediaCandidate implements Serializable { public class MediaCandidate implements Serializable {
private final int width; private final int width;
@ -24,4 +25,19 @@ public class MediaCandidate implements Serializable {
public String getUrl() { public String getUrl() {
return url; 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); // null);
// } // }
@Override @Override
public boolean equals(final Object o) { public boolean equals(final Object o) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
final User that = (User) o; final User user = (User) o;
return pk == that.pk && return pk == user.pk &&
Objects.equals(username, that.username); 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 @Override
public int hashCode() { 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.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Objects;
public class UsertagIn implements Serializable { public class UsertagIn implements Serializable {
private final User user; private final User user;
@ -19,4 +20,18 @@ public class UsertagIn implements Serializable {
public List<String> getPosition() { public List<String> getPosition() {
return position; 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.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Objects;
public class Usertags implements Serializable { public class Usertags implements Serializable {
private final List<UsertagIn> in; private final List<UsertagIn> in;
@ -13,4 +14,17 @@ public class Usertags implements Serializable {
public List<UsertagIn> getIn() { public List<UsertagIn> getIn() {
return in; 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; package awais.instagrabber.repositories.responses;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects;
public class VideoVersion implements Serializable { public class VideoVersion implements Serializable {
private final String id; private final String id;
@ -36,4 +37,21 @@ public class VideoVersion implements Serializable {
public String getUrl() { public String getUrl() {
return url; 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; package awais.instagrabber.repositories.responses.directmessages;
import androidx.annotation.NonNull;
import java.util.List; import java.util.List;
public class DirectInbox { public class DirectInbox implements Cloneable {
private final List<DirectThread> threads; private List<DirectThread> threads;
private final boolean hasOlder; private final boolean hasOlder;
private final int unseenCount; private final int unseenCount;
private final String unseenCountTs; private final String unseenCountTs;
@ -28,6 +30,10 @@ public class DirectInbox {
return threads; return threads;
} }
public void setThreads(final List<DirectThread> threads) {
this.threads = threads;
}
public boolean hasOlder() { public boolean hasOlder() {
return hasOlder; return hasOlder;
} }
@ -47,4 +53,10 @@ public class DirectInbox {
public boolean isBlendedInboxEnabled() { public boolean isBlendedInboxEnabled() {
return blendedInboxEnabled; 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.Date;
import java.util.List; import java.util.List;
import java.util.Objects;
import awais.instagrabber.models.enums.DirectItemType; import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.repositories.responses.Location; import awais.instagrabber.repositories.responses.Location;
@ -239,21 +240,47 @@ public class DirectItem implements Cloneable {
return super.clone(); return super.clone();
} }
// @Override @Override
// public boolean equals(final Object o) { public boolean equals(final Object o) {
// if (this == o) return true; if (this == o) return true;
// if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
// final DirectItem that = (DirectItem) o; final DirectItem that = (DirectItem) o;
// return userId == that.userId && return userId == that.userId &&
// timestamp == that.timestamp && timestamp == that.timestamp &&
// isPending == that.isPending && hideInThread == that.hideInThread &&
// Objects.equals(itemId, that.itemId) && isPending == that.isPending &&
// itemType == that.itemType && showForwardAttribution == that.showForwardAttribution &&
// Objects.equals(clientContext, that.clientContext); Objects.equals(itemId, that.itemId) &&
// } itemType == that.itemType &&
// Objects.equals(text, that.text) &&
// @Override Objects.equals(like, that.like) &&
// public int hashCode() { Objects.equals(link, that.link) &&
// return Objects.hash(itemId, userId, timestamp, itemType, clientContext, isPending); 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; package awais.instagrabber.repositories.responses.directmessages;
import java.util.List; import java.util.List;
import java.util.Objects;
public class DirectItemActionLog { public class DirectItemActionLog {
private final String description; private final String description;
@ -27,6 +28,21 @@ public class DirectItemActionLog {
return textAttributes; 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 { public static class TextRange {
private final int start; private final int start;
private final int end; private final int end;
@ -55,5 +71,21 @@ public class DirectItemActionLog {
public String getIntent() { public String getIntent() {
return intent; 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; package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
import awais.instagrabber.repositories.responses.AnimatedMediaImages; import awais.instagrabber.repositories.responses.AnimatedMediaImages;
public final class DirectItemAnimatedMedia { public final class DirectItemAnimatedMedia {
@ -31,4 +33,20 @@ public final class DirectItemAnimatedMedia {
public boolean isSticker() { public boolean isSticker() {
return 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; package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
public class DirectItemClip { public class DirectItemClip {
@ -12,4 +14,17 @@ public class DirectItemClip {
public Media getClip() { public Media getClip() {
return clip; 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; package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
public class DirectItemFelixShare { public class DirectItemFelixShare {
@ -12,4 +14,17 @@ public class DirectItemFelixShare {
public Media getVideo() { public Media getVideo() {
return video; 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; package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
public class DirectItemLink { public class DirectItemLink {
private final String text; private final String text;
private final DirectItemLinkContext linkContext; private final DirectItemLinkContext linkContext;
@ -31,4 +33,20 @@ public class DirectItemLink {
public String getMutationToken() { public String getMutationToken() {
return mutationToken; 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; package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
public class DirectItemLinkContext { public class DirectItemLinkContext {
private final String linkUrl; private final String linkUrl;
private final String linkTitle; private final String linkTitle;
@ -31,4 +33,20 @@ public class DirectItemLinkContext {
public String getLinkImageUrl() { public String getLinkImageUrl() {
return linkImageUrl; 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; package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
public class DirectItemPlaceholder { public class DirectItemPlaceholder {
private final boolean isLinked; private final boolean isLinked;
private final String title; private final String title;
@ -24,4 +26,19 @@ public class DirectItemPlaceholder {
public String getMessage() { public String getMessage() {
return message; 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; package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
public class DirectItemReelShare { public class DirectItemReelShare {
@ -61,4 +63,24 @@ public class DirectItemReelShare {
public long getMentionedUserId() { public long getMentionedUserId() {
return mentionedUserId; 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; package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
public class DirectItemReelShareReactionInfo { public class DirectItemReelShareReactionInfo {
private final String emoji; private final String emoji;
private final String intensity; private final String intensity;
@ -16,4 +18,18 @@ public class DirectItemReelShareReactionInfo {
public String getIntensity() { public String getIntensity() {
return intensity; 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; package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
public class DirectItemStoryShare { public class DirectItemStoryShare {
@ -54,4 +56,23 @@ public class DirectItemStoryShare {
public String getMessage() { public String getMessage() {
return message; 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; package awais.instagrabber.repositories.responses.directmessages;
import java.util.List; import java.util.List;
import java.util.Objects;
public final class DirectItemVideoCallEvent { public final class DirectItemVideoCallEvent {
private final String action; private final String action;
@ -40,4 +41,21 @@ public final class DirectItemVideoCallEvent {
public List<DirectItemActionLog.TextRange> getTextAttributes() { public List<DirectItemActionLog.TextRange> getTextAttributes() {
return textAttributes; 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; package awais.instagrabber.repositories.responses.directmessages;
import java.util.List; import java.util.List;
import java.util.Objects;
import awais.instagrabber.models.enums.RavenMediaViewMode; import awais.instagrabber.models.enums.RavenMediaViewMode;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
@ -64,4 +65,25 @@ public class DirectItemVisualMedia {
public Media getMedia() { public Media getMedia() {
return media; 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; package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
public class DirectItemVoiceMedia { public class DirectItemVoiceMedia {
@ -24,4 +26,19 @@ public class DirectItemVoiceMedia {
public String getViewMode() { public String getViewMode() {
return viewMode; 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; package awais.instagrabber.repositories.responses.directmessages;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.io.Serializable; import java.io.Serializable;
@ -9,13 +10,13 @@ import java.util.Objects;
import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.User;
public class DirectThread implements Serializable { public class DirectThread implements Serializable, Cloneable {
private final String threadId; private final String threadId;
private final String threadV2Id; private final String threadV2Id;
private final List<User> users; private List<User> users;
private final List<User> leftUsers; private List<User> leftUsers;
private final List<Long> adminUserIds; private List<Long> adminUserIds;
private final List<DirectItem> items; private List<DirectItem> items;
private final long lastActivityAt; private final long lastActivityAt;
private boolean muted; private boolean muted;
private final boolean isPin; private final boolean isPin;
@ -127,18 +128,34 @@ public class DirectThread implements Serializable {
return users; return users;
} }
public void setUsers(final List<User> users) {
this.users = users;
}
public List<User> getLeftUsers() { public List<User> getLeftUsers() {
return leftUsers; return leftUsers;
} }
public void setLeftUsers(final List<User> leftUsers) {
this.leftUsers = leftUsers;
}
public List<Long> getAdminUserIds() { public List<Long> getAdminUserIds() {
return adminUserIds; return adminUserIds;
} }
public void setAdminUserIds(final List<Long> adminUserIds) {
this.adminUserIds = adminUserIds;
}
public List<DirectItem> getItems() { public List<DirectItem> getItems() {
return items; return items;
} }
public void setItems(final List<DirectItem> items) {
this.items = items;
}
public long getLastActivityAt() { public long getLastActivityAt() {
return lastActivityAt; return lastActivityAt;
} }
@ -284,17 +301,59 @@ public class DirectThread implements Serializable {
return firstItem; return firstItem;
} }
@NonNull
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override @Override
public boolean equals(final Object o) { public boolean equals(final Object o) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
final DirectThread that = (DirectThread) o; final DirectThread that = (DirectThread) o;
return Objects.equals(threadId, that.threadId) && return lastActivityAt == that.lastActivityAt &&
Objects.equals(threadV2Id, that.threadV2Id); 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 @Override
public int hashCode() { 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; package awais.instagrabber.repositories.responses.directmessages;
import java.util.List; import java.util.List;
import java.util.Objects;
public class DirectThreadDirectStory { public class DirectThreadDirectStory {
private final List<DirectItem> items; private final List<DirectItem> items;
@ -18,4 +19,18 @@ public class DirectThreadDirectStory {
public int getUnseenCount() { public int getUnseenCount() {
return unseenCount; 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; package awais.instagrabber.repositories.responses.directmessages;
import java.util.Objects;
public class DirectThreadLastSeenAt { public class DirectThreadLastSeenAt {
private final String timestamp; private final String timestamp;
private final String itemId; private final String itemId;
@ -16,4 +18,18 @@ public class DirectThreadLastSeenAt {
public String getItemId() { public String getItemId() {
return itemId; 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 Map<Long, String> requesterUsernames;
private final String cursor; private final String cursor;
private final int totalThreadParticipants; private final int totalThreadParticipants;
private final int totalParticipantRequests; private int totalParticipantRequests;
private final String status; private final String status;
public DirectThreadParticipantRequestsResponse(final List<User> users, public DirectThreadParticipantRequestsResponse(final List<User> users,
@ -54,6 +54,10 @@ public class DirectThreadParticipantRequestsResponse implements Serializable, Cl
return totalParticipantRequests; return totalParticipantRequests;
} }
public void setTotalParticipantRequests(final int totalParticipantRequests) {
this.totalParticipantRequests = totalParticipantRequests;
}
public String getStatus() { public String getStatus() {
return status; return status;
} }

View File

@ -2,6 +2,8 @@ package awais.instagrabber.repositories.responses.directmessages;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import java.util.Objects;
public final class RavenExpiringMediaActionSummary { public final class RavenExpiringMediaActionSummary {
private final ActionType type; private final ActionType type;
private final long timestamp; private final long timestamp;
@ -25,6 +27,21 @@ public final class RavenExpiringMediaActionSummary {
return type; 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 // thanks to http://github.com/warifp/InstagramAutoPostImageUrl/blob/master/vendor/mgp25/instagram-php/src/Response/Model/ActionBadge.php
public enum ActionType { public enum ActionType {
@SerializedName("raven_delivered") @SerializedName("raven_delivered")

View File

@ -1,6 +1,7 @@
package awais.instagrabber.repositories.responses.directmessages; package awais.instagrabber.repositories.responses.directmessages;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects;
public class ThreadContext implements Serializable { public class ThreadContext implements Serializable {
private final int type; private final int type;
@ -18,4 +19,18 @@ public class ThreadContext implements Serializable {
public String getText() { public String getText() {
return text; 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.net.Uri;
import android.util.Log; import android.util.Log;
import android.util.LruCache; import android.util.LruCache;
import android.util.Pair;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; 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.User;
import awais.instagrabber.repositories.responses.VideoVersion; import awais.instagrabber.repositories.responses.VideoVersion;
import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDirectStory;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadLastSeenAt; import awais.instagrabber.repositories.responses.directmessages.DirectThreadLastSeenAt;
import awaisomereport.LogCollector; import awaisomereport.LogCollector;
@ -1126,9 +1125,12 @@ public final class ResponseBodyUtils {
public static boolean isRead(final DirectItem item, public static boolean isRead(final DirectItem item,
final Map<Long, DirectThreadLastSeenAt> lastSeenAt, final Map<Long, DirectThreadLastSeenAt> lastSeenAt,
final List<Long> userIdsToCheck, final List<Long> userIdsToCheck) {
final DirectThreadDirectStory directStory) { // Further check if directStory exists
boolean read = lastSeenAt.entrySet() // if (read && directStory != null) {
// read = false;
// }
return lastSeenAt.entrySet()
.stream() .stream()
.filter(entry -> userIdsToCheck.contains(entry.getKey())) .filter(entry -> userIdsToCheck.contains(entry.getKey()))
.anyMatch(entry -> { .anyMatch(entry -> {
@ -1138,11 +1140,6 @@ public final class ResponseBodyUtils {
final long itemTs = item.getTimestamp(); final long itemTs = item.getTimestamp();
return userTs >= itemTs; return userTs >= itemTs;
}); });
// Further check if directStory exists
if (read && directStory != null) {
read = false;
}
return read;
} }
public static StoryModel parseBroadcastItem(final JSONObject data) throws JSONException { public static StoryModel parseBroadcastItem(final JSONObject data) throws JSONException {

View File

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

View File

@ -1,188 +1,56 @@
package awais.instagrabber.viewmodels; package awais.instagrabber.viewmodels;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List; 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.User;
import awais.instagrabber.repositories.responses.directmessages.DirectBadgeCount;
import awais.instagrabber.repositories.responses.directmessages.DirectInbox; import awais.instagrabber.repositories.responses.directmessages.DirectInbox;
import awais.instagrabber.repositories.responses.directmessages.DirectInboxResponse;
import awais.instagrabber.repositories.responses.directmessages.DirectThread; 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 { public class DirectInboxViewModel extends ViewModel {
private static final String TAG = DirectInboxViewModel.class.getSimpleName(); private static final String TAG = DirectInboxViewModel.class.getSimpleName();
private final DirectMessagesService service; private final InboxManager inboxManager;
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;
public DirectInboxViewModel() { public DirectInboxViewModel() {
final String cookie = settingsHelper.getString(Constants.COOKIE); final DirectMessagesManager messagesManager = DirectMessagesManager.getInstance();
final long userId = CookieUtils.getUserIdFromCookie(cookie); inboxManager = messagesManager.getInboxManager();
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(); public LiveData<Resource<DirectInbox>> getInbox() {
fetchUnseenCount(); return inboxManager.getInbox();
} }
public LiveData<List<DirectThread>> getThreads() { public LiveData<List<DirectThread>> getThreads() {
return threads; return inboxManager.getThreads();
} }
public void setThreads(final List<DirectThread> threads) { public LiveData<Resource<Integer>> getUnseenCount() {
this.threads.postValue(threads); return inboxManager.getUnseenCount();
}
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<Integer> getPendingRequestsTotal() { public LiveData<Integer> getPendingRequestsTotal() {
return pendingRequestsTotal; return inboxManager.getPendingRequestsTotal();
} }
public User getViewer() { public User getViewer() {
return viewer; return inboxManager.getViewer();
} }
public void fetchInbox() { public void fetchInbox() {
if ((fetchingInbox.getValue() != null && fetchingInbox.getValue()) || !hasOlder) return; inboxManager.fetchInbox();
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;
} }
public void refresh() { public void refresh() {
cursor = null; inboxManager.refresh();
seqId = 0;
hasOlder = true;
fetchInbox();
fetchUnseenCount();
} }
public void onDestroy() { public void onDestroy() {
stopCurrentInboxRequest(); inboxManager.onDestroy();
// getThreads().postValue(Collections.emptyList());
} }
} }

View File

@ -1,141 +1,48 @@
package awais.instagrabber.viewmodels; package awais.instagrabber.viewmodels;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List; 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.User;
import awais.instagrabber.repositories.responses.directmessages.DirectBadgeCount;
import awais.instagrabber.repositories.responses.directmessages.DirectInbox; import awais.instagrabber.repositories.responses.directmessages.DirectInbox;
import awais.instagrabber.repositories.responses.directmessages.DirectInboxResponse;
import awais.instagrabber.repositories.responses.directmessages.DirectThread; 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 { public class DirectPendingInboxViewModel extends ViewModel {
private static final String TAG = DirectPendingInboxViewModel.class.getSimpleName(); private static final String TAG = DirectPendingInboxViewModel.class.getSimpleName();
private final DirectMessagesService service; private final InboxManager inboxManager;
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;
public DirectPendingInboxViewModel() { public DirectPendingInboxViewModel() {
final String cookie = settingsHelper.getString(Constants.COOKIE); inboxManager = DirectMessagesManager.getInstance().getPendingInboxManager();
final long userId = CookieUtils.getUserIdFromCookie(cookie); inboxManager.fetchInbox();
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();
} }
public LiveData<List<DirectThread>> getThreads() { public LiveData<List<DirectThread>> getThreads() {
return threads; return inboxManager.getThreads();
} }
public void setThreads(final List<DirectThread> threads) { public LiveData<Resource<DirectInbox>> getInbox() {
this.threads.postValue(threads); return inboxManager.getInbox();
}
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 User getViewer() { public User getViewer() {
return viewer; return inboxManager.getViewer();
} }
public void fetchInbox() { public void fetchInbox() {
if ((fetchingInbox.getValue() != null && fetchingInbox.getValue()) || !hasOlder) return; inboxManager.fetchInbox();
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;
} }
public void refresh() { public void refresh() {
cursor = null; inboxManager.refresh();
seqId = 0;
hasOlder = true;
fetchInbox();
} }
public void onDestroy() { public void onDestroy() {
stopCurrentInboxRequest(); inboxManager.onDestroy();
} }
} }

View File

@ -1,49 +1,30 @@
package awais.instagrabber.viewmodels; package awais.instagrabber.viewmodels;
import android.app.Application; import android.app.Application;
import android.content.ContentResolver;
import android.content.res.Resources; import android.content.res.Resources;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.core.util.Pair; import androidx.core.util.Pair;
import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData; 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.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.dialogs.MultiOptionDialogFragment.Option; import awais.instagrabber.dialogs.MultiOptionDialogFragment.Option;
import awais.instagrabber.managers.DirectMessagesManager;
import awais.instagrabber.managers.ThreadManager;
import awais.instagrabber.models.Resource; 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.User;
import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDetailsChangeResponse;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadParticipantRequestsResponse; import awais.instagrabber.repositories.responses.directmessages.DirectThreadParticipantRequestsResponse;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.TextUtils; 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; 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_RESTRICT = "restrict";
private static final String ACTION_UNRESTRICT = "unrestrict"; private static final String ACTION_UNRESTRICT = "unrestrict";
private final MutableLiveData<Pair<List<User>, List<User>>> users = new MutableLiveData<>( private final long viewerId;
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 Resources resources; private final Resources resources;
private final FriendshipService friendshipService; private final ThreadManager threadManager;
private final String csrfToken;
private DirectThread thread; public DirectSettingsViewModel(final Application application,
private boolean viewerIsAdmin; @NonNull final String threadId,
private User viewer; final boolean pending,
@NonNull final User currentUser) {
public DirectSettingsViewModel(final Application application) {
super(application); super(application);
final String cookie = settingsHelper.getString(Constants.COOKIE); final String cookie = settingsHelper.getString(Constants.COOKIE);
userId = CookieUtils.getUserIdFromCookie(cookie); viewerId = CookieUtils.getUserIdFromCookie(cookie);
final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID); final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID);
csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
if (TextUtils.isEmpty(csrfToken) || userId <= 0 || TextUtils.isEmpty(deviceUuid)) { if (TextUtils.isEmpty(csrfToken) || viewerId <= 0 || TextUtils.isEmpty(deviceUuid)) {
throw new IllegalArgumentException("User is not logged in!"); throw new IllegalArgumentException("User is not logged in!");
} }
directMessagesService = DirectMessagesService.getInstance(csrfToken, userId, deviceUuid); final ContentResolver contentResolver = application.getContentResolver();
friendshipService = FriendshipService.getInstance(deviceUuid, csrfToken, userId);
resources = getApplication().getResources(); resources = getApplication().getResources();
final DirectMessagesManager messagesManager = DirectMessagesManager.getInstance();
threadManager = messagesManager.getThreadManager(threadId, pending, currentUser, contentResolver);
} }
@NonNull @NonNull
public DirectThread getThread() { public LiveData<DirectThread> getThread() {
return thread; return threadManager.getThread();
} }
public void setThread(@NonNull final DirectThread thread) { // public void setThread(@NonNull final DirectThread thread) {
this.thread = thread; // this.thread = thread;
inputMode.postValue(thread.getInputMode()); // inputMode.postValue(thread.getInputMode());
List<User> users = thread.getUsers(); // List<User> users = thread.getUsers();
if (viewer != null) { // final ImmutableList.Builder<User> builder = ImmutableList.<User>builder().add(currentUser);
final ImmutableList.Builder<User> builder = ImmutableList.<User>builder().add(viewer); // if (users != null) {
if (users != null) { // builder.addAll(users);
builder.addAll(users); // }
} // users = builder.build();
users = builder.build(); // this.users.postValue(new Pair<>(users, thread.getLeftUsers()));
} // // setTitle(thread.getThreadTitle());
this.users.postValue(new Pair<>(users, thread.getLeftUsers())); // final List<Long> adminUserIds = thread.getAdminUserIds();
setTitle(thread.getThreadTitle()); // this.adminUserIds.postValue(adminUserIds);
final List<Long> adminUserIds = thread.getAdminUserIds(); // viewerIsAdmin = adminUserIds.contains(viewerId);
this.adminUserIds.postValue(adminUserIds); // muted.postValue(thread.isMuted());
viewerIsAdmin = adminUserIds.contains(userId); // mentionsMuted.postValue(thread.isMentionsMuted());
muted.postValue(thread.isMuted()); // approvalRequiredToJoin.postValue(thread.isApprovalRequiredForNewMembers());
mentionsMuted.postValue(thread.isMentionsMuted()); // isPending.postValue(thread.isPending());
approvalRequiredToJoin.postValue(thread.isApprovalRequiredForNewMembers()); // if (thread.getInputMode() != 1 && thread.isGroup() && viewerIsAdmin) {
isPending.postValue(thread.isPending()); // fetchPendingRequests();
if (thread.getInputMode() != 1 && thread.isGroup() && viewerIsAdmin) { // }
fetchPendingRequests(); // }
}
}
public LiveData<Integer> getInputMode() { public LiveData<Integer> getInputMode() {
return inputMode; return threadManager.getInputMode();
} }
public boolean isGroup() { public LiveData<Boolean> isGroup() {
if (thread != null) { return threadManager.isGroup();
return thread.isGroup();
}
return false;
} }
public LiveData<Pair<List<User>, List<User>>> getUsers() { public LiveData<List<User>> getUsers() {
return users; 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() { public LiveData<String> getTitle() {
return title; return threadManager.getThreadTitle();
} }
public void setTitle(final String title) { // public void setTitle(final String title) {
if (title == null) { // if (title == null) {
this.title.postValue(""); // this.title.postValue("");
return; // return;
} // }
this.title.postValue(title.trim()); // this.title.postValue(title.trim());
} // }
public LiveData<List<Long>> getAdminUserIds() { public LiveData<List<Long>> getAdminUserIds() {
return adminUserIds; return threadManager.getAdminUserIds();
} }
public LiveData<Boolean> getMuted() { public LiveData<Boolean> isMuted() {
return muted; return threadManager.isMuted();
} }
public LiveData<Boolean> getApprovalRequiredToJoin() { public LiveData<Boolean> getApprovalRequiredToJoin() {
return approvalRequiredToJoin; return threadManager.isApprovalRequiredToJoin();
} }
public LiveData<DirectThreadParticipantRequestsResponse> getPendingRequests() { public LiveData<DirectThreadParticipantRequestsResponse> getPendingRequests() {
return pendingRequests; return threadManager.getPendingRequests();
} }
public LiveData<Boolean> isPending() { public LiveData<Boolean> isPending() {
return isPending; return threadManager.isPending();
} }
public boolean isViewerAdmin() { public LiveData<Boolean> isViewerAdmin() {
return viewerIsAdmin; return threadManager.isViewerAdmin();
} }
public LiveData<Resource<Object>> updateTitle(final String newTitle) { public LiveData<Resource<Object>> updateTitle(final String newTitle) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.updateTitle(newTitle);
final Call<DirectThreadDetailsChangeResponse> addUsersRequest = directMessagesService
.updateTitle(thread.getThreadId(), newTitle.trim());
handleDetailsChangeRequest(data, addUsersRequest);
return data;
} }
public LiveData<Resource<Object>> addMembers(final Set<User> users) { public LiveData<Resource<Object>> addMembers(final Set<User> users) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.addMembers(users);
final Call<DirectThreadDetailsChangeResponse> addUsersRequest = directMessagesService
.addUsers(thread.getThreadId(), users.stream().map(User::getPk).collect(Collectors.toList()));
handleDetailsChangeRequest(data, addUsersRequest);
return data;
} }
public LiveData<Resource<Object>> removeMember(final User user) { public LiveData<Resource<Object>> removeMember(final User user) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.removeMember(user);
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;
} }
private LiveData<Resource<Object>> makeAdmin(final User user) { private LiveData<Resource<Object>> makeAdmin(final User user) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.makeAdmin(user);
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;
} }
private LiveData<Resource<Object>> removeAdmin(final User user) { private LiveData<Resource<Object>> removeAdmin(final User user) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.removeAdmin(user);
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;
} }
public LiveData<Resource<Object>> mute() { public LiveData<Resource<Object>> mute() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.mute();
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;
} }
public LiveData<Resource<Object>> unmute() { public LiveData<Resource<Object>> unmute() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.unmute();
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;
} }
public LiveData<Resource<Object>> muteMentions() { public LiveData<Resource<Object>> muteMentions() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.muteMentions();
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;
} }
public LiveData<Resource<Object>> unmuteMentions() { public LiveData<Resource<Object>> unmuteMentions() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.unmuteMentions();
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));
}
} }
private LiveData<Resource<Object>> blockUser(final User user) { private LiveData<Resource<Object>> blockUser(final User user) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.blockUser(user);
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;
} }
private LiveData<Resource<Object>> unblockUser(final User user) { private LiveData<Resource<Object>> unblockUser(final User user) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.unblockUser(user);
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;
} }
private LiveData<Resource<Object>> restrictUser(final User user) { private LiveData<Resource<Object>> restrictUser(final User user) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.restrictUser(user);
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;
} }
private LiveData<Resource<Object>> unRestrictUser(final User user) { private LiveData<Resource<Object>> unRestrictUser(final User user) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.unRestrictUser(user);
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;
} }
public LiveData<Resource<Object>> approveUsers(final List<User> users) { public LiveData<Resource<Object>> approveUsers(final List<User> users) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.approveUsers(users);
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;
} }
public LiveData<Resource<Object>> denyUsers(final List<User> users) { public LiveData<Resource<Object>> denyUsers(final List<User> users) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.denyUsers(users);
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;
} }
public LiveData<Resource<Object>> approvalRequired() { public LiveData<Resource<Object>> approvalRequired() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.approvalRequired();
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;
} }
public LiveData<Resource<Object>> approvalNotRequired() { public LiveData<Resource<Object>> approvalNotRequired() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.approvalNotRequired();
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;
} }
public LiveData<Resource<Object>> leave() { public LiveData<Resource<Object>> leave() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.leave();
data.postValue(Resource.loading(null));
final Call<DirectThreadDetailsChangeResponse> request = directMessagesService.leave(thread.getThreadId());
handleDetailsChangeRequest(data, request);
return data;
} }
public LiveData<Resource<Object>> end() { public LiveData<Resource<Object>> end() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); return threadManager.end();
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));
}
} }
public ArrayList<Option<String>> createUserOptions(final User user) { public ArrayList<Option<String>> createUserOptions(final User user) {
@ -632,10 +226,11 @@ public class DirectSettingsViewModel extends AndroidViewModel {
if (user == null || isSelf(user) || hasLeft(user)) { if (user == null || isSelf(user) || hasLeft(user)) {
return options; 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)); 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<>( options.add(new Option<>(
isAdmin ? getString(R.string.dms_action_remove_admin) : getString(R.string.dms_action_make_admin), isAdmin ? getString(R.string.dms_action_remove_admin) : getString(R.string.dms_action_make_admin),
isAdmin ? ACTION_REMOVE_ADMIN : 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)); // options.add(new Option<>(getString(R.string.report), ACTION_REPORT));
final Boolean isGroup = threadManager.isGroup().getValue();
if (!isGroup()) { if (isGroup != null && isGroup) {
final boolean restricted = user.getFriendshipStatus().isRestricted(); final boolean restricted = user.getFriendshipStatus().isRestricted();
options.add(new Option<>( options.add(new Option<>(
restricted ? getString(R.string.unrestrict) : getString(R.string.restrict), restricted ? getString(R.string.unrestrict) : getString(R.string.restrict),
@ -661,18 +256,13 @@ public class DirectSettingsViewModel extends AndroidViewModel {
} }
private boolean hasLeft(final User user) { private boolean hasLeft(final User user) {
final Pair<List<User>, List<User>> users = this.users.getValue(); final List<User> leftUsers = getLeftUsers().getValue();
if (users == null || users.second == null) return false; if (leftUsers == null) return false;
return users.second.contains(user); return leftUsers.contains(user);
}
private boolean isAdmin(final User user) {
final List<Long> adminUserIdsValue = adminUserIds.getValue();
return adminUserIdsValue != null && adminUserIdsValue.contains(user.getPk());
} }
private boolean isSelf(final User user) { private boolean isSelf(final User user) {
return user.getPk() == userId; return user.getPk() == viewerId;
} }
private String getString(@StringRes final int resId) { private String getString(@StringRes final int resId) {
@ -703,47 +293,7 @@ public class DirectSettingsViewModel extends AndroidViewModel {
} }
} }
public void setViewer(final User viewer) { public LiveData<User> getInviter() {
this.viewer = viewer; return threadManager.getInviter();
}
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);
}
});
} }
} }

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" android:paddingBottom="?attr/actionBarSize"
tools:listitem="@layout/layout_dm_inbox_item" /> tools:listitem="@layout/layout_dm_inbox_item" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </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> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

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

View File

@ -5,7 +5,7 @@ buildscript {
} }
dependencies { 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" def nav_version = "2.3.2"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
} }