Browse Source

Version 1.1.0

master v1.1.0
Ezerous 8 years ago
parent
commit
5cf901c703
  1. 2
      .gitlab-ci.yml
  2. 2
      VERSION
  3. 6
      app/build.gradle
  4. 14
      app/src/debug/java/mthmmy/utils/Report.java
  5. 73
      app/src/main/AndroidManifest.xml
  6. 2
      app/src/main/assets/mit_libraries.html
  7. 18
      app/src/main/java/gr/thmmy/mthmmy/activities/AboutActivity.java
  8. 142
      app/src/main/java/gr/thmmy/mthmmy/activities/BookmarkActivity.java
  9. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/LoginActivity.java
  10. 373
      app/src/main/java/gr/thmmy/mthmmy/activities/base/BaseActivity.java
  11. 48
      app/src/main/java/gr/thmmy/mthmmy/activities/board/BoardActivity.java
  12. 279
      app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsActivity.java
  13. 187
      app/src/main/java/gr/thmmy/mthmmy/activities/downloads/DownloadsAdapter.java
  14. 83
      app/src/main/java/gr/thmmy/mthmmy/activities/main/MainActivity.java
  15. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumAdapter.java
  16. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/main/forum/ForumFragment.java
  17. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java
  18. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentFragment.java
  19. 62
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/ProfileActivity.java
  20. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsAdapter.java
  21. 4
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/latestPosts/LatestPostsFragment.java
  22. 8
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/stats/StatsFragment.java
  23. 10
      app/src/main/java/gr/thmmy/mthmmy/activities/profile/summary/SummaryFragment.java
  24. 214
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicActivity.java
  25. 135
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicAdapter.java
  26. 2
      app/src/main/java/gr/thmmy/mthmmy/activities/topic/TopicParser.java
  27. 565
      app/src/main/java/gr/thmmy/mthmmy/base/BaseActivity.java
  28. 111
      app/src/main/java/gr/thmmy/mthmmy/base/BaseApplication.java
  29. 8
      app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java
  30. 73
      app/src/main/java/gr/thmmy/mthmmy/model/Bookmark.java
  31. 58
      app/src/main/java/gr/thmmy/mthmmy/model/Download.java
  32. 2
      app/src/main/java/gr/thmmy/mthmmy/model/Post.java
  33. 37
      app/src/main/java/gr/thmmy/mthmmy/model/ThmmyFile.java
  34. 89
      app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java
  35. 84
      app/src/main/java/gr/thmmy/mthmmy/receiver/Receiver.java
  36. 221
      app/src/main/java/gr/thmmy/mthmmy/services/DownloadService.java
  37. 142
      app/src/main/java/gr/thmmy/mthmmy/session/SessionManager.java
  38. 194
      app/src/main/java/gr/thmmy/mthmmy/utils/FileManager/ThmmyFile.java
  39. 1
      app/src/main/java/gr/thmmy/mthmmy/utils/ScrollAwareLinearBehavior.java
  40. BIN
      app/src/main/res/drawable-hdpi/ic_bookmark_false.png
  41. BIN
      app/src/main/res/drawable-hdpi/ic_bookmark_true.png
  42. BIN
      app/src/main/res/drawable-hdpi/ic_pin.png
  43. BIN
      app/src/main/res/drawable-hdpi/ic_remove_circle.png
  44. BIN
      app/src/main/res/drawable-mdpi/ic_bookmark_false.png
  45. BIN
      app/src/main/res/drawable-mdpi/ic_bookmark_true.png
  46. BIN
      app/src/main/res/drawable-mdpi/ic_pin.png
  47. BIN
      app/src/main/res/drawable-mdpi/ic_remove_circle.png
  48. BIN
      app/src/main/res/drawable-xhdpi/ic_bookmark_false.png
  49. BIN
      app/src/main/res/drawable-xhdpi/ic_bookmark_true.png
  50. BIN
      app/src/main/res/drawable-xhdpi/ic_pin.png
  51. BIN
      app/src/main/res/drawable-xhdpi/ic_remove_circle.png
  52. BIN
      app/src/main/res/drawable-xxhdpi/ic_bookmark_false.png
  53. BIN
      app/src/main/res/drawable-xxhdpi/ic_bookmark_true.png
  54. BIN
      app/src/main/res/drawable-xxhdpi/ic_pin.png
  55. BIN
      app/src/main/res/drawable-xxhdpi/ic_remove_circle.png
  56. BIN
      app/src/main/res/drawable-xxxhdpi/ic_bookmark_false.png
  57. BIN
      app/src/main/res/drawable-xxxhdpi/ic_bookmark_true.png
  58. BIN
      app/src/main/res/drawable-xxxhdpi/ic_pin.png
  59. BIN
      app/src/main/res/drawable-xxxhdpi/ic_remove_circle.png
  60. 11
      app/src/main/res/drawable/ic_file_upload.xml
  61. 10
      app/src/main/res/layout/activity_board.xml
  62. 15
      app/src/main/res/layout/activity_board_sub_board.xml
  63. 7
      app/src/main/res/layout/activity_board_topic.xml
  64. 57
      app/src/main/res/layout/activity_bookmark.xml
  65. 33
      app/src/main/res/layout/activity_bookmark_row.xml
  66. 62
      app/src/main/res/layout/activity_downloads.xml
  67. 76
      app/src/main/res/layout/activity_downloads_row.xml
  68. 48
      app/src/main/res/layout/activity_login.xml
  69. 14
      app/src/main/res/layout/activity_topic.xml
  70. 14
      app/src/main/res/layout/fragment_latest_posts_row.xml
  71. 13
      app/src/main/res/values/strings.xml
  72. 2
      app/src/main/res/values/styles.xml
  73. 10
      app/src/main/res/xml/provider_paths.xml
  74. 5
      app/src/release/java/mthmmy.utils/Report.java
  75. 34
      doc/forum_post.txt

2
.gitlab-ci.yml

@ -3,7 +3,7 @@ image: openjdk:8-jdk
variables: variables:
ANDROID_TARGET_SDK: "25" ANDROID_TARGET_SDK: "25"
ANDROID_BUILD_TOOLS: "25.0.1" ANDROID_BUILD_TOOLS: "25.0.1"
ANDROID_SDK_TOOLS: "25.2.4" ANDROID_SDK_TOOLS: "25.2.5"
before_script: before_script:
- apt-get --quiet update --yes - apt-get --quiet update --yes

2
VERSION

@ -1 +1 @@
1.0.0 1.1.0

6
app/build.gradle

@ -9,8 +9,8 @@ android {
applicationId "gr.thmmy.mthmmy" applicationId "gr.thmmy.mthmmy"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 25 targetSdkVersion 25
versionCode 2 versionCode 3
versionName "1.0.1" versionName "1.1.0"
archivesBaseName = "mTHMMY-v$versionName" archivesBaseName = "mTHMMY-v$versionName"
} }
@ -37,7 +37,7 @@ dependencies {
compile 'com.squareup.okhttp3:okhttp:3.5.0' compile 'com.squareup.okhttp3:okhttp:3.5.0'
compile 'com.squareup.picasso:picasso:2.5.2' compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0' compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
compile 'org.jsoup:jsoup:1.10.1' compile 'org.jsoup:jsoup:1.10.2'
compile 'com.github.franmontiel:PersistentCookieJar:v1.0.0' compile 'com.github.franmontiel:PersistentCookieJar:v1.0.0'
compile 'com.github.PhilJay:MPAndroidChart:v3.0.1' compile 'com.github.PhilJay:MPAndroidChart:v3.0.1'
compile('com.mikepenz:materialdrawer:5.8.1@aar') { compile('com.mikepenz:materialdrawer:5.8.1@aar') {

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

@ -64,4 +64,18 @@ public class Report
{ {
Log.wtf(TAG,message + ": " + tr.getMessage(),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));
}
}
} }

73
app/src/main/AndroidManifest.xml

@ -6,9 +6,9 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<application <application
android:name=".base.BaseApplication"
android:allowBackup="true" android:allowBackup="true"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
@ -26,13 +26,35 @@
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="thmmy.gr"
android:scheme="http"/>
<data
android:host="www.thmmy.gr"
android:scheme="http"/>
<data
android:host="www.thmmy.gr"
android:scheme="https"/>
<data
android:host="thmmy.gr"
android:scheme="https"/>
</intent-filter>
</activity> </activity>
<activity <activity
android:name=".activities.LoginActivity" android:name=".activities.LoginActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize|keyboardHidden"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/AppTheme.NoActionBar"> android:screenOrientation="portrait"
</activity> android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="adjustPan"/>
<activity <activity
android:name=".activities.AboutActivity" android:name=".activities.AboutActivity"
android:parentActivityName=".activities.main.MainActivity" android:parentActivityName=".activities.main.MainActivity"
@ -52,8 +74,7 @@
</activity> </activity>
<activity <activity
android:name=".activities.profile.ProfileActivity" android:name=".activities.profile.ProfileActivity"
android:theme="@style/AppTheme.NoActionBar"> android:theme="@style/AppTheme.NoActionBar"/>
</activity>
<activity <activity
android:name=".activities.board.BoardActivity" android:name=".activities.board.BoardActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
@ -63,6 +84,46 @@
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.main.MainActivity"/> android:value=".activities.main.MainActivity"/>
</activity> </activity>
<activity
android:name=".activities.downloads.DownloadsActivity"
android:parentActivityName=".activities.main.MainActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.main.MainActivity"/>
</activity>
<activity
android:name=".activities.BookmarkActivity"
android:parentActivityName=".activities.main.MainActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.main.MainActivity"/>
</activity>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
<service
android:name=".services.DownloadService"
android:exported="false"/>
<receiver
android:name=".receiver.Receiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.media.action.ACTION_DOWNLOAD"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
</application> </application>
</manifest> </manifest>

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

@ -39,7 +39,7 @@
<body> <body>
<ul> <ul>
<li> <li>
<h5><a href="https://jsoup.org//">jsoup</a>&nbsp;v1.10.1 (Copyright ©2009-2017, Jonathan Hedley &lt;jonathan@hedley.net&gt;)</h5> <h5><a href="https://jsoup.org//">jsoup</a>&nbsp;v1.10.2 (Copyright ©2009-2017, Jonathan Hedley &lt;jonathan@hedley.net&gt;)</h5>
</li> </li>
<li> <li>
<h5><a href="https://github.com/koral--/android-gif-drawable">android-gif-drawable</a>&nbsp;v1.2.3 (Copyright ©2016 Karol Wrótniak, Droids on Roids)</h5> <h5><a href="https://github.com/koral--/android-gif-drawable">android-gif-drawable</a>&nbsp;v1.2.3 (Copyright ©2016 Karol Wrótniak, Droids on Roids)</h5>

18
app/src/main/java/gr/thmmy/mthmmy/activities/AboutActivity.java

@ -16,7 +16,7 @@ import android.widget.TextView;
import gr.thmmy.mthmmy.BuildConfig; import gr.thmmy.mthmmy.BuildConfig;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
public class AboutActivity extends BaseActivity { public class AboutActivity extends BaseActivity {
private static final int TIME_INTERVAL = 1000; private static final int TIME_INTERVAL = 1000;
@ -26,7 +26,7 @@ public class AboutActivity extends BaseActivity {
private AppBarLayout appBar; private AppBarLayout appBar;
private CoordinatorLayout coordinatorLayout; private CoordinatorLayout coordinatorLayout;
AlertDialog alertDialog; private AlertDialog alertDialog;
private FrameLayout trollGif; private FrameLayout trollGif;
@Override @Override
@ -42,8 +42,10 @@ public class AboutActivity extends BaseActivity {
toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitle(R.string.about); toolbar.setTitle(R.string.about);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true);
}
createDrawer(); createDrawer();
drawer.setSelection(ABOUT_ID); drawer.setSelection(ABOUT_ID);
@ -84,11 +86,11 @@ public class AboutActivity extends BaseActivity {
} }
public void displayApacheLibraries(View v) { public void displayApacheLibraries(View v) {
LayoutInflater inflater =LayoutInflater.from(this); LayoutInflater inflater = LayoutInflater.from(this);
WebView webView = (WebView) inflater.inflate(R.layout.dialog_licenses, coordinatorLayout, false); WebView webView = (WebView) inflater.inflate(R.layout.dialog_licenses, coordinatorLayout, false);
webView.loadUrl("file:///android_asset/apache_libraries.html"); webView.loadUrl("file:///android_asset/apache_libraries.html");
int width = (int)(getResources().getDisplayMetrics().widthPixels*0.95); int width = (int) (getResources().getDisplayMetrics().widthPixels * 0.95);
int height = (int)(getResources().getDisplayMetrics().heightPixels*0.95); int height = (int) (getResources().getDisplayMetrics().heightPixels * 0.95);
alertDialog = new AlertDialog.Builder(this, R.style.AppTheme_Dark_Dialog) alertDialog = new AlertDialog.Builder(this, R.style.AppTheme_Dark_Dialog)
.setTitle(getString(R.string.apache_v2_0_libraries)) .setTitle(getString(R.string.apache_v2_0_libraries))
.setView(webView) .setView(webView)
@ -98,11 +100,11 @@ public class AboutActivity extends BaseActivity {
} }
public void displayMITLibraries(View v) { public void displayMITLibraries(View v) {
LayoutInflater inflater =LayoutInflater.from(this); LayoutInflater inflater = LayoutInflater.from(this);
WebView webView = (WebView) inflater.inflate(R.layout.dialog_licenses, coordinatorLayout, false); WebView webView = (WebView) inflater.inflate(R.layout.dialog_licenses, coordinatorLayout, false);
webView.loadUrl("file:///android_asset/mit_libraries.html"); webView.loadUrl("file:///android_asset/mit_libraries.html");
int width = (int)(getResources().getDisplayMetrics().widthPixels*0.95); int width = (int) (getResources().getDisplayMetrics().widthPixels * 0.95);
int height = (int)(getResources().getDisplayMetrics().heightPixels*0.95); int height = (int) (getResources().getDisplayMetrics().heightPixels * 0.95);
alertDialog = new AlertDialog.Builder(this, R.style.AppTheme_Dark_Dialog) alertDialog = new AlertDialog.Builder(this, R.style.AppTheme_Dark_Dialog)
.setTitle(getString(R.string.the_mit_libraries)) .setTitle(getString(R.string.the_mit_libraries))
.setView(webView) .setView(webView)

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

@ -0,0 +1,142 @@
package gr.thmmy.mthmmy.activities;
import android.content.Intent;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.board.BoardActivity;
import gr.thmmy.mthmmy.activities.topic.TopicActivity;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Bookmark;
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.topic.TopicActivity.BUNDLE_TOPIC_TITLE;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL;
public class BookmarkActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bookmark);
//Initialize toolbar
toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitle("Bookmarks");
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
createDrawer();
drawer.setSelection(BOOKMARKS_ID);
LinearLayout bookmarksLinearView = (LinearLayout) findViewById(R.id.bookmarks_container);
LayoutInflater layoutInflater = getLayoutInflater();
TextView tmp = new TextView(this);
tmp.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT
, LinearLayout.LayoutParams.WRAP_CONTENT));
tmp.setText(getString(R.string.board_bookmarks_title));
tmp.setTypeface(tmp.getTypeface(), Typeface.BOLD);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
tmp.setTextColor(getColor(R.color.primary_text));
} else {
//noinspection deprecation
tmp.setTextColor(getResources().getColor(R.color.primary_text));
}
tmp.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
tmp.setTextSize(20f);
bookmarksLinearView.addView(tmp);
for (final Bookmark bookmarkedBoard : getBoardsBookmarked()) {
if (bookmarkedBoard != null && bookmarkedBoard.getTitle() != null) {
final LinearLayout row = (LinearLayout) layoutInflater.inflate(
R.layout.activity_bookmark_row, bookmarksLinearView, false);
row.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(BookmarkActivity.this, BoardActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_BOARD_URL, "https://www.thmmy.gr/smf/index.php?board="
+ bookmarkedBoard.getId() + ".0");
extras.putString(BUNDLE_BOARD_TITLE, bookmarkedBoard.getTitle());
intent.putExtras(extras);
startActivity(intent);
finish();
}
});
((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedBoard.getTitle());
(row.findViewById(R.id.remove_bookmark)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
removeBookmark(bookmarkedBoard);
row.setVisibility(View.GONE);
}
});
bookmarksLinearView.addView(row);
}
}
tmp = new TextView(this);
tmp.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT
, LinearLayout.LayoutParams.WRAP_CONTENT));
tmp.setText(getString(R.string.topic_bookmarks_title));
tmp.setTypeface(tmp.getTypeface(), Typeface.BOLD);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
tmp.setTextColor(getColor(R.color.primary_text));
} else {
//noinspection deprecation
tmp.setTextColor(getResources().getColor(R.color.primary_text));
}
tmp.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
tmp.setTextSize(20f);
bookmarksLinearView.addView(tmp);
for (final Bookmark bookmarkedTopic : getTopicsBookmarked()) {
if (bookmarkedTopic != null && bookmarkedTopic.getTitle() != null) {
final LinearLayout row = (LinearLayout) layoutInflater.inflate(
R.layout.activity_bookmark_row, bookmarksLinearView, false);
row.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(BookmarkActivity.this, TopicActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_TOPIC_URL, "https://www.thmmy.gr/smf/index.php?topic="
+ bookmarkedTopic.getId() + ".0");
extras.putString(BUNDLE_TOPIC_TITLE, bookmarkedTopic.getTitle());
intent.putExtras(extras);
startActivity(intent);
finish();
}
});
((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedTopic.getTitle());
(row.findViewById(R.id.remove_bookmark)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
removeBookmark(bookmarkedTopic);
row.setVisibility(View.GONE);
}
});
bookmarksLinearView.addView(row);
}
}
}
@Override
protected void onResume() {
drawer.setSelection(BOOKMARKS_ID);
super.onResume();
}
}

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

@ -12,8 +12,8 @@ import android.widget.ScrollView;
import android.widget.Toast; import android.widget.Toast;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.base.BaseActivity;
import gr.thmmy.mthmmy.activities.main.MainActivity; import gr.thmmy.mthmmy.activities.main.MainActivity;
import gr.thmmy.mthmmy.base.BaseActivity;
import mthmmy.utils.Report; import mthmmy.utils.Report;
import static gr.thmmy.mthmmy.session.SessionManager.CONNECTION_ERROR; import static gr.thmmy.mthmmy.session.SessionManager.CONNECTION_ERROR;

373
app/src/main/java/gr/thmmy/mthmmy/activities/base/BaseActivity.java

@ -1,373 +0,0 @@
package gr.thmmy.mthmmy.activities.base;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import com.franmontiel.persistentcookiejar.PersistentCookieJar;
import com.franmontiel.persistentcookiejar.cache.SetCookieCache;
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor;
import com.jakewharton.picasso.OkHttp3Downloader;
import com.mikepenz.fontawesome_typeface_library.FontAwesome;
import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.materialdrawer.AccountHeader;
import com.mikepenz.materialdrawer.AccountHeaderBuilder;
import com.mikepenz.materialdrawer.Drawer;
import com.mikepenz.materialdrawer.DrawerBuilder;
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IProfile;
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader;
import com.mikepenz.materialdrawer.util.DrawerImageLoader;
import com.squareup.picasso.Picasso;
import java.util.concurrent.TimeUnit;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.AboutActivity;
import gr.thmmy.mthmmy.activities.LoginActivity;
import gr.thmmy.mthmmy.activities.main.MainActivity;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.session.SessionManager;
import okhttp3.OkHttpClient;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_THUMBNAIL_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_USERNAME;
public abstract class BaseActivity extends AppCompatActivity
{
// Client & Cookies
protected static OkHttpClient client;
private static final long connectTimeout = 30; //TimeUnit.SECONDS for all three
private static final long writeTimeout = 30;
private static final long readTimeout = 30;
protected static Picasso picasso;
private static PersistentCookieJar cookieJar;
private static SharedPrefsCookiePersistor sharedPrefsCookiePersistor;
//Shared Preferences
protected static final String SHARED_PREFS_NAME = "ThmmySharedPrefs";
protected static SharedPreferences sharedPrefs;
//SessionManager
protected static SessionManager sessionManager;
//Other variables
private static boolean init = false; //To initialize stuff only once per app start
//Common UI elements
protected Toolbar toolbar;
protected Drawer drawer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!init) {
sharedPrefs = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
sharedPrefsCookiePersistor = new SharedPrefsCookiePersistor(BaseActivity.this);
cookieJar = new PersistentCookieJar(new SetCookieCache(), sharedPrefsCookiePersistor);
client = new OkHttpClient.Builder()
.cookieJar(cookieJar)
.connectTimeout(connectTimeout, TimeUnit.SECONDS)
.writeTimeout(writeTimeout, TimeUnit.SECONDS)
.readTimeout(readTimeout, TimeUnit.SECONDS)
.build();
sessionManager = new SessionManager(client, cookieJar, sharedPrefsCookiePersistor, sharedPrefs);
picasso = new Picasso.Builder(BaseActivity.this)
.downloader(new OkHttp3Downloader(client))
.build();
Picasso.setSingletonInstance(picasso); // all following Picasso (with Picasso.with(Context context) requests will use this Picasso object
//initialize and create the image loader logic TODO move this to a singleton BaseApplication obj
DrawerImageLoader.init(new AbstractDrawerImageLoader() {
@Override
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);
}
@Override
public Drawable placeholder(Context ctx, String tag) {
if (DrawerImageLoader.Tags.PROFILE.name().equals(tag)) {
return new IconicsDrawable(ctx).icon(FontAwesome.Icon.faw_user)
.paddingDp(10)
.color(ContextCompat.getColor(ctx, R.color.primary_light))
.backgroundColor(ContextCompat.getColor(ctx, R.color.primary));
}
return super.placeholder(ctx, tag);
}
});
init = true;
}
}
@Override
protected void onResume() {
super.onResume();
updateDrawer();
}
@Override
protected void onPause() {
super.onPause();
if(drawer!=null) //close drawer animation after returning to activity
drawer.closeDrawer();
}
public static OkHttpClient getClient()
{
return client;
}
public static SessionManager getSessionManager()
{
return sessionManager;
}
//TODO: move stuff below
//------------------------------------------DRAWER STUFF----------------------------------------
protected static final int HOME_ID=0;
protected static final int LOG_ID =1;
protected static final int ABOUT_ID=2;
private AccountHeader accountHeader;
private ProfileDrawerItem profileDrawerItem;
private PrimaryDrawerItem homeItem, loginLogoutItem, aboutItem;
private IconicsDrawable homeIcon, homeIconSelected, loginIcon, logoutIcon,
aboutIcon, aboutIconSelected;
/**
* Call only after initializing Toolbar
*/
protected void createDrawer()
{
final int primaryColor = ContextCompat.getColor(this, R.color.iron);
final int selectedPrimaryColor = ContextCompat.getColor(this, R.color.primary_dark);
final int selectedSecondaryColor = ContextCompat.getColor(this, R.color.accent);
//Drawer Icons
homeIcon =new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_home)
.color(primaryColor);
homeIconSelected =new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_home)
.color(selectedSecondaryColor);
loginIcon =new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_sign_in)
.color(primaryColor);
logoutIcon =new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_sign_out)
.color(primaryColor);
aboutIcon =new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_info_circle)
.color(primaryColor);
aboutIconSelected =new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_info_circle)
.color(selectedSecondaryColor);
//Drawer Items
homeItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedPrimaryColor)
.withSelectedTextColor(selectedSecondaryColor)
.withIdentifier(HOME_ID)
.withName(R.string.home)
.withIcon(homeIcon)
.withSelectedIcon(homeIconSelected);
if (!sessionManager.isLoggedIn()) //When logged out
loginLogoutItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedSecondaryColor)
.withIdentifier(LOG_ID).withName(R.string.login)
.withIcon(loginIcon)
.withSelectable(false);
else
loginLogoutItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedSecondaryColor)
.withIdentifier(LOG_ID)
.withName(R.string.logout)
.withIcon(logoutIcon)
.withSelectable(false);
aboutItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedPrimaryColor)
.withSelectedTextColor(selectedSecondaryColor)
.withIdentifier(ABOUT_ID)
.withName(R.string.about)
.withIcon(aboutIcon)
.withSelectedIcon(aboutIconSelected);
//Profile
profileDrawerItem = new ProfileDrawerItem().withName(sessionManager.getUsername());
//AccountHeader
accountHeader = new AccountHeaderBuilder()
.withActivity(this)
.withCompactStyle(true)
.withSelectionListEnabledForSingleProfile(false)
.withHeaderBackground(R.color.primary)
.addProfiles(profileDrawerItem)
.withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() {
@Override
public boolean onProfileChanged(View view, IProfile profile, boolean currentProfile) {
if(sessionManager.isLoggedIn())
{
Intent intent = new Intent(BaseActivity.this, ProfileActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, "https://www.thmmy.gr/smf/index.php?action=profile");
if(!sessionManager.hasAvatar())
extras.putString(BUNDLE_THUMBNAIL_URL, "");
else
extras.putString(BUNDLE_THUMBNAIL_URL, sessionManager.getAvatarLink());
extras.putString(BUNDLE_USERNAME, sessionManager.getUsername());
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
return false;
}
return true;
}
})
.build();
//Drawer
drawer = new DrawerBuilder()
.withActivity(this)
.withToolbar(toolbar)
.withSliderBackgroundColor(ContextCompat.getColor(this, R.color.primary_light))
.withAccountHeader(accountHeader)
.addDrawerItems(homeItem,loginLogoutItem,aboutItem)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
public boolean onItemClick(View view, int position, IDrawerItem drawerItem) {
if(drawerItem.equals(HOME_ID))
{
if(!(BaseActivity.this instanceof MainActivity))
{
Intent i = new Intent(BaseActivity.this, MainActivity.class);
startActivity(i);
}
}
else if(drawerItem.equals(LOG_ID))
{
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
{
Intent intent = new Intent(BaseActivity.this, LoginActivity.class);
startActivity(intent);
finish();
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out);
}
else
new LogoutTask().execute();
}
else if(drawerItem.equals(ABOUT_ID))
{
if(!(BaseActivity.this instanceof AboutActivity))
{
Intent i = new Intent(BaseActivity.this, AboutActivity.class);
startActivity(i);
}
}
drawer.closeDrawer();
return true;
}
})
.build();
drawer.getActionBarDrawerToggle().setDrawerIndicatorEnabled(false);
drawer.setOnDrawerNavigationListener(new Drawer.OnDrawerNavigationListener() {
@Override
public boolean onNavigationClickListener(View clickedView) {
onBackPressed();
return true;
}
});
}
protected void updateDrawer()
{
if(drawer!=null)
{
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
{
loginLogoutItem.withName(R.string.login).withIcon(loginIcon); //Swap logout with login
profileDrawerItem.withName(sessionManager.getUsername()).withIcon(new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_user)
.paddingDp(10)
.color(ContextCompat.getColor(this, R.color.primary_light))
.backgroundColor(ContextCompat.getColor(this, R.color.primary)));
}
else
{
loginLogoutItem.withName(R.string.logout).withIcon(logoutIcon); //Swap login with logout
profileDrawerItem.withName(sessionManager.getUsername()).withIcon(sessionManager.getAvatarLink());
}
accountHeader.updateProfile(profileDrawerItem);
drawer.updateItem(loginLogoutItem);
}
}
//-------------------------------------------LOGOUT-------------------------------------------------
/**
* 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
ProgressDialog progressDialog;
protected Integer doInBackground(Void... voids) {
return sessionManager.logout();
}
protected void onPreExecute()
{ //Show a progress dialog until done
progressDialog = new ProgressDialog(BaseActivity.this,
R.style.AppTheme_Dark_Dialog);
progressDialog.setCancelable(false);
progressDialog.setIndeterminate(true);
progressDialog.setMessage("Logging out...");
progressDialog.show();
}
protected void onPostExecute(Integer result)
{
Toast.makeText(getBaseContext(), "Logged out successfully!", Toast.LENGTH_LONG).show();
updateDrawer();
progressDialog.dismiss();
}
}
//-----------------------------------------LOGOUT END-----------------------------------------------
}

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

@ -1,17 +1,15 @@
package gr.thmmy.mthmmy.activities.board; package gr.thmmy.mthmmy.activities.board;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.View; import android.view.View;
import android.widget.ImageButton;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.Toast; import android.widget.Toast;
@ -26,10 +24,10 @@ import java.util.Objects;
import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLHandshakeException;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.LoginActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.activities.base.BaseActivity;
import gr.thmmy.mthmmy.model.Board; import gr.thmmy.mthmmy.model.Board;
import gr.thmmy.mthmmy.model.LinkTarget; import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.model.Topic; import gr.thmmy.mthmmy.model.Topic;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import mthmmy.utils.Report; import mthmmy.utils.Report;
@ -76,8 +74,8 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
Bundle extras = getIntent().getExtras(); Bundle extras = getIntent().getExtras();
boardTitle = extras.getString(BUNDLE_BOARD_TITLE); boardTitle = extras.getString(BUNDLE_BOARD_TITLE);
boardUrl = extras.getString(BUNDLE_BOARD_URL); boardUrl = extras.getString(BUNDLE_BOARD_URL);
LinkTarget.Target target = LinkTarget.resolveLinkTarget(Uri.parse(boardUrl)); ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(boardUrl));
if (!target.is(LinkTarget.Target.BOARD)) { if (!target.is(ThmmyPage.PageCategory.BOARD)) {
Report.e(TAG, "Bundle came with a non board url!\nUrl:\n" + boardUrl); Report.e(TAG, "Bundle came with a non board url!\nUrl:\n" + boardUrl);
Toast.makeText(this, "An error has occurred\nAborting.", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "An error has occurred\nAborting.", Toast.LENGTH_SHORT).show();
finish(); finish();
@ -93,11 +91,16 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true);
} }
thisPageBookmark = new Bookmark(boardTitle, ThmmyPage.getBoardId(boardUrl));
thisPageBookmarkButton = (ImageButton) findViewById(R.id.bookmark);
setBoardBookmark();
createDrawer(); createDrawer();
progressBar = (MaterialProgressBar) findViewById(R.id.progressBar); progressBar = (MaterialProgressBar) findViewById(R.id.progressBar);
newTopicFAB = (FloatingActionButton) findViewById(R.id.board_fab); newTopicFAB = (FloatingActionButton) findViewById(R.id.board_fab);
newTopicFAB.setEnabled(false); newTopicFAB.setEnabled(false);
if (!sessionManager.isLoggedIn()) newTopicFAB.hide(); newTopicFAB.hide();
/*if (!sessionManager.isLoggedIn()) newTopicFAB.hide();
else { else {
newTopicFAB.setOnClickListener(new View.OnClickListener() { newTopicFAB.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -125,7 +128,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
} }
} }
}); });
} }*/
boardAdapter = new BoardAdapter(getApplicationContext(), parsedSubBoards, parsedTopics); boardAdapter = new BoardAdapter(getApplicationContext(), parsedSubBoards, parsedTopics);
RecyclerView mainContent = (RecyclerView) findViewById(R.id.board_recycler_view); RecyclerView mainContent = (RecyclerView) findViewById(R.id.board_recycler_view);
@ -178,7 +181,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
* <p>BoardTask's {@link AsyncTask#execute execute} method needs a boards's url as String * <p>BoardTask's {@link AsyncTask#execute execute} method needs a boards's url as String
* parameter!</p> * parameter!</p>
*/ */
public class BoardTask extends AsyncTask<String, Void, Boolean> { public class BoardTask extends AsyncTask<String, Void, Void> {
//Class variables //Class variables
/** /**
* Debug Tag for logging debug output to LogCat * Debug Tag for logging debug output to LogCat
@ -186,34 +189,32 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static final String TAG = "BoardTask"; //Separate tag for AsyncTask private static final String TAG = "BoardTask"; //Separate tag for AsyncTask
@Override
protected void onPreExecute() { protected void onPreExecute() {
if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE); if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE);
if (newTopicFAB.getVisibility() != View.GONE) newTopicFAB.setEnabled(false); if (newTopicFAB.getVisibility() != View.GONE) newTopicFAB.setEnabled(false);
} }
@Override @Override
protected Boolean doInBackground(String... boardUrl) { protected Void doInBackground(String... boardUrl) {
Request request = new Request.Builder() Request request = new Request.Builder()
.url(boardUrl[0]) .url(boardUrl[0])
.build(); .build();
try { try {
Response response = BaseActivity.getClient().newCall(request).execute(); Response response = BaseActivity.getClient().newCall(request).execute();
return parseBoard(Jsoup.parse(response.body().string())); parseBoard(Jsoup.parse(response.body().string()));
} catch (SSLHandshakeException e) { } catch (SSLHandshakeException e) {
Report.w(TAG, "Certificate problem (please switch to unsafe connection)."); Report.w(TAG, "Certificate problem (please switch to unsafe connection).");
} catch (Exception e) { } catch (Exception e) {
Report.e("TAG", "ERROR", e); Report.e("TAG", "ERROR", e);
} }
return false; return null;
} }
protected void onPostExecute(Boolean result) { @Override
if (!result) { //Parse failed! protected void onPostExecute(Void voids) {
Report.d(TAG, "Parse failed!"); if (boardTitle == null || Objects.equals(boardTitle, "")) toolbar.setTitle(boardTitle);
Toast.makeText(getApplicationContext()
, "Fatal error!\n Aborting...", Toast.LENGTH_LONG).show();
finish();
}
//Parse was successful //Parse was successful
++pagesLoaded; ++pagesLoaded;
if (newTopicFAB.getVisibility() != View.GONE) newTopicFAB.setEnabled(true); if (newTopicFAB.getVisibility() != View.GONE) newTopicFAB.setEnabled(true);
@ -222,7 +223,7 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
isLoadingMore = false; isLoadingMore = false;
} }
private boolean parseBoard(Document boardPage) { private void parseBoard(Document boardPage) {
if (boardTitle == null || Objects.equals(boardTitle, "")) if (boardTitle == null || Objects.equals(boardTitle, ""))
boardTitle = boardPage.select("div.nav a.nav").last().text(); boardTitle = boardPage.select("div.nav a.nav").last().text();
@ -280,9 +281,11 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
} else { } else {
pUrl = subBoardCol.select("a").first().attr("href"); pUrl = subBoardCol.select("a").first().attr("href");
pTitle = subBoardCol.select("a").first().text(); pTitle = subBoardCol.select("a").first().text();
if (subBoardCol.select("div.smalltext").first() != null) {
pMods = subBoardCol.select("div.smalltext").first().text(); pMods = subBoardCol.select("div.smalltext").first().text();
} }
} }
}
parsedSubBoards.add(new Board(pUrl, pTitle, pMods, pStats, pLastPost, pLastPostUrl)); parsedSubBoards.add(new Board(pUrl, pTitle, pMods, pStats, pLastPost, pLastPostUrl));
} }
} }
@ -324,7 +327,6 @@ public class BoardActivity extends BaseActivity implements BoardAdapter.OnLoadMo
} }
} }
} }
return true;
} }
} }
} }

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

@ -0,0 +1,279 @@
package gr.thmmy.mthmmy.activities.downloads;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.ArrayList;
import java.util.Objects;
import javax.net.ssl.SSLHandshakeException;
import gr.thmmy.mthmmy.R;
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;
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.
*/
public static final String BUNDLE_DOWNLOADS_URL = "DOWNLOADS_URL";
/**
* The key to use when putting download's title String to {@link DownloadsActivity}'s Bundle.
*/
public static final String BUNDLE_DOWNLOADS_TITLE = "DOWNLOADS_TITLE";
private static final String downloadsIndexUrl = "https://www.thmmy.gr/smf/index.php?action=tpmod;dl;";
private String downloadsUrl;
private String downloadsTitle;
private final ArrayList<Download> parsedDownloads = new ArrayList<>();
private MaterialProgressBar progressBar;
private RecyclerView recyclerView;
private DownloadsAdapter downloadsAdapter;
private FloatingActionButton uploadFAB;
private ParseDownloadPageTask parseDownloadPageTask;
private int numberOfPages = -1;
private int pagesLoaded = 0;
private boolean isLoadingMore;
private static final int visibleThreshold = 5;
private int lastVisibleItem, totalItemCount;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_downloads);
Bundle extras = getIntent().getExtras();
downloadsTitle = extras.getString(BUNDLE_DOWNLOADS_TITLE);
downloadsUrl = extras.getString(BUNDLE_DOWNLOADS_URL);
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);
Toast.makeText(this, "An error has occurred\nAborting.", Toast.LENGTH_SHORT).show();
finish();
}
} else downloadsUrl = downloadsIndexUrl;
//Initialize toolbar
toolbar = (Toolbar) findViewById(R.id.toolbar);
if (downloadsTitle == null || Objects.equals(downloadsTitle, ""))
toolbar.setTitle("Downloads");
toolbar.setTitle(downloadsTitle);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
createDrawer();
drawer.setSelection(DOWNLOADS_ID);
progressBar = (MaterialProgressBar) findViewById(R.id.progressBar);
recyclerView = (RecyclerView) findViewById(R.id.downloads_recycler_view);
recyclerView.setHasFixedSize(true);
final LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(layoutManager);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
layoutManager.getOrientation());
recyclerView.addItemDecoration(dividerItemDecoration);
downloadsAdapter = new DownloadsAdapter(getApplicationContext(), parsedDownloads);
recyclerView.setAdapter(downloadsAdapter);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
totalItemCount = layoutManager.getItemCount();
lastVisibleItem = layoutManager.findLastVisibleItemPosition();
if (!isLoadingMore && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
isLoadingMore = true;
onLoadMore();
}
}
});
uploadFAB = (FloatingActionButton) findViewById(R.id.download_fab);
uploadFAB.setEnabled(false);
uploadFAB.hide();
parseDownloadPageTask = new ParseDownloadPageTask();
parseDownloadPageTask.execute(downloadsUrl);
}
@Override
public void onLoadMore() {
if (pagesLoaded < numberOfPages) {
parsedDownloads.add(null);
downloadsAdapter.notifyItemInserted(parsedDownloads.size());
//Load data
parseDownloadPageTask = new ParseDownloadPageTask();
if (downloadsUrl.contains("tpstart"))
parseDownloadPageTask.execute(downloadsUrl.substring(0
, downloadsUrl.lastIndexOf(";tpstart=")) + ";tpstart=" + pagesLoaded * 10);
else parseDownloadPageTask.execute(downloadsUrl + ";tpstart=" + pagesLoaded * 10);
}
}
@Override
public void onBackPressed() {
if (drawer.isDrawerOpen()) {
drawer.closeDrawer();
return;
}
super.onBackPressed();
}
@Override
protected void onResume() {
drawer.setSelection(DOWNLOADS_ID);
super.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
recyclerView.setAdapter(null);
if (parseDownloadPageTask != null && parseDownloadPageTask.getStatus() != AsyncTask.Status.RUNNING)
parseDownloadPageTask.cancel(true);
}
/**
* An {@link AsyncTask} that handles asynchronous fetching of a downloads page and parsing it's
* data. {@link AsyncTask#onPostExecute(Object) OnPostExecute} method calls {@link RecyclerView#swapAdapter}
* to build graphics.
* <p>
* <p>Calling TopicTask's {@link AsyncTask#execute execute} method needs to have profile's url
* 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
protected void onPreExecute() {
if (!isLoadingMore) progressBar.setVisibility(ProgressBar.VISIBLE);
if (uploadFAB.getVisibility() != View.GONE) uploadFAB.setEnabled(false);
}
@Override
protected Void doInBackground(String... downloadsUrl) {
thisPageUrl = downloadsUrl[0];
Request request = new Request.Builder()
.url(downloadsUrl[0])
.build();
try {
Response response = BaseActivity.getClient().newCall(request).execute();
parseDownloads(Jsoup.parse(response.body().string()));
} catch (SSLHandshakeException e) {
Report.w(TAG, "Certificate problem (please switch to unsafe connection).");
} catch (Exception e) {
Report.e("TAG", "ERROR", e);
}
return null;
}
@Override
protected void onPostExecute(Void voids) {
if (downloadsTitle != null && !Objects.equals(downloadsTitle, "") &&
toolbar.getTitle() != downloadsTitle)
toolbar.setTitle(downloadsTitle);
++pagesLoaded;
if (uploadFAB.getVisibility() != View.GONE) uploadFAB.setEnabled(true);
progressBar.setVisibility(ProgressBar.INVISIBLE);
downloadsAdapter.notifyDataSetChanged();
isLoadingMore = false;
}
private void parseDownloads(Document downloadPage) {
if (downloadsTitle == null || Objects.equals(downloadsTitle, ""))
downloadsTitle = downloadPage.select("div.nav>b>a.nav").last().text();
//Removes loading item
if (isLoadingMore) {
if (parsedDownloads.size() > 0) parsedDownloads.remove(parsedDownloads.size() - 1);
}
Download.DownloadItemType type;
if (ThmmyPage.resolvePageCategory(Uri.parse(thisPageUrl)).is(ThmmyPage.
PageCategory.DOWNLOADS_CATEGORY))
type = Download.DownloadItemType.DOWNLOADS_CATEGORY;
else type = Download.DownloadItemType.DOWNLOADS_FILE;
Elements pages = downloadPage.select("a.navPages");
if (pages != null) {
for (Element page : pages) {
int pageNumber = Integer.parseInt(page.text());
if (pageNumber > numberOfPages) numberOfPages = pageNumber;
}
} else numberOfPages = 1;
Elements rows = downloadPage.select("table.tborder>tbody>tr");
if (type == Download.DownloadItemType.DOWNLOADS_CATEGORY) {
Elements navigationLinks = downloadPage.select("div.nav>b");
for (Element row : rows) {
if (row.select("td").size() == 1) continue;
String url = row.select("b>a").first().attr("href"),
title = row.select("b>a").first().text(),
subtitle = row.select("div.smalltext:not(:has(a))").text();
if (!row.select("td").last().hasClass("windowbg2")) {
if (navigationLinks.size() < 4) {
parsedDownloads.add(new Download(type, url, title, subtitle, null,
true, null));
} else {
String stats = row.text();
stats = stats.replace(title, "").replace(subtitle, "").trim();
parsedDownloads.add(new Download(type, url, title, subtitle, stats,
false, null));
}
} else {
String stats = row.text();
stats = stats.replace(title, "").replace(subtitle, "").trim();
parsedDownloads.add(new Download(type, url, title, subtitle, stats,
false, null));
}
}
} else {
parsedDownloads.add(new Download(type,
rows.select("b>a").first().attr("href"),
rows.select("b>a").first().text(),
rows.select("div.smalltext:not(:has(a))").text(),
rows.select("span:not(:has(a))").first().text(),
false,
rows.select("span:has(a)").first().text()));
}
}
}
}

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

@ -0,0 +1,187 @@
package gr.thmmy.mthmmy.activities.downloads;
import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Objects;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.model.Download;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWNLOADS_TITLE;
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;
private final Context context;
private ArrayList<Download> parsedDownloads = new ArrayList<>();
private final ArrayList<Boolean> downloadExpandableVisibility = new ArrayList<>();
DownloadsAdapter(Context context, ArrayList<Download> parsedDownloads) {
this.context = context;
this.parsedDownloads = parsedDownloads;
}
interface OnLoadMoreListener {
void onLoadMore();
}
@Override
public int getItemViewType(int position) {
return (parsedDownloads.get(position) == null) ? VIEW_TYPE_LOADING : VIEW_TYPE_DOWNLOAD;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_DOWNLOAD) {
View download = LayoutInflater.from(parent.getContext()).
inflate(R.layout.activity_downloads_row, parent, false);
return new DownloadViewHolder(download);
} else if (viewType == VIEW_TYPE_LOADING) {
View loading = LayoutInflater.from(parent.getContext()).
inflate(R.layout.recycler_loading_item, parent, false);
return new LoadingViewHolder(loading);
}
return null;
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
if (holder instanceof DownloadViewHolder) {
final Download download = parsedDownloads.get(position);
final DownloadViewHolder downloadViewHolder = (DownloadViewHolder) holder;
if (downloadExpandableVisibility.size() != parsedDownloads.size()) {
for (int i = downloadExpandableVisibility.size(); i < parsedDownloads.size(); ++i)
downloadExpandableVisibility.add(false);
}
if (download.getType() == Download.DownloadItemType.DOWNLOADS_CATEGORY) {
downloadViewHolder.downloadRow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(context, DownloadsActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_DOWNLOADS_URL, download.getUrl());
extras.putString(BUNDLE_DOWNLOADS_TITLE, download.getTitle());
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
});
if (downloadExpandableVisibility.get(downloadViewHolder.getAdapterPosition())) {
downloadViewHolder.informationExpandable.setVisibility(View.VISIBLE);
downloadViewHolder.informationExpandableBtn.setImageResource(R.drawable.ic_arrow_drop_up);
} else {
downloadViewHolder.informationExpandable.setVisibility(View.GONE);
downloadViewHolder.informationExpandableBtn.setImageResource(R.drawable.ic_arrow_drop_down);
}
downloadViewHolder.informationExpandableBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
final boolean visible = downloadExpandableVisibility.get(downloadViewHolder.
getAdapterPosition());
if (visible) {
downloadViewHolder.informationExpandable.setVisibility(View.GONE);
downloadViewHolder.informationExpandableBtn.setImageResource(R.drawable.ic_arrow_drop_down);
} else {
downloadViewHolder.informationExpandable.setVisibility(View.VISIBLE);
downloadViewHolder.informationExpandableBtn.setImageResource(R.drawable.ic_arrow_drop_up);
}
downloadExpandableVisibility.set(downloadViewHolder.getAdapterPosition(), !visible);
}
});
downloadViewHolder.title.setTypeface(Typeface.createFromAsset(context.getAssets()
, "fonts/fontawesome-webfont.ttf"));
if (download.hasSubCategory()) {
String tmp = context.getResources().getString(R.string.fa_folder) + " "
+ download.getTitle();
downloadViewHolder.title.setText(tmp);
} else {
String tmp = context.getResources().getString(R.string.fa_file) + " "
+ download.getTitle();
downloadViewHolder.title.setText(tmp);
}
} else {
//TODO implement download on click
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
downloadViewHolder.upperLinear.setBackgroundColor(context.getResources().getColor(R.color.background, null));
} else {
//noinspection deprecation
downloadViewHolder.upperLinear.setBackgroundColor(context.getResources().getColor(R.color.background));
}
downloadViewHolder.informationExpandable.setVisibility(View.VISIBLE);
downloadViewHolder.informationExpandableBtn.setVisibility(View.GONE);
downloadViewHolder.informationExpandableBtn.setEnabled(false);
downloadViewHolder.title.setText(download.getTitle());
}
downloadViewHolder.subTitle.setText(download.getSubTitle());
String tmp = download.getExtraInfo();
if (tmp != null && !Objects.equals(tmp, ""))
downloadViewHolder.extraInfo.setText(tmp);
else downloadViewHolder.extraInfo.setVisibility(View.GONE);
tmp = download.getStatNumbers();
if (tmp != null && !Objects.equals(tmp, ""))
downloadViewHolder.uploaderDate.setText(tmp);
else downloadViewHolder.uploaderDate.setVisibility(View.GONE);
} else if (holder instanceof LoadingViewHolder) {
LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder;
loadingViewHolder.progressBar.setIndeterminate(true);
}
}
@Override
public int getItemCount() {
return parsedDownloads.size();
}
private static class DownloadViewHolder extends RecyclerView.ViewHolder {
final LinearLayout upperLinear, downloadRow, informationExpandable;
final TextView title, subTitle, extraInfo, uploaderDate;
final ImageButton informationExpandableBtn;
DownloadViewHolder(View download) {
super(download);
upperLinear = (LinearLayout) download.findViewById(R.id.upper_linear);
downloadRow = (LinearLayout) download.findViewById(R.id.download_row);
informationExpandable = (LinearLayout) download.findViewById(R.id.child_board_expandable);
title = (TextView) download.findViewById(R.id.download_title);
subTitle = (TextView) download.findViewById(R.id.download_sub_title);
extraInfo = (TextView) download.findViewById(R.id.download_extra_info);
uploaderDate = (TextView) download.findViewById(R.id.download_uploader_date);
informationExpandableBtn = (ImageButton) download.findViewById(R.id.download_information_button);
}
}
private static class LoadingViewHolder extends RecyclerView.ViewHolder {
final MaterialProgressBar progressBar;
LoadingViewHolder(View itemView) {
super(itemView);
progressBar = (MaterialProgressBar) itemView.findViewById(R.id.recycler_progress_bar);
}
}
}

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

@ -1,6 +1,7 @@
package gr.thmmy.mthmmy.activities.main; package gr.thmmy.mthmmy.activities.main;
import android.content.Intent; import android.content.Intent;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.TabLayout; import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
@ -8,20 +9,29 @@ import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.LoginActivity; import gr.thmmy.mthmmy.activities.LoginActivity;
import gr.thmmy.mthmmy.activities.base.BaseActivity;
import gr.thmmy.mthmmy.activities.board.BoardActivity; import gr.thmmy.mthmmy.activities.board.BoardActivity;
import gr.thmmy.mthmmy.activities.downloads.DownloadsActivity;
import gr.thmmy.mthmmy.activities.main.forum.ForumFragment; import gr.thmmy.mthmmy.activities.main.forum.ForumFragment;
import gr.thmmy.mthmmy.activities.main.recent.RecentFragment; import gr.thmmy.mthmmy.activities.main.recent.RecentFragment;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.activities.topic.TopicActivity; import gr.thmmy.mthmmy.activities.topic.TopicActivity;
import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Board; import gr.thmmy.mthmmy.model.Board;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.model.TopicSummary; import gr.thmmy.mthmmy.model.TopicSummary;
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_TITLE; 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.board.BoardActivity.BUNDLE_BOARD_URL;
import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWNLOADS_TITLE;
import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWNLOADS_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.BUNDLE_TOPIC_TITLE; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_TITLE;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL;
@ -35,9 +45,13 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
final Intent intentFilter = getIntent();
redirectToActivityFromIntent(intentFilter);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
if (sessionManager.isLoginScreenDefault()) { if (sessionManager.isLoginScreenDefault())
{
//Go to login //Go to login
Intent intent = new Intent(MainActivity.this, LoginActivity.class); Intent intent = new Intent(MainActivity.this, LoginActivity.class);
startActivity(intent); startActivity(intent);
@ -46,7 +60,10 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
} }
//Initialize toolbar //Initialize toolbar
toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar = (Toolbar)
findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
//Initialize drawer //Initialize drawer
@ -63,6 +80,13 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
tabLayout.setupWithViewPager(mViewPager); tabLayout.setupWithViewPager(mViewPager);
} }
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
redirectToActivityFromIntent(intent);
setIntent(intent);
}
@Override @Override
protected void onResume() { protected void onResume() {
drawer.setSelection(HOME_ID); drawer.setSelection(HOME_ID);
@ -71,11 +95,10 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if(drawer.isDrawerOpen()){ if (drawer.isDrawerOpen()) {
drawer.closeDrawer(); drawer.closeDrawer();
return; return;
} } else if (mBackPressed + TIME_INTERVAL > System.currentTimeMillis()) {
else if (mBackPressed + TIME_INTERVAL > System.currentTimeMillis()) {
super.onBackPressed(); super.onBackPressed();
return; return;
} else { } else {
@ -117,10 +140,13 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
@Override @Override
public Fragment getItem(int position) { public Fragment getItem(int position) {
switch(position) { switch (position) {
case 0: return RecentFragment.newInstance(position +1); case 0:
case 1: return ForumFragment.newInstance(position +1); return RecentFragment.newInstance(position + 1);
default: return RecentFragment.newInstance(position +1); //temp (?) case 1:
return ForumFragment.newInstance(position + 1);
default:
return RecentFragment.newInstance(position + 1); //temp (?)
} }
} }
@ -144,4 +170,41 @@ public class MainActivity extends BaseActivity implements RecentFragment.RecentF
} }
//-------------------------------FragmentPagerAdapter END------------------------------------------- //-------------------------------FragmentPagerAdapter END-------------------------------------------
private void redirectToActivityFromIntent(Intent intent) {
if (intent != null) {
Uri uri = intent.getData();
if (uri != null) {
Log.d(TAG, uri.toString());
ThmmyPage.PageCategory page = ThmmyPage.resolvePageCategory(uri);
if (!page.is(ThmmyPage.PageCategory.NOT_THMMY)) {
if (page.is(ThmmyPage.PageCategory.BOARD)) {
Intent redirectIntent = new Intent(MainActivity.this, BoardActivity.class);
redirectIntent.putExtra(BUNDLE_BOARD_URL, uri.toString());
redirectIntent.putExtra(BUNDLE_BOARD_TITLE, "");
startActivity(redirectIntent);
} else if (page.is(ThmmyPage.PageCategory.TOPIC)) {
Intent redirectIntent = new Intent(MainActivity.this, TopicActivity.class);
redirectIntent.putExtra(BUNDLE_TOPIC_URL, uri.toString());
redirectIntent.putExtra(BUNDLE_TOPIC_TITLE, "");
startActivity(redirectIntent);
} else if (page.is(ThmmyPage.PageCategory.PROFILE)) {
Intent redirectIntent = new Intent(MainActivity.this, ProfileActivity.class);
redirectIntent.putExtra(BUNDLE_PROFILE_URL, uri.toString());
redirectIntent.putExtra(BUNDLE_PROFILE_THUMBNAIL_URL, "");
redirectIntent.putExtra(BUNDLE_PROFILE_USERNAME, "");
startActivity(redirectIntent);
} else if (page.is(ThmmyPage.PageCategory.DOWNLOADS)) {
Intent redirectIntent = new Intent(MainActivity.this, DownloadsActivity.class);
redirectIntent.putExtra(BUNDLE_DOWNLOADS_URL, uri.toString());
redirectIntent.putExtra(BUNDLE_DOWNLOADS_TITLE, "");
startActivity(redirectIntent);
} else if (!page.is(ThmmyPage.PageCategory.INDEX)) {
Toast.makeText(this, "This thmmy sector is not yet supported.", Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(this, "This is not thmmy.", Toast.LENGTH_LONG).show();
}
}
}
}
} }

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

@ -15,7 +15,7 @@ import com.bignerdranch.expandablerecyclerview.ParentViewHolder;
import java.util.List; import java.util.List;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.base.BaseFragment; import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.Board; import gr.thmmy.mthmmy.model.Board;
import gr.thmmy.mthmmy.model.Category; import gr.thmmy.mthmmy.model.Category;
import gr.thmmy.mthmmy.model.TopicSummary; import gr.thmmy.mthmmy.model.TopicSummary;

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

@ -24,8 +24,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.activities.base.BaseFragment; import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.Board; import gr.thmmy.mthmmy.model.Board;
import gr.thmmy.mthmmy.model.Category; import gr.thmmy.mthmmy.model.Category;
import gr.thmmy.mthmmy.session.SessionManager; import gr.thmmy.mthmmy.session.SessionManager;

2
app/src/main/java/gr/thmmy/mthmmy/activities/main/recent/RecentAdapter.java

@ -11,7 +11,7 @@ import android.widget.TextView;
import java.util.List; import java.util.List;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.base.BaseFragment; import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary; import gr.thmmy.mthmmy.model.TopicSummary;

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

@ -23,7 +23,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.base.BaseFragment; import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.TopicSummary; import gr.thmmy.mthmmy.model.TopicSummary;
import gr.thmmy.mthmmy.session.SessionManager; import gr.thmmy.mthmmy.session.SessionManager;
import gr.thmmy.mthmmy.utils.CustomRecyclerView; import gr.thmmy.mthmmy.utils.CustomRecyclerView;

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

@ -1,6 +1,5 @@
package gr.thmmy.mthmmy.activities.profile; package gr.thmmy.mthmmy.activities.profile;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
@ -12,8 +11,8 @@ import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.content.res.ResourcesCompat; import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
@ -33,14 +32,13 @@ import java.util.Objects;
import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLHandshakeException;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.LoginActivity;
import gr.thmmy.mthmmy.activities.base.BaseActivity;
import gr.thmmy.mthmmy.activities.profile.latestPosts.LatestPostsFragment; import gr.thmmy.mthmmy.activities.profile.latestPosts.LatestPostsFragment;
import gr.thmmy.mthmmy.activities.profile.stats.StatsFragment; import gr.thmmy.mthmmy.activities.profile.stats.StatsFragment;
import gr.thmmy.mthmmy.activities.profile.summary.SummaryFragment; import gr.thmmy.mthmmy.activities.profile.summary.SummaryFragment;
import gr.thmmy.mthmmy.activities.topic.TopicActivity; import gr.thmmy.mthmmy.activities.topic.TopicActivity;
import gr.thmmy.mthmmy.model.LinkTarget; import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.PostSummary; import gr.thmmy.mthmmy.model.PostSummary;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.utils.CircleTransform; import gr.thmmy.mthmmy.utils.CircleTransform;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import mthmmy.utils.Report; import mthmmy.utils.Report;
@ -53,8 +51,8 @@ import static gr.thmmy.mthmmy.activities.topic.TopicActivity.BUNDLE_TOPIC_URL;
/** /**
* Activity for user profile. When creating an Intent of this activity you need to bundle a <b>String</b> * Activity for user profile. When creating an Intent of this activity you need to bundle a <b>String</b>
* containing this user's profile url using the key {@link #BUNDLE_PROFILE_URL}, a <b>String</b> containing * containing this user's profile url using the key {@link #BUNDLE_PROFILE_URL}, a <b>String</b> containing
* this user's avatar url using the key {@link #BUNDLE_THUMBNAIL_URL} and a <b>String</b> containing * this user's avatar url using the key {@link #BUNDLE_PROFILE_THUMBNAIL_URL} and a <b>String</b> containing
* the username using the key {@link #BUNDLE_USERNAME}. * the username using the key {@link #BUNDLE_PROFILE_USERNAME}.
*/ */
public class ProfileActivity extends BaseActivity implements LatestPostsFragment.LatestPostsFragmentInteractionListener { public class ProfileActivity extends BaseActivity implements LatestPostsFragment.LatestPostsFragmentInteractionListener {
/** /**
@ -70,15 +68,16 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
* The key to use when putting user's thumbnail url String to {@link ProfileActivity}'s Bundle. * The key to use when putting user's thumbnail url String to {@link ProfileActivity}'s Bundle.
* If user doesn't have a thumbnail put an empty string or leave it null. * If user doesn't have a thumbnail put an empty string or leave it null.
*/ */
public static final String BUNDLE_THUMBNAIL_URL = "THUMBNAIL_URL"; public static final String BUNDLE_PROFILE_THUMBNAIL_URL = "THUMBNAIL_URL";
/** /**
* The key to use when putting username String to {@link ProfileActivity}'s Bundle. * The key to use when putting username String to {@link ProfileActivity}'s Bundle.
* If username is not available put an empty string or leave it null. * If username is not available put an empty string or leave it null.
*/ */
public static final String BUNDLE_USERNAME = "USERNAME"; public static final String BUNDLE_PROFILE_USERNAME = "USERNAME";
private static final int THUMBNAIL_SIZE = 200; private static final int THUMBNAIL_SIZE = 200;
private TextView usernameView; private TextView usernameView;
private ImageView thumbnailView;
private TextView personalTextView; private TextView personalTextView;
private MaterialProgressBar progressBar; private MaterialProgressBar progressBar;
private FloatingActionButton pmFAB; private FloatingActionButton pmFAB;
@ -87,6 +86,7 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
private ProfileTask profileTask; private ProfileTask profileTask;
private String personalText; private String personalText;
private String profileUrl; private String profileUrl;
private String thumbnailUrl;
private String username; private String username;
private int tabSelect; private int tabSelect;
@ -96,9 +96,10 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
setContentView(R.layout.activity_profile); setContentView(R.layout.activity_profile);
Bundle extras = getIntent().getExtras(); Bundle extras = getIntent().getExtras();
String thumbnailUrl = extras.getString(BUNDLE_THUMBNAIL_URL); thumbnailUrl = extras.getString(BUNDLE_PROFILE_THUMBNAIL_URL);
if (thumbnailUrl == null) thumbnailUrl = ""; if (thumbnailUrl == null) thumbnailUrl = "";
username = extras.getString(BUNDLE_USERNAME); Log.d(TAG, "thumbnailUrl = " + thumbnailUrl);
username = extras.getString(BUNDLE_PROFILE_USERNAME);
profileUrl = extras.getString(BUNDLE_PROFILE_URL); profileUrl = extras.getString(BUNDLE_PROFILE_URL);
//Initializes graphic elements //Initializes graphic elements
@ -114,7 +115,7 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
progressBar = (MaterialProgressBar) findViewById(R.id.progressBar); progressBar = (MaterialProgressBar) findViewById(R.id.progressBar);
ImageView thumbnailView = (ImageView) findViewById(R.id.user_thumbnail); thumbnailView = (ImageView) findViewById(R.id.user_thumbnail);
if (!Objects.equals(thumbnailUrl, "")) if (!Objects.equals(thumbnailUrl, ""))
//noinspection ConstantConditions //noinspection ConstantConditions
Picasso.with(this) Picasso.with(this)
@ -135,7 +136,8 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
pmFAB = (FloatingActionButton) findViewById(R.id.profile_fab); pmFAB = (FloatingActionButton) findViewById(R.id.profile_fab);
pmFAB.setEnabled(false); pmFAB.setEnabled(false);
if (!sessionManager.isLoggedIn()) pmFAB.hide(); pmFAB.hide();
/*if (!sessionManager.isLoggedIn()) pmFAB.hide();
else { else {
pmFAB.setOnClickListener(new View.OnClickListener() { pmFAB.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -163,18 +165,18 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
} }
} }
}); });
} }*/
LinkTarget.Target target = LinkTarget.resolveLinkTarget(Uri.parse(profileUrl)); ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(Uri.parse(profileUrl));
if (!target.is(LinkTarget.Target.PROFILE)) { if (!target.is(ThmmyPage.PageCategory.PROFILE)) {
Report.e(TAG, "Bundle came with a non profile url!\nUrl:\n" + profileUrl); Report.e(TAG, "Bundle came with a non profile url!\nUrl:\n" + profileUrl);
Toast.makeText(this, "An error has occurred\n Aborting.", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "An error has occurred\n Aborting.", Toast.LENGTH_SHORT).show();
finish(); finish();
} }
if (target.is(LinkTarget.Target.PROFILE_STATS)) { if (target.is(ThmmyPage.PageCategory.PROFILE_STATS)) {
profileUrl = profileUrl.substring(0, profileUrl.indexOf(";sa=statPanel")); profileUrl = profileUrl.substring(0, profileUrl.indexOf(";sa=statPanel"));
tabSelect = 2; tabSelect = 2;
} else if (target.is(LinkTarget.Target.PROFILE_LATEST_POSTS)) { } else if (target.is(ThmmyPage.PageCategory.PROFILE_LATEST_POSTS)) {
profileUrl = profileUrl.substring(0, profileUrl.indexOf(";sa=showPosts")); profileUrl = profileUrl.substring(0, profileUrl.indexOf(";sa=showPosts"));
tabSelect = 1; tabSelect = 1;
} }
@ -235,8 +237,18 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
if (username == null || Objects.equals(username, "")) { if (username == null || Objects.equals(username, "")) {
username = profilePage. username = profilePage.
select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tr"). select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) tr").
first().text(); first().select("td").last().text();
}
Log.d(TAG, "thumbnailUrl = " + thumbnailUrl);
if (thumbnailUrl == null || Objects.equals(thumbnailUrl, "")) { //Maybe there is an avatar
Log.d(TAG, "thumbnailUrl = " + thumbnailUrl);
Element profileAvatar = profilePage
.select(".bordercolor > tbody:nth-child(1) > tr:nth-child(2) img.avatar")
.first();
if (profileAvatar != null) thumbnailUrl = profileAvatar.attr("abs:src");
} }
Log.d(TAG, "thumbnailUrl = " + thumbnailUrl);
;
{ //Finds personal text { //Finds personal text
Element tmpEl = profilePage.select("td.windowbg:nth-child(2)").first(); Element tmpEl = profilePage.select("td.windowbg:nth-child(2)").first();
@ -270,6 +282,18 @@ public class ProfileActivity extends BaseActivity implements LatestPostsFragment
progressBar.setVisibility(ProgressBar.INVISIBLE); progressBar.setVisibility(ProgressBar.INVISIBLE);
if (usernameView.getText() != username) usernameView.setText(username); if (usernameView.getText() != username) usernameView.setText(username);
if (thumbnailUrl != null && !Objects.equals(thumbnailUrl, ""))
//noinspection ConstantConditions
Picasso.with(getApplicationContext())
.load(thumbnailUrl)
.resize(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
.centerCrop()
.error(ResourcesCompat.getDrawable(getResources()
, R.drawable.ic_default_user_thumbnail, null))
.placeholder(ResourcesCompat.getDrawable(getResources()
, R.drawable.ic_default_user_thumbnail, null))
.transform(new CircleTransform())
.into(thumbnailView);
if (personalText != null) personalTextView.setText(personalText); if (personalText != null) personalTextView.setText(personalText);
setupViewPager(viewPager, profilePage); setupViewPager(viewPager, profilePage);

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

@ -11,7 +11,7 @@ import android.widget.TextView;
import java.util.ArrayList; import java.util.ArrayList;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.base.BaseFragment; import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.PostSummary; import gr.thmmy.mthmmy.model.PostSummary;
import gr.thmmy.mthmmy.model.TopicSummary; import gr.thmmy.mthmmy.model.TopicSummary;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;

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

@ -21,8 +21,8 @@ import java.util.ArrayList;
import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLHandshakeException;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.activities.base.BaseFragment; import gr.thmmy.mthmmy.base.BaseFragment;
import gr.thmmy.mthmmy.model.PostSummary; import gr.thmmy.mthmmy.model.PostSummary;
import gr.thmmy.mthmmy.utils.ParseHelpers; import gr.thmmy.mthmmy.utils.ParseHelpers;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;

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

@ -38,7 +38,7 @@ import java.util.List;
import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLHandshakeException;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.base.BaseActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import mthmmy.utils.Report; import mthmmy.utils.Report;
import okhttp3.Request; import okhttp3.Request;
@ -248,11 +248,13 @@ public class StatsFragment extends Fragment {
postingActivityByTimeChartXAxis.setGranularity(1f); postingActivityByTimeChartXAxis.setGranularity(1f);
LineDataSet postingActivityByTimeDataSet = new LineDataSet(postingActivityByTime, null); LineDataSet postingActivityByTimeDataSet = new LineDataSet(postingActivityByTime, null);
if (isAdded()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
postingActivityByTimeDataSet.setFillDrawable(getResources().getDrawable(R.drawable.line_chart_gradient, null)); postingActivityByTimeDataSet.setFillDrawable(getResources().getDrawable(R.drawable.line_chart_gradient, null));
} else } else
//noinspection deprecation //noinspection deprecation
postingActivityByTimeDataSet.setFillDrawable(getResources().getDrawable(R.drawable.line_chart_gradient)); postingActivityByTimeDataSet.setFillDrawable(getResources().getDrawable(R.drawable.line_chart_gradient));
}
postingActivityByTimeDataSet.setDrawFilled(true); postingActivityByTimeDataSet.setDrawFilled(true);
postingActivityByTimeDataSet.setDrawCircles(false); postingActivityByTimeDataSet.setDrawCircles(false);
postingActivityByTimeDataSet.setDrawValues(false); postingActivityByTimeDataSet.setDrawValues(false);
@ -285,11 +287,13 @@ public class StatsFragment extends Fragment {
mostPopularBoardsByPostsChartYAxis.setGranularity(1f); mostPopularBoardsByPostsChartYAxis.setGranularity(1f);
BarDataSet mostPopularBoardsByPostsDataSet = new BarDataSet(mostPopularBoardsByPosts, null); BarDataSet mostPopularBoardsByPostsDataSet = new BarDataSet(mostPopularBoardsByPosts, null);
if (isAdded()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mostPopularBoardsByPostsDataSet.setColors(getResources().getColor(R.color.accent, null)); mostPopularBoardsByPostsDataSet.setColors(getResources().getColor(R.color.accent, null));
} else } else
//noinspection deprecation //noinspection deprecation
mostPopularBoardsByPostsDataSet.setColors(getResources().getColor(R.color.accent)); mostPopularBoardsByPostsDataSet.setColors(getResources().getColor(R.color.accent));
}
mostPopularBoardsByPostsDataSet.setDrawValues(false); mostPopularBoardsByPostsDataSet.setDrawValues(false);
mostPopularBoardsByPostsDataSet.setValueTextColor(Color.WHITE); mostPopularBoardsByPostsDataSet.setValueTextColor(Color.WHITE);
@ -324,11 +328,13 @@ public class StatsFragment extends Fragment {
mostPopularBoardsByActivityChartYAxis.setLabelCount(10, false); mostPopularBoardsByActivityChartYAxis.setLabelCount(10, false);
BarDataSet mostPopularBoardsByActivityDataSet = new BarDataSet(mostPopularBoardsByActivity, null); BarDataSet mostPopularBoardsByActivityDataSet = new BarDataSet(mostPopularBoardsByActivity, null);
if (isAdded()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mostPopularBoardsByActivityDataSet.setColors(getResources().getColor(R.color.accent, null)); mostPopularBoardsByActivityDataSet.setColors(getResources().getColor(R.color.accent, null));
} else } else
//noinspection deprecation //noinspection deprecation
mostPopularBoardsByActivityDataSet.setColors(getResources().getColor(R.color.accent)); mostPopularBoardsByActivityDataSet.setColors(getResources().getColor(R.color.accent));
}
mostPopularBoardsByActivityDataSet.setDrawValues(false); mostPopularBoardsByActivityDataSet.setDrawValues(false);
mostPopularBoardsByActivityDataSet.setValueTextColor(Color.WHITE); mostPopularBoardsByActivityDataSet.setValueTextColor(Color.WHITE);

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

@ -82,7 +82,7 @@ public class SummaryFragment extends Fragment {
Bundle savedInstanceState) { Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.fragment_summary, container, false); final View rootView = inflater.inflate(R.layout.fragment_summary, container, false);
mainContent = (LinearLayout) rootView.findViewById(R.id.profile_activity_content); mainContent = (LinearLayout) rootView.findViewById(R.id.profile_activity_content);
if (!parsedProfileSummaryData.isEmpty()) if (!parsedProfileSummaryData.isEmpty() && isAdded())
populateLayout(); populateLayout();
return rootView; return rootView;
} }
@ -126,7 +126,7 @@ public class SummaryFragment extends Fragment {
} }
protected void onPostExecute(Void result) { protected void onPostExecute(Void result) {
populateLayout(); if (isAdded()) populateLayout();
} }
/** /**
@ -187,12 +187,12 @@ public class SummaryFragment extends Fragment {
} }
TextView entry = new TextView(this.getContext()); TextView entry = new TextView(this.getContext());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
entry.setTextColor(getResources().getColor(R.color.primary_text, null)); entry.setTextColor(getResources().getColor(R.color.primary_text, null));
} else { else
//noinspection deprecation //noinspection deprecation
entry.setTextColor(getResources().getColor(R.color.primary_text)); entry.setTextColor(getResources().getColor(R.color.primary_text));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
entry.setText(Html.fromHtml(profileSummaryRow, Html.FROM_HTML_MODE_LEGACY)); entry.setText(Html.fromHtml(profileSummaryRow, Html.FROM_HTML_MODE_LEGACY));
} else { } else {

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

@ -1,13 +1,11 @@
package gr.thmmy.mthmmy.activities.topic; package gr.thmmy.mthmmy.activities.topic;
import android.content.DialogInterface; import android.graphics.Rect;
import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
@ -27,10 +25,10 @@ import java.util.ArrayList;
import java.util.Objects; import java.util.Objects;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.LoginActivity; import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.activities.base.BaseActivity; import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.LinkTarget;
import gr.thmmy.mthmmy.model.Post; import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.model.ThmmyPage;
import gr.thmmy.mthmmy.utils.ParseHelpers; import gr.thmmy.mthmmy.utils.ParseHelpers;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar; import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import mthmmy.utils.Report; import mthmmy.utils.Report;
@ -69,7 +67,6 @@ public class TopicActivity extends BaseActivity {
public static final ArrayList<Integer> toQuoteList = new ArrayList<>(); public static final ArrayList<Integer> toQuoteList = new ArrayList<>();
//Topic's pages //Topic's pages
private int thisPage = 1; private int thisPage = 1;
public static String base_url = "";
private int numberOfPages = 1; private int numberOfPages = 1;
private final SparseArray<String> pagesUrls = new SparseArray<>(); private final SparseArray<String> pagesUrls = new SparseArray<>();
//Page select //Page select
@ -87,9 +84,10 @@ public class TopicActivity extends BaseActivity {
private ImageButton nextPage; private ImageButton nextPage;
private ImageButton lastPage; private ImageButton lastPage;
//Other variables //Other variables
private FloatingActionButton replyFAB;
private MaterialProgressBar progressBar; private MaterialProgressBar progressBar;
private static String base_url = "";
private String topicTitle; private String topicTitle;
private FloatingActionButton replyFAB;
private String parsedTitle; private String parsedTitle;
private RecyclerView recyclerView; private RecyclerView recyclerView;
private String loadedPageUrl = ""; private String loadedPageUrl = "";
@ -102,10 +100,11 @@ public class TopicActivity extends BaseActivity {
Bundle extras = getIntent().getExtras(); Bundle extras = getIntent().getExtras();
topicTitle = extras.getString(BUNDLE_TOPIC_TITLE); topicTitle = extras.getString(BUNDLE_TOPIC_TITLE);
LinkTarget.Target target = LinkTarget.resolveLinkTarget( String topicPageUrl = extras.getString(BUNDLE_TOPIC_URL);
Uri.parse(extras.getString(BUNDLE_TOPIC_URL))); ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(
if (!target.is(LinkTarget.Target.TOPIC)) { Uri.parse(topicPageUrl));
Report.e(TAG, "Bundle came with a non topic url!\nUrl:\n" + extras.getString(BUNDLE_TOPIC_URL)); if (!target.is(ThmmyPage.PageCategory.TOPIC)) {
Report.e(TAG, "Bundle came with a non topic url!\nUrl:\n" + topicPageUrl);
Toast.makeText(this, "An error has occurred\n Aborting.", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "An error has occurred\n Aborting.", Toast.LENGTH_SHORT).show();
finish(); finish();
} }
@ -119,6 +118,9 @@ public class TopicActivity extends BaseActivity {
getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true);
} }
thisPageBookmark = new Bookmark(topicTitle, ThmmyPage.getTopicId(topicPageUrl));
thisPageBookmarkButton = (ImageButton) findViewById(R.id.bookmark);
setTopicBookmark();
createDrawer(); createDrawer();
progressBar = (MaterialProgressBar) findViewById(R.id.progressBar); progressBar = (MaterialProgressBar) findViewById(R.id.progressBar);
@ -129,13 +131,14 @@ public class TopicActivity extends BaseActivity {
recyclerView.setHasFixedSize(true); recyclerView.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext()); LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(layoutManager); recyclerView.setLayoutManager(layoutManager);
topicAdapter = new TopicAdapter(getApplicationContext(), progressBar, postsList, topicAdapter = new TopicAdapter(this, postsList,
topicTask); topicTask);
recyclerView.setAdapter(topicAdapter); recyclerView.setAdapter(topicAdapter);
replyFAB = (FloatingActionButton) findViewById(R.id.topic_fab); replyFAB = (FloatingActionButton) findViewById(R.id.topic_fab);
replyFAB.setEnabled(false); replyFAB.setEnabled(false);
if (!sessionManager.isLoggedIn()) replyFAB.hide(); replyFAB.hide();
/*if (!sessionManager.isLoggedIn()) replyFAB.hide();
else { else {
replyFAB.setOnClickListener(new View.OnClickListener() { replyFAB.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -163,7 +166,7 @@ public class TopicActivity extends BaseActivity {
} }
} }
}); });
} }*/
//Sets bottom navigation bar //Sets bottom navigation bar
firstPage = (ImageButton) findViewById(R.id.page_first_button); firstPage = (ImageButton) findViewById(R.id.page_first_button);
@ -176,11 +179,7 @@ public class TopicActivity extends BaseActivity {
initDecrementButton(previousPage, SMALL_STEP); initDecrementButton(previousPage, SMALL_STEP);
initIncrementButton(nextPage, SMALL_STEP); initIncrementButton(nextPage, SMALL_STEP);
initIncrementButton(lastPage, LARGE_STEP); initIncrementButton(lastPage, LARGE_STEP);
paginationEnabled(false);
firstPage.setEnabled(false);
previousPage.setEnabled(false);
nextPage.setEnabled(false);
lastPage.setEnabled(false);
//Gets posts //Gets posts
topicTask = new TopicTask(); topicTask = new TopicTask();
@ -210,26 +209,81 @@ public class TopicActivity extends BaseActivity {
topicTask.cancel(true); topicTask.cancel(true);
} }
//--------------------------------------BOTTOM NAV BAR METHODS-------------------------------------- //--------------------------------------BOTTOM NAV BAR METHODS----------------------------------
/**
* 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 final int step;
/**
* @param step number of pages to add/subtract on each repetition
*/
RepetitiveUpdater(int step) {
this.step = step;
}
public void run() {
long REPEAT_DELAY = 250;
if (autoIncrement) {
incrementPageRequestValue(step);
repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), REPEAT_DELAY);
} else if (autoDecrement) {
decrementPageRequestValue(step);
repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), REPEAT_DELAY);
}
}
}
private void paginationEnabled(boolean enabled) {
firstPage.setEnabled(enabled);
previousPage.setEnabled(enabled);
nextPage.setEnabled(enabled);
lastPage.setEnabled(enabled);
}
private void paginationEnabledExcept(boolean enabled, View exception) {
if (exception == firstPage) {
previousPage.setEnabled(enabled);
nextPage.setEnabled(enabled);
lastPage.setEnabled(enabled);
} else if (exception == previousPage) {
firstPage.setEnabled(enabled);
nextPage.setEnabled(enabled);
lastPage.setEnabled(enabled);
} else if (exception == nextPage) {
firstPage.setEnabled(enabled);
previousPage.setEnabled(enabled);
lastPage.setEnabled(enabled);
} else if (exception == lastPage) {
firstPage.setEnabled(enabled);
previousPage.setEnabled(enabled);
nextPage.setEnabled(enabled);
} else {
paginationEnabled(enabled);
}
}
private void initIncrementButton(ImageButton increment, final int step) { private void initIncrementButton(ImageButton increment, final int step) {
// Increment once for a click // Increment once for a click
increment.setOnClickListener(new View.OnClickListener() { increment.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
if (!autoIncrement && step == LARGE_STEP) { //If just clicked go to last page if (!autoIncrement && step == LARGE_STEP) {
changePage(numberOfPages - 1); changePage(numberOfPages - 1);
return; } else if (!autoIncrement) {
}
//Clicked and holden
autoIncrement = false; //Stop incrementing
incrementPageRequestValue(step); incrementPageRequestValue(step);
changePage(pageRequestValue - 1); changePage(pageRequestValue - 1);
} }
}
}); });
// Auto increment for a long click // Auto increment for a long click
increment.setOnLongClickListener( increment.setOnLongClickListener(
new View.OnLongClickListener() { new View.OnLongClickListener() {
public boolean onLongClick(View arg0) { public boolean onLongClick(View arg0) {
paginationEnabledExcept(false, arg0);
autoIncrement = true; autoIncrement = true;
repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), INITIAL_DELAY); repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), INITIAL_DELAY);
return false; return false;
@ -239,9 +293,21 @@ public class TopicActivity extends BaseActivity {
// When the button is released // When the button is released
increment.setOnTouchListener(new View.OnTouchListener() { increment.setOnTouchListener(new View.OnTouchListener() {
private Rect rect;
public boolean onTouch(View v, MotionEvent event) { public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP && autoIncrement) { if (event.getAction() == MotionEvent.ACTION_DOWN) {
rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
} else if (rect != null && event.getAction() == MotionEvent.ACTION_UP && autoIncrement) {
autoIncrement = false;
paginationEnabled(true);
changePage(pageRequestValue - 1); changePage(pageRequestValue - 1);
} else if (rect != null && event.getAction() == MotionEvent.ACTION_MOVE) {
if (!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) {
autoIncrement = false;
decrementPageRequestValue(pageRequestValue - thisPage);
paginationEnabled(true);
}
} }
return false; return false;
} }
@ -252,22 +318,20 @@ public class TopicActivity extends BaseActivity {
// Decrement once for a click // Decrement once for a click
decrement.setOnClickListener(new View.OnClickListener() { decrement.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
if (!autoDecrement && step == LARGE_STEP) { //If just clicked go to first page if (!autoDecrement && step == LARGE_STEP) {
changePage(0); changePage(0);
return; } else if (!autoDecrement) {
}
//Clicked and hold
autoDecrement = false; //Stop decrementing
decrementPageRequestValue(step); decrementPageRequestValue(step);
changePage(pageRequestValue - 1); changePage(pageRequestValue - 1);
} }
}
}); });
// Auto decrement for a long click // Auto decrement for a long click
decrement.setOnLongClickListener( decrement.setOnLongClickListener(
new View.OnLongClickListener() { new View.OnLongClickListener() {
public boolean onLongClick(View arg0) { public boolean onLongClick(View arg0) {
paginationEnabledExcept(false, arg0);
autoDecrement = true; autoDecrement = true;
repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), INITIAL_DELAY); repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), INITIAL_DELAY);
return false; return false;
@ -277,9 +341,21 @@ public class TopicActivity extends BaseActivity {
// When the button is released // When the button is released
decrement.setOnTouchListener(new View.OnTouchListener() { decrement.setOnTouchListener(new View.OnTouchListener() {
private Rect rect;
public boolean onTouch(View v, MotionEvent event) { public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP && autoDecrement) { if (event.getAction() == MotionEvent.ACTION_DOWN) {
rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
} else if (event.getAction() == MotionEvent.ACTION_UP && autoDecrement) {
autoDecrement = false;
paginationEnabled(true);
changePage(pageRequestValue - 1); changePage(pageRequestValue - 1);
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) {
autoIncrement = false;
incrementPageRequestValue(thisPage - pageRequestValue);
paginationEnabled(true);
}
} }
return false; return false;
} }
@ -333,15 +409,16 @@ public class TopicActivity extends BaseActivity {
private static final int OTHER_ERROR = 2; private static final int OTHER_ERROR = 2;
private static final int SAME_PAGE = 3; private static final int SAME_PAGE = 3;
@Override
protected void onPreExecute() { protected void onPreExecute() {
progressBar.setVisibility(ProgressBar.VISIBLE); progressBar.setVisibility(ProgressBar.VISIBLE);
paginationEnable(false); paginationEnabled(false);
if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(false); if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(false);
} }
protected Integer doInBackground(String... strings) { protected Integer doInBackground(String... strings) {
Document document; Document document;
base_url = strings[0].substring(0, strings[0].lastIndexOf(".")); //This topic's base url base_url = strings[0].substring(0, strings[0].lastIndexOf(".")); //New topic's base url
String newPageUrl = strings[0]; String newPageUrl = strings[0];
//Finds the index of message focus if present //Finds the index of message focus if present
@ -351,20 +428,30 @@ public class TopicActivity extends BaseActivity {
String tmp = newPageUrl.substring(newPageUrl.indexOf("msg") + 3); String tmp = newPageUrl.substring(newPageUrl.indexOf("msg") + 3);
if (tmp.contains(";")) if (tmp.contains(";"))
postFocus = Integer.parseInt(tmp.substring(0, tmp.indexOf(";"))); postFocus = Integer.parseInt(tmp.substring(0, tmp.indexOf(";")));
else else if (tmp.contains("#"))
postFocus = Integer.parseInt(tmp.substring(0, tmp.indexOf("#"))); postFocus = Integer.parseInt(tmp.substring(0, tmp.indexOf("#")));
} }
} }
//Checks if the page to be loaded is the one already shown //Checks if the page to be loaded is the one already shown
if (!Objects.equals(loadedPageUrl, "") && !loadedPageUrl.contains(base_url)) { if (!Objects.equals(loadedPageUrl, "") && loadedPageUrl.contains(base_url)) {
if (newPageUrl.contains("topicseen#new")) if (newPageUrl.contains("topicseen#new"))
if (Integer.parseInt(loadedPageUrl.substring(base_url.length())) == numberOfPages) if (thisPage == numberOfPages)
return SAME_PAGE; return SAME_PAGE;
if (Objects.equals(loadedPageUrl.substring(base_url.length()) if (newPageUrl.contains("msg")) {
, newPageUrl.substring(base_url.length()))) String tmpUrlSbstr = newPageUrl.substring(newPageUrl.indexOf("msg") + 3);
if (tmpUrlSbstr.contains("msg"))
tmpUrlSbstr = tmpUrlSbstr.substring(0, tmpUrlSbstr.indexOf("msg") - 1);
int testAgainst = Integer.parseInt(tmpUrlSbstr);
for (Post post : postsList) {
if (post.getPostIndex() == testAgainst) {
return SAME_PAGE; return SAME_PAGE;
} }
}
}
if (Integer.parseInt(newPageUrl.substring(base_url.length() + 1)) / 15 + 1 == thisPage)
return SAME_PAGE;
} else if (!Objects.equals(loadedPageUrl, "")) topicTitle = null;
loadedPageUrl = newPageUrl; loadedPageUrl = newPageUrl;
Request request = new Request.Builder() Request request = new Request.Builder()
@ -395,6 +482,11 @@ public class TopicActivity extends BaseActivity {
switch (parseResult) { switch (parseResult) {
case SUCCESS: case SUCCESS:
if (topicTitle == null || Objects.equals(topicTitle, "")) {
thisPageBookmark = new Bookmark(parsedTitle, ThmmyPage.getTopicId(loadedPageUrl));
setTopicBookmark();
}
progressBar.setVisibility(ProgressBar.INVISIBLE); progressBar.setVisibility(ProgressBar.INVISIBLE);
topicAdapter.customNotifyDataSetChanged(new TopicTask()); topicAdapter.customNotifyDataSetChanged(new TopicTask());
if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(true); if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(true);
@ -403,7 +495,7 @@ public class TopicActivity extends BaseActivity {
pageIndicator.setText(String.valueOf(thisPage) + "/" + String.valueOf(numberOfPages)); pageIndicator.setText(String.valueOf(thisPage) + "/" + String.valueOf(numberOfPages));
pageRequestValue = thisPage; pageRequestValue = thisPage;
paginationEnable(true); paginationEnabled(true);
if (topicTitle == null || Objects.equals(topicTitle, "")) if (topicTitle == null || Objects.equals(topicTitle, ""))
toolbar.setTitle(parsedTitle); toolbar.setTitle(parsedTitle);
@ -412,6 +504,11 @@ public class TopicActivity extends BaseActivity {
Toast.makeText(getBaseContext(), "Network Error", Toast.LENGTH_SHORT).show(); Toast.makeText(getBaseContext(), "Network Error", Toast.LENGTH_SHORT).show();
break; break;
case SAME_PAGE: case SAME_PAGE:
progressBar.setVisibility(ProgressBar.INVISIBLE);
topicAdapter.customNotifyDataSetChanged(new TopicTask());
if (replyFAB.getVisibility() != View.GONE) replyFAB.setEnabled(true);
paginationEnabled(true);
Toast.makeText(TopicActivity.this, "That's the same page.", Toast.LENGTH_SHORT).show();
//TODO change focus //TODO change focus
break; break;
default: default:
@ -462,37 +559,4 @@ public class TopicActivity extends BaseActivity {
//postsList = TopicParser.parseTopic(topic, language); //postsList = TopicParser.parseTopic(topic, language);
} }
} }
/**
* 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 final int step;
/**
* @param step number of pages to add/subtract on each repetition
*/
RepetitiveUpdater(int step) {
this.step = step;
}
public void run() {
long REPEAT_DELAY = 250;
if (autoIncrement) {
incrementPageRequestValue(step);
repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), REPEAT_DELAY);
} else if (autoDecrement) {
decrementPageRequestValue(step);
repeatUpdateHandler.postDelayed(new RepetitiveUpdater(step), REPEAT_DELAY);
}
}
}
private void paginationEnable(boolean enabled) {
firstPage.setEnabled(enabled);
previousPage.setEnabled(enabled);
nextPage.setEnabled(enabled);
lastPage.setEnabled(enabled);
}
} }

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

@ -7,21 +7,17 @@ import android.content.Intent;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.PowerManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.content.res.ResourcesCompat; import android.support.v4.content.res.ResourcesCompat;
import android.support.v7.widget.CardView; import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.webkit.MimeTypeMap;
import android.webkit.WebResourceRequest; import android.webkit.WebResourceRequest;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
@ -31,12 +27,9 @@ import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -44,20 +37,19 @@ import java.util.Objects;
import gr.thmmy.mthmmy.R; import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.board.BoardActivity; import gr.thmmy.mthmmy.activities.board.BoardActivity;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity; import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.model.LinkTarget; import gr.thmmy.mthmmy.base.BaseActivity;
import gr.thmmy.mthmmy.model.Post; 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 gr.thmmy.mthmmy.utils.CircleTransform;
import gr.thmmy.mthmmy.utils.FileManager.ThmmyFile;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
import mthmmy.utils.Report; import mthmmy.utils.Report;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_TITLE; import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_TITLE;
import static gr.thmmy.mthmmy.activities.board.BoardActivity.BUNDLE_BOARD_URL; 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_URL;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_THUMBNAIL_URL; import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_PROFILE_USERNAME;
import static gr.thmmy.mthmmy.activities.profile.ProfileActivity.BUNDLE_USERNAME;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.base_url;
import static gr.thmmy.mthmmy.activities.topic.TopicActivity.toQuoteList; import static gr.thmmy.mthmmy.activities.topic.TopicActivity.toQuoteList;
/** /**
@ -94,8 +86,6 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
* Index of state indicator in the boolean array. If true quote button for this post is checked. * Index of state indicator in the boolean array. If true quote button for this post is checked.
*/ */
private static final int isQuoteButtonChecked = 2; private static final int isQuoteButtonChecked = 2;
private final MaterialProgressBar progressBar;
private DownloadTask downloadTask;
private TopicActivity.TopicTask topicTask; private TopicActivity.TopicTask topicTask;
/** /**
@ -158,7 +148,7 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
* @param context the context of the {@link RecyclerView} * @param context the context of the {@link RecyclerView}
* @param postsList List of {@link Post} objects to use * @param postsList List of {@link Post} objects to use
*/ */
TopicAdapter(Context context, MaterialProgressBar progressBar, List<Post> postsList, TopicAdapter(Context context, List<Post> postsList,
TopicActivity.TopicTask topicTask) { TopicActivity.TopicTask topicTask) {
this.context = context; this.context = context;
this.postsList = postsList; this.postsList = postsList;
@ -168,8 +158,6 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
//Initializes properties, array's values will be false by default //Initializes properties, array's values will be false by default
viewProperties.add(new boolean[3]); viewProperties.add(new boolean[3]);
} }
this.progressBar = progressBar;
downloadTask = new DownloadTask();
this.topicTask = topicTask; this.topicTask = topicTask;
} }
@ -231,6 +219,7 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
} else //noinspection deprecation } else //noinspection deprecation
filesTextColor = context.getResources().getColor(R.color.accent); filesTextColor = context.getResources().getColor(R.color.accent);
holder.postFooter.removeAllViews();
for (final ThmmyFile attachedFile : currentPost.getAttachedFiles()) { for (final ThmmyFile attachedFile : currentPost.getAttachedFiles()) {
final TextView attached = new TextView(context); final TextView attached = new TextView(context);
attached.setTextSize(10f); attached.setTextSize(10f);
@ -245,8 +234,7 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
attached.setOnClickListener(new View.OnClickListener() { attached.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
downloadTask = new DownloadTask(); ((BaseActivity) context).launchDownloadService(attachedFile);
downloadTask.execute(attachedFile);
} }
}); });
@ -257,9 +245,26 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
holder.postFooter.removeAllViews(); holder.postFooter.removeAllViews();
} }
String mSpecialRank, mRank, mGender, mNumberOfPosts, mPersonalText;
int mNumberOfStars, mUserColor;
if (!currentPost.isDeleted()) { //Sets user's extra info if (!currentPost.isDeleted()) { //Sets user's extra info
String mSpecialRank = currentPost.getSpecialRank(), mRank = currentPost.getRank(), mGender = currentPost.getGender(), mNumberOfPosts = currentPost.getNumberOfPosts(), mPersonalText = currentPost.getPersonalText(); mSpecialRank = currentPost.getSpecialRank();
int mNumberOfStars = currentPost.getNumberOfStars(), mUserColor = currentPost.getUserColor(); 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) { if (!Objects.equals(mSpecialRank, "") && mSpecialRank != null) {
holder.specialRank.setText(mSpecialRank); holder.specialRank.setText(mSpecialRank);
@ -311,13 +316,14 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
} else holder.cardChildLinear.setBackground(null); } else holder.cardChildLinear.setBackground(null);
//Avoid's view's visibility recycling //Avoid's view's visibility recycling
if (viewProperties.get(position)[isUserExtraInfoVisibile]) { if (!currentPost.isDeleted() && viewProperties.get(position)[isUserExtraInfoVisibile]) {
holder.userExtraInfo.setVisibility(View.VISIBLE); holder.userExtraInfo.setVisibility(View.VISIBLE);
holder.userExtraInfo.setAlpha(1.0f); holder.userExtraInfo.setAlpha(1.0f);
} else { } else {
holder.userExtraInfo.setVisibility(View.GONE); holder.userExtraInfo.setVisibility(View.GONE);
holder.userExtraInfo.setAlpha(0.0f); holder.userExtraInfo.setAlpha(0.0f);
} }
if (!currentPost.isDeleted()) {
//Sets graphics behavior //Sets graphics behavior
holder.header.setOnClickListener(new View.OnClickListener() { holder.header.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -328,10 +334,10 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, currentPost.getProfileURL()); extras.putString(BUNDLE_PROFILE_URL, currentPost.getProfileURL());
if (currentPost.getThumbnailUrl() == null) if (currentPost.getThumbnailUrl() == null)
extras.putString(BUNDLE_THUMBNAIL_URL, ""); extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
else else
extras.putString(BUNDLE_THUMBNAIL_URL, currentPost.getThumbnailUrl()); extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, currentPost.getThumbnailUrl());
extras.putString(BUNDLE_USERNAME, currentPost.getAuthor()); extras.putString(BUNDLE_PROFILE_USERNAME, currentPost.getAuthor());
intent.putExtras(extras); intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent); context.startActivity(intent);
@ -354,7 +360,11 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
TopicAnimations.animateUserExtraInfoVisibility(v); TopicAnimations.animateUserExtraInfoVisibility(v);
} }
}); });
}//End of deleted profiles } else {
holder.header.setOnClickListener(null);
holder.userExtraInfo.setOnClickListener(null);
}
//Avoid's view's visibility recycling //Avoid's view's visibility recycling
if (viewProperties.get(position)[isPostDateAndNumberVisibile]) { //Expanded if (viewProperties.get(position)[isPostDateAndNumberVisibile]) { //Expanded
holder.postDateAndNumberExp.setVisibility(View.VISIBLE); holder.postDateAndNumberExp.setVisibility(View.VISIBLE);
@ -526,11 +536,11 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
private boolean handleUri(final Uri uri) { private boolean handleUri(final Uri uri) {
final String uriString = uri.toString(); final String uriString = uri.toString();
LinkTarget.Target target = LinkTarget.resolveLinkTarget(uri); ThmmyPage.PageCategory target = ThmmyPage.resolvePageCategory(uri);
if (target.is(LinkTarget.Target.TOPIC)) { if (target.is(ThmmyPage.PageCategory.TOPIC)) {
//This url points to a topic //This url points to a topic
//Checks if this is the current topic //Checks if this is the current topic
if (Objects.equals(uriString.substring(0, uriString.lastIndexOf(".")), base_url)) { /*if (Objects.equals(uriString.substring(0, uriString.lastIndexOf(".")), base_url)) {
//Gets uri's targeted message's index number //Gets uri's targeted message's index number
String msgIndexReq = uriString.substring(uriString.indexOf("msg") + 3); String msgIndexReq = uriString.substring(uriString.indexOf("msg") + 3);
if (msgIndexReq.contains("#")) if (msgIndexReq.contains("#"))
@ -545,10 +555,11 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
return true; return true;
} }
} }
} }*/
topicTask.execute(uri.toString()); topicTask.execute(uri.toString());
return true; return true;
} else if (target.is(LinkTarget.Target.BOARD)) { } else if (target.is(ThmmyPage.PageCategory.BOARD)) {
Intent intent = new Intent(context, BoardActivity.class); Intent intent = new Intent(context, BoardActivity.class);
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString(BUNDLE_BOARD_URL, uriString); extras.putString(BUNDLE_BOARD_URL, uriString);
@ -557,12 +568,12 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent); context.startActivity(intent);
return true; return true;
} else if (target.is(LinkTarget.Target.PROFILE)) { } else if (target.is(ThmmyPage.PageCategory.PROFILE)) {
Intent intent = new Intent(context, ProfileActivity.class); Intent intent = new Intent(context, ProfileActivity.class);
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, uriString); extras.putString(BUNDLE_PROFILE_URL, uriString);
extras.putString(BUNDLE_THUMBNAIL_URL, ""); extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
extras.putString(BUNDLE_USERNAME, ""); extras.putString(BUNDLE_PROFILE_USERNAME, "");
intent.putExtras(extras); intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK); intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent); context.startActivity(intent);
@ -610,58 +621,4 @@ class TopicAdapter extends RecyclerView.Adapter<TopicAdapter.MyViewHolder> {
return context.getResources().getString(R.string.fa_file); return context.getResources().getString(R.string.fa_file);
} }
private class DownloadTask extends AsyncTask<ThmmyFile, Void, String> {
//Class variables
/**
* Debug Tag for logging debug output to LogCat
*/
private static final String TAG = "DownloadTask"; //Separate tag for AsyncTask
private PowerManager.WakeLock mWakeLock;
@Override
protected void onPreExecute() {
super.onPreExecute();
//Locks CPU to prevent going off
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
getClass().getName());
mWakeLock.acquire();
progressBar.setVisibility(View.VISIBLE);
}
@Override
protected String doInBackground(ThmmyFile... files) {
try {
File tempFile = files[0].download(context);
if (tempFile != null) {
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
files[0].getExtension());
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(tempFile), mime);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
} catch (IOException e) {
Report.e(TAG, "Error while trying to download a file", e);
return e.toString();
} catch (OutOfMemoryError e) {
Report.e(TAG, "Error while trying to download a file", e);
return e.toString();
}
return null;
}
@Override
protected void onPostExecute(String result) {
mWakeLock.release();
if (result != null)
Toast.makeText(context, result, Toast.LENGTH_SHORT).show();
else
Toast.makeText(context, "Download complete", Toast.LENGTH_SHORT).show();
progressBar.setVisibility(View.INVISIBLE);
}
}
} }

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

@ -15,7 +15,7 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import gr.thmmy.mthmmy.model.Post; import gr.thmmy.mthmmy.model.Post;
import gr.thmmy.mthmmy.utils.FileManager.ThmmyFile; import gr.thmmy.mthmmy.model.ThmmyFile;
import gr.thmmy.mthmmy.utils.ParseHelpers; import gr.thmmy.mthmmy.utils.ParseHelpers;
import mthmmy.utils.Report; import mthmmy.utils.Report;

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

@ -0,0 +1,565 @@
package gr.thmmy.mthmmy.base;
import android.Manifest;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
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.View;
import android.widget.ImageButton;
import android.widget.Toast;
import com.mikepenz.fontawesome_typeface_library.FontAwesome;
import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.materialdrawer.AccountHeader;
import com.mikepenz.materialdrawer.AccountHeaderBuilder;
import com.mikepenz.materialdrawer.Drawer;
import com.mikepenz.materialdrawer.DrawerBuilder;
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IProfile;
import java.util.ArrayList;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.activities.AboutActivity;
import gr.thmmy.mthmmy.activities.BookmarkActivity;
import gr.thmmy.mthmmy.activities.LoginActivity;
import gr.thmmy.mthmmy.activities.downloads.DownloadsActivity;
import gr.thmmy.mthmmy.activities.main.MainActivity;
import gr.thmmy.mthmmy.activities.profile.ProfileActivity;
import gr.thmmy.mthmmy.model.Bookmark;
import gr.thmmy.mthmmy.model.ThmmyFile;
import gr.thmmy.mthmmy.services.DownloadService;
import gr.thmmy.mthmmy.session.SessionManager;
import okhttp3.OkHttpClient;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWNLOADS_TITLE;
import static gr.thmmy.mthmmy.activities.downloads.DownloadsActivity.BUNDLE_DOWNLOADS_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;
public abstract class BaseActivity extends AppCompatActivity {
// Client & Cookies
protected static OkHttpClient client;
//SessionManager
protected static SessionManager sessionManager;
//Bookmarks
private static final String BOOKMARKS_SHARED_PREFS = "bookmarksSharedPrefs";
private static final String BOOKMARKED_TOPICS_KEY = "bookmarkedTopicsKey";
private static final String BOOKMARKED_BOARDS_KEY = "bookmarkedBoardsKey";
protected Bookmark thisPageBookmark;
protected ImageButton thisPageBookmarkButton;
private SharedPreferences bookmarksFile;
private ArrayList<Bookmark> topicsBookmarked;
private ArrayList<Bookmark> boardsBookmarked;
private static Drawable bookmarked;
private static Drawable notBookmarked;
//Common UI elements
protected Toolbar toolbar;
protected Drawer drawer;
@Override
protected void onCreate(Bundle savedInstanceState) {
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();
if (sessionManager.isLoggedIn()) {
if (bookmarked == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
bookmarked = getResources().getDrawable(R.drawable.ic_bookmark_true, null);
} else //noinspection deprecation
bookmarked = getResources().getDrawable(R.drawable.ic_bookmark_true);
}
if (notBookmarked == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notBookmarked = getResources().getDrawable(R.drawable.ic_bookmark_false, null);
} else //noinspection deprecation
notBookmarked = getResources().getDrawable(R.drawable.ic_bookmark_false);
}
if (topicsBookmarked == null || boardsBookmarked == null) {
bookmarksFile = getSharedPreferences(BOOKMARKS_SHARED_PREFS, Context.MODE_PRIVATE);
loadSavedBookmarks();
}
}
}
@Override
protected void onResume() {
super.onResume();
updateDrawer();
}
@Override
protected void onPause() {
super.onPause();
if (drawer != null) //close drawer animation after returning to activity
drawer.closeDrawer();
}
public static OkHttpClient getClient() {
return client;
}
public static SessionManager getSessionManager() {
return sessionManager;
}
//TODO: move stuff below (?)
//------------------------------------------DRAWER STUFF----------------------------------------
protected static final int HOME_ID = 0;
protected static final int DOWNLOADS_ID = 1;
protected static final int BOOKMARKS_ID = 2;
protected static final int LOG_ID = 3;
protected static final int ABOUT_ID = 4;
private AccountHeader accountHeader;
private ProfileDrawerItem profileDrawerItem;
private PrimaryDrawerItem homeItem, downloadsItem, bookmarksItem, loginLogoutItem, aboutItem;
private IconicsDrawable homeIcon, homeIconSelected, downloadsIcon, downloadsIconSelected,
bookmarksIcon, bookmarksIconSelected, loginIcon, logoutIcon, aboutIcon,
aboutIconSelected;
/**
* Call only after initializing Toolbar
*/
protected void createDrawer() {
final int primaryColor = ContextCompat.getColor(this, R.color.iron);
final int selectedPrimaryColor = ContextCompat.getColor(this, R.color.primary_dark);
final int selectedSecondaryColor = ContextCompat.getColor(this, R.color.accent);
//Drawer Icons
homeIcon = new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_home)
.color(primaryColor);
homeIconSelected = new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_home)
.color(selectedSecondaryColor);
downloadsIcon = new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_download)
.color(primaryColor);
downloadsIconSelected = new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_download)
.color(selectedSecondaryColor);
bookmarksIcon = new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_bookmark)
.color(primaryColor);
bookmarksIconSelected = new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_bookmark)
.color(selectedSecondaryColor);
loginIcon = new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_sign_in)
.color(primaryColor);
logoutIcon = new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_sign_out)
.color(primaryColor);
aboutIcon = new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_info_circle)
.color(primaryColor);
aboutIconSelected = new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_info_circle)
.color(selectedSecondaryColor);
//Drawer Items
homeItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedPrimaryColor)
.withSelectedTextColor(selectedSecondaryColor)
.withIdentifier(HOME_ID)
.withName(R.string.home)
.withIcon(homeIcon)
.withSelectedIcon(homeIconSelected);
if (sessionManager.isLoggedIn()) //When logged in
{
loginLogoutItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedSecondaryColor)
.withIdentifier(LOG_ID)
.withName(R.string.logout)
.withIcon(logoutIcon)
.withSelectable(false);
downloadsItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedPrimaryColor)
.withSelectedTextColor(selectedSecondaryColor)
.withIdentifier(DOWNLOADS_ID)
.withName(R.string.downloads)
.withIcon(downloadsIcon)
.withSelectedIcon(downloadsIconSelected);
bookmarksItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedPrimaryColor)
.withSelectedTextColor(selectedSecondaryColor)
.withIdentifier(BOOKMARKS_ID)
.withName(R.string.bookmark)
.withIcon(bookmarksIcon)
.withSelectedIcon(bookmarksIconSelected);
} else
loginLogoutItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedSecondaryColor)
.withIdentifier(LOG_ID).withName(R.string.login)
.withIcon(loginIcon)
.withSelectable(false);
aboutItem = new PrimaryDrawerItem()
.withTextColor(primaryColor)
.withSelectedColor(selectedPrimaryColor)
.withSelectedTextColor(selectedSecondaryColor)
.withIdentifier(ABOUT_ID)
.withName(R.string.about)
.withIcon(aboutIcon)
.withSelectedIcon(aboutIconSelected);
//Profile
profileDrawerItem = new ProfileDrawerItem().withName(sessionManager.getUsername());
//AccountHeader
accountHeader = new AccountHeaderBuilder()
.withActivity(this)
.withCompactStyle(true)
.withSelectionListEnabledForSingleProfile(false)
.withHeaderBackground(R.color.primary)
.addProfiles(profileDrawerItem)
.withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() {
@Override
public boolean onProfileChanged(View view, IProfile profile, boolean currentProfile) {
if (sessionManager.isLoggedIn()) {
Intent intent = new Intent(BaseActivity.this, ProfileActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_PROFILE_URL, "https://www.thmmy.gr/smf/index.php?action=profile");
if (!sessionManager.hasAvatar())
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, "");
else
extras.putString(BUNDLE_PROFILE_THUMBNAIL_URL, sessionManager.getAvatarLink());
extras.putString(BUNDLE_PROFILE_USERNAME, sessionManager.getUsername());
intent.putExtras(extras);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
return false;
}
return true;
}
})
.build();
//Drawer
DrawerBuilder drawerBuilder = new DrawerBuilder()
.withActivity(this)
.withToolbar(toolbar)
.withDrawerWidthDp((int) BaseApplication.getInstance().getDpWidth() / 2)
.withSliderBackgroundColor(ContextCompat.getColor(this, R.color.primary_light))
.withAccountHeader(accountHeader)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
public boolean onItemClick(View view, int position, IDrawerItem drawerItem) {
if (drawerItem.equals(HOME_ID)) {
if (!(BaseActivity.this instanceof MainActivity)) {
Intent i = new Intent(BaseActivity.this, MainActivity.class);
startActivity(i);
}
} else if (drawerItem.equals(DOWNLOADS_ID)) {
if (!(BaseActivity.this instanceof DownloadsActivity)) {
Intent i = new Intent(BaseActivity.this, DownloadsActivity.class);
Bundle extras = new Bundle();
extras.putString(BUNDLE_DOWNLOADS_URL, "");
extras.putString(BUNDLE_DOWNLOADS_TITLE, null);
i.putExtras(extras);
startActivity(i);
}
} else if (drawerItem.equals(BOOKMARKS_ID)) {
if (!(BaseActivity.this instanceof BookmarkActivity)) {
Intent i = new Intent(BaseActivity.this, BookmarkActivity.class);
startActivity(i);
}
} else if (drawerItem.equals(LOG_ID)) {
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
{
Intent intent = new Intent(BaseActivity.this, LoginActivity.class);
startActivity(intent);
finish();
overridePendingTransition(R.anim.push_right_in, R.anim.push_right_out);
} else
new LogoutTask().execute();
} else if (drawerItem.equals(ABOUT_ID)) {
if (!(BaseActivity.this instanceof AboutActivity)) {
Intent i = new Intent(BaseActivity.this, AboutActivity.class);
startActivity(i);
}
}
drawer.closeDrawer();
return true;
}
});
if (sessionManager.isLoggedIn())
drawerBuilder.addDrawerItems(homeItem, bookmarksItem, downloadsItem, loginLogoutItem, aboutItem);
else
drawerBuilder.addDrawerItems(homeItem, loginLogoutItem, aboutItem);
drawer = drawerBuilder.build();
drawer.getActionBarDrawerToggle().setDrawerIndicatorEnabled(false);
drawer.setOnDrawerNavigationListener(new Drawer.OnDrawerNavigationListener() {
@Override
public boolean onNavigationClickListener(View clickedView) {
onBackPressed();
return true;
}
});
}
protected void updateDrawer() {
if (drawer != null) {
if (!sessionManager.isLoggedIn()) //When logged out or if user is guest
{
drawer.removeItem(DOWNLOADS_ID);
drawer.removeItem(BOOKMARKS_ID);
loginLogoutItem.withName(R.string.login).withIcon(loginIcon); //Swap logout with login
profileDrawerItem.withName(sessionManager.getUsername()).withIcon(new IconicsDrawable(this)
.icon(FontAwesome.Icon.faw_user)
.paddingDp(10)
.color(ContextCompat.getColor(this, R.color.primary_light))
.backgroundColor(ContextCompat.getColor(this, R.color.primary)));
} else {
loginLogoutItem.withName(R.string.logout).withIcon(logoutIcon); //Swap login with logout
profileDrawerItem.withName(sessionManager.getUsername()).withIcon(sessionManager.getAvatarLink());
}
accountHeader.updateProfile(profileDrawerItem);
drawer.updateItem(loginLogoutItem);
}
}
//-------------------------------------------LOGOUT-------------------------------------------------
/**
* 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
ProgressDialog progressDialog;
protected Integer doInBackground(Void... voids) {
return sessionManager.logout();
}
protected void onPreExecute() { //Show a progress dialog until done
progressDialog = new ProgressDialog(BaseActivity.this,
R.style.AppTheme_Dark_Dialog);
progressDialog.setCancelable(false);
progressDialog.setIndeterminate(true);
progressDialog.setMessage("Logging out...");
progressDialog.show();
}
protected void onPostExecute(Integer result) {
Toast.makeText(getBaseContext(), "Logged out successfully!", Toast.LENGTH_LONG).show();
updateDrawer();
progressDialog.dismiss();
}
}
//-----------------------------------------LOGOUT END-----------------------------------------------
//---------------------------------------------BOOKMARKS--------------------------------------------
protected ArrayList<Bookmark> getBoardsBookmarked() {
return boardsBookmarked;
}
protected ArrayList<Bookmark> getTopicsBookmarked() {
return topicsBookmarked;
}
protected void setTopicBookmark() {
if (thisPageBookmark.matchExists(topicsBookmarked)) {
thisPageBookmarkButton.setImageDrawable(bookmarked);
} else {
thisPageBookmarkButton.setImageDrawable(notBookmarked);
}
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() {
if (thisPageBookmark.matchExists(boardsBookmarked)) {
thisPageBookmarkButton.setImageDrawable(bookmarked);
} else {
thisPageBookmarkButton.setImageDrawable(notBookmarked);
}
thisPageBookmarkButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (thisPageBookmark.matchExists(boardsBookmarked)) {
thisPageBookmarkButton.setImageDrawable(notBookmarked);
Toast.makeText(BaseActivity.this, "Bookmark removed", Toast.LENGTH_SHORT).show();
} else {
thisPageBookmarkButton.setImageDrawable(bookmarked);
Toast.makeText(BaseActivity.this, "Bookmark added", Toast.LENGTH_SHORT).show();
}
toggleBoardToBookmarks(thisPageBookmark);
}
});
}
private void loadSavedBookmarks() {
String tmpString = bookmarksFile.getString(BOOKMARKED_TOPICS_KEY, null);
if (tmpString != null)
topicsBookmarked = Bookmark.arrayFromString(tmpString);
else {
topicsBookmarked = new ArrayList<>();
}
tmpString = bookmarksFile.getString(BOOKMARKED_BOARDS_KEY, null);
if (tmpString != null)
boardsBookmarked = Bookmark.arrayFromString(tmpString);
else {
boardsBookmarked = new ArrayList<>();
}
}
private void toggleBoardToBookmarks(Bookmark bookmark) {
if (boardsBookmarked == null) return;
if (bookmark.matchExists(boardsBookmarked)) {
boardsBookmarked.remove(bookmark.findIndex(boardsBookmarked));
} else boardsBookmarked.add(new Bookmark(bookmark.getTitle(), bookmark.getId()));
updateBoardBookmarks();
}
private void toggleTopicToBookmarks(Bookmark bookmark) {
if (topicsBookmarked == null) return;
if (bookmark.matchExists(topicsBookmarked)) {
topicsBookmarked.remove(bookmark.findIndex(topicsBookmarked));
} else {
topicsBookmarked.add(new Bookmark(bookmark.getTitle(), bookmark.getId()));
}
updateTopicBookmarks();
}
private void updateBoardBookmarks() {
String tmpString;
tmpString = Bookmark.arrayToString(boardsBookmarked);
SharedPreferences.Editor editor = bookmarksFile.edit();
editor.putString(BOOKMARKED_BOARDS_KEY, tmpString).apply();
}
private void updateTopicBookmarks() {
String tmpString;
tmpString = Bookmark.arrayToString(topicsBookmarked);
SharedPreferences.Editor editor = bookmarksFile.edit();
editor.putString(BOOKMARKED_TOPICS_KEY, tmpString).apply();
}
protected void removeBookmark(Bookmark bookmark) {
if (bookmark.matchExists(boardsBookmarked)) toggleBoardToBookmarks(bookmark);
else if (bookmark.matchExists(topicsBookmarked)) toggleTopicToBookmarks(bookmark);
}
//-------------------------------------------BOOKMARKS END------------------------------------------
//-------PERMS---------
private static final int PERMISSIONS_REQUEST_CODE = 69;
//True if permissions are OK
private boolean checkPerms() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE};
return !(checkSelfPermission(PERMISSIONS_STORAGE[0]) == PackageManager.PERMISSION_DENIED ||
checkSelfPermission(PERMISSIONS_STORAGE[1]) == PackageManager.PERMISSION_DENIED);
}
return true;
}
//Display popup gor user to grant permission
public 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,
Manifest.permission.WRITE_EXTERNAL_STORAGE};
requestPermissions(PERMISSIONS_STORAGE, PERMISSIONS_REQUEST_CODE);
}
}
@Override
public void onRequestPermissionsResult(int permsRequestCode, @NonNull String[] permissions
, @NonNull int[] grantResults) {
switch (permsRequestCode) {
case PERMISSIONS_REQUEST_CODE:
launchDownloadService();
break;
}
}
//----------------------------------DOWNLOAD----------------------
private ThmmyFile tempThmmyFile;
public void launchDownloadService(ThmmyFile thmmyFile) {
if (checkPerms())
DownloadService.startActionDownload(this, thmmyFile.getFileUrl().toString());
else {
tempThmmyFile = thmmyFile;
requestPerms();
}
}
//Uses temp file - called after permission grant
public void launchDownloadService() {
if (checkPerms())
DownloadService.startActionDownload(this, tempThmmyFile.getFileUrl().toString());
}
}

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

@ -0,0 +1,111 @@
package gr.thmmy.mthmmy.base;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.v4.content.ContextCompat;
import android.util.DisplayMetrics;
import android.widget.ImageView;
import com.franmontiel.persistentcookiejar.PersistentCookieJar;
import com.franmontiel.persistentcookiejar.cache.SetCookieCache;
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor;
import com.jakewharton.picasso.OkHttp3Downloader;
import com.mikepenz.fontawesome_typeface_library.FontAwesome;
import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader;
import com.mikepenz.materialdrawer.util.DrawerImageLoader;
import com.squareup.picasso.Picasso;
import java.util.concurrent.TimeUnit;
import gr.thmmy.mthmmy.R;
import gr.thmmy.mthmmy.session.SessionManager;
import okhttp3.OkHttpClient;
public class BaseApplication extends Application {
private static BaseApplication baseApplication; //BaseApplication singleton
// Client & SessionManager
private OkHttpClient client;
private SessionManager sessionManager;
//Shared Preferences
private final String SHARED_PREFS_NAME = "ThmmySharedPrefs";
//Display Metrics
private static float dpHeight, dpWidth;
public static BaseApplication getInstance(){
return baseApplication;
}
@Override
public void onCreate() {
super.onCreate();
baseApplication = this; //init singleton
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)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
sessionManager = new SessionManager(client, cookieJar, sharedPrefsCookiePersistor, sharedPrefs);
Picasso picasso = new Picasso.Builder(getApplicationContext())
.downloader(new OkHttp3Downloader(client))
.build();
Picasso.setSingletonInstance(picasso); //All following Picasso (with Picasso.with(Context context) requests will use this Picasso object
//Initialize and create the image loader logic
DrawerImageLoader.init(new AbstractDrawerImageLoader() {
@Override
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);
}
@Override
public Drawable placeholder(Context ctx, String tag) {
if (DrawerImageLoader.Tags.PROFILE.name().equals(tag)) {
return new IconicsDrawable(ctx).icon(FontAwesome.Icon.faw_user)
.paddingDp(10)
.color(ContextCompat.getColor(ctx, R.color.primary_light))
.backgroundColor(ContextCompat.getColor(ctx, R.color.primary));
}
return super.placeholder(ctx, tag);
}
});
DisplayMetrics displayMetrics = getApplicationContext().getResources().getDisplayMetrics();
dpHeight = displayMetrics.heightPixels / displayMetrics.density;
dpWidth = displayMetrics.widthPixels / displayMetrics.density;
}
public OkHttpClient getClient() {
return client;
}
public SessionManager getSessionManager() {
return sessionManager;
}
public float getDpHeight() {
return dpHeight;
}
public float getDpWidth() {
return dpWidth;
}
}

8
app/src/main/java/gr/thmmy/mthmmy/activities/base/BaseFragment.java → app/src/main/java/gr/thmmy/mthmmy/base/BaseFragment.java

@ -1,4 +1,4 @@
package gr.thmmy.mthmmy.activities.base; package gr.thmmy.mthmmy.base;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
@ -16,14 +16,16 @@ public abstract class BaseFragment extends Fragment {
private String TAG; private String TAG;
protected int sectionNumber; protected int sectionNumber;
protected OkHttpClient client; protected static OkHttpClient client;
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
TAG = getArguments().getString(ARG_TAG); TAG = getArguments().getString(ARG_TAG);
sectionNumber = getArguments().getInt(ARG_SECTION_NUMBER); sectionNumber = getArguments().getInt(ARG_SECTION_NUMBER);
client = BaseActivity.getClient(); if(client==null)
client = BaseApplication.getInstance().getClient(); //must check every time - e.g.
// becomes null when app restarts after crash
Report.d(TAG, "onCreate"); Report.d(TAG, "onCreate");
} }

73
app/src/main/java/gr/thmmy/mthmmy/model/Bookmark.java

@ -0,0 +1,73 @@
package gr.thmmy.mthmmy.model;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.Objects;
public class Bookmark implements java.io.Serializable {
private final String title, id;
public Bookmark(String title, String id) {
this.title = title;
this.id = id;
}
public String getTitle() {
return title;
}
public String getId() {
return id;
}
public boolean matchExists(ArrayList<Bookmark> array) {
if (array != null && !array.isEmpty()) {
for (Bookmark bookmark : array) {
if (bookmark != null) {
if (Objects.equals(bookmark.getId(), this.id)
&& Objects.equals(bookmark.getTitle(), this.title))
return true;
}
}
}
return false;
}
public int findIndex(ArrayList<Bookmark> array) {
if (array != null && !array.isEmpty()) {
for (int i = 0; i < array.size(); ++i) {
if (array.get(i) != null && Objects.equals(array.get(i).getId(), this.id)
&& Objects.equals(array.get(i).getTitle(), this.title))
return i;
}
}
return -1;
}
@Nullable
public static String arrayToString(@NonNull ArrayList<Bookmark> arrayList) {
String returnString = "";
for (Bookmark bookmark : arrayList) {
if (bookmark != null) {
returnString += (bookmark.getId() + "\t");
returnString += (bookmark.getTitle() + "\n");
}
}
if (!Objects.equals(returnString, "")) return returnString;
else return null;
}
public static ArrayList<Bookmark> arrayFromString(@NonNull String string) {
ArrayList<Bookmark> returnArray = new ArrayList<>();
String[] lines = string.split("\n");
for (String line : lines) {
if (line == null || line.isEmpty() || Objects.equals(line, "")) break;
String[] parameters = line.split("\t");
if (parameters.length != 2) break;
returnArray.add(new Bookmark(parameters[1], parameters[0]));
}
return returnArray;
}
}

58
app/src/main/java/gr/thmmy/mthmmy/model/Download.java

@ -0,0 +1,58 @@
package gr.thmmy.mthmmy.model;
public class Download {
public enum DownloadItemType {DOWNLOADS_CATEGORY, DOWNLOADS_FILE}
private final String url, title, subTitle, statNumbers, extraInfo;
private final boolean hasSubCategory;
private final DownloadItemType type;
public Download() {
type = null;
url = null;
title = null;
subTitle = null;
statNumbers = null;
hasSubCategory = false;
extraInfo = null;
}
public Download(DownloadItemType type, String url, String title, String subTitle,
String statNumbers, boolean hasSubCategory, String extraInfo) {
this.type = type;
this.url = url;
this.title = title;
this.subTitle = subTitle;
this.statNumbers = statNumbers;
this.hasSubCategory = hasSubCategory;
this.extraInfo = extraInfo;
}
public DownloadItemType getType() {
return type;
}
public String getUrl() {
return url;
}
public String getTitle() {
return title;
}
public String getSubTitle() {
return subTitle;
}
public String getStatNumbers() {
return statNumbers;
}
public String getExtraInfo() {
return extraInfo;
}
public boolean hasSubCategory() {
return hasSubCategory;
}
}

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

@ -5,8 +5,6 @@ import android.support.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects; import java.util.Objects;
import gr.thmmy.mthmmy.utils.FileManager.ThmmyFile;
/** /**
* Class that defines a topic's post. All member variables are declared final (thus no setters are * Class that defines a topic's post. All member variables are declared final (thus no setters are
* supplied). Class has two constructors and getter methods for all variables. * supplied). Class has two constructors and getter methods for all variables.

37
app/src/main/java/gr/thmmy/mthmmy/model/ThmmyFile.java

@ -0,0 +1,37 @@
package gr.thmmy.mthmmy.model;
import java.net.URL;
public class ThmmyFile {
/**
* Debug Tag for logging debug output to LogCat
*/
private static final String TAG = "ThmmyFile";
private final URL fileUrl;
private final String filename, fileInfo;
/**
* This constructor only creates a ThmmyFile object and <b>does not download</b> the file.
*
* @param fileUrl {@link URL} object with file's url
* @param filename {@link String} with desired file name
* @param fileInfo {@link String} with any extra information (like number of downloads)
*/
public ThmmyFile(URL fileUrl, String filename, String fileInfo) {
this.fileUrl = fileUrl;
this.filename = filename;
this.fileInfo = fileInfo;
}
public URL getFileUrl() {
return fileUrl;
}
public String getFilename() {
return filename;
}
public String getFileInfo() {
return fileInfo;
}
}

89
app/src/main/java/gr/thmmy/mthmmy/model/LinkTarget.java → app/src/main/java/gr/thmmy/mthmmy/model/ThmmyPage.java

@ -1,7 +1,6 @@
package gr.thmmy.mthmmy.model; package gr.thmmy.mthmmy.model;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull;
import java.util.Objects; import java.util.Objects;
@ -12,7 +11,7 @@ import mthmmy.utils.Report;
* classes). It can be used to resolve link targets as to whether they are pointing to the forum and * classes). It can be used to resolve link targets as to whether they are pointing to the forum and
* where in the forum they may point. * where in the forum they may point.
*/ */
public class LinkTarget { public class ThmmyPage {
/** /**
* Debug Tag for logging debug output to LogCat * Debug Tag for logging debug output to LogCat
*/ */
@ -23,6 +22,7 @@ public class LinkTarget {
* An enum describing a link's target by defining the types:<ul> * An enum describing a link's target by defining the types:<ul>
* <li>{@link #NOT_THMMY}</li> * <li>{@link #NOT_THMMY}</li>
* <li>{@link #THMMY}</li> * <li>{@link #THMMY}</li>
* <li>{@link #INDEX}</li>
* <li>{@link #UNKNOWN_THMMY}</li> * <li>{@link #UNKNOWN_THMMY}</li>
* <li>{@link #TOPIC}</li> * <li>{@link #TOPIC}</li>
* <li>{@link #BOARD}</li> * <li>{@link #BOARD}</li>
@ -33,7 +33,7 @@ public class LinkTarget {
* <li>{@link #PROFILE}</li> * <li>{@link #PROFILE}</li>
* </ul> * </ul>
*/ */
public enum Target { public enum PageCategory {
/** /**
* Link doesn't point to thmmy. * Link doesn't point to thmmy.
*/ */
@ -42,6 +42,10 @@ public class LinkTarget {
* Link points to thmmy. * Link points to thmmy.
*/ */
THMMY, THMMY,
/**
* Link points to thmmy index page/
*/
INDEX,
/** /**
* Link points to a thmmy page that's not (yet) supported by the app. * Link points to a thmmy page that's not (yet) supported by the app.
*/ */
@ -73,28 +77,44 @@ public class LinkTarget {
/** /**
* Link points to a profile. * Link points to a profile.
*/ */
PROFILE; PROFILE,
/**
* Link points to a download.
*/
DOWNLOADS_CATEGORY,
/**
* Link points to a download category.
*/
DOWNLOADS_FILE,
/**
* Link points to downloads.
*/
DOWNLOADS;
/** /**
* This method defines a custom equality check for {@link Target} enums. It does not check * This method defines a custom equality check for {@link PageCategory} enums. It does not check
* whether a url is equal to another. * whether a url is equal to another.
* <p>Method returns true if parameter's Target is the same as the object and in the specific * <p>Method returns true if parameter's Target is the same as the object and in the specific
* cases described below, false otherwise.</p><ul> * cases described below, false otherwise.</p><ul>
* <li>(Everything but {@link #NOT_THMMY}).is({@link #THMMY}) returns true</li> * <li>(Everything but {@link #NOT_THMMY}).is({@link #THMMY}) returns true</li>
* <li>{@link #PROFILE_SUMMARY}.is({@link #PROFILE}) returns true</li> * <li>{@link #PROFILE_SUMMARY}.is({@link #PROFILE}) returns true</li>
* <li>{@link #PROFILE_LATEST_POSTS}.is({@link #PROFILE}) returns true</li>
* <li>{@link #PROFILE_STATS}.is({@link #PROFILE}) returns true</li>
* <li>{@link #PROFILE}.is({@link #PROFILE_SUMMARY}) returns false</li> * <li>{@link #PROFILE}.is({@link #PROFILE_SUMMARY}) returns false</li>
* <li>{@link #PROFILE_LATEST_POSTS}.is({@link #PROFILE}) returns true</li>
* <li>{@link #PROFILE}.is({@link #PROFILE_LATEST_POSTS}) returns false</li> * <li>{@link #PROFILE}.is({@link #PROFILE_LATEST_POSTS}) returns false</li>
* <li>{@link #PROFILE}.is({@link #PROFILE_STATS}) returns false</li></ul> * <li>{@link #PROFILE_STATS}.is({@link #PROFILE}) returns true</li>
* <li>{@link #PROFILE}.is({@link #PROFILE_STATS}) returns false</li>
* <li>{@link #DOWNLOADS_CATEGORY}.is({@link #DOWNLOADS}) returns true</li>
* <li>{@link #DOWNLOADS}.is({@link #DOWNLOADS_CATEGORY}) returns false</li>
* <li>{@link #DOWNLOADS_FILE}.is({@link #DOWNLOADS}) returns true</li>
* <li>{@link #DOWNLOADS}.is({@link #DOWNLOADS_FILE}) returns false</li></ul>
* *
* @param other another Target * @param other another Target
* @return true if <b>enums</b> are equal, false otherwise * @return true if <b>enums</b> are equal, false otherwise
*/ */
public boolean is(Target other) { public boolean is(PageCategory other) {
return (this == PROFILE_LATEST_POSTS || return ((this == PROFILE_LATEST_POSTS || this == PROFILE_STATS || this == PROFILE_SUMMARY)
this == PROFILE_STATS || && other == PROFILE)
this == PROFILE_SUMMARY) && other == PROFILE || ((this == DOWNLOADS_FILE || this == DOWNLOADS_CATEGORY) && other == DOWNLOADS)
|| (this != NOT_THMMY && other == THMMY) || (this != NOT_THMMY && other == THMMY)
|| this == other; || this == other;
} }
@ -107,7 +127,7 @@ public class LinkTarget {
* @return true if url is pointing to thmmy, false otherwise * @return true if url is pointing to thmmy, false otherwise
*/ */
public static boolean isThmmy(Uri uri) { public static boolean isThmmy(Uri uri) {
return resolveLinkTarget(uri) != Target.NOT_THMMY; return resolvePageCategory(uri) != PageCategory.NOT_THMMY;
} }
/** /**
@ -116,24 +136,49 @@ public class LinkTarget {
* @param uri url to resolve * @param uri url to resolve
* @return resolved target * @return resolved target
*/ */
public static Target resolveLinkTarget(Uri uri) { public static PageCategory resolvePageCategory(Uri uri) {
final String host = uri.getHost(); final String host = uri.getHost();
final String uriString = uri.toString(); final String uriString = uri.toString();
if (Objects.equals(uriString, "thmmy.gr")) return PageCategory.INDEX;
if (Objects.equals(host, "www.thmmy.gr")) { if (Objects.equals(host, "www.thmmy.gr")) {
if (uriString.contains("topic=")) return Target.TOPIC; if (uriString.contains("topic=")) return PageCategory.TOPIC;
else if (uriString.contains("board=")) return Target.BOARD; else if (uriString.contains("board=")) return PageCategory.BOARD;
else if (uriString.contains("action=profile")) { else if (uriString.contains("action=profile")) {
if (uriString.contains(";sa=showPosts")) if (uriString.contains(";sa=showPosts"))
return Target.PROFILE_LATEST_POSTS; return PageCategory.PROFILE_LATEST_POSTS;
else if (uriString.contains(";sa=statPanel")) else if (uriString.contains(";sa=statPanel"))
return Target.PROFILE_STATS; return PageCategory.PROFILE_STATS;
else return Target.PROFILE_SUMMARY; else return PageCategory.PROFILE_SUMMARY;
} else if (uriString.contains("action=unread")) } else if (uriString.contains("action=unread"))
return Target.UNREAD_POSTS; return PageCategory.UNREAD_POSTS;
else if (uriString.contains("action=tpmod;dl=item"))
return PageCategory.DOWNLOADS_FILE;
else if (uriString.contains("action=tpmod;dl"))
return PageCategory.DOWNLOADS_CATEGORY;
else if (uriString.contains("action=forum") || Objects.equals(uriString, "www.thmmy.gr")
|| Objects.equals(uriString, "http://www.thmmy.gr")
|| 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); Report.v(TAG, "Unknown thmmy link found, link: " + uriString);
return Target.UNKNOWN_THMMY; return PageCategory.UNKNOWN_THMMY;
}
return PageCategory.NOT_THMMY;
}
public static String getBoardId(String boardUrl) {
if (resolvePageCategory(Uri.parse(boardUrl)) == PageCategory.BOARD) {
return boardUrl.substring(boardUrl.indexOf("board=") + 6, boardUrl.lastIndexOf("."));
}
return null;
}
public static String getTopicId(String topicUrl) {
if (resolvePageCategory(Uri.parse(topicUrl)) == PageCategory.TOPIC) {
String tmp = topicUrl.substring(topicUrl.indexOf("topic=") + 6);
return tmp.substring(0, tmp.indexOf("."));
} }
return Target.NOT_THMMY; return null;
} }
} }

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

@ -0,0 +1,84 @@
package gr.thmmy.mthmmy.receiver;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.NotificationCompat;
import android.webkit.MimeTypeMap;
import java.io.File;
import gr.thmmy.mthmmy.R;
import mthmmy.utils.Report;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static gr.thmmy.mthmmy.services.DownloadService.ACTION_DOWNLOAD;
import static gr.thmmy.mthmmy.services.DownloadService.COMPLETED;
import static gr.thmmy.mthmmy.services.DownloadService.EXTRA_DOWNLOAD_ID;
import static gr.thmmy.mthmmy.services.DownloadService.EXTRA_DOWNLOAD_STATE;
import static gr.thmmy.mthmmy.services.DownloadService.EXTRA_FILE_EXTENSION;
import static gr.thmmy.mthmmy.services.DownloadService.EXTRA_FILE_NAME;
import static gr.thmmy.mthmmy.services.DownloadService.EXTRA_NOTIFICATION_TEXT;
import static gr.thmmy.mthmmy.services.DownloadService.EXTRA_NOTIFICATION_TICKER;
import static gr.thmmy.mthmmy.services.DownloadService.EXTRA_NOTIFICATION_TITLE;
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() {
}
@Override
public void onReceive(Context context, Intent intent) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (intent.getAction().equals(ACTION_DOWNLOAD)) {
Bundle extras = intent.getExtras();
int id = extras.getInt(EXTRA_DOWNLOAD_ID);
String state = extras.getString(EXTRA_DOWNLOAD_STATE, "NONE");
String title = extras.getString(EXTRA_NOTIFICATION_TITLE);
String text = extras.getString(EXTRA_NOTIFICATION_TEXT);
String ticker = extras.getString(EXTRA_NOTIFICATION_TICKER);
builder.setContentTitle(title)
.setContentText(text)
.setTicker(ticker)
.setAutoCancel(true) //???
.setSmallIcon(R.mipmap.ic_launcher);
if (state.equals(STARTED))
builder.setOngoing(true);
else if (state.equals(COMPLETED)) {
String fileName = extras.getString(EXTRA_FILE_NAME, "NONE");
String extension = extras.getString(EXTRA_FILE_EXTENSION, "extension");
File file = new File(SAVE_DIR, fileName);
if (file.exists()) {
String type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
MimeTypeMap.getFileExtensionFromUrl(file.getAbsolutePath()));
Intent chooser = new Intent();
chooser.setAction(android.content.Intent.ACTION_VIEW);
chooser.setDataAndType(Uri.fromFile(file), type);
chooser.setFlags(FLAG_ACTIVITY_NEW_TASK);
chooser.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, chooser, PendingIntent.FLAG_CANCEL_CURRENT);
builder.setContentIntent(pendingIntent);
} else
Report.w(TAG, "File doesn't exist.");
}
Notification notification = builder.build();
notificationManager.notify(id, notification);
}
}
}

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

@ -0,0 +1,221 @@
package gr.thmmy.mthmmy.services;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Environment;
import android.support.annotation.NonNull;
import java.io.File;
import java.io.FileNotFoundException;
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;
/**
* An {@link IntentService} subclass for handling asynchronous task requests in
* a service on a separate handler thread.
*/
public class DownloadService extends IntentService {
private static final String TAG = "DownloadService";
private static int sDownloadId =0;
private Receiver receiver;
public static final String SAVE_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "mthmmy";
public static final String ACTION_DOWNLOAD = "gr.thmmy.mthmmy.services.action.DOWNLOAD";
public static final String EXTRA_DOWNLOAD_URL = "gr.thmmy.mthmmy.services.extra.DOWNLOAD_URL";
public static final String EXTRA_DOWNLOAD_ID = "gr.thmmy.mthmmy.services.extra.DOWNLOAD_ID";
public static final String EXTRA_DOWNLOAD_STATE = "gr.thmmy.mthmmy.services.extra.DOWNLOAD_STATE";
public static final String EXTRA_FILE_NAME = "gr.thmmy.mthmmy.services.extra.FILE_NAME";
public static final String EXTRA_FILE_EXTENSION = "gr.thmmy.mthmmy.services.extra.FILE_EXTENSION";
public static final String EXTRA_NOTIFICATION_TITLE = "gr.thmmy.mthmmy.services.extra.NOTIFICATION_TITLE";
public static final String EXTRA_NOTIFICATION_TEXT = "gr.thmmy.mthmmy.services.extra.NOTIFICATION_TEXT";
public static final String EXTRA_NOTIFICATION_TICKER = "gr.thmmy.mthmmy.services.extra.NOTIFICATION_TICKER";
public static final String STARTED = "Started";
public static final String COMPLETED = "Completed";
public static final String FAILED = "Failed";
public DownloadService() {
super("DownloadService");
}
@Override
public void onCreate() {
super.onCreate();
final IntentFilter filter = new IntentFilter(DownloadService.ACTION_DOWNLOAD);
receiver = new Receiver();
registerReceiver(receiver, filter);
}
@Override
public void onDestroy() {
super.onDestroy();
this.unregisterReceiver(receiver);
}
/**
* Starts this service to perform action Download with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
*/
public static void startActionDownload(Context context, String downloadUrl) {
Intent intent = new Intent(context, DownloadService.class);
intent.setAction(ACTION_DOWNLOAD);
intent.putExtra(EXTRA_DOWNLOAD_URL, downloadUrl);
context.startService(intent);
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_DOWNLOAD.equals(action)) {
final String downloadLink = intent.getStringExtra(EXTRA_DOWNLOAD_URL);
handleActionDownload(downloadLink);
}
}
}
/**
* Handle action Foo in the provided background thread with the provided
* parameters.
*/
private void handleActionDownload(String downloadLink) {
OkHttpClient client = BaseApplication.getInstance().getClient();
BufferedSink sink = null;
String fileName = "file";
String extension = "extension";
int downloadId = sDownloadId;
sDownloadId++;
try {
Request request = new Request.Builder().url(downloadLink).build();
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]"))
{
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!");
else
Report.e(TAG, "Couldn't create mTHMMY's directory...");
}
String nameFormat;
String[] tokens = fileName.split("\\.(?=[^\\.]+$)");
if(tokens.length!=2)
{
Report.w(TAG, "Couldn't get file extension...");
nameFormat = fileName + "(%d)";
}
else
{
nameFormat = tokens[0] + "(%d)." + tokens[1];
extension = tokens[1];
}
File file = new File(dirPath, fileName);
for (int i = 1;;i++) {
if (!file.exists()) {
break;
}
file = new File(dirPath, String.format(nameFormat, i));
}
fileName = file.getName();
Report.v(TAG, "Started saving file " + fileName);
sendNotification(downloadId, STARTED, fileName, extension);
sink = Okio.buffer(Okio.sink(file));
sink.writeAll(response.body().source());
sink.flush();
Report.i(TAG, "Download OK!");
sendNotification(downloadId, COMPLETED, fileName, extension);
}
else
Report.e(TAG, "Response not a binary file!");
}
catch (FileNotFoundException e){
Report.e(TAG, "FileNotFound", e);
Report.i(TAG, "Download failed...");
sendNotification(downloadId, FAILED, fileName, extension);
}
catch (IOException e){
Report.e(TAG, "IOException", e);
Report.i(TAG, "Download failed...");
sendNotification(downloadId, FAILED, fileName, extension);
} finally {
if (sink!= null) {
try {
sink.close();
} catch (IOException e) {
// Ignore - Significant errors should already have been reported
}
}
}
}
private void sendNotification(int downloadId, String type, @NonNull String fileName, String fileExtension)
{
Intent intent = new Intent(ACTION_DOWNLOAD);
switch (type) {
case STARTED: {
intent.putExtra(EXTRA_NOTIFICATION_TITLE, "Download Started");
intent.putExtra(EXTRA_NOTIFICATION_TEXT, "\"" + fileName + "\" downloading...");
intent.putExtra(EXTRA_NOTIFICATION_TICKER, "Downloading...");
break;
}
case COMPLETED: {
intent.putExtra(EXTRA_NOTIFICATION_TITLE, "Download Completed");
intent.putExtra(EXTRA_NOTIFICATION_TEXT, "\"" + fileName + "\" finished downloading.");
intent.putExtra(EXTRA_NOTIFICATION_TICKER, "Download Completed");
break;
}
case FAILED: {
intent.putExtra(EXTRA_NOTIFICATION_TITLE, "Download Failed");
intent.putExtra(EXTRA_NOTIFICATION_TEXT, "\"" + fileName + "\" failed.");
intent.putExtra(EXTRA_NOTIFICATION_TICKER, "Download Failed");
break;
}
default:{
Report.wtf(TAG, "Invalid notification case!");
return;
}
}
intent.putExtra(EXTRA_DOWNLOAD_ID, downloadId);
intent.putExtra(EXTRA_DOWNLOAD_STATE, type);
intent.putExtra(EXTRA_FILE_NAME, fileName);
intent.putExtra(EXTRA_FILE_EXTENSION, fileExtension);
sendBroadcast(intent);
}
}

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

@ -1,6 +1,7 @@
package gr.thmmy.mthmmy.session; package gr.thmmy.mthmmy.session;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.franmontiel.persistentcookiejar.PersistentCookieJar; import com.franmontiel.persistentcookiejar.PersistentCookieJar;
@ -8,7 +9,6 @@ import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersisto
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
import java.io.IOException; import java.io.IOException;
@ -27,17 +27,16 @@ import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
/** /**
This class handles all session related operations (e.g. login, logout) * This class handles all session related operations (e.g. login, logout)
and stores data to SharedPreferences (session information and cookies). * and stores data to SharedPreferences (session information and cookies).
*/ */
public class SessionManager public class SessionManager {
{
//Class TAG //Class TAG
private static final String TAG = "SessionManager"; private static final String TAG = "SessionManager";
//Generic constants //Generic constants
public static final HttpUrl indexUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php"); 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"); public static final HttpUrl forumUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=forum;theme=4");
private static final HttpUrl loginUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=login2"); private static final HttpUrl loginUrl = HttpUrl.parse("https://www.thmmy.gr/smf/index.php?action=login2");
private static final String guestName = "Guest"; private static final String guestName = "Guest";
@ -66,27 +65,25 @@ public class SessionManager
//Constructor //Constructor
public SessionManager(OkHttpClient client, PersistentCookieJar cookieJar, public SessionManager(OkHttpClient client, PersistentCookieJar cookieJar,
SharedPrefsCookiePersistor cookiePersistor, SharedPreferences sharedPrefs) SharedPrefsCookiePersistor cookiePersistor, SharedPreferences sharedPrefs) {
{
this.client = client; this.client = client;
this.cookiePersistor=cookiePersistor; this.cookiePersistor = cookiePersistor;
this.cookieJar = cookieJar; this.cookieJar = cookieJar;
this.sharedPrefs = sharedPrefs; this.sharedPrefs = sharedPrefs;
} }
//------------------------------------AUTH BEGINS---------------------------------------------- //------------------------------------AUTH BEGINS----------------------------------------------
/** /**
* Login function with two options: (username, password) or nothing (using saved cookies). * Login function with two options: (username, password) or nothing (using saved cookies).
* Always call it in a separate thread. * Always call it in a separate thread.
*/ */
public int login(String... strings) public int login(String... strings) {
{
Report.i(TAG, "Logging in..."); Report.i(TAG, "Logging in...");
//Build the login request for each case //Build the login request for each case
Request request; Request request;
if (strings.length == 2) if (strings.length == 2) {
{
clearSessionData(); clearSessionData();
String loginName = strings[0]; String loginName = strings[0];
@ -101,9 +98,7 @@ public class SessionManager
.url(loginUrl) .url(loginUrl)
.post(formBody) .post(formBody)
.build(); .build();
} } else {
else
{
request = new Request.Builder() request = new Request.Builder()
.url(loginUrl) .url(loginUrl)
.build(); .build();
@ -114,8 +109,9 @@ public class SessionManager
Response response = client.newCall(request).execute(); Response response = client.newCall(request).execute();
Document document = Jsoup.parse(response.body().string()); Document document = Jsoup.parse(response.body().string());
Element logoutButton = document.getElementById("logoutbtn"); //Attempt to find logout button Elements unreadRepliesLinks = document.select("a[href=https://www.thmmy.gr/smf/index.php?action=unreadreplies]");
if (logoutButton != null) //If logout button exists, login was successful
if (unreadRepliesLinks.size()>=2) //Normally it's just == 2, but who knows what can be posted by users
{ {
Report.i(TAG, "Login successful!"); Report.i(TAG, "Login successful!");
setPersistentCookieSession(); //Store cookies setPersistentCookieSession(); //Store cookies
@ -125,20 +121,17 @@ public class SessionManager
sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, false).apply(); sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, false).apply();
sharedPrefs.edit().putString(USERNAME, extractUserName(document)).apply(); sharedPrefs.edit().putString(USERNAME, extractUserName(document)).apply();
String avatar = extractAvatarLink(document); String avatar = extractAvatarLink(document);
if (avatar!=null) if (avatar != null) {
{ sharedPrefs.edit().putBoolean(HAS_AVATAR, true).apply();
sharedPrefs.edit().putBoolean(HAS_AVATAR,true).apply();
sharedPrefs.edit().putString(AVATAR_LINK, extractAvatarLink(document)).apply(); sharedPrefs.edit().putString(AVATAR_LINK, extractAvatarLink(document)).apply();
} } else
else sharedPrefs.edit().putBoolean(HAS_AVATAR, false).apply();
sharedPrefs.edit().putBoolean(HAS_AVATAR,false).apply();
sharedPrefs.edit().putString(LOGOUT_LINK, HttpUrl.parse(logoutButton.attr("href")).toString()).apply();
sharedPrefs.edit().putString(LOGOUT_LINK, extractLogoutLink(document)).apply();
return SUCCESS; return SUCCESS;
} } else {
else
{
Report.i(TAG, "Login failed."); Report.i(TAG, "Login failed.");
//Investigate login failure //Investigate login failure
@ -159,16 +152,13 @@ public class SessionManager
return FAILURE; return FAILURE;
} }
//Handle exception //Handle exception
} } catch (InterruptedIOException e) {
catch (InterruptedIOException e){
Report.i(TAG, "Login InterruptedIOException"); //users cancels LoginTask Report.i(TAG, "Login InterruptedIOException"); //users cancels LoginTask
return CANCELLED; return CANCELLED;
} } catch (IOException e) {
catch (IOException e) {
Report.w(TAG, "Login IOException", e); Report.w(TAG, "Login IOException", e);
return CONNECTION_ERROR; return CONNECTION_ERROR;
} } catch (Exception e) {
catch (Exception e) {
Report.w(TAG, "Login Exception (other)", e); Report.w(TAG, "Login Exception (other)", e);
return EXCEPTION; return EXCEPTION;
} }
@ -183,17 +173,14 @@ public class SessionManager
* Always call it in a separate thread in a way that won't hinder performance (e.g. after * Always call it in a separate thread in a way that won't hinder performance (e.g. after
* fragments' data are retrieved). * fragments' data are retrieved).
*/ */
public void validateSession() public void validateSession() {
{
Report.i(TAG, "Validating session..."); Report.i(TAG, "Validating session...");
if(isLoggedIn()) if (isLoggedIn()) {
{
int loginResult = login(); int loginResult = login();
if(loginResult != FAILURE) if (loginResult != FAILURE)
return; return;
} } else if (isLoginScreenDefault())
else if(isLoginScreenDefault())
return; return;
sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, true).apply(); sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, true).apply();
@ -203,8 +190,7 @@ public class SessionManager
/** /**
* Call this function when user explicitly chooses to continue as a guest (UI thread). * Call this function when user explicitly chooses to continue as a guest (UI thread).
*/ */
public void guestLogin() public void guestLogin() {
{
Report.i("TAG", "Continuing as a guest, as chosen by the user."); Report.i("TAG", "Continuing as a guest, as chosen by the user.");
clearSessionData(); clearSessionData();
sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, false).apply(); sharedPrefs.edit().putBoolean(LOGIN_SCREEN_AS_DEFAULT, false).apply();
@ -214,12 +200,11 @@ public class SessionManager
/** /**
* Logout function. Always call it in a separate thread. * Logout function. Always call it in a separate thread.
*/ */
public int logout() public int logout() {
{
Report.i(TAG, "Logging out..."); Report.i(TAG, "Logging out...");
Request request = new Request.Builder() Request request = new Request.Builder()
.url(sharedPrefs.getString(LOGOUT_LINK,"LogoutLink")) .url(sharedPrefs.getString(LOGOUT_LINK, "LogoutLink"))
.build(); .build();
try { try {
@ -271,15 +256,17 @@ public class SessionManager
return sharedPrefs.getBoolean(LOGIN_SCREEN_AS_DEFAULT, true); return sharedPrefs.getBoolean(LOGIN_SCREEN_AS_DEFAULT, true);
} }
public String getCookieHeader() {
return cookiePersistor.loadAll().get(0).toString();
}
//--------------------------------------GETTERS END--------------------------------------------- //--------------------------------------GETTERS END---------------------------------------------
//------------------------------------OTHER FUNCTIONS------------------------------------------- //------------------------------------OTHER FUNCTIONS-------------------------------------------
private void setPersistentCookieSession() private void setPersistentCookieSession() {
{
List<Cookie> cookieList = cookieJar.loadForRequest(indexUrl); List<Cookie> cookieList = cookieJar.loadForRequest(indexUrl);
if (cookieList.size() == 2) if (cookieList.size() == 2) {
{
if ((cookieList.get(0).name().equals("THMMYgrC00ki3")) if ((cookieList.get(0).name().equals("THMMYgrC00ki3"))
&& (cookieList.get(1).name().equals("PHPSESSID"))) { && (cookieList.get(1).name().equals("PHPSESSID"))) {
Cookie.Builder builder = new Cookie.Builder(); Cookie.Builder builder = new Cookie.Builder();
@ -295,19 +282,17 @@ public class SessionManager
} }
} }
private void clearSessionData() private void clearSessionData() {
{
cookieJar.clear(); cookieJar.clear();
sharedPrefs.edit().clear().apply(); //Clear session data sharedPrefs.edit().clear().apply(); //Clear session data
sharedPrefs.edit().putString(USERNAME, guestName).apply(); sharedPrefs.edit().putString(USERNAME, guestName).apply();
sharedPrefs.edit().putBoolean(LOGGED_IN, false).apply(); //User logs out sharedPrefs.edit().putBoolean(LOGGED_IN, false).apply(); //User logs out
Report.i(TAG,"Session data cleared."); Report.i(TAG, "Session data cleared.");
} }
@Nullable @Nullable
private String extractUserName(Document doc) private String extractUserName(@NonNull Document doc) {
{ //Scribbles2 Theme
if (doc != null) {
Elements user = doc.select("div[id=myuser] > h3"); Elements user = doc.select("div[id=myuser] > h3");
if (user.size() == 1) { if (user.size() == 1) {
@ -318,22 +303,45 @@ public class SessionManager
if (matcher.find()) if (matcher.find())
return matcher.group(1); return matcher.group(1);
} }
else
{
//Helios_Multi and SMF_oneBlue
user = doc.select("td.smalltext[width=100%] b");
if (user.size() == 1)
return user.first().ownText();
else
{
//SMF Default Theme
user = doc.select("td.titlebg2[height=32] b");
if (user.size() == 1)
return user.first().ownText();
}
} }
Report.w(TAG,"Extracting username failed!");
Report.e(TAG, "Extracting username failed!");
return null; return null;
} }
@Nullable @Nullable
private String extractAvatarLink(Document doc) private String extractAvatarLink(@NonNull Document doc) {
{ Elements avatar = doc.getElementsByClass("avatar");
if (doc != null) { if (!avatar.isEmpty())
Elements avatar = doc.select("#ava img"); return avatar.first().attr("src");
if (avatar.size() == 1) { Report.e(TAG, "Extracting avatar's link failed!");
return avatar.attr("src"); return null;
}
} }
Report.w(TAG,"Extracting avatar's link failed!");
@Nullable
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())
return logoutLink.first().attr("href");
Report.e(TAG, "Extracting logout link failed!");
return null; return null;
} }
//----------------------------------OTHER FUNCTIONS END----------------------------------------- //----------------------------------OTHER FUNCTIONS END-----------------------------------------

194
app/src/main/java/gr/thmmy/mthmmy/utils/FileManager/ThmmyFile.java

@ -1,194 +0,0 @@
package gr.thmmy.mthmmy.utils.FileManager;
import android.content.Context;
import android.os.Environment;
import android.os.StatFs;
import android.support.annotation.Nullable;
import android.webkit.MimeTypeMap;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Objects;
import mthmmy.utils.Report;
import okhttp3.Request;
import okhttp3.Response;
import static gr.thmmy.mthmmy.activities.base.BaseActivity.getClient;
/**
* Used for downloading and storing a file from the forum using {@link okhttp3}.
* <p>Class has one constructor, {@link #ThmmyFile(URL, String, String)}.
*/
public class ThmmyFile {
/**
* Debug Tag for logging debug output to LogCat
*/
private static final String TAG = "ThmmyFile";
private final URL fileUrl;
private final String filename, fileInfo;
private String extension, filePath;
private File file;
/**
* This constructor only creates a empty ThmmyFile object and <b>does not download</b> the file. To download
* the file use {@link #download(Context)} after setting file's url!
*/
public ThmmyFile() {
this.fileUrl = null;
this.filename = null;
this.fileInfo = null;
this.extension = null;
this.filePath = null;
this.file = null;
}
/**
* This constructor only creates a ThmmyFile object and <b>does not download</b> the file. To download
* the file use {@link #download(Context)}!
*
* @param fileUrl {@link URL} object with file's url
* @param filename {@link String} with desired file name
* @param fileInfo {@link String} with any extra information (like number of downloads)
*/
public ThmmyFile(URL fileUrl, String filename, String fileInfo) {
this.fileUrl = fileUrl;
this.filename = filename;
this.fileInfo = fileInfo;
this.extension = null;
this.filePath = null;
this.file = null;
}
public URL getFileUrl() {
return fileUrl;
}
public String getFilename() {
return filename;
}
public String getFileInfo() {
return fileInfo;
}
/**
* This is null until {@link #download(Context)} is called and has succeeded.
*
* @return String with file's extension or null
*/
@Nullable
public String getExtension() {
return extension;
}
/**
* This is null until {@link #download(Context)} is called and has succeeded.
*
* @return String with file's path or null
*/
@Nullable
public String getFilePath() {
return filePath;
}
/**
* This is null until {@link #download(Context)} is called and has succeeded.
*
* @return {@link File} or null
*/
@Nullable
public File getFile() {
return file;
}
private void setExtension(String extension) {
this.extension = extension;
}
private void setFilePath(String filePath) {
this.filePath = filePath;
}
/**
* Used to download the file. If download is successful file's extension and path will be assigned
* to object's fields and can be accessed using getter methods.
* <p>File is stored in sdcard1/Android/data/Downloads/packageName</p>
*
* @return the {@link File} if successful, null otherwise
* @throws IOException if the request could not be executed due to cancellation, a connectivity
* problem or timeout. Because networks can fail during an exchange, it is possible that the
* remote server accepted the request before the failure.
* @throws SecurityException if the requested file is not hosted by the forum.
*/
@Nullable
public File download(Context context) throws IOException, SecurityException, OutOfMemoryError {
if (fileUrl == null) {
return null;
}
if (!Objects.equals(fileUrl.getHost(), "www.thmmy.gr"))
throw new SecurityException("Downloading files from other sources is not supported");
Request request = new Request.Builder().url(fileUrl).build();
Response response = getClient().newCall(request).execute();
if (!response.isSuccessful()) {
throw new IOException("Failed to download file: " + response);
}
file = getOutputMediaFile(context, filename, fileInfo);
if (file == null) {
Report.d(TAG, "Error creating media file, check storage permissions!");
} else {
FileOutputStream fos = new FileOutputStream(file);
fos.write(response.body().bytes());
fos.close();
filePath = file.getAbsolutePath();
extension = MimeTypeMap.getFileExtensionFromUrl(
filePath.substring(filePath.lastIndexOf("/")));
}
return file;
}
@Nullable
private File getOutputMediaFile(Context context, String fileName, String fileInfo) throws OutOfMemoryError, IOException {
File mediaStorageDir;
String extState = Environment.getExternalStorageState();
if (Environment.isExternalStorageRemovable() &&
Objects.equals(extState, Environment.MEDIA_MOUNTED)) {
mediaStorageDir = new File(Environment.getExternalStorageDirectory()
+ "/Android/data/gr.thmmy.mthmmy/"
+ "Downloads/");
} else {
mediaStorageDir = new File(context.getFilesDir(), "Downloads");
}
//Creates the storage directory if it does not exist
if (!mediaStorageDir.exists()) {
if (!mediaStorageDir.mkdirs()) {
Report.d(TAG, "problem!");
throw new IOException("Error.\nCouldn't create the path!");
}
}
if (fileInfo != null) {
if (fileInfo.contains("KB")) {
float fileSize = Float.parseFloat(fileInfo
.substring(fileInfo.indexOf("(") + 1, fileInfo.indexOf("KB") - 1));
StatFs stat = new StatFs(mediaStorageDir.getPath());
long bytesAvailable = stat.getBlockSizeLong() * stat.getAvailableBlocksLong();
if ((bytesAvailable / 1024.f) < fileSize)
throw new OutOfMemoryError("There is not enough memory!");
}
}
//Creates a media file name
File mediaFile;
mediaFile = new File(mediaStorageDir.getPath() + File.separator + fileName);
return mediaFile;
}
}

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

@ -6,7 +6,6 @@ import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewCompat;
import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewPropertyAnimator; import android.view.ViewPropertyAnimator;

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 344 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

11
app/src/main/res/drawable/ic_file_upload.xml

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z"/>
</vector>

10
app/src/main/res/layout/activity_board.xml

@ -22,6 +22,16 @@
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
app:popupTheme="@style/ToolbarTheme"> app:popupTheme="@style/ToolbarTheme">
<ImageButton
android:id="@+id/bookmark"
android:layout_width="wrap_content"
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.support.v7.widget.Toolbar> </android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout> </android.support.design.widget.AppBarLayout>

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

@ -1,11 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/child_board_row"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/card_background" android:background="@color/card_background"
android:orientation="vertical">
<LinearLayout
android:id="@+id/child_board_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:orientation="vertical" android:orientation="vertical"
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingRight="16dp"> android:paddingRight="16dp">
@ -21,8 +28,9 @@
android:id="@+id/child_board_title" android:id="@+id/child_board_title"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1" android:layout_weight="1"
android:gravity="center_vertical" android:paddingBottom="2dp"
android:text="@string/child_board_title" android:text="@string/child_board_title"
android:textColor="@color/accent" android:textColor="@color/accent"
android:textSize="22sp"/> android:textSize="22sp"/>
@ -31,7 +39,7 @@
android:id="@+id/child_board_expand_collapse_button" android:id="@+id/child_board_expand_collapse_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/card_background" android:background="@null"
android:contentDescription="@string/child_board_button" android:contentDescription="@string/child_board_button"
android:src="@drawable/ic_arrow_drop_down"/> android:src="@drawable/ic_arrow_drop_down"/>
</LinearLayout> </LinearLayout>
@ -76,4 +84,5 @@
android:textColor="@color/primary_text" android:textColor="@color/primary_text"
android:textSize="12sp"/> android:textSize="12sp"/>
</LinearLayout> </LinearLayout>
</LinearLayout>
</LinearLayout> </LinearLayout>

7
app/src/main/res/layout/activity_board_topic.xml

@ -4,7 +4,9 @@
android:id="@+id/topic_row_linear" android:id="@+id/topic_row_linear"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:orientation="vertical" android:orientation="vertical"
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingRight="16dp"> android:paddingRight="16dp">
@ -20,8 +22,9 @@
android:id="@+id/topic_subject" android:id="@+id/topic_subject"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1" android:layout_weight="1"
android:gravity="center_vertical" android:paddingBottom="2dp"
android:text="@string/topic_subject" android:text="@string/topic_subject"
android:textColor="@color/primary_text" android:textColor="@color/primary_text"
android:textSize="18sp"/> android:textSize="18sp"/>
@ -30,7 +33,7 @@
android:id="@+id/topic_expand_collapse_button" android:id="@+id/topic_expand_collapse_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/background" android:background="@null"
android:contentDescription="@string/child_board_button" android:contentDescription="@string/child_board_button"
android:src="@drawable/ic_arrow_drop_down"/> android:src="@drawable/ic_arrow_drop_down"/>
</LinearLayout> </LinearLayout>

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

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".activities.topic.TopicActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/ToolbarTheme">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/ToolbarTheme">
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="@+id/bookmarks_nested_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top|start"
android:layout_marginTop="64dp"
android:background="@color/background"
android:scrollbars="none">
<LinearLayout
android:id="@+id/bookmarks_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</android.support.v4.widget.NestedScrollView>
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/progressBar"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal.NoPadding"
android:layout_width="match_parent"
android:layout_height="@dimen/progress_bar_height"
android:indeterminate="true"
android:visibility="invisible"
app:layout_anchor="@id/appbar"
app:layout_anchorGravity="bottom|center"
app:mpb_indeterminateTint="@color/accent"
app:mpb_progressStyle="horizontal"/>
</android.support.design.widget.CoordinatorLayout>

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

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bookmark_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingBottom="6dp"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:paddingTop="6dp">
<TextView
android:id="@+id/bookmark_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingBottom="2dp"
android:textColor="@color/primary_text"
android:textSize="18sp"/>
<ImageButton
android:id="@+id/remove_bookmark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/remove_bookmark"
android:src="@drawable/ic_remove_circle"/>
</LinearLayout>

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

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".activities.downloads.DownloadsActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/ToolbarTheme">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/ToolbarTheme">
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/downloads_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top|start"
android:layout_marginTop="64dp"
android:background="@color/background"
android:scrollbars="none"
tools:context="gr.thmmy.mthmmy.activities.downloads.DownloadsActivity">
</android.support.v7.widget.RecyclerView>
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/progressBar"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal.NoPadding"
android:layout_width="match_parent"
android:layout_height="@dimen/progress_bar_height"
android:indeterminate="true"
android:visibility="invisible"
app:layout_anchor="@id/appbar"
app:layout_anchorGravity="bottom|center"
app:mpb_indeterminateTint="@color/accent"
app:mpb_progressStyle="horizontal"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/download_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginBottom="@dimen/fab_margins"
android:layout_marginEnd="@dimen/fab_margins"
app:layout_behavior="gr.thmmy.mthmmy.utils.ScrollAwareFABBehavior"
app:srcCompat="@drawable/ic_file_upload"/>
</android.support.design.widget.CoordinatorLayout>

76
app/src/main/res/layout/activity_downloads_row.xml

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/upper_linear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/card_background"
android:orientation="vertical">
<LinearLayout
android:id="@+id/download_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="vertical"
android:paddingEnd="16dp"
android:paddingStart="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:orientation="horizontal">
<TextView
android:id="@+id/download_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginBottom="2dp"
android:layout_marginTop="2dp"
android:layout_weight="1"
android:paddingBottom="2dp"
android:textColor="@color/accent"
android:textSize="18sp"/>
<ImageButton
android:id="@+id/download_information_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:contentDescription="@string/child_board_button"
android:src="@drawable/ic_arrow_drop_down"/>
</LinearLayout>
<LinearLayout
android:id="@+id/child_board_expandable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:id="@+id/download_sub_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/secondary_text"/>
<TextView
android:id="@+id/download_extra_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/secondary_text"/>
<TextView
android:id="@+id/download_uploader_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/secondary_text"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>

48
app/src/main/res/layout/activity_login.xml

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:background="@color/background" android:background="@color/background"
@ -10,12 +9,13 @@
<ScrollView <ScrollView
android:id="@+id/inner_scroll_view" android:id="@+id/inner_scroll_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:fillViewport="true"
android:isScrollContainer="false">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="top"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:orientation="vertical" android:orientation="vertical"
android:paddingLeft="24dp" android:paddingLeft="24dp"
@ -23,7 +23,8 @@
<Space <Space
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="100dp"/> android:layout_height="0dp"
android:layout_weight="0.4"/>
<pl.droidsonroids.gif.GifImageView <pl.droidsonroids.gif.GifImageView
android:id="@+id/logo" android:id="@+id/logo"
@ -36,14 +37,13 @@
<Space <Space
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="50dp"/> android:layout_height="0dp"
android:layout_weight="0.45"/>
<!-- Username Label --> <!-- Username Label -->
<android.support.design.widget.TextInputLayout <android.support.design.widget.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp">
<EditText <EditText
android:id="@+id/username" android:id="@+id/username"
@ -53,12 +53,15 @@
android:inputType="textPersonName"/> android:inputType="textPersonName"/>
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.16"/>
<!-- Password Label --> <!-- Password Label -->
<android.support.design.widget.TextInputLayout <android.support.design.widget.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
app:passwordToggleEnabled="true"> app:passwordToggleEnabled="true">
<EditText <EditText
@ -69,40 +72,39 @@
android:inputType="textPassword"/> android:inputType="textPassword"/>
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.5"/>
<!-- Login Button --> <!-- Login Button -->
<android.support.v7.widget.AppCompatButton <android.support.v7.widget.AppCompatButton
android:id="@+id/btnLogin" android:id="@+id/btnLogin"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="30dp"
android:layout_marginTop="30dp"
android:background="@drawable/login_button_bg" android:background="@drawable/login_button_bg"
android:padding="12dp" android:padding="12dp"
android:text="@string/btn_login" android:text="@string/btn_login"
android:textSize="18sp"/> android:textSize="18sp"/>
<TextView <Space
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:background="@null" android:layout_weight="0.2"/>
android:gravity="center"
android:text="@string/login_or_guest"
android:textAllCaps="false"
android:textColor="@color/secondary_text"
android:textSize="10sp"
tools:ignore="SmallSp"/>
<android.support.v7.widget.AppCompatButton <android.support.v7.widget.AppCompatButton
android:id="@+id/btnContinueAsGuest" android:id="@+id/btnContinueAsGuest"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:layout_marginTop="30dip"
android:background="@drawable/guest_button_border_bg" android:background="@drawable/guest_button_border_bg"
android:text="@string/btn_continue_as_guest" android:text="@string/btn_continue_as_guest"
android:textAllCaps="false" android:textAllCaps="false"
android:textSize="15sp"/> android:textSize="15sp"/>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.3"/>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

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

@ -22,6 +22,16 @@
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
app:popupTheme="@style/ToolbarTheme"> app:popupTheme="@style/ToolbarTheme">
<ImageButton
android:id="@+id/bookmark"
android:layout_width="wrap_content"
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.support.v7.widget.Toolbar> </android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout> </android.support.design.widget.AppBarLayout>
@ -50,6 +60,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="0.8" android:layout_weight="0.8"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/button_first" android:contentDescription="@string/button_first"
app:srcCompat="@drawable/page_first"/> app:srcCompat="@drawable/page_first"/>
@ -58,6 +69,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="0.8" android:layout_weight="0.8"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/button_previous" android:contentDescription="@string/button_previous"
app:srcCompat="@drawable/page_previous"/> app:srcCompat="@drawable/page_previous"/>
@ -78,6 +90,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="0.8" android:layout_weight="0.8"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/button_next" android:contentDescription="@string/button_next"
app:srcCompat="@drawable/page_next"/> app:srcCompat="@drawable/page_next"/>
@ -86,6 +99,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="0.8" android:layout_weight="0.8"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/button_last" android:contentDescription="@string/button_last"
app:srcCompat="@drawable/page_last"/> app:srcCompat="@drawable/page_last"/>
</LinearLayout> </LinearLayout>

14
app/src/main/res/layout/fragment_latest_posts_row.xml

@ -1,12 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout <FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/card_background">
<RelativeLayout
android:id="@+id/latest_posts_row" android:id="@+id/latest_posts_row"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/card_background" android:background="?android:attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:foreground="?android:attr/selectableItemBackground" android:focusable="true"
android:paddingBottom="6dp" android:paddingBottom="6dp"
android:paddingLeft="10dp" android:paddingLeft="10dp"
android:paddingRight="10dp" android:paddingRight="10dp"
@ -52,4 +57,5 @@
android:background="@color/card_background" android:background="@color/card_background"
android:text="@string/post"/> android:text="@string/post"/>
</FrameLayout> </FrameLayout>
</RelativeLayout> </RelativeLayout>
</FrameLayout>

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

@ -5,14 +5,16 @@
<string name="login">Login</string> <string name="login">Login</string>
<string name="login_spinner">Authenticating&#8230;</string> <string name="login_spinner">Authenticating&#8230;</string>
<string name="logout">Logout</string> <string name="logout">Logout</string>
<string name="downloads">Downloads</string>
<string name="about">About</string>
<string name="home">Home</string> <string name="home">Home</string>
<string name="bookmark">Bookmarks</string>
<!--Login Activity--> <!--Login Activity-->
<string name="thmmy_img_description">thmmy.gr</string> <string name="thmmy_img_description">thmmy.gr</string>
<string name="hint_username">Username</string> <string name="hint_username">Username</string>
<string name="hint_password">Password</string> <string name="hint_password">Password</string>
<string name="btn_login">LOGIN</string> <string name="btn_login">LOGIN</string>
<string name="login_or_guest">- OR -</string>
<string name="btn_continue_as_guest">Don\'t have an account? Continue as guest!</string> <string name="btn_continue_as_guest">Don\'t have an account? Continue as guest!</string>
<!--Main Activity--> <!--Main Activity-->
@ -52,10 +54,14 @@
<string name="most_popular_boards_by_activity_title">Most Popular Boards By Activity</string> <string name="most_popular_boards_by_activity_title">Most Popular Boards By Activity</string>
<!--About Activity--> <!--About Activity-->
<string name="about">About</string>
<string name="version">v%1$s</string> <string name="version">v%1$s</string>
<string name="logo">Logo</string> <string name="logo">Logo</string>
<string name="trollPic">You should watch a funny pic!</string> <string name="trollPic">You should see a funny pic!</string>
<!--Bookmarks-->
<string name="remove_bookmark">Remove</string>
<string name="board_bookmarks_title">Your bookmarked boards:</string>
<string name="topic_bookmarks_title">Your bookmarked topics:</string>
<!--Licences--> <!--Licences-->
<string name="libraries">Libraries</string> <string name="libraries">Libraries</string>
@ -78,4 +84,5 @@
<string name="fa_file_video_o">&#xf1c8;</string> <string name="fa_file_video_o">&#xf1c8;</string>
<string name="fa_lock">&#xf023;</string> <string name="fa_lock">&#xf023;</string>
<string name="fa_sticky">&#xf249;</string> <string name="fa_sticky">&#xf249;</string>
<string name="fa_folder">&#xf07b;</string>
</resources> </resources>

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

@ -42,6 +42,4 @@
<item name="android:textColorPrimary">@color/primary_text</item> <item name="android:textColorPrimary">@color/primary_text</item>
<item name="android:colorBackground">@color/dialog_bg_semi_transparent</item> <item name="android:colorBackground">@color/dialog_bg_semi_transparent</item>
</style> </style>
</resources> </resources>

10
app/src/main/res/xml/provider_paths.xml

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="my_downloads"
path="Downloads/"/>
<files-path
name="my_downloads"
path="Downloads/"/>
</paths>

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

@ -77,5 +77,10 @@ public class Report
FirebaseCrash.report(tr); FirebaseCrash.report(tr);
} }
/**
* Does nothing in release.
*/
public static void longMessage(String TAG, String level, String message) {return;}
} }

34
doc/forum_post.txt

@ -0,0 +1,34 @@
[center][size=25pt][b]Introduction[/b][/size][/center]
Από τη συζήτηση [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!
Προς το παρόν η εφαρμογή υποστηρίζει κάποιες από τις βασικές λειτουργίες του forum. Σταδιακά θα ενσωματώνονται όλο και περισσότερες λειτουργίες, για παράδειγμα ενός συστήματος ειδοποιήσεων για νέες ανακοινώσεις του ethmmy και της σελίδας της γραμματείας, instant chat κ.ά., ανάλογα πάντα με τις ιδέες, τη διάθεση και την ενέργεια όσων θα συμμετέχουν.
Αυτή τη στιγμή με το project ασχολούμαστε εγώ και ο L, ενώ έχουν βοηθήσει ο iason1907 και ο [url=https://www.thmmy.gr/smf/index.php?topic=67565.msg1163192#msg1163192]nohponex[/url].
[hr]
[center][size=25pt][b]Κάλεσμα για contributors[/b][/size]
[img height=400]https://tctechcrunch2011.files.wordpress.com/2015/04/uncle-sam-we-want-you1-kopie_1.png[/img][/center]
Αν ενδιαφέρεσαι κι ΕΣΥ να ασχοληθείς με το 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 μας.
[/li]
[li]
Να κατεβάσεις και να δοκιμάσεις την alpha έκδοση της εφαρμογής από [url=https://play.google.com/apps/testing/gr.thmmy.mthmmy]εδώ[/url] αφού πρώτα μας δώσεις ένα gmail για να αποκτήσεις πρόσβαση.
[/li]
[li]
Να έρθεις σε άμεση επικοινωνία με την ομάδα μέσω του Discord ή στέλνοντας email στο thmmynolife@gmail.com.
[/li][/list]
[hr]
[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]
Αυτή τη στιγμή στην εφαρμογή μπορείς να κάνεις login και logout, να δεις τα "Πρόσφατα", να περιηγηθείς στα boards, topics και user profiles και να κατεβάσεις αρχεία συνημμένα σε post.
Loading…
Cancel
Save