Ammar Githam
4 years ago
20 changed files with 794 additions and 95 deletions
-
68app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java
-
53app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java
-
123app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
-
152app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.java
-
13app/src/main/java/awais/instagrabber/repositories/DirectMessagesRepository.java
-
7app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectInboxResponse.java
-
15app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectThread.java
-
21app/src/main/java/awais/instagrabber/repositories/responses/directmessages/ThreadContext.java
-
2app/src/main/java/awais/instagrabber/utils/DirectItemFactory.java
-
15app/src/main/java/awais/instagrabber/viewmodels/DirectInboxViewModel.java
-
141app/src/main/java/awais/instagrabber/viewmodels/DirectPendingInboxViewModel.java
-
6app/src/main/java/awais/instagrabber/viewmodels/DirectSettingsViewModel.java
-
77app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java
-
32app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java
-
11app/src/main/res/drawable/ic_account_clock_24.xml
-
78app/src/main/res/layout/fragment_direct_messages_thread.xml
-
23app/src/main/res/layout/fragment_direct_pending_inbox.xml
-
43app/src/main/res/navigation/direct_messages_nav_graph.xml
-
1app/src/main/res/values/ids.xml
-
4app/src/main/res/values/strings.xml
@ -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); |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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(); |
||||
|
} |
||||
|
} |
@ -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> |
@ -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> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue