Browse Source

Version 1.3.0

master v1.3.0
Ezerous 8 years ago
parent
commit
cf1abe056d
  1. 2
      .gitlab-ci.yml
  2. 16
      CONTRIBUTING.md
  3. 8
      README.md
  4. 2
      VERSION
  5. 34
      app/build.gradle
  6. 20
      app/proguard-rules.pro
  7. 57
      app/src/main/AndroidManifest.xml
  8. BIN
      app/src/main/assets/YouTube_light_color_icon.png
  9. 10
      app/src/main/assets/apache_libraries.html
  10. 2
      app/src/main/assets/mit_libraries.html
  11. 184
      app/src/main/java/gr/thmmy/mthmmy/activities/BookmarkActivity.java
  12. 1
      app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java
  13. 71
      app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java
  14. 3
      app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java
  15. 97
      app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java
  16. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java
  17. 125
      app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java
  18. 80
      app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java
  19. 147
      app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java
  20. 251
      app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java
  21. 5
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java
  22. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java
  23. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java
  24. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java
  25. 124
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java
  26. 71
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java
  27. 187
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
  28. 86
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAnimations.java
  29. 3
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
  30. 92
      app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java
  31. 2
      app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java
  32. 29
      app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java
  33. 2
      app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java
  34. 3
      app/src/main/java/gr/thmmy/mthmmy/receiver/Receiver.java
  35. 7
      app/src/main/java/gr/thmmy/mthmmy/services/DownloadService.java
  36. 11
      app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java
  37. 27
      app/src/main/java/gr/thmmy/mthmmy/utils/CrashReportingTree.java
  38. 38
      app/src/main/java/gr/thmmy/mthmmy/utils/CustomLinearLayoutManager.java
  39. 2
      app/src/main/java/gr/thmmy/mthmmy/utils/ParseHelpers.java
  40. 67
      app/src/main/java/gr/thmmy/mthmmy/utils/ParseTask.java
  41. 3
      app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java
  42. 3
      app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareLinearBehavior.java
  43. 14
      app/src/main/java/gr/thmmy/mthmmy/utils/exceptions/UnknownException.java
  44. 9
      app/src/main/res/drawable/ic_file_download.xml
  45. 9
      app/src/main/res/drawable/ic_reply.xml
  46. 34
      app/src/main/res/layout-sw600dp/activity_main.xml
  47. 114
      app/src/main/res/layout-v21/activity_topic_post_row.xml
  48. 2
      app/src/main/res/layout/activity_about.xml
  49. 4
      app/src/main/res/layout/activity_bookmark.xml
  50. 2
      app/src/main/res/layout/activity_downloads.xml
  51. 9
      app/src/main/res/layout/activity_main.xml
  52. 15
      app/src/main/res/layout/activity_topic.xml
  53. 99
      app/src/main/res/layout/activity_topic_post_row.xml
  54. 25
      app/src/main/res/layout/fragment_forum.xml
  55. 38
      app/src/main/res/layout/fragment_unread.xml
  56. 19
      app/src/main/res/layout/fragment_unread_empty_row.xml
  57. 15
      app/src/main/res/layout/fragment_unread_mark_read_row.xml
  58. 54
      app/src/main/res/layout/fragment_unread_row.xml
  59. 6
      app/src/main/res/menu/topic_menu.xml
  60. 1
      app/src/main/res/values-w820dp/dimens.xml
  61. 1
      app/src/main/res/values/dimens.xml
  62. 5
      app/src/main/res/values/strings.xml
  63. 4
      build.gradle
  64. 35
      doc/forum_post.txt

2
.gitlab-ci.yml

@ -2,7 +2,7 @@ image: openjdk:8-jdk
variables:
ANDROID_TARGET_SDK: "25"
ANDROID_BUILD_TOOLS: "25.0.2"
ANDROID_BUILD_TOOLS: "25.0.3"
ANDROID_SDK_TOOLS: "25.2.5"
before_script:

16
CONTRIBUTING.md

@ -13,35 +13,33 @@ vulnerabilities, please report them in private to
There are many ways of contributing to mTHMMY:
- Simply using the latest release version (anonymous reports are sent automatically)
- Becoming an alpha or beta tester
- Simply using the [latest release version][google-play] from Google Play (anonymous reports are sent automatically)
- Joining our [Discord server][discord-server]
- Submiting bugs and ideas on our [Trello board][trello-board]
- Getting code access to fork mTHMMY and submit [merge requests](#merge-requests)
- Submitting bugs and ideas on our [Trello board][trello-board]
- Forking mTHMMY and submitting [merge requests](#merge-requests)
- Joining our core team
- Contacting us by email at `thmmynolife@gmail.com`
## Issue tracker
The [mTHMMY Board on trello.com][trello-board] is used as an Issue Tracker, for bugs and improvements concerning the available mTHMMY releases.
Before creating a card to submit an issue please **[search the board][trello-board]** for similar entries.
Before creating a card to submit an issue please **search the board** for similar entries.
## Merge requests
Merge requests with fixes and improvements to mTHMMY are most welcome. Any developer that wants to work independently from the core team can simply
follow the workflow below to make a merge request:
1. Request and get access to the repository
1. Fork the project into your personal space on GitLab.com
1. Create a feature branch, branch away from `develop`
1. Create a feature branch, away from `develop`
1. Push the commit(s) to your fork
1. Create a merge request (MR) targeting `develop` [at mTHMMY](https://gitlab.com/ThmmyNoLife/mTHMMY/tree/develop)
1. Fill the MR title describing the change you want to make
1. Fill the MR description with a brief motive for your change and the method you used to achieve it.
1. Fill the MR description with a brief motive for your change and the method you used to achieve it
1. Submit the MR.
[google-play]: https://play.google.com/store/apps/details?id=gr.thmmy.mthmmy
[trello-board]: https://trello.com/invite/b/4MVlkrkg/44a931707bd0b84a5e0bdfc42b9ae4f1/mthmmy
[discord-server]: https://discord.gg/CVt3yrn

8
README.md

@ -12,6 +12,12 @@ mTHMMY is a mobile app for the [thmmy.gr](https://www.thmmy.gr) community.
mTHMMY can be installed on any smartphone with Android 4.4 KitKat or newer.
## Download
The latest release version is available on Google Play:
<a href='https://play.google.com/store/apps/details?id=gr.thmmy.mthmmy&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' src='https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png' width="200"/></a>
## Contributing
Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
@ -20,5 +26,7 @@ Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
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].
**Legal attribution: Google Play and the Google Play logo are trademarks of Google Inc.*
[discord-server]: https://discord.gg/CVt3yrn
[trello-board]: https://trello.com/invite/b/4MVlkrkg/44a931707bd0b84a5e0bdfc42b9ae4f1/mthmmy

2
VERSION

@ -1 +1 @@
1.2.1
1.3.0

34
app/build.gradle

@ -2,21 +2,21 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
buildToolsVersion "25.0.3"
defaultConfig {
vectorDrawables.useSupportLibrary = true
applicationId "gr.thmmy.mthmmy"
minSdkVersion 19
targetSdkVersion 25
versionCode 7
versionName "1.2.1"
versionCode 8
versionName "1.3.0"
archivesBaseName = "mTHMMY-v$versionName"
}
buildTypes {
release {
minifyEnabled false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
/*debug {
@ -28,25 +28,25 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
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.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support:support-v4:25.3.1'
compile 'com.android.support:cardview-v7:25.3.1'
compile 'com.android.support:recyclerview-v7:25.3.1'
compile 'com.google.firebase:firebase-crash:10.2.6'
compile 'com.squareup.okhttp3:okhttp:3.8.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.2@aar') {
compile 'com.github.franmontiel:PersistentCookieJar:v1.0.1'
compile 'com.github.PhilJay:MPAndroidChart:v3.0.2'
compile('com.mikepenz:materialdrawer:5.9.2@aar') {
transitive = true
}
compile 'com.mikepenz:fontawesome-typeface:4.7.0.0@aar'
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 'pl.droidsonroids.gif:android-gif-drawable:1.2.7'
compile 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1' //Deprecated!
compile 'me.zhanghai.android.materialprogressbar:library:1.4.1'
compile 'com.jakewharton.timber:timber:4.5.1'
}

20
app/proguard-rules.pro

@ -15,3 +15,23 @@
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# OkHttp
-dontwarn okio.**
-dontwarn javax.annotation.Nullable
-dontwarn javax.annotation.ParametersAreNonnullByDefault
# Picasso
-dontwarn com.squareup.okhttp.**
# Android-Iconics (fontawesome-typeface)
-keep class .R
-keep class **.R$* {
<fields>;
}
# android-gif-drawable
-keep public class pl.droidsonroids.gif.GifIOException{<init>(int, java.lang.String);}
#JSoup
-keep class org.jsoup.**

57
app/src/main/AndroidManifest.xml

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="gr.thmmy.mthmmy"
android:installLocation="auto">
package="gr.thmmy.mthmmy"
android:installLocation="auto">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name=".base.BaseApplication"
@ -23,33 +23,32 @@
android:launchMode="singleTask"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="thmmy.gr"
android:scheme="http"/>
android:scheme="http" />
<data
android:host="www.thmmy.gr"
android:scheme="http"/>
android:scheme="http" />
<data
android:host="www.thmmy.gr"
android:scheme="https"/>
android:scheme="https" />
<data
android:host="thmmy.gr"
android:scheme="https"/>
android:scheme="https" />
<data
android:host="thmmy.gr"/>
<data android:host="thmmy.gr" />
</intent-filter>
</activity>
<activity
@ -58,14 +57,15 @@
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="adjustPan"/>
android:windowSoftInputMode="adjustPan" />
<activity
android:name=".activities.AboutActivity"
android:launchMode="singleInstance"
android:parentActivityName=".activities.main.MainActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.main.MainActivity"/>
android:value=".activities.main.MainActivity" />
</activity>
<activity
android:name=".activities.topic.TopicActivity"
@ -74,11 +74,11 @@
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.main.MainActivity"/>
android:value=".activities.main.MainActivity" />
</activity>
<activity
android:name=".activities.profile.ProfileActivity"
android:theme="@style/AppTheme.NoActionBar"/>
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".activities.board.BoardActivity"
android:configChanges="orientation|screenSize"
@ -86,15 +86,16 @@
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.main.MainActivity"/>
android:value=".activities.main.MainActivity" />
</activity>
<activity
android:name=".activities.downloads.DownloadsActivity"
android:launchMode="singleInstance"
android:parentActivityName=".activities.main.MainActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.main.MainActivity"/>
android:value=".activities.main.MainActivity" />
</activity>
<activity
android:name=".activities.BookmarkActivity"
@ -102,7 +103,7 @@
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.main.MainActivity"/>
android:value=".activities.main.MainActivity" />
</activity>
<provider
@ -112,20 +113,20 @@
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
android:resource="@xml/provider_paths" />
</provider>
<service
android:name=".services.DownloadService"
android:exported="false"/>
android:exported="false" />
<receiver
android:name=".receiver.Receiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.media.action.ACTION_DOWNLOAD"/>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="android.media.action.ACTION_DOWNLOAD" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
</application>

BIN
app/src/main/assets/YouTube_light_color_icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

10
app/src/main/assets/apache_libraries.html

@ -39,25 +39,25 @@
<body>
<ul>
<li>
<h5><a href="https://square.github.io/okhttp/">OkHttp</a>&nbsp;v3.6.0 (Copyright ©2016 Square, Inc.)</h5>
<h5><a href="https://square.github.io/okhttp/">OkHttp</a>&nbsp;v3.8.0 (Copyright ©2016 Square, Inc.)</h5>
</li>
<li>
<h5><a href="https://square.github.io/picasso/">Picasso</a>&nbsp;v2.5.2 (Copyright ©2013 Square, Inc.)</h5>
</li>
<li>
<h5><a href="https://github.com/franmontiel/PersistentCookieJar">PersistentCookieJar</a>&nbsp;v1.0.0 (Copyright ©2016 Francisco José Montiel Navarro)</h5>
<h5><a href="https://github.com/franmontiel/PersistentCookieJar">PersistentCookieJar</a>&nbsp;v1.0.1 (Copyright ©2016 Francisco José Montiel Navarro)</h5>
</li>
<li>
<h5><a href="https://github.com/PhilJay/MPAndroidChart">MPAndroidChart</a>&nbsp;v3.0.1 (Copyright ©2016 Philipp Jahoda)</h5>
<h5><a href="https://github.com/PhilJay/MPAndroidChart">MPAndroidChart</a>&nbsp;v3.0.2 (Copyright ©2016 Philipp Jahoda)</h5>
</li>
<li>
<h5><a href="https://github.com/mikepenz/MaterialDrawer">MaterialDrawer</a>&nbsp;v5.8.2 (Copyright ©2016 Mike Penz)</h5>
<h5><a href="https://github.com/mikepenz/MaterialDrawer">MaterialDrawer</a>&nbsp;v5.9.2 (Copyright ©2016 Mike Penz)</h5>
</li>
<li>
<h5><a href="https://github.com/mikepenz/Android-Iconics/tree/develop/fontawesome-typeface-library">Fontawesome Typeface Library</a>&nbsp;v4.7.0.0 (Copyright ©2016 Mike Penz)</h5>
</li>
<li>
<h5><a href="https://github.com/DreaminginCodeZH/MaterialProgressBar">MaterialProgressBar</a>&nbsp;v1.3.0 (Copyright ©2015 Zhang Hai)</h5>
<h5><a href="https://github.com/DreaminginCodeZH/MaterialProgressBar">MaterialProgressBar</a>&nbsp;v1.4.1 (Copyright ©2015 Zhang Hai)</h5>
</li>
</ul>

2
app/src/main/assets/mit_libraries.html

@ -42,7 +42,7 @@
<h5><a href="https://jsoup.org//">jsoup</a>&nbsp;v1.10.2 (Copyright ©2009-2017, Jonathan Hedley &lt;jonathan@hedley.net&gt;)</h5>
</li>
<li>
<h5><a href="https://github.com/koral--/android-gif-drawable">android-gif-drawable</a>&nbsp;v1.2.5 (Copyright ©2016 Karol Wrótniak, Droids on Roids)</h5>
<h5><a href="https://github.com/koral--/android-gif-drawable">android-gif-drawable</a>&nbsp;v1.2.7 (Copyright ©2016 Karol Wrótniak, Droids on Roids)</h5>
</li>
<li>
<h5><a href="https://github.com/bignerdranch/expandable-recycler-view">Expandable RecyclerView</a>&nbsp;v3.0.0-RC1 (Copyright ©2015, Big Nerd Ranch)</h5>

184
app/src/main/java/gr/thmmy/mthmmy/activities/BookmarkActivity.java

@ -21,7 +21,12 @@ import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_URL;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL;
//TODO proper handling with adapter etc.
//TODO better UI
//TODO after clicking bookmark and then back button should return to this activity
public class BookmarkActivity extends BaseActivity {
private TextView boardsTitle;
private TextView topicsTitle;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -43,93 +48,100 @@ public class BookmarkActivity extends BaseActivity {
LinearLayout bookmarksLinearView = (LinearLayout) findViewById(R.id.bookmarks_container);
LayoutInflater layoutInflater = getLayoutInflater();
TextView tmp = new TextView(this);
tmp.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT
, LinearLayout.LayoutParams.WRAP_CONTENT));
tmp.setText(getString(R.string.board_bookmarks_title));
tmp.setTypeface(tmp.getTypeface(), Typeface.BOLD);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
tmp.setTextColor(getColor(R.color.primary_text));
} else {
//noinspection deprecation
tmp.setTextColor(getResources().getColor(R.color.primary_text));
}
tmp.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
tmp.setTextSize(20f);
bookmarksLinearView.addView(tmp);
if(!getBoardsBookmarked().isEmpty()) {
boardsTitle = new TextView(this);
boardsTitle.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT
, LinearLayout.LayoutParams.WRAP_CONTENT));
boardsTitle.setText(getString(R.string.board_bookmarks_title));
boardsTitle.setTypeface(boardsTitle.getTypeface(), Typeface.BOLD);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
boardsTitle.setTextColor(getColor(R.color.primary_text));
} else {
//noinspection deprecation
boardsTitle.setTextColor(getResources().getColor(R.color.primary_text));
}
boardsTitle.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
boardsTitle.setTextSize(20f);
bookmarksLinearView.addView(boardsTitle);
for (final Bookmark bookmarkedBoard : getBoardsBookmarked()) {
if (bookmarkedBoard != null && bookmarkedBoard.getTitle() != null) {
final LinearLayout row = (LinearLayout) layoutInflater.inflate(
R.layout.activity_bookmark_row, bookmarksLinearView, false);
row.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(BookmarkActivity.this, BoardActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_BOARD_URL, "https://www.thmmy.gr/smf/index.php?board="
+ bookmarkedBoard.getId() + ".0");
extras.putString(BUNDLE_BOARD_TITLE, bookmarkedBoard.getTitle());
intent.putExtras(extras);
startActivity(intent);
finish();
}
});
((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedBoard.getTitle());
(row.findViewById(R.id.remove_bookmark)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
removeBookmark(bookmarkedBoard);
row.setVisibility(View.GONE);
}
});
bookmarksLinearView.addView(row);
for (final Bookmark bookmarkedBoard : getBoardsBookmarked()) {
if (bookmarkedBoard != null && bookmarkedBoard.getTitle() != null) {
final LinearLayout row = (LinearLayout) layoutInflater.inflate(
R.layout.activity_bookmark_row, bookmarksLinearView, false);
row.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(BookmarkActivity.this, BoardActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_BOARD_URL, "https://www.thmmy.gr/smf/index.php?board="
+ bookmarkedBoard.getId() + ".0");
extras.putString(BUNDLE_BOARD_TITLE, bookmarkedBoard.getTitle());
intent.putExtras(extras);
startActivity(intent);
finish();
}
});
((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedBoard.getTitle());
(row.findViewById(R.id.remove_bookmark)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
removeBookmark(bookmarkedBoard);
row.setVisibility(View.GONE);
updateTitles();
}
});
bookmarksLinearView.addView(row);
}
}
}
tmp = new TextView(this);
tmp.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT
, LinearLayout.LayoutParams.WRAP_CONTENT));
tmp.setText(getString(R.string.topic_bookmarks_title));
tmp.setTypeface(tmp.getTypeface(), Typeface.BOLD);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
tmp.setTextColor(getColor(R.color.primary_text));
} else {
//noinspection deprecation
tmp.setTextColor(getResources().getColor(R.color.primary_text));
}
tmp.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
tmp.setTextSize(20f);
bookmarksLinearView.addView(tmp);
for (final Bookmark bookmarkedTopic : getTopicsBookmarked()) {
if (bookmarkedTopic != null && bookmarkedTopic.getTitle() != null) {
final LinearLayout row = (LinearLayout) layoutInflater.inflate(
R.layout.activity_bookmark_row, bookmarksLinearView, false);
row.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
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() + "." + 2147483647);
extras.putString(BUNDLE_TOPIC_TITLE, bookmarkedTopic.getTitle());
intent.putExtras(extras);
startActivity(intent);
finish();
}
});
((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedTopic.getTitle());
(row.findViewById(R.id.remove_bookmark)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
removeBookmark(bookmarkedTopic);
row.setVisibility(View.GONE);
}
});
bookmarksLinearView.addView(row);
if(!getTopicsBookmarked().isEmpty()) {
topicsTitle = new TextView(this);
topicsTitle.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT
, LinearLayout.LayoutParams.WRAP_CONTENT));
topicsTitle.setText(getString(R.string.topic_bookmarks_title));
topicsTitle.setTypeface(topicsTitle.getTypeface(), Typeface.BOLD);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
topicsTitle.setTextColor(getColor(R.color.primary_text));
} else {
//noinspection deprecation
topicsTitle.setTextColor(getResources().getColor(R.color.primary_text));
}
topicsTitle.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
topicsTitle.setTextSize(20f);
bookmarksLinearView.addView(topicsTitle);
for (final Bookmark bookmarkedTopic : getTopicsBookmarked()) {
if (bookmarkedTopic != null && bookmarkedTopic.getTitle() != null) {
final LinearLayout row = (LinearLayout) layoutInflater.inflate(
R.layout.activity_bookmark_row, bookmarksLinearView, false);
row.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
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() + "." + 2147483647);
extras.putString(BUNDLE_TOPIC_TITLE, bookmarkedTopic.getTitle());
intent.putExtras(extras);
startActivity(intent);
finish();
}
});
((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedTopic.getTitle());
(row.findViewById(R.id.remove_bookmark)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
removeBookmark(bookmarkedTopic);
row.setVisibility(View.GONE);
updateTitles();
}
});
bookmarksLinearView.addView(row);
}
}
}
}
@ -139,4 +151,12 @@ public class BookmarkActivity extends BaseActivity {
drawer.setSelection(BOOKMARKS_ID);
super.onResume();
}
private void updateTitles()
{
if(getBoardsBookmarked().isEmpty()&&boardsTitle!=null)
boardsTitle.setVisibility(View.GONE);
if(getTopicsBookmarked().isEmpty()&&topicsTitle!=null)
topicsTitle.setVisibility(View.GONE);
}
}

1
app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java

@ -14,7 +14,6 @@ import android.widget.Toast;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.main.MainActivity;
import gr.thmmy.mthmmy.base.BaseActivity;
import timber.log.Timber;
import static gr.thmmy.mthmmy.session.SessionManager.CONNECTION_ERROR;

71
app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java

@ -8,12 +8,12 @@ import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.Toast;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
@ -21,17 +21,15 @@ import org.jsoup.select.Elements;
import java.util.ArrayList;
import java.util.Objects;
import javax.net.ssl.SSLHandshakeException;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Board;
import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.model.Topic;
import gr.thmmy.mthmmy.utils.ParseTask;
import gr.thmmy.mthmmy.utils.exceptions.ParseException;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMoreListener {
@ -164,6 +162,13 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
}
}
@Override
public void onResume() {
super.onResume();
Log.d("Boardaa", "onResume called!");
refreshBoardBookmark((ImageButton) findViewById(R.id.bookmark));
}
@Override
public void onDestroy() {
super.onDestroy();
@ -176,47 +181,15 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
* <p>BoardTask's {@link AsyncTask#execute execute} method needs a boards's url as String
* parameter!</p>
*/
private class BoardTask extends AsyncTask<String, Void, Void> {
private class BoardTask extends ParseTask {
@Override
protected void onPreExecute() {
if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE);
if (newTopicFAB.getVisibility() != View.GONE) newTopicFAB.setEnabled(false);
}
@Override
protected Void doInBackground(String... boardUrl) {
Request request = new Request.Builder()
.url(boardUrl[0])
.build();
try {
Response response = client.newCall(request).execute();
parseBoard(Jsoup.parse(response.body().string()));
} catch (SSLHandshakeException e) {
Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) {
Timber.e("ERROR", e);
}
return null;
}
@Override
protected void onPostExecute(Void voids) {
if (boardTitle == null || Objects.equals(boardTitle, "")
|| !Objects.equals(boardTitle, parsedTitle)) {
boardTitle = parsedTitle;
toolbar.setTitle(boardTitle);
thisPageBookmark = new Bookmark(boardTitle, ThmmyPage.getBoardId(boardUrl));
}
//Parse was successful
++pagesLoaded;
if (newTopicFAB.getVisibility() != View.GONE) newTopicFAB.setEnabled(true);
progressBar.setVisibility(ProgressBar.INVISIBLE);
boardAdapter.notifyDataSetChanged();
isLoadingMore = false;
}
private void parseBoard(Document boardPage) {
@Override //TODO should throw ParseException
public void parse(Document boardPage) throws ParseException {
parsedTitle = boardPage.select("div.nav a.nav").last().text();
//Removes loading item
@ -320,5 +293,23 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
}
}
}
@Override
protected void postParsing(ResultCode result) {
//TODO if (result == ResultCode.SUCCESS)...
if (boardTitle == null || Objects.equals(boardTitle, "")
|| !Objects.equals(boardTitle, parsedTitle)) {
boardTitle = parsedTitle;
toolbar.setTitle(boardTitle);
thisPageBookmark = new Bookmark(boardTitle, ThmmyPage.getBoardId(boardUrl));
}
//Parse was successful
++pagesLoaded;
if (newTopicFAB.getVisibility() != View.GONE) newTopicFAB.setEnabled(true);
progressBar.setVisibility(ProgressBar.INVISIBLE);
boardAdapter.notifyDataSetChanged();
isLoadingMore = false;
}
}
}

3
app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java

@ -27,7 +27,6 @@ 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 okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
@ -189,7 +188,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
} catch (SSLHandshakeException e) {
Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) {
Timber.e("ERROR", e);
Timber.e(e, "Exception");
}
return null;
}

97
app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java

@ -8,15 +8,18 @@ import android.support.v4.app.Fragment;
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.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.LoginActivity;
import gr.thmmy.mthmmy.activities.board.BoardActivity;
import gr.thmmy.mthmmy.activities.downloads.DownloadsActivity;
import gr.thmmy.mthmmy.activities.main.forum.ForumFragment;
import gr.thmmy.mthmmy.activities.main.recent.RecentFragment;
import gr.thmmy.mthmmy.activities.main.unread.UnreadFragment;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.activities.topic.TopicActivity;
import gr.thmmy.mthmmy.base.BaseActivity;
@ -34,11 +37,13 @@ import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL;
public class MainActivity extends BaseActivity implements RecentFragment.RecentFragmentInteractionListener, ForumFragment.ForumFragmentInteractionListener {
public class MainActivity extends BaseActivity implements RecentFragment.RecentFragmentInteractionListener, ForumFragment.ForumFragmentInteractionListener, UnreadFragment.UnreadFragmentInteractionListener {
//----------------------------------------CLASS VARIABLES-----------------------------------------
//-----------------------------------------CLASS VARIABLES------------------------------------------
private static final int TIME_INTERVAL = 2000;
private long mBackPressed;
private SectionsPagerAdapter sectionsPagerAdapter;
private ViewPager viewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -48,7 +53,6 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
setContentView(R.layout.activity_main);
if (sessionManager.isLoginScreenDefault())
{
//Go to login
Intent intent = new Intent(MainActivity.this, LoginActivity.class);
@ -57,25 +61,25 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out);
}
//Initialize toolbar
toolbar = (Toolbar)
findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//Initialize drawer
createDrawer();
//Create the adapter that will return a fragment for each section of the activity
SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
sectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
sectionsPagerAdapter.addFragment(RecentFragment.newInstance(1), "RECENT");
sectionsPagerAdapter.addFragment(ForumFragment.newInstance(2), "FORUM");
if(sessionManager.isLoggedIn())
sectionsPagerAdapter.addFragment(UnreadFragment.newInstance(3), "UNREAD");
//Set up the ViewPager with the sections adapter.
ViewPager mViewPager = (ViewPager) findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter);
viewPager = (ViewPager) findViewById(R.id.container);
viewPager.setAdapter(sectionsPagerAdapter);
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(mViewPager);
tabLayout.setupWithViewPager(viewPager);
setMainActivity(this);
}
@Override
@ -88,6 +92,7 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
@Override
protected void onResume() {
drawer.setSelection(HOME_ID);
updateTabs();
super.onResume();
}
@ -122,6 +127,17 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
startActivity(i);
}
@Override
public void onUnreadFragmentInteraction(TopicSummary topicSummary) {
if (topicSummary.getLastUser() == null && topicSummary.getDateTimeModified() == null) {
return; //TODO!
}
Intent i = new Intent(MainActivity.this, TopicActivity.class);
i.putExtra(BUNDLE_TOPIC_URL, topicSummary.getTopicUrl());
i.putExtra(BUNDLE_TOPIC_TITLE, topicSummary.getSubject());
startActivity(i);
}
//---------------------------------FragmentPagerAdapter---------------------------------------------
/**
@ -130,42 +146,57 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
* it may be best to switch to a
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
private class SectionsPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> fragmentList = new ArrayList<>();
private final List<String> fragmentTitleList = new ArrayList<>();
SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
void addFragment(Fragment fragment, String title) {
fragmentList.add(fragment);
fragmentTitleList.add(title);
notifyDataSetChanged();
}
void removeFragment(int position) {
fragmentList.remove(position);
fragmentTitleList.remove(position);
notifyDataSetChanged();
if(viewPager.getCurrentItem()==position)
viewPager.setCurrentItem(position-1);
}
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return RecentFragment.newInstance(position + 1);
case 1:
return ForumFragment.newInstance(position + 1);
default:
return RecentFragment.newInstance(position + 1); //temp (?)
}
return fragmentList.get(position);
}
@Override
public int getCount() {
// Show 2 total pages.
return 2;
return fragmentList.size();
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return "RECENT POSTS";
case 1:
return "FORUM";
}
return fragmentTitleList.get(position);
}
return null;
@Override
public int getItemPosition(Object object) {
int position = fragmentList.indexOf(object);
return position == -1 ? POSITION_NONE : position;
}
}
public void updateTabs()
{
if(!sessionManager.isLoggedIn()&&sectionsPagerAdapter.getCount()==3)
sectionsPagerAdapter.removeFragment(2);
else if(sessionManager.isLoggedIn()&&sectionsPagerAdapter.getCount()==2)
sectionsPagerAdapter.addFragment(UnreadFragment.newInstance(3), "UNREAD");
}
//-------------------------------FragmentPagerAdapter END-------------------------------------------
private void redirectToActivityFromIntent(Intent intent) {

2
app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java

@ -26,7 +26,6 @@ import gr.thmmy.mthmmy.model.TopicSummary;
* specified {@link ForumFragment.ForumFragmentInteractionListener}.
*/
class ForumAdapter extends ExpandableRecyclerAdapter<Category, Board, ForumAdapter.CategoryViewHolder, ForumAdapter.BoardViewHolder> {
private final Context context;
private final LayoutInflater layoutInflater;
private final List<Category> categories;
@ -34,7 +33,6 @@ class ForumAdapter extends ExpandableRecyclerAdapter<Category, Board, ForumAdapt
ForumAdapter(Context context, @NonNull List<Category> categories, BaseFragment.FragmentInteractionListener listener) {
super(categories);
this.context = context;
this.categories = categories;
mListener = (ForumFragment.ForumFragmentInteractionListener) listener;
layoutInflater = LayoutInflater.from(context);

125
app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java

@ -2,24 +2,21 @@ package gr.thmmy.mthmmy.activities.main.forum;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.bignerdranch.expandablerecyclerview.ExpandableRecyclerAdapter;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@ -29,11 +26,12 @@ import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.Board;
import gr.thmmy.mthmmy.model.Category;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.CustomRecyclerView;
import gr.thmmy.mthmmy.utils.ParseTask;
import gr.thmmy.mthmmy.utils.exceptions.ParseException;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
/**
@ -44,12 +42,12 @@ import timber.log.Timber;
* Use the {@link ForumFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class ForumFragment extends BaseFragment
{
public class ForumFragment extends BaseFragment {
private static final String TAG = "ForumFragment";
// Fragment initialization parameters, e.g. ARG_SECTION_NUMBER
private MaterialProgressBar progressBar;
private SwipeRefreshLayout swipeRefreshLayout;
private ForumAdapter forumAdapter;
private List<Category> categories;
@ -57,11 +55,13 @@ public class ForumFragment extends BaseFragment
private ForumTask forumTask;
// Required empty public constructor
public ForumFragment() {}
public ForumFragment() {
}
/**
* Use ONLY this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @return A new instance of fragment Forum.
*/
public static ForumFragment newInstance(int sectionNumber) {
@ -82,9 +82,8 @@ public class ForumFragment extends BaseFragment
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (categories.isEmpty())
{
forumTask =new ForumTask();
if (categories.isEmpty()) {
forumTask = new ForumTask();
forumTask.execute();
}
@ -104,11 +103,10 @@ public class ForumFragment extends BaseFragment
forumAdapter.setExpandCollapseListener(new ExpandableRecyclerAdapter.ExpandCollapseListener() {
@Override
public void onParentExpanded(int parentPosition) {
if(BaseActivity.getSessionManager().isLoggedIn())
{
if(forumTask.getStatus()== AsyncTask.Status.RUNNING)
if (BaseActivity.getSessionManager().isLoggedIn()) {
if (forumTask.getStatus() == AsyncTask.Status.RUNNING)
forumTask.cancel(true);
forumTask =new ForumTask();
forumTask = new ForumTask();
forumTask.setUrl(categories.get(parentPosition).getCategoryURL());
forumTask.execute();
}
@ -116,18 +114,17 @@ public class ForumFragment extends BaseFragment
@Override
public void onParentCollapsed(int parentPosition) {
if(BaseActivity.getSessionManager().isLoggedIn())
{
if(forumTask.getStatus()== AsyncTask.Status.RUNNING)
if (BaseActivity.getSessionManager().isLoggedIn()) {
if (forumTask.getStatus() == AsyncTask.Status.RUNNING)
forumTask.cancel(true);
forumTask =new ForumTask();
forumTask = new ForumTask();
forumTask.setUrl(categories.get(parentPosition).getCategoryURL());
forumTask.execute();
}
}
});
RecyclerView recyclerView = (RecyclerView) rootView.findViewById(R.id.list);
CustomRecyclerView recyclerView = (CustomRecyclerView) rootView.findViewById(R.id.list);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(rootView.findViewById(R.id.list).getContext());
recyclerView.setLayoutManager(linearLayoutManager);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
@ -135,6 +132,20 @@ public class ForumFragment extends BaseFragment
recyclerView.addItemDecoration(dividerItemDecoration);
recyclerView.setAdapter(forumAdapter);
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swiperefresh);
swipeRefreshLayout.setOnRefreshListener(
new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if (forumTask != null && forumTask.getStatus() != AsyncTask.Status.RUNNING) {
forumTask = new ForumTask();
forumTask.execute(SessionManager.indexUrl.toString());
}
}
}
);
}
return rootView;
}
@ -142,19 +153,18 @@ public class ForumFragment extends BaseFragment
@Override
public void onDestroy() {
super.onDestroy();
if(forumTask!=null&&forumTask.getStatus()!= AsyncTask.Status.RUNNING)
if (forumTask != null && forumTask.getStatus() != AsyncTask.Status.RUNNING)
forumTask.cancel(true);
}
public interface ForumFragmentInteractionListener extends FragmentInteractionListener{
public interface ForumFragmentInteractionListener extends FragmentInteractionListener {
void onForumFragmentInteraction(Board board);
}
//---------------------------------------ASYNC TASK-----------------------------------
private class ForumTask extends AsyncTask<Void, Void, Integer> {
private class ForumTask extends ParseTask {
private HttpUrl forumUrl = SessionManager.forumUrl; //may change upon collapse/expand
private Document document;
private final List<Category> fetchedCategories;
@ -166,69 +176,52 @@ public class ForumFragment extends BaseFragment
progressBar.setVisibility(ProgressBar.VISIBLE);
}
protected Integer doInBackground(Void... voids) {
Request request = new Request.Builder()
@Override
protected Request prepareRequest(String... params) {
return new Request.Builder()
.url(forumUrl)
.build();
try {
Response response = client.newCall(request).execute();
document = Jsoup.parse(response.body().string());
parse(document);
categories.clear();
categories.addAll(fetchedCategories);
fetchedCategories.clear();
return 0;
} catch (IOException e) {
Timber.d("Network Error", e);
return 1;
} catch (Exception e) {
Timber.d("Exception", e);
return 2;
}
}
protected void onPostExecute(Integer result) {
if (result == 0)
forumAdapter.notifyParentDataSetChanged(false);
else if (result == 1)
Toast.makeText(getActivity(), "Network error", Toast.LENGTH_SHORT).show();
progressBar.setVisibility(ProgressBar.INVISIBLE);
}
private void parse(Document document)
{
@Override
public void parse(Document document) throws ParseException {
Elements categoryBlocks = document.select(".tborder:not([style])>table[cellpadding=5]");
if (categoryBlocks.size() != 0) {
for(Element categoryBlock: categoryBlocks)
{
for (Element categoryBlock : categoryBlocks) {
Element categoryElement = categoryBlock.select("td[colspan=2]>[name]").first();
String categoryUrl = categoryElement.attr("href");
Category category = new Category(categoryElement.text(), categoryUrl);
if(categoryUrl.contains("sa=collapse")|| !BaseActivity.getSessionManager().isLoggedIn())
{
if (categoryUrl.contains("sa=collapse") || !BaseActivity.getSessionManager().isLoggedIn()) {
category.setExpanded(true);
Elements boardsElements = categoryBlock.select("b [name]");
for(Element boardElement: boardsElements) {
for (Element boardElement : boardsElements) {
Board board = new Board(boardElement.attr("href"), boardElement.text(), null, null, null, null);
category.getBoards().add(board);
}
}
else
} else
category.setExpanded(false);
fetchedCategories.add(category);
}
}
else
Timber.e("Parsing failed!");
categories.clear();
categories.addAll(fetchedCategories);
fetchedCategories.clear();
} else
throw new ParseException("Parsing failed");
}
@Override
protected void postParsing(ParseTask.ResultCode result) {
if (result == ResultCode.SUCCESS)
forumAdapter.notifyParentDataSetChanged(false);
progressBar.setVisibility(ProgressBar.INVISIBLE);
swipeRefreshLayout.setRefreshing(false);
}
public void setUrl(String string)
public void setUrl(String string) //TODO delete and simplify e.g. in prepareRequest possible?
{
forumUrl = HttpUrl.parse(string);
}

80
app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java

@ -10,13 +10,10 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.Toast;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
@ -27,14 +24,12 @@ import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.CustomRecyclerView;
import gr.thmmy.mthmmy.utils.ParseTask;
import gr.thmmy.mthmmy.utils.exceptions.ParseException;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
/**
* A {@link BaseFragment} subclass.
* Activities that contain this fragment must implement the
@ -56,11 +51,13 @@ public class RecentFragment extends BaseFragment {
private RecentTask recentTask;
// Required empty public constructor
public RecentFragment() {}
public RecentFragment() {
}
/**
* Use ONLY this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @return A new instance of fragment Recent.
*/
public static RecentFragment newInstance(int sectionNumber) {
@ -81,10 +78,9 @@ public class RecentFragment extends BaseFragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (topicSummaries.isEmpty())
{
recentTask =new RecentTask();
recentTask.execute();
if (topicSummaries.isEmpty()) {
recentTask = new RecentTask();
recentTask.execute(SessionManager.indexUrl.toString());
}
Timber.d("onActivityCreated");
@ -117,7 +113,7 @@ public class RecentFragment extends BaseFragment {
public void onRefresh() {
if (recentTask != null && recentTask.getStatus() != AsyncTask.Status.RUNNING) {
recentTask = new RecentTask();
recentTask.execute();
recentTask.execute(SessionManager.indexUrl.toString());
}
}
@ -131,7 +127,7 @@ public class RecentFragment extends BaseFragment {
@Override
public void onDestroy() {
super.onDestroy();
if(recentTask!=null&&recentTask.getStatus()!= AsyncTask.Status.RUNNING)
if (recentTask != null && recentTask.getStatus() != AsyncTask.Status.RUNNING)
recentTask.cancel(true);
}
@ -141,55 +137,16 @@ public class RecentFragment extends BaseFragment {
}
//---------------------------------------ASYNC TASK-----------------------------------
private class RecentTask extends AsyncTask<Void, Void, Integer> {
private final HttpUrl thmmyUrl = SessionManager.indexUrl;
private Document document;
private class RecentTask extends ParseTask {
protected void onPreExecute() {
progressBar.setVisibility(ProgressBar.VISIBLE);
}
protected Integer doInBackground(Void... voids) {
Request request = new Request.Builder()
.url(thmmyUrl)
.build();
try {
Response response = client.newCall(request).execute();
document = Jsoup.parse(response.body().string());
parse(document);
return 0;
} catch (ParseException e) {
Timber.e("ParseException", e);
return 1;
} catch (IOException e) {
Timber.i("Network Error", e);
return 2;
} catch (Exception e) {
Timber.e("Exception", e);
return 3;
}
}
protected void onPostExecute(Integer result) {
if (result == 0)
recentAdapter.notifyDataSetChanged();
else if (result == 2)
Toast.makeText(getActivity(), "Network error", Toast.LENGTH_SHORT).show(); //Fixme, sometimes activity isn't ready
progressBar.setVisibility(ProgressBar.INVISIBLE);
swipeRefreshLayout.setRefreshing(false);
}
private void parse(Document document) throws ParseException {
@Override
public void parse(Document document) throws ParseException {
Elements recent = document.select("#block8 :first-child div");
if (!recent.isEmpty()) {
topicSummaries.clear();
for (int i = 0; i < recent.size(); i += 3) {
String link = recent.get(i).child(0).attr("href");
String title = recent.get(i).child(0).attr("title");
@ -212,11 +169,18 @@ public class RecentFragment extends BaseFragment {
topicSummaries.add(new TopicSummary(link, title, lastUser, dateTime));
}
return;
}
throw new ParseException("Parsing failed");
}
}
@Override
protected void postParsing(ParseTask.ResultCode result) {
if (result == ResultCode.SUCCESS)
recentAdapter.notifyDataSetChanged();
progressBar.setVisibility(ProgressBar.INVISIBLE);
swipeRefreshLayout.setRefreshing(false);
}
}
}

147
app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadAdapter.java

@ -0,0 +1,147 @@
package gr.thmmy.mthmmy.activities.main.unread;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary;
class UnreadAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final Context context;
private final List<TopicSummary> unreadList;
private final UnreadFragment.UnreadFragmentInteractionListener mListener;
private final MarkReadInteractionListener markReadListener;
private final int VIEW_TYPE_ITEM = 0;
private final int VIEW_TYPE_NADA = 1;
private final int VIEW_TYPE_MARK_READ = 2;
UnreadAdapter(Context context, @NonNull List<TopicSummary> topicSummaryList,
BaseFragment.FragmentInteractionListener listener,
MarkReadInteractionListener markReadInteractionListener) {
this.context = context;
this.unreadList = topicSummaryList;
mListener = (UnreadFragment.UnreadFragmentInteractionListener) listener;
markReadListener = markReadInteractionListener;
}
@Override
public int getItemViewType(int position) {
if (unreadList.get(position).getDateTimeModified() == null) return VIEW_TYPE_MARK_READ;
return unreadList.get(position).getTopicUrl() == null ? VIEW_TYPE_NADA : VIEW_TYPE_ITEM;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_ITEM) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.fragment_unread_row, parent, false);
return new ViewHolder(view);
} else if (viewType == VIEW_TYPE_NADA) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.fragment_unread_empty_row, parent, false);
return new EmptyViewHolder(view);
} else if (viewType == VIEW_TYPE_MARK_READ) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.fragment_unread_mark_read_row, parent, false);
return new MarkReadViewHolder(view);
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
if (holder instanceof UnreadAdapter.EmptyViewHolder) {
final UnreadAdapter.EmptyViewHolder emptyViewHolder = (UnreadAdapter.EmptyViewHolder) holder;
emptyViewHolder.text.setText(unreadList.get(position).getDateTimeModified());
} else if (holder instanceof UnreadAdapter.ViewHolder) {
final UnreadAdapter.ViewHolder viewHolder = (UnreadAdapter.ViewHolder) holder;
viewHolder.mTitleView.setText(unreadList.get(position).getSubject());
viewHolder.mDateTimeView.setText(unreadList.get(position).getDateTimeModified());
viewHolder.mUserView.setText(context.getString(R.string.byUser, unreadList.get(position).getLastUser()));
viewHolder.topic = unreadList.get(position);
viewHolder.mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (null != mListener) {
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mListener.onUnreadFragmentInteraction(viewHolder.topic); //?
}
}
});
} else if (holder instanceof UnreadAdapter.MarkReadViewHolder) {
final UnreadAdapter.MarkReadViewHolder markReadViewHolder = (UnreadAdapter.MarkReadViewHolder) holder;
markReadViewHolder.text.setText(unreadList.get(position).getSubject());
markReadViewHolder.topic = unreadList.get(position);
markReadViewHolder.mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (null != mListener) {
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
markReadListener.onMarkReadInteraction(unreadList.get(position).getTopicUrl());
}
}
});
}
}
@Override
public int getItemCount() {
return unreadList.size();
}
private static class ViewHolder extends RecyclerView.ViewHolder {
final View mView;
final TextView mTitleView;
final TextView mUserView;
final TextView mDateTimeView;
public TopicSummary topic;
ViewHolder(View view) {
super(view);
mView = view;
mTitleView = (TextView) view.findViewById(R.id.title);
mUserView = (TextView) view.findViewById(R.id.lastUser);
mDateTimeView = (TextView) view.findViewById(R.id.dateTime);
}
}
private static class EmptyViewHolder extends RecyclerView.ViewHolder {
final TextView text;
EmptyViewHolder(View view) {
super(view);
text = (TextView) view.findViewById(R.id.text);
}
}
private static class MarkReadViewHolder extends RecyclerView.ViewHolder {
final View mView;
final TextView text;
public TopicSummary topic;
MarkReadViewHolder(View view) {
super(view);
mView = view;
text = (TextView) view.findViewById(R.id.mark_read);
}
}
public interface MarkReadInteractionListener {
void onMarkReadInteraction(String markReadLinkUrl);
}
}

251
app/src/main/java/gr/thmmy/mthmmy/activities/main/unread/UnreadFragment.java

@ -0,0 +1,251 @@
package gr.thmmy.mthmmy.activities.main.unread;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.Toast;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary;
import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.CustomRecyclerView;
import gr.thmmy.mthmmy.utils.ParseTask;
import gr.thmmy.mthmmy.utils.exceptions.ParseException;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.Request;
import timber.log.Timber;
/**
* A {@link BaseFragment} subclass.
* Activities that contain this fragment must implement the
* {@link UnreadFragment.UnreadFragmentInteractionListener} interface
* to handle interaction events.
* Use the {@link UnreadFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class UnreadFragment extends BaseFragment {
private static final String TAG = "UnreadFragment";
// Fragment initialization parameters, e.g. ARG_SECTION_NUMBER
private MaterialProgressBar progressBar;
private SwipeRefreshLayout swipeRefreshLayout;
private UnreadAdapter unreadAdapter;
private List<TopicSummary> topicSummaries;
private UnreadTask unreadTask;
private MarkReadTask markReadTask;
// Required empty public constructor
public UnreadFragment() {
}
/**
* Use ONLY this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @return A new instance of fragment Unread.
*/
public static UnreadFragment newInstance(int sectionNumber) {
UnreadFragment fragment = new UnreadFragment();
Bundle args = new Bundle();
args.putString(ARG_TAG, TAG);
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
topicSummaries = new ArrayList<>();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (topicSummaries.isEmpty()) {
unreadTask = new UnreadTask();
unreadTask.execute(SessionManager.unreadUrl.toString());
}
markReadTask = new MarkReadTask();
Timber.d("onActivityCreated");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
final View rootView = inflater.inflate(R.layout.fragment_unread, container, false);
// Set the adapter
if (rootView instanceof RelativeLayout) {
progressBar = (MaterialProgressBar) rootView.findViewById(R.id.progressBar);
unreadAdapter = new UnreadAdapter(getActivity(), topicSummaries,
fragmentInteractionListener, new UnreadAdapter.MarkReadInteractionListener() {
@Override
public void onMarkReadInteraction(String markReadLinkUrl) {
if (markReadTask != null && markReadTask.getStatus() != AsyncTask.Status.RUNNING) {
markReadTask = new MarkReadTask();
markReadTask.execute(markReadLinkUrl);
}
}
});
CustomRecyclerView recyclerView = (CustomRecyclerView) rootView.findViewById(R.id.list);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(rootView.findViewById(R.id.list).getContext());
recyclerView.setLayoutManager(linearLayoutManager);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
linearLayoutManager.getOrientation());
recyclerView.addItemDecoration(dividerItemDecoration);
recyclerView.setAdapter(unreadAdapter);
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swiperefresh);
swipeRefreshLayout.setOnRefreshListener(
new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if (unreadTask != null && unreadTask.getStatus() != AsyncTask.Status.RUNNING) {
unreadTask = new UnreadTask();
unreadTask.execute(SessionManager.unreadUrl.toString());
}
}
}
);
}
return rootView;
}
@Override
public void onDestroy() {
super.onDestroy();
if (unreadTask != null && unreadTask.getStatus() != AsyncTask.Status.RUNNING)
unreadTask.cancel(true);
if (markReadTask != null && markReadTask.getStatus() != AsyncTask.Status.RUNNING)
markReadTask.cancel(true);
}
public interface UnreadFragmentInteractionListener extends FragmentInteractionListener {
void onUnreadFragmentInteraction(TopicSummary topicSummary);
}
//---------------------------------------ASYNC TASK-----------------------------------
private class UnreadTask extends ParseTask {
protected void onPreExecute() {
progressBar.setVisibility(ProgressBar.VISIBLE);
}
@Override
public void parse(Document document) throws ParseException {
Elements unread = document.select("table.bordercolor[cellspacing=1] tr:not(.titlebg)");
if (!unread.isEmpty()) {
topicSummaries.clear();
for (Element row : unread) {
Elements information = row.select("td");
String link = information.last().select("a").first().attr("href");
String title = information.get(2).select("a").first().text();
Element lastUserAndDate = information.get(6);
String lastUser = lastUserAndDate.select("a").text();
String dateTime = lastUserAndDate.select("span").html();
//dateTime = dateTime.replace("<br>", "");
dateTime = dateTime.substring(0, dateTime.indexOf("<br>"));
dateTime = dateTime.replace("<b>", "");
dateTime = dateTime.replace("</b>", "");
topicSummaries.add(new TopicSummary(link, title, lastUser, dateTime));
}
Element markRead = document.select("table:not(.bordercolor):not([width])").select("a")
.first();
if (markRead != null)
topicSummaries.add(new TopicSummary(markRead.attr("href"), markRead.text(), null,
null));
} else {
topicSummaries.clear();
String message = document.select("table.bordercolor[cellspacing=1]").first().text();
if (message.contains("No messages")) { //It's english
message = "No unread posts!";
} else { //It's greek
message = "Δεν υπάρχουν μη διαβασμένα μηνύματα!";
}
topicSummaries.add(new TopicSummary(null, null, null, message));
}
}
@Override
protected void postParsing(ParseTask.ResultCode result) {
if (result == ResultCode.SUCCESS)
unreadAdapter.notifyDataSetChanged();
progressBar.setVisibility(ProgressBar.INVISIBLE);
swipeRefreshLayout.setRefreshing(false);
}
}
private class MarkReadTask extends AsyncTask<String, Void, Integer> {
private static final int SUCCESS = 0;
private static final int NETWORK_ERROR = 1;
private static final int OTHER_ERROR = 2;
@Override
protected void onPreExecute() {
progressBar.setVisibility(ProgressBar.VISIBLE);
}
@Override
protected Integer doInBackground(String... strings) {
Request request = new Request.Builder()
.url(strings[0])
.build();
try {
client.newCall(request).execute();
return SUCCESS;
} catch (IOException e) {
Timber.i(e, "IO Exception");
return NETWORK_ERROR;
} catch (Exception e) {
Timber.e(e, "Exception");
return OTHER_ERROR;
}
}
@Override
protected void onPostExecute(Integer result) {
progressBar.setVisibility(ProgressBar.GONE);
if (result == NETWORK_ERROR) {
Toast.makeText(getContext()
, "Task was unsuccessful!\n Please check your internet conneciton.",
Toast.LENGTH_LONG).show();
} else if (result == OTHER_ERROR) {
Toast.makeText(getContext()
, "Fatal error!\n Task aborted...", Toast.LENGTH_LONG).show();
} else {
if (unreadTask != null && unreadTask.getStatus() != AsyncTask.Status.RUNNING) {
unreadTask = new UnreadTask();
unreadTask.execute(SessionManager.unreadUrl.toString());
}
}
}
}
}

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

@ -19,7 +19,6 @@ 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;
@ -50,7 +49,6 @@ 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 okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
@ -276,11 +274,12 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
} catch (SSLHandshakeException e) {
Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) {
Timber.e("ERROR", e);
Timber.e(e, "Exception");
}
return false;
}
//TODO: better parse error handling (ParseException etc.)
protected void onPostExecute(Boolean result) {
if (!result) { //Parse failed! //TODO report as ParseException?
Timber.d("Parse failed!");

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

@ -26,7 +26,6 @@ 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 okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
@ -167,7 +166,7 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap
} catch (SSLHandshakeException e) {
Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) {
Timber.e("ERROR", e);
Timber.e(e, "Exception");
}
return false;
}
@ -185,6 +184,7 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap
isLoadingMore = false;
}
//TODO: better parse error handling (ParseException etc.)
private boolean parseLatestPosts(Document latestPostsPage) {
Elements latestPostsRows = latestPostsPage.
select("td:has(table:Contains(Show Posts)):not([style]) > table");

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

@ -41,7 +41,6 @@ import javax.net.ssl.SSLHandshakeException;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
@ -141,11 +140,12 @@ public class StatsFragment extends Fragment {
} catch (SSLHandshakeException e) {
Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) {
Timber.e("ERROR", e);
Timber.e(e, "Exception");
}
return false;
}
//TODO: better parse error handling (ParseException etc.)
@Override
protected void onPostExecute(Boolean result) {
if (!result) { //Parse failed!

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

@ -7,7 +7,6 @@ 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;
@ -25,7 +24,6 @@ import java.util.Objects;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.utils.ParseHelpers;
import timber.log.Timber;

124
app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java

@ -11,12 +11,54 @@ import java.util.Map;
import java.util.regex.Matcher;
import okhttp3.Response;
import timber.log.Timber;
/**
* This is a utility class containing a collection of static methods to help with topic replying.
*/
class Posting {
/**
* {@link REPLY_STATUS} enum defines the different possible outcomes of a topic reply request.
*/
enum REPLY_STATUS {
SUCCESSFUL, NO_SUBJECT, EMPTY_BODY, NEW_REPLY_WHILE_POSTING, NOT_FOUND, SESSION_ENDED, OTHER_ERROR
/**
* The request was successful
*/
SUCCESSFUL,
/**
* Request was lacking a subject
*/
NO_SUBJECT,
/**
* Request had empty body
*/
EMPTY_BODY,
/**
* There were new topic replies while making the request
*/
NEW_REPLY_WHILE_POSTING,
/**
* Error 404, page was not found
*/
NOT_FOUND,
/**
* User session ended while posting the reply
*/
SESSION_ENDED,
/**
* Other undefined of unidentified error
*/
OTHER_ERROR
}
/**
* This method can be used to check whether a topic post request was successful or not and if
* not maybe get the reason why.
*
* @param response {@link okhttp3.Response} of the request
* @return a {@link REPLY_STATUS} that describes the response status
* @throws IOException method relies to {@link org.jsoup.Jsoup#parse(String)}
*/
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;
@ -26,8 +68,8 @@ class Posting {
String[] errors = postErrorPage.select("tr[id=errors] div[id=error_list]").first()
.toString().split("<br>");
for (int i = 0; i < errors.length; ++i) { //TODO test
Log.d("TAG", String.valueOf(i));
Log.d("TAG", errors[i]);
Timber.d(String.valueOf(i));
Timber.d(errors[i]);
}
for (String error : errors) {
if (error.contains("Your session timed out while posting") ||
@ -45,10 +87,19 @@ class Posting {
return REPLY_STATUS.SUCCESSFUL;
}
/**
* This is a fucked up method.. Just don't waste your time here unless you have suicidal
* tendencies.
*
* @param html the html string to be transformed to BBcode
* @return the BBcode string
*/
static String htmlToBBcode(String html) {
Log.d("Cancer", html);
Map<String, String> bbMap = new HashMap<>();
Map<String, String> smileysMap1 = new HashMap<>();
Map<String, String> smileysMap2 = new HashMap<>();
smileysMap1.put("Smiley", ":)");
smileysMap1.put("Wink", ";)");
smileysMap1.put("Cheesy", ":D");
@ -171,64 +222,66 @@ class Posting {
//html stuff on the beginning
bbMap.put("<link rel=.+\">\n ", "");
//quotes and code headers
bbMap.put("\n\\s+?<div class=\"quoteheader\">\n (.+?)\n </div>", "");
bbMap.put("\n\\s+?<div class=\"codeheader\">\n (.+?)\n </div>", "");
bbMap.put("\n\\s+?<div class=\"quote\">\n (.+?)\n </div>", "");
bbMap.put("<br>", "\n");
bbMap.put("\\s*?<div class=\"quoteheader\">(.*?(\\n))*?.*?<\\/div>", "");
bbMap.put("\\s*?<div class=\"codeheader\">(.*?(\\n))+?.*?<\\/div>", "");
bbMap.put("\\s*?<div class=\"quote\">(.*?(\\n))+?.*?<\\/div>", "");
bbMap.put("<br>", "\\\n");
//Non-breaking space
bbMap.put("&nbsp;", " ");
//bold
bbMap.put("\n\\s+?<b>(.+?)</b>", "\\[b\\]$1\\[/b\\]");
bbMap.put("\\s*?<b>([\\S\\s]+?)<\\/b>", "\\[b\\]$1\\[/b\\]");
//italics
bbMap.put("\n\\s+?<i>(.+?)</i>", "\\[i\\]$1\\[/i\\]");
bbMap.put("\\s*?<i>([\\S\\s]+?)<\\/i>", "\\[i\\]$1\\[/i\\]");
//underline
bbMap.put("\n\\s+?<span style=\"text-decoration: underline;\">(.+?)</span>", "\\[u\\]$1\\[/u\\]");
bbMap.put("\\s*?<span style=\"text-decoration: underline;\">([\\S\\s]+?)<\\/span>", "\\[u\\]$1\\[/u\\]");
//deleted
bbMap.put("\n\\s+?<del>(.+?)</del>", "\\[s\\]$1\\[/s\\]");
bbMap.put("\\s*?<del>([\\S\\s]+?)<\\/del>", "\\[s\\]$1\\[/s\\]");
//text color
bbMap.put("\n\\s+?<span style=\"color: (.+?);\">(.+?)</span>", "\\[color=$1\\]$2\\[/color\\]");
bbMap.put("\\s*?<span style=\"color: (.+?);\">([\\S\\s]+?)<\\/span>", "\\[color=$1\\]$2\\[/color\\]");
//glow
bbMap.put("\n\\s+?<span style=\"background-color: (.+?);\">(.+?)</span>", "\\[glow=$1,2,300\\]$2\\[/glow\\]");
bbMap.put("\\s*?<span style=\"background-color: (.+?);\">([\\S\\s]+?)<\\/span>", "\\[glow=$1,2,300\\]$2\\[/glow\\]");
//shadow
bbMap.put("\n\\s+?<span style=\"text-shadow: (.+?) (.+?)\">(.+?)</span>", "\\[shadow=$1,$2\\]$3\\[/shadow\\]");
bbMap.put("\\s*?<span style=\"text-shadow: (.+?) (.+?)\">([\\S\\s]+?)<\\/span>", "\\[shadow=$1,$2\\]$3\\[/shadow\\]");
//running text
bbMap.put("\\s+?<marquee>\n (.+?)\n </marquee>", "\\[move\\]$1\\[/move\\]");
bbMap.put("\\s*?<marquee>\n ([\\S\\s]+?)\n <\\/marquee>", "\\[move\\]$1\\[/move\\]");
//alignment
bbMap.put("\n\\s+?<div align=\"center\">\n (.+?)\n </div>", "\\[center\\]$1\\[/center\\]");
bbMap.put("\n\\s+?<div style=\"text-align: (.+?);\">\n (.+?)\n </div>", "\\[$1\\]$2\\[/$1\\]");
bbMap.put("\\s*?<div align=\"center\">\n ([\\S\\s]+?)\n <\\/div>", "\\[center\\]$1\\[/center\\]");
bbMap.put("\\s*?<div style=\"text-align: (.+?);\">\n ([\\S\\s]+?)\n <\\/div>", "\\[$1\\]$2\\[/$1\\]");
//preformated
bbMap.put("\n\\s+?<pre>(.+?)</pre>", "\\[pre\\]$1\\[/pre\\]");
bbMap.put("\\s*?<pre>([\\S\\s]+?)<\\/pre>", "\\[pre\\]$1\\[/pre\\]");
//horizontal rule
bbMap.put("\n\\s+?<hr>", "\\[hr\\]");
bbMap.put("\\s*?<hr>", "\\[hr\\]");
//resize
bbMap.put("\n\\s+?<span style=\"font-size: (.+?);(.+?)\">(.+?)</span>", "\\[size=$1\\]$3\\[/size\\]");
bbMap.put("\\s*?<span style=\"font-size: (.+?);(.+?)\">([\\S\\s]+?)<\\/span>", "\\[size=$1\\]$3\\[/size\\]");
//font
bbMap.put("\n\\s+?<span style=\"font-family: (.+?);\">(.+?)</span>", "\\[font=$1\\]$2\\[/font\\]");
bbMap.put("\\s*?<span style=\"font-family: (.+?);\">([\\S\\s]+?)<\\/span>", "\\[font=$1\\]$2\\[/font\\]");
//lists
bbMap.put("\\s+<li>(.+?)</li>", "\\[li\\]$1\\[/li\\]");
bbMap.put("\n\\s+<ul style=\"margin-top: 0; margin-bottom: 0;\">([\\S\\s]+?)\n\\s+</ul>",
bbMap.put("\\s+<li>(.+?)<\\/li>", "\\[li\\]$1\\[/li\\]");
bbMap.put("\n\\s+<ul style=\"margin-top: 0; margin-bottom: 0;\">([\\S\\s]+?)\n\\s+<\\/ul>",
"\\[list\\]\n$1\n\\[/list\\]");
//latex code
bbMap.put("\n\\s+?<img src=\".+?eq=(.+?)\" .+?\">", "\\[tex\\]$1\\[/tex\\]");
bbMap.put("\\s*?<img src=\".+?eq=([\\S\\s]+?)\" .+?\">", "\\[tex\\]$1\\[/tex\\]");
//code
bbMap.put("\n\\s+?<div class=\"code\">\n (.+?)\n </div>", "\\[code\\]$1\\[/code\\]");
bbMap.put("\\s*?<div class=\"code\">((.*?(\\n))+?.*?)<\\/div>", "\\[code\\]$1\\[/code\\]");
//teletype
bbMap.put("\n\\s+?<tt>(.+?)</tt>", "\\[tt\\]$1\\[/tt\\]");
bbMap.put("\\s*?<tt>([\\S\\s]+?)<\\/tt>", "\\[tt\\]$1\\[/tt\\]");
//superscript/subscript
bbMap.put("\n\\s+?<sub>(.+?)</sub>", "\\[sub\\]$1\\[/sub\\]");
bbMap.put("\n\\s+?<sup>(.+?)</sup>", "\\[sup\\]$1\\[/sup\\]");
bbMap.put("\\s*?<sub>([\\S\\s]+?)<\\/sub>", "\\[sub\\]$1\\[/sub\\]");
bbMap.put("\\s*?<sup>([\\S\\s]+?)<\\/sup>", "\\[sup\\]$1\\[/sup\\]");
//tables
bbMap.put("\\s+?<td.+?>([\\S\\s]+?)</td>", "\\[td\\]$1\\[/td\\]");
bbMap.put("<tr>([\\S\\s]+?)\n </tr>", "\\[tr\\]$1\\[/tr\\]");
bbMap.put("\n\\s+?<table style=\"(.+?)\">\n <tbody>\n ([\\S\\s]+?)\n </tbody>\n </table>"
bbMap.put("\\s*?<td.+?>([\\S\\s]+?)<\\/td>", "\\[td\\]$1\\[/td\\]");
bbMap.put("<tr>([\\S\\s]+?)\n <\\/tr>", "\\[tr\\]$1\\[/tr\\]");
bbMap.put("\\s*?<table style=\"(.+?)\">\n <tbody>\n ([\\S\\s]+?)\n <\\/tbody>\n <\\/table>"
, "\\[table\\]$2\\[/table\\]");
//videos
bbMap.put("\n\\s+?<div class=\"yt\"><a href=\".+?watch\\?v=(.+?)\"((.|\\n)*?)\\/div>\n",
bbMap.put("\\s*?<div class=\"yt\">.+?watch\\?v=(.+?)\"((.|\\n)*?)/div>\n",
"[youtube]https://www.youtube.com/watch?v=$1[/youtube]");
//ftp
bbMap.put("<a href=\"ftp:(.+?)\" .+?>([\\S\\s]+?)</a>", "\\[fpt=ftp:$1\\]$2\\[/ftp\\]");
bbMap.put("<a href=\"ftp:(.+?)\" .+?>([\\S\\s]+?)<\\/a>", "\\[fpt=ftp:$1\\]$2\\[/ftp\\]");
//mailto
bbMap.put("\n\\s+?<a href=\"mailto:(.+?)\">([\\S\\s]+?)</a>", "\\[email\\]$2\\[/email\\]");
bbMap.put("\\s*?<a href=\"mailto:(.+?)\">([\\S\\s]+?)<\\/a>", "\\[email\\]$2\\[/email\\]");
//links
bbMap.put("\n\\s+?<a href=\"(.+?)\" .+?>([\\S\\s]+?)</a>", "\\[url=$1\\]$2\\[/url\\]");
bbMap.put("\\s*?<a href=\"(.+?)\" .+?>([\\S\\s]+?)</a>", "\\[url=$1\\]$2\\[/url\\]");
//smileys
for (Map.Entry entry : smileysMap1.entrySet()) {
bbMap.put("\n <img src=\"(.+?)//www.thmmy.gr/smf/Smileys/default_dither/(.+?) alt=\""
@ -258,6 +311,7 @@ class Posting {
html = html.replaceAll("\\s+<img src=\"(.+?)\" .+? height=\"(.+?)\" .+?>", "\\[img height=$2\\]$1\\[/img\\]");
html = html.replaceAll("\\s+<img src=\"(.+?)\".+?>", "\\[img\\]$1\\[/img\\]");
Log.d("Cancer", html);
return html;
}
}

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

@ -5,15 +5,16 @@ import android.content.Intent;
import android.graphics.Rect;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
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.text.Html;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.method.ScrollingMovementMethod;
import android.text.style.ClickableSpan;
@ -47,9 +48,9 @@ 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.CustomLinearLayoutManager;
import gr.thmmy.mthmmy.utils.ParseHelpers;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import okhttp3.MultipartBody;
import okhttp3.Request;
import okhttp3.RequestBody;
@ -114,12 +115,12 @@ public class TopicActivity extends BaseActivity {
topicViewers = new SpannableStringBuilder("Loading...");
//Other variables
private MaterialProgressBar progressBar;
TextView toolbarTitle;
private TextView toolbarTitle;
private static String base_url = "";
private String topicTitle;
private String parsedTitle;
private RecyclerView recyclerView;
String loadedPageUrl = "";
private String loadedPageUrl = "";
private boolean reloadingPage = false;
@ -134,7 +135,7 @@ public class TopicActivity extends BaseActivity {
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(
Uri.parse(topicPageUrl));
if (!target.is(ThmmyPage.PageCategory.TOPIC)) {
Timber.e("Bundle came with a non topic url!\nUrl:\n" + topicPageUrl);
Timber.e("Bundle came with a non topic url!\nUrl: %s", topicPageUrl);
Toast.makeText(this, "An error has occurred\n Aborting.", Toast.LENGTH_SHORT).show();
finish();
}
@ -144,7 +145,11 @@ public class TopicActivity extends BaseActivity {
//Initializes graphics
toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbarTitle = (TextView) toolbar.findViewById(R.id.toolbar_title);
toolbarTitle.setSingleLine(true);
toolbarTitle.setEllipsize(TextUtils.TruncateAt.MARQUEE);
toolbarTitle.setMarqueeRepeatLimit(-1);
toolbarTitle.setText(topicTitle);
toolbarTitle.setSelected(true);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
@ -163,7 +168,17 @@ public class TopicActivity extends BaseActivity {
recyclerView = (RecyclerView) findViewById(R.id.topic_recycler_view);
recyclerView.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setOnTouchListener(
new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return topicTask != null && topicTask.getStatus() == AsyncTask.Status.RUNNING;
}
}
);
//LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
CustomLinearLayoutManager layoutManager = new CustomLinearLayoutManager(
getApplicationContext(), loadedPageUrl);
recyclerView.setLayoutManager(layoutManager);
topicAdapter = new TopicAdapter(this, postsList, topicTask);
recyclerView.setAdapter(topicAdapter);
@ -182,6 +197,7 @@ public class TopicActivity extends BaseActivity {
topicAdapter.prepareForReply(new ReplyTask(), topicTitle, loadedPageUrl);
replyFAB.hide();
bottomNavBar.setVisibility(View.GONE);
recyclerView.scrollToPosition(postsList.size() - 1);
}
}
});
@ -210,6 +226,7 @@ public class TopicActivity extends BaseActivity {
// 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));
super.onCreateOptionsMenu(menu);
return true;
}
@ -253,8 +270,9 @@ public class TopicActivity extends BaseActivity {
@Override
protected void onResume() {
drawer.setSelection(-1);
super.onResume();
refreshTopicBookmark();
drawer.setSelection(-1);
}
@Override
@ -517,10 +535,10 @@ public class TopicActivity extends BaseActivity {
parse(document);
return SUCCESS;
} catch (IOException e) {
Timber.i("IO Exception", e);
Timber.i(e, "IO Exception");
return NETWORK_ERROR;
} catch (Exception e) {
Timber.e("Exception", e);
Timber.e(e, "Exception");
return OTHER_ERROR;
}
}
@ -545,8 +563,12 @@ public class TopicActivity extends BaseActivity {
}
progressBar.setVisibility(ProgressBar.INVISIBLE);
topicAdapter.customNotifyDataSetChanged(new TopicTask());
if (replyPageUrl == null) replyFAB.hide();
recyclerView.getRecycledViewPool().clear(); //Avoid inconsistency detected bug
if (replyPageUrl == null) {
replyFAB.hide();
topicAdapter.customNotifyDataSetChanged(new TopicTask(), false);
} else topicAdapter.customNotifyDataSetChanged(new TopicTask(), true);
if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(true);
//Set current page
@ -560,7 +582,10 @@ public class TopicActivity extends BaseActivity {
break;
case SAME_PAGE:
progressBar.setVisibility(ProgressBar.INVISIBLE);
topicAdapter.customNotifyDataSetChanged(new TopicTask());
if (replyPageUrl == null) {
replyFAB.hide();
topicAdapter.customNotifyDataSetChanged(new TopicTask(), false);
} else topicAdapter.customNotifyDataSetChanged(new TopicTask(), true);
if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(true);
paginationEnabled(true);
Toast.makeText(TopicActivity.this, "That's the same page.", Toast.LENGTH_SHORT).show();
@ -568,7 +593,7 @@ public class TopicActivity extends BaseActivity {
break;
default:
//Parse failed - should never happen
Timber.d("Parse failed!"); //TODO report ParseException?
Timber.d("Parse failed!"); //TODO report ParseException!!!
Toast.makeText(getBaseContext(), "Fatal Error", Toast.LENGTH_SHORT).show();
finish();
break;
@ -624,6 +649,9 @@ public class TopicActivity extends BaseActivity {
}
postsList.clear();
int oldSize = postsList.size();
topicAdapter.notifyItemRangeRemoved(0, oldSize);
recyclerView.getRecycledViewPool().clear(); //Avoid inconsistency detected bug
postsList.addAll(TopicParser.parseTopic(topic, language));
}
@ -661,7 +689,13 @@ public class TopicActivity extends BaseActivity {
}
private SpannableStringBuilder getSpannableFromHtml(String html) {
CharSequence sequence = Html.fromHtml(html);
CharSequence sequence;
if (Build.VERSION.SDK_INT >= 24) {
sequence = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
} else {
//noinspection deprecation
sequence = Html.fromHtml(html);
}
SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
URLSpan[] urls = strBuilder.getSpans(0, sequence.length(), URLSpan.class);
for (URLSpan span : urls) {
@ -683,7 +717,7 @@ public class TopicActivity extends BaseActivity {
@Override
protected Boolean doInBackground(String... message) {
Document document;
String numReplies, seqnum, sc, subject, topic;
String numReplies, seqnum, sc, topic;
Request request = new Request.Builder()
.url(replyPageUrl + ";wap2")
@ -695,13 +729,12 @@ public class TopicActivity extends BaseActivity {
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);
Timber.e(e, "Post failed.");
return false;
} catch (Selector.SelectorParseException e) {
Timber.e("Post failed.", e);
Timber.e(e, "Post failed.");
return false;
}
@ -735,7 +768,7 @@ public class TopicActivity extends BaseActivity {
return true;
}
} catch (IOException e) {
Timber.e("Post failed.", e);
Timber.e(e, "Post failed.");
return false;
}
}

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

@ -18,11 +18,10 @@ 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;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
@ -54,7 +53,6 @@ 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 timber.log.Timber;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@ -76,36 +74,32 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static int THUMBNAIL_SIZE;
private final Context context;
private String topicTitle;
private ArrayList<Integer> toQuoteList = new ArrayList<>();
private final ArrayList<Integer> toQuoteList = new ArrayList<>();
private final List<Post> postsList;
/**
* Used to hold the state of visibility and other attributes for views that are animated or
* otherwise changed. Used in combination with {@link #isPostDateAndNumberVisibile},
* {@link #isUserExtraInfoVisibile} and {@link #isQuoteButtonChecked}.
* otherwise changed. Used in combination with {@link #isUserExtraInfoVisibile} and
* {@link #isQuoteButtonChecked}.
*/
private final ArrayList<boolean[]> viewProperties = new ArrayList<>();
/**
* Index of state indicator in the boolean array. If true post is expanded and post's date and
* number are visible.
*/
private static final int isPostDateAndNumberVisibile = 0;
/**
* Index of state indicator in the boolean array. If true user's extra info are expanded and
* visible.
*/
private static final int isUserExtraInfoVisibile = 1;
private static final int isUserExtraInfoVisibile = 0;
/**
* Index of state indicator in the boolean array. If true quote button for this post is checked.
*/
private static final int isQuoteButtonChecked = 2;
private static final int isQuoteButtonChecked = 1;
private TopicActivity.TopicTask topicTask;
private TopicActivity.ReplyTask replyTask;
private final int VIEW_TYPE_POST = 0;
private final int VIEW_TYPE_QUICK_REPLY = 1;
private String[] replyDataHolder = new String[2];
private int replySubject = 0, replyText = 1;
private final String[] replyDataHolder = new String[2];
private final int replySubject = 0, replyText = 1;
private String loadedPageUrl = "";
private boolean canReply = false;
/**
* @param context the context of the {@link RecyclerView}
@ -144,6 +138,23 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
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);
final EditText quickReplyText = (EditText) view.findViewById(R.id.quick_reply_text);
quickReplyText.setFocusableInTouchMode(true);
quickReplyText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
quickReplyText.post(new Runnable() {
@Override
public void run() {
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(quickReplyText, InputMethodManager.SHOW_IMPLICIT);
}
});
}
});
quickReplyText.requestFocus();
//Default post subject
replyDataHolder[replySubject] = "Re: " + topicTitle;
//Build quotes
@ -161,7 +172,8 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@SuppressLint({"SetJavaScriptEnabled", "SetTextI18n"})
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder currentHolder, final int position) {
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;
@ -169,6 +181,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
//Post's WebView parameters
holder.post.setClickable(true);
holder.post.setWebViewClient(new LinkLauncher());
holder.post.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
//Avoids errors about layout having 0 width/height
holder.thumbnail.setMinimumWidth(1);
@ -325,9 +338,23 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
if (!currentPost.isDeleted() && viewProperties.get(position)[isUserExtraInfoVisibile]) {
holder.userExtraInfo.setVisibility(View.VISIBLE);
holder.userExtraInfo.setAlpha(1.0f);
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 {
holder.userExtraInfo.setVisibility(View.GONE);
holder.userExtraInfo.setAlpha(0.0f);
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);
}
if (!currentPost.isDeleted()) {
//Sets graphics behavior
@ -355,7 +382,9 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
boolean[] tmp = viewProperties.get(holder.getAdapterPosition());
tmp[isUserExtraInfoVisibile] = !tmp[isUserExtraInfoVisibile];
viewProperties.set(holder.getAdapterPosition(), tmp);
TopicAnimations.animateUserExtraInfoVisibility(holder.userExtraInfo);
TopicAnimations.animateUserExtraInfoVisibility(holder.username,
holder.subject, Color.parseColor("#FFFFFF"),
Color.parseColor("#757575"), holder.userExtraInfo);
}
});
//Clicking the expanded part of a header (the extra info) makes it collapse
@ -366,7 +395,9 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
tmp[isUserExtraInfoVisibile] = false;
viewProperties.set(holder.getAdapterPosition(), tmp);
TopicAnimations.animateUserExtraInfoVisibility(v);
TopicAnimations.animateUserExtraInfoVisibility(holder.username,
holder.subject, Color.parseColor("#FFFFFF"),
Color.parseColor("#757575"), v);
}
});
} else {
@ -374,34 +405,10 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
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())
if (!BaseActivity.getSessionManager().isLoggedIn() || !canReply) {
holder.quoteToggle.setVisibility(View.GONE);
else {
} else {
if (viewProperties.get(position)[isQuoteButtonChecked])
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked);
else
@ -427,23 +434,6 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
}
});
}
//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));
} else if (currentHolder instanceof QuickReplyViewHolder) {
final QuickReplyViewHolder holder = (QuickReplyViewHolder) currentHolder;
@ -482,14 +472,16 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
}
}
void customNotifyDataSetChanged(TopicActivity.TopicTask topicTask) {
void customNotifyDataSetChanged(TopicActivity.TopicTask topicTask, boolean canReply) {
this.topicTask = topicTask;
this.canReply = canReply;
viewProperties.clear();
for (int i = 0; i < postsList.size(); ++i) {
//Initializes properties, array's values will be false by default
viewProperties.add(new boolean[3]);
}
notifyDataSetChanged();
notifyItemRangeInserted(0, postsList.size());
//notifyDataSetChanged();
}
@Override
@ -503,7 +495,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private class PostViewHolder extends RecyclerView.ViewHolder {
final CardView cardView;
final LinearLayout cardChildLinear;
final FrameLayout postDateAndNumberExp;
final FrameLayout postDateAndNumber;
final TextView postDate, postNum, username, subject;
final ImageView thumbnail;
final public WebView post;
@ -521,7 +513,7 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
//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);
postDateAndNumber = (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);
@ -570,71 +562,6 @@ class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
}
}
/**
* This class is a gesture detector for WebViews. It handles post's clicks, long clicks and
* touch and drag.
*/
private class CustomTouchListener implements View.OnTouchListener {
//Long press handling
private float downCoordinateX;
private float downCoordinateY;
private final float SCROLL_THRESHOLD = 7;
final private WebView post;
final private CardView cardView;
//Other variables
final static int FINGER_RELEASED = 0;
final static int FINGER_TOUCHED = 1;
final static int FINGER_DRAGGING = 2;
final static int FINGER_UNDEFINED = 3;
private int fingerState = FINGER_RELEASED;
CustomTouchListener(WebView pPost, CardView pCard) {
post = pPost;
cardView = pCard;
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
//Logs XY
downCoordinateX = motionEvent.getX();
downCoordinateY = motionEvent.getY();
if (fingerState == FINGER_RELEASED)
fingerState = FINGER_TOUCHED;
else
fingerState = FINGER_UNDEFINED;
break;
case MotionEvent.ACTION_UP:
if (fingerState != FINGER_DRAGGING) {
//Doesn't expand the card if this was a link
WebView.HitTestResult htResult = post.getHitTestResult();
if (htResult.getExtra() != null
&& htResult.getExtra() != null) {
fingerState = FINGER_RELEASED;
return false;
}
cardView.performClick();
}
fingerState = FINGER_RELEASED;
break;
case MotionEvent.ACTION_MOVE:
//Cancels long click if finger moved too much
if (((Math.abs(downCoordinateX - motionEvent.getX()) > SCROLL_THRESHOLD ||
Math.abs(downCoordinateY - motionEvent.getY()) > SCROLL_THRESHOLD))) {
fingerState = FINGER_DRAGGING;
} else fingerState = FINGER_UNDEFINED;
break;
default:
fingerState = FINGER_UNDEFINED;
}
return false;
}
}
/**
* This class is used to handle link clicks in WebViews. When link url is one that the app can
* handle internally, it does. Otherwise user is prompt to open the link in a browser.

86
app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAnimations.java

@ -7,80 +7,16 @@ import android.view.View;
import android.widget.TextView;
class TopicAnimations {
//--------------------------POST'S INFO VISIBILITY CHANGE ANIMATION METHOD--------------------------
/**
* Method that animates view's visibility changes for post's extra info
*/
static void animatePostExtraInfoVisibility(final View dateAndPostNum, TextView username,
TextView subject, int expandedColor, int collapsedColor) {
//If the view is gone fade it in
if (dateAndPostNum.getVisibility() == View.GONE) {
//Show full username
username.setMaxLines(Integer.MAX_VALUE); //As in the android sourcecode
username.setEllipsize(null);
//Show full subject
subject.setTextColor(expandedColor);
subject.setMaxLines(Integer.MAX_VALUE); //As in the android sourcecode
subject.setEllipsize(null);
dateAndPostNum.clearAnimation();
// Prepare the View for the animation
dateAndPostNum.setVisibility(View.VISIBLE);
dateAndPostNum.setAlpha(0.0f);
// Start the animation
dateAndPostNum.animate()
.translationY(0)
.alpha(1.0f)
.setDuration(300)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
dateAndPostNum.setVisibility(View.VISIBLE);
}
});
}
//If the view is visible fade it out
else {
username.setMaxLines(1); //As in the android sourcecode
username.setEllipsize(TextUtils.TruncateAt.END);
subject.setTextColor(collapsedColor);
subject.setMaxLines(1); //As in the android sourcecode
subject.setEllipsize(TextUtils.TruncateAt.END);
dateAndPostNum.clearAnimation();
// Start the animation
dateAndPostNum.animate()
.translationY(dateAndPostNum.getHeight())
.alpha(0.0f)
.setDuration(300)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
dateAndPostNum.setVisibility(View.GONE);
}
});
}
}
//------------------------POST'S INFO VISIBILITY CHANGE ANIMATION METHOD END------------------------
//--------------------------USER'S INFO VISIBILITY CHANGE ANIMATION METHOD--------------------------
/**
* Method that animates view's visibility changes for user's extra info
*/
static void animateUserExtraInfoVisibility(final View userExtra) {
static void animateUserExtraInfoVisibility(TextView username, TextView subject,
int expandedColor, int collapsedColor,
final View userExtra) {
//If the view is gone fade it in
if (userExtra.getVisibility() == View.GONE) {
userExtra.clearAnimation();
userExtra.setVisibility(View.VISIBLE);
userExtra.setAlpha(0.0f);
@ -96,6 +32,15 @@ class TopicAnimations {
userExtra.setVisibility(View.VISIBLE);
}
});
//Show full username
username.setMaxLines(Integer.MAX_VALUE); //As in the android sourcecode
username.setEllipsize(null);
//Show full subject
subject.setTextColor(expandedColor);
subject.setMaxLines(Integer.MAX_VALUE); //As in the android sourcecode
subject.setEllipsize(null);
}
//If the view is visible fade it out
else {
@ -112,6 +57,13 @@ class TopicAnimations {
userExtra.setVisibility(View.GONE);
}
});
username.setMaxLines(1); //As in the android sourcecode
username.setEllipsize(TextUtils.TruncateAt.END);
subject.setTextColor(collapsedColor);
subject.setMaxLines(1); //As in the android sourcecode
subject.setEllipsize(TextUtils.TruncateAt.END);
}
}
//------------------------POST'S INFO VISIBILITY CHANGE ANIMATION METHOD END------------------------

3
app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java

@ -1,7 +1,6 @@
package gr.thmmy.mthmmy.activities.topic;
import android.graphics.Color;
import android.util.Log;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
@ -259,7 +258,7 @@ class TopicParser {
try {
attachedUrl = new URL(tmpAttachedFileUrlAndName.attr("href"));
} catch (MalformedURLException e) {
Timber.e("Attached file malformed url", e);
Timber.e(e, "Attached file malformed url");
break;
}
String attachedFileName = tmpAttachedFileUrlAndName.text().substring(1);

92
app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java

@ -65,7 +65,6 @@ public abstract class BaseActivity extends AppCompatActivity {
private static final String BOOKMARKED_BOARDS_KEY = "bookmarkedBoardsKey";
protected Bookmark thisPageBookmark;
private MenuItem thisPageBookmarkMenuButton;
private ImageButton thisPageBookmarkImageButton;
private SharedPreferences bookmarksFile;
private ArrayList<Bookmark> topicsBookmarked;
private ArrayList<Bookmark> boardsBookmarked;
@ -76,6 +75,8 @@ public abstract class BaseActivity extends AppCompatActivity {
protected Toolbar toolbar;
protected Drawer drawer;
private MainActivity mainActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -86,25 +87,23 @@ public abstract class BaseActivity extends AppCompatActivity {
if (sessionManager == null)
sessionManager = BaseApplication.getInstance().getSessionManager();
if (sessionManager.isLoggedIn()) {
if (bookmarked == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
bookmarked = getResources().getDrawable(R.drawable.ic_bookmark_true, null);
} else //noinspection deprecation
bookmarked = getResources().getDrawable(R.drawable.ic_bookmark_true);
}
if (notBookmarked == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notBookmarked = getResources().getDrawable(R.drawable.ic_bookmark_false, null);
} else //noinspection deprecation
notBookmarked = getResources().getDrawable(R.drawable.ic_bookmark_false);
}
if (topicsBookmarked == null || boardsBookmarked == null) {
bookmarksFile = getSharedPreferences(BOOKMARKS_SHARED_PREFS, Context.MODE_PRIVATE);
loadSavedBookmarks();
}
if (bookmarked == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
bookmarked = getResources().getDrawable(R.drawable.ic_bookmark_true, null);
} else //noinspection deprecation
bookmarked = getResources().getDrawable(R.drawable.ic_bookmark_true);
}
if (notBookmarked == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notBookmarked = getResources().getDrawable(R.drawable.ic_bookmark_false, null);
} else //noinspection deprecation
notBookmarked = getResources().getDrawable(R.drawable.ic_bookmark_false);
}
if (topicsBookmarked == null || boardsBookmarked == null) {
bookmarksFile = getSharedPreferences(BOOKMARKS_SHARED_PREFS, Context.MODE_PRIVATE);
loadSavedBookmarks();
}
}
@Override
@ -221,14 +220,6 @@ public abstract class BaseActivity extends AppCompatActivity {
.withName(R.string.downloads)
.withIcon(downloadsIcon)
.withSelectedIcon(downloadsIconSelected);
bookmarksItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedPrimaryColor)
.withSelectedTextColor(selectedSecondaryColor)
.withIdentifier(BOOKMARKS_ID)
.withName(R.string.bookmark)
.withIcon(bookmarksIcon)
.withSelectedIcon(bookmarksIconSelected);
} else
loginLogoutItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
@ -237,6 +228,15 @@ public abstract class BaseActivity extends AppCompatActivity {
.withIcon(loginIcon)
.withSelectable(false);
bookmarksItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedPrimaryColor)
.withSelectedTextColor(selectedSecondaryColor)
.withIdentifier(BOOKMARKS_ID)
.withName(R.string.bookmark)
.withIcon(bookmarksIcon)
.withSelectedIcon(bookmarksIconSelected);
aboutItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedPrimaryColor)
@ -333,11 +333,13 @@ public abstract class BaseActivity extends AppCompatActivity {
if (sessionManager.isLoggedIn())
drawerBuilder.addDrawerItems(homeItem, bookmarksItem, downloadsItem, loginLogoutItem, aboutItem);
else
drawerBuilder.addDrawerItems(homeItem, loginLogoutItem, aboutItem);
drawerBuilder.addDrawerItems(homeItem, bookmarksItem, loginLogoutItem, aboutItem);
drawer = drawerBuilder.build();
drawer.getActionBarDrawerToggle().setDrawerIndicatorEnabled(false);
if (!(BaseActivity.this instanceof MainActivity))
drawer.getActionBarDrawerToggle().setDrawerIndicatorEnabled(false);
drawer.setOnDrawerNavigationListener(new Drawer.OnDrawerNavigationListener() {
@Override
public boolean onNavigationClickListener(View clickedView) {
@ -352,7 +354,6 @@ public abstract class BaseActivity extends AppCompatActivity {
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
{
drawer.removeItem(DOWNLOADS_ID);
drawer.removeItem(BOOKMARKS_ID);
loginLogoutItem.withName(R.string.login).withIcon(loginIcon); //Swap logout with login
profileDrawerItem.withName(sessionManager.getUsername()).withIcon(new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_user)
@ -395,6 +396,8 @@ public abstract class BaseActivity extends AppCompatActivity {
protected void onPostExecute(Integer result) {
Toast.makeText(getBaseContext(), "Logged out successfully!", Toast.LENGTH_LONG).show();
updateDrawer();
if (mainActivity != null)
mainActivity.updateTabs();
progressDialog.dismiss();
}
}
@ -419,6 +422,18 @@ public abstract class BaseActivity extends AppCompatActivity {
}
}
protected void refreshTopicBookmark() {
if (thisPageBookmarkMenuButton == null) {
return;
}
loadSavedBookmarks();
if (thisPageBookmark.matchExists(topicsBookmarked)) {
thisPageBookmarkMenuButton.setIcon(bookmarked);
} else {
thisPageBookmarkMenuButton.setIcon(notBookmarked);
}
}
protected void topicMenuBookmarkClick() {
if (thisPageBookmark.matchExists(topicsBookmarked)) {
thisPageBookmarkMenuButton.setIcon(notBookmarked);
@ -432,7 +447,6 @@ public abstract class BaseActivity extends AppCompatActivity {
}
protected void setBoardBookmark(final ImageButton thisPageBookmarkImageButton) {
this.thisPageBookmarkImageButton = thisPageBookmarkImageButton;
if (thisPageBookmark.matchExists(boardsBookmarked)) {
thisPageBookmarkImageButton.setImageDrawable(bookmarked);
} else {
@ -453,6 +467,17 @@ public abstract class BaseActivity extends AppCompatActivity {
});
}
protected void refreshBoardBookmark(final ImageButton thisPageBookmarkImageButton) {
if (thisPageBookmarkImageButton == null)
return;
loadSavedBookmarks();
if (thisPageBookmark.matchExists(boardsBookmarked)) {
thisPageBookmarkImageButton.setImageDrawable(bookmarked);
} else {
thisPageBookmarkImageButton.setImageDrawable(notBookmarked);
}
}
private void loadSavedBookmarks() {
String tmpString = bookmarksFile.getString(BOOKMARKED_TOPICS_KEY, null);
if (tmpString != null)
@ -565,4 +590,9 @@ public abstract class BaseActivity extends AppCompatActivity {
}
//----------------------------------MISC----------------------
protected void setMainActivity(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
}

2
app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java

@ -35,8 +35,6 @@ import okhttp3.Response;
import timber.log.Timber;
public class BaseApplication extends Application {
private static BaseApplication baseApplication; //BaseApplication singleton
// Client & SessionManager

29
app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java

@ -6,7 +6,6 @@ import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import okhttp3.OkHttpClient;
import timber.log.Timber;
public abstract class BaseFragment extends Fragment {
protected static final String ARG_SECTION_NUMBER = "SectionNumber";
@ -26,31 +25,6 @@ public abstract class BaseFragment extends Fragment {
if (client == null)
client = BaseApplication.getInstance().getClient(); //must check every time - e.g.
// becomes null when app restarts after crash
Timber.d("onCreate");
}
@Override
public void onStart() {
super.onStart();
Timber.d("onStart");
}
@Override
public void onResume() {
super.onResume();
Timber.d("onResume");
}
@Override
public void onPause() {
super.onPause();
Timber.d("onPause");
}
@Override
public void onStop() {
super.onStop();
Timber.d("onStop");
}
@Override
@ -76,6 +50,5 @@ 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 {}
}

2
app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java

@ -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;
Timber.v("Unknown thmmy link found, link: " + uriString);
Timber.v("Unknown thmmy link found, link: %s" , uriString);
return PageCategory.UNKNOWN_THMMY;
}
return PageCategory.NOT_THMMY;

3
app/src/main/java/gr/thmmy/mthmmy/receiver/Receiver.java

@ -14,7 +14,6 @@ import android.webkit.MimeTypeMap;
import java.io.File;
import gr.thmmy.mthmmy.R;
import timber.log.Timber;
import static gr.thmmy.mthmmy.services.DownloadService.ACTION_DOWNLOAD;
@ -50,7 +49,7 @@ public class Receiver extends BroadcastReceiver {
.setContentText(text)
.setTicker(ticker)
.setAutoCancel(true)
.setSmallIcon(R.mipmap.ic_launcher);
.setSmallIcon(R.drawable.ic_file_download);
if (state.equals(STARTED))
builder.setOngoing(true);

7
app/src/main/java/gr/thmmy/mthmmy/services/DownloadService.java

@ -13,7 +13,6 @@ import java.io.IOException;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.receiver.Receiver;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
@ -142,7 +141,7 @@ public class DownloadService extends IntentService {
fileName = file.getName();
Timber.v("Started saving file " + fileName);
Timber.v("Started saving file %s" , fileName);
sendNotification(downloadId, STARTED, fileName);
sink = Okio.buffer(Okio.sink(file));
@ -154,11 +153,11 @@ public class DownloadService extends IntentService {
Timber.e("Response not a binary file!");
} catch (FileNotFoundException e) {
Timber.i("Download failed...");
Timber.e("FileNotFound", e);
Timber.e(e, "FileNotFound");
sendNotification(downloadId, FAILED, fileName);
} catch (IOException e) {
Timber.i("Download failed...");
Timber.e("IOException", e);
Timber.e(e, "IOException");
sendNotification(downloadId, FAILED, fileName);
} finally {
if (sink != null) {

11
app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java

@ -36,6 +36,7 @@ public class SessionManager {
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");
private static final HttpUrl loginUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=login2");
public static final HttpUrl unreadUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=unread;all;start=0;theme=4");
private static final String guestName = "Guest";
//Response Codes
@ -154,10 +155,10 @@ public class SessionManager {
Timber.i("Login InterruptedIOException"); //users cancels LoginTask
return CANCELLED;
} catch (IOException e) {
Timber.w("Login IOException", e);
Timber.w(e ,"Login IOException");
return CONNECTION_ERROR;
} catch (Exception e) {
Timber.w("Login Exception (other)", e);
Timber.e(e, "Login Exception (other)");
return EXCEPTION;
}
}
@ -223,7 +224,7 @@ public class SessionManager {
Timber.w("Logout IOException", e);
return CONNECTION_ERROR;
} catch (Exception e) {
Timber.w("Logout Exception", e);
Timber.e(e, "Logout Exception");
return EXCEPTION;
} finally {
//All data should always be cleared from device regardless the result of logout
@ -317,7 +318,7 @@ public class SessionManager {
if (userName != null && !userName.isEmpty())
return userName;
Timber.e("ParseException", new ParseException("Parsing failed(username extraction)"));
Timber.e(new ParseException("Parsing failed(username extraction)"),"ParseException");
return "User"; //return a default username
}
@ -341,7 +342,7 @@ public class SessionManager {
if (link != null && !link.isEmpty())
return link;
}
Timber.e("ParseException", new ParseException("Parsing failed(logoutLink extraction)"));
Timber.e(new ParseException("Parsing failed(logoutLink extraction)"),"ParseException");
return "https://www.thmmy.gr/smf/index.php?action=logout"; //return a default link
}
//----------------------------------OTHER FUNCTIONS END-----------------------------------------

27
app/src/main/java/gr/thmmy/mthmmy/utils/CrashReportingTree.java

@ -4,31 +4,36 @@ import android.util.Log;
import com.google.firebase.crash.FirebaseCrash;
import gr.thmmy.mthmmy.utils.exceptions.UnknownException;
import timber.log.Timber;
import timber.log.Timber.DebugTree;
public class CrashReportingTree extends Timber.Tree {
public class CrashReportingTree extends DebugTree {
@Override
protected void log(int priority, String tag, String message, Throwable t) {
if (priority == Log.VERBOSE || priority == Log.DEBUG) {
return;
}
String level="A";
char level;
if (priority == Log.INFO)
level = "I";
level = 'I';
else if (priority == Log.WARN)
level = "W";
level = 'W';
else if(priority == Log.ERROR)
level = "E";
level = 'E';
else
level = 'A';
FirebaseCrash.log(level + "/" + tag + ": " + message);
if(t==null)
t = new UnknownException("UnknownException");
if(priority == Log.ERROR)
{
if (t!=null)
FirebaseCrash.report(t);
else
FirebaseCrash.report(new Exception(message));
}
if ((priority == Log.ERROR))
FirebaseCrash.report(t);
}
}

38
app/src/main/java/gr/thmmy/mthmmy/utils/CustomLinearLayoutManager.java

@ -0,0 +1,38 @@
package gr.thmmy.mthmmy.utils;
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import timber.log.Timber;
public class CustomLinearLayoutManager extends LinearLayoutManager {
private String pageUrl;
public CustomLinearLayoutManager(Context context, String pageUrl) {
super(context);
this.pageUrl = pageUrl;
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
Timber.wtf(e, "Inconsistency detected: topic_requested = \"" + pageUrl + "\"");
Log.d("CustomLinearLayoutMan", "Inconsistency detected: topic_requested = \""
+ pageUrl + "\"", e);
}
}
/**
* Disable predictive animations. There is a bug in RecyclerView which causes views that
* are being reloaded to pull invalid ViewHolders from the internal recycler stack if the
* adapter size has decreased since the ViewHolder was recycled.
*/
@Override
public boolean supportsPredictiveItemAnimations() {
return false;
}
}

2
app/src/main/java/gr/thmmy/mthmmy/utils/ParseHelpers.java

@ -156,7 +156,7 @@ public class ParseHelpers {
+ "<a href=\"https://www.youtube.com/watch?v="
+ embededVideosUrls.get(tmp_counter) + "\" target=\"_blank\">"
+ "<img class=\"embedded-video-play\" "
+ "src=\"http://www.youtube.com/yt/brand/media/image/YouTube_light_color_icon.png\">"
+ "src=\"YouTube_light_color_icon.png\">"
+ "</a>"
+ "<img src=\"https://img.youtube.com/vi/"
+ embededVideosUrls.get(tmp_counter)

67
app/src/main/java/gr/thmmy/mthmmy/utils/ParseTask.java

@ -0,0 +1,67 @@
package gr.thmmy.mthmmy.utils;
import android.os.AsyncTask;
import android.widget.Toast;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.IOException;
import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.utils.exceptions.ParseException;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
/**
* An {@link AsyncTask} class to be inherited for asynchronous parsing.
* Do NOT override doInBackground() and onPostExecute directly.
* Default usage while executing is ParseTask.execute(urlToParse), however feel free to override
* and modify prepareRequest() as needed.
*/
public abstract class ParseTask extends AsyncTask<String, Void, ParseTask.ResultCode> {
protected enum ResultCode {
SUCCESS, PARSING_ERROR, NETWORK_ERROR, OTHER_ERROR
}
protected abstract void parse (Document document) throws ParseException;
protected abstract void postParsing (ParseTask.ResultCode result); //ResultCode.NETWORK_ERROR is handled automatically
protected Request prepareRequest(String... params) {
return new Request.Builder()
.url(params[0])
.build();
}
@Override
protected ResultCode doInBackground(String... params) {
Request request = prepareRequest(params);
try {
Response response = BaseApplication.getInstance().getClient().newCall(request).execute();
Document document = Jsoup.parse(response.body().string());
parse(document);
return ResultCode.SUCCESS;
} catch (ParseException e) {
Timber.tag(this.getClass().getSimpleName());
Timber.e(e, "Parsing Error");
return ResultCode.PARSING_ERROR;
} catch (IOException e) {
Timber.tag(this.getClass().getSimpleName());
Timber.i(e, "Network Error");
return ResultCode.NETWORK_ERROR;
} catch (Exception e) {
Timber.tag(this.getClass().getSimpleName());
Timber.e(e, "Other Error");
return ResultCode.OTHER_ERROR;
}
}
@Override
protected void onPostExecute(ParseTask.ResultCode result) {
if (result == ResultCode.NETWORK_ERROR)
Toast.makeText(BaseApplication.getInstance().getApplicationContext(), "Network error", Toast.LENGTH_SHORT).show();
postParsing(result);
}
}

3
app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareFABBehavior.java

@ -41,8 +41,7 @@ public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior<FloatingA
fab.setVisibility(View.INVISIBLE);
}
});
} else if ((dyConsumed < 0 || (!target.canScrollVertically(1) && dyConsumed == 0
&& dyUnconsumed > 40)) && child.getVisibility() != View.VISIBLE) {
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
child.show();
}
}

3
app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareLinearBehavior.java

@ -35,8 +35,7 @@ public class ScrollAwareLinearBehavior extends CoordinatorLayout.Behavior<View>
if ((dyConsumed > 0 || (!target.canScrollVertically(-1) && dyConsumed == 0
&& dyUnconsumed < 40)) && bottomNavBar.getVisibility() == View.VISIBLE) {
hide(bottomNavBar);
} else if ((dyConsumed < 0 || (!target.canScrollVertically(1) && dyConsumed == 0
&& dyUnconsumed > 40)) && bottomNavBar.getVisibility() != View.VISIBLE) {
} else if (dyConsumed < 0 && bottomNavBar.getVisibility() != View.VISIBLE) {
show(bottomNavBar);
}
}

14
app/src/main/java/gr/thmmy/mthmmy/utils/exceptions/UnknownException.java

@ -1,14 +0,0 @@
package gr.thmmy.mthmmy.utils.exceptions;
/**
* UnknownException is thrown upon an error (see Report.java in release), when no other specific
* exception is set, to report to FireBase.
*/
public class UnknownException extends Exception {
public UnknownException() {
}
public UnknownException(String message) {
super(message);
}
}

9
app/src/main/res/drawable/ic_file_download.xml

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#ffffff"
android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
</vector>

9
app/src/main/res/drawable/ic_reply.xml

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z"/>
</vector>

34
app/src/main/res/layout-sw600dp/activity_main.xml

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".activities.main.MainActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ToolbarTheme">
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMaxWidth="0dp"
app:tabGravity="fill"
app:tabMode="fixed"
app:tabSelectedTextColor="@color/accent"
app:tabTextColor="@color/white"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>

114
app/src/main/res/layout-v21/activity_topic_post_row.xml

@ -1,48 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingEnd="4dp"
android:paddingStart="4dp"
tools:ignore="SmallSp">
<FrameLayout
android:id="@+id/post_date_and_number_exp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:animateLayoutChanges="true"
android:visibility="gone">
<TextView
android:id="@+id/post_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:paddingEnd="5dp"
android:paddingStart="5dp"
android:text=""
android:textColor="@color/card_expand_text_color"
android:textSize="8sp"
/>
<TextView
android:id="@+id/post_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:paddingEnd="5dp"
android:paddingStart="5dp"
android:text=""
android:textColor="@color/card_expand_text_color"
android:textSize="8sp"
/>
</FrameLayout>
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingEnd="4dp"
android:paddingStart="4dp"
tools:ignore="SmallSp">
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -93,7 +59,7 @@
android:maxHeight="@dimen/thumbnail_size"
android:maxWidth="@dimen/thumbnail_size"
android:src="@drawable/ic_default_user_thumbnail"
android:transitionName="user_thumbnail"/>
android:transitionName="user_thumbnail" />
</FrameLayout>
<TextView
@ -106,7 +72,7 @@
android:maxLines="1"
android:text="@string/post_author"
android:textColor="@color/primary_text"
android:textStyle="bold"/>
android:textStyle="bold" />
<TextView
android:id="@+id/subject"
@ -116,8 +82,7 @@
android:layout_toEndOf="@+id/thumbnail_holder"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/post_subject"
/>
android:text="@string/post_subject" />
</RelativeLayout>
<ImageButton
@ -129,7 +94,7 @@
android:background="@color/card_background"
android:clickable="true"
android:contentDescription="@string/post_quote_button"
android:src="@drawable/ic_format_quote_unchecked"/>
android:src="@drawable/ic_format_quote_unchecked" />
</LinearLayout>
<LinearLayout
@ -150,7 +115,7 @@
android:layout_height="wrap_content"
android:textColor="@color/card_expand_text_color"
android:textSize="10sp"
android:visibility="gone"/>
android:visibility="gone" />
<TextView
android:id="@+id/rank"
@ -158,7 +123,7 @@
android:layout_height="wrap_content"
android:textColor="@color/card_expand_text_color"
android:textSize="10sp"
android:visibility="gone"/>
android:visibility="gone" />
<TextView
android:id="@+id/stars"
@ -166,8 +131,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textSize="10sp"
android:visibility="gone">
</TextView>
android:visibility="gone" />
<TextView
android:id="@+id/gender"
@ -175,7 +139,7 @@
android:layout_height="wrap_content"
android:textColor="@color/card_expand_text_color"
android:textSize="10sp"
android:visibility="gone"/>
android:visibility="gone" />
<TextView
android:id="@+id/number_of_posts"
@ -183,7 +147,7 @@
android:layout_height="wrap_content"
android:textColor="@color/card_expand_text_color"
android:textSize="10sp"
android:visibility="gone"/>
android:visibility="gone" />
<TextView
android:id="@+id/personal_text"
@ -192,10 +156,38 @@
android:textColor="@color/card_expand_text_color"
android:textSize="10sp"
android:textStyle="italic"
android:visibility="gone"/>
android:visibility="gone" />
</LinearLayout>
<FrameLayout
android:id="@+id/post_date_and_number_exp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp">
<TextView
android:id="@+id/post_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:text=""
android:textColor="@color/card_expand_text_color"
android:textSize="10sp" />
<TextView
android:id="@+id/post_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:text=""
android:textColor="@color/card_expand_text_color"
android:textSize="10sp" />
</FrameLayout>
<View
android:id="@+id/header_body_devider"
android:layout_width="match_parent"
@ -203,8 +195,8 @@
android:layout_marginBottom="9dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
android:background="@color/divider"/>
android:layout_marginTop="5dp"
android:background="@color/divider" />
<FrameLayout
android:layout_width="match_parent"
@ -220,8 +212,7 @@
android:layout_marginRight="16dp"
android:background="@color/card_background"
android:clickable="true"
android:text="@string/post"
/>
android:text="@string/post" />
</FrameLayout>
<View
@ -233,7 +224,7 @@
android:layout_marginRight="16dp"
android:layout_marginTop="9dp"
android:background="@color/divider"
android:visibility="gone"/>
android:visibility="gone" />
<LinearLayout
android:id="@+id/post_footer"
@ -242,8 +233,7 @@
android:orientation="vertical"
android:paddingBottom="9dp"
android:paddingLeft="16dp"
android:paddingRight="16dp">
</LinearLayout>
android:paddingRight="16dp" />
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>

2
app/src/main/res/layout/activity_about.xml

@ -30,7 +30,7 @@
android:id="@+id/scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="64dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:background="@color/background">
<RelativeLayout

4
app/src/main/res/layout/activity_bookmark.xml

@ -30,9 +30,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top|start"
android:layout_marginTop="64dp"
android:background="@color/background"
android:scrollbars="none">
android:scrollbars="none"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:id="@+id/bookmarks_container"

2
app/src/main/res/layout/activity_downloads.xml

@ -30,9 +30,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top|start"
android:layout_marginTop="64dp"
android:background="@color/background"
android:scrollbars="none"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="gr.thmmy.mthmmy.activities.downloads.DownloadsActivity">
</android.support.v7.widget.RecyclerView>

9
app/src/main/res/layout/activity_main.xml

@ -13,17 +13,8 @@
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/ToolbarTheme">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/ToolbarTheme">
</android.support.v7.widget.Toolbar>
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"

15
app/src/main/res/layout/activity_topic.xml

@ -27,13 +27,6 @@
android:id="@+id/toolbar_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
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"
/>
</android.support.v7.widget.Toolbar>
@ -44,12 +37,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top|start"
android:layout_marginTop="64dp"
android:background="@color/background"
android:paddingBottom="54dp"
android:paddingTop="4dp"
android:clipToPadding="false"
android:scrollbars="none"
tools:context="gr.thmmy.mthmmy.activities.topic.TopicActivity">
tools:context="gr.thmmy.mthmmy.activities.topic.TopicActivity"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
</android.support.v7.widget.RecyclerView>
<LinearLayout
@ -130,7 +123,7 @@
android:layout_marginBottom="50dp"
android:layout_marginEnd="@dimen/fab_margins"
app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior"
app:srcCompat="@drawable/ic_add_fab"/>
app:srcCompat="@drawable/ic_reply"/>
</android.support.design.widget.CoordinatorLayout>

99
app/src/main/res/layout/activity_topic_post_row.xml

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -9,40 +8,7 @@
android:paddingStart="4dp"
tools:ignore="SmallSp">
<FrameLayout
android:id="@+id/post_date_and_number_exp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:animateLayoutChanges="true"
android:visibility="gone">
<TextView
android:id="@+id/post_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:paddingEnd="5dp"
android:paddingStart="5dp"
android:text=""
android:textColor="@color/card_expand_text_color"
android:textSize="8sp"/>
<TextView
android:id="@+id/post_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:paddingEnd="5dp"
android:paddingStart="5dp"
android:text=""
android:textColor="@color/card_expand_text_color"
android:textSize="8sp"
/>
</FrameLayout>
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -92,7 +58,7 @@
android:contentDescription="@string/post_thumbnail"
android:maxHeight="@dimen/thumbnail_size"
android:maxWidth="@dimen/thumbnail_size"
android:src="@drawable/ic_default_user_thumbnail"/>
android:src="@drawable/ic_default_user_thumbnail" />
</FrameLayout>
<TextView
@ -105,7 +71,7 @@
android:maxLines="1"
android:text="@string/post_author"
android:textColor="@color/primary_text"
android:textStyle="bold"/>
android:textStyle="bold" />
<TextView
android:id="@+id/subject"
@ -115,7 +81,7 @@
android:layout_toEndOf="@+id/thumbnail_holder"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/post_subject"/>
android:text="@string/post_subject" />
</RelativeLayout>
<ImageButton
@ -127,7 +93,7 @@
android:background="@color/card_background"
android:clickable="true"
android:contentDescription="@string/post_quote_button"
android:src="@drawable/ic_format_quote_unchecked"/>
android:src="@drawable/ic_format_quote_unchecked" />
</LinearLayout>
<LinearLayout
@ -148,7 +114,7 @@
android:layout_height="wrap_content"
android:textColor="@color/card_expand_text_color"
android:textSize="10sp"
android:visibility="gone"/>
android:visibility="gone" />
<TextView
android:id="@+id/rank"
@ -156,7 +122,7 @@
android:layout_height="wrap_content"
android:textColor="@color/card_expand_text_color"
android:textSize="10sp"
android:visibility="gone"/>
android:visibility="gone" />
<TextView
android:id="@+id/stars"
@ -164,8 +130,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textSize="10sp"
android:visibility="gone">
</TextView>
android:visibility="gone" />
<TextView
android:id="@+id/gender"
@ -173,7 +138,7 @@
android:layout_height="wrap_content"
android:textColor="@color/card_expand_text_color"
android:textSize="10sp"
android:visibility="gone"/>
android:visibility="gone" />
<TextView
android:id="@+id/number_of_posts"
@ -181,7 +146,7 @@
android:layout_height="wrap_content"
android:textColor="@color/card_expand_text_color"
android:textSize="10sp"
android:visibility="gone"/>
android:visibility="gone" />
<TextView
android:id="@+id/personal_text"
@ -190,10 +155,38 @@
android:textColor="@color/card_expand_text_color"
android:textSize="10sp"
android:textStyle="italic"
android:visibility="gone"/>
android:visibility="gone" />
</LinearLayout>
<FrameLayout
android:id="@+id/post_date_and_number_exp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp">
<TextView
android:id="@+id/post_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:text=""
android:textColor="@color/card_expand_text_color"
android:textSize="10sp" />
<TextView
android:id="@+id/post_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:text=""
android:textColor="@color/card_expand_text_color"
android:textSize="10sp" />
</FrameLayout>
<View
android:id="@+id/header_body_devider"
android:layout_width="match_parent"
@ -201,8 +194,8 @@
android:layout_marginBottom="9dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
android:background="@color/divider"/>
android:layout_marginTop="5dp"
android:background="@color/divider" />
<FrameLayout
android:layout_width="match_parent"
@ -218,8 +211,7 @@
android:layout_marginRight="16dp"
android:background="@color/card_background"
android:clickable="true"
android:text="@string/post"
/>
android:text="@string/post" />
</FrameLayout>
<View
@ -231,7 +223,7 @@
android:layout_marginRight="16dp"
android:layout_marginTop="9dp"
android:background="@color/divider"
android:visibility="gone"/>
android:visibility="gone" />
<LinearLayout
android:id="@+id/post_footer"
@ -240,8 +232,7 @@
android:orientation="vertical"
android:paddingBottom="9dp"
android:paddingLeft="16dp"
android:paddingRight="16dp">
</LinearLayout>
android:paddingRight="16dp" />
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>

25
app/src/main/res/layout/fragment_forum.xml

@ -6,17 +6,22 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/list"
android:name="gr.thmmy.mthmmy.sections.forum.ForumFragment"
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swiperefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
android:clipToPadding="false"
android:paddingBottom="4dp"
android:paddingTop="4dp"
app:layoutManager="LinearLayoutManager"
tools:context=".activities.main.forum.ForumFragment"/>
android:layout_height="match_parent">
<gr.thmmy.mthmmy.utils.CustomRecyclerView
android:id="@+id/list"
android:name="gr.thmmy.mthmmy.sections.forum.ForumFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
android:paddingBottom="4dp"
android:paddingTop="4dp"
app:layoutManager="LinearLayoutManager"
tools:context=".activities.main.forum.ForumFragment" />
</android.support.v4.widget.SwipeRefreshLayout>
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/progressBar"

38
app/src/main/res/layout/fragment_unread.xml

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swiperefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<gr.thmmy.mthmmy.utils.CustomRecyclerView
android:id="@+id/list"
android:name="gr.thmmy.mthmmy.sections.unread.UnreadFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
android:paddingBottom="4dp"
android:paddingTop="4dp"
app:layoutManager="LinearLayoutManager"
tools:context=".activities.main.unread.UnreadFragment"
tools:listitem="@layout/fragment_unread_row"/>
</android.support.v4.widget.SwipeRefreshLayout>
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/progressBar"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal.NoPadding"
android:layout_width="match_parent"
android:layout_height="@dimen/progress_bar_height"
android:layout_alignParentTop="true"
android:indeterminate="true"
android:visibility="invisible"
app:mpb_indeterminateTint="@color/accent"
app:mpb_progressStyle="horizontal"/>
</RelativeLayout>

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

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="6dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="20dp">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textAlignment="center"
android:textColor="@color/accent"
android:textSize="@dimen/big_text" />
</LinearLayout>

15
app/src/main/res/layout/fragment_unread_mark_read_row.xml

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingEnd="10dp"
android:paddingTop="7dp">
<TextView
android:id="@+id/mark_read"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="textEnd"
android:textColor="@color/accent" />
</LinearLayout>

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

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/primary_light"
android:foreground="?android:attr/selectableItemBackground"
android:paddingBottom="6dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="6dp">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:textAppearance="?attr/textAppearanceListItem"
android:textColor="@color/primary_text"/>
<Space
android:id="@+id/spacer"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_alignParentBottom="@+id/title"
android:layout_below="@+id/title"
android:layout_toEndOf="@+id/dateTime"/>
<TextView
android:id="@+id/dateTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_below="@+id/spacer"
android:textColor="@color/secondary_text"/>
<TextView
android:id="@+id/lastUser"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/dateTime"
android:layout_alignBottom="@+id/dateTime"
android:layout_alignParentEnd="true"
android:textColor="@color/secondary_text"
android:textStyle="italic"/>
</RelativeLayout>
</LinearLayout>

6
app/src/main/res/menu/topic_menu.xml

@ -5,12 +5,12 @@
android:id="@+id/menu_bookmark"
android:icon="@drawable/ic_bookmark_false"
app:showAsAction="ifRoom"
android:title="Bookmark">
android:title="@string/bookmark">
</item>
<item
android:id="@+id/menu_info"
android:icon="@drawable/ic_info"
app:showAsAction="ifRoom|withText"
android:title="Info">
app:showAsAction="ifRoom"
android:title="@string/info">
</item>
</menu>

1
app/src/main/res/values-w820dp/dimens.xml

@ -9,4 +9,5 @@
<dimen name="quote_button">144dp</dimen>
<dimen name="fab_margins">64dp</dimen>
<dimen name="progress_bar_height">40dp</dimen>
<dimen name="big_text">24sp</dimen>
</resources>

1
app/src/main/res/values/dimens.xml

@ -7,4 +7,5 @@
<dimen name="quote_button">36dp</dimen>
<dimen name="fab_margins">16dp</dimen>
<dimen name="progress_bar_height">10dp</dimen>
<dimen name="big_text">24sp</dimen>
</resources>

5
app/src/main/res/values/strings.xml

@ -9,6 +9,7 @@
<string name="about">About</string>
<string name="home">Home</string>
<string name="bookmark">Bookmarks</string>
<string name="info">Info</string>
<!--Login Activity-->
<string name="thmmy_img_description">thmmy.gr</string>
@ -63,8 +64,8 @@
<!--Bookmarks-->
<string name="remove_bookmark">Remove</string>
<string name="board_bookmarks_title">Your bookmarked boards:</string>
<string name="topic_bookmarks_title">Your bookmarked topics:</string>
<string name="board_bookmarks_title">Boards</string>
<string name="topic_bookmarks_title">Topics</string>
<!--Licences-->
<string name="libraries">Libraries</string>

4
build.gradle

@ -6,8 +6,8 @@ buildscript {
maven { url "https://jitpack.io" }
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
classpath 'com.google.gms:google-services:3.0.0'
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'com.google.gms:google-services:3.1.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}

35
doc/forum_post.txt

@ -1,35 +0,0 @@
[center][size=25pt][b]Introduction[/b][/size][/center]
Από τη συζήτηση [url=https://www.thmmy.gr/smf/index.php?topic=67629.0]εδώ[/url], ξεκίνησε ένα project με στόχο τη δημιουργία εφαρμογής για Android κινητά που θα συγκεντρώνει και θα κάνει πιο εύκολη τη πρόσβαση σε μερικές από τις βασικές σελίδες και υπηρεσίες που αφορούν τη σχολή και χρησιμοποιούμε καθημερινά.
Μετά από 2+ μήνες δουλειάς και πάνω από 9000 γραμμές κώδικα (.java, .xml και άλλα), σήμερα (16/1/2017) ανεβάσαμε για πρώτη φορά την εφαρμογή (σε closed alpha phase) στο Google Play Store!
Προς το παρόν η εφαρμογή υποστηρίζει κάποιες από τις βασικές λειτουργίες του forum. Σταδιακά θα ενσωματώνονται όλο και περισσότερες λειτουργίες, για παράδειγμα ενός συστήματος ειδοποιήσεων για νέες ανακοινώσεις του ethmmy και της σελίδας της γραμματείας, instant chat κ.ά., ανάλογα πάντα με τις ιδέες, τη διάθεση και την ενέργεια όσων θα συμμετέχουν.
Αυτή τη στιγμή με το 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]
[img height=200]https://tctechcrunch2011.files.wordpress.com/2015/04/uncle-sam-we-want-you1-kopie_1.png[/img][/center]
Αν ενδιαφέρεσαι κι [b]ΕΣΥ[/b] να ασχοληθείς με το project μπορείς να το κάνεις με πολλούς τρόπους:
[list]
[li]
Να αναφέρεις 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], [b]αφού [/b]πρώτα μας στείλεις το Gmail που έχεις στο Google Play για να αποκτήσεις πρόσβαση.
[/li]
[li]
Να έρθεις σε άμεση επικοινωνία με την ομάδα μέσω του [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][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, να κατεβάσεις αρχεία από τα downloads και από συνημμένα σε post.
Loading…
Cancel
Save