diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d02a2bf6..cd22f77e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,7 @@ image: openjdk:8-jdk variables: ANDROID_TARGET_SDK: "25" - ANDROID_BUILD_TOOLS: "25.0.1" + ANDROID_BUILD_TOOLS: "25.0.2" ANDROID_SDK_TOOLS: "25.2.5" before_script: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e28d7593..44be1c74 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,4 +45,3 @@ follow the workflow below to make a merge request: [trello-board]: https://trello.com/invite/b/4MVlkrkg/44a931707bd0b84a5e0bdfc42b9ae4f1/mthmmy [discord-server]: https://discord.gg/CVt3yrn -[gitlab-contributing-guide]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md diff --git a/README.md b/README.md index 69c60e28..89f8a134 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ [![build status](https://gitlab.com/ThmmyNoLife/mTHMMY/badges/develop/build.svg)](https://gitlab.com/ThmmyNoLife/mTHMMY/commits/develop) [![API](https://img.shields.io/badge/API-19%2B-blue.svg?style=flat)](https://android-arsenal.com/api?level=19) -[![Discord Channel](https://img.shields.io/badge/discord-public@mTHMMY-738bd7.svg?style=flat)](https://discord.gg/CVt3yrn) +[![Discord Channel](https://img.shields.io/badge/discord-public@mTHMMY-738bd7.svg?style=flat)][discord-server] +[![Trello Board](https://img.shields.io/badge/trello-mTHMMY-red.svg?style=flat)][trello-board] -mTHMMY is a mobile app for thmmy.gr + +mTHMMY is a mobile app for the [thmmy.gr](https://www.thmmy.gr) community. ## Requirements @@ -16,4 +18,7 @@ Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for details. ## Contact -Do not hesitate to contact us for any matter at `thmmynolife@gmail.com`. +Do not hesitate to contact us for any matter, either by sending an email to `thmmynolife@gmail.com`, or by joining our [Discord server][discord-server]. + +[discord-server]: https://discord.gg/CVt3yrn +[trello-board]: https://trello.com/invite/b/4MVlkrkg/44a931707bd0b84a5e0bdfc42b9ae4f1/mthmmy diff --git a/VERSION b/VERSION index 45a1b3f4..26aaba0e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.2 +1.2.0 diff --git a/app/build.gradle b/app/build.gradle index bbf5b90d..f1403f49 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,15 +2,15 @@ apply plugin: 'com.android.application' android { compileSdkVersion 25 - buildToolsVersion "25.0.1" + buildToolsVersion "25.0.2" defaultConfig { vectorDrawables.useSupportLibrary = true applicationId "gr.thmmy.mthmmy" minSdkVersion 19 targetSdkVersion 25 - versionCode 5 - versionName "1.1.2" + versionCode 6 + versionName "1.2.0" archivesBaseName = "mTHMMY-v$versionName" } @@ -19,34 +19,35 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } - debug { - def date = new Date().format('ddMMyy_HHmm'); + /*debug { + def date = new Date().format('ddMMyy_HH'); archivesBaseName = archivesBaseName + "-$date" - } + }*/ } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:25.1.0' - compile 'com.android.support:design:25.1.0' - compile 'com.android.support:support-v4:25.1.0' - compile 'com.android.support:cardview-v7:25.1.0' - compile 'com.android.support:recyclerview-v7:25.1.0' - compile 'com.google.firebase:firebase-crash:10.0.1' - compile 'com.squareup.okhttp3:okhttp:3.5.0' + compile 'com.android.support:appcompat-v7:25.3.0' + compile 'com.android.support:design:25.3.0' + compile 'com.android.support:support-v4:25.3.0' + compile 'com.android.support:cardview-v7:25.3.0' + compile 'com.android.support:recyclerview-v7:25.3.0' + compile 'com.google.firebase:firebase-crash:10.2.0' + compile 'com.squareup.okhttp3:okhttp:3.6.0' compile 'com.squareup.picasso:picasso:2.5.2' compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0' compile 'org.jsoup:jsoup:1.10.2' compile 'com.github.franmontiel:PersistentCookieJar:v1.0.0' compile 'com.github.PhilJay:MPAndroidChart:v3.0.1' - compile('com.mikepenz:materialdrawer:5.8.1@aar') { + compile('com.mikepenz:materialdrawer:5.8.2@aar') { transitive = true } compile 'com.mikepenz:fontawesome-typeface:4.7.0.0@aar' - compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.3' + compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.5' compile 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1' compile 'me.zhanghai.android.materialprogressbar:library:1.3.0' + compile 'com.jakewharton.timber:timber:4.5.1' } apply plugin: 'com.google.gms.google-services' diff --git a/app/src/debug/java/mthmmy/utils/Report.java b/app/src/debug/java/mthmmy/utils/Report.java deleted file mode 100644 index 6e6a906f..00000000 --- a/app/src/debug/java/mthmmy/utils/Report.java +++ /dev/null @@ -1,81 +0,0 @@ -package mthmmy.utils; - -import android.util.Log; - -public class Report -{ - - public static void v (String TAG, String message) - { - Log.v(TAG,message); - } - - public static void v (String TAG, String message, Throwable tr) - { - Log.v(TAG,message + ": " + tr.getMessage(),tr); - } - - public static void d (String TAG, String message) - { - Log.d(TAG,message); - } - - public static void d (String TAG, String message, Throwable tr) - { - Log.d(TAG,message + ": " + tr.getMessage(),tr); - } - - public static void i (String TAG, String message) - { - Log.i(TAG,message); - } - - public static void i (String TAG, String message, Throwable tr) - { - Log.i(TAG,message + ": " + tr.getMessage(),tr); - } - - public static void w (String TAG, String message) - { - Log.w(TAG,message); - } - - public static void w (String TAG, String message, Throwable tr) - { - Log.w(TAG,message + ": " + tr.getMessage(),tr); - } - - public static void e (String TAG, String message) - { - Log.e(TAG,message); - } - - public static void e (String TAG, String message, Throwable tr) - { - Log.e(TAG,message + ": " + tr.getMessage(),tr); - } - - public static void wtf (String TAG, String message) - { - Log.wtf(TAG,message); - } - - public static void wtf (String TAG, String message, Throwable tr) - { - Log.wtf(TAG,message + ": " + tr.getMessage(),tr); - } - - /** - * Prints long messages in logcat (debug level). - */ - public static void longMessage(String TAG, String message) - { - int maxLogSize = 1000; - for(int i = 0; i <= message.length() / maxLogSize; i++) { - int start = i * maxLogSize; - int end = (i+1) * maxLogSize; - end = end > message.length() ? message.length() : end; - Report.d(TAG, message.substring(start, end)); - } - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f95392f4..8a57fcbc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + package="gr.thmmy.mthmmy" + android:installLocation="auto"> diff --git a/app/src/main/assets/apache_libraries.html b/app/src/main/assets/apache_libraries.html index 10867db3..2c0ceabc 100644 --- a/app/src/main/assets/apache_libraries.html +++ b/app/src/main/assets/apache_libraries.html @@ -39,7 +39,7 @@
  • -
    OkHttp v3.5.0 (Copyright ©2016 Square, Inc.)
    +
    OkHttp v3.6.0 (Copyright ©2016 Square, Inc.)
  • Picasso v2.5.2 (Copyright ©2013 Square, Inc.)
    @@ -51,7 +51,7 @@
    MPAndroidChart v3.0.1 (Copyright ©2016 Philipp Jahoda)
  • -
    MaterialDrawer v5.8.1 (Copyright ©2016 Mike Penz)
    +
    MaterialDrawer v5.8.2 (Copyright ©2016 Mike Penz)
  • Fontawesome Typeface Library v4.7.0.0 (Copyright ©2016 Mike Penz)
    diff --git a/app/src/main/assets/mit_libraries.html b/app/src/main/assets/mit_libraries.html index 6d72c330..2c6cf3bb 100644 --- a/app/src/main/assets/mit_libraries.html +++ b/app/src/main/assets/mit_libraries.html @@ -42,7 +42,7 @@
    jsoup v1.10.2 (Copyright ©2009-2017, Jonathan Hedley <jonathan@hedley.net>)
  • -
    android-gif-drawable v1.2.3 (Copyright ©2016 Karol Wrótniak, Droids on Roids)
    +
    android-gif-drawable v1.2.5 (Copyright ©2016 Karol Wrótniak, Droids on Roids)
  • Expandable RecyclerView v3.0.0-RC1 (Copyright ©2015, Big Nerd Ranch)
    diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/BookmarkActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/BookmarkActivity.java index 5d854eef..ec0c2b25 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/BookmarkActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/BookmarkActivity.java @@ -114,7 +114,7 @@ public class BookmarkActivity extends BaseActivity { Intent intent = new Intent(BookmarkActivity.this, TopicActivity.class); Bundle extras = new Bundle(); extras.putString(BUNDLE_TOPIC_URL, "https://www.thmmy.gr/smf/index.php?topic=" - + bookmarkedTopic.getId() + ".0"); + + bookmarkedTopic.getId() + "." + 2147483647); extras.putString(BUNDLE_TOPIC_TITLE, bookmarkedTopic.getTitle()); intent.putExtras(extras); startActivity(intent); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java index 7dacd734..39a770c2 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java @@ -14,7 +14,8 @@ import android.widget.Toast; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.activities.main.MainActivity; import gr.thmmy.mthmmy.base.BaseActivity; -import mthmmy.utils.Report; + +import timber.log.Timber; import static gr.thmmy.mthmmy.session.SessionManager.CONNECTION_ERROR; import static gr.thmmy.mthmmy.session.SessionManager.EXCEPTION; @@ -34,12 +35,8 @@ public class LoginActivity extends BaseActivity { private String password; /* --Graphics End-- */ - //Other variables - private static final String TAG = "LoginActivity"; - private LoginTask loginTask; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -55,7 +52,7 @@ public class LoginActivity extends BaseActivity { btnLogin.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { - Report.d(TAG, "Login"); + Timber.d("Login"); //Get username and password strings username = inputUsername.getText().toString().trim(); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java index c3587b1b..d4cf2236 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java @@ -30,16 +30,11 @@ import gr.thmmy.mthmmy.model.Bookmark; import gr.thmmy.mthmmy.model.ThmmyPage; import gr.thmmy.mthmmy.model.Topic; import me.zhanghai.android.materialprogressbar.MaterialProgressBar; -import mthmmy.utils.Report; import okhttp3.Request; import okhttp3.Response; +import timber.log.Timber; public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMoreListener { - /** - * Debug Tag for logging debug output to LogCat - */ - @SuppressWarnings("unused") - private static final String TAG = "BoardActivity"; /** * The key to use when putting board's url String to {@link BoardActivity}'s Bundle. */ @@ -76,7 +71,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo boardUrl = extras.getString(BUNDLE_BOARD_URL); ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(boardUrl)); if (!target.is(ThmmyPage.PageCategory.BOARD)) { - Report.e(TAG, "Bundle came with a non board url!\nUrl:\n" + boardUrl); + Timber.e("Bundle came with a non board url!\nUrl:\n%s" , boardUrl); Toast.makeText(this, "An error has occurred\nAborting.", Toast.LENGTH_SHORT).show(); finish(); } @@ -92,8 +87,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo } thisPageBookmark = new Bookmark(boardTitle, ThmmyPage.getBoardId(boardUrl)); - thisPageBookmarkButton = (ImageButton) findViewById(R.id.bookmark); - setBoardBookmark(); + setBoardBookmark((ImageButton) findViewById(R.id.bookmark)); createDrawer(); progressBar = (MaterialProgressBar) findViewById(R.id.progressBar); @@ -182,13 +176,6 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo * parameter!

    */ public class BoardTask extends AsyncTask { - //Class variables - /** - * Debug Tag for logging debug output to LogCat - */ - @SuppressWarnings("unused") - private static final String TAG = "BoardTask"; //Separate tag for AsyncTask - @Override protected void onPreExecute() { if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE); @@ -201,12 +188,12 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo .url(boardUrl[0]) .build(); try { - Response response = BaseActivity.getClient().newCall(request).execute(); + Response response = client.newCall(request).execute(); parseBoard(Jsoup.parse(response.body().string())); } catch (SSLHandshakeException e) { - Report.w(TAG, "Certificate problem (please switch to unsafe connection)."); + Timber.w("Certificate problem (please switch to unsafe connection)."); } catch (Exception e) { - Report.e("TAG", "ERROR", e); + Timber.e("ERROR", e); } return null; } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardAdapter.java index 306302fe..b6b716be 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardAdapter.java @@ -32,7 +32,6 @@ import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; * {@link RecyclerView.Adapter} that can display a {@link gr.thmmy.mthmmy.model.Board}. */ class BoardAdapter extends RecyclerView.Adapter { - private static final String TAG = "BoardAdapter"; private final int VIEW_TYPE_SUB_BOARD_TITLE = 0; private final int VIEW_TYPE_SUB_BOARD = 1; private final int VIEW_TYPE_TOPIC_TITLE = 2; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java index 2343f78d..b4339f14 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java @@ -27,16 +27,12 @@ import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.model.Download; import gr.thmmy.mthmmy.model.ThmmyPage; import me.zhanghai.android.materialprogressbar.MaterialProgressBar; -import mthmmy.utils.Report; + import okhttp3.Request; import okhttp3.Response; +import timber.log.Timber; public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.OnLoadMoreListener { - /** - * Debug Tag for logging debug output to LogCat - */ - @SuppressWarnings("unused") - private static final String TAG = "DownloadsActivity"; /** * The key to use when putting download's url String to {@link DownloadsActivity}'s Bundle. */ @@ -73,7 +69,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter. if (downloadsUrl != null && !Objects.equals(downloadsUrl, "")) { ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(downloadsUrl)); if (!target.is(ThmmyPage.PageCategory.DOWNLOADS)) { - Report.e(TAG, "Bundle came with a non board url!\nUrl:\n" + downloadsUrl); + Timber.e("Bundle came with a non board url!\nUrl:\n%s" , downloadsUrl); Toast.makeText(this, "An error has occurred\nAborting.", Toast.LENGTH_SHORT).show(); finish(); } @@ -173,10 +169,6 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter. * as String parameter!

    */ class ParseDownloadPageTask extends AsyncTask { - /** - * Debug Tag for logging debug output to LogCat - */ - private static final String TAG = "ParseDownloadPageTask"; //Separate tag for AsyncTask private String thisPageUrl; @Override @@ -192,12 +184,12 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter. .url(downloadsUrl[0]) .build(); try { - Response response = BaseActivity.getClient().newCall(request).execute(); + Response response = client.newCall(request).execute(); parseDownloads(Jsoup.parse(response.body().string())); } catch (SSLHandshakeException e) { - Report.w(TAG, "Certificate problem (please switch to unsafe connection)."); + Timber.w("Certificate problem (please switch to unsafe connection)."); } catch (Exception e) { - Report.e("TAG", "ERROR", e); + Timber.e("ERROR", e); } return null; } diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java index a46dfeea..0253359b 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java @@ -29,11 +29,6 @@ import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWN import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWNLOADS_URL; class DownloadsAdapter extends RecyclerView.Adapter { - /** - * Debug Tag for logging debug output to LogCat - */ - @SuppressWarnings("unused") - private static final String TAG = "DownloadsAdapter"; private final int VIEW_TYPE_DOWNLOAD = 0; private final int VIEW_TYPE_LOADING = 1; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java index 5e701d6e..ddc604b4 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java @@ -9,7 +9,6 @@ import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.support.v7.widget.Toolbar; -import android.util.Log; import android.widget.Toast; import gr.thmmy.mthmmy.R; @@ -38,7 +37,6 @@ import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; public class MainActivity extends BaseActivity implements RecentFragment.RecentFragmentInteractionListener, ForumFragment.ForumFragmentInteractionListener { //----------------------------------------CLASS VARIABLES----------------------------------------- - private static final String TAG = "MainActivity"; private static final int TIME_INTERVAL = 2000; private long mBackPressed; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java index 40f14c93..9f896b29 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java @@ -36,7 +36,7 @@ class ForumAdapter extends ExpandableRecyclerAdapter { - private static final String TAG = "ForumTask"; + private class ForumTask extends AsyncTask { private HttpUrl forumUrl = SessionManager.forumUrl; //may change upon collapse/expand private Document document; @@ -179,10 +179,10 @@ public class ForumFragment extends BaseFragment fetchedCategories.clear(); return 0; } catch (IOException e) { - Report.d(TAG, "Network Error", e); + Timber.d("Network Error", e); return 1; } catch (Exception e) { - Report.d(TAG, "Exception", e); + Timber.d("Exception", e); return 2; } @@ -225,7 +225,7 @@ public class ForumFragment extends BaseFragment } } else - Report.e(TAG, "Parsing failed!"); + Timber.e("Parsing failed!"); } public void setUrl(String string) diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java index 7cd9ef63..47f4678d 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java @@ -29,10 +29,11 @@ import gr.thmmy.mthmmy.session.SessionManager; import gr.thmmy.mthmmy.utils.CustomRecyclerView; import gr.thmmy.mthmmy.utils.exceptions.ParseException; import me.zhanghai.android.materialprogressbar.MaterialProgressBar; -import mthmmy.utils.Report; + import okhttp3.HttpUrl; import okhttp3.Request; import okhttp3.Response; +import timber.log.Timber; /** * A {@link BaseFragment} subclass. @@ -86,7 +87,7 @@ public class RecentFragment extends BaseFragment { recentTask.execute(); } - Report.d(TAG, "onActivityCreated"); + Timber.d("onActivityCreated"); } @@ -141,8 +142,7 @@ public class RecentFragment extends BaseFragment { //---------------------------------------ASYNC TASK----------------------------------- - public class RecentTask extends AsyncTask { - private static final String TAG = "RecentTask"; + private class RecentTask extends AsyncTask { private final HttpUrl thmmyUrl = SessionManager.indexUrl; private Document document; @@ -161,13 +161,13 @@ public class RecentFragment extends BaseFragment { parse(document); return 0; } catch (ParseException e) { - Report.e(TAG, "ParseException", e); + Timber.e("ParseException", e); return 1; } catch (IOException e) { - Report.i(TAG, "Network Error", e); + Timber.i("Network Error", e); return 2; } catch (Exception e) { - Report.e(TAG, "Exception", e); + Timber.e("Exception", e); return 3; } @@ -179,7 +179,7 @@ public class RecentFragment extends BaseFragment { if (result == 0) recentAdapter.notifyDataSetChanged(); else if (result == 2) - Toast.makeText(getActivity(), "Network error", Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), "Network error", Toast.LENGTH_SHORT).show(); //Fixme, sometimes activity isn't ready progressBar.setVisibility(ProgressBar.INVISIBLE); swipeRefreshLayout.setRefreshing(false); 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 60fea8d3..08495dd9 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 @@ -19,6 +19,7 @@ import android.text.SpannableString; import android.text.Spanned; import android.text.style.ForegroundColorSpan; import android.text.style.RelativeSizeSpan; + import android.view.View; import android.widget.ImageView; import android.widget.ProgressBar; @@ -49,9 +50,10 @@ import gr.thmmy.mthmmy.model.ThmmyPage; import gr.thmmy.mthmmy.utils.CenterVerticalSpan; import gr.thmmy.mthmmy.utils.CircleTransform; import me.zhanghai.android.materialprogressbar.MaterialProgressBar; -import mthmmy.utils.Report; + import okhttp3.Request; import okhttp3.Response; +import timber.log.Timber; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; @@ -63,11 +65,6 @@ import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; * the username using the key {@link #BUNDLE_PROFILE_USERNAME}. */ public class ProfileActivity extends BaseActivity implements LatestPostsFragment.LatestPostsFragmentInteractionListener { - /** - * Debug Tag for logging debug output to LogCat - */ - @SuppressWarnings("unused") - private static final String TAG = "ProfileActivity"; /** * The key to use when putting profile's url String to {@link ProfileActivity}'s Bundle. */ @@ -178,7 +175,7 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(profileUrl)); if (!target.is(ThmmyPage.PageCategory.PROFILE)) { - Report.e(TAG, "Bundle came with a non profile url!\nUrl:\n" + profileUrl); + Timber.e("Bundle came with a non profile url!\nUrl:\n%s" , profileUrl); Toast.makeText(this, "An error has occurred\n Aborting.", Toast.LENGTH_SHORT).show(); finish(); } @@ -221,11 +218,6 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment */ public class ProfileTask extends AsyncTask { //Class variables - /** - * Debug Tag for logging debug output to LogCat - */ - @SuppressWarnings("unused") - private static final String TAG = "ProfileTask"; //Separate tag for AsyncTask Document profilePage; Spannable usernameSpan; @@ -243,14 +235,15 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment try { Response response = client.newCall(request).execute(); profilePage = Jsoup.parse(response.body().string()); - Elements contentsTable = profilePage.select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2)"); + Elements contentsTable = profilePage. + select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tbody"); //Finds username if missing if (username == null || Objects.equals(username, "")) { username = contentsTable.select("tr").first().select("td").last().text(); } if (thumbnailUrl == null || Objects.equals(thumbnailUrl, "")) { //Maybe there is an avatar - Element profileAvatar = contentsTable.select("img.avatar").first(); + Element profileAvatar = profilePage.select("img.avatar").first(); if (profileAvatar != null) thumbnailUrl = profileAvatar.attr("abs:src"); } { //Finds personal text @@ -260,7 +253,7 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment } else { //Should never get here! //Something is wrong. - Report.e(TAG, "An error occurred while trying to find profile's personal text."); + Timber.e("An error occurred while trying to find profile's personal text."); personalText = null; } } @@ -281,18 +274,18 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment } return true; } catch (SSLHandshakeException e) { - Report.w(TAG, "Certificate problem (please switch to unsafe connection)."); + Timber.w("Certificate problem (please switch to unsafe connection)."); } catch (Exception e) { - Report.e("TAG", "ERROR", e); + Timber.e("ERROR", e); } return false; } protected void onPostExecute(Boolean result) { - if (!result) { //Parse failed! - Report.d(TAG, "Parse failed!"); - Toast.makeText(getBaseContext() - , "Fatal error!\n Aborting...", Toast.LENGTH_LONG).show(); + if (!result) { //Parse failed! //TODO report as ParseException? + Timber.d("Parse failed!"); + Toast.makeText(getBaseContext(), "Fatal error!\n Aborting..." + , Toast.LENGTH_LONG).show(); finish(); } //Parse was successful @@ -342,7 +335,7 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment viewPager.setAdapter(adapter); } - class ViewPagerAdapter extends FragmentPagerAdapter { + private class ViewPagerAdapter extends FragmentPagerAdapter { private final List mFragmentList = new ArrayList<>(); private final List mFragmentTitleList = new ArrayList<>(); diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java index 5f0f77d2..13319607 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java @@ -21,11 +21,6 @@ import me.zhanghai.android.materialprogressbar.MaterialProgressBar; * specified {@link LatestPostsFragment.LatestPostsFragmentInteractionListener}. */ class LatestPostsAdapter extends RecyclerView.Adapter { - /** - * Debug Tag for logging debug output to LogCat - */ - @SuppressWarnings("unused") - private static final String TAG = "LatestPostsAdapter"; private final int VIEW_TYPE_ITEM = 0; private final int VIEW_TYPE_LOADING = 1; final private LatestPostsFragment.LatestPostsFragmentInteractionListener interactionListener; diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java index ccdb5593..5c3f8d40 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java @@ -26,19 +26,15 @@ import gr.thmmy.mthmmy.base.BaseFragment; import gr.thmmy.mthmmy.model.PostSummary; import gr.thmmy.mthmmy.utils.ParseHelpers; import me.zhanghai.android.materialprogressbar.MaterialProgressBar; -import mthmmy.utils.Report; + import okhttp3.Request; import okhttp3.Response; +import timber.log.Timber; /** * Use the {@link LatestPostsFragment#newInstance} factory method to create an instance of this fragment. */ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdapter.OnLoadMoreListener{ - /** - * 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. */ @@ -136,7 +132,7 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap profileLatestPostsTask.execute(profileUrl + ";sa=showPosts"); pagesLoaded = 1; } - Report.d(TAG, "onActivityCreated"); + Timber.d("onActivityCreated"); } @Override @@ -156,14 +152,7 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap *

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

    */ - public class LatestPostsTask extends AsyncTask { - //Class variables - /** - * Debug Tag for logging debug output to LogCat - */ - @SuppressWarnings("unused") - private static final String TAG = "LatestPostsTask"; //Separate tag for AsyncTask - + private class LatestPostsTask extends AsyncTask { protected void onPreExecute() { if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE); } @@ -176,16 +165,16 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap 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)."); + Timber.w("Certificate problem (please switch to unsafe connection)."); } catch (Exception e) { - Report.e("TAG", "ERROR", e); + Timber.e("ERROR", e); } return false; } protected void onPostExecute(Boolean result) { if (!result) { //Parse failed! - Report.d(TAG, "Parse failed!"); + Timber.d("Parse failed!"); Toast.makeText(getContext() , "Fatal error!\n Aborting...", Toast.LENGTH_LONG).show(); getActivity().finish(); 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 index d99f7d65..f573f3db 100644 --- 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 @@ -33,6 +33,7 @@ import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.net.ssl.SSLHandshakeException; @@ -40,16 +41,12 @@ import javax.net.ssl.SSLHandshakeException; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.base.BaseActivity; import me.zhanghai.android.materialprogressbar.MaterialProgressBar; -import mthmmy.utils.Report; + import okhttp3.Request; import okhttp3.Response; +import timber.log.Timber; public class StatsFragment extends Fragment { - /** - * Debug Tag for logging debug output to LogCat - */ - @SuppressWarnings("unused") - private static final String TAG = "StatsFragment"; /** * The key to use when putting profile's url String to {@link StatsFragment}'s Bundle. */ @@ -108,7 +105,7 @@ public class StatsFragment extends Fragment { profileStatsTask = new ProfileStatsTask(); profileStatsTask.execute(profileUrl + ";sa=statPanel"); } - Report.d(TAG, "onActivityCreated"); + Timber.d("onActivityCreated"); } @Override @@ -126,14 +123,7 @@ public class StatsFragment extends Fragment { *

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

    */ - public class ProfileStatsTask extends AsyncTask { - //Class variables - /** - * Debug Tag for logging debug output to LogCat - */ - @SuppressWarnings("unused") - private static final String TAG = "ProfileStatsTask"; //Separate tag for AsyncTask - + private class ProfileStatsTask extends AsyncTask { @Override protected void onPreExecute() { progressBar.setVisibility(ProgressBar.VISIBLE); @@ -149,9 +139,9 @@ public class StatsFragment extends Fragment { Response response = BaseActivity.getClient().newCall(request).execute(); return parseStats(Jsoup.parse(response.body().string())); } catch (SSLHandshakeException e) { - Report.w(TAG, "Certificate problem (please switch to unsafe connection)."); + Timber.w("Certificate problem (please switch to unsafe connection)."); } catch (Exception e) { - Report.e("TAG", "ERROR", e); + Timber.e("ERROR", e); } return false; } @@ -159,7 +149,7 @@ public class StatsFragment extends Fragment { @Override protected void onPostExecute(Boolean result) { if (!result) { //Parse failed! - Report.d(TAG, "Parse failed!"); + Timber.d("Parse failed!"); Toast.makeText(getContext() , "Fatal error!\n Aborting...", Toast.LENGTH_LONG).show(); getActivity().finish(); @@ -206,6 +196,7 @@ public class StatsFragment extends Fragment { Integer.parseInt(dataCols.last().text()))); mostPopularBoardsByPostsLabels.add(dataCols.first().text()); } + Collections.reverse(mostPopularBoardsByPostsLabels); } { Elements mostPopularBoardsByActivityRows = statsRows.last().select(">td").last() @@ -218,6 +209,7 @@ public class StatsFragment extends Fragment { Float.parseFloat(tmp.substring(0, tmp.indexOf("%"))))); mostPopularBoardsByActivityLabels.add(dataCols.first().text()); } + Collections.reverse(mostPopularBoardsByActivityLabels); } } return true; @@ -345,7 +337,7 @@ public class StatsFragment extends Fragment { mostPopularBoardsByActivityChart.invalidate(); } - class MyXAxisValueFormatter implements IAxisValueFormatter { + private class MyXAxisValueFormatter implements IAxisValueFormatter { private final ArrayList mValues; MyXAxisValueFormatter(ArrayList values) { diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java index a2a0eea9..70644bf4 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java @@ -6,6 +6,8 @@ import android.os.Build; import android.os.Bundle; import android.support.v4.app.Fragment; import android.text.Html; +import android.text.method.LinkMovementMethod; + import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -23,18 +25,14 @@ import java.util.Objects; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.utils.ParseHelpers; -import mthmmy.utils.Report; + +import timber.log.Timber; /** * 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. */ @@ -94,7 +92,7 @@ public class SummaryFragment extends Fragment { summaryTask = new SummaryTask(); summaryTask.execute(profileSummaryDocument); } - Report.d(TAG, "onActivityCreated"); + Timber.d("onActivityCreated"); } @Override @@ -112,14 +110,7 @@ public class SummaryFragment extends Fragment { *

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

    */ - public class SummaryTask extends AsyncTask { - //Class variables - /** - * Debug Tag for logging debug output to LogCat - */ - @SuppressWarnings("unused") - private static final String TAG = "SummaryTask"; //Separate tag for AsyncTask - + private class SummaryTask extends AsyncTask { protected Void doInBackground(Document... profileSummaryPage) { parsedProfileSummaryData = parseProfileSummary(profileSummaryPage[0]); return null; @@ -189,6 +180,16 @@ public class SummaryFragment extends Fragment { } TextView entry = new TextView(this.getContext()); + if (profileSummaryRow.contains("@") && + (profileSummaryRow.contains("Email") || profileSummaryRow.contains("E-mail"))) { + Timber.d("mpika"); + Timber.d(profileSummaryRow); + String email = profileSummaryRow.substring(profileSummaryRow.indexOf(": ") + 6); + profileSummaryRow = profileSummaryRow.replace(email, + "" + email + ""); + entry.setMovementMethod(LinkMovementMethod.getInstance()); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) entry.setTextColor(getResources().getColor(R.color.primary_text, null)); else diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java new file mode 100644 index 00000000..7c42b8cd --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java @@ -0,0 +1,263 @@ +package gr.thmmy.mthmmy.activities.topic; + +import android.util.Log; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; + +import okhttp3.Response; + +class Posting { + enum REPLY_STATUS { + SUCCESSFUL, NO_SUBJECT, EMPTY_BODY, NEW_REPLY_WHILE_POSTING, NOT_FOUND, SESSION_ENDED, OTHER_ERROR + } + + static REPLY_STATUS replyStatus(Response response) throws IOException { + if (response.code() == 404) return REPLY_STATUS.NOT_FOUND; + if (response.code() < 200 || response.code() >= 400) return REPLY_STATUS.OTHER_ERROR; + String finalUrl = response.request().url().toString(); + if (finalUrl.contains("action=post")) { + Document postErrorPage = Jsoup.parse(response.body().string()); + String[] errors = postErrorPage.select("tr[id=errors] div[id=error_list]").first() + .toString().split("
    "); + for (int i = 0; i < errors.length; ++i) { //TODO test + Log.d("TAG", String.valueOf(i)); + Log.d("TAG", errors[i]); + } + for (String error : errors) { + if (error.contains("Your session timed out while posting") || + error.contains("Υπερβήκατε τον μέγιστο χρόνο σύνδεσης κατά την αποστολή")) + return REPLY_STATUS.SESSION_ENDED; + if (error.contains("No subject was filled in") + || error.contains("Δεν δόθηκε τίτλος")) + return REPLY_STATUS.NO_SUBJECT; + if (error.contains("The message body was left empty") + || error.contains("Δεν δόθηκε κείμενο για το μήνυμα")) + return REPLY_STATUS.EMPTY_BODY; + } + return REPLY_STATUS.NEW_REPLY_WHILE_POSTING; + } + return REPLY_STATUS.SUCCESSFUL; + } + + static String htmlToBBcode(String html) { + Map bbMap = new HashMap<>(); + Map smileysMap1 = new HashMap<>(); + Map smileysMap2 = new HashMap<>(); + smileysMap1.put("Smiley", ":)"); + smileysMap1.put("Wink", ";)"); + smileysMap1.put("Cheesy", ":D"); + smileysMap1.put("Grin", ";D"); + smileysMap1.put("Angry", ">:("); + smileysMap1.put("Sad", ":("); + smileysMap1.put("Shocked", ":o"); + smileysMap1.put("Cool", "8))"); + smileysMap1.put("Huh", ":???:"); + smileysMap1.put("Roll Eyes", "::)"); + smileysMap1.put("Tongue", ":P"); + smileysMap1.put("Embarrassed", ":-["); + smileysMap1.put("Lips Sealed", ":-X"); + smileysMap1.put("Kiss", ":-*"); + smileysMap1.put("Cry", ":'("); + smileysMap1.put("heart", "<3"); + smileysMap1.put("kleidaria", "^locked^"); + smileysMap1.put("roll_over", "^rollover^"); + smileysMap1.put("redface", "^redface^"); + smileysMap1.put("confused", "^confused^"); + smileysMap1.put("innocent", "^innocent^"); + smileysMap1.put("sleep", "^sleep^"); + smileysMap1.put("lips_sealed", "^sealed^"); + smileysMap1.put("cool", "^cool^"); + smileysMap1.put("crazy", "^crazy^"); + smileysMap1.put("mad", "^mad^"); + smileysMap1.put("wav", "^wav^"); + smileysMap1.put("BinkyBaby", "^binkybaby^"); + smileysMap1.put("DontKnow", "^dontknow^"); + smileysMap1.put("angry4", ":angry4:"); + smileysMap1.put("angryAndHot", "^angryhot^"); + smileysMap1.put("angry", "^angry^"); + smileysMap1.put("bang_head", "^banghead^"); + smileysMap1.put("CryBaby", "^crybaby^"); + smileysMap1.put("Hello", "^hello^"); + smileysMap1.put("jerk", "^jerk^"); + smileysMap1.put("NoNo", "^nono^"); + smileysMap1.put("NotWorthy", "^notworthy^"); + smileysMap1.put("Off-topic", "^off-topic^"); + smileysMap1.put("Puke", "^puke^"); + smileysMap1.put("Shout", "^shout^"); + smileysMap1.put("Slurp", "^slurp^"); + smileysMap1.put("SuperConfused", "^superconfused^"); + smileysMap1.put("SuperInnocent", "^superinnocent^"); + smileysMap1.put("CellPhone", "^cellPhone^"); + smileysMap1.put("Idiot", "^idiot^"); + smileysMap1.put("Knuppel", "^knuppel^"); + smileysMap1.put("TickedOff", "^tickedOff^"); + smileysMap1.put("Peace", "^peace^"); + smileysMap1.put("Suspicious", "^suspicious^"); + smileysMap1.put("Caffine", "^caffine^"); + smileysMap1.put("argue", "^argue^"); + smileysMap1.put("banned2", "^banned2^"); + smileysMap1.put("banned", "^banned^"); + smileysMap1.put("bath", "^bath^"); + smileysMap1.put("beg", "^beg^"); + smileysMap1.put("bluescreen", "^bluescreen^"); + smileysMap1.put("boil", "^boil^"); + smileysMap1.put("bye", "^bye^"); + smileysMap1.put("callmerip", "^callmerip^"); + smileysMap1.put("carnaval", "^carnaval^"); + smileysMap1.put("clap", "^clap^"); + smileysMap1.put("coffepot", "^coffepot^"); + smileysMap1.put("crap", "^crap^"); + smileysMap1.put("curses", "^curses^"); + smileysMap1.put("funny", "^funny^"); + smileysMap1.put("guitar", "^guitar^"); + smileysMap1.put("kissy", "^kissy^"); + smileysMap1.put("band", "^band^"); + smileysMap1.put("ivres", "^ivres^"); + smileysMap1.put("kaloe", "^kaloe^"); + smileysMap1.put("kremala", "^kremala^"); + smileysMap1.put("moon", "^moon^"); + smileysMap1.put("mopping", "^mopping^"); + smileysMap1.put("mountza", "^mountza^"); + smileysMap1.put("pcsleep", "^pcsleep^"); + smileysMap1.put("pinokio", "^pinokio^"); + smileysMap1.put("poke", "^poke^"); + smileysMap1.put("seestars", "^seestars^"); + smileysMap1.put("sfyri", "^sfyri^"); + smileysMap1.put("spam", "^spam^"); + smileysMap1.put("super", "^super^"); + smileysMap1.put("tafos", "^tafos^"); + smileysMap1.put("tomato", "^tomato^"); + smileysMap1.put("ytold", "^ytold^"); + smileysMap1.put("beer", "^beer^"); + smileysMap1.put("ο fritz!!!", "^fritz^"); + smileysMap1.put("o Wade!!!", "^wade^"); + smileysMap1.put("bonjour", "^hat^"); + smileysMap1.put("bonjour2", "^miss^"); + smileysMap1.put("question", "^que^"); + smileysMap1.put("shifty", "^shifty^"); + smileysMap1.put("shy", "^shy^"); + smileysMap1.put("music_listenning", "^music_listen^"); + smileysMap1.put("bag_face", "^bagface^"); + smileysMap1.put("rotation", "^rotate^"); + smileysMap1.put("love", "^love^"); + smileysMap1.put("speech", "^speech^"); + smileysMap1.put("shocked", "^shocked^"); + smileysMap1.put("extremely_shocked", "^ex_shocked^"); + smileysMap1.put("smurf", "^smurf^"); + smileysMap1.put("monster", "^monster^"); + smileysMap1.put("pig", "^pig^"); + smileysMap1.put("lol", "^lol^"); + + smileysMap2.put("Police", "^Police^"); + smileysMap2.put("foyska", "^fouska^"); + smileysMap2.put("nista", "^nysta^"); + smileysMap2.put("10_7_3", "^sfinaki^"); + smileysMap2.put("yu", "^yue^"); + smileysMap2.put("a-eatpaper", "^eatpaper^"); + smileysMap2.put("lypi", "^lypi^"); + smileysMap2.put("megashok1wq", "^aytoxeir^"); + smileysMap2.put("victory", "^victory^"); + smileysMap2.put("filarakia", "^filarakia^"); + smileysMap2.put("rofl", "^rolfmao^"); + smileysMap2.put("locked", "^lock^"); + smileysMap2.put("facepalm", "^facepalm^"); + + //html stuff on the beginning + bbMap.put("\n ", ""); + //quotes and code headers + bbMap.put("\n\\s+?
    \n (.+?)\n
    ", ""); + bbMap.put("\n\\s+?
    \n (.+?)\n
    ", ""); + bbMap.put("\n\\s+?
    \n (.+?)\n
    ", ""); + bbMap.put("
    ", "\n"); + //bold + bbMap.put("\n\\s+?(.+?)", "\\[b\\]$1\\[/b\\]"); + //italics + bbMap.put("\n\\s+?(.+?)", "\\[i\\]$1\\[/i\\]"); + //underline + bbMap.put("\n\\s+?(.+?)", "\\[u\\]$1\\[/u\\]"); + //deleted + bbMap.put("\n\\s+?(.+?)", "\\[s\\]$1\\[/s\\]"); + //text color + bbMap.put("\n\\s+?(.+?)", "\\[color=$1\\]$2\\[/color\\]"); + //glow + bbMap.put("\n\\s+?(.+?)", "\\[glow=$1,2,300\\]$2\\[/glow\\]"); + //shadow + bbMap.put("\n\\s+?(.+?)", "\\[shadow=$1,$2\\]$3\\[/shadow\\]"); + //running text + bbMap.put("\\s+?\n (.+?)\n ", "\\[move\\]$1\\[/move\\]"); + //alignment + bbMap.put("\n\\s+?
    \n (.+?)\n
    ", "\\[center\\]$1\\[/center\\]"); + bbMap.put("\n\\s+?
    \n (.+?)\n
    ", "\\[$1\\]$2\\[/$1\\]"); + //preformated + bbMap.put("\n\\s+?
    (.+?)
    ", "\\[pre\\]$1\\[/pre\\]"); + //horizontal rule + bbMap.put("\n\\s+?
    ", "\\[hr\\]"); + //resize + bbMap.put("\n\\s+?(.+?)", "\\[size=$1\\]$3\\[/size\\]"); + //font + bbMap.put("\n\\s+?(.+?)", "\\[font=$1\\]$2\\[/font\\]"); + //lists + bbMap.put("\\s+
  • (.+?)
  • ", "\\[li\\]$1\\[/li\\]"); + bbMap.put("\n\\s+
      ([\\S\\s]+?)\n\\s+
    ", + "\\[list\\]\n$1\n\\[/list\\]"); + //latex code + bbMap.put("\n\\s+?", "\\[tex\\]$1\\[/tex\\]"); + //code + bbMap.put("\n\\s+?
    \n (.+?)\n
    ", "\\[code\\]$1\\[/code\\]"); + //teletype + bbMap.put("\n\\s+?(.+?)", "\\[tt\\]$1\\[/tt\\]"); + //superscript/subscript + bbMap.put("\n\\s+?(.+?)", "\\[sub\\]$1\\[/sub\\]"); + bbMap.put("\n\\s+?(.+?)", "\\[sup\\]$1\\[/sup\\]"); + //tables + bbMap.put("\\s+?([\\S\\s]+?)", "\\[td\\]$1\\[/td\\]"); + bbMap.put("([\\S\\s]+?)\n ", "\\[tr\\]$1\\[/tr\\]"); + bbMap.put("\n\\s+?\n \n ([\\S\\s]+?)\n \n
    " + , "\\[table\\]$2\\[/table\\]"); + //videos + bbMap.put("\n\\s+?
    \n", + "[youtube]https://www.youtube.com/watch?v=$1[/youtube]"); + //ftp + bbMap.put("([\\S\\s]+?)", "\\[fpt=ftp:$1\\]$2\\[/ftp\\]"); + //mailto + bbMap.put("\n\\s+?([\\S\\s]+?)", "\\[email\\]$2\\[/email\\]"); + //links + bbMap.put("\n\\s+?([\\S\\s]+?)", "\\[url=$1\\]$2\\[/url\\]"); + //smileys + for (Map.Entry entry : smileysMap1.entrySet()) { + bbMap.put("\n \""", entry.getValue().toString()); + } + for (Map.Entry entry : smileysMap2.entrySet()) { //Those that have empty alt tag + bbMap.put("\n ", entry.getValue().toString()); + } + + bbMap.put("\n \"Undecided\"" + , Matcher.quoteReplacement(":-\\")); + + //html stuff on the end + bbMap.put("\n
    ", ""); + + for (Map.Entry entry : bbMap.entrySet()) { + html = html.replaceAll(entry.getKey().toString(), entry.getValue().toString()); + } + + //img need to be done last or it messes up everything else + html = html.replaceAll("\\s+", + "\\[img width=$2 height=$3\\]$1\\[/img\\]"); + html = html.replaceAll("\\s+", + "\\[img height=$2 width=$3\\]$1\\[/img\\]"); + html = html.replaceAll("\\s+", "\\[img width=$2\\]$1\\[/img\\]"); + html = html.replaceAll("\\s+", "\\[img height=$2\\]$1\\[/img\\]"); + html = html.replaceAll("\\s+", "\\[img\\]$1\\[/img\\]"); + + return html; + } +} 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 8ca6f707..a733caf5 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java @@ -1,40 +1,68 @@ package gr.thmmy.mthmmy.activities.topic; +import android.content.Context; +import android.content.Intent; import android.graphics.Rect; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.support.design.widget.FloatingActionButton; +import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; -import android.util.Log; +import android.text.Html; +import android.text.SpannableStringBuilder; +import android.text.method.LinkMovementMethod; +import android.text.method.ScrollingMovementMethod; +import android.text.style.ClickableSpan; +import android.text.style.URLSpan; import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; +import android.view.inputmethod.InputMethodManager; import android.widget.ImageButton; +import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Selector; import java.io.IOException; import java.util.ArrayList; import java.util.Objects; import gr.thmmy.mthmmy.R; +import gr.thmmy.mthmmy.activities.board.BoardActivity; +import gr.thmmy.mthmmy.activities.profile.ProfileActivity; import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.model.Bookmark; import gr.thmmy.mthmmy.model.Post; import gr.thmmy.mthmmy.model.ThmmyPage; import gr.thmmy.mthmmy.utils.ParseHelpers; import me.zhanghai.android.materialprogressbar.MaterialProgressBar; -import mthmmy.utils.Report; + +import okhttp3.MultipartBody; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; +import timber.log.Timber; + +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_TITLE; +import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_URL; +import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_THUMBNAIL_URL; +import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL; +import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME; +import static gr.thmmy.mthmmy.activities.topic.Posting.replyStatus; /** * Activity for topics. When creating an Intent of this activity you need to bundle a String @@ -44,11 +72,6 @@ import okhttp3.Response; @SuppressWarnings("unchecked") public class TopicActivity extends BaseActivity { //Class variables - /** - * Debug Tag for logging debug output to LogCat - */ - @SuppressWarnings("unused") - private static final String TAG = "TopicActivity"; /** * The key to use when putting topic's url String to {@link TopicActivity}'s Bundle. */ @@ -62,10 +85,11 @@ public class TopicActivity extends BaseActivity { private TopicAdapter topicAdapter; private ArrayList postsList; private static final int NO_POST_FOCUS = -1; - private static int postFocus = NO_POST_FOCUS; + private int postFocus = NO_POST_FOCUS; private static int postFocusPosition = 0; - //Quotes - public static final ArrayList toQuoteList = new ArrayList<>(); + //Reply + private FloatingActionButton replyFAB; + private String replyPageUrl = null; //Topic's pages private int thisPage = 1; private int numberOfPages = 1; @@ -79,19 +103,24 @@ public class TopicActivity extends BaseActivity { private static final int LARGE_STEP = 10; private Integer pageRequestValue; //Bottom navigation graphics + private LinearLayout bottomNavBar; private ImageButton firstPage; private ImageButton previousPage; private TextView pageIndicator; private ImageButton nextPage; private ImageButton lastPage; + //Topic's info + private SpannableStringBuilder topicTreeAndMods = new SpannableStringBuilder("Loading..."), + topicViewers = new SpannableStringBuilder("Loading..."); //Other variables - private FloatingActionButton replyFAB; private MaterialProgressBar progressBar; + TextView toolbarTitle; private static String base_url = ""; private String topicTitle; private String parsedTitle; private RecyclerView recyclerView; - private String loadedPageUrl = ""; + String loadedPageUrl = ""; + private boolean reloadingPage = false; @Override @@ -105,23 +134,27 @@ public class TopicActivity extends BaseActivity { ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory( Uri.parse(topicPageUrl)); if (!target.is(ThmmyPage.PageCategory.TOPIC)) { - Report.e(TAG, "Bundle came with a non topic url!\nUrl:\n" + topicPageUrl); + Timber.e("Bundle came with a non topic url!\nUrl:\n" + topicPageUrl); Toast.makeText(this, "An error has occurred\n Aborting.", Toast.LENGTH_SHORT).show(); finish(); } + thisPageBookmark = new Bookmark(topicTitle, ThmmyPage.getTopicId(topicPageUrl)); + //Initializes graphics toolbar = (Toolbar) findViewById(R.id.toolbar); - toolbar.setTitle(topicTitle); + toolbarTitle = (TextView) toolbar.findViewById(R.id.toolbar_title); + toolbarTitle.setText(topicTitle); setSupportActionBar(toolbar); if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); } - thisPageBookmark = new Bookmark(topicTitle, ThmmyPage.getTopicId(topicPageUrl)); - thisPageBookmarkButton = (ImageButton) findViewById(R.id.bookmark); - setTopicBookmark(); + //Makes title scrollable + toolbarTitle.setHorizontallyScrolling(true); + toolbarTitle.setMovementMethod(new ScrollingMovementMethod()); + createDrawer(); progressBar = (MaterialProgressBar) findViewById(R.id.progressBar); @@ -132,42 +165,27 @@ public class TopicActivity extends BaseActivity { recyclerView.setHasFixedSize(true); LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext()); recyclerView.setLayoutManager(layoutManager); - topicAdapter = new TopicAdapter(this, postsList, - topicTask); + topicAdapter = new TopicAdapter(this, postsList, topicTask, topicTitle, loadedPageUrl); recyclerView.setAdapter(topicAdapter); replyFAB = (FloatingActionButton) findViewById(R.id.topic_fab); replyFAB.setEnabled(false); - replyFAB.hide(); - /*if (!sessionManager.isLoggedIn()) replyFAB.hide(); + bottomNavBar = (LinearLayout) findViewById(R.id.bottom_navigation_bar); + if (!sessionManager.isLoggedIn()) replyFAB.hide(); else { replyFAB.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (sessionManager.isLoggedIn()) { - //TODO Reply - } else { - new AlertDialog.Builder(TopicActivity.this) - .setMessage("You need to be logged in to reply!") - .setPositiveButton("Login", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - Intent intent = new Intent(TopicActivity.this, LoginActivity.class); - startActivity(intent); - finish(); - overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out); - } - }) - .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - } - }) - .show(); + postsList.add(null); + topicAdapter.prepareForReply(new ReplyTask()); + replyFAB.hide(); + bottomNavBar.setVisibility(View.GONE); + topicAdapter.notifyItemInserted(postsList.size()); } } }); - }*/ + } //Sets bottom navigation bar firstPage = (ImageButton) findViewById(R.id.page_first_button); @@ -187,6 +205,43 @@ public class TopicActivity extends BaseActivity { topicTask.execute(extras.getString(BUNDLE_TOPIC_URL)); //Attempt data parsing } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflates the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.topic_menu, menu); + setTopicBookmark(menu.getItem(0)); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle presses on the action bar items + switch (item.getItemId()) { + case R.id.menu_bookmark: + topicMenuBookmarkClick(); + return true; + case R.id.menu_info: + AlertDialog.Builder builder = new AlertDialog.Builder(this); + LayoutInflater inflater = this.getLayoutInflater(); + LinearLayout infoDialog = (LinearLayout) inflater.inflate(R.layout.dialog_topic_info + , null); + ((TextView) infoDialog.findViewById(R.id.dialog_title)).setText("Info"); + TextView treeAndMods = (TextView) infoDialog.findViewById(R.id.topic_tree_and_mods); + treeAndMods.setText(topicTreeAndMods); + treeAndMods.setMovementMethod(LinkMovementMethod.getInstance()); + TextView usersViewing = (TextView) infoDialog.findViewById(R.id.users_viewing); + usersViewing.setText(topicViewers); + usersViewing.setMovementMethod(LinkMovementMethod.getInstance()); + + builder.setView(infoDialog); + AlertDialog dialog = builder.create(); + dialog.show(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + @Override public void onBackPressed() { if (drawer.isDrawerOpen()) { @@ -216,7 +271,7 @@ public class TopicActivity extends BaseActivity { * This class is used to implement the repetitive incrementPageRequestValue/decrementPageRequestValue * of page value when long pressing one of the page navigation buttons. */ - class RepetitiveUpdater implements Runnable { + private class RepetitiveUpdater implements Runnable { private final int step; /** @@ -352,7 +407,8 @@ public class TopicActivity extends BaseActivity { paginationEnabled(true); changePage(pageRequestValue - 1); } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) { + if (rect != null && + !rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) { autoIncrement = false; incrementPageRequestValue(thisPage - pageRequestValue); paginationEnabled(true); @@ -400,11 +456,6 @@ public class TopicActivity extends BaseActivity { * as String parameter!

    */ class TopicTask extends AsyncTask { - //Class variables - /** - * Debug Tag for logging debug output to LogCat - */ - private static final String TAG = "TopicTask"; //Separate tag for AsyncTask private static final int SUCCESS = 0; private static final int NETWORK_ERROR = 1; private static final int OTHER_ERROR = 2; @@ -419,7 +470,6 @@ public class TopicActivity extends BaseActivity { protected Integer doInBackground(String... strings) { Document document; - base_url = strings[0].substring(0, strings[0].lastIndexOf(".")); //New topic's base url String newPageUrl = strings[0]; //Finds the index of message focus if present @@ -434,7 +484,7 @@ public class TopicActivity extends BaseActivity { } } //Checks if the page to be loaded is the one already shown - if (!Objects.equals(loadedPageUrl, "") && loadedPageUrl.contains(base_url)) { + if (!reloadingPage && !Objects.equals(loadedPageUrl, "") && newPageUrl.contains(base_url)) { if (newPageUrl.contains("topicseen#new") || newPageUrl.contains("#new")) if (thisPage == numberOfPages) return SAME_PAGE; @@ -448,11 +498,16 @@ public class TopicActivity extends BaseActivity { return SAME_PAGE; } } - } else if (Integer.parseInt(newPageUrl.substring(base_url.length() + 1)) / 15 + 1 == thisPage) + } else if ((Objects.equals(newPageUrl, base_url) && thisPage == 1) || + Integer.parseInt(newPageUrl.substring(base_url.length() + 1)) / 15 + 1 == thisPage) return SAME_PAGE; } else if (!Objects.equals(loadedPageUrl, "")) topicTitle = null; + if (reloadingPage) reloadingPage = !reloadingPage; loadedPageUrl = newPageUrl; + if (strings[0].substring(0, strings[0].lastIndexOf(".")).contains("topic=")) + base_url = strings[0].substring(0, strings[0].lastIndexOf(".")); //New topic's base url + replyPageUrl = null; Request request = new Request.Builder() .url(newPageUrl) .build(); @@ -462,10 +517,10 @@ public class TopicActivity extends BaseActivity { parse(document); return SUCCESS; } catch (IOException e) { - Report.i(TAG, "IO Exception", e); + Timber.i("IO Exception", e); return NETWORK_ERROR; } catch (Exception e) { - Report.e(TAG, "Exception", e); + Timber.e("Exception", e); return OTHER_ERROR; } } @@ -483,11 +538,13 @@ public class TopicActivity extends BaseActivity { case SUCCESS: if (topicTitle == null || Objects.equals(topicTitle, "")) { thisPageBookmark = new Bookmark(parsedTitle, ThmmyPage.getTopicId(loadedPageUrl)); - setTopicBookmark(); + invalidateOptionsMenu(); } progressBar.setVisibility(ProgressBar.INVISIBLE); topicAdapter.customNotifyDataSetChanged(new TopicTask()); + topicAdapter.setTopicInfo(parsedTitle, loadedPageUrl); + if (replyPageUrl == null) replyFAB.hide(); if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(true); //Set current page @@ -496,8 +553,10 @@ public class TopicActivity extends BaseActivity { paginationEnabled(true); + if (topicTitle != null) + if (parsedTitle != null) if (topicTitle == null || Objects.equals(topicTitle, "")) - toolbar.setTitle(parsedTitle); + toolbarTitle.setText(parsedTitle); break; case NETWORK_ERROR: Toast.makeText(getBaseContext(), "Network Error", Toast.LENGTH_SHORT).show(); @@ -512,7 +571,7 @@ public class TopicActivity extends BaseActivity { break; default: //Parse failed - should never happen - Report.d(TAG, "Parse failed!"); + Timber.d("Parse failed!"); //TODO report ParseException? Toast.makeText(getBaseContext(), "Fatal Error", Toast.LENGTH_SHORT).show(); finish(); break; @@ -528,6 +587,20 @@ public class TopicActivity extends BaseActivity { private void parse(Document topic) { ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic); + //Finds topic's tree, mods and users viewing + { + topicTreeAndMods = getSpannableFromHtml(topic.select("div.nav").first().html()); + topicViewers = getSpannableFromHtml(TopicParser.parseUsersViewingThisTopic(topic, language)); + } + + //Finds reply page url + { + Element replyButton = topic.select("a:has(img[alt=Reply])").first(); + if (replyButton == null) + replyButton = topic.select("a:has(img[alt=Απάντηση])").first(); + if (replyButton != null) replyPageUrl = replyButton.attr("href"); + } + //Finds topic title if missing if (topicTitle == null || Objects.equals(topicTitle, "")) { parsedTitle = topic.select("td[id=top_subject]").first().text(); @@ -537,7 +610,7 @@ public class TopicActivity extends BaseActivity { } else { parsedTitle = parsedTitle.substring(parsedTitle.indexOf("Θέμα:") + 6 , parsedTitle.indexOf("(Αναγνώστηκε") - 2); - Report.d(TAG, parsedTitle); + Timber.d(parsedTitle); } } @@ -555,7 +628,150 @@ public class TopicActivity extends BaseActivity { postsList.clear(); postsList.addAll(TopicParser.parseTopic(topic, language)); - //postsList = TopicParser.parseTopic(topic, language); + } + + private void makeLinkClickable(SpannableStringBuilder strBuilder, final URLSpan span) { + int start = strBuilder.getSpanStart(span); + int end = strBuilder.getSpanEnd(span); + int flags = strBuilder.getSpanFlags(span); + ClickableSpan clickable = new ClickableSpan() { + @Override + public void onClick(View view) { + ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(span.getURL())); + if (target.is(ThmmyPage.PageCategory.BOARD)) { + Intent intent = new Intent(getApplicationContext(), BoardActivity.class); + Bundle extras = new Bundle(); + extras.putString(BUNDLE_BOARD_URL, span.getURL()); + extras.putString(BUNDLE_BOARD_TITLE, ""); + intent.putExtras(extras); + intent.setFlags(FLAG_ACTIVITY_NEW_TASK); + getApplicationContext().startActivity(intent); + } else if (target.is(ThmmyPage.PageCategory.PROFILE)) { + Intent intent = new Intent(getApplicationContext(), ProfileActivity.class); + Bundle extras = new Bundle(); + extras.putString(BUNDLE_PROFILE_URL, span.getURL()); + extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, ""); + extras.putString(BUNDLE_PROFILE_USERNAME, ""); + intent.putExtras(extras); + intent.setFlags(FLAG_ACTIVITY_NEW_TASK); + getApplicationContext().startActivity(intent); + } else if (target.is(ThmmyPage.PageCategory.INDEX)) + finish(); + } + }; + strBuilder.setSpan(clickable, start, end, flags); + strBuilder.removeSpan(span); + } + + private SpannableStringBuilder getSpannableFromHtml(String html) { + CharSequence sequence = Html.fromHtml(html); + SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence); + URLSpan[] urls = strBuilder.getSpans(0, sequence.length(), URLSpan.class); + for (URLSpan span : urls) { + makeLinkClickable(strBuilder, span); + } + return strBuilder; + } + } + + class ReplyTask extends AsyncTask { + + @Override + protected void onPreExecute() { + progressBar.setVisibility(ProgressBar.VISIBLE); + paginationEnabled(false); + replyFAB.setEnabled(false); + } + + @Override + protected Boolean doInBackground(String... message) { + Document document; + String numReplies, seqnum, sc, subject, topic; + + Request request = new Request.Builder() + .url(replyPageUrl + ";wap2") + .build(); + try { + Response response = client.newCall(request).execute(); + document = Jsoup.parse(response.body().string()); + + numReplies = replyPageUrl.substring(replyPageUrl.indexOf("num_replies=") + 12); + seqnum = document.select("input[name=seqnum]").first().attr("value"); + sc = document.select("input[name=sc]").first().attr("value"); + //subject = document.select("input[name=subject]").first().attr("value"); + topic = document.select("input[name=topic]").first().attr("value"); + } catch (IOException e) { + Timber.e("Post failed.", e); + return false; + } catch (Selector.SelectorParseException e) { + Timber.e("Post failed.", e); + return false; + } + + RequestBody postBody = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("message", message[1]) + .addFormDataPart("num_replies", numReplies) + .addFormDataPart("seqnum", seqnum) + .addFormDataPart("sc", sc) + .addFormDataPart("subject", message[0]) + .addFormDataPart("topic", topic) + .build(); + + Request post = new Request.Builder() + .url("https://www.thmmy.gr/smf/index.php?action=post2") + .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36") + .post(postBody) + .build(); + + try { + client.newCall(post).execute(); + Response response = client.newCall(post).execute(); + switch (replyStatus(response)) { + case SUCCESSFUL: + return true; + case NEW_REPLY_WHILE_POSTING: + //TODO this... + return true; + default: + Timber.e("Malformed post. Request string:\n" + post.toString()); + return true; + } + } catch (IOException e) { + Timber.e("Post failed.", e); + return false; + } + } + + @Override + protected void onPostExecute(Boolean result) { + View view = getCurrentFocus(); + if (view != null) { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + + postsList.remove(postsList.size() - 1); + topicAdapter.notifyItemRemoved(postsList.size()); + + progressBar.setVisibility(ProgressBar.GONE); + replyFAB.setVisibility(View.VISIBLE); + bottomNavBar.setVisibility(View.VISIBLE); + + if (!result) + Toast.makeText(TopicActivity.this, "Post failed!", Toast.LENGTH_SHORT).show(); + paginationEnabled(true); + replyFAB.setEnabled(true); + + if (result) { + topicTask = new TopicTask(); + if ((postsList.get(postsList.size() - 1).getPostNumber() + 1) % 15 == 0) + topicTask.execute(base_url + "." + 2147483647); + else { + reloadingPage = true; + topicTask.execute(loadedPageUrl); + } + } } } } \ No newline at end of file diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java index 2ea65fb9..b06b684a 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 @@ -10,10 +10,15 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.content.res.ResourcesCompat; +import android.support.v7.widget.AppCompatImageButton; import android.support.v7.widget.CardView; import android.support.v7.widget.RecyclerView; +import android.text.Editable; import android.text.TextUtils; +import android.text.TextWatcher; + import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -21,6 +26,7 @@ import android.view.ViewGroup; import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebViewClient; +import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; @@ -30,8 +36,14 @@ import android.widget.TextView; import com.squareup.picasso.Picasso; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.Objects; import gr.thmmy.mthmmy.R; @@ -42,7 +54,8 @@ import gr.thmmy.mthmmy.model.Post; import gr.thmmy.mthmmy.model.ThmmyFile; import gr.thmmy.mthmmy.model.ThmmyPage; import gr.thmmy.mthmmy.utils.CircleTransform; -import mthmmy.utils.Report; + +import timber.log.Timber; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_TITLE; @@ -50,21 +63,20 @@ import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_URL; import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_THUMBNAIL_URL; import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL; import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME; -import static gr.thmmy.mthmmy.activities.topic.TopicActivity.toQuoteList; +import static gr.thmmy.mthmmy.activities.topic.Posting.htmlToBBcode; +import static gr.thmmy.mthmmy.base.BaseActivity.getSessionManager; /** * Custom {@link android.support.v7.widget.RecyclerView.Adapter} used for topics. */ -class TopicAdapter extends RecyclerView.Adapter { - /** - * Debug Tag for logging debug output to LogCat - */ - private static final String TAG = "TopicAdapter"; +class TopicAdapter extends RecyclerView.Adapter { /** * Int that holds thumbnail's size defined in R.dimen */ private static int THUMBNAIL_SIZE; private final Context context; + private String topicTitle; + private ArrayList toQuoteList = new ArrayList<>(); private final List postsList; /** * Used to hold the state of visibility and other attributes for views that are animated or @@ -87,69 +99,20 @@ class TopicAdapter extends RecyclerView.Adapter { */ private static final int isQuoteButtonChecked = 2; private TopicActivity.TopicTask topicTask; + private TopicActivity.ReplyTask replyTask; + private final int VIEW_TYPE_POST = 0; + private final int VIEW_TYPE_QUICK_REPLY = 1; - /** - * Custom {@link RecyclerView.ViewHolder} implementation - */ - class MyViewHolder extends RecyclerView.ViewHolder { - final CardView cardView; - final LinearLayout cardChildLinear; - final FrameLayout postDateAndNumberExp; - final TextView postDate, postNum, username, subject; - final ImageView thumbnail; - final public WebView post; - final ImageButton quoteToggle; - final RelativeLayout header; - final LinearLayout userExtraInfo; - final View bodyFooterDivider; - final LinearLayout postFooter; - - final TextView specialRank, rank, gender, numberOfPosts, personalText, stars; - - MyViewHolder(View view) { - super(view); - //Initializes layout's graphic elements - //Standard stuff - cardView = (CardView) view.findViewById(R.id.card_view); - cardChildLinear = (LinearLayout) view.findViewById(R.id.card_child_linear); - postDateAndNumberExp = (FrameLayout) view.findViewById(R.id.post_date_and_number_exp); - postDate = (TextView) view.findViewById(R.id.post_date); - postNum = (TextView) view.findViewById(R.id.post_number); - thumbnail = (ImageView) view.findViewById(R.id.thumbnail); - username = (TextView) view.findViewById(R.id.username); - subject = (TextView) view.findViewById(R.id.subject); - post = (WebView) view.findViewById(R.id.post); - post.setBackgroundColor(Color.argb(1, 255, 255, 255)); - quoteToggle = (ImageButton) view.findViewById(R.id.toggle_quote_button); - bodyFooterDivider = view.findViewById(R.id.body_footer_divider); - postFooter = (LinearLayout) view.findViewById(R.id.post_footer); - - //User's extra info - header = (RelativeLayout) view.findViewById(R.id.header); - userExtraInfo = (LinearLayout) view.findViewById(R.id.user_extra_info); - specialRank = (TextView) view.findViewById(R.id.special_rank); - rank = (TextView) view.findViewById(R.id.rank); - gender = (TextView) view.findViewById(R.id.gender); - numberOfPosts = (TextView) view.findViewById(R.id.number_of_posts); - personalText = (TextView) view.findViewById(R.id.personal_text); - stars = (TextView) view.findViewById(R.id.stars); - } - - /** - * Cancels all pending Picasso requests - */ - void cleanup() { - Picasso.with(context).cancelRequest(thumbnail); - thumbnail.setImageDrawable(null); - } - } + private String[] replyDataHolder = new String[2]; + private int replySubject = 0, replyText = 1; + private String loadedPageUrl = ""; /** * @param context the context of the {@link RecyclerView} * @param postsList List of {@link Post} objects to use */ - TopicAdapter(Context context, List postsList, - TopicActivity.TopicTask topicTask) { + TopicAdapter(Context context, List postsList, TopicActivity.TopicTask topicTask + , String topicTitle, String loadedPageUrl) { this.context = context; this.postsList = postsList; @@ -159,177 +122,225 @@ class TopicAdapter extends RecyclerView.Adapter { viewProperties.add(new boolean[3]); } this.topicTask = topicTask; + this.topicTitle = topicTitle; + this.loadedPageUrl = loadedPageUrl; } - @Override - public void onViewRecycled(final MyViewHolder holder) { - holder.cleanup(); + void prepareForReply(TopicActivity.ReplyTask replyTask) { + this.replyTask = replyTask; + } + + void setTopicInfo(String topicTitle, String loadedPageUrl) { + this.topicTitle = topicTitle; + this.loadedPageUrl = loadedPageUrl; } @Override - public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View itemView = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.activity_topic_post_row, parent, false); - return new MyViewHolder(itemView); + public int getItemViewType(int position) { + return postsList.get(position) == null ? VIEW_TYPE_QUICK_REPLY : VIEW_TYPE_POST; } - @SuppressLint("SetJavaScriptEnabled") @Override - public void onBindViewHolder(final MyViewHolder holder, final int position) { - final Post currentPost = postsList.get(position); - - //Post's WebView parameters - holder.post.setClickable(true); - holder.post.setWebViewClient(new LinkLauncher()); - - //Avoids errors about layout having 0 width/height - holder.thumbnail.setMinimumWidth(1); - holder.thumbnail.setMinimumHeight(1); - //Sets thumbnail size - holder.thumbnail.setMaxWidth(THUMBNAIL_SIZE); - holder.thumbnail.setMaxHeight(THUMBNAIL_SIZE); - - //noinspection ConstantConditions - Picasso.with(context) - .load(currentPost.getThumbnailUrl()) - .resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE) - .centerCrop() - .error(ResourcesCompat.getDrawable(context.getResources() - , R.drawable.ic_default_user_thumbnail, null)) - .placeholder(ResourcesCompat.getDrawable(context.getResources() - , R.drawable.ic_default_user_thumbnail, null)) - .transform(new CircleTransform()) - .into(holder.thumbnail); - - //Sets username,submit date, index number, subject, post's and attached files texts - holder.username.setText(currentPost.getAuthor()); - holder.postDate.setText(currentPost.getPostDate()); - if (currentPost.getPostNumber() != 0) - holder.postNum.setText(context.getString( - R.string.user_number_of_posts, currentPost.getPostNumber())); - else - holder.postNum.setText(""); - holder.subject.setText(currentPost.getSubject()); - holder.post.loadDataWithBaseURL("file:///android_asset/", currentPost.getContent(), "text/html", "UTF-8", null); - if (currentPost.getAttachedFiles() != null && currentPost.getAttachedFiles().size() != 0) { - holder.bodyFooterDivider.setVisibility(View.VISIBLE); - int filesTextColor; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - filesTextColor = context.getResources().getColor(R.color.accent, null); - } else //noinspection deprecation - filesTextColor = context.getResources().getColor(R.color.accent); - - holder.postFooter.removeAllViews(); - for (final ThmmyFile attachedFile : currentPost.getAttachedFiles()) { - final TextView attached = new TextView(context); - attached.setTextSize(10f); - attached.setClickable(true); - attached.setTypeface(Typeface.createFromAsset(context.getAssets() - , "fonts/fontawesome-webfont.ttf")); - attached.setText(faIconFromFilename(attachedFile.getFilename()) + " " - + attachedFile.getFilename() + attachedFile.getFileInfo()); - attached.setTextColor(filesTextColor); - attached.setPadding(0, 3, 0, 3); + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == VIEW_TYPE_POST) { + View itemView = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.activity_topic_post_row, parent, false); + return new PostViewHolder(itemView); + } else if (viewType == VIEW_TYPE_QUICK_REPLY) { + View view = LayoutInflater.from(parent.getContext()). + inflate(R.layout.activity_topic_quick_reply_row, parent, false); + view.findViewById(R.id.quick_reply_submit).setEnabled(true); + //Default post subject + replyDataHolder[replySubject] = "Re: " + topicTitle; + //Build quotes + String quotes = ""; + for (int quotePosition : toQuoteList) { + quotes += buildQuote(quotePosition); + } + if (!Objects.equals(quotes, "")) + replyDataHolder[replyText] = htmlToBBcode(quotes); + return new QuickReplyViewHolder(view, new CustomEditTextListener(replySubject), + new CustomEditTextListener(replyText)); + } + return null; + } - attached.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - ((BaseActivity) context).launchDownloadService(attachedFile); + @SuppressLint({"SetJavaScriptEnabled", "SetTextI18n"}) + @Override + public void onBindViewHolder(final RecyclerView.ViewHolder currentHolder, final int position) { + if (currentHolder instanceof PostViewHolder) { + final Post currentPost = postsList.get(position); + final PostViewHolder holder = (PostViewHolder) currentHolder; + + //Post's WebView parameters + holder.post.setClickable(true); + holder.post.setWebViewClient(new LinkLauncher()); + + //Avoids errors about layout having 0 width/height + holder.thumbnail.setMinimumWidth(1); + holder.thumbnail.setMinimumHeight(1); + //Sets thumbnail size + holder.thumbnail.setMaxWidth(THUMBNAIL_SIZE); + holder.thumbnail.setMaxHeight(THUMBNAIL_SIZE); + + //noinspection ConstantConditions + Picasso.with(context) + .load(currentPost.getThumbnailUrl()) + .resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE) + .centerCrop() + .error(ResourcesCompat.getDrawable(context.getResources() + , R.drawable.ic_default_user_thumbnail, null)) + .placeholder(ResourcesCompat.getDrawable(context.getResources() + , R.drawable.ic_default_user_thumbnail, null)) + .transform(new CircleTransform()) + .into(holder.thumbnail); + + //Sets username,submit date, index number, subject, post's and attached files texts + holder.username.setText(currentPost.getAuthor()); + holder.postDate.setText(currentPost.getPostDate()); + if (currentPost.getPostNumber() != 0) + holder.postNum.setText(context.getString( + R.string.user_number_of_posts, currentPost.getPostNumber())); + else + holder.postNum.setText(""); + holder.subject.setText(currentPost.getSubject()); + holder.post.loadDataWithBaseURL("file:///android_asset/", currentPost.getContent(), "text/html", "UTF-8", null); + if ((currentPost.getAttachedFiles() != null && currentPost.getAttachedFiles().size() != 0) + || (currentPost.getLastEdit() != null)) { + holder.bodyFooterDivider.setVisibility(View.VISIBLE); + holder.postFooter.removeAllViews(); + + if (currentPost.getAttachedFiles() != null && currentPost.getAttachedFiles().size() != 0) { + int filesTextColor; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + filesTextColor = context.getResources().getColor(R.color.accent, null); + } else //noinspection deprecation + filesTextColor = context.getResources().getColor(R.color.accent); + + for (final ThmmyFile attachedFile : currentPost.getAttachedFiles()) { + final TextView attached = new TextView(context); + attached.setTextSize(10f); + attached.setClickable(true); + attached.setTypeface(Typeface.createFromAsset(context.getAssets() + , "fonts/fontawesome-webfont.ttf")); + attached.setText(faIconFromFilename(attachedFile.getFilename()) + " " + + attachedFile.getFilename() + attachedFile.getFileInfo()); + attached.setTextColor(filesTextColor); + attached.setPadding(0, 3, 0, 3); + + attached.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ((BaseActivity) context).launchDownloadService(attachedFile); + } + }); + + holder.postFooter.addView(attached); } - }); + } + if (currentPost.getLastEdit() != null && currentPost.getLastEdit().length() > 0) { + int lastEditTextColor; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + lastEditTextColor = context.getResources().getColor(R.color.white, null); + } else //noinspection deprecation + lastEditTextColor = context.getResources().getColor(R.color.white); + + final TextView lastEdit = new TextView(context); + lastEdit.setTextSize(12f); + lastEdit.setText(currentPost.getLastEdit()); + lastEdit.setTextColor(lastEditTextColor); + lastEdit.setPadding(0, 3, 0, 3); + holder.postFooter.addView(lastEdit); + } + } else { + holder.bodyFooterDivider.setVisibility(View.GONE); + holder.postFooter.removeAllViews(); + } - holder.postFooter.addView(attached); + String mSpecialRank, mRank, mGender, mNumberOfPosts, mPersonalText; + int mNumberOfStars, mUserColor; + + if (!currentPost.isDeleted()) { //Sets user's extra info + mSpecialRank = currentPost.getSpecialRank(); + mRank = currentPost.getRank(); + mGender = currentPost.getGender(); + mNumberOfPosts = currentPost.getNumberOfPosts(); + mPersonalText = currentPost.getPersonalText(); + mNumberOfStars = currentPost.getNumberOfStars(); + mUserColor = currentPost.getUserColor(); + } else { + mSpecialRank = null; + mRank = null; + mGender = null; + mNumberOfPosts = null; + mPersonalText = null; + mNumberOfStars = 0; + mUserColor = 0; } - } else { - holder.bodyFooterDivider.setVisibility(View.GONE); - holder.postFooter.removeAllViews(); - } - String mSpecialRank, mRank, mGender, mNumberOfPosts, mPersonalText; - int mNumberOfStars, mUserColor; - - if (!currentPost.isDeleted()) { //Sets user's extra info - mSpecialRank = currentPost.getSpecialRank(); - mRank = currentPost.getRank(); - mGender = currentPost.getGender(); - mNumberOfPosts = currentPost.getNumberOfPosts(); - mPersonalText = currentPost.getPersonalText(); - mNumberOfStars = currentPost.getNumberOfStars(); - mUserColor = currentPost.getUserColor(); - } else { - mSpecialRank = null; - mRank = null; - mGender = null; - mNumberOfPosts = null; - mPersonalText = null; - mNumberOfStars = 0; - mUserColor = 0; - } + if (!Objects.equals(mSpecialRank, "") && mSpecialRank != null) { + holder.specialRank.setText(mSpecialRank); + holder.specialRank.setVisibility(View.VISIBLE); + } else + holder.specialRank.setVisibility(View.GONE); + if (!Objects.equals(mRank, "") && mRank != null) { + holder.rank.setText(mRank); + holder.rank.setVisibility(View.VISIBLE); + } else + holder.rank.setVisibility(View.GONE); + if (!Objects.equals(mGender, "") && mGender != null) { + holder.gender.setText(mGender); + holder.gender.setVisibility(View.VISIBLE); + } else + holder.gender.setVisibility(View.GONE); + if (!Objects.equals(mNumberOfPosts, "") && mNumberOfPosts != null) { + holder.numberOfPosts.setText(mNumberOfPosts); + holder.numberOfPosts.setVisibility(View.VISIBLE); + } else + holder.numberOfPosts.setVisibility(View.GONE); + if (!Objects.equals(mPersonalText, "") && mPersonalText != null) { + holder.personalText.setText("\"" + mPersonalText + "\""); + holder.personalText.setVisibility(View.VISIBLE); + } else + holder.personalText.setVisibility(View.GONE); + if (mNumberOfStars > 0) { + holder.stars.setTypeface(Typeface.createFromAsset(context.getAssets() + , "fonts/fontawesome-webfont.ttf")); - if (!Objects.equals(mSpecialRank, "") && mSpecialRank != null) { - holder.specialRank.setText(mSpecialRank); - holder.specialRank.setVisibility(View.VISIBLE); - } else - holder.specialRank.setVisibility(View.GONE); - if (!Objects.equals(mRank, "") && mRank != null) { - holder.rank.setText(mRank); - holder.rank.setVisibility(View.VISIBLE); - } else - holder.rank.setVisibility(View.GONE); - if (!Objects.equals(mGender, "") && mGender != null) { - holder.gender.setText(mGender); - holder.gender.setVisibility(View.VISIBLE); - } else - holder.gender.setVisibility(View.GONE); - if (!Objects.equals(mNumberOfPosts, "") && mNumberOfPosts != null) { - holder.numberOfPosts.setText(mNumberOfPosts); - holder.numberOfPosts.setVisibility(View.VISIBLE); - } else - holder.numberOfPosts.setVisibility(View.GONE); - if (!Objects.equals(mPersonalText, "") && mPersonalText != null) { - holder.personalText.setText("\"" + mPersonalText + "\""); - holder.personalText.setVisibility(View.VISIBLE); - } else - holder.personalText.setVisibility(View.GONE); - if (mNumberOfStars > 0) { - holder.stars.setTypeface(Typeface.createFromAsset(context.getAssets() - , "fonts/fontawesome-webfont.ttf")); - - String aStar = context.getResources().getString(R.string.fa_icon_star); - String usersStars = ""; - for (int i = 0; i < mNumberOfStars; ++i) { - usersStars += aStar; + String aStar = context.getResources().getString(R.string.fa_icon_star); + String usersStars = ""; + for (int i = 0; i < mNumberOfStars; ++i) { + usersStars += aStar; + } + holder.stars.setText(usersStars); + holder.stars.setTextColor(mUserColor); + holder.stars.setVisibility(View.VISIBLE); + } else + holder.stars.setVisibility(View.GONE); + //Special card for special member of the month! + if (mUserColor == TopicParser.USER_COLOR_PINK) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + holder.cardChildLinear.setBackground(context.getResources(). + getDrawable(R.drawable.member_of_the_month_card, null)); + } else //noinspection deprecation + holder.cardChildLinear.setBackground(context.getResources(). + getDrawable(R.drawable.member_of_the_month_card)); + } else holder.cardChildLinear.setBackground(null); + + //Avoid's view's visibility recycling + if (!currentPost.isDeleted() && viewProperties.get(position)[isUserExtraInfoVisibile]) { + holder.userExtraInfo.setVisibility(View.VISIBLE); + holder.userExtraInfo.setAlpha(1.0f); + } else { + holder.userExtraInfo.setVisibility(View.GONE); + holder.userExtraInfo.setAlpha(0.0f); } - holder.stars.setText(usersStars); - holder.stars.setTextColor(mUserColor); - holder.stars.setVisibility(View.VISIBLE); - } else - holder.stars.setVisibility(View.GONE); - //Special card for special member of the month! - if (mUserColor == TopicParser.USER_COLOR_PINK) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - holder.cardChildLinear.setBackground(context.getResources(). - getDrawable(R.drawable.member_of_the_month_card, null)); - } else //noinspection deprecation - holder.cardChildLinear.setBackground(context.getResources(). - getDrawable(R.drawable.member_of_the_month_card)); - } else holder.cardChildLinear.setBackground(null); - - //Avoid's view's visibility recycling - if (!currentPost.isDeleted() && viewProperties.get(position)[isUserExtraInfoVisibile]) { - holder.userExtraInfo.setVisibility(View.VISIBLE); - holder.userExtraInfo.setAlpha(1.0f); - } else { - holder.userExtraInfo.setVisibility(View.GONE); - holder.userExtraInfo.setAlpha(0.0f); - } - if (!currentPost.isDeleted()) { - //Sets graphics behavior - holder.header.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - //Clicking an expanded header starts profile activity - if (viewProperties.get(holder.getAdapterPosition())[isUserExtraInfoVisibile]) { + if (!currentPost.isDeleted()) { + //Sets graphics behavior + holder.thumbnail.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + //Clicking the thumbnail opens user's profile Intent intent = new Intent(context, ProfileActivity.class); Bundle extras = new Bundle(); extras.putString(BUNDLE_PROFILE_URL, currentPost.getProfileURL()); @@ -342,99 +353,138 @@ class TopicAdapter extends RecyclerView.Adapter { intent.setFlags(FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } + }); + holder.header.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + //Clicking the header makes it expand/collapse + boolean[] tmp = viewProperties.get(holder.getAdapterPosition()); + tmp[isUserExtraInfoVisibile] = !tmp[isUserExtraInfoVisibile]; + viewProperties.set(holder.getAdapterPosition(), tmp); + TopicAnimations.animateUserExtraInfoVisibility(holder.userExtraInfo); + } + }); + //Clicking the expanded part of a header (the extra info) makes it collapse + holder.userExtraInfo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + boolean[] tmp = viewProperties.get(holder.getAdapterPosition()); + tmp[isUserExtraInfoVisibile] = false; + viewProperties.set(holder.getAdapterPosition(), tmp); + + TopicAnimations.animateUserExtraInfoVisibility(v); + } + }); + } else { + holder.header.setOnClickListener(null); + holder.userExtraInfo.setOnClickListener(null); + } - boolean[] tmp = viewProperties.get(holder.getAdapterPosition()); - tmp[isUserExtraInfoVisibile] = !tmp[isUserExtraInfoVisibile]; - viewProperties.set(holder.getAdapterPosition(), tmp); - TopicAnimations.animateUserExtraInfoVisibility(holder.userExtraInfo); - } - }); - //Clicking the expanded part of a header (the extra info) makes it collapse - holder.userExtraInfo.setOnClickListener(new View.OnClickListener() { + //Avoid's view's visibility recycling + if (viewProperties.get(position)[isPostDateAndNumberVisibile]) { //Expanded + holder.postDateAndNumberExp.setVisibility(View.VISIBLE); + holder.postDateAndNumberExp.setAlpha(1.0f); + holder.postDateAndNumberExp.setTranslationY(0); + + holder.username.setMaxLines(Integer.MAX_VALUE); + holder.username.setEllipsize(null); + + holder.subject.setTextColor(Color.parseColor("#FFFFFF")); + holder.subject.setMaxLines(Integer.MAX_VALUE); + holder.subject.setEllipsize(null); + } else { //Collapsed + holder.postDateAndNumberExp.setVisibility(View.GONE); + holder.postDateAndNumberExp.setAlpha(0.0f); + holder.postDateAndNumberExp.setTranslationY(holder.postDateAndNumberExp.getHeight()); + + holder.username.setMaxLines(1); + holder.username.setEllipsize(TextUtils.TruncateAt.END); + + holder.subject.setTextColor(Color.parseColor("#757575")); + holder.subject.setMaxLines(1); + holder.subject.setEllipsize(TextUtils.TruncateAt.END); + } + //noinspection PointlessBooleanExpression,ConstantConditions + if (!BaseActivity.getSessionManager().isLoggedIn()) + holder.quoteToggle.setVisibility(View.GONE); + else { + if (viewProperties.get(position)[isQuoteButtonChecked]) + holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked); + else + holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_unchecked); + //Sets graphics behavior + holder.quoteToggle.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + boolean[] tmp = viewProperties.get(holder.getAdapterPosition()); + if (tmp[isQuoteButtonChecked]) { + if (toQuoteList.contains(postsList.indexOf(currentPost))) { + toQuoteList.remove(toQuoteList.indexOf(postsList.indexOf(currentPost))); + } else + Timber.i("An error occurred while trying to exclude post from" + + "toQuoteList, post wasn't there!"); + holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_unchecked); + } else { + toQuoteList.add(postsList.indexOf(currentPost)); + holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked); + } + tmp[isQuoteButtonChecked] = !tmp[isQuoteButtonChecked]; + viewProperties.set(holder.getAdapterPosition(), tmp); + } + }); + } + //Card expand/collapse when card is touched + holder.cardView.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(View view) { + //Change post's viewProperties accordingly boolean[] tmp = viewProperties.get(holder.getAdapterPosition()); - tmp[1] = false; + tmp[isPostDateAndNumberVisibile] = !tmp[isPostDateAndNumberVisibile]; viewProperties.set(holder.getAdapterPosition(), tmp); - TopicAnimations.animateUserExtraInfoVisibility(v); + TopicAnimations.animatePostExtraInfoVisibility(holder.postDateAndNumberExp + , holder.username, holder.subject + , Color.parseColor("#FFFFFF") + , Color.parseColor("#757575")); } }); - } else { - holder.header.setOnClickListener(null); - holder.userExtraInfo.setOnClickListener(null); - } - - //Avoid's view's visibility recycling - if (viewProperties.get(position)[isPostDateAndNumberVisibile]) { //Expanded - holder.postDateAndNumberExp.setVisibility(View.VISIBLE); - holder.postDateAndNumberExp.setAlpha(1.0f); - holder.postDateAndNumberExp.setTranslationY(0); - - holder.username.setMaxLines(Integer.MAX_VALUE); - holder.username.setEllipsize(null); - - holder.subject.setTextColor(Color.parseColor("#FFFFFF")); - holder.subject.setMaxLines(Integer.MAX_VALUE); - holder.subject.setEllipsize(null); - } else { //Collapsed - holder.postDateAndNumberExp.setVisibility(View.GONE); - holder.postDateAndNumberExp.setAlpha(0.0f); - holder.postDateAndNumberExp.setTranslationY(holder.postDateAndNumberExp.getHeight()); - - holder.username.setMaxLines(1); - holder.username.setEllipsize(TextUtils.TruncateAt.END); - - holder.subject.setTextColor(Color.parseColor("#757575")); - holder.subject.setMaxLines(1); - holder.subject.setEllipsize(TextUtils.TruncateAt.END); - } - //noinspection PointlessBooleanExpression,ConstantConditions - if (!BaseActivity.getSessionManager().isLoggedIn() || true) //Hide it until reply is implemented - holder.quoteToggle.setVisibility(View.GONE); - else { - if (viewProperties.get(position)[isQuoteButtonChecked]) - holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked); - else - holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_unchecked); - //Sets graphics behavior - holder.quoteToggle.setOnClickListener(new View.OnClickListener() { + //Also when post is clicked + holder.post.setOnTouchListener(new CustomTouchListener(holder.post, holder.cardView)); + } else if (currentHolder instanceof QuickReplyViewHolder) { + final QuickReplyViewHolder holder = (QuickReplyViewHolder) currentHolder; + + //noinspection ConstantConditions + Picasso.with(context) + .load(getSessionManager().getAvatarLink()) + .resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE) + .centerCrop() + .error(ResourcesCompat.getDrawable(context.getResources() + , R.drawable.ic_default_user_thumbnail, null)) + .placeholder(ResourcesCompat.getDrawable(context.getResources() + , R.drawable.ic_default_user_thumbnail, null)) + .transform(new CircleTransform()) + .into(holder.thumbnail); + holder.username.setText(getSessionManager().getUsername()); + holder.quickReplySubject.setText(replyDataHolder[replySubject]); + + if (replyDataHolder[replyText] != null && !Objects.equals(replyDataHolder[replyText], "")) + holder.quickReply.setText(replyDataHolder[replyText]); + + holder.submitButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - boolean[] tmp = viewProperties.get(holder.getAdapterPosition()); - if (tmp[isQuoteButtonChecked]) { - if (toQuoteList.contains(currentPost.getPostNumber())) { - toQuoteList.remove(toQuoteList.indexOf(currentPost.getPostNumber())); - } else - Report.i(TAG, "An error occurred while trying to exclude post from" + - "toQuoteList, post wasn't there!"); - holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_unchecked); - } else { - toQuoteList.add(currentPost.getPostNumber()); - holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked); - } - tmp[isQuoteButtonChecked] = !tmp[isQuoteButtonChecked]; - viewProperties.set(holder.getAdapterPosition(), tmp); + if (holder.quickReplySubject.getText().toString().isEmpty()) return; + if (holder.quickReply.getText().toString().isEmpty()) return; + holder.submitButton.setEnabled(false); + replyTask.execute(holder.quickReplySubject.getText().toString(), + holder.quickReply.getText().toString()); + + holder.quickReplySubject.getText().clear(); + holder.quickReplySubject.setText("Re: " + topicTitle); + holder.quickReply.getText().clear(); } }); } - //Card expand/collapse when card is touched - holder.cardView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - //Change post's viewProperties accordingly - boolean[] tmp = viewProperties.get(holder.getAdapterPosition()); - tmp[isPostDateAndNumberVisibile] = !tmp[isPostDateAndNumberVisibile]; - viewProperties.set(holder.getAdapterPosition(), tmp); - - TopicAnimations.animatePostExtraInfoVisibility(holder.postDateAndNumberExp - , holder.username, holder.subject - , Color.parseColor("#FFFFFF") - , Color.parseColor("#757575")); - } - }); - //Also when post is clicked - holder.post.setOnTouchListener(new CustomTouchListener(holder.post, holder.cardView)); } void customNotifyDataSetChanged(TopicActivity.TopicTask topicTask) { @@ -452,6 +502,79 @@ class TopicAdapter extends RecyclerView.Adapter { return postsList.size(); } + /** + * Custom {@link RecyclerView.ViewHolder} implementation + */ + private class PostViewHolder extends RecyclerView.ViewHolder { + final CardView cardView; + final LinearLayout cardChildLinear; + final FrameLayout postDateAndNumberExp; + final TextView postDate, postNum, username, subject; + final ImageView thumbnail; + final public WebView post; + final ImageButton quoteToggle; + final RelativeLayout header; + final LinearLayout userExtraInfo; + final View bodyFooterDivider; + final LinearLayout postFooter; + + final TextView specialRank, rank, gender, numberOfPosts, personalText, stars; + + PostViewHolder(View view) { + super(view); + //Initializes layout's graphic elements + //Standard stuff + cardView = (CardView) view.findViewById(R.id.card_view); + cardChildLinear = (LinearLayout) view.findViewById(R.id.card_child_linear); + postDateAndNumberExp = (FrameLayout) view.findViewById(R.id.post_date_and_number_exp); + postDate = (TextView) view.findViewById(R.id.post_date); + postNum = (TextView) view.findViewById(R.id.post_number); + thumbnail = (ImageView) view.findViewById(R.id.thumbnail); + username = (TextView) view.findViewById(R.id.username); + subject = (TextView) view.findViewById(R.id.subject); + post = (WebView) view.findViewById(R.id.post); + post.setBackgroundColor(Color.argb(1, 255, 255, 255)); + quoteToggle = (ImageButton) view.findViewById(R.id.toggle_quote_button); + bodyFooterDivider = view.findViewById(R.id.body_footer_divider); + postFooter = (LinearLayout) view.findViewById(R.id.post_footer); + + //User's extra info + header = (RelativeLayout) view.findViewById(R.id.header); + userExtraInfo = (LinearLayout) view.findViewById(R.id.user_extra_info); + specialRank = (TextView) view.findViewById(R.id.special_rank); + rank = (TextView) view.findViewById(R.id.rank); + gender = (TextView) view.findViewById(R.id.gender); + numberOfPosts = (TextView) view.findViewById(R.id.number_of_posts); + personalText = (TextView) view.findViewById(R.id.personal_text); + stars = (TextView) view.findViewById(R.id.stars); + } + } + + /** + * Custom {@link RecyclerView.ViewHolder} implementation + */ + private static class QuickReplyViewHolder extends RecyclerView.ViewHolder { + final ImageView thumbnail; + final TextView username; + final EditText quickReply, quickReplySubject; + final AppCompatImageButton submitButton; + final CustomEditTextListener replySubject, replyText; + + QuickReplyViewHolder(View quickReply, CustomEditTextListener replySubject + , CustomEditTextListener replyText) { + super(quickReply); + thumbnail = (ImageView) quickReply.findViewById(R.id.thumbnail); + username = (TextView) quickReply.findViewById(R.id.username); + this.quickReply = (EditText) quickReply.findViewById(R.id.quick_reply_text); + this.replyText = replyText; + this.quickReply.addTextChangedListener(replyText); + quickReplySubject = (EditText) quickReply.findViewById(R.id.quick_reply_subject); + this.replySubject = replySubject; + quickReplySubject.addTextChangedListener(replySubject); + submitButton = (AppCompatImageButton) quickReply.findViewById(R.id.quick_reply_submit); + } + } + /** * This class is a gesture detector for WebViews. It handles post's clicks, long clicks and * touch and drag. @@ -592,6 +715,84 @@ class TopicAdapter extends RecyclerView.Adapter { //Method always returns true as no url should be loaded in the WebViews return true; } + + } + + private class CustomEditTextListener implements TextWatcher { + private final int positionInDataHolder; + + CustomEditTextListener(int positionInDataHolder) { + this.positionInDataHolder = positionInDataHolder; + } + + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + replyDataHolder[positionInDataHolder] = charSequence.toString(); + } + + @Override + public void afterTextChanged(Editable editable) { + } + } + + @Nullable + private String buildQuote(int quotePosition) { + Date postDate = null; + { + String date = postsList.get(quotePosition).getPostDate(); + if (date != null) { + DateFormat format = new SimpleDateFormat("MMMM d, yyyy, h:m:s a", Locale.ENGLISH); + date = date.replace("Ιανουαρίου", "January"); + date = date.replace("Φεβρουαρίου", "February"); + date = date.replace("Μαρτίου", "March"); + date = date.replace("Απριλίου", "April"); + date = date.replace("Μαΐου", "May"); + date = date.replace("Ιουνίου", "June"); + date = date.replace("Ιουλίου", "July"); + date = date.replace("Αυγούστου", "August"); + date = date.replace("Σεπτεμβρίου", "September"); + date = date.replace("Οκτωβρίου", "October"); + date = date.replace("Νοεμβρίου", "November"); + date = date.replace("Δεκεμβρίου", "December"); + + if (date.contains("Today")) { + date = date.replace("Today at", + Calendar.getInstance().getDisplayName(Calendar.MONTH, + Calendar.LONG, Locale.ENGLISH) + + " " + Calendar.getInstance().get(Calendar.DAY_OF_MONTH) + + ", " + Calendar.getInstance().get(Calendar.YEAR) + ","); + } else if (date.contains("Σήμερα")) { + date = date.replace("Σήμερα στις", + Calendar.getInstance().getDisplayName(Calendar.MONTH, + Calendar.LONG, Locale.ENGLISH) + + " " + Calendar.getInstance().get(Calendar.DAY_OF_MONTH) + + ", " + Calendar.getInstance().get(Calendar.YEAR) + ","); + if (date.contains("πμ")) date = date.replace("πμ", "am"); + if (date.contains("μμ")) date = date.replace("μμ", "pm"); + } + try { + postDate = format.parse(date); + } catch (ParseException e) { + e.printStackTrace(); + } + } + } + if (postsList.get(quotePosition).getPostIndex() != 0) { + if (postDate != null) { + return "[quote author=" + postsList.get(quotePosition).getAuthor() + + " link=topic=" + ThmmyPage.getTopicId(loadedPageUrl) + ".msg" + + postsList.get(quotePosition).getPostIndex() + + "#msg" + postsList.get(quotePosition).getPostIndex() + + " date=" + postDate.getTime() / 1000 + "]" + + "\n" + postsList.get(quotePosition).getContent() + + "\n" + "[/quote]" + "\n\n"; + } + } + return null; } /** diff --git a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java index 9cab0649..3ce9c24d 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java +++ b/app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java @@ -1,6 +1,7 @@ package gr.thmmy.mthmmy.activities.topic; import android.graphics.Color; +import android.util.Log; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; @@ -17,7 +18,8 @@ import java.util.Objects; import gr.thmmy.mthmmy.model.Post; import gr.thmmy.mthmmy.model.ThmmyFile; import gr.thmmy.mthmmy.utils.ParseHelpers; -import mthmmy.utils.Report; +import timber.log.Timber; + /** * Singleton used for parsing a topic. @@ -36,12 +38,6 @@ class TopicParser { static final int USER_COLOR_PINK = Color.parseColor("#FF4081"); private static final int USER_COLOR_YELLOW = Color.parseColor("#FFEB3B"); - /** - * Debug Tag for logging debug output to LogCat - */ - @SuppressWarnings("unused") - private static final String TAG = "TopicParser"; - /** * Returns users currently viewing this topic. * @@ -160,7 +156,7 @@ class TopicParser { for (Element thisRow : postRows) { //Variables for Post constructor String p_userName, p_thumbnailUrl, p_subject, p_post, p_postDate, p_profileURL, p_rank, - p_specialRank, p_gender, p_personalText, p_numberOfPosts; + p_specialRank, p_gender, p_personalText, p_numberOfPosts, p_postLastEditDate; int p_postNum, p_postIndex, p_numberOfStars, p_userColor; boolean p_isDeleted = false; ArrayList p_attachedFiles; @@ -175,6 +171,7 @@ class TopicParser { p_numberOfStars = 0; p_userColor = USER_COLOR_YELLOW; p_attachedFiles = new ArrayList<>(); + p_postLastEditDate = null; //Language independent parsing //Finds thumbnail url @@ -190,21 +187,31 @@ class TopicParser { //Finds post's text p_post = ParseHelpers.youtubeEmbeddedFix(thisRow.select("div").select(".post").first()); - //Add stuff to make it work in WebView + //Adds stuff to make it work in WebView //style.css p_post = ("" + p_post); - //Find post's index + //Finds post's index //This is an int assigned by the forum used for post focusing and quotes, it is not //the same as reply index. Element postIndex = thisRow.select("a[name^=msg]").first(); - if (postIndex == null) - p_postIndex = NO_INDEX; - else { + if (postIndex != null) { String tmp = postIndex.attr("name"); p_postIndex = Integer.parseInt(tmp.substring(tmp.indexOf("msg") + 3)); + } else{ + postIndex = thisRow.select("div[id^=subject]").first(); + if (postIndex == null) + p_postIndex = NO_INDEX; + else{ + String tmp = postIndex.attr("id"); + p_postIndex = Integer.parseInt(tmp.substring(tmp.indexOf("subject") + 8)); + } } + Element postLastEditDate = thisRow.select("td.smalltext[id^=modified_]").first(); + if (postLastEditDate != null && !Objects.equals(postLastEditDate.text(), "")) + p_postLastEditDate = postLastEditDate.text(); + //Language dependent parsing Element userName; if (language.is(ParseHelpers.Language.GREEK)) { @@ -252,7 +259,7 @@ class TopicParser { try { attachedUrl = new URL(tmpAttachedFileUrlAndName.attr("href")); } catch (MalformedURLException e) { - Report.e(TAG, "Attached file malformed url", e); + Timber.e("Attached file malformed url", e); break; } String attachedFileName = tmpAttachedFileUrlAndName.text().substring(1); @@ -312,7 +319,7 @@ class TopicParser { try { attachedUrl = new URL(tmpAttachedFileUrlAndName.attr("href")); } catch (MalformedURLException e) { - Report.e(TAG, "Attached file malformed url", e); + Timber.e("Attached file malformed url", e); break; } String attachedFileName = tmpAttachedFileUrlAndName.text().substring(1); @@ -406,12 +413,12 @@ class TopicParser { parsedPostsList.add(new Post(p_thumbnailUrl, p_userName, p_subject, p_post, p_postIndex , p_postNum, p_postDate, p_profileURL, p_rank, p_specialRank, p_gender , p_numberOfPosts, p_personalText, p_numberOfStars, p_userColor - , p_attachedFiles)); + , p_attachedFiles, p_postLastEditDate)); } else { //Deleted user //Add new post in postsList, only standard information needed parsedPostsList.add(new Post(p_thumbnailUrl, p_userName, p_subject, p_post, p_postIndex - , p_postNum, p_postDate, p_userColor, p_attachedFiles)); + , p_postNum, p_postDate, p_userColor, p_attachedFiles, p_postLastEditDate)); } } return parsedPostsList; diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java index 501e2452..3be339d3 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java +++ b/app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java @@ -14,6 +14,7 @@ import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; +import android.view.MenuItem; import android.view.View; import android.widget.ImageButton; import android.widget.Toast; @@ -63,7 +64,8 @@ public abstract class BaseActivity extends AppCompatActivity { private static final String BOOKMARKED_TOPICS_KEY = "bookmarkedTopicsKey"; private static final String BOOKMARKED_BOARDS_KEY = "bookmarkedBoardsKey"; protected Bookmark thisPageBookmark; - protected ImageButton thisPageBookmarkButton; + private MenuItem thisPageBookmarkMenuButton; + private ImageButton thisPageBookmarkImageButton; private SharedPreferences bookmarksFile; private ArrayList topicsBookmarked; private ArrayList boardsBookmarked; @@ -79,6 +81,7 @@ public abstract class BaseActivity extends AppCompatActivity { super.onCreate(savedInstanceState); if (client == null) client = BaseApplication.getInstance().getClient(); //must check every time - e.g. + // they become null when app restarts after crash if (sessionManager == null) sessionManager = BaseApplication.getInstance().getSessionManager(); @@ -344,7 +347,7 @@ public abstract class BaseActivity extends AppCompatActivity { }); } - protected void updateDrawer() { + private void updateDrawer() { if (drawer != null) { if (!sessionManager.isLoggedIn()) //When logged out or if user is guest { @@ -373,7 +376,7 @@ public abstract class BaseActivity extends AppCompatActivity { * Result toast will always display a success, because when user chooses logout all data are * cleared regardless of the actual outcome */ - protected class LogoutTask extends AsyncTask { //Attempt logout + private class LogoutTask extends AsyncTask { //Attempt logout ProgressDialog progressDialog; protected Integer doInBackground(Void... voids) { @@ -407,42 +410,42 @@ public abstract class BaseActivity extends AppCompatActivity { return topicsBookmarked; } - protected void setTopicBookmark() { + protected void setTopicBookmark(MenuItem thisPageBookmarkMenuButton) { + this.thisPageBookmarkMenuButton = thisPageBookmarkMenuButton; if (thisPageBookmark.matchExists(topicsBookmarked)) { - thisPageBookmarkButton.setImageDrawable(bookmarked); + thisPageBookmarkMenuButton.setIcon(bookmarked); } else { - thisPageBookmarkButton.setImageDrawable(notBookmarked); + thisPageBookmarkMenuButton.setIcon(notBookmarked); + } + } + + protected void topicMenuBookmarkClick() { + if (thisPageBookmark.matchExists(topicsBookmarked)) { + thisPageBookmarkMenuButton.setIcon(notBookmarked); + toggleTopicToBookmarks(thisPageBookmark); + Toast.makeText(BaseActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show(); + } else { + thisPageBookmarkMenuButton.setIcon(bookmarked); + toggleTopicToBookmarks(thisPageBookmark); + Toast.makeText(BaseActivity.this, "Bookmark added", Toast.LENGTH_SHORT).show(); } - thisPageBookmarkButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (thisPageBookmark.matchExists(topicsBookmarked)) { - thisPageBookmarkButton.setImageDrawable(notBookmarked); - toggleTopicToBookmarks(thisPageBookmark); - Toast.makeText(BaseActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show(); - } else { - thisPageBookmarkButton.setImageDrawable(bookmarked); - toggleTopicToBookmarks(thisPageBookmark); - Toast.makeText(BaseActivity.this, "Bookmark added", Toast.LENGTH_SHORT).show(); - } - } - }); } - protected void setBoardBookmark() { + protected void setBoardBookmark(final ImageButton thisPageBookmarkImageButton) { + this.thisPageBookmarkImageButton = thisPageBookmarkImageButton; if (thisPageBookmark.matchExists(boardsBookmarked)) { - thisPageBookmarkButton.setImageDrawable(bookmarked); + thisPageBookmarkImageButton.setImageDrawable(bookmarked); } else { - thisPageBookmarkButton.setImageDrawable(notBookmarked); + thisPageBookmarkImageButton.setImageDrawable(notBookmarked); } - thisPageBookmarkButton.setOnClickListener(new View.OnClickListener() { + thisPageBookmarkImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (thisPageBookmark.matchExists(boardsBookmarked)) { - thisPageBookmarkButton.setImageDrawable(notBookmarked); + thisPageBookmarkImageButton.setImageDrawable(notBookmarked); Toast.makeText(BaseActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show(); } else { - thisPageBookmarkButton.setImageDrawable(bookmarked); + thisPageBookmarkImageButton.setImageDrawable(bookmarked); Toast.makeText(BaseActivity.this, "Bookmark added", Toast.LENGTH_SHORT).show(); } toggleBoardToBookmarks(thisPageBookmark); @@ -521,7 +524,7 @@ public abstract class BaseActivity extends AppCompatActivity { } //Display popup gor user to grant permission - public void requestPerms() { //Runtime permissions request for devices with API >= 23 + private void requestPerms() { //Runtime permissions request for devices with API >= 23 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { String[] PERMISSIONS_STORAGE = { Manifest.permission.READ_EXTERNAL_STORAGE, @@ -556,7 +559,7 @@ public abstract class BaseActivity extends AppCompatActivity { } //Uses temp file - called after permission grant - public void launchDownloadService() { + private void launchDownloadService() { if (checkPerms()) DownloadService.startActionDownload(this, tempThmmyFile.getFileUrl().toString()); diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java index 1e5ffdd6..3030cce0 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java +++ b/app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java @@ -19,11 +19,20 @@ import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader; import com.mikepenz.materialdrawer.util.DrawerImageLoader; import com.squareup.picasso.Picasso; +import java.io.IOException; +import java.util.Objects; import java.util.concurrent.TimeUnit; +import gr.thmmy.mthmmy.BuildConfig; import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.session.SessionManager; +import gr.thmmy.mthmmy.utils.CrashReportingTree; +import okhttp3.HttpUrl; +import okhttp3.Interceptor; import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import timber.log.Timber; public class BaseApplication extends Application { @@ -40,7 +49,7 @@ public class BaseApplication extends Application { //Display Metrics private static float dpHeight, dpWidth; - public static BaseApplication getInstance(){ + public static BaseApplication getInstance() { return baseApplication; } @@ -49,11 +58,33 @@ public class BaseApplication extends Application { super.onCreate(); baseApplication = this; //init singleton + if (BuildConfig.DEBUG) { + Timber.plant(new Timber.DebugTree()); + } else { + Timber.plant(new CrashReportingTree()); + } + SharedPreferences sharedPrefs = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE); SharedPrefsCookiePersistor sharedPrefsCookiePersistor = new SharedPrefsCookiePersistor(getApplicationContext()); PersistentCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), sharedPrefsCookiePersistor); client = new OkHttpClient.Builder() .cookieJar(cookieJar) + .addInterceptor(new Interceptor() { + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + HttpUrl oldUrl = chain.request().url(); + if (Objects.equals(chain.request().url().host(), "www.thmmy.gr")) { + if (!oldUrl.toString().contains("theme=4")) { + //Probably works but needs more testing: + HttpUrl newUrl = oldUrl.newBuilder().addQueryParameter("theme", "4").build(); + request = request.newBuilder().url(newUrl).build(); + } + } + return chain.proceed(request); + + } + }) .connectTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) @@ -71,6 +102,7 @@ public class BaseApplication extends Application { public void set(ImageView imageView, Uri uri, Drawable placeholder) { Picasso.with(imageView.getContext()).load(uri).placeholder(placeholder).into(imageView); } + @Override public void cancel(ImageView imageView) { Picasso.with(imageView.getContext()).cancelRequest(imageView); diff --git a/app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java b/app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java index c7b3d477..20bd8da2 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java +++ b/app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java @@ -5,8 +5,8 @@ import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; -import mthmmy.utils.Report; import okhttp3.OkHttpClient; +import timber.log.Timber; public abstract class BaseFragment extends Fragment { protected static final String ARG_SECTION_NUMBER = "SectionNumber"; @@ -15,7 +15,7 @@ public abstract class BaseFragment extends Fragment { protected FragmentInteractionListener fragmentInteractionListener; private String TAG; - protected int sectionNumber; + private int sectionNumber; protected static OkHttpClient client; @Override @@ -23,34 +23,34 @@ public abstract class BaseFragment extends Fragment { super.onCreate(savedInstanceState); TAG = getArguments().getString(ARG_TAG); sectionNumber = getArguments().getInt(ARG_SECTION_NUMBER); - if(client==null) + if (client == null) client = BaseApplication.getInstance().getClient(); //must check every time - e.g. // becomes null when app restarts after crash - Report.d(TAG, "onCreate"); + Timber.d("onCreate"); } @Override public void onStart() { super.onStart(); - Report.d(TAG, "onStart"); + Timber.d("onStart"); } @Override public void onResume() { super.onResume(); - Report.d(TAG, "onResume"); + Timber.d("onResume"); } @Override public void onPause() { super.onPause(); - Report.d(TAG, "onPause"); + Timber.d("onPause"); } @Override public void onStop() { super.onStop(); - Report.d(TAG, "onStop"); + Timber.d("onStop"); } @Override @@ -76,5 +76,6 @@ public abstract class BaseFragment extends Fragment { * the activity that contains it, to allow communication upon interaction, * between the fragment and the activity/ other fragments */ - public interface FragmentInteractionListener {} + public interface FragmentInteractionListener { + } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/Category.java b/app/src/main/java/gr/thmmy/mthmmy/model/Category.java index ce43c42d..46142bbf 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/model/Category.java +++ b/app/src/main/java/gr/thmmy/mthmmy/model/Category.java @@ -7,8 +7,7 @@ import java.util.List; import static android.R.attr.id; -public class Category implements Parent -{ +public class Category implements Parent { private final String title; private final String categoryURL; private boolean expanded = false; diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/Post.java b/app/src/main/java/gr/thmmy/mthmmy/model/Post.java index 15b6afe6..baf8cc20 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/model/Post.java +++ b/app/src/main/java/gr/thmmy/mthmmy/model/Post.java @@ -27,6 +27,7 @@ public class Post { private final boolean isDeleted; private final int userColor; private final ArrayList attachedFiles; + private final String lastEdit; //Extra info private final String profileURL; @@ -57,6 +58,7 @@ public class Post { personalText = ""; numberOfStars = 0; attachedFiles = null; + lastEdit = null; } /** @@ -80,12 +82,13 @@ public class Post { * @param numberOfStars author's number of stars * @param userColor author's user color * @param attachedFiles post's attached files + * @param lastEdit post's last edit date */ public Post(@Nullable String thumbnailUrl, String author, String subject, String content , int postIndex, int postNumber, String postDate, String profileURl, @Nullable String rank , @Nullable String special_rank, @Nullable String gender, @Nullable String numberOfPosts , @Nullable String personalText, int numberOfStars, int userColor - , @Nullable ArrayList attachedFiles) { + , @Nullable ArrayList attachedFiles, @Nullable String lastEdit) { if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null; else this.thumbnailUrl = thumbnailUrl; this.author = author; @@ -97,6 +100,7 @@ public class Post { this.isDeleted = false; this.userColor = userColor; this.attachedFiles = attachedFiles; + this.lastEdit = lastEdit; this.profileURL = profileURl; this.rank = rank; this.specialRank = special_rank; @@ -120,10 +124,11 @@ public class Post { * @param postDate date of submission * @param userColor author's user color * @param attachedFiles post's attached files + * @param lastEdit post's last edit date */ public Post(@Nullable String thumbnailUrl, String author, String subject, String content , int postIndex, int postNumber, String postDate, int userColor - , @Nullable ArrayList attachedFiles) { + , @Nullable ArrayList attachedFiles, @Nullable String lastEdit) { if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null; else this.thumbnailUrl = thumbnailUrl; this.author = author; @@ -135,6 +140,7 @@ public class Post { this.isDeleted = true; this.userColor = userColor; this.attachedFiles = attachedFiles; + this.lastEdit = lastEdit; profileURL = null; rank = "Rank"; specialRank = "Special rank"; @@ -310,4 +316,14 @@ public class Post { public ArrayList getAttachedFiles() { return attachedFiles; } + + /** + * Gets this post's last edit date or null if post hasn't been edited. + * + * @return date of last edit or null + */ + @Nullable + public String getLastEdit() { + return lastEdit; + } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java b/app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java index cc9a1781..9e935f69 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java +++ b/app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java @@ -4,7 +4,7 @@ import android.net.Uri; import java.util.Objects; -import mthmmy.utils.Report; +import timber.log.Timber; /** * This class consists exclusively of static classes (enums) and methods (excluding methods of inner @@ -162,7 +162,7 @@ public class ThmmyPage { || Objects.equals(uriString, "https://www.thmmy.gr") || Objects.equals(uriString, "https://www.thmmy.gr/smf/index.php")) return PageCategory.INDEX; - Report.v(TAG, "Unknown thmmy link found, link: " + uriString); + Timber.v("Unknown thmmy link found, link: " + uriString); return PageCategory.UNKNOWN_THMMY; } return PageCategory.NOT_THMMY; @@ -170,7 +170,10 @@ public class ThmmyPage { public static String getBoardId(String boardUrl) { if (resolvePageCategory(Uri.parse(boardUrl)) == PageCategory.BOARD) { - return boardUrl.substring(boardUrl.indexOf("board=") + 6, boardUrl.lastIndexOf(".")); + String returnString = boardUrl.substring(boardUrl.indexOf("board=") + 6); + if (returnString.contains(".")) + returnString = boardUrl.substring(boardUrl.indexOf("board=") + 6, boardUrl.lastIndexOf(".")); + return returnString; } return null; } diff --git a/app/src/main/java/gr/thmmy/mthmmy/receiver/Receiver.java b/app/src/main/java/gr/thmmy/mthmmy/receiver/Receiver.java index 3e811134..d93f4272 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/receiver/Receiver.java +++ b/app/src/main/java/gr/thmmy/mthmmy/receiver/Receiver.java @@ -14,7 +14,8 @@ import android.webkit.MimeTypeMap; import java.io.File; import gr.thmmy.mthmmy.R; -import mthmmy.utils.Report; + +import timber.log.Timber; import static gr.thmmy.mthmmy.services.DownloadService.ACTION_DOWNLOAD; import static gr.thmmy.mthmmy.services.DownloadService.COMPLETED; @@ -28,7 +29,6 @@ import static gr.thmmy.mthmmy.services.DownloadService.SAVE_DIR; import static gr.thmmy.mthmmy.services.DownloadService.STARTED; public class Receiver extends BroadcastReceiver { - private static final String TAG = "BroadcastReceiver"; public Receiver() { } @@ -72,7 +72,7 @@ public class Receiver extends BroadcastReceiver { builder.setContentIntent(pendingIntent); } else - Report.w(TAG, "File doesn't exist."); + Timber.w("File doesn't exist."); } Notification notification = builder.build(); notificationManager.notify(id, notification); diff --git a/app/src/main/java/gr/thmmy/mthmmy/services/DownloadService.java b/app/src/main/java/gr/thmmy/mthmmy/services/DownloadService.java index 7339fa73..52883ef4 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/services/DownloadService.java +++ b/app/src/main/java/gr/thmmy/mthmmy/services/DownloadService.java @@ -13,12 +13,13 @@ import java.io.IOException; import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.receiver.Receiver; -import mthmmy.utils.Report; + import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okio.BufferedSink; import okio.Okio; +import timber.log.Timber; /** * An {@link IntentService} subclass for handling asynchronous task requests in @@ -26,7 +27,7 @@ import okio.Okio; */ public class DownloadService extends IntentService { private static final String TAG = "DownloadService"; - private static int sDownloadId =0; + private static int sDownloadId = 0; private Receiver receiver; @@ -47,7 +48,6 @@ public class DownloadService extends IntentService { public static final String FAILED = "Failed"; - public DownloadService() { super("DownloadService"); } @@ -108,37 +108,31 @@ public class DownloadService extends IntentService { Response response = client.newCall(request).execute(); String contentType = response.headers("Content-Type").toString(); //check if link provides a binary file - if(contentType.equals("[application/octet-stream]")) - { + if (contentType.equals("[application/octet-stream]")) { fileName = response.headers("Content-Disposition").toString().split("\"")[1]; File dirPath = new File(SAVE_DIR); - if(!dirPath.isDirectory()) - { - if(dirPath.mkdirs()) - Report.i(TAG, "mTHMMY's directory created successfully!"); + if (!dirPath.isDirectory()) { + if (dirPath.mkdirs()) + Timber.i("mTHMMY's directory created successfully!"); else - Report.e(TAG, "Couldn't create mTHMMY's directory..."); + Timber.e("Couldn't create mTHMMY's directory..."); } String nameFormat; String[] tokens = fileName.split("\\.(?=[^\\.]+$)"); - if(tokens.length!=2) - { - Report.w(TAG, "Couldn't get file extension..."); + if (tokens.length != 2) { + Timber.w("Couldn't get file extension..."); nameFormat = fileName + "(%d)"; - } - else + } else nameFormat = tokens[0] + "(%d)." + tokens[1]; - - File file = new File(dirPath, fileName); - for (int i = 1;;i++) { + for (int i = 1; ; i++) { if (!file.exists()) { break; } @@ -148,29 +142,26 @@ public class DownloadService extends IntentService { fileName = file.getName(); - Report.v(TAG, "Started saving file " + fileName); + Timber.v("Started saving file " + fileName); sendNotification(downloadId, STARTED, fileName); sink = Okio.buffer(Okio.sink(file)); sink.writeAll(response.body().source()); sink.flush(); - Report.i(TAG, "Download OK!"); + Timber.i("Download OK!"); sendNotification(downloadId, COMPLETED, fileName); - } - else - Report.e(TAG, "Response not a binary file!"); - } - catch (FileNotFoundException e){ - Report.i(TAG, "Download failed..."); - Report.e(TAG, "FileNotFound", e); + } else + Timber.e("Response not a binary file!"); + } catch (FileNotFoundException e) { + Timber.i("Download failed..."); + Timber.e("FileNotFound", e); sendNotification(downloadId, FAILED, fileName); - } - catch (IOException e){ - Report.i(TAG, "Download failed..."); - Report.e(TAG, "IOException", e); + } catch (IOException e) { + Timber.i("Download failed..."); + Timber.e("IOException", e); sendNotification(downloadId, FAILED, fileName); } finally { - if (sink!= null) { + if (sink != null) { try { sink.close(); } catch (IOException e) { @@ -180,9 +171,8 @@ public class DownloadService extends IntentService { } } - private void sendNotification(int downloadId, String type, @NonNull String fileName) - { - Intent intent = new Intent(ACTION_DOWNLOAD); + private void sendNotification(int downloadId, String type, @NonNull String fileName) { + Intent intent = new Intent(ACTION_DOWNLOAD); switch (type) { case STARTED: { intent.putExtra(EXTRA_NOTIFICATION_TITLE, "Download Started"); @@ -202,8 +192,8 @@ public class DownloadService extends IntentService { intent.putExtra(EXTRA_NOTIFICATION_TICKER, "Download Failed"); break; } - default:{ - Report.wtf(TAG, "Invalid notification case!"); + default: { + Timber.e("Invalid notification case!"); return; } } diff --git a/app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java b/app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java index 2321b88a..1455fb27 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java +++ b/app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java @@ -18,7 +18,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import gr.thmmy.mthmmy.utils.exceptions.ParseException; -import mthmmy.utils.Report; import okhttp3.Cookie; import okhttp3.FormBody; import okhttp3.HttpUrl; @@ -26,15 +25,13 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; +import timber.log.Timber; /** * This class handles all session related operations (e.g. login, logout) * and stores data to SharedPreferences (session information and cookies). */ public class SessionManager { - //Class TAG - private static final String TAG = "SessionManager"; - //Generic constants public static final HttpUrl indexUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?theme=4"); public static final HttpUrl forumUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=forum;theme=4"); @@ -51,12 +48,12 @@ public class SessionManager { public static final int EXCEPTION = 6; // Client & Cookies - private OkHttpClient client; - private PersistentCookieJar cookieJar; - private SharedPrefsCookiePersistor cookiePersistor; //Used to explicitly edit cookies in cookieJar + private final OkHttpClient client; + private final PersistentCookieJar cookieJar; + private final SharedPrefsCookiePersistor cookiePersistor; //Used to explicitly edit cookies in cookieJar //Shared Preferences & its keys - private SharedPreferences sharedPrefs; + private final SharedPreferences sharedPrefs; private static final String USERNAME = "Username"; private static final String AVATAR_LINK = "AvatarLink"; private static final String HAS_AVATAR = "HasAvatar"; @@ -80,7 +77,7 @@ public class SessionManager { * Always call it in a separate thread. */ public int login(String... strings) { - Report.i(TAG, "Logging in..."); + Timber.i("Logging in..."); //Build the login request for each case Request request; @@ -112,9 +109,9 @@ public class SessionManager { Elements unreadRepliesLinks = document.select("a[href=https://www.thmmy.gr/smf/index.php?action=unreadreplies]"); - if (unreadRepliesLinks.size()>=2) //Normally it's just == 2, but who knows what can be posted by users + if (unreadRepliesLinks.size() >= 2) //Normally it's just == 2, but who knows what can be posted by users { - Report.i(TAG, "Login successful!"); + Timber.i("Login successful!"); setPersistentCookieSession(); //Store cookies //Edit SharedPreferences, save session's data @@ -133,18 +130,18 @@ public class SessionManager { return SUCCESS; } else { - Report.i(TAG, "Login failed."); + Timber.i("Login failed."); //Investigate login failure Elements error = document.select("b:contains(That username does not exist.)"); if (error.size() == 1) { //Wrong username - Report.i(TAG, "Wrong Username"); + Timber.i("Wrong Username"); return WRONG_USER; } error = document.select("body:contains(Password incorrect)"); if (error.size() == 1) { //Wrong password - Report.i(TAG, "Wrong Password"); + Timber.i("Wrong Password"); return WRONG_PASSWORD; } @@ -154,13 +151,13 @@ public class SessionManager { } //Handle exception } catch (InterruptedIOException e) { - Report.i(TAG, "Login InterruptedIOException"); //users cancels LoginTask + Timber.i("Login InterruptedIOException"); //users cancels LoginTask return CANCELLED; } catch (IOException e) { - Report.w(TAG, "Login IOException", e); + Timber.w("Login IOException", e); return CONNECTION_ERROR; } catch (Exception e) { - Report.w(TAG, "Login Exception (other)", e); + Timber.w("Login Exception (other)", e); return EXCEPTION; } } @@ -175,7 +172,7 @@ public class SessionManager { * fragments' data are retrieved). */ public void validateSession() { - Report.i(TAG, "Validating session..."); + Timber.i("Validating session..."); if (isLoggedIn()) { int loginResult = login(); @@ -192,7 +189,7 @@ public class SessionManager { * Call this function when user explicitly chooses to continue as a guest (UI thread). */ public void guestLogin() { - Report.i("TAG", "Continuing as a guest, as chosen by the user."); + Timber.i("Continuing as a guest, as chosen by the user."); clearSessionData(); sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, false).apply(); } @@ -202,7 +199,7 @@ public class SessionManager { * Logout function. Always call it in a separate thread. */ public int logout() { - Report.i(TAG, "Logging out..."); + Timber.i("Logging out..."); Request request = new Request.Builder() .url(sharedPrefs.getString(LOGOUT_LINK, "LogoutLink")) @@ -216,17 +213,17 @@ public class SessionManager { Elements loginButton = document.select("[value=Login]"); //Attempt to find login button if (!loginButton.isEmpty()) //If login button exists, logout was successful { - Report.i(TAG, "Logout successful!"); + Timber.i("Logout successful!"); return SUCCESS; } else { - Report.i(TAG, "Logout failed."); + Timber.i("Logout failed."); return FAILURE; } } catch (IOException e) { - Report.w(TAG, "Logout IOException", e); + Timber.w("Logout IOException", e); return CONNECTION_ERROR; } catch (Exception e) { - Report.w(TAG, "Logout Exception", e); + Timber.w("Logout Exception", e); return EXCEPTION; } finally { //All data should always be cleared from device regardless the result of logout @@ -288,11 +285,11 @@ public class SessionManager { sharedPrefs.edit().clear().apply(); //Clear session data sharedPrefs.edit().putString(USERNAME, guestName).apply(); sharedPrefs.edit().putBoolean(LOGGED_IN, false).apply(); //User logs out - Report.i(TAG, "Session data cleared."); + Timber.i("Session data cleared."); } @NonNull - private String extractUserName(@NonNull Document doc){ + private String extractUserName(@NonNull Document doc) { //Scribbles2 Theme Elements user = doc.select("div[id=myuser] > h3"); String userName = null; @@ -304,8 +301,7 @@ public class SessionManager { Matcher matcher = pattern.matcher(txt); if (matcher.find()) userName = matcher.group(1); - } - else { + } else { //Helios_Multi and SMF_oneBlue user = doc.select("td.smalltext[width=100%] b"); if (user.size() == 1) @@ -317,22 +313,22 @@ public class SessionManager { userName = user.first().ownText(); } } - - if(userName != null && !userName.isEmpty()) + + if (userName != null && !userName.isEmpty()) return userName; - Report.e(TAG, "ParseException", new ParseException("Parsing failed(username extraction)")); - return "User"; //return a default username + Timber.e("ParseException", new ParseException("Parsing failed(username extraction)")); + return "User"; //return a default username } @Nullable private String extractAvatarLink(@NonNull Document doc) { - Elements avatar = doc.getElementsByClass("avatar"); - if (!avatar.isEmpty()) - return avatar.first().attr("src"); + Elements avatar = doc.getElementsByClass("avatar"); + if (!avatar.isEmpty()) + return avatar.first().attr("src"); - Report.e(TAG, "Extracting avatar's link failed!"); + Timber.i("Extracting avatar's link failed!"); return null; } @@ -340,14 +336,13 @@ public class SessionManager { private String extractLogoutLink(@NonNull Document doc) { Elements logoutLink = doc.select("a[href^=https://www.thmmy.gr/smf/index.php?action=logout;sesc=]"); - if (!logoutLink.isEmpty()) - { + if (!logoutLink.isEmpty()) { String link = logoutLink.first().attr("href"); - if(link != null && !link.isEmpty()) + if (link != null && !link.isEmpty()) return link; } - Report.e(TAG, "ParseException", new ParseException("Parsing failed(logoutLink extraction)")); - return "https://www.thmmy.gr/smf/index.php?action=logout"; //return a default link + Timber.e("ParseException", new ParseException("Parsing failed(logoutLink extraction)")); + return "https://www.thmmy.gr/smf/index.php?action=logout"; //return a default link } //----------------------------------OTHER FUNCTIONS END----------------------------------------- diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/CenterVerticalSpan.java b/app/src/main/java/gr/thmmy/mthmmy/utils/CenterVerticalSpan.java index 949a9736..12f0a9e6 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/CenterVerticalSpan.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/CenterVerticalSpan.java @@ -1,20 +1,23 @@ package gr.thmmy.mthmmy.utils; -import android.text.TextPaint; -import android.text.style.SuperscriptSpan; -import android.util.Log; - -public class CenterVerticalSpan extends SuperscriptSpan { - public CenterVerticalSpan() { - } +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.support.annotation.NonNull; +import android.text.style.ReplacementSpan; +public class CenterVerticalSpan extends ReplacementSpan { @Override - public void updateDrawState(TextPaint textPaint) { - textPaint.baselineShift -= 7f; + public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { + text = text.subSequence(start, end); + return (int) paint.measureText(text.toString()); } @Override - public void updateMeasureState(TextPaint tp) { - updateDrawState(tp); + public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom,@NonNull Paint paint) { + text = text.subSequence(start, end); + Rect charSize = new Rect(); + paint.getTextBounds(text.toString(), 0, 1, charSize); + canvas.drawText(text.toString(), x, (bottom + charSize.height()) / 2f, paint); } } \ No newline at end of file diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReportingTree.java b/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReportingTree.java new file mode 100644 index 00000000..2aeca9a2 --- /dev/null +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/CrashReportingTree.java @@ -0,0 +1,34 @@ +package gr.thmmy.mthmmy.utils; + +import android.util.Log; + +import com.google.firebase.crash.FirebaseCrash; + +import gr.thmmy.mthmmy.utils.exceptions.UnknownException; +import timber.log.Timber; + +public class CrashReportingTree extends Timber.Tree { + @Override + protected void log(int priority, String tag, String message, Throwable t) { + if (priority == Log.VERBOSE || priority == Log.DEBUG) { + return; + } + + String level="A"; + + if (priority == Log.INFO) + level = "I"; + else if (priority == Log.WARN) + level = "W"; + else if(priority == Log.ERROR) + level = "E"; + + FirebaseCrash.log(level + "/" + tag + ": " + message); + + if(t==null) + t = new UnknownException("UnknownException"); + + if ((priority == Log.ERROR)) + FirebaseCrash.report(t); + } +} diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/ParseHelpers.java b/app/src/main/java/gr/thmmy/mthmmy/utils/ParseHelpers.java index 2520cc89..75a9fa72 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/ParseHelpers.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/ParseHelpers.java @@ -153,15 +153,16 @@ public class ParseHelpers { fixed = fixed.replace( fixed.substring(fixed.indexOf("") + 9) , "
    " - + "" + "" + "" + "\"\"" + "
    "); + ++tmp_counter; } return fixed; } diff --git a/app/src/main/java/gr/thmmy/mthmmy/utils/exceptions/UnknownException.java b/app/src/main/java/gr/thmmy/mthmmy/utils/exceptions/UnknownException.java index fc0e7f65..81c890ad 100644 --- a/app/src/main/java/gr/thmmy/mthmmy/utils/exceptions/UnknownException.java +++ b/app/src/main/java/gr/thmmy/mthmmy/utils/exceptions/UnknownException.java @@ -5,10 +5,10 @@ package gr.thmmy.mthmmy.utils.exceptions; * exception is set, to report to FireBase. */ public class UnknownException extends Exception { - public UnknownException() {} + public UnknownException() { + } - public UnknownException(String message) - { + public UnknownException(String message) { super(message); } } diff --git a/app/src/main/res/drawable-hdpi/ic_info.png b/app/src/main/res/drawable-hdpi/ic_info.png new file mode 100644 index 00000000..0932c17c Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_info.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_send.png b/app/src/main/res/drawable-hdpi/ic_send.png new file mode 100644 index 00000000..a8fd035d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_send.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_info.png b/app/src/main/res/drawable-mdpi/ic_info.png new file mode 100644 index 00000000..03cf044b Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_info.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_send.png b/app/src/main/res/drawable-mdpi/ic_send.png new file mode 100644 index 00000000..c914ae97 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_send.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_info.png b/app/src/main/res/drawable-xhdpi/ic_info.png new file mode 100644 index 00000000..33ec308a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_info.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_send.png b/app/src/main/res/drawable-xhdpi/ic_send.png new file mode 100644 index 00000000..c104e00d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_send.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_info.png b/app/src/main/res/drawable-xxhdpi/ic_info.png new file mode 100644 index 00000000..f8e64ad7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_info.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_send.png b/app/src/main/res/drawable-xxhdpi/ic_send.png new file mode 100644 index 00000000..0bb93fd0 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_send.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_info.png b/app/src/main/res/drawable-xxxhdpi/ic_info.png new file mode 100644 index 00000000..cb42f367 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_info.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_send.png b/app/src/main/res/drawable-xxxhdpi/ic_send.png new file mode 100644 index 00000000..ca6a867c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_send.png differ diff --git a/app/src/main/res/layout/activity_topic.xml b/app/src/main/res/layout/activity_topic.xml index 23b12737..e4735bcc 100644 --- a/app/src/main/res/layout/activity_topic.xml +++ b/app/src/main/res/layout/activity_topic.xml @@ -23,15 +23,19 @@ android:background="?attr/colorPrimary" app:popupTheme="@style/ToolbarTheme"> - + android:ellipsize="marquee" + android:fillViewport="true" + android:focusable="true" + android:focusableInTouchMode="true" + android:marqueeRepeatLimit="marquee_forever" + android:maxLines="1" + android:scrollHorizontally="true" + android:textColor="@color/white" + /> diff --git a/app/src/main/res/layout/activity_topic_post_row.xml b/app/src/main/res/layout/activity_topic_post_row.xml index 6c56547e..677a58db 100644 --- a/app/src/main/res/layout/activity_topic_post_row.xml +++ b/app/src/main/res/layout/activity_topic_post_row.xml @@ -1,12 +1,13 @@ - + + android:text="@string/post_subject"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_topic_info.xml b/app/src/main/res/layout/dialog_topic_info.xml new file mode 100644 index 00000000..47b8ec31 --- /dev/null +++ b/app/src/main/res/layout/dialog_topic_info.xml @@ -0,0 +1,33 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/topic_menu.xml b/app/src/main/res/menu/topic_menu.xml new file mode 100644 index 00000000..16c60e0a --- /dev/null +++ b/app/src/main/res/menu/topic_menu.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 45d9efda..c864d4e3 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -16,6 +16,8 @@ #323232 #3C3F41 #8B8B8B + + #FF9800 #FFFFFF #CCCCCC diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dffad4fe..d95e5e63 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,6 +44,9 @@ Page next last + Quick reply… + Subject… + Submit Username diff --git a/app/src/release/java/mthmmy.utils/Report.java b/app/src/release/java/mthmmy.utils/Report.java deleted file mode 100644 index 4a6a46b8..00000000 --- a/app/src/release/java/mthmmy.utils/Report.java +++ /dev/null @@ -1,96 +0,0 @@ -package mthmmy.utils; - -import com.google.firebase.crash.FirebaseCrash; - -import gr.thmmy.mthmmy.utils.exceptions.UnknownException; - -public class Report -{ - - public static void v (String TAG, String message) - { - log("V", TAG, message); - } - - public static void v (String TAG, String message, Throwable tr) - { - exception("V", TAG, message, tr); - } - - public static void d (String TAG, String message) - { - log("D", TAG, message); - } - - public static void d (String TAG, String message, Throwable tr) - { - exception("D", TAG, message, tr); - } - - public static void i (String TAG, String message) - { - log("I", TAG, message); - } - - public static void i (String TAG, String message, Throwable tr) - { - exception("I", TAG, message, tr); - } - - public static void w (String TAG, String message) - { - log("W", TAG, message); - } - - public static void w (String TAG, String message, Throwable tr) - { - exception("W", TAG, message, tr); - } - - public static void e (String TAG, String message) - { - log("E", TAG, message); - } - - public static void e (String TAG, String message, Throwable tr) - { - exception("E", TAG, message, tr); - } - - public static void wtf (String TAG, String message) - { - log("WTF", TAG, message); - } - - public static void wtf (String TAG, String message, Throwable tr) - { - exception("WTF", TAG, message, tr); - } - - private static void log(String level, String TAG, String message) - { - if(!level.equals("V")&&!level.equals("D")) //don't log V and D levels - { - FirebaseCrash.log(level + "/" + TAG + ": " + message); - if(level.equals("E")||level.equals("WTF")) //report only serious exceptions - FirebaseCrash.report(new UnknownException("UnknownException")); - } - } - - private static void exception(String level, String TAG, String message, Throwable tr) - { - if(!level.equals("V")&&!level.equals("D")) //don't log V and D levels - { - FirebaseCrash.log(level + "/" + TAG + ": " + message + ": " + tr.getMessage()); - if(level.equals("E")||level.equals("WTF")) //report only serious exceptions - FirebaseCrash.report(tr); - } - } - - /** - * Does nothing in release. - */ - public static void longMessage(String TAG, String level, String message) {return;} - - -} diff --git a/build.gradle b/build.gradle index 25e7e862..a40a0e30 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { maven { url "https://jitpack.io" } } dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath 'com.android.tools.build:gradle:2.3.0' classpath 'com.google.gms:google-services:3.0.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/doc/forum_post.txt b/doc/forum_post.txt index f51905fa..1957af12 100644 --- a/doc/forum_post.txt +++ b/doc/forum_post.txt @@ -1,34 +1,35 @@ [center][size=25pt][b]Introduction[/b][/size][/center] - [url=https://www.thmmy.gr/smf/index.php?topic=67629.0][/url], project Android . +Από τη συζήτηση [url=https://www.thmmy.gr/smf/index.php?topic=67629.0]εδώ[/url], ξεκίνησε ένα project με στόχο τη δημιουργία εφαρμογής για Android κινητά που θα συγκεντρώνει και θα κάνει πιο εύκολη τη πρόσβαση σε μερικές από τις βασικές σελίδες και υπηρεσίες που αφορούν τη σχολή και χρησιμοποιούμε καθημερινά. - 2+ 9000 [size=9pt](.java, .xml )[/size], ( closed alpha phase) Google Play Store! +Μετά από 2+ μήνες δουλειάς και πάνω από 9000 γραμμές κώδικα (.java, .xml και άλλα), σήμερα (16/1/2017) ανεβάσαμε για πρώτη φορά την εφαρμογή (σε closed alpha phase) στο Google Play Store! - forum. , ethmmy , instant chat .., , . +Προς το παρόν η εφαρμογή υποστηρίζει κάποιες από τις βασικές λειτουργίες του forum. Σταδιακά θα ενσωματώνονται όλο και περισσότερες λειτουργίες, για παράδειγμα ενός συστήματος ειδοποιήσεων για νέες ανακοινώσεις του ethmmy και της σελίδας της γραμματείας, instant chat κ.ά., ανάλογα πάντα με τις ιδέες, τη διάθεση και την ενέργεια όσων θα συμμετέχουν. - project L, iason1907 [url=https://www.thmmy.gr/smf/index.php?topic=67565.msg1163192#msg1163192]nohponex[/url]. +Αυτή τη στιγμή με το project ασχολούμαστε εγώ και ο L, ενώ έχουν βοηθήσει ο iason1907 και ο [url=https://www.thmmy.gr/smf/index.php?topic=67565.msg1163192#msg1163192]nohponex[/url]. [hr] -[center][size=25pt][b] contributors[/b][/size] +[center][size=25pt][b]Κάλεσμα για contributors[/b][/size] -[img height=400]https://tctechcrunch2011.files.wordpress.com/2015/04/uncle-sam-we-want-you1-kopie_1.png[/img][/center] +[img height=200]https://tctechcrunch2011.files.wordpress.com/2015/04/uncle-sam-we-want-you1-kopie_1.png[/img][/center] - project : +Αν ενδιαφέρεσαι κι [b]ΕΣΥ[/b] να ασχοληθείς με το project μπορείς να το κάνεις με πολλούς τρόπους: [list] -[li] repository. [i][/i] developers , , javadocs documentation , white-box testing, backend server . , , forks merge requests - . -[/li] + [li] - bugs, , [url=https://discord.gg/PVRVjth]Discord server[/url], Issue Tracker . +Να αναφέρεις bugs, να προτείνεις βελτιώσεις και να συμμετέχεις σε συζητήσεις στον [url=https://discord.gg/PVRVjth]Discord server[/url] μας και στον Issue Tracker στο [url=trello.com]Trello[/url] (το link του είναι pinned στο #feedback στο Discord). [/li] [li] - alpha [url=https://play.google.com/apps/testing/gr.thmmy.mthmmy][/url] gmail . +Να κατεβάσεις και να δοκιμάσεις την alpha έκδοση της εφαρμογής από [url=https://play.google.com/apps/testing/gr.thmmy.mthmmy]εδώ[/url], [b]αφού [/b]πρώτα μας στείλεις το Gmail που έχεις στο Google Play για να αποκτήσεις πρόσβαση. [/li] [li] - Discord email thmmynolife@gmail.com. +Να έρθεις σε άμεση επικοινωνία με την ομάδα μέσω του [url=https://discord.gg/PVRVjth]Discord[/url] ή στέλνοντας email στο thmmynolife@gmail.com. +[/li] +[li]Αν ξέρεις προγραμματισμό μπορείς αρχικά να ζητήσεις πρόσβαση στο repository και να συνεισφέρεις κώδικα με fork και merge requests στους ρυθμούς σου. Χρειάζονται [i]άμεσα[/i] νέοι developers για υλοποιήση καινούργιων χαρακτηριστικών, διόρθωση εντόμων, σύνταξη των javadocs και του documentation γενικότερα, white-box testing, υλοποίηση του backend στο server που στήθηκε πρόσφατα και πολλών άλλων. [/li][/list] [hr] -[center][size=25pt][b] [/b][/size][/center] +[center][size=25pt][b]Η εφαρμογή[/b][/size][/center] -[center][url=https://s6.postimg.org/4mupem3jl/image.png][img width=200]https://s6.postimg.org/4mupem3jl/image.png[/img][/url] [url=https://s6.postimg.org/ovdhmldgx/image.png][img width=200]https://s6.postimg.org/ovdhmldgx/image.png[/img][/url] [url=https://s6.postimg.org/a9mgycgoh/image.png][img width=200]https://s6.postimg.org/a9mgycgoh/image.png[/img][/url] [url=https://s6.postimg.org/5jwj9qpo1/image.png][img width=200]https://s6.postimg.org/5jwj9qpo1/image.png[/img][/url] [url=https://s6.postimg.org/fjrfpn0xd/image.png][img width=200]https://s6.postimg.org/fjrfpn0xd/image.png[/img][/url] [url=https://s6.postimg.org/z0c5c5w1d/image.png][img width=200]https://s6.postimg.org/z0c5c5w1d/image.png[/img][/url][/center] +[center][url=https://s6.postimg.org/v9mseb7n5/image.png][img width=200]https://s6.postimg.org/v9mseb7n5/image.png[/img][/url] [url=https://s6.postimg.org/3nk0tmoa9/image2.png][img width=200]https://s6.postimg.org/3nk0tmoa9/image2.png[/img][/url] [url=https://s6.postimg.org/i813ogj8x/image3.png][img width=200]https://s6.postimg.org/i813ogj8x/image3.png[/img][/url] [url=https://s6.postimg.org/4to0sfckx/image4.png][img width=200]https://s6.postimg.org/4to0sfckx/image4.png[/img][/url] [url=https://s6.postimg.org/69zjakfht/image5.png][img width=200]https://s6.postimg.org/69zjakfht/image5.png[/img][/url] [url=https://s6.postimg.org/rkx3etxm9/image6.png][img width=200]https://s6.postimg.org/rkx3etxm9/image6.png[/img][/url][/center] - login logout, "", boards, topics user profiles post. \ No newline at end of file +Αυτή τη στιγμή στην εφαρμογή μπορείς να κάνεις login και logout, να δεις τα "Πρόσφατα", να περιηγηθείς στα boards, topics και user profiles, να κατεβάσεις αρχεία από τα downloads και από συνημμένα σε post. \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 04e285f3..8b6dacea 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Dec 28 10:00:20 PST 2015 +#Wed Mar 08 11:25:21 EET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip