package jianshu.io.app; import android.animation.ObjectAnimator; import android.app.ActionBar; import android.app.DownloadManager; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Canvas; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v4.app.NavUtils; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.Button; import android.widget.ProgressBar; import android.widget.ShareActionProvider; import android.widget.TextView; import android.widget.Toast; import net.tsz.afinal.FinalHttp; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.regex.Matcher; import java.util.regex.Pattern; import jianshu.io.app.dialog.ScanFinishedDialogFragment; import jianshu.io.app.model.JianshuSession; import jianshu.io.app.model.StatePool; import jianshu.io.app.widget.LoadingTextView; import jianshu.io.app.widget.ObservableWebView; import me.imid.swipebacklayout.lib.SwipeBackLayout; import me.imid.swipebacklayout.lib.app.SwipeBackActivity; public class ArticleActivity extends SwipeBackActivity implements ScanFinishedDialogFragment.OnFragmentInteractionListener, ObservableWebView.OnScrollChangedCallback { static final String LIKE_SYMBOL = "♥"; static final String UNLIKE_SYMBOL = "♡"; static final Pattern LIKE_COUNT_PATTERN = Pattern.compile("\"([0-9]+)个喜欢\"", Pattern.DOTALL); private LoadingTextView mLoadingArticle; private String mUrl; private String mTitle; private String mSummary; private String mAuthor; private String avatarUrl; private View mLikeView; private TextView mLikeTextView; private boolean isLiking; private int likingCount = 0; private String likeUrl; private ProgressBar mLikeProgress; private boolean isLikingProgressing; private ObjectAnimator likingAnim; private ObjectAnimator unlikingAnim; private ObjectAnimator currentAnim; private Handler handler; private ObservableWebView mWebView; private Button mRetryButton; private View scanLight; private SwipeBackLayout mSwipeBackLayout; private Animation scanAnim; private Animation fadeIn; private Animation fadeOut; private ScanFinishedDialogFragment scanFinishedDialogFragment; private String imagePath; private Uri imageUri; private FinalHttp mFinalHttp; private ShareActionProvider mShareActionProvider; private DownloadManager mDownloadManager; private Bitmap scanBitmap; private String content; private Menu menu; protected static String Css; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_article); this.handler = new Handler(); Intent intent = getIntent(); mUrl = intent.getStringExtra("url"); // mTitle = intent.getStringExtra("title"); // mSummary = intent.getStringExtra("summary"); // mAuthor = intent.getStringExtra("author"); mLoadingArticle = (LoadingTextView) findViewById(R.id.loading_article); mWebView = (ObservableWebView) findViewById(R.id.web); mWebView.setOnScrollChangedCallback(this); mWebView.getSettings().setJavaScriptEnabled(true); mWebView.addJavascriptInterface(this, "article"); mRetryButton = (Button) findViewById(R.id.retry); this.scanLight = (View) findViewById(R.id.scan_light); final ArticleActivity that = this; this.fadeIn = AnimationUtils.loadAnimation(this, R.anim.fade_in); this.fadeOut = AnimationUtils.loadAnimation(this, R.anim.fade_out); this.fadeOut.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { ArticleActivity.this.mLikeView.setVisibility(View.GONE); } @Override public void onAnimationRepeat(Animation animation) { } }); this.scanAnim = AnimationUtils.loadAnimation(this, R.anim.scan); this.scanAnim.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); that.scanFinishedDialogFragment = ScanFinishedDialogFragment.newInstance(); that.scanFinishedDialogFragment.show(ft, "scan"); } @Override public void onAnimationEnd(Animation animation) { ArticleActivity.this.scanLight.setVisibility(View.GONE); } @Override public void onAnimationRepeat(Animation animation) { } }); mLikeProgress = (ProgressBar) findViewById(R.id.like_progress); this.likingAnim = ObjectAnimator.ofInt(this.mLikeProgress, "progress", 2, mLikeProgress.getMax() - 1); this.likingAnim.setDuration(2000); this.unlikingAnim = ObjectAnimator.ofInt(this.mLikeProgress, "progress", mLikeProgress.getMax() - 1, 2); this.unlikingAnim.setDuration(2000); mLikeView = findViewById(R.id.like); mLikeTextView = (TextView) findViewById(R.id.like_text); mLikeView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final ArticleActivity that = ArticleActivity.this; final int max = mLikeProgress.getMax(); if (that.isLikingProgressing) { return; } that.isLikingProgressing = true; that.currentAnim = that.isLiking ? that.unlikingAnim : that.likingAnim; that.currentAnim.start(); (new AsyncTask<Void, Void, Boolean>() { @Override protected Boolean doInBackground(Void... params) { Object httpResult = JianshuSession.getsInstance().postSync(that.likeUrl, false); if (httpResult instanceof String) { String str = (String) httpResult; if (str.startsWith("$")) { if (str.contains("addClass('note-liked')")) { that.isLiking = true; } else if (str.contains("removeClass('note-liked')")) { that.isLiking = false; } Matcher matcher = LIKE_COUNT_PATTERN.matcher(str); if (matcher.find()) { that.likingCount = Integer.parseInt(matcher.group(1)); } return true; } } return false; } @Override protected void onPostExecute(Boolean succeed) { that.isLikingProgressing = false; //即使是网络问题失败了,也要重置进度条状态 updateLike(); if (!succeed) { Toast.makeText(that, "网络似乎不给力", Toast.LENGTH_LONG).show(); } } }).execute(); } }); ActionBar actionBar = getActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); //滑动返回 mSwipeBackLayout = getSwipeBackLayout(); mSwipeBackLayout.setEdgeTrackingEnabled(SwipeBackLayout.EDGE_LEFT); mRetryButton.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { mRetryButton.setVisibility(View.INVISIBLE); mLoadingArticle.setVisibility(View.VISIBLE); loadArticle(); } }); mDownloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); mFinalHttp = new FinalHttp(); Object[] state = StatePool.getInstance().getState("article"); if(state != null) { String url = (String)state[0]; if(mUrl.equals(url)) { this.mTitle = (String)state[1]; this.mAuthor = (String)state[2]; this.content = (String)state[3]; mWebView.setVisibility(View.VISIBLE); mWebView.loadData(this.content, "text/html; charset=UTF-8", null); return; } } loadArticle(); } private void updateLike() { if (this.currentAnim != null) { this.currentAnim.end(); } // if (this.mLikeView.getVisibility() == View.GONE) { // this.mLikeView.setVisibility(View.VISIBLE); // } mLikeProgress.setProgress(isLiking ? mLikeProgress.getMax() : 0); String text = (isLiking ? LIKE_SYMBOL : UNLIKE_SYMBOL) + " " + this.likingCount; mLikeTextView.setText(text); mLikeTextView.setTextColor(getResources().getColor(isLiking ? R.color.white_trans : R.color.jianshu_trans)); } private void loadArticle() { final ArticleActivity that = this; mLoadingArticle.startAnimation(); (new AsyncTask<Void, Void, String>() { @Override protected String doInBackground(Void... params) { Object httpResult = JianshuSession.getsInstance().getSync(mUrl, true); if (httpResult instanceof String) { Document doc = Jsoup.parse((String) httpResult); //您要访问的页面不存在 if(doc.select("body.error").size() > 0) { return (String)httpResult; } parseArticleInfo(doc); //mImageUrl = doc.select("div.meta-bottom").get(0).attr("data-image"); Element likeBtnEl = doc.select(".like > .btn").get(0); String likeUrlAttr = likeBtnEl.attr("href"); //判断登录状态 if (!likeUrlAttr.equals("#login-model")) { that.likeUrl = "http://jianshu.io" + likeBtnEl.attr("href"); that.isLiking = likeBtnEl.hasClass("note-liked"); Elements functionEls = doc.select("div.comment li a"); Element likeCountEl = null; for (Element el : functionEls) { if (el.attr("href").equals("#like")) { likeCountEl = el; break; } } String countStr = likeCountEl.text().replace("个喜欢", "").trim(); that.likingCount = Integer.parseInt(countStr); } Element article = doc.select("div.preview").get(0); Element title = article.select("h1.title").get(0); Element authorInfo = article.select("div.meta-top").get(0); Element content = article.select("div.show-content").get(0); String extractedDocStr = String.format("<html lang=\"zh-CN\">" + "<head>" + "<meta charset=\"utf-8\">" + "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">" + "<style type=\"text/css\">%s</style>" + "</head>" + "<body class=\"post output zh cn reader-day-mode reader-font1 win\">" + "<div class=\"post-bg\">" + "<div class=\"container\">" + "<div class=\"article\">" + "<div class=\"preview\">" + "%s" + "%s" + "%s" + "</div>" + "</div>" + "</div>" + "</div>" + getJianshuBar() + "</body>" + "</html>", getCss(), title.toString(), authorInfo.toString(), content.toString() ); StatePool.getInstance().putState("article", new Object[]{mUrl, mTitle, mAuthor, extractedDocStr}); return extractedDocStr; } else { return null; } } @Override protected void onPostExecute(String s) { mLoadingArticle.endAnimation(); setShareIntent(that.menu); mLoadingArticle.setVisibility(View.INVISIBLE); if (s != null) { that.content = s; mWebView.setVisibility(View.VISIBLE); mWebView.loadData(s, "text/html; charset=UTF-8", null); } else { mWebView.setVisibility(View.INVISIBLE); mRetryButton.setVisibility(View.VISIBLE); } updateLike(); } }).execute(); } private void parseArticleInfo(Document doc) { mTitle = doc.select("h1.title").get(0).text(); mAuthor = doc.select("div.meta-top span").get(0).text(); } protected String getCss() { if (Css == null) { try { InputStream stream = getAssets().open("jianshu.css"); int size = stream.available(); byte[] buffer = new byte[size]; stream.read(buffer); stream.close(); Css = new String(buffer); } catch (IOException e) { // Handle exceptions here } } return Css; } @Override protected void onPause() { super.onPause(); if (mLoadingArticle.getVisibility() == View.VISIBLE) { mLoadingArticle.endAnimation(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.article, menu); this.menu = menu; return true; } private void setShareIntent(Menu menu) { if (menu != null) { MenuItem item = menu.findItem(R.id.menu_item_share); Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); shareIntent.putExtra(Intent.EXTRA_TITLE, mTitle); shareIntent.putExtra(Intent.EXTRA_TEXT, getSharedContent()); shareIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mUrl); shareIntent.setType("text/plain"); mShareActionProvider = (ShareActionProvider) item.getActionProvider(); if (mShareActionProvider != null) { mShareActionProvider.setShareIntent(shareIntent); } } } private String getSharedContent() { return String.format("《%s》 by %s %s (%s)", mTitle, mAuthor, mUrl, "分享自简书"); } private String getJianshuBar() { return "<div class='jianshu_bar'><h1>简书</h1><h3>最好的写作和阅读平台</h3><p><a href='http://jianshu.io'>http://jianshu.io</a></p></div> "; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. } int id = item.getItemId(); switch (item.getItemId()) { // Respond to the action bar's Up/Home button case android.R.id.home: Intent intent = NavUtils.getParentActivityIntent(this); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); overridePendingTransition(0, R.anim.slide_out_right); return true; case R.id.menu_item_picture: mWebView.loadUrl("javascript:document.getElementsByClassName('jianshu_bar')[0].style.display = 'block'"); this.handler.postDelayed(new Runnable() { @Override public void run() { scanContent(); } }, 1000); this.scanLight.setVisibility(View.VISIBLE); this.scanLight.startAnimation(this.scanAnim); return true; } return super.onOptionsItemSelected(item); } public void scanContent() { int[] size = this.mWebView.getRealSize(); int width = size[0]; int height = size[1]; try { this.scanBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(this.scanBitmap); this.mWebView.draw(canvas); } catch (OutOfMemoryError e) { this.scanBitmap = null; mWebView.loadUrl("javascript:document.getElementsByClassName('jianshu_bar')[0].style.display = 'none'"); this.scanFinishedDialogFragment.onScanError("篇幅过长,不能扫描"); return; } final ArticleActivity that = ArticleActivity.this; (new AsyncTask<Void, Void, Boolean>() { @Override protected Boolean doInBackground(Void... params) { try { File jianshuImageFile = new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES) + "/jianshu"); if (!jianshuImageFile.exists()) { jianshuImageFile.mkdirs(); } that.imagePath = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES) + "/jianshu/" + getImageFileName(mTitle); FileOutputStream fos = new FileOutputStream(that.imagePath); that.scanBitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); that.scanBitmap.recycle(); that.scanBitmap = null; fos.close(); return true; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return false; } @Override protected void onPostExecute(Boolean succeed) { mWebView.loadUrl("javascript:document.getElementsByClassName('jianshu_bar')[0].style.display = 'none'"); if (succeed) { that.imageUri = Uri.fromFile(new File(that.imagePath)); Intent localIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, that.imageUri); sendBroadcast(localIntent); that.scanFinishedDialogFragment.onScanFinished(); } else { that.scanFinishedDialogFragment.onScanError("保存图片时遇到错误"); } } }).execute(); } static final char[] RESERVED_CHARS = new char[]{'|', '\\', '?', '*', '<', '\"', ':', '>', '+', '[', ']', '/', '\''}; private String getImageFileName(String title) { String imageFileName = title; for (char ch : RESERVED_CHARS) { imageFileName = imageFileName.replace(ch, '%'); } return imageFileName + ".jpeg"; } @Override public void onBackPressed() { finish(); overridePendingTransition(0, R.anim.slide_out_right); } @Override public void onViewButtonPressed() { FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); Fragment f = getSupportFragmentManager().findFragmentByTag("scan"); if (f != null) { ft.remove(f); ft.commit(); } Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(this.imageUri, "image/jpeg"); startActivity(intent); } @Override public void onScrollChanged(boolean isAtTheEnd) { if (isAtTheEnd) { if (mLikeView.getVisibility() == View.GONE) { mLikeView.setVisibility(View.VISIBLE); mLikeView.startAnimation(this.fadeIn); } } else { if (mLikeView.getVisibility() == View.VISIBLE) { mLikeView.startAnimation(this.fadeOut); } } } }