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 c85795d0..cf0f651e 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 @@ -33,6 +33,7 @@ import javax.net.ssl.SSLHandshakeException; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.LoginActivity; import gr.thmmy.mthmmy.activities.base.BaseActivity; +import gr.thmmy.mthmmy.activities.profile.latestPosts.LatestPostsFragment; import gr.thmmy.mthmmy.activities.profile.summary.SummaryFragment; import gr.thmmy.mthmmy.utils.CircleTransform; import me.zhanghai.android.materialprogressbar.MaterialProgressBar; @@ -73,6 +74,7 @@ public class ProfileActivity extends BaseActivity { private static final int THUMBNAIL_SIZE = 200; private ProfileTask profileTask; private String personalText; + private String profileUrl; @Override protected void onCreate(Bundle savedInstanceState) { @@ -82,6 +84,7 @@ public class ProfileActivity extends BaseActivity { Bundle extras = getIntent().getExtras(); String thumbnailUrl = extras.getString(BUNDLE_THUMBNAIL_URL); String username = extras.getString(BUNDLE_USERNAME); + profileUrl = extras.getString(BUNDLE_PROFILE_URL); //Initializes graphic elements toolbar = (Toolbar) findViewById(R.id.toolbar); @@ -146,7 +149,7 @@ public class ProfileActivity extends BaseActivity { }); profileTask = new ProfileTask(); - profileTask.execute(extras.getString(BUNDLE_PROFILE_URL)); //Attempt data parsing + profileTask.execute(profileUrl); //Attempt data parsing } @Override @@ -235,7 +238,7 @@ public class ProfileActivity extends BaseActivity { private void setupViewPager(ViewPager viewPager, Document profilePage) { ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager()); adapter.addFrag(SummaryFragment.newInstance(profilePage), "SUMMARY"); - //adapter.addFrag(new ); + adapter.addFrag(LatestPostsFragment.newInstance(profileUrl), "LATEST POSTS"); //adapter.addFrag(new ); viewPager.setAdapter(adapter); } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java index 607fce64..e62722f2 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java @@ -1,8 +1,88 @@ package gr.thmmy.mthmmy.activities.profile.latestPosts; -/** - * Created by apostolof on 1/1/17. - */ +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebView; +import android.widget.ProgressBar; +import android.widget.TextView; -public class LatestPostsAdapter { -} +import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.data.TopicSummary; + +import static gr.thmmy.mthmmy.activities.profile.latestPosts.LatestPostsFragment.parsedTopicSummaries; + +class LatestPostsAdapter extends RecyclerView.Adapter { + private final int VIEW_TYPE_ITEM = 0; + private final int VIEW_TYPE_LOADING = 1; + private OnLoadMoreListener mOnLoadMoreListener; + + public interface OnLoadMoreListener { + void onLoadMore(); + } + + public void setOnLoadMoreListener(OnLoadMoreListener mOnLoadMoreListener) { + this.mOnLoadMoreListener = mOnLoadMoreListener; + } + + @Override + public int getItemViewType(int position) { + return parsedTopicSummaries.get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == VIEW_TYPE_ITEM) { + View view = LayoutInflater.from(parent.getContext()). + inflate(R.layout.profile_fragment_latest_posts_row, parent, false); + return new LatestPostViewHolder(view); + } else if (viewType == VIEW_TYPE_LOADING) { + View view = LayoutInflater.from(parent.getContext()). + inflate(R.layout.recycler_loading_item, parent, false); + return new LoadingViewHolder(view); + } + return null; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder instanceof LatestPostViewHolder) { + TopicSummary topic = parsedTopicSummaries.get(position); + LatestPostViewHolder latestPostViewHolder = (LatestPostViewHolder) holder; + latestPostViewHolder.postTitle.setText(topic.getTitle()); + latestPostViewHolder.postDate.setText(topic.getDateTimeModified()); + //latestPostViewHolder.post. + } else if (holder instanceof LoadingViewHolder) { + LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder; + loadingViewHolder.progressBar.setIndeterminate(true); + } + } + + @Override + public int getItemCount() { + return parsedTopicSummaries == null ? 0 : parsedTopicSummaries.size(); + } + + private static class LatestPostViewHolder extends RecyclerView.ViewHolder { + TextView postTitle; + TextView postDate; + WebView post; + + LatestPostViewHolder(View itemView) { + super(itemView); + postTitle = (TextView) itemView.findViewById(R.id.title); + postDate = (TextView) itemView.findViewById(R.id.date); + post = (WebView) itemView.findViewById(R.id.post); + } + } + + private static class LoadingViewHolder extends RecyclerView.ViewHolder { + ProgressBar progressBar; + + LoadingViewHolder(View itemView) { + super(itemView); + progressBar = (ProgressBar) itemView.findViewById(R.id.progress_bar); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java index cd2692c7..16880ffd 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java @@ -1,8 +1,228 @@ package gr.thmmy.mthmmy.activities.profile.latestPosts; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.Toast; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.util.ArrayList; + +import javax.net.ssl.SSLHandshakeException; + +import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.activities.base.BaseActivity; +import gr.thmmy.mthmmy.data.TopicSummary; +import me.zhanghai.android.materialprogressbar.MaterialProgressBar; +import mthmmy.utils.Report; +import okhttp3.Request; +import okhttp3.Response; + /** - * Created by apostolof on 1/1/17. + * Use the {@link LatestPostsFragment#newInstance} factory method to create an instance of this fragment. */ +public class LatestPostsFragment extends Fragment { + /** + * Debug Tag for logging debug output to LogCat + */ + @SuppressWarnings("unused") + private static final String TAG = "LatestPostsFragment"; + /** + * The key to use when putting profile's url String to {@link LatestPostsFragment}'s Bundle. + */ + static final String PROFILE_URL = "PROFILE_DOCUMENT"; + /** + * {@link ArrayList} of {@link TopicSummary} objects used to hold profile's latest posts. Data + * are added in {@link LatestPostsFragment.ProfileLatestPostsTask}. + */ + static ArrayList parsedTopicSummaries; + private RecyclerView mainContent; + private LatestPostsAdapter latestPostsAdapter = new LatestPostsAdapter(); + private int numberOfPages = -1; + private String profileUrl; + private ProfileLatestPostsTask profileLatestPostsTask; + private MaterialProgressBar progressBar; + private boolean isLoading; + static int visibleThreshold = 5; + private int lastVisibleItem, totalItemCount; + + public LatestPostsFragment() { + // Required empty public constructor + } + + /** + * Use ONLY this factory method to create a new instance of this fragment using the provided + * parameters. + * + * @param profileUrl String containing this profile's url + * @return A new instance of fragment Summary. + */ + public static LatestPostsFragment newInstance(String profileUrl) { + LatestPostsFragment fragment = new LatestPostsFragment(); + Bundle args = new Bundle(); + args.putString(PROFILE_URL, profileUrl); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + profileUrl = getArguments().getString(PROFILE_URL); + parsedTopicSummaries = new ArrayList<>(); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (parsedTopicSummaries.isEmpty()) { + profileLatestPostsTask = new ProfileLatestPostsTask(); + profileLatestPostsTask.execute(profileUrl); + } + Report.d(TAG, "onActivityCreated"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (profileLatestPostsTask != null && profileLatestPostsTask.getStatus() != AsyncTask.Status.RUNNING) + profileLatestPostsTask.cancel(true); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + final View rootView = inflater.inflate(R.layout.profile_fragment_latest_posts, container, false); + + mainContent = (RecyclerView) rootView.findViewById(R.id.profile_latest_posts_recycler); + mainContent.setAdapter(latestPostsAdapter); + + final LatestPostsAdapter.OnLoadMoreListener onLoadMoreListener = new LatestPostsAdapter.OnLoadMoreListener() { + @Override + public void onLoadMore() { + parsedTopicSummaries.add(null); + latestPostsAdapter.notifyItemInserted(parsedTopicSummaries.size() - 1); + + //Removes loading item + parsedTopicSummaries.remove(parsedTopicSummaries.size() - 1); + latestPostsAdapter.notifyItemRemoved(parsedTopicSummaries.size()); + + //Load data + profileLatestPostsTask = new ProfileLatestPostsTask(); + profileLatestPostsTask.execute(profileUrl); + } + }; + + latestPostsAdapter.setOnLoadMoreListener(onLoadMoreListener); + final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) mainContent.getLayoutManager(); + mainContent.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + + totalItemCount = linearLayoutManager.getItemCount(); + lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition(); + + if (!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold)) { + onLoadMoreListener.onLoadMore(); + isLoading = true; + } + } + }); + progressBar = (MaterialProgressBar) rootView.findViewById(R.id.progressBar); + return rootView; + } + + /** + * An {@link AsyncTask} that handles asynchronous fetching of a profile page and parsing this + * user's personal text. + *

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

+ */ + public class ProfileLatestPostsTask extends AsyncTask { + //Class variables + /** + * Debug Tag for logging debug output to LogCat + */ + @SuppressWarnings("unused") + private static final String TAG = "ProfileLatestPostsTask"; //Separate tag for AsyncTask + + protected void onPreExecute() { + progressBar.setVisibility(ProgressBar.VISIBLE); + } + + protected Boolean doInBackground(String... profileUrl) { + String pageUrl = profileUrl[0] + ";sa=showPosts"; //Profile's page wap url + + Request request = new Request.Builder() + .url(pageUrl) + .build(); + try { + Response response = BaseActivity.getClient().newCall(request).execute(); + return parseLatestPosts(Jsoup.parse(response.body().string())); + } catch (SSLHandshakeException e) { + Report.w(TAG, "Certificate problem (please switch to unsafe connection)."); + } catch (Exception e) { + Report.e("TAG", "ERROR", e); + } + return false; + } + + protected void onPostExecute(Boolean result) { + if (!result) { //Parse failed! + Toast.makeText(getContext() + , "Fatal error!\n Aborting...", Toast.LENGTH_LONG).show(); + getActivity().finish(); + } + //Parse was successful + progressBar.setVisibility(ProgressBar.INVISIBLE); + latestPostsAdapter.notifyDataSetChanged(); + isLoading = false; + } + + private boolean parseLatestPosts(Document latestPostsPage) { + Elements latestPostsRows = latestPostsPage. + select("td:has(table:Contains(Show Posts)):not([style]) > table"); + if (latestPostsRows == null) + latestPostsRows = latestPostsPage. + select("td:has(table:Contains(Show Posts)):not([style]) > table"); + + for (Element row : latestPostsRows) { + String pTopicUrl, pTopicTitle, pDateTime, pPost; + + if (row.text().contains("Show Posts Pages: ") || row.text().contains("")) { + if (numberOfPages == -1) { + Elements pages = row.select("tr.catbg3 a"); + for (Element page : pages) { + if (Integer.parseInt(page.text()) >= numberOfPages) + numberOfPages = Integer.parseInt(page.text()); + } + } + } else { + Elements rowHeader = row.select("td.middletext"); + if (rowHeader.size() != 2) { + return false; + } else { + pTopicTitle = rowHeader.text(); + pTopicUrl = rowHeader.first().select("a").last().attr("href"); + pDateTime = rowHeader.last().text(); + } + pPost = rowHeader.select("div.post").first().html(); -public class LatestPostsFragment { + parsedTopicSummaries.add(new TopicSummary(pTopicUrl, pTopicTitle, "", pDateTime, pPost)); + } + } + return true; + } + } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java index a5776d10..be76d2b0 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java @@ -40,7 +40,8 @@ public class SummaryFragment extends Fragment { */ static final String PROFILE_DOCUMENT = "PROFILE_DOCUMENT"; /** - * {@link ArrayList} of Strings used to hold profile's information. Data are added in {@link ProfileActivity.ProfileTask}. + * {@link ArrayList} of Strings used to hold profile's information. Data are added in + * {@link SummaryFragment.ProfileSummaryTask}. */ private ArrayList parsedProfileSummaryData; /** diff --git a/app/src/main/java/gr/thmmy/mthmmy/data/TopicSummary.java b/app/src/main/java/gr/thmmy/mthmmy/data/TopicSummary.java index 935af6b1..41ea18b3 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/data/TopicSummary.java +++ b/app/src/main/java/gr/thmmy/mthmmy/data/TopicSummary.java @@ -5,6 +5,7 @@ public class TopicSummary { private final String title; private final String lastUser; private final String dateTimeModified; + private final String post; public TopicSummary(String topicUrl, String title, String lastUser, String dateTimeModified) { @@ -12,6 +13,16 @@ public class TopicSummary { this.title = title; this.lastUser = lastUser; this.dateTimeModified = dateTimeModified; + this.post = ""; + } + + public TopicSummary(String topicUrl, String title, String username, String dateTimeModified, + String post) { + this.topicUrl = topicUrl; + this.title = title; + this.lastUser = username; + this.dateTimeModified = dateTimeModified; + this.post = post; } public String getTopicUrl() { @@ -29,4 +40,6 @@ public class TopicSummary { public String getDateTimeModified() { return dateTimeModified; } + + public String getPost(){ return post;} } diff --git a/app/src/main/res/layout/profile_fragment_latest_posts.xml b/app/src/main/res/layout/profile_fragment_latest_posts.xml index fb3d8a25..9a7cc589 100644 --- a/app/src/main/res/layout/profile_fragment_latest_posts.xml +++ b/app/src/main/res/layout/profile_fragment_latest_posts.xml @@ -1,7 +1,26 @@ - + - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/profile_fragment_latest_posts_row.xml b/app/src/main/res/layout/profile_fragment_latest_posts_row.xml index fb3d8a25..bbc2c7e3 100644 --- a/app/src/main/res/layout/profile_fragment_latest_posts_row.xml +++ b/app/src/main/res/layout/profile_fragment_latest_posts_row.xml @@ -1,7 +1,44 @@ - + - \ No newline at end of file + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/recycler_loading_item.xml b/app/src/main/res/layout/recycler_loading_item.xml new file mode 100644 index 00000000..6585f174 --- /dev/null +++ b/app/src/main/res/layout/recycler_loading_item.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file