package org.edx.mobile.view;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.google.inject.Inject;
import org.edx.mobile.R;
import org.edx.mobile.base.BaseFragment;
import org.edx.mobile.base.BaseFragmentActivity;
import org.edx.mobile.discussion.DiscussionComment;
import org.edx.mobile.discussion.DiscussionCommentPostedEvent;
import org.edx.mobile.discussion.DiscussionRequestFields;
import org.edx.mobile.discussion.DiscussionService;
import org.edx.mobile.discussion.DiscussionService.ReadBody;
import org.edx.mobile.discussion.DiscussionThread;
import org.edx.mobile.discussion.DiscussionThreadUpdatedEvent;
import org.edx.mobile.discussion.DiscussionUtils;
import org.edx.mobile.http.CallTrigger;
import org.edx.mobile.http.ErrorHandlingCallback;
import org.edx.mobile.model.Page;
import org.edx.mobile.model.api.EnrolledCoursesResponse;
import org.edx.mobile.module.analytics.ISegment;
import org.edx.mobile.view.adapters.CourseDiscussionResponsesAdapter;
import org.edx.mobile.view.adapters.InfiniteScrollUtils;
import org.edx.mobile.view.common.TaskProgressCallback;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.greenrobot.event.EventBus;
import retrofit2.Call;
import roboguice.RoboGuice;
import roboguice.inject.InjectExtra;
import roboguice.inject.InjectView;
public class CourseDiscussionResponsesFragment extends BaseFragment implements CourseDiscussionResponsesAdapter.Listener {
@InjectView(R.id.discussion_recycler_view)
private RecyclerView discussionResponsesRecyclerView;
@InjectView(R.id.create_new_item_text_view)
private TextView addResponseTextView;
@InjectView(R.id.create_new_item_layout)
private ViewGroup addResponseLayout;
@InjectExtra(Router.EXTRA_DISCUSSION_THREAD)
private DiscussionThread discussionThread;
@InjectExtra(value = Router.EXTRA_COURSE_DATA, optional = true)
private EnrolledCoursesResponse courseData;
private CourseDiscussionResponsesAdapter courseDiscussionResponsesAdapter;
@Inject
private DiscussionService discussionService;
@Inject
private Router router;
@Inject
ISegment segIO;
@Nullable
private Call<DiscussionThread> getAndReadThreadCall;
private InfiniteScrollUtils.InfiniteListController controller;
private ResponsesLoader responsesLoader;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_discussion_responses_or_comments, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
responsesLoader = new ResponsesLoader(getActivity(),
discussionThread.getIdentifier(),
discussionThread.getType() == DiscussionThread.ThreadType.QUESTION);
courseDiscussionResponsesAdapter = new CourseDiscussionResponsesAdapter(
getActivity(), this, discussionThread);
controller = InfiniteScrollUtils.configureRecyclerViewWithInfiniteList(
discussionResponsesRecyclerView, courseDiscussionResponsesAdapter, responsesLoader);
discussionResponsesRecyclerView.setAdapter(courseDiscussionResponsesAdapter);
responsesLoader.freeze();
if (getAndReadThreadCall != null) {
getAndReadThreadCall.cancel();
}
getAndReadThreadCall = discussionService.setThreadRead(
discussionThread.getIdentifier(), new ReadBody(true));
// Setting a thread's "read" state gives us back the updated Thread object.
getAndReadThreadCall.enqueue(new ErrorHandlingCallback<DiscussionThread>(getContext(),
CallTrigger.LOADING_UNCACHED, (TaskProgressCallback) null) {
@Override
protected void onResponse(@NonNull final DiscussionThread discussionThread) {
courseDiscussionResponsesAdapter.updateDiscussionThread(discussionThread);
responsesLoader.unfreeze();
EventBus.getDefault().post(new DiscussionThreadUpdatedEvent(discussionThread));
}
});
DiscussionUtils.setStateOnTopicClosed(discussionThread.isClosed(),
addResponseTextView, R.string.discussion_responses_add_response_button,
R.string.discussion_add_response_disabled_title, addResponseLayout,
new View.OnClickListener() {
@Override
public void onClick(View v) {
router.showCourseDiscussionAddResponse(getActivity(), discussionThread);
}
});
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EventBus.getDefault().register(this);
Map<String, String> values = new HashMap<>();
values.put(ISegment.Keys.TOPIC_ID, discussionThread.getTopicId());
values.put(ISegment.Keys.THREAD_ID, discussionThread.getIdentifier());
segIO.trackScreenView(ISegment.Screens.FORUM_VIEW_THREAD,
courseData.getCourse().getId(), discussionThread.getTitle(), values);
}
@Override
public void onDestroy() {
super.onDestroy();
responsesLoader.reset();
EventBus.getDefault().unregister(this);
}
@SuppressWarnings("unused")
public void onEventMainThread(DiscussionCommentPostedEvent event) {
if (discussionThread.containsComment(event.getComment())) {
if (event.getParent() == null) {
// We got a response
((BaseFragmentActivity) getActivity()).showInfoMessage(getString(R.string.discussion_response_posted));
if (!responsesLoader.hasMorePages()) {
courseDiscussionResponsesAdapter.addNewResponse(event.getComment());
discussionResponsesRecyclerView.smoothScrollToPosition(
courseDiscussionResponsesAdapter.getItemCount() - 1);
} else {
// We still need to update the response count locally
courseDiscussionResponsesAdapter.incrementResponseCount();
}
} else {
// We got a comment to a response
if (event.getParent().getChildCount() == 0) {
// We only need to show this message when the first comment is added
((BaseFragmentActivity) getActivity()).showInfoMessage(getString(R.string.discussion_comment_posted));
}
courseDiscussionResponsesAdapter.addNewComment(event.getParent());
}
}
}
@Override
public void onClickAuthor(@NonNull String username) {
router.showUserProfile(getActivity(), username);
}
@Override
public void onClickAddComment(@NonNull DiscussionComment response) {
router.showCourseDiscussionAddComment(getActivity(), response, discussionThread);
}
@Override
public void onClickViewComments(@NonNull DiscussionComment response) {
router.showCourseDiscussionComments(getActivity(), response, discussionThread);
}
private static class ResponsesLoader implements
InfiniteScrollUtils.PageLoader<DiscussionComment> {
@NonNull
private final Context context;
@NonNull
private final String threadId;
private final boolean isQuestionTypeThread;
private boolean hasMorePages = true;
@Inject
private DiscussionService discussionService;
@Nullable
private Call<Page<DiscussionComment>> getResponsesListCall;
private int nextPage = 1;
private boolean isFetchingEndorsed;
private boolean isFrozen;
private Runnable deferredDeliveryRunnable;
public ResponsesLoader(@NonNull Context context, @NonNull String threadId,
boolean isQuestionTypeThread) {
this.context = context;
this.threadId = threadId;
this.isQuestionTypeThread = isQuestionTypeThread;
this.isFetchingEndorsed = isQuestionTypeThread;
RoboGuice.injectMembers(context, this);
}
@Override
public void loadNextPage(@NonNull final InfiniteScrollUtils.PageLoadCallback<DiscussionComment> callback) {
if (getResponsesListCall != null) {
getResponsesListCall.cancel();
}
final List<String> requestedFields = Collections.singletonList(
DiscussionRequestFields.PROFILE_IMAGE.getQueryParamValue());
if (isQuestionTypeThread) {
getResponsesListCall = discussionService.getResponsesListForQuestion(
threadId, nextPage, isFetchingEndorsed, requestedFields);
} else {
getResponsesListCall = discussionService.getResponsesList(
threadId, nextPage, requestedFields);
}
getResponsesListCall.enqueue(new ErrorHandlingCallback<Page<DiscussionComment>>(context,
CallTrigger.LOADING_UNCACHED, (TaskProgressCallback) null) {
@Override
protected void onResponse(
@NonNull final Page<DiscussionComment> threadResponsesPage) {
final Runnable deliverResultRunnable = new Runnable() {
@Override
public void run() {
if (isFetchingEndorsed) {
boolean hasMoreEndorsed = threadResponsesPage.hasNext();
if (hasMoreEndorsed) {
++nextPage;
} else {
isFetchingEndorsed = false;
nextPage = 1;
}
final List<DiscussionComment> endorsedResponses =
threadResponsesPage.getResults();
if (hasMoreEndorsed || !endorsedResponses.isEmpty()) {
callback.onPageLoaded(endorsedResponses);
} else {
// If there are no endorsed responses, then just start
// loading the unendorsed ones without triggering the
// callback, since that would just cause the controller
// to wait for the scroll listener to be invoked, which
// would not happen automatically without any changes
// in the adapter dataset.
loadNextPage(callback);
}
} else {
++nextPage;
callback.onPageLoaded(threadResponsesPage);
hasMorePages = threadResponsesPage.hasNext();
}
}
};
if (isFrozen) {
deferredDeliveryRunnable = deliverResultRunnable;
} else {
deliverResultRunnable.run();
}
}
@Override
protected void onFailure(@NonNull final Throwable error) {
callback.onError();
nextPage = 1;
hasMorePages = false;
}
});
}
public void freeze() {
isFrozen = true;
}
public void unfreeze() {
if (isFrozen) {
isFrozen = false;
if (deferredDeliveryRunnable != null) {
deferredDeliveryRunnable.run();
deferredDeliveryRunnable = null;
}
}
}
public void reset() {
if (getResponsesListCall != null) {
getResponsesListCall.cancel();
getResponsesListCall = null;
}
isFetchingEndorsed = isQuestionTypeThread;
nextPage = 1;
}
public boolean hasMorePages() {
return hasMorePages;
}
}
}