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. 6
      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. 803
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
  28. 41
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
  29. 59
      app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java
  30. 34
      app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java
  31. 19
      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. 64
      app/src/main/java/gr/thmmy/mthmmy/services/DownloadService.java
  37. 75
      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. 20
      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:
ANDROID_TARGET_SDK: "25"
ANDROID_BUILD_TOOLS: "25.0.1"
ANDROID_BUILD_TOOLS: "25.0.2"
ANDROID_SDK_TOOLS: "25.2.5"
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
[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)
[![API](https://img.shields.io/badge/API-19%2B-blue.svg?style=flat)](https://android-arsenal.com/api?level=19)
[![Discord Channel](https://img.shields.io/badge/discord-public@mTHMMY-738bd7.svg?style=flat)](https://discord.gg/CVt3yrn)
[![Discord Channel](https://img.shields.io/badge/discord-public@mTHMMY-738bd7.svg?style=flat)][discord-server]
[![Trello Board](https://img.shields.io/badge/trello-mTHMMY-red.svg?style=flat)][trello-board]
mTHMMY is a mobile app for thmmy.gr
mTHMMY is a mobile app for the [thmmy.gr](https://www.thmmy.gr) community.
## Requirements
@ -16,4 +18,7 @@ Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
## Contact
Do not hesitate to contact us for any matter at `thmmynolife@gmail.com`.
Do not hesitate to contact us for any matter, either by sending an email to `thmmynolife@gmail.com`, or by joining our [Discord server][discord-server].
[discord-server]: https://discord.gg/CVt3yrn
[trello-board]: https://trello.com/invite/b/4MVlkrkg/44a931707bd0b84a5e0bdfc42b9ae4f1/mthmmy

2
VERSION

@ -1 +1 @@
1.1.2
1.2.0

31
app/build.gradle

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

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"?>
<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.ACCESS_NETWORK_STATE"/>

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

@ -39,7 +39,7 @@
<body>
<ul>
<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>
<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>
</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>
<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>
</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>
<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);
Bundle extras = new Bundle();
extras.putString(BUNDLE_TOPIC_URL, "https://www.thmmy.gr/smf/index.php?topic="
+ bookmarkedTopic.getId() + ".0");
+ bookmarkedTopic.getId() + "." + 2147483647);
extras.putString(BUNDLE_TOPIC_TITLE, bookmarkedTopic.getTitle());
intent.putExtras(extras);
startActivity(intent);

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

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

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

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

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

@ -36,7 +36,7 @@ class ForumAdapter extends ExpandableRecyclerAdapter<Category, Board, ForumAdapt
super(categories);
this.context = context;
this.categories = categories;
mListener = (ForumFragment.ForumFragmentInteractionListener)listener;
mListener = (ForumFragment.ForumFragmentInteractionListener) listener;
layoutInflater = LayoutInflater.from(context);
}
@ -68,7 +68,7 @@ class ForumAdapter extends ExpandableRecyclerAdapter<Category, Board, ForumAdapt
class CategoryViewHolder extends ParentViewHolder {
private TextView categoryTextview;
private final TextView categoryTextview;
CategoryViewHolder(View itemView) {
super(itemView);
@ -83,7 +83,7 @@ class ForumAdapter extends ExpandableRecyclerAdapter<Category, Board, ForumAdapt
class BoardViewHolder extends ChildViewHolder {
private TextView boardTextView;
private final TextView boardTextView;
public Board board;
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.session.SessionManager;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import mthmmy.utils.Report;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
/**
* A {@link BaseFragment} subclass.
@ -87,7 +88,7 @@ public class ForumFragment extends BaseFragment
forumTask.execute();
}
Report.d(TAG, "onActivityCreated");
Timber.d("onActivityCreated");
}
@Override
@ -151,8 +152,7 @@ public class ForumFragment extends BaseFragment
//---------------------------------------ASYNC TASK-----------------------------------
public class ForumTask extends AsyncTask<Void, Void, Integer> {
private static final String TAG = "ForumTask";
private class ForumTask extends AsyncTask<Void, Void, Integer> {
private HttpUrl forumUrl = SessionManager.forumUrl; //may change upon collapse/expand
private Document document;
@ -179,10 +179,10 @@ public class ForumFragment extends BaseFragment
fetchedCategories.clear();
return 0;
} catch (IOException e) {
Report.d(TAG, "Network Error", e);
Timber.d("Network Error", e);
return 1;
} catch (Exception e) {
Report.d(TAG, "Exception", e);
Timber.d("Exception", e);
return 2;
}
@ -225,7 +225,7 @@ public class ForumFragment extends BaseFragment
}
}
else
Report.e(TAG, "Parsing failed!");
Timber.e("Parsing failed!");
}
public void setUrl(String string)

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

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

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 java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.SSLHandshakeException;
@ -40,16 +41,12 @@ import javax.net.ssl.SSLHandshakeException;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.base.BaseActivity;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import mthmmy.utils.Report;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
public class StatsFragment extends Fragment {
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "StatsFragment";
/**
* The key to use when putting profile's url String to {@link StatsFragment}'s Bundle.
*/
@ -108,7 +105,7 @@ public class StatsFragment extends Fragment {
profileStatsTask = new ProfileStatsTask();
profileStatsTask.execute(profileUrl + ";sa=statPanel");
}
Report.d(TAG, "onActivityCreated");
Timber.d("onActivityCreated");
}
@Override
@ -126,14 +123,7 @@ public class StatsFragment extends Fragment {
* <p>Calling SummaryTask's {@link AsyncTask#execute execute} method needs to have profile's url
* as String parameter!</p>
*/
public 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
private class ProfileStatsTask extends AsyncTask<String, Void, Boolean> {
@Override
protected void onPreExecute() {
progressBar.setVisibility(ProgressBar.VISIBLE);
@ -149,9 +139,9 @@ public class StatsFragment extends Fragment {
Response response = BaseActivity.getClient().newCall(request).execute();
return parseStats(Jsoup.parse(response.body().string()));
} catch (SSLHandshakeException e) {
Report.w(TAG, "Certificate problem (please switch to unsafe connection).");
Timber.w("Certificate problem (please switch to unsafe connection).");
} catch (Exception e) {
Report.e("TAG", "ERROR", e);
Timber.e("ERROR", e);
}
return false;
}
@ -159,7 +149,7 @@ public class StatsFragment extends Fragment {
@Override
protected void onPostExecute(Boolean result) {
if (!result) { //Parse failed!
Report.d(TAG, "Parse failed!");
Timber.d("Parse failed!");
Toast.makeText(getContext()
, "Fatal error!\n Aborting...", Toast.LENGTH_LONG).show();
getActivity().finish();
@ -206,6 +196,7 @@ public class StatsFragment extends Fragment {
Integer.parseInt(dataCols.last().text())));
mostPopularBoardsByPostsLabels.add(dataCols.first().text());
}
Collections.reverse(mostPopularBoardsByPostsLabels);
}
{
Elements mostPopularBoardsByActivityRows = statsRows.last().select(">td").last()
@ -218,6 +209,7 @@ public class StatsFragment extends Fragment {
Float.parseFloat(tmp.substring(0, tmp.indexOf("%")))));
mostPopularBoardsByActivityLabels.add(dataCols.first().text());
}
Collections.reverse(mostPopularBoardsByActivityLabels);
}
}
return true;
@ -345,7 +337,7 @@ public class StatsFragment extends Fragment {
mostPopularBoardsByActivityChart.invalidate();
}
class MyXAxisValueFormatter implements IAxisValueFormatter {
private class MyXAxisValueFormatter implements IAxisValueFormatter {
private final ArrayList<String> mValues;
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.support.v4.app.Fragment;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -23,18 +25,14 @@ import java.util.Objects;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.utils.ParseHelpers;
import mthmmy.utils.Report;
import timber.log.Timber;
/**
* Use the {@link SummaryFragment#newInstance} factory method to create an instance of this fragment.
*/
public class SummaryFragment extends Fragment {
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "SummaryFragment";
/**
* The key to use when putting profile's source code String to {@link SummaryFragment}'s Bundle.
*/
@ -94,7 +92,7 @@ public class SummaryFragment extends Fragment {
summaryTask = new SummaryTask();
summaryTask.execute(profileSummaryDocument);
}
Report.d(TAG, "onActivityCreated");
Timber.d("onActivityCreated");
}
@Override
@ -112,14 +110,7 @@ public class SummaryFragment extends Fragment {
* <p>Calling SummaryTask's {@link AsyncTask#execute execute} method needs to have profile's url
* as String parameter!</p>
*/
public 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
private class SummaryTask extends AsyncTask<Document, Void, Void> {
protected Void doInBackground(Document... profileSummaryPage) {
parsedProfileSummaryData = parseProfileSummary(profileSummaryPage[0]);
return null;
@ -189,6 +180,16 @@ public class SummaryFragment extends Fragment {
}
TextView entry = new TextView(this.getContext());
if (profileSummaryRow.contains("@") &&
(profileSummaryRow.contains("Email") || profileSummaryRow.contains("E-mail"))) {
Timber.d("mpika");
Timber.d(profileSummaryRow);
String email = profileSummaryRow.substring(profileSummaryRow.indexOf(":</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)
entry.setTextColor(getResources().getColor(R.color.primary_text, null));
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;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.method.LinkMovementMethod;
import android.text.method.ScrollingMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Selector;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Objects;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.board.BoardActivity;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.utils.ParseHelpers;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import mthmmy.utils.Report;
import okhttp3.MultipartBody;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import timber.log.Timber;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_TITLE;
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_THUMBNAIL_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME;
import static gr.thmmy.mthmmy.activities.topic.Posting.replyStatus;
/**
* Activity for topics. When creating an Intent of this activity you need to bundle a <b>String</b>
@ -44,11 +72,6 @@ import okhttp3.Response;
@SuppressWarnings("unchecked")
public class TopicActivity extends BaseActivity {
//Class variables
/**
* Debug Tag for logging debug output to LogCat
*/
@SuppressWarnings("unused")
private static final String TAG = "TopicActivity";
/**
* The key to use when putting topic's url String to {@link TopicActivity}'s Bundle.
*/
@ -62,10 +85,11 @@ public class TopicActivity extends BaseActivity {
private TopicAdapter topicAdapter;
private ArrayList<Post> postsList;
private static final int NO_POST_FOCUS = -1;
private static int postFocus = NO_POST_FOCUS;
private int postFocus = NO_POST_FOCUS;
private static int postFocusPosition = 0;
//Quotes
public static final ArrayList<Integer> toQuoteList = new ArrayList<>();
//Reply
private FloatingActionButton replyFAB;
private String replyPageUrl = null;
//Topic's pages
private int thisPage = 1;
private int numberOfPages = 1;
@ -79,19 +103,24 @@ public class TopicActivity extends BaseActivity {
private static final int LARGE_STEP = 10;
private Integer pageRequestValue;
//Bottom navigation graphics
private LinearLayout bottomNavBar;
private ImageButton firstPage;
private ImageButton previousPage;
private TextView pageIndicator;
private ImageButton nextPage;
private ImageButton lastPage;
//Topic's info
private SpannableStringBuilder topicTreeAndMods = new SpannableStringBuilder("Loading..."),
topicViewers = new SpannableStringBuilder("Loading...");
//Other variables
private FloatingActionButton replyFAB;
private MaterialProgressBar progressBar;
TextView toolbarTitle;
private static String base_url = "";
private String topicTitle;
private String parsedTitle;
private RecyclerView recyclerView;
private String loadedPageUrl = "";
String loadedPageUrl = "";
private boolean reloadingPage = false;
@Override
@ -105,23 +134,27 @@ public class TopicActivity extends BaseActivity {
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(
Uri.parse(topicPageUrl));
if (!target.is(ThmmyPage.PageCategory.TOPIC)) {
Report.e(TAG, "Bundle came with a non topic url!\nUrl:\n" + topicPageUrl);
Timber.e("Bundle came with a non topic url!\nUrl:\n" + topicPageUrl);
Toast.makeText(this, "An error has occurred\n Aborting.", Toast.LENGTH_SHORT).show();
finish();
}
thisPageBookmark = new Bookmark(topicTitle, ThmmyPage.getTopicId(topicPageUrl));
//Initializes graphics
toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitle(topicTitle);
toolbarTitle = (TextView) toolbar.findViewById(R.id.toolbar_title);
toolbarTitle.setText(topicTitle);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
thisPageBookmark = new Bookmark(topicTitle, ThmmyPage.getTopicId(topicPageUrl));
thisPageBookmarkButton = (ImageButton) findViewById(R.id.bookmark);
setTopicBookmark();
//Makes title scrollable
toolbarTitle.setHorizontallyScrolling(true);
toolbarTitle.setMovementMethod(new ScrollingMovementMethod());
createDrawer();
progressBar = (MaterialProgressBar) findViewById(R.id.progressBar);
@ -132,42 +165,27 @@ public class TopicActivity extends BaseActivity {
recyclerView.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(layoutManager);
topicAdapter = new TopicAdapter(this, postsList,
topicTask);
topicAdapter = new TopicAdapter(this, postsList, topicTask, topicTitle, loadedPageUrl);
recyclerView.setAdapter(topicAdapter);
replyFAB = (FloatingActionButton) findViewById(R.id.topic_fab);
replyFAB.setEnabled(false);
replyFAB.hide();
/*if (!sessionManager.isLoggedIn()) replyFAB.hide();
bottomNavBar = (LinearLayout) findViewById(R.id.bottom_navigation_bar);
if (!sessionManager.isLoggedIn()) replyFAB.hide();
else {
replyFAB.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (sessionManager.isLoggedIn()) {
//TODO Reply
} else {
new AlertDialog.Builder(TopicActivity.this)
.setMessage("You need to be logged in to reply!")
.setPositiveButton("Login", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent(TopicActivity.this, LoginActivity.class);
startActivity(intent);
finish();
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out);
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
})
.show();
postsList.add(null);
topicAdapter.prepareForReply(new ReplyTask());
replyFAB.hide();
bottomNavBar.setVisibility(View.GONE);
topicAdapter.notifyItemInserted(postsList.size());
}
}
});
}*/
}
//Sets bottom navigation bar
firstPage = (ImageButton) findViewById(R.id.page_first_button);
@ -187,6 +205,43 @@ public class TopicActivity extends BaseActivity {
topicTask.execute(extras.getString(BUNDLE_TOPIC_URL)); //Attempt data parsing
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflates the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.topic_menu, menu);
setTopicBookmark(menu.getItem(0));
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.menu_bookmark:
topicMenuBookmarkClick();
return true;
case R.id.menu_info:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
LayoutInflater inflater = this.getLayoutInflater();
LinearLayout infoDialog = (LinearLayout) inflater.inflate(R.layout.dialog_topic_info
, null);
((TextView) infoDialog.findViewById(R.id.dialog_title)).setText("Info");
TextView treeAndMods = (TextView) infoDialog.findViewById(R.id.topic_tree_and_mods);
treeAndMods.setText(topicTreeAndMods);
treeAndMods.setMovementMethod(LinkMovementMethod.getInstance());
TextView usersViewing = (TextView) infoDialog.findViewById(R.id.users_viewing);
usersViewing.setText(topicViewers);
usersViewing.setMovementMethod(LinkMovementMethod.getInstance());
builder.setView(infoDialog);
AlertDialog dialog = builder.create();
dialog.show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onBackPressed() {
if (drawer.isDrawerOpen()) {
@ -216,7 +271,7 @@ public class TopicActivity extends BaseActivity {
* This class is used to implement the repetitive incrementPageRequestValue/decrementPageRequestValue
* of page value when long pressing one of the page navigation buttons.
*/
class RepetitiveUpdater implements Runnable {
private class RepetitiveUpdater implements Runnable {
private final int step;
/**
@ -352,7 +407,8 @@ public class TopicActivity extends BaseActivity {
paginationEnabled(true);
changePage(pageRequestValue - 1);
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) {
if (rect != null &&
!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) {
autoIncrement = false;
incrementPageRequestValue(thisPage - pageRequestValue);
paginationEnabled(true);
@ -400,11 +456,6 @@ public class TopicActivity extends BaseActivity {
* as String parameter!</p>
*/
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 NETWORK_ERROR = 1;
private static final int OTHER_ERROR = 2;
@ -419,7 +470,6 @@ public class TopicActivity extends BaseActivity {
protected Integer doInBackground(String... strings) {
Document document;
base_url = strings[0].substring(0, strings[0].lastIndexOf(".")); //New topic's base url
String newPageUrl = strings[0];
//Finds the index of message focus if present
@ -434,7 +484,7 @@ public class TopicActivity extends BaseActivity {
}
}
//Checks if the page to be loaded is the one already shown
if (!Objects.equals(loadedPageUrl, "") && loadedPageUrl.contains(base_url)) {
if (!reloadingPage && !Objects.equals(loadedPageUrl, "") && newPageUrl.contains(base_url)) {
if (newPageUrl.contains("topicseen#new") || newPageUrl.contains("#new"))
if (thisPage == numberOfPages)
return SAME_PAGE;
@ -448,11 +498,16 @@ public class TopicActivity extends BaseActivity {
return SAME_PAGE;
}
}
} else if (Integer.parseInt(newPageUrl.substring(base_url.length() + 1)) / 15 + 1 == thisPage)
} else if ((Objects.equals(newPageUrl, base_url) && thisPage == 1) ||
Integer.parseInt(newPageUrl.substring(base_url.length() + 1)) / 15 + 1 == thisPage)
return SAME_PAGE;
} else if (!Objects.equals(loadedPageUrl, "")) topicTitle = null;
if (reloadingPage) reloadingPage = !reloadingPage;
loadedPageUrl = newPageUrl;
if (strings[0].substring(0, strings[0].lastIndexOf(".")).contains("topic="))
base_url = strings[0].substring(0, strings[0].lastIndexOf(".")); //New topic's base url
replyPageUrl = null;
Request request = new Request.Builder()
.url(newPageUrl)
.build();
@ -462,10 +517,10 @@ public class TopicActivity extends BaseActivity {
parse(document);
return SUCCESS;
} catch (IOException e) {
Report.i(TAG, "IO Exception", e);
Timber.i("IO Exception", e);
return NETWORK_ERROR;
} catch (Exception e) {
Report.e(TAG, "Exception", e);
Timber.e("Exception", e);
return OTHER_ERROR;
}
}
@ -483,11 +538,13 @@ public class TopicActivity extends BaseActivity {
case SUCCESS:
if (topicTitle == null || Objects.equals(topicTitle, "")) {
thisPageBookmark = new Bookmark(parsedTitle, ThmmyPage.getTopicId(loadedPageUrl));
setTopicBookmark();
invalidateOptionsMenu();
}
progressBar.setVisibility(ProgressBar.INVISIBLE);
topicAdapter.customNotifyDataSetChanged(new TopicTask());
topicAdapter.setTopicInfo(parsedTitle, loadedPageUrl);
if (replyPageUrl == null) replyFAB.hide();
if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(true);
//Set current page
@ -496,8 +553,10 @@ public class TopicActivity extends BaseActivity {
paginationEnabled(true);
if (topicTitle != null)
if (parsedTitle != null)
if (topicTitle == null || Objects.equals(topicTitle, ""))
toolbar.setTitle(parsedTitle);
toolbarTitle.setText(parsedTitle);
break;
case NETWORK_ERROR:
Toast.makeText(getBaseContext(), "Network Error", Toast.LENGTH_SHORT).show();
@ -512,7 +571,7 @@ public class TopicActivity extends BaseActivity {
break;
default:
//Parse failed - should never happen
Report.d(TAG, "Parse failed!");
Timber.d("Parse failed!"); //TODO report ParseException?
Toast.makeText(getBaseContext(), "Fatal Error", Toast.LENGTH_SHORT).show();
finish();
break;
@ -528,6 +587,20 @@ public class TopicActivity extends BaseActivity {
private void parse(Document topic) {
ParseHelpers.Language language = ParseHelpers.Language.getLanguage(topic);
//Finds topic's tree, mods and users viewing
{
topicTreeAndMods = getSpannableFromHtml(topic.select("div.nav").first().html());
topicViewers = getSpannableFromHtml(TopicParser.parseUsersViewingThisTopic(topic, language));
}
//Finds reply page url
{
Element replyButton = topic.select("a:has(img[alt=Reply])").first();
if (replyButton == null)
replyButton = topic.select("a:has(img[alt=Απάντηση])").first();
if (replyButton != null) replyPageUrl = replyButton.attr("href");
}
//Finds topic title if missing
if (topicTitle == null || Objects.equals(topicTitle, "")) {
parsedTitle = topic.select("td[id=top_subject]").first().text();
@ -537,7 +610,7 @@ public class TopicActivity extends BaseActivity {
} else {
parsedTitle = parsedTitle.substring(parsedTitle.indexOf("Θέμα:") + 6
, parsedTitle.indexOf("(Αναγνώστηκε") - 2);
Report.d(TAG, parsedTitle);
Timber.d(parsedTitle);
}
}
@ -555,7 +628,150 @@ public class TopicActivity extends BaseActivity {
postsList.clear();
postsList.addAll(TopicParser.parseTopic(topic, language));
//postsList = TopicParser.parseTopic(topic, language);
}
private void makeLinkClickable(SpannableStringBuilder strBuilder, final URLSpan span) {
int start = strBuilder.getSpanStart(span);
int end = strBuilder.getSpanEnd(span);
int flags = strBuilder.getSpanFlags(span);
ClickableSpan clickable = new ClickableSpan() {
@Override
public void onClick(View view) {
ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(span.getURL()));
if (target.is(ThmmyPage.PageCategory.BOARD)) {
Intent intent = new Intent(getApplicationContext(), BoardActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_BOARD_URL, span.getURL());
extras.putString(BUNDLE_BOARD_TITLE, "");
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(intent);
} else if (target.is(ThmmyPage.PageCategory.PROFILE)) {
Intent intent = new Intent(getApplicationContext(), ProfileActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, span.getURL());
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
extras.putString(BUNDLE_PROFILE_USERNAME, "");
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(intent);
} else if (target.is(ThmmyPage.PageCategory.INDEX))
finish();
}
};
strBuilder.setSpan(clickable, start, end, flags);
strBuilder.removeSpan(span);
}
private SpannableStringBuilder getSpannableFromHtml(String html) {
CharSequence sequence = Html.fromHtml(html);
SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
URLSpan[] urls = strBuilder.getSpans(0, sequence.length(), URLSpan.class);
for (URLSpan span : urls) {
makeLinkClickable(strBuilder, span);
}
return strBuilder;
}
}
class ReplyTask extends AsyncTask<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);
}
}
}
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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.substring(fixed.indexOf("<embed"), fixed.indexOf("/noembed>") + 9)
, "<div class=\"yt\">"
+ "<a href=\"https://www.youtube.com/"
+ "<a href=\"https://www.youtube.com/watch?v="
+ embededVideosUrls.get(tmp_counter) + "\" target=\"_blank\">"
+ "<img class=\"embedded-video-play\" "
+ "src=\"http://www.youtube.com/yt/brand/media/image/YouTube_light_color_icon.png\""
+ "src=\"http://www.youtube.com/yt/brand/media/image/YouTube_light_color_icon.png\">"
+ "</a>"
+ "<img src=\"https://img.youtube.com/vi/"
+ embededVideosUrls.get(tmp_counter)
+ "/default.jpg\" alt=\"\" border=\"0\" width=\"40%\">"
+ "</div>");
++tmp_counter;
}
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.
*/
public class UnknownException extends Exception {
public UnknownException() {}
public UnknownException() {
}
public UnknownException(String message)
{
public UnknownException(String 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"
app:popupTheme="@style/ToolbarTheme">
<ImageButton
android:id="@+id/bookmark"
android:layout_width="wrap_content"
<TextView
android:id="@+id/toolbar_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|end"
android:layout_marginEnd="4dp"
android:background="@null"
android:contentDescription="@string/bookmark"
android:src="@drawable/ic_bookmark_false"/>
android:ellipsize="marquee"
android:fillViewport="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:marqueeRepeatLimit="marquee_forever"
android:maxLines="1"
android:scrollHorizontally="true"
android:textColor="@color/white"
/>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>

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

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingEnd="4dp"
android:paddingStart="4dp"
tools:ignore="SmallSp">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingEnd="4dp"
android:paddingStart="4dp"
tools:ignore="SmallSp">
<FrameLayout
android:id="@+id/post_date_and_number_exp"
@ -114,8 +115,7 @@
android:layout_toEndOf="@+id/thumbnail_holder"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/post_subject"
/>
android:text="@string/post_subject"/>
</RelativeLayout>
<ImageButton

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="card_background">#3C3F41</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="iron">#CCCCCC</color>

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

@ -44,6 +44,9 @@
<string name="button_page">Page</string>
<string name="button_next">next</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-->
<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" }
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.android.tools.build:gradle:2.3.0'
classpath 'com.google.gms:google-services:3.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

31
doc/forum_post.txt

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

Loading…
Cancel
Save