* @author Hal
* @version Mar 11, 2013
*/
public class SubmitActivity extends FragmentActivity {
}
================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/BaseFragment.java
================================================
package com.halzhang.android.apps.startupnews.ui.fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import rx.subscriptions.CompositeSubscription;
/**
* Created by Hal on 16/6/18.
*/
public class BaseFragment extends Fragment {
protected CompositeSubscription mCompositeSubscription;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCompositeSubscription = new CompositeSubscription();
}
@Override
public void onDestroyView() {
mCompositeSubscription.clear();
super.onDestroyView();
}
}
================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/CommentsListFragment.java
================================================
/**
* Copyright (C) 2013 HalZhang
*/
package com.halzhang.android.apps.startupnews.ui.fragment;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.analytics.Tracker;
import com.halzhang.android.apps.startupnews.presenter.CommentsListContract;
import com.halzhang.android.apps.startupnews.ui.DiscussActivity;
import com.halzhang.android.startupnews.data.entity.SNComment;
import com.halzhang.android.startupnews.data.entity.SNComments;
import java.util.ArrayList;
/**
* StartupNews
*
* 评论
*
*
* @author Hal
* @version Mar 7, 2013
*/
public class CommentsListFragment extends SwipeRefreshRecyclerFragment implements CommentsListContract.View {
private static final String LOG_TAG = CommentsListFragment.class.getSimpleName();
// private ArrayList mComments = new ArrayList(24);
private CommentsListContract.Presenter mPresenter;
private CommentsAdapter mAdapter;
private SNComments mSnComments = new SNComments();
private static final String NEWCOMMENTS_URL_PATH = "/newcomments";
public CommentsListFragment() {
}
public static CommentsListFragment newInstance() {
return new CommentsListFragment();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAdapter = new CommentsAdapter();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mRecyclerView.setAdapter(mAdapter);
if (mAdapter.isEmpty()) {
mPresenter.getComments(getString(R.string.host, NEWCOMMENTS_URL_PATH));
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
protected void onRefreshData() {
super.onRefreshData();
Tracker.getInstance().sendEvent("ui_action", "pull_down_list_view_refresh",
"comments_list_fragment_pull_down_list_view_refresh", 0L);
mPresenter.getComments(getString(R.string.host, NEWCOMMENTS_URL_PATH));
}
@Override
protected void onLoadMore() {
super.onLoadMore();
Tracker.getInstance().sendEvent("ui_action", "pull_up_list_view_refresh",
"comments_list_fragment_pull_up_list_view_refresh", 0L);
mPresenter.getMoreComments();
}
@Override
public void onSuccess(ArrayList snComments) {
onRefreshComplete();
mSnComments.setSnComments(snComments);
mAdapter.notifyDataSetChanged();
}
@Override
public void onFailure(Throwable e) {
Log.e(LOG_TAG, "", e);
onRefreshComplete();
Tracker.getInstance().sendException("CommentsTask", e, false);
Toast.makeText(getActivity(), R.string.error, Toast.LENGTH_LONG).show();
}
@Override
public void onAtEnd() {
onRefreshComplete();
Toast.makeText(getActivity(), R.string.tip_last_page, Toast.LENGTH_SHORT).show();
}
@Override
public void setPresenter(CommentsListContract.Presenter presenter) {
mPresenter = presenter;
}
@Override
public boolean isActive() {
return isAdded();
}
private class CommentsAdapter extends RecyclerView.Adapter {
public final class ViewHolder extends RecyclerView.ViewHolder {
public final TextView mUserId;
public final TextView mCreated;
public final TextView mCommentText;
public final TextView mArtistTitle;
public final View mItemView;
public ViewHolder(View itemView) {
super(itemView);
mItemView = itemView;
mUserId = (TextView) itemView.findViewById(R.id.comment_item_user_id);
mCreated = (TextView) itemView.findViewById(R.id.comment_item_created);
mCommentText = (TextView) itemView.findViewById(R.id.comment_item_text);
mArtistTitle = (TextView) itemView.findViewById(R.id.comment_item_artist_titile);
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(getActivity()).inflate(R.layout.comment_list_item, null));
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
SNComment comment = mSnComments.getSnComments().get(position);
holder.mUserId.setText(comment.getUser().getId());
holder.mCreated.setText(comment.getCreated());
holder.mCommentText.setText(comment.getText());
holder.mArtistTitle.setText(getString(R.string.comment_artist_title, comment.getArtistTitle()));
holder.mItemView.setTag(position);
holder.mItemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Tracker.getInstance().sendEvent("ui_action", "list_item_click", "comments_list_fragment_list_item_click", 0L);
int position = (int) v.getTag();
SNComment comment = mSnComments.getSnComments().get(position);
Intent intent = new Intent(getActivity(), DiscussActivity.class);
intent.putExtra(DiscussActivity.ARG_DISCUSS_URL, comment.getDiscussURL());
startActivity(intent);
}
});
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getItemCount() {
return mSnComments.size();
}
public boolean isEmpty() {
return getItemCount() == 0;
}
}
@Override
public int getViewLayout() {
return R.layout.refresh_recycler_view_layout;
}
}
================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/LoginFragment.java
================================================
package com.halzhang.android.apps.startupnews.ui.fragment;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.halzhang.android.apps.startupnews.Constants;
import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.presenter.LoginContract;
import com.halzhang.android.common.CDToast;
import rx.Subscription;
/**
* A simple {@link Fragment} subclass.
* Use the {@link LoginFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class LoginFragment extends BaseFragment implements LoginContract.View {
// UI references.
private EditText mUsernameView;
private EditText mPasswordView;
private View mLoginFormView;
private View mLoginStatusView;
private TextView mLoginStatusMessageView;
private LoginContract.Presenter mPresenter;
public LoginFragment() {
}
public static LoginFragment newInstance() {
return new LoginFragment();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onAttach(Context activity) {
super.onAttach(activity);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_login, null);
mUsernameView = (EditText) view.findViewById(R.id.username);
mPasswordView = (EditText) view.findViewById(R.id.password);
mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
if (id == R.id.login || id == EditorInfo.IME_NULL) {
attemptLogin();
return true;
}
return false;
}
});
mLoginFormView = view.findViewById(R.id.login_form);
mLoginStatusView = view.findViewById(R.id.login_status);
mLoginStatusMessageView = (TextView) view.findViewById(R.id.login_status_message);
mLoginStatusMessageView.setText(R.string.login_progress_init);
showProgress(false);
Button loginBtn = (Button) view.findViewById(R.id.btn_login);
loginBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
attemptLogin();
}
});
return view;
}
/**
* Attempts to sign in or register the account specified by the login
* form. If there are form errors (invalid email, missing fields, etc.),
* the errors are presented and no actual login attempt is made.
*/
public void attemptLogin() {
// Reset errors.
mUsernameView.setError(null);
mPasswordView.setError(null);
String username = mUsernameView.getText().toString();
String password = mPasswordView.getText().toString();
boolean cancel = false;
View focusView = null;
if (TextUtils.isEmpty(password)) {
mPasswordView.setError(getString(R.string.error_field_required));
focusView = mPasswordView;
cancel = true;
}
if (TextUtils.isEmpty(username)) {
mUsernameView.setError(getString(R.string.error_field_required));
focusView = mUsernameView;
cancel = true;
}
if (cancel) {
focusView.requestFocus();
} else {
mLoginStatusMessageView.setText(R.string.login_progress_signing_in);
showProgress(true);
mPresenter.login(username, password);
}
}
/**
* Shows the progress UI and hides the login form.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
public void showProgress(final boolean show) {
if (getActivity() == null) {
//防止 not attact to activity 出错
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
int shortAnimTime = getResources().getInteger(
android.R.integer.config_shortAnimTime);
mLoginStatusView.setVisibility(View.INVISIBLE);
mLoginStatusView.animate().setDuration(shortAnimTime).alpha(show ? 1 : 0)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
}
});
mLoginFormView.setVisibility(View.INVISIBLE);
mLoginFormView.animate().setDuration(shortAnimTime).alpha(show ? 0 : 1)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
});
} else {
mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
}
@Override
public void onLoginError(Throwable e) {
e.printStackTrace();
}
@Override
public void onLoginResult(String user) {
Activity activity = getActivity();
if (activity == null || activity.isFinishing()) {
return;
}
showProgress(false);
if (!TextUtils.isEmpty(user)) {
CDToast.showToast(activity, R.string.tip_login_success);
Intent intent = new Intent(Constants.IntentAction.ACTION_LOGIN);
intent.putExtra(Constants.IntentAction.EXTRA_LOGIN_USER, user);
activity.sendBroadcast(intent);
activity.finish();
} else {
CDToast.showToast(activity, R.string.tip_login_failure);
}
}
@Override
public void addSubscription(Subscription subscription) {
mCompositeSubscription.add(subscription);
}
@Override
public void setPresenter(LoginContract.Presenter presenter) {
mPresenter = presenter;
}
@Override
public boolean isActive() {
return isAdded();
}
}
================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/NewsListFragment.java
================================================
/**
* Copyright (C) 2013 HalZhang
*/
package com.halzhang.android.apps.startupnews.ui.fragment;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.TextView;
import android.widget.Toast;
import com.halzhang.android.apps.startupnews.Constants.IntentAction;
import com.halzhang.android.apps.startupnews.MyApplication;
import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.SnApiComponent;
import com.halzhang.android.apps.startupnews.analytics.Tracker;
import com.halzhang.android.apps.startupnews.presenter.DaggerNewsListFragmentComponent;
import com.halzhang.android.apps.startupnews.presenter.NewsListContract;
import com.halzhang.android.apps.startupnews.presenter.NewsListPresenter;
import com.halzhang.android.apps.startupnews.presenter.NewsListPresenterModule;
import com.halzhang.android.apps.startupnews.ui.DiscussActivity;
import com.halzhang.android.apps.startupnews.utils.AppUtils;
import com.halzhang.android.common.CDLog;
import com.halzhang.android.startupnews.data.entity.SNFeed;
import com.halzhang.android.startupnews.data.entity.SNNew;
import java.util.ArrayList;
import javax.inject.Inject;
/**
* StartupNews
*
*
*
* @author Hal
* @version Mar 7, 2013
*/
public class NewsListFragment extends SwipeRefreshRecyclerFragment implements NewsListContract.View {
private static final String LOG_TAG = NewsListFragment.class.getSimpleName();
@Inject
NewsListPresenter mNewsListPresenter;
private NewsListContract.Presenter mPresenter;
@Override
public void setPresenter(NewsListContract.Presenter presenter) {
mPresenter = presenter;
}
@Override
public boolean isActive() {
return isAdded();
}
@Override
public void onSuccess(ArrayList snNews) {
onRefreshComplete();
mSnFeed.setSnNews(snNews);
mAdapter.notifyDataSetChanged();
}
@Override
public void onFailure(Throwable e) {
CDLog.w(LOG_TAG, "", e);
onRefreshComplete();
Toast.makeText(getActivity(), R.string.error, Toast.LENGTH_LONG).show();
Tracker.getInstance().sendException("NewsTask", e, false);
}
@Override
public void onAtEnd() {
onRefreshComplete();
Toast.makeText(getActivity(), R.string.tip_last_page, Toast.LENGTH_SHORT).show();
}
/**
* {@link NewsListFragment}选中监听器
*/
public interface OnNewsSelectedListener {
/**
* 处理news被选中事件
*
* @param position list position
* @param snNew {@link SNNew}
*/
public void onNewsSelected(int position, SNNew snNew);
}
private OnNewsSelectedListener mNewsSelectedListener;
private String mNewsURL;
public static final String ARG_URL = "new_url";
private SNFeed mSnFeed = new SNFeed();
private NewsAdapter mAdapter;
public NewsListFragment() {
}
public static NewsListFragment newInstance(String url) {
NewsListFragment fragment = new NewsListFragment();
Bundle args = new Bundle(1);
args.putString(ARG_URL, url);
fragment.setArguments(args);
return fragment;
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (IntentAction.ACTION_LOGIN.equals(action)) {
String user = intent.getStringExtra(IntentAction.EXTRA_LOGIN_USER);
if (!TextUtils.isEmpty(user)) {
mPresenter.getFeed(mNewsURL);
}
}
}
};
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mNewsSelectedListener = (OnNewsSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnNewsSelectedListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mNewsSelectedListener = null;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAdapter = new NewsAdapter();
Bundle args = getArguments();
if (args != null) {
mNewsURL = args.getString(ARG_URL);
} else {
mNewsURL = getString(R.string.host, "/news");
}
IntentFilter filter = new IntentFilter();
filter.addAction(IntentAction.ACTION_LOGIN);
getActivity().registerReceiver(mReceiver, filter);
SnApiComponent snApiComponent = ((MyApplication) getActivity().getApplication()).getSnApiComponent();
DaggerNewsListFragmentComponent.builder().snApiComponent(snApiComponent)
.newsListPresenterModule(new NewsListPresenterModule(this)).build().inject(this);
}
@Override
protected int getViewLayout() {
return R.layout.refresh_recycler_view_layout;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mRecyclerView.setAdapter(mAdapter);
if (mAdapter.isEmpty()) {
mPresenter.getFeed(mNewsURL);
mSwipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
mSwipeRefreshLayout.setRefreshing(true);
}
});
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
CDLog.d(LOG_TAG, this.toString() + " destroy view!");
}
@Override
public void onDestroy() {
super.onDestroy();
getActivity().unregisterReceiver(mReceiver);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
int position = ((AdapterContextMenuInfo) item.getMenuInfo()).position;
final SNNew snNew = mSnFeed.getSnNews().get(position);
Log.i(LOG_TAG, snNew.toString());
switch (item.getItemId()) {
case R.id.menu_show_comment:
Tracker.getInstance().sendEvent("ui_action", "context_item_selected",
"newslistfragment_menu_show_comment", 0L);
openDiscuss(snNew);
return true;
case R.id.menu_show_article:
Tracker.getInstance().sendEvent("ui_action", "context_item_selected",
"newslistfragment_menu_show_acticle", 0L);
openArticle(position - 1, snNew);
return true;
case R.id.menu_up_vote:
Tracker.getInstance().sendEvent("ui_action", "context_item_selected",
"newslistfragment_menu_upvote", 0L);
// TODO: 16/8/14 投票操作
return true;
default:
break;
}
return true;
}
@Override
protected void onRefreshData() {
super.onRefreshData();
Tracker.getInstance().sendEvent("ui_action", "pull_down_list_view_refresh", "news_list_fragment_pull_down_list_view_refresh", 0L);
mPresenter.getFeed(mNewsURL);
}
@Override
protected void onLoadMore() {
super.onLoadMore();
Tracker.getInstance().sendEvent("ui_action", "pull_up_list_view_refresh", "news_list_fragment_pull_up_list_view_refresh", 0L);
mPresenter.getMoreFeed();
}
private void openArticle(int position, SNNew snNew) {
if (mNewsSelectedListener != null) {
mNewsSelectedListener.onNewsSelected(position, snNew);
}
}
private void openDiscuss(SNNew snNew) {
if (snNew == null) {
return;
}
DiscussActivity.start(getActivity(),snNew.getDiscussURL(),snNew);
}
private class NewsAdapter extends RecyclerView.Adapter {
public final class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener {
public final TextView user;
public final TextView createAt;
public final TextView title;
public final TextView subText;
public final TextView domain;
public final View mView;
public ViewHolder(View itemView) {
super(itemView);
mView = itemView;
user = (TextView) itemView.findViewById(R.id.news_item_user);
createAt = (TextView) itemView.findViewById(R.id.news_item_createat);
title = (TextView) itemView.findViewById(R.id.news_item_title);
subText = (TextView) itemView.findViewById(R.id.news_item_subtext);
domain = (TextView) itemView.findViewById(R.id.news_item_domain);
mView.setOnCreateContextMenuListener(this);
mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = (int) v.getTag();
SNNew entity = mSnFeed.getSnNews().get(position);
Tracker.getInstance().sendEvent("ui_action", "list_item_click",
"news_list_fragment_list_item_click", 0L);
mAdapter.notifyDataSetChanged();
openArticle(position, entity);
}
});
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
getActivity().getMenuInflater().inflate(R.menu.fragment_news, menu);
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.news_list_item, null));
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final SNNew entity = mSnFeed.getSnNews().get(position);
holder.user.setText(entity.getUser().getId());
holder.title.setText(entity.getTitle());
holder.subText.setText(getString(R.string.news_subtext, entity.getPoints(),
entity.getCommentsCount()));
holder.createAt.setText(entity.getCreateat());
holder.domain.setText(entity.getUrlDomain());
int textColor = AppUtils.getMyApplication(getActivity()).isHistoryContains(
entity.getUrl()) ? Color.GRAY : Color.BLACK;
holder.title.setTextColor(textColor);
holder.subText.setTextColor(textColor);
holder.domain.setTextColor(textColor);
holder.createAt.setTextColor(textColor);
holder.mView.setTag(position);
}
@Override
public int getItemCount() {
return mSnFeed.getSnNews().size();
}
public boolean isEmpty() {
return getItemCount() == 0;
}
}
}
================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/fragment/SwipeRefreshRecyclerFragment.java
================================================
package com.halzhang.android.apps.startupnews.ui.fragment;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.ui.widgets.CardViewDividerDecoration;
import com.halzhang.android.apps.startupnews.ui.widgets.DividerDecoration;
public abstract class SwipeRefreshRecyclerFragment extends Fragment {
private static final String LOG_TAG = SwipeRefreshRecyclerFragment.class.getSimpleName();
protected SwipeRefreshLayout mSwipeRefreshLayout;
protected RecyclerView mRecyclerView;
private LinearLayoutManager mLinearLayoutManager;
public SwipeRefreshRecyclerFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
protected abstract int getViewLayout();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(getViewLayout(), null);
mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipe_refresh_layout);
if (mRecyclerView == null || mSwipeRefreshLayout == null) {
throw new IllegalArgumentException("mush be have RecyclerView and SwipeRefreshLayout");
}
mSwipeRefreshLayout.setColorSchemeResources(android.R.color.holo_red_light, android.R.color.holo_orange_light, android.R.color.holo_green_light, android.R.color.holo_blue_light);
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
onRefreshData();
}
});
mLinearLayoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(mLinearLayoutManager);
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
boolean enable = false;
int visibleItemCount = mRecyclerView.getChildCount();
int itemCount = mLinearLayoutManager.getItemCount();
int firstVisibleItemPosition = mLinearLayoutManager.findFirstVisibleItemPosition();
int lastVisibleItemPosition = mLinearLayoutManager.findLastVisibleItemPosition();
// if (visibleItemCount > 0) {
// boolean firstItemVisible = firstVisibleItemPosition == 0;
// boolean topOfFirstItemVisible = mLinearLayoutManager.getChildAt(0).getTop() == 0;
// enable = firstItemVisible && topOfFirstItemVisible;
// Log.d(LOG_TAG, "SwipeRefreshLayout enable: " + enable);
// }
// mSwipeRefreshLayout.setEnabled(enable);
if (lastVisibleItemPosition == itemCount - 1) {
onLoadMore();
}
}
});
mRecyclerView.addItemDecoration(new CardViewDividerDecoration(getActivity()));
return view;
}
/**
* 刷新数据
*/
protected void onRefreshData() {
}
/**
* 加载更多
*/
protected void onLoadMore() {
}
protected void onRefreshComplete() {
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(false);
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
}
@Override
public void onDetach() {
super.onDetach();
}
}
================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/phone/BrowseActivity.java
================================================
/**
* Copyright (C) 2013 HalZhang
*/
package com.halzhang.android.apps.startupnews.ui.phone;
import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.ui.BaseFragmentActivity;
import com.halzhang.android.apps.startupnews.ui.tablet.BrowseFragment;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.text.TextUtils;
import android.view.MenuItem;
import android.view.Window;
/**
* StartupNews
*
* 浏览页面
*
*
* @author Hal
* @version Mar 7, 2013
*/
public class BrowseActivity extends BaseFragmentActivity {
// private static final String LOG_TAG =
// BrowseActivity.class.getSimpleName();
public static final String EXTRA_URL = "extra_url";
public static final String EXTRA_TITLE = "extra_title";
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
setContentView(R.layout.activity_browse);
String mOriginalUrl = getIntent().getStringExtra(EXTRA_URL);
if (TextUtils.isEmpty(mOriginalUrl)) {
finish();
return;
}
String mTitle = getIntent().getStringExtra(EXTRA_TITLE);
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
}
BrowseFragment fragment = new BrowseFragment();
Bundle bundle = new Bundle();
bundle.putString(EXTRA_URL, mOriginalUrl);
bundle.putString(EXTRA_TITLE, mTitle);
fragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, fragment)
.commitAllowingStateLoss();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/tablet/BrowseFragment.java
================================================
package com.halzhang.android.apps.startupnews.ui.tablet;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.view.MenuCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.ShareActionProvider;
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 com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.analytics.Tracker;
import com.halzhang.android.apps.startupnews.ui.BaseFragmentActivity;
import com.halzhang.android.apps.startupnews.ui.phone.BrowseActivity;
import com.halzhang.android.apps.startupnews.ui.widgets.ObservableWebView;
import com.halzhang.android.apps.startupnews.ui.widgets.ObservableWebView.OnScrollChangedCallback;
import com.halzhang.android.apps.startupnews.ui.widgets.WebViewController;
import com.halzhang.android.apps.startupnews.utils.PreferenceUtils;
import com.halzhang.android.common.CDLog;
/**
* 浏览器 Created by Hal on 13-5-25.
*/
public class BrowseFragment extends Fragment implements OnScrollChangedCallback {
private static final String LOG_TAG = BrowseFragment.class.getSimpleName();
private ObservableWebView mWebView;
private WebViewController mWebViewController;
private String mTitle;
private String mOriginalUrl;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mWebViewController = new WebViewController(getActivity());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setHasOptionsMenu(true);
final View view = inflater.inflate(R.layout.fragment_browse, null);
mWebView = (ObservableWebView) view.findViewById(R.id.webview);
Activity activity = getActivity();
mWebView.setOverScrollMode(View.OVER_SCROLL_NEVER);
mWebView.setOnScrollChangedCallback(this);
mWebViewController.initControllerView(mWebView, view);
Bundle args = getArguments();
if (args != null && args.containsKey(BrowseActivity.EXTRA_TITLE)
&& args.containsKey(BrowseActivity.EXTRA_URL)) {
mTitle = args.getString(BrowseActivity.EXTRA_TITLE);
mOriginalUrl = args.getString(BrowseActivity.EXTRA_URL);
String mHtmlProvider = PreferenceUtils.getHtmlProvider(activity);
final String url = mHtmlProvider + mOriginalUrl;
mWebViewController.loadUrl(url);
}
return view;
}
@Override
public void onDestroy() {
mWebViewController.destroy();
super.onDestroy();
}
public void setTitle(String title) {
mTitle = title;
}
public void load(String url) {
mOriginalUrl = url;
String mHtmlProvider = PreferenceUtils.getHtmlProvider(getActivity());
mWebViewController.loadUrl(mHtmlProvider + mOriginalUrl);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_browse, menu);
super.onCreateOptionsMenu(menu, inflater);
MenuItem actionItem = menu.findItem(R.id.menu_share);
ShareActionProvider actionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(actionItem);
if (actionProvider == null) {
return;
}
actionProvider.setShareHistoryFileName(ShareActionProvider.DEFAULT_SHARE_HISTORY_FILE_NAME);
actionProvider.setShareIntent(createShareIntent());
actionProvider.setOnShareTargetSelectedListener(new ShareActionProvider.OnShareTargetSelectedListener() {
@Override
public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) {
/*
* 这里改变intent是没用的,intent只是一份拷贝,只能自己启动修改后的Intent
* 然而自己启动Intent并不会改变历史记录 增加setAllowPolicyChangeIntent方法解决这问题
*/
String packageName = intent.getComponent().getPackageName();
CDLog.i(LOG_TAG, packageName);
String shareContent = getShareContent();
intent.putExtra(Intent.EXTRA_SUBJECT, mTitle);
Tracker.getInstance().sendEvent("ui_action", "share", packageName, 0L);
if (getString(R.string.weibo_package_name).equals(packageName)) {
intent.putExtra(Intent.EXTRA_TEXT, shareContent + " "
+ getString(R.string.weibo_share_suffix));
} else {
intent.putExtra(Intent.EXTRA_TEXT, shareContent);
}
return false;
}
});
}
/**
* Creates a sharing {@link Intent}.
*
* @return The sharing intent.
*/
private Intent createShareIntent() {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, mTitle);
intent.putExtra(Intent.EXTRA_TEXT, getShareContent());
return intent;
}
private String getShareContent() {
StringBuilder builder = new StringBuilder();
return builder.append(mTitle).append(" ").append(mOriginalUrl).toString();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_original_url:
Tracker.getInstance().sendEvent("ui_action", "options_item_selected", "browseactivity_menu_original_url", 0L);
mWebViewController.loadUrl(mOriginalUrl);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onScroll(int l, int t, int oldl, int oldt) {
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onScrollUp() {
//TODO 需要判断 Activity 的类型
BaseFragmentActivity activity = (BaseFragmentActivity) getActivity();
activity.getSupportActionBar().show();
mWebViewController.showBrowseBar();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onScrollDown() {
BaseFragmentActivity activity = (BaseFragmentActivity) getActivity();
activity.getSupportActionBar().hide();
mWebViewController.hideBrowseBar();
}
}
================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/tablet/DiscussFragment.java
================================================
package com.halzhang.android.apps.startupnews.ui.tablet;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.Html;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.halzhang.android.apps.startupnews.R;
import com.halzhang.android.apps.startupnews.analytics.Tracker;
import com.halzhang.android.apps.startupnews.presenter.DiscussContract;
import com.halzhang.android.apps.startupnews.ui.LoginActivity;
import com.halzhang.android.apps.startupnews.utils.ActivityUtils;
import com.halzhang.android.common.CDToast;
import com.halzhang.android.startupnews.data.entity.SNComment;
import com.halzhang.android.startupnews.data.entity.SNDiscuss;
import com.halzhang.android.startupnews.data.entity.SNNew;
import com.halzhang.android.startupnews.data.entity.Status;
import com.halzhang.android.startupnews.data.utils.SessionManager;
import javax.inject.Inject;
/**
* 查看评论
* Created by Hal on 13-5-26.
*/
public class DiscussFragment extends Fragment implements OnItemClickListener, DiscussContract.View {
public static final String ARG_DISCUSS_URL = "discuss_url";
public static final String ARG_SNNEW = "snnew";
private static final String LOG_TAG = DiscussFragment.class.getSimpleName();
private DiscussContract.Presenter mPresenter;
@Override
public void setPresenter(DiscussContract.Presenter presenter) {
mPresenter = presenter;
}
@Override
public boolean isActive() {
return isAdded();
}
@Override
public void onGetDiscuss(SNDiscuss snDiscuss) {
mSnDiscuss.clearComments();
mSnDiscuss.copy(snDiscuss);
setRefreshActionButtonState(false);
wrapHeaderView(mSnDiscuss.getSnNew());
mAdapter.notifyDataSetChanged();
}
@Override
public void onGetDiscussFailure(Throwable e) {
Tracker.getInstance().sendException("DiscussTask", e, false);
Toast.makeText(getActivity(), R.string.error, Toast.LENGTH_SHORT).show();
}
@Override
public void onCommentSuccess(Status status) {
switch (status.code){
case Status.CODE_COOKIE_VALID:
CDToast.showToast(getActivity(), R.string.tip_cookie_invalid);
startActivity(new Intent(getActivity(), LoginActivity.class));
Tracker.getInstance().sendEvent("ui_action_feedback",
"comment_feedback", getString(R.string.tip_cookie_invalid),
0L);
break;
case Status.CODE_SUCCESS:
mCommentEdit.setText(null);
CDToast.showToast(getActivity(), R.string.tip_comment_success);
Tracker.getInstance().sendEvent("ui_action_feedback",
"comment_feedback", "success", 0L);
loadData();
break;
default:
Tracker.getInstance().sendEvent("ui_action_feedback",
"comment_feedback", status.message, 0L);
CDToast.showToast(getActivity(), R.string.tip_comment_failure);
break;
}
}
@Override
public void onCommentFailure(Throwable e) {
CDToast.showToast(getActivity(), R.string.tip_comment_failure);
Tracker.getInstance().sendException("comment error!", e, false);
}
@Override
public void onSessionExpired() {
Intent intent = new Intent(getActivity(), LoginActivity.class);
startActivity(intent);
}
public interface OnMenuSelectedListener {
public void onShowArticleSelected(SNNew snNew);
public void onUpVoteSelected(String postId);
}
private SNDiscuss mSnDiscuss;
private ListView mListView;
private String mDiscussURL;
private DiscussCommentAdapter mAdapter;
private TextView mTitle;
private TextView mSubTitle;
private TextView mText;
private Menu mOptionsMenu;
private EditText mCommentEdit;
private ImageButton mSendBtn;
private OnMenuSelectedListener mListener;
@Inject
SessionManager mSessionManager;
public DiscussFragment() {
}
public static DiscussFragment newInstance(String discussURL, SNNew snNew) {
DiscussFragment fragment = new DiscussFragment();
Bundle args = new Bundle();
args.putString(ARG_DISCUSS_URL, discussURL);
args.putParcelable(ARG_SNNEW, snNew);
fragment.setArguments(args);
return fragment;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof OnMenuSelectedListener) {
mListener = (OnMenuSelectedListener) activity;
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setHasOptionsMenu(true);
View view = inflater.inflate(R.layout.fragment_discuss, null);
mListView = (ListView) view.findViewById(android.R.id.list);
mListView.setOnItemClickListener(this);
mSnDiscuss = new SNDiscuss();
Bundle args = getArguments();
SNNew snNew = null;
if (args != null && args.containsKey(ARG_DISCUSS_URL)) {
mDiscussURL = args.getString(ARG_DISCUSS_URL);
} else {
throw new IllegalArgumentException("Discuss URL is required!");
}
if (args.containsKey(ARG_SNNEW)) {
snNew = args.getParcelable(ARG_SNNEW);
mSnDiscuss.setSnNew(snNew);
}
mAdapter = new DiscussCommentAdapter();
View headerView = inflater.inflate(R.layout.discuss_header_view, null);
mTitle = (TextView) headerView.findViewById(R.id.discuss_news_title);
mSubTitle = (TextView) headerView.findViewById(R.id.discuss_news_subtitle);
mText = (TextView) headerView.findViewById(R.id.discuss_text);
mSendBtn = (ImageButton) view.findViewById(R.id.discuss_comment_send_btn);
mSendBtn.setEnabled(false);
mSendBtn.setOnClickListener(mSendBtnClickListener);
mCommentEdit = (EditText) view.findViewById(R.id.discuss_comment_edit);
mCommentEdit.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
mSendBtn.setEnabled(s.length() > 0);
}
});
mListView.addHeaderView(headerView);
mListView.setAdapter(mAdapter);
wrapHeaderView(snNew);
loadData();
return view;
}
private OnClickListener mSendBtnClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
Tracker.getInstance().sendEvent("ui_action", "view_clicked",
"discussactivity_button_comment", 0L);
mPresenter.comment(mCommentEdit.getText().toString());
}
};
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
mOptionsMenu = menu;
inflater.inflate(R.menu.fragment_discuss, menu);
}
private void wrapHeaderView(SNNew snNew) {
if (snNew != null) {
mTitle.setText(snNew.getTitle());
mSubTitle.setText(Html.fromHtml(snNew.getSubText()));
if (snNew.isDiscuss()) {
mText.setVisibility(View.VISIBLE);
mText.setText(snNew.getText());
} else {
mText.setVisibility(View.GONE);
}
}
}
public void setRefreshActionButtonState(boolean refreshing) {
if (mOptionsMenu == null) {
Log.i(LOG_TAG, "Option menu is null!");
return;
}
final MenuItem refreshItem = mOptionsMenu.findItem(R.id.menu_refresh);
if (refreshItem != null) {
if (refreshing) {
refreshItem.setActionView(R.layout.actionbar_indeterminate_progress);
} else {
refreshItem.setActionView(null);
}
}
}
public void loadData() {
mPresenter.getDiscuss(mDiscussURL);
setRefreshActionButtonState(true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh:
Tracker.getInstance().sendEvent("ui_action", "options_item_selected", "discussactivity_menu_refresh", 0L);
loadData();
return true;
case R.id.menu_show_article:
if (mListener != null) {
mListener.onShowArticleSelected(mSnDiscuss.getSnNew());
}
return true;
case R.id.menu_up_vote:
if (mListener != null) {
mListener.onUpVoteSelected(mSnDiscuss.getSnNew().getPostID());
}
default:
return super.onOptionsItemSelected(item);
}
}
private class DiscussCommentAdapter extends BaseAdapter {
@Override
public int getCount() {
return mSnDiscuss.commentSize();
}
@Override
public Object getItem(int position) {
return mSnDiscuss.getComments().get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = LayoutInflater.from(getActivity()).inflate(
R.layout.discuss_comment_item, null);
holder.mUserId = (TextView) convertView
.findViewById(R.id.discuss_comment_item_user_id);
holder.mCreated = (TextView) convertView
.findViewById(R.id.discuss_comment_item_created);
holder.mCommentText = (TextView) convertView
.findViewById(R.id.discuss_comment_item_text);
holder.mArtistTitle = (TextView) convertView
.findViewById(R.id.discuss_comment_item_artist_titile);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
SNComment comment = mSnDiscuss.getComments().get(position);
holder.mUserId.setText(comment.getUser().getId());
holder.mCreated.setText(comment.getCreated());
holder.mCommentText.setText(comment.getText());
holder.mArtistTitle.setVisibility(View.GONE);
return convertView;
}
class ViewHolder {
TextView mUserId;
TextView mCreated;
TextView mCommentText;
TextView mArtistTitle;
}
}
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
if (position == 0 && mListener == null) {
Tracker.getInstance().sendEvent("ui_action", "list_item_click",
"discuss_activity_list_header_click", 0L);
// 查看文章
ActivityUtils.openArticle(getActivity(), mSnDiscuss.getSnNew());
}
}
}
================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/CardViewDividerDecoration.java
================================================
package com.halzhang.android.apps.startupnews.ui.widgets;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import com.halzhang.android.apps.startupnews.R;
/**
* Divider for {@link RecyclerView}
* Created by Hal on 15/7/19.
*/
public class CardViewDividerDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
private int mInsets;
public CardViewDividerDecoration(Context context) {
mDivider = new ColorDrawable(context.getResources().getColor(android.R.color.transparent));
mInsets = context.getResources().getDimensionPixelSize(R.dimen.card_insets);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
drawVertical(c, parent);
}
/**
* Draw dividers underneath each child view
*/
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin + mInsets;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
//We can supply forced insets for each item view here in the Rect
outRect.set(mInsets, mInsets, mInsets, mInsets);
}
}
================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/DividerDecoration.java
================================================
package com.halzhang.android.apps.startupnews.ui.widgets;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import com.halzhang.android.apps.startupnews.R;
/**
* Divider for {@link RecyclerView}
* Created by Hal on 15/7/19.
*/
public class DividerDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = {android.R.attr.listDivider};
private Drawable mDivider;
private int mInsets;
public DividerDecoration(Context context) {
TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
mInsets = context.getResources().getDimensionPixelSize(R.dimen.card_insets);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
drawVertical(c, parent);
}
/**
* Draw dividers underneath each child view
*/
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin + mInsets;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
//We can supply forced insets for each item view here in the Rect
outRect.set(mInsets, mInsets, mInsets, mInsets);
}
}
================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/ObservableWebView.java
================================================
/*
*
* Copyright (C) 2013 HalZhang.
* http://www.gnu.org/licenses/gpl-3.0.txt
*/
package com.halzhang.android.apps.startupnews.ui.widgets;
import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebView;
/**
* StartupNews
*
*
*
* @author Hal
* @version Aug 25, 2013
*/
public class ObservableWebView extends WebView {
private final static int TOUCH_STATE_REST = 0;
private final static int TOUCH_STATE_SCROLL_UP = 1;
private final static int TOUCH_STATE_SCROLL_DOWN = 2;
private int mTouchState = TOUCH_STATE_REST;
private OnScrollChangedCallback mOnScrollChangedCallback;
public ObservableWebView(final Context context) {
super(context);
}
public ObservableWebView(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
public ObservableWebView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onScrollChanged(final int l, final int t, final int oldl, final int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mOnScrollChangedCallback != null) {
mOnScrollChangedCallback.onScroll(l, t, oldl, oldt);
if (t >= 0 && oldt >= 0) {
if (oldt > t && mTouchState != TOUCH_STATE_SCROLL_UP) {
mTouchState = TOUCH_STATE_SCROLL_UP;
mOnScrollChangedCallback.onScrollUp();
} else if (oldt < t && mTouchState != TOUCH_STATE_SCROLL_DOWN) {
mTouchState = TOUCH_STATE_SCROLL_DOWN;
mOnScrollChangedCallback.onScrollDown();
}
}
}
}
public OnScrollChangedCallback getOnScrollChangedCallback() {
return mOnScrollChangedCallback;
}
public void setOnScrollChangedCallback(final OnScrollChangedCallback onScrollChangedCallback) {
mOnScrollChangedCallback = onScrollChangedCallback;
}
/**
* Impliment in the activity/fragment/view that you want to listen to the
* webview
*/
public static interface OnScrollChangedCallback {
public void onScroll(int l, int t, int oldl, int oldt);
/**
* 向上滚动
*/
public void onScrollUp();
/**
* 向下滚动
*/
public void onScrollDown();
}
}
================================================
FILE: app/src/main/java/com/halzhang/android/apps/startupnews/ui/widgets/SwitchPreference.java
================================================
/**
* Copyright (C) 2013 HalZhang
*/
package com.halzhang.android.apps.startupnews.ui.widgets;
import android.content.Context;
import android.preference.TwoStatePreference;
import android.util.AttributeSet;
/**
* StartupNews
*
*
* @author Hal
* @version Apr 23, 2013
*/
public class SNSession implements Serializable {
private static final long serialVersionUID = 8793748185188606413L;
private String user;
private String id;
public SNSession() {
}
public SNSession(String user, String id) {
super();
this.user = user;
this.id = id;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public void clear() {
user = null;
id = null;
}
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/entity/SNUser.java
================================================
/**
* Copyright (C) 2013 HalZhang
*/
package com.halzhang.android.startupnews.data.entity;
import java.io.Serializable;
/**
* StartupNews
*
* 用户信息
*
* user: jkf created: 9 days ago karma: 17 about:
*
* @author Hal
* @version Mar 7, 2013
*/
public class SNUser implements Serializable {
/**
*
*/
private static final long serialVersionUID = -6123600100177508491L;
private String id;
private String name;
private String created;
private String karma;
private String about;
public SNUser() {
}
public SNUser(String id, String created, String karma, String about) {
super();
this.id = id;
this.created = created;
this.karma = karma;
this.about = about;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getCreated() {
return created;
}
public void setCreated(String created) {
this.created = created;
}
public String getKarma() {
return karma;
}
public void setKarma(String karma) {
this.karma = karma;
}
public String getAbout() {
return about;
}
public void setAbout(String about) {
this.about = about;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/entity/Status.java
================================================
package com.halzhang.android.startupnews.data.entity;
/**
* Created by Hal on 16/6/3.
*/
public class Status {
/**
* 操作失败
*/
public static final int CODE_FAILURE = -1;
/**
* 操作成功
*/
public static final int CODE_SUCCESS = 1;
/**
* cookie 无效,需要重新登录
*/
public static final int CODE_COOKIE_VALID = 10;
/**
* 重复操作
*/
public static final int CODE_REPEAT = 20;
public int code;
public String message;
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/exception/LoginException.java
================================================
package com.halzhang.android.startupnews.data.exception;
/**
* 登录异常
* Created by Hal on 15/12/3.
*/
public class LoginException extends Exception {
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/exception/NetworkException.java
================================================
package com.halzhang.android.startupnews.data.exception;
/**
* 网络异常
* Created by Hal on 16/6/5.
*/
public class NetworkException extends Exception {
/**
* http status code
*/
private int code;
public NetworkException(int code, String message) {
super(message);
this.code = code;
}
public NetworkException(int code, Throwable throwable) {
super(throwable);
this.code = code;
}
public int getCode() {
return code;
}
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/exception/SessionExpiredException.java
================================================
package com.halzhang.android.startupnews.data.exception;
/**
* session 过期
* Created by Hal on 16/8/24.
*/
public class SessionExpiredException extends Exception {
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/net/ISnApi.java
================================================
package com.halzhang.android.startupnews.data.net;
import com.halzhang.android.startupnews.data.entity.SNComments;
import com.halzhang.android.startupnews.data.entity.SNDiscuss;
import com.halzhang.android.startupnews.data.entity.SNFeed;
import com.halzhang.android.startupnews.data.entity.Status;
import rx.Observable;
import rx.subscriptions.BooleanSubscription;
/**
* api
* Created by Hal on 15/11/28.
*/
public interface ISnApi {
/**
* 获取新闻列表
*
* @param url 页面链接
* @return {@link SNFeed}
*/
Observable getSNFeed(String url);
Observable getFnid();
Observable login(String fnid, String username, String password);
Observable getSNComments(String url);
/**
* 投票
*
* @param postId 文章 id
* @return 状态,成功与否
*/
Observable upVote(String postId);
/**
* 评论
*
* @param text 评论内容
* @param fnid 主题的 find
* @return 状态
*/
Observable comment(String text, String fnid);
/**
* 登出
*
* @return true 登出成功
*/
Observable logout();
/**
* 获取讨论列表
*
* @param url
*/
Observable getDiscuss(String url);
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/net/JsoupConnector.java
================================================
/*
* Copyright (C) 2013 HalZhang.
*
* http://www.gnu.org/licenses/gpl-3.0.txt
*/
package com.halzhang.android.startupnews.data.net;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import com.halzhang.android.startupnews.data.utils.SessionManager;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import javax.inject.Singleton;
/**
* StartupNews
*
*
*
* @author Hal
* @version Apr 23, 2013
*/
@Singleton
public class JsoupConnector {
private static final String LOG_TAG = JsoupConnector.class.getSimpleName();
private Context mContext;
private SessionManager mSessionManager;
public JsoupConnector(Context context, SessionManager sessionManager) {
mContext = context;
mSessionManager = sessionManager;
}
public Connection newJsoupConnection(String url) {
if (TextUtils.isEmpty(url)) {
return null;
}
Connection conn = null;
String user = mSessionManager.getSessionUser();
if (TextUtils.isEmpty(user)) {
Log.i(LOG_TAG, "user is empty!");
conn = Jsoup.connect(url);
} else {
conn = Jsoup.connect(url).cookie("user", user);
}
return conn;
}
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/net/SnApiImpl.java
================================================
package com.halzhang.android.startupnews.data.net;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import android.util.Log;
import com.halzhang.android.startupnews.data.Constant;
import com.halzhang.android.startupnews.data.entity.SNComments;
import com.halzhang.android.startupnews.data.entity.SNDiscuss;
import com.halzhang.android.startupnews.data.entity.SNFeed;
import com.halzhang.android.startupnews.data.entity.Status;
import com.halzhang.android.startupnews.data.exception.NetworkException;
import com.halzhang.android.startupnews.data.parser.BaseHTMLParser;
import com.halzhang.android.startupnews.data.parser.SNCommentsParser;
import com.halzhang.android.startupnews.data.parser.SNDiscussParser;
import com.halzhang.android.startupnews.data.parser.SNFeedParser;
import com.halzhang.android.startupnews.data.utils.SessionManager;
import com.squareup.okhttp.FormEncodingBuilder;
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.HttpUrl;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.internal.http.OkHeaders;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.inject.Singleton;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Action0;
/**
* api impl
* Created by Hal on 2016/5/30.
*/
@Singleton
public class SnApiImpl implements ISnApi {
private OkHttpClient mOkHttpClient;
private Context mContext;
private SessionManager mSessionManager;
private JsoupConnector mJsoupConnector;
public SnApiImpl(OkHttpClient okHttpClient, Context context, SessionManager sessionManager, JsoupConnector jsoupConnector) {
mOkHttpClient = okHttpClient;
mContext = context;
mSessionManager = sessionManager;
mJsoupConnector = jsoupConnector;
}
@Override
public Observable getSNFeed(final String url) {
return Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super SNFeed> subscriber) {
try {
Connection conn = mJsoupConnector.newJsoupConnection(url);
Document doc = conn.get();
SNFeedParser parser = new SNFeedParser();
SNFeed feed = parser.parseDocument(doc);
subscriber.onNext(feed);
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
});
}
@Override
public Observable getFnid() {
return Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super String> subscriber) {
String loginUrl = null;
try {
Document doc = Jsoup.connect(Constant.NEWS_URL).get();
if (doc != null) {
Elements loginElements = doc.select("a:matches(Login/Register)");
if (loginElements.size() == 1) {
loginUrl = BaseHTMLParser.resolveRelativeSNURL(loginElements.first().attr(
"href"));
}
}
String fnid = null;
if (!TextUtils.isEmpty(loginUrl)) {
doc = Jsoup.connect(loginUrl).get();
if (doc != null) {
Elements inputElements = doc.select("input[name=fnid]");
if (inputElements != null && inputElements.size() > 0) {
fnid = inputElements.first().attr("value");
}
}
}
subscriber.onNext(fnid);
subscriber.onCompleted();
} catch (IOException e) {
subscriber.onError(e);
}
}
});
}
@Override
public Observable login(final String fnid, final String username, final String password) {
return Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super String> subscriber) {
FormEncodingBuilder builder = new FormEncodingBuilder();
builder.addEncoded("fnid", fnid).addEncoded("u", username).addEncoded("p", password);
Request request = new Request.Builder().url(Constant.LOGIN_URL).post(builder.build())
.addHeader("Accept-Language", "zh-cn")
.addHeader("Accept", "*/*")
.addHeader("Accept-Encoding", "gzip,deflate")
.addHeader("Connection", "keep-alive")
.build();
try {
String user = null;
Response response = mOkHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
Map> cookiesMap = mOkHttpClient.getCookieHandler().get(request.uri(), OkHeaders.toMultimap(request.headers(), null));
if (cookiesMap.size() > 0) {
List cookies = cookiesMap.get("Cookie");
for (String s : cookies) {
String[] cookie = TextUtils.split(s, "=");
if (cookie.length == 2 && "user".equals(cookie[0])) {
user = cookie[1];
break;
}
}
}
}
response.body().close();
mSessionManager.storeSession(user, username);
subscriber.onNext(user);
subscriber.onCompleted();
} catch (IOException e) {
subscriber.onError(e);
}
}
}).doOnSubscribe(new Action0() {
@Override
public void call() {
mSessionManager.clear();
}
});
}
@Override
public Observable getSNComments(final String url) {
return Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super SNComments> subscriber) {
try {
Connection conn = mJsoupConnector.newJsoupConnection(url);
Document doc = conn.get();
SNCommentsParser parser = new SNCommentsParser();
SNComments comments = parser.parseDocument(doc);
subscriber.onNext(comments);
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
});
}
@Override
public Observable upVote(final String postId) {
return Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super Status> subscriber) {
try {
HttpUrl httpUrl = new HttpUrl.Builder()
.scheme(Constant.SCHEME)
.host(Constant.HOST)
.addPathSegment("vote")
.addQueryParameter("for", postId)
.addQueryParameter("dir", "up")
.addQueryParameter("by", mSessionManager.getSessionId())
.addQueryParameter("auth", mSessionManager.getSessionUser())
.addQueryParameter("whence", "news")
.build();
Request request = new Request.Builder().url(httpUrl).build();
Response response = mOkHttpClient.newCall(request).execute();
Status status = new Status();
if (response.isSuccessful()) {
status.code = Status.CODE_SUCCESS;
} else {
String content = response.body().string();
if (content.contains("mismatch")) {
// 用户cookie无效
status.code = Status.CODE_COOKIE_VALID;
} else {
status.code = Status.CODE_REPEAT;
}
}
subscriber.onNext(status);
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
});
}
@Override
public Observable comment(final String text, final String fnid) {
return Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super Status> subscriber) {
try {
RequestBody body = new FormEncodingBuilder().add("fnid", fnid).add("text", text).build();
Request request = new Request.Builder().url(Constant.COMMENT_URL).post(body).build();
Response response = mOkHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
String refreerLocation = null;
Headers headers = response.headers();
for (int i = 0; i < headers.size(); i++) {
if ("Refreer-Location".equals(headers.name(i))) {
refreerLocation = headers.value(i);
}
}
Status status = new Status();
if (TextUtils.isEmpty(refreerLocation) || refreerLocation.contains("fnid")) {
//Location:fnid=xxxxx Cookie失效,重新登陆
status.code = Status.CODE_COOKIE_VALID;
} else if (refreerLocation.contains("item")) {
status.code = Status.CODE_SUCCESS;
} else {
status.code = Status.CODE_FAILURE;
}
subscriber.onNext(status);
subscriber.onCompleted();
} else {
NetworkException networkException = new NetworkException(response.code(),
response.message());
subscriber.onError(networkException);
}
} catch (Exception e) {
e.printStackTrace();
subscriber.onError(e);
}
}
});
}
@Override
public Observable logout() {
return Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super Boolean> subscriber) {
try {
String logoutUrl = null;
Boolean result = false;
Connection conn = mJsoupConnector.newJsoupConnection(Constant.HOST + "/news");
Document doc = conn.get();
Elements elements = doc.select("a:matches(logout)");
if (elements.size() > 0) {
logoutUrl = BaseHTMLParser.resolveRelativeSNURL(elements.attr("href"));
} else {
// 用户可能在pc注销了
mSessionManager.clear();
result = true;
}
if (!TextUtils.isEmpty(logoutUrl)) {
Request request = new Request.Builder().url(logoutUrl).build();
Response response = mOkHttpClient.newCall(request).execute();
result = response.isSuccessful();
}
subscriber.onNext(result);
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
});
}
@Override
public Observable getDiscuss(final String url) {
return Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super SNDiscuss> subscriber) {
try {
Connection conn = mJsoupConnector.newJsoupConnection(url);
Document doc = conn.get();
SNDiscussParser parser = new SNDiscussParser();
SNDiscuss discuss = parser.parseDocument(doc);
subscriber.onNext(discuss);
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
});
}
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/parser/BaseHTMLParser.java
================================================
/**
* Copyright (C) 2013 HalZhang
*/
package com.halzhang.android.startupnews.data.parser;
import android.text.TextUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.TextNode;
import org.w3c.dom.Node;
import java.net.URI;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
/**
* StartupNews
*
* html解析
*
*
* @author Hal
* @version Mar 18, 2013
*/
public abstract class BaseHTMLParser {
public static final Pattern CREATEAT_PATTERN = Pattern.compile("\\d{1,2}\\s\\w+\\sago");
public static final int UNDEFINED = -1;
public T parse(String input) throws Exception {
return parseDocument(Jsoup.parse(input));
}
public abstract T parseDocument(Document doc) throws Exception;
public static String getDomainName(String url) {
URI uri;
try {
uri = new URI(url);
String domain = uri.getHost();
return domain.startsWith("www.") ? domain.substring(4) : domain;
} catch (Exception e) {
return url;
}
}
public static T getSafe(List list, int index) {
if (list.size() - 1 >= index) {
return list.get(index);
} else {
return null;
}
}
public static String getFirstTextValueInElementChildren(Element element) {
if (element == null) {
return "";
}
for (org.jsoup.nodes.Node node : element.childNodes()) {
if (node instanceof TextNode) {
return ((TextNode) node).text();
}
}
return "";
}
public static String getStringValue(String query, Node source, XPath xpath) {
try {
return ((Node) xpath.evaluate(query, source, XPathConstants.NODE)).getNodeValue();
} catch (Exception e) {
// TODO insert Google Analytics tracking here?
}
return "";
}
public static Integer getIntValueFollowedBySuffix(String value, String suffix) {
if (value == null || suffix == null)
return 0;
int suffixWordIdx = value.indexOf(suffix);
if (suffixWordIdx >= 0) {
String extractedValue = value.substring(0, suffixWordIdx);
try {
return Integer.parseInt(extractedValue);
} catch (NumberFormatException e) {
return UNDEFINED;
}
}
return UNDEFINED;
}
public static String getStringValuePrefixedByPrefix(String value, String prefix) {
int prefixWordIdx = value.indexOf(prefix);
if (prefixWordIdx >= 0) {
return value.substring(prefixWordIdx + prefix.length());
}
return null;
}
public static String resolveRelativeSNURL(String url) {
if (TextUtils.isEmpty(url)) {
return null;
}
String snurl = "http://news.dbanotes.net/";
if (url.startsWith("http") || url.startsWith("ftp")) {
return url;
} else if (url.startsWith("/")) {
return snurl + url.substring(1);
} else {
return snurl + url;
}
}
public String getCreateAt(String text) {
if (TextUtils.isEmpty(text)) {
return null;
}
Matcher matcher = CREATEAT_PATTERN.matcher(text);
if (matcher.find()) {
return matcher.group();
}
return null;
}
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/parser/SNCommentsParser.java
================================================
/**
* Copyright (C) 2013 HalZhang
*/
package com.halzhang.android.startupnews.data.parser;
import com.halzhang.android.startupnews.data.entity.SNComment;
import com.halzhang.android.startupnews.data.entity.SNComments;
import com.halzhang.android.startupnews.data.entity.SNUser;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
/**
* StartupNews
*
* 评论
*
*
* @author Hal
* @version Mar 18, 2013
*/
public class SNCommentsParser extends BaseHTMLParser {
public SNCommentsParser(){}
@Override
public SNComments parseDocument(Document doc) throws Exception {
SNComments comments = new SNComments();
if (doc == null) {
return comments;
}
Elements tableRows = doc.body().select("table tr table tr");
if (tableRows != null && tableRows.size() > 0) {
tableRows.remove(0);
// 获取下一页链接
Elements moreURLElements = tableRows.select("a:matches(More)");
String moreURL = null;
if (moreURLElements.size() > 0) {
moreURL = resolveRelativeSNURL(moreURLElements.attr("href"));
}
comments.setMoreURL(moreURL);
String linkURL = null;
String parentURL = null;
String discussURL = null;
String text = null;
String created = null;
SNUser user = null;
String artistTitle = null;// 文章标题
String voteURL = null;
for (int row = 0; row < tableRows.size(); row++) {
int rowInPost = row % 2;
Element rowElement = tableRows.get(row);
if (rowInPost == 0) {
Element textElement = rowElement.select("tr > td:eq(1) > span").first();
if (textElement == null) {
break;
}
text = textElement.text();
user = new SNUser();
Element spanElement = rowElement.select("tr > td:eq(1) > div > span").first();
created = getCreateAt(spanElement.text());
Elements aElements = spanElement.select("span > a");
if (aElements != null && aElements.size() >= 4) {
int size = aElements.size();
Element anthorURLElement = aElements.first();
user.setId(anthorURLElement.text());
Element linkURLElement = aElements.get(1);
linkURL = resolveRelativeSNURL(linkURLElement.attr("href"));
Element parentURLElement = aElements.get(2);
parentURL = resolveRelativeSNURL(parentURLElement.attr("href"));
Element artistAElement = aElements.last();
discussURL = resolveRelativeSNURL(artistAElement.attr("href"));
artistTitle = artistAElement.text();
if (size == 6) {
// TODO edit delete
}
}
Element voteAElement = rowElement.select("tr > td:eq(0) a").first();
if (voteAElement != null) {
// 登录用户的评论没有url
voteURL = resolveRelativeSNURL(voteAElement.attr("href"));
}
comments.addComment(new SNComment(linkURL, parentURL, discussURL, text, created,
user, artistTitle, voteURL,null));
}
}
}
return comments;
}
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/parser/SNCommentsParserV1.java
================================================
/**
* Copyright (C) 2013 HalZhang
*/
package com.halzhang.android.startupnews.data.parser;
import com.halzhang.android.startupnews.data.entity.SNComment;
import com.halzhang.android.startupnews.data.entity.SNComments;
import com.halzhang.android.startupnews.data.entity.SNUser;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.Iterator;
/**
* StartupNews
*
*
*
* @author Hal
* @version Mar 19, 2013
*/
public class SNCommentsParserV1 extends BaseHTMLParser {
@Override
public SNComments parseDocument(Document doc) throws Exception {
SNComments comments = new SNComments();
if (doc == null) {
return comments;
}
Element body = doc.body();
Elements commentSpans = body.select("span.comment");
Elements comHeadSpans = body.select("span.comhead");
if (!commentSpans.isEmpty()) {
Iterator spanCommentIt = commentSpans.iterator();
Iterator spanComHeadIt = comHeadSpans.iterator();
SNComment comment = null;
SNUser user = null;
while (spanComHeadIt.hasNext() && spanCommentIt.hasNext()) {
String commentText = spanCommentIt.next().text();
Element span = spanComHeadIt.next();
Elements as = span.getElementsByTag("a");
user = new SNUser();
user.setId(as.get(0).text());
String link = as.get(1).attr("href");
String parent = as.get(2).attr("href");
String discuss = as.get(3).attr("href");
String title = as.get(3).text();
comment = new SNComment();
comment.setUser(user);
comment.setLinkURL(resolveRelativeSNURL(link));
comment.setParentURL(resolveRelativeSNURL(parent));
comment.setDiscussURL(resolveRelativeSNURL(discuss));
comment.setText(commentText);
comment.setArtistTitle(title);
comments.addComment(comment);
}
}
Elements moreURLElements = body.select("a:matches(More)");
String moreURL = null;
if (moreURLElements.size() > 0) {
moreURL = resolveRelativeSNURL(moreURLElements.attr("href"));
}
comments.setMoreURL(moreURL);
return comments;
}
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/parser/SNDiscussParser.java
================================================
/**
* Copyright (C) 2013 HalZhang
*/
package com.halzhang.android.startupnews.data.parser;
import com.halzhang.android.startupnews.data.entity.SNComment;
import com.halzhang.android.startupnews.data.entity.SNDiscuss;
import com.halzhang.android.startupnews.data.entity.SNNew;
import com.halzhang.android.startupnews.data.entity.SNUser;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
/**
* StartupNews
*
*
*
* @author Hal
* @version Mar 19, 2013
*/
public class SNDiscussParser extends BaseHTMLParser {
@SuppressWarnings("unused")
private static final String LOG_TAG = SNDiscussParser.class.getSimpleName();
@Override
public SNDiscuss parseDocument(Document doc) throws Exception {
SNDiscuss discuss = new SNDiscuss();
if (doc == null) {
return discuss;
}
// news
Elements tableElements = doc.select("table tr table");
if (tableElements != null && tableElements.size() > 1) {
String voteURL = null;
String title = null;
String url = null;
String urlDomain = null;
String subText = null;
boolean isDiscuss = false;
SNUser user = null;
int points = 0;
int commentsCount = 0;
String postID = null;
String discussURL = null;
String text = null;
String createat = null;
String fnid = null;
Element newsTableElement = tableElements.get(1);
Elements trElements = newsTableElement.getElementsByTag("tr");
isDiscuss = trElements.size() > 4;// 讨论帖的有6个tr
Element titleTrElement = trElements.get(0);
Element voteElement = titleTrElement.select("tr > td:eq(0) a").first();
if (voteElement != null) {
voteURL = resolveRelativeSNURL(voteElement.attr("href"));
}
Element titleAElement = titleTrElement.select("tr > td:eq(1) a").first();
if (titleAElement != null) {
/*
* fixed #5 issue “No Activity found to handle Intent { act=android.intent.action.VIEW dat=item?id=2901 }”
* 由于没有对url进行处理,对于讨论帖的标题的a标签的href(item?id=2901)是无法直接打开的
*/
url = resolveRelativeSNURL(titleAElement.attr("href"));
urlDomain = getDomainName(url);
title = titleAElement.text();
}
Element subTextTdeElement = trElements.get(1).select("td.subtext").first();
subText = subTextTdeElement.html();
createat = getCreateAt(subText);
points = getIntValueFollowedBySuffix(subTextTdeElement.select("td > span").text(), " p");
String author = subTextTdeElement.select("td > a[href*=user]").text();
user = new SNUser();
user.setName(author);
user.setId(author);
Element e2 = subTextTdeElement.select("td > a[href*=item]").first();
if (e2 != null) {
commentsCount = getIntValueFollowedBySuffix(e2.text(), " c");
if (commentsCount == BaseHTMLParser.UNDEFINED && e2.text().contains("discuss"))
commentsCount = 0;
postID = getStringValuePrefixedByPrefix(e2.attr("href"), "id=");
discussURL = resolveRelativeSNURL(e2.attr("href"));
} else {
commentsCount = BaseHTMLParser.UNDEFINED;
}
if (isDiscuss) {
text = trElements.get(3).text();
fnid = trElements.get(5).getElementsByTag("input").first().attr("value");
} else {
fnid = trElements.get(3).getElementsByTag("input").first().attr("value");
}
discuss.setFnid(fnid);
discuss.setSnNew(new SNNew(url, title, urlDomain, voteURL, points, commentsCount,
subText, discussURL, user, postID, isDiscuss, text, createat));
}
Elements tableRows = doc.select("table tr table tr table tr");
if (tableRows != null && tableRows.size() > 0) {
Element rowElement = null;
SNComment comment = null;
for (int row = 0; row < tableRows.size(); row++) {
comment = new SNComment();
rowElement = tableRows.get(row);
Element voteAElement = rowElement.select("tr > td:eq(1) a").first();
if (voteAElement != null) {
comment.setVoteURL(resolveRelativeSNURL(voteAElement.attr("href")));
}
Elements aElements = rowElement.select("tr > td:eq(2) a");
if(aElements == null || aElements.size() < 1){
continue;
}
SNUser user = new SNUser();
user.setId(aElements.first().text());
comment.setUser(user);
comment.setLinkURL(resolveRelativeSNURL(aElements.last().attr("href")));
comment.setText(rowElement.select("tr > td:eq(2) > span").first().text());
comment.setReplayURL(resolveRelativeSNURL(rowElement
.select("tr > td:eq(2) a[href^=reply]").first().attr("href")));
discuss.getComments().add(comment);
}
}
return discuss;
}
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/parser/SNFeedParser.java
================================================
/**
* Copyright (C) 2013 HalZhang
*/
package com.halzhang.android.startupnews.data.parser;
import android.util.Log;
import com.halzhang.android.startupnews.data.entity.SNFeed;
import com.halzhang.android.startupnews.data.entity.SNNew;
import com.halzhang.android.startupnews.data.entity.SNUser;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.ArrayList;
/**
* StartupNews
*
* news解析
*
*
* @author Hal
* @version Mar 18, 2013
*/
public class SNFeedParser extends BaseHTMLParser {
private static final String LOG_TAG = SNFeedParser.class.getSimpleName();
@Override
public SNFeed parseDocument(Document doc) throws Exception {
SNFeed feed = new SNFeed();
if (doc == null) {
return feed;
}
long start = System.currentTimeMillis();
// Elements loginout = doc.select("a:matches(Login/Register|logout)");
// if (loginout.size() > 0) {
// String loginoutUrl = resolveRelativeSNURL(loginout.attr("href"));
// Log.i(LOG_TAG, "Login or out url: " + loginoutUrl);
// }
Elements tableRows = doc.select("table tr table tr");
tableRows.remove(0);// 顶部导航
// 获取下一页链接
Elements moreURLElements = tableRows.select("a:matches(More)");
String moreURL = null;
if (moreURLElements.size() > 0) {
moreURL = resolveRelativeSNURL(moreURLElements.attr("href"));
}
feed.setMoreUrl(moreURL);
ArrayList snNews = new ArrayList(32);
String url = null;
String title = null;
String urlDomain = null;
String voteURL = null;
int points = 0;
int commentsCount = 0;
String discussURL = null;
String subText = null;
SNUser user = null;
String postID = null;
String createat = null;
boolean endParse = false;
for (int row = 0; row < tableRows.size(); row++) {
int rowInPost = row % 3;
Element rowElement = tableRows.get(row);
switch (rowInPost) {
case 0:
// 标题
Element titleAElement = rowElement.select("tr > td:eq(2) > a").first();
if (titleAElement == null) {
endParse = true;
break;
}
title = titleAElement.text();
url = resolveRelativeSNURL(titleAElement.attr("href"));
urlDomain = getDomainName(url);
Element voteAElement = rowElement.select("tr > td:eq(1) a").first();
if (voteAElement != null) {
voteURL = resolveRelativeSNURL(voteAElement.attr("href"));
} else {
voteURL = null;
}
break;
case 1:
// 副标题
Element tdElement = rowElement.select("tr > td:eq(1)").first();
subText = tdElement.text();
createat = getCreateAt(subText);
points = getIntValueFollowedBySuffix(tdElement.select("td > span").text(), " p");
String author = tdElement.select("td > a[href*=user]").text();
user = new SNUser();
user.setName(author);
user.setId(author);
Element e2 = tdElement.select("td > a[href*=item]").first();
if (e2 != null) {
commentsCount = getIntValueFollowedBySuffix(e2.text(), " c");
if (commentsCount == BaseHTMLParser.UNDEFINED
&& e2.text().contains("discuss"))
commentsCount = 0;
postID = getStringValuePrefixedByPrefix(e2.attr("href"), "id=");
discussURL = resolveRelativeSNURL(e2.attr("href"));
} else {
commentsCount = BaseHTMLParser.UNDEFINED;
}
snNews.add(new SNNew(url, title, urlDomain, voteURL, points, commentsCount,
subText, discussURL, user, postID, createat));
break;
default:
break;
}
if (endParse) {
break;
}
}
feed.setSnNews(snNews);
Log.i(LOG_TAG, "Take Time:" + (System.currentTimeMillis() - start));
return feed;
}
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/utils/CookieFactoryImpl.java
================================================
package com.halzhang.android.startupnews.data.utils;
import com.halzhang.android.startupnews.data.utils.OkHttpClientHelper.CookieFactory;
/**
* Created by Hal on 16/8/14.
*/
public class CookieFactoryImpl implements CookieFactory {
private SessionManager mSessionManager;
public CookieFactoryImpl(SessionManager sessionManager) {
mSessionManager = sessionManager;
}
@Override
public String getCookie() {
return mSessionManager.getCookieString();
}
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/utils/NetworkUtils.java
================================================
package com.halzhang.android.startupnews.data.utils;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
/**
* 网络工具
* Created by Hal on 2015/9/28.
*/
public class NetworkUtils {
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager cm = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm != null) {
// 如果仅仅是用来判断网络连接
// 则可以使用 cm.getActiveNetworkInfo().isAvailable();
NetworkInfo[] info = cm.getAllNetworkInfo();
if (info != null) {
for (NetworkInfo anInfo : info) {
if (anInfo.getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
}
}
return false;
}
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/utils/OkHttpClientHelper.java
================================================
package com.halzhang.android.startupnews.data.utils;
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.webkit.WebSettings;
import com.squareup.okhttp.Cache;
import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import java.io.File;
import java.io.IOException;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* okhttp
* Created by Hal on 2015/11/12.
*/
@Singleton
public class OkHttpClientHelper {
public interface CookieFactory {
public String getCookie();
}
/**
* USER-AGENT
*/
public static final String USER_AGENT = "Mozilla/5.0 (Linux; Android " + Build.VERSION.RELEASE
+ "; " + Build.MODEL + " Build/" + Build.ID + ")";
private static final long SIZE_OF_CACHE = 10 * 1024 * 1024; // 10 MB
private OkHttpClient mOkHttpClient;
private CookieFactory mCookieFactory;
@NonNull
private Context mContext;
@Inject
public OkHttpClientHelper(CookieFactory cookieFactory, @NonNull Context context) {
mCookieFactory = cookieFactory;
mContext = context;
init(mContext, mCookieFactory);
}
private void init(final Context context, CookieFactory factory) {
mCookieFactory = factory;
if (mOkHttpClient == null) {
Interceptor mCacheControlInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder builder = request.newBuilder();
builder.addHeader("Accept-Language", "zh-cn");
builder.addHeader("Accept", "*/*");
if (mCookieFactory != null) {
builder.addHeader("Cookie", mCookieFactory.getCookie());
}
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
builder.header("User-Agent", WebSettings.getDefaultUserAgent(context));
} else {
builder.header("User-Agent", USER_AGENT);
}
// Add Cache Control only for GET methods
if (request.method().equals("GET")) {
if (NetworkUtils.isNetworkAvailable(context)) {
// 1 day
request.newBuilder().header("Cache-Control", "only-if-cached").build();
} else {
// 4 weeks stale
request.newBuilder().header("Cache-Control", "public, max-stale=2419200").build();
}
}
Response response = chain.proceed(request);
// Re-write response CC header to force use of cache
return response.newBuilder()
.header("Cache-Control", "public, max-age=86400") // 1 day
.build();
}
};
Cache cache = new Cache(new File(context.getCacheDir(), "http"), SIZE_OF_CACHE);
mOkHttpClient = new OkHttpClient();
mOkHttpClient.setCache(cache);
mOkHttpClient.setConnectTimeout(30, TimeUnit.SECONDS);
mOkHttpClient.setReadTimeout(30, TimeUnit.SECONDS);
// Add Cache-Control Interceptor
mOkHttpClient.networkInterceptors().add(mCacheControlInterceptor);
//重试
mOkHttpClient.interceptors().add(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// try the request
Response response = chain.proceed(request);
int tryCount = 0;
while (!response.isSuccessful() && tryCount < 3) {
tryCount++;
// retry the request
response = chain.proceed(request);
}
// otherwise just pass the original response on
return response;
}
});
mOkHttpClient.setCookieHandler(new CookieManager(new PersistentCookieStore(context), CookiePolicy.ACCEPT_ALL));
}
}
public OkHttpClient getOkHttpClient() {
if (mOkHttpClient == null) {
throw new RuntimeException("okhttp uninit!");
}
return mOkHttpClient;
}
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/utils/PersistentCookieStore.java
================================================
/*
Android Asynchronous Http Client
Copyright (c) 2011 James Smith
http://loopj.com
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 com.halzhang.android.startupnews.data.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.CookieHandler;
import java.net.CookieStore;
import java.net.HttpCookie;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* A persistent cookie store which implements the Apache HttpClient
* {@link CookieStore} interface. Cookies are stored and will persist on the
* user's device between application sessions since they are serialized and
* stored in {@link SharedPreferences}.
*
* Instances of this class are designed to be used with
* {@link com.squareup.okhttp.OkHttpClient#setCookieHandler(CookieHandler)}, but can also be used with a
* regular old apache HttpClient/HttpContext if you prefer.
*/
public class PersistentCookieStore implements CookieStore {
private static final String COOKIE_PREFS = "CookiePrefsFile";
private static final String COOKIE_NAME_PREFIX = "cookie_";
/**
* -host:
* --cookieToken:value
*/
private final ConcurrentHashMap> cookies;
private final SharedPreferences cookiePrefs;
/**
* Construct a persistent cookie store.
*/
public PersistentCookieStore(Context context) {
cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
cookies = new ConcurrentHashMap<>();
// Load any previously stored cookies into the store
Map cookiePrefsAll = cookiePrefs.getAll();
for (Map.Entry entry : cookiePrefsAll.entrySet()) {
if (entry.getValue() != null && !entry.getKey().startsWith(COOKIE_NAME_PREFIX)) {
String host = entry.getKey();
if (!cookies.containsKey(host)) {
cookies.put(host, new ConcurrentHashMap());
}
String[] cookieNames = TextUtils.split((String) entry.getValue(), ",");
for (String cookieName : cookieNames) {
String encodedCookie = (String) cookiePrefsAll.get(COOKIE_NAME_PREFIX + cookieName);
if (!TextUtils.isEmpty(encodedCookie)) {
cookies.get(host).put(cookieName, decodeCookie(encodedCookie));
}
}
}
}
}
@Override
public void add(URI uri, HttpCookie cookie) {
String name = getCookieToken(uri, cookie);
if (!cookie.hasExpired()) {
if (!cookies.containsKey(uri.getHost())) {
cookies.put(uri.getHost(), new ConcurrentHashMap());
}
cookies.get(uri.getHost()).put(name, cookie);
} else {
if (cookies.containsKey(uri.getHost())) {
cookies.get(uri.getHost()).remove(name);
}
}
cookiePrefs.edit()
.putString(uri.getHost(), TextUtils.join(",", cookies.keySet()))
.putString(COOKIE_NAME_PREFIX + name, encodeCookie(new SerializableCookie(cookie)))
.apply();
}
@Override
public List get(URI uri) {
List httpCookies = new ArrayList<>(0);
if (cookies.containsKey(uri.getHost())) {
httpCookies.addAll(cookies.get(uri.getHost()).values());
}
return httpCookies;
}
@Override
public List getCookies() {
List httpCookies = new ArrayList<>(0);
for (String host : cookies.keySet()) {
httpCookies.addAll(cookies.get(host).values());
}
return httpCookies;
}
@Override
public List getURIs() {
List uris = new ArrayList<>(cookies.size());
for (String key : cookies.keySet()) {
try {
uris.add(new URI(key));
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
return uris;
}
@Override
public boolean remove(URI uri, HttpCookie cookie) {
String name = getCookieToken(uri, cookie);
if (cookies.containsKey(uri.getHost()) && cookies.get(uri.getHost()).containsKey(name)) {
cookies.get(uri.getHost()).remove(name);
SharedPreferences.Editor editor = cookiePrefs.edit();
if (cookiePrefs.contains(COOKIE_NAME_PREFIX + name)) {
editor.remove(COOKIE_NAME_PREFIX + name);
}
editor.putString(uri.getHost(), TextUtils.join(",", cookies.keySet()));
editor.apply();
return true;
}
return false;
}
@Override
public boolean removeAll() {
cookies.clear();
return cookiePrefs.edit().clear().commit();
}
protected String getCookieToken(URI uri, HttpCookie cookie) {
return cookie.getName() + cookie.getDomain();
}
//
// Cookie serialization/deserialization
//
protected String encodeCookie(SerializableCookie cookie) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ObjectOutputStream outputStream = new ObjectOutputStream(os);
outputStream.writeObject(cookie);
} catch (Exception e) {
return null;
}
return byteArrayToHexString(os.toByteArray());
}
protected HttpCookie decodeCookie(String cookieStr) {
byte[] bytes = hexStringToByteArray(cookieStr);
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
HttpCookie cookie = null;
try {
ObjectInputStream ois = new ObjectInputStream(is);
cookie = ((SerializableCookie) ois.readObject()).getCookie();
} catch (Exception e) {
e.printStackTrace();
}
return cookie;
}
// Using some super basic byte array <-> hex conversions so we don't have
// to rely on any large Base64 libraries. Can be overridden if you like!
protected String byteArrayToHexString(byte[] b) {
StringBuffer sb = new StringBuffer(b.length * 2);
for (byte element : b) {
int v = element & 0xff;
if (v < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(v));
}
return sb.toString().toUpperCase();
}
protected byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
}
return data;
}
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/utils/PrefUtils.java
================================================
package com.halzhang.android.startupnews.data.utils;
import android.content.Context;
import android.preference.PreferenceManager;
/**
* Created by Hal on 2016/5/30.
*/
public class PrefUtils {
public static void set(Context ctx, final String key, final String value) {
PreferenceManager.getDefaultSharedPreferences(ctx).edit().putString(key, value).apply();
}
public static String get(Context context, final String key) {
return PreferenceManager.getDefaultSharedPreferences(context).getString(key, null);
}
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/utils/SerializableCookie.java
================================================
/*
Android Asynchronous Http Client
Copyright (c) 2011 James Smith
http://loopj.com
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 com.halzhang.android.startupnews.data.utils;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.HttpCookie;
/**
* A wrapper class around {@link HttpCookie} and/or {@link HttpCookie}
* designed for use in {@link PersistentCookieStore}.
*/
public class SerializableCookie implements Serializable {
private static final long serialVersionUID = 6374381828722046732L;
private transient final HttpCookie cookie;
private transient HttpCookie clientCookie;
public SerializableCookie(HttpCookie cookie) {
this.cookie = cookie;
}
public HttpCookie getCookie() {
HttpCookie bestCookie = cookie;
if(clientCookie != null) {
bestCookie = clientCookie;
}
return bestCookie;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeObject(cookie.getName());
out.writeObject(cookie.getValue());
out.writeObject(cookie.getComment());
out.writeObject(cookie.getDomain());
out.writeLong(cookie.getMaxAge());
out.writeObject(cookie.getPath());
out.writeInt(cookie.getVersion());
out.writeBoolean(cookie.getSecure());
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
String name = (String)in.readObject();
String value = (String)in.readObject();
clientCookie = new HttpCookie(name, value);
clientCookie.setComment((String) in.readObject());
clientCookie.setDomain((String) in.readObject());
clientCookie.setMaxAge(in.readLong());
clientCookie.setPath((String)in.readObject());
clientCookie.setVersion(in.readInt());
clientCookie.setSecure(in.readBoolean());
}
}
================================================
FILE: data/src/main/java/com/halzhang/android/startupnews/data/utils/SessionManager.java
================================================
/*
* Copyright (C) 2013 HalZhang.
*
* http://www.gnu.org/licenses/gpl-3.0.txt
*/
package com.halzhang.android.startupnews.data.utils;
import android.content.Context;
import android.text.TextUtils;
import com.halzhang.android.startupnews.data.R;
import com.halzhang.android.startupnews.data.entity.SNSession;
import javax.inject.Singleton;
/**
* StartupNews
*
* Session管理
*
*
* @author Hal
* @version Apr 23, 2013
*/
@Singleton
public class SessionManager {
private SNSession mSession;
private Context mContext;
public SessionManager(Context context) {
mSession = new SNSession();
initSession(context);
}
public void initSession(Context context) {
mContext = context;
initSessionFromPref();
}
public void storeSession(SNSession session) {
mSession = session;
saveSessionToPref();
}
public void storeSession(String user, String id) {
if (mSession == null) {
mSession = new SNSession(user, id);
} else {
mSession.setId(id);
mSession.setUser(user);
}
storeSession(mSession);
}
private void saveSessionToPref() {
if (mSession != null) {
StringBuilder builder = new StringBuilder();
builder.append(mSession.getId()).append(";").append(mSession.getUser());
PrefUtils.set(mContext, mContext.getString(R.string.pref_key_cookie),
builder.toString());
}
}
private void initSessionFromPref() {
String cookie = PrefUtils.get(mContext, mContext.getString(R.string.pref_key_cookie));
if (!TextUtils.isEmpty(cookie)) {
String[] datas = cookie.split(";");
if (datas.length == 2) {
if (mSession == null) {
mSession = new SNSession(datas[1], datas[0]);
} else {
mSession.setId(datas[0]);
mSession.setUser(datas[1]);
}
}
}
}
public void clear() {
PrefUtils.set(mContext, mContext.getString(R.string.pref_key_cookie), "");
mSession.clear();
}
public String getSessionId() {
if (mSession != null) {
return mSession.getId();
}
return null;
}
public String getSessionUser() {
if (mSession != null) {
return mSession.getUser();
}
return null;
}
/**
* session是否有效
*
* @return true 有效
*/
public boolean isValid() {
return mSession != null && !TextUtils.isEmpty(mSession.getUser());
}
public String getCookieString() {
return "user=" + mSession.getUser();
}
}
================================================
FILE: data/src/main/res/values/strings.xml
================================================
datapref_key_cookie
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Thu Nov 26 14:56:12 CST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: settings.gradle
================================================
include ':data',
':3rdlib:commonlog',
':3rdlib:commontoast',
':app'
================================================
FILE: wait_for_emulator.sh
================================================
#!/bin/bash
# Originally written by Ralf Kistner , but placed in the public domain
set +e
bootanim=""
failcounter=0
until [[ "$bootanim" =~ "stopped" ]]; do
bootanim=`adb -e shell getprop init.svc.bootanim 2>&1`
echo "$bootanim"
if [[ "$bootanim" =~ "not found" ]]; then
let "failcounter += 1"
if [[ $failcounter -gt 15 ]]; then
echo "Failed to start emulator"
exit 1
fi
fi
sleep 1
done
echo "Done"