diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java index 9362f14a..24f4278a 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java @@ -18,6 +18,8 @@ import gr.thmmy.mthmmy.activities.main.recent.RecentFragment; import gr.thmmy.mthmmy.activities.topic.TopicActivity; import gr.thmmy.mthmmy.data.TopicSummary; +import static gr.thmmy.mthmmy.activities.topic.TopicActivity.EXTRAS_TOPIC_TITLE; +import static gr.thmmy.mthmmy.activities.topic.TopicActivity.EXTRAS_TOPIC_URL; import static gr.thmmy.mthmmy.session.SessionManager.LOGGED_OUT; public class MainActivity extends BaseActivity implements RecentFragment.RecentFragmentInteractionListener, ForumFragment.ForumFragmentInteractionListener { @@ -83,8 +85,8 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF @Override public void onFragmentInteraction(TopicSummary topicSummary) { Intent i = new Intent(MainActivity.this, TopicActivity.class); - i.putExtra("TOPIC_URL", topicSummary.getTopicUrl()); - i.putExtra("TOPIC_TITLE", topicSummary.getTitle()); + i.putExtra(EXTRAS_TOPIC_URL, topicSummary.getTopicUrl()); + i.putExtra(EXTRAS_TOPIC_TITLE, topicSummary.getTitle()); startActivity(i); } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java index 00db479d..94f2e13d 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java @@ -72,6 +72,7 @@ public class ProfileActivity extends BaseActivity { * {@link ArrayList} of Strings used to hold profile's information. Data are added in {@link ProfileTask}. */ private ArrayList parsedProfileData; + private ProfileTask profileTask; private static final int THUMBNAIL_SIZE = 200; @Override @@ -82,7 +83,7 @@ public class ProfileActivity extends BaseActivity { PACKAGE_NAME = getApplicationContext().getPackageName(); Bundle extras = getIntent().getExtras(); - //Initialize graphic elements + //Initializes graphic elements toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar.setTitle(null); setSupportActionBar(toolbar); @@ -90,7 +91,9 @@ public class ProfileActivity extends BaseActivity { getSupportActionBar().setDisplayShowTitleEnabled(false); getSupportActionBar().setDisplayHomeAsUpEnabled(true); } + createDrawer(); + progressBar = (ProgressBar) findViewById(R.id.progressBar); userThumbnail = (ImageView) findViewById(R.id.user_thumbnail); @@ -100,24 +103,19 @@ public class ProfileActivity extends BaseActivity { replyFAB = (FloatingActionButton) findViewById(R.id.profile_fab); replyFAB.setEnabled(false); - replyFAB.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { SharedPreferences sharedPrefs = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE); int tmp_curr_status = sharedPrefs.getInt(LOGIN_STATUS, -1); if (tmp_curr_status == -1) { + Report.e(TAG, "Error while getting LOGIN_STATUS"); new AlertDialog.Builder(ProfileActivity.this) .setTitle("ERROR!") - .setMessage("An error occurred while trying to find your LOGIN_STATUS.\n" + - "Please sent below info to developers:\n" - + getLocalClassName() + "." + "l" - + Thread.currentThread().getStackTrace()[1].getLineNumber()) + .setMessage("An error occurred while trying to find your LOGIN_STATUS.") .setNeutralButton("Dismiss", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { - //Todo - //Maybe sent info back to developers? } }) .show(); @@ -146,7 +144,17 @@ public class ProfileActivity extends BaseActivity { } }); - new ProfileTask().execute(extras.getString(EXTRAS_PROFILE_URL)); //Attempt data parsing + //Gets info + parsedProfileData = new ArrayList<>(); + profileTask = new ProfileTask(); + profileTask.execute(extras.getString(EXTRAS_PROFILE_URL)); //Attempt data parsing + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (profileTask != null && profileTask.getStatus() != AsyncTask.Status.RUNNING) + profileTask.cancel(true); } /** diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java index a3148293..9d7f8544 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java @@ -1,25 +1,19 @@ package gr.thmmy.mthmmy.activities.topic; -import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; -import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import android.os.Environment; import android.os.Handler; -import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; -import android.util.Log; import android.util.SparseArray; import android.view.MotionEvent; import android.view.View; -import android.webkit.MimeTypeMap; import android.widget.ImageButton; import android.widget.ProgressBar; import android.widget.TextView; @@ -28,8 +22,6 @@ import android.widget.Toast; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -40,34 +32,47 @@ import gr.thmmy.mthmmy.activities.LoginActivity; import gr.thmmy.mthmmy.activities.base.BaseActivity; import gr.thmmy.mthmmy.data.Post; import mthmmy.utils.Report; -import okhttp3.Call; -import okhttp3.Callback; import okhttp3.Request; import okhttp3.Response; -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static gr.thmmy.mthmmy.session.SessionManager.LOGGED_IN; import static gr.thmmy.mthmmy.session.SessionManager.LOGIN_STATUS; +/** + * Activity for topics. When creating an Intent of this activity you need to bundle a String + * containing this topics's url using the key {@link #EXTRAS_TOPIC_URL} and a String containing + * this topic's title using the key {@link #EXTRAS_TOPIC_TITLE}. + */ @SuppressWarnings("unchecked") public class TopicActivity extends BaseActivity { - - //-----------------------------------------CLASS VARIABLES------------------------------------------ + //Class variables + /** + * Debug Tag for logging debug output to LogCat + */ + @SuppressWarnings("unused") + private static final String TAG = "TopicActivity"; + /** + * The key to use when putting topic's url String to {@link TopicActivity}'s Bundle. + */ + public static final String EXTRAS_TOPIC_URL = "TOPIC_URL"; + /** + * The key to use when putting topic's title String to {@link TopicActivity}'s Bundle. + */ + public static final String EXTRAS_TOPIC_TITLE = "TOPIC_TITLE"; + static String PACKAGE_NAME; private TopicTask topicTask; - - /* --Posts-- */ + //About posts private List postsList; static final int NO_POST_FOCUS = -1; static int postFocus = NO_POST_FOCUS; - //Quote + //Quotes public static final ArrayList toQuoteList = new ArrayList<>(); - /* --Topic's pages-- */ + //Topic's pages private int thisPage = 1; public static 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; @@ -75,22 +80,20 @@ public class TopicActivity extends BaseActivity { private static final int SMALL_STEP = 1; private static final int LARGE_STEP = 10; private Integer pageRequestValue; - + //Bottom navigation graphics private ImageButton firstPage; private ImageButton previousPage; + private TextView pageIndicator; private ImageButton nextPage; private ImageButton lastPage; - //Other variables private ProgressBar progressBar; - @SuppressWarnings("unused") - private static final String TAG = "TopicActivity"; private String topicTitle; private FloatingActionButton replyFAB; private String parsedTitle; private RecyclerView recyclerView; private RecyclerView.LayoutManager layoutManager; - static String PACKAGE_NAME; + @Override protected void onCreate(Bundle savedInstanceState) { @@ -98,11 +101,10 @@ public class TopicActivity extends BaseActivity { setContentView(R.layout.activity_topic); PACKAGE_NAME = getApplicationContext().getPackageName(); - Bundle extras = getIntent().getExtras(); - topicTitle = getIntent().getExtras().getString("TOPIC_TITLE"); + topicTitle = extras.getString("TOPIC_TITLE"); - //Initialize toolbar, drawer and ProgressBar + //Initializes graphics toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar.setTitle(topicTitle); setSupportActionBar(toolbar); @@ -115,44 +117,29 @@ public class TopicActivity extends BaseActivity { progressBar = (ProgressBar) findViewById(R.id.progressBar); - postsList = new ArrayList<>(); + postsList = new ArrayList<>(); - 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); + recyclerView = (RecyclerView) findViewById(R.id.topic_recycler_view); + recyclerView.setHasFixedSize(true); + layoutManager = new LinearLayoutManager(getApplicationContext()); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setAdapter(new TopicAdapter(getApplicationContext(), postsList)); replyFAB = (FloatingActionButton) findViewById(R.id.topic_fab); replyFAB.setEnabled(false); - replyFAB.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { SharedPreferences sharedPrefs = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE); int tmp_curr_status = sharedPrefs.getInt(LOGIN_STATUS, -1); if (tmp_curr_status == -1) { + Report.e(TAG, "Error while getting LOGIN_STATUS"); new AlertDialog.Builder(TopicActivity.this) .setTitle("ERROR!") - .setMessage("An error occurred while trying to find your LOGIN_STATUS.\n" + - "Please sent below info to developers:\n" - + getLocalClassName() + "." + "l" - + Thread.currentThread().getStackTrace()[1].getLineNumber()) + .setMessage("An error occurred while trying to find your LOGIN_STATUS.") .setNeutralButton("Dismiss", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { - //Todo - //Maybe sent info back to developers? } }) .show(); @@ -181,14 +168,26 @@ public class TopicActivity extends BaseActivity { } }); - recyclerView = (RecyclerView) findViewById(R.id.topic_recycler_view); - recyclerView.setHasFixedSize(true); - layoutManager = new LinearLayoutManager(getApplicationContext()); - recyclerView.setLayoutManager(layoutManager); - recyclerView.setAdapter(new TopicAdapter(getApplicationContext(), postsList)); + //Sets bottom navigation bar + 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); + //Gets posts topicTask = new TopicTask(); - topicTask.execute(extras.getString("TOPIC_URL")); //Attempt data parsing + topicTask.execute(extras.getString(EXTRAS_TOPIC_URL)); //Attempt data parsing } @Override @@ -260,8 +259,8 @@ public class TopicActivity extends BaseActivity { changePage(0); return; } - //Clicked and holden - autoDecrement = false; //Stop incrementing + //Clicked and hold + autoDecrement = false; //Stop decrementing decrementPageRequestValue(step); changePage(pageRequestValue - 1); } @@ -296,10 +295,6 @@ public class TopicActivity extends BaseActivity { } else pageRequestValue = numberOfPages; pageIndicator.setText(pageRequestValue + "/" + String.valueOf(numberOfPages)); - if (pageRequestValue >= 1000) - pageIndicator.setTextSize(16); - else - pageIndicator.setTextSize(20); } private void decrementPageRequestValue(int step) { @@ -308,10 +303,6 @@ public class TopicActivity extends BaseActivity { else pageRequestValue = 1; pageIndicator.setText(pageRequestValue + "/" + String.valueOf(numberOfPages)); - if (numberOfPages >= 1000) - pageIndicator.setTextSize(16); - else - pageIndicator.setTextSize(20); } private void changePage(int pageRequested) { @@ -326,15 +317,24 @@ public class TopicActivity extends BaseActivity { } //------------------------------------BOTTOM NAV BAR METHODS END------------------------------------ - //---------------------------------------TOPIC ASYNC TASK------------------------------------------- + /** + * An {@link AsyncTask} that handles asynchronous fetching of a topic page and parsing it's + * data. {@link AsyncTask#onPostExecute(Object) OnPostExecute} method calls {@link RecyclerView#swapAdapter} + * to build graphics. + *

+ *

Calling ProfileTask's {@link AsyncTask#execute execute} method needs to have profile's url + * as String parameter!

+ */ public class TopicTask extends AsyncTask { //Class variables + /** + * Debug Tag for logging debug output to LogCat + */ private static final String TAG = "TopicTask"; //Separate tag for AsyncTask private static final int SUCCESS = 0; private static final int NETWORK_ERROR = 1; private static final int OTHER_ERROR = 2; - //Show a progress bar until done protected void onPreExecute() { progressBar.setVisibility(ProgressBar.VISIBLE); replyFAB.setEnabled(false); @@ -343,9 +343,9 @@ public class TopicActivity extends BaseActivity { protected Integer 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 + String pageUrl = strings[0]; - //Find message focus if present + //Finds message focus if present { postFocus = NO_POST_FOCUS; if (pageUrl.contains("msg")) { @@ -363,7 +363,7 @@ public class TopicActivity extends BaseActivity { try { Response response = client.newCall(request).execute(); document = Jsoup.parse(response.body().string()); - parse(document); //Parse data + parse(document); return SUCCESS; } catch (IOException e) { Report.i(TAG, "IO Exception", e); @@ -374,18 +374,26 @@ public class TopicActivity extends BaseActivity { } } - protected void onPostExecute(Integer result) { - switch (result) { + protected void onPostExecute(Integer parseResult) { + switch (parseResult) { case SUCCESS: - //Parse was successful - progressBar.setVisibility(ProgressBar.INVISIBLE); //Hide progress bar - populateLayout(); //Show parsed data + progressBar.setVisibility(ProgressBar.INVISIBLE); + + recyclerView.swapAdapter(new TopicAdapter(getApplicationContext(), postsList), false); + //Set post focus + if (postFocus != NO_POST_FOCUS) { + for (int i = postsList.size() - 1; i >= 0; --i) { + int currentPostIndex = postsList.get(i).getPostIndex(); + if (currentPostIndex == postFocus) { + layoutManager.scrollToPosition(i); + } + } + } + replyFAB.setEnabled(true); //Set current page pageIndicator.setText(String.valueOf(thisPage) + "/" + String.valueOf(numberOfPages)); pageRequestValue = thisPage; - if (numberOfPages >= 1000) - pageIndicator.setTextSize(16); firstPage.setEnabled(true); previousPage.setEnabled(true); @@ -406,13 +414,18 @@ public class TopicActivity extends BaseActivity { } } - /* Parse method */ - private void parse(Document document) { - String language = TopicParser.defineLanguage(document); + /** + * All the parsing a topic needs. + * + * @param topic {@link Document} object containing this topic's source code + * @see org.jsoup.Jsoup Jsoup + */ + private void parse(Document topic) { + String language = TopicParser.defineLanguage(topic); - //Find topic title if missing + //Finds topic title if missing if (topicTitle == null || Objects.equals(topicTitle, "")) { - parsedTitle = document.select("td[id=top_subject]").first().text(); + parsedTitle = topic.select("td[id=top_subject]").first().text(); if (parsedTitle.contains("Topic:")) { parsedTitle = parsedTitle.substring(parsedTitle.indexOf("Topic:") + 7 , parsedTitle.indexOf("(Read") - 2); @@ -423,11 +436,11 @@ public class TopicActivity extends BaseActivity { } } - { //Find current page's index - thisPage = TopicParser.parseCurrentPageIndex(document, language); + { //Finds current page's index + thisPage = TopicParser.parseCurrentPageIndex(topic, language); } - { //Find number of pages - numberOfPages = TopicParser.parseTopicNumberOfPages(document, thisPage, language); + { //Finds number of pages + numberOfPages = TopicParser.parseTopicNumberOfPages(topic, thisPage, language); for (int i = 0; i < numberOfPages; i++) { //Generate each page's url from topic's base url +".15*numberOfPage" @@ -435,44 +448,20 @@ public class TopicActivity extends BaseActivity { } } - postsList = TopicParser.parseTopic(document, language); - } - /* Parse method end */ - } -//-------------------------------------TOPIC ASYNC TASK END----------------------------------------- - -//----------------------------------------POPULATE UI METHOD---------------------------------------- - - /** - * This method runs on the main thread. It reads from the postsList and dynamically - * adds a card for each post to the ScrollView. - */ - private void populateLayout() { - recyclerView.swapAdapter(new TopicAdapter(getApplicationContext(), postsList), false); - - //Set post focus - if (postFocus != NO_POST_FOCUS) { - for (int i = postsList.size() - 1; i >= 0; --i) { - int currentPostIndex = postsList.get(i).getPostIndex(); - if (currentPostIndex == postFocus) { - layoutManager.scrollToPosition(i); - } - } + postsList = TopicParser.parseTopic(topic, language); } - replyFAB.setEnabled(true); } -//--------------------------------------POPULATE UI METHOD END-------------------------------------- - -//----------------------------------------REPETITIVE UPDATER---------------------------------------- - /** - * This class is used to implement the repetitive incrementPageRequestValue/decrementPageRequestValue of page value - * when long pressing one of the page navigation buttons. + * This class is used to implement the repetitive incrementPageRequestValue/decrementPageRequestValue + * of page value when long pressing one of the page navigation buttons. */ class RepetitiveUpdater implements Runnable { private final int step; + /** + * @param step number of pages to add/subtract on each repetition + */ RepetitiveUpdater(int step) { this.step = step; } @@ -488,76 +477,4 @@ public class TopicActivity extends BaseActivity { } } } -//--------------------------------------REPETITIVE UPDATER END-------------------------------------- - -//------------------------------METHODS FOR DOWNLOADING ATTACHED FILES------------------------------ - - /** - * Create a File - */ - static void downloadFileAsync(final String downloadUrl, final String fileName, final Context context) { - Request request = new Request.Builder().url(downloadUrl).build(); - - getClient().newCall(request).enqueue(new Callback() { - public void onFailure(Call call, IOException e) { - e.printStackTrace(); - } - - public void onResponse(Call call, Response response) throws IOException { - if (!response.isSuccessful()) { - throw new IOException("Failed to download file: " + response); - } - File tmpFile = getOutputMediaFile(PACKAGE_NAME, fileName); - if (tmpFile == null) { - Report.d(TAG - , "Error creating media file, check storage permissions!"); - } else { - FileOutputStream fos = new FileOutputStream(tmpFile); - fos.write(response.body().bytes()); - fos.close(); - - String filePath = tmpFile.getAbsolutePath(); - String extension = MimeTypeMap.getFileExtensionFromUrl( - filePath.substring(filePath.lastIndexOf("/"))); - String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - - Intent intent = new Intent(); - intent.setAction(android.content.Intent.ACTION_VIEW); - intent.setDataAndType(Uri.fromFile(tmpFile), mime); - intent.setFlags(FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - } - } - }); - } - - /** - * Create a File - */ - @Nullable - private static File getOutputMediaFile(String packageName, String fileName) { - // To be safe, you should check that the SDCard is mounted - // using Environment.getExternalStorageState() before doing this. - File mediaStorageDir = new File(Environment.getExternalStorageDirectory() - + "/Android/data/" - + packageName - + "/Downloads"); - - // This location works best if you want the created files to be shared - // between applications and persist after your app has been uninstalled. - - // Create the storage directory if it does not exist - if (!mediaStorageDir.exists()) { - if (!mediaStorageDir.mkdirs()) { - Log.d(TAG, "problem!"); - return null; - } - } - // Create a media file name - File mediaFile; - mediaFile = new File(mediaStorageDir.getPath() + File.separator + fileName); - return mediaFile; - } - -//----------------------------METHODS FOR DOWNLOADING ATTACHED FILES END---------------------------- } \ No newline at end of file diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java index 111788bb..5bd8c5fc 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java @@ -7,6 +7,7 @@ import android.content.Intent; import android.graphics.Color; import android.graphics.Typeface; import android.net.Uri; +import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -19,6 +20,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.webkit.MimeTypeMap; import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebViewClient; @@ -31,6 +33,8 @@ import android.widget.TextView; import com.squareup.picasso.Picasso; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -39,13 +43,14 @@ import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.profile.ProfileActivity; import gr.thmmy.mthmmy.data.Post; import gr.thmmy.mthmmy.utils.CircleTransform; +import gr.thmmy.mthmmy.utils.FileManager.ThmmyFile; import mthmmy.utils.Report; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.EXTRAS_PROFILE_URL; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.NO_POST_FOCUS; +import static gr.thmmy.mthmmy.activities.topic.TopicActivity.PACKAGE_NAME; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.base_url; -import static gr.thmmy.mthmmy.activities.topic.TopicActivity.downloadFileAsync; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.postFocus; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.toQuoteList; @@ -197,30 +202,28 @@ class TopicAdapter extends RecyclerView.Adapter { if (currentPost.getAttachedFiles().size() != 0) { holder.bodyFooterDivider.setVisibility(View.VISIBLE); - int filesTextColor = 0; + int filesTextColor; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { filesTextColor = context.getResources().getColor(R.color.accent, null); } else //noinspection deprecation filesTextColor = context.getResources().getColor(R.color.accent); - for (final String[] attachedFile : currentPost.getAttachedFiles()) { + for (final ThmmyFile attachedFile : currentPost.getAttachedFiles()) { final TextView attached = new TextView(context); attached.setTextSize(10f); attached.setClickable(true); attached.setTypeface(Typeface.createFromAsset(context.getAssets() , "fonts/fontawesome-webfont.ttf")); - attached.setText(faIconFromExtension(attachedFile[1]) + " " + attachedFile[1] + attachedFile[2]); + attached.setText(faIconFromExtension(attachedFile.getFilename()) + " " + + attachedFile.getFilename() + attachedFile.getFileInfo()); attached.setTextColor(filesTextColor); attached.setPadding(0, 3, 0, 3); attached.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - try { - downloadFileAsync(attachedFile[0], attachedFile[1], context); - } catch (Exception e) { - e.printStackTrace(); - } + DownloadTask downloadTask = new DownloadTask(); + downloadTask.execute(attachedFile); } }); @@ -578,4 +581,31 @@ class TopicAdapter extends RecyclerView.Adapter { return context.getResources().getString(R.string.fa_file); } + + private class DownloadTask extends AsyncTask { + //Class variables + /** + * Debug Tag for logging debug output to LogCat + */ + private static final String TAG = "DownloadTask"; //Separate tag for AsyncTask + + protected Void doInBackground(ThmmyFile... files) { + try { + File tmpFile = files[0].download(PACKAGE_NAME); + if (tmpFile != null) { + String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension( + files[0].getExtension()); + + Intent intent = new Intent(); + intent.setAction(android.content.Intent.ACTION_VIEW); + intent.setDataAndType(Uri.fromFile(tmpFile), mime); + intent.setFlags(FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + } catch (IOException e) { + Report.e(TAG, "Error while trying to download a file", e); + } + return null; + } + } } \ No newline at end of file diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java index 0bae59a5..095a02b1 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java @@ -7,12 +7,16 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; +import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import gr.thmmy.mthmmy.data.Post; +import gr.thmmy.mthmmy.utils.FileManager.ThmmyFile; +import mthmmy.utils.Report; /** * Singleton used for parsing a topic. @@ -20,8 +24,7 @@ import gr.thmmy.mthmmy.data.Post; *
  • {@link #parseCurrentPageIndex(Document, String)}
  • *
  • {@link #parseTopicNumberOfPages(Document, int, String)}
  • *
  • {@link #parseTopic(Document, String)}
  • - *
  • {@link #defineLanguage(Document)}
  • - *
  • (private) {@link #colorPicker(String)}
  • + *
  • {@link #defineLanguage(Document)}
  • */ class TopicParser { //Languages supported @@ -165,7 +168,7 @@ class TopicParser { p_specialRank, p_gender, p_personalText, p_numberOfPosts; int p_postNum, p_postIndex, p_numberOfStars, p_userColor; boolean p_isDeleted = false; - ArrayList p_attachedFiles; + ArrayList p_attachedFiles; //Initialize variables p_profileURL = null; @@ -274,21 +277,26 @@ class TopicParser { String postAttachmentsText = postAttachments.text(); for (int i = 0; i < attachedFiles.size(); ++i) { - String[] attachedArray = new String[3]; + URL attachedUrl; //Gets file's url and filename Element tmpAttachedFileUrlAndName = attachedFiles.get(i); - attachedArray[0] = tmpAttachedFileUrlAndName.attr("href"); - attachedArray[1] = tmpAttachedFileUrlAndName.text().substring(1); + try { + attachedUrl = new URL(tmpAttachedFileUrlAndName.attr("href")); + } catch (MalformedURLException e) { + Report.e(TAG,"Attached file malformed url", e); + break; + } + String attachedFileName = tmpAttachedFileUrlAndName.text().substring(1); //Gets file's info (size and download count) String postAttachmentsTextSbstr = postAttachmentsText.substring( - postAttachmentsText.indexOf(attachedArray[1])); + postAttachmentsText.indexOf(attachedFileName)); - attachedArray[2] = postAttachmentsTextSbstr.substring(attachedArray[1] + String attachedFileInfo = postAttachmentsTextSbstr.substring(attachedFileName .length(), postAttachmentsTextSbstr.indexOf("φορές.")) + "φορές.)"; - p_attachedFiles.add(attachedArray); + p_attachedFiles.add(new ThmmyFile(attachedUrl,attachedFileName,attachedFileInfo)); } } } else { @@ -329,21 +337,26 @@ class TopicParser { String postAttachmentsText = postAttachments.text(); for (int i = 0; i < attachedFiles.size(); ++i) { - String[] attachedArray = new String[3]; + URL attachedUrl; //Gets file's url and filename Element tmpAttachedFileUrlAndName = attachedFiles.get(i); - attachedArray[0] = tmpAttachedFileUrlAndName.attr("href"); - attachedArray[1] = tmpAttachedFileUrlAndName.text().substring(1); + try { + attachedUrl = new URL(tmpAttachedFileUrlAndName.attr("href")); + } catch (MalformedURLException e) { + Report.e(TAG,"Attached file malformed url", e); + break; + } + String attachedFileName = tmpAttachedFileUrlAndName.text().substring(1); //Gets file's info (size and download count) String postAttachmentsTextSbstr = postAttachmentsText.substring( - postAttachmentsText.indexOf(attachedArray[1])); + postAttachmentsText.indexOf(attachedFileName)); - attachedArray[2] = postAttachmentsTextSbstr.substring(attachedArray[1] + String attachedFileInfo = postAttachmentsTextSbstr.substring(attachedFileName .length(), postAttachmentsTextSbstr.indexOf("times.")) + "times.)"; - p_attachedFiles.add(attachedArray); + p_attachedFiles.add(new ThmmyFile(attachedUrl,attachedFileName,attachedFileInfo)); } } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/data/Post.java b/app/src/main/java/gr/thmmy/mthmmy/data/Post.java index d8241e9b..9ce37162 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/data/Post.java +++ b/app/src/main/java/gr/thmmy/mthmmy/data/Post.java @@ -2,6 +2,8 @@ package gr.thmmy.mthmmy.data; import java.util.ArrayList; +import gr.thmmy.mthmmy.utils.FileManager.ThmmyFile; + public class Post { //Standard info (exists in every post) private final String thumbnailUrl; @@ -22,13 +24,13 @@ public class Post { private final String personalText; private final int numberOfStars; private final int userColor; - private final ArrayList attachedFiles; + private final ArrayList attachedFiles; public Post(String thumbnailUrl, String author, String subject, String content , int postIndex, int postNumber, String postDate, String profileURl, String rank , String special_rank, String gender, String numberOfPosts , String personalText, int numberOfStars, int userColor - , ArrayList attachedFiles) { + , ArrayList attachedFiles) { this.thumbnailUrl = thumbnailUrl; this.author = author; this.subject = subject; @@ -50,7 +52,7 @@ public class Post { public Post(String thumbnailUrl, String author, String subject, String content , int postIndex, int postNumber, String postDate, int userColor - , ArrayList attachedFiles) { + , ArrayList attachedFiles) { this.thumbnailUrl = thumbnailUrl; this.author = author; this.subject = subject; @@ -135,7 +137,7 @@ public class Post { return userColor; } - public ArrayList getAttachedFiles() { + public ArrayList getAttachedFiles() { return attachedFiles; } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/FileManager/ThmmyFile.java b/app/src/main/java/gr/thmmy/mthmmy/utils/FileManager/ThmmyFile.java new file mode 100644 index 00000000..906afd9c --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/FileManager/ThmmyFile.java @@ -0,0 +1,183 @@ +package gr.thmmy.mthmmy.utils.FileManager; + +import android.os.Environment; +import android.support.annotation.Nullable; +import android.webkit.MimeTypeMap; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.util.Objects; + +import mthmmy.utils.Report; +import okhttp3.Request; +import okhttp3.Response; + +import static gr.thmmy.mthmmy.activities.base.BaseActivity.getClient; + +/** + * Used for downloading and storing a file from the forum using {@link okhttp3}. + *

    Class has one constructor:

    • {@link #ThmmyFile(URL, String, String)}
    + * and the methods:
    • getters
    • + *
    • {@link #download()}
    • + *
    • {@link #download(String)}

    + */ +public class ThmmyFile { + /** + * Debug Tag for logging debug output to LogCat + */ + private static final String TAG = "ThmmyFile"; + /** + * Folder name used when downloading files without a package name. + */ + private static final String NO_PACKAGE_FOLDER_NAME = "Other"; + private final URL fileUrl; + private final String filename, fileInfo; + private String extension, filePath; + private File file; + + /** + * This constructor only creates a ThmmyFile object and does not download the file. To download + * the file use {@link #download(String)} or {@link #download()}! + * + * @param fileUrl {@link URL} object with file's url + * @param filename {@link String} with desired file name + * @param fileInfo {@link String} with any extra information (like number of downloads) + */ + public ThmmyFile(URL fileUrl, String filename, String fileInfo) { + this.fileUrl = fileUrl; + this.filename = filename; + this.fileInfo = fileInfo; + this.extension = null; + this.filePath = null; + this.file = null; + } + + public URL getFileUrl() { + return fileUrl; + } + + public String getFilename() { + return filename; + } + + public String getFileInfo() { + return fileInfo; + } + + /** + * This is null until {@link #download(String)} or {@link #download()} is called and has succeeded. + * + * @return String with file's extension or null + */ + @Nullable + public String getExtension() { + return extension; + } + + /** + * This is null until {@link #download(String)} or {@link #download()} is called and has succeeded. + * + * @return String with file's path or null + */ + @Nullable + public String getFilePath() { + return filePath; + } + + /** + * This is null until {@link #download(String)} or {@link #download()} is called and has succeeded. + * + * @return {@link File} or null + */ + @Nullable + public File getFile() { + return file; + } + + private void setExtension(String extension) { + this.extension = extension; + } + + private void setFilePath(String filePath) { + this.filePath = filePath; + } + + /** + * Used to download the file. If download is successful file's extension and path will be assigned + * to object's fields and can be accessed using getter methods. + *

    File is stored in sdcard1/Android/data/Downloads/{@link #NO_PACKAGE_FOLDER_NAME}

    + * + * @return the {@link File} if successful, null otherwise + * @throws IOException + * @throws SecurityException + */ + @Nullable + public File download() throws IOException, SecurityException { + return download(NO_PACKAGE_FOLDER_NAME); + } + + /** + * Used to download the file. If download is successful file's extension and path will be assigned + * to object's fields and can be accessed using getter methods. + *

    File is stored in sdcard1/Android/data/Downloads/packageName

    + * + * @param packageName package's name to use as folder name for file's path + * @return the {@link File} if successful, null otherwise + * @throws IOException if the request could not be executed due to cancellation, a connectivity + * problem or timeout. Because networks can fail during an exchange, it is possible that the + * remote server accepted the request before the failure. + * @throws SecurityException if the requested file is not hosted by the forum. + */ + @Nullable + public File download(final String packageName) throws IOException, SecurityException { + if (!Objects.equals(fileUrl.getHost(), "www.thmmy.gr")) + throw new SecurityException("Downloading files from other sources is not supported"); + + Request request = new Request.Builder().url(fileUrl).build(); + + Response response = getClient().newCall(request).execute(); + if (!response.isSuccessful()) { + throw new IOException("Failed to download file: " + response); + } + file = getOutputMediaFile(packageName, filename); + if (file == null) { + Report.d(TAG, "Error creating media file, check storage permissions!"); + } else { + FileOutputStream fos = new FileOutputStream(file); + fos.write(response.body().bytes()); + fos.close(); + + filePath = file.getAbsolutePath(); + extension = MimeTypeMap.getFileExtensionFromUrl( + filePath.substring(filePath.lastIndexOf("/"))); + } + return file; + } + + @Nullable + private static File getOutputMediaFile(String packageName, String fileName) { + // To be safe, you should check that the SDCard is mounted + // using Environment.getExternalStorageState() before doing this. + File mediaStorageDir = new File(Environment.getExternalStorageDirectory() + + "/Android/data/gr.thmmy.mthmmy/" + + "Downloads/" + + packageName); + + // This location works best if you want the created files to be shared + // between applications and persist after your app has been uninstalled. + + // Create the storage directory if it does not exist + if (!mediaStorageDir.exists()) { + if (!mediaStorageDir.mkdirs()) { + Report.d(TAG, "problem!"); + return null; + } + } + // Create a media file name + File mediaFile; + mediaFile = new File(mediaStorageDir.getPath() + File.separator + fileName); + return mediaFile; + } +}