/* * Copyright 2014 Artem Chikin * Copyright 2014 Artem Herasymchuk * Copyright 2014 Tom Krywitsky * Copyright 2014 Henry Pabst * Copyright 2014 Bradley Simons * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ca.ualberta.cmput301w14t08.geochan.fragments; import java.math.RoundingMode; import java.text.DecimalFormat; import java.util.ArrayList; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ImageButton; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import ca.ualberta.cmput301w14t08.geochan.R; import ca.ualberta.cmput301w14t08.geochan.adapters.ThreadViewAdapter; import ca.ualberta.cmput301w14t08.geochan.helpers.ConnectivityBroadcastReceiver; import ca.ualberta.cmput301w14t08.geochan.helpers.ConnectivityHelper; import ca.ualberta.cmput301w14t08.geochan.helpers.HashHelper; import ca.ualberta.cmput301w14t08.geochan.helpers.LocationListenerService; import ca.ualberta.cmput301w14t08.geochan.helpers.SortUtil; import ca.ualberta.cmput301w14t08.geochan.helpers.Toaster; import ca.ualberta.cmput301w14t08.geochan.interfaces.UpdateDialogListenerInterface; import ca.ualberta.cmput301w14t08.geochan.managers.CacheManager; import ca.ualberta.cmput301w14t08.geochan.managers.PreferencesManager; import ca.ualberta.cmput301w14t08.geochan.managers.ThreadManager; import ca.ualberta.cmput301w14t08.geochan.models.Comment; import ca.ualberta.cmput301w14t08.geochan.models.FavouritesLog; import ca.ualberta.cmput301w14t08.geochan.models.GeoLocation; import ca.ualberta.cmput301w14t08.geochan.models.ThreadComment; import eu.erikw.PullToRefreshListView; import eu.erikw.PullToRefreshListView.OnRefreshListener; /** * Fragment which displays the contents of a ThreadComment and performs all * actions on the objects in that ThreadComment. * * @author Henry Pabst * @author Artem Chikin */ public class ThreadViewFragment extends Fragment implements UpdateDialogListenerInterface { private BroadcastReceiver updateReceiver; private PullToRefreshListView threadView; private ThreadViewAdapter adapter; private int threadIndex; private CacheManager cache = null; private ThreadComment thread = null; private ConnectivityHelper connectHelper = null; private LocationListenerService locationListener = null; private PreferencesManager prefManager = null; private int container; private int isFavCom; private boolean refresh = false; private static int locSortFlag = 0; /** * Gets the fragment arguments, retrieves correct * ThreadComment object from either ThreadList or Cache or FavouritesLog, * Starts the refresh from server. * @param savedInstanceState The previously saved state of the Fragment. */ @Override public void onCreate (Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle bundle = getArguments(); threadIndex = (int) bundle.getLong("id"); isFavCom = bundle.getInt("favCom"); thread = bundle.getParcelable("thread"); if (locationListener == null) { locationListener = new LocationListenerService(getActivity()); } if (prefManager == null) { prefManager = PreferencesManager.getInstance(); } // Assign custom adapter to the thread listView. adapter = new ThreadViewAdapter(getActivity(), thread, getFragmentManager(), threadIndex); if (isFavCom != -1) { connectHelper = ConnectivityHelper.getInstance(); cache = CacheManager.getInstance(); ArrayList<Comment> comments = cache.deserializeThreadCommentById(thread.getId()); if (comments != null) { thread.getBodyComment().setChildren(comments); } if (!connectHelper.isConnected()) { Toaster.toastShort("No network connection."); } } } /** * Sets the proper sort option in our options menu. * * @param menu The fragment's menu. */ @Override public void onPrepareOptionsMenu(Menu menu){ int sortType = prefManager.getCommentSort(); setSortCheck(sortType, menu); super.onPrepareOptionsMenu(menu); } /** * Checks the proper sort option in our options menu. * @param sort Code for the sort type. * @param menu The fragment's menu. */ private void setSortCheck(int sort, Menu menu){ MenuItem item; switch(sort){ case SortUtil.SORT_DATE_NEWEST: item = menu.findItem(R.id.comment_sort_date_new); item.setChecked(true); return; case SortUtil.SORT_DATE_OLDEST: item = menu.findItem(R.id.comment_sort_date_new); item.setChecked(true); return; case SortUtil.SORT_LOCATION: item = menu.findItem(R.id.comment_sort_location); item.setChecked(true); return; case SortUtil.SORT_USER_SCORE_HIGHEST: item = menu.findItem(R.id.comment_sort_score_high); item.setChecked(true); return; case SortUtil.SORT_USER_SCORE_LOWEST: item = menu.findItem(R.id.comment_sort_score_low); item.setChecked(true); return; case SortUtil.SORT_IMAGE: item = menu.findItem(R.id.comment_sort_image); item.setChecked(true); return; default: return; } } /** * Initializes the variables used in displaying the contents of a * thread. If the user just returned from selecting a custom location to * sort by, it sorts the comments accordingly and resets locSortFlag. */ @Override public void onResume() { setHasOptionsMenu(true); if (locSortFlag == 1) { prefManager.setCommentSort(SortUtil.SORT_LOCATION); SortUtil.sortComments(SortUtil.SORT_LOCATION, thread.getBodyComment().getChildren()); adapter = new ThreadViewAdapter(getActivity(), thread, getFragmentManager(), threadIndex); threadView.setAdapter(adapter); adapter.notifyDataSetChanged(); locSortFlag = 0; } super.onResume(); } /** * Set up the fragment UI. * * @param inflater The LayoutInflater used to inflate the fragment's UI. * @param container The parent View that the fragment's UI is attached to. * @param savedInstanceState The previously saved state of the fragment. * @return The View for the fragment's UI. * */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_thread_view, container, false); } /** * Sets up the menu for the fragment. * @param menu The Menu item for the fragment itself. * @param inflater The inflater for inflating the fragment's menu. */ @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // Inflate the menu; this adds items to the action bar if it is present. inflater.inflate(R.menu.thread_view, menu); MenuItem item = menu.findItem(R.id.action_settings); item.setVisible(true); super.onCreateOptionsMenu(menu, inflater); } /** * Set up the ListView, adapter, listener for pullRoRefresh and OnItemClick listener. */ @Override public void onStart() { super.onStart(); threadView = (PullToRefreshListView) getView().findViewById(R.id.thread_view_list); adapter = new ThreadViewAdapter(getActivity(), thread, getFragmentManager(), threadIndex); threadView.setAdapter(adapter); adapter.notifyDataSetChanged(); threadView.setOnItemClickListener(commentButtonListener); threadView.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh() { if (!connectHelper.isConnected()) { Toaster.toastShort("No network connection."); threadView.onRefreshComplete(); } else if (isFavCom == -1) { threadView.onRefreshComplete(); } else { reload(); } } }); // Toggle PullToRefresh programatically on start if (!refresh && isFavCom != -1 && connectHelper.isConnected()) { threadView.setRefreshing(); ThreadManager.startGetComments(this, threadIndex); refresh = true; } //threadView.setRefreshing(); Fragment fav = getFragmentManager().findFragmentByTag("favThrFragment"); if (fav != null) { container = R.id.container; } else { container = R.id.fragment_container; } updateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (isVisible() && connectHelper.getWasNotConnected() == true) { connectHelper.setWasNotConnected(false); UpdateDialogFragment fragment = new UpdateDialogFragment(); fragment.show(getFragmentManager(), "updateDialogFrag"); } } }; getActivity().getApplicationContext().registerReceiver(updateReceiver, new IntentFilter(ConnectivityBroadcastReceiver.UPDATE_FROM_SERVER_INTENT)); } /** * When comment is selected, additional information is displayed in the form * of location coordinates and action buttons. * This method sets that location field TextView in * the view. * * @param view The View of the Comment that was selected. * @param comment The Comment itself that was selected by the user. */ public void setLocationField(View view, Comment comment) { TextView replyLocationText = (TextView) view .findViewById(R.id.thread_view_comment_location); GeoLocation repLocCom = comment.getLocation(); if (repLocCom != null) { if (repLocCom.getLocationDescription() != null) { replyLocationText.setText("near: " + repLocCom.getLocationDescription()); } else { DecimalFormat format = new DecimalFormat(); format.setRoundingMode(RoundingMode.HALF_EVEN); format.setMinimumFractionDigits(0); format.setMaximumFractionDigits(4); replyLocationText.setText("Latitude: " + format.format(repLocCom.getLatitude()) + " Longitude: " + format.format(repLocCom.getLongitude())); } } else { replyLocationText.setText("Error: No location found"); } } /** * Called when the star button is pressed on the selected comment. Save the * comment as favourite. * * @param comment The Comment to be added to favourites. */ public void favouriteAComment(Comment comment) { Toast.makeText(getActivity(), "Comment saved to Favourites.", Toast.LENGTH_SHORT) .show(); FavouritesLog log = FavouritesLog.getInstance(getActivity()); ThreadComment thread = new ThreadComment(comment,""); thread.setId(Long.parseLong(comment.getId())); log.addFavComment(thread); } /** * Called when the star button is pressed in the selected comment when * comment is already starred. Remove the comment as favourite. * * @param id The ID of the Comment to be removed from favourites. */ public void unfavouriteAComment(String id) { Toast.makeText(getActivity(), "Comment removed from Favourites.", Toast.LENGTH_SHORT) .show(); FavouritesLog log = FavouritesLog.getInstance(getActivity()); log.removeFavComment(id); } /** * Set up and launch the postCommentFragment when the user wishes to reply * to a comment. The fragment takes as input the index of the thread and the * comment object to reply to. * * @param comment The Comment being replied to. * @param threadIndex The index of the ThreadComment where the reply is taking place. */ public void replyToComment(Comment comment, int threadIndex) { Fragment fragment = new PostFragment(); Bundle bundle = new Bundle(); bundle.putParcelable("cmt", comment); bundle.putLong("id", threadIndex); fragment.setArguments(bundle); boolean fromFavs = false; Fragment fav = getFragmentManager().findFragmentByTag("favThrFragment"); if(fav != null){ fromFavs = true; } bundle.putBoolean("fromFavs", fromFavs); getFragmentManager().beginTransaction() .replace(container, fragment, "postFrag").addToBackStack(null) .commit(); getFragmentManager().executePendingTransactions(); } /** * OnItemClickListener for comments in the list view. on click, inflates an additional relative layout * that contains the comment's location information and action buttons: * reply, favourite, edit (if user's comment). Contains listeners for the buttons. */ private OnItemClickListener commentButtonListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (position < 2) { return; } if (getArguments().getInt("favCom") == -1) { return; } LayoutInflater inflater = (LayoutInflater) view.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); // "+1" is necessary because of PullToRefresh final Comment comment = (Comment) threadView.getItemAtPosition(position + 1); RelativeLayout relativeInflater = (RelativeLayout) view .findViewById(R.id.relative_inflater); View child = inflater.inflate(R.layout.comment_buttons, null); /* * If the child layout with buttons is already inflated, remove it. * If not, inflate it. */ if (relativeInflater != null && relativeInflater.getChildCount() > 0) { relativeInflater.removeAllViews(); return; } else { resetOtherCommentLayouts(position); relativeInflater.addView(child); setLocationField(view, comment); } if (comment.hasImage()) { ImageButton thumbnail = (ImageButton) view .findViewById(R.id.thread_view_comment_thumbnail); thumbnail.setVisibility(View.VISIBLE); thumbnail.setFocusable(false); } final ImageButton replyButton = (ImageButton) view .findViewById(R.id.comment_reply_button); replyButton.setFocusable(false); final ImageButton starButton = (ImageButton) view .findViewById(R.id.comment_star_button); starButton.setFocusable(false); // Check if the comment is by the user to decide // wether or not to display the edit button. if (HashHelper.getHash(comment.getUser()).equals(comment.getHash())) { final ImageButton editButton = (ImageButton) view .findViewById(R.id.comment_edit_button); editButton.setVisibility(View.VISIBLE); editButton.setFocusable(false); editButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // SETUP FOR COMMENT EDITING GOES HERE editComment(comment); } }); } // Check if the favourites log already has a copy. if (FavouritesLog.getInstance(getActivity()).hasFavComment(comment.getId())) { starButton.setImageResource(R.drawable.ic_rating_marked); } starButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // Favourite or unfavourite depending on current state if (!FavouritesLog.getInstance(getActivity()).hasFavComment(comment.getId())) { starButton.setImageResource(R.drawable.ic_rating_marked); favouriteAComment(comment); if (comment.hasImage()) { ThreadManager.startGetImage(comment.getId(), null, null); } } else { starButton.setImageResource(R.drawable.ic_rating_important); unfavouriteAComment(comment.getId()); } } }); replyButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // Perform action on click replyToComment(comment, threadIndex); } }); } }; /** * Set up and launch the EditFragment for a given comment. * * @param comment The Comment that is being edited. */ private void editComment(Comment comment){ Fragment fragment = new EditFragment(); Bundle bundle = new Bundle(); boolean fromFavs = false; bundle.putInt("threadIndex", threadIndex); bundle.putString("commentId", comment.getId()); Fragment fav = getFragmentManager().findFragmentByTag("favThrFragment"); if(fav != null){ fromFavs = true; } bundle.putBoolean("fromFavs", fromFavs); fragment.setArguments(bundle); getFragmentManager().beginTransaction() .replace(container, fragment, "editFrag").addToBackStack(null) .commit(); getFragmentManager().executePendingTransactions(); } /** * When a comment is clicked, this method deflates the additional * (location and button) layout of other comments in the list. * * TODO: make it also deflate the layouts of comments off the screen. * * @param position The position of the Comment that will not be deflated. */ private void resetOtherCommentLayouts(int position) { for (int i = 2; i < threadView.getCount() + 1; ++i) { if (i == position + 1) { continue; } View v = threadView.getChildAt(i); if (v == null) { continue; } RelativeLayout relativeInflater = (RelativeLayout) v .findViewById(R.id.relative_inflater); if (relativeInflater != null && relativeInflater.getChildCount() > 0) { relativeInflater.removeAllViews(); } } } /** * Determines which sorting option the user selected and sorts the comments * accordingly. * * @param item The MenuItem that was selected by the user. * */ @Override public boolean onOptionsItemSelected(MenuItem item) { item.setChecked(true); switch (item.getItemId()) { case (R.id.comment_sort_date_new): // User wants to push newer comments to the top. sortByTag(SortUtil.SORT_DATE_NEWEST); return true; case (R.id.comment_sort_date_old): // User wants to push older comments to the top. sortByTag(SortUtil.SORT_DATE_OLDEST); return true; case (R.id.comment_sort_image): // User wants to push comments with images to the top. sortByTag(SortUtil.SORT_IMAGE); return true; case (R.id.comment_sort_score_high): // User wants to push comments with a high score/relevance to the // top. sortByTag(SortUtil.SORT_USER_SCORE_HIGHEST); return true; case (R.id.comment_sort_score_low): // User wants to push comments with a low score/relevance to the // top. sortByTag(SortUtil.SORT_USER_SCORE_LOWEST); return true; case (R.id.comment_sort_location): // User wants to push comments near a selected location to the top. locSortFlag = 1; this.getSortingLoc(); return true; default: return super.onOptionsItemSelected(item); } } /** * Given a sorting tag, perform sort, remember chosen sorting method and * reset the adapter to reflect changes. * * @param tag * tag to sort by. Tags are defined in SortUtil.java */ private void sortByTag(int tag) { prefManager.setCommentSort(tag); SortUtil.sortComments(tag, thread.getBodyComment().getChildren()); adapter = new ThreadViewAdapter(getActivity(), thread, getFragmentManager(), threadIndex); threadView.setAdapter(adapter); adapter.notifyDataSetChanged(); } /** * Sends the user into a CustomLocationFragment so they can choose a custom * location to sort comments by. * */ private void getSortingLoc() { Bundle args = new Bundle(); args.putInt("postType", CustomLocationFragment.SORT_COMMENT); CustomLocationFragment frag = new CustomLocationFragment(); frag.setArguments(args); getFragmentManager().beginTransaction().replace(container, frag, "customLocFrag") .addToBackStack(null).commit(); getFragmentManager().executePendingTransactions(); } /** * Starts a new thread of execution for retrieving Comments from ElasticSearch. */ @Override public void reload() { ThreadManager.startGetComments(this, threadIndex); } /** * On finishing pullToRefresh reload, notify the adapter. */ public void finishReload() { SortUtil.sortComments(prefManager.getCommentSort(), thread.getBodyComment().getChildren()); adapter = new ThreadViewAdapter(getActivity(), thread, getFragmentManager(), threadIndex); // Assign custom adapter to the thread listView. threadView.setAdapter(adapter); adapter.notifyDataSetChanged(); threadView.onRefreshComplete(); } }