Add pending inbox

This commit is contained in:
Ammar Githam 2021-01-24 23:39:46 +09:00
parent 6aee7ea863
commit 5e3aed38b9
20 changed files with 794 additions and 95 deletions

View File

@ -1,11 +1,15 @@
package awais.instagrabber.fragments.directmessages;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@ -17,11 +21,13 @@ import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.badge.BadgeDrawable;
import com.google.android.material.badge.BadgeUtils;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.util.List;
@ -41,7 +47,6 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
private CoordinatorLayout root;
private RecyclerLazyLoaderAtEdge lazyLoader;
private DirectInboxViewModel viewModel;
// private boolean refreshInbox = false;
private boolean shouldRefresh = true;
private FragmentDirectMessagesInboxBinding binding;
private DMRefreshBroadcastReceiver receiver;
@ -50,6 +55,9 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
private boolean scrollToTop = false;
private boolean navigating;
private Observer<List<DirectThread>> threadsObserver;
private MenuItem pendingRequestsMenuItem;
private BadgeDrawable pendingRequestTotalBadgeDrawable;
private boolean isPendingRequestTotalBadgeAttached;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -60,6 +68,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
viewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
}
setHasOptionsMenu(true);
}
@Override
@ -99,7 +108,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
@Override
public void onResume() {
super.onResume();
observeViewModel();
setupObservers();
final Context context = getContext();
if (context == null) return;
receiver = new DMRefreshBroadcastReceiver(() -> {
@ -109,10 +118,36 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
context.registerReceiver(receiver, new IntentFilter(DMRefreshBroadcastReceiver.ACTION_REFRESH_DM));
}
@SuppressLint("UnsafeExperimentalUsageError")
@Override
public void onDestroyView() {
super.onDestroyView();
unregisterReceiver();
isPendingRequestTotalBadgeAttached = false;
if (pendingRequestTotalBadgeDrawable != null) {
BadgeUtils.detachBadgeDrawable(pendingRequestTotalBadgeDrawable, fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId());
pendingRequestTotalBadgeDrawable = null;
}
}
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
pendingRequestsMenuItem = menu.add(Menu.NONE, R.id.pending_requests, Menu.NONE, "Pending requests");
pendingRequestsMenuItem.setIcon(R.drawable.ic_account_clock_24)
.setVisible(false)
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS);
attachPendingRequestsBadge(viewModel.getPendingRequestsTotal().getValue());
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == R.id.pending_requests) {
final NavDirections directions = DirectMessageInboxFragmentDirections.actionInboxToPendingInbox();
NavHostFragment.findNavController(this).navigate(directions);
return true;
}
return super.onOptionsItemSelected(item);
}
private void unregisterReceiver() {
@ -136,7 +171,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
viewModel.onDestroy();
}
private void observeViewModel() {
private void setupObservers() {
threadsObserver = list -> {
if (inboxAdapter == null) return;
inboxAdapter.submitList(list, () -> {
@ -148,6 +183,31 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
viewModel.getThreads().observe(fragmentActivity, threadsObserver);
viewModel.getFetchingInbox().observe(getViewLifecycleOwner(), fetching -> binding.swipeRefreshLayout.setRefreshing(fetching));
viewModel.getUnseenCount().observe(getViewLifecycleOwner(), this::setBottomNavBarBadge);
viewModel.getPendingRequestsTotal().observe(getViewLifecycleOwner(), this::attachPendingRequestsBadge);
}
@SuppressLint("UnsafeExperimentalUsageError")
private void attachPendingRequestsBadge(@Nullable final Integer count) {
if (pendingRequestsMenuItem == null) return;
if (pendingRequestTotalBadgeDrawable == null) {
final Context context = getContext();
if (context == null) return;
pendingRequestTotalBadgeDrawable = BadgeDrawable.create(context);
}
if (count == null || count == 0) {
BadgeUtils.detachBadgeDrawable(pendingRequestTotalBadgeDrawable, fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId());
isPendingRequestTotalBadgeAttached = false;
pendingRequestTotalBadgeDrawable.setNumber(0);
pendingRequestsMenuItem.setVisible(false);
return;
}
pendingRequestsMenuItem.setVisible(true);
if (pendingRequestTotalBadgeDrawable.getNumber() == count) return;
pendingRequestTotalBadgeDrawable.setNumber(count);
if (!isPendingRequestTotalBadgeAttached) {
BadgeUtils.attachBadgeDrawable(pendingRequestTotalBadgeDrawable, fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId());
isPendingRequestTotalBadgeAttached = true;
}
}
private void removeViewModelObservers() {
@ -161,7 +221,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
private void init() {
final Context context = getContext();
if (context == null) return;
observeViewModel();
setupObservers();
binding.swipeRefreshLayout.setOnRefreshListener(this);
binding.inboxList.setHasFixedSize(true);
binding.inboxList.setItemViewCacheSize(20);

View File

@ -56,6 +56,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadParticipantRequestsResponse;
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
import awais.instagrabber.viewmodels.DirectInboxViewModel;
import awais.instagrabber.viewmodels.DirectPendingInboxViewModel;
import awais.instagrabber.viewmodels.DirectSettingsViewModel;
public class DirectMessageSettingsFragment extends Fragment implements ConfirmDialogFragmentCallback {
@ -70,22 +71,28 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
private boolean isPendingRequestsSetupDone = false;
private DirectPendingUsersAdapter pendingUsersAdapter;
private Set<User> approvalRequiredUsers;
// private List<Option<String>> options;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Bundle arguments = getArguments();
if (arguments == null) return;
final NavController navController = NavHostFragment.findNavController(this);
final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
final DirectInboxViewModel inboxViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
final List<DirectThread> threads = inboxViewModel.getThreads().getValue();
final Bundle arguments = getArguments();
if (arguments == null) {
navController.navigateUp();
return;
final DirectMessageSettingsFragmentArgs args = DirectMessageSettingsFragmentArgs.fromBundle(arguments);
final boolean pending = args.getPending();
final List<DirectThread> threads;
final User viewer;
if (pending) {
final DirectPendingInboxViewModel inboxViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectPendingInboxViewModel.class);
threads = inboxViewModel.getThreads().getValue();
viewer = inboxViewModel.getViewer();
} else {
final DirectInboxViewModel inboxViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
threads = inboxViewModel.getThreads().getValue();
viewer = inboxViewModel.getViewer();
}
final DirectMessageSettingsFragmentArgs fragmentArgs = DirectMessageSettingsFragmentArgs.fromBundle(arguments);
final String threadId = fragmentArgs.getThreadId();
final String threadId = args.getThreadId();
final Optional<DirectThread> first = threads != null ? threads.stream()
.filter(thread -> thread.getThreadId().equals(threadId))
.findFirst()
@ -95,7 +102,7 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
return;
}
viewModel = new ViewModelProvider(this).get(DirectSettingsViewModel.class);
viewModel.setViewer(inboxViewModel.getViewer());
viewModel.setViewer(viewer);
viewModel.setThread(first.get());
}
@ -105,37 +112,14 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
final ViewGroup container,
final Bundle savedInstanceState) {
binding = FragmentDirectMessagesSettingsBinding.inflate(inflater, container, false);
// final String threadId = DirectMessageSettingsFragmentArgs.fromBundle(getArguments()).getThreadId();
// threadTitle = DirectMessageSettingsFragmentArgs.fromBundle(getArguments()).getTitle();
// binding.swipeRefreshLayout.setEnabled(false);
// final ActionBar actionBar = fragmentActivity.getSupportActionBar();
// if (actionBar != null) {
// actionBar.setTitle(threadTitle);
// }
// titleSend.setOnClickListener(v -> new ChangeSettings(titleText.getText().toString()).execute("update_title"));
// binding.titleText.addTextChangedListener(new TextWatcherAdapter() {
// @Override
// public void onTextChanged(CharSequence s, int start, int before, int count) {
// binding.titleSend.setVisibility(s.toString().equals(threadTitle) ? View.GONE : View.VISIBLE);
// }
// });
// final AppCompatButton btnLeave = binding.btnLeave;
// btnLeave.setOnClickListener(v -> new AlertDialog.Builder(context)
// .setTitle(R.string.dms_action_leave_question)
// .setPositiveButton(R.string.yes, (x, y) -> new ChangeSettings(titleText.getText().toString()).execute("leave"))
// .setNegativeButton(R.string.no, null)
// .show());
// currentlyRunning = new DirectMessageInboxThreadFetcher(threadId, null, null, fetchListener).execute();
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
final Bundle arguments = getArguments();
if (arguments == null) return;
init();
setupObservers();
}
@ -169,6 +153,7 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
usersAdapter.setAdminUserIds(adminUserIds);
});
viewModel.getMuted().observe(getViewLifecycleOwner(), muted -> binding.muteMessages.setChecked(muted));
viewModel.isPending().observe(getViewLifecycleOwner(), pending -> binding.muteMessages.setVisibility(pending ? View.GONE : View.VISIBLE));
if (viewModel.isViewerAdmin()) {
viewModel.getApprovalRequiredToJoin().observe(getViewLifecycleOwner(), required -> binding.approvalRequired.setChecked(required));
viewModel.getPendingRequests().observe(getViewLifecycleOwner(), this::setPendingRequests);

View File

@ -105,6 +105,7 @@ import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.AppStateViewModel;
import awais.instagrabber.viewmodels.DirectInboxViewModel;
import awais.instagrabber.viewmodels.DirectPendingInboxViewModel;
import awais.instagrabber.viewmodels.DirectThreadViewModel;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
@ -347,9 +348,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
final int itemId = item.getItemId();
if (itemId == R.id.info) {
final NavDirections action = DirectMessageThreadFragmentDirections
.actionDMThreadFragmentToDMSettingsFragment(viewModel.getThreadId(), null);
NavHostFragment.findNavController(this).navigate(action);
final DirectMessageThreadFragmentDirections.ActionThreadToSettings directions = DirectMessageThreadFragmentDirections
.actionThreadToSettings(viewModel.getThreadId(), null);
final Boolean pending = viewModel.isPending().getValue();
directions.setPending(pending == null ? false : pending);
NavHostFragment.findNavController(this).navigate(directions);
return true;
}
if (itemId == R.id.mark_as_seen) {
@ -470,10 +473,20 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
}
private void getInitialData() {
final Bundle arguments = getArguments();
if (arguments == null) return;
final DirectMessageThreadFragmentArgs args = DirectMessageThreadFragmentArgs.fromBundle(arguments);
final boolean pending = args.getPending();
final NavController navController = NavHostFragment.findNavController(this);
final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
final DirectInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
final List<DirectThread> threads = threadListViewModel.getThreads().getValue();
final List<DirectThread> threads;
if (!pending) {
final DirectInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
threads = threadListViewModel.getThreads().getValue();
} else {
final DirectPendingInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectPendingInboxViewModel.class);
threads = threadListViewModel.getThreads().getValue();
}
final Optional<DirectThread> first = threads != null
? threads.stream()
.filter(thread -> thread.getThreadId().equals(viewModel.getThreadId()))
@ -529,19 +542,26 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
}
private void setObservers() {
viewModel.isPending().observe(getViewLifecycleOwner(), isPending -> {
if (isPending == null) {
hideInput();
return;
}
if (isPending) {
showPendingOptions();
return;
}
hidePendingOptions();
final Integer inputMode = viewModel.getInputMode().getValue();
if (inputMode != null && inputMode == 1) return;
showInput();
});
viewModel.getInputMode().observe(getViewLifecycleOwner(), inputMode -> {
final Boolean isPending = viewModel.isPending().getValue();
if (isPending != null && isPending) return;
if (inputMode == null || inputMode == 0) return;
if (inputMode == 1) {
binding.emojiToggle.setVisibility(View.GONE);
binding.camera.setVisibility(View.GONE);
binding.gallery.setVisibility(View.GONE);
binding.input.setVisibility(View.GONE);
binding.inputBg.setVisibility(View.GONE);
binding.recordView.setVisibility(View.GONE);
binding.send.setVisibility(View.GONE);
if (itemTouchHelper != null) {
itemTouchHelper.attachToRecyclerView(null);
}
hideInput();
}
});
viewModel.getThreadTitle().observe(getViewLifecycleOwner(), this::setTitle);
@ -614,6 +634,81 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
prevLength = length;
});
viewModel.getPendingRequestsCount().observe(getViewLifecycleOwner(), this::attachPendingRequestsBadge);
viewModel.getUsers().observe(getViewLifecycleOwner(), users -> {
if (users == null || users.isEmpty()) return;
final User user = users.get(0);
binding.acceptPendingRequestQuestion.setText(getString(R.string.accept_request_from_user, user.getUsername(), user.getFullName()));
});
}
private void hidePendingOptions() {
binding.acceptPendingRequestQuestion.setVisibility(View.GONE);
binding.decline.setVisibility(View.GONE);
binding.accept.setVisibility(View.GONE);
}
private void showPendingOptions() {
binding.acceptPendingRequestQuestion.setVisibility(View.VISIBLE);
binding.decline.setVisibility(View.VISIBLE);
binding.accept.setVisibility(View.VISIBLE);
binding.accept.setOnClickListener(v -> {
final LiveData<Resource<Object>> resourceLiveData = viewModel.acceptRequest();
handlePendingChangeResource(resourceLiveData, false);
});
binding.decline.setOnClickListener(v -> {
final LiveData<Resource<Object>> resourceLiveData = viewModel.declineRequest();
handlePendingChangeResource(resourceLiveData, true);
});
}
private void handlePendingChangeResource(final LiveData<Resource<Object>> resourceLiveData, final boolean isDecline) {
resourceLiveData.observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return;
final Resource.Status status = resource.status;
switch (status) {
case SUCCESS:
resourceLiveData.removeObservers(getViewLifecycleOwner());
if (isDecline) {
final NavController navController = NavHostFragment.findNavController(this);
navController.navigateUp();
}
break;
case LOADING:
break;
case ERROR:
if (resource.message != null) {
Snackbar.make(binding.getRoot(), resource.message, Snackbar.LENGTH_LONG).show();
}
resourceLiveData.removeObservers(getViewLifecycleOwner());
break;
}
});
}
private void hideInput() {
binding.emojiToggle.setVisibility(View.GONE);
binding.camera.setVisibility(View.GONE);
binding.gallery.setVisibility(View.GONE);
binding.input.setVisibility(View.GONE);
binding.inputBg.setVisibility(View.GONE);
binding.recordView.setVisibility(View.GONE);
binding.send.setVisibility(View.GONE);
if (itemTouchHelper != null) {
itemTouchHelper.attachToRecyclerView(null);
}
}
private void showInput() {
binding.emojiToggle.setVisibility(View.VISIBLE);
binding.camera.setVisibility(View.VISIBLE);
binding.gallery.setVisibility(View.VISIBLE);
binding.input.setVisibility(View.VISIBLE);
binding.inputBg.setVisibility(View.VISIBLE);
binding.recordView.setVisibility(View.VISIBLE);
binding.send.setVisibility(View.VISIBLE);
if (itemTouchHelper != null) {
itemTouchHelper.attachToRecyclerView(binding.chats);
}
}
@SuppressLint("UnsafeExperimentalUsageError")

View File

@ -0,0 +1,152 @@
package awais.instagrabber.fragments.directmessages;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.DirectMessageInboxAdapter;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge;
import awais.instagrabber.databinding.FragmentDirectPendingInboxBinding;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.viewmodels.DirectPendingInboxViewModel;
public class DirectPendingInboxFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = DirectPendingInboxFragment.class.getSimpleName();
private CoordinatorLayout root;
private RecyclerLazyLoaderAtEdge lazyLoader;
private DirectPendingInboxViewModel viewModel;
private boolean shouldRefresh = true;
private FragmentDirectPendingInboxBinding binding;
private DirectMessageInboxAdapter inboxAdapter;
private MainActivity fragmentActivity;
private boolean scrollToTop = false;
private boolean navigating;
private Observer<List<DirectThread>> threadsObserver;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) getActivity();
if (fragmentActivity != null) {
final NavController navController = NavHostFragment.findNavController(this);
final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
viewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectPendingInboxViewModel.class);
}
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) {
if (root != null) {
shouldRefresh = false;
return root;
}
binding = FragmentDirectPendingInboxBinding.inflate(inflater, container, false);
root = binding.getRoot();
return root;
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
init();
}
@Override
public void onRefresh() {
lazyLoader.resetState();
scrollToTop = true;
if (viewModel != null) {
viewModel.refresh();
}
}
@Override
public void onResume() {
super.onResume();
setupObservers();
}
@Override
public void onConfigurationChanged(@NonNull final Configuration newConfig) {
super.onConfigurationChanged(newConfig);
init();
}
@Override
public void onDestroy() {
super.onDestroy();
removeViewModelObservers();
viewModel.onDestroy();
}
private void setupObservers() {
threadsObserver = list -> {
if (inboxAdapter == null) return;
inboxAdapter.submitList(list, () -> {
if (!scrollToTop) return;
binding.pendingInboxList.smoothScrollToPosition(0);
scrollToTop = false;
});
};
viewModel.getThreads().observe(fragmentActivity, threadsObserver);
viewModel.getFetchingInbox().observe(getViewLifecycleOwner(), fetching -> binding.swipeRefreshLayout.setRefreshing(fetching));
}
private void removeViewModelObservers() {
if (viewModel == null) return;
if (threadsObserver != null) {
viewModel.getThreads().removeObserver(threadsObserver);
}
}
private void init() {
final Context context = getContext();
if (context == null) return;
setupObservers();
binding.swipeRefreshLayout.setOnRefreshListener(this);
binding.pendingInboxList.setHasFixedSize(true);
binding.pendingInboxList.setItemViewCacheSize(20);
final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
binding.pendingInboxList.setLayoutManager(layoutManager);
inboxAdapter = new DirectMessageInboxAdapter(thread -> {
if (navigating) return;
navigating = true;
if (isAdded()) {
final DirectPendingInboxFragmentDirections.ActionPendingInboxToThread directions = DirectPendingInboxFragmentDirections
.actionPendingInboxToThread(thread.getThreadId(), thread.getThreadTitle());
directions.setPending(true);
NavHostFragment.findNavController(this).navigate(directions);
}
navigating = false;
});
inboxAdapter.setHasStableIds(true);
binding.pendingInboxList.setAdapter(inboxAdapter);
lazyLoader = new RecyclerLazyLoaderAtEdge(layoutManager, page -> {
if (viewModel == null) return;
viewModel.fetchInbox();
});
binding.pendingInboxList.addOnScrollListener(lazyLoader);
}
}

View File

@ -24,6 +24,9 @@ public interface DirectMessagesRepository {
@GET("/api/v1/direct_v2/inbox/")
Call<DirectInboxResponse> fetchInbox(@QueryMap Map<String, Object> queryMap);
@GET("/api/v1/direct_v2/pending_inbox/")
Call<DirectInboxResponse> fetchPendingInbox(@QueryMap Map<String, Object> queryMap);
@GET("/api/v1/direct_v2/threads/{threadId}/")
Call<DirectThreadFeedResponse> fetchThread(@Path("threadId") String threadId,
@QueryMap Map<String, Object> queryMap);
@ -132,4 +135,14 @@ public interface DirectMessagesRepository {
@POST("/api/v1/direct_v2/threads/{threadId}/remove_all_users/")
Call<DirectThreadDetailsChangeResponse> end(@Path("threadId") String threadId,
@FieldMap final Map<String, String> form);
@FormUrlEncoded
@POST("/api/v1/direct_v2/threads/{threadId}/approve/")
Call<String> approveRequest(@Path("threadId") String threadId,
@FieldMap final Map<String, String> form);
@FormUrlEncoded
@POST("/api/v1/direct_v2/threads/{threadId}/decline/")
Call<String> declineRequest(@Path("threadId") String threadId,
@FieldMap final Map<String, String> form);
}

View File

@ -8,6 +8,7 @@ public class DirectInboxResponse {
private final long seqId;
private final long snapshotAtMs;
private final int pendingRequestsTotal;
private final boolean hasPendingTopRequests;
private final User mostRecentInviter;
private final String status;
@ -16,6 +17,7 @@ public class DirectInboxResponse {
final long seqId,
final long snapshotAtMs,
final int pendingRequestsTotal,
final boolean hasPendingTopRequests,
final User mostRecentInviter,
final String status) {
this.viewer = viewer;
@ -23,6 +25,7 @@ public class DirectInboxResponse {
this.seqId = seqId;
this.snapshotAtMs = snapshotAtMs;
this.pendingRequestsTotal = pendingRequestsTotal;
this.hasPendingTopRequests = hasPendingTopRequests;
this.mostRecentInviter = mostRecentInviter;
this.status = status;
}
@ -47,6 +50,10 @@ public class DirectInboxResponse {
return pendingRequestsTotal;
}
public boolean hasPendingTopRequests() {
return hasPendingTopRequests;
}
public User getMostRecentInviter() {
return mostRecentInviter;
}

View File

@ -21,7 +21,7 @@ public class DirectThread implements Serializable {
private final boolean isPin;
private final boolean named;
private final boolean canonical;
private final boolean pending;
private boolean pending;
private final boolean archived;
private final boolean valuedRequest;
private final String threadType;
@ -43,6 +43,7 @@ public class DirectThread implements Serializable {
private final DirectThreadDirectStory directStory;
private boolean approvalRequiredForNewMembers;
private int inputMode;
private final List<ThreadContext> threadContextItems;
public DirectThread(final String threadId,
final String threadV2Id,
@ -76,7 +77,8 @@ public class DirectThread implements Serializable {
final DirectItem lastPermanentItem,
final DirectThreadDirectStory directStory,
final boolean approvalRequiredForNewMembers,
final int inputMode) {
final int inputMode,
final List<ThreadContext> threadContextItems) {
this.threadId = threadId;
this.threadV2Id = threadV2Id;
this.users = users;
@ -110,6 +112,7 @@ public class DirectThread implements Serializable {
this.directStory = directStory;
this.approvalRequiredForNewMembers = approvalRequiredForNewMembers;
this.inputMode = inputMode;
this.threadContextItems = threadContextItems;
}
public String getThreadId() {
@ -164,6 +167,10 @@ public class DirectThread implements Serializable {
return pending;
}
public void setPending(final boolean pending) {
this.pending = pending;
}
public boolean isArchived() {
return archived;
}
@ -260,6 +267,10 @@ public class DirectThread implements Serializable {
this.inputMode = inputMode;
}
public List<ThreadContext> getThreadContextItems() {
return threadContextItems;
}
@Nullable
public DirectItem getFirstDirectItem() {
DirectItem firstItem = null;

View File

@ -0,0 +1,21 @@
package awais.instagrabber.repositories.responses.directmessages;
import java.io.Serializable;
public class ThreadContext implements Serializable {
private final int type;
private final String text;
public ThreadContext(final int type, final String text) {
this.type = type;
this.text = text;
}
public int getType() {
return type;
}
public String getText() {
return text;
}
}

View File

@ -16,7 +16,7 @@ import awais.instagrabber.repositories.responses.VideoVersion;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemVoiceMedia;
public class DirectItemFactory {
public final class DirectItemFactory {
public static DirectItem createText(final long userId,
final String clientContext,

View File

@ -3,6 +3,7 @@ package awais.instagrabber.viewmodels;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
@ -33,6 +34,7 @@ public class DirectInboxViewModel extends ViewModel {
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;
@ -54,12 +56,12 @@ public class DirectInboxViewModel extends ViewModel {
fetchUnseenCount();
}
public MutableLiveData<List<DirectThread>> getThreads() {
public LiveData<List<DirectThread>> getThreads() {
return threads;
}
public void setThreads(final List<DirectThread> threads) {
getThreads().postValue(threads);
this.threads.postValue(threads);
}
public void addThreads(final Collection<DirectThread> threads) {
@ -70,14 +72,18 @@ public class DirectInboxViewModel extends ViewModel {
this.threads.postValue(list);
}
public MutableLiveData<Integer> getUnseenCount() {
public LiveData<Integer> getUnseenCount() {
return unseenCount;
}
public MutableLiveData<Boolean> getFetchingInbox() {
public LiveData<Boolean> getFetchingInbox() {
return fetchingInbox;
}
public LiveData<Integer> getPendingRequestsTotal() {
return pendingRequestsTotal;
}
public User getViewer() {
return viewer;
}
@ -126,6 +132,7 @@ public class DirectInboxViewModel extends ViewModel {
}
cursor = inbox.getOldestCursor();
hasOlder = inbox.hasOlder();
pendingRequestsTotal.postValue(response.getPendingRequestsTotal());
// unseenCount.postValue(inbox.getUnseenCount());
}

View File

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

View File

@ -67,6 +67,7 @@ public class DirectSettingsViewModel extends AndroidViewModel {
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;
@ -115,6 +116,7 @@ public class DirectSettingsViewModel extends AndroidViewModel {
muted.postValue(thread.isMuted());
mentionsMuted.postValue(thread.isMentionsMuted());
approvalRequiredToJoin.postValue(thread.isApprovalRequiredForNewMembers());
isPending.postValue(thread.isPending());
if (thread.getInputMode() != 1 && thread.isGroup() && viewerIsAdmin) {
fetchPendingRequests();
}
@ -163,6 +165,10 @@ public class DirectSettingsViewModel extends AndroidViewModel {
return pendingRequests;
}
public LiveData<Boolean> isPending() {
return isPending;
}
public boolean isViewerAdmin() {
return viewerIsAdmin;
}

View File

@ -85,6 +85,7 @@ public class DirectThreadViewModel extends AndroidViewModel {
private final MutableLiveData<DirectItem> replyToItem = new MutableLiveData<>();
private final MutableLiveData<Integer> pendingRequestsCount = new MutableLiveData<>(null);
private final MutableLiveData<Integer> inputMode = new MutableLiveData<>(0);
private final MutableLiveData<Boolean> isPending = new MutableLiveData<>(null);
private final DirectMessagesService service;
private final ContentResolver contentResolver;
@ -340,6 +341,10 @@ public class DirectThreadViewModel extends AndroidViewModel {
return inputMode;
}
public LiveData<Boolean> isPending() {
return isPending;
}
public void fetchChats() {
final Boolean isFetching = fetching.getValue();
if ((isFetching != null && isFetching) || !hasOlder) return;
@ -404,6 +409,7 @@ public class DirectThreadViewModel extends AndroidViewModel {
users.postValue(thread.getUsers());
leftUsers.postValue(thread.getLeftUsers());
fetching.postValue(false);
isPending.postValue(thread.isPending());
final List<Long> adminUserIds = thread.getAdminUserIds();
viewerIsAdmin = adminUserIds.contains(viewerId);
if (thread.getInputMode() != 1 && thread.isGroup() && viewerIsAdmin) {
@ -1105,4 +1111,75 @@ public class DirectThreadViewModel extends AndroidViewModel {
}
});
}
public LiveData<Resource<Object>> acceptRequest() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
final Call<String> request = service.approveRequest(threadId);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call,
@NonNull final Response<String> response) {
if (!response.isSuccessful()) {
try {
final String string = response.errorBody() != null ? 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);
data.postValue(Resource.error(msg, null));
return;
} catch (IOException e) {
Log.e(TAG, "onResponse: ", e);
}
return;
}
isPending.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>> declineRequest() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
final Call<String> request = service.declineRequest(threadId);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call,
@NonNull final Response<String> response) {
if (!response.isSuccessful()) {
try {
final String string = response.errorBody() != null ? 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);
data.postValue(Resource.error(msg, null));
return;
} catch (IOException e) {
Log.e(TAG, "onResponse: ", e);
}
return;
}
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;
}
}

View File

@ -409,4 +409,36 @@ public class DirectMessagesService extends BaseService {
);
return repository.end(threadId, form);
}
public Call<DirectInboxResponse> fetchPendingInbox(final String cursor, final long seqId) {
final ImmutableMap.Builder<String, Object> queryMapBuilder = ImmutableMap.<String, Object>builder()
.put("visual_message_return_type", "unseen")
.put("thread_message_limit", 10)
.put("persistentBadging", true)
.put("limit", 10);
if (!TextUtils.isEmpty(cursor)) {
queryMapBuilder.put("cursor", cursor);
queryMapBuilder.put("direction", "older");
}
if (seqId != 0) {
queryMapBuilder.put("seq_id", seqId);
}
return repository.fetchPendingInbox(queryMapBuilder.build());
}
public Call<String> approveRequest(@NonNull final String threadId) {
final ImmutableMap<String, String> form = ImmutableMap.of(
"_csrftoken", csrfToken,
"_uuid", deviceUuid
);
return repository.approveRequest(threadId, form);
}
public Call<String> declineRequest(@NonNull final String threadId) {
final ImmutableMap<String, String> form = ImmutableMap.of(
"_csrftoken", csrfToken,
"_uuid", deviceUuid
);
return repository.declineRequest(threadId, form);
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M10.63,14.1C12.23,10.58 16.38,9.03 19.9,10.63C23.42,12.23 24.97,16.38 23.37,19.9C22.24,22.4 19.75,24 17,24C14.3,24 11.83,22.44 10.67,20H1V18C1.06,16.86 1.84,15.93 3.34,15.18C4.84,14.43 6.72,14.04 9,14C9.57,14 10.11,14.05 10.63,14.1V14.1M9,4C10.12,4.03 11.06,4.42 11.81,5.17C12.56,5.92 12.93,6.86 12.93,8C12.93,9.14 12.56,10.08 11.81,10.83C11.06,11.58 10.12,11.95 9,11.95C7.88,11.95 6.94,11.58 6.19,10.83C5.44,10.08 5.07,9.14 5.07,8C5.07,6.86 5.44,5.92 6.19,5.17C6.94,4.42 7.88,4.03 9,4M17,22A5,5 0 0,0 22,17A5,5 0 0,0 17,12A5,5 0 0,0 12,17A5,5 0 0,0 17,22M16,14H17.5V16.82L19.94,18.23L19.19,19.53L16,17.69V14Z" />
</vector>

View File

@ -11,12 +11,18 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:scrollbars="none"
app:layout_constraintBottom_toTopOf="@id/reply_info"
app:layout_constraintBottom_toTopOf="@id/chats_barrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/layout_dm_base" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/chats_barrier"
android:layout_width="0dp"
android:layout_height="0dp"
app:barrierDirection="bottom" />
<View
android:id="@+id/reply_bg"
android:layout_width="0dp"
@ -44,7 +50,7 @@
app:layout_constraintBottom_toTopOf="@id/reply_preview_text"
app:layout_constraintEnd_toStartOf="@id/reply_preview_image"
app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toBottomOf="@id/chats"
app:layout_constraintTop_toBottomOf="@id/chats_barrier"
tools:text="Replying to yourself"
tools:visibility="gone" />
@ -110,6 +116,7 @@
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:background="@drawable/bg_input"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input"
app:layout_constraintEnd_toStartOf="@id/send"
app:layout_constraintStart_toStartOf="parent"
@ -124,6 +131,7 @@
android:layout_marginEnd="2dp"
android:background="@android:color/transparent"
android:scrollbars="none"
android:visibility="gone"
app:icon="@drawable/ic_face_24"
app:iconGravity="textStart"
app:iconSize="24dp"
@ -148,6 +156,7 @@
android:paddingBottom="12dp"
android:textColor="@color/white"
android:textColorHint="@color/grey_500"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/camera"
app:layout_constraintStart_toEndOf="@id/emoji_toggle"
@ -163,12 +172,13 @@
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:scaleType="fitCenter"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/gallery"
app:layout_constraintStart_toEndOf="@id/input"
app:layout_constraintTop_toTopOf="@id/input"
app:srcCompat="@drawable/ic_camera_24"
tools:visibility="visible" />
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/gallery"
@ -181,22 +191,23 @@
android:paddingEnd="4dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_image_24"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/send"
app:layout_constraintStart_toEndOf="@id/camera"
app:layout_constraintTop_toTopOf="@id/input"
tools:visibility="visible" />
tools:visibility="gone" />
<awais.instagrabber.customviews.RecordView
android:id="@+id/record_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="visible"
android:visibility="gone"
app:counter_time_color="@color/white"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toEndOf="@id/input_bg"
app:layout_constraintStart_toStartOf="@id/input"
app:layout_constraintTop_toBottomOf="@id/chats"
app:layout_constraintTop_toBottomOf="@id/chats_barrier"
app:slide_to_cancel_arrow="@drawable/recv_ic_arrow"
app:slide_to_cancel_arrow_color="@color/white"
app:slide_to_cancel_bounds="0dp"
@ -210,6 +221,7 @@
style="@style/Widget.MaterialComponents.Button.Icon.NoInsets"
android:layout_width="48dp"
android:layout_height="48dp"
android:visibility="gone"
app:backgroundTint="@color/blue_900"
app:elevation="4dp"
app:icon="@drawable/avd_mic_to_send_anim"
@ -226,17 +238,55 @@
android:layout_width="0dp"
android:layout_height="250dp"
android:translationY="250dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<View
android:id="@+id/long_click_backdrop"
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accept_pending_request_question"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:elevation="5dp"
android:focusable="true"
android:focusableInTouchMode="true"
android:visibility="gone" />
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="8dp"
android:text="@string/accept_request_from_user"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/decline"
app:layout_constraintTop_toBottomOf="@id/chats_barrier"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/decline"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:padding="16dp"
android:text="@string/decline"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/red_500"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/accept"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accept_pending_request_question"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accept"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:padding="16dp"
android:text="@string/accept"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/decline"
app:layout_constraintTop_toBottomOf="@id/accept_pending_request_question"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/pending_inbox_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="?attr/actionBarSize"
tools:listitem="@layout/layout_dm_inbox_item" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -80,25 +80,7 @@
<action
android:id="@+id/action_global_user_search"
app:destination="@id/user_search_nav_graph">
<!--<argument-->
<!-- android:name="multiple"-->
<!-- app:argType="boolean" />-->
<!--<argument-->
<!-- android:name="title"-->
<!-- app:argType="string"-->
<!-- app:nullable="true" />-->
<!--<argument-->
<!-- android:name="action_label"-->
<!-- app:argType="string"-->
<!-- app:nullable="true" />-->
<!--<argument-->
<!-- android:name="hideUserIds"-->
<!-- app:argType="long[]" />-->
</action>
app:destination="@id/user_search_nav_graph" />
<fragment
android:id="@+id/directMessagesInboxFragment"
@ -108,6 +90,9 @@
<action
android:id="@+id/action_inbox_to_thread"
app:destination="@id/directMessagesThreadFragment" />
<action
android:id="@+id/action_inbox_to_pending_inbox"
app:destination="@id/directPendingInboxFragment" />
</fragment>
<fragment
android:id="@+id/directMessagesThreadFragment"
@ -119,8 +104,12 @@
<argument
android:name="title"
app:argType="string" />
<argument
android:name="pending"
android:defaultValue="false"
app:argType="boolean" />
<action
android:id="@+id/action_dMThreadFragment_to_dMSettingsFragment"
android:id="@+id/action_thread_to_settings"
app:destination="@id/directMessagesSettingsFragment" />
<action
android:id="@+id/action_thread_to_image_edit"
@ -144,6 +133,11 @@
app:argType="string"
app:nullable="true" />
<argument
android:name="pending"
android:defaultValue="false"
app:argType="boolean" />
<action
android:id="@+id/action_settings_to_inbox"
app:destination="@id/directMessagesInboxFragment"
@ -170,4 +164,13 @@
android:name="options"
app:argType="awais.instagrabber.repositories.requests.StoryViewerOptions" />
</fragment>
<fragment
android:id="@+id/directPendingInboxFragment"
android:name="awais.instagrabber.fragments.directmessages.DirectPendingInboxFragment"
android:label="@string/pending_requests"
tools:layout="@layout/fragment_direct_pending_inbox">
<action
android:id="@+id/action_pending_inbox_to_thread"
app:destination="@id/directMessagesThreadFragment" />
</fragment>
</navigation>

View File

@ -3,4 +3,5 @@
<item name="reply" type="id" />
<item name="unsend" type="id" />
<item name="forward" type="id" />
<item name="pending_requests" type="id" />
</resources>

View File

@ -418,4 +418,8 @@
<string name="dms_action_end">End chat</string>
<string name="dms_action_end_question">End chat?</string>
<string name="dms_action_end_description">All members will be removed from the group. They will still be able to view the chat history.</string>
<string name="pending_requests">Pending Requests</string>
<string name="accept_request_from_user">Accept request from %1s (%2s)?</string>
<string name="decline">Decline</string>
<string name="accept">Accept</string>
</resources>