package org.opensourcetlapp.tl; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import org.htmlcleaner.ContentNode; import org.htmlcleaner.HtmlCleaner; import org.htmlcleaner.TagNode; import org.htmlcleaner.XPatherException; import org.opensourcetlapp.tl.Structs.PostInfo; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.ListActivity; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.os.AsyncTask; import android.os.Bundle; import android.os.Message; import android.text.Html; import android.text.InputFilter.LengthFilter; 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.webkit.WebView; import android.widget.ArrayAdapter; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; public class ShowThread extends Activity implements Runnable { private String BASE_JS ="<script type='text/javascript'>function toggleShowQuote(b){var a=document.getElementById('showQuoteRest_'+b);a.style.display=a.style.display=='none'?'block':'none';}" + "function toggleShowSpoiler2(b,c){var a=document.getElementById('spoiler_'+c);a.style.display=a.style.display=='none'?'block':'none';}"+ "function toggleShowSpoiler(c,a,b){toggleShowSpoiler2(c,b);}</script>"; private static final String SUB_THREAD_PATH = "/tbody/tr/td/a[@id='subscribe_link']"; private static final String PREV_PAGE_XPATH = "./tbody/tr/td[2]/a"; private String postURL; private int topicId; private String postTopic; private int lastPage; private String lastURL; private boolean postLocked; private boolean subscribed; private static CustomImageGetter imageGetter; private int displayPostId; private int currentPage; private HtmlCleaner cleaner; private Context context; private static PostData[] postList; private ShowPostHandler handler; private ProgressDialog progressDialog; private LayoutInflater mInflater; private LinearLayout container; private Dialog gotoPageDialog; private EditText gotoPageEditText; private Button gotoPageButton; private Button gotoPageCancelButton; /** * pattern of different html elements we want to parse */ private static final String PARSE_NODE = "<table width=\"742\" cellspacing=\"0\" cellpadding=\"0\">"; private static final String FORUM_XPATH = "//table[@width='742']"; @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); } public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.show_thread); mInflater = LayoutInflater.from(this); container = (LinearLayout)findViewById(R.id.listThread); Bundle extras = getIntent().getExtras(); postURL = extras.getString("postURL"); postTopic = extras.getString("postTopic"); // TODO: Make the app pull the topic from the HTML postLocked = extras.getBoolean("postLocked"); // TODO: This only works properly when opening this activity is opened from the ShowForum. Add closed-thread detection. imageGetter = new CustomImageGetter(container,this); findViewById(R.id.next).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) {loadPostURL(buildPostURL(currentPage+1));} }); findViewById(R.id.prev).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) {loadPostURL(buildPostURL(currentPage-1));} }); findViewById(R.id.last).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) {loadPostURL(buildPostURL(lastPage));} }); findViewById(R.id.first).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) {loadPostURL(buildPostURL(1));} }); cleaner = TLLib.buildDefaultHtmlCleaner(); context = this; refresh(true); } private void loadPostURL(String url) { postURL = url; parsePostURL(); refresh(true); } private String buildPostURL(int pageNumber){ String temp = postURL; int c = postURL.indexOf('?'); if (c != -1){ temp = postURL.substring(0, c); } temp += String.format("?topic_id=%d¤tpage=%d", topicId, pageNumber); return temp; } private void parsePostURL() { currentPage = 1; String getAttributes = postURL.split("\\?")[1]; String [] temp = getAttributes.split("#"); if (temp.length == 2){ displayPostId = Integer.parseInt(temp[1]); } String [] attributes = temp[0].split("&"); for (String attribute : attributes){ String [] nameValue = attribute.split("="); if (nameValue[0].equals("topic_id")){ topicId = Integer.parseInt(nameValue[1]); } else if (nameValue[0].equals("currentpage")){ currentPage = Integer.parseInt(nameValue[1]); } } } private void parseLastPageFromPostURL(){ if (lastURL != null){ String getAttributes = lastURL.split("\\?")[1]; String [] temp = getAttributes.split("#"); String [] attributes = temp[0].split("&"); for (String attribute : attributes){ String [] nameValue = attribute.split("="); if (nameValue[0].equals("currentpage")){ lastPage = Integer.parseInt(nameValue[1]); } } } else { lastPage = currentPage; } } private void refresh(boolean resetScrollPosition){ progressDialog = ProgressDialog.show(this, null, "Loading...", true, true); handler = new ShowPostHandler(resetScrollPosition, progressDialog, this); container.removeAllViews(); new Thread(this).start(); } private void refreshDisplay() { String absolutePostURL = postURL; parsePostURL(); if (postURL.charAt(0) == '/') { absolutePostURL = TLLib.getAbsoluteURL(postURL); } try { SharedPreferences settings = context.getSharedPreferences(Settings.SETTINGS_FILE_NAME, 0); boolean viewAll = settings.getBoolean(Settings.VIEW_ALL, false); if (viewAll && !absolutePostURL.contains("currentpage")){ absolutePostURL += "¤tpage=All"; } URL url = new URL(absolutePostURL); TagNode node = TLLib.TagNodeFromURLEx2(cleaner, url, handler, context, PARSE_NODE, false); Object[] forum = node.evaluateXPath(FORUM_XPATH); TagNode forumTagNode; // TODO: ASAP Figure out what's causing the TagParser to occasionally break // It may have something to do with the post "GSL S3 ...." It seems to be hitting EOF prematurely if (forum.length > 0){ forumTagNode = (TagNode) forum[0]; } else { node = TLLib.TagNodeFromURLEx2(cleaner, url, handler, context, null, false); forum = node.evaluateXPath(FORUM_XPATH); forumTagNode = (TagNode) forum[0]; } Object[] posts = node.evaluateXPath("//table[@width='742']/tbody/tr"); int offset = ((TagNode)posts[posts.length-1]).evaluateXPath("//form[@name='theform']").length > 0 ? 2 : 2; if (TLLib.loginStatus == true) { TagNode subNode = forumTagNode.findElementByAttValue("id", "subscribe_link", true, true); if (subNode.getText().equals("Unsubscribe")) subscribed = true; } /** get number of page */ Object[] nextPages = forumTagNode.evaluateXPath(PREV_PAGE_XPATH); if (nextPages.length > 0) { int nextPageLength = nextPages.length; for (int i = nextPageLength - 1; i >= nextPageLength - 2; i--) { TagNode nextPage = (TagNode) nextPages[i]; if (nextPage.getChildren().iterator().next().toString() .trim().equals("Next")) { TagNode lastPage = (TagNode) nextPages[i - 1]; lastURL = Html.fromHtml( lastPage.getAttributeByName("href")).toString(); } } } parseLastPageFromPostURL(); postList = new PostData[posts.length - offset]; for (int i = 0;i<posts.length - offset;i++) { TagNode post = (TagNode)posts[i]; Object[] postTr = post.evaluateXPath("//table[@width='752']/tbody/tr"); TagNode header = (TagNode)postTr[0]; TagNode content = parsePostContent(postTr); PostData postData = new PostData(); TagNode firstTd = (TagNode)((TagNode)postTr[1]).getChildren().get(0); boolean type = (firstTd.getAttributeByName("class").equals("forumPost")); postData.setContent(BASE_JS + cleaner.getInnerHtml(((TagNode)content.evaluateXPath("//td[@class='forumPost']")[0]))); postData.buildHeader(type,header); postList[i] = postData; } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { handler.progressStatus = TLHandler.PROGRESS_NETWORK_DOWN; } catch (XPatherException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private TagNode parsePostContent(Object[] post) { TagNode firstTd = (TagNode)((TagNode)post[1]).getChildren().get(0); return (TagNode)((firstTd.getAttributeByName("class").equals("forumPost")) ? post[2] : post[1]); } @Override public void run() { refreshDisplay(); handler.sendEmptyMessage(0); } private class ShowPostHandler extends TLHandler { private boolean resetScrollPosition; public ShowPostHandler(boolean resetScrollPosition, ProgressDialog progressDialog, Context context) { super(progressDialog, context); this.resetScrollPosition = resetScrollPosition; } @Override public void handleMessage(Message msg) { if (msg.what == 0 && handler.progressStatus == TLHandler.PROGRESS_OKAY) { setTitle(TLLib.makeActivityTitle(String.format("(%d/%d) %s", currentPage, lastPage, postTopic))); buildViews(); progressDialog.dismiss(); } else { super.handleMessage(msg); } } }; /** build the view from the list of postData **/ private void buildViews() { for (int i = 0; i<postList.length;i++) { View postView = mInflater.inflate(R.layout.show_thread_row, null); WebView content = (WebView)postView.findViewById(R.id.postContent); content.getSettings().setJavaScriptEnabled(true); TextView poster = (TextView)postView.findViewById(R.id.posterName); TextView countryDate = (TextView)postView.findViewById(R.id.postDate); ImageView icon = (ImageView)postView.findViewById(R.id.postIcon); LinearLayout options = (LinearLayout)postView.findViewById(R.id.posterInfos); content.loadDataWithBaseURL("http://www.teamliquid.net/",postList[i].getContent(), "text/html", null,null); icon.setImageDrawable(imageGetter.getDrawable(postList[i].getIcon())); /** load values depending on the type of the post (normal,news)*/ if (postList[i].getType().equals("news")) { poster.setText(postList[i].getTitle()); } else { poster.setText(postList[i].getPoster()); postView.findViewById(R.id.postHeaderThread).setOnClickListener(new ThreadOnClickListener(options)); if (postList[i].postId !=null) { options.findViewById(R.id.quoteButton).setOnClickListener(new QuoteOnClickListener(Integer.parseInt(postList[i].postId))); } else options.findViewById(R.id.quoteButton).setVisibility(TextView.GONE); if (postList[i].getPoster() !=null && TLLib.loginStatus) { options.findViewById(R.id.pmButton).setOnClickListener(new PMOnClickListener(postList[i].getPoster())); } else options.findViewById(R.id.pmButton).setVisibility(TextView.GONE); countryDate.setText(postList[i].getCountryDate()); } container.addView(postView,i); } if (currentPage == 1) { findViewById(R.id.prev).setEnabled(false); findViewById(R.id.first).setEnabled(false); } else { findViewById(R.id.prev).setEnabled(true); findViewById(R.id.first).setEnabled(true); } if (currentPage == lastPage) { findViewById(R.id.next).setEnabled(false); findViewById(R.id.last).setEnabled(false); } else { findViewById(R.id.next).setEnabled(true); findViewById(R.id.last).setEnabled(true); } ((TextView)findViewById(R.id.threadPage)).setText(currentPage+"/"+lastPage); } public class ThreadOnClickListener implements OnClickListener { private LinearLayout options; public ThreadOnClickListener(LinearLayout options) { this.options = options; } @Override public void onClick(View v) { options.setVisibility(options.getVisibility() == LinearLayout.GONE ? LinearLayout.VISIBLE : LinearLayout.GONE); } } public class QuoteOnClickListener implements OnClickListener { private int postId; public QuoteOnClickListener(int postId) { this.postId = postId; } @Override public void onClick(View v) { Intent intent = new Intent().setClass(context, PostMessage.class); intent.putExtra("postId", postId); intent.putExtra("topicId", topicId); context.startActivity(intent); } } public class PMOnClickListener implements OnClickListener { private String login; public PMOnClickListener(String login) { this.login = login; } @Override public void onClick(View v) { Intent intent = new Intent().setClass(context, ShowMyPMReply.class); intent.putExtra("to", login); context.startActivity(intent); } } /** Data class containing all the information concerning a post*/ public class PostData { private String content; private String poster; private String countryDate; private String icon; private String title; private String textBy; private String type; private String postId; public String getContent() { return content; } public void buildHeader(boolean type2, TagNode post) { try { this.setIcon(((TagNode)post.evaluateXPath("//img")[0]).getAttributeByName("src")); if (!type2) { TagNode node = null; Object[] nodesArray = post.evaluateXPath("//span[@class='forummsginfo']"); if (nodesArray.length > 0) node = (TagNode)nodesArray[0]; else { node = (TagNode)post.evaluateXPath("//span[@class='forummsginfoa']")[0]; } String[] infos = null; if (node.getChildren().size() > 2 && ((TagNode)node.getChildren().get(1)).getName().equals("img")) { /** Depending on the size on the header get the poster and the country/date string */ if (node.getChildren().size() > 6) { Object[] nodeInfos = node.getChildren().toArray(); if (!((TagNode)nodeInfos[3]).getName().equals("img")) this.setPoster(((TagNode)nodeInfos[3]).getChildren().get(0).toString()); else this.setPoster(((ContentNode)nodeInfos[2]).toString().replaceAll(" ", "")); this.setCountryDate(((ContentNode)nodeInfos[6]).toString().replaceAll(" ", "")); } else if (node.getChildren().size() > 5) { Object[] nodeInfos = node.getChildren().toArray(); this.setPoster(((TagNode)nodeInfos[3]).getChildren().get(0).toString()); this.setCountryDate(((TagNode)nodeInfos[5]).toString().replaceAll(" ", "")); } else if (node.getChildren().size() > 4) { Object[] nodeInfos = node.getChildren().toArray(); if(TLLib.loginStatus) { if (((TagNode)nodeInfos[3]).getName().equals("img")) this.setPoster(((ContentNode)nodeInfos[2]).toString().replaceAll(" ", "")); else this.setPoster(((TagNode)nodeInfos[3]).getChildren().get(0).toString()); this.setCountryDate(((ContentNode)nodeInfos[4]).toString().replaceAll(" ", "")); } else { this.setPoster(((ContentNode)nodeInfos[2]).toString().replaceAll(" ", "")); this.setCountryDate(((ContentNode)nodeInfos[4]).toString().replaceAll(" ", "")); } } else { infos = node.getChildren().get(2).toString().split(" "); this.setPoster(infos[1]); this.setCountryDate(infos[2]); } } else { /** special for PoP */ this.setPoster("Pop!"); this.setCountryDate(node.getChildren().get(0).toString().replaceAll(" ", "")); } Object[] links = ((TagNode)post.getChildren().get(1)).evaluateXPath("//a"); for(int i = 0; i< links.length;i++) { if (((TagNode)links[i]).getChildren().get(0).toString().equals("Quote")) { postId = ((TagNode)links[i]).getAttributeByName("href").split("\\?")[1].split("&")[0].split("=")[1]; } } } else { this.setTitle(((TagNode)((TagNode)((TagNode)post.evaluateXPath("//td")[0]).getChildren().get(2)).getChildren().get(1)).getChildren().get(0).toString()); } this.setType(type2 ? "news" : "normal"); } catch (XPatherException e) { Log.d("show thread", "Problem parsing header's stuff"); } } public void setContent(String content) { this.content = content; } public String getPoster() { return poster; } public void setPoster(String poster) { this.poster = poster; } public String getIcon() { return icon; } public void setIcon(String icon) { this.icon = icon; } public String getTextBy() { return textBy; } public void setTextBy(String textBy) { this.textBy = textBy; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getPostId() { return postId; } public void setPostId(String postId) { this.postId = postId; } public String getCountryDate() { return countryDate; } public void setCountryDate(String countryDate) { this.countryDate = countryDate; } } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.show_post_menu, menu); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuItem subMenuItem = menu.findItem(R.id.subscribeThread); if (subscribed) subMenuItem.setTitle("Unsubscribe"); else subMenuItem.setTitle("Subscribe"); return true; } private void showGotoPageDialog(){ if (gotoPageDialog == null){ gotoPageDialog = new Dialog(this); gotoPageDialog.setContentView(R.layout.goto_page_popup); gotoPageDialog.setCancelable(true); gotoPageDialog.setTitle(String.format("Enter page number (%d %s)", lastPage, lastPage==1?"page":"pages")); gotoPageButton = (Button) gotoPageDialog.findViewById(R.id.gotoPageButton); gotoPageCancelButton = (Button) gotoPageDialog.findViewById(R.id.gotoPageCancelButton); gotoPageEditText = (EditText) gotoPageDialog.findViewById(R.id.gotoPageEditText); gotoPageButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { try { int pageNumber = Integer.parseInt(gotoPageEditText.getText().toString()); if (pageNumber < 1){ AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setMessage("Page number must be greater than 0.") .setCancelable(false) .setPositiveButton("Yes", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { } }); AlertDialog alert = builder.create(); alert.show(); } else if (pageNumber > lastPage){ AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setMessage("Page number must be no greater than " +lastPage) .setCancelable(false) .setPositiveButton("Yes", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { } }); AlertDialog alert = builder.create(); alert.show(); } else { loadPostURL(buildPostURL(pageNumber)); gotoPageDialog.dismiss(); } } catch (NumberFormatException e){ } } }); } gotoPageCancelButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { gotoPageDialog.dismiss(); } }); gotoPageEditText.setText(""); gotoPageDialog.show(); } public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.gotoPage: showGotoPageDialog(); break; case R.id.refresh: refresh(false); break; case R.id.reply: if (!TLLib.loginStatus){ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); alertDialogBuilder.setTitle("Login Error"); alertDialogBuilder.setMessage("Please Login before posting.\n"); alertDialogBuilder.setPositiveButton("Okay", null); alertDialogBuilder.show(); } else if (postLocked){ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); alertDialogBuilder.setTitle("Thread Closed"); alertDialogBuilder.setMessage("This thread has been locked by a Moderator."); alertDialogBuilder.setPositiveButton("Okay", null); alertDialogBuilder.show(); } else { Intent intent = new Intent().setClass(this, PostMessage.class); intent.putExtra("topicId", topicId); startActivity(intent); } break; case R.id.subscribeThread: if (!TLLib.loginStatus){ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); alertDialogBuilder.setTitle("Login Error"); alertDialogBuilder.setMessage("Please Login before posting.\n"); alertDialogBuilder.setPositiveButton("Okay", null); alertDialogBuilder.show(); } else { SubscribeThreadTask subscribeThreadTask = new SubscribeThreadTask(); subscribeThreadTask.execute(); } break; } return true; } private class SubscribeThreadTask extends AsyncTask<Void, Void, Boolean> { @Override protected Boolean doInBackground(Void... arg0) { try { TLLib.subscribeThread(Integer.toString(topicId)); } catch (IOException e) { e.printStackTrace(); return false; } return true; } @Override protected void onPostExecute(Boolean result) { if (result) { String unsub = "Thread unsubscribed!"; String sub = "Thread subscribed!"; Toast.makeText(context, subscribed ? unsub : sub, Toast.LENGTH_SHORT).show(); subscribed = !subscribed; } } } }