/*
* 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.adapters;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.ArrayList;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import ca.ualberta.cmput301w14t08.geochan.R;
import ca.ualberta.cmput301w14t08.geochan.fragments.EditFragment;
import ca.ualberta.cmput301w14t08.geochan.fragments.ExpandImageFragment;
import ca.ualberta.cmput301w14t08.geochan.fragments.MapViewFragment;
import ca.ualberta.cmput301w14t08.geochan.fragments.PostFragment;
import ca.ualberta.cmput301w14t08.geochan.helpers.HashHelper;
import ca.ualberta.cmput301w14t08.geochan.managers.PreferencesManager;
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;
/**
* Adapter used for displaying a ThreadComment in the ThreadViewFragment. It
* inflates layouts for OP, top level comments and comment replies and listens to
* the buttons of the listView elements that are visible at all times.
*
* @author Artem Chikin
*/
public class ThreadViewAdapter extends BaseAdapter {
// Constants for all the poosible layout types.
private static final int TYPE_COMMENT0 = 0;
private static final int TYPE_COMMENT1 = 1;
private static final int TYPE_COMMENT2 = 2;
private static final int TYPE_COMMENT3 = 3;
private static final int TYPE_COMMENT4 = 4;
private static final int TYPE_COMMENT5 = 5;
private static final int TYPE_COMMENT6 = 6;
private static final int TYPE_COMMENT7 = 7;
private static final int TYPE_COMMENTMAX = 10;
private static final int TYPE_OP = 8;
private static final int TYPE_SEPARATOR = 9;
private static final int TYPE_MAX_COUNT = 11;
private Context context;
private int id;
private ThreadComment thread;
private ArrayList<Comment> comments;
private FragmentManager manager;
/**
* Constructs the adapter and initializes all of its components.
* @param context The Context the adapter is running in.
* @param thread The ThreadComment to display.
* @param manager The FragmentManager with this adapter's fragment.
* @param id The index of the ThreadComment in the ThreadList.
*/
public ThreadViewAdapter(Context context, ThreadComment thread, FragmentManager manager, int id) {
super();
this.context = context;
this.thread = thread;
this.manager = manager;
this.id = id;
this.comments = new ArrayList<Comment>();
buildAList(thread.getBodyComment());
}
/**
* Takes a comment and recursively builds a list of comment
* objects from the Comment's children tree.
* This is the list we display in our listView.
*
* @param comment The Comment to recursively build a list
* from.
*
*/
private void buildAList(Comment comment) {
ArrayList<Comment> children = comment.getChildren();
if (children.size() == 0) {
return;
} else {
for (Comment c : children) {
comments.add(c);
buildAList(c);
}
}
}
/**
* Sets the ThreadComment that the adapter is displaying
* and refreshes the adapter.
*
* @param thread The new ThreadComment for the adapter to display.
*
*/
public void setThread(ThreadComment thread) {
this.thread = thread;
buildAList(thread.getBodyComment());
this.notifyDataSetChanged();
}
/**
* Returns the number of items in this adapter,
* includes all posts (including the bodyComment) and the separator
* between the bodyComment and the other Comments.
*
* @return The number of items in the adapter.
*/
@Override
public int getCount() {
int size = getCountChildren(thread.getBodyComment());
return size + 2; // The +2 is for OP + Separator
}
/**
* This method recursively counts the total amount of children a comment
* object has.
*
* @param comment The Comment to determine the number of children of.
* @return The number of children.
*
*/
private int getCountChildren(Comment comment) {
if (comment.getChildren().size() == 0) {
return 0;
}
int size = 0;
for (Comment c : comment.getChildren()) {
++size;
size += getCountChildren(c);
}
return size;
}
/**
* Gets an item at the specific position in the list.
* @param position The position.
* @return The item.
*/
@Override
public Object getItem(int position) {
if (position == 0) {
return thread.getBodyComment();
}
if (position == 1) {
return null;
} else {
// -2 because of OP and SEPARATOR
return comments.get(position - 2);
}
}
/**
* Gets the id of an item at a specific position.
* @param position The position.
* @return The id.
*/
@Override
public long getItemId(int position) {
return position;
}
/**
* Returns the number of views that this adapter can create.
* @return The number of views the adapter can create.
*/
@Override
public int getViewTypeCount() {
return TYPE_MAX_COUNT;
}
/**
* Return the layout type for a list element,
* OP, SEPARATOR, 7 COMMENT depths and COMMENT_MAX types.
*
* @param position The position.
* @return The layout type.
*/
@Override
public int getItemViewType(int position) {
int type = 0;
if (position == 0) {
type = TYPE_OP;
} else if (position == 1) {
type = TYPE_SEPARATOR;
} else {
int depth = ((Comment) getItem(position)).getDepth();
if (depth <= 7) {
type = depth;
} else {
type = TYPE_COMMENTMAX;
}
}
return type;
}
/**
* Depending on the list item type, inflate the correct
* layout, start listening for its buttons.
* @param position The position.
* @param convertView A previous recycled View.
* @param parent The parent View.
* @return The View.
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
int type = getItemViewType(position);
final Comment comment = (Comment) getItem(position);
switch (type) {
case TYPE_OP:
convertView = setConvertView(convertView, R.layout.thread_view_op);
setOPFields(convertView);
listenForThreadButtons(convertView, thread);
break;
case TYPE_COMMENT0:
convertView = setConvertView(convertView, R.layout.thread_view_comment_0);
setCommentFields(convertView, comment);
listenForThumbnail(convertView, comment);
break;
case TYPE_COMMENT1:
convertView = setConvertView(convertView, R.layout.thread_view_comment_1);
setCommentFields(convertView, comment);
listenForThumbnail(convertView, comment);
break;
case TYPE_COMMENT2:
convertView = setConvertView(convertView, R.layout.thread_view_comment_2);
setCommentFields(convertView, comment);
listenForThumbnail(convertView, comment);
break;
case TYPE_COMMENT3:
convertView = setConvertView(convertView, R.layout.thread_view_comment_3);
setCommentFields(convertView, comment);
listenForThumbnail(convertView, comment);
break;
case TYPE_COMMENT4:
convertView = setConvertView(convertView, R.layout.thread_view_comment_4);
setCommentFields(convertView, comment);
listenForThumbnail(convertView, comment);
break;
case TYPE_COMMENT5:
convertView = setConvertView(convertView, R.layout.thread_view_comment_5);
setCommentFields(convertView, comment);
listenForThumbnail(convertView, comment);
break;
case TYPE_COMMENT6:
convertView = setConvertView(convertView, R.layout.thread_view_comment_6);
setCommentFields(convertView, comment);
listenForThumbnail(convertView, comment);
break;
case TYPE_COMMENT7:
convertView = setConvertView(convertView, R.layout.thread_view_comment_7);
setCommentFields(convertView, comment);
listenForThumbnail(convertView, comment);
break;
case TYPE_SEPARATOR:
convertView = setConvertView(convertView, R.layout.thread_view_separator);
TextView numComments = (TextView) convertView.findViewById(R.id.textSeparator);
numComments.setText(Integer.toString(getCount() - 2) + " Comments:");
break;
case TYPE_COMMENTMAX:
final Comment commentMax = (Comment) getItem(position);
convertView = setConvertView(convertView, R.layout.thread_view_comment_max);
setCommentFields(convertView, commentMax);
// TYPE_COMMENTMAX has an extra field: depth relative to the maximum
// depth layout.
TextView depthMeter = (TextView) convertView
.findViewById(R.id.thread_view_comment_depth_meter);
depthMeter.setText("Max depth + " + Integer.toString(commentMax.getDepth() - 7));
listenForThumbnail(convertView, comment);
break;
}
return convertView;
}
/**
* Listen for the thumbnail of a comment, onClick start the
* ExpandImageFragment.
*
* @param convertView The view that contains the thumbnail.
* @param comment The corresponding Comment object.
*/
private void listenForThumbnail(View convertView, final Comment comment) {
ImageButton thumbnail = (ImageButton) convertView
.findViewById(R.id.thread_view_comment_thumbnail);
if (thumbnail != null) {
thumbnail.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click
Fragment fragment = new ExpandImageFragment();
Bundle bundle = new Bundle();
bundle.putString("id", comment.getId());
fragment.setArguments(bundle);
Fragment fav = manager.findFragmentByTag("favThrFragment");
Fragment favCom = manager.findFragmentByTag("favComFragment");
if (fav != null) {
manager.beginTransaction()
.add(R.id.container, fragment, "picFrag")
.addToBackStack(null).commit();
} else if (favCom != null) {
manager.beginTransaction()
.add(R.id.container, fragment, "picFrag")
.addToBackStack(null).commit();
} else {
manager.beginTransaction()
.add(R.id.fragment_container, fragment, "picFrag")
.addToBackStack(null).commit();
}
manager.executePendingTransactions();
}
});
}
}
/**
* Assign convertview to layout.
*
* @param convertView
* The View to inflate
* @param layout
* An R.Layout resource.
* @return convertView The inflated View.
*/
private View setConvertView(View convertView, int layout) {
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(layout, null);
}
return convertView;
}
/**
* Listener for the buttons of the thread op (TYPE_OP) listView item. Comments have
* listeners in ThreadViewFragment because they are only displayed after
* onItemClick, threadComment buttons have a listener in the adapter for
* ease of access to the permanently displayed buttons.
*
* @param convertView view that contains the ThreadComments OP.
* @param thread threadComment object that contains the ThreadComments OP.
*
*/
private void listenForThreadButtons(View convertView, final ThreadComment thread) {
// Here handle button presses
final ImageButton replyButton = (ImageButton) convertView
.findViewById(R.id.comment_reply_button);
final ImageButton starButton = (ImageButton) convertView
.findViewById(R.id.comment_star_button);
if (FavouritesLog.getInstance(context).hasThreadComment(thread.getId())) {
starButton.setImageResource(R.drawable.ic_rating_marked);
}
final ImageButton mapButton = (ImageButton) convertView
.findViewById(R.id.thread_map_button);
final ImageButton editButton = (ImageButton) convertView
.findViewById(R.id.thread_edit_button);
if (HashHelper.getHash(thread.getBodyComment().getUser()).equals(
thread.getBodyComment().getHash())) {
editButton.setVisibility(View.VISIBLE);
}
if (thread.getBodyComment().hasImage()) {
listenForThumbnail(convertView, thread.getBodyComment());
}
// Listen for the star(favourites) button.
if (starButton != null) {
starButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click depending on if the thread has
// been favourited.
if (!FavouritesLog.getInstance(context).hasThreadComment(thread.getId())) {
Toast.makeText(context, "Thread saved to Favourites.", Toast.LENGTH_SHORT)
.show();
starButton.setImageResource(R.drawable.ic_rating_marked);
FavouritesLog log = FavouritesLog.getInstance(context);
log.addThreadComment(thread);
} else {
Toast.makeText(context, "Thread removed from Favourites.",
Toast.LENGTH_SHORT).show();
starButton.setImageResource(R.drawable.ic_rating_important);
FavouritesLog log = FavouritesLog.getInstance(context);
log.removeThreadComment(thread);
}
}
});
}
// Listen for the edit button.
if (editButton != null) {
editButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Fragment fragment = new EditFragment();
Bundle bundle = new Bundle();
bundle.putInt("threadIndex", id);
bundle.putString("commentId", thread.getBodyComment().getId());
fragment.setArguments(bundle);
manager.beginTransaction()
.replace(R.id.fragment_container, fragment, "editFrag")
.addToBackStack(null).commit();
manager.executePendingTransactions();
}
});
}
// Listen for the map button.
if (mapButton != null) {
mapButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click to launch mapFragment
Bundle bundle = new Bundle();
bundle.putParcelable("thread_comment", thread.getBodyComment());
Fragment mapFrag = new MapViewFragment();
mapFrag.setArguments(bundle);
Fragment fav = manager.findFragmentByTag("favThrFragment");
if (fav != null) {
manager.beginTransaction().replace(R.id.container, mapFrag, "mapFrag")
.addToBackStack(null).commit();
} else {
manager.beginTransaction()
.replace(R.id.fragment_container, mapFrag, "mapFrag")
.addToBackStack(null).commit();
}
manager.executePendingTransactions();
}
});
}
// Listen for the reply button.
if (replyButton != null) {
replyButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click to launch postCommentFragment
Fragment fragment = new PostFragment();
Bundle bundle = new Bundle();
bundle.putParcelable("cmt", thread.getBodyComment());
bundle.putLong("id", id);
fragment.setArguments(bundle);
Fragment fav = manager.findFragmentByTag("favThrFragment");
if (fav != null) {
manager.beginTransaction()
.replace(R.id.container, fragment, "postFrag")
.addToBackStack(null).commit();
manager.executePendingTransactions();
} else {
manager.beginTransaction()
.replace(R.id.fragment_container, fragment, "postFrag")
.addToBackStack(null).commit();
manager.executePendingTransactions();
}
}
});
}
}
/**
* Sets the required fields of the orignal post of the thread.
* Title, creator, comment, timestamp, location.
*
* @param convertView
* View container of a listView item.
*/
private void setOPFields(View convertView) {
// Thread title
TextView title = (TextView) convertView.findViewById(R.id.thread_view_op_threadTitle);
// Special case of Viewing a Favourite Comment in ThreadView
if (thread.getTitle().equals("")) {
title.setVisibility(View.GONE);
LinearLayout buttons = (LinearLayout) convertView.findViewById(R.id.thread_view_op_buttons);
buttons.setVisibility(View.GONE);
} else {
title.setText(thread.getTitle());
}
// Thread creator
TextView threadBy = (TextView) convertView.findViewById(R.id.thread_view_op_commentBy);
threadBy.setText("Posted by " + thread.getBodyComment().getUser() + "#"
+ thread.getBodyComment().getHash() + " ");
if (HashHelper.getHash(thread.getBodyComment().getUser()).equals(
thread.getBodyComment().getHash())) {
threadBy.setBackgroundResource(R.drawable.username_background_thread_rect);
threadBy.setTextColor(Color.WHITE);
}
// Thread body comment
TextView body = (TextView) convertView.findViewById(R.id.thread_view_op_commentBody);
body.setText(thread.getBodyComment().getTextPost());
// Thread timestamp
TextView threadTime = (TextView) convertView.findViewById(R.id.thread_view_op_commentDate);
threadTime.setText(thread.getBodyComment().getCommentDateString());
// Location text
TextView origPostLocationText = (TextView) convertView
.findViewById(R.id.thread_view_op_locationText);
GeoLocation loc = thread.getBodyComment().getLocation();
String locDescriptor = loc.getLocationDescription();
if (loc != null) {
if (locDescriptor != null) {
origPostLocationText.setText("near: " + locDescriptor);
} else {
// The rounding of long and lat for max 4 decimal digits.
DecimalFormat format = new DecimalFormat();
format.setRoundingMode(RoundingMode.HALF_EVEN);
format.setMinimumFractionDigits(0);
format.setMaximumFractionDigits(4);
origPostLocationText.setText("Latitude: " + format.format(loc.getLatitude())
+ " Longitude: " + format.format(loc.getLongitude()));
}
}
// Set the thumbnail if there is an image
if (thread.getBodyComment().hasImage()) {
ImageButton thumbnail = (ImageButton) convertView
.findViewById(R.id.thread_view_comment_thumbnail);
thumbnail.setVisibility(View.VISIBLE);
thumbnail.setFocusable(false);
thumbnail.setImageBitmap(thread.getBodyComment().getImageThumb());
}
}
/**
* Sets all the required views for a comment reply. Comment text,
* creator, time.
*
* @param convertView View that contains the comment.
* @param reply the Comment object that we are setting fields for.
*
*/
private void setCommentFields(View convertView, Comment reply) {
if (reply.hasImage()) {
ImageButton thumbnail = (ImageButton) convertView
.findViewById(R.id.thread_view_comment_thumbnail);
thumbnail.setVisibility(View.VISIBLE);
thumbnail.setFocusable(false);
thumbnail.setImageBitmap(reply.getImageThumb());
} else {
ImageButton thumbnail = (ImageButton) convertView
.findViewById(R.id.thread_view_comment_thumbnail);
thumbnail.setVisibility(View.GONE);
thumbnail.setFocusable(false);
}
// Comment body
TextView replyBody = (TextView) convertView
.findViewById(R.id.thread_view_comment_commentBody);
replyBody.setText(reply.getTextPost());
// Comment creator
TextView replyBy = (TextView) convertView.findViewById(R.id.thread_view_comment_commentBy);
replyBy.setText(reply.getUser() + "#" + reply.getHash() + " ");
String username = PreferencesManager.getInstance().getUser();
if (HashHelper.getHash(reply.getUser()).equals(reply.getHash())) {
replyBy.setBackgroundResource(R.drawable.username_background_rect);
replyBy.setTextColor(Color.WHITE);
}
// Comment timestamp
TextView replyTime = (TextView) convertView
.findViewById(R.id.thread_view_comment_commentDate);
replyTime.setText(reply.getCommentDateString());
}
}