Browse Source

Merge branch 'wipProfileActivityTabs' into develop

# Conflicts:
#	app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileParser.java
pull/24/head
Apostolos Fanakis 8 years ago
parent
commit
6a7364187b
  1. 203
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java
  2. 96
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java
  3. 272
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java
  4. 8
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java
  5. 231
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java
  6. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java
  7. 15
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
  8. 13
      app/src/main/java/gr/thmmy/mthmmy/data/TopicSummary.java
  9. 27
      app/src/main/res/layout-v21/activity_profile.xml
  10. 30
      app/src/main/res/layout/activity_profile.xml
  11. 26
      app/src/main/res/layout/profile_fragment_latest_posts.xml
  12. 54
      app/src/main/res/layout/profile_fragment_latest_posts_row.xml
  13. 7
      app/src/main/res/layout/profile_fragment_stats.xml
  14. 19
      app/src/main/res/layout/profile_fragment_summary.xml
  15. 17
      app/src/main/res/layout/recycler_loading_item.xml

203
app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java

@ -2,20 +2,19 @@ package gr.thmmy.mthmmy.activities.profile;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.text.Html;
import android.util.Log;
import android.view.View;
import android.webkit.WebView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
@ -24,64 +23,68 @@ import com.squareup.picasso.Picasso;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import java.util.ArrayList;
import java.util.List;
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;
import mthmmy.utils.Report;
import okhttp3.Request;
import okhttp3.Response;
import static gr.thmmy.mthmmy.activities.profile.ProfileParser.PERSONAL_TEXT_INDEX;
import static gr.thmmy.mthmmy.activities.profile.ProfileParser.THUMBNAIL_URL_INDEX;
import static gr.thmmy.mthmmy.activities.profile.ProfileParser.USERNAME_INDEX;
import static gr.thmmy.mthmmy.activities.profile.ProfileParser.parseProfileSummary;
import static gr.thmmy.mthmmy.session.SessionManager.LOGGED_IN;
/**
* Activity for user's profile. When creating an Intent of this activity you need to bundle a <b>String</b>
* containing this user's profile url using the key {@link #EXTRAS_PROFILE_URL}.
* Activity for user profile. When creating an Intent of this activity you need to bundle a <b>String</b>
* containing this user's profile url using the key {@link #BUNDLE_PROFILE_URL}, a <b>String</b> containing
* this user's avatar url and a <b>String</b> containing the username.
*/
public class ProfileActivity extends BaseActivity {
//Graphic element variables
private ImageView userThumbnail;
private TextView userName;
private TextView personalText;
private LinearLayout mainContent;
//Graphics
private TextView personalTextView;
private MaterialProgressBar progressBar;
private FloatingActionButton replyFAB;
//Other variables
private ViewPager viewPager;
//Other variables and constants
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "ProfileActivity";
static String PACKAGE_NAME;
/**
* The key to use when putting profile's url String to {@link ProfileActivity}'s Bundle.
*/
public static final String EXTRAS_PROFILE_URL = "PROFILE_URL";
public static final String BUNDLE_PROFILE_URL = "PROFILE_URL";
/**
* {@link ArrayList} of Strings used to hold profile's information. Data are added in {@link ProfileTask}.
* The key to use when putting user's thumbnail url String to {@link ProfileActivity}'s Bundle.
* If user doesn't have a thumbnail put an empty string.
*/
private ArrayList<String> parsedProfileData;
private ProfileTask profileTask;
public static final String BUNDLE_THUMBNAIL_URL = "THUMBNAIL_URL";
/**
* The key to use when putting username String to {@link ProfileActivity}'s Bundle.
*/
public static final String BUNDLE_USERNAME = "USERNAME";
private static final int THUMBNAIL_SIZE = 200;
private ProfileTask profileTask;
private String personalText;
private String profileUrl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_profile);
PACKAGE_NAME = getApplicationContext().getPackageName();
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);
@ -96,10 +99,24 @@ public class ProfileActivity extends BaseActivity {
progressBar = (MaterialProgressBar) findViewById(R.id.progressBar);
userThumbnail = (ImageView) findViewById(R.id.user_thumbnail);
userName = (TextView) findViewById(R.id.profile_act_username);
personalText = (TextView) findViewById(R.id.profile_activity_personal_text);
mainContent = (LinearLayout) findViewById(R.id.profile_activity_content);
ImageView thumbnailView = (ImageView) findViewById(R.id.user_thumbnail);
if (thumbnailUrl != null)
//noinspection ConstantConditions
Picasso.with(this)
.load(thumbnailUrl)
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(this.getResources()
, R.drawable.ic_default_user_thumbnail, null))
.placeholder(ResourcesCompat.getDrawable(this.getResources()
, R.drawable.ic_default_user_thumbnail, null))
.transform(new CircleTransform())
.into(thumbnailView);
TextView usernameView = (TextView) findViewById(R.id.profile_activity_username);
usernameView.setText(username);
personalTextView = (TextView) findViewById(R.id.profile_activity_personal_text);
viewPager = (ViewPager) findViewById(R.id.profile_tab_container);
replyFAB = (FloatingActionButton) findViewById(R.id.profile_fab); //TODO hide fab while logged out
replyFAB.setEnabled(false);
@ -131,10 +148,8 @@ public class ProfileActivity extends BaseActivity {
}
});
//Gets info
parsedProfileData = new ArrayList<>();
profileTask = new ProfileTask();
profileTask.execute(extras.getString(EXTRAS_PROFILE_URL)); //Attempt data parsing
profileTask.execute(profileUrl); //Attempt data parsing
}
@Override
@ -145,19 +160,19 @@ public class ProfileActivity extends BaseActivity {
}
/**
* An {@link AsyncTask} that handles asynchronous fetching of a profile page and parsing it's
* data. {@link AsyncTask#onPostExecute(Object) OnPostExecute} method calls {@link #populateLayout()}
* to build graphics.
* <p>
* <p>Calling ProfileTask's {@link AsyncTask#execute execute} method needs to have profile's url
* as String parameter!</p>
* 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 ProfileTask extends AsyncTask<String, Void, Boolean> {
//Class variables
/**
* Debug Tag for logging debug output to LogCat
*/
private static final String TAG = "TopicTask"; //Separate tag for AsyncTask
@SuppressWarnings("unused")
private static final String TAG = "ProfileTask"; //Separate tag for AsyncTask
Document profilePage;
protected void onPreExecute() {
progressBar.setVisibility(ProgressBar.VISIBLE);
@ -165,7 +180,6 @@ public class ProfileActivity extends BaseActivity {
}
protected Boolean doInBackground(String... profileUrl) {
Document document;
String pageUrl = profileUrl[0] + ";wap"; //Profile's page wap url
Request request = new Request.Builder()
@ -173,10 +187,18 @@ public class ProfileActivity extends BaseActivity {
.build();
try {
Response response = client.newCall(request).execute();
document = Jsoup.parse(response.body().string());
//long parseStartTime = System.nanoTime();
parsedProfileData = parseProfileSummary(document);
//long parseEndTime = System.nanoTime();
profilePage = Jsoup.parse(response.body().string());
{ //Finds personal text
Element tmpEl = profilePage.select("td.windowbg:nth-child(2)").first();
if (tmpEl != null) {
personalText = tmpEl.text().trim();
} else {
//Should never get here!
//Something is wrong.
Report.e(TAG, "An error occurred while trying to find profile's personal text.");
personalText = null;
}
}
return true;
} catch (SSLHandshakeException e) {
Report.w(TAG, "Certificate problem (please switch to unsafe connection).");
@ -194,62 +216,55 @@ public class ProfileActivity extends BaseActivity {
}
//Parse was successful
progressBar.setVisibility(ProgressBar.INVISIBLE);
populateLayout();
if (personalText != null) {
personalTextView.setVisibility(View.VISIBLE);
personalTextView.setText(personalText);
} else {
personalTextView.setVisibility(View.GONE);
}
setupViewPager(viewPager, profilePage);
TabLayout tabLayout = (TabLayout) findViewById(R.id.profile_tabs);
tabLayout.setupWithViewPager(viewPager);
}
}
/**
* Simple method that builds the UI of a {@link ProfileActivity}.
* <p>Use this method <b>only after</b> parsing profile's data with {@link ProfileTask} as it
* reads from {@link #parsedProfileData}</p>
* Simple method that sets up the {@link ViewPager} of a {@link ProfileActivity}
* @param viewPager the ViewPager to be setup
* @param profilePage this profile's parsed page
*/
private void populateLayout() {
if (parsedProfileData.get(THUMBNAIL_URL_INDEX) != null)
//noinspection ConstantConditions
Picasso.with(this)
.load(parsedProfileData.get(THUMBNAIL_URL_INDEX))
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(this.getResources()
, R.drawable.ic_default_user_thumbnail, null))
.placeholder(ResourcesCompat.getDrawable(this.getResources()
, R.drawable.ic_default_user_thumbnail, null))
.transform(new CircleTransform())
.into(userThumbnail);
private void setupViewPager(ViewPager viewPager, Document profilePage) {
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFrag(SummaryFragment.newInstance(profilePage), "SUMMARY");
adapter.addFrag(LatestPostsFragment.newInstance(profileUrl), "LATEST POSTS");
//adapter.addFrag(new );
viewPager.setAdapter(adapter);
}
userName.setText(parsedProfileData.get(USERNAME_INDEX));
class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
if (parsedProfileData.get(PERSONAL_TEXT_INDEX) != null) {
personalText.setVisibility(View.VISIBLE);
personalText.setText(parsedProfileData.get(PERSONAL_TEXT_INDEX));
} else {
personalText.setVisibility(View.GONE);
ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
for (int i = PERSONAL_TEXT_INDEX + 1; i < parsedProfileData.size(); ++i) {
if (parsedProfileData.get(i).contains("Signature")
|| parsedProfileData.get(i).contains("Υπογραφή")) {
WebView signatureEntry = new WebView(this);
signatureEntry.loadDataWithBaseURL("file:///android_asset/", parsedProfileData.get(i), "text/html", "UTF-8", null);
}
TextView entry = new TextView(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
entry.setTextColor(getResources().getColor(R.color.primary_text, null));
} else {
//noinspection deprecation
entry.setTextColor(getResources().getColor(R.color.primary_text));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
entry.setText(Html.fromHtml(parsedProfileData.get(i), Html.FROM_HTML_MODE_LEGACY));
} else {
//noinspection deprecation
entry.setText(Html.fromHtml(parsedProfileData.get(i)));
}
mainContent.addView(entry);
Log.d(TAG, "new: " + parsedProfileData.get(i));
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList.size();
}
void addFrag(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
@Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
}

96
app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java

@ -0,0 +1,96 @@
package gr.thmmy.mthmmy.activities.profile.latestPosts;
import android.annotation.SuppressLint;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import android.widget.TextView;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.data.TopicSummary;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import static gr.thmmy.mthmmy.activities.profile.latestPosts.LatestPostsFragment.parsedTopicSummaries;
class LatestPostsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String TAG = "LatestPostsAdapter";
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) {
Log.d(TAG, "GOT");
View view = LayoutInflater.from(parent.getContext()).
inflate(R.layout.recycler_loading_item, parent, false);
return new LoadingViewHolder(view);
}
return null;
}
@SuppressLint("SetJavaScriptEnabled")
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof LatestPostViewHolder) {
TopicSummary topic = parsedTopicSummaries.get(position);
final LatestPostViewHolder latestPostViewHolder = (LatestPostViewHolder) holder;
latestPostViewHolder.postTitle.setText(topic.getTitle());
latestPostViewHolder.postDate.setText(topic.getDateTimeModified());
latestPostViewHolder.post.loadDataWithBaseURL("file:///android_asset/"
, topic.getPost(), "text/html", "UTF-8", null);
} 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 {
MaterialProgressBar progressBar;
LoadingViewHolder(View itemView) {
super(itemView);
progressBar = (MaterialProgressBar) itemView.findViewById(R.id.recycler_progress_bar);
}
}
}

272
app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java

@ -0,0 +1,272 @@
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.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
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;
/**
* 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 LatestPostsAdapter latestPostsAdapter = new LatestPostsAdapter();
private int numberOfPages = -1;
private int pagesLoaded = 0;
private String profileUrl;
private ProfileLatestPostsTask profileLatestPostsTask;
private MaterialProgressBar progressBar;
private boolean isLoadingMore;
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 View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.profile_fragment_latest_posts, container, false);
RecyclerView mainContent = (RecyclerView) rootView.findViewById(R.id.profile_latest_posts_recycler);
mainContent.setAdapter(latestPostsAdapter);
final LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
mainContent.setLayoutManager(layoutManager);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mainContent.getContext(),
layoutManager.getOrientation());
mainContent.addItemDecoration(dividerItemDecoration);
final LatestPostsAdapter.OnLoadMoreListener onLoadMoreListener = new LatestPostsAdapter.OnLoadMoreListener() {
@Override
public void onLoadMore() {
if (pagesLoaded < numberOfPages) {
parsedTopicSummaries.add(null);
latestPostsAdapter.notifyItemInserted(parsedTopicSummaries.size() - 1);
//Load data
profileLatestPostsTask = new ProfileLatestPostsTask();
profileLatestPostsTask.execute(profileUrl + ";sa=showPosts;start=" + pagesLoaded * 15);
++pagesLoaded;
}
}
};
latestPostsAdapter.setOnLoadMoreListener(onLoadMoreListener);
mainContent.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
totalItemCount = layoutManager.getItemCount();
lastVisibleItem = layoutManager.findLastVisibleItemPosition();
if (!isLoadingMore && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
isLoadingMore = true;
onLoadMoreListener.onLoadMore();
}
}
});
progressBar = (MaterialProgressBar) rootView.findViewById(R.id.progressBar);
return rootView;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (parsedTopicSummaries.isEmpty()) {
profileLatestPostsTask = new ProfileLatestPostsTask();
profileLatestPostsTask.execute(profileUrl + ";sa=showPosts");
pagesLoaded = 1;
}
Report.d(TAG, "onActivityCreated");
}
@Override
public void onDestroy() {
super.onDestroy();
if (profileLatestPostsTask != null && profileLatestPostsTask.getStatus() != AsyncTask.Status.RUNNING)
profileLatestPostsTask.cancel(true);
}
/**
* 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() {
if (!isLoadingMore) {
Log.d(TAG, "false");
progressBar.setVisibility(ProgressBar.VISIBLE);
}
}
protected Boolean doInBackground(String... profileUrl) {
String pageUrl = profileUrl[0];
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();
isLoadingMore = 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");
//Removes loading item
if (isLoadingMore) {
parsedTopicSummaries.remove(parsedTopicSummaries.size() - 1);
}
for (Element row : latestPostsRows) {
String pTopicUrl, pTopicTitle, pDateTime, pPost;
if (Integer.parseInt(row.attr("cellpadding")) == 4) {
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 = row.select("div.post").first().outerHtml();
{ //Fixes embedded videos
Elements noembedTag = row.select("div").select(".post").first().select("noembed");
ArrayList<String> embededVideosUrls = new ArrayList<>();
for (Element _noembed : noembedTag) {
embededVideosUrls.add(_noembed.text().substring(_noembed.text()
.indexOf("href=\"https://www.youtube.com/watch?") + 38
, _noembed.text().indexOf("target") - 2));
}
int tmp_counter = 0;
while (pPost.contains("<embed")) {
if (tmp_counter > embededVideosUrls.size())
break;
pPost = pPost.replace(
pPost.substring(pPost.indexOf("<embed"), pPost.indexOf("/noembed>") + 9)
, "<div class=\"embedded-video\">"
+ "<a href=\"https://www.youtube.com/"
+ embededVideosUrls.get(tmp_counter) + "\" target=\"_blank\">"
+ "<img src=\"https://img.youtube.com/vi/"
+ embededVideosUrls.get(tmp_counter) + "/default.jpg\" alt=\"\" border=\"0\">"
+ "</a>"
//+ "<img class=\"embedded-video-play\" src=\"http://www.youtube.com/yt/brand/media/image/YouTube_light_color_icon.png\">"
+ "</div>");
}
}
//Add stuff to make it work in WebView
//style.css
pPost = ("<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />" + pPost);
parsedTopicSummaries.add(new TopicSummary(pTopicUrl, pTopicTitle, "", pDateTime, pPost));
}
}
return true;
}
}
}

8
app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java

@ -0,0 +1,8 @@
package gr.thmmy.mthmmy.activities.profile.stats;
/**
* Created by apostolof on 1/1/17.
*/
public class StatsFragment {
}

231
app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java

@ -0,0 +1,231 @@
package gr.thmmy.mthmmy.activities.profile.summary;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.Html;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.LinearLayout;
import android.widget.TextView;
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.Objects;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import mthmmy.utils.Report;
/**
* Use the {@link SummaryFragment#newInstance} factory method to create an instance of this fragment.
*/
public class SummaryFragment extends Fragment {
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "SummaryFragment";
/**
* The key to use when putting profile's source code String to {@link SummaryFragment}'s Bundle.
*/
static final String PROFILE_DOCUMENT = "PROFILE_DOCUMENT";
/**
* {@link ArrayList} of Strings used to hold profile's information. Data are added in
* {@link SummaryFragment.ProfileSummaryTask}.
*/
private ArrayList<String> parsedProfileSummaryData;
/**
* A {@link Document} holding this profile's source code.
*/
private Document profileSummaryDocument;
private ProfileSummaryTask profileSummaryTask;
private LinearLayout mainContent;
public SummaryFragment() {
// Required empty public constructor
}
/**
* Use ONLY this factory method to create a new instance of this fragment using the provided
* parameters.
*
* @param profileSummaryDocument a {@link Document} containing this profile's parsed page
* @return A new instance of fragment Summary.
*/
public static SummaryFragment newInstance(Document profileSummaryDocument) {
SummaryFragment fragment = new SummaryFragment();
Bundle args = new Bundle();
args.putString(PROFILE_DOCUMENT, profileSummaryDocument.toString());
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
profileSummaryDocument = Jsoup.parse(getArguments().getString(PROFILE_DOCUMENT));
parsedProfileSummaryData = new ArrayList<>();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (parsedProfileSummaryData.isEmpty()) {
profileSummaryTask = new ProfileSummaryTask();
profileSummaryTask.execute(profileSummaryDocument);
}
Report.d(TAG, "onActivityCreated");
}
@Override
public void onDestroy() {
super.onDestroy();
if (profileSummaryTask != null && profileSummaryTask.getStatus() != AsyncTask.Status.RUNNING)
profileSummaryTask.cancel(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.profile_fragment_summary, container, false);
mainContent = (LinearLayout) rootView.findViewById(R.id.profile_activity_content);
return rootView;
}
/**
* An {@link AsyncTask} that handles asynchronous parsing of a profile page's data.
* {@link AsyncTask#onPostExecute(Object) OnPostExecute} method calls {@link #populateLayout()}
* to build graphics.
* <p>
* <p>Calling ProfileSummaryTask's {@link AsyncTask#execute execute} method needs to have profile's url
* as String parameter!</p>
*/
public class ProfileSummaryTask extends AsyncTask<Document, Void, Void> {
//Class variables
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "TopicTask"; //Separate tag for AsyncTask
protected Void doInBackground(Document... profileSummaryPage) {
parsedProfileSummaryData = parseProfileSummary(profileSummaryPage[0]);
return null;
}
protected void onPostExecute(Void result) {
populateLayout();
}
/**
* This method is used to parse all available information in a user profile.
*
* @param profile {@link Document} object containing this profile's source code
* @return ArrayList containing this profile's parsed information
* @see org.jsoup.Jsoup Jsoup
*/
ArrayList<String> parseProfileSummary(Document profile) {
//Method's variables
ArrayList<String> parsedInformation = new ArrayList<>();
//Contains all summary's rows
Elements summaryRows = profile.select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tr");
for (Element row : summaryRows) {
String rowText = row.text(), pHtml = "";
//Horizontal rule rows
if (row.select("td").size() == 1)
pHtml = "";
else if (rowText.contains("Signature") || rowText.contains("Υπογραφή")) {
//This needs special handling since it may have css
{ //Fix embedded videos
Elements noembedTag = row.select("noembed");
ArrayList<String> embededVideosUrls = new ArrayList<>();
for (Element _noembed : noembedTag) {
embededVideosUrls.add(_noembed.text().substring(_noembed.text()
.indexOf("href=\"https://www.youtube.com/watch?") + 38
, _noembed.text().indexOf("target") - 2));
}
pHtml = row.html();
int tmp_counter = 0;
while (pHtml.contains("<embed")) {
if (tmp_counter > embededVideosUrls.size())
break;
pHtml = pHtml.replace(
pHtml.substring(pHtml.indexOf("<embed"), pHtml.indexOf("/noembed>") + 9)
, "<div class=\"embedded-video\">"
+ "<a href=\"https://www.youtube.com/"
+ embededVideosUrls.get(tmp_counter) + "\" target=\"_blank\">"
+ "<img src=\"https://img.youtube.com/vi/"
+ embededVideosUrls.get(tmp_counter) + "/default.jpg\" alt=\"\" border=\"0\">"
+ "</a>"
//+ "<img class=\"embedded-video-play\" src=\"http://www.youtube.com/yt/brand/media/image/YouTube_light_color_icon.png\">"
+ "</div>");
}
}
//Add stuff to make it work in WebView
//style.css
pHtml = ("<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />" + pHtml);
} else if (!rowText.contains("Name") && !rowText.contains("Όνομα")) { //Don't add username twice
if (Objects.equals(row.select("td").get(1).text(), ""))
continue;
//Style parsed information with html
pHtml = "<b>" + row.select("td").first().text() + "</b> "
+ row.select("td").get(1).text();
}
parsedInformation.add(pHtml);
}
return parsedInformation;
}
}
/**
* Simple method that builds the UI of a {@link SummaryFragment}.
* <p>Use this method <b>only after</b> parsing profile's data with
* {@link gr.thmmy.mthmmy.activities.profile.ProfileActivity.ProfileTask} as it reads from
* {@link #parsedProfileSummaryData}</p>
*/
private void populateLayout() {
for (String profileSummaryRow : parsedProfileSummaryData) {
if (profileSummaryRow.contains("Signature")
|| profileSummaryRow.contains("Υπογραφή")) {
WebView signatureEntry = new WebView(this.getContext());
signatureEntry.loadDataWithBaseURL("file:///android_asset/", profileSummaryRow,
"text/html", "UTF-8", null);
}
TextView entry = new TextView(this.getContext());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
entry.setTextColor(getResources().getColor(R.color.primary_text, null));
} else {
//noinspection deprecation
entry.setTextColor(getResources().getColor(R.color.primary_text));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
entry.setText(Html.fromHtml(profileSummaryRow, Html.FROM_HTML_MODE_LEGACY));
} else {
//noinspection deprecation
entry.setText(Html.fromHtml(profileSummaryRow));
}
mainContent.addView(entry);
Log.d(TAG, "new: " + profileSummaryRow);
}
}
}

2
app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java

@ -306,7 +306,7 @@ public class TopicActivity extends BaseActivity {
* data. {@link AsyncTask#onPostExecute(Object) OnPostExecute} method calls {@link RecyclerView#swapAdapter}
* to build graphics.
* <p>
* <p>Calling ProfileTask's {@link AsyncTask#execute execute} method needs to have profile's url
* <p>Calling TopicTask's {@link AsyncTask#execute execute} method needs to have profile's url
* as String parameter!</p>
*/
class TopicTask extends AsyncTask<String, Void, Integer> {

15
app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java

@ -50,7 +50,9 @@ import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
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.profile.ProfileActivity.BUNDLE_PROFILE_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_THUMBNAIL_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_USERNAME;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.base_url;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.toQuoteList;
@ -309,9 +311,14 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
if (viewProperties.get(holder.getAdapterPosition())[isUserExtraInfoVisibile]) {
Intent intent = new Intent(context, ProfileActivity.class);
Bundle b = new Bundle();
b.putString(EXTRAS_PROFILE_URL, currentPost.getProfileURL());
intent.putExtras(b);
Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, currentPost.getProfileURL());
if(currentPost.getThumbnailUrl() == null)
extras.putString(BUNDLE_THUMBNAIL_URL, "");
else
extras.putString(BUNDLE_THUMBNAIL_URL, currentPost.getThumbnailUrl());
extras.putString(BUNDLE_USERNAME, currentPost.getAuthor());
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}

13
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;}
}

27
app/src/main/res/layout-v21/activity_profile.xml

@ -64,36 +64,29 @@
app:popupTheme="@style/ToolbarTheme">
<TextView
android:id="@+id/profile_act_username"
android:id="@+id/profile_activity_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="@color/accent"
android:textSize="25sp"/>
</android.support.v7.widget.Toolbar>
<android.support.design.widget.TabLayout
android:id="@+id/profile_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="@+id/nested_scroll"
<android.support.v4.view.ViewPager
android:id="@+id/profile_tab_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top|start"
android:background="@color/background"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:scrollbars="none"
app:layout_anchor="@id/appbar"
app:layout_anchorGravity="bottom|center"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:id="@+id/profile_activity_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/progressBar"
@ -102,7 +95,7 @@
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="invisible"
app:layout_anchor="@id/nested_scroll"
app:layout_anchor="@id/profile_tab_container"
app:layout_anchorGravity="top|center"
app:mpb_indeterminateTint="@color/accent"
app:mpb_progressStyle="horizontal"/>

30
app/src/main/res/layout/activity_profile.xml

@ -63,36 +63,32 @@
app:popupTheme="@style/ToolbarTheme">
<TextView
android:id="@+id/profile_act_username"
android:id="@+id/profile_activity_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="@color/accent"
android:textSize="25sp"/>
</android.support.v7.widget.Toolbar>
<android.support.design.widget.TabLayout
android:id="@+id/profile_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="fixed"
app:tabGravity="fill"
android:gravity="center"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="@+id/nested_scroll"
<android.support.v4.view.ViewPager
android:id="@+id/profile_tab_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top|start"
android:background="@color/background"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:scrollbars="none"
app:layout_anchor="@id/appbar"
app:layout_anchorGravity="bottom|center"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:id="@+id/profile_activity_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/progressBar"
@ -101,7 +97,7 @@
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="invisible"
app:layout_anchor="@id/nested_scroll"
app:layout_anchor="@id/profile_tab_container"
app:layout_anchorGravity="top|center"
app:mpb_indeterminateTint="@color/accent"
app:mpb_progressStyle="horizontal"/>

26
app/src/main/res/layout/profile_fragment_latest_posts.xml

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<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">
<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>

54
app/src/main/res/layout/profile_fragment_latest_posts_row.xml

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/card_background"
android:clickable="true"
android:foreground="?android:attr/selectableItemBackground"
android:paddingBottom="6dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="6dp">
<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"/>
<View
android:id="@+id/spacer_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@+id/date"
android:layout_marginBottom="3dp"
android:layout_marginTop="3dp"
android:background="@color/divider"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_below="@+id/spacer_divider">
<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>

7
app/src/main/res/layout/profile_fragment_stats.xml

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>

19
app/src/main/res/layout/profile_fragment_summary.xml

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/nested_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:scrollbars="none">
<LinearLayout
android:id="@+id/profile_activity_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
</LinearLayout>
</android.support.v4.widget.NestedScrollView>

17
app/src/main/res/layout/recycler_loading_item.xml

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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="wrap_content"
android:orientation="vertical">
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/recycler_progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:indeterminate="true"
app:mpb_indeterminateTint="@color/accent"/>
</LinearLayout>
Loading…
Cancel
Save