mirror of https://github.com/ThmmyNoLife/mTHMMY
Apostolos Fanakis
8 years ago
8 changed files with 406 additions and 20 deletions
@ -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<RecyclerView.ViewHolder> { |
|||
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); |
|||
} |
|||
} |
|||
} |
@ -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<TopicSummary> 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. |
|||
* <p>ProfileTask's {@link AsyncTask#execute execute} method needs a profile's url as String |
|||
* parameter!</p> |
|||
*/ |
|||
public class ProfileLatestPostsTask extends AsyncTask<String, Void, Boolean> { |
|||
//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; |
|||
} |
|||
} |
|||
} |
|||
|
@ -1,7 +1,26 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:orientation="vertical" |
|||
<RelativeLayout |
|||
xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:app="http://schemas.android.com/apk/res-auto" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent"> |
|||
|
|||
</LinearLayout> |
|||
<android.support.v7.widget.RecyclerView |
|||
android:id="@+id/profile_latest_posts_recycler" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:background="@color/background" |
|||
android:scrollbars="none"> |
|||
</android.support.v7.widget.RecyclerView> |
|||
|
|||
<me.zhanghai.android.materialprogressbar.MaterialProgressBar |
|||
android:id="@+id/progressBar" |
|||
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal.NoPadding" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_alignParentTop="true" |
|||
android:indeterminate="true" |
|||
android:visibility="invisible" |
|||
app:mpb_indeterminateTint="@color/accent" |
|||
app:mpb_progressStyle="horizontal"/> |
|||
</RelativeLayout> |
@ -1,7 +1,44 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:orientation="vertical" |
|||
<RelativeLayout |
|||
xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent"> |
|||
android:layout_height="wrap_content" |
|||
android:background="@color/primary_light" |
|||
android:clickable="true" |
|||
android:foreground="?android:attr/selectableItemBackground" |
|||
android:paddingBottom="6dp" |
|||
android:paddingLeft="10dp" |
|||
android:paddingRight="10dp" |
|||
android:paddingTop="6dp"> |
|||
|
|||
</LinearLayout> |
|||
<TextView |
|||
android:id="@+id/title" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_alignParentStart="true" |
|||
android:layout_alignParentTop="true" |
|||
android:textAppearance="?attr/textAppearanceListItem" |
|||
android:textColor="@color/primary_text"/> |
|||
|
|||
<TextView |
|||
android:id="@+id/date" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_alignParentStart="true" |
|||
android:layout_below="@+id/title" |
|||
android:textColor="@color/secondary_text"/> |
|||
|
|||
<FrameLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:layout_alignParentStart="true" |
|||
android:layout_below="@+id/date"> |
|||
|
|||
<WebView |
|||
android:id="@+id/post" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:background="@color/card_background" |
|||
android:text="@string/post"/> |
|||
</FrameLayout> |
|||
</RelativeLayout> |
@ -0,0 +1,13 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<LinearLayout |
|||
xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:orientation="vertical"> |
|||
|
|||
<ProgressBar |
|||
android:id="@+id/progress_bar" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="center_horizontal"/> |
|||
</LinearLayout> |
Loading…
Reference in new issue