package de.bsd.zwitscher;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Build;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import de.bsd.zwitscher.account.Account;
import de.bsd.zwitscher.helper.NetworkHelper;
import de.bsd.zwitscher.helper.SpannableBuilder;
import de.bsd.zwitscher.helper.TriggerPictureDownloadTask;
import de.bsd.zwitscher.helper.UserImageView;
import twitter4j.DirectMessage;
import twitter4j.MediaEntity;
import twitter4j.Status;
import twitter4j.TwitterResponse;
import twitter4j.URLEntity;
import twitter4j.User;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.RejectedExecutionException;
/**
* Adapter for individual list rows of
* the TweetList
*
* The Adapter's getView() method is repeatedly called when
* the user is scrolling through the list, so it needs to be quick.
* So one thing is to use the ViewHolder pattern in order to prevent
* the repeated inflation of views, which is expensive. When running
* through the adapter, we record the list of read statuses in a list and
* have the outer activity persiste that result, when the adapter is
* about to be replaced or destroyed (in onPause).
*
* The loading of user images is done in an AsyncTask, that gets the
* image view passed. We need to tag that view, as the user may scroll
* quickly and in that case the image required on a certain position may
* already be a different one. So the background task compares the tag
* on the view with the expected one and skips setting the image if the
* tags don't match.
*
* @author Heiko W. Rupp
*/
class StatusAdapter<T extends TwitterResponse> extends AbstractAdapter<T> {
private TwitterHelper th;
private UserDisplayMode userDisplay;
public List<Long> readIds;
private long oldLast;
private final boolean downloadImages;
public Set<Long> newOlds = new HashSet<Long>();
public StatusAdapter(Context context, Account account, int textViewResourceId, List<T> objects, long oldLast, List<Long> readIds) {
super(context, textViewResourceId, objects);
this.readIds = readIds;
this.oldLast = oldLast;
th = new TwitterHelper(context, account);
NetworkHelper networkHelper = new NetworkHelper(context);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
String tmp = preferences.getString("screen_name_overview","USER");
userDisplay = UserDisplayMode.valueOf(tmp);
downloadImages = networkHelper.mayDownloadImages();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
// Debug.startMethodTracing("sta");
// Use ViewHolder pattern to only inflate once
if (convertView ==null) {
convertView = inflater.inflate(R.layout.tweet_list_item,parent,false);
viewHolder = new ViewHolder();
viewHolder.iv = (UserImageView) convertView.findViewById(R.id.ListImageView);
viewHolder.iv.setFocusable(false);
viewHolder.statusText = (TextView) convertView.findViewById(R.id.ListTextView);
viewHolder.userInfo = (TextView) convertView.findViewById(R.id.ListUserView);
viewHolder.timeClientInfo = (TextView) convertView.findViewById(R.id.ListTimeView);
convertView.setTag(viewHolder);
}
else {
viewHolder = (ViewHolder) convertView.getTag();
}
// System.out.println("IV:" + viewHolder.iv + " @ " + position + " | " + viewHolder.iv.getTag());
T response = items.get(position);
boolean isOld = false;
if (response instanceof Status) {
Status status = (Status) response;
long oid;
oid = status.getId();
long rtStatusId = -1;
if (status.isRetweet()) {
rtStatusId = status.getRetweetedStatus().getId();
}
if (oid <= oldLast ) {
convertView.setBackgroundColor(Color.BLACK);
isOld = true;
} else if (readIds.contains(oid)) {
convertView.setBackgroundColor(Color.rgb(0,0,40)); // todo debug color
isOld=true;
} else if (newOlds.contains(oid)) {
convertView.setBackgroundColor(Color.rgb(0,0,60));
isOld=true;
} else if (status.isRetweet() && (readIds.contains(rtStatusId) || newOlds.contains(rtStatusId))) {
convertView.setBackgroundColor(Color.rgb(40,0,0));
isOld=true;
}
// If the status is a RT and we also have the original one
// on file, then mark the original as seen too
if (status.isRetweet() && ! readIds.contains(rtStatusId) && !newOlds.contains(rtStatusId)) {
// Only color when we have the original one too
Status rtStatus = th.getStatusById(rtStatusId,null,false,false,true);
if (rtStatus!=null) {
newOlds.add(rtStatusId);
readIds.add(rtStatusId); // If we have seen the retweet once it is enough
}
}
newOlds.add(status.getId());
/* If this is enabled, RTs are marked as one goes, but the coloring is too aggressive when slightly scrolling around
Need to still decide if I want that
if (rtStatusId!=-1)
newOlds.add(rtStatusId);
*/
}
// Color the lines
if (!isOld) {
if (position % 2 == 0)
convertView.setBackgroundColor(Color.rgb(15,40,20));
else
convertView.setBackgroundColor(Color.DKGRAY);
}
SpannableBuilder builder = new SpannableBuilder(extContext);
User userOnPicture;
String statusText;
if (response instanceof Status) {
Status status = (Status)response;
if (status.getRetweetedStatus()==null) {
userOnPicture =status.getUser();
appendUserInfo(userOnPicture,builder);
if (status.getInReplyToScreenName()!=null) {
builder.appendSpace();
builder.append(R.string.in_reply_to, Typeface.NORMAL).appendSpace();
builder.append(status.getInReplyToScreenName(), Typeface.BOLD); // we only have the screen name here
}
}
else {
userOnPicture = status.getRetweetedStatus().getUser();
appendUserInfo(userOnPicture,builder);
builder.appendSpace()
.append(R.string.resent_by, Typeface.NORMAL)
.appendSpace();
appendUserInfo(status.getUser(), builder);
}
statusText = textWithReplaceTokens(status);
}
else if (response instanceof DirectMessage) {
DirectMessage msg = (DirectMessage) response;
userOnPicture = msg.getSender();
statusText=msg.getText();
builder.append(R.string.From,Typeface.NORMAL)
.appendSpace()
.append(msg.getSender().getName(),Typeface.BOLD)
.appendSpace()
.append(R.string.to,Typeface.NORMAL)
.appendSpace()
.append(msg.getRecipient().getName(),Typeface.BOLD);
}
else
throw new IllegalArgumentException("Unknown type " + response);
// User images will all be loaded asynchronously
// It this is a status, then pass it along as well.
Status status=null;
if (response instanceof Status)
status = (Status) response;
viewHolder.iv.setTag(userOnPicture.getScreenName()); // tag to remember which image should be shown
TriggerPictureDownloadTask downloadTask = new TriggerPictureDownloadTask(viewHolder.iv, userOnPicture, downloadImages, status);
try {
if (Build.VERSION.SDK_INT<16) {
downloadTask.execute();
}
else {
downloadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
} catch (RejectedExecutionException e) {
// This means, the threadpool is full - in that case
// the image is just not set/downloaded. This will happen
// on further scrolling
Log.w("StatusAdapter","Could not execute the picture download: " + e.getMessage());
}
viewHolder.userInfo.setText(builder.toSpannableString());
viewHolder.statusText.setText(statusText);
String text = th.getStatusDate(response) ;
viewHolder.timeClientInfo.setText((text));
if (!isOld) {
viewHolder.userInfo.setTextColor(Color.LTGRAY);
viewHolder.statusText.setTextColor(Color.LTGRAY);
viewHolder.timeClientInfo.setTextColor(Color.LTGRAY);
} else {
viewHolder.userInfo.setTextColor(Color.GRAY);
viewHolder.statusText.setTextColor(Color.GRAY);
viewHolder.timeClientInfo.setTextColor(Color.GRAY);
}
//Debug.stopMethodTracing();
return convertView;
}
private void appendUserInfo(User user,SpannableBuilder builder) {
switch (userDisplay) {
case USER:
builder.append(user.getName(), Typeface.BOLD);
break;
case SCREENNAME:
builder.append(user.getScreenName());
break;
case BOTH:
builder.append(user.getName(),Typeface.BOLD)
.append(" (").append(user.getScreenName()).append(")");
}
}
private String textWithReplaceTokens(Status status) {
if (status==null || status.getText()==null)
return "";
String[] tokens;
if (status.isRetweet()) {
tokens = status.getRetweetedStatus().getText().split(" ");
} else {
tokens = status.getText().split(" ");
}
StringBuilder builder = new StringBuilder();
for (String token : tokens) {
boolean found=false;
if (status.getMediaEntities()!=null) {
for (MediaEntity me : status.getMediaEntities()) {
String meURL = me.getURL();
if (meURL != null && meURL.equals(token) && me.getDisplayURL() != null) {
builder.append(me.getDisplayURL());
found=true;
break;
}
}
}
if (!found && status.getURLEntities()!=null) {
for (URLEntity ue : status.getURLEntities()) {
String ueURL = ue.getURL();
if (ueURL !=null && ueURL.equals(token) && ue.getDisplayURL()!=null) {
builder.append(ue.getDisplayURL());
found=true;
break;
}
}
}
if (!found)
builder.append(token);
builder.append(" "); // TODO not at the end
}
return builder.toString();
}
}