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 e48ac81a..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 @@ -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 String - * 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 String + * containing this user's profile url using the key {@link #BUNDLE_PROFILE_URL}, a String containing + * this user's avatar url and a String 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 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. - * - * Calling ProfileTask's {@link AsyncTask#execute execute} method needs to have profile's url - * as String parameter! + * 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 ProfileTask extends AsyncTask { //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}. - * Use this method only after parsing profile's data with {@link ProfileTask} as it - * reads from {@link #parsedProfileData} + * 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 mFragmentList = new ArrayList<>(); + private final List 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); } } } 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 new file mode 100644 index 00000000..401298c2 --- /dev/null +++ b/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 { + 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); + } + } +} \ 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 new file mode 100644 index 00000000..5cd81712 --- /dev/null +++ b/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 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. + * 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() { + 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 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(" embededVideosUrls.size()) + break; + pPost = pPost.replace( + pPost.substring(pPost.indexOf("") + 9) + , "" + + "" + + "" + + "" + //+ "" + + ""); + } + } + //Add stuff to make it work in WebView + //style.css + pPost = ("" + pPost); + + parsedTopicSummaries.add(new TopicSummary(pTopicUrl, pTopicTitle, "", pDateTime, pPost)); + } + } + return true; + } + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java new file mode 100644 index 00000000..b16139c4 --- /dev/null +++ b/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 { +} 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 new file mode 100644 index 00000000..be76d2b0 --- /dev/null +++ b/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 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. + * + * Calling ProfileSummaryTask's {@link AsyncTask#execute execute} method needs to have profile's url + * as String parameter! + */ + public class ProfileSummaryTask extends AsyncTask { + //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 parseProfileSummary(Document profile) { + //Method's variables + ArrayList 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 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(" embededVideosUrls.size()) + break; + pHtml = pHtml.replace( + pHtml.substring(pHtml.indexOf("") + 9) + , "" + + "" + + "" + + "" + //+ "" + + ""); + } + } + + //Add stuff to make it work in WebView + //style.css + pHtml = ("" + 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 = "" + row.select("td").first().text() + " " + + row.select("td").get(1).text(); + } + parsedInformation.add(pHtml); + } + return parsedInformation; + } + } + + /** + * Simple method that builds the UI of a {@link SummaryFragment}. + * Use this method only after parsing profile's data with + * {@link gr.thmmy.mthmmy.activities.profile.ProfileActivity.ProfileTask} as it reads from + * {@link #parsedProfileSummaryData} + */ + 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); + } + } +} 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 c3784812..4da904ab 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 @@ -306,7 +306,7 @@ public class TopicActivity extends BaseActivity { * 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 + * Calling TopicTask's {@link AsyncTask#execute execute} method needs to have profile's url * as String parameter! */ class TopicTask extends AsyncTask { 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 6229a0e5..c9f265d9 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 @@ -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 { 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); } 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-v21/activity_profile.xml b/app/src/main/res/layout-v21/activity_profile.xml index ddda7c59..7601b753 100644 --- a/app/src/main/res/layout-v21/activity_profile.xml +++ b/app/src/main/res/layout-v21/activity_profile.xml @@ -64,36 +64,29 @@ app:popupTheme="@style/ToolbarTheme"> + + - - - - - + app:layout_behavior="@string/appbar_scrolling_view_behavior"/> diff --git a/app/src/main/res/layout/activity_profile.xml b/app/src/main/res/layout/activity_profile.xml index 095558e5..47599ee8 100644 --- a/app/src/main/res/layout/activity_profile.xml +++ b/app/src/main/res/layout/activity_profile.xml @@ -63,36 +63,32 @@ app:popupTheme="@style/ToolbarTheme"> + + - - - - - + app:layout_behavior="@string/appbar_scrolling_view_behavior"/> diff --git a/app/src/main/res/layout/profile_fragment_latest_posts.xml b/app/src/main/res/layout/profile_fragment_latest_posts.xml new file mode 100644 index 00000000..9a7cc589 --- /dev/null +++ b/app/src/main/res/layout/profile_fragment_latest_posts.xml @@ -0,0 +1,26 @@ + + + + + + + + \ 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 new file mode 100644 index 00000000..5623df43 --- /dev/null +++ b/app/src/main/res/layout/profile_fragment_latest_posts_row.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/profile_fragment_stats.xml b/app/src/main/res/layout/profile_fragment_stats.xml new file mode 100644 index 00000000..fb3d8a25 --- /dev/null +++ b/app/src/main/res/layout/profile_fragment_stats.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/profile_fragment_summary.xml b/app/src/main/res/layout/profile_fragment_summary.xml new file mode 100644 index 00000000..7a5a14a1 --- /dev/null +++ b/app/src/main/res/layout/profile_fragment_summary.xml @@ -0,0 +1,19 @@ + + + + + + \ 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..cddf98b2 --- /dev/null +++ b/app/src/main/res/layout/recycler_loading_item.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file
- *
Calling ProfileTask's {@link AsyncTask#execute execute} method needs to have profile's url - * as String parameter!
ProfileTask's {@link AsyncTask#execute execute} method needs a profile's url as String + * parameter!
Use this method only after parsing profile's data with {@link ProfileTask} as it - * reads from {@link #parsedProfileData}
+ *
Calling ProfileSummaryTask's {@link AsyncTask#execute execute} method needs to have profile's url + * as String parameter!
Use this method only after parsing profile's data with + * {@link gr.thmmy.mthmmy.activities.profile.ProfileActivity.ProfileTask} as it reads from + * {@link #parsedProfileSummaryData}
Calling ProfileTask's {@link AsyncTask#execute execute} method needs to have profile's url + *
Calling TopicTask's {@link AsyncTask#execute execute} method needs to have profile's url * as String parameter!