diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java index 545b68f4..5d086d53 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java @@ -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> 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); diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java index 7162940a..f29dd393 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java @@ -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 approvalRequiredUsers; - // private List> 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 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 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 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); diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java index c2d2982f..0eb2fc61 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java @@ -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 threads = threadListViewModel.getThreads().getValue(); + final List 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 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> resourceLiveData = viewModel.acceptRequest(); + handlePendingChangeResource(resourceLiveData, false); + }); + binding.decline.setOnClickListener(v -> { + final LiveData> resourceLiveData = viewModel.declineRequest(); + handlePendingChangeResource(resourceLiveData, true); + }); + } + + private void handlePendingChangeResource(final LiveData> 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") diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.java new file mode 100644 index 00000000..fc702ab3 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.java @@ -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> 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); + } +} diff --git a/app/src/main/java/awais/instagrabber/repositories/DirectMessagesRepository.java b/app/src/main/java/awais/instagrabber/repositories/DirectMessagesRepository.java index 21e26ae7..0db1c42e 100644 --- a/app/src/main/java/awais/instagrabber/repositories/DirectMessagesRepository.java +++ b/app/src/main/java/awais/instagrabber/repositories/DirectMessagesRepository.java @@ -24,6 +24,9 @@ public interface DirectMessagesRepository { @GET("/api/v1/direct_v2/inbox/") Call fetchInbox(@QueryMap Map queryMap); + @GET("/api/v1/direct_v2/pending_inbox/") + Call fetchPendingInbox(@QueryMap Map queryMap); + @GET("/api/v1/direct_v2/threads/{threadId}/") Call fetchThread(@Path("threadId") String threadId, @QueryMap Map queryMap); @@ -132,4 +135,14 @@ public interface DirectMessagesRepository { @POST("/api/v1/direct_v2/threads/{threadId}/remove_all_users/") Call end(@Path("threadId") String threadId, @FieldMap final Map form); + + @FormUrlEncoded + @POST("/api/v1/direct_v2/threads/{threadId}/approve/") + Call approveRequest(@Path("threadId") String threadId, + @FieldMap final Map form); + + @FormUrlEncoded + @POST("/api/v1/direct_v2/threads/{threadId}/decline/") + Call declineRequest(@Path("threadId") String threadId, + @FieldMap final Map form); } diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectInboxResponse.java b/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectInboxResponse.java index 1a8a5401..9db0b2a5 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectInboxResponse.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectInboxResponse.java @@ -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; } diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectThread.java b/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectThread.java index 97d5f763..0e58f08e 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectThread.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectThread.java @@ -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 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 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 getThreadContextItems() { + return threadContextItems; + } + @Nullable public DirectItem getFirstDirectItem() { DirectItem firstItem = null; diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/ThreadContext.java b/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/ThreadContext.java new file mode 100644 index 00000000..d48c2b74 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/ThreadContext.java @@ -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; + } +} diff --git a/app/src/main/java/awais/instagrabber/utils/DirectItemFactory.java b/app/src/main/java/awais/instagrabber/utils/DirectItemFactory.java index 8d7954c3..4b319032 100644 --- a/app/src/main/java/awais/instagrabber/utils/DirectItemFactory.java +++ b/app/src/main/java/awais/instagrabber/utils/DirectItemFactory.java @@ -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, diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectInboxViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectInboxViewModel.java index 9351a579..e012c040 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectInboxViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectInboxViewModel.java @@ -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> threads = new MutableLiveData<>(); private final MutableLiveData fetchingUnseenCount = new MutableLiveData<>(false); private final MutableLiveData unseenCount = new MutableLiveData<>(0); + private final MutableLiveData pendingRequestsTotal = new MutableLiveData<>(0); private Call inboxRequest; private Call unseenCountRequest; @@ -54,12 +56,12 @@ public class DirectInboxViewModel extends ViewModel { fetchUnseenCount(); } - public MutableLiveData> getThreads() { + public LiveData> getThreads() { return threads; } public void setThreads(final List threads) { - getThreads().postValue(threads); + this.threads.postValue(threads); } public void addThreads(final Collection threads) { @@ -70,14 +72,18 @@ public class DirectInboxViewModel extends ViewModel { this.threads.postValue(list); } - public MutableLiveData getUnseenCount() { + public LiveData getUnseenCount() { return unseenCount; } - public MutableLiveData getFetchingInbox() { + public LiveData getFetchingInbox() { return fetchingInbox; } + public LiveData 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()); } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectPendingInboxViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectPendingInboxViewModel.java new file mode 100644 index 00000000..f6972e9a --- /dev/null +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectPendingInboxViewModel.java @@ -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 fetchingInbox = new MutableLiveData<>(false); + private final MutableLiveData> threads = new MutableLiveData<>(); + + private Call inboxRequest; + private Call 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> getThreads() { + return threads; + } + + public void setThreads(final List threads) { + this.threads.postValue(threads); + } + + public void addThreads(final Collection threads) { + if (threads == null) return; + List list = getThreads().getValue(); + list = list == null ? new LinkedList<>() : new LinkedList<>(list); + list.addAll(threads); + this.threads.postValue(list); + } + + public LiveData 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() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + parseInboxResponse(response.body()); + fetchingInbox.postValue(false); + } + + @Override + public void onFailure(@NonNull final Call 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 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(); + } +} diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectSettingsViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectSettingsViewModel.java index cdb209e7..c65b6c80 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectSettingsViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectSettingsViewModel.java @@ -67,6 +67,7 @@ public class DirectSettingsViewModel extends AndroidViewModel { private final MutableLiveData approvalRequiredToJoin = new MutableLiveData<>(false); private final MutableLiveData pendingRequests = new MutableLiveData<>(null); private final MutableLiveData inputMode = new MutableLiveData<>(null); + private final MutableLiveData 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 isPending() { + return isPending; + } + public boolean isViewerAdmin() { return viewerIsAdmin; } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java index 11f71ebe..a12e3dbe 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java @@ -85,6 +85,7 @@ public class DirectThreadViewModel extends AndroidViewModel { private final MutableLiveData replyToItem = new MutableLiveData<>(); private final MutableLiveData pendingRequestsCount = new MutableLiveData<>(null); private final MutableLiveData inputMode = new MutableLiveData<>(0); + private final MutableLiveData 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 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 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> acceptRequest() { + final MutableLiveData> data = new MutableLiveData<>(); + final Call request = service.approveRequest(threadId); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, + @NonNull final Response 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 call, @NonNull final Throwable t) { + Log.e(TAG, "onFailure: ", t); + data.postValue(Resource.error(t.getMessage(), null)); + } + }); + return data; + } + + public LiveData> declineRequest() { + final MutableLiveData> data = new MutableLiveData<>(); + final Call request = service.declineRequest(threadId); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, + @NonNull final Response 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 call, @NonNull final Throwable t) { + Log.e(TAG, "onFailure: ", t); + data.postValue(Resource.error(t.getMessage(), null)); + } + }); + return data; + } } diff --git a/app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java b/app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java index 984df066..c4db80f2 100644 --- a/app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java +++ b/app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java @@ -409,4 +409,36 @@ public class DirectMessagesService extends BaseService { ); return repository.end(threadId, form); } + + public Call fetchPendingInbox(final String cursor, final long seqId) { + final ImmutableMap.Builder queryMapBuilder = ImmutableMap.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 approveRequest(@NonNull final String threadId) { + final ImmutableMap form = ImmutableMap.of( + "_csrftoken", csrfToken, + "_uuid", deviceUuid + ); + return repository.approveRequest(threadId, form); + } + + public Call declineRequest(@NonNull final String threadId) { + final ImmutableMap form = ImmutableMap.of( + "_csrftoken", csrfToken, + "_uuid", deviceUuid + ); + return repository.declineRequest(threadId, form); + } } diff --git a/app/src/main/res/drawable/ic_account_clock_24.xml b/app/src/main/res/drawable/ic_account_clock_24.xml new file mode 100644 index 00000000..41f4ed9b --- /dev/null +++ b/app/src/main/res/drawable/ic_account_clock_24.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_direct_messages_thread.xml b/app/src/main/res/layout/fragment_direct_messages_thread.xml index 88b3aa31..79a1091c 100644 --- a/app/src/main/res/layout/fragment_direct_messages_thread.xml +++ b/app/src/main/res/layout/fragment_direct_messages_thread.xml @@ -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" /> + + @@ -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" /> + tools: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" /> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_direct_pending_inbox.xml b/app/src/main/res/layout/fragment_direct_pending_inbox.xml new file mode 100644 index 00000000..dfb19d63 --- /dev/null +++ b/app/src/main/res/layout/fragment_direct_pending_inbox.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/app/src/main/res/navigation/direct_messages_nav_graph.xml b/app/src/main/res/navigation/direct_messages_nav_graph.xml index 819a428c..5cea2822 100644 --- a/app/src/main/res/navigation/direct_messages_nav_graph.xml +++ b/app/src/main/res/navigation/direct_messages_nav_graph.xml @@ -80,25 +80,7 @@ - - - - - - - - - - - - - - - - - - + app:destination="@id/user_search_nav_graph" /> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index c5ed4d4c..26a474c0 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -3,4 +3,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1e8edf4c..6f6f6d39 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -418,4 +418,8 @@ End chat End chat? All members will be removed from the group. They will still be able to view the chat history. + Pending Requests + Accept request from %1s (%2s)? + Decline + Accept