// Copyright 2009 Google Inc. // Copyright 2011 NPR // // 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 org.npr.android.news; import android.content.Context; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Message; import android.text.Html; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.*; import org.npr.android.util.PlaylistRepository; import org.npr.api.Book; import org.npr.api.Client; import org.npr.api.Story; import org.npr.api.Story.Audio; import org.npr.api.Story.StoryFactory; import org.w3c.dom.Node; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.util.List; import java.util.Map; public class NewsListAdapter extends ArrayAdapter<Story> { private static final String LOG_TAG = NewsListAdapter.class.getName(); private final LayoutInflater inflater; private final ImageThreadLoader imageLoader; private RootActivity rootActivity = null; private final PlaylistRepository repository; private long lastUpdate = -1; private StoriesLoadedListener storiesLoadedListener; public NewsListAdapter(Context context) { super(context, R.layout.news_item); if (context instanceof RootActivity) { rootActivity = (RootActivity) context; } inflater = LayoutInflater.from(getContext()); imageLoader = ImageThreadLoader.getOnDiskInstance(context); repository = new PlaylistRepository(getContext().getApplicationContext(), context.getContentResolver()); } private List<Story> moreStories; private boolean endReached = false; private final Handler handler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what >= 0) { if (moreStories != null) { lastUpdate = System.currentTimeMillis(); remove(null); for (Story s : moreStories) { if (getPosition(s) < 0) { add(s); } } if (!endReached) { add(null); } } if (storiesLoadedListener != null) { storiesLoadedListener.storiesLoaded(); } } else { Toast.makeText(rootActivity, rootActivity.getResources() .getText(R.string.msg_check_connection), Toast.LENGTH_LONG).show(); } } }; public boolean isPlayable(Story story) { for (Audio a : story.getAudios()) { if (a.getType().equals("primary")) { for (Audio.Format f : a.getFormats()) { if (f.getMp3() != null) { return true; } } } } return false; } @Override public View getView(final int position, View convertView, final ViewGroup parent) { if (convertView == null) { convertView = inflater.inflate(R.layout.news_item, parent, false); } Story story = getItem(position); ImageView icon = (ImageView) convertView.findViewById(R.id.NewsItemIcon); TextView topic = (TextView) convertView.findViewById(R.id.NewsItemTopicText); TextView name = (TextView) convertView.findViewById(R.id.NewsItemNameText); final ImageView image = (ImageView) convertView.findViewById(R.id.NewsItemImage); if (story != null) { if (isPlayable(story)) { if (repository.getPlaylistItemFromStoryId(story.getId()) == null) { icon.setImageDrawable( getContext().getResources().getDrawable(R.drawable.speaker_icon) ); } else { icon.setImageDrawable( getContext().getResources().getDrawable(R.drawable .news_item_in_playlist) ); } icon.setVisibility(View.VISIBLE); } else { // Because views are re-used we have to set this each time icon.setVisibility(View.INVISIBLE); } name.setText(Html.fromHtml(story.toString())); // Need to (re)set this because the views are reused. If we don't then // while scrolling, some items will replace the old "Load more stories" // view and will be in italics name.setTypeface(name.getTypeface(), Typeface.BOLD); String topicText = story.getSlug(); for (Story.Parent p : story.getParents()) { if (p.isPrimary()) { topicText = p.getTitle(); } } if (topicText != null) { topic.setText(topicText.toLowerCase()); } else { topic.setText(""); } topic.setVisibility(View.VISIBLE); String imageUrl = null; if (story.getThumbnails().size() > 0) { imageUrl = story.getThumbnails().get(0).getMedium(); } else if (story.getImages().size() > 0) { for (Map.Entry<String, Story.Image> entry : story.getImages().entrySet()) { if (imageUrl == null) { imageUrl = entry.getValue().getSrc(); } else if (entry.getValue().getType().equals("primary")) { imageUrl = entry.getValue().getSrc(); break; } } } if (imageUrl != null) { Drawable cachedImage = imageLoader.loadImage( imageUrl, new ImageLoadListener(position, (ListView) parent) ); image.setImageDrawable(cachedImage); image.setVisibility(View.VISIBLE); } else { image.setVisibility(View.GONE); } } else { // null marker means it's the end of the list. icon.setVisibility(View.INVISIBLE); topic.setVisibility(View.GONE); image.setVisibility(View.GONE); name.setTypeface(name.getTypeface(), Typeface.ITALIC); name.setText(R.string.msg_load_more); } return convertView; } public void addMoreStories(final String url, final int count) { new Thread(new Runnable() { @Override public void run() { if (getMoreStories(url, count)) { handler.sendEmptyMessage(0); } else { handler.sendEmptyMessage(-1); } } }).start(); } public void addAllStories(final String url) { addMoreStories(url, Integer.MAX_VALUE); } private boolean getMoreStories(String url, int count) { Node stories = null; try { stories = new Client(url, getContext()).execute(); } catch (IOException e) { Log.e(LOG_TAG, "", e); return false; } catch (SAXException e) { Log.e(LOG_TAG, "", e); } catch (ParserConfigurationException e) { Log.e(LOG_TAG, "", e); } if (stories == null) { Log.d(LOG_TAG, "stories: none"); } else { Log.d(LOG_TAG, "stories: " + stories.getNodeName()); moreStories = StoryFactory.parseStories(stories); if (moreStories != null) { if (moreStories.size() < count) { endReached = true; } NewsListActivity.addAllToStoryCache(moreStories); for (Story story : moreStories) { for (Story.Parent parent : story.getParents()) { if (parent.getType().equals("book")) { List<Book> books = Book.downloadBooks(parent.getApiLink(), story.getId(),getContext()); if (books != null) { NewsListActivity.addBooksToCache(story.getId(), books); } } } } } } return true; } /** * @return a comma-separated list of story ID's */ public String getStoryIdList() { StringBuilder ids = new StringBuilder(); for (int i = 0; i < getCount(); i++) { Story story = getItem(i); if (story != null) { ids.append(story.getId()).append(","); } } String result = ids.toString(); if (result.endsWith(",")) { result = result.substring(0, result.length() - 1); } return result; } /** * Returns the time (in milliseconds since the epoch) of when * the last update was. * * @return A time unit in milliseconds since the epoch */ public long getLastUpdate() { return lastUpdate; } /** * A call back that can be used to be notified when stories are done * loading. */ public interface StoriesLoadedListener { void storiesLoaded(); } /** * Sets a listener to be notified when stories are done loading * * @param listener A {@link StoriesLoadedListener} */ public void setStoriesLoadedListener(StoriesLoadedListener listener) { storiesLoadedListener = listener; } private class ImageLoadListener implements ImageThreadLoader.ImageLoadedListener { private int position; private ListView parent; public ImageLoadListener(int position, ListView parent) { this.position = position; this.parent = parent; } public void imageLoaded(Drawable imageBitmap) { View itemView = parent.getChildAt(position - parent.getFirstVisiblePosition()); if (itemView == null) { Log.w(LOG_TAG, "Could not find list item at position " + position); return; } ImageView img = (ImageView) itemView.findViewById(R.id.NewsItemImage); if (img == null) { Log.w(LOG_TAG, "Could not find image for list item at " + "position " + position); return; } Log.d(LOG_TAG, "Drawing image at position " + position); img.setImageDrawable(imageBitmap); } } }