Skip to content

Commit

Permalink
Added clickable links to news items, don't sort by score, preserve th…
Browse files Browse the repository at this point in the history
…e order we get from the API, don't load comments if they've already been downloaded
  • Loading branch information
emmaguy committed Mar 29, 2015
1 parent 5d8305b commit cb35698
Show file tree
Hide file tree
Showing 16 changed files with 122 additions and 42 deletions.
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ dependencies {
compile 'com.android.support:appcompat-v7:22.0.+'
compile 'com.android.support:recyclerview-v7:21.0.+'
compile 'com.jakewharton:butterknife:6.1.+'
compile 'net.danlew:android.joda:2.7.1'

// treeview for comments
compile 'com.github.bmelnychuk:atv:1.2.+'

// material ripple for touch feedback
compile 'com.balysv:material-ripple:1.0.1'

testCompile 'junit:junit:4.+'
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/java/com/emmaguy/hn/Utils.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.emmaguy.hn;

import android.content.Intent;
import android.text.format.DateUtils;
import android.util.Log;

import org.joda.time.DateTime;

/**
* Created by emma on 29/03/15.
Expand All @@ -13,4 +17,8 @@ public static Intent getShareIntent(String url) {
i.putExtra(Intent.EXTRA_TEXT, url);
return Intent.createChooser(i, null);
}

public static CharSequence getRelativeTimeSpanString(long time) {
return DateUtils.getRelativeTimeSpanString(time * 1000, DateTime.now().getMillis(), DateUtils.SECOND_IN_MILLIS);
}
}
14 changes: 13 additions & 1 deletion app/src/main/java/com/emmaguy/hn/comments/CommentsActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
public class CommentsActivity extends ActionBarActivity implements CommentsView {
public static final String EXTRA_NEWS_ITEM_ID = "key_news_item_id";
public static final String EXTRA_NEWS_ITEM_TITLE = "key_news_item_title";
public static final String EXTRA_NEWS_ITEM_AUTHOR = "key_news_item_author";
public static final String EXTRA_NEWS_ITEM_PERMALINK = "key_news_item_permalink";
public static final String EXTRA_NEWS_ITEM_COMMENT_KEYS_ID = "key_news_item_comment_ids";

Expand All @@ -45,10 +46,13 @@ public class CommentsActivity extends ActionBarActivity implements CommentsView
private String mTitle;
private String mPermalink;
private String mNewsItemId;
private String mNewsItemAuthor;

private NewsDataSource mDataSource;
private CommentsPresenter mPresenter;

private boolean mCommentsTreeViewAdded = false;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Expand All @@ -61,8 +65,9 @@ protected void onCreate(Bundle savedInstanceState) {
mTitle = getIntent().getStringExtra(EXTRA_NEWS_ITEM_TITLE);
setTitle(mTitle);

mPermalink = getIntent().getStringExtra(EXTRA_NEWS_ITEM_PERMALINK);
mNewsItemId = getIntent().getStringExtra(EXTRA_NEWS_ITEM_ID);
mNewsItemAuthor = getIntent().getStringExtra(EXTRA_NEWS_ITEM_AUTHOR);
mPermalink = getIntent().getStringExtra(EXTRA_NEWS_ITEM_PERMALINK);
ArrayList<String> ids = getIntent().getStringArrayListExtra(EXTRA_NEWS_ITEM_COMMENT_KEYS_ID);

mDataSource = HackerNewsDataSource.getInstance();
Expand Down Expand Up @@ -152,6 +157,11 @@ public void showComments(List<Comment> comments) {
addTreeView(root);
}

@Override
public boolean isEmpty() {
return !mCommentsTreeViewAdded;
}

private void addNode(TreeNode root, ArrayList<Comment> toRemoveComments, HashMap<String, TreeNode> addedNodes, Comment c) {
TreeNode node = new TreeNode(c);
root.addChild(node);
Expand All @@ -161,6 +171,8 @@ private void addNode(TreeNode root, ArrayList<Comment> toRemoveComments, HashMap
}

private void addTreeView(TreeNode root) {
mCommentsTreeViewAdded = true;

int horizontal = getResources().getDimensionPixelOffset(R.dimen.activity_horizontal_margin);
int vertical = getResources().getDimensionPixelOffset(R.dimen.activity_vertical_margin);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@

import butterknife.ButterKnife;
import butterknife.InjectView;
import butterknife.OnClick;


public class NewsItemDetailActivity extends ActionBarActivity {
public static final String EXTRA_NEWS_ITEM_KEY_URL = "key_news_item_url";
public static final String EXTRA_NEWS_ITEM_KEY_TITLE = "key_news_item_title";

@InjectView(R.id.toolbar) Toolbar mToolbar;
@InjectView(R.id.news_item_detail_web_view_content) WebView mWebView;
@InjectView(R.id.news_item_detail_progress_bar_loading) ProgressBar mProgressBar;

private String mTitle;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Expand All @@ -34,6 +38,8 @@ protected void onCreate(Bundle savedInstanceState) {
ButterKnife.inject(this);

setSupportActionBar(mToolbar);
mTitle = getIntent().getStringExtra(EXTRA_NEWS_ITEM_KEY_TITLE);
setTitle(mTitle);

String url = getIntent().getStringExtra(EXTRA_NEWS_ITEM_KEY_URL);

Expand All @@ -60,6 +66,11 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) {
});
}

@OnClick(R.id.toolbar)
void viewFullTitle() {
Toast.makeText(this, mTitle, Toast.LENGTH_SHORT).show();
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
Expand Down
26 changes: 20 additions & 6 deletions app/src/main/java/com/emmaguy/hn/newsitems/NewsItemsAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.text.format.DateUtils;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.emmaguy.hn.R;
import com.emmaguy.hn.Utils;
import com.emmaguy.hn.comments.CommentsActivity;
import com.emmaguy.hn.model.NewsItem;
import com.emmaguy.hn.newsitemdetail.NewsItemDetailActivity;
Expand Down Expand Up @@ -53,22 +54,29 @@ public NewsItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
public void onBindViewHolder(NewsItemHolder holder, int position) {
NewsItem newsItem = mNewsItems.get(position);

if (TextUtils.isEmpty(newsItem.getUrl())) {
holder.mUrl.setVisibility(View.GONE);
} else {
holder.mUrl.setVisibility(View.VISIBLE);
holder.mUrl.setText(newsItem.getUrl());
}

holder.mTitle.setText(newsItem.getTitle());
String text = mContext.getResources().getQuantityString(R.plurals.news_item_description,
newsItem.getScore(),
newsItem.getScore(),
newsItem.getAuthor(),
DateUtils.getRelativeTimeSpanString(newsItem.getTime(), System.currentTimeMillis(), DateUtils.SECOND_IN_MILLIS));
Utils.getRelativeTimeSpanString(newsItem.getTime()));
holder.mDescription.setText(text);
}


@Override
public int getItemCount() {
return mNewsItems.size();
}

public class NewsItemHolder extends RecyclerView.ViewHolder {
@InjectView(R.id.row_news_item_textview_url) TextView mUrl;
@InjectView(R.id.row_news_item_textview_title) TextView mTitle;
@InjectView(R.id.row_news_item_textview_description) TextView mDescription;

Expand All @@ -84,6 +92,7 @@ public void viewComments() {

Intent intent = new Intent(mContext, CommentsActivity.class);
intent.putExtra(CommentsActivity.EXTRA_NEWS_ITEM_ID, newsItem.getId());
intent.putExtra(CommentsActivity.EXTRA_NEWS_ITEM_AUTHOR, newsItem.getAuthor());
intent.putExtra(CommentsActivity.EXTRA_NEWS_ITEM_TITLE, newsItem.getTitle());
intent.putExtra(CommentsActivity.EXTRA_NEWS_ITEM_PERMALINK, newsItem.getPermalink());
intent.putExtra(CommentsActivity.EXTRA_NEWS_ITEM_COMMENT_KEYS_ID, newsItem.getRootCommentIds());
Expand All @@ -94,9 +103,14 @@ public void viewComments() {
public void viewLink() {
NewsItem newsItem = mNewsItems.get(getPosition());

Intent intent = new Intent(mContext, NewsItemDetailActivity.class);
intent.putExtra(NewsItemDetailActivity.EXTRA_NEWS_ITEM_KEY_URL, newsItem.getUrl());
mContext.startActivity(intent);
if (TextUtils.isEmpty(newsItem.getUrl())) {
viewComments();
} else {
Intent intent = new Intent(mContext, NewsItemDetailActivity.class);
intent.putExtra(NewsItemDetailActivity.EXTRA_NEWS_ITEM_KEY_URL, newsItem.getUrl());
intent.putExtra(NewsItemDetailActivity.EXTRA_NEWS_ITEM_KEY_TITLE, newsItem.getTitle());
mContext.startActivity(intent);
}
}
}
}
5 changes: 5 additions & 0 deletions app/src/main/res/color/selector_news_item_web_link.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="@color/main_secondary" />
<item android:color="@color/main" />
</selector>
4 changes: 2 additions & 2 deletions app/src/main/res/drawable/news_item_detail_progress_bar.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
<shape>
<corners android:radius="5dip" />

<solid android:color="@color/main" />
<solid android:color="@color/main_secondary" />
</shape>
</clip>

<color android:color="@color/main" />
<color android:color="@color/main_secondary" />
</item>
</layer-list>
5 changes: 5 additions & 0 deletions app/src/main/res/drawable/selector_news_item_background.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@color/main_alpha" />
<item android:drawable="@android:color/transparent" />
</selector>
17 changes: 16 additions & 1 deletion app/src/main/res/layout/row_news_item.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,32 @@
android:paddingRight="@dimen/news_item_padding"
android:textColor="@color/text_main"
android:maxLines="1"
android:singleLine="true"
android:ellipsize="end"
android:textSize="15sp"
tools:text="awesome story title blah blah" />

<TextView
android:id="@+id/row_news_item_textview_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/news_item_padding"
android:autoLink="web"
android:textColorLink="@color/selector_news_item_web_link"
android:background="@drawable/selector_news_item_background"
android:paddingTop="1dp"
android:paddingBottom="1dp"
android:maxLines="1"
android:singleLine="true"
android:textSize="12sp"
tools:text="www.emmaisawesome.com" />

<TextView
android:id="@+id/row_news_item_textview_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/text_secondary"
android:paddingLeft="@dimen/news_item_padding"
android:paddingTop="4dp"
android:textSize="10sp"
tools:text="123 points" />

Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="main">#FF6F00</color>
<color name="main_alpha">#1FFF6F00</color>

<color name="main_secondary">#E65100</color>

<color name="text_main">#000000</color>
Expand All @@ -9,4 +11,5 @@
<color name="background">#FFFFFF</color>
<color name="background_main">#EFEFEF</color>
<color name="background_news_item_contrast">#F6F6F6</color>

</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import rx.schedulers.Schedulers;

/**
* A @{link NewsDataSource} which retrieves stories from the Hacker News API
* A {@link NewsDataSource} which retrieves the latest, top news items and comments from the Hacker News API
* <p/>
* Created by emma on 21/03/15.
*/
Expand Down Expand Up @@ -58,15 +58,17 @@ public void getLatestNewsItems() {
}

/**
* Creates an observable which fetches the @{link MAX_NUMBER_STORIES} top new items
* Creates an observable which retrieves the ids of the latest top stories and then fetches a list containing
* {@link MAX_NUMBER_STORIES} {@link NewsItem}s. Uses concatMap to preserve ordering, so we don't have to sort,
* we can just show the news items in the order we're given
*
* @return Observable list of the top @{link NewsItem}s
* @return Observable list of the top {@link NewsItem}s
*/
private Observable<List<NewsItem>> createLatestNewsItemsObservable() {
return mHackerNewsApiService.topStories()
.lift(this.<String>flattenList())
.limit(MAX_NUMBER_STORIES)
.flatMap(new Func1<String, Observable<NewsItem>>() {
.concatMap(new Func1<String, Observable<NewsItem>>() {
@Override
public Observable<NewsItem> call(String id) {
return mHackerNewsApiService.item(id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ public OnListNewsItemNextListener(Bus networkBus) {

@Override
public void call(List<NewsItem> newsItems) {
// using rxjava's 'toSortedList' causes it to create an unmodifiable list wrapper which we do not want
// as then otto won't receive the items. So we have to sort the items manually
Collections.sort(newsItems, new NewsItemComparator());

mNetworkBus.post(new Events.NewsItemsSuccessEvent(newsItems));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ public CommentsPresenterImpl(CommentsView view, ArrayList<String> ids, NewsDataS
public void onStart() {
mNetworkBus.register(this);

mView.showLoadingIndicator();
mDataSource.getComments(mIds);
if (mView.isEmpty()) {
mView.showLoadingIndicator();
mDataSource.getComments(mIds);
}
}

@Override
Expand Down
2 changes: 2 additions & 0 deletions mvp/src/main/java/com/emmaguy/hn/view/CommentsView.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ public interface CommentsView {
void hideLoadingIndicator();

void showComments(List<Comment> comments);

boolean isEmpty();
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,4 @@ public void test_getLatestNewsItems_postsNewsItemsOnEventBus() {

verify(mMockNetworkBus, times(1)).post(any(Events.NewsItemsSuccessEvent.class));
}

@Test
public void test_postingNewsItems_sortsNewsItems() {
NewsItem item1 = new NewsItem();
item1.setScore(10);

NewsItem item2 = new NewsItem();
item2.setScore(50);

NewsItem item3 = new NewsItem();
item3.setScore(30);

List<NewsItem> unsortedNewsItems = Arrays.asList(item1, item2, item3);
mNextListener.call(unsortedNewsItems);

verify(mMockNetworkBus, times(1)).post(any(Events.NewsItemsSuccessEvent.class));

assertThat(unsortedNewsItems, contains(item2, item3, item1));
}
}
Loading

0 comments on commit cb35698

Please sign in to comment.