@ -1,19 +1,25 @@
package gr.thmmy.mthmmy.activities ;
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.v7.app.ActionBar ;
import android.support.v7.widget.CardView ;
import android.util.Log ;
import android.util.SparseArray ;
import android.view.Gravity ;
import android.view.KeyEvent ;
import android.view.LayoutInflater ;
import android.view.MotionEvent ;
import android.view.View ;
import android.view.inputmethod.EditorInfo ;
import android.webkit.WebResourceRequest ;
import android.webkit.WebView ;
import android.widget.EditText ;
import android.webkit.WebViewClient ;
import android.widget.FrameLayout ;
import android.widget.ImageButton ;
import android.widget.LinearLayout ;
import android.widget.ProgressBar ;
import android.widget.TextView ;
@ -28,6 +34,7 @@ import org.jsoup.select.Elements;
import java.util.ArrayList ;
import java.util.List ;
import java.util.Objects ;
import javax.net.ssl.SSLHandshakeException ;
@ -42,16 +49,27 @@ public class TopicActivity extends BaseActivity {
//-----------------------------------------CLASS VARIABLES------------------------------------------
/* --Post-- */
/* --Posts -- */
private List < Post > postsList ;
private LinearLayout postsLinearLayout ;
/* --Topic's page-- */
private static final int NO_POST_FOCUS = - 1 ;
private int postFocus = NO_POST_FOCUS ;
/* --Posts end-- */
/* --Topic's pages-- */
private int thisPage = 1 ;
private String base_url = "" ;
private int numberOfPages = 1 ;
private EditText pageSelect ;
private final SparseArray < String > pagesUrls = new SparseArray < > ( ) ;
/* --Topic's page end-- */
//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 ;
/* --Topic's pages end-- */
/* --Thumbnail-- */
private static final int THUMBNAIL_SIZE = 80 ;
private ImageLoader imageLoader = ImageController . getInstance ( ) . getImageLoader ( ) ;
@ -60,8 +78,9 @@ public class TopicActivity extends BaseActivity {
//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 ) {
@ -69,7 +88,7 @@ public class TopicActivity extends BaseActivity {
setContentView ( R . layout . activity_topic ) ;
Bundle extras = getIntent ( ) . getExtras ( ) ;
final String topicTitle = getIntent ( ) . getExtras ( ) . getString ( "TOPIC_TITLE" ) ;
topicTitle = getIntent ( ) . getExtras ( ) . getString ( "TOPIC_TITLE" ) ;
//Variables initialization
postsLinearLayout = ( LinearLayout ) findViewById ( R . id . posts_list ) ;
@ -81,57 +100,131 @@ public class TopicActivity extends BaseActivity {
postsList = new ArrayList < > ( ) ;
ActionBar actionbar = getSupportActionBar ( ) ;
actionbar = getSupportActionBar ( ) ;
if ( actionbar ! = null )
if ( ! Objects . equals ( topicTitle , "" ) )
actionbar . setTitle ( topicTitle ) ;
/* Add page select field to the Action Bar */
//Edit Action Bar's parameters
ActionBar . LayoutParams lp = new ActionBar . LayoutParams (
LinearLayout . LayoutParams . WRAP_CONTENT
, LinearLayout . LayoutParams . WRAP_CONTENT
, Gravity . END | Gravity . CENTER_VERTICAL ) ;
//Initialize layout to be added
View customNav = LayoutInflater . from ( this ) . inflate ( R . layout . topic_page_select , null ) ;
//Initialize text field
pageSelect = ( EditText ) customNav . findViewById ( R . id . select_page ) ;
pageSelect . setOnEditorActionListener ( new TextView . OnEditorActionListener ( ) {
ImageButton firstPage = ( ImageButton ) findViewById ( R . id . page_first_button ) ;
ImageButton previousPage = ( ImageButton ) findViewById ( R . id . page_previous_button ) ;
pageIndicator = ( TextView ) findViewById ( R . id . page_indicator ) ;
ImageButton nextPage = ( ImageButton ) findViewById ( R . id . page_next_button ) ;
ImageButton lastPage = ( ImageButton ) findViewById ( R . id . page_last_button ) ;
initDecrementButton ( firstPage , LARGE_STEP ) ;
initDecrementButton ( previousPage , SMALL_STEP ) ;
initIncrementButton ( nextPage , SMALL_STEP ) ;
initIncrementButton ( lastPage , LARGE_STEP ) ;
new TopicTask ( ) . execute ( extras . getString ( "TOPIC_URL" ) ) ; //Attempt data parsing
}
@Override
public boolean onEditorAction ( TextView v , int actionId , KeyEvent event ) { //When page is changed
if ( actionId = = EditorInfo . IME_ACTION_GO ) {
//Get page requested by user
int pageRequested = Integer . parseInt ( pageSelect . getText ( ) . toString ( ) ) ;
if ( pageRequested = = thisPage ) {
Toast . makeText ( getBaseContext ( )
, "You already are here!" , Toast . LENGTH_LONG ) . show ( ) ;
} else if ( pageRequested > = 1 & & pageRequested < = numberOfPages ) {
//Restart activity with new page
Intent intent = getIntent ( ) ;
intent . putExtra ( "TOPIC_URL" , pagesUrls . get ( pageRequested - 1 ) ) ;
intent . putExtra ( "TOPIC_TITLE" , topicTitle ) ;
finish ( ) ;
startActivity ( intent ) ;
} else { //Invalid page request
Toast . makeText ( getBaseContext ( )
, "There is no such page!" , Toast . LENGTH_LONG ) . show ( ) ;
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 ) {
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 RepetetiveUpdater ( step ) , INITIAL_DELAY ) ;
return false ;
}
}
) ;
// When the button is released, if we're auto incrementing, stop
increment . setOnTouchListener ( new View . OnTouchListener ( ) {
public boolean onTouch ( View v , MotionEvent event ) {
if ( event . getAction ( ) = = MotionEvent . ACTION_UP & & autoIncrement ) {
autoIncrement = false ;
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 ) {
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 RepetetiveUpdater ( step ) , INITIAL_DELAY ) ;
return false ;
}
}
) ;
// When the button is released, if we're auto decrementing, stop
decrement . setOnTouchListener ( new View . OnTouchListener ( ) {
public boolean onTouch ( View v , MotionEvent event ) {
if ( event . getAction ( ) = = MotionEvent . ACTION_UP & & autoDecrement ) {
autoDecrement = false ;
changePage ( pageValue - 1 ) ;
}
return true ;
}
} ) ;
assert actionbar ! = null ;
actionbar . setCustomView ( customNav , lp ) ; //Add feature to Action Bar
actionbar . setDisplayShowCustomEnabled ( true ) ;
}
new TopicTask ( ) . execute ( extras . getString ( "TOPIC_URL" ) ) ; //Attempt data parsing
private void increment ( int step ) {
if ( pageValue < numberOfPages - step - 1 ) {
pageValue = pageValue + step ;
}
pageIndicator . setText ( pageValue + "/" + String . valueOf ( numberOfPages ) ) ;
if ( pageValue > = 1000 )
pageIndicator . setTextSize ( 16 ) ;
else
pageIndicator . setTextSize ( 20 ) ;
}
@Override
protected void onDestroy ( ) { //When finished cancel whatever request can still be canceled
super . onDestroy ( ) ;
ImageController . getInstance ( ) . cancelPendingRequests ( ) ;
private void decrement ( int step ) {
if ( pageValue > step + 1 ) {
pageValue = pageValue - step ;
}
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 < String , Void , Boolean > {
//Class variables
@ -145,10 +238,18 @@ public class TopicActivity extends BaseActivity {
protected Boolean doInBackground ( String . . . strings ) {
Document document ;
base_url = strings [ 0 ] . substring ( 0 , strings [ 0 ] . lastIndexOf ( "." ) ) ; //This topic's base url
String pageLink = strings [ 0 ] ; //This page's 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 ) ;
postFocus = Integer . parseInt ( tmp . substring ( 0 , tmp . indexOf ( ";" ) ) ) ;
}
}
Request request = new Request . Builder ( )
. url ( pageLink )
. url ( pageUrl )
. build ( ) ;
try {
Response response = client . newCall ( request ) . execute ( ) ;
@ -174,11 +275,26 @@ public class TopicActivity extends BaseActivity {
progressBar . setVisibility ( ProgressBar . INVISIBLE ) ; //Hide progress bar
populateLayout ( ) ; //Show parsed data
//Set current page
pageSelect . setHint ( String . valueOf ( thisPage ) + "/" + String . valueOf ( numberOfPages ) ) ;
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 ) {
@ -209,8 +325,8 @@ public class TopicActivity extends BaseActivity {
for ( Element item : rows ) { //For every post
//Variables to pass
String p_userName , p_thumbnailUrl , p_subject , p_post ;
int p_postNum ;
String p_userName , p_thumbnailUrl , p_subject , p_post , p_postDate ;
int p_postNum , p_postIndex ;
//Find the Username
Element userName = item . select ( "a[title^=View the profile of]" ) . first ( ) ;
@ -238,7 +354,13 @@ public class TopicActivity extends BaseActivity {
p_post = ( "<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />"
+ p_post ) ; //style.css
//Find post's index number
//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 ;
@ -247,16 +369,34 @@ public class TopicActivity extends BaseActivity {
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 ) ) ;
}
//Add new post in postsList
postsList . add ( new Post ( p_thumbnailUrl , p_userName , p_subject
, p_post , p_postNum ) ) ;
, p_post , p_postDate , p_post Num , p_postIndex ) ) ;
}
}
/* Parse method end */
}
//-------------------------------------TOPIC ASYNC TASK END-----------------------------------------
//----------------------------------------POPULATE UI METHOD----------------------------------------
private void populateLayout ( ) { //Show parsed data
//Set topic title if not already present
if ( topicTitle = = null | | Objects . equals ( topicTitle , "" ) ) {
topicTitle = parsedTitle ;
if ( actionbar ! = null ) {
actionbar . setTitle ( topicTitle ) ;
}
}
//Initialize an inflater
LayoutInflater inflater = ( LayoutInflater ) getApplicationContext ( )
. getSystemService ( Context . LAYOUT_INFLATER_SERVICE ) ;
@ -270,11 +410,18 @@ public class TopicActivity extends BaseActivity {
imageLoader = ImageController . getInstance ( ) . getImageLoader ( ) ;
//Initialize layout's graphic elements
final FrameLayout cardExpandable = ( FrameLayout ) convertView . findViewById ( R . id . card_expandable ) ;
TextView postDate = ( TextView ) convertView . findViewById ( R . id . post_date ) ;
TextView postNum = ( TextView ) convertView . findViewById ( R . id . post_number ) ;
CircularNetworkImageView thumbnail = ( CircularNetworkImageView ) convertView . findViewById ( R . id . thumbnail ) ;
TextView username = ( TextView ) convertView . findViewById ( R . id . username ) ;
TextView postNum = ( TextView ) convertView . findViewById ( R . id . post_number ) ;
TextView subject = ( TextView ) convertView . findViewById ( R . id . subject ) ;
WebView post = ( WebView ) convertView . findViewById ( R . id . post ) ;
CardView cardView = ( CardView ) convertView . findViewById ( R . id . card_view ) ;
//Post's WebView parameters set
post . setClickable ( true ) ;
post . setWebViewClient ( new LinkLauncher ( ) ) ;
//Avoiding errors about layout having 0 width/height
thumbnail . setMinimumWidth ( 1 ) ;
@ -291,6 +438,9 @@ public class TopicActivity extends BaseActivity {
//Username set
username . setText ( item . getAuthor ( ) ) ;
//Post's submit date set
postDate . setText ( item . getPostDate ( ) ) ;
//Post's index number set
if ( item . getPostNumber ( ) ! = 0 )
postNum . setText ( "#" + item . getPostNumber ( ) ) ;
@ -300,10 +450,138 @@ public class TopicActivity extends BaseActivity {
//Post's text set
post . loadDataWithBaseURL ( "file:///android_asset/" , item . getContent ( ) , "text/html" , "UTF-8" , null ) ;
post . setEnabled ( false ) ;
/* --"Card expand/collapse"-like functionality-- */
if ( item . getPostNumber ( ) ! = 0 ) {
//Should expand/collapse when card is touched
cardView . setOnClickListener ( new View . OnClickListener ( ) {
@Override
public void onClick ( View v ) {
if ( cardExpandable . getVisibility ( ) = = View . GONE )
cardExpandable . setVisibility ( View . VISIBLE ) ;
else
cardExpandable . setVisibility ( View . GONE ) ;
}
} ) ;
//Also when post is clicked
post . setOnTouchListener ( new View . OnTouchListener ( ) {
final static int FINGER_RELEASED = 0 ;
final static int FINGER_TOUCHED = 1 ;
final static int FINGER_DRAGGING = 2 ;
final static int FINGER_UNDEFINED = 3 ;
private int fingerState = FINGER_RELEASED ;
@Override
public boolean onTouch ( View view , MotionEvent motionEvent ) {
switch ( motionEvent . getAction ( ) ) {
case MotionEvent . ACTION_DOWN :
if ( fingerState = = FINGER_RELEASED )
fingerState = FINGER_TOUCHED ;
else
fingerState = FINGER_UNDEFINED ;
break ;
case MotionEvent . ACTION_UP :
fingerState = FINGER_RELEASED ;
if ( cardExpandable . getVisibility ( ) = = View . GONE )
cardExpandable . setVisibility ( View . VISIBLE ) ;
else
cardExpandable . setVisibility ( View . GONE ) ;
break ;
case MotionEvent . ACTION_MOVE :
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--------------------------------------
//--------------------------------------CUSTOM WEBVIEW CLIENT---------------------------------------
private class LinkLauncher extends WebViewClient {
//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=" ) ) {
//Restart activity with new topic
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-------------------------------------
class RepetetiveUpdater implements Runnable {
private final int step ;
RepetetiveUpdater ( int step ) { this . step = step ; }
public void run ( ) {
long REPEAT_DELAY = 250 ;
if ( autoIncrement ) {
increment ( step ) ;
repeatUpdateHandler . postDelayed ( new RepetetiveUpdater ( step ) , REPEAT_DELAY ) ;
} else if ( autoDecrement ) {
decrement ( step ) ;
repeatUpdateHandler . postDelayed ( new RepetetiveUpdater ( step ) , REPEAT_DELAY ) ;
}
}
}
}