Browse Source

Version 1.2.0

master v1.2.0
Ezerous 8 years ago
parent
commit
eb1fb17d5a
  1. 2
      .gitlab-ci.yml
  2. 1
      CONTRIBUTING.md
  3. 11
      README.md
  4. 2
      VERSION
  5. 31
      app/build.gradle
  6. 81
      app/src/debug/java/mthmmy/utils/Report.java
  7. 3
      app/src/main/AndroidManifest.xml
  8. 4
      app/src/main/assets/apache_libraries.html
  9. 2
      app/src/main/assets/mit_libraries.html
  10. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/BookmarkActivity.java
  11. 9
      app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java
  12. 25
      app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java
  13. 1
      app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardAdapter.java
  14. 20
      app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java
  15. 5
      app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java
  16. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java
  17. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java
  18. 14
      app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java
  19. 16
      app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java
  20. 37
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java
  21. 5
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java
  22. 25
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java
  23. 30
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java
  24. 31
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java
  25. 263
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/Posting.java
  26. 332
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java
  27. 371
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
  28. 39
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
  29. 45
      app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java
  30. 32
      app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java
  31. 17
      app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java
  32. 3
      app/src/main/java/gr/thmmy/mthmmy/model/Category.java
  33. 20
      app/src/main/java/gr/thmmy/mthmmy/model/Post.java
  34. 9
      app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java
  35. 6
      app/src/main/java/gr/thmmy/mthmmy/receiver/Receiver.java
  36. 52
      app/src/main/java/gr/thmmy/mthmmy/services/DownloadService.java
  37. 57
      app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java
  38. 25
      app/src/main/java/gr/thmmy/mthmmy/utils/CenterVerticalSpan.java
  39. 34
      app/src/main/java/gr/thmmy/mthmmy/utils/CrashReportingTree.java
  40. 5
      app/src/main/java/gr/thmmy/mthmmy/utils/ParseHelpers.java
  41. 6
      app/src/main/java/gr/thmmy/mthmmy/utils/exceptions/UnknownException.java
  42. BIN
      app/src/main/res/drawable-hdpi/ic_info.png
  43. BIN
      app/src/main/res/drawable-hdpi/ic_send.png
  44. BIN
      app/src/main/res/drawable-mdpi/ic_info.png
  45. BIN
      app/src/main/res/drawable-mdpi/ic_send.png
  46. BIN
      app/src/main/res/drawable-xhdpi/ic_info.png
  47. BIN
      app/src/main/res/drawable-xhdpi/ic_send.png
  48. BIN
      app/src/main/res/drawable-xxhdpi/ic_info.png
  49. BIN
      app/src/main/res/drawable-xxhdpi/ic_send.png
  50. BIN
      app/src/main/res/drawable-xxxhdpi/ic_info.png
  51. BIN
      app/src/main/res/drawable-xxxhdpi/ic_send.png
  52. 20
      app/src/main/res/layout/activity_topic.xml
  53. 6
      app/src/main/res/layout/activity_topic_post_row.xml
  54. 118
      app/src/main/res/layout/activity_topic_quick_reply_row.xml
  55. 33
      app/src/main/res/layout/dialog_topic_info.xml
  56. 16
      app/src/main/res/menu/topic_menu.xml
  57. 2
      app/src/main/res/values/colors.xml
  58. 3
      app/src/main/res/values/strings.xml
  59. 96
      app/src/release/java/mthmmy.utils/Report.java
  60. 2
      build.gradle
  61. 31
      doc/forum_post.txt
  62. 4
      gradle/wrapper/gradle-wrapper.properties

2
.gitlab-ci.yml

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

1
CONTRIBUTING.md

@ -45,4 +45,3 @@ follow the workflow below to make a merge request:
[trello-board]: https://trello.com/invite/b/4MVlkrkg/44a931707bd0b84a5e0bdfc42b9ae4f1/mthmmy [trello-board]: https://trello.com/invite/b/4MVlkrkg/44a931707bd0b84a5e0bdfc42b9ae4f1/mthmmy
[discord-server]: https://discord.gg/CVt3yrn [discord-server]: https://discord.gg/CVt3yrn
[gitlab-contributing-guide]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md

11
README.md

@ -2,9 +2,11 @@
[![build status](https://gitlab.com/ThmmyNoLife/mTHMMY/badges/develop/build.svg)](https://gitlab.com/ThmmyNoLife/mTHMMY/commits/develop) [![build status](https://gitlab.com/ThmmyNoLife/mTHMMY/badges/develop/build.svg)](https://gitlab.com/ThmmyNoLife/mTHMMY/commits/develop)
[![API](https://img.shields.io/badge/API-19%2B-blue.svg?style=flat)](https://android-arsenal.com/api?level=19) [![API](https://img.shields.io/badge/API-19%2B-blue.svg?style=flat)](https://android-arsenal.com/api?level=19)
[![Discord Channel](https://img.shields.io/badge/discord-public@mTHMMY-738bd7.svg?style=flat)](https://discord.gg/CVt3yrn) [![Discord Channel](https://img.shields.io/badge/discord-public@mTHMMY-738bd7.svg?style=flat)][discord-server]
[![Trello Board](https://img.shields.io/badge/trello-mTHMMY-red.svg?style=flat)][trello-board]
mTHMMY is a mobile app for thmmy.gr
mTHMMY is a mobile app for the [thmmy.gr](https://www.thmmy.gr) community.
## Requirements ## Requirements
@ -16,4 +18,7 @@ Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
## Contact ## Contact
Do not hesitate to contact us for any matter at `thmmynolife@gmail.com`. Do not hesitate to contact us for any matter, either by sending an email to `thmmynolife@gmail.com`, or by joining our [Discord server][discord-server].
[discord-server]: https://discord.gg/CVt3yrn
[trello-board]: https://trello.com/invite/b/4MVlkrkg/44a931707bd0b84a5e0bdfc42b9ae4f1/mthmmy

2
VERSION

@ -1 +1 @@
1.1.2 1.2.0

31
app/build.gradle

@ -2,15 +2,15 @@ apply plugin: 'com.android.application'
android { android {
compileSdkVersion 25 compileSdkVersion 25
buildToolsVersion "25.0.1" buildToolsVersion "25.0.2"
defaultConfig { defaultConfig {
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
applicationId "gr.thmmy.mthmmy" applicationId "gr.thmmy.mthmmy"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 25 targetSdkVersion 25
versionCode 5 versionCode 6
versionName "1.1.2" versionName "1.2.0"
archivesBaseName = "mTHMMY-v$versionName" archivesBaseName = "mTHMMY-v$versionName"
} }
@ -19,34 +19,35 @@ android {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
debug { /*debug {
def date = new Date().format('ddMMyy_HHmm'); def date = new Date().format('ddMMyy_HH');
archivesBaseName = archivesBaseName + "-$date" archivesBaseName = archivesBaseName + "-$date"
} }*/
} }
} }
dependencies { dependencies {
compile fileTree(dir: 'libs', include: ['*.jar']) compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.1.0' compile 'com.android.support:appcompat-v7:25.3.0'
compile 'com.android.support:design:25.1.0' compile 'com.android.support:design:25.3.0'
compile 'com.android.support:support-v4:25.1.0' compile 'com.android.support:support-v4:25.3.0'
compile 'com.android.support:cardview-v7:25.1.0' compile 'com.android.support:cardview-v7:25.3.0'
compile 'com.android.support:recyclerview-v7:25.1.0' compile 'com.android.support:recyclerview-v7:25.3.0'
compile 'com.google.firebase:firebase-crash:10.0.1' compile 'com.google.firebase:firebase-crash:10.2.0'
compile 'com.squareup.okhttp3:okhttp:3.5.0' compile 'com.squareup.okhttp3:okhttp:3.6.0'
compile 'com.squareup.picasso:picasso:2.5.2' compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0' compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
compile 'org.jsoup:jsoup:1.10.2' compile 'org.jsoup:jsoup:1.10.2'
compile 'com.github.franmontiel:PersistentCookieJar:v1.0.0' compile 'com.github.franmontiel:PersistentCookieJar:v1.0.0'
compile 'com.github.PhilJay:MPAndroidChart:v3.0.1' compile 'com.github.PhilJay:MPAndroidChart:v3.0.1'
compile('com.mikepenz:materialdrawer:5.8.1@aar') { compile('com.mikepenz:materialdrawer:5.8.2@aar') {
transitive = true transitive = true
} }
compile 'com.mikepenz:fontawesome-typeface:4.7.0.0@aar' compile 'com.mikepenz:fontawesome-typeface:4.7.0.0@aar'
compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.3' compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.5'
compile 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1' compile 'com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1'
compile 'me.zhanghai.android.materialprogressbar:library:1.3.0' compile 'me.zhanghai.android.materialprogressbar:library:1.3.0'
compile 'com.jakewharton.timber:timber:4.5.1'
} }
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'

81
app/src/debug/java/mthmmy/utils/Report.java

@ -1,81 +0,0 @@
package mthmmy.utils;
import android.util.Log;
public class Report
{
public static void v (String TAG, String message)
{
Log.v(TAG,message);
}
public static void v (String TAG, String message, Throwable tr)
{
Log.v(TAG,message + ": " + tr.getMessage(),tr);
}
public static void d (String TAG, String message)
{
Log.d(TAG,message);
}
public static void d (String TAG, String message, Throwable tr)
{
Log.d(TAG,message + ": " + tr.getMessage(),tr);
}
public static void i (String TAG, String message)
{
Log.i(TAG,message);
}
public static void i (String TAG, String message, Throwable tr)
{
Log.i(TAG,message + ": " + tr.getMessage(),tr);
}
public static void w (String TAG, String message)
{
Log.w(TAG,message);
}
public static void w (String TAG, String message, Throwable tr)
{
Log.w(TAG,message + ": " + tr.getMessage(),tr);
}
public static void e (String TAG, String message)
{
Log.e(TAG,message);
}
public static void e (String TAG, String message, Throwable tr)
{
Log.e(TAG,message + ": " + tr.getMessage(),tr);
}
public static void wtf (String TAG, String message)
{
Log.wtf(TAG,message);
}
public static void wtf (String TAG, String message, Throwable tr)
{
Log.wtf(TAG,message + ": " + tr.getMessage(),tr);
}
/**
* Prints long messages in logcat (debug level).
*/
public static void longMessage(String TAG, String message)
{
int maxLogSize = 1000;
for(int i = 0; i <= message.length() / maxLogSize; i++) {
int start = i * maxLogSize;
int end = (i+1) * maxLogSize;
end = end > message.length() ? message.length() : end;
Report.d(TAG, message.substring(start, end));
}
}
}

3
app/src/main/AndroidManifest.xml

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="gr.thmmy.mthmmy"> package="gr.thmmy.mthmmy"
android:installLocation="auto">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

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

@ -39,7 +39,7 @@
<body> <body>
<ul> <ul>
<li> <li>
<h5><a href="https://square.github.io/okhttp/">OkHttp</a>&nbsp;v3.5.0 (Copyright ©2016 Square, Inc.)</h5> <h5><a href="https://square.github.io/okhttp/">OkHttp</a>&nbsp;v3.6.0 (Copyright ©2016 Square, Inc.)</h5>
</li> </li>
<li> <li>
<h5><a href="https://square.github.io/picasso/">Picasso</a>&nbsp;v2.5.2 (Copyright ©2013 Square, Inc.)</h5> <h5><a href="https://square.github.io/picasso/">Picasso</a>&nbsp;v2.5.2 (Copyright ©2013 Square, Inc.)</h5>
@ -51,7 +51,7 @@
<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.1 (Copyright ©2016 Philipp Jahoda)</h5>
</li> </li>
<li> <li>
<h5><a href="https://github.com/mikepenz/MaterialDrawer">MaterialDrawer</a>&nbsp;v5.8.1 (Copyright ©2016 Mike Penz)</h5> <h5><a href="https://github.com/mikepenz/MaterialDrawer">MaterialDrawer</a>&nbsp;v5.8.2 (Copyright ©2016 Mike Penz)</h5>
</li> </li>
<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> <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>

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> <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>
<li> <li>
<h5><a href="https://github.com/koral--/android-gif-drawable">android-gif-drawable</a>&nbsp;v1.2.3 (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.5 (Copyright ©2016 Karol Wrótniak, Droids on Roids)</h5>
</li> </li>
<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> <h5><a href="https://github.com/bignerdranch/expandable-recycler-view">Expandable RecyclerView</a>&nbsp;v3.0.0-RC1 (Copyright ©2015, Big Nerd Ranch)</h5>

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

@ -114,7 +114,7 @@ public class BookmarkActivity extends BaseActivity {
Intent intent = new Intent(BookmarkActivity.this, TopicActivity.class); Intent intent = new Intent(BookmarkActivity.this, TopicActivity.class);
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString(BUNDLE_TOPIC_URL, "https://www.thmmy.gr/smf/index.php?topic=" extras.putString(BUNDLE_TOPIC_URL, "https://www.thmmy.gr/smf/index.php?topic="
+ bookmarkedTopic.getId() + ".0"); + bookmarkedTopic.getId() + "." + 2147483647);
extras.putString(BUNDLE_TOPIC_TITLE, bookmarkedTopic.getTitle()); extras.putString(BUNDLE_TOPIC_TITLE, bookmarkedTopic.getTitle());
intent.putExtras(extras); intent.putExtras(extras);
startActivity(intent); startActivity(intent);

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

@ -14,7 +14,8 @@ import android.widget.Toast;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.main.MainActivity; import gr.thmmy.mthmmy.activities.main.MainActivity;
import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import mthmmy.utils.Report;
import timber.log.Timber;
import static gr.thmmy.mthmmy.session.SessionManager.CONNECTION_ERROR; import static gr.thmmy.mthmmy.session.SessionManager.CONNECTION_ERROR;
import static gr.thmmy.mthmmy.session.SessionManager.EXCEPTION; import static gr.thmmy.mthmmy.session.SessionManager.EXCEPTION;
@ -34,12 +35,8 @@ public class LoginActivity extends BaseActivity {
private String password; private String password;
/* --Graphics End-- */ /* --Graphics End-- */
//Other variables
private static final String TAG = "LoginActivity";
private LoginTask loginTask; private LoginTask loginTask;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -55,7 +52,7 @@ public class LoginActivity extends BaseActivity {
btnLogin.setOnClickListener(new View.OnClickListener() { btnLogin.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) { public void onClick(View view) {
Report.d(TAG, "Login"); Timber.d("Login");
//Get username and password strings //Get username and password strings
username = inputUsername.getText().toString().trim(); username = inputUsername.getText().toString().trim();

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

@ -30,16 +30,11 @@ import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.ThmmyPage; import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.model.Topic; import gr.thmmy.mthmmy.model.Topic;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import mthmmy.utils.Report;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber;
public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMoreListener { public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMoreListener {
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "BoardActivity";
/** /**
* The key to use when putting board's url String to {@link BoardActivity}'s Bundle. * The key to use when putting board's url String to {@link BoardActivity}'s Bundle.
*/ */
@ -76,7 +71,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
boardUrl = extras.getString(BUNDLE_BOARD_URL); boardUrl = extras.getString(BUNDLE_BOARD_URL);
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(boardUrl)); ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(boardUrl));
if (!target.is(ThmmyPage.PageCategory.BOARD)) { if (!target.is(ThmmyPage.PageCategory.BOARD)) {
Report.e(TAG, "Bundle came with a non board url!\nUrl:\n" + boardUrl); Timber.e("Bundle came with a non board url!\nUrl:\n%s" , boardUrl);
Toast.makeText(this, "An error has occurred\nAborting.", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "An error has occurred\nAborting.", Toast.LENGTH_SHORT).show();
finish(); finish();
} }
@ -92,8 +87,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
} }
thisPageBookmark = new Bookmark(boardTitle, ThmmyPage.getBoardId(boardUrl)); thisPageBookmark = new Bookmark(boardTitle, ThmmyPage.getBoardId(boardUrl));
thisPageBookmarkButton = (ImageButton) findViewById(R.id.bookmark); setBoardBookmark((ImageButton) findViewById(R.id.bookmark));
setBoardBookmark();
createDrawer(); createDrawer();
progressBar = (MaterialProgressBar) findViewById(R.id.progressBar); progressBar = (MaterialProgressBar) findViewById(R.id.progressBar);
@ -182,13 +176,6 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
* parameter!</p> * parameter!</p>
*/ */
public class BoardTask extends AsyncTask<String, Void, Void> { public class BoardTask extends AsyncTask<String, Void, Void> {
//Class variables
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "BoardTask"; //Separate tag for AsyncTask
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE); if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE);
@ -201,12 +188,12 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
.url(boardUrl[0]) .url(boardUrl[0])
.build(); .build();
try { try {
Response response = BaseActivity.getClient().newCall(request).execute(); Response response = client.newCall(request).execute();
parseBoard(Jsoup.parse(response.body().string())); parseBoard(Jsoup.parse(response.body().string()));
} catch (SSLHandshakeException e) { } catch (SSLHandshakeException e) {
Report.w(TAG, "Certificate problem (please switch to unsafe connection)."); Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) { } catch (Exception e) {
Report.e("TAG", "ERROR", e); Timber.e("ERROR", e);
} }
return null; return null;
} }

1
app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardAdapter.java

@ -32,7 +32,6 @@ import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL;
* {@link RecyclerView.Adapter} that can display a {@link gr.thmmy.mthmmy.model.Board}. * {@link RecyclerView.Adapter} that can display a {@link gr.thmmy.mthmmy.model.Board}.
*/ */
class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { class BoardAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String TAG = "BoardAdapter";
private final int VIEW_TYPE_SUB_BOARD_TITLE = 0; private final int VIEW_TYPE_SUB_BOARD_TITLE = 0;
private final int VIEW_TYPE_SUB_BOARD = 1; private final int VIEW_TYPE_SUB_BOARD = 1;
private final int VIEW_TYPE_TOPIC_TITLE = 2; private final int VIEW_TYPE_TOPIC_TITLE = 2;

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

@ -27,16 +27,12 @@ import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Download; import gr.thmmy.mthmmy.model.Download;
import gr.thmmy.mthmmy.model.ThmmyPage; import gr.thmmy.mthmmy.model.ThmmyPage;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import mthmmy.utils.Report;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber;
public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.OnLoadMoreListener { public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.OnLoadMoreListener {
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "DownloadsActivity";
/** /**
* The key to use when putting download's url String to {@link DownloadsActivity}'s Bundle. * The key to use when putting download's url String to {@link DownloadsActivity}'s Bundle.
*/ */
@ -73,7 +69,7 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
if (downloadsUrl != null && !Objects.equals(downloadsUrl, "")) { if (downloadsUrl != null && !Objects.equals(downloadsUrl, "")) {
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(downloadsUrl)); ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(downloadsUrl));
if (!target.is(ThmmyPage.PageCategory.DOWNLOADS)) { if (!target.is(ThmmyPage.PageCategory.DOWNLOADS)) {
Report.e(TAG, "Bundle came with a non board url!\nUrl:\n" + downloadsUrl); Timber.e("Bundle came with a non board url!\nUrl:\n%s" , downloadsUrl);
Toast.makeText(this, "An error has occurred\nAborting.", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "An error has occurred\nAborting.", Toast.LENGTH_SHORT).show();
finish(); finish();
} }
@ -173,10 +169,6 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
* as String parameter!</p> * as String parameter!</p>
*/ */
class ParseDownloadPageTask extends AsyncTask<String, Void, Void> { class ParseDownloadPageTask extends AsyncTask<String, Void, Void> {
/**
* Debug Tag for logging debug output to LogCat
*/
private static final String TAG = "ParseDownloadPageTask"; //Separate tag for AsyncTask
private String thisPageUrl; private String thisPageUrl;
@Override @Override
@ -192,12 +184,12 @@ public class DownloadsActivity extends BaseActivity implements DownloadsAdapter.
.url(downloadsUrl[0]) .url(downloadsUrl[0])
.build(); .build();
try { try {
Response response = BaseActivity.getClient().newCall(request).execute(); Response response = client.newCall(request).execute();
parseDownloads(Jsoup.parse(response.body().string())); parseDownloads(Jsoup.parse(response.body().string()));
} catch (SSLHandshakeException e) { } catch (SSLHandshakeException e) {
Report.w(TAG, "Certificate problem (please switch to unsafe connection)."); Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) { } catch (Exception e) {
Report.e("TAG", "ERROR", e); Timber.e("ERROR", e);
} }
return null; return null;
} }

5
app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java

@ -29,11 +29,6 @@ import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWN
import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWNLOADS_URL; import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWNLOADS_URL;
class DownloadsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { class DownloadsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "DownloadsAdapter";
private final int VIEW_TYPE_DOWNLOAD = 0; private final int VIEW_TYPE_DOWNLOAD = 0;
private final int VIEW_TYPE_LOADING = 1; private final int VIEW_TYPE_LOADING = 1;

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

@ -9,7 +9,6 @@ import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
@ -38,7 +37,6 @@ import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL;
public class MainActivity extends BaseActivity implements RecentFragment.RecentFragmentInteractionListener, ForumFragment.ForumFragmentInteractionListener { public class MainActivity extends BaseActivity implements RecentFragment.RecentFragmentInteractionListener, ForumFragment.ForumFragmentInteractionListener {
//----------------------------------------CLASS VARIABLES----------------------------------------- //----------------------------------------CLASS VARIABLES-----------------------------------------
private static final String TAG = "MainActivity";
private static final int TIME_INTERVAL = 2000; private static final int TIME_INTERVAL = 2000;
private long mBackPressed; private long mBackPressed;

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

@ -68,7 +68,7 @@ class ForumAdapter extends ExpandableRecyclerAdapter<Category, Board, ForumAdapt
class CategoryViewHolder extends ParentViewHolder { class CategoryViewHolder extends ParentViewHolder {
private TextView categoryTextview; private final TextView categoryTextview;
CategoryViewHolder(View itemView) { CategoryViewHolder(View itemView) {
super(itemView); super(itemView);
@ -83,7 +83,7 @@ class ForumAdapter extends ExpandableRecyclerAdapter<Category, Board, ForumAdapt
class BoardViewHolder extends ChildViewHolder { class BoardViewHolder extends ChildViewHolder {
private TextView boardTextView; private final TextView boardTextView;
public Board board; public Board board;
BoardViewHolder(View itemView) { BoardViewHolder(View itemView) {

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

@ -30,10 +30,11 @@ import gr.thmmy.mthmmy.model.Board;
import gr.thmmy.mthmmy.model.Category; import gr.thmmy.mthmmy.model.Category;
import gr.thmmy.mthmmy.session.SessionManager; import gr.thmmy.mthmmy.session.SessionManager;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import mthmmy.utils.Report;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber;
/** /**
* A {@link BaseFragment} subclass. * A {@link BaseFragment} subclass.
@ -87,7 +88,7 @@ public class ForumFragment extends BaseFragment
forumTask.execute(); forumTask.execute();
} }
Report.d(TAG, "onActivityCreated"); Timber.d("onActivityCreated");
} }
@Override @Override
@ -151,8 +152,7 @@ public class ForumFragment extends BaseFragment
//---------------------------------------ASYNC TASK----------------------------------- //---------------------------------------ASYNC TASK-----------------------------------
public class ForumTask extends AsyncTask<Void, Void, Integer> { private class ForumTask extends AsyncTask<Void, Void, Integer> {
private static final String TAG = "ForumTask";
private HttpUrl forumUrl = SessionManager.forumUrl; //may change upon collapse/expand private HttpUrl forumUrl = SessionManager.forumUrl; //may change upon collapse/expand
private Document document; private Document document;
@ -179,10 +179,10 @@ public class ForumFragment extends BaseFragment
fetchedCategories.clear(); fetchedCategories.clear();
return 0; return 0;
} catch (IOException e) { } catch (IOException e) {
Report.d(TAG, "Network Error", e); Timber.d("Network Error", e);
return 1; return 1;
} catch (Exception e) { } catch (Exception e) {
Report.d(TAG, "Exception", e); Timber.d("Exception", e);
return 2; return 2;
} }
@ -225,7 +225,7 @@ public class ForumFragment extends BaseFragment
} }
} }
else else
Report.e(TAG, "Parsing failed!"); Timber.e("Parsing failed!");
} }
public void setUrl(String string) public void setUrl(String string)

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

@ -29,10 +29,11 @@ import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.CustomRecyclerView; import gr.thmmy.mthmmy.utils.CustomRecyclerView;
import gr.thmmy.mthmmy.utils.exceptions.ParseException; import gr.thmmy.mthmmy.utils.exceptions.ParseException;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import mthmmy.utils.Report;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber;
/** /**
* A {@link BaseFragment} subclass. * A {@link BaseFragment} subclass.
@ -86,7 +87,7 @@ public class RecentFragment extends BaseFragment {
recentTask.execute(); recentTask.execute();
} }
Report.d(TAG, "onActivityCreated"); Timber.d("onActivityCreated");
} }
@ -141,8 +142,7 @@ public class RecentFragment extends BaseFragment {
//---------------------------------------ASYNC TASK----------------------------------- //---------------------------------------ASYNC TASK-----------------------------------
public class RecentTask extends AsyncTask<Void, Void, Integer> { private class RecentTask extends AsyncTask<Void, Void, Integer> {
private static final String TAG = "RecentTask";
private final HttpUrl thmmyUrl = SessionManager.indexUrl; private final HttpUrl thmmyUrl = SessionManager.indexUrl;
private Document document; private Document document;
@ -161,13 +161,13 @@ public class RecentFragment extends BaseFragment {
parse(document); parse(document);
return 0; return 0;
} catch (ParseException e) { } catch (ParseException e) {
Report.e(TAG, "ParseException", e); Timber.e("ParseException", e);
return 1; return 1;
} catch (IOException e) { } catch (IOException e) {
Report.i(TAG, "Network Error", e); Timber.i("Network Error", e);
return 2; return 2;
} catch (Exception e) { } catch (Exception e) {
Report.e(TAG, "Exception", e); Timber.e("Exception", e);
return 3; return 3;
} }
@ -179,7 +179,7 @@ public class RecentFragment extends BaseFragment {
if (result == 0) if (result == 0)
recentAdapter.notifyDataSetChanged(); recentAdapter.notifyDataSetChanged();
else if (result == 2) else if (result == 2)
Toast.makeText(getActivity(), "Network error", Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), "Network error", Toast.LENGTH_SHORT).show(); //Fixme, sometimes activity isn't ready
progressBar.setVisibility(ProgressBar.INVISIBLE); progressBar.setVisibility(ProgressBar.INVISIBLE);
swipeRefreshLayout.setRefreshing(false); swipeRefreshLayout.setRefreshing(false);

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

@ -19,6 +19,7 @@ import android.text.SpannableString;
import android.text.Spanned; import android.text.Spanned;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan; import android.text.style.RelativeSizeSpan;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
@ -49,9 +50,10 @@ import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.utils.CenterVerticalSpan; import gr.thmmy.mthmmy.utils.CenterVerticalSpan;
import gr.thmmy.mthmmy.utils.CircleTransform; import gr.thmmy.mthmmy.utils.CircleTransform;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import mthmmy.utils.Report;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL;
@ -63,11 +65,6 @@ import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL;
* the username using the key {@link #BUNDLE_PROFILE_USERNAME}. * the username using the key {@link #BUNDLE_PROFILE_USERNAME}.
*/ */
public class ProfileActivity extends BaseActivity implements LatestPostsFragment.LatestPostsFragmentInteractionListener { public class ProfileActivity extends BaseActivity implements LatestPostsFragment.LatestPostsFragmentInteractionListener {
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "ProfileActivity";
/** /**
* The key to use when putting profile's url String to {@link ProfileActivity}'s Bundle. * The key to use when putting profile's url String to {@link ProfileActivity}'s Bundle.
*/ */
@ -178,7 +175,7 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(profileUrl)); ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(profileUrl));
if (!target.is(ThmmyPage.PageCategory.PROFILE)) { if (!target.is(ThmmyPage.PageCategory.PROFILE)) {
Report.e(TAG, "Bundle came with a non profile url!\nUrl:\n" + profileUrl); Timber.e("Bundle came with a non profile url!\nUrl:\n%s" , profileUrl);
Toast.makeText(this, "An error has occurred\n Aborting.", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "An error has occurred\n Aborting.", Toast.LENGTH_SHORT).show();
finish(); finish();
} }
@ -221,11 +218,6 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
*/ */
public class ProfileTask extends AsyncTask<String, Void, Boolean> { public class ProfileTask extends AsyncTask<String, Void, Boolean> {
//Class variables //Class variables
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "ProfileTask"; //Separate tag for AsyncTask
Document profilePage; Document profilePage;
Spannable usernameSpan; Spannable usernameSpan;
@ -243,14 +235,15 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
try { try {
Response response = client.newCall(request).execute(); Response response = client.newCall(request).execute();
profilePage = Jsoup.parse(response.body().string()); profilePage = Jsoup.parse(response.body().string());
Elements contentsTable = profilePage.select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2)"); Elements contentsTable = profilePage.
select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tbody");
//Finds username if missing //Finds username if missing
if (username == null || Objects.equals(username, "")) { if (username == null || Objects.equals(username, "")) {
username = contentsTable.select("tr").first().select("td").last().text(); username = contentsTable.select("tr").first().select("td").last().text();
} }
if (thumbnailUrl == null || Objects.equals(thumbnailUrl, "")) { //Maybe there is an avatar if (thumbnailUrl == null || Objects.equals(thumbnailUrl, "")) { //Maybe there is an avatar
Element profileAvatar = contentsTable.select("img.avatar").first(); Element profileAvatar = profilePage.select("img.avatar").first();
if (profileAvatar != null) thumbnailUrl = profileAvatar.attr("abs:src"); if (profileAvatar != null) thumbnailUrl = profileAvatar.attr("abs:src");
} }
{ //Finds personal text { //Finds personal text
@ -260,7 +253,7 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
} else { } else {
//Should never get here! //Should never get here!
//Something is wrong. //Something is wrong.
Report.e(TAG, "An error occurred while trying to find profile's personal text."); Timber.e("An error occurred while trying to find profile's personal text.");
personalText = null; personalText = null;
} }
} }
@ -281,18 +274,18 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
} }
return true; return true;
} catch (SSLHandshakeException e) { } catch (SSLHandshakeException e) {
Report.w(TAG, "Certificate problem (please switch to unsafe connection)."); Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) { } catch (Exception e) {
Report.e("TAG", "ERROR", e); Timber.e("ERROR", e);
} }
return false; return false;
} }
protected void onPostExecute(Boolean result) { protected void onPostExecute(Boolean result) {
if (!result) { //Parse failed! if (!result) { //Parse failed! //TODO report as ParseException?
Report.d(TAG, "Parse failed!"); Timber.d("Parse failed!");
Toast.makeText(getBaseContext() Toast.makeText(getBaseContext(), "Fatal error!\n Aborting..."
, "Fatal error!\n Aborting...", Toast.LENGTH_LONG).show(); , Toast.LENGTH_LONG).show();
finish(); finish();
} }
//Parse was successful //Parse was successful
@ -342,7 +335,7 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
viewPager.setAdapter(adapter); viewPager.setAdapter(adapter);
} }
class ViewPagerAdapter extends FragmentPagerAdapter { private class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>(); private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>(); private final List<String> mFragmentTitleList = new ArrayList<>();

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

@ -21,11 +21,6 @@ import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
* specified {@link LatestPostsFragment.LatestPostsFragmentInteractionListener}. * specified {@link LatestPostsFragment.LatestPostsFragmentInteractionListener}.
*/ */
class LatestPostsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { class LatestPostsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "LatestPostsAdapter";
private final int VIEW_TYPE_ITEM = 0; private final int VIEW_TYPE_ITEM = 0;
private final int VIEW_TYPE_LOADING = 1; private final int VIEW_TYPE_LOADING = 1;
final private LatestPostsFragment.LatestPostsFragmentInteractionListener interactionListener; final private LatestPostsFragment.LatestPostsFragmentInteractionListener interactionListener;

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

@ -26,19 +26,15 @@ import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.PostSummary; import gr.thmmy.mthmmy.model.PostSummary;
import gr.thmmy.mthmmy.utils.ParseHelpers; import gr.thmmy.mthmmy.utils.ParseHelpers;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import mthmmy.utils.Report;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber;
/** /**
* Use the {@link LatestPostsFragment#newInstance} factory method to create an instance of this fragment. * Use the {@link LatestPostsFragment#newInstance} factory method to create an instance of this fragment.
*/ */
public class LatestPostsFragment extends BaseFragment implements LatestPostsAdapter.OnLoadMoreListener{ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdapter.OnLoadMoreListener{
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "LatestPostsFragment";
/** /**
* The key to use when putting profile's url String to {@link LatestPostsFragment}'s Bundle. * The key to use when putting profile's url String to {@link LatestPostsFragment}'s Bundle.
*/ */
@ -136,7 +132,7 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap
profileLatestPostsTask.execute(profileUrl + ";sa=showPosts"); profileLatestPostsTask.execute(profileUrl + ";sa=showPosts");
pagesLoaded = 1; pagesLoaded = 1;
} }
Report.d(TAG, "onActivityCreated"); Timber.d("onActivityCreated");
} }
@Override @Override
@ -156,14 +152,7 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap
* <p>LatestPostsTask's {@link AsyncTask#execute execute} method needs a profile's url as String * <p>LatestPostsTask's {@link AsyncTask#execute execute} method needs a profile's url as String
* parameter!</p> * parameter!</p>
*/ */
public class LatestPostsTask extends AsyncTask<String, Void, Boolean> { private class LatestPostsTask extends AsyncTask<String, Void, Boolean> {
//Class variables
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "LatestPostsTask"; //Separate tag for AsyncTask
protected void onPreExecute() { protected void onPreExecute() {
if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE); if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE);
} }
@ -176,16 +165,16 @@ public class LatestPostsFragment extends BaseFragment implements LatestPostsAdap
Response response = BaseActivity.getClient().newCall(request).execute(); Response response = BaseActivity.getClient().newCall(request).execute();
return parseLatestPosts(Jsoup.parse(response.body().string())); return parseLatestPosts(Jsoup.parse(response.body().string()));
} catch (SSLHandshakeException e) { } catch (SSLHandshakeException e) {
Report.w(TAG, "Certificate problem (please switch to unsafe connection)."); Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) { } catch (Exception e) {
Report.e("TAG", "ERROR", e); Timber.e("ERROR", e);
} }
return false; return false;
} }
protected void onPostExecute(Boolean result) { protected void onPostExecute(Boolean result) {
if (!result) { //Parse failed! if (!result) { //Parse failed!
Report.d(TAG, "Parse failed!"); Timber.d("Parse failed!");
Toast.makeText(getContext() Toast.makeText(getContext()
, "Fatal error!\n Aborting...", Toast.LENGTH_LONG).show(); , "Fatal error!\n Aborting...", Toast.LENGTH_LONG).show();
getActivity().finish(); getActivity().finish();

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

@ -33,6 +33,7 @@ import org.jsoup.nodes.Element;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLHandshakeException;
@ -40,16 +41,12 @@ import javax.net.ssl.SSLHandshakeException;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import mthmmy.utils.Report;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber;
public class StatsFragment extends Fragment { public class StatsFragment extends Fragment {
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "StatsFragment";
/** /**
* The key to use when putting profile's url String to {@link StatsFragment}'s Bundle. * The key to use when putting profile's url String to {@link StatsFragment}'s Bundle.
*/ */
@ -108,7 +105,7 @@ public class StatsFragment extends Fragment {
profileStatsTask = new ProfileStatsTask(); profileStatsTask = new ProfileStatsTask();
profileStatsTask.execute(profileUrl + ";sa=statPanel"); profileStatsTask.execute(profileUrl + ";sa=statPanel");
} }
Report.d(TAG, "onActivityCreated"); Timber.d("onActivityCreated");
} }
@Override @Override
@ -126,14 +123,7 @@ public class StatsFragment extends Fragment {
* <p>Calling SummaryTask's {@link AsyncTask#execute execute} method needs to have profile's url * <p>Calling SummaryTask's {@link AsyncTask#execute execute} method needs to have profile's url
* as String parameter!</p> * as String parameter!</p>
*/ */
public class ProfileStatsTask extends AsyncTask<String, Void, Boolean> { private class ProfileStatsTask extends AsyncTask<String, Void, Boolean> {
//Class variables
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "ProfileStatsTask"; //Separate tag for AsyncTask
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
progressBar.setVisibility(ProgressBar.VISIBLE); progressBar.setVisibility(ProgressBar.VISIBLE);
@ -149,9 +139,9 @@ public class StatsFragment extends Fragment {
Response response = BaseActivity.getClient().newCall(request).execute(); Response response = BaseActivity.getClient().newCall(request).execute();
return parseStats(Jsoup.parse(response.body().string())); return parseStats(Jsoup.parse(response.body().string()));
} catch (SSLHandshakeException e) { } catch (SSLHandshakeException e) {
Report.w(TAG, "Certificate problem (please switch to unsafe connection)."); Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) { } catch (Exception e) {
Report.e("TAG", "ERROR", e); Timber.e("ERROR", e);
} }
return false; return false;
} }
@ -159,7 +149,7 @@ public class StatsFragment extends Fragment {
@Override @Override
protected void onPostExecute(Boolean result) { protected void onPostExecute(Boolean result) {
if (!result) { //Parse failed! if (!result) { //Parse failed!
Report.d(TAG, "Parse failed!"); Timber.d("Parse failed!");
Toast.makeText(getContext() Toast.makeText(getContext()
, "Fatal error!\n Aborting...", Toast.LENGTH_LONG).show(); , "Fatal error!\n Aborting...", Toast.LENGTH_LONG).show();
getActivity().finish(); getActivity().finish();
@ -206,6 +196,7 @@ public class StatsFragment extends Fragment {
Integer.parseInt(dataCols.last().text()))); Integer.parseInt(dataCols.last().text())));
mostPopularBoardsByPostsLabels.add(dataCols.first().text()); mostPopularBoardsByPostsLabels.add(dataCols.first().text());
} }
Collections.reverse(mostPopularBoardsByPostsLabels);
} }
{ {
Elements mostPopularBoardsByActivityRows = statsRows.last().select(">td").last() Elements mostPopularBoardsByActivityRows = statsRows.last().select(">td").last()
@ -218,6 +209,7 @@ public class StatsFragment extends Fragment {
Float.parseFloat(tmp.substring(0, tmp.indexOf("%"))))); Float.parseFloat(tmp.substring(0, tmp.indexOf("%")))));
mostPopularBoardsByActivityLabels.add(dataCols.first().text()); mostPopularBoardsByActivityLabels.add(dataCols.first().text());
} }
Collections.reverse(mostPopularBoardsByActivityLabels);
} }
} }
return true; return true;
@ -345,7 +337,7 @@ public class StatsFragment extends Fragment {
mostPopularBoardsByActivityChart.invalidate(); mostPopularBoardsByActivityChart.invalidate();
} }
class MyXAxisValueFormatter implements IAxisValueFormatter { private class MyXAxisValueFormatter implements IAxisValueFormatter {
private final ArrayList<String> mValues; private final ArrayList<String> mValues;
MyXAxisValueFormatter(ArrayList<String> values) { MyXAxisValueFormatter(ArrayList<String> values) {

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

@ -6,6 +6,8 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.text.Html; import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -23,18 +25,14 @@ import java.util.Objects;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.utils.ParseHelpers; import gr.thmmy.mthmmy.utils.ParseHelpers;
import mthmmy.utils.Report;
import timber.log.Timber;
/** /**
* Use the {@link SummaryFragment#newInstance} factory method to create an instance of this fragment. * Use the {@link SummaryFragment#newInstance} factory method to create an instance of this fragment.
*/ */
public class SummaryFragment extends Fragment { public class SummaryFragment extends Fragment {
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "SummaryFragment";
/** /**
* The key to use when putting profile's source code String to {@link SummaryFragment}'s Bundle. * The key to use when putting profile's source code String to {@link SummaryFragment}'s Bundle.
*/ */
@ -94,7 +92,7 @@ public class SummaryFragment extends Fragment {
summaryTask = new SummaryTask(); summaryTask = new SummaryTask();
summaryTask.execute(profileSummaryDocument); summaryTask.execute(profileSummaryDocument);
} }
Report.d(TAG, "onActivityCreated"); Timber.d("onActivityCreated");
} }
@Override @Override
@ -112,14 +110,7 @@ public class SummaryFragment extends Fragment {
* <p>Calling SummaryTask's {@link AsyncTask#execute execute} method needs to have profile's url * <p>Calling SummaryTask's {@link AsyncTask#execute execute} method needs to have profile's url
* as String parameter!</p> * as String parameter!</p>
*/ */
public class SummaryTask extends AsyncTask<Document, Void, Void> { private class SummaryTask extends AsyncTask<Document, Void, Void> {
//Class variables
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "SummaryTask"; //Separate tag for AsyncTask
protected Void doInBackground(Document... profileSummaryPage) { protected Void doInBackground(Document... profileSummaryPage) {
parsedProfileSummaryData = parseProfileSummary(profileSummaryPage[0]); parsedProfileSummaryData = parseProfileSummary(profileSummaryPage[0]);
return null; return null;
@ -189,6 +180,16 @@ public class SummaryFragment extends Fragment {
} }
TextView entry = new TextView(this.getContext()); TextView entry = new TextView(this.getContext());
if (profileSummaryRow.contains("@") &&
(profileSummaryRow.contains("Email") || profileSummaryRow.contains("E-mail"))) {
Timber.d("mpika");
Timber.d(profileSummaryRow);
String email = profileSummaryRow.substring(profileSummaryRow.indexOf(":</b> ") + 6);
profileSummaryRow = profileSummaryRow.replace(email,
"<a href=\"mailto:" + email + "\">" + email + "</a>");
entry.setMovementMethod(LinkMovementMethod.getInstance());
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
entry.setTextColor(getResources().getColor(R.color.primary_text, null)); entry.setTextColor(getResources().getColor(R.color.primary_text, null));
else else

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

@ -0,0 +1,263 @@
package gr.thmmy.mthmmy.activities.topic;
import android.util.Log;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import okhttp3.Response;
class Posting {
enum REPLY_STATUS {
SUCCESSFUL, NO_SUBJECT, EMPTY_BODY, NEW_REPLY_WHILE_POSTING, NOT_FOUND, SESSION_ENDED, OTHER_ERROR
}
static REPLY_STATUS replyStatus(Response response) throws IOException {
if (response.code() == 404) return REPLY_STATUS.NOT_FOUND;
if (response.code() < 200 || response.code() >= 400) return REPLY_STATUS.OTHER_ERROR;
String finalUrl = response.request().url().toString();
if (finalUrl.contains("action=post")) {
Document postErrorPage = Jsoup.parse(response.body().string());
String[] errors = postErrorPage.select("tr[id=errors] div[id=error_list]").first()
.toString().split("<br>");
for (int i = 0; i < errors.length; ++i) { //TODO test
Log.d("TAG", String.valueOf(i));
Log.d("TAG", errors[i]);
}
for (String error : errors) {
if (error.contains("Your session timed out while posting") ||
error.contains("Υπερβήκατε τον μέγιστο χρόνο σύνδεσης κατά την αποστολή"))
return REPLY_STATUS.SESSION_ENDED;
if (error.contains("No subject was filled in")
|| error.contains("Δεν δόθηκε τίτλος"))
return REPLY_STATUS.NO_SUBJECT;
if (error.contains("The message body was left empty")
|| error.contains("Δεν δόθηκε κείμενο για το μήνυμα"))
return REPLY_STATUS.EMPTY_BODY;
}
return REPLY_STATUS.NEW_REPLY_WHILE_POSTING;
}
return REPLY_STATUS.SUCCESSFUL;
}
static String htmlToBBcode(String html) {
Map<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");
smileysMap1.put("Grin", ";D");
smileysMap1.put("Angry", ">:(");
smileysMap1.put("Sad", ":(");
smileysMap1.put("Shocked", ":o");
smileysMap1.put("Cool", "8))");
smileysMap1.put("Huh", ":???:");
smileysMap1.put("Roll Eyes", "::)");
smileysMap1.put("Tongue", ":P");
smileysMap1.put("Embarrassed", ":-[");
smileysMap1.put("Lips Sealed", ":-X");
smileysMap1.put("Kiss", ":-*");
smileysMap1.put("Cry", ":'(");
smileysMap1.put("heart", "<3");
smileysMap1.put("kleidaria", "^locked^");
smileysMap1.put("roll_over", "^rollover^");
smileysMap1.put("redface", "^redface^");
smileysMap1.put("confused", "^confused^");
smileysMap1.put("innocent", "^innocent^");
smileysMap1.put("sleep", "^sleep^");
smileysMap1.put("lips_sealed", "^sealed^");
smileysMap1.put("cool", "^cool^");
smileysMap1.put("crazy", "^crazy^");
smileysMap1.put("mad", "^mad^");
smileysMap1.put("wav", "^wav^");
smileysMap1.put("BinkyBaby", "^binkybaby^");
smileysMap1.put("DontKnow", "^dontknow^");
smileysMap1.put("angry4", ":angry4:");
smileysMap1.put("angryAndHot", "^angryhot^");
smileysMap1.put("angry", "^angry^");
smileysMap1.put("bang_head", "^banghead^");
smileysMap1.put("CryBaby", "^crybaby^");
smileysMap1.put("Hello", "^hello^");
smileysMap1.put("jerk", "^jerk^");
smileysMap1.put("NoNo", "^nono^");
smileysMap1.put("NotWorthy", "^notworthy^");
smileysMap1.put("Off-topic", "^off-topic^");
smileysMap1.put("Puke", "^puke^");
smileysMap1.put("Shout", "^shout^");
smileysMap1.put("Slurp", "^slurp^");
smileysMap1.put("SuperConfused", "^superconfused^");
smileysMap1.put("SuperInnocent", "^superinnocent^");
smileysMap1.put("CellPhone", "^cellPhone^");
smileysMap1.put("Idiot", "^idiot^");
smileysMap1.put("Knuppel", "^knuppel^");
smileysMap1.put("TickedOff", "^tickedOff^");
smileysMap1.put("Peace", "^peace^");
smileysMap1.put("Suspicious", "^suspicious^");
smileysMap1.put("Caffine", "^caffine^");
smileysMap1.put("argue", "^argue^");
smileysMap1.put("banned2", "^banned2^");
smileysMap1.put("banned", "^banned^");
smileysMap1.put("bath", "^bath^");
smileysMap1.put("beg", "^beg^");
smileysMap1.put("bluescreen", "^bluescreen^");
smileysMap1.put("boil", "^boil^");
smileysMap1.put("bye", "^bye^");
smileysMap1.put("callmerip", "^callmerip^");
smileysMap1.put("carnaval", "^carnaval^");
smileysMap1.put("clap", "^clap^");
smileysMap1.put("coffepot", "^coffepot^");
smileysMap1.put("crap", "^crap^");
smileysMap1.put("curses", "^curses^");
smileysMap1.put("funny", "^funny^");
smileysMap1.put("guitar", "^guitar^");
smileysMap1.put("kissy", "^kissy^");
smileysMap1.put("band", "^band^");
smileysMap1.put("ivres", "^ivres^");
smileysMap1.put("kaloe", "^kaloe^");
smileysMap1.put("kremala", "^kremala^");
smileysMap1.put("moon", "^moon^");
smileysMap1.put("mopping", "^mopping^");
smileysMap1.put("mountza", "^mountza^");
smileysMap1.put("pcsleep", "^pcsleep^");
smileysMap1.put("pinokio", "^pinokio^");
smileysMap1.put("poke", "^poke^");
smileysMap1.put("seestars", "^seestars^");
smileysMap1.put("sfyri", "^sfyri^");
smileysMap1.put("spam", "^spam^");
smileysMap1.put("super", "^super^");
smileysMap1.put("tafos", "^tafos^");
smileysMap1.put("tomato", "^tomato^");
smileysMap1.put("ytold", "^ytold^");
smileysMap1.put("beer", "^beer^");
smileysMap1.put("ο fritz!!!", "^fritz^");
smileysMap1.put("o Wade!!!", "^wade^");
smileysMap1.put("bonjour", "^hat^");
smileysMap1.put("bonjour2", "^miss^");
smileysMap1.put("question", "^que^");
smileysMap1.put("shifty", "^shifty^");
smileysMap1.put("shy", "^shy^");
smileysMap1.put("music_listenning", "^music_listen^");
smileysMap1.put("bag_face", "^bagface^");
smileysMap1.put("rotation", "^rotate^");
smileysMap1.put("love", "^love^");
smileysMap1.put("speech", "^speech^");
smileysMap1.put("shocked", "^shocked^");
smileysMap1.put("extremely_shocked", "^ex_shocked^");
smileysMap1.put("smurf", "^smurf^");
smileysMap1.put("monster", "^monster^");
smileysMap1.put("pig", "^pig^");
smileysMap1.put("lol", "^lol^");
smileysMap2.put("Police", "^Police^");
smileysMap2.put("foyska", "^fouska^");
smileysMap2.put("nista", "^nysta^");
smileysMap2.put("10_7_3", "^sfinaki^");
smileysMap2.put("yu", "^yue^");
smileysMap2.put("a-eatpaper", "^eatpaper^");
smileysMap2.put("lypi", "^lypi^");
smileysMap2.put("megashok1wq", "^aytoxeir^");
smileysMap2.put("victory", "^victory^");
smileysMap2.put("filarakia", "^filarakia^");
smileysMap2.put("rofl", "^rolfmao^");
smileysMap2.put("locked", "^lock^");
smileysMap2.put("facepalm", "^facepalm^");
//html stuff on the beginning
bbMap.put("<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");
//bold
bbMap.put("\n\\s+?<b>(.+?)</b>", "\\[b\\]$1\\[/b\\]");
//italics
bbMap.put("\n\\s+?<i>(.+?)</i>", "\\[i\\]$1\\[/i\\]");
//underline
bbMap.put("\n\\s+?<span style=\"text-decoration: underline;\">(.+?)</span>", "\\[u\\]$1\\[/u\\]");
//deleted
bbMap.put("\n\\s+?<del>(.+?)</del>", "\\[s\\]$1\\[/s\\]");
//text color
bbMap.put("\n\\s+?<span style=\"color: (.+?);\">(.+?)</span>", "\\[color=$1\\]$2\\[/color\\]");
//glow
bbMap.put("\n\\s+?<span style=\"background-color: (.+?);\">(.+?)</span>", "\\[glow=$1,2,300\\]$2\\[/glow\\]");
//shadow
bbMap.put("\n\\s+?<span style=\"text-shadow: (.+?) (.+?)\">(.+?)</span>", "\\[shadow=$1,$2\\]$3\\[/shadow\\]");
//running text
bbMap.put("\\s+?<marquee>\n (.+?)\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\\]");
//preformated
bbMap.put("\n\\s+?<pre>(.+?)</pre>", "\\[pre\\]$1\\[/pre\\]");
//horizontal rule
bbMap.put("\n\\s+?<hr>", "\\[hr\\]");
//resize
bbMap.put("\n\\s+?<span style=\"font-size: (.+?);(.+?)\">(.+?)</span>", "\\[size=$1\\]$3\\[/size\\]");
//font
bbMap.put("\n\\s+?<span style=\"font-family: (.+?);\">(.+?)</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>",
"\\[list\\]\n$1\n\\[/list\\]");
//latex code
bbMap.put("\n\\s+?<img src=\".+?eq=(.+?)\" .+?\">", "\\[tex\\]$1\\[/tex\\]");
//code
bbMap.put("\n\\s+?<div class=\"code\">\n (.+?)\n </div>", "\\[code\\]$1\\[/code\\]");
//teletype
bbMap.put("\n\\s+?<tt>(.+?)</tt>", "\\[tt\\]$1\\[/tt\\]");
//superscript/subscript
bbMap.put("\n\\s+?<sub>(.+?)</sub>", "\\[sub\\]$1\\[/sub\\]");
bbMap.put("\n\\s+?<sup>(.+?)</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>"
, "\\[table\\]$2\\[/table\\]");
//videos
bbMap.put("\n\\s+?<div class=\"yt\"><a href=\".+?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\\]");
//mailto
bbMap.put("\n\\s+?<a href=\"mailto:(.+?)\">([\\S\\s]+?)</a>", "\\[email\\]$2\\[/email\\]");
//links
bbMap.put("\n\\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=\""
+ entry.getKey().toString() + "\" .+?\">", entry.getValue().toString());
}
for (Map.Entry entry : smileysMap2.entrySet()) { //Those that have empty alt tag
bbMap.put("\n <img src=\"(.+?)//www.thmmy.gr/smf/Smileys/default_dither/"
+ entry.getKey().toString() + ".gif\" .+?\">", entry.getValue().toString());
}
bbMap.put("\n <img src=\"(.+?)//www.thmmy.gr/smf/Smileys/default_dither/undecided.gif\" alt=\"Undecided\" border=\"0\">"
, Matcher.quoteReplacement(":-\\"));
//html stuff on the end
bbMap.put("\n</div>", "");
for (Map.Entry entry : bbMap.entrySet()) {
html = html.replaceAll(entry.getKey().toString(), entry.getValue().toString());
}
//img need to be done last or it messes up everything else
html = html.replaceAll("\\s+<img src=\"(.+?)\" .+? width=\"(.+?)\" .+? height=\"(.+?)\" .+?>",
"\\[img width=$2 height=$3\\]$1\\[/img\\]");
html = html.replaceAll("\\s+<img src=\"(.+?)\" .+? height=\"(.+?)\" .+? width=\"(.+?)\" .+?>",
"\\[img height=$2 width=$3\\]$1\\[/img\\]");
html = html.replaceAll("\\s+<img src=\"(.+?)\" .+? width=\"(.+?)\" .+?>", "\\[img width=$2\\]$1\\[/img\\]");
html = html.replaceAll("\\s+<img src=\"(.+?)\" .+? height=\"(.+?)\" .+?>", "\\[img height=$2\\]$1\\[/img\\]");
html = html.replaceAll("\\s+<img src=\"(.+?)\".+?>", "\\[img\\]$1\\[/img\\]");
return html;
}
}

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

@ -1,40 +1,68 @@
package gr.thmmy.mthmmy.activities.topic; package gr.thmmy.mthmmy.activities.topic;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect; import android.graphics.Rect;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.util.Log; import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.method.LinkMovementMethod;
import android.text.method.ScrollingMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Selector;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects; import java.util.Objects;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.board.BoardActivity;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Bookmark; import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.Post; import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.ThmmyPage; import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.utils.ParseHelpers; import gr.thmmy.mthmmy.utils.ParseHelpers;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import mthmmy.utils.Report;
import okhttp3.MultipartBody;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_TITLE;
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_THUMBNAIL_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME;
import static gr.thmmy.mthmmy.activities.topic.Posting.replyStatus;
/** /**
* Activity for topics. When creating an Intent of this activity you need to bundle a <b>String</b> * Activity for topics. When creating an Intent of this activity you need to bundle a <b>String</b>
@ -44,11 +72,6 @@ import okhttp3.Response;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class TopicActivity extends BaseActivity { public class TopicActivity extends BaseActivity {
//Class variables //Class variables
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "TopicActivity";
/** /**
* The key to use when putting topic's url String to {@link TopicActivity}'s Bundle. * The key to use when putting topic's url String to {@link TopicActivity}'s Bundle.
*/ */
@ -62,10 +85,11 @@ public class TopicActivity extends BaseActivity {
private TopicAdapter topicAdapter; private TopicAdapter topicAdapter;
private ArrayList<Post> postsList; private ArrayList<Post> postsList;
private static final int NO_POST_FOCUS = -1; private static final int NO_POST_FOCUS = -1;
private static int postFocus = NO_POST_FOCUS; private int postFocus = NO_POST_FOCUS;
private static int postFocusPosition = 0; private static int postFocusPosition = 0;
//Quotes //Reply
public static final ArrayList<Integer> toQuoteList = new ArrayList<>(); private FloatingActionButton replyFAB;
private String replyPageUrl = null;
//Topic's pages //Topic's pages
private int thisPage = 1; private int thisPage = 1;
private int numberOfPages = 1; private int numberOfPages = 1;
@ -79,19 +103,24 @@ public class TopicActivity extends BaseActivity {
private static final int LARGE_STEP = 10; private static final int LARGE_STEP = 10;
private Integer pageRequestValue; private Integer pageRequestValue;
//Bottom navigation graphics //Bottom navigation graphics
private LinearLayout bottomNavBar;
private ImageButton firstPage; private ImageButton firstPage;
private ImageButton previousPage; private ImageButton previousPage;
private TextView pageIndicator; private TextView pageIndicator;
private ImageButton nextPage; private ImageButton nextPage;
private ImageButton lastPage; private ImageButton lastPage;
//Topic's info
private SpannableStringBuilder topicTreeAndMods = new SpannableStringBuilder("Loading..."),
topicViewers = new SpannableStringBuilder("Loading...");
//Other variables //Other variables
private FloatingActionButton replyFAB;
private MaterialProgressBar progressBar; private MaterialProgressBar progressBar;
TextView toolbarTitle;
private static String base_url = ""; private static String base_url = "";
private String topicTitle; private String topicTitle;
private String parsedTitle; private String parsedTitle;
private RecyclerView recyclerView; private RecyclerView recyclerView;
private String loadedPageUrl = ""; String loadedPageUrl = "";
private boolean reloadingPage = false;
@Override @Override
@ -105,23 +134,27 @@ public class TopicActivity extends BaseActivity {
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory( ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(
Uri.parse(topicPageUrl)); Uri.parse(topicPageUrl));
if (!target.is(ThmmyPage.PageCategory.TOPIC)) { if (!target.is(ThmmyPage.PageCategory.TOPIC)) {
Report.e(TAG, "Bundle came with a non topic url!\nUrl:\n" + topicPageUrl); Timber.e("Bundle came with a non topic url!\nUrl:\n" + topicPageUrl);
Toast.makeText(this, "An error has occurred\n Aborting.", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "An error has occurred\n Aborting.", Toast.LENGTH_SHORT).show();
finish(); finish();
} }
thisPageBookmark = new Bookmark(topicTitle, ThmmyPage.getTopicId(topicPageUrl));
//Initializes graphics //Initializes graphics
toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitle(topicTitle); toolbarTitle = (TextView) toolbar.findViewById(R.id.toolbar_title);
toolbarTitle.setText(topicTitle);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
if (getSupportActionBar() != null) { if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true);
} }
thisPageBookmark = new Bookmark(topicTitle, ThmmyPage.getTopicId(topicPageUrl)); //Makes title scrollable
thisPageBookmarkButton = (ImageButton) findViewById(R.id.bookmark); toolbarTitle.setHorizontallyScrolling(true);
setTopicBookmark(); toolbarTitle.setMovementMethod(new ScrollingMovementMethod());
createDrawer(); createDrawer();
progressBar = (MaterialProgressBar) findViewById(R.id.progressBar); progressBar = (MaterialProgressBar) findViewById(R.id.progressBar);
@ -132,42 +165,27 @@ public class TopicActivity extends BaseActivity {
recyclerView.setHasFixedSize(true); recyclerView.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext()); LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(layoutManager); recyclerView.setLayoutManager(layoutManager);
topicAdapter = new TopicAdapter(this, postsList, topicAdapter = new TopicAdapter(this, postsList, topicTask, topicTitle, loadedPageUrl);
topicTask);
recyclerView.setAdapter(topicAdapter); recyclerView.setAdapter(topicAdapter);
replyFAB = (FloatingActionButton) findViewById(R.id.topic_fab); replyFAB = (FloatingActionButton) findViewById(R.id.topic_fab);
replyFAB.setEnabled(false); replyFAB.setEnabled(false);
replyFAB.hide(); bottomNavBar = (LinearLayout) findViewById(R.id.bottom_navigation_bar);
/*if (!sessionManager.isLoggedIn()) replyFAB.hide(); if (!sessionManager.isLoggedIn()) replyFAB.hide();
else { else {
replyFAB.setOnClickListener(new View.OnClickListener() { replyFAB.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
if (sessionManager.isLoggedIn()) { if (sessionManager.isLoggedIn()) {
//TODO Reply postsList.add(null);
} else { topicAdapter.prepareForReply(new ReplyTask());
new AlertDialog.Builder(TopicActivity.this) replyFAB.hide();
.setMessage("You need to be logged in to reply!") bottomNavBar.setVisibility(View.GONE);
.setPositiveButton("Login", new DialogInterface.OnClickListener() { topicAdapter.notifyItemInserted(postsList.size());
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent(TopicActivity.this, LoginActivity.class);
startActivity(intent);
finish();
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out);
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
})
.show();
} }
} }
}); });
}*/ }
//Sets bottom navigation bar //Sets bottom navigation bar
firstPage = (ImageButton) findViewById(R.id.page_first_button); firstPage = (ImageButton) findViewById(R.id.page_first_button);
@ -187,6 +205,43 @@ public class TopicActivity extends BaseActivity {
topicTask.execute(extras.getString(BUNDLE_TOPIC_URL)); //Attempt data parsing topicTask.execute(extras.getString(BUNDLE_TOPIC_URL)); //Attempt data parsing
} }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflates the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.topic_menu, menu);
setTopicBookmark(menu.getItem(0));
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.menu_bookmark:
topicMenuBookmarkClick();
return true;
case R.id.menu_info:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
LayoutInflater inflater = this.getLayoutInflater();
LinearLayout infoDialog = (LinearLayout) inflater.inflate(R.layout.dialog_topic_info
, null);
((TextView) infoDialog.findViewById(R.id.dialog_title)).setText("Info");
TextView treeAndMods = (TextView) infoDialog.findViewById(R.id.topic_tree_and_mods);
treeAndMods.setText(topicTreeAndMods);
treeAndMods.setMovementMethod(LinkMovementMethod.getInstance());
TextView usersViewing = (TextView) infoDialog.findViewById(R.id.users_viewing);
usersViewing.setText(topicViewers);
usersViewing.setMovementMethod(LinkMovementMethod.getInstance());
builder.setView(infoDialog);
AlertDialog dialog = builder.create();
dialog.show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (drawer.isDrawerOpen()) { if (drawer.isDrawerOpen()) {
@ -216,7 +271,7 @@ public class TopicActivity extends BaseActivity {
* This class is used to implement the repetitive incrementPageRequestValue/decrementPageRequestValue * This class is used to implement the repetitive incrementPageRequestValue/decrementPageRequestValue
* of page value when long pressing one of the page navigation buttons. * of page value when long pressing one of the page navigation buttons.
*/ */
class RepetitiveUpdater implements Runnable { private class RepetitiveUpdater implements Runnable {
private final int step; private final int step;
/** /**
@ -352,7 +407,8 @@ public class TopicActivity extends BaseActivity {
paginationEnabled(true); paginationEnabled(true);
changePage(pageRequestValue - 1); changePage(pageRequestValue - 1);
} else if (event.getAction() == MotionEvent.ACTION_MOVE) { } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) { if (rect != null &&
!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) {
autoIncrement = false; autoIncrement = false;
incrementPageRequestValue(thisPage - pageRequestValue); incrementPageRequestValue(thisPage - pageRequestValue);
paginationEnabled(true); paginationEnabled(true);
@ -400,11 +456,6 @@ public class TopicActivity extends BaseActivity {
* as String parameter!</p> * as String parameter!</p>
*/ */
class TopicTask extends AsyncTask<String, Void, Integer> { class TopicTask extends AsyncTask<String, Void, Integer> {
//Class variables
/**
* Debug Tag for logging debug output to LogCat
*/
private static final String TAG = "TopicTask"; //Separate tag for AsyncTask
private static final int SUCCESS = 0; private static final int SUCCESS = 0;
private static final int NETWORK_ERROR = 1; private static final int NETWORK_ERROR = 1;
private static final int OTHER_ERROR = 2; private static final int OTHER_ERROR = 2;
@ -419,7 +470,6 @@ public class TopicActivity extends BaseActivity {
protected Integer doInBackground(String... strings) { protected Integer doInBackground(String... strings) {
Document document; Document document;
base_url = strings[0].substring(0, strings[0].lastIndexOf(".")); //New topic's base url
String newPageUrl = strings[0]; String newPageUrl = strings[0];
//Finds the index of message focus if present //Finds the index of message focus if present
@ -434,7 +484,7 @@ public class TopicActivity extends BaseActivity {
} }
} }
//Checks if the page to be loaded is the one already shown //Checks if the page to be loaded is the one already shown
if (!Objects.equals(loadedPageUrl, "") && loadedPageUrl.contains(base_url)) { if (!reloadingPage && !Objects.equals(loadedPageUrl, "") && newPageUrl.contains(base_url)) {
if (newPageUrl.contains("topicseen#new") || newPageUrl.contains("#new")) if (newPageUrl.contains("topicseen#new") || newPageUrl.contains("#new"))
if (thisPage == numberOfPages) if (thisPage == numberOfPages)
return SAME_PAGE; return SAME_PAGE;
@ -448,11 +498,16 @@ public class TopicActivity extends BaseActivity {
return SAME_PAGE; return SAME_PAGE;
} }
} }
} else if (Integer.parseInt(newPageUrl.substring(base_url.length() + 1)) / 15 + 1 == thisPage) } else if ((Objects.equals(newPageUrl, base_url) && thisPage == 1) ||
Integer.parseInt(newPageUrl.substring(base_url.length() + 1)) / 15 + 1 == thisPage)
return SAME_PAGE; return SAME_PAGE;
} else if (!Objects.equals(loadedPageUrl, "")) topicTitle = null; } else if (!Objects.equals(loadedPageUrl, "")) topicTitle = null;
if (reloadingPage) reloadingPage = !reloadingPage;
loadedPageUrl = newPageUrl; loadedPageUrl = newPageUrl;
if (strings[0].substring(0, strings[0].lastIndexOf(".")).contains("topic="))
base_url = strings[0].substring(0, strings[0].lastIndexOf(".")); //New topic's base url
replyPageUrl = null;
Request request = new Request.Builder() Request request = new Request.Builder()
.url(newPageUrl) .url(newPageUrl)
.build(); .build();
@ -462,10 +517,10 @@ public class TopicActivity extends BaseActivity {
parse(document); parse(document);
return SUCCESS; return SUCCESS;
} catch (IOException e) { } catch (IOException e) {
Report.i(TAG, "IO Exception", e); Timber.i("IO Exception", e);
return NETWORK_ERROR; return NETWORK_ERROR;
} catch (Exception e) { } catch (Exception e) {
Report.e(TAG, "Exception", e); Timber.e("Exception", e);
return OTHER_ERROR; return OTHER_ERROR;
} }
} }
@ -483,11 +538,13 @@ public class TopicActivity extends BaseActivity {
case SUCCESS: case SUCCESS:
if (topicTitle == null || Objects.equals(topicTitle, "")) { if (topicTitle == null || Objects.equals(topicTitle, "")) {
thisPageBookmark = new Bookmark(parsedTitle, ThmmyPage.getTopicId(loadedPageUrl)); thisPageBookmark = new Bookmark(parsedTitle, ThmmyPage.getTopicId(loadedPageUrl));
setTopicBookmark(); invalidateOptionsMenu();
} }
progressBar.setVisibility(ProgressBar.INVISIBLE); progressBar.setVisibility(ProgressBar.INVISIBLE);
topicAdapter.customNotifyDataSetChanged(new TopicTask()); topicAdapter.customNotifyDataSetChanged(new TopicTask());
topicAdapter.setTopicInfo(parsedTitle, loadedPageUrl);
if (replyPageUrl == null) replyFAB.hide();
if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(true); if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(true);
//Set current page //Set current page
@ -496,8 +553,10 @@ public class TopicActivity extends BaseActivity {
paginationEnabled(true); paginationEnabled(true);
if (topicTitle != null)
if (parsedTitle != null)
if (topicTitle == null || Objects.equals(topicTitle, "")) if (topicTitle == null || Objects.equals(topicTitle, ""))
toolbar.setTitle(parsedTitle); toolbarTitle.setText(parsedTitle);
break; break;
case NETWORK_ERROR: case NETWORK_ERROR:
Toast.makeText(getBaseContext(), "Network Error", Toast.LENGTH_SHORT).show(); Toast.makeText(getBaseContext(), "Network Error", Toast.LENGTH_SHORT).show();
@ -512,7 +571,7 @@ public class TopicActivity extends BaseActivity {
break; break;
default: default:
//Parse failed - should never happen //Parse failed - should never happen
Report.d(TAG, "Parse failed!"); Timber.d("Parse failed!"); //TODO report ParseException?
Toast.makeText(getBaseContext(), "Fatal Error", Toast.LENGTH_SHORT).show(); Toast.makeText(getBaseContext(), "Fatal Error", Toast.LENGTH_SHORT).show();
finish(); finish();
break; break;
@ -528,6 +587,20 @@ public class TopicActivity extends BaseActivity {
private void parse(Document topic) { private void parse(Document topic) {
ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic); ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic);
//Finds topic's tree, mods and users viewing
{
topicTreeAndMods = getSpannableFromHtml(topic.select("div.nav").first().html());
topicViewers = getSpannableFromHtml(TopicParser.parseUsersViewingThisTopic(topic, language));
}
//Finds reply page url
{
Element replyButton = topic.select("a:has(img[alt=Reply])").first();
if (replyButton == null)
replyButton = topic.select("a:has(img[alt=Απάντηση])").first();
if (replyButton != null) replyPageUrl = replyButton.attr("href");
}
//Finds topic title if missing //Finds topic title if missing
if (topicTitle == null || Objects.equals(topicTitle, "")) { if (topicTitle == null || Objects.equals(topicTitle, "")) {
parsedTitle = topic.select("td[id=top_subject]").first().text(); parsedTitle = topic.select("td[id=top_subject]").first().text();
@ -537,7 +610,7 @@ public class TopicActivity extends BaseActivity {
} else { } else {
parsedTitle = parsedTitle.substring(parsedTitle.indexOf("Θέμα:") + 6 parsedTitle = parsedTitle.substring(parsedTitle.indexOf("Θέμα:") + 6
, parsedTitle.indexOf("(Αναγνώστηκε") - 2); , parsedTitle.indexOf("(Αναγνώστηκε") - 2);
Report.d(TAG, parsedTitle); Timber.d(parsedTitle);
} }
} }
@ -555,7 +628,150 @@ public class TopicActivity extends BaseActivity {
postsList.clear(); postsList.clear();
postsList.addAll(TopicParser.parseTopic(topic, language)); postsList.addAll(TopicParser.parseTopic(topic, language));
//postsList = TopicParser.parseTopic(topic, language); }
private void makeLinkClickable(SpannableStringBuilder strBuilder, final URLSpan span) {
int start = strBuilder.getSpanStart(span);
int end = strBuilder.getSpanEnd(span);
int flags = strBuilder.getSpanFlags(span);
ClickableSpan clickable = new ClickableSpan() {
@Override
public void onClick(View view) {
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(span.getURL()));
if (target.is(ThmmyPage.PageCategory.BOARD)) {
Intent intent = new Intent(getApplicationContext(), BoardActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_BOARD_URL, span.getURL());
extras.putString(BUNDLE_BOARD_TITLE, "");
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(intent);
} else if (target.is(ThmmyPage.PageCategory.PROFILE)) {
Intent intent = new Intent(getApplicationContext(), ProfileActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, span.getURL());
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
extras.putString(BUNDLE_PROFILE_USERNAME, "");
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(intent);
} else if (target.is(ThmmyPage.PageCategory.INDEX))
finish();
}
};
strBuilder.setSpan(clickable, start, end, flags);
strBuilder.removeSpan(span);
}
private SpannableStringBuilder getSpannableFromHtml(String html) {
CharSequence sequence = Html.fromHtml(html);
SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
URLSpan[] urls = strBuilder.getSpans(0, sequence.length(), URLSpan.class);
for (URLSpan span : urls) {
makeLinkClickable(strBuilder, span);
}
return strBuilder;
}
}
class ReplyTask extends AsyncTask<String, Void, Boolean> {
@Override
protected void onPreExecute() {
progressBar.setVisibility(ProgressBar.VISIBLE);
paginationEnabled(false);
replyFAB.setEnabled(false);
}
@Override
protected Boolean doInBackground(String... message) {
Document document;
String numReplies, seqnum, sc, subject, topic;
Request request = new Request.Builder()
.url(replyPageUrl + ";wap2")
.build();
try {
Response response = client.newCall(request).execute();
document = Jsoup.parse(response.body().string());
numReplies = replyPageUrl.substring(replyPageUrl.indexOf("num_replies=") + 12);
seqnum = document.select("input[name=seqnum]").first().attr("value");
sc = document.select("input[name=sc]").first().attr("value");
//subject = document.select("input[name=subject]").first().attr("value");
topic = document.select("input[name=topic]").first().attr("value");
} catch (IOException e) {
Timber.e("Post failed.", e);
return false;
} catch (Selector.SelectorParseException e) {
Timber.e("Post failed.", e);
return false;
}
RequestBody postBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("message", message[1])
.addFormDataPart("num_replies", numReplies)
.addFormDataPart("seqnum", seqnum)
.addFormDataPart("sc", sc)
.addFormDataPart("subject", message[0])
.addFormDataPart("topic", topic)
.build();
Request post = new Request.Builder()
.url("https://www.thmmy.gr/smf/index.php?action=post2")
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36")
.post(postBody)
.build();
try {
client.newCall(post).execute();
Response response = client.newCall(post).execute();
switch (replyStatus(response)) {
case SUCCESSFUL:
return true;
case NEW_REPLY_WHILE_POSTING:
//TODO this...
return true;
default:
Timber.e("Malformed post. Request string:\n" + post.toString());
return true;
}
} catch (IOException e) {
Timber.e("Post failed.", e);
return false;
}
}
@Override
protected void onPostExecute(Boolean result) {
View view = getCurrentFocus();
if (view != null) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
postsList.remove(postsList.size() - 1);
topicAdapter.notifyItemRemoved(postsList.size());
progressBar.setVisibility(ProgressBar.GONE);
replyFAB.setVisibility(View.VISIBLE);
bottomNavBar.setVisibility(View.VISIBLE);
if (!result)
Toast.makeText(TopicActivity.this, "Post failed!", Toast.LENGTH_SHORT).show();
paginationEnabled(true);
replyFAB.setEnabled(true);
if (result) {
topicTask = new TopicTask();
if ((postsList.get(postsList.size() - 1).getPostNumber() + 1) % 15 == 0)
topicTask.execute(base_url + "." + 2147483647);
else {
reloadingPage = true;
topicTask.execute(loadedPageUrl);
}
}
} }
} }
} }

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

@ -10,10 +10,15 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.res.ResourcesCompat; import android.support.v4.content.res.ResourcesCompat;
import android.support.v7.widget.AppCompatImageButton;
import android.support.v7.widget.CardView; import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
@ -21,6 +26,7 @@ import android.view.ViewGroup;
import android.webkit.WebResourceRequest; import android.webkit.WebResourceRequest;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
import android.widget.EditText;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
@ -30,8 +36,14 @@ import android.widget.TextView;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
@ -42,7 +54,8 @@ import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.ThmmyFile; import gr.thmmy.mthmmy.model.ThmmyFile;
import gr.thmmy.mthmmy.model.ThmmyPage; import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.utils.CircleTransform; import gr.thmmy.mthmmy.utils.CircleTransform;
import mthmmy.utils.Report;
import timber.log.Timber;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_TITLE; import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_TITLE;
@ -50,21 +63,20 @@ import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_THUMBNAIL_URL; import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_THUMBNAIL_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL; import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME; import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.toQuoteList; import static gr.thmmy.mthmmy.activities.topic.Posting.htmlToBBcode;
import static gr.thmmy.mthmmy.base.BaseActivity.getSessionManager;
/** /**
* Custom {@link android.support.v7.widget.RecyclerView.Adapter} used for topics. * Custom {@link android.support.v7.widget.RecyclerView.Adapter} used for topics.
*/ */
class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> { class TopicAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
/**
* Debug Tag for logging debug output to LogCat
*/
private static final String TAG = "TopicAdapter";
/** /**
* Int that holds thumbnail's size defined in R.dimen * Int that holds thumbnail's size defined in R.dimen
*/ */
private static int THUMBNAIL_SIZE; private static int THUMBNAIL_SIZE;
private final Context context; private final Context context;
private String topicTitle;
private ArrayList<Integer> toQuoteList = new ArrayList<>();
private final List<Post> postsList; private final List<Post> postsList;
/** /**
* Used to hold the state of visibility and other attributes for views that are animated or * Used to hold the state of visibility and other attributes for views that are animated or
@ -87,69 +99,20 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
*/ */
private static final int isQuoteButtonChecked = 2; private static final int isQuoteButtonChecked = 2;
private TopicActivity.TopicTask topicTask; 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];
* Custom {@link RecyclerView.ViewHolder} implementation private int replySubject = 0, replyText = 1;
*/ private String loadedPageUrl = "";
class MyViewHolder extends RecyclerView.ViewHolder {
final CardView cardView;
final LinearLayout cardChildLinear;
final FrameLayout postDateAndNumberExp;
final TextView postDate, postNum, username, subject;
final ImageView thumbnail;
final public WebView post;
final ImageButton quoteToggle;
final RelativeLayout header;
final LinearLayout userExtraInfo;
final View bodyFooterDivider;
final LinearLayout postFooter;
final TextView specialRank, rank, gender, numberOfPosts, personalText, stars;
MyViewHolder(View view) {
super(view);
//Initializes layout's graphic elements
//Standard stuff
cardView = (CardView) view.findViewById(R.id.card_view);
cardChildLinear = (LinearLayout) view.findViewById(R.id.card_child_linear);
postDateAndNumberExp = (FrameLayout) view.findViewById(R.id.post_date_and_number_exp);
postDate = (TextView) view.findViewById(R.id.post_date);
postNum = (TextView) view.findViewById(R.id.post_number);
thumbnail = (ImageView) view.findViewById(R.id.thumbnail);
username = (TextView) view.findViewById(R.id.username);
subject = (TextView) view.findViewById(R.id.subject);
post = (WebView) view.findViewById(R.id.post);
post.setBackgroundColor(Color.argb(1, 255, 255, 255));
quoteToggle = (ImageButton) view.findViewById(R.id.toggle_quote_button);
bodyFooterDivider = view.findViewById(R.id.body_footer_divider);
postFooter = (LinearLayout) view.findViewById(R.id.post_footer);
//User's extra info
header = (RelativeLayout) view.findViewById(R.id.header);
userExtraInfo = (LinearLayout) view.findViewById(R.id.user_extra_info);
specialRank = (TextView) view.findViewById(R.id.special_rank);
rank = (TextView) view.findViewById(R.id.rank);
gender = (TextView) view.findViewById(R.id.gender);
numberOfPosts = (TextView) view.findViewById(R.id.number_of_posts);
personalText = (TextView) view.findViewById(R.id.personal_text);
stars = (TextView) view.findViewById(R.id.stars);
}
/**
* Cancels all pending Picasso requests
*/
void cleanup() {
Picasso.with(context).cancelRequest(thumbnail);
thumbnail.setImageDrawable(null);
}
}
/** /**
* @param context the context of the {@link RecyclerView} * @param context the context of the {@link RecyclerView}
* @param postsList List of {@link Post} objects to use * @param postsList List of {@link Post} objects to use
*/ */
TopicAdapter(Context context, List<Post> postsList, TopicAdapter(Context context, List<Post> postsList, TopicActivity.TopicTask topicTask
TopicActivity.TopicTask topicTask) { , String topicTitle, String loadedPageUrl) {
this.context = context; this.context = context;
this.postsList = postsList; this.postsList = postsList;
@ -159,24 +122,55 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
viewProperties.add(new boolean[3]); viewProperties.add(new boolean[3]);
} }
this.topicTask = topicTask; this.topicTask = topicTask;
this.topicTitle = topicTitle;
this.loadedPageUrl = loadedPageUrl;
}
void prepareForReply(TopicActivity.ReplyTask replyTask) {
this.replyTask = replyTask;
}
void setTopicInfo(String topicTitle, String loadedPageUrl) {
this.topicTitle = topicTitle;
this.loadedPageUrl = loadedPageUrl;
} }
@Override @Override
public void onViewRecycled(final MyViewHolder holder) { public int getItemViewType(int position) {
holder.cleanup(); return postsList.get(position) == null ? VIEW_TYPE_QUICK_REPLY : VIEW_TYPE_POST;
} }
@Override @Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_POST) {
View itemView = LayoutInflater.from(parent.getContext()) View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.activity_topic_post_row, parent, false); .inflate(R.layout.activity_topic_post_row, parent, false);
return new MyViewHolder(itemView); return new PostViewHolder(itemView);
} } else if (viewType == VIEW_TYPE_QUICK_REPLY) {
View view = LayoutInflater.from(parent.getContext()).
@SuppressLint("SetJavaScriptEnabled") inflate(R.layout.activity_topic_quick_reply_row, parent, false);
view.findViewById(R.id.quick_reply_submit).setEnabled(true);
//Default post subject
replyDataHolder[replySubject] = "Re: " + topicTitle;
//Build quotes
String quotes = "";
for (int quotePosition : toQuoteList) {
quotes += buildQuote(quotePosition);
}
if (!Objects.equals(quotes, ""))
replyDataHolder[replyText] = htmlToBBcode(quotes);
return new QuickReplyViewHolder(view, new CustomEditTextListener(replySubject),
new CustomEditTextListener(replyText));
}
return null;
}
@SuppressLint({"SetJavaScriptEnabled", "SetTextI18n"})
@Override @Override
public void onBindViewHolder(final MyViewHolder holder, final int position) { public void onBindViewHolder(final RecyclerView.ViewHolder currentHolder, final int position) {
if (currentHolder instanceof PostViewHolder) {
final Post currentPost = postsList.get(position); final Post currentPost = postsList.get(position);
final PostViewHolder holder = (PostViewHolder) currentHolder;
//Post's WebView parameters //Post's WebView parameters
holder.post.setClickable(true); holder.post.setClickable(true);
@ -211,15 +205,18 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
holder.postNum.setText(""); holder.postNum.setText("");
holder.subject.setText(currentPost.getSubject()); holder.subject.setText(currentPost.getSubject());
holder.post.loadDataWithBaseURL("file:///android_asset/", currentPost.getContent(), "text/html", "UTF-8", null); holder.post.loadDataWithBaseURL("file:///android_asset/", currentPost.getContent(), "text/html", "UTF-8", null);
if (currentPost.getAttachedFiles() != null && currentPost.getAttachedFiles().size() != 0) { if ((currentPost.getAttachedFiles() != null && currentPost.getAttachedFiles().size() != 0)
|| (currentPost.getLastEdit() != null)) {
holder.bodyFooterDivider.setVisibility(View.VISIBLE); holder.bodyFooterDivider.setVisibility(View.VISIBLE);
holder.postFooter.removeAllViews();
if (currentPost.getAttachedFiles() != null && currentPost.getAttachedFiles().size() != 0) {
int filesTextColor; int filesTextColor;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
filesTextColor = context.getResources().getColor(R.color.accent, null); filesTextColor = context.getResources().getColor(R.color.accent, null);
} else //noinspection deprecation } else //noinspection deprecation
filesTextColor = context.getResources().getColor(R.color.accent); filesTextColor = context.getResources().getColor(R.color.accent);
holder.postFooter.removeAllViews();
for (final ThmmyFile attachedFile : currentPost.getAttachedFiles()) { for (final ThmmyFile attachedFile : currentPost.getAttachedFiles()) {
final TextView attached = new TextView(context); final TextView attached = new TextView(context);
attached.setTextSize(10f); attached.setTextSize(10f);
@ -240,6 +237,21 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
holder.postFooter.addView(attached); holder.postFooter.addView(attached);
} }
}
if (currentPost.getLastEdit() != null && currentPost.getLastEdit().length() > 0) {
int lastEditTextColor;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
lastEditTextColor = context.getResources().getColor(R.color.white, null);
} else //noinspection deprecation
lastEditTextColor = context.getResources().getColor(R.color.white);
final TextView lastEdit = new TextView(context);
lastEdit.setTextSize(12f);
lastEdit.setText(currentPost.getLastEdit());
lastEdit.setTextColor(lastEditTextColor);
lastEdit.setPadding(0, 3, 0, 3);
holder.postFooter.addView(lastEdit);
}
} else { } else {
holder.bodyFooterDivider.setVisibility(View.GONE); holder.bodyFooterDivider.setVisibility(View.GONE);
holder.postFooter.removeAllViews(); holder.postFooter.removeAllViews();
@ -325,11 +337,10 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
} }
if (!currentPost.isDeleted()) { if (!currentPost.isDeleted()) {
//Sets graphics behavior //Sets graphics behavior
holder.header.setOnClickListener(new View.OnClickListener() { holder.thumbnail.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View view) {
//Clicking an expanded header starts profile activity //Clicking the thumbnail opens user's profile
if (viewProperties.get(holder.getAdapterPosition())[isUserExtraInfoVisibile]) {
Intent intent = new Intent(context, ProfileActivity.class); Intent intent = new Intent(context, ProfileActivity.class);
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, currentPost.getProfileURL()); extras.putString(BUNDLE_PROFILE_URL, currentPost.getProfileURL());
@ -342,7 +353,11 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent); context.startActivity(intent);
} }
});
holder.header.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//Clicking the header makes it expand/collapse
boolean[] tmp = viewProperties.get(holder.getAdapterPosition()); boolean[] tmp = viewProperties.get(holder.getAdapterPosition());
tmp[isUserExtraInfoVisibile] = !tmp[isUserExtraInfoVisibile]; tmp[isUserExtraInfoVisibile] = !tmp[isUserExtraInfoVisibile];
viewProperties.set(holder.getAdapterPosition(), tmp); viewProperties.set(holder.getAdapterPosition(), tmp);
@ -354,7 +369,7 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
boolean[] tmp = viewProperties.get(holder.getAdapterPosition()); boolean[] tmp = viewProperties.get(holder.getAdapterPosition());
tmp[1] = false; tmp[isUserExtraInfoVisibile] = false;
viewProperties.set(holder.getAdapterPosition(), tmp); viewProperties.set(holder.getAdapterPosition(), tmp);
TopicAnimations.animateUserExtraInfoVisibility(v); TopicAnimations.animateUserExtraInfoVisibility(v);
@ -390,7 +405,7 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
holder.subject.setEllipsize(TextUtils.TruncateAt.END); holder.subject.setEllipsize(TextUtils.TruncateAt.END);
} }
//noinspection PointlessBooleanExpression,ConstantConditions //noinspection PointlessBooleanExpression,ConstantConditions
if (!BaseActivity.getSessionManager().isLoggedIn() || true) //Hide it until reply is implemented if (!BaseActivity.getSessionManager().isLoggedIn())
holder.quoteToggle.setVisibility(View.GONE); holder.quoteToggle.setVisibility(View.GONE);
else { else {
if (viewProperties.get(position)[isQuoteButtonChecked]) if (viewProperties.get(position)[isQuoteButtonChecked])
@ -403,14 +418,14 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
public void onClick(View view) { public void onClick(View view) {
boolean[] tmp = viewProperties.get(holder.getAdapterPosition()); boolean[] tmp = viewProperties.get(holder.getAdapterPosition());
if (tmp[isQuoteButtonChecked]) { if (tmp[isQuoteButtonChecked]) {
if (toQuoteList.contains(currentPost.getPostNumber())) { if (toQuoteList.contains(postsList.indexOf(currentPost))) {
toQuoteList.remove(toQuoteList.indexOf(currentPost.getPostNumber())); toQuoteList.remove(toQuoteList.indexOf(postsList.indexOf(currentPost)));
} else } else
Report.i(TAG, "An error occurred while trying to exclude post from" + Timber.i("An error occurred while trying to exclude post from" +
"toQuoteList, post wasn't there!"); "toQuoteList, post wasn't there!");
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_unchecked); holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_unchecked);
} else { } else {
toQuoteList.add(currentPost.getPostNumber()); toQuoteList.add(postsList.indexOf(currentPost));
holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked); holder.quoteToggle.setImageResource(R.drawable.ic_format_quote_checked);
} }
tmp[isQuoteButtonChecked] = !tmp[isQuoteButtonChecked]; tmp[isQuoteButtonChecked] = !tmp[isQuoteButtonChecked];
@ -435,6 +450,41 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
}); });
//Also when post is clicked //Also when post is clicked
holder.post.setOnTouchListener(new CustomTouchListener(holder.post, holder.cardView)); holder.post.setOnTouchListener(new CustomTouchListener(holder.post, holder.cardView));
} else if (currentHolder instanceof QuickReplyViewHolder) {
final QuickReplyViewHolder holder = (QuickReplyViewHolder) currentHolder;
//noinspection ConstantConditions
Picasso.with(context)
.load(getSessionManager().getAvatarLink())
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail, null))
.placeholder(ResourcesCompat.getDrawable(context.getResources()
, R.drawable.ic_default_user_thumbnail, null))
.transform(new CircleTransform())
.into(holder.thumbnail);
holder.username.setText(getSessionManager().getUsername());
holder.quickReplySubject.setText(replyDataHolder[replySubject]);
if (replyDataHolder[replyText] != null && !Objects.equals(replyDataHolder[replyText], ""))
holder.quickReply.setText(replyDataHolder[replyText]);
holder.submitButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (holder.quickReplySubject.getText().toString().isEmpty()) return;
if (holder.quickReply.getText().toString().isEmpty()) return;
holder.submitButton.setEnabled(false);
replyTask.execute(holder.quickReplySubject.getText().toString(),
holder.quickReply.getText().toString());
holder.quickReplySubject.getText().clear();
holder.quickReplySubject.setText("Re: " + topicTitle);
holder.quickReply.getText().clear();
}
});
}
} }
void customNotifyDataSetChanged(TopicActivity.TopicTask topicTask) { void customNotifyDataSetChanged(TopicActivity.TopicTask topicTask) {
@ -452,6 +502,79 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
return postsList.size(); return postsList.size();
} }
/**
* Custom {@link RecyclerView.ViewHolder} implementation
*/
private class PostViewHolder extends RecyclerView.ViewHolder {
final CardView cardView;
final LinearLayout cardChildLinear;
final FrameLayout postDateAndNumberExp;
final TextView postDate, postNum, username, subject;
final ImageView thumbnail;
final public WebView post;
final ImageButton quoteToggle;
final RelativeLayout header;
final LinearLayout userExtraInfo;
final View bodyFooterDivider;
final LinearLayout postFooter;
final TextView specialRank, rank, gender, numberOfPosts, personalText, stars;
PostViewHolder(View view) {
super(view);
//Initializes layout's graphic elements
//Standard stuff
cardView = (CardView) view.findViewById(R.id.card_view);
cardChildLinear = (LinearLayout) view.findViewById(R.id.card_child_linear);
postDateAndNumberExp = (FrameLayout) view.findViewById(R.id.post_date_and_number_exp);
postDate = (TextView) view.findViewById(R.id.post_date);
postNum = (TextView) view.findViewById(R.id.post_number);
thumbnail = (ImageView) view.findViewById(R.id.thumbnail);
username = (TextView) view.findViewById(R.id.username);
subject = (TextView) view.findViewById(R.id.subject);
post = (WebView) view.findViewById(R.id.post);
post.setBackgroundColor(Color.argb(1, 255, 255, 255));
quoteToggle = (ImageButton) view.findViewById(R.id.toggle_quote_button);
bodyFooterDivider = view.findViewById(R.id.body_footer_divider);
postFooter = (LinearLayout) view.findViewById(R.id.post_footer);
//User's extra info
header = (RelativeLayout) view.findViewById(R.id.header);
userExtraInfo = (LinearLayout) view.findViewById(R.id.user_extra_info);
specialRank = (TextView) view.findViewById(R.id.special_rank);
rank = (TextView) view.findViewById(R.id.rank);
gender = (TextView) view.findViewById(R.id.gender);
numberOfPosts = (TextView) view.findViewById(R.id.number_of_posts);
personalText = (TextView) view.findViewById(R.id.personal_text);
stars = (TextView) view.findViewById(R.id.stars);
}
}
/**
* Custom {@link RecyclerView.ViewHolder} implementation
*/
private static class QuickReplyViewHolder extends RecyclerView.ViewHolder {
final ImageView thumbnail;
final TextView username;
final EditText quickReply, quickReplySubject;
final AppCompatImageButton submitButton;
final CustomEditTextListener replySubject, replyText;
QuickReplyViewHolder(View quickReply, CustomEditTextListener replySubject
, CustomEditTextListener replyText) {
super(quickReply);
thumbnail = (ImageView) quickReply.findViewById(R.id.thumbnail);
username = (TextView) quickReply.findViewById(R.id.username);
this.quickReply = (EditText) quickReply.findViewById(R.id.quick_reply_text);
this.replyText = replyText;
this.quickReply.addTextChangedListener(replyText);
quickReplySubject = (EditText) quickReply.findViewById(R.id.quick_reply_subject);
this.replySubject = replySubject;
quickReplySubject.addTextChangedListener(replySubject);
submitButton = (AppCompatImageButton) quickReply.findViewById(R.id.quick_reply_submit);
}
}
/** /**
* This class is a gesture detector for WebViews. It handles post's clicks, long clicks and * This class is a gesture detector for WebViews. It handles post's clicks, long clicks and
* touch and drag. * touch and drag.
@ -592,6 +715,84 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
//Method always returns true as no url should be loaded in the WebViews //Method always returns true as no url should be loaded in the WebViews
return true; return true;
} }
}
private class CustomEditTextListener implements TextWatcher {
private final int positionInDataHolder;
CustomEditTextListener(int positionInDataHolder) {
this.positionInDataHolder = positionInDataHolder;
}
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
replyDataHolder[positionInDataHolder] = charSequence.toString();
}
@Override
public void afterTextChanged(Editable editable) {
}
}
@Nullable
private String buildQuote(int quotePosition) {
Date postDate = null;
{
String date = postsList.get(quotePosition).getPostDate();
if (date != null) {
DateFormat format = new SimpleDateFormat("MMMM d, yyyy, h:m:s a", Locale.ENGLISH);
date = date.replace("Ιανουαρίου", "January");
date = date.replace("Φεβρουαρίου", "February");
date = date.replace("Μαρτίου", "March");
date = date.replace("Απριλίου", "April");
date = date.replace("Μαΐου", "May");
date = date.replace("Ιουνίου", "June");
date = date.replace("Ιουλίου", "July");
date = date.replace("Αυγούστου", "August");
date = date.replace("Σεπτεμβρίου", "September");
date = date.replace("Οκτωβρίου", "October");
date = date.replace("Νοεμβρίου", "November");
date = date.replace("Δεκεμβρίου", "December");
if (date.contains("Today")) {
date = date.replace("Today at",
Calendar.getInstance().getDisplayName(Calendar.MONTH,
Calendar.LONG, Locale.ENGLISH)
+ " " + Calendar.getInstance().get(Calendar.DAY_OF_MONTH)
+ ", " + Calendar.getInstance().get(Calendar.YEAR) + ",");
} else if (date.contains("Σήμερα")) {
date = date.replace("Σήμερα στις",
Calendar.getInstance().getDisplayName(Calendar.MONTH,
Calendar.LONG, Locale.ENGLISH)
+ " " + Calendar.getInstance().get(Calendar.DAY_OF_MONTH)
+ ", " + Calendar.getInstance().get(Calendar.YEAR) + ",");
if (date.contains("πμ")) date = date.replace("πμ", "am");
if (date.contains("μμ")) date = date.replace("μμ", "pm");
}
try {
postDate = format.parse(date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
if (postsList.get(quotePosition).getPostIndex() != 0) {
if (postDate != null) {
return "[quote author=" + postsList.get(quotePosition).getAuthor()
+ " link=topic=" + ThmmyPage.getTopicId(loadedPageUrl) + ".msg"
+ postsList.get(quotePosition).getPostIndex()
+ "#msg" + postsList.get(quotePosition).getPostIndex()
+ " date=" + postDate.getTime() / 1000 + "]"
+ "\n" + postsList.get(quotePosition).getContent()
+ "\n" + "[/quote]" + "\n\n";
}
}
return null;
} }
/** /**

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

@ -1,6 +1,7 @@
package gr.thmmy.mthmmy.activities.topic; package gr.thmmy.mthmmy.activities.topic;
import android.graphics.Color; import android.graphics.Color;
import android.util.Log;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
@ -17,7 +18,8 @@ import java.util.Objects;
import gr.thmmy.mthmmy.model.Post; import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.ThmmyFile; import gr.thmmy.mthmmy.model.ThmmyFile;
import gr.thmmy.mthmmy.utils.ParseHelpers; import gr.thmmy.mthmmy.utils.ParseHelpers;
import mthmmy.utils.Report; import timber.log.Timber;
/** /**
* Singleton used for parsing a topic. * Singleton used for parsing a topic.
@ -36,12 +38,6 @@ class TopicParser {
static final int USER_COLOR_PINK = Color.parseColor("#FF4081"); static final int USER_COLOR_PINK = Color.parseColor("#FF4081");
private static final int USER_COLOR_YELLOW = Color.parseColor("#FFEB3B"); private static final int USER_COLOR_YELLOW = Color.parseColor("#FFEB3B");
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "TopicParser";
/** /**
* Returns users currently viewing this topic. * Returns users currently viewing this topic.
* *
@ -160,7 +156,7 @@ class TopicParser {
for (Element thisRow : postRows) { for (Element thisRow : postRows) {
//Variables for Post constructor //Variables for Post constructor
String p_userName, p_thumbnailUrl, p_subject, p_post, p_postDate, p_profileURL, p_rank, String p_userName, p_thumbnailUrl, p_subject, p_post, p_postDate, p_profileURL, p_rank,
p_specialRank, p_gender, p_personalText, p_numberOfPosts; p_specialRank, p_gender, p_personalText, p_numberOfPosts, p_postLastEditDate;
int p_postNum, p_postIndex, p_numberOfStars, p_userColor; int p_postNum, p_postIndex, p_numberOfStars, p_userColor;
boolean p_isDeleted = false; boolean p_isDeleted = false;
ArrayList<ThmmyFile> p_attachedFiles; ArrayList<ThmmyFile> p_attachedFiles;
@ -175,6 +171,7 @@ class TopicParser {
p_numberOfStars = 0; p_numberOfStars = 0;
p_userColor = USER_COLOR_YELLOW; p_userColor = USER_COLOR_YELLOW;
p_attachedFiles = new ArrayList<>(); p_attachedFiles = new ArrayList<>();
p_postLastEditDate = null;
//Language independent parsing //Language independent parsing
//Finds thumbnail url //Finds thumbnail url
@ -190,20 +187,30 @@ class TopicParser {
//Finds post's text //Finds post's text
p_post = ParseHelpers.youtubeEmbeddedFix(thisRow.select("div").select(".post").first()); p_post = ParseHelpers.youtubeEmbeddedFix(thisRow.select("div").select(".post").first());
//Add stuff to make it work in WebView //Adds stuff to make it work in WebView
//style.css //style.css
p_post = ("<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />" + p_post); p_post = ("<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />" + p_post);
//Find post's index //Finds post's index
//This is an int assigned by the forum used for post focusing and quotes, it is not //This is an int assigned by the forum used for post focusing and quotes, it is not
//the same as reply index. //the same as reply index.
Element postIndex = thisRow.select("a[name^=msg]").first(); Element postIndex = thisRow.select("a[name^=msg]").first();
if (postIndex != null) {
String tmp = postIndex.attr("name");
p_postIndex = Integer.parseInt(tmp.substring(tmp.indexOf("msg") + 3));
} else{
postIndex = thisRow.select("div[id^=subject]").first();
if (postIndex == null) if (postIndex == null)
p_postIndex = NO_INDEX; p_postIndex = NO_INDEX;
else{ else{
String tmp = postIndex.attr("name"); String tmp = postIndex.attr("id");
p_postIndex = Integer.parseInt(tmp.substring(tmp.indexOf("msg") + 3)); p_postIndex = Integer.parseInt(tmp.substring(tmp.indexOf("subject") + 8));
} }
}
Element postLastEditDate = thisRow.select("td.smalltext[id^=modified_]").first();
if (postLastEditDate != null && !Objects.equals(postLastEditDate.text(), ""))
p_postLastEditDate = postLastEditDate.text();
//Language dependent parsing //Language dependent parsing
Element userName; Element userName;
@ -252,7 +259,7 @@ class TopicParser {
try { try {
attachedUrl = new URL(tmpAttachedFileUrlAndName.attr("href")); attachedUrl = new URL(tmpAttachedFileUrlAndName.attr("href"));
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
Report.e(TAG, "Attached file malformed url", e); Timber.e("Attached file malformed url", e);
break; break;
} }
String attachedFileName = tmpAttachedFileUrlAndName.text().substring(1); String attachedFileName = tmpAttachedFileUrlAndName.text().substring(1);
@ -312,7 +319,7 @@ class TopicParser {
try { try {
attachedUrl = new URL(tmpAttachedFileUrlAndName.attr("href")); attachedUrl = new URL(tmpAttachedFileUrlAndName.attr("href"));
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
Report.e(TAG, "Attached file malformed url", e); Timber.e("Attached file malformed url", e);
break; break;
} }
String attachedFileName = tmpAttachedFileUrlAndName.text().substring(1); String attachedFileName = tmpAttachedFileUrlAndName.text().substring(1);
@ -406,12 +413,12 @@ class TopicParser {
parsedPostsList.add(new Post(p_thumbnailUrl, p_userName, p_subject, p_post, p_postIndex parsedPostsList.add(new Post(p_thumbnailUrl, p_userName, p_subject, p_post, p_postIndex
, p_postNum, p_postDate, p_profileURL, p_rank, p_specialRank, p_gender , p_postNum, p_postDate, p_profileURL, p_rank, p_specialRank, p_gender
, p_numberOfPosts, p_personalText, p_numberOfStars, p_userColor , p_numberOfPosts, p_personalText, p_numberOfStars, p_userColor
, p_attachedFiles)); , p_attachedFiles, p_postLastEditDate));
} else { //Deleted user } else { //Deleted user
//Add new post in postsList, only standard information needed //Add new post in postsList, only standard information needed
parsedPostsList.add(new Post(p_thumbnailUrl, p_userName, p_subject, p_post, p_postIndex parsedPostsList.add(new Post(p_thumbnailUrl, p_userName, p_subject, p_post, p_postIndex
, p_postNum, p_postDate, p_userColor, p_attachedFiles)); , p_postNum, p_postDate, p_userColor, p_attachedFiles, p_postLastEditDate));
} }
} }
return parsedPostsList; return parsedPostsList;

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

@ -14,6 +14,7 @@ import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.Toast; import android.widget.Toast;
@ -63,7 +64,8 @@ public abstract class BaseActivity extends AppCompatActivity {
private static final String BOOKMARKED_TOPICS_KEY = "bookmarkedTopicsKey"; private static final String BOOKMARKED_TOPICS_KEY = "bookmarkedTopicsKey";
private static final String BOOKMARKED_BOARDS_KEY = "bookmarkedBoardsKey"; private static final String BOOKMARKED_BOARDS_KEY = "bookmarkedBoardsKey";
protected Bookmark thisPageBookmark; protected Bookmark thisPageBookmark;
protected ImageButton thisPageBookmarkButton; private MenuItem thisPageBookmarkMenuButton;
private ImageButton thisPageBookmarkImageButton;
private SharedPreferences bookmarksFile; private SharedPreferences bookmarksFile;
private ArrayList<Bookmark> topicsBookmarked; private ArrayList<Bookmark> topicsBookmarked;
private ArrayList<Bookmark> boardsBookmarked; private ArrayList<Bookmark> boardsBookmarked;
@ -79,6 +81,7 @@ public abstract class BaseActivity extends AppCompatActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (client == null) if (client == null)
client = BaseApplication.getInstance().getClient(); //must check every time - e.g. client = BaseApplication.getInstance().getClient(); //must check every time - e.g.
// they become null when app restarts after crash // they become null when app restarts after crash
if (sessionManager == null) if (sessionManager == null)
sessionManager = BaseApplication.getInstance().getSessionManager(); sessionManager = BaseApplication.getInstance().getSessionManager();
@ -344,7 +347,7 @@ public abstract class BaseActivity extends AppCompatActivity {
}); });
} }
protected void updateDrawer() { private void updateDrawer() {
if (drawer != null) { if (drawer != null) {
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
{ {
@ -373,7 +376,7 @@ public abstract class BaseActivity extends AppCompatActivity {
* Result toast will always display a success, because when user chooses logout all data are * Result toast will always display a success, because when user chooses logout all data are
* cleared regardless of the actual outcome * cleared regardless of the actual outcome
*/ */
protected class LogoutTask extends AsyncTask<Void, Void, Integer> { //Attempt logout private class LogoutTask extends AsyncTask<Void, Void, Integer> { //Attempt logout
ProgressDialog progressDialog; ProgressDialog progressDialog;
protected Integer doInBackground(Void... voids) { protected Integer doInBackground(Void... voids) {
@ -407,42 +410,42 @@ public abstract class BaseActivity extends AppCompatActivity {
return topicsBookmarked; return topicsBookmarked;
} }
protected void setTopicBookmark() { protected void setTopicBookmark(MenuItem thisPageBookmarkMenuButton) {
this.thisPageBookmarkMenuButton = thisPageBookmarkMenuButton;
if (thisPageBookmark.matchExists(topicsBookmarked)) { if (thisPageBookmark.matchExists(topicsBookmarked)) {
thisPageBookmarkButton.setImageDrawable(bookmarked); thisPageBookmarkMenuButton.setIcon(bookmarked);
} else { } else {
thisPageBookmarkButton.setImageDrawable(notBookmarked); thisPageBookmarkMenuButton.setIcon(notBookmarked);
} }
thisPageBookmarkButton.setOnClickListener(new View.OnClickListener() { }
@Override
public void onClick(View view) { protected void topicMenuBookmarkClick() {
if (thisPageBookmark.matchExists(topicsBookmarked)) { if (thisPageBookmark.matchExists(topicsBookmarked)) {
thisPageBookmarkButton.setImageDrawable(notBookmarked); thisPageBookmarkMenuButton.setIcon(notBookmarked);
toggleTopicToBookmarks(thisPageBookmark); toggleTopicToBookmarks(thisPageBookmark);
Toast.makeText(BaseActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show(); Toast.makeText(BaseActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show();
} else { } else {
thisPageBookmarkButton.setImageDrawable(bookmarked); thisPageBookmarkMenuButton.setIcon(bookmarked);
toggleTopicToBookmarks(thisPageBookmark); toggleTopicToBookmarks(thisPageBookmark);
Toast.makeText(BaseActivity.this, "Bookmark added", Toast.LENGTH_SHORT).show(); Toast.makeText(BaseActivity.this, "Bookmark added", Toast.LENGTH_SHORT).show();
} }
} }
});
}
protected void setBoardBookmark() { protected void setBoardBookmark(final ImageButton thisPageBookmarkImageButton) {
this.thisPageBookmarkImageButton = thisPageBookmarkImageButton;
if (thisPageBookmark.matchExists(boardsBookmarked)) { if (thisPageBookmark.matchExists(boardsBookmarked)) {
thisPageBookmarkButton.setImageDrawable(bookmarked); thisPageBookmarkImageButton.setImageDrawable(bookmarked);
} else { } else {
thisPageBookmarkButton.setImageDrawable(notBookmarked); thisPageBookmarkImageButton.setImageDrawable(notBookmarked);
} }
thisPageBookmarkButton.setOnClickListener(new View.OnClickListener() { thisPageBookmarkImageButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
if (thisPageBookmark.matchExists(boardsBookmarked)) { if (thisPageBookmark.matchExists(boardsBookmarked)) {
thisPageBookmarkButton.setImageDrawable(notBookmarked); thisPageBookmarkImageButton.setImageDrawable(notBookmarked);
Toast.makeText(BaseActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show(); Toast.makeText(BaseActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show();
} else { } else {
thisPageBookmarkButton.setImageDrawable(bookmarked); thisPageBookmarkImageButton.setImageDrawable(bookmarked);
Toast.makeText(BaseActivity.this, "Bookmark added", Toast.LENGTH_SHORT).show(); Toast.makeText(BaseActivity.this, "Bookmark added", Toast.LENGTH_SHORT).show();
} }
toggleBoardToBookmarks(thisPageBookmark); toggleBoardToBookmarks(thisPageBookmark);
@ -521,7 +524,7 @@ public abstract class BaseActivity extends AppCompatActivity {
} }
//Display popup gor user to grant permission //Display popup gor user to grant permission
public void requestPerms() { //Runtime permissions request for devices with API >= 23 private void requestPerms() { //Runtime permissions request for devices with API >= 23
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
String[] PERMISSIONS_STORAGE = { String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,
@ -556,7 +559,7 @@ public abstract class BaseActivity extends AppCompatActivity {
} }
//Uses temp file - called after permission grant //Uses temp file - called after permission grant
public void launchDownloadService() { private void launchDownloadService() {
if (checkPerms()) if (checkPerms())
DownloadService.startActionDownload(this, tempThmmyFile.getFileUrl().toString()); DownloadService.startActionDownload(this, tempThmmyFile.getFileUrl().toString());

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

@ -19,11 +19,20 @@ import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader;
import com.mikepenz.materialdrawer.util.DrawerImageLoader; import com.mikepenz.materialdrawer.util.DrawerImageLoader;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import gr.thmmy.mthmmy.BuildConfig;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.session.SessionManager; import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.CrashReportingTree;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
public class BaseApplication extends Application { public class BaseApplication extends Application {
@ -49,11 +58,33 @@ public class BaseApplication extends Application {
super.onCreate(); super.onCreate();
baseApplication = this; //init singleton baseApplication = this; //init singleton
if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
} else {
Timber.plant(new CrashReportingTree());
}
SharedPreferences sharedPrefs = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE); SharedPreferences sharedPrefs = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
SharedPrefsCookiePersistor sharedPrefsCookiePersistor = new SharedPrefsCookiePersistor(getApplicationContext()); SharedPrefsCookiePersistor sharedPrefsCookiePersistor = new SharedPrefsCookiePersistor(getApplicationContext());
PersistentCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), sharedPrefsCookiePersistor); PersistentCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), sharedPrefsCookiePersistor);
client = new OkHttpClient.Builder() client = new OkHttpClient.Builder()
.cookieJar(cookieJar) .cookieJar(cookieJar)
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl oldUrl = chain.request().url();
if (Objects.equals(chain.request().url().host(), "www.thmmy.gr")) {
if (!oldUrl.toString().contains("theme=4")) {
//Probably works but needs more testing:
HttpUrl newUrl = oldUrl.newBuilder().addQueryParameter("theme", "4").build();
request = request.newBuilder().url(newUrl).build();
}
}
return chain.proceed(request);
}
})
.connectTimeout(30, TimeUnit.SECONDS) .connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS)
@ -71,6 +102,7 @@ public class BaseApplication extends Application {
public void set(ImageView imageView, Uri uri, Drawable placeholder) { public void set(ImageView imageView, Uri uri, Drawable placeholder) {
Picasso.with(imageView.getContext()).load(uri).placeholder(placeholder).into(imageView); Picasso.with(imageView.getContext()).load(uri).placeholder(placeholder).into(imageView);
} }
@Override @Override
public void cancel(ImageView imageView) { public void cancel(ImageView imageView) {
Picasso.with(imageView.getContext()).cancelRequest(imageView); Picasso.with(imageView.getContext()).cancelRequest(imageView);

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

@ -5,8 +5,8 @@ import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import mthmmy.utils.Report;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import timber.log.Timber;
public abstract class BaseFragment extends Fragment { public abstract class BaseFragment extends Fragment {
protected static final String ARG_SECTION_NUMBER = "SectionNumber"; protected static final String ARG_SECTION_NUMBER = "SectionNumber";
@ -15,7 +15,7 @@ public abstract class BaseFragment extends Fragment {
protected FragmentInteractionListener fragmentInteractionListener; protected FragmentInteractionListener fragmentInteractionListener;
private String TAG; private String TAG;
protected int sectionNumber; private int sectionNumber;
protected static OkHttpClient client; protected static OkHttpClient client;
@Override @Override
@ -26,31 +26,31 @@ public abstract class BaseFragment extends Fragment {
if (client == null) if (client == null)
client = BaseApplication.getInstance().getClient(); //must check every time - e.g. client = BaseApplication.getInstance().getClient(); //must check every time - e.g.
// becomes null when app restarts after crash // becomes null when app restarts after crash
Report.d(TAG, "onCreate"); Timber.d("onCreate");
} }
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
Report.d(TAG, "onStart"); Timber.d("onStart");
} }
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
Report.d(TAG, "onResume"); Timber.d("onResume");
} }
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
Report.d(TAG, "onPause"); Timber.d("onPause");
} }
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
Report.d(TAG, "onStop"); Timber.d("onStop");
} }
@Override @Override
@ -76,5 +76,6 @@ public abstract class BaseFragment extends Fragment {
* the activity that contains it, to allow communication upon interaction, * the activity that contains it, to allow communication upon interaction,
* between the fragment and the activity/ other fragments * between the fragment and the activity/ other fragments
*/ */
public interface FragmentInteractionListener {} public interface FragmentInteractionListener {
}
} }

3
app/src/main/java/gr/thmmy/mthmmy/model/Category.java

@ -7,8 +7,7 @@ import java.util.List;
import static android.R.attr.id; import static android.R.attr.id;
public class Category implements Parent<Board> public class Category implements Parent<Board> {
{
private final String title; private final String title;
private final String categoryURL; private final String categoryURL;
private boolean expanded = false; private boolean expanded = false;

20
app/src/main/java/gr/thmmy/mthmmy/model/Post.java

@ -27,6 +27,7 @@ public class Post {
private final boolean isDeleted; private final boolean isDeleted;
private final int userColor; private final int userColor;
private final ArrayList<ThmmyFile> attachedFiles; private final ArrayList<ThmmyFile> attachedFiles;
private final String lastEdit;
//Extra info //Extra info
private final String profileURL; private final String profileURL;
@ -57,6 +58,7 @@ public class Post {
personalText = ""; personalText = "";
numberOfStars = 0; numberOfStars = 0;
attachedFiles = null; attachedFiles = null;
lastEdit = null;
} }
/** /**
@ -80,12 +82,13 @@ public class Post {
* @param numberOfStars author's number of stars * @param numberOfStars author's number of stars
* @param userColor author's user color * @param userColor author's user color
* @param attachedFiles post's attached files * @param attachedFiles post's attached files
* @param lastEdit post's last edit date
*/ */
public Post(@Nullable String thumbnailUrl, String author, String subject, String content public Post(@Nullable String thumbnailUrl, String author, String subject, String content
, int postIndex, int postNumber, String postDate, String profileURl, @Nullable String rank , int postIndex, int postNumber, String postDate, String profileURl, @Nullable String rank
, @Nullable String special_rank, @Nullable String gender, @Nullable String numberOfPosts , @Nullable String special_rank, @Nullable String gender, @Nullable String numberOfPosts
, @Nullable String personalText, int numberOfStars, int userColor , @Nullable String personalText, int numberOfStars, int userColor
, @Nullable ArrayList<ThmmyFile> attachedFiles) { , @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit) {
if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null; if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null;
else this.thumbnailUrl = thumbnailUrl; else this.thumbnailUrl = thumbnailUrl;
this.author = author; this.author = author;
@ -97,6 +100,7 @@ public class Post {
this.isDeleted = false; this.isDeleted = false;
this.userColor = userColor; this.userColor = userColor;
this.attachedFiles = attachedFiles; this.attachedFiles = attachedFiles;
this.lastEdit = lastEdit;
this.profileURL = profileURl; this.profileURL = profileURl;
this.rank = rank; this.rank = rank;
this.specialRank = special_rank; this.specialRank = special_rank;
@ -120,10 +124,11 @@ public class Post {
* @param postDate date of submission * @param postDate date of submission
* @param userColor author's user color * @param userColor author's user color
* @param attachedFiles post's attached files * @param attachedFiles post's attached files
* @param lastEdit post's last edit date
*/ */
public Post(@Nullable String thumbnailUrl, String author, String subject, String content public Post(@Nullable String thumbnailUrl, String author, String subject, String content
, int postIndex, int postNumber, String postDate, int userColor , int postIndex, int postNumber, String postDate, int userColor
, @Nullable ArrayList<ThmmyFile> attachedFiles) { , @Nullable ArrayList<ThmmyFile> attachedFiles, @Nullable String lastEdit) {
if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null; if (Objects.equals(thumbnailUrl, "")) this.thumbnailUrl = null;
else this.thumbnailUrl = thumbnailUrl; else this.thumbnailUrl = thumbnailUrl;
this.author = author; this.author = author;
@ -135,6 +140,7 @@ public class Post {
this.isDeleted = true; this.isDeleted = true;
this.userColor = userColor; this.userColor = userColor;
this.attachedFiles = attachedFiles; this.attachedFiles = attachedFiles;
this.lastEdit = lastEdit;
profileURL = null; profileURL = null;
rank = "Rank"; rank = "Rank";
specialRank = "Special rank"; specialRank = "Special rank";
@ -310,4 +316,14 @@ public class Post {
public ArrayList<ThmmyFile> getAttachedFiles() { public ArrayList<ThmmyFile> getAttachedFiles() {
return attachedFiles; return attachedFiles;
} }
/**
* Gets this post's last edit date or null if post hasn't been edited.
*
* @return date of last edit or null
*/
@Nullable
public String getLastEdit() {
return lastEdit;
}
} }

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

@ -4,7 +4,7 @@ import android.net.Uri;
import java.util.Objects; import java.util.Objects;
import mthmmy.utils.Report; import timber.log.Timber;
/** /**
* This class consists exclusively of static classes (enums) and methods (excluding methods of inner * This class consists exclusively of static classes (enums) and methods (excluding methods of inner
@ -162,7 +162,7 @@ public class ThmmyPage {
|| Objects.equals(uriString, "https://www.thmmy.gr") || Objects.equals(uriString, "https://www.thmmy.gr")
|| Objects.equals(uriString, "https://www.thmmy.gr/smf/index.php")) || Objects.equals(uriString, "https://www.thmmy.gr/smf/index.php"))
return PageCategory.INDEX; return PageCategory.INDEX;
Report.v(TAG, "Unknown thmmy link found, link: " + uriString); Timber.v("Unknown thmmy link found, link: " + uriString);
return PageCategory.UNKNOWN_THMMY; return PageCategory.UNKNOWN_THMMY;
} }
return PageCategory.NOT_THMMY; return PageCategory.NOT_THMMY;
@ -170,7 +170,10 @@ public class ThmmyPage {
public static String getBoardId(String boardUrl) { public static String getBoardId(String boardUrl) {
if (resolvePageCategory(Uri.parse(boardUrl)) == PageCategory.BOARD) { if (resolvePageCategory(Uri.parse(boardUrl)) == PageCategory.BOARD) {
return boardUrl.substring(boardUrl.indexOf("board=") + 6, boardUrl.lastIndexOf(".")); String returnString = boardUrl.substring(boardUrl.indexOf("board=") + 6);
if (returnString.contains("."))
returnString = boardUrl.substring(boardUrl.indexOf("board=") + 6, boardUrl.lastIndexOf("."));
return returnString;
} }
return null; return null;
} }

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

@ -14,7 +14,8 @@ import android.webkit.MimeTypeMap;
import java.io.File; import java.io.File;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import mthmmy.utils.Report;
import timber.log.Timber;
import static gr.thmmy.mthmmy.services.DownloadService.ACTION_DOWNLOAD; import static gr.thmmy.mthmmy.services.DownloadService.ACTION_DOWNLOAD;
import static gr.thmmy.mthmmy.services.DownloadService.COMPLETED; import static gr.thmmy.mthmmy.services.DownloadService.COMPLETED;
@ -28,7 +29,6 @@ import static gr.thmmy.mthmmy.services.DownloadService.SAVE_DIR;
import static gr.thmmy.mthmmy.services.DownloadService.STARTED; import static gr.thmmy.mthmmy.services.DownloadService.STARTED;
public class Receiver extends BroadcastReceiver { public class Receiver extends BroadcastReceiver {
private static final String TAG = "BroadcastReceiver";
public Receiver() { public Receiver() {
} }
@ -72,7 +72,7 @@ public class Receiver extends BroadcastReceiver {
builder.setContentIntent(pendingIntent); builder.setContentIntent(pendingIntent);
} else } else
Report.w(TAG, "File doesn't exist."); Timber.w("File doesn't exist.");
} }
Notification notification = builder.build(); Notification notification = builder.build();
notificationManager.notify(id, notification); notificationManager.notify(id, notification);

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

@ -13,12 +13,13 @@ import java.io.IOException;
import gr.thmmy.mthmmy.base.BaseApplication; import gr.thmmy.mthmmy.base.BaseApplication;
import gr.thmmy.mthmmy.receiver.Receiver; import gr.thmmy.mthmmy.receiver.Receiver;
import mthmmy.utils.Report;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import okio.BufferedSink; import okio.BufferedSink;
import okio.Okio; import okio.Okio;
import timber.log.Timber;
/** /**
* An {@link IntentService} subclass for handling asynchronous task requests in * An {@link IntentService} subclass for handling asynchronous task requests in
@ -47,7 +48,6 @@ public class DownloadService extends IntentService {
public static final String FAILED = "Failed"; public static final String FAILED = "Failed";
public DownloadService() { public DownloadService() {
super("DownloadService"); super("DownloadService");
} }
@ -108,34 +108,28 @@ public class DownloadService extends IntentService {
Response response = client.newCall(request).execute(); Response response = client.newCall(request).execute();
String contentType = response.headers("Content-Type").toString(); //check if link provides a binary file String contentType = response.headers("Content-Type").toString(); //check if link provides a binary file
if(contentType.equals("[application/octet-stream]")) if (contentType.equals("[application/octet-stream]")) {
{
fileName = response.headers("Content-Disposition").toString().split("\"")[1]; fileName = response.headers("Content-Disposition").toString().split("\"")[1];
File dirPath = new File(SAVE_DIR); File dirPath = new File(SAVE_DIR);
if(!dirPath.isDirectory()) if (!dirPath.isDirectory()) {
{
if (dirPath.mkdirs()) if (dirPath.mkdirs())
Report.i(TAG, "mTHMMY's directory created successfully!"); Timber.i("mTHMMY's directory created successfully!");
else else
Report.e(TAG, "Couldn't create mTHMMY's directory..."); Timber.e("Couldn't create mTHMMY's directory...");
} }
String nameFormat; String nameFormat;
String[] tokens = fileName.split("\\.(?=[^\\.]+$)"); String[] tokens = fileName.split("\\.(?=[^\\.]+$)");
if(tokens.length!=2) if (tokens.length != 2) {
{ Timber.w("Couldn't get file extension...");
Report.w(TAG, "Couldn't get file extension...");
nameFormat = fileName + "(%d)"; nameFormat = fileName + "(%d)";
} } else
else
nameFormat = tokens[0] + "(%d)." + tokens[1]; nameFormat = tokens[0] + "(%d)." + tokens[1];
File file = new File(dirPath, fileName); File file = new File(dirPath, fileName);
for (int i = 1; ; i++) { for (int i = 1; ; i++) {
@ -148,26 +142,23 @@ public class DownloadService extends IntentService {
fileName = file.getName(); fileName = file.getName();
Report.v(TAG, "Started saving file " + fileName); Timber.v("Started saving file " + fileName);
sendNotification(downloadId, STARTED, fileName); sendNotification(downloadId, STARTED, fileName);
sink = Okio.buffer(Okio.sink(file)); sink = Okio.buffer(Okio.sink(file));
sink.writeAll(response.body().source()); sink.writeAll(response.body().source());
sink.flush(); sink.flush();
Report.i(TAG, "Download OK!"); Timber.i("Download OK!");
sendNotification(downloadId, COMPLETED, fileName); sendNotification(downloadId, COMPLETED, fileName);
} } else
else Timber.e("Response not a binary file!");
Report.e(TAG, "Response not a binary file!"); } catch (FileNotFoundException e) {
} Timber.i("Download failed...");
catch (FileNotFoundException e){ Timber.e("FileNotFound", e);
Report.i(TAG, "Download failed...");
Report.e(TAG, "FileNotFound", e);
sendNotification(downloadId, FAILED, fileName); sendNotification(downloadId, FAILED, fileName);
} } catch (IOException e) {
catch (IOException e){ Timber.i("Download failed...");
Report.i(TAG, "Download failed..."); Timber.e("IOException", e);
Report.e(TAG, "IOException", e);
sendNotification(downloadId, FAILED, fileName); sendNotification(downloadId, FAILED, fileName);
} finally { } finally {
if (sink != null) { if (sink != null) {
@ -180,8 +171,7 @@ public class DownloadService extends IntentService {
} }
} }
private void sendNotification(int downloadId, String type, @NonNull String fileName) private void sendNotification(int downloadId, String type, @NonNull String fileName) {
{
Intent intent = new Intent(ACTION_DOWNLOAD); Intent intent = new Intent(ACTION_DOWNLOAD);
switch (type) { switch (type) {
case STARTED: { case STARTED: {
@ -203,7 +193,7 @@ public class DownloadService extends IntentService {
break; break;
} }
default: { default: {
Report.wtf(TAG, "Invalid notification case!"); Timber.e("Invalid notification case!");
return; return;
} }
} }

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

@ -18,7 +18,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import gr.thmmy.mthmmy.utils.exceptions.ParseException; import gr.thmmy.mthmmy.utils.exceptions.ParseException;
import mthmmy.utils.Report;
import okhttp3.Cookie; import okhttp3.Cookie;
import okhttp3.FormBody; import okhttp3.FormBody;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
@ -26,15 +25,13 @@ import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber;
/** /**
* This class handles all session related operations (e.g. login, logout) * This class handles all session related operations (e.g. login, logout)
* and stores data to SharedPreferences (session information and cookies). * and stores data to SharedPreferences (session information and cookies).
*/ */
public class SessionManager { public class SessionManager {
//Class TAG
private static final String TAG = "SessionManager";
//Generic constants //Generic constants
public static final HttpUrl indexUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?theme=4"); 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"); public static final HttpUrl forumUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=forum;theme=4");
@ -51,12 +48,12 @@ public class SessionManager {
public static final int EXCEPTION = 6; public static final int EXCEPTION = 6;
// Client & Cookies // Client & Cookies
private OkHttpClient client; private final OkHttpClient client;
private PersistentCookieJar cookieJar; private final PersistentCookieJar cookieJar;
private SharedPrefsCookiePersistor cookiePersistor; //Used to explicitly edit cookies in cookieJar private final SharedPrefsCookiePersistor cookiePersistor; //Used to explicitly edit cookies in cookieJar
//Shared Preferences & its keys //Shared Preferences & its keys
private SharedPreferences sharedPrefs; private final SharedPreferences sharedPrefs;
private static final String USERNAME = "Username"; private static final String USERNAME = "Username";
private static final String AVATAR_LINK = "AvatarLink"; private static final String AVATAR_LINK = "AvatarLink";
private static final String HAS_AVATAR = "HasAvatar"; private static final String HAS_AVATAR = "HasAvatar";
@ -80,7 +77,7 @@ public class SessionManager {
* Always call it in a separate thread. * Always call it in a separate thread.
*/ */
public int login(String... strings) { public int login(String... strings) {
Report.i(TAG, "Logging in..."); Timber.i("Logging in...");
//Build the login request for each case //Build the login request for each case
Request request; Request request;
@ -114,7 +111,7 @@ public class SessionManager {
if (unreadRepliesLinks.size() >= 2) //Normally it's just == 2, but who knows what can be posted by users if (unreadRepliesLinks.size() >= 2) //Normally it's just == 2, but who knows what can be posted by users
{ {
Report.i(TAG, "Login successful!"); Timber.i("Login successful!");
setPersistentCookieSession(); //Store cookies setPersistentCookieSession(); //Store cookies
//Edit SharedPreferences, save session's data //Edit SharedPreferences, save session's data
@ -133,18 +130,18 @@ public class SessionManager {
return SUCCESS; return SUCCESS;
} else { } else {
Report.i(TAG, "Login failed."); Timber.i("Login failed.");
//Investigate login failure //Investigate login failure
Elements error = document.select("b:contains(That username does not exist.)"); Elements error = document.select("b:contains(That username does not exist.)");
if (error.size() == 1) { //Wrong username if (error.size() == 1) { //Wrong username
Report.i(TAG, "Wrong Username"); Timber.i("Wrong Username");
return WRONG_USER; return WRONG_USER;
} }
error = document.select("body:contains(Password incorrect)"); error = document.select("body:contains(Password incorrect)");
if (error.size() == 1) { //Wrong password if (error.size() == 1) { //Wrong password
Report.i(TAG, "Wrong Password"); Timber.i("Wrong Password");
return WRONG_PASSWORD; return WRONG_PASSWORD;
} }
@ -154,13 +151,13 @@ public class SessionManager {
} }
//Handle exception //Handle exception
} catch (InterruptedIOException e) { } catch (InterruptedIOException e) {
Report.i(TAG, "Login InterruptedIOException"); //users cancels LoginTask Timber.i("Login InterruptedIOException"); //users cancels LoginTask
return CANCELLED; return CANCELLED;
} catch (IOException e) { } catch (IOException e) {
Report.w(TAG, "Login IOException", e); Timber.w("Login IOException", e);
return CONNECTION_ERROR; return CONNECTION_ERROR;
} catch (Exception e) { } catch (Exception e) {
Report.w(TAG, "Login Exception (other)", e); Timber.w("Login Exception (other)", e);
return EXCEPTION; return EXCEPTION;
} }
} }
@ -175,7 +172,7 @@ public class SessionManager {
* fragments' data are retrieved). * fragments' data are retrieved).
*/ */
public void validateSession() { public void validateSession() {
Report.i(TAG, "Validating session..."); Timber.i("Validating session...");
if (isLoggedIn()) { if (isLoggedIn()) {
int loginResult = login(); int loginResult = login();
@ -192,7 +189,7 @@ public class SessionManager {
* Call this function when user explicitly chooses to continue as a guest (UI thread). * Call this function when user explicitly chooses to continue as a guest (UI thread).
*/ */
public void guestLogin() { public void guestLogin() {
Report.i("TAG", "Continuing as a guest, as chosen by the user."); Timber.i("Continuing as a guest, as chosen by the user.");
clearSessionData(); clearSessionData();
sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, false).apply(); sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, false).apply();
} }
@ -202,7 +199,7 @@ public class SessionManager {
* Logout function. Always call it in a separate thread. * Logout function. Always call it in a separate thread.
*/ */
public int logout() { public int logout() {
Report.i(TAG, "Logging out..."); Timber.i("Logging out...");
Request request = new Request.Builder() Request request = new Request.Builder()
.url(sharedPrefs.getString(LOGOUT_LINK, "LogoutLink")) .url(sharedPrefs.getString(LOGOUT_LINK, "LogoutLink"))
@ -216,17 +213,17 @@ public class SessionManager {
Elements loginButton = document.select("[value=Login]"); //Attempt to find login button Elements loginButton = document.select("[value=Login]"); //Attempt to find login button
if (!loginButton.isEmpty()) //If login button exists, logout was successful if (!loginButton.isEmpty()) //If login button exists, logout was successful
{ {
Report.i(TAG, "Logout successful!"); Timber.i("Logout successful!");
return SUCCESS; return SUCCESS;
} else { } else {
Report.i(TAG, "Logout failed."); Timber.i("Logout failed.");
return FAILURE; return FAILURE;
} }
} catch (IOException e) { } catch (IOException e) {
Report.w(TAG, "Logout IOException", e); Timber.w("Logout IOException", e);
return CONNECTION_ERROR; return CONNECTION_ERROR;
} catch (Exception e) { } catch (Exception e) {
Report.w(TAG, "Logout Exception", e); Timber.w("Logout Exception", e);
return EXCEPTION; return EXCEPTION;
} finally { } finally {
//All data should always be cleared from device regardless the result of logout //All data should always be cleared from device regardless the result of logout
@ -288,7 +285,7 @@ public class SessionManager {
sharedPrefs.edit().clear().apply(); //Clear session data sharedPrefs.edit().clear().apply(); //Clear session data
sharedPrefs.edit().putString(USERNAME, guestName).apply(); sharedPrefs.edit().putString(USERNAME, guestName).apply();
sharedPrefs.edit().putBoolean(LOGGED_IN, false).apply(); //User logs out sharedPrefs.edit().putBoolean(LOGGED_IN, false).apply(); //User logs out
Report.i(TAG, "Session data cleared."); Timber.i("Session data cleared.");
} }
@NonNull @NonNull
@ -304,8 +301,7 @@ public class SessionManager {
Matcher matcher = pattern.matcher(txt); Matcher matcher = pattern.matcher(txt);
if (matcher.find()) if (matcher.find())
userName = matcher.group(1); userName = matcher.group(1);
} } else {
else {
//Helios_Multi and SMF_oneBlue //Helios_Multi and SMF_oneBlue
user = doc.select("td.smalltext[width=100%] b"); user = doc.select("td.smalltext[width=100%] b");
if (user.size() == 1) if (user.size() == 1)
@ -321,7 +317,7 @@ public class SessionManager {
if (userName != null && !userName.isEmpty()) if (userName != null && !userName.isEmpty())
return userName; return userName;
Report.e(TAG, "ParseException", new ParseException("Parsing failed(username extraction)")); Timber.e("ParseException", new ParseException("Parsing failed(username extraction)"));
return "User"; //return a default username return "User"; //return a default username
} }
@ -332,7 +328,7 @@ public class SessionManager {
if (!avatar.isEmpty()) if (!avatar.isEmpty())
return avatar.first().attr("src"); return avatar.first().attr("src");
Report.e(TAG, "Extracting avatar's link failed!"); Timber.i("Extracting avatar's link failed!");
return null; return null;
} }
@ -340,13 +336,12 @@ public class SessionManager {
private String extractLogoutLink(@NonNull Document doc) { private String extractLogoutLink(@NonNull Document doc) {
Elements logoutLink = doc.select("a[href^=https://www.thmmy.gr/smf/index.php?action=logout;sesc=]"); Elements logoutLink = doc.select("a[href^=https://www.thmmy.gr/smf/index.php?action=logout;sesc=]");
if (!logoutLink.isEmpty()) if (!logoutLink.isEmpty()) {
{
String link = logoutLink.first().attr("href"); String link = logoutLink.first().attr("href");
if (link != null && !link.isEmpty()) if (link != null && !link.isEmpty())
return link; return link;
} }
Report.e(TAG, "ParseException", new ParseException("Parsing failed(logoutLink extraction)")); Timber.e("ParseException", new ParseException("Parsing failed(logoutLink extraction)"));
return "https://www.thmmy.gr/smf/index.php?action=logout"; //return a default link return "https://www.thmmy.gr/smf/index.php?action=logout"; //return a default link
} }
//----------------------------------OTHER FUNCTIONS END----------------------------------------- //----------------------------------OTHER FUNCTIONS END-----------------------------------------

25
app/src/main/java/gr/thmmy/mthmmy/utils/CenterVerticalSpan.java

@ -1,20 +1,23 @@
package gr.thmmy.mthmmy.utils; package gr.thmmy.mthmmy.utils;
import android.text.TextPaint; import android.graphics.Canvas;
import android.text.style.SuperscriptSpan; import android.graphics.Paint;
import android.util.Log; import android.graphics.Rect;
import android.support.annotation.NonNull;
public class CenterVerticalSpan extends SuperscriptSpan { import android.text.style.ReplacementSpan;
public CenterVerticalSpan() {
}
public class CenterVerticalSpan extends ReplacementSpan {
@Override @Override
public void updateDrawState(TextPaint textPaint) { public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
textPaint.baselineShift -= 7f; text = text.subSequence(start, end);
return (int) paint.measureText(text.toString());
} }
@Override @Override
public void updateMeasureState(TextPaint tp) { public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom,@NonNull Paint paint) {
updateDrawState(tp); text = text.subSequence(start, end);
Rect charSize = new Rect();
paint.getTextBounds(text.toString(), 0, 1, charSize);
canvas.drawText(text.toString(), x, (bottom + charSize.height()) / 2f, paint);
} }
} }

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

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

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

@ -153,15 +153,16 @@ public class ParseHelpers {
fixed = fixed.replace( fixed = fixed.replace(
fixed.substring(fixed.indexOf("<embed"), fixed.indexOf("/noembed>") + 9) fixed.substring(fixed.indexOf("<embed"), fixed.indexOf("/noembed>") + 9)
, "<div class=\"yt\">" , "<div class=\"yt\">"
+ "<a href=\"https://www.youtube.com/" + "<a href=\"https://www.youtube.com/watch?v="
+ embededVideosUrls.get(tmp_counter) + "\" target=\"_blank\">" + embededVideosUrls.get(tmp_counter) + "\" target=\"_blank\">"
+ "<img class=\"embedded-video-play\" " + "<img class=\"embedded-video-play\" "
+ "src=\"http://www.youtube.com/yt/brand/media/image/YouTube_light_color_icon.png\"" + "src=\"http://www.youtube.com/yt/brand/media/image/YouTube_light_color_icon.png\">"
+ "</a>" + "</a>"
+ "<img src=\"https://img.youtube.com/vi/" + "<img src=\"https://img.youtube.com/vi/"
+ embededVideosUrls.get(tmp_counter) + embededVideosUrls.get(tmp_counter)
+ "/default.jpg\" alt=\"\" border=\"0\" width=\"40%\">" + "/default.jpg\" alt=\"\" border=\"0\" width=\"40%\">"
+ "</div>"); + "</div>");
++tmp_counter;
} }
return fixed; return fixed;
} }

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

@ -5,10 +5,10 @@ package gr.thmmy.mthmmy.utils.exceptions;
* exception is set, to report to FireBase. * exception is set, to report to FireBase.
*/ */
public class UnknownException extends Exception { public class UnknownException extends Exception {
public UnknownException() {} public UnknownException() {
}
public UnknownException(String message) public UnknownException(String message) {
{
super(message); super(message);
} }
} }

BIN
app/src/main/res/drawable-hdpi/ic_info.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

BIN
app/src/main/res/drawable-hdpi/ic_send.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

BIN
app/src/main/res/drawable-mdpi/ic_info.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

BIN
app/src/main/res/drawable-mdpi/ic_send.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

BIN
app/src/main/res/drawable-xhdpi/ic_info.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

BIN
app/src/main/res/drawable-xhdpi/ic_send.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

BIN
app/src/main/res/drawable-xxhdpi/ic_info.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
app/src/main/res/drawable-xxhdpi/ic_send.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

BIN
app/src/main/res/drawable-xxxhdpi/ic_info.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
app/src/main/res/drawable-xxxhdpi/ic_send.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

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

@ -23,15 +23,19 @@
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
app:popupTheme="@style/ToolbarTheme"> app:popupTheme="@style/ToolbarTheme">
<ImageButton <TextView
android:id="@+id/bookmark" android:id="@+id/toolbar_title"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|end" android:ellipsize="marquee"
android:layout_marginEnd="4dp" android:fillViewport="true"
android:background="@null" android:focusable="true"
android:contentDescription="@string/bookmark" android:focusableInTouchMode="true"
android:src="@drawable/ic_bookmark_false"/> android:marqueeRepeatLimit="marquee_forever"
android:maxLines="1"
android:scrollHorizontally="true"
android:textColor="@color/white"
/>
</android.support.v7.widget.Toolbar> </android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout> </android.support.design.widget.AppBarLayout>

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

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -114,8 +115,7 @@
android:layout_toEndOf="@+id/thumbnail_holder" android:layout_toEndOf="@+id/thumbnail_holder"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:text="@string/post_subject" android:text="@string/post_subject"/>
/>
</RelativeLayout> </RelativeLayout>
<ImageButton <ImageButton

118
app/src/main/res/layout/activity_topic_quick_reply_row.xml

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
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:layout_marginBottom="5dp"
android:paddingEnd="4dp"
android:paddingStart="4dp">
<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"
android:layout_gravity="center"
android:foreground="?android:attr/selectableItemBackground"
card_view:cardBackgroundColor="@color/card_background"
card_view:cardCornerRadius="5dp"
card_view:cardElevation="2dp"
card_view:cardPreventCornerOverlap="false"
card_view:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/header"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:clickable="true"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp">
<FrameLayout
android:id="@+id/thumbnail_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerVertical="true"
android:layout_marginEnd="16dp">
<ImageView
android:id="@+id/thumbnail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:contentDescription="@string/post_thumbnail"
android:maxHeight="@dimen/thumbnail_size"
android:maxWidth="@dimen/thumbnail_size"
android:src="@drawable/ic_default_user_thumbnail"/>
</FrameLayout>
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toEndOf="@+id/thumbnail_holder"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/post_author"
android:textColor="@color/primary_text"
android:textStyle="bold"/>
<EditText
android:id="@+id/quick_reply_subject"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/username"
android:layout_toEndOf="@+id/thumbnail_holder"
android:hint="@string/quick_reply_subject"
android:inputType="textMultiLine"
android:maxLength="80"
android:textSize="10sp"
tools:ignore="SmallSp"/>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<android.support.design.widget.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<EditText
android:id="@+id/quick_reply_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/quick_reply"
android:inputType="textMultiLine"/>
</android.support.design.widget.TextInputLayout>
<android.support.v7.widget.AppCompatImageButton
android:id="@+id/quick_reply_submit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginBottom="5dp"
android:layout_marginEnd="5dp"
android:background="@color/card_background"
android:contentDescription="@string/quick_reply_submit"
android:src="@drawable/ic_send"/>
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
</FrameLayout>

33
app/src/main/res/layout/dialog_topic_info.xml

@ -0,0 +1,33 @@
<?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:paddingEnd="24dp"
android:paddingStart="24dp">
<TextView
android:id="@+id/dialog_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:layout_marginTop="24dp"
android:textColor="@color/white"
android:textSize="20sp"/>
<TextView
android:id="@+id/topic_tree_and_mods"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:textColor="@color/white"
android:textColorLink="@color/link_color"/>
<TextView
android:id="@+id/users_viewing"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:textColor="@color/white"
android:textColorLink="@color/link_color"/>
</LinearLayout>

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

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_bookmark"
android:icon="@drawable/ic_bookmark_false"
app:showAsAction="ifRoom"
android:title="Bookmark">
</item>
<item
android:id="@+id/menu_info"
android:icon="@drawable/ic_info"
app:showAsAction="ifRoom|withText"
android:title="Info">
</item>
</menu>

2
app/src/main/res/values/colors.xml

@ -16,6 +16,8 @@
<color name="background">#323232</color> <color name="background">#323232</color>
<color name="card_background">#3C3F41</color> <color name="card_background">#3C3F41</color>
<color name="divider">#8B8B8B</color> <color name="divider">#8B8B8B</color>
<!--<color name="link_color">#FFC107</color>-->
<color name="link_color">#FF9800</color>
<color name="white">#FFFFFF</color> <color name="white">#FFFFFF</color>
<color name="iron">#CCCCCC</color> <color name="iron">#CCCCCC</color>

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

@ -44,6 +44,9 @@
<string name="button_page">Page</string> <string name="button_page">Page</string>
<string name="button_next">next</string> <string name="button_next">next</string>
<string name="button_last">last</string> <string name="button_last">last</string>
<string name="quick_reply">Quick reply&#8230;</string>
<string name="quick_reply_subject">Subject&#8230;</string>
<string name="quick_reply_submit">Submit</string>
<!--Profile Activity--> <!--Profile Activity-->
<string name="username">Username</string> <string name="username">Username</string>

96
app/src/release/java/mthmmy.utils/Report.java

@ -1,96 +0,0 @@
package mthmmy.utils;
import com.google.firebase.crash.FirebaseCrash;
import gr.thmmy.mthmmy.utils.exceptions.UnknownException;
public class Report
{
public static void v (String TAG, String message)
{
log("V", TAG, message);
}
public static void v (String TAG, String message, Throwable tr)
{
exception("V", TAG, message, tr);
}
public static void d (String TAG, String message)
{
log("D", TAG, message);
}
public static void d (String TAG, String message, Throwable tr)
{
exception("D", TAG, message, tr);
}
public static void i (String TAG, String message)
{
log("I", TAG, message);
}
public static void i (String TAG, String message, Throwable tr)
{
exception("I", TAG, message, tr);
}
public static void w (String TAG, String message)
{
log("W", TAG, message);
}
public static void w (String TAG, String message, Throwable tr)
{
exception("W", TAG, message, tr);
}
public static void e (String TAG, String message)
{
log("E", TAG, message);
}
public static void e (String TAG, String message, Throwable tr)
{
exception("E", TAG, message, tr);
}
public static void wtf (String TAG, String message)
{
log("WTF", TAG, message);
}
public static void wtf (String TAG, String message, Throwable tr)
{
exception("WTF", TAG, message, tr);
}
private static void log(String level, String TAG, String message)
{
if(!level.equals("V")&&!level.equals("D")) //don't log V and D levels
{
FirebaseCrash.log(level + "/" + TAG + ": " + message);
if(level.equals("E")||level.equals("WTF")) //report only serious exceptions
FirebaseCrash.report(new UnknownException("UnknownException"));
}
}
private static void exception(String level, String TAG, String message, Throwable tr)
{
if(!level.equals("V")&&!level.equals("D")) //don't log V and D levels
{
FirebaseCrash.log(level + "/" + TAG + ": " + message + ": " + tr.getMessage());
if(level.equals("E")||level.equals("WTF")) //report only serious exceptions
FirebaseCrash.report(tr);
}
}
/**
* Does nothing in release.
*/
public static void longMessage(String TAG, String level, String message) {return;}
}

2
build.gradle

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

31
doc/forum_post.txt

@ -1,34 +1,35 @@
[center][size=25pt][b]Introduction[/b][/size][/center] [center][size=25pt][b]Introduction[/b][/size][/center]
Από τη συζήτηση [url=https://www.thmmy.gr/smf/index.php?topic=67629.0]εδώ[/url], ξεκίνησε ένα project με στόχο τη δημιουργία εφαρμογής για Android κινητά που θα συγκεντρώνει και θα κάνει πιο εύκολη τη πρόσβαση σε μερικές από τις βασικές σελίδες και υπηρεσίες που αφορούν τη σχολή και χρησιμοποιούμε καθημερινά. Από τη συζήτηση [url=https://www.thmmy.gr/smf/index.php?topic=67629.0]εδώ[/url], ξεκίνησε ένα project με στόχο τη δημιουργία εφαρμογής για Android κινητά που θα συγκεντρώνει και θα κάνει πιο εύκολη τη πρόσβαση σε μερικές από τις βασικές σελίδες και υπηρεσίες που αφορούν τη σχολή και χρησιμοποιούμε καθημερινά.
Μετά από 2+ μήνες δουλειάς και πάνω από 9000 γραμμές κώδικα [size=9pt](.java, .xml και άλλα)[/size], σήμερα ανεβάσαμε για πρώτη φορά την εφαρμογή (σε closed alpha phase) στο Google Play Store! Μετά από 2+ μήνες δουλειάς και πάνω από 9000 γραμμές κώδικα (.java, .xml και άλλα), σήμερα (16/1/2017) ανεβάσαμε για πρώτη φορά την εφαρμογή (σε closed alpha phase) στο Google Play Store!
Προς το παρόν η εφαρμογή υποστηρίζει κάποιες από τις βασικές λειτουργίες του forum. Σταδιακά θα ενσωματώνονται όλο και περισσότερες λειτουργίες, για παράδειγμα ενός συστήματος ειδοποιήσεων για νέες ανακοινώσεις του ethmmy και της σελίδας της γραμματείας, instant chat κ.ά., ανάλογα πάντα με τις ιδέες, τη διάθεση και την ενέργεια όσων θα συμμετέχουν. Προς το παρόν η εφαρμογή υποστηρίζει κάποιες από τις βασικές λειτουργίες του forum. Σταδιακά θα ενσωματώνονται όλο και περισσότερες λειτουργίες, για παράδειγμα ενός συστήματος ειδοποιήσεων για νέες ανακοινώσεις του ethmmy και της σελίδας της γραμματείας, instant chat κ.ά., ανάλογα πάντα με τις ιδέες, τη διάθεση και την ενέργεια όσων θα συμμετέχουν.
Αυτή τη στιγμή με το project ασχολούμαστε εγώ και ο L, ενώ έχουν βοηθήσει ο iason1907 και ο [url=https://www.thmmy.gr/smf/index.php?topic=67565.msg1163192#msg1163192]nohponex[/url]. Αυτή τη στιγμή με το project ασχολούμαστε εγώ και ο L, ενώ έχουν βοηθήσει ο iason1907 και ο [url=https://www.thmmy.gr/smf/index.php?topic=67565.msg1163192#msg1163192]nohponex[/url].
[hr] [hr]
[center][size=25pt][b]Κάλεσμα για contributors[/b][/size] [center][size=25pt][b]Κάλεσμα για contributors[/b][/size]
[img height=400]https://tctechcrunch2011.files.wordpress.com/2015/04/uncle-sam-we-want-you1-kopie_1.png[/img][/center] [img height=200]https://tctechcrunch2011.files.wordpress.com/2015/04/uncle-sam-we-want-you1-kopie_1.png[/img][/center]
Αν ενδιαφέρεσαι κι ΕΣΥ να ασχοληθείς με το project μπορείς να το κάνεις με πολλούς τρόπους: Αν ενδιαφέρεσαι κι [b]ΕΣΥ[/b] να ασχοληθείς με το project μπορείς να το κάνεις με πολλούς τρόπους:
[list] [list]
[li]Αν ξέρεις προγραμματισμό μπορείς αρχικά να ζητήσεις πρόσβαση στο repository. Χρειάζονται [i]άμεσα[/i] νέοι developers για υλοποιήση καινούργιων χαρακτηριστικών, διόρθωση εντόμων, σύνταξη των javadocs και του documentation γενικότερα, white-box testing, υλοποίηση του backend στο server που στήθηκε πρόσφατα και πολλών άλλων. Αυτό, αρχικά, θα γίνεται με forks και merge requests - και πάντα στο ρυθμό που μπορείς να συνεισφέρεις.
[/li]
[li] [li]
Να αναφέρεις bugs, να προτείνεις βελτιώσεις και να συμμετέχεις σε συζητήσεις, είτε στον [url=https://discord.gg/PVRVjth]Discord server[/url], είτε αποκτώντας πρόσβαση στον Issue Tracker μας. Να αναφέρεις bugs, να προτείνεις βελτιώσεις και να συμμετέχεις σε συζητήσεις στον [url=https://discord.gg/PVRVjth]Discord server[/url] μας και στον Issue Tracker στο [url=trello.com]Trello[/url] (το link του είναι pinned στο #feedback στο Discord).
[/li] [/li]
[li] [li]
Να κατεβάσεις και να δοκιμάσεις την alpha έκδοση της εφαρμογής από [url=https://play.google.com/apps/testing/gr.thmmy.mthmmy]εδώ[/url] αφού πρώτα μας δώσεις ένα gmail για να αποκτήσεις πρόσβαση. Να κατεβάσεις και να δοκιμάσεις την alpha έκδοση της εφαρμογής από [url=https://play.google.com/apps/testing/gr.thmmy.mthmmy]εδώ[/url], [b]αφού [/b]πρώτα μας στείλεις το Gmail που έχεις στο Google Play για να αποκτήσεις πρόσβαση.
[/li] [/li]
[li] [li]
Να έρθεις σε άμεση επικοινωνία με την ομάδα μέσω του Discord ή στέλνοντας email στο thmmynolife@gmail.com. Να έρθεις σε άμεση επικοινωνία με την ομάδα μέσω του [url=https://discord.gg/PVRVjth]Discord[/url] ή στέλνοντας email στο thmmynolife@gmail.com.
[/li]
[li]Αν ξέρεις προγραμματισμό μπορείς αρχικά να ζητήσεις πρόσβαση στο repository και να συνεισφέρεις κώδικα με fork και merge requests στους ρυθμούς σου. Χρειάζονται [i]άμεσα[/i] νέοι developers για υλοποιήση καινούργιων χαρακτηριστικών, διόρθωση εντόμων, σύνταξη των javadocs και του documentation γενικότερα, white-box testing, υλοποίηση του backend στο server που στήθηκε πρόσφατα και πολλών άλλων.
[/li][/list] [/li][/list]
[hr] [hr]
[center][size=25pt][b]Η εφαρμογή[/b][/size][/center] [center][size=25pt][b]Η εφαρμογή[/b][/size][/center]
[center][url=https://s6.postimg.org/4mupem3jl/image.png][img width=200]https://s6.postimg.org/4mupem3jl/image.png[/img][/url] [url=https://s6.postimg.org/ovdhmldgx/image.png][img width=200]https://s6.postimg.org/ovdhmldgx/image.png[/img][/url] [url=https://s6.postimg.org/a9mgycgoh/image.png][img width=200]https://s6.postimg.org/a9mgycgoh/image.png[/img][/url] [url=https://s6.postimg.org/5jwj9qpo1/image.png][img width=200]https://s6.postimg.org/5jwj9qpo1/image.png[/img][/url] [url=https://s6.postimg.org/fjrfpn0xd/image.png][img width=200]https://s6.postimg.org/fjrfpn0xd/image.png[/img][/url] [url=https://s6.postimg.org/z0c5c5w1d/image.png][img width=200]https://s6.postimg.org/z0c5c5w1d/image.png[/img][/url][/center] [center][url=https://s6.postimg.org/v9mseb7n5/image.png][img width=200]https://s6.postimg.org/v9mseb7n5/image.png[/img][/url] [url=https://s6.postimg.org/3nk0tmoa9/image2.png][img width=200]https://s6.postimg.org/3nk0tmoa9/image2.png[/img][/url] [url=https://s6.postimg.org/i813ogj8x/image3.png][img width=200]https://s6.postimg.org/i813ogj8x/image3.png[/img][/url] [url=https://s6.postimg.org/4to0sfckx/image4.png][img width=200]https://s6.postimg.org/4to0sfckx/image4.png[/img][/url] [url=https://s6.postimg.org/69zjakfht/image5.png][img width=200]https://s6.postimg.org/69zjakfht/image5.png[/img][/url] [url=https://s6.postimg.org/rkx3etxm9/image6.png][img width=200]https://s6.postimg.org/rkx3etxm9/image6.png[/img][/url][/center]
Αυτή τη στιγμή στην εφαρμογή μπορείς να κάνεις login και logout, να δεις τα "Πρόσφατα", να περιηγηθείς στα boards, topics και user profiles και να κατεβάσεις αρχεία συνημμένα σε post. Αυτή τη στιγμή στην εφαρμογή μπορείς να κάνεις login και logout, να δεις τα "Πρόσφατα", να περιηγηθείς στα boards, topics και user profiles, να κατεβάσεις αρχεία από τα downloads και από συνημμένα σε post.

4
gradle/wrapper/gradle-wrapper.properties

@ -1,6 +1,6 @@
#Mon Dec 28 10:00:20 PST 2015 #Wed Mar 08 11:25:21 EET 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip

Loading…
Cancel
Save