package gr.thmmy.mthmmy.activities; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBar; import android.support.v7.widget.CardView; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import com.android.volley.toolbox.ImageLoader; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import javax.net.ssl.SSLHandshakeException; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.data.Post; import gr.thmmy.mthmmy.utils.CircularNetworkImageView; import gr.thmmy.mthmmy.utils.ImageController; import okhttp3.Request; import okhttp3.Response; public class TopicActivity extends BaseActivity { //-----------------------------------------CLASS VARIABLES------------------------------------------ /* --Posts-- */ private List postsList; private LinearLayout postsLinearLayout; private static final int NO_POST_FOCUS = -1; private int postFocus = NO_POST_FOCUS; //Quote //TODO /* --Topic's pages-- */ private int thisPage = 1; private String base_url = ""; private int numberOfPages = 1; private final SparseArray pagesUrls = new SparseArray<>(); //Page select private TextView pageIndicator; private final Handler repeatUpdateHandler = new Handler(); private final long INITIAL_DELAY = 500; private boolean autoIncrement = false; private boolean autoDecrement = false; private static final int SMALL_STEP = 1; private static final int LARGE_STEP = 10; private Integer pageValue; private ImageButton firstPage; private ImageButton previousPage; private ImageButton nextPage; private ImageButton lastPage; /* --Thumbnail-- */ private static final int THUMBNAIL_SIZE = 80; private ImageLoader imageLoader = ImageController.getInstance().getImageLoader(); //Other variables private ProgressBar progressBar; private static final String TAG = "TopicActivity"; private String topicTitle; private String parsedTitle; private ActionBar actionbar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_topic); Bundle extras = getIntent().getExtras(); topicTitle = getIntent().getExtras().getString("TOPIC_TITLE"); //Variables initialization postsLinearLayout = (LinearLayout) findViewById(R.id.posts_list); progressBar = (ProgressBar) findViewById(R.id.progressBar); if (imageLoader == null) imageLoader = ImageController.getInstance().getImageLoader(); postsList = new ArrayList<>(); actionbar = getSupportActionBar(); if (actionbar != null) if(!Objects.equals(topicTitle, "")) actionbar.setTitle(topicTitle); firstPage = (ImageButton) findViewById(R.id.page_first_button); previousPage = (ImageButton) findViewById(R.id.page_previous_button); pageIndicator = (TextView) findViewById(R.id.page_indicator); nextPage = (ImageButton) findViewById(R.id.page_next_button); lastPage = (ImageButton) findViewById(R.id.page_last_button); initDecrementButton(firstPage, LARGE_STEP); initDecrementButton(previousPage, SMALL_STEP); initIncrementButton(nextPage, SMALL_STEP); initIncrementButton(lastPage, LARGE_STEP); firstPage.setEnabled(false); previousPage.setEnabled(false); nextPage.setEnabled(false); lastPage.setEnabled(false); new TopicTask().execute(extras.getString("TOPIC_URL")); //Attempt data parsing } @Override protected void onDestroy() { //When finished cancel whatever request can still be canceled super.onDestroy(); ImageController.getInstance().cancelPendingRequests(); } private void initIncrementButton(ImageButton increment, final int step){ // Increment once for a click increment.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if(!autoIncrement && step == LARGE_STEP){ //If just clicked go to last page changePage(numberOfPages - 1); return; } //Clicked and holden autoIncrement = false; //Stop incrementing increment(step); changePage(pageValue - 1); } }); // Auto increment for a long click increment.setOnLongClickListener( new View.OnLongClickListener(){ public boolean onLongClick(View arg0) { autoIncrement = true; repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), INITIAL_DELAY); return false; } } ); // When the button is released increment.setOnTouchListener( new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { if( event.getAction() == MotionEvent.ACTION_UP && autoIncrement ){ changePage(pageValue - 1); } return false; } }); } private void initDecrementButton(ImageButton decrement, final int step){ // Decrement once for a click decrement.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if(!autoDecrement && step == LARGE_STEP){ //If just clicked go to first page changePage(0); return; } //Clicked and holden autoDecrement = false; //Stop incrementing decrement(step); changePage(pageValue - 1); } }); // Auto Decrement for a long click decrement.setOnLongClickListener( new View.OnLongClickListener(){ public boolean onLongClick(View arg0) { autoDecrement = true; repeatUpdateHandler.postDelayed( new RepetitiveUpdater(step), INITIAL_DELAY); return false; } } ); // When the button is released decrement.setOnTouchListener( new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { if( event.getAction() == MotionEvent.ACTION_UP && autoDecrement ){ changePage(pageValue - 1); } return false; } }); } private void increment(int step){ if( pageValue < numberOfPages - step){ pageValue = pageValue + step; } else pageValue = numberOfPages; pageIndicator.setText(pageValue + "/" + String.valueOf(numberOfPages)); if(pageValue >= 1000) pageIndicator.setTextSize(16); else pageIndicator.setTextSize(20); } private void decrement(int step){ if( pageValue > step) pageValue = pageValue - step; else pageValue = 1; pageIndicator.setText(pageValue + "/" + String.valueOf(numberOfPages)); if(numberOfPages >= 1000) pageIndicator.setTextSize(16); else pageIndicator.setTextSize(20); } private void changePage(int pageRequested){ if(pageRequested != thisPage - 1){ //Restart activity with new page Intent intent = getIntent(); intent.putExtra("TOPIC_URL", pagesUrls.get(pageRequested)); intent.putExtra("TOPIC_TITLE", topicTitle); finish(); startActivity(intent); } } //---------------------------------------TOPIC ASYNC TASK------------------------------------------- public class TopicTask extends AsyncTask { //Class variables private static final String TAG = "TopicTask"; //Separate tag for AsyncTask //Show a progress bar until done protected void onPreExecute() { progressBar.setVisibility(ProgressBar.VISIBLE); } protected Boolean doInBackground(String... strings) { Document document; base_url = strings[0].substring(0, strings[0].lastIndexOf(".")); //This topic's base url String pageUrl = strings[0]; //This page's url //Find message focus if present { if(pageUrl.contains("msg")){ String tmp = pageUrl.substring(pageUrl.indexOf("msg") + 3); if(tmp.contains(";")) postFocus = Integer.parseInt(tmp.substring(0, tmp.indexOf(";"))); else postFocus = Integer.parseInt(tmp.substring(0, tmp.indexOf("#"))); } } Request request = new Request.Builder() .url(pageUrl) .build(); try { Response response = client.newCall(request).execute(); document = Jsoup.parse(response.body().string()); parse(document); //Parse data return true; } catch (SSLHandshakeException e) { Log.w(TAG, "Certificate problem (please switch to unsafe connection)."); } catch (Exception e) { Log.e("TAG", "ERROR", e); } return false; } protected void onPostExecute(Boolean result) { if (!result) { //Parse failed! //Should never happen Toast.makeText(getBaseContext() , "Fatal error!\n Aborting...", Toast.LENGTH_LONG).show(); finish(); } //Parse was successful progressBar.setVisibility(ProgressBar.INVISIBLE); //Hide progress bar populateLayout(); //Show parsed data //Set current page pageIndicator.setText(String.valueOf(thisPage) + "/" + String.valueOf(numberOfPages)); pageValue = thisPage; if(numberOfPages >= 1000) pageIndicator.setTextSize(16); } /* Parse method */ private void parse(Document document) { //Method's variables final int NO_INDEX = -1; //Find topic title if missing if(topicTitle == null || Objects.equals(topicTitle, "")){ parsedTitle = document.select("td[id=top_subject]").first().text(); Log.d(TAG, parsedTitle); parsedTitle = parsedTitle.substring(parsedTitle.indexOf("Topic:") + 7 , parsedTitle.indexOf("(Read") - 8); Log.d(TAG, parsedTitle); } { //Find current page's index Elements findCurrentPage = document.select("td:contains(Pages:)>b"); //Contains pages for (Element item : findCurrentPage) { if (!item.text().contains("...") //It's not "..." && !item.text().contains("Pages")) { //Nor "Pages" thisPage = Integer.parseInt(item.text()); break; } } } { //Find number of pages Elements pages = document.select("td:contains(Pages:)>a.navPages"); //Contains all pages if (pages.size() != 0) { numberOfPages = thisPage; //Initialize the number for (Element item : pages) { //Just a max if (Integer.parseInt(item.text()) > numberOfPages) numberOfPages = Integer.parseInt(item.text()); } } for (int i = 0; i < numberOfPages; i++) { //Generate each page's url from topic's base url +".15*numberOfPage" pagesUrls.put(i, base_url + "." + String.valueOf(i * 15)); } } //Each element is a post's row Elements rows = document.select("form[id=quickModForm]>table>tbody>tr:matches(on)"); for (Element item : rows) { //For every post //Variables to pass String p_userName, p_thumbnailUrl, p_subject, p_post, p_postDate, p_rank, p_specialRank, p_gender, p_personalText, p_numberOfPosts, p_urlOfStars; int p_postNum, p_postIndex, p_numberOfStars; boolean p_isDeleted = false; //Initialize variables p_rank = "Rank"; p_specialRank = "Special rank"; p_gender = ""; p_personalText = ""; p_numberOfPosts = ""; p_urlOfStars = ""; p_numberOfStars = 0; //Find the Username Element userName = item.select("a[title^=View the profile of]").first(); if (userName == null) { //Deleted profile p_isDeleted = true; p_userName = item .select("td:has(div.smalltext:containsOwn(Guest))[style^=overflow]") .first().text(); p_userName = p_userName.substring(0, p_userName.indexOf(" Guest")); } else p_userName = userName.html(); //Find thumbnail url Element thumbnailUrl = item.select("img.avatar").first(); p_thumbnailUrl = null; //In case user doesn't have an avatar if (thumbnailUrl != null) { p_thumbnailUrl = thumbnailUrl.attr("abs:src"); } //Find subject p_subject = item.select("div[id^=subject_]").first().select("a").first().text(); //Find post's text p_post = item.select("div").select(".post").first().html(); //Add stuff to make it work in WebView p_post = ("" + p_post); //style.css //Find post's submit date Element postDate = item.select("div.smalltext:matches(on:)").first(); p_postDate = postDate.text(); p_postDate = p_postDate.substring(p_postDate.indexOf("on:") + 4 , p_postDate.indexOf(" ยป")); //Find post's number Element postNum = item.select("div.smalltext:matches(Reply #)").first(); if (postNum == null) { //Topic starter p_postNum = 0; } else { String tmp_str = postNum.text().substring(9); p_postNum = Integer.parseInt(tmp_str.substring(0, tmp_str.indexOf(" on"))); } //Find post's index Element postIndex = item.select("a[name^=msg]").first(); if (postIndex == null) p_postIndex = NO_INDEX; else { String tmp = postIndex.attr("name"); p_postIndex = Integer.parseInt(tmp.substring(tmp.indexOf("msg") + 3)); } if (!p_isDeleted) { //Active user //Get extra info int postsLineIndex = -1; int starsLineIndex = -1; Element info = userName.parent().nextElementSibling(); //Get sibling "div" List infoList = Arrays.asList(info.html().split("
")); for (String line : infoList) { Log.i(TAG, line); if (line.contains("Posts:")) { postsLineIndex = infoList.indexOf(line); //Remove any line breaks and spaces on the start and end p_numberOfPosts = line.replace("\n", "") .replace("\r", "").trim(); } if (line.contains("Gender:")) { if (line.contains("alt=\"Male\"")) p_gender = "Gender: Male"; else p_gender = "Gender: Female"; } if (line.contains("alt=\"*\"")) { starsLineIndex = infoList.indexOf(line); Document starsHtml = Jsoup.parse(line); p_numberOfStars = starsHtml.select("img[alt]").size(); p_urlOfStars = starsHtml.select("img[alt]").first().attr("abs:src"); } } //If this member has no stars yet ==> New member, //or is just a member if (starsLineIndex == -1 || starsLineIndex == 1) { //In this case: p_rank = infoList.get(0).trim(); //First line has the rank p_specialRank = null; //They don't have a special rank } else if (starsLineIndex == 2) { //This member has a special rank p_specialRank = infoList.get(0).trim(); //First line has the special rank p_rank = infoList.get(1).trim(); //Second line has the rank } for (int i = postsLineIndex + 1; i < infoList.size() - 1; ++i) { //Search under "Posts:" //and above "Personal Message", "View Profile" etc buttons String thisLine = infoList.get(i); //If this line isn't empty and doesn't contain user's avatar if (!Objects.equals(thisLine, "") && thisLine != null && !Objects.equals(thisLine, " \n") && !thisLine.contains("avatar") && !thisLine.contains(" SCROLL_THRESHOLD || Math.abs(downCoordinateY - motionEvent.getY()) > SCROLL_THRESHOLD))) { webViewLongClickHandler.removeCallbacks(WebViewLongClick); } if (fingerState == FINGER_TOUCHED || fingerState == FINGER_DRAGGING) fingerState = FINGER_DRAGGING; else fingerState = FINGER_UNDEFINED; break; default: fingerState = FINGER_UNDEFINED; } return false; } }); /* --Card expand/collapse-like functionality end-- */ //Add view to the linear layout that holds all posts postsLinearLayout.addView(convertView); //Set post focus if(postFocus != NO_POST_FOCUS){ if(item.getPostIndex() == postFocus){ //TODO } } } } //--------------------------------------POPULATE UI METHOD END-------------------------------------- //--------------------------POST'S INFO VISIBILITY CHANGE ANIMATION METHOD-------------------------- /** * Method that animates view's visibility changes for post's extra info */ private void animatePostExtraInfoVisibility(final View dateAndPostNum, TextView username, TextView subject) { //If the view is gone fade it in if (dateAndPostNum.getVisibility() == View.GONE) { //Show full username username.setMaxLines(Integer.MAX_VALUE); //As in the android sourcecode username.setEllipsize(null); //Show full subject subject.setTextColor(ContextCompat.getColor(this, R.color.black)); subject.setMaxLines(Integer.MAX_VALUE); //As in the android sourcecode subject.setEllipsize(null); dateAndPostNum.clearAnimation(); // Prepare the View for the animation dateAndPostNum.setVisibility(View.VISIBLE); dateAndPostNum.setAlpha(0.0f); // Start the animation dateAndPostNum.animate() .translationY(0) .alpha(1.0f) .setDuration(300) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); dateAndPostNum.setVisibility(View.VISIBLE); } }); } //If the view is visible fade it out else { username.setMaxLines(1); //As in the android sourcecode username.setEllipsize(TextUtils.TruncateAt.END); subject.setTextColor(ContextCompat.getColor(this, R.color.secondary_text)); subject.setMaxLines(1); //As in the android sourcecode subject.setEllipsize(TextUtils.TruncateAt.END); dateAndPostNum.clearAnimation(); // Start the animation dateAndPostNum.animate() .translationY(dateAndPostNum.getHeight()) .alpha(0.0f) .setDuration(300) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); dateAndPostNum.setVisibility(View.GONE); } }); } } //------------------------POST'S INFO VISIBILITY CHANGE ANIMATION METHOD END------------------------ //--------------------------USER'S INFO VISIBILITY CHANGE ANIMATION METHOD-------------------------- /** * Method that animates view's visibility changes for user's extra info */ private void animateUserExtraInfoVisibility(final View userExtra){ //If the view is gone fade it in if (userExtra.getVisibility() == View.GONE) { userExtra.clearAnimation(); userExtra.setVisibility(View.VISIBLE); userExtra.setAlpha(0.0f); // Start the animation userExtra.animate() .translationY(0) .alpha(1.0f) .setDuration(300) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); userExtra.setVisibility(View.VISIBLE); } }); } //If the view is visible fade it out else { userExtra.clearAnimation(); // Start the animation userExtra.animate() .translationY(userExtra.getHeight()) .alpha(0.0f) .setDuration(300) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); userExtra.setVisibility(View.GONE); } }); } } //------------------------POST'S INFO VISIBILITY CHANGE ANIMATION METHOD END------------------------ //--------------------------------------CUSTOM WEBVIEW CLIENT--------------------------------------- /** * This class is used to handle link clicks in WebViews. * When link url is one that the app can handle internally, it does. * Otherwise user is prompt to open the link in a browser. */ private class LinkLauncher extends WebViewClient { //Used to handle link clicks //Older versions @SuppressWarnings("deprecation") @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { final Uri uri = Uri.parse(url); return handleUri(uri); } //Newest versions @TargetApi(Build.VERSION_CODES.N) @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { final Uri uri = request.getUrl(); return handleUri(uri); } //Handle url clicks private boolean handleUri(final Uri uri) { //Method always returns true as we don't want any url to be loaded in WebViews Log.i(TAG, "Uri =" + uri); final String host = uri.getHost(); //Get requested url's host //Determine if you are going to pass the url to a //host's application activity or load it in a browser. if (Objects.equals(host, "www.thmmy.gr")) { //This is my web site, so figure out what Activity should launch if (uri.toString().contains("topic=")) { //This url points to a topic //Is the link pointing to current topic? if(Objects.equals( uri.toString().substring(0, uri.toString().lastIndexOf(".")), base_url)){ //Don't restart Activity //Just change post focus //TODO } else { //Restart activity with new data Intent intent = getIntent(); intent.putExtra("TOPIC_URL", uri.toString()); intent.putExtra("TOPIC_TITLE", ""); finish(); startActivity(intent); } } return true; } //Otherwise, the link is not for a page on my site, so launch //another Activity that handles URLs Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); return true; } } //------------------------------------CUSTOM WEBVIEW CLIENT END------------------------------------- //----------------------------------------REPETITIVE UPDATER---------------------------------------- /** * This class is used to implement the repetitive increment/decrement of page value * when long pressing one of the page navigation buttons. */ class RepetitiveUpdater implements Runnable { private final int step; RepetitiveUpdater(int step){this.step = step;} public void run() { long REPEAT_DELAY = 250; if( autoIncrement ){ increment(step); repeatUpdateHandler.postDelayed( new RepetitiveUpdater(step), REPEAT_DELAY); } else if( autoDecrement ){ decrement(step); repeatUpdateHandler.postDelayed( new RepetitiveUpdater(step), REPEAT_DELAY); } } } //--------------------------------------REPETITIVE UPDATER END-------------------------------------- }